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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@circle-fin/adapter-circle-wallets

v1.7.0

Published

Circle Wallets blockchain adapter

Readme

Circle Wallets Adapter

npm version TypeScript License Discord

🔐 Enterprise-grade hybrid adapter for EVM and Solana powered by Circle Wallets

Seamlessly manage cross-chain operations with developer-controlled wallets

⚠️ SERVER ENTRIES ONLY - The main, /utils, and /ucw/server entries require secrets and must never be bundled into a browser. Use /ucw/client for the browser-safe UCW surface.

Table of Contents

Overview

The Circle Wallets Adapter is a hybrid adapter that provides unified access to both EVM and Solana blockchains through Circle's developer-controlled wallet infrastructure. Unlike ecosystem-specific adapters (Viem, Ethers, Solana), this adapter seamlessly operates across both ecosystems using a single instance.

Built for enterprise and backend applications, this adapter integrates Circle Wallets and Circle Contracts to provide secure, programmatic wallet management.

Why Circle Wallets Adapter?

  • 🌉 True Cross-Ecosystem Support - Single adapter instance works across EVM chains (Ethereum, Base, Polygon, etc.) AND Solana
  • 🏢 Enterprise-Grade - Built on Circle's institutional wallet infrastructure with developer-controlled addressing
  • 🔐 Secure by Design - API key and entity secret authentication keeps credentials server-side only
  • 🔒 Type-Safe - Built with TypeScript strict mode for complete type safety
  • 🎯 Simplified Management - One adapter for all chains eliminates the need to manage separate EVM and Solana adapters
  • 🔄 Complete Transaction Lifecycle - Full prepare/estimate/execute workflow across both ecosystems
  • 🚀 Production Ready - Leverage Circle's reliable infrastructure and built-in wallet management

When and How Should I Use The Circle Wallets Adapter?

I'm a developer building a backend service

If you're building a server-side application that needs to perform blockchain operations across EVM and Solana using Circle Wallets, this adapter provides a unified interface for both ecosystems.

Perfect for:

  • Backend services performing automated cross-chain transfers
  • Enterprise applications managing wallets programmatically
  • Server-side scripts requiring multi-ecosystem support
  • Applications using Circle's custody infrastructure

Example:

import { createCircleWalletsAdapter } from '@circle-fin/adapter-circle-wallets'

// Server-side only - credentials from secure environment variables
const adapter = createCircleWalletsAdapter({
  apiKey: process.env.CIRCLE_API_KEY!,
  entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
})

// Same adapter works for both EVM and Solana operations!

I'm a developer making a Kit Provider

If you're building a provider (e.g., a custom BridgingProvider) that needs to work across both EVM and Solana, the Circle Wallets Adapter provides a unified abstraction. You don't need separate adapters for each ecosystem.

Benefits:

  • Single adapter instance for cross-ecosystem operations
  • Consistent API across EVM and Solana chains
  • Built-in wallet management through Circle's infrastructure
  • Developer-controlled addressing for explicit wallet specification

⚠️ Server-Side Only

The main entry, /utils, and /ucw/server can only be used in server-side (Node.js) environments. Browser builds resolve these entries to a guard that directs the integrator to /ucw/client.

Security Reasons:

  • API Keys - Your Circle API key must never be exposed to client-side code
  • Entity Secrets - Entity secrets are sensitive credentials that must remain server-side only
  • Wallet Control - Developer-controlled wallets should only be accessible from trusted backend services

Technical Reasons:

  • Node.js Dependencies - Requires Node.js built-in modules (crypto, buffer, etc.) not available in browsers
  • SDK Requirements - Circle's Developer Controlled Wallets SDK requires a Node.js runtime environment

✅ For UCW Browser Applications:

Import only the transport-agnostic transaction waiter and signing-rejection helpers from the client entry:

import { createWaitForTransaction } from '@circle-fin/adapter-circle-wallets/ucw/client'

const waitForTransaction = createWaitForTransaction({
  getStatus: ({ challengeId, signal }) =>
    fetch(`/api/transactions/${challengeId}`, { signal }).then((response) =>
      response.json(),
    ),
})

const result = await waitForTransaction({ challengeId })

The browser helper calls the endpoint supplied by your application. It never calls Circle directly and does not prescribe how the server authenticates the request.

UCW Typed-Data Signing

The server-side UCW adapter can sign EIP-712 typed data through a Circle challenge. Unlike a transaction — whose result the adapter reads back from Circle — a signature is delivered only to the client that approves the challenge, in the W3S browser SDK's challenge result. Hand it back through resolveTypedDataSignature:

