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

# Quickstart

> Execute your first cross-chain transfer using the Value Transfer API.

Get started with the Value Transfer API in minutes. This guide walks you through the complete transfer flow.

## Prerequisites

* An API key from LayerZero
* A funded wallet on the source chain

## Base URL

```
https://transfer.layerzero-api.com/v1
```

***

<Steps>
  <Step title="Check chains and tokens">
    Before requesting a quote, verify your source and destination chains are supported and discover available tokens.

    ### List supported chains

    <Tabs>
      <Tab title="cURL">
        ```bash wrap theme={null}
        curl -X GET "https://transfer.layerzero-api.com/v1/chains"
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript wrap theme={null}
        const response = await fetch('https://transfer.layerzero-api.com/v1/chains');

        const {chains} = await response.json();
        console.log(chains);
        ```
      </Tab>

      <Tab title="Python">
        ```python wrap theme={null}
        import requests

        response = requests.get("https://transfer.layerzero-api.com/v1/chains")
        chains = response.json()["chains"]

        print(chains)
        ```
      </Tab>
    </Tabs>

    #### Response

    | Field       | Description                                               |
    | ----------- | --------------------------------------------------------- |
    | `chainKey`  | Unique chain identifier (e.g., `ethereum`, `base`)        |
    | `chainType` | Blockchain type (`EVM`, `SOLANA`, `STARKNET`)             |
    | `chainId`   | Native chain ID (e.g., `1` for Ethereum, `8453` for Base) |

    <Info>
      See the [Chains API reference](/v2/developers/value-transfer-api/api-reference/chains) for complete endpoint documentation.
    </Info>

    ### List supported tokens

    Query tokens with optional filters. Without parameters, the API returns all tokens across all supported chains.

    <Tabs>
      <Tab title="cURL">
        ```bash wrap theme={null}
        # Get all tokens (full catalog)
        curl -X GET "https://transfer.layerzero-api.com/v1/tokens"

        # Get valid destinations for ETH from Base
        curl -X GET "https://transfer.layerzero-api.com/v1/tokens?transferrableFromChainKey=&transferrableFromTokenAddress=&pagination%5BnextToken%5D="
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript wrap theme={null}
        // Get tokens transferrable from Base ETH
        const response = await fetch(
          'https://transfer.layerzero-api.com/v1/tokens?' +
            new URLSearchParams({
              transferrableFromChainKey: 'base',
              transferrableFromTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
            }),
        );
        const {tokens} = await response.json();

        console.log(tokens);
        ```
      </Tab>

      <Tab title="Python">
        ```python wrap theme={null}
        import requests

        # Get tokens transferrable from Base ETH
        response = requests.get(
          "https://transfer.layerzero-api.com/v1/tokens",
          params={
            "transferrableFromChainKey": "base",
            "transferrableFromTokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
          },
        )
        tokens = response.json()["tokens"]

        print(tokens)
        ```
      </Tab>
    </Tabs>

    #### Query parameters

    | Parameter                       | Type   | Description                                                                                                               |
    | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------- |
    | `transferrableFromChainKey`     | string | Source chain key (e.g., `base`, `ethereum`). **Must be combined** with `transferrableFromTokenAddress` to filter results. |
    | `transferrableFromTokenAddress` | string | Source token address. **Must be combined** with `transferrableFromChainKey` to filter results.                            |

    <Tip>
      **Valid destinations:** To get all destination tokens you can transfer to, provide **both** the source chain and token address. See [Tokens](/v2/developers/value-transfer-api/api-reference/tokens) for the full endpoint reference.
    </Tip>

    #### Response

    | Field         | Description                                  |
    | ------------- | -------------------------------------------- |
    | `isSupported` | Whether the token is available for transfers |
    | `chainKey`    | Chain identifier (e.g., `ethereum`, `base`)  |
    | `address`     | Token contract address                       |
    | `decimals`    | Token decimal places                         |
    | `symbol`      | Token symbol (e.g., `ETH`, `USDC`)           |
    | `name`        | Full token name                              |
    | `price.usd`   | Current price in USD                         |
  </Step>

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

    <Note>
      **Quote response:** The quote includes `userSteps` with transaction or signature data to execute. See [Quotes](/v2/developers/value-transfer-api/api-reference/quotes) for the full request/response schema.
    </Note>

    <Tabs>
      <Tab title="cURL">
        ```bash wrap theme={null}
        curl -X POST "https://transfer.layerzero-api.com/v1/quotes" \
          -H "x-api-key: YOUR_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{
            "srcChainKey": "base",
            "dstChainKey": "optimism",
            "srcTokenAddress": "<0xTOKEN_ADDRESS>",
            "dstTokenAddress": "<0xTOKEN_ADDRESS>",
            "srcWalletAddress": "<0xYOUR_WALLET>",
            "dstWalletAddress": "<0xYOUR_WALLET>",
            "amount": "<AMOUNT_IN_LOCAL_DECIMALS>",
            "options": {
              "amountType": "EXACT_SRC_AMOUNT",
              "feeTolerance": { "type": "PERCENT", "amount": 2 }
            }
          }'
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript wrap theme={null}
        const response = 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: 'base',
            dstChainKey: 'optimism',
            srcTokenAddress: '<0xTOKEN_ADDRESS>',
            dstTokenAddress: '<0xTOKEN_ADDRESS>',
            srcWalletAddress: '<0xYOUR_WALLET>',
            dstWalletAddress: '<0xYOUR_WALLET>',
            amount: '<AMOUNT_IN_LOCAL_DECIMALS>', // 0.0001 ETH in wei
            options: {
              amountType: 'EXACT_SRC_AMOUNT',
              feeTolerance: {type: 'PERCENT', amount: 2},
            },
          }),
        });

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

        console.log('Quote ID:', quote.id);
        console.log('Fee:', quote.feeUsd, 'USD');
        ```
      </Tab>

      <Tab title="Python">
        ```python wrap theme={null}
        import requests

        response = requests.post(
          "https://transfer.layerzero-api.com/v1/quotes",
          headers={
            "x-api-key": "YOUR_API_KEY",
            "Content-Type": "application/json",
          },
          json={
            "srcChainKey": "base",
            "dstChainKey": "optimism",
            "srcTokenAddress": "<0xTOKEN_ADDRESS>",
            "dstTokenAddress": "<0xTOKEN_ADDRESS>",
            "srcWalletAddress": "<0xYOUR_WALLET>",
            "dstWalletAddress": "<0xYOUR_WALLET>",
            "amount": "<AMOUNT_IN_LOCAL_DECIMALS>",  # 0.0001 ETH in wei
            "options": {
              "amountType": "EXACT_SRC_AMOUNT",
              "feeTolerance": {"type": "PERCENT", "amount": 2},
            },
          },
        )

        quote = response.json()["quotes"][0]

        print(f"Quote ID: {quote['id']}")
        print(f"Fee: {quote.get('feeUsd', 'N/A')} USD")
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Build user steps (Solana only)">
    For **EVM transfers**, the quote response already includes `userSteps` with transaction data - skip to Step 4.

    For **Solana transfers**, you must call `/build-user-steps` to get the encoded transaction data. Solana transactions have short blockhash validity (\~60 seconds), so this step generates fresh transaction data.

    <Info>
      **EVM vs Solana:**

      | Chain type | Where to get `userSteps`            |
      | ---------- | ----------------------------------- |
      | **EVM**    | Directly from the quote response    |
      | **Solana** | Must call `/build-user-steps` first |

      See [Build User Steps](/v2/developers/value-transfer-api/api-reference/build-user-steps) for complete documentation.
    </Info>

    <Tabs>
      <Tab title="cURL">
        ```bash wrap theme={null}
        curl -X POST "https://transfer.layerzero-api.com/v1/build-user-steps" \
          -H "x-api-key: YOUR_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{"quoteId": "QUOTE_ID"}'
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript wrap theme={null}
        const response = 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 response.json();

        console.log('User steps:', userSteps);
        ```
      </Tab>

      <Tab title="Python">
        ```python wrap theme={null}
        import requests

        response = requests.post(
          "https://transfer.layerzero-api.com/v1/build-user-steps",
          headers={
            "x-api-key": "YOUR_API_KEY",
            "Content-Type": "application/json",
          },
          json={"quoteId": "QUOTE_ID"},
        )

        user_steps = response.json()["userSteps"]

        print("User steps:", user_steps)
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Execute the transfer">
    Process each user step in order. For transactions, sign and submit to the blockchain. For signatures, sign and submit to the API.

    <Danger>
      **Never approve the LZMulticall (Wrapper) as a token spender**

      LZMulticall executes bridge transactions. It is not the right spender, and approving it will lose you tokens. The correct spender is the **TransferDelegate**, and the API's approve step already has this set in the calldata. Execute every `userStep` as returned.

      See [Contracts Overview](/v2/developers/value-transfer-api/contracts/overview) for details on the contract architecture.
    </Danger>

    <Note>
      **No cURL for execution:** Executing requires a **wallet** to sign and broadcast the transaction. This step cannot be done with cURL alone - you need a signing library (viem, web3.py, @solana/web3.js) and an RPC connection.
    </Note>

    **Where to get `userSteps`:**

    * **EVM**: Use `quote.userSteps` directly from the quote response
    * **Solana**: Use `userSteps` from the `/build-user-steps` response

    ### Execute transaction steps

    Loop through every `userStep` and execute each one in order. ERC20 transfers return two transaction steps (approve + bridge) — you must execute both.

    <Tabs>
      <Tab title="TypeScript (viem)">
        ```typescript wrap theme={null}
        import {createWalletClient, createPublicClient, http} from 'viem';
        import {privateKeyToAccount} from 'viem/accounts';
        import {base} from 'viem/chains';

        const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY');
        const wallet = createWalletClient({account, chain: base, transport: http()});
        const client = createPublicClient({chain: base, transport: http()});

        let txHash;

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

          const tx = step.transaction.encoded;
          txHash = await wallet.sendTransaction({
            to: tx.to,
            data: tx.data,
            value: BigInt(tx.value ?? '0'),
          });

          // Wait for confirmation before executing the next step
          await client.waitForTransactionReceipt({hash: txHash});
          console.log(`${step.description} tx confirmed:`, txHash);
        }
        ```
      </Tab>

      <Tab title="Python (web3.py)">
        ```python wrap theme={null}
        from web3 import Web3

        w3 = Web3(Web3.HTTPProvider("https://mainnet.base.org"))
        account = w3.eth.account.from_key("YOUR_PRIVATE_KEY")

        tx_hash = None

        for step in user_steps:
            if step["type"] != "TRANSACTION":
                continue

            tx = step["transaction"]["encoded"]
            signed = account.sign_transaction(
                {
                    "to": tx["to"],
                    "data": tx["data"],
                    "value": int(tx.get("value", 0)),
                    "gas": 200000,
                    "gasPrice": w3.eth.gas_price,
                    "nonce": w3.eth.get_transaction_count(account.address),
                    "chainId": tx["chainId"],
                }
            )

            tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
            w3.eth.wait_for_transaction_receipt(tx_hash)
            print(f"{step['description']} tx confirmed: {tx_hash.hex()}")
        ```
      </Tab>
    </Tabs>

    ### Submit a signature step

    For intent-based routes, sign the EIP-712 data and submit to the API.

    <Tip>
      **Signature steps:** See [Submit Signature](/v2/developers/value-transfer-api/api-reference/submit-signature) for EIP-712 typed data handling and BigInt conversion requirements.
    </Tip>

    <Tabs>
      <Tab title="TypeScript (viem)">
        ```typescript wrap theme={null}
        // Sign the typed data from userStep.signature.typedData
        const typed = userStep.signature.typedData;

        const signature = await wallet.signTypedData({
          domain: typed.domain,
          types: typed.types,
          primaryType: typed.primaryType,
          message: typed.message,
        });

        // Submit to API
        await fetch('https://transfer.layerzero-api.com/v1/submit-signature', {
          method: 'POST',
          headers: {
            'x-api-key': 'YOUR_API_KEY',
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            quoteId: quote.id,
            signatures: [signature],
          }),
        });
        ```
      </Tab>

      <Tab title="Python">
        ```python wrap theme={null}
        from eth_account.messages import encode_typed_data

        # Sign the typed data from userStep["signature"]["typedData"]
        typed = user_step["signature"]["typedData"]
        signable = encode_typed_data(full_message=typed)
        signed = account.sign_message(signable)

        # Submit to API
        requests.post(
          "https://transfer.layerzero-api.com/v1/submit-signature",
          headers={
            "x-api-key": "YOUR_API_KEY",
            "Content-Type": "application/json",
          },
          json={
            "quoteId": quote["id"],
            "signatures": [signed.signature.hex()],
          },
        )
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Track the transfer">
    After executing all user steps, poll the status endpoint to monitor completion.

    <Tip>
      **Polling:** Poll every 4 seconds until `status` is `SUCCEEDED` or `FAILED`. See [Status](/v2/developers/value-transfer-api/api-reference/status) for response details and error handling.
    </Tip>

    <Tabs>
      <Tab title="cURL">
        ```bash wrap theme={null}
        curl -X GET "https://transfer.layerzero-api.com/v1/status/QUOTE_ID?txHash=0xYOUR_TX_HASH" \
          -H "x-api-key: YOUR_API_KEY"
        ```
      </Tab>

      <Tab title="TypeScript">
        ```typescript wrap theme={null}
        async function pollStatus(quoteId: string, txHash?: string): Promise<string> {
          const query = txHash ? `?txHash=${txHash}` : '';

          while (true) {
            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' || status === 'FAILED' || status === 'UNKNOWN') {
              console.log('Explorer:', explorerUrl);
              return status;
            }

            await new Promise((resolve) => setTimeout(resolve, 4000));
          }
        }
        ```
      </Tab>

      <Tab title="Python">
        ```python wrap theme={null}
        import time
        from urllib.parse import quote as url_quote

        import requests


        def poll_status(quote_id: str, tx_hash: str | None = None) -> str:
          query = f"?txHash={tx_hash}" if tx_hash else ""

          while True:
            response = requests.get(
              f"https://transfer.layerzero-api.com/v1/status/{url_quote(quote_id, safe='')}{query}",
              headers={"x-api-key": "YOUR_API_KEY"},
            )

            data = response.json()
            print(f"Status: {data['status']}")

            if data["status"] in ("SUCCEEDED", "FAILED", "UNKNOWN"):
              print(f"Explorer: {data.get('explorerUrl')}")
              return data["status"]

            time.sleep(4)
        ```
      </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>

***

## Next steps

* **[EVM Example](/v2/developers/value-transfer-api/examples/evm)**: Complete TypeScript example with viem
* **[Solana Example](/v2/developers/value-transfer-api/examples/solana)**: Complete TypeScript example for Solana transfers
* **[API Reference](/v2/developers/value-transfer-api/api-reference/overview)**: Explore all endpoints with interactive testing
