|
| 1 | +## USDC |
| 2 | +USDC is ranked as the 7th largest cryptocurrency, with a circulation of $65 billion. Nearly 5 million monthly active users engage with USDC on Polygon protocols. Cost-efficient transactions on Polygon makes it the default chain for USDC payments, especially micro or high-volume payments. |
| 3 | + |
| 4 | +### Integrating USDC |
| 5 | +Circle has [excellent documentation,](https://developers.circle.com/) including ready-to-use SDKs and sample applications |
| 6 | + |
| 7 | +For those looking to easily integrate USDC to Polygon, we have two options below: **Native USDC integration** or crosschain USDC using Circle’s **Gateway integration** |
| 8 | + |
| 9 | +### Native USDC integration: |
| 10 | + |
| 11 | +USDC is an ERC-20 contract with the standard `approve`/`transfer` process |
| 12 | + |
| 13 | +```ts |
| 14 | +// pnpm add viem |
| 15 | +import { createPublicClient, createWalletClient, http, parseUnits } from "viem"; |
| 16 | +import { polygon } from "viem/chains"; |
| 17 | +import { privateKeyToAccount } from "viem/accounts"; |
| 18 | + |
| 19 | +// Native USDC on Polygon PoS (NOT the old bridged USDC.e) |
| 20 | +// Source: Circle "USDC Contract Addresses" |
| 21 | +const USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"; |
| 22 | + |
| 23 | +// Testnet USDC on Amoy |
| 24 | +// const USDC = "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582"; |
| 25 | + |
| 26 | +const erc20 = [ |
| 27 | + { type: "function", name: "decimals", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] }, |
| 28 | + { type: "function", name: "balanceOf", stateMutability: "view", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }] }, |
| 29 | + { type: "function", name: "approve", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] }, |
| 30 | + { type: "function", name: "transfer", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] }, |
| 31 | +]; |
| 32 | + |
| 33 | +const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`); |
| 34 | + |
| 35 | +const rpc = http(process.env.POLYGON_RPC_URL); // e.g. https://polygon-rpc.com |
| 36 | +const pub = createPublicClient({ chain: polygon, transport: rpc }); |
| 37 | +const wallet = createWalletClient({ chain: polygon, transport: rpc, account }); |
| 38 | + |
| 39 | +async function main() { |
| 40 | + const me = account.address as `0x${string}`; |
| 41 | + const decimals = await pub.readContract({ address: USDC, abi: erc20, functionName: "decimals" }); |
| 42 | + const bal = await pub.readContract({ address: USDC, abi: erc20, functionName: "balanceOf", args: [me] }); |
| 43 | + console.log("USDC balance:", Number(bal) / 10 ** Number(decimals)); |
| 44 | + |
| 45 | + // Transfer 1 USDC |
| 46 | + const amount = parseUnits("1", Number(decimals)); |
| 47 | + const hash = await wallet.writeContract({ address: USDC, abi: erc20, functionName: "transfer", args: ["0xRecipient...", amount] }); |
| 48 | + console.log("tx:", hash); |
| 49 | +} |
| 50 | +main(); |
| 51 | +``` |
| 52 | + |
| 53 | +### Gateway integration: |
| 54 | + |
| 55 | +```ts |
| 56 | + |
| 57 | +import { createPublicClient, createWalletClient, http, parseUnits } from "viem"; |
| 58 | +import { polygon } from "viem/chains"; |
| 59 | +import { privateKeyToAccount } from "viem/accounts"; |
| 60 | +import { fetch } from "undici"; |
| 61 | + |
| 62 | +const USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"; |
| 63 | +const GATEWAY_WALLET = "0x77777777Dcc4d5A8B6E418Fd04D8997ef11000eE"; |
| 64 | +const GATEWAY_MINTER = "0x2222222d7164433c4C09B0b0D809a9b52C04C205"; |
| 65 | + |
| 66 | +const erc20 = [ |
| 67 | + { type: "function", name: "decimals", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] }, |
| 68 | + { type: "function", name: "approve", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] }, |
| 69 | +]; |
| 70 | + |
| 71 | +const gatewayWalletAbi = [ |
| 72 | + { type: "function", name: "deposit", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [] }, |
| 73 | +]; |
| 74 | + |
| 75 | +const gatewayMinterAbi = [ |
| 76 | + { type: "function", name: "gatewayMint", stateMutability: "nonpayable", inputs: [{ type: "bytes" }, { type: "bytes" }], outputs: [] }, |
| 77 | +]; |
| 78 | + |
| 79 | +const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`); |
| 80 | +const rpc = http(process.env.POLYGON_RPC_URL); |
| 81 | +const pub = createPublicClient({ chain: polygon, transport: rpc }); |
| 82 | +const wallet = createWalletClient({ chain: polygon, transport: rpc, account }); |
| 83 | + |
| 84 | +async function gatewayTransfer() { |
| 85 | + // 1) Approve + deposit into GatewayWallet on Polygon |
| 86 | + const decimals = await pub.readContract({ address: USDC, abi: erc20, functionName: "decimals" }); |
| 87 | + const amount = parseUnits("25", Number(decimals)); // example |
| 88 | + |
| 89 | + await wallet.writeContract({ address: USDC, abi: erc20, functionName: "approve", args: [GATEWAY_WALLET, amount] }); |
| 90 | + await wallet.writeContract({ address: GATEWAY_WALLET, abi: gatewayWalletAbi, functionName: "deposit", args: [USDC, amount] }); |
| 91 | + |
| 92 | + // 2) Request attestation from Circle Gateway API |
| 93 | + // Build and sign a BurnIntent (EIP-712 typed data) for source=Polygon, destination=Polygon (same-chain "withdraw") or another chain. |
| 94 | + // For brevity, assume you already produced `burnIntent` and EIP-712 signature with your EOA. |
| 95 | + const res = await fetch("https://gateway-api.circle.com/v1/transfer", { |
| 96 | + method: "POST", |
| 97 | + headers: { "content-type": "application/json", authorization: `Bearer ${process.env.CIRCLE_API_KEY}` }, |
| 98 | + body: JSON.stringify({ |
| 99 | + burnIntent: { |
| 100 | + maxBlockHeight: "999999999999", |
| 101 | + maxFee: "0", |
| 102 | + spec: { |
| 103 | + version: 1, |
| 104 | + sourceDomain: 7, // Polygon PoS domain id (per Circle docs) |
| 105 | + destinationDomain: 7, // or another supported domain id |
| 106 | + sourceContract: GATEWAY_WALLET, |
| 107 | + destinationContract: GATEWAY_MINTER, |
| 108 | + sourceToken: USDC, |
| 109 | + destinationToken: USDC, |
| 110 | + sourceDepositor: account.address, |
| 111 | + destinationRecipient: "0xRecipient...", // who receives on destination |
| 112 | + sourceSigner: account.address, |
| 113 | + destinationCaller: "0x0000000000000000000000000000000000000000", |
| 114 | + value: amount.toString(), |
| 115 | + }, |
| 116 | + }, |
| 117 | + // include your EIP-712 signature of BurnIntent here if required by your flow |
| 118 | + }), |
| 119 | + }); |
| 120 | + const { attestationPayload, signature } = await res.json(); |
| 121 | + |
| 122 | + // 3) Submit attestation to GatewayMinter on destination chain |
| 123 | + const tx = await wallet.writeContract({ |
| 124 | + address: GATEWAY_MINTER, |
| 125 | + abi: gatewayMinterAbi, |
| 126 | + functionName: "gatewayMint", |
| 127 | + args: [attestationPayload as `0x${string}`, signature as `0x${string}`], |
| 128 | + }); |
| 129 | + console.log("gatewayMint tx:", tx); |
| 130 | +} |
| 131 | + |
| 132 | +gatewayTransfer(); |
| 133 | +``` |
| 134 | + |
| 135 | +## Tether |
| 136 | + |
| 137 | +USDT makes up over half of the stablecoins on Polygon and has been upgraded from USDT to Polygon-native USDT0, the omnichain deployment of USDT. |
| 138 | + |
| 139 | +### Integrating Tether |
| 140 | + |
| 141 | +Tether is an ERC-20 and follows the common `approve`/`transfer` flow: |
| 142 | + |
| 143 | + |
| 144 | + |
| 145 | +```ts |
| 146 | +import { createPublicClient, createWalletClient, http, parseUnits } from "viem"; |
| 147 | +import { polygon } from "viem/chains"; |
| 148 | +import { privateKeyToAccount } from "viem/accounts"; |
| 149 | + |
| 150 | +// Mainnet, No testnet version of USDT |
| 151 | +const USDT = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"; |
| 152 | +const erc20 = [ |
| 153 | + { type: "function", name: "decimals", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] }, |
| 154 | + { type: "function", name: "balanceOf", stateMutability: "view", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }] }, |
| 155 | + { type: "function", name: "approve", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] }, |
| 156 | + { type: "function", name: "transfer", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] }, |
| 157 | +]; |
| 158 | + |
| 159 | +const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`); |
| 160 | +const rpc = http(process.env.POLYGON_RPC_URL); |
| 161 | +const pub = createPublicClient({ chain: polygon, transport: rpc }); |
| 162 | +const wallet = createWalletClient({ chain: polygon, transport: rpc, account }); |
| 163 | + |
| 164 | +async function main() { |
| 165 | + const me = account.address as `0x${string}`; |
| 166 | + const decimals = await pub.readContract({ address: USDT, abi: erc20, functionName: "decimals" }); |
| 167 | + const bal = await pub.readContract({ address: USDT, abi: erc20, functionName: "balanceOf", args: [me] }); |
| 168 | + console.log("USDT balance:", Number(bal) / 10 ** Number(decimals)); |
| 169 | + |
| 170 | + // Transfer 1 USDT |
| 171 | + const amount = parseUnits("1", Number(decimals)); |
| 172 | + const hash = await wallet.writeContract({ |
| 173 | + address: USDT, |
| 174 | + abi: erc20, |
| 175 | + functionName: "transfer", |
| 176 | + args: ["0xRecipient...", amount], |
| 177 | + }); |
| 178 | + console.log("tx:", hash); |
| 179 | +} |
| 180 | +main(); |
| 181 | +``` |
| 182 | + |
| 183 | +## Stripe |
| 184 | + |
| 185 | + |
| 186 | + |
| 187 | +## Bridge |
| 188 | + |
| 189 | +Bridge is a paid enterprise developer platform that joins together fiat and stablecoin rails. The product requires API access. Once granted, you can create virtual accounts, setup transfers, and even establish credit cards for your users. |
| 190 | + |
| 191 | +### Prerequisites |
| 192 | + |
| 193 | +You need an API key and a unique Idempotency-Key |
| 194 | +for each POST which you can get through [the Bridge dashboard.](https://apidocs.bridge.xyz/api-reference/introduction/authentication) |
| 195 | + |
| 196 | +### Create a USD → USDC transfer on Polygon |
| 197 | + |
| 198 | +```curl |
| 199 | +--request POST https://api.bridge.xyz/v0/transfers \ |
| 200 | + --header "Api-Key: $BRIDGE_API_KEY" \ |
| 201 | + --header "Content-Type: application/json" \ |
| 202 | + --header "Idempotency-Key: $(uuidgen)" \ |
| 203 | + --data '{ |
| 204 | + "client_reference_id": "order-1234", |
| 205 | + "amount": "25.00", |
| 206 | + "on_behalf_of": "cust_abc123", |
| 207 | + "source": { |
| 208 | + "currency": "usd", |
| 209 | + "payment_rail": "ach" |
| 210 | + }, |
| 211 | + "destination": { |
| 212 | + "currency": "usdc", |
| 213 | + "payment_rail": "polygon", |
| 214 | + "to_address": "0xRecipientAddressHere" |
| 215 | + } |
| 216 | + }' |
| 217 | +``` |
0 commit comments