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
- Flare Hardhat Starter Kit
- Flare Network Periphery Contracts
- Understanding of FAssets
- LP tokens (vault shares) in the Upshift vault to redeem.
Upshift Vault Instant Redeem Script
The following script demonstrates how to perform an instant redemption from the Upshift vault:
/**
* 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:
- 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.
- Preview redemption: Calls
previewRedemption()to see the expected assets before and after the instant redemption fee. - Get asset balance: Records the user's asset balance before redemption.
- Execute instant redemption: Calls
instantRedeem()to burn LP tokens and receive assets immediately. - 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:
- 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/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.
To continue your Upshift development journey, you can:
- Learn more about FAssets and how the system works.
- Learn how to deposit assets into an Upshift vault.
- Learn how to request a redemption from an Upshift vault for delayed liquidity.
- Learn how to claim assets from an Upshift vault after your requested redemption is claimable.