const adapter = await createCircleUserWalletAdapter({
  apiKey,
  userToken,
  wallet,
  // Your existing approval channel: forward the challenge to the browser.
  onChallenge: ({ challengeId }) => notifyApprovalSurface(challengeId),
  // The other half: return the signature the browser received for it.
  resolveTypedDataSignature: ({ challengeId }) =>
    awaitSignatureFromApprovalSurface(challengeId),
})

const signature = await adapter.signTypedData(typedData, { chain })

In the browser, the signature is on the challenge result the W3S SDK already gives you for PIN approval:

sdk.execute(challengeId, (error, result) => {
  if (error) return reportRejected(challengeId, error)
  reportSignature(challengeId, result?.data?.signature)
})

Without resolveTypedDataSignature the adapter reports supportsSignTypedData() === false and rejects typed-data requests, so a caller can pick an on-chain alternative rather than discover the gap after the owner has approved.

Keep the signTypedData invocation and its resolveTypedDataSignature promise alive until the approving client returns the signature. Circle's completed challenge has no server-readable signature, so if the process exits after approval, the result cannot be recovered and the caller must start a new signing operation. Run this flow in a durable server or worker process; a stateless request handler that can end before browser approval is not a supported deployment shape.

Authenticate the client-to-server return channel and bind each challenge id to the user and in-flight signing request before accepting its signature. The adapter validates the returned signature's format, but your application owns authorization for that relay.

For an SCA wallet, Circle returns the wallet owner's 65-byte ECDSA signature, which the wallet validates through ERC-1271 isValidSignature. Plain ecrecover recovers the owner's key, not the SCA address, so use an ERC-1271-aware verifier — USDC's signature-checker path, used by ERC-3009 authorizations, accepts it. Verifiers that only ecrecover, including standard EIP-2612 permit and Gateway burn-intent flows, cannot validate it. An EOA wallet produces the standard signature that ecrecover validates against its address.

For other browser wallet flows, use a browser-compatible adapter:

Installation

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

Prerequisites:

Initial Setup - Generate and Register Entity Secret (If Not Already Done)

