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/swap-kit

v1.0.1

Published

SDK for seamless token swaps

Downloads

231

Readme

SwapKit

A strongly-typed TypeScript SDK for single-chain token swaps with comprehensive validation and type safety.

Table of Contents

Overview

SwapKit provides a foundation for building tree-shakeable swap operations with:

  • 🔒 Type Safety: Full TypeScript support with strict type checking
  • Runtime Validation: Comprehensive Zod schemas for parameter validation
  • 📝 Rich Documentation: Complete JSDoc with runnable examples
  • 🎯 Developer Experience: IntelliSense support and clear error messages
  • 🔧 Flexible Configuration: Customizable fee policies and swap strategies

Installation

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

Quick Start

SwapKit offers two usage patterns - choose the one that fits your project best:

  • Chain selection is constrained to swap-supported networks. Supported chains include mainnet networks with CCTP v2 and at least one supported token (USDC, USDT, EURC, or other registered tokens). Current examples: Ethereum, Base, Polygon, Arbitrum, Optimism, Avalanche, Monad, Solana, and others. Use getSupportedChains() to get the complete, up-to-date list.

Class-Based Usage

import { SwapKit } from '@circle-fin/swap-kit'
import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'

// Create a SwapKit instance
const kit = new SwapKit()

// Create an adapter
const adapter = createViemAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY,
})

// Define swap parameters
const params = {
  from: { adapter, chain: 'Ethereum' },
  tokenIn: 'USDC',
  tokenOut: 'USDT',
  amountIn: '100.50',
  config: {
    slippageBps: 300, // 3% slippage tolerance
    allowanceStrategy: 'permit',
  },
}

// Get an estimate
const estimate = await kit.estimate(params)
console.log(
  `Estimated output: ${estimate.estimatedOutput?.amount} ${estimate.estimatedOutput?.token}`,
)

// Execute the swap (implementation pending)
// const result = await kit.swap(params)

Functional Usage (Tree-Shakeable)

import { createSwapKitContext, estimate, swap } from '@circle-fin/swap-kit'
import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'

// Create a swap context
const context = createSwapKitContext()

// Create an adapter
const adapter = createViemAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY,
})

// Define swap parameters
const params = {
  from: { adapter, chain: 'Ethereum' },
  tokenIn: 'USDC',
  tokenOut: 'USDT',
  amountIn: '100.50',
  config: {
    slippageBps: 300,
    allowanceStrategy: 'permit',
  },
}

// Get an estimate
const quoteResult = await estimate(context, params)
console.log(
  `Estimated output: ${quoteResult.estimatedOutput?.amount} ${quoteResult.estimatedOutput?.token}`,
)

// Execute the swap (implementation pending)
// const result = await swap(context, params)

Both patterns provide identical functionality - the class-based approach offers a familiar class-based interface, while the functional approach allows for better tree-shaking and smaller bundle sizes.

Configuration

Custom Fees

SwapKit supports two approaches for charging custom developer fees.

1. Percentage-Based Fees

For straightforward percentage-based fees:

await kit.swap({
  from: { adapter, chain: Ethereum },
  tokenIn: 'USDC',
  tokenOut: 'DAI',
  amountIn: '100',
  config: {
    customFee: {
      percentageBps: 1000, // 10% fee (100 = 1%)
      recipientAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
    },
  },
})

2. Callback-Based Fees

For complex fee logic:

Class-Based:

const kit = new SwapKit({
  customFeePolicy: {
    computeFee: async (ctx) => {
      // Full context with adapter, chain, tokens, amounts
      if (ctx.type === 'output') {
        // Output fee scenario
        // Access to minAmount and estimatedAmount
        const user = await getUser(ctx.from.address)
        if (user.isVIP) {
          return (parseFloat(ctx.minAmount) * 0.05).toString()
        }
        return (parseFloat(ctx.estimatedAmount) * 0.1).toString()
      }

      // Input fee scenario
      return (parseFloat(ctx.amountIn) * 0.05).toString()
    },

    resolveFeeRecipientAddress: (chain, ctx) => {
      return chain.type === 'solana'
        ? 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'
        : '0x1234567890123456789012345678901234567890'
    },
  },
})

// Or set/update it later
kit.setCustomFeePolicy({
  computeFee: (ctx) => {
    if (ctx.from.chain.name === 'Ethereum') {
      return (parseFloat(ctx.amountIn) * 0.01).toString()
    }
    return (parseFloat(ctx.amountIn) * 0.005).toString()
  },
  resolveFeeRecipientAddress: (chain) => '0x...',
})

