Skip to content

feat(bsv): add exact scheme support for BSV - #2890

Open
sirdeggen wants to merge 26 commits into
x402-foundation:mainfrom
bsv-blockchain:feat/bsv-exact-scheme
Open

sirdeggen wants to merge 26 commits into
x402-foundation:mainfrom
bsv-blockchain:feat/bsv-exact-scheme

Conversation

@sirdeggen

@sirdeggen sirdeggen commented Jul 17, 2026

Copy link
Copy Markdown

Summary

Adds BSV (Bitcoin SV) as an x402 payment mechanism for the exact scheme: a new @x402/bsv package (client / server / facilitator), a per-chain spec, example wiring, and a USD price feed. It builds on the BRC-121 "Simple 402 Payments" flow (BRC-29 payments over BRC-42 key derivation) already used in production BSV tooling.

Per CONTRIBUTING's new-chain process this PR is the spec + TypeScript reference implementation; Go/Python ports would follow once this is accepted. The CAIP-2 bsv namespace is registered (ChainAgnostic/namespaces#190, merged 2026-08-18; spec: bsv/caip2.md).

How BRC-121 maps onto x402

  • payTo = the recipient wallet's BRC-100 identity public key. It never appears on chain — every payment pays a fresh key derived from it (BRC-42), so on-chain observers cannot link payments to the payee.
  • Payload = a BRC-29 remittance: { transaction (base64 BEEF), derivationPrefix, derivationSuffix, senderIdentityKey, outputIndex }. The suffix decodes to a Unix-ms timestamp (BRC-121 freshness).
  • The client's BRC-100 wallet funds, fees, and signs the transaction via createAction — the payer covers the miner fee, so there is no facilitator gas sponsorship.
  • The facilitator is the recipient's own wallet. Because BRC-42 destinations are ECDH-derived, a third party cannot take custody and cannot verify the destination unilaterally. verify checks structure, wallet-chain agreement, freshness, exact amount, and the derived destination; settle internalizes the output via internalizeAction, backed by a txid dedup cache plus the wallet's isMerge signal for replay defense. (Either counterparty can voluntarily prove a payment's linkage via revealSpecificKeyLinkage — BRC-69, verifiable through BRC-94's Schnorr ZKP — see the spec's auditability appendix and note 2 below.)
  • Networks: bsv:mainnet / bsv:testnet / bsv:ttn / bsv:tstn.

What's included

@x402/bsv (typescript/packages/mechanisms/bsv), modeled on the Concordium mechanism:

  • exact/client — derives the per-payment key and builds the funded BEEF payment
  • exact/facilitator — verifies and settles into the recipient wallet
  • exact/server — builds PaymentRequirements with satoshi price parsing (8 decimals)
  • createWhatsOnChainMoneyParser — converts USD prices ("$0.001") to satoshis via the WhatsOnChain exchange-rate API (60 s cache, single-flight refresh, bounded stale fallback)
  • constants / types, README, package + tsup + tsconfig + vitest config, changeset, npm publish workflow

Spec: specs/schemes/exact/scheme_exact_bsv.md (full exact-on-BSV spec with a Key Linkage Revelation & Auditability appendix), plus BSV safety invariants added to the shared scheme_exact.md appendix.

Examples: all three all_networks examples wired —

  • client: BSV_WALLET=true + a running BRC-100 wallet (e.g. BSV Desktop)
  • facilitator: BSV_SERVER_PRIVATE_KEY + BSV_WALLET_STORAGE_URL via @bsv/simple ServerWallet
  • server: BSV_IDENTITY_KEY, priced in USD through the money parser

Security

Key facilitator invariants (also in the spec's Critical Validation Requirements):

  • Destination correctnessverify derives the expected BRC-42 key (getPublicKey forSelf) and matches it against the P2PKH output, so an unfundable or mispaid BEEF is rejected before any resource work runs.
  • Subject-tx integrity — the subject transaction is resolved via the Atomic BEEF atomicTxid, i.e. exactly the transaction the wallet will internalize (not "the last tx in the BEEF").
  • Exact amount — strict equality with requirements.amount (stricter than plain BRC-121, which tolerates overpayment).
  • Freshness — ±30 s at verify; at settle the window extends by maxTimeoutSeconds so slow handlers don't void a valid payment.
  • Replay — facilitator-side txid dedup cache plus the wallet's isMerge (a wallet-toolbox extension, not core BRC-100); verify re-runs at settlement.
  • Chain agreement — the facilitator checks getNetwork() against the requested network; transaction is empty on any failure per the core v2 schema.

Testing

  • 103 unit tests (pnpm test in the package) — payload construction, verify/settle rules, Atomic BEEF subject resolution, replay paths, timestamp windows, and the rate-feed parser (cache / stale / error paths).
  • Live smoke — money parser against the real WhatsOnChain API ($0.001 → 7,440 sats at $13.44/BSV).
  • Live wallet round tripBSV_INTEGRATION=true pnpm test:integration with a BRC-100 wallet running (create → verify → settle → replay-reject); requires a funded wallet.

Notes for reviewers

  1. Network identifiers / namespace. bsv:mainnet / bsv:testnet / bsv:ttn / bsv:tstn use human-readable network names (following the casper CAIP-2 precedent). The bsv namespace has been registered (ChainAgnostic/namespaces#190, merged 2026-08-18; spec: bsv/caip2.md). bip122 could alternatively identify BSV by its fork-block hash (as it does for Bitcoin Cash); happy to switch the network IDs if that's preferred.
  2. Facilitator trust model. The recipient-wallet facilitator is faithful to BRC-29 but differs from the third-party-facilitator model on other chains. Extension paths, if a shared/third-party facilitator is wanted: a plain-P2PKH addressing variant (drops the privacy model), or a key-linkage-proof flow (BRC-69/BRC-94) that gives destination verification without custody — also the auditability route for regulated assets such as BSV-issued stablecoins.
  3. Strict amount equality for exact diverges from BRC-121's overpay tolerance; documented in the spec.
  4. Scope. No core packages are modified. Reference implementation is TypeScript only; Go/Python would be follow-up PRs.

AI-usage disclosure (per CONTRIBUTING): the implementation, tests, spec, and this description were drafted with AI assistance (Claude Code) and reviewed by the author before submission; an AI-driven adversarial review pass produced 24 fixes now folded into the branch.

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

@sirdeggen is attempting to deploy a commit to the Coinbase Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added specs Spec changes or additions typescript sdk Changes to core v2 packages examples Changes to examples ci labels Jul 17, 2026
Add new @x402/bsv mechanism package implementing the exact payment scheme for the BSV blockchain, including client, server, and facilitator scheme implementations with unit and integration tests.

- Add scheme_exact_bsv.md spec and reference it from scheme_exact.md
- Wire BSV into advanced client, server, and facilitator all_networks examples
- Add scoped npm publish workflow and changeset for the new package

Signed-off-by: Deggen <d.kellenschwiler@bsvassociation.org>
@sirdeggen
sirdeggen force-pushed the feat/bsv-exact-scheme branch from e96ec4c to ecb9235 Compare July 17, 2026 16:46
@BSVanon

BSVanon commented Jul 18, 2026

Copy link
Copy Markdown

On (1) — network identifiers

Agree with names over bip122 — fork-block-hash is genuinely ambiguous given the shared genesis, and the BSV app layer (wallets, overlays, ARC, SPV clients) identifies networks by name anyway. It also lines up with the CAIP-2 namespace being registered in parallel (#190), which uses the same bsv:mainnet / bsv:testnet strings — worth keeping those two deliberately in lockstep.

One wrinkle worth pinning while it's cheap: bsv:testnet is getting ambiguous. Alongside classic testnet there's STN (the Scaling Test Network) and now Teratestnet (the Teranode test network) — and a per-request, high-throughput payments protocol is exactly what people will want to exercise against Teranode. Not suggesting you enumerate them all now, just: is bsv:testnet meant as "the one canonical test target," or should the syntax extend to something like bsv:stn / a teranode testnet later without overloading testnet? Probably worth deciding at the same time as the CAIP-2 registration, so x402 and CAIP-2 don't diverge on what "testnet" means. Happy to raise it on #190 too.

On (3) — strict amount equality

Keep rule 6 exact. From designing a BSV merchant gateway on WooCommerce, I'd argue the case for equality in a machine protocol is stronger than just "defensible."

The core reason isn't tolerance bands — it's that a checkout has out-of-band recovery a 402 exchange doesn't. In my gateway an overpayment becomes "contact the customer to arrange a refund," and an underpayment becomes "waiting for the remaining amount," which only resolves because a reused address lets the buyer top up or the storeowner steps in. A machine-to-machine 402 has neither channel: the payer builds one funding tx to a unique derived output and that's the whole conversation. Tolerating a mismatch there doesn't degrade gracefully, it produces a state with no automated resolution. Equality means the only outcomes are "paid" and "retry" — which is what you want when there's no human in the loop.

Where my own code does tolerate a shortfall, it's context-specific rather than a global policy: a background poller watching a reused address slackens by max(1000 sats, 0.1%) because it's answering "did value show up here?" across dust and top-ups, while the structured PaymentACK receiver rejects any underpayment outright. Different jobs, different rules — which is exactly why baking one tolerance value into a shared spec is the thing to avoid.

The drift one would otherwise use a tolerance band to absorb — price/FX movement between quote and pay — is better handled the way this scheme already does it: pin the amount at challenge issuance, bound it with the freshness window, and fold any buffer into the quoted price. It's a quote-validity lever, not an accepted-amount lever. Strict equality + the 30s / maxTimeoutSeconds window is the cleaner composition.

On conformance / test vectors

A note for when the Go/Python ports arrive. The pieces that already have test vectors are BRC-42 key derivation (the sample vectors in the BRC-42 spec) and the BRC-100 wire format (vectors live with that project). What has none yet is the part this PR newly adds: the exact-amount check at outputIndex, the millisecond timestamp in derivationSuffix plus the freshness-window edges, and the replay signals (txid dedup and the isMerge / no-new-satoshis case). Those are where two independent implementations are most likely to disagree, because there's nothing to test against yet.

I've spent a good amount of time working in a Rust port of the BRC-100 wallet-toolbox, so the replay/internalize path in particular is familiar. Happy to help put together a small known-answer fixture for just those new rules as a follow-up, once there's a second implementation to check against.

@sirdeggen

Copy link
Copy Markdown
Author

Wrinkle on network is fair. Perhaps we just register them all now.

mainnet
testnet
ttn
tstn

I think no one is using stn since Teranode. We also have mockchain in some software. Which is modern regtest with spv, but neither are needed for this which would only make sense if there is some network being used.

So perhaps just adding two additional networks would suffice.

Add security and correctness hardening to the BSV exact scheme:

- Enforce BSV-only asset: reject non-BSV PaymentRequirements.asset at verify
- Require the BRC-29 derivation prefix to base64-decode to at least 8 bytes
- Add optional spvOnVerify callback to SPV-check the BEEF at verify time, closing an unfunded-BEEF DoS surface (settle stays authoritative)
- Size the duplicate-settlement dedup TTL to the full settlement window (paymentWindow + maxTimeoutSeconds + margin) with a fixed floor, so a replay cannot outlive re-verification
- Roll back the dedup mark on soft wallet rejection so a valid retry is not falsely flagged as a duplicate
- Validate explicit satoshi amounts (positive integer within supply) at parse time on the server scheme

Docs: note single-network scheme registration, dedup persistence/sticky-session requirements, and float-conversion trade-off. Add unit tests covering all new paths.
Signed-off-by: Deggen <d.kellenschwiler@bsvassociation.org>
@andyrowe

Copy link
Copy Markdown

Wrinkle on network is fair. Perhaps we just register them all now.

mainnet testnet ttn tstn

I think no one is using stn since Teranode. We also have mockchain in some software. Which is modern regtest with spv, but neither are needed for this which would only make sense if there is some network being used.

So perhaps just adding two additional networks would suffice.

Registering them explicitly now seems right — naming a network later is much cheaper than migrating clients that already guessed.

One implementer data point, since we're running a plain-P2PKH exact mode on mainnet today: we currently emit
bip122:000000000019d6689c085ae165831e93 , and we treat that as a bug rather than a choice. It can't distinguish BSV from BTC, and the failure it enables is unrecoverable — a client that guesses wrong pays the right amount on the wrong chain. So whatever set gets registered, I'd argue the spec should require clients to refuse to spend on an ambiguous Bitcoin-family identifier rather than fall back to a default. We'll move to bsv:mainnet the moment #190 lands.

Agree on skipping mockchain/regtest — there's nothing to pay on.

Align BSV network identifiers with ChainAgnostic namespaces#190:
bsv:mainnet, bsv:testnet, bsv:ttn, bsv:tstn. Map CAIP-2 ids to BRC-100
wallet network names, and require clients/facilitators to refuse
ambiguous Bitcoin-family (bip122 genesis) identifiers rather than
defaulting them to BSV.
@sirdeggen

Copy link
Copy Markdown
Author

Network identifiers update

Following the discussion on registering the Teranode test networks explicitly (and @andyrowe’s note that ambiguous Bitcoin-family IDs are an unrecoverable footgun), this PR is now aligned with ChainAgnostic/namespaces#190:

Registered CAIP-2 networks

Network Identifier
Mainnet bsv:mainnet
Testnet bsv:testnet
Teranode Test Net (Teratestnet) bsv:ttn
Teranode Scaling Test Net bsv:tstn

Ambiguous bip122 refusal

The spec and TypeScript reference now require clients/facilitators to refuse ambiguous Bitcoin-family identifiers (including genesis-only bip122:000000000019d6689c085ae165831e93) rather than defaulting them to BSV. Paying the right amount on the wrong chain is unrecoverable.

Implementation notes

  • @x402/bsv constants: BSV_TTN_CAIP2, BSV_TSTN_CAIP2, BSV_NETWORKS, toBsvWalletNetwork() mapping to BRC-100 getNetwork() names (mainnet / testnet / ttn / tstn)
  • Facilitator wallet-chain agreement uses that map
  • Examples accept BSV_NETWORK=bsv:ttn|bsv:tstn
  • 121 unit tests passing

Namespaces PR updated in lockstep: ChainAgnostic/namespaces#190

Resolve conflicts with upstream main (ALGORAND_TESTNET_CAIP2 import and
Starknet safety appendix) while keeping BSV exact-scheme support.
@milai123

milai123 commented Aug 7, 2026

Copy link
Copy Markdown

nice

@vegardwikeby

Copy link
Copy Markdown

x402 for sure will live up to its aim; include everyone, so I’m sure this will be added without any fuzz.

@tonesnotes

Copy link
Copy Markdown

Extending the already class leading x402 support! Yeah!

@davewantsmoore

Copy link
Copy Markdown

X402

@PhillipJFryz

Copy link
Copy Markdown

good

@gptix

gptix commented Aug 7, 2026

Copy link
Copy Markdown

BSV!

@mo-jaber

mo-jaber commented Aug 7, 2026

Copy link
Copy Markdown

+1 on getting this merged. We have users and use cases lined up ready to build on x402 payments over BSV as soon as it lands.

@Colin-jpg

Copy link
Copy Markdown

gm

@captainsvbot

Copy link
Copy Markdown

This is a well-structured and detailed PR @sirdeggen
+1 on getting this merged.

@SteffenNS

Copy link
Copy Markdown

It is obviously right to have good BSV support because BSV has proof-of-concept tested over 1 million peer-to-peer transactions per second, and has very low transaction fees.

@Zwilla

Zwilla commented Aug 7, 2026

Copy link
Copy Markdown

Really thorough write-up — you clearly went deep on the BRC-121 → x402 mapping rather than bolting on a generic "chain #N" integration. A few things that stand out:

Explicit registration of bsv:ttn and bsv:tstn alongside bsv:mainnet / bsv:testnet, together with the hard refusal of ambiguous bip122 (genesis-hash) identifiers, is the correct hardening. Paying the right amount on the wrong chain is unrecoverable in an automated protocol.

The divergence from BRC-121's overpayment tolerance to strict amount equality is well-justified for machine-to-machine flows. Equality produces only two terminal states ("paid" or "retry"), which is exactly the contract an automated agent needs. Pinning the amount at challenge time and bounding it with the freshness window is the cleaner composition.

Keeping the facilitator as the recipient's own BRC-100 wallet preserves the core privacy property (the identity key never appears on-chain) and eliminates custody risk. The optional spvOnVerify callback is a useful early filter.

Citing the specific BRC standards (BRC-121, BRC-29, BRC-42, BRC-100, BRC-69, BRC-94) and showing how they compose end-to-end is a strong signal that the underlying specs were actually read.

103 unit tests + live integration smoke is solid. Known-answer vectors for the exact-amount check at outputIndex, the ms timestamp in the derivation suffix (+ freshness edges), and the replay signals (txid dedup TTL + isMerge / no-new-satoshis) would help future Go/Python ports.

The Key Linkage Revelation & Auditability appendix (BRC-69/BRC-94) is a thoughtful addition, especially for regulated-asset use cases.

@BMX-Casey94

BMX-Casey94 commented Aug 7, 2026

Copy link
Copy Markdown

x402 on BSV is the only viable solution despite any surrounding politics, imo.
Following this closely, nice one!

@BSVanon

BSVanon commented Aug 7, 2026

Copy link
Copy Markdown

Great job Deggen! AI will find BSV the undeniably optimal choice for machine 2 machine economies.

@jefflightweb

Copy link
Copy Markdown

I'd like to see this merged.

@andyrowe

Copy link
Copy Markdown

Quick update on the network-identifier question flagged above: the CAIP-2 registration this PR was waiting on just merged — ChainAgnostic/namespaces#190 (2026-08-18). bsv is now a registered CAIP-2 namespace with references bsv:mainnet, bsv:testnet, and the Teranode test nets bsv:ttn / bsv:tstn.

So bsv:mainnet is no longer a proposed identifier — it's the registered one, and bip122:<genesis-hash> is now the strictly-worse option, since BSV shares that genesis hash byte-for-byte with BTC and BCH and so can't actually name the chain. That resolves one of the two open maintainer calls without further work.

As an interop datapoint (not a competing proposal): we switched our live BSV x402 service over to emit network: "bsv:mainnet" today. Settlement keys off the broadcast tx, never the network string, so it was a clean swap and round-trips fine. Our plain-P2PKH variant is the companion you scoped out of this PR, so nothing here overlaps the BRC-29/42 exact scheme — just confirming the registered identifier works end to end.

claude added 2 commits August 19, 2026 09:08
# Conflicts:
#	examples/typescript/facilitator/advanced/all_networks.ts
Upstream added required defaultAssetTransferMethod/paymentFlows to
SchemeNetworkServer and widened MoneyParser's amount param to
string | number (and parseMoneyString now returns a string). Update
the BSV exact scheme and money parser to match.

@andyrowe andyrowe left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, approving. Confirmed this tracks the upstream SchemeNetworkServer/MoneyParser changes cleanly — defaultAssetTransferMethod = "default" + the single authorization flow lines up with the other non-EVM mechanisms (near/aptos/hedera/keeta), and widening the WhatsOnChain parser to string | number keeps the guards intact (empty string → 0 and non-numeric → NaN are both still rejected).

One non-blocking note: the fork's CI is sitting at action_required, so unit/lint haven't actually run on this commit. — might be worth kicking off the workflow so it's green on record before merge.

andyrowe pushed a commit to andyrowe/BRCs that referenced this pull request Aug 21, 2026
A wallet-less, publicly-verifiable profile for HTTP 402 settlement on BSV
using plain P2PKH payments to a per-invoice address. Any BSV holder can pay
(no BRC-100 wallet, BRC-42 derivation, identity key, or BEEF), and settlement
is verifiable by any third party from public chain data alone. Complements
x402-foundation/x402#2890 (BRC-29/42 mode) rather than competing with it.

Backed by a live mainnet reference implementation at bsv.cx and a captured
real settlement transaction. Adds the file to the root and payments indexes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
andyrowe pushed a commit to andyrowe/BRCs that referenced this pull request Aug 21, 2026
A wallet-less, publicly-verifiable profile for HTTP 402 settlement on BSV
using plain P2PKH payments to a per-invoice address. Any BSV holder can pay
(no BRC-100 wallet, BRC-42 derivation, identity key, or BEEF), and settlement
is verifiable by any third party from public chain data alone. Complements
x402-foundation/x402#2890 (BRC-29/42 mode) rather than competing with it.

Backed by a live mainnet reference implementation at bsv.cx and a captured
real settlement transaction. Adds the file to the root and payments indexes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
andyrowe pushed a commit to andyrowe/BRCs that referenced this pull request Aug 21, 2026
A wallet-less, publicly-verifiable profile for HTTP 402 settlement on BSV
using plain P2PKH payments to a per-invoice address. Any BSV holder can pay
(no BRC-100 wallet, BRC-42 derivation, identity key, or BEEF), and settlement
is verifiable by any third party from public chain data alone. Complements
x402-foundation/x402#2890 (BRC-29/42 mode) rather than competing with it.

Backed by a live mainnet reference implementation at bsv.cx and a captured
real settlement transaction. Adds the file to the root and payments indexes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The text merge of upstream/main left a stale vitest resolution entry
for @x402/bsv. Regenerating via pnpm install fixes it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CnGKX5y5ib3oMZcxRGSeZe
# Conflicts:
#	e2e/pnpm-lock.yaml
#	examples/typescript/clients/advanced/all_networks.ts
#	examples/typescript/clients/advanced/package.json
#	examples/typescript/facilitator/advanced/all_networks.ts
#	examples/typescript/facilitator/advanced/package.json
#	examples/typescript/pnpm-lock.yaml
#	examples/typescript/pnpm-workspace.yaml
#	examples/typescript/servers/advanced/all_networks.ts
#	examples/typescript/servers/advanced/package.json
#	typescript/.changeset/config.json
#	typescript/pnpm-lock.yaml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci examples Changes to examples sdk Changes to core v2 packages specs Spec changes or additions typescript

Projects

None yet

Development

Successfully merging this pull request may close these issues.