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

zerc20-client-sdk

v0.2.1

Published

Client SDK for interacting with ZERC20 rollup services from browser or node environments.

Downloads

462

Readme

zerc20-client-sdk

Client SDK for interacting with zERC20 privacy-preserving token system from browser or Node.js environments.

Overview

zERC20 is a privacy-preserving, cross-chain ERC-20 token system that enables:

  • Private transfers using zero-knowledge proofs (Nova and Groth16 circuits)
  • Stealth transactions via a messaging layer on the Internet Computer (ICP)
  • Cross-chain functionality using LayerZero as the interoperability protocol

This SDK provides TypeScript bindings for all client-side operations.

Installation

npm install zerc20-client-sdk

Requirements

  • Node.js >= 18
  • Browser with WebAssembly support

Compatibility Notes

  • This package is ESM-only ("type": "module"). CommonJS (require) is not supported.
  • HTTP-based modules (e.g. Decider client, LayerZero Scan) rely on fetch.
    • Node.js 18+ includes fetch globally.
    • In custom runtimes, provide a compatible fetch implementation where needed.

Provider Abstraction (Library/Framework Agnostic)

The SDK public API is designed to be wallet/provider agnostic:

  • Read paths accept EvmReadProvider
  • Write paths accept EvmWriteProvider
  • UI frameworks are not part of the SDK surface

This lets integrators use viem, ethers, web3.js, or custom adapters without depending on React/Vue internals.

import type { EvmReadProvider, EvmWriteProvider } from "zerc20-client-sdk";

Quick Start

import { createSdk, preparePrivateSend, scanReceivings } from "zerc20-client-sdk";

// Initialize SDK
const sdk = createSdk({
  // configuration options
});

// Create a stealth client for ICP communication
const client = sdk.createStealthClient({
  agent,
  storageCanisterId,
  keyManagerCanisterId,
});

// Prepare a private send
const prepared = await preparePrivateSend({
  client,
  seed,
  recipientChainId,
  recipientAddress,
  // ...
});

// Scan for received payments
const announcements = await scanReceivings({
  client,
  vetKey,
  // ...
});

Architecture

src/
├── ic/           # Internet Computer (ICP) integration
│   ├── client.ts       # StealthCanisterClient for canister communication
│   ├── idl.ts          # Candid IDL factory definitions
│   ├── encryption.ts   # IBE + AES-GCM encryption
│   ├── authorization.ts # EIP-191 signing & VetKD authorization
│   └── keys.ts         # View key derivation
├── operations/   # High-level protocol operations
│   ├── privateSend.ts  # Private payment flow
│   ├── receive/        # Receive/scan announcements
│   ├── invoice.ts      # Invoice creation
│   ├── teleport.ts     # Single teleport proof
│   ├── teleportProof.ts # Batch teleport proof (Nova + Decider)
│   ├── liquidityManager/ # Wrap/unwrap with slippage protection
│   └── layerzeroScan/   # LayerZero message tracking & decoding
├── wasm/         # WASM runtime & bindings
│   ├── index.ts        # WasmRuntime class
│   ├── loader.ts       # WASM bindings loader (browser/Node.js)
│   └── serialization.ts # Type conversion utilities
├── zkp/          # Zero-knowledge proof orchestration
├── decider/      # Decider proof generation (HTTP client)
├── chain/        # Chain metadata (names, explorers, aliases)
├── registry/     # Token & hub configuration
├── onchain/      # Contract interaction (ABI decoding, token reads)
└── utils/        # Shared utilities

Key Modules

WASM Configuration

Configure the WASM runtime for cryptographic operations:

import { configureWasmLocator } from "zerc20-client-sdk";

// Point to your hosted WASM binary
configureWasmLocator({ url: "/zerc20_wasm_bg.wasm" });

StealthCanisterClient

Communicates with ICP canisters for announcements and invoices:

