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

Circle Gateway allows you to establish a unified USDC balance consisting of USDC
stored on multiple source chains. Once established, you can transfer this
balance instantly to any destination chain.

This guide demonstrates how to establish a unified USDC balance by depositing
USDC into the [Gateway Wallet contract](/gateway/references/contract-addresses).
You can perform this action on multiple chains to establish a chain-abstracted
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 Arc testnet network to your wallet
        ([network details](https://docs.arc.io/arc/references/connect-to-arc#wallet-setup))
    * Funded your testnet wallet with USDC and native tokens
    * 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 establish a unified USDC balance on Arc Testnet. You can
    adapt this example for any
    [supported EVM chain](/gateway/references/supported-blockchains#testnet).

    ### Step 1. Approve the Gateway Wallet to transfer USDC from your address

    Create a new file called `deposit.ts` in the root of your project, and add the
    following code to it. This code calls the `approve()` method on the USDC
    contract to allow the Gateway Wallet contract to transfer USDC from your wallet.

    ```ts deposit.ts theme={null}
    import {
      createPublicClient,
      createWalletClient,
      getContract,
      http,
      erc20Abi,
      formatUnits,
    } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { arcTestnet } from "viem/chains";

    /* Constants */
    const GATEWAY_WALLET_ADDRESS = "0x0077777d7EBA4688BDeF3E311b846F25870A19B9";
    const USDC_ADDRESS = "0x3600000000000000000000000000000000000000";
    const DEPOSIT_AMOUNT = 10_000_000n; // 10 USDC (6 decimals)

    const gatewayWalletAbi = [
      {
        type: "function",
        name: "deposit",
        inputs: [
          { name: "token", type: "address" },
          { name: "value", type: "uint256" },
        ],
        outputs: [],
        stateMutability: "nonpayable",
      },
    ] 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}`,
    );

    // Set up clients
    const publicClient = createPublicClient({
      chain: arcTestnet,
      transport: http(),
    });

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

    // Get contract instances
    const usdc = getContract({
      address: USDC_ADDRESS,
      abi: erc20Abi,
      client: walletClient,
    });

    const gatewayWallet = getContract({
      address: GATEWAY_WALLET_ADDRESS,
      abi: gatewayWalletAbi,
      client: walletClient,
    });

    // Approve Gateway Wallet to spend USDC
    console.log(`Approving ${formatUnits(DEPOSIT_AMOUNT, 6)} USDC...`);

    const approvalTx = await usdc.write.approve(
      [gatewayWallet.address, DEPOSIT_AMOUNT],
      { account },
    );
    await publicClient.waitForTransactionReceipt({ hash: approvalTx });
    console.log(`Approved: ${approvalTx}`);
    ```

    ### Step 2. Call the deposit method on the Gateway Wallet contract

    Add the following code to the `deposit.ts` file to call the
    [`deposit()`](/gateway/references/contract-interfaces-and-events#deposit) method
    on the Gateway Wallet contract. Note that you must use the `deposit()` method
    and not the standard transfer on the USDC contract.

    <Warning>
      **Warning:** Directly transferring USDC to the Gateway Wallet contract with a
      standard ERC-20 transfer will result in loss of that USDC. You must use one of
      the deposit methods on the wallet contract to get a unified USDC balance.
    </Warning>

    ```ts deposit.ts theme={null}
    console.log(
      `Depositing ${formatUnits(DEPOSIT_AMOUNT, 6)} USDC to Gateway Wallet...`,
    );

    const depositTx = await gatewayWallet.write.deposit(
      [usdc.address, DEPOSIT_AMOUNT],
      { account },
    );
    await publicClient.waitForTransactionReceipt({ hash: depositTx });
    console.log(`Deposit tx: ${depositTx}`);
    ```

    ### Step 3. Run the script

    Run the script with the following command:

    ```shell theme={null}
    node --env-file=.env deposit.ts
    ```

    ### Step 4. Wait for the required number of block confirmations

    Wait for the
    [required number of block confirmations](/gateway/references/supported-blockchains#required-block-confirmations).
    Once the deposit transaction is final, your unified balance reflects the amount
    from Arc Testnet. If you have balances on other chains, the total balance is the
    sum of all the USDC from deposit transactions across all supported chains that
    have reached finality.
  </Tab>

  <Tab title="Solana">
    ## Prerequisites

    Before you begin, ensure that you've:

    * Installed [Node.js v22.6+](https://nodejs.org/)
    * Prepared a Solana Devnet wallet with the keypair exported as a JSON array
    * Funded your testnet wallet with USDC and native tokens
    * Created a TypeScript project and have the following dependencies installed:
      * `@solana/web3.js`
      * `@solana/spl-token`
      * `@coral-xyz/anchor`
      * `bn.js`
    * You've set up a `.env` file with the following variable:

      ```text .env theme={null}
      SOLANA_PRIVATE_KEYPAIR={YOUR_SOLANA_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 establish a unified USDC balance on Solana. The high level
    steps are the same for EVM chains, consult the EVM example for adding additional
    USDC to your unified balance from EVM chains.

    ### Step 1. Set up your Solana client

    Create a new file called `deposit.ts` in the root of your project and add the
    following code. This code sets up the Solana connection and wallet client.

    ```ts deposit.ts theme={null}
    import {
      Wallet,
      AnchorProvider,
      setProvider,
      Program,
      utils,
    } from "@coral-xyz/anchor";
    import { Connection, Keypair, PublicKey, SystemProgram } from "@solana/web3.js";
    import {
      getAssociatedTokenAddressSync,
      TOKEN_PROGRAM_ID,
    } from "@solana/spl-token";
    import BN from "bn.js";

    /* Constants */
    const GATEWAY_WALLET_ADDRESS = "GATEwdfmYNELfp5wDmmR6noSr2vHnAfBPMm2PvCzX5vu";
    const USDC_ADDRESS = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
    const RPC_ENDPOINT = "https://api.devnet.solana.com";
    const DEPOSIT_AMOUNT = new BN(10_000_000); // 10 USDC (6 decimals)

    /* Gateway Wallet IDL (partial, for deposit only) */
    const gatewayWalletIdl = {
      address: GATEWAY_WALLET_ADDRESS,
      metadata: {
        name: "gatewayWallet",
        version: "0.1.0",
        spec: "0.1.0",
      },
      instructions: [
        {
          name: "deposit",
          discriminator: [22, 0],
          accounts: [
            { name: "payer", writable: true, signer: true },
            { name: "owner", signer: true },
            { name: "gatewayWallet" },
            { name: "ownerTokenAccount", writable: true },
            { name: "custodyTokenAccount", writable: true },
            { name: "deposit", writable: true },
            { name: "depositorDenylist" },
            { name: "tokenProgram" },
            { name: "systemProgram" },
            { name: "eventAuthority" },
            { name: "program" },
          ],
          args: [{ name: "amount", type: "u64" }],
        },
      ],
    };

    // Get keypair from environment
    if (!process.env.SOLANA_PRIVATE_KEYPAIR)
      throw new Error("SOLANA_PRIVATE_KEYPAIR not set");
    const keypair = Keypair.fromSecretKey(
      Uint8Array.from(JSON.parse(process.env.SOLANA_PRIVATE_KEYPAIR)),
    );

    // Set up connection and public keys
    const connection = new Connection(RPC_ENDPOINT, "confirmed");
    const usdcMint = new PublicKey(USDC_ADDRESS);
    const programId = new PublicKey(GATEWAY_WALLET_ADDRESS);
    const owner = keypair.publicKey;
    ```

    ### Step 2. Set up Anchor provider and program and derive PDAs

    Add the following code to `deposit.ts` to initialize the Anchor client and
    derive the Program Derived Addresses (PDAs) required by the Gateway Wallet.

    ```ts deposit.ts theme={null}
    // Set up Anchor provider and program
    const anchorWallet = new Wallet(keypair);
    const provider = new AnchorProvider(
      connection,
      anchorWallet,
      AnchorProvider.defaultOptions(),
    );
    setProvider(provider);
    const program = new Program(gatewayWalletIdl, provider);

    // Derive PDAs
    const pdas = {
      wallet: PublicKey.findProgramAddressSync(
        [Buffer.from(utils.bytes.utf8.encode("gateway_wallet"))],
        programId,
      )[0],
      custody: PublicKey.findProgramAddressSync(
        [
          Buffer.from(utils.bytes.utf8.encode("gateway_wallet_custody")),
          usdcMint.toBuffer(),
        ],
        programId,
      )[0],
      deposit: PublicKey.findProgramAddressSync(
        [Buffer.from("gateway_deposit"), usdcMint.toBuffer(), owner.toBuffer()],
        programId,
      )[0],
      denylist: PublicKey.findProgramAddressSync(
        [Buffer.from("denylist"), owner.toBuffer()],
        programId,
      )[0],
    };
    ```

    ### Step 3. Call the deposit instruction on the Gateway Wallet program

    Add the following code to `deposit.ts` to call the deposit instruction on the
    Gateway Wallet program. Note that you must use the Gateway Wallet's deposit
    instruction and not a standard SPL token transfer.

    <Warning>
      Directly transferring USDC to the Gateway Wallet program with a standard SPL
      token transfer will result in loss of that USDC. You must use the deposit
      instruction on the wallet program to get a unified USDC balance.
    </Warning>

    ```ts deposit.ts theme={null}
    // Deposit USDC into Gateway Wallet
    console.log(
      `Depositing ${Number(DEPOSIT_AMOUNT.toString()) / 1_000_000} USDC to Gateway Wallet...`,
    );

    const tx = await program.methods
      .deposit(DEPOSIT_AMOUNT)
      .accountsPartial({
        payer: owner,
        owner: owner,
        gatewayWallet: pdas.wallet,
        ownerTokenAccount: getAssociatedTokenAddressSync(usdcMint, owner),
        custodyTokenAccount: pdas.custody,
        deposit: pdas.deposit,
        depositorDenylist: pdas.denylist,
        tokenProgram: TOKEN_PROGRAM_ID,
        systemProgram: SystemProgram.programId,
      })
      .signers([keypair])
      .rpc();

    // Wait for confirmation
    const latest = await connection.getLatestBlockhash();
    await connection.confirmTransaction(
      {
        signature: tx,
        blockhash: latest.blockhash,
        lastValidBlockHeight: latest.lastValidBlockHeight,
      },
      "confirmed",
    );
    console.log(`Deposit tx: ${tx}`);
    ```

    ### Step 4. Run the script

    Run the script with the following command:

    ```shell theme={null}
    node --env-file=.env deposit.ts
    ```

    ### Step 5. Wait for finality

    Wait for the deposit transaction to reach finality. On Solana Devnet, finality
    is typically reached in a few seconds. Once the deposit transaction is final,
    your unified balance reflects the amount from Solana. If you have balances on
    other chains, the total balance is the sum of all the USDC from deposit
    transactions across all supported chains that have reached finality.
  </Tab>
</Tabs>