// Remove the fee policy when needed
kit.removeCustomFeePolicy()

Functional:

import {
  createSwapKitContext,
  setCustomFeePolicy,
  removeCustomFeePolicy,
} from '@circle-fin/swap-kit'

const context = createSwapKitContext({
  customFeePolicy: {
    computeFee: async (ctx) => {
      if (ctx.type === 'output') {
        return (parseFloat(ctx.estimatedAmount) * 0.1).toString()
      }
      return (parseFloat(ctx.amountIn) * 0.05).toString()
    },
    resolveFeeRecipientAddress: (chain, ctx) => {
      return chain.type === 'solana' ? 'Sol...' : '0x...'
    },
  },
})

// Or set/update later
setCustomFeePolicy(context, {
  computeFee: () => '0.05',
  resolveFeeRecipientAddress: (chain) => '0x...',
})

// Remove when needed
removeCustomFeePolicy(context)

Custom Providers (typed)

import { createSwapKitContext } from '@circle-fin/swap-kit'

class ExperimentalSwapProvider {
  readonly name = 'ExperimentalSwapProvider'
  // implements SwappingProvider methods (supportsRoute, estimate, swap)
}

const extraProviders = [new ExperimentalSwapProvider()] as const
const context = createSwapKitContext({ providers: extraProviders })

// TypeScript knows the exact provider types:
type RegisteredProviders = typeof context.providers
//    ^? readonly [...DefaultProviders, ExperimentalSwapProvider]

The context merges default providers with any custom providers you supply while preserving their literal types. This keeps context.providers strongly typed and ready for future provider integrations.

Swap Configuration Options

const params = {
  from: { adapter, chain: Ethereum },
  tokenIn: 'USDC',
  tokenOut: 'USDT',
  amountIn: '100',
  config: {
    // Allowance strategy (default: 'permit')
    allowanceStrategy: 'permit', // or 'approve'

    // Maximum slippage in basis points (default: 300 = 3%)
    slippageBps: 300,

    // Minimum output amount (stop-limit)
    stopLimit: '95000000', // 95 USDT in smallest units

    // Custom fee for this specific swap (percentage approach)
    customFee: {
      percentageBps: 100, // 1% fee
      recipientAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
    },
  },
}

Note: Use transaction-level percentageBps for simple fees, or kit-level setCustomFeePolicy() for complex logic. They are mutually exclusive.

Supported Tokens

SwapKit supports 16 tokens for fee collection. At least one token (input OR output) must be from this list.

Stablecoins (6 decimals):

  • USDC - USD Coin
  • EURC - Euro Coin
  • USDT - Tether USD
  • PYUSD - PayPal USD

Stablecoins (18 decimals):

  • DAI - MakerDAO stablecoin
  • USDE - Ethena USD

Wrapped Tokens:

  • WBTC - Wrapped Bitcoin (8 decimals)
  • WETH - Wrapped Ethereum (18 decimals)
  • WSOL - Wrapped Solana (9 decimals)
  • WAVAX - Wrapped Avalanche (18 decimals)
  • WPOL - Wrapped Polygon (18 decimals)

Native Token:

  • NATIVE - Resolves to chain's native gas token (ETH on Ethereum, SOL on Solana, etc.)

Important: At least one token (input OR output) must be from the supported list. Non-supported → Non-supported swaps are not allowed.

Examples:

// Swap between 6-decimal stablecoins
await kit.swap({
  from: { adapter, chain: 'Ethereum' },
  tokenIn: 'EURC',
  tokenOut: 'USDC',
  amountIn: '100.50',
})

// Swap DAI (18 decimals) to USDC
await kit.swap({
  from: { adapter, chain: 'Ethereum' },
  tokenIn: 'DAI',
  tokenOut: 'USDC',
  amountIn: '500.0', // Automatically handles 18-decimal precision
})

// Swap native gas token to stablecoin (generic)
await kit.swap({
  from: { adapter, chain: 'Ethereum' },
  tokenIn: 'NATIVE', // ETH on Ethereum, POL on Polygon, SOL on Solana, etc.
  tokenOut: 'USDC',
  amountIn: '1.5',
})

// Swap wrapped ETH to stablecoin on Polygon
await kit.swap({
  from: { adapter, chain: 'Polygon' },
  tokenIn: 'WETH', // Wrapped ETH on Polygon
  tokenOut: 'USDC',
  amountIn: '0.5',
})

Note: Use NATIVE for the chain's native gas token. Chain-specific symbols like ETH, POL are not supported as swap token aliases — use NATIVE or contract addresses instead.

