agent-receipt-verify 0.2.2 → 0.2.3

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.
package/bin/cli.mjs CHANGED
@@ -57,6 +57,11 @@ function gebruik() {
57
57
  console.log(' the same ten vectors under both amount policies, plus the two cases no')
58
58
  console.log(' published vector covers: overpayment, and one nonce across two payers.')
59
59
  console.log(' Those two are the open questions in x402 issue 2725.')
60
+ console.log('')
61
+ console.log(' npx github:holistis/agent-receipt-verify link-vectors')
62
+ console.log(' twelve vectors for the half AFTER paying: does this receipt belong to')
63
+ console.log(' that one settlement? Eleven run offline, the chain-reading one is')
64
+ console.log(' marked and skipped rather than failed.')
60
65
  }
61
66
 
62
67
  // Het argument bepaalt welke module draait. De modules zijn ook los te draaien en te
@@ -70,6 +75,7 @@ const onderdelen = {
70
75
  liveness: '../src/x402-index-liveness.mjs',
71
76
  kanon: '../src/eip3009-autorisatie.mjs',
72
77
  beleid: '../bench/eip3009-beleid.mjs',
78
+ 'link-vectors': '../src/settlement-link-vectors.mjs',
73
79
  }
74
80
 
75
81
  if (!eerste || eerste === '--help' || eerste === '-h' || eerste === 'help') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-receipt-verify",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Checks x402 endpoint conformance before an agent pays, and links the receipt to its settlement.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -8,7 +8,7 @@
8
8
  "node": ">=20"
9
9
  },
10
10
  "scripts": {
11
- "test": "node test/interop.mjs && node test/canonical.mjs && node test/link-onchain.mjs && node test/profiel.mjs && node test/eip3009.mjs && node test/conformance.mjs && node test/jws-offer.mjs && node test/census.mjs && node test/poortwachters.mjs && node test/metingen-bundel.mjs && node test/mcp-server.mjs && node test/cli.mjs && node test/bundel.mjs",
11
+ "test": "node test/interop.mjs && node test/canonical.mjs && node test/link-onchain.mjs && node test/profiel.mjs && node test/eip3009.mjs && node test/conformance.mjs && node test/jws-offer.mjs && node test/census.mjs && node test/poortwachters.mjs && node test/metingen-bundel.mjs && node test/mcp-server.mjs && node test/cli.mjs && node test/bundel.mjs && node test/settlement-link-vectors.mjs",
12
12
  "link-example": "node src/x402-link-onchain.mjs --receipt=examples/receipt-live-base-sepolia.json && node src/x402-link-onchain.mjs --receipt=examples/receipt-live-base-sepolia-2.json && node src/x402-link-onchain.mjs --receipt=examples/receipt-live-base-sepolia-3.json",
13
13
  "conformance": "node src/x402-conformance-check.mjs",
14
14
  "bench": "node bench/robustness.mjs",
@@ -19,7 +19,8 @@
19
19
  "mcp": "node src/mcp-server.mjs",
20
20
  "bundel": "node tools/bundel-bouw.mjs build/bundle",
21
21
  "kanon": "node src/eip3009-autorisatie.mjs --vectoren",
22
- "bench-eip3009": "node bench/eip3009-beleid.mjs"
22
+ "bench-eip3009": "node bench/eip3009-beleid.mjs",
23
+ "link-vectors": "node src/settlement-link-vectors.mjs"
23
24
  },
24
25
  "optionalDependencies": {
25
26
  "ethers": "^6.17.0"
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ // Draait de afrekening-vectoren: hoort dit bonnetje bij die ene betaling?
3
+ //
4
+ // WAAROM DIT EEN LOPER IS EN NIET EEN DOCUMENT. Een vectorbestand dat niemand kan
5
+ // draaien is een bewering met extra stappen. Dit commando leest de vectoren, rekent elk
6
+ // commitment opnieuw uit, en vergelijkt dat met wat er verwacht wordt. Een vreemde
7
+ // draait het zonder account, zonder sleutel, en voor elf van de twaalf gevallen ook
8
+ // zonder internet.
9
+ //
10
+ // DE DRIE UITKOMSTEN, en de derde is het hele punt. LINKED als het commitment op
11
+ // precies een kandidaat opent. NOT_LINKED als er kandidaten waren en geen enkele opent.
12
+ // NOT_MEASURED als er geen kandidatenlijst is VERKREGEN. Dat laatste is geen afkeuring
13
+ // van het bonnetje maar een onvolledige controle, en een woordenlijst met twee waarden
14
+ // kan dat verschil niet uitdrukken. Precies dat gat benoemde de auteur van de
15
+ // kanon-vectoren op 2026-10-17 als iets dat hij wil toevoegen.
16
+ //
17
+ // LET OP HET VERSCHIL TUSSEN LEEG EN AFWEZIG. `candidates: null` betekent dat er geen
18
+ // lijst is opgehaald, `candidates: []` dat er wel een lijst is en die leeg was. Het
19
+ // eerste is NOT_MEASURED, het tweede NOT_LINKED. Een loper die die twee door elkaar
20
+ // haalt, gooit geldige bonnetjes weg bij een haperende RPC.
21
+ //
22
+ // Gebruik:
23
+ // node src/settlement-link-vectors.mjs de offline vectoren
24
+ // npx agent-receipt-verify link-vectors hetzelfde, als commando
25
+
26
+ import fs from 'node:fs'
27
+ import path from 'node:path'
28
+ import { fileURLToPath } from 'node:url'
29
+ import { berekenCommitment } from './x402-offer-receipt.mjs'
30
+
31
+ const WORTEL = fileURLToPath(new URL('..', import.meta.url))
32
+ export const VECTORMAP = path.join(WORTEL, 'vectors', 'settlement-link')
33
+
34
+ /**
35
+ * Beoordeel een enkele vector, puur rekenkundig.
36
+ *
37
+ * Geeft altijd een `outcome` terug uit de drie, nooit een throw voor een gewone
38
+ * uitkomst: een verifieerder die bij een ongeldig bonnetje crasht, is zelf de storing.
39
+ */
40
+ export function beoordeelVector(v) {
41
+ if (v.requires_chain_read) return { outcome: 'SKIPPED', reden: 'requires an RPC; run the link command for this one' }
42
+
43
+ // Het geval met twee bonnetjes tegelijk: elk moet op zijn eigen kandidaat openen en
44
+ // op geen enkele andere.
45
+ if (Array.isArray(v.input?.receipts)) {
46
+ const kandidaten = v.input.candidates ?? []
47
+ const mapping = []
48
+ for (const [i, r] of v.input.receipts.entries()) {
49
+ const p = r.receipt.payload
50
+ const passend = kandidaten.filter((k) =>
51
+ String(berekenCommitment({
52
+ method: p.authorizationMethod ?? 'eip3009',
53
+ asset: r.asset,
54
+ authorizer: k.authorizer,
55
+ identifier: k.nonce,
56
+ salt: r.authorizationSalt,
57
+ })).toLowerCase() === String(p.authorizationCommitment).toLowerCase())
58
+ if (passend.length !== 1) {
59
+ return { outcome: 'NOT_LINKED', reden: `receipt ${i} opened on ${passend.length} candidates, expected exactly one` }
60
+ }
61
+ mapping.push({ receipt_index: i, identifier: passend[0].nonce })
62
+ }
63
+ return { outcome: 'LINKED', mapping }
64
+ }
65
+
66
+ const p = v.input?.receipt?.payload
67
+ if (!p) return { outcome: 'NOT_MEASURED', reden: 'no receipt payload in the vector' }
68
+ if (!p.authorizationCommitment) {
69
+ // Een bonnetje van versie 1 draagt geen commitment. Dat is geen fout van het
70
+ // bonnetje en ook geen afkeuring, er valt alleen niets te openen.
71
+ return { outcome: 'NOT_MEASURED', reden: 'receipt is version 1 and carries no commitment' }
72
+ }
73
+
74
+ const kandidaten = v.input.candidates
75
+ if (kandidaten == null) return { outcome: 'NOT_MEASURED', reden: 'no candidate set was obtained' }
76
+
77
+ const passend = kandidaten.filter((k) =>
78
+ String(berekenCommitment({
79
+ method: p.authorizationMethod ?? 'eip3009',
80
+ asset: v.input.asset,
81
+ authorizer: k.authorizer,
82
+ identifier: k.nonce,
83
+ salt: v.input.authorizationSalt,
84
+ })).toLowerCase() === String(p.authorizationCommitment).toLowerCase())
85
+
86
+ if (passend.length === 1) return { outcome: 'LINKED', identifier: passend[0].nonce }
87
+ if (passend.length === 0) return { outcome: 'NOT_LINKED', reden: `none of ${kandidaten.length} candidates opened the commitment` }
88
+ return { outcome: 'AMBIGUOUS', reden: `${passend.length} candidates opened the same commitment, which a random 32 byte salt should make practically impossible` }
89
+ }
90
+
91
+ export function laadVectoren(map = VECTORMAP) {
92
+ if (!fs.existsSync(map)) return []
93
+ return fs.readdirSync(map).filter((f) => f.endsWith('.json')).sort()
94
+ .map((f) => ({ bestand: f, ...JSON.parse(fs.readFileSync(path.join(map, f), 'utf8')) }))
95
+ }
96
+
97
+ const ditBestand = fileURLToPath(import.meta.url)
98
+ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(ditBestand)) {
99
+ const vectoren = laadVectoren()
100
+ if (!vectoren.length) {
101
+ console.error('no vectors found in vectors/settlement-link')
102
+ process.exitCode = 1
103
+ } else {
104
+ console.log('settlement-link vectors: does this receipt belong to that one payment?')
105
+ console.log('')
106
+ let goed = 0, fout = 0, overgeslagen = 0
107
+ for (const v of vectoren) {
108
+ const u = beoordeelVector(v)
109
+ if (u.outcome === 'SKIPPED') {
110
+ overgeslagen++
111
+ console.log(` skip ${v.id}`)
112
+ console.log(` ${u.reden}`)
113
+ continue
114
+ }
115
+ const ok = u.outcome === v.expected.outcome
116
+ if (ok) goed++; else fout++
117
+ console.log(` ${ok ? 'pass' : 'FAIL'} ${v.id}`)
118
+ console.log(` expected ${v.expected.outcome}, got ${u.outcome}${u.reden ? ' (' + u.reden + ')' : ''}`)
119
+ }
120
+ console.log('')
121
+ console.log(`${goed} agree, ${fout} disagree, ${overgeslagen} skipped because they need an RPC`)
122
+ console.log('')
123
+ console.log('Every positive case here was re-derived from the signed receipt, not copied:')
124
+ console.log('the commitment is recomputed from tag, method, asset, authorizer, identifier and')
125
+ console.log('salt, and compared against what the seller actually signed.')
126
+ process.exitCode = fout === 0 ? 0 : 1
127
+ }
128
+ }
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env node
2
+ // Bouwt testvectoren voor de helft NA het betalen: hoort dit bonnetje bij die ene
3
+ // afrekening?
4
+ //
5
+ // WAAROM DEZE ER ZIJN. De gepubliceerde vectoren van iamonuwa/kanon dekken de
6
+ // AUTORISATIE van een koper voor de afrekening. Niemand heeft vectoren gepubliceerd
7
+ // voor de stap erna. In x402#3396 zei de auteur van die vectoren op 2026-10-17
8
+ // letterlijk: "if you want receipt-to-settlement vectors in Kanon's shape, I'm open to
9
+ // that, with the part that needs a chain read marked as such." Dit bestand maakt
10
+ // precies dat, in zijn vorm, met dat veld.
11
+ //
12
+ // DE SPLITSING DIE HIJ VROEG, en die is inhoudelijk het hele punt. Een bonnetje aan een
13
+ // afrekening binden bestaat uit twee stappen met heel verschillende eisen:
14
+ //
15
+ // 1. VIND de kandidaat-nonces die deze betaler in dit venster verbruikte.
16
+ // Dat vereist een ketenlezing. Niet reproduceerbaar zonder RPC, en de uitkomst
17
+ // hangt af van hoe ver je terugkijkt.
18
+ // 2. OPEN het commitment op precies een van die kandidaten.
19
+ // Dat is pure rekenkunde: sha256 over tag, methode, asset, autorisator,
20
+ // identifier en zout. Offline, deterministisch, en voor altijd reproduceerbaar.
21
+ //
22
+ // Stap 2 is waar de spec iets beweert, en stap 2 heeft GEEN keten nodig zodra je de
23
+ // kandidaten meegeeft. Daarom draagt elke vector hier `requires_chain_read`, en zijn de
24
+ // offline vectoren degene waar een vreemde iets aan heeft.
25
+ //
26
+ // ALLE DATA IS ECHT. De bonnetjes komen uit echte betalingen op Base Sepolia aan
27
+ // https://api.tokenizen.nl/api/paid/verify-ownership. De nonces en transactiehashes
28
+ // zijn door onze eigen linker op de keten gevonden. Koper en verkoper zijn allebei wij,
29
+ // dus dit bewijst dat de keten sluit, niet dat iemand anders hem gebruikt.
30
+ //
31
+ // DEZE GENERATOR WEIGERT TE LIEGEN. Voor elke positieve vector wordt het commitment
32
+ // opnieuw berekend uit de losse velden en vergeleken met wat in het ondertekende
33
+ // bonnetje staat. Klopt dat niet, dan schrijft hij niets en faalt hij luid. Een
34
+ // vectorbestand dat zichzelf niet opent is erger dan geen vectorbestand, want iedereen
35
+ // die het gebruikt bouwt op een fout.
36
+ //
37
+ // Gebruik: node tools/bouw-afrekening-vectoren.mjs [--droog]
38
+
39
+ import fs from 'node:fs'
40
+ import path from 'node:path'
41
+ import { fileURLToPath } from 'node:url'
42
+ import { berekenCommitment } from '../src/x402-offer-receipt.mjs'
43
+
44
+ const WORTEL = fileURLToPath(new URL('..', import.meta.url))
45
+ const UIT = path.join(WORTEL, 'vectors', 'settlement-link')
46
+
47
+ // De drie echte betalingen, met de afrekening die onze linker op de keten vond.
48
+ // De nonce staat hier NIET omdat wij hem kozen, maar omdat hij gevonden is; de
49
+ // controle hieronder bewijst dat hij bij dit bonnetje hoort.
50
+ const ECHT = [
51
+ {
52
+ bestand: 'receipt-live-base-sepolia.json',
53
+ nonce: '0x1120412c78ea1b2e8bf9eacfc3264c71a23ecd9b1fa579eb16030693344d5dfe',
54
+ tx: '0xed87b09bfb20a8c8b69908e163fcea6432d193d389640c0465afcb5561dfeb30',
55
+ kandidaten: 2,
56
+ },
57
+ {
58
+ bestand: 'receipt-live-base-sepolia-2.json',
59
+ nonce: '0x515895dfac6f148a4cfdf5c4f8266b7859dbd6534cdf77104b1be9f484eb5005',
60
+ tx: '0x2898d0ceb97ddf60af98e19c94fc0adddbdf1e64f9032e62454085e37acfe36b',
61
+ kandidaten: 3,
62
+ },
63
+ {
64
+ bestand: 'receipt-live-base-sepolia-3.json',
65
+ nonce: '0x4b133bbec3ce0d8177a9010784faedb459f04384b45316bb526a5f96c90a70de',
66
+ tx: '0x2a9e95d63a5af8210767baa69143b364dad51caffc29a152c77e03bc508d7199',
67
+ kandidaten: 2,
68
+ },
69
+ ]
70
+
71
+ const lees = (b) => JSON.parse(fs.readFileSync(path.join(WORTEL, 'examples', b), 'utf8'))
72
+
73
+ function basis(id, beschrijving, encodes) {
74
+ return {
75
+ id,
76
+ schema_version: '3.0.0',
77
+ protocol: 'x402',
78
+ scheme: 'exact',
79
+ network: 'eip155:84532',
80
+ asset_transfer_method: 'eip3009',
81
+ encodes,
82
+ provenance: [
83
+ 'x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main',
84
+ 'EIP-3009',
85
+ ],
86
+ description: beschrijving,
87
+ }
88
+ }
89
+
90
+ const vectoren = []
91
+ const fouten = []
92
+
93
+ for (const e of ECHT) {
94
+ const bron = lees(e.bestand)
95
+ const p = bron.receipt.payload
96
+ const betaler = p.payer
97
+
98
+ // DE CONTROLE DIE DEZE GENERATOR EERLIJK HOUDT. Herbereken het commitment uit de
99
+ // losse velden. Opent het niet op de nonce die wij hier opschrijven, dan is onze
100
+ // toewijzing fout en mag er niets geschreven worden.
101
+ const herberekend = berekenCommitment({
102
+ method: 'eip3009',
103
+ asset: bron.asset,
104
+ authorizer: betaler,
105
+ identifier: e.nonce,
106
+ salt: bron.authorizationSalt,
107
+ })
108
+ if (String(herberekend).toLowerCase() !== String(p.authorizationCommitment).toLowerCase()) {
109
+ fouten.push(`${e.bestand}: het commitment opent NIET op de opgegeven nonce. ` +
110
+ `berekend ${herberekend}, in het bonnetje ${p.authorizationCommitment}`)
111
+ continue
112
+ }
113
+
114
+ const kort = e.bestand.replace('receipt-live-base-sepolia', 'live').replace('.json', '')
115
+
116
+ // 1. De offline positieve: de kandidaten zijn MEEGEGEVEN, dus geen keten nodig.
117
+ vectoren.push({
118
+ ...basis(
119
+ `x402-evm-settlement-link-opens-${kort}`,
120
+ 'A version 2 receipt whose authorizationCommitment opens on exactly one of the candidate nonces supplied with it. No chain access is needed because the candidates are given; this is the arithmetic half of section 5.6.4.',
121
+ 'Receipt-to-settlement binding verified offline against a supplied candidate set.',
122
+ ),
123
+ requires_chain_read: false,
124
+ input: {
125
+ receipt: bron.receipt,
126
+ authorizationSalt: bron.authorizationSalt,
127
+ asset: bron.asset,
128
+ candidates: [{ authorizer: betaler, nonce: e.nonce }],
129
+ },
130
+ context: {
131
+ note: 'Real payment on Base Sepolia to https://api.tokenizen.nl/api/paid/verify-ownership. Buyer and seller are the same party, which proves the chain closes end to end and nothing about adoption.',
132
+ settlement_transaction: e.tx,
133
+ },
134
+ expected: { linked: true, outcome: 'LINKED', identifier: e.nonce },
135
+ })
136
+
137
+ // 2. De offline negatieve die het meest zegt: het zout van een ANDER bonnetje.
138
+ // Dit is waar de hele constructie op rust, want zonder het zout zouden twee
139
+ // identieke betalingen niet uit elkaar te houden zijn.
140
+ const ander = ECHT.find((x) => x.bestand !== e.bestand)
141
+ const anderBron = lees(ander.bestand)
142
+ vectoren.push({
143
+ ...basis(
144
+ `x402-evm-settlement-link-wrong-salt-${kort}`,
145
+ 'The same receipt and the same candidate, but the salt of a different payment. The commitment must not open. This is the case the whole construction rests on: without the salt, two payments with identical terms could not be told apart.',
146
+ 'A salt that belongs to another payment must not open this commitment.',
147
+ ),
148
+ requires_chain_read: false,
149
+ input: {
150
+ receipt: bron.receipt,
151
+ authorizationSalt: anderBron.authorizationSalt,
152
+ asset: bron.asset,
153
+ candidates: [{ authorizer: betaler, nonce: e.nonce }],
154
+ },
155
+ context: { note: 'Both salts are real and come from two separate payments with identical terms.' },
156
+ expected: { linked: false, outcome: 'NOT_LINKED' },
157
+ })
158
+
159
+ // 3. De offline negatieve met de nonce van een andere betaling.
160
+ vectoren.push({
161
+ ...basis(
162
+ `x402-evm-settlement-link-wrong-nonce-${kort}`,
163
+ 'The correct receipt and salt, but a candidate nonce that belongs to a different settlement by the same payer. The commitment must not open on it.',
164
+ 'A candidate that is not this payment must not satisfy the commitment.',
165
+ ),
166
+ requires_chain_read: false,
167
+ input: {
168
+ receipt: bron.receipt,
169
+ authorizationSalt: bron.authorizationSalt,
170
+ asset: bron.asset,
171
+ candidates: [{ authorizer: betaler, nonce: ander.nonce }],
172
+ },
173
+ context: { note: 'Both nonces were really consumed by this payer; only one belongs to this receipt.' },
174
+ expected: { linked: false, outcome: 'NOT_LINKED' },
175
+ })
176
+ }
177
+
178
+ // 4. De vector die de twee identieke betalingen naast elkaar zet. Dit is het geval dat
179
+ // een lezer overtuigt, want hier zijn de voorwaarden letterlijk gelijk.
180
+ if (ECHT.length >= 2 && fouten.length === 0) {
181
+ const a = lees(ECHT[0].bestand)
182
+ const b = lees(ECHT[1].bestand)
183
+ vectoren.push({
184
+ ...basis(
185
+ 'x402-evm-settlement-link-two-identical-payments',
186
+ 'Two payments with identical terms (same payer, same asset, same amount, same resource) each open on their own settlement and on no other. Given both candidate sets together, each receipt selects exactly one.',
187
+ 'Disambiguation of two otherwise indistinguishable payments.',
188
+ ),
189
+ requires_chain_read: false,
190
+ input: {
191
+ receipts: [
192
+ { receipt: a.receipt, authorizationSalt: a.authorizationSalt, asset: a.asset },
193
+ { receipt: b.receipt, authorizationSalt: b.authorizationSalt, asset: b.asset },
194
+ ],
195
+ candidates: [
196
+ { authorizer: a.receipt.payload.payer, nonce: ECHT[0].nonce },
197
+ { authorizer: b.receipt.payload.payer, nonce: ECHT[1].nonce },
198
+ ],
199
+ },
200
+ context: {
201
+ note: 'Both are real payments on Base Sepolia with the same terms. Without the salt these two could not be told apart by any later reader.',
202
+ settlements: [ECHT[0].tx, ECHT[1].tx],
203
+ },
204
+ expected: {
205
+ linked: true,
206
+ outcome: 'LINKED',
207
+ mapping: [
208
+ { receipt_index: 0, identifier: ECHT[0].nonce },
209
+ { receipt_index: 1, identifier: ECHT[1].nonce },
210
+ ],
211
+ },
212
+ })
213
+ }
214
+
215
+ // 5. De uitkomst die kanon nog niet kent, en die wij juist wel nodig hebben: een
216
+ // controle die NIET KON DRAAIEN is geen afkeuring. Dit vector beschrijft dat geval
217
+ // zonder een keten nodig te hebben: er zijn geen kandidaten verkregen.
218
+ vectoren.push({
219
+ ...basis(
220
+ 'x402-evm-settlement-link-no-candidates-not-measured',
221
+ 'No candidate set could be obtained, for example because no chain access was available or the lookup window produced nothing. The correct answer is NOT_MEASURED and explicitly not NOT_LINKED: a check that could not run is not a receipt that fails. This is the third outcome a two-valued vocabulary cannot express.',
222
+ 'An incomplete check must be distinguishable from a failed one.',
223
+ ),
224
+ requires_chain_read: false,
225
+ input: {
226
+ receipt: lees(ECHT[0].bestand).receipt,
227
+ authorizationSalt: lees(ECHT[0].bestand).authorizationSalt,
228
+ asset: lees(ECHT[0].bestand).asset,
229
+ candidates: null,
230
+ },
231
+ context: { note: 'candidates is null rather than an empty list, and the two mean different things: null is "we did not obtain a list", empty is "we obtained a list and it was empty".' },
232
+ expected: { linked: null, outcome: 'NOT_MEASURED' },
233
+ })
234
+
235
+ // 6. En het geval dat wel een keten nodig heeft, expliciet als zodanig gemarkeerd.
236
+ vectoren.push({
237
+ ...basis(
238
+ 'x402-evm-settlement-link-find-candidates-onchain',
239
+ 'The full section 5.6.4 walk, starting from only the receipt, the salt and the asset: find the nonces this payer consumed in a window, then open the commitment on exactly one. Requires an archive-capable RPC for Base Sepolia. Included so the chain-dependent half is named rather than hidden, but a runner without RPC access should skip it and report that, not fail it.',
240
+ 'The chain-reading half of the walk, marked as such.',
241
+ ),
242
+ requires_chain_read: true,
243
+ input: {
244
+ receipt: lees(ECHT[0].bestand).receipt,
245
+ authorizationSalt: lees(ECHT[0].bestand).authorizationSalt,
246
+ asset: lees(ECHT[0].bestand).asset,
247
+ },
248
+ context: {
249
+ rpc: 'any Base Sepolia endpoint that serves eth_getLogs over the window around the receipt issuedAt',
250
+ expected_candidate_count: ECHT[0].kandidaten,
251
+ note: 'The candidate count is what the window actually produced on 2026-10-18 and will grow as this payer makes more payments; a runner should assert "exactly one opens", not the count.',
252
+ },
253
+ expected: { linked: true, outcome: 'LINKED', identifier: ECHT[0].nonce, settlement_transaction: ECHT[0].tx },
254
+ })
255
+
256
+ if (fouten.length) {
257
+ console.error('GEEN ENKELE VECTOR GESCHREVEN. De controle faalde:')
258
+ for (const f of fouten) console.error(' ' + f)
259
+ process.exitCode = 1
260
+ } else {
261
+ const droog = process.argv.includes('--droog')
262
+ if (!droog) {
263
+ fs.mkdirSync(UIT, { recursive: true })
264
+ for (const v of vectoren) {
265
+ fs.writeFileSync(path.join(UIT, v.id.replace('x402-evm-settlement-link-', '') + '.json'), JSON.stringify(v, null, 2) + '\n')
266
+ }
267
+ }
268
+ const offline = vectoren.filter((v) => !v.requires_chain_read).length
269
+ console.log(`${vectoren.length} vectoren${droog ? ' (droog, niets geschreven)' : ' geschreven naar vectors/settlement-link/'}`)
270
+ console.log(` offline, zonder keten te lezen : ${offline}`)
271
+ console.log(` met ketenlezing, gemarkeerd : ${vectoren.length - offline}`)
272
+ console.log(` elk commitment opnieuw berekend en gelijk bevonden aan het ondertekende bonnetje`)
273
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-find-candidates-onchain",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "The chain-reading half of the walk, marked as such.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "The full section 5.6.4 walk, starting from only the receipt, the salt and the asset: find the nonces this payer consumed in a window, then open the commitment on exactly one. Requires an archive-capable RPC for Base Sepolia. Included so the chain-dependent half is named rather than hidden, but a runner without RPC access should skip it and report that, not fail it.",
14
+ "requires_chain_read": true,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792187030,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0x5c35a61a09a566a9c284fad18a70ac2ddfff34d01d2a3ce776ce94e8d5b38ff8"
27
+ },
28
+ "signature": "0x78615cb22986af0aad6e08b74ca78773bb58ece21660e84e4b35b3e5c1841b0075238ca9bfdf0f969ad3f2903af8f391fafd1b3fc7ebbf1fb5c949a3948fccde1c"
29
+ },
30
+ "authorizationSalt": "0xea85b2473363fd8fff1ccdd7f8638be8b47c7c05023e3e2e3f81bebb085ba2ad",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
32
+ },
33
+ "context": {
34
+ "rpc": "any Base Sepolia endpoint that serves eth_getLogs over the window around the receipt issuedAt",
35
+ "expected_candidate_count": 2,
36
+ "note": "The candidate count is what the window actually produced on 2026-10-18 and will grow as this payer makes more payments; a runner should assert \"exactly one opens\", not the count."
37
+ },
38
+ "expected": {
39
+ "linked": true,
40
+ "outcome": "LINKED",
41
+ "identifier": "0x1120412c78ea1b2e8bf9eacfc3264c71a23ecd9b1fa579eb16030693344d5dfe",
42
+ "settlement_transaction": "0xed87b09bfb20a8c8b69908e163fcea6432d193d389640c0465afcb5561dfeb30"
43
+ }
44
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-no-candidates-not-measured",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "An incomplete check must be distinguishable from a failed one.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "No candidate set could be obtained, for example because no chain access was available or the lookup window produced nothing. The correct answer is NOT_MEASURED and explicitly not NOT_LINKED: a check that could not run is not a receipt that fails. This is the third outcome a two-valued vocabulary cannot express.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792187030,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0x5c35a61a09a566a9c284fad18a70ac2ddfff34d01d2a3ce776ce94e8d5b38ff8"
27
+ },
28
+ "signature": "0x78615cb22986af0aad6e08b74ca78773bb58ece21660e84e4b35b3e5c1841b0075238ca9bfdf0f969ad3f2903af8f391fafd1b3fc7ebbf1fb5c949a3948fccde1c"
29
+ },
30
+ "authorizationSalt": "0xea85b2473363fd8fff1ccdd7f8638be8b47c7c05023e3e2e3f81bebb085ba2ad",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
32
+ "candidates": null
33
+ },
34
+ "context": {
35
+ "note": "candidates is null rather than an empty list, and the two mean different things: null is \"we did not obtain a list\", empty is \"we obtained a list and it was empty\"."
36
+ },
37
+ "expected": {
38
+ "linked": null,
39
+ "outcome": "NOT_MEASURED"
40
+ }
41
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-opens-live-2",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "Receipt-to-settlement binding verified offline against a supplied candidate set.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "A version 2 receipt whose authorizationCommitment opens on exactly one of the candidate nonces supplied with it. No chain access is needed because the candidates are given; this is the arithmetic half of section 5.6.4.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792187518,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0xfecee93e1aec7d6858a218bf324f23e2a4eed07925d8d4542b9662c5026e3d22"
27
+ },
28
+ "signature": "0x02f5ba0d2b7928a2dbb1d8845b664a79d38eebcd0a2c4532ed3c6b922051bf8d47c8aafb0520a875a708e3ed480d84a537962ea90d66ce2ee3811f4229aa8b401b"
29
+ },
30
+ "authorizationSalt": "0x63072ea656fe922bf04c9cefc4275314d695fff01e5b57f6628c8df0252cc5dc",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
32
+ "candidates": [
33
+ {
34
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
35
+ "nonce": "0x515895dfac6f148a4cfdf5c4f8266b7859dbd6534cdf77104b1be9f484eb5005"
36
+ }
37
+ ]
38
+ },
39
+ "context": {
40
+ "note": "Real payment on Base Sepolia to https://api.tokenizen.nl/api/paid/verify-ownership. Buyer and seller are the same party, which proves the chain closes end to end and nothing about adoption.",
41
+ "settlement_transaction": "0x2898d0ceb97ddf60af98e19c94fc0adddbdf1e64f9032e62454085e37acfe36b"
42
+ },
43
+ "expected": {
44
+ "linked": true,
45
+ "outcome": "LINKED",
46
+ "identifier": "0x515895dfac6f148a4cfdf5c4f8266b7859dbd6534cdf77104b1be9f484eb5005"
47
+ }
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-opens-live-3",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "Receipt-to-settlement binding verified offline against a supplied candidate set.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "A version 2 receipt whose authorizationCommitment opens on exactly one of the candidate nonces supplied with it. No chain access is needed because the candidates are given; this is the arithmetic half of section 5.6.4.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792188211,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0xea8dd34b8beac90f021a4e8d893a689c25249f86947770c8e6647c1cde206008"
27
+ },
28
+ "signature": "0x5b3a70cce6978ab44e51828b5ebbc55522e98b015045d94559d369420698da3b5309b28050dcb00ce14d65c8b230a273f8ff5ebff6410c6ae2a308e422ed5a611b"
29
+ },
30
+ "authorizationSalt": "0x349ddb90f38a3119817c5fc9cc253a2c20bcbe44aa845302df76889082b314d8",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
32
+ "candidates": [
33
+ {
34
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
35
+ "nonce": "0x4b133bbec3ce0d8177a9010784faedb459f04384b45316bb526a5f96c90a70de"
36
+ }
37
+ ]
38
+ },
39
+ "context": {
40
+ "note": "Real payment on Base Sepolia to https://api.tokenizen.nl/api/paid/verify-ownership. Buyer and seller are the same party, which proves the chain closes end to end and nothing about adoption.",
41
+ "settlement_transaction": "0x2a9e95d63a5af8210767baa69143b364dad51caffc29a152c77e03bc508d7199"
42
+ },
43
+ "expected": {
44
+ "linked": true,
45
+ "outcome": "LINKED",
46
+ "identifier": "0x4b133bbec3ce0d8177a9010784faedb459f04384b45316bb526a5f96c90a70de"
47
+ }
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-opens-live",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "Receipt-to-settlement binding verified offline against a supplied candidate set.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "A version 2 receipt whose authorizationCommitment opens on exactly one of the candidate nonces supplied with it. No chain access is needed because the candidates are given; this is the arithmetic half of section 5.6.4.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792187030,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0x5c35a61a09a566a9c284fad18a70ac2ddfff34d01d2a3ce776ce94e8d5b38ff8"
27
+ },
28
+ "signature": "0x78615cb22986af0aad6e08b74ca78773bb58ece21660e84e4b35b3e5c1841b0075238ca9bfdf0f969ad3f2903af8f391fafd1b3fc7ebbf1fb5c949a3948fccde1c"
29
+ },
30
+ "authorizationSalt": "0xea85b2473363fd8fff1ccdd7f8638be8b47c7c05023e3e2e3f81bebb085ba2ad",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
32
+ "candidates": [
33
+ {
34
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
35
+ "nonce": "0x1120412c78ea1b2e8bf9eacfc3264c71a23ecd9b1fa579eb16030693344d5dfe"
36
+ }
37
+ ]
38
+ },
39
+ "context": {
40
+ "note": "Real payment on Base Sepolia to https://api.tokenizen.nl/api/paid/verify-ownership. Buyer and seller are the same party, which proves the chain closes end to end and nothing about adoption.",
41
+ "settlement_transaction": "0xed87b09bfb20a8c8b69908e163fcea6432d193d389640c0465afcb5561dfeb30"
42
+ },
43
+ "expected": {
44
+ "linked": true,
45
+ "outcome": "LINKED",
46
+ "identifier": "0x1120412c78ea1b2e8bf9eacfc3264c71a23ecd9b1fa579eb16030693344d5dfe"
47
+ }
48
+ }
@@ -0,0 +1,86 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-two-identical-payments",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "Disambiguation of two otherwise indistinguishable payments.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "Two payments with identical terms (same payer, same asset, same amount, same resource) each open on their own settlement and on no other. Given both candidate sets together, each receipt selects exactly one.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipts": [
17
+ {
18
+ "receipt": {
19
+ "format": "eip712",
20
+ "payload": {
21
+ "version": 2,
22
+ "network": "eip155:84532",
23
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
24
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
25
+ "issuedAt": 1792187030,
26
+ "transaction": "",
27
+ "authorizationMethod": "eip3009",
28
+ "authorizationCommitment": "0x5c35a61a09a566a9c284fad18a70ac2ddfff34d01d2a3ce776ce94e8d5b38ff8"
29
+ },
30
+ "signature": "0x78615cb22986af0aad6e08b74ca78773bb58ece21660e84e4b35b3e5c1841b0075238ca9bfdf0f969ad3f2903af8f391fafd1b3fc7ebbf1fb5c949a3948fccde1c"
31
+ },
32
+ "authorizationSalt": "0xea85b2473363fd8fff1ccdd7f8638be8b47c7c05023e3e2e3f81bebb085ba2ad",
33
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
34
+ },
35
+ {
36
+ "receipt": {
37
+ "format": "eip712",
38
+ "payload": {
39
+ "version": 2,
40
+ "network": "eip155:84532",
41
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
42
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
43
+ "issuedAt": 1792187518,
44
+ "transaction": "",
45
+ "authorizationMethod": "eip3009",
46
+ "authorizationCommitment": "0xfecee93e1aec7d6858a218bf324f23e2a4eed07925d8d4542b9662c5026e3d22"
47
+ },
48
+ "signature": "0x02f5ba0d2b7928a2dbb1d8845b664a79d38eebcd0a2c4532ed3c6b922051bf8d47c8aafb0520a875a708e3ed480d84a537962ea90d66ce2ee3811f4229aa8b401b"
49
+ },
50
+ "authorizationSalt": "0x63072ea656fe922bf04c9cefc4275314d695fff01e5b57f6628c8df0252cc5dc",
51
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
52
+ }
53
+ ],
54
+ "candidates": [
55
+ {
56
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
57
+ "nonce": "0x1120412c78ea1b2e8bf9eacfc3264c71a23ecd9b1fa579eb16030693344d5dfe"
58
+ },
59
+ {
60
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
61
+ "nonce": "0x515895dfac6f148a4cfdf5c4f8266b7859dbd6534cdf77104b1be9f484eb5005"
62
+ }
63
+ ]
64
+ },
65
+ "context": {
66
+ "note": "Both are real payments on Base Sepolia with the same terms. Without the salt these two could not be told apart by any later reader.",
67
+ "settlements": [
68
+ "0xed87b09bfb20a8c8b69908e163fcea6432d193d389640c0465afcb5561dfeb30",
69
+ "0x2898d0ceb97ddf60af98e19c94fc0adddbdf1e64f9032e62454085e37acfe36b"
70
+ ]
71
+ },
72
+ "expected": {
73
+ "linked": true,
74
+ "outcome": "LINKED",
75
+ "mapping": [
76
+ {
77
+ "receipt_index": 0,
78
+ "identifier": "0x1120412c78ea1b2e8bf9eacfc3264c71a23ecd9b1fa579eb16030693344d5dfe"
79
+ },
80
+ {
81
+ "receipt_index": 1,
82
+ "identifier": "0x515895dfac6f148a4cfdf5c4f8266b7859dbd6534cdf77104b1be9f484eb5005"
83
+ }
84
+ ]
85
+ }
86
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-wrong-nonce-live-2",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "A candidate that is not this payment must not satisfy the commitment.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "The correct receipt and salt, but a candidate nonce that belongs to a different settlement by the same payer. The commitment must not open on it.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792187518,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0xfecee93e1aec7d6858a218bf324f23e2a4eed07925d8d4542b9662c5026e3d22"
27
+ },
28
+ "signature": "0x02f5ba0d2b7928a2dbb1d8845b664a79d38eebcd0a2c4532ed3c6b922051bf8d47c8aafb0520a875a708e3ed480d84a537962ea90d66ce2ee3811f4229aa8b401b"
29
+ },
30
+ "authorizationSalt": "0x63072ea656fe922bf04c9cefc4275314d695fff01e5b57f6628c8df0252cc5dc",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
32
+ "candidates": [
33
+ {
34
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
35
+ "nonce": "0x1120412c78ea1b2e8bf9eacfc3264c71a23ecd9b1fa579eb16030693344d5dfe"
36
+ }
37
+ ]
38
+ },
39
+ "context": {
40
+ "note": "Both nonces were really consumed by this payer; only one belongs to this receipt."
41
+ },
42
+ "expected": {
43
+ "linked": false,
44
+ "outcome": "NOT_LINKED"
45
+ }
46
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-wrong-nonce-live-3",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "A candidate that is not this payment must not satisfy the commitment.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "The correct receipt and salt, but a candidate nonce that belongs to a different settlement by the same payer. The commitment must not open on it.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792188211,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0xea8dd34b8beac90f021a4e8d893a689c25249f86947770c8e6647c1cde206008"
27
+ },
28
+ "signature": "0x5b3a70cce6978ab44e51828b5ebbc55522e98b015045d94559d369420698da3b5309b28050dcb00ce14d65c8b230a273f8ff5ebff6410c6ae2a308e422ed5a611b"
29
+ },
30
+ "authorizationSalt": "0x349ddb90f38a3119817c5fc9cc253a2c20bcbe44aa845302df76889082b314d8",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
32
+ "candidates": [
33
+ {
34
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
35
+ "nonce": "0x1120412c78ea1b2e8bf9eacfc3264c71a23ecd9b1fa579eb16030693344d5dfe"
36
+ }
37
+ ]
38
+ },
39
+ "context": {
40
+ "note": "Both nonces were really consumed by this payer; only one belongs to this receipt."
41
+ },
42
+ "expected": {
43
+ "linked": false,
44
+ "outcome": "NOT_LINKED"
45
+ }
46
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-wrong-nonce-live",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "A candidate that is not this payment must not satisfy the commitment.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "The correct receipt and salt, but a candidate nonce that belongs to a different settlement by the same payer. The commitment must not open on it.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792187030,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0x5c35a61a09a566a9c284fad18a70ac2ddfff34d01d2a3ce776ce94e8d5b38ff8"
27
+ },
28
+ "signature": "0x78615cb22986af0aad6e08b74ca78773bb58ece21660e84e4b35b3e5c1841b0075238ca9bfdf0f969ad3f2903af8f391fafd1b3fc7ebbf1fb5c949a3948fccde1c"
29
+ },
30
+ "authorizationSalt": "0xea85b2473363fd8fff1ccdd7f8638be8b47c7c05023e3e2e3f81bebb085ba2ad",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
32
+ "candidates": [
33
+ {
34
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
35
+ "nonce": "0x515895dfac6f148a4cfdf5c4f8266b7859dbd6534cdf77104b1be9f484eb5005"
36
+ }
37
+ ]
38
+ },
39
+ "context": {
40
+ "note": "Both nonces were really consumed by this payer; only one belongs to this receipt."
41
+ },
42
+ "expected": {
43
+ "linked": false,
44
+ "outcome": "NOT_LINKED"
45
+ }
46
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-wrong-salt-live-2",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "A salt that belongs to another payment must not open this commitment.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "The same receipt and the same candidate, but the salt of a different payment. The commitment must not open. This is the case the whole construction rests on: without the salt, two payments with identical terms could not be told apart.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792187518,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0xfecee93e1aec7d6858a218bf324f23e2a4eed07925d8d4542b9662c5026e3d22"
27
+ },
28
+ "signature": "0x02f5ba0d2b7928a2dbb1d8845b664a79d38eebcd0a2c4532ed3c6b922051bf8d47c8aafb0520a875a708e3ed480d84a537962ea90d66ce2ee3811f4229aa8b401b"
29
+ },
30
+ "authorizationSalt": "0xea85b2473363fd8fff1ccdd7f8638be8b47c7c05023e3e2e3f81bebb085ba2ad",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
32
+ "candidates": [
33
+ {
34
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
35
+ "nonce": "0x515895dfac6f148a4cfdf5c4f8266b7859dbd6534cdf77104b1be9f484eb5005"
36
+ }
37
+ ]
38
+ },
39
+ "context": {
40
+ "note": "Both salts are real and come from two separate payments with identical terms."
41
+ },
42
+ "expected": {
43
+ "linked": false,
44
+ "outcome": "NOT_LINKED"
45
+ }
46
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-wrong-salt-live-3",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "A salt that belongs to another payment must not open this commitment.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "The same receipt and the same candidate, but the salt of a different payment. The commitment must not open. This is the case the whole construction rests on: without the salt, two payments with identical terms could not be told apart.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792188211,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0xea8dd34b8beac90f021a4e8d893a689c25249f86947770c8e6647c1cde206008"
27
+ },
28
+ "signature": "0x5b3a70cce6978ab44e51828b5ebbc55522e98b015045d94559d369420698da3b5309b28050dcb00ce14d65c8b230a273f8ff5ebff6410c6ae2a308e422ed5a611b"
29
+ },
30
+ "authorizationSalt": "0xea85b2473363fd8fff1ccdd7f8638be8b47c7c05023e3e2e3f81bebb085ba2ad",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
32
+ "candidates": [
33
+ {
34
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
35
+ "nonce": "0x4b133bbec3ce0d8177a9010784faedb459f04384b45316bb526a5f96c90a70de"
36
+ }
37
+ ]
38
+ },
39
+ "context": {
40
+ "note": "Both salts are real and come from two separate payments with identical terms."
41
+ },
42
+ "expected": {
43
+ "linked": false,
44
+ "outcome": "NOT_LINKED"
45
+ }
46
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "id": "x402-evm-settlement-link-wrong-salt-live",
3
+ "schema_version": "3.0.0",
4
+ "protocol": "x402",
5
+ "scheme": "exact",
6
+ "network": "eip155:84532",
7
+ "asset_transfer_method": "eip3009",
8
+ "encodes": "A salt that belongs to another payment must not open this commitment.",
9
+ "provenance": [
10
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
11
+ "EIP-3009"
12
+ ],
13
+ "description": "The same receipt and the same candidate, but the salt of a different payment. The commitment must not open. This is the case the whole construction rests on: without the salt, two payments with identical terms could not be told apart.",
14
+ "requires_chain_read": false,
15
+ "input": {
16
+ "receipt": {
17
+ "format": "eip712",
18
+ "payload": {
19
+ "version": 2,
20
+ "network": "eip155:84532",
21
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
22
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
23
+ "issuedAt": 1792187030,
24
+ "transaction": "",
25
+ "authorizationMethod": "eip3009",
26
+ "authorizationCommitment": "0x5c35a61a09a566a9c284fad18a70ac2ddfff34d01d2a3ce776ce94e8d5b38ff8"
27
+ },
28
+ "signature": "0x78615cb22986af0aad6e08b74ca78773bb58ece21660e84e4b35b3e5c1841b0075238ca9bfdf0f969ad3f2903af8f391fafd1b3fc7ebbf1fb5c949a3948fccde1c"
29
+ },
30
+ "authorizationSalt": "0x63072ea656fe922bf04c9cefc4275314d695fff01e5b57f6628c8df0252cc5dc",
31
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
32
+ "candidates": [
33
+ {
34
+ "authorizer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
35
+ "nonce": "0x1120412c78ea1b2e8bf9eacfc3264c71a23ecd9b1fa579eb16030693344d5dfe"
36
+ }
37
+ ]
38
+ },
39
+ "context": {
40
+ "note": "Both salts are real and come from two separate payments with identical terms."
41
+ },
42
+ "expected": {
43
+ "linked": false,
44
+ "outcome": "NOT_LINKED"
45
+ }
46
+ }