> ## 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 a unified USDC balance instantly

Once you have established a unified USDC balance, you can transfer it instantly
to any supported destination chain.

This guide demonstrates how to transfer your unified balance.

Select a tab below for EVM or Solana-specific instructions.

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

    Before you begin, ensure that you've:

    * Installed [Node.js v22.6+](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, Avalanche Fuji and
        Sei Testnet)
    * Funded your testnet wallet with native tokens on the destination chain (this
      guide uses Sei Testnet)
    * [Deposited 10 USDC into the Gateway Wallet](/gateway/howtos/create-unified-usdc-balance)
      contracts on Arc Testnet and Avalanche Fuji (creating a unified balance of 20
      USDC)
    * 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. This example uses a
    unified balance split between Arc Testnet and Avalanche Fuji. You can adapt it
    for any chains where you hold a unified balance.

    ### Step 1. Create and sign burn intents for the source chains

    Create a new file called `transfer.ts` in the root of your project and add the
    following code to it. This code creates and signs
    [burn intents](/gateway/references/technical-guide#burn-intent) for 5 USDC on
    Arc Testnet and 5 USDC on Avalanche Fuji.

    ```ts transfer.ts expandable theme={null}
    import { randomBytes } from "node:crypto";
    import {
      http,
      maxUint256,
      zeroAddress,
      pad,
      createPublicClient,
      getContract,
      createWalletClient,
      formatUnits,
    } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { avalancheFuji, arcTestnet, seiTestnet } from "viem/chains";

    /* Constants */
    const GATEWAY_WALLET_ADDRESS = "0x0077777d7EBA4688BDeF3E311b846F25870A19B9";
    const GATEWAY_MINTER_ADDRESS = "0x0022222ABE238Cc2C7Bb1f21003F0a260052475B";

    const TRANSFER_VALUE = 5_000000n; // 5 USDC (6 decimals)
    const MAX_FEE = 2_010000n;

    // Source chains configuration
    const sourceChains = [
      {
        name: "arcTestnet",
        chain: arcTestnet,
        usdcAddress: "0x3600000000000000000000000000000000000000",
        domainId: 26,
      },
      {
        name: "avalancheFuji",
        chain: avalancheFuji,
        usdcAddress: "0x5425890298aed601595a70ab815c96711a31bc65",
        domainId: 1,
      },
    ];

    // Destination chain configuration
    const destinationChain = {
      name: "seiTestnet",
      chain: seiTestnet,
      usdcAddress: "0x4fCF1784B31630811181f670Aea7A7bEF803eaED",
      domainId: 16,
    };

    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;

    const gatewayMinterAbi = [
      {
        type: "function",
        name: "gatewayMint",
        inputs: [
          { name: "attestationPayload", type: "bytes" },
          { name: "signature", type: "bytes" },
        ],
        outputs: [],
        stateMutability: "nonpayable",
      },
    ] as const;

    // Get account from environment
    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(`Transferring from: ${sourceChains.map((c) => c.name).join(", ")}`);
    console.log(`Transferring to: ${destinationChain.name}\n`);

    // Create and sign burn intents for each source chain
    const requests = [];

    for (const sourceChain of sourceChains) {
      console.log(
        `Creating burn intent from ${sourceChain.name} → ${destinationChain.name}...`,
      );

      const burnIntent = {
        maxBlockHeight: maxUint256,
        maxFee: MAX_FEE,
        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 typedData = {
        types: { EIP712Domain, TransferSpec, BurnIntent },
        domain,
        primaryType: "BurnIntent" as const,
        message: {
          ...burnIntent,
          spec: {
            ...burnIntent.spec,
            sourceContract: pad(
              burnIntent.spec.sourceContract.toLowerCase() as `0x${string}`,
              { size: 32 },
            ),
            destinationContract: pad(
              burnIntent.spec.destinationContract.toLowerCase() as `0x${string}`,
              { size: 32 },
            ),
            sourceToken: pad(
              burnIntent.spec.sourceToken.toLowerCase() as `0x${string}`,
              { size: 32 },
            ),
            destinationToken: pad(
              burnIntent.spec.destinationToken.toLowerCase() as `0x${string}`,
              { size: 32 },
            ),
            sourceDepositor: pad(
              burnIntent.spec.sourceDepositor.toLowerCase() as `0x${string}`,
              { size: 32 },
            ),
            destinationRecipient: pad(
              burnIntent.spec.destinationRecipient.toLowerCase() as `0x${string}`,
              { size: 32 },
            ),
            sourceSigner: pad(
              burnIntent.spec.sourceSigner.toLowerCase() as `0x${string}`,
              { size: 32 },
            ),
            destinationCaller: pad(
              burnIntent.spec.destinationCaller.toLowerCase() as `0x${string}`,
              { size: 32 },
            ),
          },
        },
      };

      const signature = await account.signTypedData(
        typedData as Parameters<typeof account.signTypedData>[0],
      );
      requests.push({ burnIntent: typedData.message, signature });
    }

    console.log("Signed burn intents.\n");
    ```

    <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
      balances are 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),
      [Solana](/gateway/quickstarts/unified-balance-solana)).
    </Note>

    ### Step 2. Submit the burn intents to the Gateway API to obtain an attestation

    Add the following code to `transfer.ts`. This code constructs a Gateway API
    request to the
    [`/transfer`](/api-reference/gateway/all/create-transfer-attestation) endpoint
    and obtains the attestation from that endpoint.

    ```ts transfer.ts theme={null}
    console.log("Submitting to Gateway API...");
    const response = await fetch(
      "https://gateway-api-testnet.circle.com/v1/transfer",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(requests, (_key, value) =>
          typeof value === "bigint" ? value.toString() : value,
        ),
      },
    );

    if (!response.ok) {
      console.error("Gateway API error status:", response.status);
      console.error(await response.text());
      throw new Error("Gateway API request failed");
    }

    const json = await response.json();
    console.log("Gateway API response:", JSON.stringify(json, null, 2));

    const { attestation, signature } = json;
    ```

    ### Step 3. Transfer USDC to the destination chain

    Add the following code to `transfer.ts`. This code performs a call to the
    [Gateway Minter contract](/gateway/references/contract-interfaces-and-events#gatewayminter)
    on Sei Testnet to instantly mint the USDC to your account on that chain.

    ```ts transfer.ts theme={null}
    console.log(`\nMinting funds on ${destinationChain.chain.name}...`);

    const publicClient = createPublicClient({
      chain: destinationChain.chain,
      transport: http(),
    });

    const walletClient = createWalletClient({
      account,
      chain: destinationChain.chain,
      transport: http(),
    });

    const gatewayMinter = getContract({
      address: GATEWAY_MINTER_ADDRESS,
      abi: gatewayMinterAbi,
      client: walletClient,
    });

    const mintTx = await gatewayMinter.write.gatewayMint([attestation, signature], {
      account,
    });
    await publicClient.waitForTransactionReceipt({ hash: mintTx });

    const totalMinted = BigInt(requests.length) * TRANSFER_VALUE;
    console.log(`\nMinted ${formatUnits(totalMinted, 6)} USDC`);
    console.log(`Mint transaction hash:`, mintTx);
    ```

    ### Step 4. Run the script

    Run the script with the following command:

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

  <Tab title="Solana">
    ## Prerequisites

    Before you begin, ensure that you've:

    * Installed [Node.js v22.6+](https://nodejs.org/)
    * Prepared Solana Devnet wallets (sender and recipient) and have the private key
      pairs exported as JSON arrays
    * Funded your testnet wallet with USDC and native tokens
    * [Deposited 10 USDC into the Gateway Wallet](/gateway/howtos/create-unified-usdc-balance)
      contract on Solana Devnet
    * Created a TypeScript project and have the following dependencies installed:
      * `@solana/web3.js`
      * `@solana/spl-token`
      * `@coral-xyz/anchor`
      * `@solana/buffer-layout`
      * `bn.js`
      * `bs58`
    * You've set up a `.env` file with the following variable:

      ```text .env theme={null}
      SOLANA_PRIVATE_KEYPAIR={YOUR_SOLANA_KEYPAIR_ARRAY}
      RECIPIENT_KEYPAIR={YOUR_RECIPIENT_KEYPAIR_ARRAY}
      ```

    <Note>
      To generate a new Solana keypair, run:

      ```shell theme={null}
      solana-keygen new -o keypair.json --no-bip39-passphrase
      ```

      Copy the byte array from the JSON file to your `.env` file.

      If your wallet exports a private key hash instead, you can use `bs58` to convert
      it:

      ```ts TypeScript theme={null}
      const bytes = bs58.decode({ YOUR_PRIVATE_KEY_HASH });
      console.log(JSON.stringify(Array.from(bytes)));
      ```
    </Note>

    ## Steps

    Follow these steps to transfer a unified USDC balance from Solana to another
    Solana account. You can adapt this example for crosschain transfers.

    ### Step 1. Set up your Solana client and helpers

    Create a new file called `transfer.ts` in the root of your project and add the
    following code. This code imports required dependencies and defines constants
    for the Solana connection, Gateway addresses, and transfer parameters.

    ```ts transfer.ts theme={null}
    import { randomBytes } from "node:crypto";
    import * as crypto from "crypto";
    import {
      Wallet,
      AnchorProvider,
      setProvider,
      Program,
      utils,
    } from "@coral-xyz/anchor";
    import {
      Connection,
      Keypair,
      PublicKey,
      SystemProgram,
      Transaction,
      sendAndConfirmTransaction,
    } from "@solana/web3.js";
    import {
      TOKEN_PROGRAM_ID,
      getAssociatedTokenAddressSync,
      createAssociatedTokenAccountIdempotentInstruction,
    } from "@solana/spl-token";
    import {
      u32be,
      nu64be,
      struct,
      seq,
      blob,
      offset,
      Layout,
    } from "@solana/buffer-layout";
    import bs58 from "bs58";

    /* Constants */
    const RPC_ENDPOINT = "https://api.devnet.solana.com";
    const GATEWAY_WALLET_ADDRESS = "GATEwdfmYNELfp5wDmmR6noSr2vHnAfBPMm2PvCzX5vu";
    const GATEWAY_MINTER_ADDRESS = "GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr";
    const USDC_ADDRESS = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
    const SOLANA_ZERO_ADDRESS = "11111111111111111111111111111111";

    const SOLANA_DOMAIN = 5;
    const TRANSFER_AMOUNT = 2; // 2 USDC
    const TRANSFER_VALUE = BigInt(Math.floor(TRANSFER_AMOUNT * 1e6));
    const MAX_FEE = 2_010000n;
    const MAX_UINT64 = 2n ** 64n - 1n;

    const TRANSFER_SPEC_MAGIC = 0xca85def7;
    const BURN_INTENT_MAGIC = 0x070afbc2;
    ```

    ### Step 2. Set up buffer layouts and IDL

    Add the following code to `transfer.ts` to define custom layout classes for
    serialization, structures for encoding burn intents and decoding attestations,
    and the Gateway Minter IDL. Solana doesn't support EIP-712 typed data, so burn
    intents must be encoded manually using buffer layouts.

    ```ts transfer.ts expandable theme={null}
    // Custom layout for Solana PublicKey (32 bytes)
    class PublicKeyLayout extends Layout<PublicKey> {
      constructor(property: string) {
        super(32, property);
      }
      decode(b: Buffer, offset = 0): PublicKey {
        return new PublicKey(b.subarray(offset, offset + 32));
      }
      encode(src: PublicKey, b: Buffer, offset = 0): number {
        const pubkeyBuffer = src.toBuffer();
        pubkeyBuffer.copy(b, offset);
        return 32;
      }
    }

    const publicKey = (property: string) => new PublicKeyLayout(property);

    // Custom layout for 256-bit unsigned integers
    class UInt256BE extends Layout<bigint> {
      constructor(property: string) {
        super(32, property);
      }
      decode(b: Buffer, offset = 0) {
        const buffer = b.subarray(offset, offset + 32);
        return buffer.readBigUInt64BE(24);
      }
      encode(src: bigint, b: Buffer, offset = 0) {
        const buffer = Buffer.alloc(32);
        buffer.writeBigUInt64BE(BigInt(src), 24);
        buffer.copy(b, offset);
        return 32;
      }
    }

    const uint256be = (property: string) => new UInt256BE(property);

    // Type 'as any' used due to @solana/buffer-layout's incomplete TypeScript definitions (archived Jan 2025)
    const BurnIntentLayout = struct([
      u32be("magic"),
      uint256be("maxBlockHeight"),
      uint256be("maxFee"),
      u32be("transferSpecLength"),
      struct(
        [
          u32be("magic"),
          u32be("version"),
          u32be("sourceDomain"),
          u32be("destinationDomain"),
          publicKey("sourceContract"),
          publicKey("destinationContract"),
          publicKey("sourceToken"),
          publicKey("destinationToken"),
          publicKey("sourceDepositor"),
          publicKey("destinationRecipient"),
          publicKey("sourceSigner"),
          publicKey("destinationCaller"),
          uint256be("value"),
          blob(32, "salt"),
          u32be("hookDataLength"),
          blob(offset(u32be(), -4), "hookData"),
        ] as any,
        "spec",
      ),
    ] as any);

    const MintAttestationElementLayout = struct([
      publicKey("destinationToken"),
      publicKey("destinationRecipient"),
      nu64be("value"),
      blob(32, "transferSpecHash"),
      u32be("hookDataLength"),
      blob(offset(u32be(), -4), "hookData"),
    ] as any);

    const MintAttestationSetLayout = struct([
      u32be("magic"),
      u32be("version"),
      u32be("destinationDomain"),
      publicKey("destinationContract"),
      publicKey("destinationCaller"),
      nu64be("maxBlockHeight"),
      u32be("numAttestations"),
      seq(MintAttestationElementLayout, offset(u32be(), -4), "attestations"),
    ] as any);

    // Sample-local IDL subset for this example.
    const gatewayMinterIdl = {
      address: GATEWAY_MINTER_ADDRESS,
      metadata: { name: "gatewayMinter", version: "0.1.0", spec: "0.1.0" },
      instructions: [
        {
          name: "gatewayMint",
          discriminator: [12, 0],
          accounts: [
            { name: "payer", writable: true, signer: true },
            { name: "destinationCaller", signer: true },
            { name: "gatewayMinter" },
            { name: "systemProgram" },
            { name: "tokenProgram" },
            { name: "eventAuthority" },
            { name: "program" },
          ],
          args: [
            { name: "params", type: { defined: { name: "gatewayMintParams" } } },
          ],
        },
      ],
      types: [
        {
          name: "gatewayMintParams",
          type: {
            kind: "struct",
            fields: [
              { name: "attestation", type: "bytes" },
              { name: "signature", type: "bytes" },
            ],
          },
        },
      ],
    };
    ```

    ### Step 3. Define helper functions

    Add the following code to `transfer.ts` to define helper functions for creating,
    encoding, and signing burn intents, converting addresses between formats,
    decoding attestations from the Gateway API, and finding Program Derived
    Addresses (PDAs). The signing process uses Ed25519 with a specific prefix
    required by the Gateway Wallet program.

    ```ts transfer.ts expandable theme={null}
    function createBurnIntent(params: {
      sourceDepositor: string;
      destinationRecipient: string;
      sourceSigner: string;
    }) {
      const { sourceDepositor, destinationRecipient, sourceSigner } = params;

      return {
        maxBlockHeight: MAX_UINT64,
        maxFee: MAX_FEE,
        spec: {
          version: 1,
          sourceDomain: SOLANA_DOMAIN,
          destinationDomain: SOLANA_DOMAIN,
          sourceContract: addressToBytes32(GATEWAY_WALLET_ADDRESS),
          destinationContract: addressToBytes32(GATEWAY_MINTER_ADDRESS),
          sourceToken: addressToBytes32(USDC_ADDRESS),
          destinationToken: addressToBytes32(USDC_ADDRESS),
          sourceDepositor: addressToBytes32(sourceDepositor),
          destinationRecipient: addressToBytes32(destinationRecipient),
          sourceSigner: addressToBytes32(sourceSigner),
          destinationCaller: addressToBytes32(SOLANA_ZERO_ADDRESS),
          value: TRANSFER_VALUE,
          salt: "0x" + randomBytes(32).toString("hex"),
          hookData: "0x",
        },
      };
    }

    function encodeBurnIntent(bi: any): Buffer {
      const hookData = Buffer.from((bi.spec.hookData || "0x").slice(2), "hex");
      const prepared = {
        magic: BURN_INTENT_MAGIC,
        maxBlockHeight: bi.maxBlockHeight,
        maxFee: bi.maxFee,
        transferSpecLength: 340 + hookData.length,
        spec: {
          magic: TRANSFER_SPEC_MAGIC,
          version: bi.spec.version,
          sourceDomain: bi.spec.sourceDomain,
          destinationDomain: bi.spec.destinationDomain,
          sourceContract: hexToPublicKey(bi.spec.sourceContract),
          destinationContract: hexToPublicKey(bi.spec.destinationContract),
          sourceToken: hexToPublicKey(bi.spec.sourceToken),
          destinationToken: hexToPublicKey(bi.spec.destinationToken),
          sourceDepositor: hexToPublicKey(bi.spec.sourceDepositor),
          destinationRecipient: hexToPublicKey(bi.spec.destinationRecipient),
          sourceSigner: hexToPublicKey(bi.spec.sourceSigner),
          destinationCaller: hexToPublicKey(bi.spec.destinationCaller),
          value: bi.spec.value,
          salt: Buffer.from(bi.spec.salt.slice(2), "hex"),
          hookDataLength: hookData.length,
          hookData,
        },
      };
      const buffer = Buffer.alloc(72 + 340 + hookData.length);
      const bytesWritten = BurnIntentLayout.encode(prepared, buffer);
      return buffer.subarray(0, bytesWritten);
    }

    function signBurnIntent(keypair: Keypair, payload: any): string {
      const encoded = encodeBurnIntent(payload);
      const prefixed = Buffer.concat([
        Buffer.from([0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
        encoded,
      ]);
      const privateKey = crypto.createPrivateKey({
        key: Buffer.concat([
          Buffer.from("302e020100300506032b657004220420", "hex"),
          Buffer.from(keypair.secretKey.slice(0, 32)),
        ]),
        format: "der",
        type: "pkcs8",
      });
      return `0x${crypto.sign(null, prefixed, privateKey).toString("hex")}`;
    }

    function addressToBytes32(address: string): string {
      const decoded = Buffer.from(bs58.decode(address));
      return `0x${decoded.toString("hex")}`;
    }

    function hexToPublicKey(hex: string): PublicKey {
      return new PublicKey(Buffer.from(hex.slice(2), "hex"));
    }

    function decodeAttestationSet(attestation: string) {
      const buffer = Buffer.from(attestation.slice(2), "hex");
      return MintAttestationSetLayout.decode(buffer) as {
        attestations: Array<{
          destinationToken: PublicKey;
          destinationRecipient: PublicKey;
          transferSpecHash: Uint8Array;
        }>;
      };
    }

    function findCustodyPda(
      mint: PublicKey,
      minterProgramId: PublicKey,
    ): PublicKey {
      return PublicKey.findProgramAddressSync(
        [Buffer.from("gateway_minter_custody"), mint.toBuffer()],
        minterProgramId,
      )[0];
    }

    function findTransferSpecHashPda(
      transferSpecHash: Uint8Array | Buffer,
      minterProgramId: PublicKey,
    ): PublicKey {
      return PublicKey.findProgramAddressSync(
        [Buffer.from("used_transfer_spec_hash"), Buffer.from(transferSpecHash)],
        minterProgramId,
      )[0];
    }
    ```

    ### Step 4. Set up the environment and create the burn intent

    Add the following code to `transfer.ts` to load keypairs from environment
    variables, create a Solana connection, set up the recipient's Associated Token
    Account, and create and sign the burn intent.

    <Info>
      For transfers to Solana, the `destinationRecipient` must be an initialized
      USDC Token Account. If the intended recipient is a standard wallet address,
      use its Associated Token Account (ATA), not the recipient wallet address
      itself.
    </Info>

    ```ts transfer.ts theme={null}
    if (!process.env.SOLANA_PRIVATE_KEYPAIR)
      throw new Error("SOLANA_PRIVATE_KEYPAIR not set");
    if (!process.env.RECIPIENT_KEYPAIR)
      throw new Error("RECIPIENT_KEYPAIR not set");

    const senderKeypair = Keypair.fromSecretKey(
      Uint8Array.from(JSON.parse(process.env.SOLANA_PRIVATE_KEYPAIR)),
    );
    const recipientKeypair = Keypair.fromSecretKey(
      Uint8Array.from(JSON.parse(process.env.RECIPIENT_KEYPAIR)),
    );
    const connection = new Connection(RPC_ENDPOINT, "confirmed");
    const usdcMint = new PublicKey(USDC_ADDRESS);

    console.log(`Using account: ${senderKeypair.publicKey.toBase58()}`);
    console.log(`Transferring from: Solana Devnet → Solana Devnet\n`);

    // Create recipient's Associated Token Account
    const recipientAta = getAssociatedTokenAddressSync(
      usdcMint,
      recipientKeypair.publicKey,
    );

    console.log("Creating recipient's Associated Token Account...");
    const createAtaIx = createAssociatedTokenAccountIdempotentInstruction(
      senderKeypair.publicKey,
      recipientAta,
      recipientKeypair.publicKey,
      usdcMint,
    );
    const tx = new Transaction().add(createAtaIx);
    await sendAndConfirmTransaction(connection, tx, [senderKeypair]);
    console.log(`Recipient ATA: ${recipientAta.toBase58()}\n`);

    // Create and sign burn intent
    console.log(`Creating burn intent from Solana Devnet → Solana Devnet...`);
    const burnIntent = createBurnIntent({
      sourceDepositor: senderKeypair.publicKey.toBase58(),
      destinationRecipient: recipientAta.toBase58(),
      sourceSigner: senderKeypair.publicKey.toBase58(),
    });

    const burnIntentSignature = signBurnIntent(senderKeypair, burnIntent);
    const request = [{ burnIntent, signature: burnIntentSignature }];
    console.log("Signed burn intent.\n");
    ```

    ### Step 5. Submit to Gateway API and decode the attestation

    Add the following code to `transfer.ts` to submit the burn intent to the Gateway
    API, obtain an attestation, and decode the attestation set for use in the mint
    operation.

    ```ts transfer.ts theme={null}
    console.log("Submitting to Gateway API...");
    const response = await fetch(
      "https://gateway-api-testnet.circle.com/v1/transfer",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(request, (_key, value) =>
          typeof value === "bigint" ? value.toString() : value,
        ),
      },
    );

    const json = await response.json();
    if (json.success === false) {
      throw new Error(`Gateway API error: ${json.message}`);
    }
    console.log("Gateway API response:", JSON.stringify(json, null, 2));

    const { attestation, signature: mintSignature } = json;
    const decoded = decodeAttestationSet(attestation);
    ```

    ### Step 6. Mint USDC on the destination chain

    Add the following code to `transfer.ts` to set up the Anchor program, prepare
    the required accounts, and call the Gateway Minter to mint USDC on Solana Devnet
    using the attestation from the previous step.

    <Info>
      The ordered remaining-account list and PDA derivations are documented in
      [Solana Programs and
      Interfaces](/gateway/references/solana-programs#gatewaymint). For static
      instruction definitions, use the onchain IDLs linked from that page.
    </Info>

    ```ts transfer.ts theme={null}
    const minterProgramId = new PublicKey(GATEWAY_MINTER_ADDRESS);
    const anchorWallet = new Wallet(senderKeypair);
    const provider = new AnchorProvider(
      connection,
      anchorWallet,
      AnchorProvider.defaultOptions(),
    );
    setProvider(provider);
    const minterProgram = new Program(gatewayMinterIdl, provider);

    const [minterPda] = PublicKey.findProgramAddressSync(
      [Buffer.from(utils.bytes.utf8.encode("gateway_minter"))],
      minterProgramId,
    );

    // Mint on Solana
    const remainingAccounts = decoded.attestations.flatMap((e) => [
      {
        pubkey: findCustodyPda(e.destinationToken, minterProgramId),
        isWritable: true,
        isSigner: false,
      },
      { pubkey: e.destinationRecipient, isWritable: true, isSigner: false },
      {
        pubkey: findTransferSpecHashPda(e.transferSpecHash, minterProgramId),
        isWritable: true,
        isSigner: false,
      },
    ]);

    const attestationBytes = Buffer.from(attestation.slice(2), "hex");
    const signatureBytes = Buffer.from(mintSignature.slice(2), "hex");

    console.log("\nMinting funds on Solana Devnet...");
    const mintTx = await minterProgram.methods
      .gatewayMint({ attestation: attestationBytes, signature: signatureBytes })
      .accountsPartial({
        gatewayMinter: minterPda,
        destinationCaller: senderKeypair.publicKey,
        payer: senderKeypair.publicKey,
        systemProgram: SystemProgram.programId,
        tokenProgram: TOKEN_PROGRAM_ID,
      })
      .remainingAccounts(remainingAccounts)
      .signers([senderKeypair])
      .rpc();

    // Wait for confirmation
    const latest = await connection.getLatestBlockhash();
    await connection.confirmTransaction(
      {
        signature: mintTx,
        blockhash: latest.blockhash,
        lastValidBlockHeight: latest.lastValidBlockHeight,
      },
      "confirmed",
    );

    console.log(`\nMinted ${Number(TRANSFER_VALUE) / 1_000_000} USDC`);
    console.log(`Mint transaction hash:`, mintTx);
    ```

    ### Step 7. Run the script

    Run the script with the following command:

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