Claim Redemption from Upshift Vault
This guide demonstrates how to claim assets from a previously requested redemption in an Upshift vault. The Upshift vault implements an ERC-4626 style tokenized vault that supports requested redemptions with a lower fee than instant redemptions.
Claiming is the final step after you have requested a redemption.
Once the lag duration has passed, you call claim() to receive your underlying assets (e.g., FXRP) in your wallet.
Prerequisites
- Flare Hardhat Starter Kit
- Flare Network Periphery Contracts
- Understanding of FAssets
- A pending redemption request from a previous request redeem call.
Upshift Vault Claim Script
The following script demonstrates how to claim assets from the Upshift vault:
/**
* Upshift Tokenized Vault Claim Script
*
* This script claims assets from a previously requested redemption.
* Use requestRedeem.ts first to schedule a redemption.
*
*/
import { web3 } from "hardhat";
import { 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 RECEIVER_ADDRESS = ""; // Leave empty to use sender's address
// Update these with the date from your requestRedeem call
const YEAR = 2026;
const MONTH = 1;
const DAY = 23;
const ITokenizedVault = artifacts.require("ITokenizedVault");
const IERC20 = artifacts.require("IERC20");
const IERC20Metadata = artifacts.require("IERC20Metadata");
const IFAsset = artifacts.require("IFAsset");
async function getReferenceAssetInfo(vault: ITokenizedVaultInstance) {
const referenceAsset = await vault.asset();
const refAsset = await IFAsset.at(referenceAsset);
const refDecimals = Number(await refAsset.decimals());
const refSymbol = await refAsset.symbol();
return { referenceAsset, refAsset, refDecimals, refSymbol };
}
async function checkPendingRedemption(
vault: ITokenizedVaultInstance,
year: number,
month: number,
day: number,
receiverAddr: string,
) {
console.log("\n1. Checking pending redemption...");
const shares = await vault.getBurnableAmountByReceiver(
year,
month,
day,
receiverAddr,
);
if (BigInt(shares.toString()) === 0n) {
console.log("No shares found for this date and receiver address");
return null;
}
return shares;
}
async function getLPTokenInfo(vault: ITokenizedVaultInstance) {
const lpTokenAddress = await vault.lpTokenAddress();
const lpToken = await IERC20Metadata.at(lpTokenAddress);
const lpDecimals = Number(await lpToken.decimals());
const lpSymbol = await lpToken.symbol();
return { lpTokenAddress, lpDecimals, lpSymbol };
}
async function previewRedemption(
vault: ITokenizedVaultInstance,
shares: { toString(): string },
refDecimals: number,
refSymbol: string,
) {
console.log("\n2. Previewing redemption...");
const preview = await vault.previewRedemption(shares.toString(), false);
const assetsAmount = preview[0];
const assetsAfterFee = preview[1];
console.log(
`Assets (before fee): ${formatUnits(assetsAmount.toString(), refDecimals)} ${refSymbol}`,
);
console.log(
`Assets (after fee): ${formatUnits(assetsAfterFee.toString(), refDecimals)} ${refSymbol}`,
);
const fee =
BigInt(assetsAmount.toString()) - BigInt(assetsAfterFee.toString());
console.log(`Fee: ${formatUnits(fee.toString(), refDecimals)} ${refSymbol}`);
return { assetsAmount, assetsAfterFee };
}
async function checkIfClaimable(year: number, month: number, day: number) {
console.log("\n3. Checking if claimable...");
const block = await web3.eth.getBlock("latest");
const blockTimestamp = BigInt(block.timestamp);
const claimableDate = new Date(Date.UTC(year, month - 1, day, 0, 0, 0));
const claimableEpoch = BigInt(Math.floor(claimableDate.getTime() / 1000));
const TIMESTAMP_MANIPULATION_WINDOW = 300n; // 5 minutes
console.log(`Current Timestamp: ${blockTimestamp.toString()}`);
console.log(`Claimable Epoch: ${claimableEpoch.toString()}`);
const canClaim =
blockTimestamp + TIMESTAMP_MANIPULATION_WINDOW >= claimableEpoch;
if (!canClaim) {
const timeUntil =
claimableEpoch - blockTimestamp - TIMESTAMP_MANIPULATION_WINDOW;
const hoursUntil = Number(timeUntil) / 3600;
console.log("Cannot claim yet!");
console.log(`Wait approximately ${hoursUntil.toFixed(2)} more hours`);
console.log(
`Claimable after: ${new Date(Number(claimableEpoch) * 1000).toISOString()}`,
);
return false;
}
console.log("Ready to claim!");
return true;
}
async function getBalanceBefore(
referenceAsset: string,
receiverAddr: string,
refDecimals: number,
refSymbol: string,
) {
console.log("\n4. Checking receiver balance before...");
const refToken: IERC20Instance = await IERC20.at(referenceAsset);
const balanceBefore = await refToken.balanceOf(receiverAddr);
console.log(
`Balance: ${formatUnits(balanceBefore.toString(), refDecimals)} ${refSymbol}`,
);
return { refToken, balanceBefore };
}
async function executeClaim(
vault: ITokenizedVaultInstance,
year: number,
month: number,
day: number,
receiverAddr: string,
) {
console.log("\n5. Claiming...");
const claimTx = await vault.claim(year, month, day, receiverAddr);
console.log(
"Claim: (tx:",
claimTx.tx,
", block:",
claimTx.receipt.blockNumber,
")",
);
}
async function verifyClaim(
refToken: IERC20Instance,
receiverAddr: string,
balanceBefore: { toString(): string },
assetsAfterFee: { toString(): string },
refDecimals: number,
refSymbol: string,
) {
console.log("\n6. Verifying claim...");
const balanceAfter = await refToken.balanceOf(receiverAddr);
const received =
BigInt(balanceAfter.toString()) - BigInt(balanceBefore.toString());
console.log(
`Balance After: ${formatUnits(balanceAfter.toString(), refDecimals)} ${refSymbol}`,
);
console.log(
`Received: ${formatUnits(received.toString(), refDecimals)} ${refSymbol}`,
);
if (received === BigInt(assetsAfterFee.toString())) {
console.log("Claim successful!");
} else {
console.log(
"Received amount differs from expected (may be due to rounding)",
);
}
}
async function main() {
// 1. Initialize: Get user account from Hardhat network
const accounts = await web3.eth.getAccounts();
const userAddress = accounts[0];
const receiverAddr = RECEIVER_ADDRESS || userAddress;
console.log("CLAIM REDEMPTION\n");
console.log("Vault Address:", VAULT_ADDRESS);
console.log("User Address:", userAddress);
console.log("Receiver Address:", receiverAddr);
console.log(
`Redemption Date: ${YEAR}-${String(MONTH).padStart(2, "0")}-${String(DAY).padStart(2, "0")}`,
);
// 2. Connect to the vault contract instance
const vault: ITokenizedVaultInstance =
await ITokenizedVault.at(VAULT_ADDRESS);
// 3. Get reference asset info
const { referenceAsset, refDecimals, refSymbol } =
await getReferenceAssetInfo(vault);
// 4. Check pending redemption
const shares = await checkPendingRedemption(
vault,
YEAR,
MONTH,
DAY,
receiverAddr,
);
if (!shares) return;
// 5. Get LP token info
const { lpTokenAddress, lpDecimals, lpSymbol } = await getLPTokenInfo(vault);
console.log(
`Shares to claim: ${formatUnits(shares.toString(), lpDecimals)} ${lpSymbol}`,
);
console.log(`LP Token Address: ${lpTokenAddress}`);
// 6. Preview redemption
const { assetsAfterFee } = await previewRedemption(
vault,
shares,
refDecimals,
refSymbol,
);
// 7. Check if claimable
const canClaim = await checkIfClaimable(YEAR, MONTH, DAY);
if (!canClaim) return;
// 8. Get balance before
const { refToken, balanceBefore } = await getBalanceBefore(
referenceAsset,
receiverAddr,
refDecimals,
refSymbol,
);
// 9. Execute claim
await executeClaim(vault, YEAR, MONTH, DAY, receiverAddr);
// 10. Verify claim
await verifyClaim(
refToken,
receiverAddr,
balanceBefore,
assetsAfterFee,
refDecimals,
refSymbol,
);
}
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, user, receiver addresses, and redemption date.
- 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.
- Check pending redemption: Verifies that there are shares to claim for the given date and receiver.
- Get LP token info: Retrieves the LP token address, decimals, and symbol.
- Preview redemption: Calls
previewRedemption()to see expected assets before and after the withdrawal fee. - Check if claimable: Ensures the current block timestamp is past the claimable date (plus a small buffer) before proceeding.
- Get balance before: Records the receiver's asset balance before the claim.
- Execute claim: Calls
claim()with the redemption date and receiver to transfer assets. - Verify claim: Confirms the claim by comparing the receiver's new asset balance to the expected amount.
Understanding the Claim Process
The claim step completes the requested redemption flow:
- Request: You previously called
requestRedeem()to lock LP tokens and create a withdrawal request for a specific epoch. - Wait: The lag duration must pass before assets can be claimed.
- Claim: Once the claimable date has been reached, call
claim(year, month, day, receiver)to receive your assets.
The script uses the redemption date (year, month, day) that was output when you ran the request redeem script.
You must set YEAR, MONTH, and DAY in the script to match that date.
Running the Script
To run the Upshift vault claim script:
- Ensure you have the Flare Hardhat Starter Kit set up.
- Update the
VAULT_ADDRESSconstant with the correct vault address for your network. - Set
YEAR,MONTH, andDAYto the claimable date from your request redeem output. - Optionally set
RECEIVER_ADDRESSto claim to a different address (leave empty to use the signer). - Ensure the claimable date has passed (the script checks and exits with a message if not).
- Run the script using Hardhat:
npx hardhat run scripts/upshift/claim.ts --network coston2
Output
The script outputs the following information about the claim:
CLAIM REDEMPTION
Vault Address: 0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81
User Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2
Receiver Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2
Redemption Date: 2026-01-23
1. Checking pending redemption...
2. Previewing redemption...
Assets (before fee): 1.0 FTestXRP
Assets (after fee): 0.995 FTestXRP
Fee: 0.005 FTestXRP
Shares to claim: 1.0
LP Token Address: 0x...
3. Checking if claimable...
Current Timestamp: 1737648000
Claimable Epoch: 1737586800
Ready to claim!
4. Checking receiver balance before...
Balance: 50.0 FTestXRP
5. Claiming...
Claim: (tx: 0x3f1bd8c766852d3b835bcde79f6d8e20afeeb227d737e0ed28d057dc0e6b2ba9 , block: 12345678 )
6. Verifying claim...
Balance After: 50.995 FTestXRP
Received: 0.995 FTestXRP
Claim successful!
Summary
In this guide, you learned how to claim assets from an Upshift vault after a requested redemption. You use the redemption date (year, month, day) from your request redeem output, and once the lag period has passed, the claim script transfers the underlying assets to your wallet.
To continue your Upshift development journey, you can:
- Learn how to get Upshift vault status to monitor your redemption requests.
- Learn how to request a redemption from an Upshift vault.
- 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.