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

# How-to: Transfer with the Forwarding Service

> Transfer USDC crosschain using the Forwarding Service to automatically handle destination chain minting

Transfer your unified USDC balance to a destination chain without needing a
wallet or gas on that chain. The
[Forwarding Service](/gateway/references/forwarding-service) handles the
destination chain mint automatically.

This guide demonstrates how to estimate fees, create a burn intent, submit it
with forwarding enabled, and poll for transfer completion.

Select a tab below for EVM or Solana destination instructions.

<Tabs>
  <Tab title="EVM">
    ## Prerequisites

    Before you begin, ensure that you've:

    * Installed [Node.js v22+](https://nodejs.org/)
    * Prepared an EVM testnet wallet with the private key available
      * Added the
        [supported Testnets](/gateway/references/supported-blockchains#testnet) of
        your choice to your wallet (this guide uses Arc Testnet and Base Sepolia)
    * Funded your testnet wallet with native tokens on the source chain (this guide
      uses Arc Testnet). With the Forwarding Service, you do **not** need native
      tokens on the destination chain.
    * [Deposited 10 USDC into the Gateway Wallet](/gateway/howtos/create-unified-usdc-balance)
      contract on Arc Testnet
    * Created a TypeScript project and have `viem` installed
    * You've set up a `.env` file with the following variables:

      ```text .env theme={null}
      EVM_PRIVATE_KEY={YOUR_PRIVATE_KEY}
      ```

    ## Steps

    Follow these steps to transfer a unified USDC balance using the Forwarding
    Service. This example transfers 10 USDC from Arc Testnet to Base Sepolia. You
    can adapt it for another
    [supported chain](/gateway/references/supported-blockchains).

    ### Step 1. Create the transfer spec and estimate fees

    Create a new file called `transfer.ts` in the root of your project and add the
    following code to it. This code creates a
    [transfer spec](/gateway/references/technical-guide#burn-intent) for 10 USDC on
    Arc Testnet, then calls the
    [`/estimate`](/api-reference/gateway/all/estimate-transfer) endpoint with
    `enableForwarder=true` to determine the `maxFee` and `maxBlockHeight` values.
    Using the estimate endpoint ensures the `maxFee` covers the gas fee, transfer
    fee, and forwarding fee.

    ```ts transfer.ts expandable theme={null}
    import { randomBytes } from "node:crypto";
    import { pad, zeroAddress, formatUnits } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { arcTestnet, baseSepolia } from "viem/chains";

    /* Constants */
    const GATEWAY_API_BASE = "https://gateway-api-testnet.circle.com";
    const GATEWAY_WALLET_ADDRESS = "0x0077777d7EBA4688BDeF3E311b846F25870A19B9";
    const GATEWAY_MINTER_ADDRESS = "0x0022222ABE238Cc2C7Bb1f21003F0a260052475B";

    const TRANSFER_VALUE = 10_000000n; // 10 USDC (6 decimals)
    const POLL_INTERVAL_MS = 5_000;
    const POLL_TIMEOUT_MS = 300_000; // 5 minutes

    const sourceChain = {
      name: "arcTestnet",
      chain: arcTestnet,
      usdcAddress: "0x3600000000000000000000000000000000000000",
      domainId: 26,
    };

    const destinationChain = {
      name: "baseSepolia",
      chain: baseSepolia,
      usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
      domainId: 6,
    };

    const domain = { name: "GatewayWallet", version: "1" };

    const EIP712Domain = [
      { name: "name", type: "string" },
      { name: "version", type: "string" },
    ] as const;

    const TransferSpec = [
      { name: "version", type: "uint32" },
      { name: "sourceDomain", type: "uint32" },
      { name: "destinationDomain", type: "uint32" },
      { name: "sourceContract", type: "bytes32" },
      { name: "destinationContract", type: "bytes32" },
      { name: "sourceToken", type: "bytes32" },
      { name: "destinationToken", type: "bytes32" },
      { name: "sourceDepositor", type: "bytes32" },
      { name: "destinationRecipient", type: "bytes32" },
      { name: "sourceSigner", type: "bytes32" },
      { name: "destinationCaller", type: "bytes32" },
      { name: "value", type: "uint256" },
      { name: "salt", type: "bytes32" },
      { name: "hookData", type: "bytes" },
    ] as const;

    const BurnIntent = [
      { name: "maxBlockHeight", type: "uint256" },
      { name: "maxFee", type: "uint256" },
      { name: "spec", type: "TransferSpec" },
    ] as const;

    if (!process.env.EVM_PRIVATE_KEY) throw new Error("EVM_PRIVATE_KEY not set");
    const account = privateKeyToAccount(
      process.env.EVM_PRIVATE_KEY as `0x${string}`,
    );

    console.log(`Using account: ${account.address}`);
    console.log(`Transfer: ${sourceChain.name} → ${destinationChain.name}`);
    console.log(`Amount: ${formatUnits(TRANSFER_VALUE, 6)} USDC`);
    console.log(`Forwarding: enabled\n`);

    // Create the transfer spec
    const spec = {
      version: 1,
      sourceDomain: sourceChain.domainId,
      destinationDomain: destinationChain.domainId,
      sourceContract: GATEWAY_WALLET_ADDRESS,
      destinationContract: GATEWAY_MINTER_ADDRESS,
      sourceToken: sourceChain.usdcAddress,
      destinationToken: destinationChain.usdcAddress,
      sourceDepositor: account.address,
      destinationRecipient: account.address,
      sourceSigner: account.address,
      destinationCaller: zeroAddress,
      value: TRANSFER_VALUE,
      salt: "0x" + randomBytes(32).toString("hex"),
      hookData: "0x",
    };

    const specBytes32 = {
      ...spec,
      sourceContract: pad(spec.sourceContract.toLowerCase() as `0x${string}`, {
        size: 32,
      }),
      destinationContract: pad(
        spec.destinationContract.toLowerCase() as `0x${string}`,
        { size: 32 },
      ),
      sourceToken: pad(spec.sourceToken.toLowerCase() as `0x${string}`, {
        size: 32,
      }),
      destinationToken: pad(spec.destinationToken.toLowerCase() as `0x${string}`, {
        size: 32,
      }),
      sourceDepositor: pad(spec.sourceDepositor.toLowerCase() as `0x${string}`, {
        size: 32,
      }),
      destinationRecipient: pad(
        spec.destinationRecipient.toLowerCase() as `0x${string}`,
        { size: 32 },
      ),
      sourceSigner: pad(spec.sourceSigner.toLowerCase() as `0x${string}`, {
        size: 32,
      }),
      destinationCaller: pad(
        spec.destinationCaller.toLowerCase() as `0x${string}`,
        { size: 32 },
      ),
    };

    // Estimate fees
    console.log("Estimating fees...");
    const estimateResponse = await fetch(
      `${GATEWAY_API_BASE}/v1/estimate?enableForwarder=true`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify([{ spec: specBytes32 }], (_key, value) =>
          typeof value === "bigint" ? value.toString() : value,
        ),
      },
    );

    if (!estimateResponse.ok) {
      const text = await estimateResponse.text();
      throw new Error(`Estimate API error: ${estimateResponse.status} ${text}`);
    }

    const estimateResult = await estimateResponse.json();
    const estimated = estimateResult.body[0].burnIntent;
    const maxFee = BigInt(estimated.maxFee);
    const maxBlockHeight = BigInt(estimated.maxBlockHeight);
    const { fees } = estimateResult;

    if (fees.forwardingFee) {
      console.log(`  Forwarding fee: ${fees.forwardingFee} ${fees.token}`);
    }
    console.log(`  Estimated maxFee: ${formatUnits(maxFee, 6)} ${fees.token}`);
    ```

    <Note>
      **Note:** For production apps, verifying the balance on each chain before
      creating burn intents is best practice. For this how-to, it's assumed that the
      balance is created per the [prerequisites](#prerequisites). For a complete
      end-to-end example that includes checking and error handling, see the Gateway
      quickstarts ([EVM](/gateway/quickstarts/unified-balance-evm)).
    </Note>

    ### Step 2. Sign and submit the burn intent to the Gateway API

    Add the following code to `transfer.ts`. This code constructs the
    [EIP-712 typed data](/gateway/references/technical-guide#burn-intent), signs the
    burn intent using the estimated `maxFee` and `maxBlockHeight`, and submits it to
    the [`/transfer`](/api-reference/gateway/all/create-transfer-attestation)
    endpoint with `enableForwarder=true`.

    <Info>
      In forwarded flows, the `POST /v1/transfer` response may omit top-level
      `attestation` and `signature` fields. Use the returned `transferId` to poll
      `GET /v1/transfer/{id}` for the full transfer record.
    </Info>

    ```ts transfer.ts theme={null}
    const typedData = {
      types: { EIP712Domain, TransferSpec, BurnIntent },
      domain,
      primaryType: "BurnIntent" as const,
      message: { maxBlockHeight, maxFee, spec: specBytes32 },
    };

    const signature = await account.signTypedData(
      typedData as Parameters<typeof account.signTypedData>[0],
    );
    console.log("\nSigned burn intent.");

    console.log("Submitting to Gateway API...");
    const response = await fetch(
      `${GATEWAY_API_BASE}/v1/transfer?enableForwarder=true`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(
          [{ burnIntent: typedData.message, signature }],
          (_key, value) => (typeof value === "bigint" ? value.toString() : value),
        ),
      },
    );

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Gateway API error: ${response.status} ${text}`);
    }

    const json = await response.json();
    const transferId = json.transferId;
    if (!transferId) throw new Error("Missing transferId in response");
    console.log(`Transfer ID: ${transferId}`);
    ```

    ### Step 3. Poll for transfer completion

    Add the following code to `transfer.ts`. Because the Forwarding Service handles
    the destination chain mint, you don't need to call the minter contract. Instead,
    poll the [`/transfer/{id}`](/api-reference/gateway/all/get-transfer-by-id)
    endpoint until the status reaches `confirmed` or `finalized`.

    ```ts transfer.ts theme={null}
    console.log(`\nPolling for transfer completion...`);
    const pollStart = Date.now();
    let completed = false;

    while (Date.now() - pollStart < POLL_TIMEOUT_MS) {
      const pollRes = await fetch(`${GATEWAY_API_BASE}/v1/transfer/${transferId}`);

      if (!pollRes.ok) {
        console.error(`Poll error: ${pollRes.status}`);
        await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
        continue;
      }

      const details = await pollRes.json();
      console.log(`  Status: ${details.status}`);

      if (details.status === "finalized" || details.status === "confirmed") {
        completed = true;
        break;
      }

      if (details.status === "failed") {
        const reason = details.forwardingDetails?.failureReason ?? "unknown";
        throw new Error(`Transfer failed: ${reason}`);
      }

      if (details.status === "expired") {
        throw new Error("Transfer attestation expired before forwarding");
      }

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

    if (!completed) {
      throw new Error("Polling timed out waiting for transfer completion");
    }

    console.log(
      `\nTransfer complete. ${formatUnits(TRANSFER_VALUE, 6)} USDC forwarded to ${destinationChain.name}.`,
    );
    ```

    ### Step 4. Run the script

    Run the script with the following command:

    ```shell theme={null}
    npx tsx --env-file=.env transfer.ts
    ```
  </Tab>

  <Tab title="Solana">
    When the destination is Solana, you need to ensure that `destinationRecipient`
    is an initialized USDC token account. If the recipient wallet address does not
    already have a USDC token account, you can request
    [automatic ATA creation](/gateway/references/forwarding-service#automatic-ata-creation-for-solana)
    using the Forwarding Service.

    This guide demonstrates how to forward from an EVM source blockchain to a Solana
    destination with automatic Associated Token Account (ATA) creation.

    ## Prerequisites

    Before you begin, ensure that you've:

    * Installed [Node.js v22+](https://nodejs.org/)
    * Prepared an EVM testnet wallet with the private key available
      * Added the
        [supported Testnets](/gateway/references/supported-blockchains#testnet) of
        your choice to your wallet (this guide uses Arc Testnet)
    * Funded your testnet wallet with native tokens on the source chain (this guide
      uses Arc Testnet). With the Forwarding Service, you do **not** need native
      tokens on the destination chain.
    * [Deposited 10 USDC into the Gateway Wallet](/gateway/howtos/create-unified-usdc-balance)
      contract on Arc Testnet
    * Created a TypeScript project and have `viem`, `@solana/web3.js`, and
      `@solana/spl-token` installed
    * You've set up a `.env` file with the following variables:

      ```text .env theme={null}
      EVM_PRIVATE_KEY={YOUR_PRIVATE_KEY}
      SOLANA_RECIPIENT_WALLET={RECIPIENT_SOLANA_WALLET_ADDRESS}
      ```

    ## Steps

    Follow these steps to transfer a unified USDC balance to a Solana destination
    using the Forwarding Service with automatic ATA creation. This example transfers
    10 USDC from Arc Testnet to Solana Devnet.

    ### Step 1. Create the transfer spec and estimate fees

    Create a new file called `transfer-solana.ts` in the root of your project and
    add the following code to it. This code derives the recipient's ATA, creates a
    transfer spec targeting Solana Devnet, and calls the
    [`/estimate`](/api-reference/gateway/all/estimate-transfer) endpoint with
    `enableForwarder=true` and `recipientSetupOptions` for automatic ATA creation.

    <Note>
      The forwarding fee for Solana destinations includes the rent cost for ATA
      creation when `recipientSetupOptions` is provided. If the recipient already
      has an initialized USDC token account, omit `recipientSetupOptions` to avoid
      the additional rent fee.
    </Note>

    ```ts transfer-solana.ts expandable highlight={4-5,15-20,28-33,66-67,73-82,86-87,101,151-154} theme={null}
    import { randomBytes } from "node:crypto";
    import { pad, zeroAddress, formatUnits } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { PublicKey } from "@solana/web3.js";
    import { getAssociatedTokenAddressSync } from "@solana/spl-token";

    /* Constants */
    const GATEWAY_API_BASE = "https://gateway-api-testnet.circle.com";
    const GATEWAY_WALLET_ADDRESS = "0x0077777d7EBA4688BDeF3E311b846F25870A19B9";

    const TRANSFER_VALUE = 10_000000n; // 10 USDC (6 decimals)
    const POLL_INTERVAL_MS = 5_000;
    const POLL_TIMEOUT_MS = 300_000; // 5 minutes

    const SOLANA_USDC_MINT = new PublicKey(
      "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
    );
    const SOLANA_GATEWAY_MINTER_ADDRESS = new PublicKey(
      "GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr",
    );

    const sourceChain = {
      name: "arcTestnet",
      usdcAddress: "0x3600000000000000000000000000000000000000",
      domainId: 26,
    };

    const destinationChain = {
      name: "solanaDevnet",
      minterAddress:
        "0x" + Buffer.from(SOLANA_GATEWAY_MINTER_ADDRESS.toBytes()).toString("hex"),
      usdcAddress: "0x" + Buffer.from(SOLANA_USDC_MINT.toBytes()).toString("hex"),
      domainId: 5,
    };

    const domain = { name: "GatewayWallet", version: "1" };

    const EIP712Domain = [
      { name: "name", type: "string" },
      { name: "version", type: "string" },
    ] as const;

    const TransferSpec = [
      { name: "version", type: "uint32" },
      { name: "sourceDomain", type: "uint32" },
      { name: "destinationDomain", type: "uint32" },
      { name: "sourceContract", type: "bytes32" },
      { name: "destinationContract", type: "bytes32" },
      { name: "sourceToken", type: "bytes32" },
      { name: "destinationToken", type: "bytes32" },
      { name: "sourceDepositor", type: "bytes32" },
      { name: "destinationRecipient", type: "bytes32" },
      { name: "sourceSigner", type: "bytes32" },
      { name: "destinationCaller", type: "bytes32" },
      { name: "value", type: "uint256" },
      { name: "salt", type: "bytes32" },
      { name: "hookData", type: "bytes" },
    ] as const;

    const BurnIntent = [
      { name: "maxBlockHeight", type: "uint256" },
      { name: "maxFee", type: "uint256" },
      { name: "spec", type: "TransferSpec" },
    ] as const;

    if (!process.env.EVM_PRIVATE_KEY) throw new Error("EVM_PRIVATE_KEY not set");
    if (!process.env.SOLANA_RECIPIENT_WALLET)
      throw new Error("SOLANA_RECIPIENT_WALLET not set");

    const account = privateKeyToAccount(
      process.env.EVM_PRIVATE_KEY as `0x${string}`,
    );

    // Derive the recipient's USDC ATA from their wallet address
    const recipientWallet = new PublicKey(process.env.SOLANA_RECIPIENT_WALLET);
    const recipientAta = getAssociatedTokenAddressSync(
      SOLANA_USDC_MINT,
      recipientWallet,
    );
    const recipientAtaHex =
      "0x" + Buffer.from(recipientAta.toBytes()).toString("hex");
    const recipientWalletHex =
      "0x" + Buffer.from(recipientWallet.toBytes()).toString("hex");

    console.log(`Using account: ${account.address}`);
    console.log(`Transfer: ${sourceChain.name} → ${destinationChain.name}`);
    console.log(`Recipient wallet: ${recipientWallet.toBase58()}`);
    console.log(`Recipient ATA: ${recipientAta.toBase58()}`);
    console.log(`Amount: ${formatUnits(TRANSFER_VALUE, 6)} USDC`);
    console.log(`Forwarding: enabled\n`);

    // Create the transfer spec
    const spec = {
      version: 1,
      sourceDomain: sourceChain.domainId,
      destinationDomain: destinationChain.domainId,
      sourceContract: GATEWAY_WALLET_ADDRESS,
      destinationContract: destinationChain.minterAddress,
      sourceToken: sourceChain.usdcAddress,
      destinationToken: destinationChain.usdcAddress,
      sourceDepositor: account.address,
      destinationRecipient: recipientAtaHex,
      sourceSigner: account.address,
      destinationCaller: zeroAddress,
      value: TRANSFER_VALUE,
      salt: "0x" + randomBytes(32).toString("hex"),
      hookData: "0x",
    };

    const specBytes32 = {
      ...spec,
      sourceContract: pad(spec.sourceContract.toLowerCase() as `0x${string}`, {
        size: 32,
      }),
      destinationContract: pad(
        spec.destinationContract.toLowerCase() as `0x${string}`,
        { size: 32 },
      ),
      sourceToken: pad(spec.sourceToken.toLowerCase() as `0x${string}`, {
        size: 32,
      }),
      destinationToken: pad(spec.destinationToken.toLowerCase() as `0x${string}`, {
        size: 32,
      }),
      sourceDepositor: pad(spec.sourceDepositor.toLowerCase() as `0x${string}`, {
        size: 32,
      }),
      destinationRecipient: pad(
        spec.destinationRecipient.toLowerCase() as `0x${string}`,
        { size: 32 },
      ),
      sourceSigner: pad(spec.sourceSigner.toLowerCase() as `0x${string}`, {
        size: 32,
      }),
      destinationCaller: pad(
        spec.destinationCaller.toLowerCase() as `0x${string}`,
        { size: 32 },
      ),
    };

    // Estimate fees with recipientSetupOptions for automatic ATA creation
    console.log("Estimating fees...");
    const estimateResponse = await fetch(
      `${GATEWAY_API_BASE}/v1/estimate?enableForwarder=true`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(
          [
            {
              spec: specBytes32,
              recipientSetupOptions: {
                includeRecipientSetup: true,
                recipientOwnerAddress: recipientWalletHex,
              },
            },
          ],
          (_key, value) => (typeof value === "bigint" ? value.toString() : value),
        ),
      },
    );

    if (!estimateResponse.ok) {
      const text = await estimateResponse.text();
      throw new Error(`Estimate API error: ${estimateResponse.status} ${text}`);
    }

    const estimateResult = await estimateResponse.json();
    const estimated = estimateResult.body[0].burnIntent;
    const maxFee = BigInt(estimated.maxFee);
    const maxBlockHeight = BigInt(estimated.maxBlockHeight);
    const { fees } = estimateResult;

    if (fees.forwardingFee) {
      console.log(`  Forwarding fee: ${fees.forwardingFee} ${fees.token}`);
    }
    console.log(`  Estimated maxFee: ${formatUnits(maxFee, 6)} ${fees.token}`);
    ```

    ### Step 2. Sign and submit the burn intent to the Gateway API

    Add the following code to `transfer-solana.ts`. The signing step is the same as
    the EVM flow, but the request body includes `recipientSetupOptions` so the
    Forwarding Service creates the ATA on Solana.

    The `recipientOwnerAddress` is the recipient's Solana wallet address in bytes32
    hex format. The API validates that the `destinationRecipient` in the transfer
    spec matches the canonical ATA derived from this address and the USDC token
    mint. If they don't match, the request is rejected.

    <Info>
      In forwarded flows, the `POST /v1/transfer` response may omit top-level
      `attestation` and `signature` fields. Use the returned `transferId` to poll
      `GET /v1/transfer/{id}` for the full transfer record.
    </Info>

    ```ts transfer-solana.ts highlight={24-27} theme={null}
    const typedData = {
      types: { EIP712Domain, TransferSpec, BurnIntent },
      domain,
      primaryType: "BurnIntent" as const,
      message: { maxBlockHeight, maxFee, spec: specBytes32 },
    };

    const signature = await account.signTypedData(
      typedData as Parameters<typeof account.signTypedData>[0],
    );
    console.log("\nSigned burn intent.");

    console.log("Submitting to Gateway API...");
    const response = await fetch(
      `${GATEWAY_API_BASE}/v1/transfer?enableForwarder=true`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(
          [
            {
              burnIntent: {
                ...typedData.message,
                recipientSetupOptions: {
                  includeRecipientSetup: true,
                  recipientOwnerAddress: recipientWalletHex,
                },
              },
              signature,
            },
          ],
          (_key, value) => (typeof value === "bigint" ? value.toString() : value),
        ),
      },
    );

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Gateway API error: ${response.status} ${text}`);
    }

    const json = await response.json();
    const transferId = json.transferId;
    if (!transferId) throw new Error("Missing transferId in response");
    console.log(`Transfer ID: ${transferId}`);
    ```

    ### Step 3. Poll for transfer completion

    Add the following code to `transfer-solana.ts`. Because the Forwarding Service
    handles the destination chain mint, you don't need to call the minter contract.
    Instead, poll the
    [`/transfer/{id}`](/api-reference/gateway/all/get-transfer-by-id) endpoint until
    the status reaches `confirmed` or `finalized`.

    ```ts transfer-solana.ts theme={null}
    console.log(`\nPolling for transfer completion...`);
    const pollStart = Date.now();
    let completed = false;

    while (Date.now() - pollStart < POLL_TIMEOUT_MS) {
      const pollRes = await fetch(`${GATEWAY_API_BASE}/v1/transfer/${transferId}`);

      if (!pollRes.ok) {
        console.error(`Poll error: ${pollRes.status}`);
        await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
        continue;
      }

      const details = await pollRes.json();
      console.log(`  Status: ${details.status}`);

      if (details.status === "finalized" || details.status === "confirmed") {
        completed = true;
        break;
      }

      if (details.status === "failed") {
        const reason = details.forwardingDetails?.failureReason ?? "unknown";
        throw new Error(`Transfer failed: ${reason}`);
      }

      if (details.status === "expired") {
        throw new Error("Transfer attestation expired before forwarding");
      }

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

    if (!completed) {
      throw new Error("Polling timed out waiting for transfer completion");
    }

    console.log(
      `\nTransfer complete. ${formatUnits(TRANSFER_VALUE, 6)} USDC forwarded to ${destinationChain.name}.`,
    );
    ```

    ### Step 4. Run the script

    Run the script with the following command:

    ```shell theme={null}
    npx tsx --env-file=.env transfer-solana.ts
    ```
  </Tab>
</Tabs>
