Redeem FAssets
Overview
In this guide, you will learn how to redeem FAssets using the Asset Manager smart contract.
Redemption is the process of burning FAssets (e.g., FXRP) on Flare in exchange for their equivalent value on the original chain (e.g., XRP on XRPL).
See the Redemption overview for more details.
Prerequisites
Redeem FAssets Smart Contract
The following example demonstrates how to redeem FAssets using the AssetManager
smart contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {ContractRegistry} from "flare-periphery-contracts-fassets-test/coston2/ContractRegistry.sol";
import {IAssetManager} from "flare-periphery-contracts-fassets-test/coston2/IAssetManager.sol";
import {AssetManagerSettings} from "flare-periphery-contracts-fassets-test/coston2/data/AssetManagerSettings.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {RedemptionRequestInfo} from "flare-periphery-contracts-fassets-test/coston2/data/RedemptionRequestInfo.sol";
contract FAssetsRedeem {
// 1. Redeem FAssets
function redeem(
uint256 _lots,
string memory _redeemerUnderlyingAddressString
) public returns (uint256) {
// Calculate the amount of FXRP needed for redemption
IAssetManager assetManager = ContractRegistry.getAssetManagerFXRP();
AssetManagerSettings.Data memory settings = assetManager.getSettings();
uint256 amountToRedeem = settings.lotSizeAMG * _lots;
IERC20 fAssetToken = IERC20(getFXRPAddress());
// Transfer FXRP from caller to AssetManager
fAssetToken.transferFrom(msg.sender, address(this), amountToRedeem);
uint256 redeemedAmountUBA = assetManager.redeem(
_lots,
_redeemerUnderlyingAddressString,
payable(address(0))
);
return redeemedAmountUBA;
}
function getFXRPAddress() public view returns (address) {
IAssetManager assetManager = ContractRegistry.getAssetManagerFXRP();
return address(assetManager.fAsset());
}
// 2. Get the AssetManager settings
function getSettings()
public
view
returns (uint256 lotSizeAMG, uint256 assetDecimals)
{
IAssetManager assetManager = ContractRegistry.getAssetManagerFXRP();
AssetManagerSettings.Data memory settings = assetManager.getSettings();
lotSizeAMG = settings.lotSizeAMG;
assetDecimals = settings.assetDecimals;
return (lotSizeAMG, assetDecimals);
}
// 3. Get the redemption request info
function getRedemptionRequestInfo(
uint256 _redemptionTicketId
) public view returns (RedemptionRequestInfo.Data memory) {
IAssetManager assetManager = ContractRegistry.getAssetManagerFXRP();
return assetManager.redemptionRequestInfo(_redemptionTicketId);
}
}
Code Breakdown
- Redeem the FAssets using the
redeem
function by specifying the number of lots to redeem and the underlying chain address. - Retrieve the asset manager settings to calculate the redeemed amount; for this, you need to obtain the
lotSizeAMG
andassetDecimals
from the asset manager settings document. - Get the redemption request info to check the redemption status.
In this example, you are not using the executor vault address, but you can use it to redeem FAssets on behalf of another address.
Deploy and Interact with the Smart Contract
To deploy the contract and redeem FAssets, you can use the Flare Hardhat Starter Kit.
Create a new file, for example, scripts/fassets/redeem.ts
and add the following code:
Import the Required Contracts
At first, you need to import the required dependencies and smart contract TypeScript types:
import { web3, run } from "hardhat";
import { formatUnits } from "ethers";
import { FAssetsRedeemInstance, ERC20Instance } from "../../typechain-types";
import { logEvents } from "../../scripts/utils/core";
Define the Constants
Define the constants:
LOTS_TO_REDEEM
- the number of lots to redeem;UNDERLYING_ADDRESS
- underlying chain address where the redeemed asset will be sent;
const LOTS_TO_REDEEM = 1;
const UNDERLYING_ADDRESS = "rSHYuiEvsYsKR8uUHhBTuGP5zjRcGt4nm";
Import Contract Artifacts
Import the contract artifacts to interact with the smart contracts:
FAssetsRedeem
- theFAssetsRedeem
contract;IAssetManager
- the asset manager interface;IERC20
- theIERC20
contract for FXRP token;
const FAssetsRedeem = artifacts.require("FAssetsRedeem");
const AssetManager = artifacts.require("IAssetManager");
const IERC20 = artifacts.require("IERC20");
Deploy and Verify the Redeem Contract
Deploy and verify the smart contract providing the asset manager address as a constructor argument:
async function deployAndVerifyContract() {
const fAssetsRedeem: FAssetsRedeemInstance = await FAssetsRedeem.new();
const fAssetsRedeemAddress = fAssetsRedeem.address;
try {
await run("verify:verify", {
address: fAssetsRedeemAddress,
constructorArguments: [],
});
} catch (e: any) {
console.log(e);
}
console.log("FAssetsRedeem deployed to:", fAssetsRedeemAddress);
return fAssetsRedeem;
}
Approve the FXRP transfer to the Redeem Contract
To redeem FAssets, you must approve a sufficient amount of transfer of FXRP to the FAssetsRedeem
contract address after it is deployed, as it acts as the invoker during the redemption process.
async function approveFAssets(fAssetsRedeem: any, amountToRedeem: string) {
console.log("Approving FAssetsRedeem contract to spend FXRP...");
const fxrpAddress = await fAssetsRedeem.getFXRPAddress();
const fxrp: ERC20Instance = await IERC20.at(fxrpAddress);
const approveTx = await fxrp.approve(
await fAssetsRedeem.address,
amountToRedeem,
);
console.log("FXRP approval completed");
}
In a production environment, it is recommended to use a more secure method for approving the transfer of FXRP to a smart contract.
Parse the Redemption Events
During the redemption process, the AssetManager
emits events:
RedemptionRequested
- holds the agent vault address, redemption request id, the amount of FAssets to redeem, and other important information.RedemptionTicketCreated
- holds the redemption ticket information updated during the redemption process.RedemptionTicketUpdated
- holds the redemption ticket information updated during the redemption process.
To parse the redemption events, you can use the following function:
async function parseRedemptionEvents(
transactionReceipt: any,
fAssetsRedeem: any,
) {
console.log("\nParsing events...", transactionReceipt.rawLogs);
const redemptionRequestedEvents = logEvents(
transactionReceipt.rawLogs,
"RedemptionRequested",
AssetManager.abi,
);
logEvents(
transactionReceipt.rawLogs,
"RedemptionTicketCreated",
AssetManager.abi,
);
logEvents(
transactionReceipt.rawLogs,
"RedemptionTicketUpdated",
AssetManager.abi,
);
return redemptionRequestedEvents;
}
The logEvents
function is used to parse and log the events interacting using the Truffle suite of tools.
You can find this function in the Flare Hardhat Starter Kit.
In this guide, we are not focusing on the event parsing.
Output the Redemption Result
The following function is used to output the redemption request information.
async function printRedemptionRequestInfo(
fAssetsRedeem: any,
redemptionRequestedEvents: any[],
) {
console.log("\n=== Redemption Request Information ===");
for (const event of redemptionRequestedEvents) {
const redemptionRequestInfo = await fAssetsRedeem.getRedemptionRequestInfo(
event.decoded.requestId,
);
console.log("Redemption request info:", redemptionRequestInfo);
}
}
Redeeming FAssets
To put it altogether you can use the following function to deploy the contract, transfer FXRP to it, redeem the FAssets, and parse the redemption events:
async function main() {
// Deploy and verify the contract
const fAssetsRedeem = await deployAndVerifyContract();
// Get the lot size and decimals to calculate the amount to redeem
const settings = await fAssetsRedeem.getSettings();
const lotSize = settings[0];
const decimals = settings[1];
console.log("Lot size:", lotSize.toString());
console.log("Asset decimals:", decimals.toString());
// Calculate the amount to redeem according to the lot size and the number of lots to redeem
const amountToRedeem = web3.utils
.toBN(lotSize)
.mul(web3.utils.toBN(LOTS_TO_REDEEM));
console.log(
`Required FXRP amount ${formatUnits(amountToRedeem.toString(), Number(decimals))} FXRP`,
);
console.log(`Required amount in base units: ${amountToRedeem.toString()}`);
// Approve FXRP for redemption
await approveFAssets(fAssetsRedeem, amountToRedeem.toString());
// Call redeem function and wait for transaction
const redeemTx = await fAssetsRedeem.redeem(
LOTS_TO_REDEEM,
UNDERLYING_ADDRESS,
);
console.log("Redeem transaction receipt", redeemTx);
// Parse events from the transaction
const redemptionRequestedEvents = await parseRedemptionEvents(
redeemTx.receipt,
fAssetsRedeem,
);
// Print redemption request info for each redemption requested event
await printRedemptionRequestInfo(fAssetsRedeem, redemptionRequestedEvents);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run the Script
To run the script, use the Flare Hardhat Starter Kit with the following command:
npx hardhat run scripts/fassets/getLotSize.ts --network coston2
Once the script is executed, two events hold the important information:
RedemptionRequested
The RedemptionRequested
event contains the agent vault address, redeemer address, underlying chain address, amount of FAssets to redeem, redemption fee, and other important information.
RedemptionRequested Result {
'0': '0xaADbF766Fd9131996C7B270A0cCB2b5c88Fc810D',
'1': '0xDc8B65D8C07825280F2A2E08D5b25B9eea892E8f',
'2': '6797056',
'3': 'rSHYuiEvsYsKR8uUHhBTuGP5zjRcGt4nm',
'4': '10000000',
'5': '50000',
'6': '9113416',
'7': '9113939',
'8': '1753190628',
'9': '0x464250526641000200000000000000000000000000000000000000000067b700',
'10': '0x0000000000000000000000000000000000000000',
'11': '0',
__length__: 12,
agentVault: '0xaADbF766Fd9131996C7B270A0cCB2b5c88Fc810D',
redeemer: '0xDc8B65D8C07825280F2A2E08D5b25B9eea892E8f',
requestId: '6797056',
paymentAddress: 'rSHYuiEvsYsKR8uUHhBTuGP5zjRcGt4nm',
valueUBA: '10000000',
feeUBA: '50000',
firstUnderlyingBlock: '9113416',
lastUnderlyingBlock: '9113939',
lastUnderlyingTimestamp: '1753190628',
paymentReference: '0x464250526641000200000000000000000000000000000000000000000067b700',
executor: '0x0000000000000000000000000000000000000000',
executorFeeNatWei: '0'
}
When decoding an event, the most important data from the event is:
- The agent vault address that will redeem the FAssets is
0x3c831Fe4417bEFFAc721d24996985eE2dd627053
; - The redeemer address is
0xCA7C9fBbA56E44C508bcb4872775c5fEd169cDb3
; - The redemption ticket id is
1898730
; - The underlying chain address is
rSHYuiEvsYsKR8uUHhBTuGP5zjRcGt4nm
; - The amount of FAssets to redeem is
20000000
; - The redemption fee is
20000
;
You can view the full event description here.
RedemptionTicketUpdated
The event RedemptionTicketUpdated
contains the redemption ticket information, including the agent vault address, redemption ticket ID, and the value of the redemption ticket in the underlying chain currency.
For every minting, a redemption ticket is created, and during the redemption process, the redemption ticket is updated with the new redemption status.
RedemptionTicketUpdated Result {
'0': '0xaADbF766Fd9131996C7B270A0cCB2b5c88Fc810D',
'1': '1628',
'2': '5360000000',
__length__: 3,
agentVault: '0xaADbF766Fd9131996C7B270A0cCB2b5c88Fc810D',
redemptionTicketId: '1628',
ticketValueUBA: '5360000000'
}
Once decoding the most important data from the event is:
- The agent vault address that will redeem the FAssets is
0x3c831Fe4417bEFFAc721d24996985eE2dd627053
; - The redemption ticket id is
870
; - The value of the redemption ticket in underlying chain currency is
3440000000
(partially redeemed).
You can read the full event description here.
Agent Process
The FAssets agent should perform the redemption, and the user must retrieve the redeemed assets from the agent.
If the agent is unable to redeem the assets on the underlying chain in the specified time.
In that case, the user can execute the redemptionPaymentDefault
function to receive compensation from the agent's collateral.
Next Steps
This is only the first step of the redemption process. The redeemer or agent completes the redemption process when proof of payment is presented to the FAssets system. Read more about the redemption process in the here.