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

@xochi/sdk

v0.2.0

Published

TypeScript SDK for generating and verifying [Xochi ZKP](https://github.com/xochi-fi/erc-xochi-zkp) compliance proofs. Produce EVM-compatible zero-knowledge proofs client-side using Noir circuits and Barretenberg UltraHonk.

Readme

@xochi/sdk

TypeScript SDK for generating and verifying Xochi ZKP compliance proofs. Produce EVM-compatible zero-knowledge proofs client-side using Noir circuits and Barretenberg UltraHonk.

Also provides trust tier system, privacy level modeling, attestation scoring, settlement splitting (XIP-1), and execution planning (XIP-2).

Install

npm install @xochi/sdk

Latest published on npm: 0.1.1. Current source: 0.2.0 (unpublished -- adds the F-1..F-9 audit fixes, signed-variant proofs, the @xochi/sdk/provider signing module, and additional typed contract errors). Peer dependency: viem@^2.0.0 (required for Oracle/Verifier/SettlementRegistry clients).

Quick start

import { XochiProver } from "@xochi/sdk";
import { BundledCircuitLoader } from "@xochi/sdk/node";

const prover = new XochiProver(new BundledCircuitLoader());

// Generate a compliance proof (EU jurisdiction, single provider)
const result = await prover.proveCompliance({
  score: 25,
  jurisdictionId: 0, // EU
  providerSetHash: "0x14b6becf...",
  submitter: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
  timestamp: String(Math.floor(Date.now() / 1000)),
});

// result.proofHex and result.publicInputsHex are ready for on-chain submission
const valid = await prover.verify("compliance", result.proof, result.publicInputs);

await prover.destroy();

Proof types

| Type | Method | Use case | | ----------------- | ------------------------- | ------------------------------------------------------- | | Compliance | proveCompliance() | Risk score below jurisdiction threshold | | Risk Score | proveRiskScore() | Custom threshold (GT/LT) or range proofs | | Pattern | provePattern() | Anti-structuring, velocity, round amounts | | Attestation | proveAttestation() | KYC/credential verification | | Membership | proveMembership() | Merkle inclusion (whitelist) | | Non-Membership | proveNonMembership() | Sorted Merkle adjacency (sanctions exclusion) | | Compliance Signed | proveComplianceSigned() | Compliance with provider-signed signals (anti-grinding) | | Risk Score Signed | proveRiskScoreSigned() | Risk score claim with provider-signed signals |

Multi-provider support

Both compliance and risk score accept multiple screening providers:

const result = await prover.proveCompliance({
  signals: [25, 30, 20], // risk scores from 3 providers (0-100)
  weights: [50, 30, 20], // importance weights
  providerIds: ["1", "2", "3"],
  jurisdictionId: 0,
  providerSetHash: "0x...",
  submitter: account.address, // binds proof to this address (anti-frontrun)
});

Single-provider shorthand:

const result = await prover.proveCompliance({
  score: 25,
  jurisdictionId: 0,
  providerSetHash: "0x...",
  submitter: account.address,
});

Trust tiers

Five tiers with fee rates and MEV rebates:

import { getTierFromScore, getFeeRate, getMevRebate } from "@xochi/sdk";

getTierFromScore(60); // { name: "Verified", min: 50, max: 74, rate: 0.2 }
getFeeRate(60); // 0.2  (0.20%)
getMevRebate(60); // 0.2  (20%)

| Tier | Score | Fee | MEV Rebate | | ------------- | ----- | ----- | ---------- | | Standard | 0-24 | 0.30% | 10% | | Trusted | 25-49 | 0.25% | 15% | | Verified | 50-74 | 0.20% | 20% | | Premium | 75-99 | 0.15% | 25% | | Institutional | 100+ | 0.10% | 30% |

Privacy levels

Six levels gated by trust score:

import { getMaxPrivacyLevel, isPrivacyLevelAllowed } from "@xochi/sdk";

getMaxPrivacyLevel(60); // "private"
isPrivacyLevelAllowed("sovereign", 60); // false (needs 75+)
isPrivacyLevelAllowed("stealth", 60); // true

| Level | Min Score | Settlement | | ------------------------ | --------- | ---------- | | open / public / standard | 0 | Public L1 | | stealth | 25 | ERC-5564 | | private | 50 | Aztec L2 | | sovereign | 75 | Aztec L2 |

Attestation scoring

Calculate trust scores from attestations with diminishing returns:

import { calculateScoreFromAttestations } from "@xochi/sdk";

const result = calculateScoreFromAttestations([
  { category: "humanity", points: 20 },
  { category: "identity", points: 30 },
  { category: "reputation", points: 8 },
  { category: "compliance", points: 25 },
]);
// { total: 83, byCategory: { humanity: 20, identity: 30, reputation: 8, compliance: 25 } }

Within each category, the 1st provider contributes at 100%, 2nd at 25%, 3rd+ at 10%. Category caps: humanity (25), identity (35), reputation (20), compliance (40).

Tier proofs

Prove "score >= threshold" without revealing exact score:

import { generateTierProof, verifyTierProof } from "@xochi/sdk";
import { BundledCircuitLoader } from "@xochi/sdk/node";

const loader = new BundledCircuitLoader();
const proof = await generateTierProof(loader, 60, 25, account.address);

const result = await verifyTierProof(loader, proof);
// { valid: true, threshold: 25, tierName: "Trusted", feeRate: 0.25 }

generateHighestTierProof picks the best tier automatically:

import { generateHighestTierProof } from "@xochi/sdk";

const highest = await generateHighestTierProof(loader, 60, account.address);
// Proves score >= 50 (Verified tier)

Provider-signed proofs

The signed-variant proofs (COMPLIANCE_SIGNED, RISK_SCORE_SIGNED) cryptographically anchor the screening signals to a registered provider's secp256k1 signature, closing audit finding I-1 (signal honesty). The Oracle authenticates the signer via its on-chain _validSignerPubkeyHashes registry -- only proofs whose signer_pubkey_hash public input was previously registered are accepted.

Direct (server-side) via signSignals

import { Barretenberg } from "@aztec/bb.js";
import { XochiProver } from "@xochi/sdk";
import { BundledCircuitLoader } from "@xochi/sdk/node";
import { RawKeyLoader, loadSignerKey, signSignals } from "@xochi/sdk/provider";

const api = await Barretenberg.new();
const signerKey = await loadSignerKey(new RawKeyLoader(privateKeyBytes, "provider-1"));

// 1. Provider signs the screening bundle. chainId + oracleAddress (audit F-6)
//    are committed in the in-circuit Pedersen digest the signature is over.
const signed = await signSignals(api, signerKey, {
  chainId: 1n, // EVM chain ID of the consuming Oracle
  oracleAddress: BigInt("0x..."), // address of the consuming Oracle (uint160 Field)
  providerSetHash: BigInt("0x..."),
  signals: [25n, 0n, 0n, 0n, 0n, 0n, 0n, 0n], // length 8, zero-pad inactive
  weights: [100n, 0n, 0n, 0n, 0n, 0n, 0n, 0n],
  timestamp: BigInt(Math.floor(Date.now() / 1000)),
  submitter: BigInt(account.address),
});

// 2. Generate the signed-variant proof. chainId + oracleAddress MUST match
//    what was signed -- the in-circuit ECDSA verify recomputes the digest.
const prover = new XochiProver(new BundledCircuitLoader());
const result = await prover.proveComplianceSigned({
  score: 25,
  jurisdictionId: 0,
  providerSetHash: "0x...",
  submitter: account.address,
  timestamp: String(Math.floor(Date.now() / 1000)),
  chainId: 1n,
  oracleAddress: "0x...",
  signedBundle: signed,
});

Via the signing daemon

The repo also ships a daemon (daemon/src/server.ts) that holds the signing key and exposes POST /sign. Useful when the signing key shouldn't live in the proof-generating process:

const res = await fetch(`${daemonUrl}/sign`, {
  method: "POST",
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
  body: JSON.stringify({
    chainId: 1,
    oracleAddress: "0x...",
    providerSetHash: "0x...",
    signals: [25, 0, 0, 0, 0, 0, 0, 0],
    weights: [100, 0, 0, 0, 0, 0, 0, 0],
    timestamp: Math.floor(Date.now() / 1000),
    submitter: account.address,
  }),
});
// 200: { signature, pubkeyX, pubkeyY, signerPubkeyHash, payloadHash } as 0x-hex
// 409: replay detected (MemoryReplayDb / persistent backing store)
// 400: validation error

GET /pubkey-hash returns the daemon's signerPubkeyHash for one-time on-chain registration via oracle.registerSignerPubkeyHash(...). The daemon enforces replay protection per request.

Binding (audit F-6): the chainId + oracleAddress you pass to the signer and the prover MUST be the values you submit against. The on-chain Oracle asserts they match block.chainid and address(this); mismatches revert with PublicInputMismatch. A mismatch between signer-side and prover-side fails witness generation with invalid provider signature on signals.

On-chain submission

XochiOracle submits proofs and queries attestations:

import { XochiOracle, PROOF_TYPES } from "@xochi/sdk";
import { createPublicClient, createWalletClient, http } from "viem";
import { mainnet } from "viem/chains";

const oracle = new XochiOracle(
  "0x...", // oracle contract address
  createPublicClient({ chain: mainnet, transport: http() }),
  createWalletClient({ chain: mainnet, transport: http(), account }),
  mainnet,
);

// Submit a single proof (timestamp in publicInputs must be within 1 hour of block.timestamp)
const txHash = await oracle.submitCompliance({
  jurisdictionId: 0,
  proofType: PROOF_TYPES.COMPLIANCE,
  proof: result.proofHex,
  publicInputs: result.publicInputsHex,
  providerSetHash: "0x...",
});

// Check compliance status
const { valid, attestation } = await oracle.checkCompliance("0x...", 0);
// attestation: { subject, jurisdictionId, proofType, meetsThreshold, timestamp,
//   expiresAt, proofHash, providerSetHash, publicInputsHash, verifierUsed }

// Filter by proof type (e.g., require an attestation backed by a PATTERN proof)
const patternStatus = await oracle.checkComplianceByType("0x...", 0, PROOF_TYPES.PATTERN);

// Retrieve historical proofs
const history = await oracle.getAttestationHistory("0x...", 0);
const proof = await oracle.getHistoricalProof(history[0]);

Batch submission

Submit all proofs from a proveBatch or provePlan result atomically in a single transaction via the on-chain submitComplianceBatch. Reverts atomically if any sub-trade fails. Max 100 proofs per batch (MAX_BATCH_SIZE).

const batchResult = await oracle.submitBatch({
  batch, // from proveBatch() or provePlan()
  jurisdictionId: 0,
  proofType: PROOF_TYPES.COMPLIANCE,
  providerSetHash: "0x...",
});

// batchResult.submissions[i].proofHash -> pass to SettlementRegistryClient

On-chain verification

XochiVerifier verifies proofs directly against the on-chain verifier contracts:

import { XochiVerifier, PROOF_TYPES } from "@xochi/sdk";

const verifier = new XochiVerifier("0x...", publicClient);

// Single proof
const valid = await verifier.verifyProof(PROOF_TYPES.COMPLIANCE, proofHex, publicInputsHex);

// Batch (atomic all-or-nothing)
const batchValid = await verifier.verifyProofBatch(
  [PROOF_TYPES.COMPLIANCE, PROOF_TYPES.MEMBERSHIP],
  [proof1Hex, proof2Hex],
  [pi1Hex, pi2Hex],
);

// Historical verification at a specific verifier version
const historicalValid = await verifier.verifyProofAtVersion(
  PROOF_TYPES.COMPLIANCE,
  1n,
  proofHex,
  publicInputsHex,
);

// Emergency revocation (owner-only, requires WalletClient)
const adminVerifier = new XochiVerifier("0x...", publicClient, walletClient, mainnet);
const revoked = await adminVerifier.isVersionRevoked(PROOF_TYPES.COMPLIANCE, 1n);
await adminVerifier.revokeVerifierVersion(PROOF_TYPES.COMPLIANCE, 1n);

Lightweight oracle client

For environments without viem (Cloudflare Workers, edge functions):

import { OracleLite, PROOF_TYPES } from "@xochi/sdk";

const oracle = new OracleLite({
  address: "0x...",
  rpcUrl: "https://rpc.example.com",
});

const status = await oracle.checkCompliance("0x...", 0);

const result = await oracle.verifyProof(
  "0x...", // wallet (used as msg.sender in simulation)
  PROOF_TYPES.RISK_SCORE,
  proofHex,
  publicInputsHex,
);

Settlement splitting (XIP-1)

Split large trades into sub-trades, generate compliance proofs for each, submit them, and settle on-chain. The full pipeline:

import {
  planSplit,
  proveBatch,
  planExecution,
  provePlan,
  SettlementRegistryClient,
  PROOF_TYPES,
} from "@xochi/sdk";
import { BundledCircuitLoader } from "@xochi/sdk/node";

const loader = new BundledCircuitLoader();
const prover = new XochiProver(loader);

// 1. Plan the split
const splitPlan = planSplit(500n * 10n ** 18n, 0, account.address, {
  splitThreshold: 100n * 10n ** 18n, // split above 100 ETH
  maxSubTrades: 10,
  minSubTradeSize: 1n * 10n ** 18n,
});
// splitPlan.subTrades: [{ index: 0, amount: 100e18 }, ..., { index: 4, amount: 100e18 }]

// 2. Generate compliance proofs for all sub-trades
const batch = await proveBatch(prover, splitPlan, {
  score: 25,
  jurisdictionId: 0,
  providerSetHash: "0x...",
  submitter: account.address,
});

// 3. Submit all proofs to oracle
const batchResult = await oracle.submitBatch({
  batch,
  jurisdictionId: 0,
  proofType: PROOF_TYPES.COMPLIANCE,
  providerSetHash: "0x...",
});

// 4. Register trade and record sub-settlements
const registry = new SettlementRegistryClient(registryAddr, publicClient, walletClient, chain);
await registry.registerTrade(splitPlan.tradeId, 0, splitPlan.subTrades.length);

for (const sub of batchResult.submissions) {
  await registry.recordSubSettlement(splitPlan.tradeId, sub.index, sub.proofHash);
}

// 5. Finalize with a pattern proof (anti-structuring)
await registry.finalizeTrade(splitPlan.tradeId, patternProofHash);

Execution planning (XIP-2)

planExecution composes split planning, venue routing, and diffusion scheduling into a single call:

import { planExecution, provePlan } from "@xochi/sdk";

const plan = planExecution(
  500n * 10n ** 18n, // total amount
  0, // jurisdiction (EU)
  account.address,
  { trustScore: 60, gasEstimates: { public: 65_000n, stealth: 150_000n, shielded: 400_000n } },
  { diffusionWindow: 300 }, // spread submissions over 5 minutes
);

// plan.subTrades includes venue assignment and target timestamps
// plan.subTrades[i].venue: "public" | "stealth" | "shielded"
// plan.subTrades[i].targetTimestamp: seconds relative to T0

// Generate proofs for the execution plan
const batch = await provePlan(prover, plan, complianceInput);

Venue assignment respects trust score thresholds: public (0+), stealth (25+), shielded (50+). The diffusion scheduler enforces a minimum 12-second gap between consecutive submissions.

Circuit loaders

Three loaders for different environments:

// Node.js: bundled circuit artifacts
import { BundledCircuitLoader } from "@xochi/sdk/node";

// Node.js: load from erc-xochi-zkp repo path (development)
import { NodeCircuitLoader } from "@xochi/sdk/node";
const loader = new NodeCircuitLoader("/path/to/erc-xochi-zkp");

// Browser: load via fetch
import { BrowserCircuitLoader } from "@xochi/sdk/browser";
const loader = new BrowserCircuitLoader("https://cdn.example.com/circuits");

Input builders

If you need to construct circuit inputs manually (outside of XochiProver):

import {
  buildComplianceInputs,
  buildRiskScoreInputs,
  buildPatternInputs,
  buildAttestationInputs,
  buildMembershipInputs,
  buildNonMembershipInputs,
  buildComplianceSignedInputs,
  buildRiskScoreSignedInputs,
} from "@xochi/sdk";

Each builder validates constraints (signal range, weight bounds, timestamp limits, Merkle depth) and throws before you waste time on an invalid proof.

Submitter binding: All 8 circuits include submitter as a public input. The Oracle contract enforces submitter == msg.sender for every proof type, so the SDK no longer post-processes publicInputsHex -- pass the submitter address to the input builder and the prover handles the rest.

Signed-variant binding (audit F-6): buildComplianceSignedInputs and buildRiskScoreSignedInputs additionally require chainId and oracleAddress. These MUST equal the values the provider used when signing -- they're committed in the in-circuit Pedersen digest the ECDSA signature is checked against. The on-chain Oracle asserts they also match block.chainid and address(this), so a single provider signature cannot mint attestations on multiple Oracle instances or chains.

Proof type mappings

import {
  PROOF_TYPES,
  proofTypeToCircuit,
  circuitToProofType,
  PUBLIC_INPUT_COUNTS,
} from "@xochi/sdk";

proofTypeToCircuit(0x01); // "compliance"
circuitToProofType("risk_score"); // 0x02
PUBLIC_INPUT_COUNTS[0x01]; // 6 -- compliance: 6, risk_score: 8, pattern: 6, attestation: 6,
//      membership: 5, non_membership: 5,
//      compliance_signed: 9, risk_score_signed: 11
// (signed variants include signer_pubkey_hash + chain_id + oracle_address)

Typed contract errors

Solidity reverts from XochiZKPOracle, XochiZKPVerifier, and SettlementRegistry are decoded into named JS error classes so you can branch on them in try/catch instead of regex-matching messages.

import {
  SubmitterMismatchError,
  ProofAlreadyUsedError,
  BatchTooLargeError,
  VersionRevokedError,
  XochiContractError,
} from "@xochi/sdk";

try {
  await oracle.submitCompliance(params);
} catch (err) {
  if (err instanceof SubmitterMismatchError) {
    // proof was bound to a different address -- regenerate with the right submitter
  } else if (err instanceof ProofAlreadyUsedError) {
    console.log(`Replay rejected, proof already used: ${err.proofHash}`);
  } else if (err instanceof XochiContractError) {
    // any other decoded contract revert -- err.errorName + err.args available
    console.error(`Contract reverted with ${err.errorName}`, err.args);
  } else {
    throw err; // network error, gas estimation failure, etc.
  }
}

Available error classes: SubmitterMismatchError, ProofAlreadyUsedError, ProofTimestampStaleError, TimeWindowTooSmallError, EmptyBatchError, BatchTooLargeError, BatchLengthMismatchError, VersionRevokedError, TimelockNotElapsedError, TradeAlreadyExistsError, TradeNotFoundError, AttestationNotFoundError, SignedSignalsRequiredError, InvalidSignerPubkeyHashError. Any other Solidity custom error decodes to a base XochiContractError with errorName + args populated.

For lower-level use, decodeContractError(err, abi) returns the typed error or null, and withDecodedErrors(abi, fn) wraps any async call.

Development

npm install
npm test                 # unit tests only (219 tests; integration excluded via vitest.config.ts)
npm run test:integration # proof generation + anvil tests (50 tests, ~30s; uses vitest.integration.config.ts)
npm run typecheck        # tsc --noEmit
npm run format           # prettier --write
npm run format:check     # prettier --check (run in CI / prepublishOnly)
npm run build            # compile to dist/

# Sync circuit artifacts from erc-xochi-zkp
./scripts/sync-circuits.sh ../erc-xochi-zkp

Integration tests deploy the full contract stack (XochiZKPVerifier, XochiZKPOracle, SettlementRegistry) on a local anvil node. Requires foundry and a local clone of erc-xochi-zkp with compiled artifacts.

Related

  • erc-xochi-zkp -- On-chain contracts and Noir circuit source
  • XIPs -- Protocol improvement proposals (XIP-1: settlement splitting, XIP-2: adaptive settlement)
  • xochi -- Protocol frontend

License

MIT