Redeem Assets from Firelight Vault
Overview
This guide demonstrates how to create a redemption request from a Firelight vault. The Firelight vault implements the ERC-4626 standard and uses a period-based logic for redemptions.
Redeeming shares burns vault shares and allows assets to be withdrawn. Redemptions create a withdrawal request that is processed after the current period ends. The assets are not immediately transferred; they must be claimed once the period has ended.
Prerequisites
- Flare Hardhat Starter Kit
- Flare Network Periphery Contracts
- Understanding of FAssets
- Vault shares in the Firelight vault to redeem.
Firelight Vault Redeem Script
The following script demonstrates how to create a redemption request from the Firelight vault:
/**
* FirelightVault Redeem Script
*
* This script creates a redemption request from the FirelightVault (ERC-4626).
* Redeem burns shares to withdraw assets. Redemptions are delayed and must be claimed after the period ends.
*/
import { ethers } from "hardhat";
import type { IFirelightVaultInstance } from "../../typechain-types/contracts/firelight/IFirelightVault";
import type { ERC20Instance } from "../../typechain-types/@openzeppelin/contracts/token/ERC20/ERC20";
import { bnToBigInt } from "../utils/core";
export const FIRELIGHT_VAULT_ADDRESS =
"0x91Bfe6A68aB035DFebb6A770FFfB748C03C0E40B";
export const IFirelightVault = artifacts.require("IFirelightVault");
const sharesToRedeem = 1; // Number of shares to redeem
// @ts-expect-error - Type definitions issue, but works at runtime
const IERC20 = artifacts.require(
"@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20",
);
async function getAccount() {
const [signer] = await ethers.getSigners();
return { signer, account: signer.address };
}
async function getVaultAndAsset() {
const vault = (await IFirelightVault.at(
FIRELIGHT_VAULT_ADDRESS,
)) as IFirelightVaultInstance;
const assetAddress = await vault.asset();
const assetToken = (await IERC20.at(assetAddress)) as ERC20Instance;
return { vault, assetAddress, assetToken };
}
async function getAssetInfo(assetToken: ERC20Instance) {
const symbol = await assetToken.symbol();
const assetDecimals = (await assetToken.decimals()).toNumber();
return { symbol, assetDecimals };
}
function logRedeemInfo(
account: string,
assetAddress: string,
symbol: string,
assetDecimals: number,
sharesAmount: bigint,
) {
console.log("=== Redeem (ERC-4626) ===");
console.log("Sender:", account);
console.log("Vault:", FIRELIGHT_VAULT_ADDRESS);
console.log("Asset:", assetAddress, `(${symbol}, decimals=${assetDecimals})`);
console.log(
"Shares to redeem:",
sharesAmount.toString(),
`(= ${sharesToRedeem} share${sharesToRedeem > 1 ? "s" : ""})`,
);
}
async function validateRedeem(
vault: IFirelightVaultInstance,
account: string,
sharesAmount: bigint,
) {
const maxRedeem = bnToBigInt(await vault.maxRedeem(account));
console.log("Max redeem:", maxRedeem);
if (sharesAmount > maxRedeem) {
console.error(
`Cannot redeem ${sharesAmount.toString()} shares. Max allowed: ${maxRedeem.toString()}`,
);
process.exit(1);
}
}
async function checkUserBalance(
vault: IFirelightVaultInstance,
account: string,
sharesAmount: bigint,
assetDecimals: number,
) {
const userBalance = await vault.balanceOf(account);
const formattedUserBalance = (
Number(userBalance.toString()) / Math.pow(10, assetDecimals)
).toFixed(assetDecimals);
console.log(
"User balance (shares):",
userBalance.toString(),
`(= ${formattedUserBalance} shares)`,
);
if (bnToBigInt(userBalance) < sharesAmount) {
console.error(
`Insufficient balance. Need ${sharesAmount.toString()} shares, have ${userBalance.toString()}`,
);
process.exit(1);
}
}
async function executeRedeem(
vault: IFirelightVaultInstance,
sharesAmount: bigint,
account: string,
) {
const redeemTx = await vault.redeem(
sharesAmount.toString(),
account,
account,
{ from: account },
);
console.log("Redeem tx:", redeemTx.tx);
}
async function main() {
// 1. Get the account
const { account } = await getAccount();
// 2. Get the vault and asset token
const { vault, assetAddress, assetToken } = await getVaultAndAsset();
// 3. Get asset info (symbol, decimals)
const { symbol, assetDecimals } = await getAssetInfo(assetToken);
// 4. Calculate the shares amount to redeem
const sharesAmount = BigInt(sharesToRedeem * 10 ** assetDecimals);
// 5. Log redeem info
logRedeemInfo(account, assetAddress, symbol, assetDecimals, sharesAmount);
// 6. Validate the redeem (check max redeem)
await validateRedeem(vault, account, sharesAmount);
// 7. Check user balance
await checkUserBalance(vault, account, sharesAmount, assetDecimals);
// 8. Execute the redemption
await executeRedeem(vault, sharesAmount, account);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Script Breakdown
The main() function executes the following steps:
- Get the account: Retrieves the signer account from the Hardhat environment.
- Get the vault and asset token: Connects to the vault contract and retrieves the underlying asset token.
- Get asset info: Fetches the asset token's symbol and decimals.
- Calculate the shares amount to redeem: Converts the desired shares into the correct units based on decimals.
- Log redeem info: Displays the redemption details, including sender, vault, and shares.
- Validate the redeem: Checks if the amount exceeds the maximum allowed.
- Check user balance: Verifies the user has sufficient shares.
- Execute the redemption: Calls
redeem()to create a redemption request for the current period.
Understanding Redemption Process
The Firelight vault uses a period-based redemption system:
- Redemption Request: When you call
redeem, it creates a redemption request associated with the current period. - Period End: The redemption is processed after the current period ends.
- Claim Redemption: Once the period has ended, you must claim the redemption to receive your assets.
This delayed redemption mechanism is part of the vault's period-based logic and helps manage liquidity and ensure fair distribution of rewards.
Running the Script
To run the Firelight vault redeem script:
- Ensure you have the Flare Hardhat Starter Kit set up.
- Update the
FIRELIGHT_VAULT_ADDRESSconstant with the correct vault address for your network. - Adjust the
SHARES_TO_REDEEMconstant to the desired number of shares. - Ensure your account has sufficient vault shares to cover the redemption.
- Run the script using Hardhat:
npx hardhat run scripts/firelight/redeem.ts --network coston2
Output
The script outputs the following information:
=== Redeem (ERC-4626) ===
Sender: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2
Vault: 0x91Bfe6A68aB035DFebb6A770FFfB748C03C0E40B
Asset: 0x0b6A3645c240605887a5532109323A3E12273dc7 (FTestXRP, decimals=6)
Shares to redeem: 1000000 (= 1 share)
Max redeem: 1000061
User balance (shares): 1000061 (= 1.000061 shares)
Redeem tx: 0x3f1bd8c766852d3b835bcde79f6d8e20afeeb227d737e0ed28d057dc0e6b2ba9
Difference Between Redeem and Withdraw
The Firelight vault provides two ways to remove assets:
redeem: You specify the number of shares to redeem, and the vault calculates how many assets you'll receive.withdraw: You specify the amount of assets to withdraw, and the vault calculates how many shares need to be burned based on the current exchange rate.
Both functions follow the ERC-4626 standard and create withdrawal requests that must be claimed after the period ends.
Summary
In this guide, you learned how to create a redemption request from a Firelight vault by specifying the number of shares to redeem. Remember that redemptions are delayed and must be claimed after the current period ends.
To continue your Firelight development journey, you can:
- Learn how to get Firelight vault status to monitor your redemption requests.
- Learn how to withdraw assets from a Firelight vault by specifying the amount of assets.
- Learn how to deposit assets into a Firelight vault.
- Learn how to mint Firelight vault shares by specifying the number of shares.
- Explore the FAssets system overview to understand the broader ecosystem.