npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@circle-fin/adapter-viem-v2

v1.1.1

Published

EVM blockchain adapter powered by Viem v2

Downloads

3,450

Readme

Viem v2 Adapter

npm version TypeScript License Discord

Type-safe EVM blockchain adapter powered by Viem v2

Seamlessly interact with 16+ EVM networks using a single, strongly-typed interface

Table of Contents

Overview

The Viem v2 Adapter is a strongly-typed implementation of the Adapter interface for the EVM-compatible blockchains. Built on top of the popular Viem 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, 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.

Why Viem Adapter?

  • ⚡ Zero-config defaults - Built-in reliable RPC endpoints for all supported EVM chains, no setup required
  • 🌐 Full EVM compatibility - Works with any Ethereum-compatible blockchain
  • 🔧 Bring your own setup: Use your existing Viem PublicClient and WalletClient instances
  • 🚀 Instant connectivity - Connect to Ethereum, Base, Arbitrum, and more without researching RPC providers
  • 🔒 Type-safe: Built with TypeScript strict mode for complete type safety
  • 🎯 Simple API: Clean abstraction over complex blockchain operations
  • 🔄 Transaction lifecycle - Complete prepare/estimate/execute workflow
  • 🌉 Cross-chain ready - Seamlessly bridge USDC between EVM chains and Solana

When and How Should I Use The Viem Adapter?

I'm a developer using a kit

If you're using one of the kits to do some action, e.g. bridging from chain 'A' to chain 'B', then you only need to instantiate the adapter for your chain and pass it to the kit.

Example
// Private keys can be provided with or without '0x' prefix
const adapter = createAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY as string, // Works with or without '0x'
})

// Both formats are automatically normalized:
const adapter1 = createAdapterFromPrivateKey({
  privateKey: '0x1234...', // With prefix ✅
})

const adapter2 = createAdapterFromPrivateKey({
  privateKey: '1234...', // Without prefix ✅ (automatically normalized)
})

I'm a developer making a Kit Provider

If you are making a provider for other Kit users to plug in to the kit, e.g. a BridgingProvider, and you'll need to interact with diff chains, then you'll need to use the abstracted Adapter methods to execute on chain.

Installation

npm install @circle-fin/adapter-viem-v2 viem
# or
yarn add @circle-fin/adapter-viem-v2 viem

Peer Dependencies

This adapter requires viem as a peer dependency. Install it alongside the adapter:

npm install @circle-fin/adapter-viem-v2 viem
# or
yarn add @circle-fin/adapter-viem-v2 viem

Supported Versions: ^2.30.0 (2.30.x through 2.x.x)

Troubleshooting Version Conflicts

If you encounter peer dependency warnings:

  • Check your viem version: npm ls viem
  • Ensure viem is between 2.30.0 and 3.0.0 (exclusive)
  • Use npm install viem@^2.30.0 to install a compatible version

Quick Start

🚀 Easy Setup with Factory Methods (Recommended)

The simplest way to get started is with our factory methods. With reliable default RPC endpoints - no need to research providers or configure endpoints! Plus, you can create just one adapter and use it across different chains!

import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'

const adapter = createAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY as string, // Both '0x...' and '...' work
  capabilities: {
    addressContext: 'user-controlled',
    supportedChains: [Ethereum, Base, Polygon],
  },
})

// Ready to use with the Bridge Kit!
// Address will be automatically resolved during operations

✨ Key Feature: All chain definitions include reliable default RPC endpoints with automatic failover:

  • Ethereum: https://eth.merkle.iohttps://ethereum.publicnode.com 🔄
  • Base: https://mainnet.base.orghttps://base.publicnode.com 🔄
  • Polygon: https://polygon-rpc.comhttps://polygon.publicnode.com 🔄
  • Unichain: https://rpc.unichain.orghttps://mainnet.unichain.org 🔄
  • Arbitrum: https://arb1.arbitrum.io/rpc (single endpoint)
  • And all other supported EVM chains!

🚀 Automatic Failover: Chains with multiple endpoints automatically switch to backup RPCs if the primary fails!

🏭 Production Considerations

While the default RPC endpoints are reliable, you may want to use your own RPC providers for production applications:

import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

// Option 1: Use your own PublicClient with custom RPC
const customPublicClient = createPublicClient({
  chain: mainnet,
  transport: http('https://your-custom-rpc-endpoint.com'),
})

const adapter = createAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY as string,
  capabilities: {
    addressContext: 'user-controlled',
    supportedChains: [Ethereum],
  },
  getPublicClient: () => customPublicClient,
})

Default RPC Benefits:

  • Instant setup - Start building without provider research
  • Reliable uptime - Cloudflare and official chain endpoints
  • No API keys - Public endpoints with generous rate limits
  • Development friendly - Perfect for prototyping and testing

