> ## Documentation Index
> Fetch the complete documentation index at: https://circle-devdocs-test-ai-codegen-component.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart: Create and Transfer a Unified USDC Balance

This guide walks you through the process of creating a Unified Crosschain USDC
Balance on two chains using Circle Gateway, and transferring it to a third
chain.

This quickstart shows you how to use Circle Gateway to create chain abstracted
USDC balances to build an application experience similar to traditional finance.

<Note>
  **Note:** This quickstart provides all the code you need to create a unified
  crosschain balance and transfer it to a new chain. You can find the
  [full code for this quickstart on GitHub](https://github.com/circlefin/evm-gateway-contracts/tree/master/quickstart).
</Note>

## Prerequisites

Before you start building the sample app, ensure that **Node.js** and **npm**
are installed. You can download [Node.js](https://nodejs.org) directly, or use a
version manager like [nvm](https://github.com/nvm-sh/nvm). The npm binary comes
with Node.js.

You should also have created an Ethereum testnet wallet and have the address and
the private key available.

## Step 1: Set up your project

The following sections demonstrate how to initialize your project and set up the
required clients and smart contracts to interact with Circle Gateway.

### 1.1. Set up your development environment

Create a new directory for your project and initialize it with npm. Set the
package type to `module` and install the necessary dependencies:

```shell Shell theme={null}
mkdir unified-balance-quickstart && cd unified-balance-quickstart
npm init -y
npm pkg set type="module"
npm install --save viem dotenv
```

Create a new `.env` file.

```shell Shell theme={null}
touch .env
```

Edit the `.env` file and add the following variables, replacing
`{YOUR_PRIVATE_KEY}` with the private key for your testnet wallet:

```text theme={null}
PRIVATE_KEY={YOUR_PRIVATE_KEY}
```

### 1.2. Define contract ABIs

Create a file called `abis.js` in the project directory and add the following
code to it. This helper code defines the contract functions that you use to
establish a unified USDC balance and mint it on a new chain.

<Note>
  **Note**: For brevity, the ABIs in this code are not complete ABIs for the
  wallet and minter contracts. They only define the functions necessary for this
  quickstart. See
  [Contract Interfaces and Events](/gateway/references/contract-interfaces-and-events)
  for a full description of each contract's methods.
</Note>

```javascript Javascript theme={null}
///////////////////////////////////////////////////////////////////////////////
// ABIs used for the Gateway contracts

// The subset of the GatewayWallet ABI that is used in the quickstart guide
export const gatewayWalletAbi = [
  {
    type: "function",
    name: "deposit",
    inputs: [
      {
        name: "token",
        type: "address",
        internalType: "address",
      },
      {
        name: "value",
        type: "uint256",
        internalType: "uint256",
      },
    ],
    outputs: [],
    stateMutability: "nonpayable",
  },
];

// The subset of the GatewayMinter ABI that is used in the quickstart guide
export const gatewayMinterAbi = [
  {
    type: "function",
    name: "gatewayMint",
    inputs: [
      {
        name: "attestationPayload",
        type: "bytes",
        internalType: "bytes",
      },
      {
        name: "signature",
        type: "bytes",
        internalType: "bytes",
      },
    ],
    outputs: [],
    stateMutability: "nonpayable",
  },
];
```

### 1.3. Define the Gateway API client

Create a file called `gateway-client.js` in the project directory and add the
following code to it. This helper code provides data and methods for interacting
with the Gateway API.

```javascript Javascript theme={null}
/////////////////////////////////////////////////////////////////
// A lightweight API client for interacting with the Gateway API.

export class GatewayClient {
  static GATEWAY_API_BASE_URL = "https://gateway-api-testnet.circle.com/v1";

  // Identifiers used for supported blockchains
  // See https://developers.circle.com/cctp/cctp-supported-blockchains#cctp-supported-domains
  static DOMAINS = {
    ethereum: 0,
    mainnet: 0,
    sepolia: 0,
    avalanche: 1,
    avalancheFuji: 1,
    base: 6,
    baseSepolia: 6,
  };

  // Human-readable names for the supported blockchains, by domain
  static CHAINS = {
    0: "Ethereum",
    1: "Avalanche",
    6: "Base",
  };

  // Gets info about supported chains and contracts
  async info() {
    return this.#get("/info");
  }

  // Checks balances for a given depositor for the given domains. If no domains
  // are specified, it defaults to all supported domains.
  async balances(token, depositor, domains) {
    if (!domains)
      domains = Object.keys(GatewayClient.CHAINS).map((d) => parseInt(d));
    return this.#post("/balances", {
      token,
      sources: domains.map((domain) => ({ depositor, domain })),
    });
  }

  // Sends burn intents to the API to retrieve an attestation
  async transfer(body) {
    return this.#post("/transfer", body);
  }

  // Private method to do a GET request to the Gateway API
  async #get(path) {
    const url = GatewayClient.GATEWAY_API_BASE_URL + path;
    const response = await fetch(url);
    return response.json();
  }

  // Private method to do a POST request to the Gateway API
  async #post(path, body) {
    const url = GatewayClient.GATEWAY_API_BASE_URL + path;
    const headers = { "Content-Type": "application/json" };
    const response = await fetch(url, {
      method: "POST",
      headers,
      // Serialize bigints as strings
      body: JSON.stringify(body, (_key, value) =>
        typeof value === "bigint" ? value.toString() : value,
      ),
    });
    return response.json();
  }
}
```

### 1.4. Define typed data

Create a file called `typed-data.js` in the project directory and add the
following code to it:

```javascript Javascript theme={null}
import { randomBytes } from "node:crypto";
import { pad, zeroAddress, maxUint256 } from "viem";

///////////////////////////////////////////////////////////////////////////////
// EIP-712 typed data utils for burn intents and burn intent sets

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

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

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" },
];

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

const BurnIntentSet = [{ name: "intents", type: "BurnIntent[]" }];

function addressToBytes32(address) {
  return pad(address.toLowerCase(), { size: 32 });
}

export function burnIntent({ account, from, to, amount, recipient }) {
  return {
    // Needs to be at least 7 days in the future
    maxBlockHeight: maxUint256,
    // 2.01 USDC will cover the fee for any chain.
    maxFee: 2_010000n,
    // The details of the transfer
    spec: {
      version: 1,
      sourceDomain: from.domain,
      destinationDomain: to.domain,
      sourceContract: from.gatewayWallet.address,
      destinationContract: to.gatewayMinter.address,
      sourceToken: from.usdc.address,
      destinationToken: to.usdc.address,
      sourceDepositor: account.address,
      destinationRecipient: recipient || account.address,
      sourceSigner: account.address,
      destinationCaller: zeroAddress, // Anyone can use the attestation
      value: BigInt(Math.floor(amount * 1e6)), // Convert the amount string to USDC atomic units
      salt: "0x" + randomBytes(32).toString("hex"),
      hookData: "0x", // No hook data for now
    },
  };
}

export function burnIntentTypedData(burnIntent) {
  return {
    types: { EIP712Domain, TransferSpec, BurnIntent },
    domain,
    primaryType: "BurnIntent",
    message: {
      ...burnIntent,
      spec: {
        ...burnIntent.spec,
        sourceContract: addressToBytes32(burnIntent.spec.sourceContract),
        destinationContract: addressToBytes32(
          burnIntent.spec.destinationContract,
        ),
        sourceToken: addressToBytes32(burnIntent.spec.sourceToken),
        destinationToken: addressToBytes32(burnIntent.spec.destinationToken),
        sourceDepositor: addressToBytes32(burnIntent.spec.sourceDepositor),
        destinationRecipient: addressToBytes32(
          burnIntent.spec.destinationRecipient,
        ),
        sourceSigner: addressToBytes32(burnIntent.spec.sourceSigner),
        destinationCaller: addressToBytes32(
          burnIntent.spec.destinationCaller ?? zeroAddress,
        ),
      },
    },
  };
}

export function burnIntentSetTypedData({ intents }) {
  return {
    types: { EIP712Domain, TransferSpec, BurnIntent, BurnIntentSet },
    domain,
    primaryType: "BurnIntentSet",
    message: {
      intents: intents.map((intent) => burnIntentTypedData(intent).message),
    },
  };
}
```

### 1.5. Initialize clients and contracts

Create a file called `setup.js` in the project directory and add the following
code to it. This code provides a client and contract addresses on the relevant
chains for this quickstart:

```javascript Javascript theme={null}
import "dotenv/config";
import { createPublicClient, getContract, http, erc20Abi } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import * as chains from "viem/chains";
import { GatewayClient } from "./gateway-client.js";
import { gatewayWalletAbi, gatewayMinterAbi } from "./abis.js";

// Addresses that are needed across networks
const gatewayWalletAddress = "0x0077777d7EBA4688BDeF3E311b846F25870A19B9";
const gatewayMinterAddress = "0x0022222ABE238Cc2C7Bb1f21003F0a260052475B";
const usdcAddresses = {
  sepolia: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
  baseSepolia: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
  avalancheFuji: "0x5425890298aed601595a70ab815c96711a31bc65",
};

// Sets up a client and contracts for the given chain and account
function setup(chainName, account) {
  const chain = chains[chainName];
  const client = createPublicClient({
    chain,
    account,
    // Use the flashblocks-aware RPC for Base Sepolia, otherwise use the default RPC
    transport:
      chainName === "baseSepolia"
        ? http("https://sepolia-preconf.base.org")
        : http(),
  });

  return {
    client,
    name: chain.name,
    domain: GatewayClient.DOMAINS[chainName],
    currency: chain.nativeCurrency.symbol,
    usdc: getContract({
      address: usdcAddresses[chainName],
      abi: erc20Abi,
      client,
    }),
    gatewayWallet: getContract({
      address: gatewayWalletAddress,
      abi: gatewayWalletAbi,
      client,
    }),
    gatewayMinter: getContract({
      address: gatewayMinterAddress,
      abi: gatewayMinterAbi,
      client,
    }),
  };
}

// Create an account from the private key set in .env
export const account = privateKeyToAccount(process.env.PRIVATE_KEY);
console.log(`Using account: ${account.address}`);

// Set up clients and contracts for each chain
export const ethereum = setup("sepolia", account);
export const base = setup("baseSepolia", account);
export const avalanche = setup("avalancheFuji", account);
```

### 1.6. Add testnet funds to your wallet

To interact with Gateway, you need USDC and testnet native tokens in your wallet
on each chain that you deposit from. You also need testnet native tokens on the
destination chain to make the call to the Gateway Minter contract. Use the
[Circle Faucet](https://faucet.circle.com/) to get 10 USDC on the Avalanche Fuji
and Ethereum Sepolia testnets.

Use the following faucets to get testnet native tokens in your wallet:

* [Avalanche Fuji faucet](https://core.app/tools/testnet-faucet)
* [Base Sepolia faucet](https://www.alchemy.com/faucets/base-sepolia)
* [Ethereum Sepolia faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia)

## Step 2: Create a unified crosschain balance

In this step, you deposit USDC into the Gateway Wallet contract on Avalanche
Fuji and Ethereum Sepolia, creating a unified crosschain balance for your
address in Gateway.

### 2.1. Deposit USDC into the Gateway Wallet contracts

Create a file called `deposit.js` in the project directory and add the following
code to it. This code deposits USDC into the Gateway Wallet contracts.

Note that for the transfer, the code is not calling the ERC-20 `transfer`
function, as doing so would result in a loss of funds. Instead, the code first
calls the `approve` function on the USDC contract to allow the Gateway Wallet
contract to transfer USDC on behalf of your account. Then the code calls the
`deposit` function on the Gateway Wallet contract to move the USDC.

```javascript Javascript theme={null}
import { account, ethereum, base, avalanche } from "./setup.js";

const DEPOSIT_AMOUNT = 10_000000n; // 10 USDC

// Deposit into the GatewayWallet contract on all chains
for (const chain of [ethereum, base, avalanche]) {
  // Get the wallet's current USDC balance
  console.log(`Checking USDC balance on ${chain.name}...`);
  const balance = await chain.usdc.read.balanceOf([account.address]);

  // Ensure the balance is sufficient for the deposit
  if (balance < DEPOSIT_AMOUNT) {
    console.error(`Insufficient USDC balance on ${chain.name}!`);
    console.error("Please top up at https://faucet.circle.com.");
    process.exit(1);
  }

  // Attempt to approve and deposit USDC into the GatewayWallet contract, and
  // handle the error if the waallet does not have enough funds to pay for gas
  try {
    // Approve the GatewayWallet contract for the wallet's USDC
    console.log("Approving the GatewayWallet contract for USDC...");
    const approvalTx = await chain.usdc.write.approve([
      chain.gatewayWallet.address,
      DEPOSIT_AMOUNT,
    ]);
    await chain.client.waitForTransactionReceipt({ hash: approvalTx });
    console.log("Done! Transaction hash:", approvalTx);

    // Deposit USDC into the GatewayWallet contract
    console.log("Depositing USDC into the GatewayWallet contract...");
    const depositTx = await chain.gatewayWallet.write.deposit([
      chain.usdc.address,
      DEPOSIT_AMOUNT,
    ]);
    await chain.client.waitForTransactionReceipt({ hash: depositTx });
    console.log("Done! Transaction hash:", depositTx);
  } catch (error) {
    if (error.details.includes("insufficient funds")) {
      // If there wasn't enough for gas, log an error message and exit
      console.error(
        `The wallet does not have enough ${chain.currency} to pay for gas on ${chain.name}!`,
      );
      console.error(`Please top up using a faucet.`);
    } else {
      // Log any other errors for debugging
      console.error(error);
    }
    process.exit(1);
  }
}
```

### 2.2. Run the script to create a crosschain balance

Run the `deposit.js` script to make the deposits. Note that if you don't have
enough USDC or testnet native tokens, the code returns an error message.

```shell Shell theme={null}
node deposit.js
```

<Note>
  **Note:** You must wait for finality on both chains for the unified balance to
  be updated. On Avalanche, finality is reached immediately. On Ethereum, you
  may need to wait up to 20 minutes for finality.
</Note>

## Step 3: Transfer USDC from the crosschain balance to Base

In this step, you create a burn intent, submit it to the API and receive an
attestation, which you can use to mint USDC on Base Sepolia. Funds are burned
from the crosschain balance on Avalanche and Base.

### 3.1. Sign burn intents

Create a file called `transfer.js` in the project directory and add the
following code to it. This code performs checks that the API supports the chains
that you want to interact with, and verifies that your balance on the source
chains is enough to cover the transfer.

```javascript Javascript theme={null}
import { account, ethereum, base, avalanche } from "./setup.js";
import { GatewayClient } from "./gateway-client.js";
import { burnIntent, burnIntentTypedData } from "./typed-data.js";

// Initialize a lightweight API client for interacting with Gateway
const gatewayClient = new GatewayClient();

// Check the info endpoint to confirm which chains are supported
// Not necessary for the transfer, but useful information
console.log("Fetching Gateway API info...");
const info = await gatewayClient.info();
for (const domain of info.domains) {
  console.log(
    `  - ${domain.chain} ${domain.network}`,
    `(wallet: ${"walletContract" in domain}, minter: ${"minterContract" in domain})`,
  );
}

// Check the account's balances with the Gateway API
console.log(`Checking balances...`);
const { balances } = await gatewayClient.balances("USDC", account.address);
for (const balance of balances) {
  console.log(
    `  - ${GatewayClient.CHAINS[balance.domain]}:`,
    `${balance.balance} USDC`,
  );
}

// These are the amounts we intent on transferring from each chain we deposited on
const fromEthereumAmount = 2;
const fromAvalancheAmount = 3;

// Check to see if Gateway has picked up the Avalanche deposit yet
// Since Avalanche has instant finality, this should be quick
const avalancheBalance = balances.find(
  (b) => b.domain === GatewayClient.DOMAINS.avalanche,
).balance;
if (parseFloat(avalancheBalance) < fromAvalancheAmount) {
  console.error(
    "Gateway deposit not yet picked up on Avalanche, wait until finalization",
  );
  process.exit(1);
} else {
  console.error("Gateway deposit picked up on Avalanche!");
}

// Check to see if Gateway has picked up the Ethereum deposit yet
// Ethereum takes about 20 minutes to finalize blocks, so you may need to wait a bit
const ethereumBalance = balances.find(
  (b) => b.domain === GatewayClient.DOMAINS.ethereum,
).balance;
if (parseFloat(ethereumBalance) < fromEthereumAmount) {
  console.error(
    "Gateway deposit not yet picked up on Ethereum, wait until finalization",
  );
  process.exit(1);
} else {
  console.error("Gateway deposit picked up on Ethereum!");
}

// Construct the burn intents
console.log("Constructing burn intent set...");
const burnIntents = [
  burnIntent({
    account,
    from: ethereum,
    to: base,
    amount: fromEthereumAmount,
    recipient: account.address,
  }),
  burnIntent({
    account,
    from: avalanche,
    to: base,
    amount: fromAvalancheAmount,
    recipient: account.address,
  }),
];

// Sign the burn intents
console.log("Signing burn intents...");
const request = await Promise.all(
  burnIntents.map(async (intent) => {
    const typedData = burnIntentTypedData(intent);
    const signature = await account.signTypedData(typedData);
    return { burnIntent: typedData.message, signature };
  }),
);
```

### 3.2. Submit burn intents to the API

Add the following code to `transfer.js` to submit the burn intents to the
Gateway in exchange for an attestation:

```javascript Javascript theme={null}
// Request the attestation
console.log("Requesting attestation from Gateway API...");
const start = performance.now();
const response = await gatewayClient.transfer(request);
const end = performance.now();
if (response.success === false) {
  console.error("Error from Gateway API:", response.message);
  process.exit(1);
}
console.log(
  "Received attestation from Gateway API in",
  (end - start).toFixed(2),
  "ms",
);
```

### 3.3. Mint USDC on Base Sepolia

Add the following code to `transfer.js` to mint USDC on Base Sepolia using the
attestation:

```javascript Javascript theme={null}
// Mint the funds on Base
console.log("Minting funds on Base...");
const { attestation, signature } = response;
const mintTx = await base.gatewayMinter.write.gatewayMint([
  attestation,
  signature,
]);
await base.client.waitForTransactionReceipt({ hash: mintTx });
console.log("Done! Transaction hash:", mintTx);
process.exit(0);
```

### 3.4. Run the transfer script

Run the `transfer.js` script to perform the USDC transfer from the crosschain
balance to Base.

```shell Shell theme={null}
node transfer.js
```
