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.7.3

Published

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

Readme

@zerc20/client-sdk

Note: This package was previously published as zerc20-client-sdk. Versions up to 0.3.3 remain available under the old name, and 0.4.0+ are published here under @zerc20/client-sdk. Please update your dependency to @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, scanReceivingsForRecipient } 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 scanReceivingsForRecipient({
  client,
  vetKey,
  expectedRecipientAddress: account.address,
  // ...
});

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)
│   ├── relay/          # Relay node HTTP client (redeem/swap)
│   ├── 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
});

Relay Operations

Interact with a relay node for relayer-based redeem and token-to-native swap flows:

import {
  estimateRelayFee,
  fetchSwapQuote,
  submitRelaySwap,
  submitRelayTeleport,
} from "@zerc20/client-sdk";

const fee = await estimateRelayFee("https://relay.example", 1);
// fee.relayerFee: bigint
// fee.redeemFeeBps: number, quoted from the destination Verifier

const quote = await fetchSwapQuote("https://relay.example", { chainId: 1, amount: 1_000_000n });
if (quote.priceFallback) {
  console.warn("Relay is using fallback oracle prices");
}

await submitRelaySwap("https://relay.example", {
  chainId: 1,
  tokenAmount: 1_000_000n,
  minNativeAmount: 900_000n,
  maxNativeAmount: quote.nativeAmount,
  recipient: "0x...",
  owner: "0x...",
  permitDeadline: 1_700_000_000n,
  permitV: 27,
  permitR: "0x...",
  permitS: "0x...",
});

Blocklist

Check whether an address is on the OFAC sanctions blocklist before executing a private send. This prevents funds from being permanently locked when the recipient cannot redeem:

import { isBlockedAddress } from "@zerc20/client-sdk";

const blocked = await isBlockedAddress(readProvider, blocklistAddress, recipientAddress);
if (blocked) {
  throw new Error("Recipient is on the OFAC sanctions blocklist");
}

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 message retrieval, signature validation, ECDSA canonicalization, 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.

Before hashing, the signature is canonicalized (low-s normalization and v normalized to 27/28) so the same key always yields the same seed even if a wallet or hardware signer returns a valid-but-non-canonical encoding. Standard wallets (low-s, v ∈ {27,28}) are unaffected — the seed is unchanged.

To also discover funds received before canonicalization, use deriveSeedCandidates. It signs once and returns every seed the key could have produced — the canonical seed plus each pre-canonicalization (malleable-encoding) seed:

import { deriveSeedCandidates } from "@zerc20/client-sdk";

// Scan all candidate seeds during the migration window so existing funds stay visible.
const candidates = await deriveSeedCandidates(signMessage);

Deriving the candidates from a single signature (rather than re-signing and hashing raw bytes) keeps old funds discoverable even if the wallet later returns a different equivalent encoding. Receiving going forward should use deriveSeed.

Stability still depends on deterministic (RFC 6979) signing. Canonicalization only collapses signature malleability; a signer that changes its ECDSA nonce produces a different r — and therefore a different, unrecoverable seed.

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,
  prepareRedeemTransactionForRecipient,
  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 });

// Redeem fee data is read from the Verifier. `redeemableDiff` is the new
// redeemable delta before redeem / relayer fee deductions.
if (redeemContext.redeemBlockedReason === "amount_too_small") {
  throw new Error("Redeem amount is too small after redeem fee");
}
console.log({
  redeemFeeBps: redeemContext.redeemFeeBps,
  feeRecipient: redeemContext.feeRecipient,
  redeemableDiff: redeemContext.redeemableDiff,
  redeemFee: redeemContext.redeemFee,
  redeemNetValue: redeemContext.redeemNetValue,
});

// 2. Prepare the transaction (SDK picks single vs batch automatically)
const tx = await prepareRedeemTransactionForRecipient({
  redeemContext,
  burn,
  expectedRecipientAddress: account.address,
  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,
});

indexerFetchLimit is optional and defaults to 20. Receive flows accept values from 1 to MAX_INDEXER_FETCH_LIMIT (100), matching the maximum number of leaf proofs fetched in a single indexer proof request.

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

Redeem API migration note

Recipient-checked APIs are preferred for new integrations:

  • Use scanReceivingsForRecipient instead of deprecated scanReceivings.
  • Use prepareRedeemTransactionForRecipient instead of deprecated prepareRedeemTransaction.
  • Use buildBatchRedeemTransactionForRecipient instead of deprecated buildBatchRedeemTransaction.

The recipient-checked variants require expectedRecipientAddress and reject or skip decoded burn payloads whose generalRecipient.address does not match it. This prevents callers from preparing redeem calldata for an unexpected recipient while preserving the old entrypoints during the migration window.

Local teleport mode