import { StealthCanisterClient } from "zerc20-client-sdk";

const client = new StealthCanisterClient(agent, storageCanisterId, keyManagerCanisterId);

// Submit an announcement
await client.submitAnnouncement(announcementInput);

// List announcements
const page = await client.listAnnouncements(startAfter, limit);

Liquidity Manager

Wrap and unwrap tokens with slippage protection:

import {
  unwrapWithLiquidityManager,
  buildCrossUnwrapQuote,
  applySlippage,
} from "zerc20-client-sdk";

// Local unwrap with slippage protection
await unwrapWithLiquidityManager({
  writeProvider,
  readProvider,
  liquidityManagerAddress,
  zerc20TokenAddress,
  amount: 1000n,
  minAmountOut: 950n, // Reject if output < 950
});

// Cross-chain unwrap with slippage tolerance (basis points)
const quote = await buildCrossUnwrapQuote({
  sourceToken,
  destinationToken,
  amount: 1000n,
  account,
  readProviderSource: sourceReadProvider,
  readProviderDestination: destReadProvider,
  slippageBps: 50, // 0.5% slippage tolerance
});

Adaptor Withdraw (Stuck Fund Recovery)

When a cross-chain unwrap fails (e.g. due to Stargate liquidity shortage), user funds may remain in the destination chain's Adaptor contract. The SDK provides functions to detect and recover these stuck funds:

import {
  fetchAdaptorBalances,
  hasStuckFunds,
  withdrawFromAdaptor,
  NATIVE_TOKEN_ADDRESS,
} from "zerc20-client-sdk";

// Check if a user has stuck funds in an adaptor
const stuck = await hasStuckFunds({
  provider: readProvider,
  account: "0xUser...",
  adaptorAddress: "0xAdaptor...",
});

// Fetch detailed balances
const balances = await fetchAdaptorBalances({
  provider: readProvider,
  account: "0xUser...",
  adaptorAddress: "0xAdaptor...",
});
// balances.underlyingTokenBalance, balances.zerc20Balance, balances.nativeBalance

// Withdraw stuck funds
const result = await withdrawFromAdaptor({
  writeProvider,
  adaptorAddress: "0xAdaptor...",
  token: balances.underlyingTokenAddress, // or zerc20TokenAddress, or NATIVE_TOKEN_ADDRESS
  amount: balances.underlyingTokenBalance,
});

LayerZero Scan

Track and decode LayerZero cross-chain messages. Configuration and providers are injected as parameters, making this module framework-agnostic:

import {
  fetchWalletStatus,
  type FetchWalletStatusParams,
  type LayerZeroScanConfig,
} from "zerc20-client-sdk";

const scanConfig: LayerZeroScanConfig = {
  baseUrl: "https://scan.layerzero-api.com/v1/",
  apiKey: "your-api-key", // optional
};

const result = await fetchWalletStatus({
  address: "0xuser",
  tokens,
  scanConfig,
  createReadProvider: (token) => createPublicClient({ chain: ... }),
  limit: 10,
  filterByToken: true,
});

// result.items: LayerZeroMessageSummary[]
// result.walletUrl: string (link to LZ Scan)
// result.nextToken: string | undefined (pagination cursor)

Notes:

  • fetchWalletStatus can use transaction input (getTransaction) and receipt logs (getTransactionReceipt) when available.
  • If those methods are unavailable, decoding still falls back to payload-based paths where possible.

Seed Derivation

Derive a seed from a wallet signature. The SDK handles the message retrieval, signature validation, and keccak256 hashing internally, while accepting a wallet-agnostic sign function:

import { deriveSeed } from "zerc20-client-sdk";

// Works with any wallet library (wagmi, ethers.js, web3.js, etc.)
const seed = await deriveSeed(async (message) => {
  // Return the signature as a 0x-prefixed hex string (65 bytes for personal_sign)
  return wallet.signMessage(message);
});

The function validates that the returned signature is a valid hex string of exactly 65 bytes before hashing. Invalid signatures throw descriptive errors to prevent silent seed misderivation.

Chain Metadata

Look up chain display names, short labels, block explorer URLs, and resolve chain name aliases — all from a single source of truth shared across products:

import {
  getChainMetadata,
  getChainDisplayName,
  getExplorerTxUrl,
  resolveChainId,
  resolveNetworkDisplayName,
} from "zerc20-client-sdk";

getChainDisplayName(42161);                 // "Arbitrum"
getChainDisplayName(999999);                // "Chain 999999"

getExplorerTxUrl(42161, "0xabc...");        // "https://arbiscan.io/tx/0xabc..."

resolveChainId("arb-sepolia");              // 421614
resolveNetworkDisplayName("arb-sepolia");   // "Arbitrum Sepolia"

Token Loading with RPC Overrides

Load and normalize token configuration with runtime RPC URL overrides (e.g. environment-specific Alchemy/Infura endpoints):

import { normalizeTokensWithOverrides } from "zerc20-client-sdk";
import tokensJson from "./tokens.json";

const tokens = normalizeTokensWithOverrides(
  tokensJson as TokensFile, // snake_case JSON is accepted as-is
  {
    tokens: {
      "arb-mainnet": ["https://arb-mainnet.g.alchemy.com/v2/KEY"],
      "base-mainnet": ["https://base-mainnet.g.alchemy.com/v2/KEY"],
    },
    hub: ["https://base-mainnet.g.alchemy.com/v2/KEY"],
  },
);

Without overrides, normalizeTokensWithOverrides(file) behaves identically to normalizeTokens(file).

Redeem Flow

Prepare a redeem transaction from a collected redeem context. The SDK handles single vs batch proof selection, GeneralRecipient construction, and contract call parameter assembly internally:

import {
  collectRedeemContext,
  prepareRedeemTransaction,
  createTeleportProofClient,
  HttpDeciderClient,
} from "zerc20-client-sdk";

// Initialize proof client and decider
const teleportProofClient = createTeleportProofClient();
const decider = new HttpDeciderClient("https://decider.example.com");

// 1. Collect redeem context (eligible events, proofs, etc.)
const redeemContext = await collectRedeemContext({ burn, tokens, hub, verifierContract, indexerUrl });

// 2. Prepare the transaction (SDK picks single vs batch automatically)
const tx = await prepareRedeemTransaction({
  redeemContext,
  burn,
  teleportProofClient,
  decider, // required only when eligible.length > 1
});

// 3. Submit via your wallet library
const hash = await walletClient.writeContract({
  address: tx.address as `0x${string}`,
  abi: tx.abi,
  functionName: tx.functionName,
  args: tx.args,
  account,
  chain,
});

The returned RedeemTransaction is pure data ({ address, abi, functionName, args, mode }) with no wallet dependency.

Manual batch flow with UI progress

For batch redeems, callers can use the 2-step proof API to insert UI updates between Nova and Decider steps:

import {
  collectRedeemContext,
  buildBatchRedeemTransaction,
  createTeleportProofClient,
  HttpDeciderClient,
} from "zerc20-client-sdk";

const teleportProofClient = createTeleportProofClient();
const decider = new HttpDeciderClient("https://decider.example.com");
const redeemContext = await collectRedeemContext({ burn, tokens, hub, verifierContract, indexerUrl });

// Step 1: Nova proof
const novaResult = await teleportProofClient.createNovaProof({
  aggregationState: redeemContext.aggregationState,
  recipientFr: burn.generalRecipient.fr,
  secretHex: burn.secret,
  events: redeemContext.events.eligible,
  proofs: redeemContext.globalProofs,
});

updateUI("Requesting decider proof…"); // ← insert UI update here

// Step 2: Decider proof
const deciderProof = await teleportProofClient.requestDeciderProof(decider, novaResult.ivcProof);

