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/unified-balance-kit

v1.0.2

Published

SDK for cross-chain USDC deposits, spending, and balance queries

Downloads

1,047

Readme

Unified Balance Kit

npm version TypeScript License Discord

A strongly-typed SDK for cross-chain USDC deposits, spending, and balance queries

Move USDC across chains with simple method calls—no manual bridging, no liquidity pre-positioning, no on-chain delays

Table of Contents

Overview

The Stablecoin 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 Unified Balance Kit provides a high-level abstraction for cross-chain USDC operations via Circle's Gateway protocol. Deposit, spend (mint), and query balances across multiple chains with simple method calls—no manual bridging steps, no liquidity pre-positioning, and no on-chain delays.

Why Unified Balance Kit?

  • 🌐 Unified balance model: Single abstraction for deposits, spends, and balance queries across chains
  • ⚡ Instant cross-chain moves: Pull from multiple source chains and mint on destination in one flow
  • 🔧 Bring your own adapters: Use viem, ethers, or @solana/web3.js adapters
  • 🔒 Production-ready: Leverages Circle's Gateway v1 with attestations and deterministic flows
  • 🚀 Developer experience: Complete TypeScript support, comprehensive validation, and structured errors
  • 📦 Multi-source spends: Allocate amounts from multiple chains in a single spend operation
  • 🛡️ Robust error handling: Structured KitError with recoverability and retry support
  • 📡 Event monitoring: Track lifecycle events for deposits, spends, and balance operations

Architecture Flow

The Unified Balance Kit follows a three-layer architecture designed for flexibility and type safety:

┌─────────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│  Unified Balance    │────│     Provider     │────│   Adapter       │
│  Kit (Orchestrator) │    │  (Gateway v1)    │    │ (Blockchain)    │
└─────────────────────┘    └──────────────────┘    └─────────────────┘
  1. Adapter: Handles blockchain-specific operations (wallets, transactions, gas) and enables you to use whatever framework you're comfortable with (viem, ethers, @solana/web3.js)
  2. Provider: Implements the Gateway protocol (currently Gateway v1)
  3. Unified Balance Kit: Orchestrates adapters and providers with validation and routing

This separation ensures that each component has a single responsibility while maintaining seamless integration across the entire cross-chain lifecycle.

Installation

npm install @circle-fin/unified-balance-kit
# or
yarn add @circle-fin/unified-balance-kit

Adapters

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
# or
yarn add @circle-fin/adapter-ethers-v6

# 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-token

Quick Start

🚀 Easiest Setup: Deposit and Spend

Best for: Getting started quickly, simple deposits and cross-chain spends

import {
  createUnifiedBalanceKitContext,
  deposit,
  spend,
} from '@circle-fin/unified-balance-kit'
import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'

// Create context with default Gateway v1 provider
const context = createUnifiedBalanceKitContext()

// Create adapter that works across chains
const adapter = createViemAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
})

// Deposit USDC on Ethereum
await deposit(context, {
  from: { adapter, chain: 'Ethereum' },
  amount: '100',
})

// Spend (mint) USDC on Base by pulling from Ethereum
const result = await spend(context, {
  amount: '50',
  from: {
    adapter,
    allocations: { amount: '50', chain: 'Ethereum' },
  },
  to: { adapter, chain: 'Base' },
})

console.log('Spend tx:', result.txHash, result.explorerUrl)

🎯 Send to Different Address

Best for: Sending funds to someone else's wallet, custodial services

Use recipientAddress when the recipient is different from your adapter's address:

const result = await spend(context, {
  amount: '50',
  from: {
    adapter,
    allocations: { amount: '50', chain: 'Ethereum' },
  },
  to: {
    adapter,
    chain: 'Base',
    recipientAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
  },
})

📊 Query Balances

Best for: Displaying aggregated or per-chain USDC balances

import {
  createUnifiedBalanceKitContext,
  getBalances,
} from '@circle-fin/unified-balance-kit'

const context = createUnifiedBalanceKitContext()
const adapter = createViemAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
})

