This repository contains a complete Circle Gateway demo, showcasing cross-chain USDC transfers using both traditional EOA (Externally Owned Account) and Account Abstraction modes through Particle Network's AA SDK.
The demo deposits 2 USDC into Gateway on Ethereum Sepolia, Base Sepolia and Avalanche Fuji and then transfers it instantly to Base Sepolia, demonstrating Circle's Gateway protocol capabilities with optional gasless transactions via Account Abstraction.
The flow is:
- Deposit 2 USDC into Gateway on Ethereum Sepolia, Base Sepolia and Avalanche Fuji
- Wait for finalization (Avalanche: instant, Ethereum/Base: ~20 minutes)
- Perform cross-chain transfer
- Verify final balances
Note how due to the AA architecture we need to delegate the EOA into the Gateway smart contract in order to perform the cross-chain transfer. This is required by the Gateway API as it needs to sign the burn intents on behalf of the smart account.
npm install
Copy the example environment file:
cp .env.example .env
Configure your .env
file:
# Required: Private key for EOA (or smart account owner in AA mode)
PRIVATE_KEY="your-private-key-here"
# Smart Account Configuration (optional)
USE_AA=true # Set to 'false' or omit for EOA mode
# Particle Network Configuration (required if USE_AA=true)
PARTICLE_PROJECT_ID="your-project-id"
PARTICLE_CLIENT_KEY="your-client-key"
PARTICLE_APP_ID="your-app-id"
- Visit Particle Dashboard
- Create a new project or use an existing one
- Copy your
Project ID
,Client Key
, andApp ID
- Add them to your
.env
file
Get USDC from the Circle Faucet and native gas tokens for each chain (if using EOA mode).
The demo, by default, deposits 2 USDC from Ethereum Sepolia, Avalanche Fuji, Base Sepolia to the Gateway contract.
You can edit this value in deposit.js
in const DEPOSIT_AMOUNT = 2000000n; // 2 USDC
# Deposit USDC into Gateway on all chains
node deposit.js
# Wait for finalization (Avalanche: instant, Ethereum/Base: ~20 minutes)
# Perform cross-chain transfer
node transfer.js
# Check balances (optional)
node check-balances.js
# Set in .env: USE_AA=false (or omit)
node deposit.js
node transfer.js
node check-balances.js
- Gas: User pays gas fees in native tokens
- Address: Uses EOA address for all operations
- Setup: Simple single client configuration
# Set in .env: USE_AA=true
node deposit.js
node transfer.js
node check-balances.js
or
USE_AA=true node deposit.js
USE_AA=true node transfer.js
USE_AA=true node check-balances.js
- Gas: Gasless transactions via Particle Network paymaster (testnet)
- Address: Uses smart account address for deposits/transfers
- Setup: Dual client architecture with AA provider
quickstart/
├── config/ # Configuration files
│ └── aa-config.js # Account Abstraction configuration
├── lib/ # Core libraries and utilities
│ ├── abis.js # Contract ABIs
│ ├── gateway-client.js # Gateway API client
│ └── typed-data.js # EIP-712 typed data utilities
├── utils/ # Helper utilities
│ └── aa-utils.js # AA transaction utilities
├── deposit.js # Main demo: Deposit USDC into Gateway
├── transfer.js # Main demo: Cross-chain USDC transfer
├── check-balances.js # Utility: Check balances across chains
├── setup.js # Core setup and chain configuration
└── .env.example # Environment configuration template
EOA Mode:
Private Key → EOA → Direct Transaction → Blockchain
↓
Gas Payment Required
Smart Account Mode:
Private Key → EOA → Smart Account → UserOperation → Bundler → Blockchain
↓ ↓
Owner/Signer Paymaster (gasless)
// Simple setup - account is the signer
client = createPublicClient({ chain, account, transport: http() });
walletClient = client;
accountAddress = account.address;
// 1. Create EOA provider that wraps the private key account
const eoaProvider = {
request: async ({ method, params }) => {
// Handles signing methods but delegates transactions
}
};
// 2. Create smart account with EOA as owner
smartAccount = createSmartAccount(eoaProvider, chainName);
const aaSetup = createAAWalletClient(smartAccount, chain);
accountAddress = await getSmartAccountAddress(smartAccount);
// 3. Create viem wallet client with AA provider as transport
walletClient = createWalletClient({
account: { address: accountAddress, type: 'json-rpc' },
chain,
transport: custom({
request: async ({ method, params }) => {
return await aaSetup.aaProvider.request({ method, params });
}
})
});
Deposits USDC into Gateway Wallet contracts on all supported chains.
What it does:
- Smart Account Deployment: Automatically deploys smart accounts if needed (AA mode only)
- Balance Validation: Checks USDC balance on each chain before depositing
- USDC Approval: Approves Gateway Wallet contract to spend USDC
- Deposit Execution: Deposits USDC into Gateway Wallet on each chain
- Error Handling: Provides clear feedback for insufficient balances or gas
Key Features:
- Configurable amount (currently 0.5 USDC per chain)
- Multi-chain support (Ethereum Sepolia, Base Sepolia, Avalanche Fuji)
- AA retry logic with nonce management
- Transaction delays for proper synchronization
Performs cross-chain transfer using Circle's Gateway protocol.
What it does:
- Balance Verification: Checks Gateway API balances to ensure deposits are finalized
- Delegation Setup: (AA mode only) Authorizes EOA to sign on behalf of smart account
- Burn Intent Creation: Creates EIP-712 signed burn intents for cross-chain transfer
- Gateway API Call: Submits burn intents to Gateway API for attestation
- Minting: Uses attestation to mint USDC on destination chain (Base)
Key Features:
- Cross-chain transfer (1 USDC each from Ethereum + Avalanche → Base)
- EIP-712 signatures for burn intents
- Delegation system for AA authorization
- Attestation flow integration with Circle's Gateway API
- Ethereum Sepolia (testnet)
- Base Sepolia (testnet)
- Avalanche Fuji (testnet)
All chains use the same Gateway contract addresses and support both EOA and AA modes.
- Reading: Both modes use
createPublicClient()
for blockchain reads - Writing:
- EOA: Same client instance handles reads and writes
- AA: Separate
createWalletClient()
with custom AA transport
The eoaProvider
acts as a bridge between your EOA and the AA system:
- Signing methods (
personal_sign
,eth_signTypedData_v4
): Handled by EOA - Transaction methods (
eth_sendTransaction
): Delegated to AA provider - Account queries: Returns smart account address, not EOA
- EOA Mode: Uses EOA address directly
- AA Mode: Uses smart account address for deposits/transfers, but EOA signs burn intents
- Delegation Required: EOA must be authorized as delegate for smart account
- Signing: EOA signs burn intents even in AA mode (Gateway API requirement)
- Nonce Management: Uses retry logic for proper transaction sequencing
# 1. Fund accounts with USDC
# Visit https://faucet.circle.com
# 2. Deposit USDC into Gateway Wallets
USE_AA=true node deposit.js
# 3. Wait for finalization
# - Avalanche: instant
# - Ethereum/Base: ~20 minutes
# 4. Perform cross-chain transfer
USE_AA=true node transfer.js
# 5. Verify final balances
USE_AA=true node check-balances.js
- Clear Separation: Main demo scripts easily identifiable in root
- Organized Dependencies: Related utilities grouped logically
- Maintainability: Configuration and utilities separated from main logic
- Scalability: Easy to add new utilities or configuration files
- User Experience: Simple commands from root directory
- Gasless Transactions: No need for native tokens on testnets
- Automatic Deployment: Smart accounts deployed on first transaction
- Seamless Integration: Same API for both EOA and AA modes
- Enhanced UX: Users don't need to manage gas tokens
- Insufficient Balance: Ensure USDC balance before deposits
- Finalization Wait: Ethereum/Base deposits need ~20 minutes to finalize
- AA Nonce Issues: Script includes retry logic for nonce management
- Missing Credentials: Ensure Particle Network credentials are set for AA mode
PRIVATE_KEY
: Required - EOA private key (owner in AA mode)USE_AA
: Optional - Set to 'true' for AA modePARTICLE_PROJECT_ID
: Required for AA modePARTICLE_CLIENT_KEY
: Required for AA modePARTICLE_APP_ID
: Required for AA mode