x402-rack 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.claude/plans/20260325-bsv-module.md +384 -0
- data/.claude/plans/20260326-rack-stack-architecture.md +304 -0
- data/.rspec +3 -0
- data/.rubocop.yml +33 -0
- data/CLAUDE.md +47 -0
- data/DESIGN.md +216 -0
- data/LICENSE.txt +56 -0
- data/README.md +74 -0
- data/Rakefile +18 -0
- data/docs/process-flow/pay_gateway.md +69 -0
- data/lib/x402/bsv/gateway.rb +148 -0
- data/lib/x402/bsv/pay_gateway.rb +160 -0
- data/lib/x402/bsv/proof_gateway.rb +145 -0
- data/lib/x402/bsv.rb +5 -0
- data/lib/x402/configuration.rb +89 -0
- data/lib/x402/errors.rb +17 -0
- data/lib/x402/middleware.rb +86 -0
- data/lib/x402/protocol/base64url.rb +19 -0
- data/lib/x402/protocol/challenge.rb +63 -0
- data/lib/x402/protocol/proof.rb +36 -0
- data/lib/x402/protocol/request_binding.rb +32 -0
- data/lib/x402/settlement_result.rb +14 -0
- data/lib/x402/verification/protocol_checks.rb +43 -0
- data/lib/x402/version.rb +5 -0
- data/lib/x402.rb +24 -0
- data/sig/x402.rbs +4 -0
- metadata +140 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bd77486c75afc23677145007680438a0a261f168a6314bc5e8038de1429d51b0
|
|
4
|
+
data.tar.gz: ce8eaeff559f9a7f376991fc33c4233d3c85883e4f71000c4ab609e15fd60eb8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9b692baf1bfc107e95dabeed52cb672447e578b25d7d135a2983d9caa61c719ab9b0e4552bf5ea57ccb28d9f0a06b2ab873ced1bbb8630c18d84e63452e2d992
|
|
7
|
+
data.tar.gz: ecec6258c8b30604a33f4894af7dec5477e929eafc2340a0de88417e2a89f56bb3b8469934c4087c90568f6b9559d89f803cde8ff17aea537aded30df9ed5d26
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Plan: X402::BSV Module — Responsibilities and Interface
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
The codebase has BSV-specific logic in `X402::Verification::SettlementChecks` that needs to move behind an `X402::BSV` boundary. Through analysis of the merkleworks reference implementation (including Profile B settlement flow), the Coinbase x402 ecosystem, and direct discussion with Rui at merkleworks, the architecture has clarified into clean component boundaries.
|
|
6
|
+
|
|
7
|
+
### Ecosystem landscape
|
|
8
|
+
|
|
9
|
+
Two x402 ecosystems exist with different conventions:
|
|
10
|
+
|
|
11
|
+
**Coinbase x402 v2** (broad ecosystem):
|
|
12
|
+
- Headers: `Payment-Required` / `Payment-Signature` / `Payment-Response`
|
|
13
|
+
- Client signs authorisation, facilitator broadcasts (verify → serve → settle)
|
|
14
|
+
- No server-provided nonces — replay protection is chain-native (EIP-712, etc.)
|
|
15
|
+
- No request binding beyond resource URL
|
|
16
|
+
- `accepts` array in challenge for multi-chain/multi-scheme negotiation
|
|
17
|
+
|
|
18
|
+
**Merkleworks x402** (BSV-specific):
|
|
19
|
+
- Headers: `X402-Challenge` / `X402-Proof`
|
|
20
|
+
- Client broadcasts, server checks mempool (proof-of-payment model)
|
|
21
|
+
- Server-provided 1-sat nonce UTXO for challenge binding + replay protection
|
|
22
|
+
- Strong request binding (method, path, query, headers hash, body hash)
|
|
23
|
+
- Single-chain, single-scheme
|
|
24
|
+
|
|
25
|
+
**Our position**: Our PayGateway implements the Coinbase v2 header spec (`Payment-Required` / `Payment-Signature` / `Payment-Response`) with BSV as the settlement network. This makes BSV a first-class citizen in the broader x402 ecosystem — any Coinbase-compatible server or client can interoperate with us. We also support merkleworks via a separate ProofGateway that uses the `X402-*` headers. The `X402-*` headers are merkleworks' territory; we speak the standard x402 v2 language for our own scheme.
|
|
26
|
+
|
|
27
|
+
## Component Boundaries
|
|
28
|
+
|
|
29
|
+
### Gatekeeper (`X402::Middleware`)
|
|
30
|
+
|
|
31
|
+
The Rack middleware. A pure dispatcher with no blockchain knowledge. It:
|
|
32
|
+
|
|
33
|
+
1. **On request**: polls each configured gateway for challenge headers, returns all of them in the 402 response
|
|
34
|
+
2. **On proof**: checks which proof/payment header the client sent, dispatches to the matching gateway
|
|
35
|
+
3. **On result**: the gateway returns allow/deny — the middleware serves or rejects accordingly
|
|
36
|
+
|
|
37
|
+
The gatekeeper **MUST NOT sign transactions or hold private keys** (Invariant A-2 from the merkleworks spec). It manages HTTP headers, route matching, and dispatch. Everything else is the gateway's job.
|
|
38
|
+
|
|
39
|
+
### Gateway (`X402::BSV::ProofGateway`, `X402::BSV::PayGateway`)
|
|
40
|
+
|
|
41
|
+
The bridge between the gatekeeper and the BSV network. Gateways **can** hold keys and sign transactions — they are separate components from the gatekeeper. Each gateway:
|
|
42
|
+
|
|
43
|
+
- Builds challenge data (including partial transaction templates)
|
|
44
|
+
- Verifies and settles proofs
|
|
45
|
+
- Interacts with ARC and/or a treasury service
|
|
46
|
+
|
|
47
|
+
### Treasury (external service or local key)
|
|
48
|
+
|
|
49
|
+
Holds the nonce key, mints nonce UTXOs. The ProofGateway either talks to an external treasury service (`wallet_url`) or holds a key directly (`nonce_key`). The PayGateway doesn't need a treasury.
|
|
50
|
+
|
|
51
|
+
### Delegator (separate service, not our concern)
|
|
52
|
+
|
|
53
|
+
Adds fee inputs to client-constructed partial txs, signs only its fee inputs. Lives between the client and the network. The gatekeeper and gateways never talk to it.
|
|
54
|
+
|
|
55
|
+
## Architecture: Unified Template Model
|
|
56
|
+
|
|
57
|
+
### The key insight: both gateways produce partial transaction templates
|
|
58
|
+
|
|
59
|
+
The challenge is not just metadata — it includes a **partial transaction template** that the client extends by adding funding inputs (and optionally change outputs).
|
|
60
|
+
|
|
61
|
+
**Base behaviour** (all gateways): build a partial tx with the payment output (amount to payee). This is the invoice.
|
|
62
|
+
|
|
63
|
+
**ProofGateway override**: prepend the nonce UTXO input at index 0, signed with `SIGHASH_SINGLE | ANYONECANPAY | FORKID (0xC3)`. This locks the payment output (at output index 0) while allowing the client to append inputs and outputs freely. Then add the payment output.
|
|
64
|
+
|
|
65
|
+
**PayGateway**: just the base behaviour. Payment output, no nonce.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
# Pseudocode
|
|
69
|
+
class Gateway
|
|
70
|
+
def build_template(route, config)
|
|
71
|
+
tx = Transaction.new
|
|
72
|
+
tx.add_output(payee: config.payee_locking_script_hex, amount: route.amount_sats)
|
|
73
|
+
tx
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class ProofGateway < Gateway
|
|
78
|
+
def build_template(route, config)
|
|
79
|
+
tx = Transaction.new
|
|
80
|
+
nonce = fetch_nonce # from treasury
|
|
81
|
+
# Input 0: nonce UTXO, signed with 0xC3
|
|
82
|
+
# SIGHASH_SINGLE commits to output[0] only
|
|
83
|
+
# ANYONECANPAY allows additional inputs
|
|
84
|
+
tx.add_signed_input(nonce, key: @nonce_key, sighash: 0xC3)
|
|
85
|
+
# Output 0: payment (locked by the 0xC3 signature)
|
|
86
|
+
tx.add_output(payee: config.payee_locking_script_hex, amount: route.amount_sats)
|
|
87
|
+
tx
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class PayGateway < Gateway
|
|
92
|
+
# inherits build_template — just the payment output, no nonce
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The client's job is identical regardless of scheme: add funding inputs, sign them, and either broadcast (BSV-proof) or hand to the server (BSV-pay). The delegator fits the same way in both flows — it receives the extended template and appends fee inputs.
|
|
97
|
+
|
|
98
|
+
### Why 0xC3 for the nonce signature
|
|
99
|
+
|
|
100
|
+
`SIGHASH_SINGLE | ANYONECANPAY | FORKID`:
|
|
101
|
+
- `SIGHASH_SINGLE`: commits only to `output[input_index]` — since the nonce is at input 0, the signature protects only output 0 (the payment). Additional outputs (change, etc.) can be added freely.
|
|
102
|
+
- `ANYONECANPAY`: excludes other inputs — funding inputs and fee inputs can be appended without invalidating the gateway's signature.
|
|
103
|
+
- `FORKID`: BSV fork ID flag (required).
|
|
104
|
+
|
|
105
|
+
Using `0xC1` (`SIGHASH_ALL | ANYONECANPAY`) would commit to ALL outputs, breaking the extensibility model.
|
|
106
|
+
|
|
107
|
+
## Multi-protocol Support
|
|
108
|
+
|
|
109
|
+
### HTTP headers
|
|
110
|
+
|
|
111
|
+
| Scheme | Challenge header | Proof header | Receipt header |
|
|
112
|
+
|--------|-----------------|--------------|----------------|
|
|
113
|
+
| BSV-pay (ours) | `Payment-Required` | `Payment-Signature` | `Payment-Response` |
|
|
114
|
+
| BSV-proof (merkleworks) | `X402-Challenge` | `X402-Proof` | — |
|
|
115
|
+
|
|
116
|
+
Our PayGateway uses the Coinbase v2 headers — the standard x402 ecosystem language. The ProofGateway uses the merkleworks `X402-*` headers. A server running both sends both:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
HTTP/1.1 402 Payment Required
|
|
120
|
+
Payment-Required: <base64(v2 PaymentRequired JSON with BSV in accepts)>
|
|
121
|
+
X402-Challenge: <base64url(merkleworks challenge JSON)>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The `Payment-Required` challenge follows the Coinbase v2 structure. The `extra` field carries our BSV-specific enhancements — specifically the partial tx template built by `Gateway.build_template`:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"x402Version": 2,
|
|
129
|
+
"resource": { "url": "/api/expensive" },
|
|
130
|
+
"accepts": [
|
|
131
|
+
{
|
|
132
|
+
"scheme": "exact",
|
|
133
|
+
"network": "bsv:mainnet",
|
|
134
|
+
"amount": "100",
|
|
135
|
+
"asset": "BSV",
|
|
136
|
+
"payTo": "1A1zP1...",
|
|
137
|
+
"maxTimeoutSeconds": 60,
|
|
138
|
+
"extra": {
|
|
139
|
+
"partialTx": "<base64 of partial tx template from Gateway.build_template>"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Progressive enhancement via `extra.partialTx`
|
|
147
|
+
|
|
148
|
+
The `extra` field is the Coinbase v2 mechanism for scheme-specific data (EVM uses it for `assetTransferMethod`, `name`, `version`). We use it to carry the partial tx template.
|
|
149
|
+
|
|
150
|
+
**Basic client** (any x402 v2 client): ignores `extra.partialTx`, constructs a tx from scratch using `payTo` + `amount`. This works but has the drawbacks of Profile A — the client must know how to build a BSV transaction from an address.
|
|
151
|
+
|
|
152
|
+
**Smart client** (BSV-aware): reads `extra.partialTx`, deserialises the partial tx, extends it by adding funding inputs and signing. This is Profile B — the gateway has already constructed the payment output. The client just funds it.
|
|
153
|
+
|
|
154
|
+
The template is an optimisation, not a requirement. The `payTo` + `amount` fields are the canonical payment specification and always sufficient on their own. The template is the gateway saying "here, I've already started building this for you."
|
|
155
|
+
|
|
156
|
+
This maps directly to `X402::BSV::Gateway.build_template`:
|
|
157
|
+
- **PayGateway**: template has payment output only → `extra.partialTx` contains a tx with one output
|
|
158
|
+
- **ProofGateway** (if it also contributed to `Payment-Required`): template has nonce input (signed 0xC3) + payment output → `extra.partialTx` contains both
|
|
159
|
+
|
|
160
|
+
BSV appears in the `accepts` array alongside any other chains. A multi-chain server could offer USDC on Base and BSV in the same `Payment-Required` header. The `extra.partialTx` is invisible to non-BSV clients.
|
|
161
|
+
|
|
162
|
+
### Gateway interface
|
|
163
|
+
|
|
164
|
+
Each gateway implements:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
# #challenge_headers(rack_request, route) → Hash
|
|
168
|
+
# Returns header name → value pairs for the 402 response.
|
|
169
|
+
# A gateway may contribute to multiple headers.
|
|
170
|
+
#
|
|
171
|
+
# #proof_header_names → Array<String>
|
|
172
|
+
# The HTTP header names this gateway recognises as proofs.
|
|
173
|
+
#
|
|
174
|
+
# #settle!(header_name, proof_payload, rack_request, route) → result
|
|
175
|
+
# Verify and settle the proof. Raises X402::VerificationError on failure.
|
|
176
|
+
# Returns a result object (with optional receipt headers).
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Middleware dispatch flow
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
# Simplified — challenge issuance
|
|
183
|
+
def issue_challenge(rack_request, route)
|
|
184
|
+
headers = {}
|
|
185
|
+
config.gateways.each do |gw|
|
|
186
|
+
gw.challenge_headers(rack_request, route).each do |name, value|
|
|
187
|
+
headers[name] = value
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
[402, headers, []]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Simplified — proof dispatch
|
|
194
|
+
def verify_proof(rack_request, route)
|
|
195
|
+
config.gateways.each do |gw|
|
|
196
|
+
gw.proof_header_names.each do |header_name|
|
|
197
|
+
proof = rack_request.get_header(header_name)
|
|
198
|
+
next unless proof
|
|
199
|
+
return gw.settle!(header_name, proof, rack_request, route)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
raise X402::Error, "no recognised proof header"
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Configuration
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
X402.configure do |config|
|
|
210
|
+
config.domain = "api.example.com"
|
|
211
|
+
config.payee_locking_script_hex = "76a914...88ac"
|
|
212
|
+
|
|
213
|
+
config.gateways = [
|
|
214
|
+
X402::BSV::ProofGateway.new(
|
|
215
|
+
nonce_key: "...", # or wallet_url: "https://treasury.example.com"
|
|
216
|
+
arc_url: "https://arc.taal.com",
|
|
217
|
+
arc_api_key: "..."
|
|
218
|
+
),
|
|
219
|
+
X402::BSV::PayGateway.new(
|
|
220
|
+
arc_url: "https://arc.taal.com",
|
|
221
|
+
arc_api_key: "..."
|
|
222
|
+
)
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
config.protect method: :GET, path: "/api/expensive", amount_sats: 100
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Two BSV Schemes
|
|
230
|
+
|
|
231
|
+
### BSV-proof (merkleworks x402 spec)
|
|
232
|
+
|
|
233
|
+
Implements the merkleworks protocol. Client broadcasts, server checks mempool.
|
|
234
|
+
|
|
235
|
+
**Headers**: `X402-Challenge` / `X402-Proof`
|
|
236
|
+
|
|
237
|
+
**Challenge**: merkleworks JSON format including a pre-signed partial tx template (Profile B) with nonce UTXO at input 0 signed with `0xC3`, payment output at output 0, plus request binding metadata (method, path, query, headers hash, body hash), expiry, and `require_mempool_accept: true`.
|
|
238
|
+
|
|
239
|
+
**Client flow**: extend template with funding inputs → delegator adds fees → broadcast → retry with proof (txid + rawtx)
|
|
240
|
+
|
|
241
|
+
**Settlement flow**:
|
|
242
|
+
1. Decode proof JSON (echoed challenge + payment txid + rawtx)
|
|
243
|
+
2. Verify echoed challenge (hash, binding, expiry)
|
|
244
|
+
3. Decode raw tx
|
|
245
|
+
4. Verify nonce UTXO spent as input at index 0 (challenge binding)
|
|
246
|
+
5. Verify payment output (amount + payee)
|
|
247
|
+
6. Check mempool visibility via ARC
|
|
248
|
+
7. Return result
|
|
249
|
+
|
|
250
|
+
**Requires**: Treasury (for nonce provision + template signing) + ARC (for mempool queries).
|
|
251
|
+
|
|
252
|
+
**Why client broadcasts** (per Rui at merkleworks): Broadcasting is settlement, not authorisation. If the server broadcasts, it takes on transaction submission responsibility, retry/reconciliation logic, and mempool interaction state — pushing it towards a stateful payment processor. Client-side broadcast keeps the server stateless and HTTP authorisation cleanly decoupled from network settlement.
|
|
253
|
+
|
|
254
|
+
### BSV-pay (our BSV-native scheme)
|
|
255
|
+
|
|
256
|
+
Server broadcasts via ARC. Simpler flow, no nonces needed. Uses Coinbase v2 header spec.
|
|
257
|
+
|
|
258
|
+
**Headers**: `Payment-Required` / `Payment-Signature` / `Payment-Response`
|
|
259
|
+
|
|
260
|
+
**Challenge**: Coinbase v2 `PaymentRequired` structure with `extra.partialTx` carrying the template from `Gateway.build_template` (payment output only, no nonce). See [Multi-protocol Support > Progressive enhancement](#progressive-enhancement-via-extrapartialtx) for the full JSON structure.
|
|
261
|
+
|
|
262
|
+
A basic client constructs a tx from `payTo` + `amount`. A smart client extends `extra.partialTx` by adding funding inputs, signs, and hands the completed tx to the server.
|
|
263
|
+
|
|
264
|
+
**Payment payload** (Coinbase v2 `PaymentPayload` structure with BSV-specific payload):
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"x402Version": 2,
|
|
268
|
+
"accepted": {
|
|
269
|
+
"scheme": "exact",
|
|
270
|
+
"network": "bsv:mainnet",
|
|
271
|
+
"amount": "100",
|
|
272
|
+
"asset": "BSV",
|
|
273
|
+
"payTo": "1A1zP1...",
|
|
274
|
+
"maxTimeoutSeconds": 60
|
|
275
|
+
},
|
|
276
|
+
"payload": {
|
|
277
|
+
"rawtx": "<hex-encoded raw transaction>",
|
|
278
|
+
"txid": "<double-SHA256 of raw tx bytes>"
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Settlement flow**:
|
|
284
|
+
1. Decode `Payment-Signature` header (base64 → PaymentPayload JSON)
|
|
285
|
+
2. Verify `accepted` matches a valid route
|
|
286
|
+
3. Decode raw tx from `payload.rawtx`
|
|
287
|
+
4. Verify payment output (amount + payee)
|
|
288
|
+
5. Verify OP_RETURN request binding (if present or if strict mode)
|
|
289
|
+
6. Broadcast to ARC
|
|
290
|
+
7. ARC 200 → return `Payment-Response` header with settlement result
|
|
291
|
+
8. ARC error → raise VerificationError (relay ARC response to client)
|
|
292
|
+
|
|
293
|
+
**No nonces needed**: ARC is the replay gate. Each tx can only be accepted once.
|
|
294
|
+
|
|
295
|
+
**Request binding via OP_RETURN**: The partial tx template includes an OP_RETURN output binding the payment to the specific request:
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
Output 0: payment (amount to payee)
|
|
299
|
+
Output 1: OP_RETURN <SHA256(method + path + query)>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The gateway adds this when building the template. At settlement time, the gateway recomputes the hash from the current request and verifies it matches the OP_RETURN in the submitted tx. This prevents a client from taking a template generated for one URL and submitting it to a different endpoint on the same server.
|
|
303
|
+
|
|
304
|
+
The binding is in the template itself — the client can't modify it because they only append inputs and outputs at higher indices. Cheap (~30 bytes), on-chain, and verifiable.
|
|
305
|
+
|
|
306
|
+
For basic clients that ignore `extra.partialTx` and construct a tx from scratch, binding isn't enforced (they won't include the OP_RETURN). The gateway can choose to require it or not via config — strict mode rejects txs without the binding output, permissive mode accepts either.
|
|
307
|
+
|
|
308
|
+
**Requires**: ARC only. No treasury, no nonce provision.
|
|
309
|
+
|
|
310
|
+
**ARC as the settlement oracle**:
|
|
311
|
+
- ARC 200 OK → relay client request to application
|
|
312
|
+
- Anything else → relay ARC response to client
|
|
313
|
+
|
|
314
|
+
See: https://docs.bsvblockchain.org/important-concepts/details/spv/broadcasting
|
|
315
|
+
|
|
316
|
+
**ARC configuration defaults**:
|
|
317
|
+
- `arc_wait_for: "SEEN_ON_NETWORK"` — tx has propagated to multiple nodes. Safe default for micropayments.
|
|
318
|
+
- `arc_timeout: 5` — seconds. Returns 504 Gateway Timeout if ARC doesn't respond.
|
|
319
|
+
- Both configurable per gateway. Lower-value endpoints could use `ACCEPTED_BY_NETWORK` for speed. Higher-value could bump the timeout.
|
|
320
|
+
|
|
321
|
+
### Comparison
|
|
322
|
+
|
|
323
|
+
| | BSV-proof (merkleworks) | BSV-pay (ours) |
|
|
324
|
+
|---|---|---|
|
|
325
|
+
| Header spec | Merkleworks `X402-*` | Coinbase v2 `Payment-*` |
|
|
326
|
+
| Challenge header | `X402-Challenge` | `Payment-Required` |
|
|
327
|
+
| Proof header | `X402-Proof` | `Payment-Signature` |
|
|
328
|
+
| Receipt header | — | `Payment-Response` |
|
|
329
|
+
| Template contains | Nonce input (signed 0xC3) + payment output | Payment output + OP_RETURN binding |
|
|
330
|
+
| Who broadcasts | Client | Server (via ARC) |
|
|
331
|
+
| Nonce needed | Yes (challenge binding) | No (ARC is replay gate) |
|
|
332
|
+
| Request binding | Yes (in challenge metadata) | Yes (OP_RETURN in template) |
|
|
333
|
+
| Settlement check | Mempool visibility query | ARC broadcast response |
|
|
334
|
+
| Treasury needed | Yes (nonce provision + signing) | No |
|
|
335
|
+
| Minimum infrastructure | Treasury + ARC | ARC only |
|
|
336
|
+
| Ecosystem compatibility | Merkleworks BSV clients | Any x402 v2 client |
|
|
337
|
+
|
|
338
|
+
## File Changes
|
|
339
|
+
|
|
340
|
+
### New files
|
|
341
|
+
- `lib/x402/bsv.rb` — Module entry point
|
|
342
|
+
- `lib/x402/bsv/gateway.rb` — `X402::BSV::Gateway` base class (builds payment-output-only template)
|
|
343
|
+
- `lib/x402/bsv/proof_gateway.rb` — `X402::BSV::ProofGateway` (extends base, adds nonce, merkleworks settlement)
|
|
344
|
+
- `lib/x402/bsv/pay_gateway.rb` — `X402::BSV::PayGateway` (extends base, ARC broadcast settlement)
|
|
345
|
+
|
|
346
|
+
### Modified files
|
|
347
|
+
- `lib/x402/configuration.rb` — Replace `nonce_provider` + `arc_url`/`arc_api_key` with `gateways` array
|
|
348
|
+
- `lib/x402/middleware.rb` — Multi-gateway dispatch (poll for challenges, route proofs)
|
|
349
|
+
- `lib/x402/protocol/challenge.rb` — Challenge building moves into gateway
|
|
350
|
+
- `lib/x402/verification/pipeline.rb` — Pipeline moves into gateway
|
|
351
|
+
- `lib/x402.rb` — Require `x402/bsv`
|
|
352
|
+
- `DESIGN.md` — Update to reflect dispatcher architecture, template model, two schemes, ecosystem context
|
|
353
|
+
|
|
354
|
+
### Removed
|
|
355
|
+
- `lib/x402/verification/settlement_checks.rb` — Logic moves into gateway classes
|
|
356
|
+
|
|
357
|
+
### Specs
|
|
358
|
+
- New specs for `X402::BSV::Gateway`, `X402::BSV::ProofGateway`, `X402::BSV::PayGateway`
|
|
359
|
+
- Update middleware specs for multi-gateway dispatch
|
|
360
|
+
- Update configuration specs
|
|
361
|
+
|
|
362
|
+
## DESIGN.md Updates
|
|
363
|
+
|
|
364
|
+
1. **Component boundaries**: Gatekeeper (middleware, no keys), Gateway (holds keys, signs, settles), Treasury (external or local nonce service), Delegator (separate, not our concern).
|
|
365
|
+
2. **Unified template model**: all gateways produce partial tx templates. ProofGateway adds nonce at index 0 with 0xC3. PayGateway produces payment output only.
|
|
366
|
+
3. **Middleware as dispatcher**: no blockchain knowledge, polls gateways for challenges, routes proofs.
|
|
367
|
+
4. **Multi-protocol headers**: payment content negotiation across ecosystems.
|
|
368
|
+
5. **Two BSV schemes**: BSV-proof (merkleworks) and BSV-pay (ours). Same template model, different settlement.
|
|
369
|
+
6. **ARC as settlement oracle**: authoritative for BSV-pay.
|
|
370
|
+
7. **Ecosystem context**: merkleworks BSV spec, Coinbase v2 broad ecosystem, our position.
|
|
371
|
+
|
|
372
|
+
## Verification
|
|
373
|
+
|
|
374
|
+
1. `bundle exec rake spec` — all tests pass
|
|
375
|
+
2. `bundle exec rubocop` — no lint violations
|
|
376
|
+
3. Boundary test: could someone write `X402::EVM::Gateway` implementing `challenge_headers`, `proof_header_names`, and `settle!` without touching `lib/x402/`? Yes.
|
|
377
|
+
|
|
378
|
+
## Future Considerations (deferred)
|
|
379
|
+
|
|
380
|
+
- **Coinbase facilitator pattern**: verify-then-settle two-step. Our PayGateway could support this — verify tx structure first, serve resource, broadcast to ARC after.
|
|
381
|
+
- **Extensions mechanism**: gas sponsoring, request binding as extension, custom auth schemes.
|
|
382
|
+
- **Multi-chain accepts array**: BSV alongside EVM chains in the same `Payment-Required` header — now possible since we use the v2 structure.
|
|
383
|
+
- **Runar contract AST**: client sends ContractNode AST, gateway compiles via runar. A future scheme on the same dispatcher.
|
|
384
|
+
- **Header size**: standard BSV payment txs are ~400-600 bytes base64-encoded, well within 8KB+ server limits. Multipart form data for POST requests if this becomes an issue.
|