diff --git a/docs/fxrp/overview.mdx b/docs/fxrp/overview.mdx index e76cc8b9..da8813b5 100644 --- a/docs/fxrp/overview.mdx +++ b/docs/fxrp/overview.mdx @@ -6,6 +6,7 @@ keywords: [fassets, fxrp, xrp, flare-network, firelight, vault, defi] import DocCardList from "@theme/DocCardList"; import Firelight from "./firelight/_firelight.mdx"; +import Upshift from "./upshift/_upshift.mdx"; FXRP is the [FAsset](/fassets/overview) representation of XRP on the Flare network. It is an [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token that represents XRP bridged from the XRP Ledger (XRPL) to Flare. @@ -123,11 +124,4 @@ FXRP is deployed as an [Omnichain Fungible Token (OFT)](/fxrp/oft) using LayerZe ## Upshift Vaults -[Upshift vault](https://app.upshift.finance/pools/14/0x373D7d201C8134D4a2f7b5c63560da217e3dEA28) is part of the XRP finance stack on Flare Network. -It inherits the transparency model of the [FAssets](/fassets/overview) system: minting and redemption are proof-backed, and vault interactions are recorded on-chain. - -- **Funds are on-chain:** User deposits, vault balances, withdrawal requests, and claims are executed as smart contract state changes on Flare. - You can verify all vault holdings on [DeBank](https://debank.com/profile/0xEDb7B1e92B8D3621b46843AD024949F10438374B) portfolio tracker. -- **User ownership is verifiable:** A user's position is represented directly on-chain and can be queried from smart contracts. -- **Instruction flow is auditable:** XRPL-triggered actions are executed on Flare via [Smart Accounts](/smart-accounts/overview) with [Flare Data Connector](/fdc/overview) backed verification and onchain execution. - You can verify all smart account activity on the [Flare Systems Explorer](https://flare-systems-explorer.flare.network/smart-accounts). + diff --git a/docs/fxrp/upshift/01-status.mdx b/docs/fxrp/upshift/01-status.mdx new file mode 100644 index 00000000..286accbe --- /dev/null +++ b/docs/fxrp/upshift/01-status.mdx @@ -0,0 +1,108 @@ +--- +title: Get Upshift Vault Status +tags: [intermediate, upshift, fassets] +slug: status +description: Learn how to get Upshift vault status +keywords: [fassets, flare-network, fxrp, upshift, vault] +sidebar_position: 1 +--- + +import CodeBlock from "@theme/CodeBlock"; +import UpshiftStatus from "!!raw-loader!/examples/developer-hub-javascript/upshift-status.ts"; + +This guide demonstrates how to retrieve information about an Upshift vault, including vault configuration, LP token info, user balances, and withdrawal epoch information. + +The Upshift vault is an [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) style tokenized vault compatible with [FAssets](/fassets/overview). + +## Prerequisites + +- [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) +- [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) +- Understanding of [FAssets](/fassets/overview) + +## Upshift Vault Status Script + +The following script retrieves and displays information about the Upshift vault: + + + {UpshiftStatus} + + +## 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 vault's LP token address, which represents user shares. +5. **Get vault configuration:** Fetches key vault parameters. +6. **Get withdrawal epoch info:** Retrieves the current withdrawal epoch (year, month, day) and claimable epoch. +7. **Get user balances:** Shows the user's reference asset balance and LP token balance. +8. **Check allowances:** Displays the user's token allowances to the vault for both the reference asset and LP token. + +## Running the Script + +To run the Upshift vault status script: + +1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. +2. Update the `VAULT_ADDRESS` constant with the correct vault address for your network. +3. Run the script using Hardhat: + +```bash +npx hardhat run scripts/upshift/status.ts --network coston2 +``` + +## Output + +The script outputs the following information about the Upshift vault: + +```bash +VAULT STATUS + +Vault Address: 0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81 +User Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 + +Reference Asset +Address: 0x0b6A3645c240605887a5532109323A3E12273dc7 +Symbol: FTestXRP +Decimals: 6 + +LP Token +Address: 0x1234567890abcdef1234567890abcdef12345678 + +Vault Configuration +Withdrawals Paused: false +Lag Duration: 86400 seconds +Withdrawal Fee: 0.5% +Instant Redemption Fee: 1.0% +Max Withdrawal Amount: 1000000.0 FTestXRP + +Withdrawal Epoch +Year: 2025, Month: 1, Day: 15 +Claimable Epoch: 14 + +User Balances +FTestXRP Balance: 100.0 +LP Token Balance: 95.5 + +Allowances +FTestXRP Allowance to Vault: 0.0 +LP Token Allowance to Vault: 0.0 +``` + +## Summary + +In this guide, you learned how to retrieve status information from an Upshift vault, including vault configuration, LP token details, user balances, and withdrawal epoch information. + +:::tip[What's next] + +To continue your Upshift development journey, you can: + +- Learn more about [FAssets](/fassets/overview) and how the system works. +- Learn how to [deposit assets into an Upshift vault](/fxrp/upshift/deposit). +- Learn how to [instantly redeem from an Upshift vault](/fxrp/upshift/instant-redeem) for immediate liquidity. +- Learn how to [request a redemption from an Upshift vault](/fxrp/upshift/request-redeem) for delayed liquidity. +- Learn how to [claim assets from an Upshift vault](/fxrp/upshift/claim) after your requested redemption is claimable. + +::: diff --git a/docs/fxrp/upshift/02-deposit.mdx b/docs/fxrp/upshift/02-deposit.mdx new file mode 100644 index 00000000..9967b24c --- /dev/null +++ b/docs/fxrp/upshift/02-deposit.mdx @@ -0,0 +1,106 @@ +--- +title: Deposit Assets into Upshift Vault +tags: [intermediate, upshift, fassets] +slug: deposit +description: Learn how to deposit assets into an Upshift vault +keywords: [fassets, flare-network, fxrp, upshift, vault] +sidebar_position: 2 +--- + +import CodeBlock from "@theme/CodeBlock"; +import UpshiftDeposit from "!!raw-loader!/examples/developer-hub-javascript/upshift-deposit.ts"; + +This guide demonstrates how to deposit assets into an Upshift vault. +The Upshift vault implements an [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) style tokenized vault, allowing users to deposit assets (e.g., FXRP) and receive LP tokens (vault shares) in return. + +Depositing assets is the process of transferring them to the vault and receiving LP tokens that represent your proportional ownership of the vault's assets. + +## Prerequisites + +- [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) +- [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) +- Understanding of [FAssets](/fassets/overview) +- Sufficient asset balance (e.g., FXRP) to deposit into the vault. + +## Upshift Vault Deposit Script + +The following script demonstrates how to deposit assets into the Upshift vault: + + + {UpshiftDeposit} + + +## 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. **Calculate deposit amount:** Converts the desired amount into the correct units based on decimals. +5. **Check balance:** Verifies the user has sufficient balance to cover the deposit. +6. **Approve allowance:** Approves the vault to spend the deposit amount if needed. +7. **Preview deposit:** Calls [`previewDeposit()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-7.-previewdeposit-uint256-assets) to see the expected shares and amount in reference tokens. +8. **Get LP token info:** Records the LP token balance before the deposit. +9. **Execute deposit:** Calls [`deposit()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-1.-deposit-uint256-assets-address-receiver) to transfer assets and mint LP tokens. +10. **Verify deposit:** Confirms the deposit by checking the new LP token balance and shares received. + +## Running the Script + +To run the Upshift vault deposit script: + +1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. +2. Update the `VAULT_ADDRESS` constant with the correct vault address for your network. +3. Adjust the `DEPOSIT_AMOUNT` constant to the desired number of tokens. +4. Ensure your account has sufficient asset balance (e.g., FXRP) to cover the deposit. +5. Run the script using Hardhat: + +```bash +npx hardhat run scripts/upshift/deposit.ts --network coston2 +``` + +## Output + +The script outputs the following information about the deposit: + +```bash +DEPOSIT TO VAULT + +Vault Address: 0x24c1a47cD5e8473b64EAB2a94515a196E10C7C81 +User Address: 0x0d09ff7630588E05E2449aBD3dDD1D8d146bc5c2 + +Reference Asset (asset depositing): 0x0b6A3645c240605887a5532109323A3E12273dc7 + +Deposit Amount: 1 FTestXRP (1000000) +Balance: 100.0 FTestXRP +Current Allowance: 0.0 FTestXRP + +Approving vault to spend 1 FTestXRP tokens +Approval Tx: 0x87a36c1009575719fd3adb9a1bb2e3062a601bf910fc7fac3248da71891c39a4 + +Expected Shares: 0.99 +Amount in Reference Tokens: 1.0 +LP Balance Before: 0.0 + +Deposit: (tx: 0x446b7a171859d676677fc870cff81c7e8c0d618fc3588e60665792da86b94c50 , block: 12345678 ) + +Verifying deposit... +LP Balance After: 0.99 +Shares Received: 0.99 +``` + +## Summary + +In this guide, you learned how to deposit assets into an Upshift vault by specifying the amount of assets to deposit and receiving LP tokens in return. + +:::tip[What's next] + +To continue your Upshift development journey, you can: + +- Learn more about [FAssets](/fassets/overview) and how the system works. +- Learn how to [get Upshift vault status](/fxrp/upshift/status) to monitor your position. +- Learn how to [instantly redeem from an Upshift vault](/fxrp/upshift/instant-redeem) for immediate liquidity. +- Learn how to [request a redemption from an Upshift vault](/fxrp/upshift/request-redeem) for delayed liquidity. +- Learn how to [claim assets from an Upshift vault](/fxrp/upshift/claim) after your requested redemption is claimable. + +::: diff --git a/docs/fxrp/upshift/03-instant-redeem.mdx b/docs/fxrp/upshift/03-instant-redeem.mdx new file mode 100644 index 00000000..0691b2aa --- /dev/null +++ b/docs/fxrp/upshift/03-instant-redeem.mdx @@ -0,0 +1,113 @@ +--- +title: Instant Redeem from Upshift Vault +tags: [intermediate, upshift, fassets] +slug: instant-redeem +description: Learn how to instantly redeem LP tokens from an Upshift vault +keywords: [fassets, flare-network, fxrp, upshift, vault, redeem] +sidebar_position: 3 +--- + +import CodeBlock from "@theme/CodeBlock"; +import UpshiftInstantRedeem from "!!raw-loader!/examples/developer-hub-javascript/upshift-instant-redeem.ts"; + +This guide demonstrates how to perform an instant redemption from an Upshift vault. +The Upshift vault implements an [ERC-4626](https://eips.ethereum.org/EIPS/eip-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](/network/guides/hardhat-foundry-starter-kit) +- [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) +- Understanding of [FAssets](/fassets/overview) +- 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: + + + {UpshiftInstantRedeem} + + +## 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()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-8.-previewredeem-uint256-shares) 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()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-4.-instantredeem-uint256-shares-address-receiveraddr-address-holderaddr) 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()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-8.-previewredeem-uint256-shares) 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](/network/guides/hardhat-foundry-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: + +```bash +npx hardhat run scripts/upshift/instantRedeem.ts --network coston2 +``` + +## Output + +The script outputs the following information about the instant redemption: + +```bash +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. + +:::tip[What's next] + +To continue your Upshift development journey, you can: + +- Learn more about [FAssets](/fassets/overview) and how the system works. +- Learn how to [deposit assets into an Upshift vault](/fxrp/upshift/deposit). +- Learn how to [request a redemption from an Upshift vault](/fxrp/upshift/request-redeem) for delayed liquidity. +- Learn how to [claim assets from an Upshift vault](/fxrp/upshift/claim) after your requested redemption is claimable. + +::: diff --git a/docs/fxrp/upshift/04-request-redeem.mdx b/docs/fxrp/upshift/04-request-redeem.mdx new file mode 100644 index 00000000..fc5f44eb --- /dev/null +++ b/docs/fxrp/upshift/04-request-redeem.mdx @@ -0,0 +1,131 @@ +--- +title: Request Redeem from Upshift Vault +tags: [intermediate, upshift, fassets] +slug: request-redeem +description: Learn how to request a delayed redemption from an Upshift vault +keywords: [fassets, flare-network, fxrp, upshift, vault, redeem, withdrawal] +sidebar_position: 4 +--- + +import CodeBlock from "@theme/CodeBlock"; +import UpshiftRequestRedeem from "!!raw-loader!/examples/developer-hub-javascript/upshift-request-redeem.ts"; + +This guide demonstrates how to request a delayed redemption from an Upshift vault. +The Upshift vault implements an [ERC-4626](https://eips.ethereum.org/EIPS/eip-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](/fxrp/upshift/claim). + +## Prerequisites + +- [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) +- [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) +- Understanding of [FAssets](/fassets/overview) +- 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: + + + {UpshiftRequestRedeem} + + +## 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()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-8.-previewredeem-uint256-shares) 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()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-2.-requestredeem-uint256-shares-address-receiveraddr-address-holderaddr) 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()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-2.-requestredeem-uint256-shares-address-receiveraddr-address-holderaddr) 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](/fxrp/upshift/claim) 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](/network/guides/hardhat-foundry-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: + +```bash +npx hardhat run scripts/upshift/requestRedeem.ts --network coston2 +``` + +## Output + +The script outputs the following information about the request redemption: + +```bash +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. + +:::tip[What's next] + +To continue your Upshift development journey, you can: + +- Learn how to [get Upshift vault status](/fxrp/upshift/status) to monitor your redemption requests. +- Learn how to [claim assets from an Upshift vault](/fxrp/upshift/claim) after your requested redemption is claimable. +- Learn how to [instantly redeem from an Upshift vault](/fxrp/upshift/instant-redeem) for immediate liquidity. +- Learn how to [deposit assets into an Upshift vault](/fxrp/upshift/deposit). +- Learn more about [FAssets](/fassets/overview) and how the system works. + +::: diff --git a/docs/fxrp/upshift/05-claim.mdx b/docs/fxrp/upshift/05-claim.mdx new file mode 100644 index 00000000..6afb6a28 --- /dev/null +++ b/docs/fxrp/upshift/05-claim.mdx @@ -0,0 +1,129 @@ +--- +title: Claim Redemption from Upshift Vault +tags: [intermediate, upshift, fassets] +slug: claim +description: Learn how to claim assets from a previously requested Upshift vault redemption +keywords: [fassets, flare-network, fxrp, upshift, vault, claim, redemption] +sidebar_position: 5 +--- + +import CodeBlock from "@theme/CodeBlock"; +import UpshiftClaim from "!!raw-loader!/examples/developer-hub-javascript/upshift-claim.ts"; + +This guide demonstrates how to claim assets from a previously requested redemption in an Upshift vault. +The Upshift vault implements an [ERC-4626](https://eips.ethereum.org/EIPS/eip-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](/fxrp/upshift/request-redeem). +Once the lag duration has passed, you call [`claim()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-3.-claim-uint256-year-uint256-month-uint256-day-address-receiveraddr) to receive your underlying assets (e.g., FXRP) in your wallet. + +## Prerequisites + +- [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) +- [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) +- Understanding of [FAssets](/fassets/overview) +- A pending redemption request from a previous [request redeem](/fxrp/upshift/request-redeem) call. + +## Upshift Vault Claim Script + +The following script demonstrates how to claim assets from the Upshift vault: + + + {UpshiftClaim} + + +## Script Breakdown + +The `main()` function executes the following steps: + +1. **Initialize:** Gets the user account and logs the vault, user, receiver addresses, and redemption date. +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. **Check pending redemption:** Verifies that there are shares to claim for the given date and receiver. +5. **Get LP token info:** Retrieves the LP token address, decimals, and symbol. +6. **Preview redemption:** Calls [`previewRedemption()`](https://docs.upshift.finance/developer-docs/vault-contract-interface#id-8.-previewredeem-uint256-shares) to see expected assets before and after the withdrawal fee. +7. **Check if claimable:** Ensures the current block timestamp is past the claimable date (plus a small buffer) before proceeding. +8. **Get balance before:** Records the receiver's asset balance before the claim. +9. **Execute claim:** Calls [`claim()`](https://docs.upshift.finance/developer-docs/vault-contract-interface?q=getBurnableAmountByReceiver#id-3.-claim-uint256-year-uint256-month-uint256-day-address-receiveraddr) with the redemption date and receiver to transfer assets. +10. **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: + +1. **Request:** You previously called [`requestRedeem()`](/fxrp/upshift/request-redeem) to lock LP tokens and create a withdrawal request for a specific epoch. +2. **Wait:** The lag duration must pass before assets can be claimed. +3. **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: + +1. Ensure you have the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit) set up. +2. Update the `VAULT_ADDRESS` constant with the correct vault address for your network. +3. Set `YEAR`, `MONTH`, and `DAY` to the claimable date from your [request redeem](/fxrp/upshift/request-redeem) output. +4. Optionally set `RECEIVER_ADDRESS` to claim to a different address (leave empty to use the signer). +5. Ensure the claimable date has passed (the script checks and exits with a message if not). +6. Run the script using Hardhat: + +```bash +npx hardhat run scripts/upshift/claim.ts --network coston2 +``` + +## Output + +The script outputs the following information about the claim: + +```bash +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. + +:::tip[What's next] + +To continue your Upshift development journey, you can: + +- Learn how to [get Upshift vault status](/fxrp/upshift/status) to monitor your redemption requests. +- Learn how to [request a redemption](/fxrp/upshift/request-redeem) from an Upshift vault. +- Learn how to [instantly redeem](/fxrp/upshift/instant-redeem) from an Upshift vault for immediate liquidity. +- Learn how to [deposit assets](/fxrp/upshift/deposit) into an Upshift vault. +- Learn more about [FAssets](/fassets/overview) and how the system works. + +::: diff --git a/docs/fxrp/upshift/_upshift.mdx b/docs/fxrp/upshift/_upshift.mdx new file mode 100644 index 00000000..913fb31a --- /dev/null +++ b/docs/fxrp/upshift/_upshift.mdx @@ -0,0 +1,53 @@ +import DocCardList from "@theme/DocCardList"; + +[Upshift](https://upshift.finance) is a yield-generating protocol that supports FXRP through strategy-driven vaults on Flare. +It provides [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) style vaults that allow users to deposit FXRP and earn yield generated from on-chain DeFi strategies, while abstracting away strategy execution and risk management. + +Upshift vaults support both instant and requested redemptions, depending on available liquidity. +Users receive vault shares that represent their proportional ownership of the vault's assets, with yield accruing as share value increases over time. + +[Upshift vault](https://app.upshift.finance/pools/14/0x373D7d201C8134D4a2f7b5c63560da217e3dEA28) is part of the XRP finance stack on Flare Network. +It inherits the transparency model of the [FAssets](/fassets/overview) system: minting and redemption are proof-backed, and vault interactions are recorded on-chain. + +- **Funds are on-chain:** User deposits, vault balances, withdrawal requests, and claims are executed as smart contract state changes on Flare. + You can verify all vault holdings on [DeBank](https://debank.com/profile/0xEDb7B1e92B8D3621b46843AD024949F10438374B) portfolio tracker. +- **User ownership is verifiable:** A user's position is represented directly on-chain and can be queried from smart contracts. +- **Instruction flow is auditable:** XRPL-triggered actions are executed on Flare via [Smart Accounts](/smart-accounts/overview) with [Flare Data Connector](/fdc/overview) backed verification and onchain execution. + You can verify all smart account activity on the [Flare Systems Explorer](https://flare-systems-explorer.flare.network/smart-accounts). + + + +:::tip[What's next] + +- Learn more about [FAssets](/fassets/overview) and how the system works. +- Explore how to [mint FXRP](/fassets/developer-guides/fassets-mint) from XRP. +- Discover how to [redeem FXRP](/fassets/developer-guides/fassets-redeem) back to XRP. + +::: diff --git a/docs/fxrp/upshift/index.mdx b/docs/fxrp/upshift/index.mdx new file mode 100644 index 00000000..87a297ba --- /dev/null +++ b/docs/fxrp/upshift/index.mdx @@ -0,0 +1,9 @@ +--- +title: Upshift Vaults +description: Learn how to interact with Upshift vaults, ERC-4626 vaults compatible with FXRP. +keywords: [fassets, flare-network, fxrp, upshift, vault, erc-4626] +--- + +import Upshift from "./_upshift.mdx"; + + diff --git a/docs/tags.yml b/docs/tags.yml index fbe3ef48..61875fbd 100644 --- a/docs/tags.yml +++ b/docs/tags.yml @@ -14,6 +14,8 @@ fassets: label: fassets firelight: label: firelight +upshift: + label: upshift python: label: python javascript: diff --git a/examples/developer-hub-javascript/upshift-claim.ts b/examples/developer-hub-javascript/upshift-claim.ts new file mode 100644 index 00000000..f3659926 --- /dev/null +++ b/examples/developer-hub-javascript/upshift-claim.ts @@ -0,0 +1,260 @@ +/** + * 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; +}); diff --git a/examples/developer-hub-javascript/upshift-deposit.ts b/examples/developer-hub-javascript/upshift-deposit.ts new file mode 100644 index 00000000..a9ffab2a --- /dev/null +++ b/examples/developer-hub-javascript/upshift-deposit.ts @@ -0,0 +1,216 @@ +/** + * Upshift Tokenized Vault Deposit Script + * + * This script deposits assets into 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 DEPOSIT_AMOUNT = "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("\nReference Asset (asset depositing):", referenceAsset); + + return { referenceAsset, refAsset, decimals, symbol }; +} + +async function checkBalance( + refAsset: IERC20Instance, + userAddress: string, + depositAmount: bigint, + decimals: number, + symbol: string, +) { + const balance = await refAsset.balanceOf(userAddress); + console.log( + `Balance: ${formatUnits(balance.toString(), decimals)} ${symbol}`, + ); + + if (BigInt(balance.toString()) < depositAmount) { + console.log("Insufficient balance!"); + return false; + } + return true; +} + +async function checkAndApproveAllowance( + refAsset: IERC20Instance, + userAddress: string, + depositAmount: bigint, + decimals: number, + symbol: string, +) { + const allowance = await refAsset.allowance(userAddress, VAULT_ADDRESS); + console.log( + `Current Allowance: ${formatUnits(allowance.toString(), decimals)} ${symbol}`, + ); + + if (BigInt(allowance.toString()) < depositAmount) { + console.log( + `\nApproving vault to spend ${DEPOSIT_AMOUNT} ${symbol} tokens`, + ); + const approveTx = await refAsset.approve( + VAULT_ADDRESS, + depositAmount.toString(), + ); + console.log("Approval Tx:", approveTx.tx); + } +} + +async function previewDeposit( + vault: ITokenizedVaultInstance, + referenceAsset: string, + depositAmount: bigint, + decimals: number, +) { + const preview = await vault.previewDeposit( + referenceAsset, + depositAmount.toString(), + ); + const expectedShares = preview[0]; + const amountInRefTokens = preview[1]; + + console.log( + `\nExpected Shares: ${formatUnits(expectedShares.toString(), decimals)}`, + ); + console.log( + `Amount in Reference Tokens: ${formatUnits(amountInRefTokens.toString(), decimals)}`, + ); + + return { expectedShares }; +} + +async function getLPTokenInfo( + vault: ITokenizedVaultInstance, + userAddress: string, + decimals: number, +) { + const lpTokenAddress = await vault.lpTokenAddress(); + const lpToken: IERC20Instance = await IERC20.at(lpTokenAddress); + const lpBalanceBefore = await lpToken.balanceOf(userAddress); + + console.log( + `LP Balance Before: ${formatUnits(lpBalanceBefore.toString(), decimals)}`, + ); + + return { lpToken, lpBalanceBefore }; +} + +async function executeDeposit( + vault: ITokenizedVaultInstance, + referenceAsset: string, + depositAmount: bigint, + userAddress: string, +) { + const depositTx = await vault.deposit( + referenceAsset, + depositAmount.toString(), + userAddress, + ); + console.log( + "\nDeposit: (tx:", + depositTx.tx, + ", block:", + depositTx.receipt.blockNumber, + ")", + ); +} + +async function verifyDeposit( + lpToken: IERC20Instance, + userAddress: string, + lpBalanceBefore: { toString(): string }, + decimals: number, +) { + console.log("\nVerifying deposit..."); + + const lpBalanceAfter = await lpToken.balanceOf(userAddress); + const sharesReceived = + BigInt(lpBalanceAfter.toString()) - BigInt(lpBalanceBefore.toString()); + + console.log( + `LP Balance After: ${formatUnits(lpBalanceAfter.toString(), decimals)}`, + ); + console.log( + `Shares Received: ${formatUnits(sharesReceived.toString(), decimals)}`, + ); +} + +async function main() { + // 1. Initialize: Get user account from Hardhat network + const accounts = await web3.eth.getAccounts(); + const userAddress = accounts[0]; + + console.log("DEPOSIT TO 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 { referenceAsset, refAsset, decimals, symbol } = + await getReferenceAssetInfo(vault); + + // 4. Convert deposit amount from human-readable to token units + const depositAmount = parseUnits(DEPOSIT_AMOUNT, decimals); + console.log( + `\nDeposit Amount: ${DEPOSIT_AMOUNT} ${symbol} (${depositAmount.toString()})`, + ); + + // 5. Check user balance + const hasBalance = await checkBalance( + refAsset, + userAddress, + depositAmount, + decimals, + symbol, + ); + if (!hasBalance) return; + + // 6. Check and approve allowance + await checkAndApproveAllowance( + refAsset, + userAddress, + depositAmount, + decimals, + symbol, + ); + + // 7. Preview deposit + await previewDeposit(vault, referenceAsset, depositAmount, decimals); + + // 8. Get LP token info before deposit + const { lpToken, lpBalanceBefore } = await getLPTokenInfo( + vault, + userAddress, + decimals, + ); + + // 9. Execute deposit + await executeDeposit(vault, referenceAsset, depositAmount, userAddress); + + // 10. Verify deposit + await verifyDeposit(lpToken, userAddress, lpBalanceBefore, decimals); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/developer-hub-javascript/upshift-instant-redeem.ts b/examples/developer-hub-javascript/upshift-instant-redeem.ts new file mode 100644 index 00000000..bd2e483f --- /dev/null +++ b/examples/developer-hub-javascript/upshift-instant-redeem.ts @@ -0,0 +1,207 @@ +/** + * 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; +}); diff --git a/examples/developer-hub-javascript/upshift-request-redeem.ts b/examples/developer-hub-javascript/upshift-request-redeem.ts new file mode 100644 index 00000000..6ef8508a --- /dev/null +++ b/examples/developer-hub-javascript/upshift-request-redeem.ts @@ -0,0 +1,249 @@ +/** + * 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; +}); diff --git a/examples/developer-hub-javascript/upshift-status.ts b/examples/developer-hub-javascript/upshift-status.ts new file mode 100644 index 00000000..bd90a7cc --- /dev/null +++ b/examples/developer-hub-javascript/upshift-status.ts @@ -0,0 +1,153 @@ +/** + * Upshift Tokenized Vault Status Script + * + * This script displays the current status of the vault and user balances. + * + */ + +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 ITokenizedVault = artifacts.require("ITokenizedVault"); +const IERC20 = artifacts.require("IERC20"); +const IFAsset = artifacts.require("IFAsset"); + +async function printReferenceAssetInfo(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("\nReference Asset"); + console.log(`Address: ${referenceAsset}`); + console.log(`Symbol: ${symbol}`); + console.log(`Decimals: ${decimals}`); + + return { referenceAsset, refAsset, decimals, symbol }; +} + +async function printLPTokenInfo(vault: ITokenizedVaultInstance) { + const lpTokenAddress = await vault.lpTokenAddress(); + const lpToken: IERC20Instance = await IERC20.at(lpTokenAddress); + + console.log("\nLP Token"); + console.log(`Address: ${lpTokenAddress}`); + + return { lpTokenAddress, lpToken }; +} + +async function printVaultConfiguration( + vault: ITokenizedVaultInstance, + decimals: number, + symbol: string, +) { + console.log("\nVault Configuration"); + + const withdrawalsPaused = await vault.withdrawalsPaused(); + const lagDuration = await vault.lagDuration(); + const withdrawalFee = await vault.withdrawalFee(); + const instantRedemptionFee = await vault.instantRedemptionFee(); + const maxWithdrawalAmount = await vault.maxWithdrawalAmount(); + + console.log(`Withdrawals Paused: ${withdrawalsPaused}`); + console.log(`Lag Duration: ${lagDuration.toString()} seconds`); + console.log(`Withdrawal Fee: ${formatUnits(withdrawalFee.toString(), 16)}%`); + console.log( + `Instant Redemption Fee: ${formatUnits(instantRedemptionFee.toString(), 16)}%`, + ); + console.log( + `Max Withdrawal Amount: ${formatUnits(maxWithdrawalAmount.toString(), decimals)} ${symbol}`, + ); +} + +async function printWithdrawalEpoch(vault: ITokenizedVaultInstance) { + console.log("\nWithdrawal Epoch"); + + const epochInfo = await vault.getWithdrawalEpoch(); + console.log( + `Year: ${epochInfo[0].toString()}, Month: ${epochInfo[1].toString()}, Day: ${epochInfo[2].toString()}`, + ); + console.log(`Claimable Epoch: ${epochInfo[3].toString()}`); +} + +async function printUserBalances( + userAddress: string, + refAsset: IERC20Instance, + lpToken: IERC20Instance, + decimals: number, + symbol: string, +) { + console.log("\nUser Balances"); + + const refBalance = await refAsset.balanceOf(userAddress); + const lpBalance = await lpToken.balanceOf(userAddress); + + console.log( + `${symbol} Balance: ${formatUnits(refBalance.toString(), decimals)}`, + ); + console.log( + `LP Token Balance: ${formatUnits(lpBalance.toString(), decimals)}`, + ); +} + +async function printAllowances( + userAddress: string, + refAsset: IERC20Instance, + lpToken: IERC20Instance, + decimals: number, + symbol: string, +) { + console.log("\nAllowances"); + + const refAllowance = await refAsset.allowance(userAddress, VAULT_ADDRESS); + const lpAllowance = await lpToken.allowance(userAddress, VAULT_ADDRESS); + + console.log( + `${symbol} Allowance to Vault: ${formatUnits(refAllowance.toString(), decimals)}`, + ); + console.log( + `LP Token Allowance to Vault: ${formatUnits(lpAllowance.toString(), decimals)}`, + ); +} + +async function main() { + // 1. Initialize: Get user account from Hardhat network + const accounts = await web3.eth.getAccounts(); + const userAddress = accounts[0]; + + console.log("VAULT STATUS\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 and print reference asset info + const { refAsset, decimals, symbol } = await printReferenceAssetInfo(vault); + + // 4. Get and print LP token info + const { lpToken } = await printLPTokenInfo(vault); + + // 5. Print vault configuration + await printVaultConfiguration(vault, decimals, symbol); + + // 6. Print withdrawal epoch info + await printWithdrawalEpoch(vault); + + // 7. Print user balances + await printUserBalances(userAddress, refAsset, lpToken, decimals, symbol); + + // 8. Print allowances + await printAllowances(userAddress, refAsset, lpToken, decimals, symbol); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/lychee.toml b/lychee.toml index 6142037f..dd4cacae 100644 --- a/lychee.toml +++ b/lychee.toml @@ -25,4 +25,5 @@ exclude = [ '^https://docs\.eigencloud\.xyz/', '^https://coston2\.testnet\.flarescan\.com/', '^https://jq-verifier-test\.flare\.rocks', + '^https://jq-verifier-test.flare.rocks', ] diff --git a/sidebars.ts b/sidebars.ts index 475d921a..9d07972a 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -310,6 +310,21 @@ const sidebars: SidebarsConfig = { "fxrp/firelight/claim", ], }, + { + type: "category", + label: "Upshift Vaults", + link: { + type: "doc", + id: "fxrp/upshift/index", + }, + items: [ + "fxrp/upshift/status", + "fxrp/upshift/deposit", + "fxrp/upshift/instant-redeem", + "fxrp/upshift/request-redeem", + "fxrp/upshift/claim", + ], + }, ], }, {