Class: X402::BSV::PayGateway

Inherits:
Gateway
  • Object
show all
Defined in:
lib/x402/bsv/pay_gateway.rb

Overview

BSV-native payment gateway using Coinbase v2 headers.

Challenge: Payment-Required (v2 PaymentRequired JSON with extra.partialTx) Proof: Payment-Signature (v2 PaymentPayload JSON with rawtx + txid) Receipt: Payment-Response (settlement result JSON)

Server broadcasts via ARC. No nonces needed — ARC is the replay gate.

Constant Summary collapse

DEFAULT_ARC_WAIT_FOR =
"SEEN_ON_NETWORK"
DEFAULT_ARC_TIMEOUT =
5
DEFAULT_MAX_TIMEOUT_SECONDS =
60
ASSET =
"BSV"
SCHEME =
"exact"

Constants inherited from Gateway

Gateway::BRC29_PROTOCOL_ID

Instance Attribute Summary collapse

Attributes inherited from Gateway

#payee_locking_script_hex

Instance Method Summary collapse

Methods inherited from Gateway

#build_template, #request_binding_hash

Constructor Details

#initialize(arc_client:, arc_wait_for: DEFAULT_ARC_WAIT_FOR, arc_timeout: DEFAULT_ARC_TIMEOUT, binding_mode: :strict, payee_locking_script_hex: nil, wallet: nil, challenge_secret: nil, settlement_worker: nil, txid_store: nil) ⇒ PayGateway

Returns a new instance of PayGateway.

Parameters:

  • arc_client (#broadcast)

    ARC client for broadcasting

  • arc_wait_for (String) (defaults to: DEFAULT_ARC_WAIT_FOR)

    ARC X-WaitFor header value

  • arc_timeout (Integer) (defaults to: DEFAULT_ARC_TIMEOUT)

    seconds before ARC timeout

  • binding_mode (Symbol) (defaults to: :strict)

    :strict or :permissive for OP_RETURN binding

  • payee_locking_script_hex (String, nil) (defaults to: nil)

    payee script (falls back to config)

  • settlement_worker (#enqueue, nil) (defaults to: nil)

    async settlement worker

  • txid_store (#seen?, #record!, nil) (defaults to: nil)

    optional txid deduplication store. When provided, rejects proofs whose txid has already been settled.



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/x402/bsv/pay_gateway.rb', line 32

def initialize(arc_client:, arc_wait_for: DEFAULT_ARC_WAIT_FOR,
               arc_timeout: DEFAULT_ARC_TIMEOUT, binding_mode: :strict,
               payee_locking_script_hex: nil, wallet: nil, challenge_secret: nil,
               settlement_worker: nil, txid_store: nil)
  super(payee_locking_script_hex: payee_locking_script_hex, wallet: wallet,
        challenge_secret: challenge_secret)
  @arc_client = arc_client
  @arc_wait_for = arc_wait_for
  @arc_timeout = arc_timeout
  @binding_mode = binding_mode
  @settlement_worker = settlement_worker
  @txid_store = txid_store
end

Instance Attribute Details

#arc_clientObject (readonly)

Returns the value of attribute arc_client.



22
23
24
# File 'lib/x402/bsv/pay_gateway.rb', line 22

def arc_client
  @arc_client
end

#arc_timeoutObject (readonly)

Returns the value of attribute arc_timeout.



22
23
24
# File 'lib/x402/bsv/pay_gateway.rb', line 22

def arc_timeout
  @arc_timeout
end

#arc_wait_forObject (readonly)

Returns the value of attribute arc_wait_for.



22
23
24
# File 'lib/x402/bsv/pay_gateway.rb', line 22

def arc_wait_for
  @arc_wait_for
end

#binding_modeObject (readonly)

Returns the value of attribute binding_mode.



22
23
24
# File 'lib/x402/bsv/pay_gateway.rb', line 22

def binding_mode
  @binding_mode
end

#settlement_workerObject (readonly)

Returns the value of attribute settlement_worker.



22
23
24
# File 'lib/x402/bsv/pay_gateway.rb', line 22

def settlement_worker
  @settlement_worker
end

Instance Method Details

#challenge_headers(rack_request, route) ⇒ Hash

Build a 402 challenge with Coinbase v2 +Payment-Required+ header.

Parameters:

Returns:

  • (Hash)

    challenge headers



51
52
53
54
# File 'lib/x402/bsv/pay_gateway.rb', line 51

def challenge_headers(rack_request, route)
  challenge = build_challenge(rack_request, route)
  { "Payment-Required" => Base64.strict_encode64(JSON.generate(challenge)) }
end

#proof_header_namesArray<String>

Returns proof header names this gateway responds to.

Returns:

  • (Array<String>)

    proof header names this gateway responds to



57
58
59
# File 'lib/x402/bsv/pay_gateway.rb', line 57

def proof_header_names
  ["Payment-Signature"]
end

#settle!(_header_name, proof_payload, rack_request, route) ⇒ SettlementResult

Verify and broadcast a Coinbase v2 payment.

Parameters:

  • _header_name (String)

    which proof header matched

  • proof_payload (String)

    base64-encoded payment payload

  • rack_request (Rack::Request)
  • route (X402::Configuration::Route)

Returns:

Raises:

  • (VerificationError)

    on invalid payment, insufficient amount, or broadcast failure



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/x402/bsv/pay_gateway.rb', line 69

def settle!(_header_name, proof_payload, rack_request, route)
  required_sats = route.resolve_amount_sats
  payload = decode_payment_payload(proof_payload)
  verify_accepted!(payload, required_sats)
  accepted_payee = payload.dig("accepted", "payTo")
  pay_to_sig = payload.dig("accepted", "extra", "payToSig")
  prefix = payload.dig("accepted", "extra", "derivationPrefix")
  suffix = payload.dig("accepted", "extra", "derivationSuffix")
  verify_pay_to_signature!(accepted_payee, pay_to_sig, prefix, suffix)
  transaction = decode_transaction(payload)
  beef_b64 = payload.dig("payload", "beef")
  check_txid_unique!(transaction)
  verify_payment_output!(transaction, required_sats, accepted_payee)
  verify_binding!(transaction, rack_request)
  settle_transaction!(transaction, route)
  relay_to_wallet(transaction, prefix, suffix, beef_b64)
  build_settlement_result(transaction)
end