Skip to main content

Instant Redeem from Upshift Vault

This guide demonstrates how to perform an instant redemption from an Upshift vault. The Upshift vault implements an ERC-4626 style tokenized vault that supports instant redemptions for immediate liquidity when available.

Instant redemption burns your LP tokens (vault shares) and immediately returns the underlying assets to your wallet, subject to an instant redemption fee.

Prerequisites

Upshift Vault Instant Redeem Script

The following script demonstrates how to perform an instant redemption from the Upshift vault:

scripts/upshift/instantRedeem.ts
/**
* Upshift Tokenized Vault Instant Redeem Script
*
* This script performs an instant redemption of LP shares from the Upshift Tokenized Vault.
*
*/

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 previewRedemption(
vault: ITokenizedVaultInstance,
sharesToRedeem: bigint,
decimals: number,
symbol: string,
) {
const instantRedemptionFee = await vault.instantRedemptionFee();
console.log(
`\nInstant Redemption Fee: ${formatUnits(instantRedemptionFee.toString(), 16)}%`,
);

const preview = await vault.previewRedemption(
sharesToRedeem.toString(),
true,
);
const assetsAmount = preview[0];
const assetsAfterFee = preview[1];

console.log(
`Expected Assets (before fee): ${formatUnits(assetsAmount.toString(), decimals)} ${symbol}`,
);
console.log(
`Expected Assets (after fee): ${formatUnits(assetsAfterFee.toString(), decimals)} ${symbol}`,
);
}

async function getAssetBalanceBefore(
refAsset: IERC20Instance,
userAddress: string,
decimals: number,
symbol: string,
) {
const assetBalanceBefore = await refAsset.balanceOf(userAddress);
console.log(
`\nAsset Balance Before: ${formatUnits(assetBalanceBefore.toString(), decimals)} ${symbol}`,
);

return { assetBalanceBefore };
}

async function executeInstantRedeem(
vault: ITokenizedVaultInstance,
sharesToRedeem: bigint,
userAddress: string,
) {
const redeemTx = await vault.instantRedeem(
sharesToRedeem.toString(),
userAddress,
);
console.log(
"\nInstant Redeem: (tx:",
redeemTx.tx,
", block:",
redeemTx.receipt.blockNumber,
")",
);
}

async function verifyRedemption(
lpToken: IERC20Instance,
refAsset: IERC20Instance,
userAddress: string,
lpBalanceBefore: { toString(): string },
assetBalanceBefore: { toString(): string },
decimals: number,
symbol: string,
) {
console.log("\nVerifying redemption...");

const lpBalanceAfter = await lpToken.balanceOf(userAddress);
const assetBalanceAfter = await refAsset.balanceOf(userAddress);

const sharesRedeemed =
BigInt(lpBalanceBefore.toString()) - BigInt(lpBalanceAfter.toString());
const assetsReceived =
BigInt(assetBalanceAfter.toString()) -
BigInt(assetBalanceBefore.toString());

console.log(
`LP Balance After: ${formatUnits(lpBalanceAfter.toString(), decimals)}`,
);
console.log(
`Shares Redeemed: ${formatUnits(sharesRedeemed.toString(), decimals)}`,
);
console.log(
`Assets Received: ${formatUnits(assetsReceived.toString(), decimals)} ${symbol}`,
);
}

async function main() {
// 1. Initialize: Get user account from Hardhat network
const accounts = await web3.eth.getAccounts();
const userAddress = accounts[0];

console.log("INSTANT 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 { refAsset, 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. Preview redemption
await previewRedemption(vault, sharesToRedeem, decimals, symbol);

// 7. Get asset balance before redemption
const { assetBalanceBefore } = await getAssetBalanceBefore(
refAsset,
userAddress,
decimals,
symbol,
);

// 8. Execute instant redemption
await executeInstantRedeem(vault, sharesToRedeem, userAddress);

// 9. Verify redemption
await verifyRedemption(
lpToken,
refAsset,
userAddress,
lpBalance,
assetBalanceBefore,
decimals,
symbol,
);
}

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. Preview redemption: Calls previewRedemption() to see the expected assets before and after the instant redemption fee.
  7. Get asset balance: Records the user's asset balance before redemption.
  8. Execute instant redemption: Calls instantRedeem() to burn LP tokens and receive assets immediately.
  9. Verify redemption: Confirms the redemption by checking the updated LP and asset balances.

Understanding Instant 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 instant redemption fee compensates the vault for providing immediate liquidity. The previewRedemption() function shows both the gross and net amounts you'll receive.

Running the Script

To run the Upshift vault instant 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/instantRedeem.ts --network coston2

Output

The script outputs the following information about the instant redemption:

INSTANT REDEEM FROM VAULT

Vault Address: 0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81
User Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2
Reference Asset (asset receiving): 0x0b6A3645c240605887a5532109323A3E12273dc7

LP Balance: 10.0
Shares to Redeem: 1 (1000000)

Instant Redemption Fee: 1.0%
Expected Assets (before fee): 1.0 FTestXRP
Expected Assets (after fee): 0.99 FTestXRP

Asset Balance Before: 50.0 FTestXRP

Instant Redeem: (tx: 0x3f1bd8c766852d3b835bcde79f6d8e20afeeb227d737e0ed28d057dc0e6b2ba9 , block: 12345678 )

Verifying redemption...
LP Balance After: 9.0
Shares Redeemed: 1.0
Assets Received: 0.99 FTestXRP

Summary

In this guide, you learned how to perform an instant redemption from an Upshift vault by specifying the number of LP tokens (shares) to redeem. The instant redemption provides immediate liquidity but incurs a fee.

What's next

To continue your Upshift development journey, you can: