Request Redeem from Upshift Vault
This guide demonstrates how to request a delayed redemption from an Upshift vault. The Upshift vault implements an ERC-4626 style tokenized vault that supports requested redemptions with a lower fee than instant redemptions.
Requesting a redemption locks your LP tokens (vault shares) and creates a withdrawal request. After the lag duration passes, you can claim your assets using the claim redemption script.
Prerequisites
- Flare Hardhat Starter Kit
- Flare Network Periphery Contracts
- Understanding of FAssets
- LP tokens (vault shares) in the Upshift vault to redeem.
Upshift Vault Request Redeem Script
The following script demonstrates how to request a delayed redemption from the Upshift vault:
/**
* Upshift Tokenized Vault Request Redeem Script
*
* This script requests a delayed redemption of LP shares from the Upshift Tokenized Vault.
* After the lag duration passes, use the claim script to receive assets.
*
*/
import { web3 } from "hardhat";
import { parseUnits, formatUnits } from "ethers";
import type { ITokenizedVaultInstance } from "../../typechain-types/contracts/upshift/ITokenizedVault";
import type { IERC20Instance } from "../../typechain-types/@openzeppelin/contracts/token/ERC20/IERC20";
const VAULT_ADDRESS = "0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81";
const SHARES_TO_REDEEM = "1";
const ITokenizedVault = artifacts.require("ITokenizedVault");
const IERC20 = artifacts.require("IERC20");
const IFAsset = artifacts.require("IFAsset");
async function getReferenceAssetInfo(vault: ITokenizedVaultInstance) {
const referenceAsset = await vault.asset();
const refAsset = await IFAsset.at(referenceAsset);
const decimals = Number(await refAsset.decimals());
const symbol = await refAsset.symbol();
console.log("Reference Asset (asset receiving):", referenceAsset);
return { referenceAsset, refAsset, decimals, symbol };
}
async function getLPTokenInfo(
vault: ITokenizedVaultInstance,
userAddress: string,
decimals: number,
) {
const lpTokenAddress = await vault.lpTokenAddress();
const lpToken: IERC20Instance = await IERC20.at(lpTokenAddress);
const lpBalance = await lpToken.balanceOf(userAddress);
console.log(`\nLP Balance: ${formatUnits(lpBalance.toString(), decimals)}`);
return { lpToken, lpBalance };
}
function checkLPBalance(
lpBalance: { toString(): string },
sharesToRedeem: bigint,
) {
if (BigInt(lpBalance.toString()) < sharesToRedeem) {
console.log("Insufficient LP balance!");
return false;
}
return true;
}
async function checkAndApproveLPAllowance(
lpToken: IERC20Instance,
userAddress: string,
sharesToRedeem: bigint,
decimals: number,
) {
const lpAllowance = await lpToken.allowance(userAddress, VAULT_ADDRESS);
console.log(
`\nCurrent LP Allowance: ${formatUnits(lpAllowance.toString(), decimals)}`,
);
if (BigInt(lpAllowance.toString()) < sharesToRedeem) {
console.log(`\nApproving vault to spend ${SHARES_TO_REDEEM} LP tokens...`);
const approveTx = await lpToken.approve(
VAULT_ADDRESS,
sharesToRedeem.toString(),
);
console.log("Approval Tx:", approveTx.tx);
}
}
async function checkVaultConfiguration(
vault: ITokenizedVaultInstance,
sharesToRedeem: bigint,
decimals: number,
symbol: string,
) {
const lagDuration = await vault.lagDuration();
const withdrawalFee = await vault.withdrawalFee();
const withdrawalsPaused = await vault.withdrawalsPaused();
const maxWithdrawalAmount = await vault.maxWithdrawalAmount();
console.log(`\nLag Duration: ${lagDuration.toString()} seconds`);
console.log(`Withdrawal Fee: ${formatUnits(withdrawalFee.toString(), 16)}%`);
console.log(`Withdrawals Paused: ${withdrawalsPaused}`);
console.log(
`Max Withdrawal Amount: ${formatUnits(maxWithdrawalAmount.toString(), decimals)} ${symbol}`,
);
if (withdrawalsPaused) {
console.log("\nError: Withdrawals are currently paused!");
return false;
}
if (
BigInt(maxWithdrawalAmount.toString()) > 0n &&
sharesToRedeem > BigInt(maxWithdrawalAmount.toString())
) {
console.log("\nError: Shares to redeem exceeds max withdrawal amount!");
return false;
}
return true;
}
async function previewRedemption(
vault: ITokenizedVaultInstance,
sharesToRedeem: bigint,
decimals: number,
symbol: string,
) {
const preview = await vault.previewRedemption(
sharesToRedeem.toString(),
false,
);
const assetsAmount = preview[0];
const assetsAfterFee = preview[1];
console.log(
`\nExpected Assets (before fee): ${formatUnits(assetsAmount.toString(), decimals)} ${symbol}`,
);
console.log(
`Expected Assets (after fee): ${formatUnits(assetsAfterFee.toString(), decimals)} ${symbol}`,
);
}
async function printWithdrawalEpoch(vault: ITokenizedVaultInstance) {
const epochInfo = await vault.getWithdrawalEpoch();
console.log(
`\nCurrent Epoch - Year: ${epochInfo[0].toString()}, Month: ${epochInfo[1].toString()}, Day: ${epochInfo[2].toString()}`,
);
console.log(`Claimable Epoch: ${epochInfo[3].toString()}`);
}
async function executeRequestRedeem(
vault: ITokenizedVaultInstance,
sharesToRedeem: bigint,
userAddress: string,
) {
const requestTx = await vault.requestRedeem(
sharesToRedeem.toString(),
userAddress,
);
console.log(
"\nRequest Redeem: (tx:",
requestTx.tx,
", block:",
requestTx.receipt.blockNumber,
")",
);
}
async function verifyRequest(
vault: ITokenizedVaultInstance,
lpToken: IERC20Instance,
userAddress: string,
lpBalanceBefore: { toString(): string },
decimals: number,
) {
const lpBalanceAfter = await lpToken.balanceOf(userAddress);
const sharesLocked =
BigInt(lpBalanceBefore.toString()) - BigInt(lpBalanceAfter.toString());
console.log(
`LP Balance After: ${formatUnits(lpBalanceAfter.toString(), decimals)}`,
);
console.log(
`Shares Locked: ${formatUnits(sharesLocked.toString(), decimals)}`,
);
const newEpochInfo = await vault.getWithdrawalEpoch();
console.log(
`\nClaim your assets after: ${newEpochInfo[0].toString()}/${newEpochInfo[1].toString()}/${newEpochInfo[2].toString()}`,
);
}
async function main() {
// 1. Initialize: Get user account from Hardhat network
const accounts = await web3.eth.getAccounts();
const userAddress = accounts[0];
console.log("REQUEST REDEEM FROM VAULT\n");
console.log("Vault Address:", VAULT_ADDRESS);
console.log("User Address:", userAddress);
// 2. Connect to the vault contract instance
const vault: ITokenizedVaultInstance =
await ITokenizedVault.at(VAULT_ADDRESS);
// 3. Get reference asset info
const { decimals, symbol } = await getReferenceAssetInfo(vault);
// 4. Get LP token info
const { lpToken, lpBalance } = await getLPTokenInfo(
vault,
userAddress,
decimals,
);
// 5. Convert shares amount and validate balance
const sharesToRedeem = parseUnits(SHARES_TO_REDEEM, decimals);
console.log(
`Shares to Redeem: ${SHARES_TO_REDEEM} (${sharesToRedeem.toString()})`,
);
const hasBalance = checkLPBalance(lpBalance, sharesToRedeem);
if (!hasBalance) return;
// 6. Check and approve LP allowance
await checkAndApproveLPAllowance(
lpToken,
userAddress,
sharesToRedeem,
decimals,
);
// 7. Check vault configuration
const canProceed = await checkVaultConfiguration(
vault,
sharesToRedeem,
decimals,
symbol,
);
if (!canProceed) return;
// 8. Preview redemption
await previewRedemption(vault, sharesToRedeem, decimals, symbol);
// 9. Print withdrawal epoch info
await printWithdrawalEpoch(vault);
// 10. Execute request redeem
await executeRequestRedeem(vault, sharesToRedeem, userAddress);
// 11. Verify request
await verifyRequest(vault, lpToken, userAddress, lpBalance, decimals);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Script Breakdown
The main() function executes the following steps:
- Initialize: Gets the user account and logs the vault and user addresses.
- Connect to vault: Creates an instance of the Upshift vault contract.
- Get reference asset info: Retrieves the vault's reference asset (FXRP) address, symbol, and decimals.
- Get LP token info: Retrieves the LP token address and the user's current LP balance.
- Validate balance: Converts the shares amount and checks if the user has sufficient LP tokens.
- Approve LP allowance: Approves the vault to spend LP tokens if the current allowance is insufficient.
- Check vault configuration: Verifies withdrawals are not paused and the amount doesn't exceed maximum limits.
- Preview redemption: Calls
previewRedemption()to see expected assets before and after the withdrawal fee. - Print withdrawal epoch: Displays the current epoch and claimable epoch information.
- Execute request redeem: Calls
requestRedeem()to lock LP tokens and create a withdrawal request. - Verify request: Confirms the request by checking the updated LP balance and showing when assets can be claimed.
Understanding Requested Redemption
The Upshift vault supports two types of redemptions:
- Instant Redemption: Immediately burns LP tokens and returns assets, but incurs an instant redemption fee.
- Requested Redemption: Creates a withdrawal request that is processed after a lag period, with a lower fee.
The requested redemption process works as follows:
- Request: Call
requestRedeem()to lock your LP tokens and create a withdrawal request for the current epoch. - Wait: The lag duration must pass before you can claim your assets.
- Claim: After the lag period execute the claim redemption script to receive your assets.
This delayed mechanism allows the vault to manage liquidity more efficiently while offering users a lower fee option.
Running the Script
To run the Upshift vault request redeem script:
- Ensure you have the Flare Hardhat Starter Kit set up.
- Update the
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 LP tokens (vault shares) to cover the redemption.
- Run the script using Hardhat:
npx hardhat run scripts/upshift/requestRedeem.ts --network coston2
Output
The script outputs the following information about the request redemption:
REQUEST REDEEM FROM VAULT
Vault Address: 0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81
User Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2
Reference Asset (asset receiving): 0x0b6A3645c240605887a5532109323A3E12273dc7
LP Balance: 10.0
Shares to Redeem: 1 (1000000)
Current LP Allowance: 0.0
Approving vault to spend 1 LP tokens...
Approval Tx: 0x87a36c1009575719fd3adb9a1bb2e3062a601bf910fc7fac3248da71891c39a4
Lag Duration: 86400 seconds
Withdrawal Fee: 0.5%
Withdrawals Paused: false
Max Withdrawal Amount: 1000000.0 FTestXRP
Expected Assets (before fee): 1.0 FTestXRP
Expected Assets (after fee): 0.995 FTestXRP
Current Epoch - Year: 2025, Month: 1, Day: 15
Claimable Epoch: 14
Request Redeem: (tx: 0x3f1bd8c766852d3b835bcde79f6d8e20afeeb227d737e0ed28d057dc0e6b2ba9 , block: 12345678 )
LP Balance After: 9.0
Shares Locked: 1.0
Claim your assets after: 2025/1/16
Summary
In this guide, you learned how to request a delayed redemption from an Upshift vault by specifying the number of LP tokens (shares) to redeem. The requested redemption has a lower fee than instant redemption but requires waiting for the lag duration before claiming your assets.
To continue your Upshift development journey, you can:
- Learn how to get Upshift vault status to monitor your redemption requests.
- Learn how to claim assets from an Upshift vault after your requested redemption is claimable.
- Learn how to instantly redeem from an Upshift vault for immediate liquidity.
- Learn how to deposit assets into an Upshift vault.
- Learn more about FAssets and how the system works.