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.
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.
You can perform this action on multiple chains to establish a chain-abstracted
balance.
Select a tab below for EVM or Solana-specific instructions.
Prerequisites
Before you begin, ensure that you’ve:
-
Installed Node.js v22+
-
Prepared an EVM testnet wallet with the private key available
-
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:
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.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.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() method
on the Gateway Wallet contract. Note that you must use the deposit() method
and not the standard transfer on the USDC contract.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.
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:npx tsx --env-file=.env deposit.ts
Step 4. Wait for the required number of block confirmations
Wait for the
required number of 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.Prerequisites
Before you begin, ensure that you’ve:
-
Installed Node.js v22+
-
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:
SOLANA_PRIVATE_KEYPAIR={YOUR_SOLANA_KEYPAIR_ARRAY}
To generate a new Solana key pair, run: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:const bytes = bs58.decode({ YOUR_PRIVATE_KEY_HASH });
console.log(JSON.stringify(Array.from(bytes)));
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.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.// 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.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.
// 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:npx tsx --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.