> ## Documentation Index
> Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
> Use this file to discover all available pages before exploring further.

# LayerZero OFT Transfer API Usage Guide

> Use OFT Transfer API Usage Guide with LayerZero V2. Developer tools for building and debugging omnichain applications. LayerZero enables crosschain messaging.

A developer guide to using the LayerZero OFT Transfer API to simply orchestrate OFT transfers between chains.

## Overview

The LayerZero OFT Transfer API provides a simple way to fetch the calldata necessary to call the `send` implementation on any known OFT deployment. This guide demonstrates complete integration examples for both EVM and Solana chains, showing how the same API endpoints work across different blockchain environments.

**Key Benefits:**

* **Universal API**: Same endpoints work for EVM ↔ EVM, Solana ↔ EVM, and EVM ↔ Solana transfers
* **Chain-agnostic discovery**: Find tokens across all supported chains with a single API call
* **Pre-built transaction data**: Get ready-to-execute transaction data for any OFT transfer between chains
* **Built-in validation and error handling**
* **LayerZero transaction tracking**

## Crosschain Architecture

The LayerZero OFT API abstracts the complexity of crosschain transfers by providing the same interface regardless of source and destination chains.

Whether you're transferring from Ethereum to Solana or Solana to BSC, you use the same API endpoints with chain-specific transaction execution.

## Prerequisites

