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

cow-wallet

v0.2.10

Published

COol Wallet — multichain wallet library (Aptos, Solana, EVM) with CCTP V1/V2 cross-chain USDC transfers

Readme

cow-wallet

Multichain wallet library for Aptos, Solana, and EVM chains. Supports native + token transfers, CCTP V1/V2 cross-chain USDC, and arbitrary contract calls — all behind a single promise-based API with keyring-isolated signing, auth-gate prompts, session reuse, and pluggable secure storage.

Built on Effect TS internally; zero Effect knowledge required for application code.

Install

pnpm add cow-wallet
# or
npm install cow-wallet

Quick start

import { createWalletClient } from "cow-wallet/client"

const wallet = createWalletClient({
  chains: [
    {
      chainId: "evm:1",
      kind: "evm",
      name: "Ethereum",
      rpcUrl: "https://eth.llamarpc.com",
      nativeAsset: {
        chain: "evm:1", type: "native", symbol: "ETH", decimals: 18,
      },
    },
  ],
})

const { mnemonic, keys } = await wallet.generate()
const portfolio = await wallet.getPortfolio()

Only chains is required; cctp, auth, and keyring get sensible defaults.

What you can do

Transfer (native + tokens, same-chain)

await wallet.transfer({
  from: { chain: "evm:1", address: srcAddr },
  to:   { chain: "evm:1", address: recipientAddr },
  asset: wallet.asset("USDC", "evm:1")!,
  amount: 10_000_000n, // 10 USDC
})

Cross-chain USDC (CCTP)

await wallet.transfer({
  from: { chain: "evm:1",    address: srcAddr },
  to:   { chain: "evm:8453", address: recipientOnBase },
  asset: wallet.asset("USDC", "evm:1")!,
  amount: 50_000_000n,
})
// burn → poll Circle attestation → mint — all automatic

If the app closes mid-transfer, resume on restart:

const pending = await wallet.listPendingTransfers()
for (const t of pending) await wallet.resumeTransfer(t.id)

Receiving USDC on Aptos (CCTP V1)

Aptos is CCTP V1-only until Circle ships V2 there (H1 2026). Burns from Ethereum/Arbitrum/Base/etc. destined for Aptos must use the V1 TokenMessenger on the source chain — set version: "v1" on that chain's CctpContractAddresses. The library ships Circle's compiled Move scripts for Aptos mainnet (handle_receive_message.mv, deposit_for_burn.mv, deposit_for_burn_with_caller.mv) so the mint-on-Aptos leg works out of the box via makeAptosAwareRegistryLive:

import {
  createWalletClient,
  makeAptosAwareRegistryLive,
  APTOS_CCTP_V1_MAINNET,   // bundled bytecode + USDC metadata
} from "cow-wallet"
import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"

const aptos = new Aptos(new AptosConfig({ network: Network.MAINNET }))
const aptosClients = new Map([["aptos", aptos]])

const wallet = await createWalletClient({
  chains: [
    { chainId: "aptos", kind: "aptos", name: "Aptos", rpcUrl: "...", cctpDomain: 9, nativeAsset: { chain: "aptos", type: "native", symbol: "APT", decimals: 8 } },
    { chainId: "evm:1", kind: "evm",   name: "Ethereum", rpcUrl: "...", cctpDomain: 0, nativeAsset: { chain: "evm:1", type: "native", symbol: "ETH", decimals: 18 } },
  ],
  cctp: {
    contractAddresses: {
      "evm:1": {
        tokenMessenger:     "0xBd3fa81B58Ba92a82136038B25aDec7066af3155",
        messageTransmitter: "0x0a992d191DEeC32aFe36203Ad87D7d289a738F81",
        usdcToken:          "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        version: "v1",    // required — V2 messages can't mint on Aptos
      },
      // Aptos entry is optional: omit it to use APTOS_CCTP_V1_MAINNET
      // automatically, or pass APTOS_CCTP_V1_TESTNET (re-exported) for
      // testnet. Supply your own entry only if you're pinning a custom
      // USDC address or re-compiled scripts.
    },
  },
}, {
  chainRegistry: makeAptosAwareRegistryLive(aptosClients),
})

await wallet.transfer({
  from: { chain: "evm:1", address: evmKey.address },
  to:   { chain: "aptos", address: aptKey.address },
  asset: wallet.asset("USDC", "evm:1")!,
  amount: 25_000_000n,
})

The mint submits a single Move script transaction that atomically chains message_transmitter::receive_messagetoken_messenger_minter::handle_receive_messagemessage_transmitter::complete_receive_message. The recipient pays APT gas by default; wire a GasStationTransactionSubmitter into aptosClient and pass the chain id to makeAptosAwareRegistryLive(aptosClients, sponsored) to sponsor it.

Arbitrary contract / program / entry-function calls

For anything beyond transfers — contract interactions, program invocations, entry functions — use sendCall / buildCall / simulateCall. They reuse the exact same signing pipeline as transfer(), so auth-gate prompts, sessions, and elevated-fee escalation all apply identically.

EVM

import { encodeFunctionData } from "viem"

const data = encodeFunctionData({
  abi: [{ type: "function", name: "approve", stateMutability: "nonpayable",
          inputs: [{ name: "s", type: "address" }, { name: "a", type: "uint256" }],
          outputs: [{ type: "bool" }] }] as const,
  functionName: "approve",
  args: [router, 10_000_000n],
})

const receipt = await wallet.sendCall({
  kind: "evm",
  chain: "evm:1",
  from: evmKey.address,
  to: usdcAddress,
  data,
  value: 0n,
  label: "Approve USDC for Router",
})

