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

@bsvkey/x402-bsv-client

v0.7.1

Published

Pay an x402 HTTP 402 in BSV — the missing bsv-p2pkh payment builder for x402 agents. Also verifies BSVKey inference usage receipts and per-call x402 delivered receipts.

Readme

@bsvkey/x402-bsv-client

npm version license: MIT node

Pay an x402 402 Payment Required in BSV, in one line.

Live gateway: inference.bsvkey.com/#x402 · discovery at /v1/x402

x402 ships payment builders for EVM (EIP-3009) and Solana. This is the missing one for the bsv-p2pkh scheme — so an AI agent can pay a BSV-settled x402 endpoint (like inference.bsvkey.com) with true sub-cent, per-call micropayments.

npm i @bsvkey/x402-bsv-client @bsv/sdk

Use it

import { fetchWithX402, readSettlement } from '@bsvkey/x402-bsv-client';

const res = await fetchWithX402(
  'https://inference.bsvkey.com/v1/x402/chat/completions',
  {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ model: 'grok-4.3', messages: [{ role: 'user', content: 'Say hi in 3 words' }] }),
  },
  { wif: process.env.BSV_WIF } // a funded BSV private key (WIF)
);

const data = await res.json();
console.log(data.choices[0].message.content);        // the answer
console.log(readSettlement(res));                     // { success, transaction (on-chain txid), network, ... }

That's the whole x402 handshake, handled for you:

  1. POST the request → server replies 402 with the quoted price.
  2. This lib builds + signs a BSV tx paying the quoted amount to the server's payTo.
  3. Retries with the X-PAYMENT header → server settles on-chain and returns 200 + the answer.

Lower level

import { buildX402Payment } from '@bsvkey/x402-bsv-client';

// Given an x402 402 response body, produce the base64 X-PAYMENT header value:
const xPayment = await buildX402Payment(body402, wif);

Notes

  • Key format: wif accepts either a WIF (starts K/L/5) or a 12/24-word recovery phrase (derived on the BSV path m/44'/236'/0'/0/0, the same one inference.bsvkey.com uses) — so a wallet generated on the site works here directly.
  • Funding: the key's address must hold enough BSV to cover the quoted amount + a small fee. Fund it like any BSV address (the payTo/QR flow on inference.bsvkey.com works).
  • Back-to-back calls are safe: the client chains each payment off its own change, so a loop of paid calls won't double-spend while the mempool catches up.
  • Non-custodial: your key never leaves the process; the signed transaction pays the server's address directly.
  • Networks: bsv (mainnet) and bsv-testnet. Testnet lets you dry-run for free.

Verify usage receipts

If you use the BSVKey inference broker over a prepaid channel, every settled call returns a signed usageReceipt. This package verifies them offline, so you can audit the broker's meter without trusting its word and without a round-trip.

import { verifyReceiptChain } from '@bsvkey/x402-bsv-client/usage-receipt';

// Pin the broker key once (GET https://inference.bsvkey.com/v1/receipt-key).
const { receiptPubKey } = await (await fetch('https://inference.bsvkey.com/v1/receipt-key')).json();

// `receipts` = the usageReceipt from each call on your channel.
const audit = await verifyReceiptChain(receipts, {
  expectedSigner: receiptPubKey,   // REQUIRED (0.7.0+): missing refuses with 'unpinned'
  channelId: myChannelId,
  fundedSats: myChannelFundedSats,
});
// { ok: true, count, cumSats, cumTokens }  — or { ok: false, reason, seq }

verifyReceiptChain checks, across the whole chain, that: each signature recovers to the pinned broker key, every receipt is for your channel, the sequence has no gap or replay, the running totals reconcile (cumSats/cumTokens), spending never exceeds the funded amount, and each charge recomputes from the published formula (you can be charged less, never more). An empty chain is refused (reason: 'empty_chain') — it proves nothing, so a client that lost its receipts fails the check rather than passing vacuously.

Two things worth pinning down as an unattended client:

  • fundedSats is yours to supply. The receipt carries a fundedSats, but that is the broker's own signed assertion, not proof of the funding transaction. For "spending never exceeds what the channel actually paid" to mean the on-chain amount, pass opts.fundedSats set to the amount you funded. When you do, a receipt that claims more funding than you supplied is rejected (funded_mismatch), so the broker can't widen the cap by signing a larger number, and cumSats is bounded by each receipt's own fundedSats. Top-ups are supported: fundedSats may rise across the chain (a drop is rejected as funded_decreased), so after you top a channel up, pass your new current total and the whole chain — the receipts before and after the top-up — verifies in one call. Omit opts.fundedSats and the check falls back to the broker-asserted number.
  • Pass opts.channelId. The "my channel and no other" check only runs when you pass the channel you funded; without it a receipt for a different channel is not rejected.
  • The receipt is the ledger; the balance endpoint is a cache. The broker's GET /v1/channels/:id can lag the signed receipt by ~20s. Trust the receipt's cumSats/balanceSatsAfter; treat the endpoint as eventually-consistent.

Single-receipt helpers: verifyReceipt(receipt) → { ok, signer }, verifyCharge(receipt), and verifyMeter(receipt, { messages, completion }) (or { system, prompt, completion } on the raw /v1/infer path — see below).

Verify the meter (the token count itself)

The v2 receipt binds the exact bytes and is metered by a pinned, deterministic tokenizer, so you recompute the token count from what you sent and received.

On the OpenAI-compatible /v1/chat/completions path, pass the same messages array you sent — that endpoint meters the flattened messages (system joined with \n; every other turn rendered as User: … / Assistant: …, joined with \n), so verifyMeter needs the messages to reproduce that transform for you:

import { verifyMeter } from '@bsvkey/x402-bsv-client/usage-receipt';
const m = verifyMeter(receipt, { messages, completion }); // { ok } / { ok:false, reason }

On the broker-native /v1/infer path (raw fields), pass { system, prompt, completion } instead. (messagesToPrompt(messages) is exported if you want the flattened { system, prompt } yourself.)

What this proves: the broker signed these exact numbers (non-repudiable), the token count is the published function of the exact bytes you exchanged, the charge is the published function of those tokens, none were double-counted, and the totals stay within what you funded. What it does not prove: that bsvkey-meter/1 equals a model provider's internal token count (it is BSVKey's own published unit). Spec: https://inference.bsvkey.com/usage-receipts.md