// Get confirmed balances across all supported chains
const balances = await getBalances(context, {
  sources: { adapter },
})

console.log('Confirmed:', balances.totalConfirmedBalance)

// Include pending deposits in the result
const withPending = await getBalances(context, {
  sources: { adapter },
  includePending: true,
})

console.log('Confirmed:', withPending.totalConfirmedBalance)
console.log('Pending:', withPending.totalPendingBalance)

💰 Cost Estimation

Best for: Showing users fees upfront before spending

import {
  createUnifiedBalanceKitContext,
  estimateSpend,
} from '@circle-fin/unified-balance-kit'

const context = createUnifiedBalanceKitContext()

const estimate = await estimateSpend(context, {
  amount: '100',
  from: {
    adapter,
    allocations: { amount: '100', chain: 'Ethereum' },
  },
  to: { adapter, chain: 'Base' },
})

console.log('Estimated fees:', estimate.fees)

Configuration

Kit Context

The kit uses a context object that holds providers. Create it with createUnifiedBalanceKitContext:

// Default: Gateway v1 provider
const context = createUnifiedBalanceKitContext()

// Or with additional providers
const customContext = createUnifiedBalanceKitContext({
  providers: [myCustomProvider],
})

Spend Parameters

// Single source allocation
const spendParams = {
  amount: '100',
  from: {
    adapter,
    allocations: { amount: '100', chain: 'Ethereum' },
  },
  to: { adapter, chain: 'Base' },
}

// Multi-source allocation (pull from multiple chains)
const multiSourceSpend = {
  amount: '100',
  from: {
    adapter,
    allocations: [
      { amount: '50', chain: 'Ethereum' },
      { amount: '50', chain: 'Base' },
    ],
  },
  to: { adapter, chain: 'Avalanche' },
}

Balance Parameters

// By adapter (wallet-controlled)
const balances = await getBalances(context, {
  sources: { adapter },
})

// By address (read-only, no adapter needed)
const balancesByAddress = await getBalances(context, {
  sources: { address: '0x...', chains: ['Ethereum', 'Base'] },
})

Custom Fees

The kit supports custom fees on spend operations. Fees are added on top of the transfer amount. Use the class API with setCustomFeePolicy for dynamic fee calculation, or pass config.customFee per spend:

import { UnifiedBalanceKit } from '@circle-fin/unified-balance-kit'

const kit = new UnifiedBalanceKit()

// Kit-level policy (class API)
kit.setCustomFeePolicy({
  computeFee: (params) => {
    const total = parseFloat(params.amount)
    return (total * 0.01).toFixed(6) // 1%
  },
  resolveFeeRecipientAddress: (feePayoutChain) => {
    return feePayoutChain.type === 'solana'
      ? 'SolanaAddressBase58...'
      : '0xEvmAddress...'
  },
})

// Or per-spend override (works with both APIs)
await spend(context, {
  amount: '100',
  from: { adapter, allocations: { amount: '100', chain: 'Ethereum' } },
  to: { adapter, chain: 'Base' },
  config: {
    customFee: {
      value: '1.0',
      recipientAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
    },
  },
})

Error Handling

The kit uses structured KitError instances with consistent properties:

  • code: Numeric error code (e.g., 1001, 5001)
  • name: Human-readable ID (e.g., INPUT_VALIDATION_FAILED, ONCHAIN_TRANSACTION_REVERTED)
  • type: Error category (INPUT, BALANCE, ONCHAIN, RPC, NETWORK)
  • recoverability: FATAL, RETRYABLE, or RESUMABLE
  • message: User-friendly explanation
  • cause.trace: Additional context for debugging
import {
  createUnifiedBalanceKitContext,
  spend,
  KitError,
  isKitError,
  getErrorCode,
} from '@circle-fin/unified-balance-kit'

const context = createUnifiedBalanceKitContext()

try {
  await spend(context, params)
} catch (error) {
  if (isKitError(error)) {
    console.error(`Error ${error.code}: ${error.name}`, error.message)
    if (error.recoverability === 'RESUMABLE' && error.cause?.trace) {
      // Handle retry (see Retrying Failed Mints)
    }
  }
  throw error
}

