diff --git a/docs/fxrp/oft/fassets-autoredeem.mdx b/docs/fxrp/oft/fassets-autoredeem.mdx
index cec4c9c8..ecd68395 100644
--- a/docs/fxrp/oft/fassets-autoredeem.mdx
+++ b/docs/fxrp/oft/fassets-autoredeem.mdx
@@ -33,7 +33,7 @@ This guide covers four key functionalities:
- [LayerZero Composer](https://docs.layerzero.network/v2/developers/evm/oft/composing) pattern for executing custom logic (like FAsset redemption) automatically when tokens arrive on the destination chain.
- [Hyperliquid spotSend API](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#spot-transfer) for transferring tokens between HyperCore and HyperEVM.
-This guide includes a Solidity smart contract (`FAssetRedeemComposer.sol`) and four TypeScript scripts (`bridgeToHyperEVM.ts`, `bridgeToHyperCore.ts`, `autoRedeemFromHyperEVM.ts`, and `autoRedeemFromHyperCore.ts`) that demonstrate the complete workflows.
+A shared [`FAssetRedeemComposer`](https://coston2-explorer.flare.network/address/0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52?tab=contract) contract is pre-deployed on Coston2, so users do not need to deploy their own. This guide includes four TypeScript scripts (`bridgeToHyperEVM.ts`, `bridgeToHyperCore.ts`, `autoRedeemFromHyperEVM.ts`, and `autoRedeemFromHyperCore.ts`) that demonstrate the complete workflows.
Clone the [Flare Hardhat Starter](https://github.com/flare-foundation/flare-hardhat-starter) to follow along.
@@ -77,39 +77,65 @@ yarn hardhat run scripts/fassets/autoRedeemFromHyperEVM.ts --network hyperliquid
### What It Is
-`FAssetRedeemComposer.sol` is a LayerZero Composer contract that automatically redeems FAssets to their underlying assets when tokens arrive on Flare Testnet Coston2 via LayerZero's compose message feature.
+The [`FAssetRedeemComposer`](https://coston2-explorer.flare.network/address/0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52?tab=contract) is a shared LayerZero Composer contract **pre-deployed on Coston2** that automatically redeems FAssets to their underlying assets when tokens arrive via LayerZero's compose message feature.
### How It Works
-The composer implements the [IOAppComposer](https://docs.layerzero.network/v2/developers/evm/composer/overview) interface and processes LayerZero compose messages:
+The composer implements the [ILayerZeroComposer](https://docs.layerzero.network/v2/developers/evm/composer/overview) interface and creates per-user `FAssetRedeemerAccount` contracts automatically:
1. **Receives Compose Message**: LayerZero endpoint calls [lzCompose()](https://docs.layerzero.network/v2/developers/evm/composer/overview#:~:text=/**%0A%20%20%20%20%20*%20%40notice%20Handles,messages%0A%20%20%20%20%7D%0A%7D) with the incoming OFT transfer and compose data.
-2. **Extracts Parameters**: Decodes the compose message to get:
- - Amount to redeem (in lots).
- - Underlying XRP address on the XRP Ledger.
- - Redeemer EVM address on Flare Chain.
-3. **Gets Asset Manager**: Retrieves the FAsset AssetManager from Flare's ContractRegistry.
-4. **Calculates Lots**: Determines how many lots can be redeemed based on received balance.
-5. **Approves Tokens**: Grants AssetManager permission to spend the FAsset tokens (which is in the [ERC-20](https://eips.ethereum.org/EIPS/eip-20) standard).
-6. **Executes Redemption**: Calls [`assetManager.redeem()`](/fassets/reference/IAssetManager#redeem) to burn FAssets and release underlying XRP.
-
-
- {FassetRedeemComposer}
-
+2. **Decodes `RedeemComposeData`**: Extracts the redeemer's EVM address and their underlying XRP address from the compose message.
+3. **Creates Redeemer Account**: Deploys (or reuses) a deterministic per-user `FAssetRedeemerAccount` contract via CREATE2.
+4. **Deducts Composer Fee**: Takes a percentage fee (configurable per source chain, currently 1%) from the received FXRP.
+5. **Transfers Tokens**: Sends the remaining FXRP to the redeemer account.
+6. **Executes Redemption**: The redeemer account calls [`assetManager.redeem()`](/fassets/reference/IAssetManager#redeem) to burn FAssets and release underlying XRP.
+
+### Compose Message Format
+
+The auto-redeem scripts encode the compose message using the `RedeemComposeData` struct:
+
+```solidity
+struct RedeemComposeData {
+ /// @notice EVM address that owns the per-redeemer account.
+ address redeemer;
+ /// @notice Underlying-chain redemption destination passed to the asset manager.
+ string redeemerUnderlyingAddress;
+}
+```
+
+In TypeScript, this is encoded as:
+
+```typescript
+const composeMsg = web3.eth.abi.encodeParameters(
+ ["address", "string"],
+ [redeemer, xrpAddress],
+);
+```
+
+### Executor Fee
-### Code Breakdown
+The `lzCompose` call must include native value to cover the executor fee on Coston2. This is set via `addExecutorLzComposeOption`:
-The `_processRedemption` function contains the core redemption logic with numbered steps in the comments:
+```typescript
+const options = Options.newOptions()
+ .addExecutorLzReceiveOption(EXECUTOR_GAS, 0)
+ .addExecutorComposeOption(0, COMPOSE_GAS, COMPOSE_VALUE);
+```
-1. **Decode Message**: Extracts the underlying XRP address and redeemer EVM address from the compose message.
-2. **Get Asset Manager & fXRP Token**: Retrieves the FAsset AssetManager.
-3. **Check Balance**: Verifies that tokens were received from LayerZero. Reverts with `InsufficientBalance()` if balance is zero.
-4. **Calculate Lots**: Determines how many complete lots can be redeemed based on the received balance and the lot size from AssetManager settings.
-5. **Calculate Amount**: Computes the exact amount to redeem (must be a multiple of lot size). Reverts with `AmountTooSmall()` if less than 1 lot.
-6. **Approve AssetManager**: Grants the AssetManager permission to spend the FAsset tokens using the ERC-20 `approve` pattern.
-7. **Redeem**: Calls [`assetManager.redeem()`](/fassets/reference/IAssetManager#redeem) to burn FAssets and trigger the release of underlying XRP to the specified address.
+The current executor fee can be queried via `getExecutorData()` on the [composer contract](https://coston2-explorer.flare.network/address/0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52?tab=contract).
-The contract also includes `recoverTokens()` and `recoverNative()` helper functions that allow the owner to recover any stuck tokens or native currency if needed.
+### Contract Source
+
+
+View `FAssetRedeemComposer.sol`
+
+
+ {FassetRedeemComposer}
+
+
+The full verified source is available on the [Coston2 explorer](https://coston2-explorer.flare.network/address/0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52?tab=contract).
+
+
## Bridge FXRP to Hyperliquid EVM (Step 1)
@@ -190,7 +216,7 @@ This gets your FXRP tokens onto Hyperliquid EVM.
# .env file
COSTON2_RPC_URL=https://coston2-api.flare.network/ext/C/rpc
DEPLOYER_PRIVATE_KEY=your_private_key_here
- COSTON2_COMPOSER=0x... # Optional for this step, required for Step 2
+ COSTON2_COMPOSER=0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52
```
3. **Run the Script on Flare Testnet Coston2**:
@@ -287,51 +313,21 @@ Choose based on whether you want to use FXRP in smart contracts (HyperEVM) or tr
#### Flow Diagram
-```
-┌─────────────────────────┐
-│ Developer │
-│ (Coston2) │
-└────────┬────────────────┘
- │
- │ 1. Has FXRP tokens
- ▼
-┌─────────────────────────┐
-│ FXRP (Coston2) │
-│ Balance: 11 FXRP │
-└────────┬────────────────┘
- │
- │ 2. Send via OFT Adapter
- │ with Compose Message
- ▼
-┌─────────────────────────┐
-│ LayerZero Endpoint │
-│ - lzSend() │
-│ - Compose enabled │
-└────────┬────────────────┘
- │
- │ 3. Cross-chain message
- ▼
-┌─────────────────────────┐
-│ HyperEVM Endpoint │
-│ - lzReceive() │
-│ - lzCompose() │
-└────────┬────────────────┘
- │
- │ 4. Calls Composer
- ▼
-┌─────────────────────────┐
-│ HyperliquidComposer │
-│ - Receives FXRP │
-│ - Transfers to system │
-│ address │
-└────────┬────────────────┘
- │
- │ 5. HyperCore credit
- ▼
-┌─────────────────────────┐
-│ HyperCore Spot Wallet │
-│ FXRP credited │
-└─────────────────────────┘
+```mermaid
+sequenceDiagram
+ participant Dev as Developer (Coston2)
+ participant OFT as OFT Adapter (Coston2)
+ participant LZ as LayerZero
+ participant EP as HyperEVM Endpoint
+ participant Comp as HyperliquidComposer
(shared, pre-deployed)
+ participant HC as HyperCore Spot Wallet
+
+ Dev->>OFT: 1. Send FXRP via OFT Adapter
with Compose Message
+ OFT->>LZ: 2. lzSend()
+ LZ->>EP: 3. Cross-chain message
+ EP->>Comp: 4. lzReceive() + lzCompose()
+ Comp->>HC: 5. Transfer to system address
+ Note over HC: FXRP credited to
spot wallet
```
#### Step-by-Step Process
@@ -360,7 +356,7 @@ Choose based on whether you want to use FXRP in smart contracts (HyperEVM) or tr
You can get some from the Flare Testnet [faucet](https://faucet.flare.network/).
- **Deployed Contracts**:
- HyperliquidComposer must be deployed on HyperEVM Testnet.
- - Composer address must be set in `.env`.
+ - Set `COSTON2_COMPOSER=0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52` in `.env`.
### Configuration
@@ -492,66 +488,30 @@ It uses LayerZero's compose feature to trigger the `FAssetRedeemComposer` contra
- You must have FXRP OFT tokens on Hyperliquid EVM Testnet.
- If you don't have FXRP on Hyperliquid, run `bridgeToHyperEVM.ts` first ([Step 1](#step-1-bridge-fxrp-to-hyperliquid-evm-required-first)).
-- The FAssetRedeemComposer contract must be deployed on Flare Testnet Coston2.
+- The shared FAssetRedeemComposer is pre-deployed on Coston2 at `0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52`.
### How It Works
#### Flow Diagram
-```
-┌──────────────────┐
-│ Developer │
-│ (Hyperliquid EVM)│
-└────────┬─────────┘
- │
- │ 1. Has FXRP OFT
- ▼
-┌─────────────────────────┐
-│ FXRP OFT (Hyperliquid) │
-│ Balance: 10 FXRP │
-└────────┬────────────────┘
- │
- │ 2. Send with Compose Message
- │ - Destination: Coston2 Composer
- │ - Compose Data: (amount, xrpAddress, redeemer)
- ▼
-┌─────────────────────────┐
-│ LayerZero Endpoint │
-│ - lzSend() │
-│ - Compose enabled │
-└────────┬────────────────┘
- │
- │ 3. Cross-chain message
- ▼
-┌─────────────────────────┐
-│ Coston2 Endpoint │
-│ - lzReceive() │
-│ - lzCompose() │
-└────────┬────────────────┘
- │
- │ 4. Calls Composer
- ▼
-┌─────────────────────────┐
-│ FAssetRedeemComposer │
-│ - Receives FXRP │
-│ - Calculates lots │
-│ - Calls AssetManager │
-└────────┬────────────────┘
- │
- │ 5. Redemption
- ▼
-┌─────────────────────────┐
-│ FAsset AssetManager │
-│ - Burns FXRP │
-│ - Releases XRP │
-└────────┬────────────────┘
- │
- │ 6. XRP sent to address
- ▼
-┌─────────────────────────┐
-│ XRP Ledger Address │
-│ rpHuw4b... │
-└─────────────────────────┘
+```mermaid
+sequenceDiagram
+ participant Dev as Developer (HyperEVM)
+ participant OFT as FXRP OFT (HyperEVM)
+ participant LZ as LayerZero
+ participant EP as Coston2 Endpoint
+ participant Comp as FAssetRedeemComposer
(shared, pre-deployed)
+ participant Acct as FAssetRedeemerAccount
(per-user, CREATE2)
+ participant XRP as XRP Ledger Address
+
+ Dev->>OFT: 1. send() with Compose Message
Data: (redeemer, xrpAddress)
+ OFT->>LZ: 2. lzSend()
+ LZ->>EP: 3. Cross-chain message
+ EP->>Comp: 4. lzReceive() + lzCompose()
+ Comp->>Comp: 5. Deduct composer fee
+ Comp->>Acct: 6. Transfer FXRP to redeemer account
+ Acct->>Acct: 7. assetManager.redeem()
Burns FXRP
+ Acct-->>XRP: 8. XRP sent to underlying address
```
#### Step-by-Step Process
@@ -561,11 +521,11 @@ It uses LayerZero's compose feature to trigger the `FAssetRedeemComposer` contra
- Gets the signer account.
2. **Connect to FXRP OFT**: Gets the OFT contract on Hyperliquid using the `FXRPOFT` artifact.
3. **Prepare Redemption Parameters**:
- - Number of lots to send (default: 1 lot, amount calculated using `calculateAmountToSend` utility).
+ - Number of lots to send (default: 1 lot).
- XRP address to receive native XRP.
- Redeemer address (EVM address).
4. **Encode Compose Message**:
- - Encodes `(uint256 amount, string xrpAddress, address redeemer)`.
+ - Encodes `(address redeemer, string xrpAddress)` matching the `RedeemComposeData` struct.
- This tells the composer what to do when tokens arrive.
5. **Build LayerZero Options**:
- Executor gas for `lzReceive()`: 1,000,000
@@ -587,8 +547,8 @@ It uses LayerZero's compose feature to trigger the `FAssetRedeemComposer` contra
- FXRP OFT tokens on Hyperliquid (amount you want to redeem).
- HYPE tokens (for gas fees + LayerZero fees).
- **Deployed Contracts**:
- - FAssetRedeemComposer must be deployed on Flare Testnet Coston2.
- - Composer address must be set in `.env`.
+ - The shared FAssetRedeemComposer is pre-deployed on Coston2 at `0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52`.
+ - Set `COSTON2_COMPOSER=0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52` in `.env`.
### Configuration
@@ -599,12 +559,15 @@ const CONFIG = {
HYPERLIQUID_FXRP_OFT:
process.env.HYPERLIQUID_FXRP_OFT ||
"0x14bfb521e318fc3d5e92A8462C65079BC7d4284c",
+ // Shared FAssetRedeemComposer (pre-deployed on Coston2)
COSTON2_COMPOSER:
process.env.COSTON2_COMPOSER ||
- "0x5051E8db650E9e0E2a3f03010Ee5c60e79CF583E",
+ "0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52",
COSTON2_EID: EndpointId.FLARE_V2_TESTNET,
EXECUTOR_GAS: 1_000_000, // Gas for receiving on Coston2
COMPOSE_GAS: 1_000_000, // Gas for compose execution
+ // Native value forwarded to cover executor fee on Coston2
+ COMPOSE_VALUE: BigInt("1000000000000000"), // 0.001 ETH
SEND_LOTS: "1", // Number of lots to redeem
XRP_ADDRESS: "rpHuw4bKSjonKRrKKVYYVedg1jyPrmp", // Your XRP address
};
@@ -622,21 +585,17 @@ If you don't have FXRP on Hyperliquid yet:
Once you have FXRP on Hyperliquid:
-1. **Deploy FAssetRedeemComposer** (first time only):
-
- ```bash
- npx hardhat deploy --network coston2 --tags FAssetRedeemComposer
- ```
+````
2. **Configure Environment**:
- ```bash
- # .env file
- HYPERLIQUID_TESTNET_RPC_URL=https://api.hyperliquid-testnet.xyz/evm
- DEPLOYER_PRIVATE_KEY=your_private_key_here
- COSTON2_COMPOSER=0x... # Required! Set this after deploying the composer
- HYPERLIQUID_FXRP_OFT=0x14bfb521e318fc3d5e92A8462C65079BC7d4284c
- ```
+```bash
+# .env file
+HYPERLIQUID_TESTNET_RPC_URL=https://api.hyperliquid-testnet.xyz/evm
+DEPLOYER_PRIVATE_KEY=your_private_key_here
+COSTON2_COMPOSER=0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52
+HYPERLIQUID_FXRP_OFT=0x14bfb521e318fc3d5e92A8462C65079BC7d4284c
+````
3. **Update XRP Address**:
- Edit `CONFIG.XRP_ADDRESS` in the script to your XRP ledger address
@@ -719,82 +678,33 @@ Tokens must be transferred from HyperCore to HyperEVM before they can be bridged
- You must have FXRP tokens in your HyperCore spot wallet.
- You need HYPE tokens on HyperEVM for LayerZero fees.
-- The FAssetRedeemComposer contract must be deployed on Flare Testnet Coston2.
+- The shared FAssetRedeemComposer is pre-deployed on Coston2 at `0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52`.
### How It Works
#### Flow Diagram
-```
-┌─────────────────────────┐
-│ Developer │
-│ (HyperCore Spot) │
-└────────┬────────────────┘
- │
- │ 1. Has FXRP in spot wallet
- ▼
-┌─────────────────────────┐
-│ HyperCore Spot Wallet │
-│ Balance: 10 FXRP │
-└────────┬────────────────┘
- │
- │ 2. spotSend to system address
- │ (EIP-712 signed)
- ▼
-┌─────────────────────────┐
-│ Hyperliquid API │
-│ - /exchange endpoint │
-│ - spotSend action │
-└────────┬────────────────┘
- │
- │ 3. Tokens appear on HyperEVM
- ▼
-┌─────────────────────────┐
-│ FXRP OFT (HyperEVM) │
-│ Balance: 10 FXRP │
-└────────┬────────────────┘
- │
- │ 4. Send with Compose Message
- │ - Destination: Coston2 Composer
- │ - Compose Data: (amount, xrpAddress, redeemer)
- ▼
-┌─────────────────────────┐
-│ LayerZero Endpoint │
-│ - lzSend() │
-│ - Compose enabled │
-└────────┬────────────────┘
- │
- │ 5. Cross-chain message
- ▼
-┌─────────────────────────┐
-│ Coston2 Endpoint │
-│ - lzReceive() │
-│ - lzCompose() │
-└────────┬────────────────┘
- │
- │ 6. Calls Composer
- ▼
-┌─────────────────────────┐
-│ FAssetRedeemComposer │
-│ - Receives FXRP │
-│ - Calculates lots │
-│ - Calls AssetManager │
-└────────┬────────────────┘
- │
- │ 7. Redemption
- ▼
-┌─────────────────────────┐
-│ FAsset AssetManager │
-│ - Burns FXRP │
-│ - Releases XRP │
-└────────┬────────────────┘
- │
- │ 8. XRP sent to address
- ▼
-┌─────────────────────────┐
-│ XRP Ledger Address │
-│ rpHuw4b... │
-└─────────────────────────┘
+```mermaid
+sequenceDiagram
+ participant Dev as Developer (HyperCore)
+ participant API as Hyperliquid API
+ participant OFT as FXRP OFT (HyperEVM)
+ participant LZ as LayerZero
+ participant EP as Coston2 Endpoint
+ participant Comp as FAssetRedeemComposer
(shared, pre-deployed)
+ participant Acct as FAssetRedeemerAccount
(per-user, CREATE2)
+ participant XRP as XRP Ledger Address
+
+ Dev->>API: 1. spotSend to system address
(EIP-712 signed)
+ API->>OFT: 2. Tokens appear on HyperEVM
+ Dev->>OFT: 3. send() with Compose Message
Data: (redeemer, xrpAddress)
+ OFT->>LZ: 4. lzSend()
+ LZ->>EP: 5. Cross-chain message
+ EP->>Comp: 6. lzReceive() + lzCompose()
+ Comp->>Comp: 7. Deduct composer fee
+ Comp->>Acct: 8. Transfer FXRP to redeemer account
+ Acct->>Acct: 9. assetManager.redeem()
Burns FXRP
+ Acct-->>XRP: 10. XRP sent to underlying address
```
#### Step-by-Step Process
@@ -816,7 +726,7 @@ Tokens must be transferred from HyperCore to HyperEVM before they can be bridged
- Redeemer address (EVM address).
6. **Check HyperEVM Balance**: Verifies tokens arrived from HyperCore.
7. **Encode Compose Message**:
- - Encodes `(uint256 amount, string xrpAddress, address redeemer)`.
+ - Encodes `(address redeemer, string xrpAddress)` matching the `RedeemComposeData` struct.
- This tells the composer what to do when tokens arrive.
8. **Build LayerZero Options**:
- Executor gas for `lzReceive()`: 1,000,000
@@ -865,8 +775,8 @@ FXRP_SYSTEM_ADDRESS: "0x20000000000000000000000000000000000005a3";
- FXRP tokens in your HyperCore spot wallet (amount you want to redeem).
- HYPE tokens on HyperEVM (for gas fees + LayerZero fees).
- **Deployed Contracts**:
- - FAssetRedeemComposer must be deployed on Flare Testnet Coston2.
- - Composer address must be set in `.env`.
+ - The shared FAssetRedeemComposer is pre-deployed on Coston2 at `0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52`.
+ - Set `COSTON2_COMPOSER=0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52` in `.env`.
### Configuration
@@ -883,12 +793,15 @@ const CONFIG = {
// System address for FXRP on testnet (token index 1443 = 0x5A3)
FXRP_SYSTEM_ADDRESS: "0x20000000000000000000000000000000000005a3",
FXRP_TOKEN_ID: "FXRP:0x2af78df5b575b45eea8a6a1175026dd6",
+ // Shared FAssetRedeemComposer (pre-deployed on Coston2)
COSTON2_COMPOSER:
process.env.COSTON2_COMPOSER ||
- "0x5051E8db650E9e0E2a3f03010Ee5c60e79CF583E",
+ "0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52",
COSTON2_EID: EndpointId.FLARE_V2_TESTNET,
EXECUTOR_GAS: 1_000_000, // Gas for receiving on Coston2
COMPOSE_GAS: 1_000_000, // Gas for compose execution
+ // Native value forwarded to cover executor fee on Coston2
+ COMPOSE_VALUE: BigInt("1000000000000000"), // 0.001 HYPE
SEND_LOTS: "1", // Number of lots to redeem
XRP_ADDRESS: process.env.XRP_ADDRESS || "rpHuw4bKSjonKRrKKVYYVedg1jyPrmp",
HYPERLIQUID_CHAIN: "Testnet",
@@ -899,22 +812,18 @@ const CONFIG = {
**PREREQUISITE: You must have FXRP tokens in your HyperCore spot wallet before running this script.**
-1. **Deploy FAssetRedeemComposer** (first time only):
-
- ```bash
- npx hardhat deploy --network coston2 --tags FAssetRedeemComposer
- ```
+````
2. **Configure Environment**:
- ```bash
- # .env file
- HYPERLIQUID_TESTNET_RPC_URL=https://api.hyperliquid-testnet.xyz/evm
- DEPLOYER_PRIVATE_KEY=your_private_key_here
- COSTON2_COMPOSER=0x... # Required! Set this after deploying the composer
- HYPERLIQUID_FXRP_OFT=0x14bfb521e318fc3d5e92A8462C65079BC7d4284c
- XRP_ADDRESS=rYourXRPAddressHere # Your XRP ledger address
- ```
+```bash
+# .env file
+HYPERLIQUID_TESTNET_RPC_URL=https://api.hyperliquid-testnet.xyz/evm
+DEPLOYER_PRIVATE_KEY=your_private_key_here
+COSTON2_COMPOSER=0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52
+HYPERLIQUID_FXRP_OFT=0x14bfb521e318fc3d5e92A8462C65079BC7d4284c
+XRP_ADDRESS=rYourXRPAddressHere # Your XRP ledger address
+````
3. **Ensure you have FXRP on HyperCore**:
- Bridge FXRP to HyperCore using `bridgeToHyperEVM.ts` followed by a transfer to HyperCore.
@@ -934,7 +843,7 @@ FXRP Auto-Redemption from HyperCore
HyperCore → HyperEVM → Coston2 → XRP Ledger
============================================================
Using account: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
-✓ Coston2 Composer configured: 0x5051E8db650E9e0E2a3f03010Ee5c60e79CF583E
+✓ Coston2 Composer configured: 0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52
📊 Checking HyperCore spot balance...
HyperCore FXRP balance: 25.0
@@ -964,7 +873,7 @@ Compose message encoded for auto-redemption
📤 Step 2: Sending FXRP from HyperEVM to Coston2 with auto-redeem...
Amount: 10.0 FXRP
- Target composer: 0x5051E8db650E9e0E2a3f03010Ee5c60e79CF583E
+ Target composer: 0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52
XRP destination: rpHuw4bKSjonKRrKKVYYVedg1jyPrmp
✓ Transaction sent: 0xabc123...
diff --git a/examples/developer-hub-javascript/autoRedeemFromHyperCore.ts b/examples/developer-hub-javascript/autoRedeemFromHyperCore.ts
index de92b817..941036ae 100644
--- a/examples/developer-hub-javascript/autoRedeemFromHyperCore.ts
+++ b/examples/developer-hub-javascript/autoRedeemFromHyperCore.ts
@@ -37,10 +37,12 @@ const CONFIG = {
FXRP_TOKEN_ID: "FXRP:0x2af78df5b575b45eea8a6a1175026dd6",
COSTON2_COMPOSER:
process.env.COSTON2_COMPOSER ||
- "0x5051E8db650E9e0E2a3f03010Ee5c60e79CF583E",
+ "0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52",
COSTON2_EID: EndpointId.FLARE_V2_TESTNET,
EXECUTOR_GAS: 1_000_000,
COMPOSE_GAS: 1_000_000,
+ // Native value forwarded to FAssetRedeemerAccount to cover executor fee on Coston2
+ COMPOSE_VALUE: BigInt("1000000000000000"), // 0.001 HYPE
SEND_LOTS: "1",
XRP_ADDRESS: process.env.XRP_ADDRESS || "rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp",
HYPERLIQUID_CHAIN: "Testnet",
@@ -251,9 +253,10 @@ async function connectToOFT(): Promise {
* Encodes the compose message for auto-redemption
*/
function encodeComposeMessage(params: RedemptionParams): string {
+ // Matches the RedeemComposeData struct on the shared FAssetRedeemComposer
const composeMsg = web3.eth.abi.encodeParameters(
- ["uint256", "string", "address"],
- [params.amountToSend.toString(), params.underlyingAddress, params.redeemer],
+ ["address", "string"],
+ [params.redeemer, params.underlyingAddress],
);
console.log("Compose message encoded for auto-redemption");
@@ -267,7 +270,11 @@ function encodeComposeMessage(params: RedemptionParams): string {
function buildComposeOptions(): string {
const options = Options.newOptions()
.addExecutorLzReceiveOption(CONFIG.EXECUTOR_GAS, 0)
- .addExecutorComposeOption(0, CONFIG.COMPOSE_GAS, 0);
+ .addExecutorComposeOption(
+ 0,
+ CONFIG.COMPOSE_GAS,
+ CONFIG.COMPOSE_VALUE.toString(),
+ );
return options.toHex();
}
diff --git a/examples/developer-hub-javascript/autoRedeemFromHyperEVM.ts b/examples/developer-hub-javascript/autoRedeemFromHyperEVM.ts
index 062d30d0..5e7d73fe 100644
--- a/examples/developer-hub-javascript/autoRedeemFromHyperEVM.ts
+++ b/examples/developer-hub-javascript/autoRedeemFromHyperEVM.ts
@@ -26,10 +26,12 @@ const CONFIG = {
"0x14bfb521e318fc3d5e92A8462C65079BC7d4284c",
COSTON2_COMPOSER:
process.env.COSTON2_COMPOSER ||
- "0x5051E8db650E9e0E2a3f03010Ee5c60e79CF583E",
+ "0x80c5ebBD65b4857CA2f917EAB1e9F83bcC947c52",
COSTON2_EID: EndpointId.FLARE_V2_TESTNET,
EXECUTOR_GAS: 1_000_000,
COMPOSE_GAS: 1_000_000,
+ // Native value forwarded to FAssetRedeemerAccount to cover executor fee on Coston2
+ COMPOSE_VALUE: BigInt("1000000000000000"), // 0.001 ETH
SEND_LOTS: "1",
XRP_ADDRESS: "rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp",
} as const;
@@ -117,13 +119,13 @@ async function connectToOFT(): Promise {
/**
* Encodes the compose message with redemption details
- * Format: (amountToRedeem, underlyingAddress, redeemer)
+ * Format: (address redeemer, string redeemerUnderlyingAddress)
+ * Matches the RedeemComposeData struct on the shared FAssetRedeemComposer
*/
function encodeComposeMessage(params: RedemptionParams): string {
- // redeem(uint256 _lots, string memory _redeemerUnderlyingAddressString, executor address)
const composeMsg = web3.eth.abi.encodeParameters(
- ["uint256", "string", "address"],
- [params.amountToSend.toString(), params.underlyingAddress, params.redeemer],
+ ["address", "string"],
+ [params.redeemer, params.underlyingAddress],
);
console.log("Compose message encoded");
@@ -137,7 +139,11 @@ function encodeComposeMessage(params: RedemptionParams): string {
function buildComposeOptions(): string {
const options = Options.newOptions()
.addExecutorLzReceiveOption(CONFIG.EXECUTOR_GAS, 0)
- .addExecutorComposeOption(0, CONFIG.COMPOSE_GAS, 0);
+ .addExecutorComposeOption(
+ 0,
+ CONFIG.COMPOSE_GAS,
+ CONFIG.COMPOSE_VALUE.toString(),
+ );
return options.toHex();
}
diff --git a/examples/developer-hub-solidity/FAssetRedeemComposer.sol b/examples/developer-hub-solidity/FAssetRedeemComposer.sol
index c7e7e4c9..98168546 100644
--- a/examples/developer-hub-solidity/FAssetRedeemComposer.sol
+++ b/examples/developer-hub-solidity/FAssetRedeemComposer.sol
@@ -1,101 +1,441 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.25;
+pragma solidity ^0.8.27;
-import {IOAppComposer} from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol";
-import {OFTComposeMsgCodec} from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol";
+import {IAssetManager} from "flare-periphery/src/flare/IAssetManager.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
-import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
-import {IAssetManager} from "@flarenetwork/flare-periphery-contracts/coston2/IAssetManager.sol";
-import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/coston2/ContractRegistry.sol";
+import {ILayerZeroComposer} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol";
+import {OFTComposeMsgCodec} from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol";
+import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
+import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
+import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
+import {IIFAssetRedeemerAccount} from "../interface/IIFAssetRedeemerAccount.sol";
+import {FAssetRedeemerAccountProxy} from "../proxy/FAssetRedeemerAccountProxy.sol";
+import {IFAssetRedeemComposer} from "../../userInterfaces/IFAssetRedeemComposer.sol";
+import {IBeacon} from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
+import {OwnableWithTimelock} from "../../utils/implementation/OwnableWithTimelock.sol";
+import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
-contract FAssetRedeemComposer is IOAppComposer, Ownable, ReentrancyGuard {
+/**
+ * @title FAssetRedeemComposer
+ * @notice LayerZero compose handler that orchestrates deterministic redeemer accounts and f-asset redemption.
+ */
+contract FAssetRedeemComposer is
+ IFAssetRedeemComposer,
+ OwnableWithTimelock,
+ UUPSUpgradeable,
+ ReentrancyGuardTransient
+{
using SafeERC20 for IERC20;
- address public immutable endpoint;
+ uint256 private constant PPM_DENOMINATOR = 1_000_000;
+
+ /// @notice Mapping of redeemer to deterministic redeemer account address.
+ mapping(address redeemer => address redeemerAccount)
+ private redeemerToRedeemerAccount;
- event RedemptionTriggered(
- address indexed redeemer,
- string underlyingAddress,
- uint256 indexed amountRedeemed,
- uint256 indexed lots
- );
+ /// @notice Trusted endpoint allowed to invoke `lzCompose`.
+ address public endpoint;
+ /// @notice Asset manager used for f-asset redemption.
+ IAssetManager public assetManager;
+ /// @notice FAsset token.
+ IERC20 public fAsset;
+ /// @notice Stable coin token - returned in case of a redemption failure.
+ IERC20 public stableCoin;
+ /// @notice Wrapped native token - returned in case of a redemption failure if stable coin balance is insufficient.
+ IERC20 public wNat;
+ /// @notice Trusted source OApp address (FAssetOFTAdapter).
+ address public trustedSourceOApp;
+ /// @notice Current beacon implementation for redeemer account proxies.
+ address public redeemerAccountImplementation;
+ /// @notice Recipient of composer fee collected in fAsset.
+ address public composerFeeRecipient;
+ /// @notice Default composer fee in PPM.
+ uint256 public defaultComposerFeePPM;
+ /// @notice Optional srcEid-specific composer fee in PPM, stored as fee + 1 to distinguish unset values.
+ mapping(uint32 srcEid => uint256 feePPM) private composerFeesPPM;
+ /// @notice The redeem executor.
+ address payable private executor;
+ /// @notice The native fee expected by the executor for redeem execution.
+ uint256 private executorFee;
- error OnlyEndpoint();
- error InsufficientBalance();
- error AmountTooSmall();
+ /**
+ * @notice Disables initializers on implementation contract.
+ */
+ constructor() {
+ _disableInitializers();
+ }
+
+ /**
+ * @notice Initializes composer proxy state.
+ * @param _initialOwner Owner address for administrative operations.
+ * @param _endpoint Trusted endpoint allowed to invoke `lzCompose`.
+ * @param _trustedSourceOApp Trusted source OApp address.
+ * @param _assetManager Asset manager used for redemption.
+ * @param _stableCoin Stable coin token - returned in case of a redemption failure.
+ * @param _wNat Wrapped native token - returned in case of a redemption failure
+ * if stable coin balance is insufficient.
+ * @param _composerFeeRecipient Recipient of composer fee collected in fAsset.
+ * @param _defaultComposerFeePPM Default composer fee in PPM.
+ * @param _redeemerAccountImplementation Beacon implementation for redeemer accounts.
+ */
+ function initialize(
+ address _initialOwner,
+ address _endpoint,
+ address _trustedSourceOApp,
+ IAssetManager _assetManager,
+ IERC20 _stableCoin,
+ IERC20 _wNat,
+ address _composerFeeRecipient,
+ uint256 _defaultComposerFeePPM,
+ address _redeemerAccountImplementation
+ ) external initializer {
+ require(_initialOwner != address(0), InvalidAddress());
+ require(_endpoint != address(0), InvalidAddress());
+ require(_trustedSourceOApp != address(0), InvalidAddress());
+ require(address(_assetManager).code.length > 0, InvalidAddress());
+ require(address(_stableCoin).code.length > 0, InvalidAddress());
+ require(address(_wNat).code.length > 0, InvalidAddress());
+ require(_composerFeeRecipient != address(0), InvalidAddress());
+ require(
+ _defaultComposerFeePPM < PPM_DENOMINATOR,
+ InvalidComposerFeePPM()
+ );
+ require(
+ _redeemerAccountImplementation.code.length > 0,
+ InvalidRedeemerAccountImplementation()
+ );
+
+ __Ownable_init(_initialOwner);
- constructor(address _endpoint) Ownable(msg.sender) {
endpoint = _endpoint;
+ trustedSourceOApp = _trustedSourceOApp;
+ assetManager = _assetManager;
+ fAsset = _assetManager.fAsset();
+ require(address(fAsset).code.length > 0, InvalidAddress());
+ stableCoin = _stableCoin;
+ wNat = _wNat;
+ composerFeeRecipient = _composerFeeRecipient;
+ defaultComposerFeePPM = _defaultComposerFeePPM;
+ redeemerAccountImplementation = _redeemerAccountImplementation;
+
+ emit ComposerFeeRecipientSet(_composerFeeRecipient);
+ emit DefaultComposerFeeSet(_defaultComposerFeePPM);
+ emit RedeemerAccountImplementationSet(redeemerAccountImplementation);
+ }
+
+ /**
+ * @notice Updates default composer fee in PPM.
+ * @param _defaultComposerFeePPM New default composer fee in PPM.
+ */
+ function setDefaultComposerFee(
+ uint256 _defaultComposerFeePPM
+ ) external onlyOwnerWithTimelock {
+ require(
+ _defaultComposerFeePPM < PPM_DENOMINATOR,
+ InvalidComposerFeePPM()
+ );
+ defaultComposerFeePPM = _defaultComposerFeePPM;
+ emit DefaultComposerFeeSet(_defaultComposerFeePPM);
+ }
+
+ /**
+ * @notice Sets srcEid-specific composer fees in PPM.
+ * @dev Uses fee+1 storage to distinguish unset (0) from an explicit zero fee.
+ * @param _srcEids List of OFT source endpoint IDs.
+ * @param _composerFeesPPM Composer fee values in PPM for corresponding srcEids.
+ */
+ function setComposerFees(
+ uint32[] calldata _srcEids,
+ uint256[] calldata _composerFeesPPM
+ ) external onlyOwnerWithTimelock {
+ require(_srcEids.length == _composerFeesPPM.length, LengthMismatch());
+
+ for (uint256 i = 0; i < _srcEids.length; i++) {
+ uint32 srcEid = _srcEids[i];
+ uint256 feePPM = _composerFeesPPM[i];
+ require(feePPM < PPM_DENOMINATOR, InvalidComposerFeePPM());
+ composerFeesPPM[srcEid] = feePPM + 1;
+ emit ComposerFeeSet(srcEid, feePPM);
+ }
+ }
+
+ /**
+ * @notice Removes srcEid-specific composer fee overrides.
+ * @param _srcEids List of OFT source endpoint IDs.
+ */
+ function removeComposerFees(
+ uint32[] calldata _srcEids
+ ) external onlyOwnerWithTimelock {
+ for (uint256 i = 0; i < _srcEids.length; i++) {
+ uint32 srcEid = _srcEids[i];
+ require(composerFeesPPM[srcEid] != 0, ComposerFeeNotSet(srcEid));
+ delete composerFeesPPM[srcEid];
+ emit ComposerFeeRemoved(srcEid);
+ }
+ }
+
+ /**
+ * @notice Updates recipient for collected composer fee.
+ * @param _composerFeeRecipient New recipient address.
+ */
+ function setComposerFeeRecipient(
+ address _composerFeeRecipient
+ ) external onlyOwnerWithTimelock {
+ require(
+ _composerFeeRecipient != address(0),
+ InvalidComposerFeeRecipient()
+ );
+ composerFeeRecipient = _composerFeeRecipient;
+ emit ComposerFeeRecipientSet(_composerFeeRecipient);
+ }
+
+ /**
+ * @notice Updates beacon implementation used by redeemer accounts.
+ * @param _implementation New implementation address.
+ */
+ function setRedeemerAccountImplementation(
+ address _implementation
+ ) external onlyOwnerWithTimelock {
+ require(
+ _implementation.code.length > 0,
+ InvalidRedeemerAccountImplementation()
+ );
+ redeemerAccountImplementation = _implementation;
+ emit RedeemerAccountImplementationSet(_implementation);
+ }
+
+ /**
+ * @notice Updates executor data used for redemption execution.
+ * @param _executor New executor address.
+ * @param _executorFee New expected fee for executor.
+ */
+ function setExecutorData(
+ address payable _executor,
+ uint256 _executorFee
+ ) external onlyOwnerWithTimelock {
+ require(
+ _executor != address(0) || _executorFee == 0,
+ InvalidExecutorData()
+ );
+ executor = _executor;
+ executorFee = _executorFee;
+ emit ExecutorDataSet(_executor, _executorFee);
}
- receive() external payable {}
+ /**
+ * @notice Transfers f-assets held by composer to a target address.
+ * @dev Recovery function for funds stuck on composer when compose flow fails or is not invoked.
+ * @param _to Recipient address.
+ * @param _amount Amount of f-asset to transfer.
+ */
+ function transferFAsset(
+ address _to,
+ uint256 _amount
+ ) external onlyOwnerWithTimelock {
+ require(_to != address(0), InvalidAddress());
+ fAsset.safeTransfer(_to, _amount);
+ emit FAssetTransferred(_to, _amount);
+ }
+ /**
+ * @notice Transfers native tokens held by composer to a target address.
+ * @dev Recovery function for funds stuck on composer when compose flow fails.
+ * @param _to Recipient address.
+ * @param _amount Amount of native tokens to transfer.
+ */
+ function transferNative(
+ address _to,
+ uint256 _amount
+ ) external onlyOwnerWithTimelock {
+ require(_to != address(0), InvalidAddress());
+ (bool success, ) = _to.call{value: _amount}("");
+ require(success, NativeTransferFailed());
+ emit NativeTransferred(_to, _amount);
+ }
+
+ /// @inheritdoc ILayerZeroComposer
function lzCompose(
- address /* _from */,
- bytes32 /* _guid */,
+ address _from,
+ bytes32 _guid,
bytes calldata _message,
address /* _executor */,
bytes calldata /* _extraData */
- ) external payable override nonReentrant {
- if (msg.sender != endpoint) revert OnlyEndpoint();
+ ) external payable nonReentrant {
+ require(msg.sender == endpoint, OnlyEndpoint());
+ require(_from == trustedSourceOApp, InvalidSourceOApp(_from));
+
+ uint32 srcEid = OFTComposeMsgCodec.srcEid(_message);
+ uint256 amountLD = OFTComposeMsgCodec.amountLD(_message);
+ uint256 composerFeePPM = _getComposerFeePPM(srcEid);
+ uint256 composerFee = Math.mulDiv(
+ amountLD,
+ composerFeePPM,
+ PPM_DENOMINATOR
+ );
+ uint256 amountToRedeemAfterFee = amountLD - composerFee;
+ RedeemComposeData memory data = abi.decode(
+ OFTComposeMsgCodec.composeMsg(_message),
+ (RedeemComposeData)
+ );
+ require(data.redeemer != address(0), InvalidAddress());
- bytes memory composeMsg = OFTComposeMsgCodec.composeMsg(_message);
- _processRedemption(composeMsg);
+ if (composerFee > 0) {
+ fAsset.safeTransfer(composerFeeRecipient, composerFee);
+ emit ComposerFeeCollected(
+ _guid,
+ srcEid,
+ composerFeeRecipient,
+ composerFee
+ );
+ }
+
+ address redeemerAccount = _getOrCreateRedeemerAccount(data.redeemer);
+ fAsset.safeTransfer(redeemerAccount, amountToRedeemAfterFee);
+ emit FAssetTransferred(redeemerAccount, amountToRedeemAfterFee);
+
+ try
+ IIFAssetRedeemerAccount(redeemerAccount).redeemFAsset{
+ value: msg.value
+ }(
+ assetManager,
+ amountToRedeemAfterFee,
+ data.redeemerUnderlyingAddress,
+ executor,
+ executorFee
+ )
+ returns (uint256 _redeemedAmountUBA) {
+ emit FAssetRedeemed(
+ _guid,
+ srcEid,
+ data.redeemer,
+ redeemerAccount,
+ amountToRedeemAfterFee,
+ data.redeemerUnderlyingAddress,
+ executor,
+ executorFee,
+ _redeemedAmountUBA
+ );
+ } catch {
+ emit FAssetRedeemFailed(
+ _guid,
+ srcEid,
+ data.redeemer,
+ redeemerAccount,
+ amountToRedeemAfterFee
+ );
+ }
}
- function _processRedemption(bytes memory composeMsg) internal {
- // 1. Decode message
- (, string memory underlyingAddress, address redeemer) = abi.decode(
- composeMsg,
- (uint256, string, address)
- );
+ /// @inheritdoc IFAssetRedeemComposer
+ function getComposerFeePPM(
+ uint32 _srcEid
+ ) external view returns (uint256 _composerFeePPM) {
+ _composerFeePPM = _getComposerFeePPM(_srcEid);
+ }
- // 2. Get Asset Manager & fXRP Token from Registry
- IAssetManager assetManager = ContractRegistry.getAssetManagerFXRP();
- IERC20 fAssetToken = IERC20(address(assetManager.fAsset()));
+ /// @inheritdoc IBeacon
+ function implementation() external view returns (address) {
+ return redeemerAccountImplementation;
+ }
- // 3. Check Actual Balance received from LayerZero
- uint256 currentBalance = fAssetToken.balanceOf(address(this));
- if (currentBalance == 0) revert InsufficientBalance();
+ /// @inheritdoc IFAssetRedeemComposer
+ function getExecutorData()
+ external
+ view
+ returns (address payable _executor, uint256 _executorFee)
+ {
+ _executor = executor;
+ _executorFee = executorFee;
+ }
- // 4. Calculate Lots
- uint256 lotSizeUBA = assetManager.getSettings().lotSizeAMG;
- uint256 lots = currentBalance / lotSizeUBA;
+ /// @inheritdoc IFAssetRedeemComposer
+ function getRedeemerAccountAddress(
+ address _redeemer
+ ) external view returns (address _redeemerAccount) {
+ _redeemerAccount = redeemerToRedeemerAccount[_redeemer];
+ if (_redeemerAccount == address(0)) {
+ bytes memory bytecode = _generateRedeemerAccountBytecode(_redeemer);
+ _redeemerAccount = Create2.computeAddress(
+ bytes32(0),
+ keccak256(bytecode)
+ );
+ }
+ }
- if (lots == 0) revert AmountTooSmall();
+ /**
+ * @inheritdoc UUPSUpgradeable
+ * @dev Only owner can call this method.
+ */
+ function upgradeToAndCall(
+ address _newImplementation,
+ bytes memory _data
+ ) public payable override onlyOwnerWithTimelock {
+ super.upgradeToAndCall(_newImplementation, _data);
+ }
- // 5. Calculate amount to burn
- uint256 amountToRedeem = lots * lotSizeUBA;
+ /**
+ * Unused. Present just to satisfy UUPSUpgradeable requirement as call is timelocked.
+ * The real check is in onlyOwnerWithTimelock modifier on upgradeToAndCall.
+ */
+ function _authorizeUpgrade(address _newImplementation) internal override {}
- // 6. Approve AssetManager to spend the tokens
- fAssetToken.forceApprove(address(assetManager), amountToRedeem);
+ /**
+ * @notice Gets existing redeemer account or creates a deterministic one.
+ * @param _redeemer Redeemer account owner address.
+ * @return _redeemerAccount Redeemer account address.
+ */
+ function _getOrCreateRedeemerAccount(
+ address _redeemer
+ ) internal returns (address _redeemerAccount) {
+ _redeemerAccount = redeemerToRedeemerAccount[_redeemer];
+ if (_redeemerAccount != address(0)) {
+ return _redeemerAccount;
+ }
- // 7. Redeem
- uint256 redeemedAmount = assetManager.redeem(
- lots,
- underlyingAddress,
- payable(address(0))
- );
+ // redeemer account does not exist, create it
+ bytes memory bytecode = _generateRedeemerAccountBytecode(_redeemer);
+ _redeemerAccount = Create2.deploy(0, bytes32(0), bytecode); // reverts on failure
+ redeemerToRedeemerAccount[_redeemer] = _redeemerAccount;
+ emit RedeemerAccountCreated(_redeemer, _redeemerAccount);
- emit RedemptionTriggered(
- redeemer,
- underlyingAddress,
- redeemedAmount,
- lots
+ // set unlimited allowances for fAsset, stable coin and wNat
+ // to enable redeemer to transfer funds to redeemer address in case of redemption failure
+ IIFAssetRedeemerAccount(_redeemerAccount).setMaxAllowances(
+ fAsset,
+ stableCoin,
+ wNat
);
}
- function recoverTokens(
- address token,
- address to,
- uint256 amount
- ) external onlyOwner {
- IERC20(token).safeTransfer(to, amount);
+ /**
+ * @notice Builds CREATE2 deployment bytecode for redeemer account proxy.
+ * @param _redeemer Redeemer account owner address.
+ * @return Bytecode used for deterministic deployment.
+ */
+ function _generateRedeemerAccountBytecode(
+ address _redeemer
+ ) internal view returns (bytes memory) {
+ return
+ abi.encodePacked(
+ type(FAssetRedeemerAccountProxy).creationCode,
+ abi.encode(address(this), _redeemer)
+ );
}
- function recoverNative() external onlyOwner {
- payable(owner()).transfer(address(this).balance);
+ /**
+ * @notice Retrieves composer fee in PPM for a given srcEid, falling back to default if not set.
+ * @param _srcEid OFT source endpoint ID.
+ * @return _composerFeePPM Composer fee in PPM.
+ */
+ function _getComposerFeePPM(
+ uint32 _srcEid
+ ) internal view returns (uint256 _composerFeePPM) {
+ uint256 srcEidFeePlusOne = composerFeesPPM[_srcEid];
+ if (srcEidFeePlusOne > 0) {
+ return srcEidFeePlusOne - 1;
+ }
+
+ return defaultComposerFeePPM;
}
}