> ## 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.

# Solana Integration

> Complete TypeScript example for cross-chain Solana transfers using the Value Transfer API.

This example demonstrates a complete cross-chain transfer from Solana to Arbitrum using TypeScript and the Solana Web3.js library.

## Prerequisites

* Node.js 18+
* An API key from LayerZero
* A funded Solana wallet with the token you want to transfer

## Installation

<Tabs>
  <Tab title="pnpm">
    ```bash wrap theme={null}

        pnpm add @solana/web3.js bs58 dotenv

    ```
  </Tab>

  <Tab title="npm">
    ```bash wrap theme={null}

        npm install @solana/web3.js bs58 dotenv

    ```
  </Tab>

  <Tab title="yarn">
    ```bash wrap theme={null}

        yarn add @solana/web3.js bs58 dotenv

    ```
  </Tab>
</Tabs>

## Environment setup

Create a `.env` file in your project root:

```bash wrap theme={null}

    VT_API_KEY=your_api_key_here
    SOLANA_PRIVATE_KEY=your_base58_private_key_here

```

***

<Steps>
  <Step title="Validate transfer path">
    Before requesting a quote, verify that the destination token is reachable from your source token.

    Query the tokens endpoint with filters to check if your destination exists in the list of transferrable tokens:

    <Tabs>
      <Tab title="TypeScript">
        ```typescript wrap theme={null}
        const response = await fetch(
          'https://transfer.layerzero-api.com/v1/tokens?' +
            new URLSearchParams({
              transferrableFromChainKey: 'solana',
              transferrableFromTokenAddress: 'CAW777xcHVTQZ4CRwVQGB8CV1BVKPm5bNVxFJHWFKiH8',
            }),
        );

        const {tokens} = await response.json();

        // Check if destination token exists in reachable tokens
        const isSupported = tokens.some(
          (t) => t.chainKey === 'arbitrum' && t.address === '0x16f1967565aaD72DD77588a332CE445e7cEF752b',
        );

        if (!isSupported) {
          throw new Error('Transfer path not supported');
        }

        console.log(`Found ${tokens.length} reachable destinations`);
        ```
      </Tab>
    </Tabs>

    <Tip>
      **Why validate first?** Validating the transfer path before requesting quotes prevents unnecessary API calls and provides immediate feedback if a route doesn't exist.
    </Tip>
  </Step>

  <Step title="Get quotes">
    Request a quote for your cross-chain transfer. The API returns available routes with fees, estimated duration, and quote ID.

    <Tabs>
      <Tab title="TypeScript">
        ```typescript wrap theme={null}
        const quoteResponse = await fetch('https://transfer.layerzero-api.com/v1/quotes', {
          method: 'POST',
          headers: {
            'x-api-key': 'YOUR_API_KEY',
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            srcChainKey: 'solana',
            dstChainKey: 'arbitrum',
            srcTokenAddress: 'CAW777xcHVTQZ4CRwVQGB8CV1BVKPm5bNVxFJHWFKiH8',
            dstTokenAddress: '0x16f1967565aaD72DD77588a332CE445e7cEF752b',
            srcWalletAddress: 'YourSolanaPublicKey',
            dstWalletAddress: '0xYourEVMWallet',
            amount: '1000000000000',
            options: {
              amountType: 'EXACT_SRC_AMOUNT',
              feeTolerance: {type: 'PERCENT', amount: 2},
            },
          }),
        });

        const {quotes} = await quoteResponse.json();
        const quote = quotes[0];

        console.log('Quote ID:', quote.id);
        console.log('Route:', quote.routeSteps[0].type);
        console.log('Fee:', quote.feeUsd, 'USD');
        ```
      </Tab>
    </Tabs>

    **Quote response structure:**

    ```typescript wrap theme={null}

        {
          id: '0x0000...7a', // hex quote ID
          routeSteps: [
            {
              type: 'OFT_V2',
              srcChainKey: 'solana',
              description: 'Transfer OFT from Solana to Arbitrum',
            },
          ],
          feeUsd: '0.15',
          feePercent: '0.015',
          srcAmount: '1000000000000',
          dstAmount: '999850000000',
          duration: {estimated: '120000'},
        }

    ```
  </Step>

  <Step title="Build and execute user steps">
    For Solana transfers, call `/build-user-steps` to get fresh transaction data, then sign and submit the transaction.

    ### Build user steps

    Solana transactions have short blockhash validity, so you must generate transaction data immediately before signing:

    <Tabs>
      <Tab title="TypeScript">
        ```typescript wrap theme={null}
        const stepsResponse = await fetch('https://transfer.layerzero-api.com/v1/build-user-steps', {
          method: 'POST',
          headers: {
            'x-api-key': 'YOUR_API_KEY',
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({quoteId: quote.id}),
        });

        const {userSteps} = await stepsResponse.json();
        ```
      </Tab>
    </Tabs>

    **User steps response:**

    ```typescript wrap theme={null}
    [
      {
        type: 'TRANSACTION',
        chainKey: 'solana',
        chainType: 'SOLANA',
        description: 'Send OFT tokens via LayerZero',
        signerAddress: 'YourSolanaPublicKey',
        transaction: {
          encoded: {
            encoding: 'base64',
            data: 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDAf...',
          },
        },
      },
    ];
    ```

    ### Execute the transaction

    Deserialize, sign, and submit the transaction using Solana Web3.js:

    <Tabs>
      <Tab title="TypeScript">
        ```typescript wrap theme={null}
        import * as web3 from '@solana/web3.js';
        import bs58 from 'bs58';

        const connection = new web3.Connection(web3.clusterApiUrl('mainnet-beta'), 'confirmed');

        // Parse private key (supports hex or base58)
        function parseSolanaSecretKey(raw: string): Uint8Array {
          const isHex = /^0x[0-9a-fA-F]+$/.test(raw) || /^[0-9a-fA-F]+$/.test(raw);
          if (isHex) {
            const hex = raw.replace(/^0x/, '');
            return new Uint8Array(Buffer.from(hex, 'hex'));
          }
          return bs58.decode(raw);
        }

        const keypair = web3.Keypair.fromSecretKey(parseSolanaSecretKey(process.env.SOLANA_PRIVATE_KEY!));

        let lastSignature;

        for (const step of userSteps) {
          if (step.type !== 'TRANSACTION') continue;

          const tx = step.transaction.encoded;
          if (tx.encoding !== 'base64') continue;

          // Deserialize the transaction
          const raw = Buffer.from(tx.data, 'base64');
          const vtx = web3.VersionedTransaction.deserialize(new Uint8Array(raw));

          // Sign the transaction
          vtx.sign([keypair]);

          // Send the transaction
          lastSignature = await connection.sendTransaction(vtx);
          console.log('Transaction sent:', lastSignature);

          // Wait for confirmation
          const latest = await connection.getLatestBlockhash();
          await connection.confirmTransaction({
            signature: lastSignature,
            blockhash: latest.blockhash,
            lastValidBlockHeight: latest.lastValidBlockHeight,
          });
          console.log('Transaction confirmed');
        }
        ```
      </Tab>
    </Tabs>

    <Info>
      **Solana requires build-user-steps:** Unlike EVM chains where `userSteps` are included in the quote response, Solana requires calling `/build-user-steps` to generate fresh transaction data with a valid blockhash.
    </Info>
  </Step>

  <Step title="Track transfer status">
    Poll the status endpoint until the transfer completes. The API returns the current status and explorer link.

    <Tabs>
      <Tab title="TypeScript">
        ```typescript wrap theme={null}
        async function pollStatus(quoteId: string, txSignature?: string) {
          const deadline = Date.now() + 5 * 60_000; // 5 minute timeout

          while (Date.now() < deadline) {
            const query = txSignature ? `?txHash=${encodeURIComponent(txSignature)}` : '';
            const response = await fetch(
              `https://transfer.layerzero-api.com/v1/status/${encodeURIComponent(quoteId)}${query}`,
              {headers: {'x-api-key': 'YOUR_API_KEY'}},
            );

            const {status, explorerUrl} = await response.json();
            console.log('Status:', status);

            if (status === 'SUCCEEDED') {
              console.log('Transfer complete!');
              console.log('Explorer:', explorerUrl);
              return status;
            }

            if (status === 'FAILED' || status === 'UNKNOWN') {
              throw new Error(`Transfer ${status.toLowerCase()}`);
            }

            await new Promise((r) => setTimeout(r, 4000));
          }

          throw new Error('Transfer timed out');
        }

        await pollStatus(quote.id, lastSignature);
        ```
      </Tab>
    </Tabs>

    **Status values:**

    | Status       | Description                                  |
    | ------------ | -------------------------------------------- |
    | `PENDING`    | Transfer initiated, waiting for confirmation |
    | `PROCESSING` | Transfer in progress across chains           |
    | `SUCCEEDED`  | Transfer completed successfully              |
    | `FAILED`     | Transfer failed                              |
    | `UNKNOWN`    | Status cannot be determined                  |
  </Step>
</Steps>

***

## Complete example

```typescript wrap theme={null}
import * as web3 from '@solana/web3.js';
import bs58 from 'bs58';
import * as dotenv from 'dotenv';
dotenv.config();

