Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.polygon.technology/llms.txt

Use this file to discover all available pages before exploring further.

Payments require handling sensitive data. The code examples below demonstrate the integration pattern and should not be used in production as-is. In production, store sensitive information such as API keys and private keys in a secrets manager or vault.
USDC is one of the most-used stablecoins on Polygon PoS. Low transaction costs on Polygon make it a practical choice for USDC payments, including micro-payments and high-volume payment flows. Circle provides full USDC documentation, including SDKs and sample applications. Two integration paths are available: native USDC transfer using ERC-20, and cross-chain transfer using Circle’s Gateway.

Native USDC transfer

Native USDC on Polygon PoS is an ERC-20 contract that uses the standard approve/transfer pattern.
// pnpm add viem
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
import { polygon } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

// Native USDC on Polygon PoS (NOT the old bridged USDC.e)
// Source: Circle "USDC Contract Addresses"
const USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";

// Testnet USDC on Amoy
// const USDC = "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582";

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

const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`);

const rpc = http(process.env.POLYGON_RPC_URL); // e.g. https://polygon-rpc.com
const pub = createPublicClient({ chain: polygon, transport: rpc });
const wallet = createWalletClient({ chain: polygon, transport: rpc, account });

async function main() {
  const me = account.address as `0x${string}`;
  const decimals = await pub.readContract({ address: USDC, abi: erc20, functionName: "decimals" });
  const bal = await pub.readContract({ address: USDC, abi: erc20, functionName: "balanceOf", args: [me] });
  console.log("USDC balance:", Number(bal) / 10 ** Number(decimals));

  // Transfer 1 USDC
  const amount = parseUnits("1", Number(decimals));
  const hash = await wallet.writeContract({ address: USDC, abi: erc20, functionName: "transfer", args: ["0xRecipient...", amount] });
  console.log("tx:", hash);
}
main();

Gateway integration (cross-chain)

Circle Gateway aggregates USDC balances across any supported chain. Users deposit USDC into the Gateway Wallet (not an ERC-20 address). Once deposited, they can send USDC to any supported chain using the Gateway. For more details on setting up a Gateway balance, see Circle’s documentation.

Prerequisites


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 into GatewayWallet on Polygon
  const decimals = await pub.readContract({ address: USDC, abi: erc20, functionName: "decimals" });
  const amount = parseUnits("25", Number(decimals)); // example

  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
  // Build and sign a BurnIntent (EIP-712 typed data) for source=Polygon, destination=Polygon (same-chain "withdraw") or another chain.
  // For brevity, assume you already produced `burnIntent` and EIP-712 signature with your EOA.
  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: {
        maxBlockHeight: "999999999999",
        maxFee: "0",
        spec: {
          version: 1,
          sourceDomain: 7,          // Polygon PoS domain id (per Circle docs)
          destinationDomain: 7,     // or another supported domain id
          sourceContract: GATEWAY_WALLET,
          destinationContract: GATEWAY_MINTER,
          sourceToken: USDC,
          destinationToken: USDC,
          sourceDepositor: account.address,
          destinationRecipient: "0xRecipient...", // who receives on destination
          sourceSigner: account.address,
          destinationCaller: "0x0000000000000000000000000000000000000000",
          value: amount.toString(),
        },
      },
      // include your EIP-712 signature of BurnIntent here if required by your flow
    }),
  });
  const { attestationPayload, signature } = await res.json();

  // 3) Submit attestation to GatewayMinter on destination chain
  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();