* Node.js 16+ and npm/yarn
* LayerZero API key ([request here](https://forms.monday.com/forms/c64c278b03d2b40a24e305943a743527?r=use1))
* Basic knowledge of the [OFT standard](../../concepts/applications/oft-standard)

## Installation

<CodeGroup>
  ```bash wrap EVM Chains theme={null}
  npm install ethers axios dotenv @layerzerolabs/lz-definitions
  ```

  ```bash wrap Solana theme={null}
  npm install @metaplex-foundation/umi-bundle-defaults @metaplex-foundation/umi @metaplex-foundation/umi-web3js-adapters @solana/web3.js axios bs58 dotenv @layerzerolabs/lz-definitions
  ```
</CodeGroup>

## Environment Setup

<Tabs>
  <Tab title="EVM Chains">
    Create a `.env` file in your project root:

    ```bash wrap theme={null}
    # Required for API access
    OFT_API_KEY=your-api-key-here

    # Required for executing transactions
    PRIVATE_KEY=your-private-key-here
    ```
  </Tab>

  <Tab title="Solana">
    Create a `.env` file in your project root:

    ```bash wrap theme={null}
    # Required for API access
    OFT_API_KEY=your-api-key-here

    # Required for executing transactions (base58 format)
    SOLANA_PRIVATE_KEY=your-solana-private-key-here

    # Optional: Custom RPC endpoint
    SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
    ```
  </Tab>
</Tabs>

## Understanding Chain Names

The LayerZero OFT API uses **chain names** to identify different blockchain networks. These chain names are standardized across the LayerZero ecosystem and can be imported from the `@layerzerolabs/lz-definitions` package for type safety.

### Available Chain Names

You can import chain names as constants to avoid typos and get TypeScript autocomplete:

```typescript wrap theme={null}
import {Chain} from '@layerzerolabs/lz-definitions';

// Examples of available chains
[Chain.ETHEREUM][Chain.BSC][Chain.ARBITRUM][Chain.ABSTRACT][Chain.BASE][Chain.OPTIMISM][ // "ethereum" // "bsc" // "arbitrum" // "abstract" // "base" // "optimism"
  Chain.POLYGON
]; // "polygon"
[Chain.SOLANA]; // "solana"
```

### Using Chain Names in API Calls

Chain names are used directly in API requests without any conversion needed:

```typescript wrap theme={null}
import {Chain} from '@layerzerolabs/lz-definitions';

// Use chain constants in API requests
const response = await axios.get(`${API_BASE_URL}/list`, {
  params: {chainNames: `${Chain.SOLANA},${Chain.BSC}`},
});
```

## LayerZero OFT API Endpoints

The OFT API provides two main endpoints for token operations:

### 1. Token Discovery API (`/list`)

**Purpose**: Discover available OFT tokens across chains and get their canonical contract addresses. Use this to find where a token is deployed and whether it's an OFT.

**Endpoint**: `GET https://metadata.layerzero-api.com/v1/metadata/experiment/ofts/list`

**Parameters**:

* `chainNames` (optional, string): Comma-separated list of chain names to search across
* `symbols` (optional, string): Comma-separated list of token symbols to filter by

**Common Usage Patterns**:

```typescript wrap theme={null}
import {Chain} from '@layerzerolabs/lz-definitions';

// 1. Find all deployments of a specific token (recommended approach)
const response = await axios.get(`${API_BASE_URL}/list`, {
  params: {symbols: 'PENGU'}, // Discovers PENGU on all available chains
});

// 2. Search for tokens on specific chains
const response = await axios.get(`${API_BASE_URL}/list`, {
  params: {
    chainNames: `${Chain.ABSTRACT},${Chain.BSC}`,
    symbols: 'PENGU,USDC',
  },
});

// 3. List all available OFTs (no filters)
const response = await axios.get(`${API_BASE_URL}/list`);
```

**Response Structure**:

```json wrap theme={null}
{
  "USDT0": [
    {
      "name": "USDT0",
      "sharedDecimals": 6,
      "endpointVersion": "v2",
      "deployments": {
        "ethereum": {
          "address": "0x6c96de32cea08842dcc4058c14d3aaad7fa41dee",
          "localDecimals": 6,
          "type": "OFT_ADAPTER",
          "innerToken": "0xdac17f958d2ee523a2206206994597c13d831ec7",
          "approvalRequired": true
        },
        "solana": {
          "address": "So11111111111111111111111111111111111111112",
          "localDecimals": 6,
          "type": "OFT",
          "approvalRequired": false
        }
      }
    }
  ]
}
```

**Key Response Fields**:

* `name`: Token display name
* `sharedDecimals`: Number of decimals used across all chains for LayerZero transfers
* `endpointVersion`: LayerZero endpoint version ("v2")
* `deployments`: Object with chain names as keys, containing deployment details for each chain
* `address`: The OFT contract address on that specific chain (Program ID for Solana)
* `localDecimals`: Number of decimals the token uses on that specific chain
* `type`: Contract type ("OFT\_ADAPTER" for wrapped tokens, "OFT" for native OFTs)
* `innerToken`: The underlying ERC20 token address (for OFT\_ADAPTER types, not applicable to Solana)
* `approvalRequired`: Whether token approval is required before transfers (always false for Solana)

<Info>
  ### Understanding Decimals

  For detailed explanations of `sharedDecimals` and `localDecimals` concepts, including the decimal conversion process and overflow considerations, see the [**OFT Technical Reference**](../../concepts/technical-reference/oft-reference#1-transferring-value-across-different-vms).
</Info>

**Using the Response**:

```typescript wrap theme={null}
const response = await axios.get(`${API_BASE_URL}/list`, {
  params: {symbols: 'PENGU'},
});

const tokenData = response.data['PENGU'][0];
const availableChains = Object.keys(tokenData.deployments);
console.log(`PENGU available on: ${availableChains.join(', ')}`);

// Get contract address for a specific chain
const contractAddress = tokenData.deployments['abstract'].address;
```

### 2. Transfer Transaction API (`/transfer`)

**Purpose**: Generate pre-built transaction data for executing OFT transfers from a source to destination network. The API returns chain-specific transaction data that can be executed using the appropriate blockchain SDK.

**Endpoint**: `GET https://metadata.layerzero-api.com/v1/metadata/experiment/ofts/transfer`

**Authentication Required**:

```typescript wrap theme={null}
headers: { 'x-layerzero-api-key': API_KEY }
```

**Parameters**:

* `srcChainName` (string): Source chain name (e.g., "solana", "ethereum", "bsc")
* `dstChainName` (string): Destination chain name (e.g., "ethereum", "bsc", "solana")
* `srcAddress` (string): Source chain OFT contract address or Program ID
* `amount` (string): Transfer amount in token's smallest unit
* `from` (string): Sender wallet address (public key for Solana)
* `to` (string): Recipient wallet address on destination chain
* `validate` (boolean): Pre-validate balances and parameters
* `options` (string, optional): Structured LayerZero execution options as JSON string

**Complete Example Workflow**:

```typescript wrap theme={null}
import {Chain} from '@layerzerolabs/lz-definitions';

// Step 1: Discover token deployments
const listResponse = await axios.get(`${API_BASE_URL}/list`, {
  params: {symbols: 'PENGU'},
});
const tokenData = listResponse.data['PENGU'][0];

// Step 2: Choose your transfer route from available deployments
const fromChain = Chain.ABSTRACT;
const toChain = Chain.BSC;
const contractAddress = tokenData.deployments[fromChain].address;

// Step 3: Get transfer calldata
const transferResponse = await axios.get(`${API_BASE_URL}/transfer`, {
  params: {
    srcChainName: fromChain, // "abstract"
    dstChainName: toChain, // "bsc"
    srcAddress: contractAddress, // Contract address from /list
    amount: '1000000000000000000', // 1 token (18 decimals)
    from: wallet.address,
    to: wallet.address,
    validate: true,
  },
  headers: {'x-layerzero-api-key': API_KEY},
});
```

**Response Structure**:

<CodeGroup>
  ```json wrap EVM Response theme={null}
  {
    "transactionData": {
      "populatedTransaction": {
        "to": "0x...",
        "data": "0x...",
        "value": "0x...",
        "gasLimit": "0x..."
      },
      "approvalTransaction": {
        "to": "0x...",
        "data": "0x...",
        "gasLimit": "0x..."
      }
    }
  }
  ```

  ```json wrap Solana Response theme={null}
  {
    "transactionData": {
      "populatedTransaction": "base64-encoded-versioned-transaction"
    }
  }
  ```
</CodeGroup>

**Key Response Fields**:

* `populatedTransaction`: The main transfer transaction ready to be sent via `wallet.sendTransaction()`
* `approvalTransaction`: Token approval transaction (if required for OFT adapters)
* Both transactions contain pre-built calldata and gas estimates

**Executing the Transactions**:

```typescript wrap theme={null}
const {transactionData} = transferResponse.data;

// Step 4: Configure RPC for the source chain to execute transactions
const RPC_URLS = {
  [Chain.ABSTRACT]: 'https://api.mainnet.abs.xyz',
  [Chain.BSC]: 'https://bsc.drpc.org',
  // Add other chains as needed
};

const provider = new ethers.providers.JsonRpcProvider(RPC_URLS[fromChain]);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

// Step 5: Execute approval if needed (for OFT adapters)
if (transactionData.approvalTransaction) {
  const approvalTx = await wallet.sendTransaction(transactionData.approvalTransaction);
  await approvalTx.wait();
}

// Step 6: Execute the transfer
const transferTx = await wallet.sendTransaction(transactionData.populatedTransaction);
await transferTx.wait();
```

### (Optional) Add extraOptions

For advanced use cases, you can include LayerZero execution options to extend the base OFT functionality. The `options` parameter allows you to specify additional gas limits, native token drops, and compose message settings.

**Example with extraOptions**:

```typescript wrap theme={null}
const transferResponse = await axios.get(`${API_BASE_URL}/transfer`, {
  params: {
    srcChainName: fromChain,
    dstChainName: toChain,
    srcAddress: contractAddress,
    amount: '1000000000000000000',
    from: wallet.address,
    to: wallet.address,
    validate: true,
    // Optional: Add extra execution options
    options: JSON.stringify({
      executor: {
        lzReceive: {
          gasLimit: 300000, // Extra gas for complex lzReceive logic
        },
        nativeDrops: [
          {
            amount: '1000000000000000', // 0.001 ETH in wei
            receiver: '0xd8538fa8fdd5872e68c4040449f64452ae536fa6',
          },
        ],
      },
    }),
  },
  headers: {'x-layerzero-api-key': API_KEY},
});
```

**How extraOptions work**:

* **`lzReceive` gas limit**: The gas you specify here is **added to** the base gas limit already set by the OFT deployer. For example, if the OFT enforces 65,000 gas and you add 35,000, the total execution will have 100,000 gas available.

* **`nativeDrops`**: Allows you to send native chain currency (ETH, MATIC, BNB, etc.) to any receiver wallet address alongside your token transfer. The amount is specified in wei and sent directly to the specified receiver address.

* **`composeOptions`**: Used specifically for [omnichain composers](../../concepts/applications/composer-standard) when your OFT transfer triggers additional smart contract logic on the destination chain. See the [EVM Composer Overview](../../developers/evm/composer/overview) for implementation details.

* For detailed information about LayerZero message options, see [Message Options](../../concepts/message-options) and [Message Execution Options](../sdks/options).

### Chain Name Reference

Here are common chain names available in the `@layerzerolabs/lz-definitions` package:

| Chain Constant   | Chain Name | Network          |
| ---------------- | ---------- | ---------------- |
| `Chain.ETHEREUM` | `ethereum` | Ethereum Mainnet |
| `Chain.BSC`      | `bsc`      | BNB Smart Chain  |
| `Chain.ARBITRUM` | `arbitrum` | Arbitrum One     |
| `Chain.OPTIMISM` | `optimism` | Optimism         |
| `Chain.BASE`     | `base`     | Base             |
| `Chain.POLYGON`  | `polygon`  | Polygon          |
| `Chain.ABSTRACT` | `abstract` | Abstract         |

**Usage Tips**:

* Import `Chain` constants to avoid typos and get autocomplete
* Use `/list` API without chain filters to discover all supported chains
* If you see a chain missing, make sure your `@layerzerolabs/lz-definitions` package is updated to the latest version
* Check the [LayerZero API reference](/v2/developers/evm/api/oft-api) for the complete list of supported chains

## Complete Transfer Examples

<Tabs>
  <Tab title="EVM to EVM">
    ### Example: Send \$PENGU from BSC to Abstract

    ```typescript wrap theme={null}
    import {Chain} from '@layerzerolabs/lz-definitions';
    import {ethers} from 'ethers';
    import axios from 'axios';
    import 'dotenv/config';

    /**
     * LayerZero OFT Transfer API - Ethers.js Integration Example
     *
     * This example demonstrates how to integrate the LayerZero OFT Transfer API
     * to execute OFT transfers using ethers.js and TypeScript.
     *
     * How it works:
     * 1. **Token Discovery**: Uses the LayerZero API to discover tokens
     *    across multiple chains and get their canonical contract addresses
     *
     * 2. **Fetching Transaction Data**: Requests pre-built transaction data from the
     *    LayerZero API instead of manually encoding contract calls
     *
     * 3. **Executing the Transaction**: Executes the transaction using the provider wallet
     *
     * 4. **LayerZero Scan Tracking**: Provides LayerZero scan links to track the
     *    complete crosschain journey of transfers
     *
     * Key Benefits:
     * - No need to understand complex LayerZero contract interfaces
     * - Built-in validation and error handling from the API
     * - Automatic gas estimation and fee calculation
     * - Handling for both OFTs and OFT Adapters
     * - Real-time transaction tracking across chains
     *
     */

    // Configuration
    const API_KEY = process.env.OFT_API_KEY!;
    const API_BASE_URL = 'https://metadata.layerzero-api.com/v1/metadata/experiment/ofts';

    // RPC configuration using chain names from LayerZero API
    // RPCs are required only for sending the /transfer transaction, not for /list
    const RPC_URLS: Record<string, string> = {
      [Chain.BSC]: 'https://bsc.drpc.org',
      [Chain.ABSTRACT]: 'https://api.mainnet.abs.xyz',
    };

    /**
     * Get the appropriate RPC URL for a chain name.
     * Chain names come directly from LayerZero API discovery.
     */
    function getRpcUrl(chainName: string): string {
      const url = RPC_URLS[chainName];
      if (!url) {
        throw new Error(`RPC URL not configured for chain: ${chainName}`);
      }
      return url;
    }

    /**
     * Discover where a token is available across chains.
     * This is the typical user flow - start with a token symbol and see where it's deployed.
     */
    async function discoverTokenDeployments(symbol: string) {
      try {
        console.log('🔍 Token Discovery:');
        console.log(`  Searching for ${symbol} deployments...`);

        const response = await axios.get(`${API_BASE_URL}/list`, {
          params: {symbols: symbol},
        });

        const tokenData = response.data[symbol]?.[0];
        if (!tokenData) {
          throw new Error(`Token ${symbol} not found`);
        }

        const availableChains = Object.keys(tokenData.deployments);
        console.log(`  ✓ ${symbol} found on: ${availableChains.join(', ')}`);
        console.log();

        return response.data;
      } catch (error: any) {
        console.error('Error discovering token:', error.response?.data || error.message);
        throw error;
      }
    }

    /**
     * Extract the OFT contract address for a token on a specific chain.
     * Prevents address lookup errors and handles chain-specific deployments.
     */
    function getOftAddress(tokens: any, symbol: string, chainName: string): string {
      const tokenData = tokens[symbol]?.[0];
      if (!tokenData) {
        throw new Error(`Token ${symbol} not found`);
      }

      const address = tokenData.deployments[chainName]?.address;
      if (!address) {
        throw new Error(`Token ${symbol} not available on ${chainName}`);
      }

      return address;
    }

    /**
     * Generate LayerZero scan links for crosschain transaction tracking.
     * LayerZero scan shows the complete crosschain journey, unlike regular block explorers.
     */
    function getLayerZeroScanLink(hash: string): string {
      // For simplicity, always use mainnet scan since we're working with mainnet chains
      return `https://layerzeroscan.com/tx/${hash}`;
    }

    /**
     * Execute an OFT token transfer using the LayerZero API.
     * Uses chain names discovered from the LayerZero API.
     */
    async function transferOFT(fromChain: string, toChain: string, oftAddress: string, amount: string) {
      try {
        console.log('🚀 OFT Transfer:');
        console.log(`  Route: ${fromChain} → ${toChain}`);
        console.log();

        // Get RPC URL for the source chain
        const rpcUrl = getRpcUrl(fromChain);

        // Initialize wallet on the source chain
        const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
        const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);

        // Check wallet balance
        const balance = await wallet.getBalance();

        console.log('Wallet Information:');
        console.log(`  Address: ${wallet.address}`);
        console.log(`  Balance: ${ethers.utils.formatEther(balance)} native tokens`);
        console.log();

        // Request pre-built transaction data from LayerZero API
        console.log('Transaction Preparation:');
        console.log('  Requesting transaction data from LayerZero API...');

        const response = await axios.get(`${API_BASE_URL}/transfer`, {
          params: {
            srcChainName: fromChain,
            dstChainName: toChain,
            srcAddress: oftAddress, // Source chain OFT contract address
            amount, // Amount in token's smallest unit (wei)
            from: wallet.address,
            to: wallet.address, // Same address on destination chain
            validate: true, // Pre-validate balances and parameters
          },
          headers: {'x-layerzero-api-key': API_KEY},
        });

        const {transactionData} = response.data;
        console.log('  ✓ Transaction data prepared');
        console.log();

        // Handle token approval if required (for OFT adapters wrapping existing ERC20s)
        if (transactionData.approvalTransaction) {
          console.log('Token Approval:');
          console.log('  Sending approval transaction...');
          const approvalTx = await wallet.sendTransaction(transactionData.approvalTransaction);
          await approvalTx.wait();
          console.log('  ✓ Approval confirmed');
          console.log();
        }

        // Execute the omnichain transfer transaction (includes LayerZero messaging fees)
        console.log('OFT Transfer:');
        console.log('  Sending transfer transaction...');
        const transferTx = await wallet.sendTransaction(transactionData.populatedTransaction);
        await transferTx.wait();
        console.log('  ✓ Transaction confirmed');
        console.log();

        // Provide tracking link for the complete crosschain journey
        const scanLink = getLayerZeroScanLink(transferTx.hash);

        console.log('🎉 Transaction Results:');
        console.log(`  LayerZero Scan: ${scanLink}`);
        console.log();

        return transferTx.hash;
      } catch (error: any) {
        // Extract meaningful error messages from API responses
        if (error.response?.data) {
          throw new Error(`API Error: ${JSON.stringify(error.response.data)}`);
        }
        throw error;
      }
    }

    /**
     * Example: OFT transfer
     *
     * Demonstrates the complete integration flow:
     * 1. Use explicit LayerZero chain names for chain identification
     * 2. Dynamically discover contract addresses
     * 3. Execute transfers with automatic approval handling
     * 4. Provide LayerZero transaction tracking
     */
    async function main() {
      try {
        console.log('LayerZero OFT API - Ethers.js Example');
        console.log('==========================================\n');

        // Discover where PENGU is available and choose transfer route
        const tokens = await discoverTokenDeployments('PENGU');

        // Choose source and destination chains from available deployments
        const fromChain = 'abstract';
        const toChain = 'bsc';

        // Extract the source chain OFT contract address (always use FROM chain address)
        const oftAddress = getOftAddress(tokens, 'PENGU', fromChain);
        console.log(`OFT contract: ${oftAddress}`);
        console.log();

        // Execute the crosschain transfer using chain names from API
        await transferOFT(
          fromChain, // Source chain name
          toChain, // Destination chain name
          oftAddress, // Source chain OFT contract address
          '1000000000000', // Amount in token's smallest unit
        );

        console.log('Example completed successfully!');
      } catch (error: any) {
        // Handle errors gracefully with actionable feedback
        if (error.response?.data) {
          console.error('API Error:', error.response.data);
        } else if (error.message) {
          console.error('Error:', error.message);
        } else {
          console.error('Unknown error:', error);
        }
      }
    }

    // Export functions for use in other scripts
    export {discoverTokenDeployments, transferOFT, getRpcUrl, getOftAddress};

    // Run main function if this script is executed directly
    if (require.main === module) {
      main();
    }
    ```
  </Tab>

  <Tab title="Solana to EVM">
    ### Example: Send \$PENGU from Solana to BSC

    ```typescript wrap theme={null}
    import {createUmi} from '@metaplex-foundation/umi-bundle-defaults';
    import {createSignerFromKeypair, signerIdentity} from '@metaplex-foundation/umi';
    import {fromWeb3JsKeypair} from '@metaplex-foundation/umi-web3js-adapters';
    import {Connection, Keypair, VersionedTransaction} from '@solana/web3.js';
    import axios from 'axios';
    import bs58 from 'bs58';
    import 'dotenv/config';

    /**
     * LayerZero OFT Transfer API - Solana to EVM Example
     *
     * This example demonstrates sending PENGU tokens from Solana to any EVM chain
     * using the LayerZero OFT Transfer API and Solana web3.js for transaction handling.
     *
     * Key Differences from EVM:
     * 1. **Transaction Format**: Solana returns base64-encoded VersionedTransaction
     * 2. **No Approval Required**: Solana doesn't require separate approval transactions
     * 3. **Different Signing**: Uses Solana keypairs instead of private key strings
     * 4. **Program IDs**: Uses Solana Program IDs instead of contract addresses
     */

    // Configuration
    const API_KEY = process.env.OFT_API_KEY!;
    const API_BASE_URL = 'https://metadata.layerzero-api.com/v1/metadata/experiment/ofts';
    const SOLANA_RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';

    /**
     * Initialize Solana connection and wallet
     */
    function initializeSolana() {
      const connection = new Connection(SOLANA_RPC_URL);

      // Load wallet from private key (base58 format)
      const privateKeyString = process.env.SOLANA_PRIVATE_KEY!;
      if (!privateKeyString) {
        throw new Error('SOLANA_PRIVATE_KEY environment variable is required (base58 format)');
      }

      // Create keypair from base58 private key
      const privateKeyBytes = bs58.decode(privateKeyString);
      const keypair = Keypair.fromSecretKey(privateKeyBytes);

      return {connection, keypair};
    }

    /**
     * Discover PENGU token deployments (same API as EVM)
     */
    async function discoverTokenDeployments(symbol: string) {
      try {
        console.log('🔍 Token Discovery:');
        console.log(`  Searching for ${symbol} deployments...`);

        const response = await axios.get(`${API_BASE_URL}/list`, {
          params: {symbols: symbol},
        });

        const tokenData = response.data[symbol]?.[0];
        if (!tokenData) {
          throw new Error(`Token ${symbol} not found`);
        }

        const availableChains = Object.keys(tokenData.deployments);
        console.log(`  ✓ ${symbol} found on: ${availableChains.join(', ')}`);
        console.log();

        return response.data;
      } catch (error: any) {
        console.error('Error discovering token:', error.response?.data || error.message);
        throw error;
      }
    }

    /**
     * Send PENGU from Solana to EVM chain using LayerZero API
     */
    async function transferFromSolana(
      tokens: any,
      destinationChain: string,
      amount: string,
      recipientAddress: string,
    ) {
      try {
        console.log('🚀 OFT Transfer:');
        console.log(`  Route: solana → ${destinationChain}`);
        console.log();

        // Initialize Solana connection and wallet
        const {connection, keypair} = initializeSolana();

        // Check wallet balance
        const balance = await connection.getBalance(keypair.publicKey);
        console.log('Wallet Information:');
        console.log(`  Address: ${keypair.publicKey.toString()}`);
        console.log(`  SOL Balance: ${(balance / 1e9).toFixed(4)} SOL`);
        console.log();

        // Get Solana PENGU Program ID
        const solanaOftAddress = tokens.PENGU[0].deployments.solana.address;

        // Request transaction data from LayerZero API (same API as EVM)
        console.log('Transaction Preparation:');
        console.log('  Requesting transaction data from LayerZero API...');

        const response = await axios.get(`${API_BASE_URL}/transfer`, {
          params: {
            srcChainName: 'solana',
            dstChainName: destinationChain,
            srcAddress: solanaOftAddress,
            amount,
            from: keypair.publicKey.toString(),
            to: recipientAddress,
            validate: true,
          },
          headers: {'x-layerzero-api-key': API_KEY},
        });

        const {transactionData} = response.data;
        console.log('  ✓ Transaction data received from API');
        console.log();

        // Key Difference: Solana transaction execution
        if (transactionData.populatedTransaction) {
          console.log('Transaction Execution:');
          console.log('  Deserializing Solana transaction...');

          // Deserialize the base64-encoded VersionedTransaction
          const transactionBuffer = Buffer.from(transactionData.populatedTransaction, 'base64');
          const transaction = VersionedTransaction.deserialize(transactionBuffer);

          console.log('  Signing and sending transaction...');

          // Sign transaction with Solana keypair
          transaction.sign([keypair]);

          // Send transaction to Solana network
          const signature = await connection.sendRawTransaction(transaction.serialize());

          // Wait for confirmation
          console.log('  Waiting for confirmation...');
          await connection.confirmTransaction(signature, 'confirmed');

          console.log('  ✓ Transaction confirmed on Solana');
          console.log();

          // Provide tracking links
          console.log('🎉 Transfer Initiated:');
          console.log(`  Solana Transaction: https://solscan.io/tx/${signature}`);
          console.log(`  LayerZero Scan: https://layerzeroscan.com/tx/${signature}`);
          console.log();

          return signature;
        } else {
          throw new Error('API did not return Solana transaction data');
        }
      } catch (error: any) {
        if (error.response?.data) {
          console.error('API Error:', JSON.stringify(error.response.data, null, 2));
        } else {
          console.error('Error:', error.message);
        }
        throw error;
      }
    }

    /**
     * Main example: Send PENGU from Solana to BSC
     */
    async function main() {
      try {
        console.log('LayerZero OFT API - Solana to EVM Example');
        console.log('==========================================\n');

        // Step 1: Discover PENGU token (same API as EVM)
        const tokens = await discoverTokenDeployments('PENGU');

        // Step 2: Choose destination and set transfer parameters
        const destinationChain = 'bsc';
        const recipientAddress = '0x742d35Cc6634C0532925a3b8D45A5E6e8b4b5Bca'; // Replace with your address
        const amount = '1000000000000'; // Amount in smallest units

        // Step 3: Execute the transfer
        await transferFromSolana(tokens, destinationChain, amount, recipientAddress);

        console.log('PENGU transfer example completed successfully!');
      } catch (error: any) {
        console.error('\n❌ Transfer failed:');
        if (error.response?.data) {
          console.error('API Response:', JSON.stringify(error.response.data, null, 2));
        } else {
          console.error('Error:', error.message);
        }
      }
    }

    // Export functions for use in other scripts
    export {discoverTokenDeployments, transferFromSolana, initializeSolana};

    // Run main function if this script is executed directly
    if (require.main === module) {
      main();
    }
    ```
  </Tab>
</Tabs>

## Expected Output

<Tabs>
  <Tab title="EVM Transfer">
    After running the EVM example, you should see:

    ```bash wrap theme={null}
    ✗ npx ts-node scripts/testOFTAPI.ts

    LayerZero OFT API - Ethers.js Example
    ==========================================

    🔍 Token Discovery:
      Searching for PENGU deployments...
      ✓ PENGU found on: abstract, bsc, ethereum, solana

    OFT contract: 0x9ebe3a824ca958e4b3da772d2065518f009cba62

    🚀 OFT Transfer:
      Route: abstract → bsc

    Wallet Information:
      Address: 0xed422098669cBB60CAAf26E01485bAFdbAF9eBEA
      Balance: 0.009736997308229952 native tokens

    Transaction Preparation:
      Requesting transaction data from LayerZero API...
      ✓ Transaction data prepared

    OFT Transfer:
      Sending transfer transaction...
      ✓ Transaction confirmed

    🎉 Transaction Results:
      LayerZero Scan: https://layerzeroscan.com/tx/0x49c44f1ff5ab82ceaeee6c780e991863d75ad544cb6123583c2e335b314b77ab

    Example completed successfully!
    ```
  </Tab>

  <Tab title="Solana Transfer">
    After running the Solana example, you should see:

    ```bash wrap theme={null}
    ✗ npx ts-node scripts/testSolanaOFTAPI.ts

    LayerZero OFT API - Solana to EVM Example
    ==========================================

    🔍 Token Discovery:
      Searching for PENGU deployments...
      ✓ PENGU found on: abstract, bsc, ethereum, solana

    🚀 OFT Transfer:
      Route: solana → bsc

    Wallet Information:
      Address: 7BgBvyjrZX1YKz4oh9mjb8XScatufuNqPH7YLyRWCATp
      SOL Balance: 0.0421 SOL

    Transaction Preparation:
      Requesting transaction data from LayerZero API...
      ✓ Transaction data received from API

    Transaction Execution:
      Deserializing Solana transaction...
      Signing and sending transaction...
      Waiting for confirmation...
      ✓ Transaction confirmed on Solana

    🎉 Transfer Initiated:
      Solana Transaction: https://solscan.io/tx/2xK8vKv7...
      LayerZero Scan: https://layerzeroscan.com/tx/2xK8vKv7...

    PENGU transfer example completed successfully!
    ```
  </Tab>
</Tabs>

## Common Issues & Solutions

### Amount Validation Errors

**Error:**

```
API Error: {"code":4000,"message":"Amount Invalid. Config: {\"sharedDecimals\":6,\"localDecimals\":18,\"currentAmount\":\"1000\",\"minAmount\":\"1000000000000\"}"}
```

**Cause:** This error occurs due to the decimal conversion rate between `localDecimals` and `sharedDecimals`. The OFT standard enforces that transfer amounts must be greater than or equal to the decimal conversion rate to prevent precision loss when transferring tokens between blockchains.

The `minAmount` in the error response represents the decimal conversion rate: `10^(localDecimals - sharedDecimals)`. In this example: `10^(18-6) = 10^12 = 1000000000000`.

**Solution:** Ensure your amount (in minor units) is greater than or equal to the decimal conversion rate:

```typescript wrap theme={null}
// ❌ Wrong - amount smaller than conversion rate
const amount = '1000'; // Less than 10**(localDecimals - sharedDecimals)

// ✅ Correct - amount meets minimum conversion rate requirement
const amount = '1000000000000'; // Exactly 10**(localDecimals - sharedDecimals) (minimum)
const amount = '5000000000000000000'; // 5 tokens (5*10**localDecimals)
```

For detailed explanation of how `sharedDecimals` and `localDecimals` work together to enforce minimum transfer amounts, see the [OFT Technical Reference](../../concepts/technical-reference/oft-reference#1-transferring-value-across-different-vms).

### Insufficient Balance

**Error:**

```
API Error: {"code":4000,"message":"Insufficient OFT balance: 114000000000000 < 100000000000000000000000"}
```

**Cause:** This error occurs when the API validates that your wallet doesn't have enough OFT tokens to perform the requested transfer. The error message shows your current balance vs. the requested transfer amount (both in the token's smallest unit).

In this example: `114000000000000` (current balance) \< `100000000000000000000000` (requested amount).

**Solution:** Check both native token (for fees) and token balances:

```typescript wrap theme={null}
// Check native balance for gas fees
const nativeBalance = await wallet.getBalance();
console.log(`Native balance: ${ethers.utils.formatEther(nativeBalance)}`);

// Check token balance
const tokenContract = new ethers.Contract(
  tokenAddress,
  ['function balanceOf(address) view returns (uint256)'],
  provider,
);
const tokenBalance = await tokenContract.balanceOf(wallet.address);
console.log(`Token balance: ${tokenBalance.toString()}`);

// Ensure your transfer amount is <= your token balance
const transferAmount = '1000000000000000000'; // 1 token with 18 decimals
if (tokenBalance.lt(transferAmount)) {
  console.error('Insufficient token balance for transfer');
}
```

### Network Not Supported

**Error:** `Unsupported source chain: chainName`

**Solution:** Ensure the chain is configured in your RPC\_URLS mapping and supported by the API.

***

*By using the OFT API, you agree to the [OFT API Terms of Use](./terms).*
