Skip to main content

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

Upshift Vault Request Redeem Script

The following script demonstrates how to request a delayed redemption from the Upshift vault:

scripts/upshift/requestRedeem.ts
/**
* 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:

  1. Initialize: Gets the user account and logs the vault and user addresses.
  2. Connect to vault: Creates an instance of the Upshift vault contract.
  3. Get reference asset info: Retrieves the vault's reference asset (FXRP) address, symbol, and decimals.
  4. Get LP token info: Retrieves the LP token address and the user's current LP balance.
  5. Validate balance: Converts the shares amount and checks if the user has sufficient LP tokens.
  6. Approve LP allowance: Approves the vault to spend LP tokens if the current allowance is insufficient.
  7. Check vault configuration: Verifies withdrawals are not paused and the amount doesn't exceed maximum limits.
  8. Preview redemption: Calls previewRedemption() to see expected assets before and after the withdrawal fee.
  9. Print withdrawal epoch: Displays the current epoch and claimable epoch information.
  10. Execute request redeem: Calls requestRedeem() to lock LP tokens and create a withdrawal request.
  11. 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:

  1. Request: Call requestRedeem() to lock your LP tokens and create a withdrawal request for the current epoch.
  2. Wait: The lag duration must pass before you can claim your assets.
  3. 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:

  1. Ensure you have the Flare Hardhat Starter Kit set up.
  2. Update the VAULT_ADDRESS constant with the correct vault address for your network.
  3. Adjust the SHARES_TO_REDEEM constant to the desired number of shares.
  4. Ensure your account has sufficient LP tokens (vault shares) to cover the redemption.
  5. 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.

What's next

To continue your Upshift development journey, you can: