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, for instance, XRP from the XRP Ledger into an FAsset, enabling it to be used within the Flare blockchain ecosystem.
See the Minting overview for more details.
Prerequisites
Minting Process Steps
The minting process is a multi-step process that 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
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
CollateralReserved
event. - Start the minting reservation process at the script's entry point.
- Call
findBestAgent
with 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
getAgentInfo
to 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
assetMintingDecimals
to determine the XRP token's decimal precision. - Parse the
CollateralReserved
event. - 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, and you can use this script to do that.
// 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
xrpl
package — it's 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 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
executeMinting
function 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
parseExecutemintingEvents
to extract and log eventsRedemptionTicketCreated
andMintingExecuted
.
Next Steps
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 convert your FAssets (e.g., FXRP) back into the original asset (e.g., XRP), follow the steps in the FAssets Redemption Guide.