Skip to main content

Direct Mint FXRP with Tag

Overview

This guide walks you through minting FXRP using direct minting with an XRPL destination tag reserved on Flare that maps to a recipient (and optionally an executor). Once a tag is reserved and bound, every XRPL payment to the Core Vault carrying that destination tag mints FXRP to the configured recipient — no per-payment memo required.

The complete runnable example is available in the flare-viem-starter repository.

Tag vs. memo

Compared to the memo-based direct minting, the tag flow is better suited for repeat minters:

Prerequisites

  • Flare smart account controlled by your XRPL wallet.
  • Native FLR (or C2FLR on Coston2) on the Flare wallet to cover the tag reservation fee.
  • Testnet XRP on the XRP Ledger Testnet — get it from the XRP Testnet Faucet.
  • Environment variables:
    • XRPL_TESTNET_RPC_URL — XRPL Testnet RPC endpoint.
    • XRPL_SEED — seed for the XRPL wallet that will send the payment.
    • MINTING_TAGoptional. If set, the script reuses this tag instead of reserving a new one.

Direct Minting Tag Script

src/fassets/direct-mint-tag.ts
import { Client, Wallet } from "xrpl";
import type { Address } from "viem";
import { sendXrplPayment } from "./utils/xrpl";
import { account, publicClient, walletClient } from "./utils/client";
import { getPersonalAccountAddress } from "./utils/smart-accounts";
import {
getContractAddressByName,
getDirectMintingPaymentAddress,
getMintingTagManagerAddress,
} from "./utils/flare-contract-registry";
import {
computeDirectMintingPaymentAmountXrp,
getFxrpBalance,
waitForDirectMintingExecuted,
} from "./utils/fassets";
import { coston2 } from "@flarenetwork/flare-wagmi-periphery-package";

// 1. Reserve a new tag from MintingTagManager. The reservation fee is paid in
// native currency (FLR/SGB). The contract returns the next available tag.
async function reserveTag(mintingTagManagerAddress: Address): Promise<bigint> {
const reservationFee = await publicClient.readContract({
address: mintingTagManagerAddress,
abi: coston2.iMintingTagManagerAbi,
functionName: "reservationFee",
});
console.log("Tag reservation fee (wei):", reservationFee, "\n");

const { result, request } = await publicClient.simulateContract({
account,
address: mintingTagManagerAddress,
abi: coston2.iMintingTagManagerAbi,
functionName: "reserve",
value: reservationFee,
});

const txHash = await walletClient.writeContract(request);
await publicClient.waitForTransactionReceipt({ hash: txHash });

const tag = result;
console.log("Reserved tag:", tag, "\n");

return tag;
}

// 2. Bind the reserved tag to the Flare recipient that should receive FXRP
// when payments arrive at the Core Vault with this destination tag.
async function setMintingRecipient(
mintingTagManagerAddress: Address,
tag: bigint,
recipient: Address,
): Promise<void> {
const { request } = await publicClient.simulateContract({
account,
address: mintingTagManagerAddress,
abi: coston2.iMintingTagManagerAbi,
functionName: "setMintingRecipient",
args: [tag, recipient],
});

const txHash = await walletClient.writeContract(request);
await publicClient.waitForTransactionReceipt({ hash: txHash });
console.log("Set minting recipient for tag", tag, "to", recipient, "\n");
}

// 3. Read the configured minting recipient for a tag.
async function getMintingRecipient(
mintingTagManagerAddress: Address,
tag: bigint,
): Promise<Address> {
return publicClient.readContract({
address: mintingTagManagerAddress,
abi: coston2.iMintingTagManagerAbi,
functionName: "mintingRecipient",
args: [tag],
});
}

// 4. Reuse MINTING_TAG from the .env if set, otherwise reserve a new tag and
// bind it to the recipient. Tags are ERC-721 NFTs and can be reused.
async function getOrReserveTag(
mintingTagManagerAddress: Address,
recipient: Address,
): Promise<bigint> {
if (process.env.MINTING_TAG) {
const tag = BigInt(process.env.MINTING_TAG);
console.log("Using existing minting tag from .env:", tag, "\n");
return tag;
}

const tag = await reserveTag(mintingTagManagerAddress);
await setMintingRecipient(mintingTagManagerAddress, tag, recipient);
console.log(
"Add MINTING_TAG=" + tag.toString() + " to your .env to reuse this tag.\n",
);
return tag;
}