Type Definitions

SwapParams

interface SwapParams {
  from: AdapterContext // Source adapter and chain
  tokenIn: SupportedToken // Input token
  tokenOut: SupportedToken // Output token
  amountIn: string // Decimal string (e.g., '10.5')
  config?: SwapConfig // Optional configuration
}

// SupportedToken can be:
// - Stablecoin aliases (6 decimals): 'USDC' | 'EURC' | 'USDT' | 'PYUSD'
// - Stablecoin aliases (18 decimals): 'DAI' | 'USDE'
// - Wrapped tokens: 'WBTC' (8 dec) | 'WETH' | 'WSOL' (9 dec) | 'WAVAX' | 'WPOL' (18 dec)
// - Native token: 'NATIVE' (chain's native gas token)
type SupportedToken =
  | 'USDC'
  | 'EURC'
  | 'USDT'
  | 'USDE'
  | 'DAI'
  | 'PYUSD'
  | 'WBTC'
  | 'WETH'
  | 'WSOL'
  | 'WAVAX'
  | 'WPOL'
  | 'NATIVE'

SwapEstimate

The estimate result includes input context fields for correlating estimates with inputs:

interface SwapEstimate {
  // Input context (populated from your params)
  tokenIn: SupportedToken // e.g., 'NATIVE'
  tokenOut: SupportedToken // e.g., 'USDC'
  amountIn: string // e.g., '0.00001'
  chain: string // e.g., 'Ethereum'
  fromAddress: string // e.g., '0x2971...5EE9f'
  toAddress: string // e.g., '0x2971...5EE9f'

  // Estimate details
  transaction: object // Raw transaction to sign and submit
  stopLimit: string // Minimum output in base units
  estimatedOutput?: string // Human-readable expected output
  fees?: ServiceSwapFee[] // Fee breakdown
}

SwapResult

The swap result uses a simplified chain field:

interface SwapResult {
  tokenIn: SupportedToken
  tokenOut: SupportedToken
  chain: string // e.g., 'Ethereum'
  amountIn: string // Human-readable amount
  fromAddress: string
  toAddress: string
  txHash: string
  explorerUrl?: string
  fees?: ServiceSwapFee[]
  config?: SwapResultConfig
}

Resolving Chain Name to ChainDefinition

Both SwapEstimate and SwapResult return a simplified chain string (e.g., 'Ethereum') If you need the full chain definition, use the getChainByEnum utility:

import { getChainByEnum } from '@circle-fin/swap-kit'

// After getting a swap result or estimate
const result = await kit.swap(params)

// Resolve the chain name back to full ChainDefinition
const chainDef = getChainByEnum(result.chain)
console.log(chainDef.chainId) // 1 (for Ethereum)
console.log(chainDef.rpcEndpoints) // ['https://...']

CustomFeePolicy

interface CustomFeePolicy {
  computeFee: (params: SwapParams) => Promise<string> | string
  resolveFeeRecipientAddress: (
    chain: ChainDefinition,
    params: SwapParams,
  ) => Promise<string> | string
}

Validation

SwapKit provides comprehensive runtime validation:

import { assertSwapParams, swapParamsSchema } from '@circle-fin/swap-kit'

try {
  assertSwapParams(params, swapParamsSchema)
  // Parameters are valid
} catch (error) {
  console.error('Validation failed:', error.message)
}

Usage Patterns: Class vs Functional

SwapKit supports both class-based and functional programming styles. Choose based on your preferences:

Use Class-Based (SwapKit) When:

  • ✅ You prefer traditional class-based patterns
  • ✅ Your codebase already uses class-based libraries
  • ✅ You want a familiar API similar to other SDKs
  • ✅ You don't need aggressive tree-shaking optimizations

Use Functional (createSwapKitContext + operations) When:

  • ✅ You prefer functional programming patterns
  • ✅ You need maximum tree-shaking for smaller bundles
  • ✅ You want to import only specific operations
  • ✅ You're building a performance-critical application

Both approaches:

  • ✅ Provide identical functionality
  • ✅ Use the same underlying operations
  • ✅ Offer full TypeScript support
  • ✅ Include comprehensive validation

Example: Choosing an approach

// Class-based: Great for general use
import { SwapKit } from '@circle-fin/swap-kit'
const kit = new SwapKit()
await kit.swap(params)

// Functional: Great for tree-shaking
import { createSwapKitContext, swap } from '@circle-fin/swap-kit'
const context = createSwapKitContext()
await swap(context, params)

License

Apache-2.0

Support

For issues and questions, please visit the GitHub repository.