Retrying Failed Mints

When the on-chain mint step fails after the transfer was committed (funds locked), the kit throws a KitError with recoverability: 'RESUMABLE' and attestation/signature in cause.trace. Use config.retry to reattempt:

try {
  const result = await spend(context, params)
  console.log('Success:', result.txHash)
} catch (error) {
  if (
    error instanceof KitError &&
    error.recoverability === 'RESUMABLE' &&
    error.cause?.trace
  ) {
    const { attestation, signature } = error.cause.trace as {
      attestation: string
      signature: string
    }
    // Retry with the attestation
    const result = await spend(context, {
      ...params,
      config: { retry: { attestation, signature } },
    })
    console.log('Retry success:', result.txHash)
  } else {
    throw error
  }
}

API Reference

Core Methods

| Method | Description | | ------------------------------------- | ------------------------------------------------------------------ | | deposit(context, params) | Deposit USDC into the caller's account on a chain | | depositFor(context, params) | Deposit USDC into another account | | spend(context, params) | Spend (mint) USDC on a destination chain by pulling from source(s) | | estimateSpend(context, params) | Get fee estimate before spending | | getBalances(context, params) | Query aggregated and per-chain balances | | getSupportedChains(context, token?) | Get chains supported by configured providers | | addDelegate(context, params) | Add a delegate for the account | | removeDelegate(context, params) | Remove a delegate | | getDelegateStatus(context, params) | Check delegate status: 'none', 'pending', or 'ready' | | initiateRemoveFund(context, params) | Initiate withdrawal from Gateway | | removeFund(context, params) | Complete withdrawal (mint on destination) |

Delegate status and finality

After calling addDelegate, the delegate may not be immediately usable for spend on chains with slow finality (e.g. Ethereum, Base, Arbitrum). getDelegateStatus returns a tri-state that reflects Gateway's finality view:

const status = await kit.getDelegateStatus({ from, delegateAddress })
if (status === 'ready') { await kit.spend(...) }
if (status === 'pending') { /* poll until 'ready' */ }
  • 'none' — not a delegate on-chain
  • 'pending' — delegate set on-chain but Gateway hasn't finalized it yet; spend will fail
  • 'ready' — finalized at Gateway; spend will succeed

Functional vs Class API

Functional API (recommended):

import {
  createUnifiedBalanceKitContext,
  deposit,
  spend,
  getBalances,
} from '@circle-fin/unified-balance-kit'

const context = createUnifiedBalanceKitContext()
await deposit(context, { from: { adapter, chain: 'Ethereum' }, amount: '100' })
const result = await spend(context, {
  amount: '50',
  from: { adapter, allocations: { amount: '50', chain: 'Ethereum' } },
  to: { adapter, chain: 'Base' },
})
const balances = await getBalances(context, { sources: { adapter } })

Class API:

import { UnifiedBalanceKit } from '@circle-fin/unified-balance-kit'

const kit = new UnifiedBalanceKit()

kit.on('gateway.deposit.succeeded', (payload) => {
  console.log('Deposit succeeded:', payload.data)
})

kit.on('gateway.spend.succeeded', (payload) => {
  console.log('Spend succeeded:', payload.data.txHash)
})

await kit.deposit({ from: { adapter, chain: 'Ethereum' }, amount: '100' })
const result = await kit.spend({
  amount: '50',
  from: { adapter, allocations: { amount: '50', chain: 'Ethereum' } },
  to: { adapter, chain: 'Base' },
})

Development

Building

# From the root of the monorepo
nx build @circle-fin/unified-balance-kit

Testing

# From the root of the monorepo
nx test @circle-fin/unified-balance-kit

Local Development

# Install dependencies
yarn install

# Build all packages
yarn build

# Build the unified-balance-kit specifically
nx build @circle-fin/unified-balance-kit

# Run tests
nx test @circle-fin/unified-balance-kit

Community & Support

License

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


Ready to build cross-chain USDC apps?

Join Discord

Built with ❤️ by Circle