If you haven't already set up an entity secret with Circle, you'll need to complete a one-time setup process. The adapter provides convenience utilities (re-exported from Circle's SDK) to simplify this:

import {
  generateEntitySecret,
  registerEntitySecretCiphertext,
} from '@circle-fin/adapter-circle-wallets/utils'

// Step 1: Generate a new entity secret
const entitySecret = generateEntitySecret()
console.log('Generated entity secret:', entitySecret)

// Step 2: Register the entity secret with Circle
const response = await registerEntitySecretCiphertext({
  apiKey: 'TEST_API_KEY:abc123:def456', // Your Circle API key
  entitySecret: entitySecret,
})

console.log('Recovery file:', response.data?.recoveryFile)
// The recovery file is also automatically downloaded to your filesystem

Important Security Notes:

  • ⚠️ Entity Secret - Store this securely in environment variables. Never commit it to version control.
  • ⚠️ Recovery File - Back up the generated recovery_file_<timestamp>.dat securely. This is your only way to restore access if the entity secret is lost.
  • ⚠️ One-Time Setup - This only needs to be done once per Circle account/environment (test/production). If you already have an entity secret registered, skip this step.

After completing this setup (or if you already have credentials), store your entity secret in environment variables:

# .env file (add to .gitignore!)
CIRCLE_API_KEY=TEST_API_KEY:abc123:def456
CIRCLE_ENTITY_SECRET=your-generated-64-char-secret

Note: These utilities are re-exported from @circle-fin/developer-controlled-wallets for convenience. For more details, see the Circle Developer Controlled Wallets documentation.

Quick Start

🚀 Basic Setup

Create an adapter instance with your Circle credentials:

import { createCircleWalletsAdapter } from '@circle-fin/adapter-circle-wallets'

// Initialize adapter with Circle credentials (server-side only!)
const adapter = createCircleWalletsAdapter({
  apiKey: process.env.CIRCLE_API_KEY!, // Format: TEST_API_KEY:abc:def or Base64
  entitySecret: process.env.CIRCLE_ENTITY_SECRET!, // Format: 64 lowercase alphanumeric chars
})

// Works across both EVM and Solana!
// No need to create separate adapters for each ecosystem

API Key Format:

  • Environment-prefixed: TEST_API_KEY:abc123:def456 or LIVE_API_KEY:xyz:uvw
  • Base64 encoded: Standard Base64 string

Entity Secret Format:

  • 64 lowercase alphanumeric characters (e.g., abc123... × 64 chars)

Usage Examples

Cross-Chain Bridging with Bridge Kit

Use the Circle Wallets Adapter with Bridge Kit for seamless cross-chain USDC transfers across both EVM and Solana:

import { createCircleWalletsAdapter } from '@circle-fin/adapter-circle-wallets'
import { BridgeKit } from '@circle-fin/bridge-kit'

// Single adapter instance for both ecosystems
const adapter = createCircleWalletsAdapter({
  apiKey: process.env.CIRCLE_API_KEY!,
  entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
})

const kit = new BridgeKit()

// Bridge from Ethereum (EVM) to Solana - same adapter for both!
const result = await kit.bridge({
  from: {
    adapter,
    chain: 'Ethereum',
    address: '0x1234...', // EVM address (developer-controlled)
  },
  to: {
    adapter, // Same adapter instance!
    chain: 'Solana',
    address: 'ABC123...', // Solana address (developer-controlled)
  },
  amount: '100.00',
  token: 'USDC',
})

console.log('Bridge transaction:', result.transactionHash)

Key Benefits:

  • ✅ One adapter for cross-ecosystem bridging (EVM ↔ Solana)
  • ✅ Developer-controlled addressing for explicit wallet specification
  • ✅ Unified API regardless of source/destination chain type

Direct SDK Access

Access the underlying Circle Wallets SDK for advanced operations:

import { createCircleWalletsAdapter } from '@circle-fin/adapter-circle-wallets'

const adapter = createCircleWalletsAdapter({
  apiKey: process.env.CIRCLE_API_KEY!,
  entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
})

// Access Circle's Developer Controlled Wallets and Smart Contract Platform clients
const sdk = await adapter.getSdk()

// Use Developer Controlled Wallets client
const wallets = await sdk.devc.listWallets()
console.log('Available wallets:', wallets)

// Use Smart Contract Platform client
const contracts = await sdk.scp.listContracts()
console.log('Deployed contracts:', contracts)

API Reference

Factory Function

createCircleWalletsAdapter(options)

Creates a Circle Wallets adapter instance for server-side use.

Parameters:

  • apiKey - Circle API key (environment-prefixed or Base64 format)
  • entitySecret - Circle entity secret (64 lowercase alphanumeric characters)
  • baseUrl? - Optional custom Circle API base URL (default: Circle's production endpoint)

Returns: CircleWalletsAdapter instance

Throws: Validation error if API key or entity secret format is invalid

const adapter = createCircleWalletsAdapter({
  apiKey: process.env.CIRCLE_API_KEY!,
  entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
  baseUrl: 'https://api.circle.com/v1', // Optional
})

Adapter Methods

getSdk()

Returns the underlying Circle Wallets SDK clients for advanced operations.

Returns: Promise<CircleWalletsSDK>

const sdk = await adapter.getSdk()

// Access Developer Controlled Wallets client
sdk.devc.listWallets()

// Access Smart Contract Platform client
sdk.scp.listContracts()

Inherited Methods

The Circle Wallets Adapter extends HybridAdapter and supports all standard adapter methods:

  • prepare(params, ctx) - Prepare contract transactions
  • signTypedData(typedData, ctx) - Sign EIP-712 typed data (EVM) or messages (Solana)
  • getAddress(ctx) - Get wallet address for specified chain
  • validateChainSupport(chain) - Validate chain compatibility

All methods support the developer-controlled addressing pattern - you must specify the address in the operation context.

User-Controlled Wallets (UCW)

The /ucw/server entry (also re-exported from the main entry) builds adapters whose signer is the user's own Circle wallet. Every state change becomes a Circle challenge the wallet's owner approves on your surface, so the adapter holds no signing authority. The factory accepts Circle smart contract accounts (SCAs), so batched steps (e.g. a bridge's approve + burn) execute atomically in one approval. The wallet helpers can still find or provision EOA wallets for applications that manage them outside this adapter.

createCircleUserWalletAdapter(options)

Creates a user-controlled adapter. By default it also finds or creates the user's wallets before resolving.

Parameters: the Circle UCW connection (apiKey + userToken, a client, or a proxy baseUrl), an optional onChallenge callback for approval notifications, an optional resolveTypedDataSignature callback that enables typed-data signing, and one of five wallet forms:

| Form | Wallet resolution | Returns | | --- | --- | --- | | chain | Finds or creates the wallet on that chain | single-chain ViemAdapter | | chains | Finds or creates one wallet per chain, provisioning missing ones in one challenge | MultiChainCircleUserWalletAdapter | | wallet | Uses the given SCA CircleUserWallet and derives its chain from wallet.blockchain | single-chain ViemAdapter | | walletId + walletAddress + chain + accountType: 'SCA' | Uses an explicitly chain-bound SCA identity | single-chain ViemAdapter | | wallets | Routes each chain's operations to that chain's wallet | MultiChainCircleUserWalletAdapter |

Optional across all forms: rpcUrls (per-chain overrides, keyed by EVM chain id), onProgress, pollIntervalMs, and timeoutMs. The chain and chains forms also accept signal while finding or creating wallets. Explicit wallet objects and wallet sets derive their supported chains from each wallet's blockchain; contradictory chains overrides are rejected.

Returns: Promise<ViemAdapter>, or Promise<MultiChainCircleUserWalletAdapter> for the chains / wallets forms. The multi-chain adapter serves both sides of a bridge from one instance.

Throws: KitError when the options are invalid, a chain is unsupported by Circle UCW, the user has not completed wallet setup, or approval times out.

import { createCircleUserWalletAdapter } from '@circle-fin/adapter-circle-wallets/ucw/server'
import { BridgeKit } from '@circle-fin/bridge-kit'

const adapter = await createCircleUserWalletAdapter({
  apiKey: process.env.CIRCLE_API_KEY!,
  userToken, // the end user's Circle session token
  chains: ['Arc_Testnet', 'Base_Sepolia'],
  onChallenge: ({ challengeId }) => sendToBrowser(challengeId),
})

// One adapter, both sides of the bridge — no address in the context, because
// a user-controlled adapter resolves its own operating address.
const kit = new BridgeKit()
const result = await kit.bridge({
  from: { adapter, chain: 'Arc_Testnet' },
  to: { adapter, chain: 'Base_Sepolia' },
  amount: '1.5',
})

onChallenge hands each challenge id to your application, which forwards it to the owner's browser to approve with Circle's W3S browser SDK. The browser-safe /ucw/client entry's createWaitForTransaction then waits for the result through your own status endpoint — it never calls Circle directly.

Use onProgress to publish server-held lifecycle state. The strategy invokes handlers serially in observation order, and every update carries a monotonic sequence as a secondary guard against stale writes across transports or retries.

Wallet Helpers

Manage the user's wallets directly — all three take the connection plus an options object, and name chains the way the kits do:

  • findCircleUserWallet(connection, { chain, accountType? }) - the user's wallet on a chain, or undefined. Never creates one.
  • ensureCircleUserWallet(connection, { chain, accountType?, onChallenge?, pollIntervalMs?, timeoutMs?, signal? }) - finds the wallet, creating it through a challenge when missing.
  • ensureCircleUserWallets(connection, { chains, ... }) - the same for several chains, provisioning every missing one in a single challenge and returning the wallets in the requested order.

Throws: KitError when the chain is unrecognized or has no Circle equivalent, or (for the ensure helpers) when the user has not established a signing method yet — check for CIRCLE_USER_WALLET_PIN_NOT_SET_ERROR_CODE via circleErrorCode(error) and run your setup flow before retrying.

import {
  ensureCircleUserWallet,
  findCircleUserWallet,
} from '@circle-fin/adapter-circle-wallets/ucw/server'

const existing = await findCircleUserWallet(auth, { chain: 'Arc_Testnet' })

const wallet = await ensureCircleUserWallet(auth, {
  chain: 'Arc_Testnet',
  onChallenge: ({ challengeId }) => sendToBrowser(challengeId),
})

// Pass the whole wallet to skip re-resolving it.
const adapter = await createCircleUserWalletAdapter({ ...auth, wallet })

Supported Chains

The Circle Wallets Adapter supports a curated subset of CCTP v2-enabled chains across both EVM and Solana ecosystems:

Mainnet (9 chains):

  • Arbitrum
  • Avalanche
  • Base
  • Ethereum
  • Monad
  • OP Mainnet
  • Polygon PoS
  • Solana
  • Unichain

Testnet (10 chains):

  • Arc Testnet
  • Arbitrum Sepolia
  • Avalanche Fuji
  • Base Sepolia
  • Ethereum Sepolia
  • Monad Testnet
  • OP Sepolia
  • Polygon PoS Amoy
  • Solana Devnet
  • Unichain Sepolia

Note: Circle Wallets supports a subset of all CCTP v2 chains. For full CCTP v2 chain support with external wallets, use @circle-fin/adapter-viem-v2 (47 EVM chains) or @circle-fin/adapter-solana-kit for Solana.

License

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


Ready to integrate?

Join DiscordVisit our Help-DeskCircle Developer Docs

Built with ❤️ by Circle