Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[0.10.0] - 2026-04-16

Added

  • BRC105Gateway and BRC121Gateway now broadcast payment BEEF to ARC before internalisation, enforcing the "no credit without on-chain settlement" invariant (#148)
  • Configuration auto-injects shared_arc_client into BRC105Gateway and BRC121Gateway (same pattern as PayGateway)
  • ARC broadcast is idempotent — both client and server can broadcast the same tx safely

Changed

  • Status endpoint resolves identity via shared_wallet.get_public_key instead of parsing server_wif directly — works with local wallets, remote wallets, and WIF-backed proto-wallets (#143)
  • Status endpoint error messages are now wallet-agnostic

Dependencies

  • Requires bsv-sdk with broadcast_beef support (sgbett/bsv-ruby-sdk@88799c0)
  • Bump rake 13.3.1 → 13.4.1, yard 0.9.39 → 0.9.40

[0.9.1] - 2026-04-13

Fixed

  • Replace deprecated TAAL ARC endpoints with ARCADE (arcade.gorillapool.io, testnet.arcade.gorillapool.io) across all e2e specs, docs, and configuration examples (#139)

[0.9.0] - 2026-04-13

Added

  • Node.js wallet server fixture and RemoteWallet adapter for e2e testing
  • Relay-mode config.ru for e2e PayGateway tests

Fixed

  • Accept BEEF (BRC-62) from client for wallet internalisation — BRC-100 wallets require BEEF, not raw tx bytes (#134)
  • Call internalize_action with positional Hash matching ProtoWallet convention (#137)
  • RemoteWallet#get_public_key calling convention mismatch with ProtoWallet interface
  • BRC-100 wallet compatibility for PayGateway relay (base64 derivation params, identity key)
  • Scope rescue in teardown to prevent health check spin

Changed

  • Bump bsv-sdk 0.11.0, bsv-wallet 0.7.0

[0.8.0] - 2026-04-12

Added

  • RemoteWallet adapter (X402::RemoteWallet) — HTTP client implementing the duck-typed wallet interface by calling a remote @bsv/simple server wallet API. Supports get_public_key (with identity key caching) and internalize_action for settlement relay. Thread-safe.
  • BRC121Gateway — new gateway implementing the BRC-121 "Simple 402 Payments" spec. Stateless server with BRC-100 wallet-backed validation via internalize_action. 30-second timestamp freshness window, TxidStore replay protection, and isMerge checking for wallet-level deduplication.
  • operator_wallet_url configuration — point at an existing @bsv/simple server wallet and PayGateway is auto-enabled with no keys on the server. The minimal three-line configuration path.
  • config.wallet = DSL — first-class wallet attribute. When set with no explicit enable calls, both PayGateway and BRC121Gateway are auto-enabled.
  • config.network = attribute — parameterised BSV network (bsv:mainnet or bsv:testnet). Respects BSV_NETWORK environment variable. Replaces hardcoded NETWORK constants across all four gateways.
  • ARC default fallbackbuild_arc_client falls back to ARC.default (GorillaPool Arcade) when no explicit arc_url is configured.
  • PayGateway wallet relay — after ARC broadcast, relays settlement to the operator's wallet via internalize_action (log-and-continue on failure). BRC-29 derivation params round-trip through the challenge for wallet- spendable addresses.
  • X402::Wallet.load — resolves signing WIF from SERVER_WIF env or ~/.bsv-wallet/wallet.key, returns a fully-constructed WalletClient.
  • rake x402:wallet:setup — interactive wallet create/restore task. Never overwrites existing wallets. File permissions enforced (0600/0700).
  • Rails Railtie — auto-loads rake tasks in Rails applications.

Changed

  • BRC105Gateway: wallet: replaces arc_client:settle! now uses wallet.internalize_action instead of direct ARC broadcast, per the BRC-105 spec. Constructor takes wallet: parameter; arc_client: removed. (Breaking for direct BRC105Gateway construction; DSL users update seamlessly.)
  • Gateway base class uses BRC-29 protocol ID[2, "3241645161d8"] replaces [2, "x402 payment"]. Derived addresses are now wallet-spendable via standard BRC-29 key derivation.
  • PayGateway challenge carries derivationPrefix/derivationSuffix instead of keyId. HMAC covers payTo + prefix + suffix.
  • Gemspec dependency floors raisedbsv-sdk >= 0.9.0, bsv-wallet >= 0.5.0.

Fixed

  • PayGateway BRC-29 derivation alignment — was using a custom protocol ID that no wallet recognised; operator funds were unrecoverable. Now uses the standard BRC-29 protocol ID shared with BRC-105 and BRC-121.
  • ProofGateway lenient base64Base64.decode64 replaced with strict_decode64, consistent with all other gateways.
  • PayGateway verify_binding! — now uses build_op_return_script instead of duplicating the OP_RETURN hex via string interpolation.
  • BRC121Gateway nonce validation — two-layer check (regex + strict_decode64).
  • BRC121Gateway check_internalization_result! — checks isMerge and accepted on the wallet result per BRC-121 §5 step 5.
  • Wallet setup file permissionsFile.chmod enforced after write on both new and pre-existing files/directories.

[0.7.0] - 2026-04-08

Added

  • ProofGateway challenge cache — new X402::BSV::ChallengeStore::Memory, a per-process TTL-bounded store of issued challenges keyed by their canonical sha256. ProofGateway records each challenge at issuance and looks it up at settlement instead of trusting a client-echoed X402-Challenge header. Closes the forged-challenge provenance hole (#23) and kills same-proof replay (the entry is consumed on successful settlement). Matches the merkleworks reference implementation's mandatory ChallengeCache pattern without otherwise aligning with their architecture.

Changed

  • ProofGateway no longer consults the echoed X402-Challenge header at settlement. The merkleworks spec only mandates that clients echo challenge_sha256 in the proof; the server recovers the original challenge from its own store. Proofs referencing a challenge that was never issued by this server (or has been consumed / expired) are now rejected with challenge not found or expired (400).
  • ProofGateway documentation softened — the "experimental, do not use in production" warning on the BSV-proof scheme doc has been replaced with an "under development" note reflecting the closed provenance hole and the per-instance cache limitation.
  • ProofGateway mempool check is now propagation-tolerant. check_mempool! retries ARC status (4 attempts, ~1.75s total worst case) to absorb the window between broadcast and SEEN_ON_NETWORK. The acceptable-status whitelist has been broadened to include every non-error ARC state (RECEIVED, STORED, ANNOUNCED_TO_NETWORK, REQUESTED_BY_NETWORK, SENT_TO_NETWORK, ACCEPTED_BY_NETWORK, SEEN_ON_NETWORK, MINED). Matches the merkleworks reference implementation's visible check — Bitcoin's single-spend at the network layer remains the actual replay gate. Error message now surfaces the last observed status to aid ops debugging.

Fixed

  • ProofGateway concurrent-settlement race. consume_challenge! previously ignored the return value of the store's atomic consume!, allowing two threads racing the same proof to both return a SettlementResult. Losers of the race now raise challenge not found or expired (400), so exactly one settlement succeeds per issued challenge even under concurrency.
  • Middleware now preserves the gateway's status when challenge issuance fails. X402::Middleware#issue_challenge gained a VerificationError rescue matching settle_and_forward, so a 503 from a saturated ChallengeStore reaches the client as 503 instead of bubbling to a generic 500.

[0.6.0] - 2026-04-05

Added

  • Configurable structured logging — pluggable logger on configuration with structured request lifecycle messages (route match, identity key, proof dispatch, settlement outcome). (#102)
  • BRC-105 settlement logging — structured log output for derivation, key ID, locking script verification, and settlement result in BRC105Gateway. (#96)
  • BRC-105 §6.2 response body — settlement success and error responses now include spec-conformant JSON body with receipt details. Spec-anchored tests. (#91)
  • BRC-105 response headersx-bsv-payment-version and x-bsv-payment-satoshis-paid headers on successful settlement. (#91)
  • API documentation — YARD-style docs for public interfaces. (#89)

Changed

  • Client identity key required for BRC-105x-bsv-auth-identity-key header is now mandatory for BRC-105 settlement (§7.1). Requests without it receive a 401 error. (#99)

Fixed

  • ARC broadcast error details — error messages from ARC are now logged and surfaced in the 502 response rather than swallowed silently.
  • Base64 derivationSuffix accepted — client-generated suffixes may be base64-encoded (not just hex). Validation updated to accept both formats. (#93)

Build

  • Dependency updates — bsv-sdk 0.6.0 → 0.6.1, rack 3.2.6.

0.5.1 - 2026-04-04

Fixed

  • PaymentObserver now protocol-agnosticextract_and_validate no longer hardcodes the Coinbase v2 envelope format. Pluggable extractor: parameter (duck-typed #call(proof_payload)Transaction or nil) enables BRC-105 and custom payment formats. Default CoinbaseV2Extractor preserves backwards compatibility. (#84)

0.5.0 - 2026-04-04

Changed

  • Default binding_mode now :strict — OP_RETURN request binding enforced by default. Set binding_mode: :permissive to restore previous behaviour.

Added

  • Txid deduplication storerecord_if_unseen! prevents double-processing of the same transaction across settlement and observer paths.

Fixed

  • Security audit quick wins — handle numeric JSON amount, non-string derivation components (H-3, M-2, M-4, M-5).
  • SettlementWorker hardeningon_failure callback, capped queue via Queue with size check (replaces SizedQueue), exponential backoff improvements.
  • Atomic txid deduplicationrecord_if_unseen! provides thread-safe check-and-insert in a single call.
  • Runtime warnings scoped — warnings emitted only for relevant gateways, check value not just key presence.
  • Production environment warnings — warn on ephemeral challenge_secret and in-memory PrefixStore.

0.4.0 - 2026-04-03

Added

  • Server walletconfig.server_wif builds a shared ProtoWallet (BRC-42/43 key derivation) for all gateways. Per-payment derived addresses, no address reuse. Falls back to static payee_locking_script_hex when not set. Per-gateway overrides (wallet:, key_deriver:) take precedence.
  • Settlement workerX402::SettlementWorker for async background broadcast. Ruby stdlib only (Thread + Queue), exponential backoff retry, zero dependencies. Pluggable interface (#enqueue(tx_binary)) for Sidekiq/Redis.
  • Per-route ARC thresholdsconfig.protect accepts arc_wait_for: to override the gateway default. :async validates tx locally then enqueues, responding 200 immediately.
  • Payment observerX402::PaymentObserver Rack middleware for voluntary ungated payments. Watches for payment headers, validates payee, enqueues to settlement worker. Never gates access. Configurable proof headers and on_payment callback.
  • Pluggable recogniserPaymentObserver accepts recogniser: (any object responding to #ours?(locking_script_hex)) for BRC-29 derived address payment channels. StaticRecogniser wraps the existing static payee behaviour.
  • Fiat-denominated pricingconfig.protect accepts amount_usd: resolved to sats at challenge time via exchange_rate_provider. Also accepts callable amount_sats: for any dynamic pricing. Provider interface: #sats_for(currency, amount).

Changed

  • build_template signature — now accepts required_sats integer instead of route object. Gateways snapshot the resolved amount once per request.
  • ProofGateway rejects callable pricing — raises ConfigurationError at challenge time. The merkleworks canonical hash includes amount_sats; a callable would produce different hashes across requests.

Fixed

  • arc_wait_for coerced to string — prevents Symbol values being passed to ARC client.
  • PaymentObserver pass-through guarantee — enqueue/callback failures wrapped in rescue, never break the request.
  • Recogniser interface validatedConfigurationError if recogniser doesn't respond to #ours?.
  • Static payee hex canonicalised — round-trips through Script.from_hex.to_hex in StaticRecogniser.
  • Exchange rate provider validatedConfigurationError if provider doesn't respond to #sats_for.

0.3.0 - 2026-04-02

Added

  • Configuration DSLconfig.enable :pay_gateway with shared dependencies (ARC client, payee script) wired automatically. Convenience options: server_wif: builds KeyDeriver, nonce_wif: builds PrivateKey, default PrefixStore for BRC-105. Per-gateway overrides supported. Deferred construction at validate! time. Full backwards compatibility with config.gateways = [...].
  • Copilot review instructions.github/copilot-instructions.md with payment-bypass-focused review guidance.
  • Dependabot — weekly checks for bundler and GitHub Actions dependencies.

Changed

  • Treasury refactor — ProofGateway no longer holds the treasury's private key (nonce_key: removed). The nonce_provider callable now optionally returns a pre-signed partial_tx: for Profile B. Signing responsibility pushed from gateway to treasury. Trust boundary: [(X)+(B)] <-> [(T)].
  • nonce_provider interface — now receives payee: and amount: kwargs. Profile detection moved from constructor config to provider response.
  • Dependenciesbsv-sdk ~> 0.4, bsv-wallet ~> 0.2 (BRC-100 wallet interface now available).

Fixed

  • ARC wait_for enforced — PayGateway now passes arc_wait_for to broadcast(tx, wait_for:). Previously stored but never used.
  • Mempool status validated — ProofGateway check_mempool! now verifies tx_status is SEEN_ON_NETWORK, ANNOUNCED_TO_NETWORK, or MINED. Non-propagated statuses (RECEIVED, STORED) correctly rejected.
  • Error messages hardened — all gateways, middleware, and protocol parsers now return fixed generic strings. No SDK exception messages forwarded to HTTP clients.
  • Config DSL memoisationshared_arc_client checks injected arc_client on every call, not just first.

Removed

  • nonce_key: parameter from ProofGateway constructor (breaking change for Profile B users — signing moves to nonce_provider).
  • nonce_wif: and nonce_key: convenience options from configuration DSL (no longer applicable).

0.2.0 - 2026-03-30

Added

  • BRC-105 GatewayX402::BSV::BRC105Gateway implementing the BSV Association's native payment protocol (x-bsv-* headers). Uses BRC-29 key derivation for unique per-payment addresses and AtomicBEEF (BRC-95) transaction format.
  • Prefix StoreX402::BSV::PrefixStore::Memory for BRC-105 derivation prefix replay protection. Thread-safe via Monitor, with TTL-based expiry (default 300s) and max capacity cap (default 10,000).
  • BRC-103 composition — BRC105Gateway works standalone (advertises server identity key in header) or composes with future BRC-103 mutual authentication middleware. Detects mode automatically from env['brc103.identity_key'].
  • BRC-105 e2e test — full standalone payment flow against BSV testnet (derive address, build tx, encode AtomicBEEF, verify, broadcast).
  • Comprehensive documentation — scheme doc (docs/schemes/brc-105.md), process flow diagrams for both standalone and authenticated modes, security analysis, client integration guide.

Security

  • Derivation prefix consumed after full transaction validation, not before (prevents MITM prefix burning).
  • BRC-103 identity key validated as compressed pubkey hex before trusting as BRC-29 counterparty (prevents sentinel injection).
  • SDK exception messages not forwarded to HTTP clients — fixed generic strings returned.
  • PrefixStore::Memory bounded with TTL and max capacity to prevent heap exhaustion from unauthenticated challenge requests.
  • StoreFullError returns 503 (server at capacity), not 400.

0.1.0 - 2026-03-28

Added

Middleware

  • X402::Middleware — pure Rack dispatcher for payment-gated HTTP. No blockchain knowledge, no keys. Matches routes, polls gateways for challenge headers, dispatches proofs to the matching gateway.
  • Multi-gateway support — multiple gateways can be configured simultaneously. The middleware issues challenge headers from all gateways and dispatches proofs to whichever gateway recognises the proof header.
  • Payment content negotiation — different x402 ecosystems use different HTTP headers. A server sends multiple challenge headers; the client picks the one it can satisfy.

Gateways

  • X402::BSV::PayGateway — Coinbase v2 headers (Payment-Required / Payment-Signature / Payment-Response). Server broadcasts via ARC. HMAC-signed payToSig prevents payee address tampering. OP_RETURN request binding (strict/permissive mode). This is the recommended "BSV way" — vendor verifies, vendor broadcasts, vendor serves.
  • X402::BSV::ProofGateway — merkleworks x402 headers (X402-Challenge / X402-Proof). Client broadcasts, server checks mempool. Profile A (bare nonce metadata) and Profile B (pre-signed template with 0xC3 nonce signature for provenance).
  • X402::BSV::Gateway — base class for template-based gateways. Builds partial transaction templates with payment output and OP_RETURN binding. Supports wallet-based address derivation (BRC-43) or static payee address.

Configuration

  • Route protectionconfig.protect method: :GET, path: "/api/expensive", amount_sats: 100
  • Duplicate proof header detection — validates no two gateways claim the same proof header name.

Testing

  • E2e test suite — PayGateway, ProofGateway (Profile B), and fee delegation flows tested against BSV testnet with real ARC broadcasts.
  • E2ELogger — pretty logging with actors, timestamps, transaction links, and timestamped markdown log files.
  • Fee delegation e2e — treasury + client + delegator + payee four-wallet flow with 0xC3 sighash alignment.

Documentation

  • Architecture docs — middleware as dispatcher, gateway interface, component boundaries, unified template model, 0xC3 sighash rationale.
  • Scheme docs — BSV-pay and BSV-proof with headers, challenge/settlement flows, replay protection.
  • Process flow diagrams — mermaid sequence diagrams for PayGateway and ProofGateway.
  • Security docs — threat model, payToSig HMAC, nonce provenance (Profile B), OP_RETURN binding, error handling.
  • Operations docs — deployment, performance, treasury/nonce lifecycle.
  • Ecosystem docs — Coinbase v2, merkleworks, BRC-105 positioning and header namespace reservations.