Agent documentation index: llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Skip to main content

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:

FieldMeaning
redemptionTicketIdTicket id. The redemption-ticket events emit the same value.
agentVaultVault of the agent backing that ticket. The agent pays the underlying asset when the ticket is redeemed.
ticketValueUBATicket 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:

  • _firstRedemptionTicketId of 0 starts at the head of the FIFO.
  • _pageSize is the maximum number of tickets in that page. This script passes maxRedeemedTickets.
  • _queue is the page of RedemptionTicketInfo structs, in FIFO order.
  • _nextRedemptionTicketId is the id to pass on the next call. It is 0 when 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 publicClient pointing 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​

src/fassets/redemption-queue.ts
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​

  1. MAX_REDEMPTION_QUEUE_PAGES stops the walk after 100 pages. A cursor that never returns 0 cannot loop.
  2. formatUba prints the raw UBA amount beside the XRP equivalent from dropsToXrp.
  3. lotsFromTicketValue divides a UBA amount by the lot size. Integer division leaves a remainder smaller than one lot in the UBA total.
  4. readRedemptionQueueParameters resolves AssetManagerFXRP with getContractAddressByName through the Flare Contract Registry. getBlockNumber pins the snapshot to one block. getAssetManagerSettings reads getSettings at that block. The page size is maxRedeemedTickets. A zero page size cannot advance the cursor, so the script throws. Lot size in UBA is lotSizeAMG * assetMintingGranularityUBA. A zero lot size cannot convert ticket values, so the script throws.
  5. fetchRedemptionTickets calls redemptionQueue, starting at ticket id 0. Each page is appended, and the next call starts at _nextRedemptionTicketId. A cursor of 0 returns the tickets collected so far. After 100 pages with a non-zero cursor, the script throws.
  6. totalTicketValueUBA sums ticketValueUBA across the snapshot.
  7. groupTicketsByAgent counts tickets and sums value for each agentVault.
  8. printRedemptionQueueHeader logs the asset manager address, block, page size, and lot size.
  9. printRedemptionQueue logs Redemption queue is empty. when the snapshot has no tickets. Otherwise it prints each ticket, the queue totals, and the per-agent summary.
  10. main reads 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. getAssetManagerSettings and every redemptionQueue call use the same blockNumber.
  • maxRedeemedTickets is both the page size and the per-request ticket cap. A redemption that needs more tickets emits RedemptionAmountIncomplete and 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 * maxRedeemedTickets tickets makes the script throw, so the printed total is complete whenever the script exits successfully.
What's next

To continue your FAssets development journey, you can: