Class X402::BSV::BRC121Gateway ¶
Inherits: Object
BRC-121 ("Simple 402 Payments") gateway for BSV settlement-gated HTTP.
BRC-121 is the BSV Association's simple HTTP payment protocol. The server is stateless: replay protection comes from a 30-second timestamp freshness window (§5 step 2) plus the wallet's handling of duplicate incoming transactions.
Unlike BRC-105, BRC-121 does not use server-generated derivation prefixes or a PrefixStore. The client generates the derivation prefix, chooses a timestamp as the suffix, constructs a BRC-29 payment, and submits it all in one round trip with no prior handshake.
Spec: https://hub.bsvblockchain.org/brc/payments/0121
Headers¶
Challenge (server → client): x-bsv-sats — required satoshi amount x-bsv-server — server's identity public key (compressed hex)
Proof (client → server): x-bsv-beef — base64 BEEF transaction (the proof header) x-bsv-sender — client's identity public key (compressed hex) x-bsv-nonce — base64 BRC-29 derivation prefix x-bsv-time — decimal Unix millisecond timestamp x-bsv-vout — decimal output index of the payment
Replay protection¶
BRC-121 §5 step 2: the x-bsv-time header MUST be within 30 seconds of the server's current time. This is the primary defence against capture and delayed replay.
BRC-121 §5 step 5 also specifies checking isMerge on the wallet's internalization result. The current Ruby BSV::Wallet::WalletClient does not return an isMerge field, so this gateway additionally uses an X402::BSV::TxidStore to reject duplicate txids within the freshness window.
Implements the three-method gateway interface required by Middleware: challenge_headers(rack_request, route) → Hash proof_header_names → Array
Constants¶
BASE64_NONCE ¶
BRC-29 derivation prefix is base64. Cap length at 128 chars (~96 bytes decoded) — well above realistic nonce sizes.
CLIENT_HEADERS ¶
Not documented.
COMPRESSED_PUBKEY_HEX ¶
Not documented.
FRESHNESS_WINDOW_MS ¶
Not documented.
PROOF_HEADER ¶
Not documented.
PROTOCOL ¶
Not documented.
Public Instance Methods¶
challenge_headers(_rack_request, route) ¶
Issue a 402 challenge with BRC-121 headers. - @param _rack_request [Rack::Request] unused (stateless protocol) - @param route [X402::Configuration::Route] - @return [Hash] challenge headers (+x-bsv-sats+, +x-bsv-server+)
initialize(wallet:, txid_store: = nil, arc_client: = nil, network_visibility_cache: = nil) ¶
- @param
wallet[#internalize_action, #get_public_key] BRC-100 wallet. Must respond to +#internalize_action(args)+ (per bsv-ruby-sdk +BSV::Wallet::WalletClient+) and +#get_public_key(identity_key: true)+ for the server identity key. - @param
txid_store[#record_if_unseen!, nil] replay protection for settled txids. Defaults to +X402::BSV::TxidStore::Memory.new+. - @param
arc_client[#status, nil] ARC client used to confirm the payment txid is visible on the BSV network before the wallet internalises it. When +nil+ (backward compatibility), the visibility check is skipped entirely. - @param
network_visibility_cache[NetworkVisibility::Cache, nil] per-gateway positive-only TTL cache shielding ARC from duplicate-submission bursts. Defaults to a fresh +NetworkVisibility::Cache.new+. - @return [BRC121Gateway] a new instance of BRC121Gateway
proof_header_names() ¶
Header names that carry the proof/payment from the client. - @return [Array
settle!(_header_name, _proof_payload, rack_request, route) ¶
Verify and internalise a BRC-121 payment. - @param _header_name [String] which proof header matched - @param _proof_payload [String] the +x-bsv-beef+ header value (read directly from the rack env alongside the other four client headers) - @param rack_request [Rack::Request] - @param route [X402::Configuration::Route] - @raise [VerificationError] on missing/invalid headers, stale timestamp, insufficient payment, replay, or internalisation failure - @return [SettlementResult]