@circle-fin/app-kit
v1.5.0
Published
A one-stop Circle SDK solution for building stablecoin (e.g. USDC) applications, with bridging, swapping, and more on-chain operations
Downloads
2,902
Readme
App Kit
A one-stop Circle SDK for building stablecoin (e.g. USDC) applications, providing a unified set of on-chain tools for bridging, swapping, unified balance management, and other stablecoin operations.
Making cross-chain transfers, same-chain swaps, unified balance management, and token sends as simple as a single function call
Table of Contents
- App Kit
Overview
The App Kit ecosystem is Circle's open-source effort to streamline stablecoin development with SDKs that are easy to use correctly and hard to misuse. Kits are cross-framework (viem, ethers, @solana/web3) and integrate cleanly into any stack. They're opinionated with sensible defaults, but offer escape hatches for full control. A pluggable architecture makes implementation flexible, and all kits are interoperable, so they can be composed to suit a wide range of use cases.
The App Kit provides a unified interface for cross-chain transfers, same-chain swaps, and unified balance management, abstracting away the complexity of choosing between operations. It combines the power of Bridge Kit, Swap Kit, and Unified Balance Kit into a single, cohesive API.
Why App Kit?
- 🎯 Unified interface: Single API for bridge, swap, and unified balance operations
- 🤖 Smart routing: Automatically selects the appropriate operation (bridge vs swap)
- ⚡ Zero-config defaults: Built-in reliable RPC endpoints - start building right away
- 🔧 Bring your own infrastructure: Seamlessly integrate with your existing setup when needed
- 🔒 Production-ready security: Leverages Circle's CCTPv2 for bridging and trusted swap providers
- 🚀 Developer experience: Complete TypeScript support, comprehensive validation, and instant connectivity
- 🌍 Multi-chain support: Bridge across 45 chains with 968 total bridge routes through Circle's CCTPv2
- Mainnet (22 chains): Arbitrum, Avalanche, Base, Codex, Edge, Ethereum, HyperEVM, Injective, Ink, Linea, Monad, Morph, OP Mainnet, Pharos, Plume, Polygon PoS, Sei, Solana, Sonic, Unichain, World Chain, XDC
- Testnet (23 chains): Arc Testnet, Arbitrum Sepolia, Avalanche Fuji, Base Sepolia, Codex Testnet, Edge Testnet, Ethereum Sepolia, HyperEVM Testnet, Injective Testnet, Ink Testnet, Linea Sepolia, Monad Testnet, Morph Testnet, OP Sepolia, Pharos Atlantic, Plume Testnet, Polygon PoS Amoy, Sei Testnet, Solana Devnet, Sonic Testnet, Unichain Sepolia, World Chain Sepolia, XDC Apothem
- 🔄 Swap support: Same-chain token swaps powered by Circle's Stablecoin Service
- 🎯 Flexible adapters: Supporting EVM (Viem, Ethers) and Solana (@solana/web3)
- 📡 Real-time event monitoring: Track progress throughout the operation lifecycle
- 🛡️ Robust error handling: Graceful partial success recovery
- ✈️ Pre-flight validation: Verify operations with cost estimation before execution
Developer Documentation
Architecture Flow
The App Kit follows a composable architecture that integrates Bridge Kit, Swap Kit, and Unified Balance Kit:
┌─────────────────────────────────────────────────────────────┐
│ App Kit │
│ (Unified Interface) │
└────────────────────────────┬────────────────────────────────┘
│
┌────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐
│ Bridge Kit │ │ Swap Kit │ │ Unified Balance Kit │
│ (Cross-Chain) │ │ (Same-Chain) │ │ (Cross-Chain) │
└────────┬────────┘ └────────┬────────┘ └──────────┬──────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐
│ Provider │ │ Provider │ │ Provider │
│ (CCTPv2) │ │ (Service) │ │ (Gateway v1) │
└────────┬────────┘ └────────┬────────┘ └──────────┬──────────┘
│ │ │
└───────────────────┼─────────────────────┘
│
▼
┌─────────────────┐
│ Adapter │
│ (Blockchain) │
└─────────────────┘- Adapter: Handles blockchain-specific operations (wallets, transactions, gas)
- Provider: Implements protocols (CCTPv2 for bridging, Circle Service for swaps, Gateway for unified balance)
- Bridge/Swap/Unified Balance Kit: Specialized kits for each operation type
- App Kit: Unified orchestration layer providing a consistent API
This separation ensures that each component has a single responsibility while maintaining seamless integration across the entire stablecoin operation lifecycle.
Installation
npm install @circle-fin/app-kit
# or
yarn add @circle-fin/app-kitAdapters
Choose the appropriate adapter for your target chains:
# For EVM chains (Ethereum, Base, Arbitrum, etc.)
npm install @circle-fin/adapter-viem-v2 viem
# or
yarn add @circle-fin/adapter-viem-v2 viem
# For EVM chains using Ethers.js
npm install @circle-fin/adapter-ethers-v6 ethers
# or
yarn add @circle-fin/adapter-ethers-v6 ethers
# For Solana
npm install @circle-fin/adapter-solana @solana/web3.js @solana/spl-token
# or
yarn add @circle-fin/adapter-solana @solana/web3.js @solana/spl-tokenChain Definitions
Import chain definitions directly from the kit:
import { Ethereum, Base, Polygon, Solana } from '@circle-fin/app-kit/chains'
// Use with adapter context
const result = await kit.bridge({
from: { adapter, chain: Ethereum },
to: { adapter, chain: Base },
amount: '10.50',
})All 41 CCTPv2-supported chains are available for import.
Quick Start
🌉 Cross-Chain Bridge
Transfer USDC from one chain to another:
import { AppKit } from '@circle-fin/app-kit'
import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
// Initialize the kit
const kit = new AppKit()
// Create ONE adapter that works across all chains
const adapter = createViemAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as string,
})
// Bridge from Ethereum to Base
const result = await kit.bridge({
from: { adapter, chain: 'Ethereum' },
to: { adapter, chain: 'Base' },
amount: '10.50',
})🔄 Same-Chain Swap
Swap tokens on the same chain with support for multiple stablecoins:
// Swap USDC to USDT on Ethereum
const result = await kit.swap({
from: { adapter, chain: 'Ethereum' },
tokenIn: 'USDC',
tokenOut: 'USDT',
amountIn: '100.0',
config: {
kitKey: process.env.KIT_KEY,
},
})
// Swap DAI to USDC (18 decimals handled automatically)
const daiSwap = await kit.swap({
from: { adapter, chain: 'Ethereum' },
tokenIn: 'DAI',
tokenOut: 'USDC',
amountIn: '500.0',
config: {
kitKey: process.env.KIT_KEY,
},
})
// Swap native ETH to stablecoin
const nativeSwap = await kit.swap({
from: { adapter, chain: 'Ethereum' },
tokenIn: 'NATIVE', // ETH on Ethereum
tokenOut: 'USDC',
amountIn: '1.5',
config: {
kitKey: process.env.KIT_KEY,
},
})📊 Estimate Operations
Get cost estimates before executing operations:
// Estimate bridge operation
const bridgeEstimate = await kit.estimateBridge({
from: { adapter, chain: 'Ethereum' },
to: { adapter, chain: 'Base' },
amount: '10.50',
})
// Estimate swap operation
const swapEstimate = await kit.estimateSwap({
from: { adapter, chain: 'Ethereum' },
tokenIn: 'USDC',
tokenOut: 'USDT',
amountIn: '100.0',
config: {
kitKey: process.env.KIT_KEY,
},
})
console.log('Bridge fees:', bridgeEstimate.fees)
console.log(
'Swap estimated output:',
swapEstimate.estimatedOutput?.amount,
swapEstimate.estimatedOutput?.token,
)🔍 Query Supported Chains
Check which chains support specific operations:
// Get all chains (bridge, swap, and unified balance)
const allChains = kit.getSupportedChains()
// Get all chains that support bridging
const bridgeChains = kit.getSupportedChains('bridge')
// Get all chains that support swapping
const swapChains = kit.getSupportedChains('swap')
// Get chains that support unified balance operations
const ubChains = kit.getSupportedChains('unifiedBalance')💰 Unified Balance
Manage a unified, cross-chain USDC balance — deposit on any chain, spend on any other, query balances, and manage delegates:
import { AppKit } from '@circle-fin/app-kit'
import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
const kit = new AppKit()
const adapter = createViemAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as string,
})
// Query balances across all chains
const balances = await kit.unifiedBalance.getBalances({
token: 'USDC',
sources: { adapter },
})
// Deposit USDC into unified balance
const deposit = await kit.unifiedBalance.deposit({
from: { adapter, chain: 'Ethereum' },
amount: '100',
token: 'USDC',
})
// Spend from unified balance on a different chain
const spend = await kit.unifiedBalance.spend({
amount: '50',
token: 'USDC',
from: { adapter, allocations: [{ amount: '50', chain: 'Ethereum' }] },
to: { adapter, chain: 'Base' },
})
// Estimate spend fees before executing
const estimate = await kit.unifiedBalance.estimateSpend({
amount: '50',
token: 'USDC',
from: { adapter, allocations: [{ amount: '50', chain: 'Ethereum' }] },
to: { adapter, chain: 'Base' },
})
// Manage delegates
await kit.unifiedBalance.addDelegate({
from: { adapter, chain: 'Ethereum' },
delegateAddress: '0xDelegate…',
})
// Listen to unified balance events
kit.on('unifiedBalance.gateway.spend.succeeded', (payload) => {
console.log('Spend succeeded:', payload)
})For the full Unified Balance API, see the @circle-fin/unified-balance-kit README.
Configuration
Send Parameters
The send() method transfers tokens on the same chain from the source adapter to a recipient address or adapter:
interface SendParams {
from: AdapterContext // Source wallet and chain
to: Adapter | string // Destination wallet or address
amount: string // Amount to transfer (e.g., '10.50')
token?: TokenAlias | string // Optional, defaults to 'USDC'
}Token Support
The token field accepts both known aliases and custom token contract addresses:
Known aliases (for send operations):
'USDC'- USD Coin (6 decimals)'USDT'- Tether USD (6 decimals)'NATIVE'- Chain's native currency (ETH, SOL, etc.)
Custom addresses:
- EVM contract addresses (e.g.,
0x6B175474E89094C44Da98b954EedeAC495271d0F) - Solana SPL mint addresses
Note: For swap operations, additional stablecoins (EURC, DAI, USDE, PYUSD) are supported. See Swap Parameters section below.
Examples
// Using USDC (default)
await kit.send({
from: { adapter, chain: 'Ethereum' },
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
amount: '10.0',
token: 'USDC',
})
// Using USDT
await kit.send({
from: { adapter, chain: 'Ethereum' },
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
amount: '100.0',
token: 'USDT',
})
// Using NATIVE
await kit.send({
from: { adapter, chain: 'Ethereum' },
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
amount: '1.5',
token: 'NATIVE', // ETH on Ethereum
})
// Using a custom ERC-20 token address (e.g., DAI)
await kit.send({
from: { adapter, chain: 'Ethereum' },
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
amount: '50.0',
token: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI contract address
})
// Using a custom SPL token on Solana
await kit.send({
from: { adapter, chain: 'Solana' },
to: 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN',
amount: '50.0',
token: 'So11111111111111111111111111111111111111112', // Wrapped SOL
})Swap Parameters
For explicit same-chain swaps:
interface SwapParams {
from: AdapterContext // Source wallet and chain
tokenIn: SupportedToken // Input token
tokenOut: SupportedToken // Output token
amountIn: string // Amount to swap
to?: string // Optional recipient address
config?: SwapConfig // Optional swap configuration
}
// SupportedToken includes:
// - Stablecoins (6 decimals): 'USDC', 'EURC', 'USDT', 'PYUSD'
// - Stablecoins (18 decimals): 'DAI', 'USDE'
// - Native: 'NATIVE' (chain's native currency)Swap Examples with Different Tokens
// Swap between 6-decimal stablecoins
await kit.swap({
from: { adapter, chain: 'Ethereum' },
tokenIn: 'EURC',
tokenOut: 'USDC',
amountIn: '100.0',
config: {
kitKey: process.env.KIT_KEY,
},
})
// Swap DAI (18 decimals) to USDT
await kit.swap({
from: { adapter, chain: 'Ethereum' },
tokenIn: 'DAI',
tokenOut: 'USDT',
amountIn: '500.0', // SDK automatically handles 18-decimal precision
config: {
kitKey: process.env.KIT_KEY,
},
})
// Swap native token to stablecoin
await kit.swap({
from: { adapter, chain: 'Ethereum' },
tokenIn: 'NATIVE', // ETH on Ethereum
tokenOut: 'USDC',
amountIn: '2.5',
config: {
kitKey: process.env.KIT_KEY,
},
})Bridge Parameters
For explicit cross-chain bridges:
interface BridgeParams {
from: AdapterContext // Source wallet and chain
to: BridgeDestination // Destination wallet/address and chain
amount: string // Amount to transfer (e.g., '10.50')
token?: 'USDC' // Optional, defaults to 'USDC'
config?: BridgeConfig // Optional bridge configuration
}Custom Fees
App Kit supports custom developer fees on both bridge and swap operations.
Operation-Level Custom Fees
Apply custom fees to individual operations:
// Bridge with custom fee
await kit.bridge({
from: { adapter, chain: 'Ethereum' },
to: { adapter, chain: 'Base' },
amount: '1000',
config: {
customFee: {
value: '10', // 10 USDC fee
recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
},
},
})
// Swap with custom fee
await kit.swap({
from: { adapter, chain: 'Ethereum' },
tokenIn: 'USDC',
tokenOut: 'USDT',
amountIn: '1000',
config: {
kitKey: process.env.KIT_KEY,
customFee: {
value: '10',
recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
},
},
})Kit-Level Fee Policies
For dynamic fee calculation across all operations, use kit-level policies:
import { AppKit } from '@circle-fin/app-kit'
import { formatUnits } from 'viem'
const kit = new AppKit()
kit.setCustomFeePolicy({
calculateFee: (params) => {
// Calculate fee based on operation type and parameters
const amount = Number(formatUnits(BigInt(params.amount), 6))
const feePercentage = type === 'bridge' ? 0.01 : 0.005 // 1% for bridge, 0.5% for swap
return (amount * feePercentage).toFixed(6)
},
resolveFeeRecipientAddress: (type, info) => {
// Return appropriate address based on operation type and chain
return info.chain.type === 'solana'
? 'SolanaAddressBase58...'
: '0xEvmAddress...'
},
})
// All subsequent operations will use this policy
await kit.bridge({
from: { adapter, chain: 'Ethereum' },
to: { adapter, chain: 'Base' },
amount: '1000', // Custom fee calculated automatically
})Error Handling
The kit uses a thoughtful error handling approach:
- Hard errors (thrown): Validation, configuration, and authentication errors
- Soft errors (returned): Recoverable issues like insufficient balance or network errors
import { AppKit } from '@circle-fin/app-kit'
import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
const kit = new AppKit()
const adapter = createViemAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as string,
})
const result = await kit.bridge({
from: { adapter, chain: 'Ethereum' },
to: { adapter, chain: 'Base' },
amount: '100.0',
})
if (result.state === 'success') {
console.log('Operation successful!')
} else {
// Handle partial completion with recovery information
console.log(
'Successful steps:',
result.steps.filter((s) => s.state === 'success'),
)
}Examples
Basic Cross-Chain Transfer
import { AppKit } from '@circle-fin/app-kit'
import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
const kit = new AppKit()
const adapter = createViemAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as string,
})
// Bridge USDC from Ethereum to Base
const result = await kit.bridge({
from: { adapter, chain: 'Ethereum' },
to: { adapter, chain: 'Base' },
amount: '50.0',
})
console.log('Bridge result:', result)Same-Chain Token Swap
// Swap USDC to USDT on Ethereum
const result = await kit.swap({
from: { adapter, chain: 'Ethereum' },
tokenIn: 'USDC',
tokenOut: 'USDT',
amountIn: '100.0',
config: {
kitKey: process.env.KIT_KEY,
},
})
console.log('Swap result:', result)Send to Different Address
// Send to a different address on the same chain
const result = await kit.send({
from: { adapter, chain: 'Ethereum' },
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
amount: '10.50',
token: 'USDC',
})Event Monitoring
// Listen to bridge events
kit.on('bridge.approve', (payload) => {
console.log('Approval transaction:', payload.values.txHash)
})
kit.on('bridge.burn', (payload) => {
console.log('Burn transaction:', payload.values.txHash)
})
// Listen to all events
kit.on('*', (payload) => {
console.log('Action:', payload.method)
})
// Execute operation
await kit.bridge({
from: { adapter, chain: 'Ethereum' },
to: { adapter, chain: 'Base' },
amount: '10.0',
})API Reference
Core Methods
kit.send(params)- Send tokens to a recipient on the same chainkit.bridge(params)- Execute cross-chain bridge operationkit.swap(params)- Execute same-chain swap operationkit.estimateBridge(params)- Get cost estimates for bridgingkit.estimateSwap(params)- Get cost estimates for swappingkit.estimateSend(params)- Get cost estimates for send operationkit.getSupportedChains(operationType?)- Query supported chains by operation type ('bridge','swap','unifiedBalance')kit.setCustomFeePolicy(policy)- Set kit-level custom fee policykit.on(event, handler)- Listen to operation eventskit.off(event, handler)- Remove event listenerkit.unifiedBalance.*- Unified balance operations (deposit, spend, getBalances, estimateSpend, delegates, and more). See the@circle-fin/unified-balance-kitREADME for the full API.
Development
Building
# From the root of the monorepo
nx build @circle-fin/app-kitTesting
# From the root of the monorepo
nx test @circle-fin/app-kitLocal Development
# Install dependencies
yarn install
# Build all packages
yarn build
# Build the app-kit specifically
nx build @circle-fin/app-kit
# Run tests
nx test @circle-fin/app-kitCommunity & Support
- 💬 Discord: Join our community
License
This project is licensed under the Apache 2.0 License. Contact support for details.
Ready to start building?
Built with ❤️ by Circle