Hidden tokens (usage-receipt/3, 0.6.0+)

Thinking models (Claude Sonnet 5, Opus 5, Opus 5.5, Fable 5.1) reason before they answer, and the provider bills that reasoning as output tokens you never see. A call with such tokens gets a bsvkey.usage-receipt/3: the v2 fields plus hiddenInputTokens, hiddenOutputTokens, hiddenSource (provider or estimate) and maxOutputTokens. Every other call stays v2, unchanged.

The same verifiers handle both. For v3, verifyCharge bills the hidden tokens at the receipt's own rates and refuses hidden_output_exceeds_allowance if the hidden output is larger than the room your own max_tokens left; verifyMeter still checks the visible tokens against your bytes. The hidden counts are the provider's usage as relayed by the broker (provider), or the broker's published estimate when the provider reported none (estimate). Versions before 0.6.0 reject v3 with bad_schema.

Verify per-call x402 receipts

The prepaid-channel path returns a usageReceipt (above). The per-call paths (/v1/x402/chat/completions, /v1/x402/base/chat/completions) return a per-call delivered receipt instead: schema bsvkey.x402-receipt/1, no channel or balances, binding one response to one on-chain settlement. Its verifier is a separate schema, so the channel verifier rejects it with unknown_field:rail. Accept a receipt with verifyX402ReceiptFull (per call) or verifyReceiptChain (channel). Both refuse unless you pass expectedSigner. verifyAnyReceipt only checks integrity and hands back the signer: it is not an acceptance check on its own, because a receipt re-signed by any key passes it.

import { readSettlement, verifyX402ReceiptFull } from '@bsvkey/x402-bsv-client';

const res = await fetchWithX402(url, init, { wif });
const receipt = (await res.clone().json()).receipt;   // the delivered receipt
const paid = readSettlement(res);                     // { txid, payer, ... } YOU paid

const { receiptPubKey } = await (await fetch('https://inference.bsvkey.com/v1/receipt-key')).json();
const v = await verifyX402ReceiptFull(receipt, {
  expectedSigner: receiptPubKey,       // REQUIRED: the pinned broker key; missing refuses
  settlementRef: paid.txid,            // REQUIRED — the tx you paid, from your context
  payer: paid.payer,                   // optional — the address you paid from
  messages, completion,                // the response you hold
}); // { ok:true, signer } / { ok:false, reason }

Bind it to your own payment. A receipt is a bearer object: recomputing its id, recovering its signer, and matching the delivered bytes are all true for whoever holds it. It is yours only when you check its settlementRef against the txid you actually paid — which is why settlementRef is required and an unbound check refuses rather than passing. Pass it from readSettlement(res), never read it back out of the receipt. The same goes for the signer: it is yours to pin, so expectedSigner is also required and a missing pin refuses (unpinned) instead of accepting whatever key signed it. Lower-level pieces if you want them (none of these is an acceptance check alone): verifyAnyReceipt (integrity + returns the signer, for either schema), verifyX402Receipt (authorship), bindX402Receipt(receipt, { settlementRef, payTo, amountAtomic, payer }) (the binding), verifyX402Delivery(receipt, { messages, completion }) (the bytes).

What this proves: the broker signed that this exact response was delivered for that settlement, and — once bound — for your settlement. What it does not prove: that the compute was correct.

MIT.