collectRedeemContext and getAnnouncementStatus accept teleportMode: "local" for single-chain local-only redeem flows. Local mode skips Hub aggregation state reads, so hub is optional. Provide per-chain Verifier readers instead:

import { createLocalRootReader } from "@zerc20/client-sdk";

const verifierContractsByChain = new Map([
  [token.chainId, createLocalRootReader(readProvider, token.verifierAddress)],
]);

const redeemContext = await collectRedeemContext({
  burn,
  tokens: [token],
  verifierContract,
  indexerUrl,
  teleportMode: "local",
  verifierContractsByChain,
});

const tx = await prepareRedeemTransactionForRecipient({
  redeemContext,
  burn,
  expectedRecipientAddress: account.address,
  teleportMode: "local",
  teleportProofClient,
  decider, // required only when eligible.length > 1
});

When teleportMode is omitted or set to "global", hub is still required and the SDK fetches aggregation state from the Hub as before.

collectRedeemContext and getAnnouncementStatus use the Verifier as the source of truth for redeem fee data:

  • redeemFeeBps is read from redeemFeeBps()
  • redeemFee is read from quoteRedeemFee(redeemableDiff)
  • feeRecipient is read from feeRecipient() and identifies the wallet authorized to withdraw accrued protocol redeem fees
  • Accrued protocol redeem fees are tracked by redeemFeeBalance() and minted only when feeRecipient calls withdrawRedeemFees()
  • redeemableDiff is totalEligibleValue - totalTeleported, clamped to 0
  • redeemNetValue is redeemableDiff - redeemFee, clamped to 0
  • redeemBlockedReason is set to "amount_too_small" when the Verifier would reject because redeemFee >= redeemableDiff

Redeem-fee view failures are surfaced to callers instead of being treated as a zero-fee redeem. Callers should retry or surface the read failure rather than displaying a stale or fee-free quote.

Relay fee quotes also include redeemFeeBps. submitRelayTeleport sends the quoted bps to the relay; if the relay reports that the bps changed before submission, the SDK throws RedeemFeeBpsDriftError so callers can re-quote.

For accounting, prefer receipt-derived values over pre-submit quotes. submitRedeemTransaction returns redeemFeeAmount / redeemFeeGrossAmount when the provider's receipt includes logs. For relayed redeems, call readRedeemFeeAccruedFromReceipt({ readProvider, transactionHash, verifierAddress }) with the returned relay transaction hash to decode the actual RedeemFeeAccrued event from chain.

Per-chain error tolerance

collectRedeemContext (and the lower-level fetchTransferEvents / getAnnouncementStatus) accepts an optional chainErrorPolicy:

  • "fail-fast" (default, unchanged): any chain's failure rejects the whole call. Use when you need strict all-or-nothing semantics.
  • "log-and-continue": each token is queried independently in parallel. A chain that fails (e.g. transient RPC outage on a chain where the burn recipient holds no balance) is logged via console.warn with the chainId and yields an empty event list for that chain id; events from other chains are still returned. Recommended for long-running pollers (e.g. redeem bots) where one unreachable chain should not block redemption of events on healthy chains. Any value other than "fail-fast" / "log-and-continue" is rejected at call time so a typo from a JS caller cannot silently switch to data-dropping behavior.
const redeemContext = await collectRedeemContext({
  burn,
  tokens,
  hub,
  verifierContract,
  indexerUrl,
  chainErrorPolicy: "log-and-continue", // opt in
});

// Each per-chain entry carries a `failed` flag; the top-level result also
// exposes the aggregate list. Callers MUST consult these before deriving
// any terminal classification (e.g. orphan detection) from totals.
//
// `failedChainIds` is typed as optional for source-compat with downstream
// fixtures, but the SDK always populates it (defaulting to `[]`).
if ((redeemContext.failedChainIds ?? []).length > 0) {
  // One or more chains were unreachable this cycle. Empty events on those
  // chains are inconclusive — do NOT classify the announcement as orphan
  // based on `eligible=0 && pending=0`; retry on the next cycle instead.
}

Orphan-detection caveat

A failed chain is retried normally on the next call, but within the single failing call, that chain's empty events are indistinguishable from "no events" unless you check failedChainIds / chains[i].failed. If a caller drives orphan / terminal classification off totalIndexedValue === 0 together with an age threshold, a real cross-chain announcement on a transiently-unreachable chain can be mis-classified as orphan and have its cursor advanced. Callers running orphan detection alongside "log-and-continue" MUST suppress the classification when failedChainIds is non-empty (or when the relevant chains[i].failed is true). The "fail-fast" default is safe under orphan logic because it rejects the whole call when any chain fails.

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,
  buildBatchRedeemTransactionForRecipient,
  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 = buildBatchRedeemTransactionForRecipient({
  redeemContext,
  burn,
  expectedRecipientAddress: account.address,
  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

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