bridge-stablecoin
Overview
Crosschain Transfer Protocol (CCTP) is Circle's native protocol for burning USDC on one chain and minting it on another. App Kit (@circle-fin/app-kit) is Circle's all-inclusive SDK for payment and liquidity workflows -- it covers bridge, swap, send, and future capabilities in a single package. Standalone Bridge Kit (@circle-fin/bridge-kit) provides the same bridge API surface in a lighter package for bridge-only use cases.
Both SDKs orchestrate the full CCTP lifecycle -- approve, burn, attestation fetch, and mint -- in a single kit.bridge() call across EVM chains and Solana. Bridge operations do not require a kit key (kit key is only needed for swap and send operations in App Kit). Recommend App Kit for most users because it provides easier extensibility to swap and send without switching SDKs. Only recommend Bridge Kit when the user explicitly wants bridge-only functionality.
Prerequisites / Setup
Installation
App Kit with Viem adapter (recommended):
npm install @circle-fin/app-kit @circle-fin/adapter-viem-v2
Bridge Kit standalone with Viem adapter:
npm install @circle-fin/bridge-kit @circle-fin/adapter-viem-v2
For Solana support, also install:
npm install @circle-fin/adapter-solana-kit
For Circle Wallets (developer-controlled) support:
npm install @circle-fin/adapter-circle-wallets
Environment Variables
PRIVATE_KEY= # EVM wallet private key (hex, 0x-prefixed)
EVM_PRIVATE_KEY= # EVM private key (when also using Solana)
SOLANA_PRIVATE_KEY= # Solana wallet private key (base58)
CIRCLE_API_KEY= # Circle API key (for Circle Wallets adapter)
CIRCLE_ENTITY_SECRET= # Entity secret (for Circle Wallets adapter)
EVM_WALLET_ADDRESS= # Developer-controlled EVM wallet address
SOLANA_WALLET_ADDRESS= # Developer-controlled Solana wallet address
No KIT_KEY is needed for bridge operations. A kit key is only required if you also use swap or send features via App Kit.
SDK Initialization
App Kit (recommended):
import { AppKit } from "@circle-fin/app-kit";
const kit = new AppKit();
Bridge Kit (standalone):
import { BridgeKit } from "@circle-fin/bridge-kit";
const kit = new BridgeKit();
Decision Guide
ALWAYS walk through these questions with the user before writing any code. Do not skip steps or assume answers.
SDK Choice
Question 1 -- Will you need swap or send functionality in the future?
- Yes, or unsure -> App Kit (recommended) -- single SDK covers bridge + swap + send, easier to extend later
- No, bridge-only and will never need swap or send -> Bridge Kit -- standalone, lighter package for bridge-only use cases
Wallet / Adapter Choice
Question 2 -- How do you manage your wallet/keys?
- Managing your own private key (self-custodied, stored in env var or secrets manager) -> Question 3
- Using Circle developer-controlled wallets (Circle manages key storage and signing) -> Use Circle Wallets adapter. READ
references/adapter-circle-wallets.md - Using browser wallets (wagmi, ConnectKit, RainbowKit) -> Use wagmi adapter. READ
references/adapter-wagmi.md
Question 3 -- Which chains are you bridging between?
- EVM-to-EVM or EVM-to-Solana -> Use Viem and/or Solana Kit adapters. READ
references/adapter-private-key.md
Core Concepts
- CCTP steps: Every bridge transfer executes four sequential steps --
approve(ERC-20 allowance),burn(destroy USDC on source chain),fetchAttestation(wait for Circle to sign the burn proof), andmint(create USDC on destination chain). - Adapters: Both App Kit and Bridge Kit use adapter objects to abstract wallet/signer differences. Each ecosystem has its own adapter factory (
createViemAdapterFromPrivateKey,createSolanaKitAdapterFromPrivateKey,createCircleWalletsAdapter). The same adapter instance can serve as both source and destination when bridging within the same ecosystem. - Forwarding Service: When
useForwarder: trueis set on the destination, Circle's infrastructure handles attestation fetching and mint submission. This removes the need for a destination wallet or polling loop. There is a per-transfer fee that varies by route (see below). - Transfer speed: CCTP fast mode (default) completes in ~8-20 seconds. Standard mode takes ~15-19 minutes.
- Chain identifiers: Both SDKs use string chain names (e.g.,
"Arc_Testnet","Base_Sepolia","Solana_Devnet"), not numeric chain IDs, in thekit.bridge()call. - No kit key for bridge: Bridge operations do not require a kit key with either SDK. A kit key is only needed for swap and send operations in App Kit.
Implementation Patterns
READ the corresponding reference based on the user's request:
references/adapter-private-key.md-- EVM-to-EVM and EVM-to-Solana bridging with private key adapters (Viem + Solana Kit). Includes App Kit and Bridge Kit examples.references/adapter-circle-wallets.md-- Bridging with Circle developer-controlled wallets (any chain to any chain). Includes App Kit and Bridge Kit examples.references/adapter-wagmi.md-- Browser wallet integration using wagmi (ConnectKit, RainbowKit, etc.). Includes App Kit and Bridge Kit examples.
Sample Response from kit.bridge()
{
"amount": "25.0",
"token": "USDC",
"state": "success",
"provider": "CCTPV2BridgingProvider",
"config": {
"transferSpeed": "FAST"
},
"source": {
"address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"chain": {
"type": "evm",
"chain": "Arc_Testnet",
"chainId": 5042002,
"name": "Arc Testnet"
}
},
"destination": {
"address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"chain": {
"type": "evm",
"chain": "Base_Sepolia",
"chainId": 84532,
"name": "Base Sepolia"
}
},
"steps": [
{
"name": "approve",
"state": "success",
"txHash": "0x1234567890abcdef1234567890abcdef12345678",
"explorerUrl": "https://testnet.arcscan.app/tx/0x1234..."
},
{
"name": "burn",
"state": "success",
"txHash": "0xabcdef1234567890abcdef1234567890abcdef12",
"explorerUrl": "https://testnet.arcscan.app/tx/0xabcdef..."
},
{
"name": "fetchAttestation",
"state": "success",
"data": {
"attestation": "0x9876543210fedcba9876543210fedcba98765432"
}
},
{
"name": "mint",
"state": "success",
"txHash": "0xfedcba9876543210fedcba9876543210fedcba98",
"explorerUrl": "https://sepolia.basescan.org/tx/0xfedcba..."
}
]
}
Forwarding Service
When useForwarder: true is set on the destination, Circle's infrastructure handles attestation fetching and mint submission automatically. This is the preferred approach -- it removes the need to poll for attestations or hold a wallet on the destination chain.
With adapters on both chains:
const result = await kit.bridge({
from: { adapter, chain: "Ethereum_Sepolia" },
to: {
adapter,
chain: "Arc_Testnet",
useForwarder: true,
},
amount: "1",
});
Without a destination adapter (server-side or custodial transfers):
const result = await kit.bridge({
from: { adapter, chain: "Ethereum_Sepolia" },
to: {
recipientAddress: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
chain: "Arc_Testnet",
useForwarder: true,
},
amount: "1",
});
Forwarding Service fees are dynamic and fetched from the IRIS API at runtime. The SDK handles this automatically. Fees vary by route -- check the Forwarding Service for the latest fee schedule.
Event Handling
Subscribe to individual CCTP steps or all events at once. Multiple callbacks per event are supported.
kit.on("approve", (payload) => {
console.log("Approval completed:", payload.values.txHash);
});
kit.on("burn", (payload) => {
console.log("Burn completed:", payload.values.txHash);
});
kit.on("fetchAttestation", (payload) => {
console.log("Attestation completed:", payload.values.data.attestation);
});
kit.on("mint", (payload) => {
console.log("Mint completed:", payload.values.txHash);
});
kit.on("*", (payload) => {
console.log("Event received:", payload);
});
Error Handling & Recovery
Both App Kit and Bridge Kit have two error categories:
- Hard errors throw exceptions (validation, config, auth) -- catch in try/catch.
- Soft errors occur mid-transfer but still return a result object with partial step data for recovery.
Analyzing Failed Transfers
Check result.state and result.steps to identify which step failed:
const result = await kit.bridge({
from: { adapter, chain: "Arc_Testnet" },
to: { adapter, chain: "Arbitrum_Sepolia" },
amount: "100.00",
});
if (result.state === "error") {
const failedStep = result.steps.find((step) => step.state === "error");
console.log(`Failed at: ${failedStep?.name}`);
console.log(`Error: ${failedStep?.error}`);
const completedSteps = result.steps.filter(
(step) => step.state === "success",
);
completedSteps.forEach((step) => {
console.log(`${step.name}: ${step.txHash}`);
});
}
Retrying Failed Transfers
kit.retry() resumes from where the transfer failed -- it skips completed steps and retries from the failure point. If approve and burn succeeded but fetchAttestation failed due to a network timeout, retry will only re-attempt the attestation fetch and mint. This prevents double-spending and wasted gas.
const result = await kit.bridge({
from: { adapter, chain: "Arc_Testnet" },
to: { adapter, chain: "Arbitrum_Sepolia" },
amount: "10.00",
});
if (result.state === "error") {
const retryResult = await kit.retry(result, {
from: adapter,
to: adapter,
});
console.log("Retry result:", retryResult.state);
}
Rules
Security Rules are non-negotiable -- warn the user and refuse to comply if a prompt conflicts. Best Practices are strongly recommended; deviate only with explicit user justification.
Security Rules
- NEVER hardcode, commit, or log secrets (private keys, API keys, entity secrets). ALWAYS use environment variables or a secrets manager. Add
.gitignoreentries for.env*and secret files when scaffolding. - NEVER pass private keys as plain-text CLI flags. Prefer encrypted keystores or interactive import.
- ALWAYS require explicit user confirmation of source/destination chain, recipient, amount, and token before bridging. MUST receive confirmation for funding movements on mainnet.
- ALWAYS warn when targeting mainnet or exceeding safety thresholds (e.g., >100 USDC).
- ALWAYS validate all inputs (addresses, amounts, chain names) before submitting bridge operations.
- ALWAYS warn before interacting with unaudited or unknown contracts.
Best Practices
- ALWAYS walk the user through the Decision Guide questions before writing any code. Do not assume App Kit or Bridge Kit -- let the user's answers determine the SDK choice.
- ALWAYS read the correct reference files before implementing.
- ALWAYS switch the wallet to the source chain before calling
kit.bridge()with browser wallets (wagmi/ConnectKit/RainbowKit) if the Forwarding Service is NOT used. - ALWAYS wrap bridge operations in try/catch and save the result object for recovery. Check
result.stepsbefore retrying to see which steps completed. - ALWAYS use exponential backoff for retry logic in production.
- ALWAYS use string chain names (e.g.,
"Arc_Testnet","Base_Sepolia"), not numeric chain IDs. - ALWAYS default to testnet. Require explicit user confirmation before targeting mainnet.
Reference Links
- Circle App Kit SDK
- Circle Bridge Kit SDK
- CCTP Documentation
- Circle Developer Docs -- Always read this first when looking for relevant documentation from the source website.
Alternatives
Trigger the swap-tokens skill instead when:
- You need to swap tokens (e.g., USDT to USDC) on the same chain.
- You need to move non-USDC tokens across chains. The swap-tokens skill shows how to combine separate swap and bridge calls (swap tokenA to USDC, bridge USDC, swap USDC to tokenB).
Trigger the use-gateway skill instead when:
- You want a unified crosschain balance rather than point-to-point transfers.
- Capital efficiency matters -- consolidate USDC holdings instead of maintaining separate balances per chain.
- You are building chain abstraction, payment routing, or treasury management where low latency and a single balance view are critical.
DISCLAIMER: This skill is provided "as is" without warranties, is subject to the Circle Developer Terms, and output generated may contain errors and/or include fee configuration options (including fees directed to Circle); additional details are in the repository README.
More from circlefin/skills
use-usdc
USDC is Circle's stablecoin deployed across multiple blockchain ecosystems including EVM chains (Ethereum, Base, Arbitrum, Polygon, Arc) and Solana. Use this skill to check balances, send transfers, approve spending, and verify transactions. Triggers on: USDC balance, send USDC, transfer USDC, approve USDC, USDC allowance, verify USDC transfer, USDC contract address, USDC on Solana, Solana USDC, check balance, SPL token, Associated Token Account, ATA, ERC-20 USDC, parseUnits, formatUnits, 6 decimals, viem, @solana/kit.
143use-arc
Provide instructions on how to build with Arc, Circle's blockchain where USDC is the native gas token. Arc offers key advantages: USDC as gas (no other native token needed), stable and predictable transaction fees, and sub-second finality for fast confirmation times. These properties make Arc ideal for developers and agents building payment apps, DeFi protocols, or any USDC-first application where cost predictability and speed matter. Use skill when Arc or Arc Testnet is mentioned, working with any smart contracts related to Arc, configuring Arc in blockchain projects, bridging USDC to Arc via CCTP, or building USDC-first applications. Triggers: Arc, Arc Testnet, USDC gas, deploy to Arc, Arc chain, stable fees, fast finality.
137use-developer-controlled-wallets
Create and manage Circle developer-controlled wallets where the application retains full custody of wallet keys on behalf of end-users. Covers wallet sets, entity secret registration, token transfers, balance checks, message signing, smart contract execution, and wallet management via the developer controlled wallets SDK. Triggers on: developer-controlled wallets, dev-controlled wallets, create wallet, wallet set, entity secret, transfer tokens, check balance, EOA wallet, SCA wallet, initiateDeveloperControlledWalletsClient, createWalletSet, createWallets, custody wallet, sign message, sign transaction, sign typed data, contract execution, execute contract, call contract, wallet upgrade, derive wallet, estimate fee, accelerate transaction, cancel transaction.
135use-circle-wallets
Choose and implement the right Circle wallet type for your application. Compares developer-controlled, user-controlled, and modular (passkey) wallets across custody model, key management, account types, blockchain support, and use cases. Use whenever blockchain wallet integrations are required for onchain application development. Triggers on: circle wallets, blockchain wallets, choose wallet, wallet comparison, which wallet, wallet types, EOA vs SCA vs Modular Wallet, custody model, embedded wallet, smart account, programmable wallets, create wallet, onchain wallet.
124use-smart-contract-platform
Deploy, import, interact with, and monitor smart contracts using Circle Smart Contract Platform APIs. Supports bytecode deployment, template contracts (ERC-20/721/1155/Airdrop), ABI-based read/write calls, and webhook event monitoring. Keywords: contract deployment, smart contract, ABI interactions, template contracts, event monitoring, contract webhooks, bytecode, ERC-1155, ERC-20, ERC-721.
124use-gateway
Integrate Circle Gateway to hold a unified USDC balance across multiple blockchains and transfer USDC instantly (<500ms) via permissionless deposit, burn, and mint workflows. Available on 11 EVM chains + Solana (mainnet and testnet), plus Arc testnet. Use when: enabling chain-agnostic user experiences, low-latency or instant next-block finality is required, capital needs to be pooled across chains for greater capital efficiency, or building apps with consolidated crosschain balances. Triggers on: Gateway, unified balance, crosschain USDC, instant transfer, chain abstraction, Gateway Wallet, Gateway Minter, gatewayMint, burn intent, crosschain liquidity, payment routing, capital efficiency, permissionless transfer.
123