Get Redemption Queue
Overview
This guide reads the global FXRP redemption queue with redemptionQueue on AssetManagerFXRP.
The script is read-only.
It walks the FIFO from the head, prints each ticket, and totals the queued value and lots, with a summary per agent vault.
The complete runnable example is available in the flare-viem-starter repository. From that repository, run:
pnpm run script src/fassets/redemption-queue.ts
How the queue works
Every mint creates a redemption ticket and appends it to one FIFO queue shared by all agents.
Agent self-minting adds a ticket the same way.
A redemption takes tickets from the front until it covers the requested amount.
A single redemption request can consume at most maxRedeemedTickets tickets.
That cap comes from getSettings and keeps the gas of one call bounded.
A request that needs more tickets than that cap is only partly filled, and the contract emits RedemptionAmountIncomplete.
Each RedemptionTicketInfo contains:
| Field | Meaning |
|---|---|
redemptionTicketId | Ticket id. The redemption-ticket events emit the same value. |
agentVault | Vault of the agent backing that ticket. The agent pays the underlying asset when the ticket is redeemed. |
ticketValueUBA | Ticket amount in underlying base units (UBA). For XRP, 1 XRP = 1,000,000 UBA. |
The ticket stores value in UBA.
One lot in UBA is lotSizeAMG * assetMintingGranularityUBA, both fields of getSettings.
The size of one AMG in UBA is assetMintingGranularityUBA, so the product converts the lot size out of AMG.
A call to redemptionQueue returns one page of tickets plus a cursor:
_firstRedemptionTicketIdof0starts at the head of the FIFO._pageSizeis the maximum number of tickets in that page. This script passesmaxRedeemedTickets._queueis the page ofRedemptionTicketInfostructs, in FIFO order._nextRedemptionTicketIdis the id to pass on the next call. It is0when the page has reached the end of the queue.
The agentRedemptionQueue function returns the same ticket shape for one agent vault.
This script walks the global queue and groups the result by agentVault off-chain.
Prerequisites
- The flare-viem-starter cloned locally with dependencies installed.
- A configured
publicClientpointing to the Flare network you want to query (Coston2 by default in the starter). - No signing key. The script only calls view functions.
Redemption Queue Script
import { coston2 } from "@flarenetwork/flare-wagmi-periphery-package";
import type { Address } from "viem";
import { dropsToXrp } from "xrpl";
import { publicClient } from "./utils/client";
import { getContractAddressByName } from "./utils/flare-contract-registry";
import { getAssetManagerSettings } from "./settings";
const MAX_REDEMPTION_QUEUE_PAGES = 100;
type RedemptionTicket = {
redemptionTicketId: bigint;
agentVault: Address;
ticketValueUBA: bigint;
};
type RedemptionQueueParameters = {
assetManagerAddress: Address;
blockNumber: bigint;
pageSize: bigint;
lotSizeUBA: bigint;
};
type AgentQueueSummary = {
ticketCount: number;
valueUBA: bigint;
};
function formatUba(uba: bigint): string {
return `${uba.toString()} UBA (${dropsToXrp(uba.toString())} XRP)`;
}
function lotsFromTicketValue(
ticketValueUBA: bigint,
lotSizeUBA: bigint,
): bigint {
return ticketValueUBA / lotSizeUBA;
}
async function readRedemptionQueueParameters(): Promise<RedemptionQueueParameters> {
const assetManagerAddress =
await getContractAddressByName("AssetManagerFXRP");
const blockNumber = await publicClient.getBlockNumber();
const settings = await getAssetManagerSettings(
assetManagerAddress,
blockNumber,
);
const pageSize = BigInt(settings.maxRedeemedTickets);
if (pageSize === 0n) {
throw new Error(
"maxRedeemedTickets is 0; cannot page the redemption queue.",
);
}
// One lot in UBA is lotSizeAMG converted by the minting granularity.
const lotSizeUBA =
BigInt(settings.lotSizeAMG) * BigInt(settings.assetMintingGranularityUBA);
if (lotSizeUBA === 0n) {
throw new Error("Lot size is 0; cannot convert ticket values to lots.");
}
return { assetManagerAddress, blockNumber, pageSize, lotSizeUBA };
}
async function fetchRedemptionTickets({
assetManagerAddress,
blockNumber,
pageSize,
}: {
assetManagerAddress: Address;
blockNumber: bigint;
pageSize: bigint;
}): Promise<RedemptionTicket[]> {
const tickets: RedemptionTicket[] = [];
let firstRedemptionTicketId = 0n;
for (let page = 0; page < MAX_REDEMPTION_QUEUE_PAGES; page++) {
const [queue, nextRedemptionTicketId] = await publicClient.readContract({
address: assetManagerAddress,
abi: coston2.iAssetManagerAbi,
functionName: "redemptionQueue",
args: [firstRedemptionTicketId, pageSize],
blockNumber,
});
tickets.push(...queue);
if (nextRedemptionTicketId === 0n) {
return tickets;
}
firstRedemptionTicketId = nextRedemptionTicketId;
}
throw new Error(
`Redemption queue pagination exceeded ${MAX_REDEMPTION_QUEUE_PAGES} pages.`,
);
}
function totalTicketValueUBA(tickets: RedemptionTicket[]): bigint {
return tickets.reduce((sum, ticket) => sum + ticket.ticketValueUBA, 0n);
}
function groupTicketsByAgent(
tickets: RedemptionTicket[],
): Map<Address, AgentQueueSummary> {
const byAgent = new Map<Address, AgentQueueSummary>();
for (const ticket of tickets) {
const summary = byAgent.get(ticket.agentVault) ?? {
ticketCount: 0,
valueUBA: 0n,
};
summary.ticketCount += 1;
summary.valueUBA += ticket.ticketValueUBA;
byAgent.set(ticket.agentVault, summary);
}
return byAgent;
}
function printRedemptionQueueHeader({
assetManagerAddress,
blockNumber,
pageSize,
lotSizeUBA,
}: RedemptionQueueParameters): void {
console.log("AssetManagerFXRP address:", assetManagerAddress);
console.log("Block:", blockNumber.toString());
console.log("Max redeemed tickets:", pageSize.toString());
console.log("Lot size:", formatUba(lotSizeUBA), "\n");
}
function printRedemptionTicket(
index: number,
ticket: RedemptionTicket,
lotSizeUBA: bigint,
): void {
const lots = lotsFromTicketValue(ticket.ticketValueUBA, lotSizeUBA);
console.log(
`#${index + 1} ticket ${ticket.redemptionTicketId.toString()} agent ${ticket.agentVault} ${formatUba(ticket.ticketValueUBA)} (${lots.toString()} lots)`,
);
}
function printQueueTotals(
tickets: RedemptionTicket[],
lotSizeUBA: bigint,
): void {
const totalValueUBA = totalTicketValueUBA(tickets);
console.log("\nTickets:", tickets.length);
console.log("Total value:", formatUba(totalValueUBA));
console.log(
"Total lots:",
lotsFromTicketValue(totalValueUBA, lotSizeUBA).toString(),
"\n",
);
}
function printAgentSummaries(
byAgent: Map<Address, AgentQueueSummary>,
lotSizeUBA: bigint,
): void {
console.log("By agent:");
for (const [agentVault, summary] of byAgent) {
const ticketLabel = summary.ticketCount === 1 ? "ticket" : "tickets";
const lots = lotsFromTicketValue(summary.valueUBA, lotSizeUBA);
console.log(
`${agentVault} ${summary.ticketCount} ${ticketLabel} ${formatUba(summary.valueUBA)} (${lots.toString()} lots)`,
);
}
}
function printRedemptionQueue(
tickets: RedemptionTicket[],
lotSizeUBA: bigint,
): void {
if (tickets.length === 0) {
console.log("Redemption queue is empty.");
return;
}
tickets.forEach((ticket, index) =>
printRedemptionTicket(index, ticket, lotSizeUBA),
);
printQueueTotals(tickets, lotSizeUBA);
printAgentSummaries(groupTicketsByAgent(tickets), lotSizeUBA);
}
async function main() {
const parameters = await readRedemptionQueueParameters();
printRedemptionQueueHeader(parameters);
const tickets = await fetchRedemptionTickets(parameters);
printRedemptionQueue(tickets, parameters.lotSizeUBA);
}
void main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Code Breakdown
MAX_REDEMPTION_QUEUE_PAGESstops the walk after 100 pages. A cursor that never returns0cannot loop.formatUbaprints the raw UBA amount beside the XRP equivalent fromdropsToXrp.lotsFromTicketValuedivides a UBA amount by the lot size. Integer division leaves a remainder smaller than one lot in the UBA total.readRedemptionQueueParametersresolvesAssetManagerFXRPwithgetContractAddressByNamethrough the Flare Contract Registry.getBlockNumberpins the snapshot to one block.getAssetManagerSettingsreadsgetSettingsat that block. The page size ismaxRedeemedTickets. A zero page size cannot advance the cursor, so the script throws. Lot size in UBA islotSizeAMG * assetMintingGranularityUBA. A zero lot size cannot convert ticket values, so the script throws.fetchRedemptionTicketscallsredemptionQueue, starting at ticket id0. Each page is appended, and the next call starts at_nextRedemptionTicketId. A cursor of0returns the tickets collected so far. After 100 pages with a non-zero cursor, the script throws.totalTicketValueUBAsumsticketValueUBAacross the snapshot.groupTicketsByAgentcounts tickets and sums value for eachagentVault.printRedemptionQueueHeaderlogs the asset manager address, block, page size, and lot size.printRedemptionQueuelogsRedemption queue is empty.when the snapshot has no tickets. Otherwise it prints each ticket, the queue totals, and the per-agent summary.mainreads the parameters, prints the header, fetches the tickets, and prints the queue.
Important Notes
- View calls only. Every contract call in the script is a read against
AssetManagerFXRP. - One block for the whole walk.
getAssetManagerSettingsand everyredemptionQueuecall use the sameblockNumber. maxRedeemedTicketsis both the page size and the per-request ticket cap. A redemption that needs more tickets emitsRedemptionAmountIncompleteand leaves the rest of the queue in place.- Lot counts use integer division. Value that is not a whole lot still appears in the UBA total.
- The 100-page cap is a safety stop. A queue longer than
100 * maxRedeemedTicketstickets makes the script throw, so the printed total is complete whenever the script exits successfully.
To continue your FAssets development journey, you can:
- Redeem any amount with Redeem FXRP by Amount.
- Redeem to an exchange address with Redeem FXRP with Tag.
- Read the protocol flow in Redemption.