Withdraw Assets from Firelight Vault
Overview
This guide demonstrates how to create a withdrawal request from a Firelight vault. The Firelight vault implements the ERC-4626 standard and uses a period-based logic for withdrawals.
Withdrawing assets creates 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 withdraw.
Firelight Vault Withdraw Script
The following script demonstrates how to create a withdrawal request from the Firelight vault:
/**
* FirelightVault Withdraw Script
*
* This script creates a withdrawal request from the FirelightVault (ERC-4626).
* Withdrawals are delayed and must be claimed after the period ends.
*/
import { ethers } from "hardhat";
import { bnToBigInt } from "../utils/core";
import type { IFirelightVaultInstance } from "../../typechain-types/contracts/firelight/IFirelightVault";
import type { ERC20Instance } from "../../typechain-types/@openzeppelin/contracts/token/ERC20/ERC20";
export const FIRELIGHT_VAULT_ADDRESS =
"0x91Bfe6A68aB035DFebb6A770FFfB748C03C0E40B";
export const IFirelightVault = artifacts.require("IFirelightVault");
const tokensToWithdraw = 1; // Number of tokens to withdraw
// @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 logWithdrawInfo(
account: string,
assetAddress: string,
symbol: string,
assetDecimals: number,
withdrawAmount: bigint,
) {
console.log("=== Withdraw (ERC-4626) ===");
console.log("Sender:", account);
console.log("Vault:", FIRELIGHT_VAULT_ADDRESS);
console.log("Asset:", assetAddress, `(${symbol}, decimals=${assetDecimals})`);
console.log(
"Withdraw amount:",
withdrawAmount.toString(),
`(= ${tokensToWithdraw} ${symbol})`,
);
}
async function validateWithdraw(
vault: IFirelightVaultInstance,
account: string,
withdrawAmount: bigint,
) {
const maxWithdraw = bnToBigInt(await vault.maxWithdraw(account));
console.log("Max withdraw:", maxWithdraw);
if (withdrawAmount > maxWithdraw) {
console.error(
`Cannot withdraw ${withdrawAmount.toString()} assets. Max allowed: ${maxWithdraw.toString()}`,
);
process.exit(1);
}
}
async function checkUserBalance(
vault: IFirelightVaultInstance,
account: string,
withdrawAmount: 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)`,
);
// Use previewWithdraw to calculate how many shares are needed for this withdrawal
const sharesNeeded = await vault.previewWithdraw(withdrawAmount.toString());
if (bnToBigInt(userBalance) < bnToBigInt(sharesNeeded)) {
console.error(
`Insufficient balance. Need ${sharesNeeded.toString()} shares for withdrawal, have ${userBalance.toString()}`,
);
process.exit(1);
}
}
async function executeWithdraw(
vault: IFirelightVaultInstance,
withdrawAmount: bigint,
account: string,
) {
const withdrawTx = await vault.withdraw(
withdrawAmount.toString(),
account,
account,
{ from: account },
);
console.log("Withdraw tx:", withdrawTx.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 withdrawal amount
const withdrawAmount = BigInt(tokensToWithdraw * 10 ** assetDecimals);
// 5. Log withdraw info
logWithdrawInfo(account, assetAddress, symbol, assetDecimals, withdrawAmount);
// 6. Validate the withdrawal (check max withdraw)
await validateWithdraw(vault, account, withdrawAmount);
// 7. Check user balance and shares needed
await checkUserBalance(vault, account, withdrawAmount, assetDecimals);
// 8. Execute the withdrawal
await executeWithdraw(vault, withdrawAmount, 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 withdrawal amount: Converts the desired amount into the correct units based on decimals.
- Log withdraw info: Displays the withdrawal details, including sender, vault, and amount.
- Validate the withdrawal: Checks if the amount exceeds the maximum allowed.
- Check user balance and shares needed: Verifies the user has sufficient shares for the withdrawal.
- Execute the withdrawal: Calls
withdraw()to create a withdrawal request for the current period.
Understanding Withdrawal Process
The Firelight vault uses a period-based withdrawal system:
- Withdrawal Request: When you call
withdraw, it creates a withdrawal request associated with the current period. - Period End: The withdrawal is processed after the current period ends.
- Claim Withdrawal: Once the period has ended, you must claim the withdrawal to receive your assets.
This delayed withdrawal 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 withdraw 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
WITHDRAW_AMOUNTconstant to the desired number of tokens. - Ensure your account has sufficient vault shares to cover the withdrawal.
- Run the script using Hardhat:
npx hardhat run scripts/firelight/withdraw.ts --network coston2
Output
The script outputs the following information:
=== Withdraw (ERC-4626) ===
Sender: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2
Vault: 0x91Bfe6A68aB035DFebb6A770FFfB748C03C0E40B
Asset: 0x0b6A3645c240605887a5532109323A3E12273dc7 (FTestXRP, decimals=6)
Withdraw amount: 1000000 (= 1 FTestXRP)
Max withdraw: 2000061
User balance (shares): 2000061 (= 2.000061 shares)
Withdraw tx: 0x23d8f34b582ef9935d3e3686a15e7fff34417ac01f68f7f928b14e2b4ef10ba9
Difference Between Withdraw and Redeem
The Firelight vault provides two ways to remove assets:
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.redeem: You specify the number of shares to redeem, and the vault calculates how many assets you'll receive.
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 withdrawal request from a Firelight vault by specifying the amount of assets to withdraw. Remember that withdrawals 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 withdrawal requests.
- Learn how to redeem assets from a Firelight vault by specifying the number of shares.
- 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.