diff --git a/docs/fassets/developer-guides.mdx b/docs/fassets/developer-guides.mdx
index e652516a..1f19b31e 100644
--- a/docs/fassets/developer-guides.mdx
+++ b/docs/fassets/developer-guides.mdx
@@ -87,6 +87,12 @@ Guides for [redeeming FAssets](/fassets/redemption) back to underlying assets.
href: "/fassets/developer-guides/fassets-redemption-queue",
docId: "fassets/developer-guides/fassets-redemption-queue",
},
+ {
+ type: "link",
+ label: "FAsset Auto-Redemption",
+ href: "/fassets/developer-guides/fassets-autoredeem",
+ docId: "fassets/developer-guides/fassets-autoredeem",
+ },
]}
/>
diff --git a/docs/fassets/developer-guides/12-fassets-autoredeem.mdx b/docs/fassets/developer-guides/12-fassets-autoredeem.mdx
new file mode 100644
index 00000000..712d83e2
--- /dev/null
+++ b/docs/fassets/developer-guides/12-fassets-autoredeem.mdx
@@ -0,0 +1,585 @@
+---
+title: FAsset Auto-Redemption
+tags: [intermediate, fassets]
+slug: fassets-autoredeem
+description: Auto-redeem FAssets to native XRP cross-chain
+keywords: [fassets, flare-network]
+sidebar_position: 12
+---
+
+import CodeBlock from "@theme/CodeBlock";
+import FassetRedeemComposer from "!!raw-loader!/examples/developer-hub-solidity/FAssetRedeemComposer.sol";
+import bridgeToHyperEVM from "!!raw-loader!/examples/developer-hub-javascript/bridgeToHyperEVM.ts";
+import autoRedeemHyperEVM from "!!raw-loader!/examples/developer-hub-javascript/autoRedeemHyperEVM.ts";
+import getOftPeers from "!!raw-loader!/examples/developer-hub-javascript/getOftPeers.ts";
+
+## Overview
+
+In this guide, you will learn how to bridge FAssets (specifically FXRP) between Flare Testnet Coston2 and Hyperliquid EVM Testnet using LayerZero's cross-chain messaging protocol, with support for **automatic redemption** - converting FXRP back to native XRP on the XRP Ledger in a single transaction.
+
+This guide covers two key functionalities:
+
+1. **Bridging FXRP from Flare Testnet Coston2 to Hyperliquid EVM** (`bridgeToHyperEVM.ts`) - Transfer wrapped XRP tokens to Hyperliquid for trading or DeFi use.
+2. **Auto-Redeem from Hyperliquid to Underlying Asset** (`autoRedeemHyperEVM.ts`) - Send FXRP from Hyperliquid back to Flare Testnet Coston2 and automatically redeem it for native XRP.
+
+**Key technologies:**
+
+- [LayerZero OFT](https://docs.layerzero.network/v2/developers/evm/oft/quickstart) (Omnichain Fungible Token) for cross-chain token transfers.
+ OFT works by burning tokens on the source chain and minting equivalent tokens on the destination chain, enabling seamless movement of assets across different blockchains.
+- Flare's [FAsset](/fassets/overview) system for tokenizing non-smart contract assets.
+- [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.
+
+This guide includes three main components: a Solidity smart contract (`FAssetRedeemComposer.sol`) and two TypeScript scripts (`bridgeToHyperEVM.ts` and `autoRedeemHyperEVM.ts`) that demonstrate the complete workflow.
+
+Clone the [Flare Hardhat Starter](https://github.com/flare-foundation/flare-hardhat-starter) to follow along.
+
+## Getting Started: The Two-Step Process
+
+:::warning
+
+To successfully test the auto-redemption feature, you must run the scripts in the correct order.
+You must complete [Step 1](#step-1-bridge-fxrp-to-hyperliquid-evm-required-first) (bridging TO Hyperliquid) before you can execute [Step 2](#step-2-auto-redeem-to-native-xrp-requires-step-1) (auto-redeeming FROM Hyperliquid).
+
+:::
+
+### Step 1: Bridge FXRP to Hyperliquid EVM (Required First)
+
+Run [`bridgeToHyperEVM.ts`](https://github.com/flare-foundation/flare-hardhat-starter/blob/main/scripts/fassets/bridgeToHyperEVM.ts) on **Flare Testnet Coston2** network to transfer your FXRP tokens from Coston2 to Hyperliquid EVM Testnet.
+This script does NOT involve auto-redemption - it simply moves your tokens to Hyperliquid where you can use them to prepare for the auto-redemption process.
+
+:::info
+
+You need FXRP tokens on Hyperliquid EVM before you can test the auto-redeem functionality.
+The auto-redeem script will fail if you don't have tokens there.
+
+:::
+
+```bash
+# Step 1: Bridge tokens TO Hyperliquid
+yarn hardhat run scripts/fassets/bridgeToHyperEVM.ts --network coston2
+```
+
+### Step 2: Auto-Redeem to Native XRP (Requires Step 1)
+
+Once you have FXRP tokens on Hyperliquid (from Step 1), run [`autoRedeemHyperEVM.ts`](https://github.com/flare-foundation/flare-hardhat-starter/blob/main/scripts/fassets/autoRedeemHyperEVM.ts) on **Hyperliquid Testnet** network to send them back to Flare Testnet Coston2 with automatic redemption to native XRP.
+This is the auto-redemption feature that converts FXRP back to native XRP on the XRP Ledger in a single transaction.
+
+```bash
+# Step 2: Auto-redeem back to native XRP
+yarn hardhat run scripts/fassets/autoRedeemHyperEVM.ts --network hyperliquidTestnet
+```
+
+## FAssetRedeemComposer Contract
+
+### 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.
+
+### How It Works
+
+The composer implements the [IOAppComposer](https://docs.layerzero.network/v2/developers/evm/composer/overview) interface and processes LayerZero compose messages:
+
+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}
+
+
+### Code Breakdown
+
+The `_processRedemption` function contains the core redemption logic with numbered steps in the comments:
+
+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 contract also includes `recoverTokens()` and `recoverNative()` helper functions that allow the owner to recover any stuck tokens or native currency if needed.
+
+## Bridge FXRP to Hyperliquid EVM (Step 1)
+
+### What It Is
+
+**This is Step 1 of the two-step process** and is a **prerequisite** for testing the auto-redemption feature.
+
+This script bridges FXRP tokens from Flare Testnet Coston2 to Hyperliquid EVM Testnet using LayerZero's OFT Adapter pattern.
+It wraps existing ERC20 FAsset tokens into LayerZero OFT format for cross-chain transfer.
+
+:::warning
+
+This script does NOT perform auto-redemption.
+
+:::
+Its purpose is to get your FXRP tokens onto Hyperliquid EVM Testnet so that:
+
+- You can use them for trading or DeFi on Hyperliquid, OR
+- You can run the auto-redeem script (Step 2) to convert them back to native XRP
+
+You must successfully complete this step before running the `autoRedeemHyperEVM.ts` script.
+
+### How It Works
+
+#### Step-by-Step Process
+
+1. **Balance Check**: Verifies user has sufficient FTestXRP tokens.
+2. **Token Approval**:
+ - Approves OFT Adapter to spend FTestXRP (2x amount for safety).
+ - Approves Composer (if needed for future operations).
+3. **Build Send Parameters**:
+ - Destination: Hyperliquid EVM Testnet (EndpointID for Hyperliquid Testnet: `40294`).
+ - Recipient: Same address on destination chain.
+ - Amount: Calculated from configured lots (default 1 lot, with 10% buffer).
+ - LayerZero options: Executor gas limit set to 200,000.
+4. **Quote Fee**: Calculates the LayerZero cross-chain messaging fee.
+5. **Execute Bridge**: Sends tokens via `oftAdapter.send()`.
+6. **Confirmation**: Waits for transaction confirmation and provides tracking link to the LayerZero Explorer page.
+
+### Prerequisites
+
+- **Balance Requirements**:
+ - FTestXRP tokens (amount you want to bridge).
+ - C2FLR tokens (for gas fees + LayerZero fees).
+ You can get some from the Flare Testnet [faucet](https://faucet.flare.network/).
+- **Environment Setup**:
+ - Private key configured in Hardhat for Flare Testnet Coston2 and Hyperliquid Testnet.
+
+### Configuration
+
+Edit the `CONFIG` object in the script:
+
+```typescript
+const CONFIG = {
+ COSTON2_OFT_ADAPTER: "0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639",
+ COSTON2_COMPOSER: process.env.COSTON2_COMPOSER || "",
+ HYPERLIQUID_EID: EndpointId.HYPERLIQUID_V2_TESTNET,
+ EXECUTOR_GAS: 200_000,
+ BRIDGE_LOTS: "1", // Change this to your desired number of lots
+};
+```
+
+### How to Run
+
+**Run this script FIRST before attempting auto-redemption.**
+This gets your FXRP tokens onto Hyperliquid EVM.
+
+1. **Install Dependencies**:
+
+ ```bash
+ yarn install
+ ```
+
+2. **Configure Environment**:
+
+ ```bash
+ # .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
+ ```
+
+3. **Run the Script on Flare Testnet Coston2**:
+
+ ```bash
+ yarn hardhat run scripts/fassets/bridgeToHyperEVM.ts --network coston2
+ ```
+
+4. **Wait for Completion**:
+ - Monitor the transaction on LayerZero Scan (link provided in output)
+ - Allow 2-5 minutes for cross-chain delivery
+ - Verify FXRP balance increased on Hyperliquid EVM before proceeding to Step 2
+
+### Expected Output
+
+```
+Using account: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
+
+š Bridge Details:
+From: Coston2
+To: Hyperliquid EVM Testnet
+Amount: 11.0 FXRP
+Recipient: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
+
+Your FTestXRP balance: 100.0
+
+1ļøā£ Checking OFT Adapter token address...
+ OFT Adapter's inner token: 0x8b4abA9C4BD7DD961659b02129beE20c6286e17F
+ Expected token: 0x8b4abA9C4BD7DD961659b02129beE20c6286e17F
+ Match: true
+
+ Approving FTestXRP for OFT Adapter...
+ OFT Adapter address: 0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639
+ Amount: 11.0 FXRP
+ā
OFT Adapter approved
+ Verified allowance: 22.0 FXRP
+
+3ļøā£ LayerZero Fee: 0.001234 C2FLR
+
+4ļøā£ Sending FXRP to Hyperliquid EVM Testnet...
+Transaction sent: 0xabc123...
+ā
Confirmed in block: 12345678
+
+š Success! Your FXRP is on the way to Hyperliquid EVM Testnet!
+
+Track your transaction:
+https://testnet.layerzeroscan.com/tx/0xabc123...
+
+It may take a few minutes to arrive on Hyperliquid EVM Testnet.
+```
+
+
+View `bridgeToHyperEVM.ts` source code
+
+
+ {bridgeToHyperEVM}
+
+
+
+
+### Troubleshooting
+
+**Error: Insufficient FTestXRP balance**
+
+- Solution: Acquire FTestXRP tokens on Flare Testnet Coston2 [faucet](https://faucet.flare.network/coston2) or follow our Fasset minting [guide](/fassets/developer-guides/fassets-mint).
+
+**Error: Insufficient C2FLR for gas**
+
+- Solution: Get C2FLR from Flare Testnet Coston2 [faucet](https://faucet.flare.network/coston2).
+
+**Error: Transaction reverted**
+
+- Check that the OFT Adapter address matches the FTestXRP token.
+- Verify LayerZero endpoint is operational.
+- Ensure gas limits are sufficient.
+
+## Auto-Redeem from Hyperliquid EVM (Step 2)
+
+### What It Is
+
+**This is Step 2 of the two-step process** and **requires you to have FXRP tokens on Hyperliquid EVM first** (from [Step 1](#step-1-bridge-fxrp-to-hyperliquid-evm-required-first)).
+
+This script sends FXRP from Hyperliquid EVM Testnet back to Flare Testnet Coston2 with **automatic redemption** to native XRP on the XRP Ledger.
+It uses LayerZero's compose feature to trigger the `FAssetRedeemComposer` contract upon arrival.
+
+**Prerequisites:**
+
+- 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.
+
+### 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... ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+#### Step-by-Step Process
+
+1. **Validate Setup**:
+ - Checks that `COSTON2_COMPOSER` is configured.
+ - Gets the signer account.
+2. **Prepare Redemption Parameters**:
+ - Number of lots to send (default: 1 lot, amount calculated from lot size).
+ - XRP address to receive native XRP.
+ - Redeemer address (EVM address).
+3. **Connect to FXRP OFT**: Gets the OFT contract on Hyperliquid.
+4. **Encode Compose Message**:
+ - Encodes `(uint256 amount, string xrpAddress, address redeemer)`.
+ - This tells the composer what to do when tokens arrive.
+5. **Build LayerZero Options**:
+ - Executor gas for `lzReceive()`: 400,000
+ - Compose gas for `lzCompose()`: 700,000
+6. **Build Send Parameters**:
+ - Destination: Coston2 (Endpoint ID: `40296`).
+ - Recipient: FAssetRedeemComposer contract.
+ - Amount: Calculated from configured lots (default 1 lot).
+ - Compose message included
+7. **Check Balance**: Verifies user has sufficient FXRP OFT.
+8. **Quote Fee**: Calculates LayerZero messaging fee.
+9. **Execute Send**: Sends FXRP with compose message.
+10. **Auto-Redemption**: On arrival, composer automatically redeems to XRP.
+
+### Prerequisites
+
+- **Network**: Must run on Hyperliquid EVM Testnet.
+- **Balance Requirements**:
+ - 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`.
+
+### Configuration
+
+Edit the `CONFIG` object in the script:
+
+```typescript
+const CONFIG = {
+ HYPERLIQUID_FXRP_OFT:
+ process.env.HYPERLIQUID_FXRP_OFT ||
+ "0x14bfb521e318fc3d5e92A8462C65079BC7d4284c",
+ COSTON2_COMPOSER: process.env.COSTON2_COMPOSER || "",
+ COSTON2_EID: EndpointId.FLARE_V2_TESTNET,
+ EXECUTOR_GAS: 400_000, // Gas for receiving on Coston2
+ COMPOSE_GAS: 700_000, // Gas for compose execution
+ SEND_LOTS: "1", // Number of lots to redeem
+ XRP_ADDRESS: "rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp", // Your XRP address
+};
+```
+
+### How to Run
+
+**PREREQUISITE: You must have FXRP tokens on Hyperliquid EVM Testnet before running this script.**
+
+If you don't have FXRP on Hyperliquid yet:
+
+- Run `bridgeToHyperEVM.ts` first ([Step 1](#step-1-bridge-fxrp-to-hyperliquid-evm-required-first))
+- Wait for the bridge transaction to complete (2-5 minutes)
+- Verify your FXRP balance on Hyperliquid EVM before proceeding
+
+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
+ ```
+
+3. **Update XRP Address**:
+ - Edit `CONFIG.XRP_ADDRESS` in the script to your XRP ledger address
+ - This is where you'll receive the native XRP
+ - Must be a valid XRP address format (starts with 'r')
+
+4. **Run the Script on Hyperliquid Testnet**:
+ ```bash
+ yarn hardhat run scripts/fassets/autoRedeemHyperEVM.ts --network hyperliquidTestnet
+ ```
+
+### Expected Output
+
+```
+Using account: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
+ā Composer configured: 0x123...
+
+š Redemption Parameters:
+Amount: 10.0 FXRP
+XRP Address: rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp
+Redeemer: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
+
+Connecting to FXRP OFT on Hyperliquid EVM: 0x14bfb521e318fc3d5e92A8462C65079BC7d4284c
+
+ā Connected to FXRP OFT: 0x14bfb521e318fc3d5e92A8462C65079BC7d4284c
+OFT address: 0x14bfb521e318fc3d5e92A8462C65079BC7d4284c
+
+Compose message encoded
+
+š° Current FXRP balance: 25.0
+Sufficient balance
+
+šµ LayerZero Fee: 0.002456 HYPE
+
+š Sending 10.0 FXRP to Coston2 with auto-redeem...
+Target composer: 0x123...
+Underlying address: rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp
+
+ā Transaction sent: 0xdef456...
+Waiting for confirmation...
+ā
Confirmed in block: 9876543
+
+š Success! Your FXRP is on the way to Coston2!
+
+š Track your cross-chain transaction:
+https://testnet.layerzeroscan.com/tx/0xdef456...
+
+ā³ The auto-redeem will execute once the message arrives on Coston2.
+XRP will be sent to: rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp
+```
+
+
+View `autoRedeemHyperEVM.ts` source code
+
+
+ {autoRedeemHyperEVM}
+
+
+
+
+## FAQ
+
+**Q: How long does bridging take?**
+A: Typically 2-5 minutes for LayerZero message delivery + execution time.
+
+**Q: What's the minimum amount I can bridge?**
+A: Any amount for bridging to Hyperliquid. For auto-redeem, minimum is 1 [lot](https://dev.flare.network/fassets/minting#lots) (10 FXRP for XRP).
+
+**Q: Can I bridge to a different address?**
+A: Yes, edit the `recipientAddress` parameter in the scripts.
+
+**Q: What happens if compose execution fails?**
+A: Tokens will be stuck in the composer. Owner can recover using `recoverTokens()`.
+
+**Q: Can I use this on mainnet?**
+A: This is designed for testnet. For mainnet, update contract addresses, thoroughly test, and audit all code.
+
+**Q: How do I get FTestXRP on Flare Testnet Coston2?**
+A: Use Flare's FAsset minting process via the AssetManager contract.
+
+**Q: What if I don't have HYPE tokens?**
+A: Get them from Hyperliquid testnet [faucet](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/testnet-faucet) or DEX.
+
+## Discovering Available Bridge Routes
+
+The `getOftPeers.ts` utility script discovers all configured LayerZero peers for the FXRP OFT Adapter on Flare Testnet Coston2.
+It scans all LayerZero V2 testnet endpoints to find which EVM chains have been configured as valid bridge destinations.
+
+Before bridging FXRP to another chain, you need to know which chains are supported.
+This script:
+
+- Automatically discovers all configured peer addresses
+- Shows which EVM chains you can bridge FXRP to/from
+- Provides the peer contract addresses for each chain
+- Outputs results in both human-readable and JSON formats
+
+#### How It Works
+
+1. **Loads V2 Testnet Endpoints**: Dynamically retrieves all LayerZero V2 testnet endpoint IDs from the [`@layerzerolabs/lz-definitions`](https://docs.layerzero.network/plugins#layerzerolabslz-definitions) package.
+2. **Queries Peers**: For each endpoint, calls the `peers()` [function](https://coston2-explorer.flare.network/address/0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639?tab=read_write_proxy&source_address=0x82BC5e114D0843160be1cEaB8196Ba91f87473FE#0xbb0b6a53) on the OFT Adapter contract.
+3. **Filters Results**: Only shows endpoints that have a non-zero peer address configured.
+4. **Formats Output**: Displays results in a table format and as JSON for programmatic use.
+
+#### How to Run
+
+```bash
+yarn hardhat run scripts/layerzero/getOFTPeers.ts --network coston2
+```
+
+#### Expected Output
+
+```
+=== FXRP OFT Adapter Peers Discovery ===
+
+OFT Adapter: 0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639
+Network: Coston2 (Flare Testnet)
+
+Scanning 221 LayerZero V2 Testnet endpoints...
+
+ā
Bsc (EID: 40102): 0xac7c4a07670589cf83b134a843bfe86c45a4bf4e
+ā
Sepolia (EID: 40161): 0x81672c5d42f3573ad95a0bdfbe824faac547d4e6
+ā
Hyperliquid (EID: 40362): 0x14bfb521e318fc3d5e92a8462c65079bc7d4284c
+
+============================================================
+SUMMARY: Configured Peers
+============================================================
+
+Found 3 configured peer(s):
+
+| Chain | EID | Peer Address |
+|-------|-----|--------------|
+| Bsc | 40102 | 0xac7c4a07670589cf83b134a843bfe86c45a4bf4e |
+| Sepolia | 40161 | 0x81672c5d42f3573ad95a0bdfbe824faac547d4e6 |
+| Hyperliquid | 40362 | 0x14bfb521e318fc3d5e92a8462c65079bc7d4284c |
+
+--- Available Routes ---
+You can bridge FXRP to/from the following chains:
+
+ ⢠Bsc
+ ⢠Sepolia
+ ⢠Hyperliquid
+```
+
+Once you've identified available peers, you can:
+
+1. **Bridge to any discovered chain**: Update the destination EID in `bridgeToHyperEVM.ts` to target a different chain
+2. **Verify peer addresses**: Use the peer addresses to interact with OFT contracts on other chains
+3. **Build integrations**: Use the JSON output to programmatically determine available routes
+
+
+View `getOftPeers.ts` source code
+
+
+ {getOftPeers}
+
+
+
+
+:::tip Next Steps
+To continue your FAssets development journey, you can:
+
+- Learn how to [mint FXRP](/fassets/developer-guides/fassets-mint)
+- Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem)
+- Explore [FAssets system settings](/fassets/operational-parameters)
+ :::
diff --git a/examples/developer-hub-javascript/autoRedeemHyperEVM.ts b/examples/developer-hub-javascript/autoRedeemHyperEVM.ts
new file mode 100644
index 00000000..6d8f0997
--- /dev/null
+++ b/examples/developer-hub-javascript/autoRedeemHyperEVM.ts
@@ -0,0 +1,279 @@
+/**
+ * Example script to send FXRP from Hyperliquid EVM Testnet to Coston2 with automatic redemption
+ *
+ * This demonstrates how to:
+ * 1. Send OFT tokens from Hyperliquid EVM Testnet (where FXRP is an OFT)
+ * 2. Use LayerZero compose to trigger automatic redemption on Hyperliquid
+ * 3. Redeem the underlying asset (XRP) to a specified address
+ *
+ * Usage:
+ * yarn hardhat run scripts/fassets/autoRedeemHyperEVM.ts --network hyperliquidTestnet
+ */
+
+import { ethers } from "hardhat";
+import { formatUnits, zeroPadValue, AbiCoder, Contract } from "ethers";
+import { Options } from "@layerzerolabs/lz-v2-utilities";
+import { EndpointId } from "@layerzerolabs/lz-definitions";
+
+import { getAssetManagerFXRP } from "../utils/getters";
+
+// Configuration - using existing deployed contracts
+const CONFIG = {
+ HYPERLIQUID_FXRP_OFT:
+ process.env.HYPERLIQUID_FXRP_OFT ||
+ "0x14bfb521e318fc3d5e92A8462C65079BC7d4284c",
+ COSTON2_COMPOSER: process.env.COSTON2_COMPOSER || "",
+ COSTON2_EID: EndpointId.FLARE_V2_TESTNET, // Coston2 EID (destination)
+ EXECUTOR_GAS: 400_000,
+ COMPOSE_GAS: 700_000,
+ SEND_LOTS: "1",
+ XRP_ADDRESS: "rpHuw4bKSjonKRrKKVYUZYYVedg1jyPrmp", // Change this to the XRP address you are auto-redeeming to
+} as const;
+
+type RedemptionParams = {
+ amountToSend: bigint;
+ underlyingAddress: string;
+ redeemer: string;
+ signerAddress: string;
+ executor: string;
+};
+
+type SendParams = {
+ dstEid: EndpointId;
+ to: string;
+ amountLD: bigint;
+ minAmountLD: bigint;
+ extraOptions: string;
+ composeMsg: string;
+ oftCmd: string;
+};
+
+async function calculateAmountToSend(lots: bigint) {
+ const assetManager = await getAssetManagerFXRP();
+ const lotSize = BigInt(await assetManager.lotSize());
+
+ return lotSize * lots;
+}
+
+/**
+ * Gets the signer and validates composer is deployed
+ */
+async function validateSetup() {
+ const [signer] = await ethers.getSigners();
+
+ console.log("Using account:", signer.address);
+
+ if (!CONFIG.COSTON2_COMPOSER) {
+ throw new Error(
+ "ā HYPERLIQUID_COMPOSER not set in .env!\n" +
+ " Deploy FAssetRedeemComposer first on Hyperliquid:\n" +
+ " npx hardhat deploy --network hyperliquid --tags FAssetRedeemComposer",
+ );
+ }
+
+ console.log("ā Composer configured:", CONFIG.COSTON2_COMPOSER);
+
+ return signer;
+}
+
+/**
+ * Prepares redemption parameters
+ */
+async function prepareRedemptionParams(
+ signerAddress: string,
+): Promise {
+ const amountToSend = await calculateAmountToSend(BigInt(CONFIG.SEND_LOTS));
+ const underlyingAddress = CONFIG.XRP_ADDRESS;
+ const redeemer = signerAddress;
+
+ console.log("\nš Redemption Parameters:");
+ console.log("Amount:", formatUnits(amountToSend.toString(), 6), "FXRP");
+ console.log("XRP Address:", underlyingAddress);
+ console.log("Redeemer:", redeemer);
+
+ const executor = "0x0000000000000000000000000000000000000000";
+
+ return { amountToSend, underlyingAddress, redeemer, signerAddress, executor };
+}
+
+/**
+ * Connects to the OFT contract on Hyperliquid EVM
+ */
+async function connectToOFT() {
+ console.log(
+ "Connecting to FXRP OFT on Hyperliquid EVM:",
+ CONFIG.HYPERLIQUID_FXRP_OFT,
+ );
+ const oft = await ethers.getContractAt(
+ "FXRPOFT",
+ CONFIG.HYPERLIQUID_FXRP_OFT,
+ );
+
+ console.log("\nā Connected to FXRP OFT:", CONFIG.HYPERLIQUID_FXRP_OFT);
+ console.log("OFT address:", oft.target);
+
+ return oft;
+}
+
+/**
+ * Encodes the compose message with redemption details
+ * Format: (amountToRedeem, underlyingAddress, redeemer)
+ */
+function encodeComposeMessage(params: RedemptionParams): string {
+ const abiCoder = AbiCoder.defaultAbiCoder();
+ // redeem(uint256 _lots, string memory _redeemerUnderlyingAddressString, executor address)
+ const composeMsg = abiCoder.encode(
+ ["uint256", "string", "address"],
+ [params.amountToSend, params.underlyingAddress, params.redeemer],
+ );
+
+ console.log("Compose message encoded");
+
+ return composeMsg;
+}
+
+/**
+ * Builds LayerZero options with compose support
+ */
+function buildComposeOptions(): string {
+ const options = Options.newOptions()
+ .addExecutorLzReceiveOption(CONFIG.EXECUTOR_GAS, 0)
+ .addExecutorComposeOption(0, CONFIG.COMPOSE_GAS, 0);
+
+ return options.toHex();
+}
+
+/**
+ * Builds the send parameters for LayerZero
+ */
+function buildSendParams(
+ params: RedemptionParams,
+ composeMsg: string,
+ options: string,
+): SendParams {
+ return {
+ dstEid: CONFIG.COSTON2_EID,
+ to: zeroPadValue(CONFIG.COSTON2_COMPOSER, 32),
+ amountLD: params.amountToSend,
+ minAmountLD: params.amountToSend,
+ extraOptions: options,
+ composeMsg: composeMsg,
+ oftCmd: "0x",
+ };
+}
+
+/**
+ * Checks if user has sufficient FXRP balance
+ */
+async function checkBalance(
+ oft: Contract,
+ signerAddress: string,
+ amountToSend: bigint,
+): Promise {
+ console.log("signer address", signerAddress);
+ console.log("oft.address", oft.address);
+ const balance = await oft.balanceOf(signerAddress);
+ console.log("signer address", signerAddress);
+ console.log("\nš° Current FXRP balance:", formatUnits(balance, 6));
+
+ if (balance < amountToSend) {
+ console.error("\nā Insufficient FXRP balance!");
+ console.log(" Required:", formatUnits(amountToSend, 6), "FXRP");
+ console.log(" Available:", formatUnits(balance, 6), "FXRP");
+ throw new Error("Insufficient FXRP balance");
+ }
+
+ console.log("Sufficient balance");
+}
+
+/**
+ * Quotes the LayerZero fee for the send transaction
+ */
+async function quoteFee(
+ oft: Contract,
+ sendParam: SendParams,
+): Promise<{ nativeFee: bigint; lzTokenFee: bigint }> {
+ const result = await oft.quoteSend(sendParam, false);
+ const nativeFee = result.nativeFee;
+ const lzTokenFee = result.lzTokenFee;
+
+ console.log("\nšµ LayerZero Fee:", formatUnits(nativeFee, 18), "HYPE");
+
+ return { nativeFee, lzTokenFee };
+}
+
+/**
+ * Executes the send with auto-redeem
+ */
+async function executeSendAndRedeem(
+ oft: Contract,
+ sendParam: SendParams,
+ nativeFee: bigint,
+ lzTokenFee: bigint,
+ params: RedemptionParams,
+): Promise {
+ console.log(
+ "\nš Sending",
+ formatUnits(params.amountToSend, 6),
+ "FXRP to Coston2 with auto-redeem...",
+ );
+ console.log("Target composer:", CONFIG.COSTON2_COMPOSER);
+ console.log("Underlying address:", params.underlyingAddress);
+
+ const tx = await oft.send(
+ sendParam,
+ { nativeFee, lzTokenFee },
+ params.signerAddress,
+ { value: nativeFee },
+ );
+
+ console.log("\nā Transaction sent:", tx.hash);
+ console.log("Waiting for confirmation...");
+
+ const receipt = await tx.wait();
+ console.log("ā
Confirmed in block:", receipt?.blockNumber);
+
+ console.log("\nš Success! Your FXRP is on the way to Coston2!");
+ console.log("\nš Track your cross-chain transaction:");
+ console.log(`https://testnet.layerzeroscan.com/tx/${tx.hash}`);
+ console.log(
+ "\nā³ The auto-redeem will execute once the message arrives on Coston2.",
+ );
+ console.log("XRP will be sent to:", params.underlyingAddress);
+}
+
+async function main() {
+ // 1. Validate setup and get signer
+ const signer = await validateSetup();
+
+ // 2. Prepare redemption parameters
+ const params = await prepareRedemptionParams(signer.address);
+
+ // 3. Connect to OFT contract
+ const oft = await connectToOFT();
+ console.log("3. oft.address", oft.address);
+
+ // 4. Encode compose message
+ const composeMsg = encodeComposeMessage(params);
+
+ // 5. Build LayerZero options
+ const options = buildComposeOptions();
+
+ // 6. Build send parameters
+ const sendParam = buildSendParams(params, composeMsg, options);
+
+ console.log("7. oft.address", oft.address);
+ // 7. Check balance
+ await checkBalance(oft, params.signerAddress, params.amountToSend);
+
+ // 8. Quote fee
+ const { nativeFee, lzTokenFee } = await quoteFee(oft, sendParam);
+
+ // 9. Execute send with auto-redeem
+ await executeSendAndRedeem(oft, sendParam, nativeFee, lzTokenFee, params);
+}
+
+main().catch((error) => {
+ console.error(error);
+ process.exitCode = 1;
+});
diff --git a/examples/developer-hub-javascript/bridgeToHyperEVM.ts b/examples/developer-hub-javascript/bridgeToHyperEVM.ts
new file mode 100644
index 00000000..d5f3664b
--- /dev/null
+++ b/examples/developer-hub-javascript/bridgeToHyperEVM.ts
@@ -0,0 +1,273 @@
+/**
+ * Bridge FXRP from Coston2 to Hyperliquid EVM Testnet
+ *
+ * This script helps you get FXRP on Hyperliquid EVM Testnet by bridging from Coston2
+ *
+ * Prerequisites:
+ * - FTestXRP tokens on Coston2
+ * - CFLR on Coston2 for gas
+ *
+ * Usage:
+ * yarn hardhat run scripts/fassets/bridgeToHyperEVM.ts --network coston2
+ */
+
+import { web3 } from "hardhat";
+import { formatUnits } from "ethers";
+import { EndpointId } from "@layerzerolabs/lz-definitions";
+import { Options } from "@layerzerolabs/lz-v2-utilities";
+import {
+ IERC20Instance,
+ FAssetOFTAdapterInstance,
+} from "../../typechain-types";
+import { getAssetManagerFXRP } from "../utils/getters";
+
+// Get the contracts
+const IERC20 = artifacts.require("IERC20");
+const FAssetOFTAdapter = artifacts.require("FAssetOFTAdapter");
+
+const CONFIG = {
+ COSTON2_FTESTXRP: "0x8b4abA9C4BD7DD961659b02129beE20c6286e17F",
+ COSTON2_OFT_ADAPTER: "0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639",
+ COSTON2_COMPOSER: process.env.COSTON2_COMPOSER || "",
+ HYPERLIQUID_EID: EndpointId.HYPERLIQUID_V2_TESTNET, // Hyperliquid testnet EID
+ EXECUTOR_GAS: 200_000,
+ BRIDGE_LOTS: "1",
+} as const;
+
+type BridgeParams = {
+ amountToBridge: bigint;
+ recipientAddress: string;
+ signerAddress: string;
+};
+
+type SendParams = {
+ dstEid: EndpointId;
+ to: string;
+ amountLD: string;
+ minAmountLD: string;
+ extraOptions: string;
+ composeMsg: string;
+ oftCmd: string;
+};
+
+async function calculateAmountToBridge(lots: bigint) {
+ const assetManager = await getAssetManagerFXRP();
+ const lotSize = await assetManager.lotSize();
+ const amountToBridge = lotSize * lots;
+
+ return amountToBridge * BigInt(1.1); // 10% buffer
+}
+
+/**
+ * Gets the signer and displays account information
+ */
+async function getSigner() {
+ const accounts = await web3.eth.getAccounts();
+ const signerAddress = accounts[0];
+
+ console.log("Using account:", signerAddress);
+ console.log("Token address:", CONFIG.COSTON2_FTESTXRP);
+
+ return signerAddress;
+}
+
+/**
+ * Prepares bridge parameters
+ */
+async function prepareBridgeParams(
+ signerAddress: string,
+): Promise {
+ const amountToBridge = await calculateAmountToBridge(
+ BigInt(CONFIG.BRIDGE_LOTS),
+ );
+ const recipientAddress = signerAddress;
+
+ console.log("\nš Bridge Details:");
+ console.log("From: Coston2");
+ console.log("To: Hyperliquid EVM Testnet");
+ console.log("Amount:", formatUnits(amountToBridge.toString(), 6), "FXRP");
+ console.log("Recipient:", recipientAddress);
+
+ return { amountToBridge, recipientAddress, signerAddress };
+}
+
+/**
+ * Checks if user has sufficient balance to bridge
+ */
+async function checkBalance(params: BridgeParams): Promise {
+ const fTestXRP: IERC20Instance = await IERC20.at(CONFIG.COSTON2_FTESTXRP);
+
+ const balance = await fTestXRP.balanceOf(params.signerAddress);
+ console.log("\nYour FTestXRP balance:", formatUnits(balance.toString(), 6));
+
+ if (BigInt(balance.toString()) > params.amountToBridge) {
+ console.error("\nā Insufficient FTestXRP balance!");
+ console.log(" Token address: " + CONFIG.COSTON2_FTESTXRP);
+ throw new Error("Insufficient balance");
+ }
+
+ return fTestXRP;
+}
+
+/**
+ * Approves OFT Adapter AND Composer to spend FTestXRP
+ */
+async function approveTokens(
+ fTestXRP: IERC20Instance,
+ amountToBridge: bigint,
+ signerAddress: string,
+): Promise {
+ const oftAdapter: FAssetOFTAdapterInstance = await FAssetOFTAdapter.at(
+ CONFIG.COSTON2_OFT_ADAPTER,
+ );
+
+ console.log("\n1ļøā£ Checking OFT Adapter token address...");
+ const innerToken = await oftAdapter.token();
+ console.log(" OFT Adapter's inner token:", innerToken);
+ console.log(" Expected token:", CONFIG.COSTON2_FTESTXRP);
+ console.log(
+ " Match:",
+ innerToken.toLowerCase() === CONFIG.COSTON2_FTESTXRP.toLowerCase(),
+ );
+
+ console.log("\n Approving FTestXRP for OFT Adapter...");
+ console.log(" OFT Adapter address:", oftAdapter.address);
+ console.log(" Amount:", formatUnits(amountToBridge.toString(), 6), "FXRP");
+
+ // Approve a much larger amount to account for any potential fees
+ const largeAmount = amountToBridge * BigInt(2);
+ await fTestXRP.approve(oftAdapter.address, largeAmount.toString());
+ console.log("ā
OFT Adapter approved");
+
+ // Verify the allowance
+ const oftAdapterAllowance = await fTestXRP.allowance(
+ signerAddress,
+ oftAdapter.address,
+ );
+ console.log(
+ " Verified allowance:",
+ formatUnits(oftAdapterAllowance.toString(), 6),
+ "FXRP",
+ );
+
+ console.log("\n2ļøā£ Approving FTestXRP for Composer...");
+ console.log(" Composer address:", CONFIG.COSTON2_COMPOSER);
+ await fTestXRP.approve(CONFIG.COSTON2_COMPOSER, amountToBridge.toString());
+ console.log("ā
Composer approved");
+
+ // Verify the allowance
+ const composerAllowance = await fTestXRP.allowance(
+ signerAddress,
+ CONFIG.COSTON2_COMPOSER,
+ );
+ console.log(
+ " Verified allowance:",
+ formatUnits(composerAllowance.toString(), 6),
+ "FXRP",
+ );
+
+ return oftAdapter;
+}
+
+/**
+ * Builds LayerZero send parameters
+ */
+function buildSendParams(params: BridgeParams): SendParams {
+ // See https://docs.layerzero.network/v2/tools/sdks/options#generating-options
+ const options = Options.newOptions().addExecutorLzReceiveOption(
+ CONFIG.EXECUTOR_GAS,
+ 0,
+ );
+ // Review send parameters here: https://docs.layerzero.network/v2/developers/evm/oft/oft-patterns-extensions#:~:text=Sending%20Token%E2%80%8B,composeMsg%20in%20bytes.
+ return {
+ dstEid: CONFIG.HYPERLIQUID_EID as EndpointId,
+ to: web3.utils.padLeft(params.recipientAddress, 64), // 32 bytes = 64 hex chars
+ amountLD: params.amountToBridge.toString(),
+ minAmountLD: params.amountToBridge.toString(),
+ extraOptions: options.toHex(),
+ composeMsg: "0x",
+ oftCmd: "0x",
+ };
+}
+
+/**
+ * Quotes the LayerZero fee for the bridge transaction
+ */
+async function quoteFee(
+ oftAdapter: FAssetOFTAdapterInstance,
+ sendParam: SendParams,
+): Promise {
+ const result = await oftAdapter.quoteSend(sendParam, false);
+ const nativeFee = web3.utils.toBN(result.nativeFee.toString());
+ console.log(
+ "\n3ļøā£ LayerZero Fee:",
+ formatUnits(nativeFee.toString(), 18),
+ "C2FLR",
+ );
+ return nativeFee;
+}
+
+/**
+ * Executes the bridge transaction
+ */
+async function executeBridge(
+ oftAdapter: FAssetOFTAdapterInstance,
+ sendParam: SendParams,
+ nativeFee: BN,
+ signerAddress: string,
+): Promise {
+ console.log("\n4ļøā£ Sending FXRP to Hyperliquid EVM Testnet...");
+
+ const tx = await oftAdapter.send(
+ sendParam,
+ { nativeFee: nativeFee.toString(), lzTokenFee: "0" },
+ signerAddress,
+ {
+ value: nativeFee.toString(),
+ },
+ );
+
+ console.log("Transaction sent:", tx.tx);
+ console.log("ā
Confirmed in block:", tx.receipt.blockNumber);
+
+ console.log(
+ "\nš Success! Your FXRP is on the way to Hyperliquid EVM Testnet!",
+ );
+ console.log("\nTrack your transaction:");
+ console.log(`https://testnet.layerzeroscan.com/tx/${tx.tx}`);
+ console.log(
+ "\nIt may take a few minutes to arrive on Hyperliquid EVM Testnet.",
+ );
+}
+
+async function main() {
+ // 1. Get signer and display account info
+ const signerAddress = await getSigner();
+
+ // 2. Prepare bridge parameters
+ const params = await prepareBridgeParams(signerAddress);
+
+ // 3. Check balance and get token contract
+ const fTestXRP = await checkBalance(params);
+
+ // 4. Approve tokens and get OFT adapter
+ const oftAdapter = await approveTokens(
+ fTestXRP,
+ params.amountToBridge,
+ signerAddress,
+ );
+
+ // 5. Build send parameters
+ const sendParam = buildSendParams(params);
+
+ // 6. Quote the fee
+ const nativeFee = await quoteFee(oftAdapter, sendParam);
+
+ // 7. Execute the bridge transaction
+ await executeBridge(oftAdapter, sendParam, nativeFee, signerAddress);
+}
+
+main().catch((error) => {
+ console.error(error);
+ process.exitCode = 1;
+});
diff --git a/examples/developer-hub-javascript/getOftPeers.ts b/examples/developer-hub-javascript/getOftPeers.ts
new file mode 100644
index 00000000..47d9c722
--- /dev/null
+++ b/examples/developer-hub-javascript/getOftPeers.ts
@@ -0,0 +1,129 @@
+/**
+ * Get all LayerZero peers for the FXRP OFT Adapter on Coston2
+ *
+ * Usage:
+ * yarn hardhat run scripts/layerzero/getOFTPeers.ts --network coston2
+ */
+
+import { web3 } from "hardhat";
+import { EndpointId } from "@layerzerolabs/lz-definitions";
+
+// FXRP OFT Adapter on Coston2
+const OFT_ADAPTER_ADDRESS = "0xCd3d2127935Ae82Af54Fc31cCD9D3440dbF46639";
+
+// Minimal OApp ABI for peers function
+const OAPP_ABI = [
+ {
+ inputs: [{ internalType: "uint32", name: "eid", type: "uint32" }],
+ name: "peers",
+ outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }],
+ stateMutability: "view",
+ type: "function",
+ },
+];
+
+// Get ALL V2 Testnet endpoints dynamically from the EndpointId enum
+function getAllV2TestnetEndpoints(): { name: string; eid: number }[] {
+ const endpoints: { name: string; eid: number }[] = [];
+
+ for (const [key, value] of Object.entries(EndpointId)) {
+ // Only include V2 testnet endpoints (they end with _V2_TESTNET and have numeric values)
+ if (key.endsWith("_V2_TESTNET") && typeof value === "number") {
+ // Convert key like "SEPOLIA_V2_TESTNET" to "Sepolia"
+ const name = key
+ .replace("_V2_TESTNET", "")
+ .split("_")
+ .map((word) => word.charAt(0) + word.slice(1).toLowerCase())
+ .join(" ");
+ endpoints.push({ name, eid: value });
+ }
+ }
+
+ // Sort by EID for consistent output
+ return endpoints.sort((a, b) => a.eid - b.eid);
+}
+
+const V2_TESTNET_ENDPOINTS = getAllV2TestnetEndpoints();
+
+const ZERO_BYTES32 =
+ "0x0000000000000000000000000000000000000000000000000000000000000000";
+
+async function main() {
+ console.log("=== FXRP OFT Adapter Peers Discovery ===\n");
+ console.log(`OFT Adapter: ${OFT_ADAPTER_ADDRESS}`);
+ console.log(`Network: Coston2 (Flare Testnet)\n`);
+
+ const oftAdapter = new web3.eth.Contract(OAPP_ABI, OFT_ADAPTER_ADDRESS);
+
+ const configuredPeers: { name: string; eid: number; peer: string }[] = [];
+ const errors: { name: string; eid: number; error: string }[] = [];
+
+ console.log(
+ `Scanning ${V2_TESTNET_ENDPOINTS.length} LayerZero V2 Testnet endpoints...\n`,
+ );
+
+ for (const endpoint of V2_TESTNET_ENDPOINTS) {
+ try {
+ const peer = await oftAdapter.methods.peers(endpoint.eid).call();
+
+ if (peer && peer !== ZERO_BYTES32) {
+ // Convert bytes32 to address (last 20 bytes)
+ const peerAddress = "0x" + peer.slice(-40);
+ configuredPeers.push({
+ name: endpoint.name,
+ eid: endpoint.eid,
+ peer: peerAddress,
+ });
+ console.log(
+ `ā
${endpoint.name} (EID: ${endpoint.eid}): ${peerAddress}`,
+ );
+ }
+ } catch (error: unknown) {
+ // Some endpoints might not exist or the contract might revert
+ const errorMessage =
+ error instanceof Error ? error.message.slice(0, 50) : "Unknown error";
+ errors.push({
+ name: endpoint.name,
+ eid: endpoint.eid,
+ error: errorMessage,
+ });
+ }
+ }
+
+ console.log("\n" + "=".repeat(60));
+ console.log("SUMMARY: Configured Peers");
+ console.log("=".repeat(60) + "\n");
+
+ if (configuredPeers.length === 0) {
+ console.log("No peers configured for the FXRP OFT Adapter.\n");
+ } else {
+ console.log(`Found ${configuredPeers.length} configured peer(s):\n`);
+
+ console.log("| Chain | EID | Peer Address |");
+ console.log("|-------|-----|--------------|");
+ for (const peer of configuredPeers) {
+ console.log(`| ${peer.name} | ${peer.eid} | ${peer.peer} |`);
+ }
+
+ console.log("\n--- Available Routes ---");
+ console.log("You can bridge FXRP to/from the following chains:\n");
+ for (const peer of configuredPeers) {
+ console.log(` ⢠${peer.name}`);
+ }
+ }
+
+ if (errors.length > 0) {
+ console.log(
+ `\n(${errors.length} endpoints had errors or are not available)`,
+ );
+ }
+
+ // Export as JSON for programmatic use
+ console.log("\n--- JSON Output ---");
+ console.log(JSON.stringify(configuredPeers, null, 2));
+}
+
+main().catch((error) => {
+ console.error(error);
+ process.exit(1);
+});
diff --git a/examples/developer-hub-solidity/FAssetRedeemComposer.sol b/examples/developer-hub-solidity/FAssetRedeemComposer.sol
new file mode 100644
index 00000000..c7e7e4c9
--- /dev/null
+++ b/examples/developer-hub-solidity/FAssetRedeemComposer.sol
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.25;
+
+import {IOAppComposer} from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol";
+import {OFTComposeMsgCodec} from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.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";
+
+contract FAssetRedeemComposer is IOAppComposer, Ownable, ReentrancyGuard {
+ using SafeERC20 for IERC20;
+
+ address public immutable endpoint;
+
+ event RedemptionTriggered(
+ address indexed redeemer,
+ string underlyingAddress,
+ uint256 indexed amountRedeemed,
+ uint256 indexed lots
+ );
+
+ error OnlyEndpoint();
+ error InsufficientBalance();
+ error AmountTooSmall();
+
+ constructor(address _endpoint) Ownable(msg.sender) {
+ endpoint = _endpoint;
+ }
+
+ receive() external payable {}
+
+ function lzCompose(
+ address /* _from */,
+ bytes32 /* _guid */,
+ bytes calldata _message,
+ address /* _executor */,
+ bytes calldata /* _extraData */
+ ) external payable override nonReentrant {
+ if (msg.sender != endpoint) revert OnlyEndpoint();
+
+ bytes memory composeMsg = OFTComposeMsgCodec.composeMsg(_message);
+ _processRedemption(composeMsg);
+ }
+
+ function _processRedemption(bytes memory composeMsg) internal {
+ // 1. Decode message
+ (, string memory underlyingAddress, address redeemer) = abi.decode(
+ composeMsg,
+ (uint256, string, address)
+ );
+
+ // 2. Get Asset Manager & fXRP Token from Registry
+ IAssetManager assetManager = ContractRegistry.getAssetManagerFXRP();
+ IERC20 fAssetToken = IERC20(address(assetManager.fAsset()));
+
+ // 3. Check Actual Balance received from LayerZero
+ uint256 currentBalance = fAssetToken.balanceOf(address(this));
+ if (currentBalance == 0) revert InsufficientBalance();
+
+ // 4. Calculate Lots
+ uint256 lotSizeUBA = assetManager.getSettings().lotSizeAMG;
+ uint256 lots = currentBalance / lotSizeUBA;
+
+ if (lots == 0) revert AmountTooSmall();
+
+ // 5. Calculate amount to burn
+ uint256 amountToRedeem = lots * lotSizeUBA;
+
+ // 6. Approve AssetManager to spend the tokens
+ fAssetToken.forceApprove(address(assetManager), amountToRedeem);
+
+ // 7. Redeem
+ uint256 redeemedAmount = assetManager.redeem(
+ lots,
+ underlyingAddress,
+ payable(address(0))
+ );
+
+ emit RedemptionTriggered(
+ redeemer,
+ underlyingAddress,
+ redeemedAmount,
+ lots
+ );
+ }
+
+ function recoverTokens(
+ address token,
+ address to,
+ uint256 amount
+ ) external onlyOwner {
+ IERC20(token).safeTransfer(to, amount);
+ }
+
+ function recoverNative() external onlyOwner {
+ payable(owner()).transfer(address(this).balance);
+ }
+}