const API = 'https://transfer.layerzero-api.com/v1';
const API_KEY = process.env.VT_API_KEY!;
const PRIVATE_KEY = process.env.SOLANA_PRIVATE_KEY!;

const connection = new web3.Connection(web3.clusterApiUrl('mainnet-beta'), 'confirmed');

function parseSolanaSecretKey(raw: string): Uint8Array {
  const isHex = /^0x[0-9a-fA-F]+$/.test(raw) || /^[0-9a-fA-F]+$/.test(raw);
  if (isHex) {
    const hex = raw.replace(/^0x/, '');
    return new Uint8Array(Buffer.from(hex, 'hex'));
  }
  return bs58.decode(raw);
}

const keypair = web3.Keypair.fromSecretKey(parseSolanaSecretKey(PRIVATE_KEY));

async function main() {
  // Step 1: Validate transfer path
  const tokensRes = await fetch(
    `${API}/tokens?` +
      new URLSearchParams({
        transferrableFromChainKey: 'solana',
        transferrableFromTokenAddress: 'CAW777xcHVTQZ4CRwVQGB8CV1BVKPm5bNVxFJHWFKiH8',
      }),
  );
  const {tokens} = await tokensRes.json();
  const isSupported = tokens.some(
    (t) => t.chainKey === 'arbitrum' && t.address === '0x16f1967565aaD72DD77588a332CE445e7cEF752b',
  );
  if (!isSupported) throw new Error('Transfer path not supported');
  console.log(`Found ${tokens.length} reachable destinations`);

  // Step 2: Get quote
  const quoteRes = await fetch(`${API}/quotes`, {
    method: 'POST',
    headers: {'x-api-key': API_KEY, 'Content-Type': 'application/json'},
    body: JSON.stringify({
      srcChainKey: 'solana',
      dstChainKey: 'arbitrum',
      srcTokenAddress: 'CAW777xcHVTQZ4CRwVQGB8CV1BVKPm5bNVxFJHWFKiH8',
      dstTokenAddress: '0x16f1967565aaD72DD77588a332CE445e7cEF752b',
      srcWalletAddress: keypair.publicKey.toBase58(),
      dstWalletAddress: '0x6d9798053f498451bec79c0397f7f95b079bdcd6',
      amount: '1000000000000',
      options: {amountType: 'EXACT_SRC_AMOUNT', feeTolerance: {type: 'PERCENT', amount: 2}},
    }),
  });

  const {quotes} = await quoteRes.json();
  const quote = quotes[0];
  if (!quote) throw new Error('No quote available');

  console.log('Quote received:', quote.id);

  // Step 3: Build user steps (required for Solana)
  const stepsRes = await fetch(`${API}/build-user-steps`, {
    method: 'POST',
    headers: {'x-api-key': API_KEY, 'Content-Type': 'application/json'},
    body: JSON.stringify({quoteId: quote.id}),
  });

  const {userSteps} = await stepsRes.json();

  // Execute transactions
  let lastSignature: string | undefined;
  for (const step of userSteps) {
    if (step.type !== 'TRANSACTION') continue;

    const tx = step.transaction.encoded;
    if (tx.encoding !== 'base64') continue;

    const raw = Buffer.from(tx.data, 'base64');
    const vtx = web3.VersionedTransaction.deserialize(new Uint8Array(raw));
    vtx.sign([keypair]);

    lastSignature = await connection.sendTransaction(vtx);
    console.log('Transaction sent:', lastSignature);

    const latest = await connection.getLatestBlockhash();
    await connection.confirmTransaction({
      signature: lastSignature,
      blockhash: latest.blockhash,
      lastValidBlockHeight: latest.lastValidBlockHeight,
    });
  }

  // Step 4: Poll status
  const deadline = Date.now() + 5 * 60_000;
  while (Date.now() < deadline) {
    const query = lastSignature ? `?txHash=${encodeURIComponent(lastSignature)}` : '';
    const statusRes = await fetch(`${API}/status/${encodeURIComponent(quote.id)}${query}`, {
      headers: {'x-api-key': API_KEY},
    });
    const {status, explorerUrl} = await statusRes.json();
    console.log('Status:', status);
    if (status === 'SUCCEEDED') {
      console.log('Explorer:', explorerUrl);
      break;
    }
    if (status === 'FAILED' || status === 'UNKNOWN') throw new Error(`Transfer ${status.toLowerCase()}`);
    await new Promise((r) => setTimeout(r, 4000));
  }
}

main().catch(console.error);
```

## Key differences from EVM

| Aspect             | EVM                             | Solana                               |
| ------------------ | ------------------------------- | ------------------------------------ |
| Transaction format | JSON with `to`, `data`, `value` | Base64-encoded versioned transaction |
| User steps         | Included in quote response      | Requires `/build-user-steps` call    |
| Signing            | EIP-191 or EIP-712              | Ed25519                              |
| Confirmation       | `waitForTransactionReceipt`     | `confirmTransaction` with blockhash  |

## Next steps

* [EVM Example](/v2/developers/value-transfer-api/examples/evm) — Transfer tokens between EVM chains
* [API Reference](/v2/developers/value-transfer-api/api-reference/overview) — Explore all endpoints