When to use custom RPCs:

  • 🎯 High throughput - Your app needs dedicated bandwidth
  • 🔧 Specific features - Archive nodes, debug APIs, etc.
  • 📊 Analytics - Custom monitoring and logging
  • 🔒 Compliance - Your organization requires specific providers
import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
import { createPublicClient, http } from 'viem'

// Production-ready setup with custom RPC endpoints
const adapter = createAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
  capabilities: {
    addressContext: 'user-controlled',
    supportedChains: [Ethereum, Base, Polygon],
  },
  getPublicClient: ({ chain }) =>
    createPublicClient({
      chain,
      transport: http(
        `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
        {
          retryCount: 3,
          timeout: 10000,
        },
      ),
    }),
})

🌐 Browser Support with Wallet Providers

For browser environments with wallet providers like MetaMask:

import { createAdapterFromProvider } from '@circle-fin/adapter-viem-v2'

// Create an adapter from a browser wallet
const adapter = await createAdapterFromProvider({
  provider: window.ethereum,
  capabilities: {
    addressContext: 'user-controlled',
    supportedChains: [Ethereum, Base, Polygon],
  },
})

🔧 Advanced Manual Setup

For advanced patterns like lazy initialization or environment-adaptive configuration:

import { ViemAdapter } from '@circle-fin/adapter-viem-v2'
import { createPublicClient, createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'

// Create clients manually for full control
const account = privateKeyToAccount(process.env.PRIVATE_KEY as string)

// Manual constructor with getter pattern and explicit capabilities
const adapter = new ViemAdapter(
  {
    getPublicClient: ({ chain }) =>
      createPublicClient({
        chain: mainnet,
        transport: http('https://your-custom-rpc.com'),
      }),
    getWalletClient: async ({ chain }) => {
      // Your custom logic here - sync or async, chain-aware
      // The chain parameter allows you to create wallet clients for specific chains
      return createWalletClient({
        chain,
        account,
        transport: http('https://your-custom-rpc.com'),
      })
    },
  },
  {
    addressContext: 'user-controlled', // or 'developer-controlled'
    supportedChains: [Ethereum, Base, Polygon], // Specify supported chains
  },
)

Benefits: Lazy initialization, environment adaptation, custom caching logic.

OperationContext Pattern

The Viem v2 Adapter supports the OperationContext pattern for flexible per-operation chain and address specification. This enables advanced use cases like multi-chain operations and enterprise address management.

Address Context Modes

The adapter's behavior is determined by the addressContext capability:

'user-controlled' (Default, Recommended)

  • Use case: Browser wallets, private keys, hardware wallets, development
  • Address handling: Automatically resolved from connected wallet or key
  • Best for: MetaMask, Coinbase Wallet, WalletConnect, private key development
  • Key benefit: Simpler API - no need to pass address to every operation

When to use:

  • Using a private key (createAdapterFromPrivateKey)
  • Using a browser wallet (MetaMask, Coinbase Wallet, etc.)
  • Single address with automatic resolution
  • Building a standard dApp or backend service
// Private key adapter - uses 'user-controlled' by default
const adapter = createAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY,
  // Defaults to 'user-controlled' - address resolved automatically
})

// Address is resolved automatically from wallet
await adapter.prepare(contractParams, { chain: 'Base' })

'developer-controlled' (For Enterprise/Multi-Address Systems)

  • Use case: Enterprise custody, multi-address management, institutional wallets
  • Address handling: Must be explicitly provided for each operation
  • Best for: Fireblocks, Circle Wallets, Coinbase Prime, custom custody solutions
  • Key benefit: Flexibility to use different addresses per operation

When to use:

  • Managing multiple addresses from a single provider (Fireblocks vaults)
  • Requiring different addresses for different operations
  • Custody solution with explicit address specification
  • Integrating with institutional-grade wallet infrastructure
// Enterprise custody - use 'developer-controlled'
const adapter = createAdapterFromProvider({
  provider: fireblocksProvider,
  capabilities: {
    addressContext: 'developer-controlled',
    supportedChains: [Ethereum, Base],
  },
})

// Address must be specified per operation - enables multi-address flexibility
await adapter.prepare(contractParams, {
  chain: 'Base',
  address: '0x123...', // Required: specify which vault/address to use
})

Decision Guide:

  • Private keys: Use default ('user-controlled')
  • Browser wallets: Use default ('user-controlled')
  • Fireblocks/Custody: Use 'developer-controlled'
  • Single address: Use default ('user-controlled')
  • Multiple addresses: Use 'developer-controlled'

OperationContext Usage

The second parameter to prepare() is required and specifies the operation context:

// OperationContext is required for all operations
const prepared = await adapter.prepare(
  {
    address: '0x...',
    abi: contractAbi,
    functionName: 'transfer',
    args: ['0xto', '1000'],
  },
  {
    chain: 'Base', // Chain specified in context
  },
)

API Reference

Constructor Options

The ViemAdapter constructor requires both configuration options and adapter capabilities:

constructor(options: ViemAdapterOptions, capabilities: AdapterCapabilities)

interface ViemAdapterOptions {
  getPublicClient: (params: { chain: Chain }) => PublicClient
  getWalletClient: (params: { chain: Chain }) => Promise<WalletClient> | WalletClient
}

interface AdapterCapabilities {
  addressContext: 'user-controlled' | 'developer-controlled'
  supportedChains: ChainDefinition[]
}

getPublicClient

A function that returns a PublicClient for the specified chain.

  • Type: (params: { chain: Chain }) => PublicClient
  • Purpose: Provides read-only blockchain access for the given chain
  • Called: Every time the adapter needs to read blockchain data or switch chains

getWalletClient

A function that returns a WalletClient for signing and sending transactions.

  • Type: (params: { chain: Chain }) => Promise<WalletClient> | WalletClient
  • Purpose: Provides wallet access for signing transactions and managing accounts
  • Called: Only when needed (lazy initialization) and cached automatically per chain
  • Supports: Both synchronous and asynchronous initialization
  • Chain-aware: Requires chain parameter for explicit multi-chain wallet support

capabilities (Required)

Defines the adapter's capabilities and operational model.

  • Type: AdapterCapabilities
  • Purpose: Specifies address control model and supported chains
  • addressContext:
    • 'user-controlled' - Address managed by wallet (MetaMask, private keys)
    • 'developer-controlled' - Address must be specified per operation (enterprise custody)
  • supportedChains: Array of blockchain networks this adapter can operate on

Example:

import { ViemAdapter } from '@circle-fin/adapter-viem-v2'
import { createPublicClient, createWalletClient, http } from 'viem'

const adapter = new ViemAdapter(
  {
    getPublicClient: ({ chain }) =>
      createPublicClient({ chain, transport: http() }),
    getWalletClient: async ({ chain }) => {
      // Your initialization logic - now chain-aware
      return createWalletClient({
        chain,
        transport: http(),
        /* ... */
      })
    },
  },
  {
    addressContext: 'user-controlled', // Address managed by wallet
    supportedChains: [Ethereum, Base, Polygon], // Specify supported chains
  },
)

Methods

  • calculateTransactionFee(baseComputeUnits, bufferBasisPoints?, chain) - Calculate transaction fees with optional buffer
  • ensureChain(targetChain) - Ensures the adapter is connected to the correct chain
  • fetchEIP2612Nonce(tokenAddress, ownerAddress, ctx) - Fetch EIP-2612 nonce for permit signatures
  • fetchGasPrice(chain) - Fetch current gas price from the network
  • getAddress(chain) - Get the connected wallet address
  • getPublicClient(chainDef) - Get the cached PublicClient or initialize it for the specified chain
  • getViemChain(chain) - Get the Viem Chain object for the given chain definition
  • initializeWalletClient(chain) - Initialize wallet client with proper caching and error handling
  • prepare(params, ctx: OperationContext) - Prepare transactions for execution
    • params: Contract parameters (address, ABI, function, args)
    • ctx: Required operation context specifying the chain and address for this operation
  • prepareAction(action, params, ctx) - Prepare (but do not execute) an action for the connected blockchain
  • readContract<T>(params, chain) - Read data from smart contract functions
  • resetState() - Reset all cached state in the adapter, including Viem-specific caches
  • signTypedData(typedData, ctx: OperationContext) - Sign EIP-712 typed data with required operation context
  • switchToChain(chain) - Switch the adapter to operate on the specified chain
  • validateChainSupport(targetChain) - Validate that the target chain is supported by this adapter
  • waitForTransaction(txHash, config?, chain) - Wait for transaction confirmation

prepare() Method Details

The prepare() method requires an OperationContext for all operations:

// OperationContext is required - chain specified in context
const prepared = await adapter.prepare(
  {
    address: '0x...',
    abi: contractAbi,
    functionName: 'transfer',
    args: ['0xto', '1000'],
  },
  {
    chain: 'Base', // Chain specified in context
    address: '0x...', // Only required for developer-controlled adapters
  },
)

Token Operations via Actions

For token balance and allowance operations, this adapter uses the standardized action-based system inherited from EvmAdapter:

  • token.balanceOf - Get balance for any ERC-20 token
  • token.allowance - Get allowance for any ERC-20 token
  • usdc.balanceOf - Get USDC balance (uses token.balanceOf with USDC address)
  • usdc.allowance - Get USDC allowance (uses token.allowance with USDC address)

These actions provide type-safe, validated interfaces and use the adapter's readContract() method internally.

Development

This package is part of the Stablecoin Kits monorepo.

# Build
nx build @circle-fin/adapter-viem-v2

# Test
nx test @circle-fin/adapter-viem-v2

License

This project is licensed under the Apache 2.0 License. Contact support for details.


Ready to integrate?

Join DiscordVisit our Help-Desk

Built with ❤️ by Circle