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:
- One-time setup: reserve a tag once via
MintingTagManager— pay thereservationFeein native token and bind the recipient withsetMintingRecipient. - Reusable: every subsequent XRPL payment with the same destination tag routes to the bound recipient without rebuilding a memo.
- Transferable: tags are ERC-721 NFTs and can be transferred to another owner with
transfer. - Optional preferred executor: the tag owner may restrict execution via
setAllowedExecutor. AfterothersCanExecuteAfterSeconds, anyone can finalize.
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_TAG— optional. 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
reserveTagfunction reads thereservationFeefromMintingTagManagercontract and callsreservewith the fee. The contract assigns the next available tag and returns it.setMintingRecipientfunction binds the tag to a recipient address viasetMintingRecipient. Payments arriving at the Core Vault with this destination tag will mint FXRP to that recipient.getMintingRecipientreads themintingRecipientcurrently configured for a tag.getOrReserveTagreusesMINTING_TAGfrom the.envvariables if set; otherwise, it reserves a new tag and binds it to the recipient.- Set the net FXRP amount to mint in XRP. Minting and executor fees are added to the the XRPL payment amount.
- Connect to the XRPL Testnet using
XRPL_TESTNET_RPC_URLand load the sender wallet fromXRPL_SEED. - Resolve the recipient — the Flare
PersonalAccountfor the XRPL wallet — viagetPersonalAccountAddress, and look upAssetManagerFXRPthrough the Flare Contract Registry usinggetContractAddressByName. - Resolve the
MintingTagManagercontract address from theAssetManagerusinggetMintingTagManager. - Reserve a new tag (or reuse one from
MINTING_TAG) and confirm the configured recipient. - 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 theAssetManagerFXRPcontract.
- Send the XRPL payment to the Core Vault with
destinationTag: Number(tag)— no memo required. - Wait for the
DirectMintingExecutedevent onAssetManagerFXRPusing thewaitForDirectMintingExecutedfunction. The event'smintedAmountUBA,mintingFeeUBA, andexecutorFeeUBAdescribe how the underlying payment was split. - 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
setAllowedExecutorfunction, 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,
DirectMintingDelayedis emitted instead ofDirectMintingExecuted, with anexecutionAllowedAttimestamp.
What's next
To continue your FAssets development journey, you can:
- Mint without a tag using memo-based direct minting.
- Read the protocol details in Direct Minting.
- Redeem FAssets with
redeemAmountorredeemWithTag.