Solana

await wallet.sendCall({
  kind: "solana",
  chain: "solana",
  from: solKey.address,
  instructions: [
    {
      programId: "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr",
      keys: [{ pubkey: solKey.address, isSigner: true, isWritable: false }],
      data: new TextEncoder().encode("hello"),
    },
  ],
  label: "Post memo",
})

Aptos

await wallet.sendCall({
  kind: "aptos",
  chain: "aptos",
  from: aptKey.address,
  function: "0x1::coin::transfer",
  typeArguments: ["0x1::aptos_coin::AptosCoin"],
  functionArguments: [recipient, "1000"],
})

Three methods share the same CallRequest:

  • wallet.buildCall(req)UnsignedTx (build without signing)
  • wallet.sendCall(req)TxReceipt (build + sign + broadcast)
  • wallet.simulateCall(req){ success, returnData?, revertReason?, gasUsed?, logs?, raw? } (dry-run)

Sessions (approve once, sign many)

await wallet.approveSession("Batch operations")
await wallet.transfer(intent1)
await wallet.sendCall(callReq)
await wallet.transfer(intent2)
await wallet.endSession()

Sessions span CCTP cross-chain transfers — burn and mint are signed within one session even across minutes of attestation waiting.

Keys, balances, backup

// Multiple accounts on one chain (BIP-44 derivation)
const acct1 = await wallet.addAccount("evm:1")
const acct2 = await wallet.addAccount("evm:1")

// Import an external private key
await wallet.importPrivateKey("solana", privateKeyBytes)

// Encrypted backup
const encKey = await wallet.deriveEncryptionKey()
const bundle = await wallet.exportBackup(encKey)
// later:
const keys = await wallet.importBackup(bundle, encKey)

Error handling

All errors have a _tag discriminator:

try {
  await wallet.transfer(intent)
} catch (e) {
  if (e._tag === "InsufficientBalanceError") { /* e.required, e.available */ }
  if (e._tag === "AuthDeniedError")          { /* e.reason */ }
  if (e._tag === "CctpAttestationTimeout")   { /* resume via wallet.resumeTransfer(id) */ }
}

Full error list: KeyGenerationError, KeyNotFoundError, AuthDeniedError, AuthTimeoutError, InsufficientBalanceError, BroadcastError, FeeEstimationError, CctpAttestationTimeout, CctpMintError, UnsupportedChainError, UnsupportedRouteError, StorageError, BackupError, BackupDecryptionError, FetchError.

Production setup

The library warns on startup if you're using dangerous defaults:

  • [cow] No storage adapter provided — keys are in-memory, lost on refresh.
  • [cow] No auth gate provided — every transaction auto-approves without user confirmation.

Wire production adapters to silence these:

import {
  createWalletClient,
  makeWebAuthnAuthGate,
  makeSecureStorageAdapter,
  iCloudBackupAdapter,
} from "cow-wallet"

const wallet = createWalletClient(config, {
  authGate: makeWebAuthnAuthGate({
    rpId: "wallet.example.com",
    credentialIds: [storedCredentialId],
    userVerification: "required",
  }),
  storage: makeSecureStorageAdapter({
    save:   (key, value) => secureStore.set(key, value),
    load:   (key)        => secureStore.get(key),
    delete: (key)        => secureStore.delete(key),
    list:   (prefix)     => secureStore.keys(prefix),
  }),
  backup: iCloudBackupAdapter({ saveFile, loadFile, checkStatus }),
})

React Native / custom UI use makeCallbackAuthGate({ promptApproval, getEncryptionKey }).

Effect TS API

For callers who want full composition, streaming, or custom Layer overrides:

import { Effect } from "effect"
import { createWallet, KeyringService, TransferService, CallService } from "cow-wallet"

const layer = createWallet(config)

const program = Effect.gen(function* () {
  const keyring  = yield* KeyringService
  const transfer = yield* TransferService
  const calls    = yield* CallService

  const { keys } = yield* keyring.generate()
  yield* transfer.execute(intent)
  yield* calls.execute(callReq)
})

await Effect.runPromise(Effect.provide(program, layer))

All adapters are swappable via the Layer system, which makes testing trivial — see cow-wallet/test for helpers.

Supported chains

| Chain | Kind | Signing | CCTP | Status | |-----------|----------|--------------------|------|------------------------| | Ethereum | evm | secp256k1 / 1559 | V2 | Production | | Base | evm | secp256k1 / 1559 | V2 | Production | | Arbitrum | evm | secp256k1 / 1559 | V2 | Production | | Optimism | evm | secp256k1 / 1559 | V2 | Production | | Avalanche | evm | secp256k1 / 1559 | V2 | Production | | Solana | solana | ed25519 | V1 (scaffolded) | RPC + signing ready | | Aptos | aptos | ed25519 | V1 (burn + mint) | Requires SDK client |

Any EVM chain works — just add an entry to chains with kind: "evm" and the right chainId: "evm:<numeric-id>". Aptos requires a caller-constructed Aptos client wired in via makeAptosAwareRegistryLive. Aptos Gas Station sponsored transactions are supported via the SDK's TRANSACTION_SUBMITTER plugin.

Architecture (one-liner)

TransferService / CallService
  → RouterService → SignerService (auth gate → keyring)
    → ChainAdapter (viem / @solana/web3.js / @aptos-labs/ts-sdk)
      → BroadcastService

Private keys never leave KeyringService. All HTTP flows through an injected FetchAdapter — no direct fetch, no Node.js APIs, no Buffer. Works in browsers, React Native, and Node.

Links

License

MIT