async function main() {
// 5. Net FXRP amount to mint in XRP. Minting + executor fees are added on top
// by computeDirectMintingPaymentAmountXrp to form the XRPL payment amount.
const fxrpMintAmount = 10;

// 6. Connect to the XRPL Testnet and load the sender wallet from XRPL_SEED.
const xrplClient = new Client(process.env.XRPL_TESTNET_RPC_URL!);
const xrplWallet = Wallet.fromSeed(process.env.XRPL_SEED!);

// 7. Resolve the Flare smart-account recipient and the AssetManagerFXRP address.
const [personalAccountAddress, assetManagerAddress] = await Promise.all([
getPersonalAccountAddress(xrplWallet.address),
getContractAddressByName("AssetManagerFXRP"),
]);
console.log("Personal account address:", personalAccountAddress, "\n");

// 8. Resolve the MintingTagManager contract address from the AssetManager.
const mintingTagManagerAddress =
await getMintingTagManagerAddress(assetManagerAddress);
console.log("MintingTagManager address:", mintingTagManagerAddress, "\n");

// 9. Reserve a new tag (or reuse one from MINTING_TAG) and confirm the recipient.
const tag = await getOrReserveTag(
mintingTagManagerAddress,
personalAccountAddress,
);
const configuredRecipient = await getMintingRecipient(
mintingTagManagerAddress,
tag,
);
console.log("Minting recipient for tag:", configuredRecipient, "\n");

// 10. Read the Core Vault XRPL payment address, the recipient's initial FXRP
// balance, and the gross XRP amount (net mint + minting fee + executor fee).
const [coreVaultXrplAddress, initialBalance, paymentAmountXrp] =
await Promise.all([
getDirectMintingPaymentAddress(assetManagerAddress),
getFxrpBalance(personalAccountAddress),
computeDirectMintingPaymentAmountXrp({
netMintAmountXrp: fxrpMintAmount,
}),
]);
console.log("Core Vault XRPL address:", coreVaultXrplAddress, "\n");
console.log("AssetManagerFXRP address:", assetManagerAddress, "\n");
console.log("Initial FXRP balance:", initialBalance, "\n");
console.log("Payment amount (XRP, net mint + fees):", paymentAmountXrp, "\n");

// 11. Send the XRPL payment with the destination tag (no memo needed).
const transaction = await sendXrplPayment({
destination: coreVaultXrplAddress,
amount: paymentAmountXrp,
destinationTag: Number(tag),
wallet: xrplWallet,
client: xrplClient,
});
console.log(
"Direct mint XRPL transaction hash:",
transaction.result.hash,
"\n",
);

// 12. Wait for the DirectMintingExecuted event from AssetManagerFXRP.
const mintEvent = await waitForDirectMintingExecuted({
assetManagerAddress,
targetAddress: personalAccountAddress,
});

console.log("DirectMintingExecuted event:", mintEvent, "\n");
console.log("Minted amount (UBA):", mintEvent.args.mintedAmountUBA, "\n");
console.log("Minting fee (UBA):", mintEvent.args.mintingFeeUBA, "\n");
console.log("Executor fee (UBA):", mintEvent.args.executorFeeUBA, "\n");

// 13. Read the final FXRP balance and log the delta.
const finalBalance = await getFxrpBalance(personalAccountAddress);
console.log("Final FXRP balance:", finalBalance, "\n");
console.log("FXRP minted:", finalBalance - initialBalance, "\n");
}

void main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

Code Breakdown

  1. reserveTag function reads the reservationFee from MintingTagManager contract and calls reserve with the fee. The contract assigns the next available tag and returns it.
  2. setMintingRecipient function binds the tag to a recipient address via setMintingRecipient. Payments arriving at the Core Vault with this destination tag will mint FXRP to that recipient.
  3. getMintingRecipient reads the mintingRecipient currently configured for a tag.
  4. getOrReserveTag reuses MINTING_TAG from the .env variables if set; otherwise, it reserves a new tag and binds it to the recipient.
  5. Set the net FXRP amount to mint in XRP. Minting and executor fees are added to the the XRPL payment amount.
  6. Connect to the XRPL Testnet using XRPL_TESTNET_RPC_URL and load the sender wallet from XRPL_SEED.
  7. Resolve the recipient — the Flare PersonalAccount for the XRPL wallet — via getPersonalAccountAddress, and look up AssetManagerFXRP through the Flare Contract Registry using getContractAddressByName.
  8. Resolve the MintingTagManager contract address from the AssetManager using getMintingTagManager.
  9. Reserve a new tag (or reuse one from MINTING_TAG) and confirm the configured recipient.
  10. Read three values in parallel:
    • getDirectMintingPaymentAddress — the Core Vault XRPL address that receives the payment.
    • getFxrpBalance — the recipient's FXRP balance before minting.
    • computeDirectMintingPaymentAmountXrp — the gross XRP amount equal to the net mint amount plus the minting fee and the executor fee, as quoted by the AssetManagerFXRP contract.
  11. Send the XRPL payment to the Core Vault with destinationTag: Number(tag) — no memo required.
  12. Wait for the DirectMintingExecuted event on AssetManagerFXRP using the waitForDirectMintingExecuted function. The event's mintedAmountUBA, mintingFeeUBA, and executorFeeUBA describe how the underlying payment was split.
  13. Read the final FXRP balance and log the delta.

Important Notes

  • Reservations cost native currency. Tag reservation pays a fee in native token. Tags are assigned sequentially.
  • Executor changes are not immediate. After calling the setAllowedExecutor function, the new executor only becomes active after a 10-minute cooldown to protect in-flight FDC proofs. See Executor Restrictions.
  • Transferring a tag resets it. When ownership changes, the recipient is reset to the new owner, and the executor is reset to the zero address.
  • Rate limits may delay execution. If the minting hits hourly, daily, or large-minting thresholds, DirectMintingDelayed is emitted instead of DirectMintingExecuted, with an executionAllowedAt timestamp.
What's next

To continue your FAssets development journey, you can: