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

@btx-tools/challenges-sdk

v1.2.0

Published

TypeScript SDK for BTX service challenges — chain-anchored proof-of-work admission control for APIs, agent gateways, and form submissions

Readme

@btx-tools/challenges-sdk

License npm

Put a proof-of-work checkpoint in front of any endpoint — no CAPTCHA, no login, no API keys, no third-party service.

Your server asks a caller to burn a few seconds of verifiable compute before you do something expensive or abusable. The work is a domain-bound MatMul proof defined and checked by the BTX chain — so there's no centralized issuer to trust, and a proof can't be replayed.

📖 API Reference · 🟢 Stable 1.0.0 (API frozen under SemVer). RPC + pure-JS solver cross-validated byte-equal against btxd's own pinned test vectors; opt-in retry/backoff (onRetry), per-method timeouts, AbortSignal plumbing. All audit findings closed. CHANGELOG.

What is this?

Why use it

Slowing down bots / scraping / spam usually means a CAPTCHA (annoys real users, increasingly bot-solved), accounts / API keys (signup friction + a user database), or a hosted anti-bot service (a third party, a bill, a privacy trade-off). A BTX service challenge instead makes the caller prove they spent a little real compute — cheap to verify, costly to spam at scale, anchored to a public chain, and fully self-hosted.

How it works — issue → solve → redeem

Client ── POST /expensive ─────────────────▶  Server
                                               │  no proof yet → issue a challenge
Client ◀── 402 Payment Required ───────────────┤  (challenge rides in the X-BTX-Challenge header)
   │  solve the matmul work-proof
   │  (server-side via a nearby NON-mining btxd RPC → ~1–4 s; see Performance)
   ▼
Client ── POST /expensive + proof headers ──▶  Server
                                               │  redeem: verify + consume (anti-replay)
Client ◀── 200 OK — your handler runs ─────────┘

The middleware packages run this whole handshake for you, with no server-side challenge store (the challenge echoes back in a header on retry).

Use cases

  • 🤖 Gate AI / inference APIs without a CAPTCHA or login wall
  • 🛡️ Per-tool-call proof-of-work for MCP / agent gateways (see @btx-tools/mcp-gateway)
  • 📝 Anonymous form / submission rate-limiting without accounts
  • 🚦 Replace hCaptcha / reCAPTCHA with self-hosted, chain-anchored proof — on the server side

Prerequisites: you need a BTX node

This SDK talks to a BTX full node (btxd) over JSON-RPC. It does not bundle or call any hosted service, and there is no public/shared endpoint — a project with zero BTX infrastructure can't just npm install and start gating traffic. You (or someone) must run a btxd you can reach:

  • The gate (your server) calls issue / verify / redeem against btxd — lightweight, fast RPCs.
  • Solving a challenge fast (~1–4 s) uses btxd's solvematmulservicechallenge on a node that is NOT mining — on a mining node it queues behind block work and can take 15+ minutes. A ~$5/mo VPS running btxd with gen=0 is enough.
  • Pure-JS solving (the browser-compatible Solver) needs no node, but is minutes-to-hours at production difficulty — practical only for low/calibrated difficulty or non-interactive (cron/batch) flows. See Performance.

Minimal setup from zero: run a btxd full node (sync the chain, set rpcauth, expose RPC over TLS or bind to 127.0.0.1), then point rpcUrl / rpcAuth at it. There's deliberately no centralized solver service — that would defeat the proof's attacker-vs-defender asymmetry.

Install

npm install @btx-tools/challenges-sdk
# or
pnpm add @btx-tools/challenges-sdk

Quickstart

import { BtxChallengeClient } from '@btx-tools/challenges-sdk';

const client = new BtxChallengeClient({
  rpcUrl: 'http://127.0.0.1:19332',
  rpcAuth: { user: 'rpcuser', pass: 'rpcpass' },
});

// Server: issue a challenge bound to the requested resource
const challenge = await client.issue({
  purpose: 'ai_inference_gate',
  resource: 'model:gpt-x|route:/v1/generate',
  subject: 'tenant:abc123',
  target_solve_time_s: 2,
  expires_in_s: 60,
});

// ... ship challenge to client; client solves locally and returns (nonce, digest) ...

// Server: verify-and-consume atomically (anti-replay admission)
const result = await client.redeem(challenge, nonce64_hex, digest_hex);

if (result.valid && result.reason === 'ok') {
  // Run the expensive action
}

Security

HTTPS / TLS

Basic-auth credentials are sent on every RPC call. Use HTTPS (or a localhost-only deployment) when btxd's RPC port is exposed beyond 127.0.0.1.

Recommended terminations:

  • stunnel, nginx, or Caddy in front of btxd
  • Cloudflare Tunnel for remote operator access
  • Never expose btxd's RPC port (default 19332) directly to the public internet

The SDK does NOT enforce HTTPS — that's a deployment concern. If you set rpcUrl: 'http://example.com:19332' from a production service, the SDK will happily transmit your credentials in plaintext.

Error handling

import {
  BtxError, // base class — all SDK errors extend this
  BtxRpcError, // btxd returned a JSON-RPC error envelope
  BtxHttpError, // non-2xx HTTP status
  BtxParseError, // 2xx but body wasn't valid JSON
  BtxTimeoutError, // request exceeded timeoutMs
  BtxNetworkError, // DNS/TCP/TLS-level failure
} from '@btx-tools/challenges-sdk';

try {
  await client.redeem(challenge, nonce, digest);
} catch (err) {
  if (err instanceof BtxRpcError && err.code === -8) {
    // btxd rejected the request shape
  } else if (err instanceof BtxTimeoutError) {
    // user took too long to solve
  } else if (err instanceof BtxError) {
    // any other SDK-originated error
  }
}

Error response bodies are scanned and Authorization: Basic <token> patterns are redacted before storage — safe to log.

API

BtxChallengeClient

| Method | RPC | Description | | ---------------------- | ----------------------------- | -------------------------------------------------------------- | | issue(params) | getmatmulservicechallenge | Issue a fresh challenge bound to (purpose, resource, subject). | | verify(...) | verifymatmulserviceproof | Stateless verify. Does NOT consume the challenge. | | redeem(...) | redeemmatmulserviceproof | Atomic verify + consume. Use for admission control. | | verifyBatch(entries) | verifymatmulserviceproofs | Batch (1–256) verify. No consumption. | | redeemBatch(entries) | redeemmatmulserviceproofs | Batch verify + consume, sequential. | | solve(challenge) | solvematmulservicechallenge | Server-side solver (fixtures + tests). | | call(method, params) | (any) | Low-level escape hatch. |

Solver

Four modes:

  • 'rpc' — delegates to btxd's solvematmulservicechallenge RPC. Server-side / Node only. Fast (sub-second to a few seconds) on a dedicated non-mining node — see the deployment note below.
  • 'wasm' — solves locally with the optional @btx-tools/matmul-wasm kernel: a byte-exact Rust→WASM port of the matmul PoW, ~24× faster than 'pure-js' (byte-identical proof). No node required. Throws a clear error if the package isn't installed. The published build targets browsers/bundlers (Vite, Next, Workers); in plain Node, build the package's nodejs target from source or use 'rpc'.
  • 'pure-js' — solves locally in pure TypeScript with @noble/hashes SHA-256. Browser-compatible, no optional package. Slowest at production difficulty (see the performance section).
  • 'auto' (default) — picks 'rpc' if opts.rpcClient is provided, else 'wasm' if @btx-tools/matmul-wasm is installed, else 'pure-js'.
import { BtxChallengeClient, Solver } from '@btx-tools/challenges-sdk';

// Server-side (RPC mode): delegates the solve to btxd
const client = new BtxChallengeClient({ rpcUrl: '...', rpcAuth: { ... } });
const proof = await Solver.solve(challenge, { mode: 'rpc', rpcClient: client });

// No-node, fast (WASM mode): install `@btx-tools/matmul-wasm`, ~24× pure-JS
const proof = await Solver.solve(challenge, { mode: 'wasm' });

// Browser / no-RPC (pure-JS mode): solves locally, no optional package
const proof = await Solver.solve(challenge, {
  mode: 'pure-js',
  pureJs: { maxTries: 5_000 },   // cap on attempts before giving up
});

// 'auto' (default) — rpc if a client is passed, else wasm (if installed), else pure-js
const proof = await Solver.solve(challenge, { rpcClient: client });

Algorithm correctness

The pure-JS solver is a direct port of the canonical CPU path from btxd v0.29.7 src/matmul/. We cross-validate against 5 pinned golden vectors lifted from btxd's own test suite — see tests/unit/matmul/btxd-vectors.test.ts. Match is byte-equal for:

  • fromSeedRect(zero, 8)matrix_from_seed_deterministic
  • deriveNoiseSeed(TAG_EL, zero_sigma)noise_derived_seed_pinned_EL
  • noise.generate(zero_sigma, 4, 2) E_L + E_R — noise_EL_pinned_elements / noise_ER_pinned_elements
  • canonicalMatMul(n=8, b=4) transcript_hash — canonical_matmul_n8_b4_pinned_transcript
  • Live deriveSigma (2 nonces) — verifymatmulserviceproof.proof.sigma from a real btxd

Plus 170+ internal unit tests covering field arithmetic, matrix ops, header serialization, retry/timeout/abort behavior, and solver dispatch.

⚠️ Deployment note — RPC mode against a mining btxd

btxd's service-challenge solver shares the matmul backend with block-template mining. On a node that's actively mining, solvematmulservicechallenge queues behind block work and can take 15+ minutes per call — measured 2026-05-20 on a production mining rental, where the solve RPC didn't return even after btx-cli's own 15-minute transient-error timeout fired.

For RPC mode at advertised latency (~1–4 seconds), point it at a dedicated btxd that is NOT mining (e.g., a $5/mo DO droplet with gen=0 in btx.conf). The SDK itself works fine — the bottleneck is the upstream solver service-sharing.

Performance

Pure-JS solver bench at production matmul shape (n=512, b=16, r=8) on M-series Mac arm64 (2026-05-22, 5-sample mean):

| Engine | Mean / attempt | vs Node 22 | | ------------------------ | -------------- | ------------------------------------- | | Node 22.20 / V8 | 4.6 s | 1.0× (baseline) | | Deno 2.7 / V8 | 4.2 s | 0.92× (slightly faster, within noise) | | Bun 1.3 / JavaScriptCore | 9.8 s | 2.1× slower | | Firefox SpiderMonkey | untested | — | | Safari JavaScriptCore | untested | — |

mul and the dot accumulator use bigint because the worst-case M31 product ((2^31-1)^2 ≈ 2^62) exceeds Number's 2^53 precision. The bigint-bounded inner loop is the dominant cost. Bun's JavaScriptCore engine is ~2× slower than V8 for bigint-heavy workloads — if Bun is your runtime, factor that into your target_solve_time_s calibration.

Expected end-to-end solve time depends on challenge difficulty. At btxd's lowest service-challenge difficulty (target_solve_time_s = min_solve_time_s = 0.001), per-attempt success ≈ 1.3·10⁻³, so expected ≈ 770 attempts:

| Engine | Expected solve at floor difficulty | | ------------- | ---------------------------------- | | Node 22 / V8 | ~59 min | | Deno 2.7 / V8 | ~54 min | | Bun 1.3 / JSC | ~2.1 hr |

Default difficulty is too slow for online browser use. Workable today for:

  • Server-side gating where you control difficulty (calibrate via target_solve_time_s for your target user wait)
  • Backend cron / batch jobs
  • Examples + demos with manually-issued low-difficulty challenges

The WASM kernel (@btx-tools/matmul-wasm) makes no-node solving ~24× faster — but it is still not a casual browser captcha. It's a byte-exact Rust→WASM port of the matmul hot loop (~24× over pure-JS BigInt; byte-identical proof). On an 8-worker browser pool a floor-difficulty solve is ~16 s, and difficulty calibrates to the chain's fast native solver — so a "1–4 s" challenge is multiple seconds-to-minutes in a browser at the live n=512. SIMD's 2–4× doesn't close that gap. The matmul proof is shaped for GPU-fast native mining. Use mode: 'wasm' for fast no-node solving (server/edge, CLI, high-friction gates) and mode: 'rpc' against a nearby non-mining btxd (~1–4 s) for production server-side gating. A casual sub-second browser captcha needs an upstream browser-friendly proof primitive. See USE-CASES.md.

Reproduce the bench:

npx tsx packages/core/tests/perf/solver-bench.ts 10                              # Node
deno run --allow-all --unstable-sloppy-imports tests/perf/solver-bench.ts 10     # Deno
bun tests/perf/solver-bench.ts 10                                                # Bun

Drop-in middleware

For Express apps, install the companion package:

npm install @btx-tools/middleware-express
import express from 'express';
import { BtxChallengeClient } from '@btx-tools/challenges-sdk';
import { btxAdmission } from '@btx-tools/middleware-express';

const client = new BtxChallengeClient({ rpcUrl: '...', rpcAuth: { ... } });
const app = express();

app.post(
  '/v1/generate',
  btxAdmission({
    client,
    purpose: 'ai_inference_gate',
    resource: (req) => `model:${req.body.model}|route:${req.path}`,
    subject: (req) => `tenant:${req.body.tenant_id}`,
  }),
  (req, res) => res.json({ ok: true, generated: '...' }),
);

That's it — one line, your route is gated by a BTX service challenge. Full docs at @btx-tools/middleware-express or in the package README.

Also available: @btx-tools/middleware-fastify (Fastify plugin) and @btx-tools/middleware-hono (Hono — Node + edge: Cloudflare Workers, Deno, Bun). Same btxAdmission shape across all three.

Status & roadmap

Shipped & stable at 1.0.0 — RPC client + Solver (rpc + pure-JS), pure-JS matmul port cross-validated against btxd goldens, retry/backoff + per-method timeouts + AbortSignal, Express/Fastify/Hono adapters, the @btx-tools/mcp-gateway companion, runnable examples, and a published API reference. Two deep audits closed every finding.

Post-1.0 candidates (additive, non-breaking) — Cloudflare Worker template, WordPress plugin, Python SDK, LangChain bindings — are listed in the monorepo README.

Testing

pnpm test                # all tests
pnpm test:unit           # msw-mocked HTTP only (fast)
pnpm test:integration    # live btxd via SSH (requires fleet access)

The integration test target is btx-iowa by default — change SSH_TARGET in tests/integration/smoke.test.ts to retarget any healthy at-tip BTX node.

Links

License

MIT