Skip to main content
The following examples are demonstrations of integrations and should not be used in production. In production, store sensitive information such as API keys and private keys in a secrets manager or vault.
This guide covers the Gateway approach to USDC on Polygon. Gateway USDC uses Circle’s Cross-Chain Transfer Protocol (CCTP), which lets users hold a single omnichain balance and move it across chains without separate per-chain contracts. For payments that stay within Polygon, see USDC Native Integration.
ApproachDescriptionWhen to use
Native USDCStandard ERC-20 contract directly on Polygon PoSWhen you only need payments within Polygon
Gateway USDCCircle’s cross-chain system that moves USDC between blockchains (CCTP)When your users need to send or receive USDC across chains

How Gateway USDC Works

The Circle Gateway is a cross-chain USDC liquidity layer:
  • Deposits: Users send USDC to a Gateway Wallet contract.
  • Attestations: Circle verifies the burn event on the source chain.
  • Mints: A Gateway Minter contract releases the same amount on the destination chain.
This design allows trust-minimized USDC movement between chains.

Example: Deposit, Attest, and Mint

The following example:
  1. Deposits USDC into the Gateway wallet on Polygon
  2. Requests an attestation from Circle’s API
  3. Mints the funds back via the Gateway Minter
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
import { polygon } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { fetch } from "undici";

const USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
const GATEWAY_WALLET = "0x77777777Dcc4d5A8B6E418Fd04D8997ef11000eE";
const GATEWAY_MINTER = "0x2222222d7164433c4C09B0b0D809a9b52C04C205";

const erc20 = [
  { type: "function", name: "decimals", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] },
  { type: "function", name: "approve", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] },
];

const gatewayWalletAbi = [
  { type: "function", name: "deposit", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [] },
];

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

const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`);
const rpc = http(process.env.POLYGON_RPC_URL);
const pub = createPublicClient({ chain: polygon, transport: rpc });
const wallet = createWalletClient({ chain: polygon, transport: rpc, account });

async function gatewayTransfer() {
  // 1. Approve + deposit
  const decimals = await pub.readContract({ address: USDC, abi: erc20, functionName: "decimals" });
  const amount = parseUnits("25", Number(decimals));

  await wallet.writeContract({ address: USDC, abi: erc20, functionName: "approve", args: [GATEWAY_WALLET, amount] });
  await wallet.writeContract({ address: GATEWAY_WALLET, abi: gatewayWalletAbi, functionName: "deposit", args: [USDC, amount] });

  // 2. Request attestation from Circle Gateway API
  const res = await fetch("https://gateway-api.circle.com/v1/transfer", {
    method: "POST",
    headers: { "content-type": "application/json", authorization: `Bearer ${process.env.CIRCLE_API_KEY}` },
    body: JSON.stringify({
      burnIntent: {
        spec: {
          version: 1,
          sourceDomain: 7, // Polygon domain ID
          destinationDomain: 7, // same-chain "withdraw", or change for cross-chain
          sourceContract: GATEWAY_WALLET,
          destinationContract: GATEWAY_MINTER,
          sourceToken: USDC,
          destinationToken: USDC,
          sourceDepositor: account.address,
          destinationRecipient: "0xRecipient...",
          value: amount.toString(),
        },
      },
    }),
  });
  const { attestationPayload, signature } = await res.json();

  // 3. Submit attestation to GatewayMinter
  const tx = await wallet.writeContract({
    address: GATEWAY_MINTER,
    abi: gatewayMinterAbi,
    functionName: "gatewayMint",
    args: [attestationPayload as `0x${string}`, signature as `0x${string}`],
  });
  console.log("gatewayMint tx:", tx);
}

gatewayTransfer();