Class: X402::BSV::ProofGateway
- Defined in:
- lib/x402/bsv/proof_gateway.rb
Overview
Merkleworks x402 compatible gateway.
Challenge: X402-Challenge (merkleworks JSON with nonce UTXO + request binding) Proof: X402-Proof (echoed challenge hash + rawtx + txid)
Supports two modes:
- Profile A: challenge includes nonce UTXO metadata only
- Profile B: nonce_provider returns a pre-signed partial_tx template
Constant Summary collapse
- ACCEPTABLE_MEMPOOL_STATUSES =
ARC status values that confirm ARC knows about the tx and hasn't rejected it. ARC progresses a broadcast tx through roughly:
RECEIVED → STORED → ANNOUNCED_TO_NETWORK → REQUESTED_BY_NETWORK → SENT_TO_NETWORK → ACCEPTED_BY_NETWORK → SEEN_ON_NETWORK → MINED
Any of these mean "ARC has the tx and is processing it"; the client has demonstrably broadcast. The bad states we must reject are UNKNOWN (never seen), REJECTED, DOUBLE_SPEND_ATTEMPTED — anything in this whitelist is acceptable.
Bitcoin's single-spend guarantee at the network layer is the actual replay gate; this check just confirms the client fulfilled their broadcast obligation. Matches the merkleworks reference implementation's
visiblecheck. %w[ RECEIVED STORED ANNOUNCED_TO_NETWORK REQUESTED_BY_NETWORK SENT_TO_NETWORK ACCEPTED_BY_NETWORK SEEN_ON_NETWORK MINED ].freeze
- RETRY_DELAYS_SECONDS =
Backoff schedule between ARC polls. One immediate attempt plus three retries → 4 attempts total, absorbing ~1.75s of propagation lag between the client's broadcast and ARC observing the tx.
[0.25, 0.5, 1.0].freeze
Constants inherited from Gateway
Instance Attribute Summary
Attributes inherited from Gateway
Instance Method Summary collapse
-
#challenge_headers(rack_request, route) ⇒ Hash
Build a 402 challenge with merkleworks +X402-Challenge+ header.
-
#initialize(nonce_provider:, arc_client:, payee_locking_script_hex: nil, wallet: nil, challenge_secret: nil, challenge_store: nil) ⇒ ProofGateway
constructor
A new instance of ProofGateway.
-
#proof_header_names ⇒ Array<String>
Proof header names this gateway responds to.
-
#settle!(_header_name, proof_payload, rack_request, route) ⇒ SettlementResult
Verify a merkleworks x402 proof against the challenge and check mempool.
Methods inherited from Gateway
#build_template, #request_binding_hash
Constructor Details
#initialize(nonce_provider:, arc_client:, payee_locking_script_hex: nil, wallet: nil, challenge_secret: nil, challenge_store: nil) ⇒ ProofGateway
Returns a new instance of ProofGateway.
72 73 74 75 76 77 78 79 80 |
# File 'lib/x402/bsv/proof_gateway.rb', line 72 def initialize(nonce_provider:, arc_client:, payee_locking_script_hex: nil, wallet: nil, challenge_secret: nil, challenge_store: nil) super(payee_locking_script_hex: payee_locking_script_hex, wallet: wallet, challenge_secret: challenge_secret) @nonce_provider = nonce_provider @arc_client = arc_client @challenge_store = challenge_store || ChallengeStore::Memory.new end |
Instance Method Details
#challenge_headers(rack_request, route) ⇒ Hash
Build a 402 challenge with merkleworks +X402-Challenge+ header.
88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/x402/bsv/proof_gateway.rb', line 88 def challenge_headers(rack_request, route) if route.amount_sats.respond_to?(:call) raise ConfigurationError, "proof_gateway does not support callable amount_sats (fiat pricing) — use a static value" end challenge = build_merkleworks_challenge(rack_request, route) begin @challenge_store.store!(challenge.sha256_hex, challenge) rescue ChallengeStore::StoreFullError raise VerificationError.new("server at capacity — try again later", status: 503) end { "X402-Challenge" => challenge.to_header } end |
#proof_header_names ⇒ Array<String>
Returns proof header names this gateway responds to.
103 104 105 |
# File 'lib/x402/bsv/proof_gateway.rb', line 103 def proof_header_names ["X402-Proof"] end |
#settle!(_header_name, proof_payload, rack_request, route) ⇒ SettlementResult
Verify a merkleworks x402 proof against the challenge and check mempool.
115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/x402/bsv/proof_gateway.rb', line 115 def settle!(_header_name, proof_payload, rack_request, route) required_sats = route.resolve_amount_sats proof = Proof.from_header(proof_payload) challenge = lookup_challenge!(proof) run_protocol_checks!(challenge, proof, rack_request) decode_and_verify_transaction!(proof, challenge, required_sats) check_mempool!(proof.txid) consume_challenge!(proof) SettlementResult.new(txid: proof.txid, network: X402.configuration.network) end |