Mint FAssets
Overview
This guide walks you through the complete process of minting FAssets (e.g., FXRP) on the Flare network.
Minting FAssets is the process of wrapping underlying tokens (e.g., XRP from the XRP Ledger) into FAssets, enabling them to be used within the Flare blockchain ecosystem.
See the Minting overview for more details.
Prerequisites
Minting Process Steps
The minting process involves the following steps:
- Reserve collateral from a suitable agent.
- Send the underlying asset (e.g., XRP) to the agent.
- Use Flare Data Connector (FDC) to generate proof of payment.
- Execute minting with the proof to receive FAssets.
Reserve Collateral
Reserving collateral is the first step in the minting process.
Minting Fees
When reserving collateral, there are several fees that are paid.
| Fee Type | Paid In | Details |
|---|---|---|
| Collateral Reservation Fee (CRF) | Native tokens (FLR/SGB) | Compensates the agent and the collateral pool token (CPT) holders for locking their collateral during the minting process. Defined by governance as a percentage of the minted value. |
| Minting Fee | Underlying currency (e.g., XRP for FXRP) | Main source of revenue for the agents and the CPT holders. Percentage of the minted amount (varies by agent). |
| Executor Fee (Optional) | Native tokens (FLR/SGB) | Incentivizes the executor to process minting requests. Configurable fee denominated in FLR. |
If minting fails, the Collateral Reservation Fee is not returned to the minter. It is distributed to the agent and the pool in the same manner as the minting fee.
Reserve Collateral Script
The following code demonstrates how to reserve collateral by calling the reserveCollateral function on the AssetManager contract.
import { getAssetManagerFXRP } from "../utils/fassets";
import { IAssetManagerInstance } from "../../typechain-types";
import { logEvents } from "../../scripts/utils/core";
// 1. Define constants
// Number of lots to reserve
const LOTS_TO_MINT = 1;
// Use zero address for executor since we're not using it
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
// 2. Get the AssetManager artifact
const AssetManager = artifacts.require("IAssetManager");
// 3. Function to find the best agent with enough free collateral lots
// Function from FAssets Bot repository
// https://github.com/flare-foundation/fasset-bots/blob/main/packages/fasset-bots-core/src/commands/InfoBotCommands.ts#L83
async function findBestAgent(
assetManager: IAssetManagerInstance,
minAvailableLots = 1,
) {
// get max 100 agents
const agents = (await assetManager.getAvailableAgentsDetailedList(0, 100))
._agents;
// filter agents with enough free collateral lots
let agentsWithLots = agents.filter(
(agent) => agent.freeCollateralLots > minAvailableLots,
);
if (agentsWithLots.length === 0) {
return undefined;
}
// sort by lowest fee
agentsWithLots.sort((a, b) => a.feeBIPS - b.feeBIPS);
while (agentsWithLots.length > 0) {
const lowestFee = agentsWithLots[0].feeBIPS;
// get all agents with the lowest fee
let optimal = agentsWithLots.filter((a) => a.feeBIPS == lowestFee);
while (optimal.length > 0) {
// const agentVault = (requireNotNull(randomChoice(optimal)) as any).agentVault; // list must be nonempty
// get a random agent from the list
const agentVault =
optimal[Math.floor(Math.random() * optimal.length)].agentVault;
// const agentVault = (randomChoice(optimal) as any).agentVault;
const info = await assetManager.getAgentInfo(agentVault);
// 0 = NORMAL
if (Number(info.status) === 0) {
return agentVault;
}
// If not found remote this agent and do another round
optimal = optimal.filter((a) => a.agentVault !== agentVault);
agentsWithLots = agentsWithLots.filter(
(a) => a.agentVault !== agentVault,
);
}
}
}
// 4. Function to parse the CollateralReserved event
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function parseCollateralReservedEvent(transactionReceipt: any) {
console.log("\nParsing events...", transactionReceipt.rawLogs);
// The logEvents function is included in the Flare starter kit
const collateralReservedEvents = logEvents(
transactionReceipt.rawLogs,
"CollateralReserved",
AssetManager.abi,
);
return collateralReservedEvents[0].decoded;
}
// 5. Main function
async function main() {
// 6. Get the AssetManager contract from the Flare Contract Registry
const assetManager: IAssetManagerInstance = await getAssetManagerFXRP();
// 7. Find the best agent with enough free collateral lots
const agentVaultAddress = await findBestAgent(assetManager, LOTS_TO_MINT);
if (!agentVaultAddress) {
throw new Error("No suitable agent found with enough free collateral lots");
}
console.log(agentVaultAddress);
// 8. Get the agent info
const agentInfo = await assetManager.getAgentInfo(agentVaultAddress);
console.log("Agent info:", agentInfo);
// 9. Get the collateral reservation fee according to the number of lots to reserve
// https://dev.flare.network/fassets/minting/#collateral-reservation-fee
const collateralReservationFee =
await assetManager.collateralReservationFee(LOTS_TO_MINT);
console.log(
"Collateral reservation fee:",
collateralReservationFee.toString(),
);
console.log("agentVaultAddress", agentVaultAddress);
console.log("LOTS_TO_MINT", LOTS_TO_MINT);
console.log("agentInfo.feeBIPS", agentInfo.feeBIPS);
console.log("ZERO_ADDRESS", ZERO_ADDRESS);
console.log("collateralReservationFee", collateralReservationFee);
// 10. Reserve collateral
// https://dev.flare.network/fassets/reference/IAssetManager#reservecollateral
const tx = await assetManager.reserveCollateral(
agentVaultAddress,
LOTS_TO_MINT,
agentInfo.feeBIPS,
// Not using the executor
ZERO_ADDRESS,
// Sending the collateral reservation fee as native tokens
{ value: collateralReservationFee },
);
console.log("Collateral reservation successful:", tx);
// 11. Get the asset decimals
const decimals = await assetManager.assetMintingDecimals();
// 12. Parse the CollateralReserved event
const collateralReservedEvent = await parseCollateralReservedEvent(
tx.receipt,
);
// 13. Get the collateral reservation info
const collateralReservationInfo =
await assetManager.collateralReservationInfo(
collateralReservedEvent.collateralReservationId,
);
console.log("Collateral reservation info:", collateralReservationInfo);
// 14. Calculate the total XRP value required for payment
const valueUBA = BigInt(collateralReservedEvent.valueUBA.toString());
const feeUBA = BigInt(collateralReservedEvent.feeUBA.toString());
const totalUBA = valueUBA + feeUBA;
const totalXRP = Number(totalUBA) / 10 ** decimals;
console.log(`You need to pay ${totalXRP} XRP`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Collateral Reservation Script Breakdown
- Define constants
LOTS_TO_MINT: Number of FAsset lots to reserve.ZERO_ADDRESS: Placeholder forexecutor(not used in this script).
- Retrieve and filter agents with enough free collateral and select the agent with the lowest fee and normal status.
- Parse
CollateralReservedevent. - Start the minting reservation process at the script's entry point.
- Call
findBestAgentwith the required number of lots. - Get the asset manager from the Flare Contract Registry.
- Find the best agent with enough free collateral lots.
- Fetch agent metadata from
getAgentInfoto get the agent'sfeeBIPS, which is used to calculate the collateral reservation fee. - Calculate the collateral reservation fee by calling
collateralReservationFee. - Reserve collateral from agent by calling
reserveCollateral - Call
assetMintingDecimalsto determine the XRP token's decimal precision. - Parse the
CollateralReservedevent. - Calculate the total XRP value required for payment.
Send Payment on XRP Ledger
The next step is to send the XRP Ledger payment to the agent. Before making the payment, it is important to understand the payment timeframes and constraints.
Payment Timeframes
The minting process has specific timeframes for underlying payments, controlled by operational parameters:
| Parameter | Unit | Purpose |
|---|---|---|
underlyingSecondsForPayment | seconds | The minimum time allowed for a minter to pay on the underlying chain |
underlyingBlocksForPayment | blocks | The number of underlying blocks during which the minter can make the payment |
These parameters work together as a dual safety mechanism that ensures payment happens within a reasonable number of blocks and guarantees payment occurs within a reasonable amount of time.
Both constraints must be satisfied for the payment to be considered valid.
Payment Deadline Calculation
When you reserve collateral, the system emits the following values in the CollateralReserved event:
lastUnderlyingBlock: The final block number for a valid payment.lastUnderlyingTimestamp: The deadline timestamp for payment.
Valid payments must occur before both the last block AND the last timestamp.
Payment Failure Handling
If the minter fails to pay on the underlying chain within the required timeframe:
- The agent must prove nonpayment using the Flare Data Connector.
- After nonpayment is proved, the agent's collateral that was reserved is released.
- The agent receives the Collateral Reservation Fee, which is not returned to the minter.
- The minter loses the opportunity to mint and must restart the process.
Payment Script
After understanding the underlying payment timeframes, you can use the provided script to execute the payment.
// 1. install xrpl package
// https://www.npmjs.com/package/xrpl
import { Client, Wallet, xrpToDrops, Payment, TxResponse } from "xrpl";
// 2. Define the constants
const AGENT_ADDRESS = "r4KgCNzn9ZuNjpf17DEHZnyyiqpuj599Wm"; // Agent underlying chain address
const AMOUNT_XRP = "10.025"; // XRP amount to send
const PAYMENT_REFERENCE =
"4642505266410001000000000000000000000000000000000000000000f655fb"; // Reference
async function send20XrpWithReference() {
// 3. Create a client to connect to the XRP Ledger Testnet
const client = new Client("wss://s.altnet.rippletest.net:51233"); // Testnet
await client.connect();
// 4. XRP Ledger Testnet seed
const wallet: Wallet = Wallet.fromSeed("s000000000000000000000000000000"); // Sender wallet seed
// 5. Create a payment transaction
const paymentTx: Payment = {
TransactionType: "Payment",
Account: wallet.classicAddress,
// Agent underlying chain address
Destination: AGENT_ADDRESS,
// XRP amount to send
Amount: xrpToDrops(AMOUNT_XRP),
// Payment reference
Memos: [
{
Memo: {
MemoData: PAYMENT_REFERENCE,
},
},
],
};
console.log(paymentTx);
// 6. Execute the transaction
const prepared = await client.autofill(paymentTx);
const signed = wallet.sign(prepared);
const result: TxResponse = await client.submitAndWait(signed.tx_blob);
console.log("Transaction hash:", signed.hash);
console.log("Explorer: https://testnet.xrpl.org/transactions/" + signed.hash);
console.log("Result:", result);
await client.disconnect();
}
send20XrpWithReference().catch(console.error);
XRP Payment Script Breakdown
- Install the
xrplpackage — it is not included in the Flare Hardhat Starter Kit by default. - Specify the correct constants from the reserve collateral script:
AGENT_ADDRESS- Agent's XRP Ledger address.AMOUNT_XRP- XRP amount to send.PAYMENT_REFERENCE- Payment reference from the reserve collateral script.
- Create a client to connect to the XRP Ledger Testnet.
- Load the sender wallet.
- Construct the payment transaction.
- Sign and submit the transaction.
Generate Proof with Flare Data Connector
Use the FDC Payment script to validate the XRP payment and generate a Merkle proof.
Execute Minting
Once the XRP payment is validated, you can retrieve the FDC proof from the Data Availability Layer and call the executeMinting function on the AssetManager contract.
This script demonstrates how to retrieve the FDC proof and execute minting.
import { getAssetManagerFXRP } from "../utils/fassets";
import { prepareAttestationRequestBase } from "../utils/fdc";
import { IAssetManagerInstance } from "../../typechain-types";
import { logEvents } from "../../scripts/utils/core";
// yarn hardhat run scripts/fassets/executeMinting.ts --network coston2
// 1. Environment variables
const { COSTON2_DA_LAYER_URL, VERIFIER_URL_TESTNET, VERIFIER_API_KEY_TESTNET } =
process.env;
// 2. Collateral reservation ID
const COLLATERAL_RESERVATION_ID = 10255417;
// 3. FDC round id to get the proof for
const TARGET_ROUND_ID = 1053806;
// 4. FDC request data
const attestationTypeBase = "Payment";
const sourceIdBase = "testXRP";
const verifierUrlBase = VERIFIER_URL_TESTNET;
const urlTypeBase = "xrp";
const transactionId =
"EC0FC5F40FBE6AEAD31138898C71687B2902E462FD1BFEF3FB443BE5E2C018F9";
const inUtxo = "0";
const utxo = "0";
// 5. AssetManager contract
const AssetManager = artifacts.require("IAssetManager");
// 6. Prepare FDC request
async function prepareFdcRequest(
transactionId: string,
inUtxo: string,
utxo: string,
) {
const requestBody = {
transactionId: transactionId,
inUtxo: inUtxo,
utxo: utxo,
};
const url = `${verifierUrlBase}verifier/${urlTypeBase}/Payment/prepareRequest`;
return await prepareAttestationRequestBase(
url,
VERIFIER_API_KEY_TESTNET,
attestationTypeBase,
sourceIdBase,
requestBody,
);
}
// 7. Get proof from FDC
async function getProof(roundId: number) {
const request = await prepareFdcRequest(transactionId, inUtxo, utxo);
const proofAndData = await fetch(
`${COSTON2_DA_LAYER_URL}api/v0/fdc/get-proof-round-id-bytes`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": VERIFIER_API_KEY_TESTNET,
},
body: JSON.stringify({
votingRoundId: roundId,
requestBytes: request.abiEncodedRequest,
}),
},
);
return await proofAndData.json();
}
// 8. Parse events
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function parseEvents(receipt: any) {
console.log("\nParsing events...", receipt.rawLogs);
logEvents(receipt.rawLogs, "RedemptionTicketCreated", AssetManager.abi);
logEvents(receipt.rawLogs, "MintingExecuted", AssetManager.abi);
}
async function main() {
// 9. Get proof from FDC
const proof = await getProof(TARGET_ROUND_ID);
const assetManager: IAssetManagerInstance = await getAssetManagerFXRP();
// 10. Execute minting
const tx = await assetManager.executeMinting(
{
merkleProof: proof.proof,
data: proof.response,
},
COLLATERAL_RESERVATION_ID,
);
console.log("Transaction successful:", tx);
// 11. Parse execute minting log events
await parseEvents(tx.receipt);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Execute Minting Script Breakdown
- Get environment variables.
- Set the collateral reservation ID to the previously reserved minting request.
- Set the FDC round ID to retrieve the proof.
- Provide the FDC request data.
- Import the Asset Manager contract artifact.
- Define the function to prepare the FDC request.
- Create a function to get the proof from the FDC. It sends a POST request to the Flare Data Availability Layer and returns a Merkle proof and attestation response from FDC.
- Define the function to parse the events.
- Retrieve the FDC proof from the Data Availability Layer.
- Call the
executeMintingfunction on the AssetManager contract and send a transaction to the Flare network to convert the attested XRP payment into FXRP (minting). - On a successful transaction call the
parseExecutemintingEventsfunction to extract and log eventsRedemptionTicketCreatedandMintingExecuted.
Video Tutorial
Summary
Now that you have successfully minted FAssets, you can use them in Flare dApps or transfer them to other users or smart contracts within the Flare ecosystem.
To continue your FAssets development journey, you can:
- Learn how to mint FAssets using the executor.
- Understand how to redeem FXRP.
- Explore FAssets system settings.