
Product
Introducing Webhook Events for Alert Changes
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.
@circle-fin/adapter-ethers-v6
Advanced tools
Type-safe EVM blockchain adapter powered by Ethers v6
Seamlessly interact with 16+ EVM networks using a single, strongly-typed interface
The Ethers v6 Adapter is a strongly-typed implementation of the Adapter interface for EVM-compatible blockchains. Built on top of the popular Ethers v6 library, it provides type-safe blockchain interactions through a unified interface that's designed to work seamlessly with the Bridge Kit for cross-chain USDC transfers between Solana and EVM networks, as well as any future kits for additional stablecoin operations. It can be used by any Kit built using the Stablecoin Kits architecture and/or any providers plugged into those kits.
JsonRpcProvider and Wallet instancesIf you're using the Bridge Kit or other Stablecoin Kits for cross-chain operations, you only need to instantiate one adapter and pass it to the kit. The same adapter works across all supported chains.
// Single adapter instance for multi-chain operations
// Note: Private keys can be provided with or without '0x' prefix
const adapter = createAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as `0x${string}`, // Both '0x...' and '...' work
})
// Both formats are automatically normalized:
const adapter1 = createAdapterFromPrivateKey({
privateKey: '0x1234...', // With prefix ✅
})
const adapter2 = createAdapterFromPrivateKey({
privateKey: '1234...', // Without prefix ✅ (automatically normalized)
})
If you're building a provider (e.g., a custom BridgingProvider implementation), you'll use the adapter's abstracted methods to interact with different chains. The OperationContext pattern makes multi-chain operations seamless.
npm install @circle-fin/adapter-ethers-v6 ethers
# or
yarn add @circle-fin/adapter-ethers-v6 ethers
This adapter requires ethers (v6) as a peer dependency. Install it alongside the adapter:
npm install @circle-fin/adapter-ethers-v6 ethers
# or
yarn add @circle-fin/adapter-ethers-v6 ethers
Supported Versions: ^6.11.0 (6.11.x through 6.x.x, excluding 7.x.x)
If you encounter peer dependency warnings:
ethers version: npm ls ethersnpm install ethers@^6.11.0 to install a compatible versionThe simplest way to get started with lazy initialization. Default configuration handles adapter setup automatically.
import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
// Minimal configuration with lazy initialization
// Note: Private keys work with or without '0x' prefix
const adapter = createAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as `0x${string}`, // Both '0x...' and '...' work
// Defaults applied:
// - addressContext: 'user-controlled'
// - supportedChains: all EVM chains (~34 networks)
// - Lazy initialization: wallet connects to chain on first operation
})
// Chain specified per operation via OperationContext
const prepared = await adapter.prepare(
{
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
abi: usdcAbi,
functionName: 'transfer',
args: ['0xrecipient', '1000000'],
},
{ chain: 'Ethereum' }, // Chain specified in context
)
const txHash = await prepared.execute()
For production use, provide custom RPC endpoints for better reliability and performance:
import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
import { JsonRpcProvider } from 'ethers'
import { Ethereum, Base, Polygon } from '@core/chains'
// Production-ready with custom RPC endpoints and lazy initialization
const adapter = createAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as `0x${string}`,
// Custom RPC provider with explicit chain mapping
getProvider: ({ chain }) => {
// Map chain names to RPC endpoints
// Customize this mapping based on your RPC provider
const rpcEndpoints: Record<string, string> = {
Ethereum: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
Base: `https://base-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
Polygon: `https://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
}
const endpoint = rpcEndpoints[chain.name]
if (!endpoint) {
throw new Error(`RPC endpoint not configured for chain: ${chain.name}`)
}
return new JsonRpcProvider(endpoint, chain.chainId)
},
// Optionally restrict to specific chains
capabilities: {
supportedChains: [Ethereum, Base, Polygon],
},
})
⚠️ Production Note: Default factory methods use public RPC endpoints which may have rate limits. For production, use dedicated providers like Alchemy, Infura, or QuickNode.
For browser environments with MetaMask or WalletConnect:
import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
// Minimal browser wallet configuration
const adapter = await createAdapterFromProvider({
provider: window.ethereum,
// Default capabilities applied automatically
})
// User will be prompted to connect wallet
// Address is automatically resolved from connected wallet
const prepared = await adapter.prepare(
{
address: '0xcontract',
abi: contractAbi,
functionName: 'approve',
args: ['0xspender', '1000000'],
},
{ chain: 'Polygon' },
)
The OperationContext pattern is the modern approach for multi-chain operations. Instead of locking an adapter to a single chain, you specify the chain per operation. This enables powerful patterns like using a single adapter for cross-chain bridging.
Benefits:
Every operation accepts an OperationContext parameter that specifies the chain:
import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
// Create adapter without specifying a chain - true lazy initialization
const adapter = createAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as `0x${string}`,
})
// Chain specified explicitly in every operation
const prepared = await adapter.prepare(
{
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
abi: usdcAbi,
functionName: 'transfer',
args: ['0xrecipient', '1000000'],
},
{ chain: 'Ethereum' },
)
const gas = await prepared.estimate()
const txHash = await prepared.execute()
Use a single adapter instance for operations across multiple chains:
// Create adapter once for use across multiple chains
const adapter = createAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as `0x${string}`,
})
// Transfer USDC on Ethereum
const ethPrepared = await adapter.prepare(
{
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
abi: usdcAbi,
functionName: 'transfer',
args: ['0xrecipient', '1000000'],
},
{ chain: 'Ethereum' },
)
// Transfer USDC on Base using the same adapter
const basePrepared = await adapter.prepare(
{
address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
abi: usdcAbi,
functionName: 'transfer',
args: ['0xrecipient', '1000000'],
},
{ chain: 'Base' },
)
// Execute both transfers
await ethPrepared.execute()
await basePrepared.execute()
The adapter supports two address control patterns. Choose the one that fits your use case.
Best for: Private key wallets, browser wallets (MetaMask), hardware wallets
How it works: Address is automatically resolved from the connected signer/wallet. You don't need to specify it in the OperationContext.
When to use:
// User-controlled adapter (default for factory functions)
const adapter = createAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as `0x${string}`,
// addressContext: 'user-controlled' is the default
})
// Address automatically resolved from private key/wallet
const prepared = await adapter.prepare(
{
address: '0xcontract',
abi: contractAbi,
functionName: 'approve',
args: ['0xspender', '1000000'],
},
{ chain: 'Polygon' }, // No address needed in context for user-controlled
)
Best for: Custody solutions, multi-entity systems, enterprise applications
How it works: Address must be explicitly provided in the OperationContext for each operation.
When to use:
import { EthersAdapter } from '@circle-fin/adapter-ethers-v6'
import { Ethereum, Base } from '@core/chains'
// Developer-controlled adapter (manual constructor)
const adapter = new EthersAdapter(
{
getProvider: ({ chain }) => new JsonRpcProvider('https://...'),
signer: wallet,
},
{
addressContext: 'developer-controlled', // ← Explicit address required
supportedChains: [Ethereum, Base],
},
)
// Address must be provided in context for developer-controlled adapters
const prepared = await adapter.prepare(
{
address: '0xcontract',
abi: contractAbi,
functionName: 'approve',
args: ['0xspender', '1000000'],
},
{
chain: 'Ethereum',
address: '0x1234...', // Required for developer-controlled
},
)
Transfer USDC across different chains with the same adapter:
import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
import { parseAbi } from 'ethers'
// Create adapter with lazy initialization
const adapter = createAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as `0x${string}`,
})
const usdcAbi = parseAbi([
'function transfer(address to, uint256 amount) returns (bool)',
])
// Transfer on Ethereum - chain specified in operation
const ethPrepared = await adapter.prepare(
{
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
abi: usdcAbi,
functionName: 'transfer',
args: ['0xrecipient', '1000000'], // 1 USDC (6 decimals)
},
{ chain: 'Ethereum' },
)
// Estimate and execute
const gas = await ethPrepared.estimate()
console.log('Estimated gas:', gas.gas)
const txHash = await ethPrepared.execute()
console.log('Transaction hash:', txHash)
Sign permit approvals for gasless token approvals:
import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
const adapter = createAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as `0x${string}`,
})
// Sign ERC-2612 permit (gasless USDC approval)
const signature = await adapter.signTypedData(
{
domain: {
name: 'USD Coin',
version: '2',
chainId: 1,
verifyingContract: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
},
types: {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
},
primaryType: 'Permit',
message: {
owner: '0xowner',
spender: '0xspender',
value: '1000000',
nonce: '0',
deadline: '1735689600',
},
},
{ chain: 'Ethereum' }, // Chain must be specified
)
console.log('Permit signature:', signature)
// Use signature for gasless approval
Bridge USDC using the Bridge Kit with OperationContext:
import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
import { BridgeKit } from '@circle-fin/bridge-kit'
// Create adapter for multi-chain operations
const adapter = createAdapterFromPrivateKey({
privateKey: process.env.PRIVATE_KEY as `0x${string}`,
})
const kit = new BridgeKit()
// Bridge from Ethereum to Base using the same adapter
const result = await kit.bridge({
from: { adapter, chain: 'Ethereum' },
to: { adapter, chain: 'Base' },
amount: '100.50',
token: 'USDC',
})
console.log('Bridge transaction:', result.transactionHash)
createAdapterFromPrivateKey(params)Creates an adapter from a private key for server-side use.
Parameters:
privateKey - 32-byte hex string with 0x prefixgetProvider? - Optional custom provider functioncapabilities? - Optional partial capabilities (defaults: user-controlled + all EVM chains)Returns: EthersAdapter instance with lazy initialization
Note: No chain required at creation time. The adapter connects to chains lazily on first operation.
const adapter = createAdapterFromPrivateKey({
privateKey: '0x...',
})
createAdapterFromProvider(params)Creates an adapter from a browser wallet provider (MetaMask, WalletConnect, etc.).
Parameters:
provider - EIP-1193 compatible providergetProvider? - Optional custom provider functioncapabilities? - Optional partial capabilities (defaults: user-controlled + all EVM chains)Returns: Promise<EthersAdapter> instance
const adapter = await createAdapterFromProvider({
provider: window.ethereum,
})
prepare(params, ctx)Prepares a contract function call for estimation and execution.
Parameters:
params - Contract call parameters (address, abi, functionName, args)ctx - Required OperationContext with chain specificationReturns: Promise<PreparedChainRequest> with estimate() and execute() methods
const prepared = await adapter.prepare(
{
address: '0xcontract',
abi: contractAbi,
functionName: 'transfer',
args: ['0xto', '1000000'],
},
{ chain: 'Ethereum' }, // Required
)
signTypedData(typedData, ctx)Signs EIP-712 typed data for permits, meta-transactions, etc.
Parameters:
typedData - EIP-712 structured datactx - Required OperationContext with chain specificationReturns: Promise<string> - Signature as hex string
const signature = await adapter.signTypedData(permitData, {
chain: 'Ethereum',
})
waitForTransaction(txHash, config?)Waits for transaction confirmation.
Parameters:
txHash - Transaction hash to wait forconfig? - Optional wait configuration (confirmations, timeout)Returns: Promise<TransactionReceipt>
const receipt = await adapter.waitForTransaction('0x...')
getAddress(chain)Gets the connected wallet address. Chain parameter is provided automatically by OperationContext resolution.
Returns: Promise<string> - Wallet address
Built-in token operations using the action system:
// Get USDC balance
const balance = await adapter.actions.usdc.balanceOf({
address: '0xwallet',
chain: 'Ethereum',
})
// Get token allowance
const allowance = await adapter.actions.token.allowance({
tokenAddress: '0xtoken',
owner: '0xowner',
spender: '0xspender',
chain: 'Base',
})
The Ethers v6 adapter supports 34 EVM-compatible chains across mainnet and testnet environments through Circle's CCTP v2 protocol:
Arbitrum, Avalanche, Base, Celo, Codex, Ethereum, HyperEVM, Ink, Linea, OP Mainnet, Plume, Polygon PoS, Sonic, Unichain, World Chain, XDC, ZKSync Era
Arbitrum Sepolia, Avalanche Fuji, Base Sepolia, Celo Alfajores, Codex Testnet, Ethereum Sepolia, HyperEVM Testnet, Ink Testnet, Linea Sepolia, OP Sepolia, Plume Testnet, Polygon PoS Amoy, Sonic Testnet, Unichain Sepolia, World Chain Sepolia, XDC Apothem, ZKSync Era Sepolia
This package is part of the Stablecoin Kits monorepo.
# Build
nx build @circle-fin/adapter-ethers-v6
# Test
nx test @circle-fin/adapter-ethers-v6
This project is licensed under the Apache 2.0 License. Contact support for details.
FAQs
EVM blockchain adapter powered by Ethers v6
We found that @circle-fin/adapter-ethers-v6 demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.

Security News
ENISA has become a CVE Program Root, giving the EU a central authority for coordinating vulnerability reporting, disclosure, and cross-border response.

Product
Socket now scans OpenVSX extensions, giving teams early detection of risky behaviors, hidden capabilities, and supply chain threats in developer tools.