// Step 3: Build transaction
const tx = buildBatchRedeemTransaction({ redeemContext, burn, deciderProof });

LayerZero Decode Utilities

Decode LayerZero OFT transaction data — send() calldata, OFTSent event logs, and BridgeRequest compose messages:

import {
  decodeSendPayload,
  extractOftSentAmount,
  decodeBridgeRequest,
} from "zerc20-client-sdk";

// Decode send() transaction input
const payload = decodeSendPayload(txData);
// → { dstEid, to, amountLD, minAmountLD, composeMsg }

// Extract amountReceivedLD from OFTSent event logs
const amount = extractOftSentAmount(receipt.logs);

// Decode a BridgeRequest compose message (returns null on failure)
const bridgeReq = decodeBridgeRequest(payload.composeMsg);
// → { dstEid, to, refundAddress, minAmountOut } | null

Proof Generation

import {
  HttpDeciderClient,
  createTeleportProofClient,
} from "zerc20-client-sdk";

const proofClient = createTeleportProofClient();

// Single teleport proof (Groth16)
const singleProof = await proofClient.createSingleTeleportProof(/* ... */);

// Batch teleport proof (Nova + Decider) — two-step API
const novaResult = await proofClient.createNovaProof({
  aggregationState,
  recipientFr,
  secretHex,
  events,
  proofs,
});

// Callers can update UI between steps (e.g. progress indicators)

const decider = new HttpDeciderClient("https://decider.example.com");
const deciderProof = await proofClient.requestDeciderProof(decider, novaResult.ivcProof);

Development

Build

npm run build

Test

npm test

Type Check

npm run typecheck

Tests

npm test

Current (as of 2026-03-05): 943 passed, 2 skipped Note: This number will drift over time; treat npm test output as the source of truth.

Skipped Tests

  1. src/zkp/__tests__/runNovaProver.test.ts

    • Skipped when artifact files are missing
    • Artifacts are expected under public/artifacts/:
      • withdraw_local_groth16_pk.bin
      • withdraw_local_groth16_vk.bin
      • withdraw_global_groth16_pk.bin
      • withdraw_global_groth16_vk.bin
      • withdraw_local_nova_pp.bin
      • withdraw_local_nova_vp.bin
      • withdraw_global_nova_pp.bin
      • withdraw_global_nova_vp.bin
  2. src/ic/__tests__/storage_localnet.test.ts

    • Skipped by design
    • Requires running local ICP replica (dfx start)

API Stability and Refactoring Policy

zerc20-client-sdk is published and used by downstream applications (e.g. zerc20-frontend). As a rule, prefer non-breaking refactors.

  • Prefer importing from the package root (import { ... } from "zerc20-client-sdk"). Subpath imports are considered internal and may change.
  • Avoid changing the public surface (notably src/index.ts exports and re-exports) unless necessary.
  • Avoid breaking changes to the public surface (notably src/index.ts exports).
  • When a rename/restructure is desired, keep the old entrypoint and mark it @deprecated, and introduce the new API in parallel.
  • Deprecation guideline:
    • Keep deprecated entrypoints for at least one minor release (or a clearly communicated timeframe) before removal.
    • Removal (or behavioral changes) should only happen in a major version bump, with a migration note.
  • If a breaking change is unavoidable:
    • Provide a migration note (old → new) in the release/PR description.
    • Bump the version appropriately (SemVer).
    • Validate against downstream usage (at minimum run TypeScript typecheck/build in zerc20-frontend).

Downstream Verification (Recommended)

Before merging changes that touch public exports/types, validate against the downstream app:

# In zerc20-frontend
npm run typecheck
npm run build

Dependencies

  • @dfinity/* - Internet Computer SDK
  • viem - Ethereum library
  • @noble/curves - Cryptographic primitives
  • poseidon-lite - Poseidon hash function
  • pako - Compression (gzip)

License

MIT