agent-receipt-verify 0.2.3 → 0.2.5

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.
Files changed (31) hide show
  1. package/README.md +38 -3
  2. package/bin/cli.mjs +41 -24
  3. package/package.json +5 -3
  4. package/src/acceptance-binding.mjs +309 -0
  5. package/src/settlement-link-vectors.mjs +124 -11
  6. package/src/verify-delivery.mjs +507 -0
  7. package/src/x402-conformance-check.mjs +41 -5
  8. package/src/x402-discovery-census.mjs +24 -1
  9. package/src/x402-offer-receipt.mjs +49 -3
  10. package/tools/naar-kanon-vorm.mjs +172 -0
  11. package/tools/pack-inhoud.mjs +45 -1
  12. package/vectors/acceptance-binding/crossed-deliverable.json +40 -0
  13. package/vectors/acceptance-binding/pairs-same-deliverable.json +39 -0
  14. package/vectors/acceptance-binding/real-record-as-issued.json +68 -0
  15. package/vectors/acceptance-binding/real-record-crossed-deliverable.json +65 -0
  16. package/vectors/acceptance-binding/real-record-crossed-x402-keys.json +74 -0
  17. package/vectors/acceptance-binding/real-record-with-producer-key.json +66 -0
  18. package/vectors/acceptance-binding/real-record-x402-keys-match.json +76 -0
  19. package/vectors/settlement-link-kanon/README.md +69 -0
  20. package/vectors/settlement-link-kanon/find-candidates-onchain.json +43 -0
  21. package/vectors/settlement-link-kanon/no-candidates-not-measured.json +42 -0
  22. package/vectors/settlement-link-kanon/opens-live-2.json +47 -0
  23. package/vectors/settlement-link-kanon/opens-live-3.json +47 -0
  24. package/vectors/settlement-link-kanon/opens-live.json +47 -0
  25. package/vectors/settlement-link-kanon/two-identical-payments.json +76 -0
  26. package/vectors/settlement-link-kanon/wrong-nonce-live-2.json +46 -0
  27. package/vectors/settlement-link-kanon/wrong-nonce-live-3.json +46 -0
  28. package/vectors/settlement-link-kanon/wrong-nonce-live.json +46 -0
  29. package/vectors/settlement-link-kanon/wrong-salt-live-2.json +46 -0
  30. package/vectors/settlement-link-kanon/wrong-salt-live-3.json +46 -0
  31. package/vectors/settlement-link-kanon/wrong-salt-live.json +46 -0
@@ -0,0 +1,66 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "receipt-to-acceptance",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "trilatic.acceptance-receipt/1.2, live record fetched from https://api.babyblueviper.com/work-acceptance/record/4c7273afcdb5333586b0a41c4238a968f3dedc23f66f71c5a1928ff3d279ab3d on 18 October 2026",
7
+ "the two binding keys were specified by babyblueviper1 in erc-8004/erc-8004-contracts#99 on 18 October 2026",
8
+ "the record is issued by invinoveritas, not by us; it is copied here as test material"
9
+ ],
10
+ "id": "acceptance-binding-real-record-with-producer-key",
11
+ "encodes": "The positive control, and at the same time the exact one-field diff that makes the record auditable.",
12
+ "description": "Byte-identical to real-record-as-issued except that parties.producer.key_id is set to a distinct key. Now the checker and the producer are both named in the same namespace and differ, so a third party can verify the independence claim instead of accepting it. This must link. Without this vector the set would only contain refusals, which proves nothing about the harness.",
13
+ "input": {
14
+ "doc": {
15
+ "record": {
16
+ "receipt": {
17
+ "schema": "trilatic.acceptance-receipt/1.2",
18
+ "receipt_id": "INV-WA-aa4b8bc740bc2524",
19
+ "issuer": "invinoveritas",
20
+ "issued_utc": "2026-10-18T00:52:09Z",
21
+ "criteria": {
22
+ "id": "invinoveritas-p1-selftest",
23
+ "sha256": "d74ad7d7492908f1146b233e89db6ca205065c67f02d33758ff0aa9f8b9e02fc",
24
+ "committed_at": "2026-10-18T00:52:03Z"
25
+ },
26
+ "files": {
27
+ "remediated": {
28
+ "name": "refund-policy.txt",
29
+ "bytes": 121,
30
+ "sha256": "9c27eee03d50f4ff00fb60a1551a03d7311dbed257b7b943b3ff5d13eb30c083"
31
+ }
32
+ },
33
+ "parties": {
34
+ "checker": {
35
+ "name": "invinoveritas",
36
+ "key_id": "invinoveritas-checker-a4cecab2"
37
+ },
38
+ "producer": {
39
+ "name": "Example Vendor (self-test)",
40
+ "key_id": "example-vendor-producer-7f31aa02"
41
+ }
42
+ },
43
+ "independence": "independent",
44
+ "verdict": "pass"
45
+ }
46
+ },
47
+ "bindings_sidecar": {
48
+ "version": "invinoveritas.work-acceptance-bindings/v0",
49
+ "receipt_sha256": "4c7273afcdb5333586b0a41c4238a968f3dedc23f66f71c5a1928ff3d279ab3d",
50
+ "criteria_id": "d74ad7d7492908f1146b233e89db6ca205065c67f02d33758ff0aa9f8b9e02fc",
51
+ "bindings": {}
52
+ }
53
+ },
54
+ "receiptSha256": "4c7273afcdb5333586b0a41c4238a968f3dedc23f66f71c5a1928ff3d279ab3d",
55
+ "deliverableSha256": "9c27eee03d50f4ff00fb60a1551a03d7311dbed257b7b943b3ff5d13eb30c083"
56
+ },
57
+ "expected": {
58
+ "valid": true,
59
+ "reason_code": "LINKED",
60
+ "independence": "VERIFIEERBAAR_ONAFHANKELIJK"
61
+ },
62
+ "limits": [
63
+ "The signature on the record is not checked here, and neither are its criteria. That is the issuing checker's own verifier.",
64
+ "A LINKED verdict says the two artifacts are about the same thing, not that the acceptance record itself is sound."
65
+ ]
66
+ }
@@ -0,0 +1,76 @@
1
+ {
2
+ "schema_version": "3.0.0",
3
+ "verifies": "receipt-to-acceptance",
4
+ "requires_chain_read": false,
5
+ "provenance": [
6
+ "trilatic.acceptance-receipt/1.2, live record fetched from https://api.babyblueviper.com/work-acceptance/record/4c7273afcdb5333586b0a41c4238a968f3dedc23f66f71c5a1928ff3d279ab3d on 18 October 2026",
7
+ "the two binding keys were specified by babyblueviper1 in erc-8004/erc-8004-contracts#99 on 18 October 2026",
8
+ "the record is issued by invinoveritas, not by us; it is copied here as test material"
9
+ ],
10
+ "id": "acceptance-binding-real-record-x402-keys-match",
11
+ "encodes": "Both payment keys present and each compared against its own counterpart. The positive control for the two-key split.",
12
+ "description": "The sidecar carries the commitment in the commitment key and the nonce in the nonce key, both matching the receipt. This must link, and a verifier should report which pairs it actually compared, so that a reader can tell a full comparison from a record that simply carried neither key.",
13
+ "input": {
14
+ "doc": {
15
+ "record": {
16
+ "receipt": {
17
+ "schema": "trilatic.acceptance-receipt/1.2",
18
+ "receipt_id": "INV-WA-aa4b8bc740bc2524",
19
+ "issuer": "invinoveritas",
20
+ "issued_utc": "2026-10-18T00:52:09Z",
21
+ "criteria": {
22
+ "id": "invinoveritas-p1-selftest",
23
+ "sha256": "d74ad7d7492908f1146b233e89db6ca205065c67f02d33758ff0aa9f8b9e02fc",
24
+ "committed_at": "2026-10-18T00:52:03Z"
25
+ },
26
+ "files": {
27
+ "remediated": {
28
+ "name": "refund-policy.txt",
29
+ "bytes": 121,
30
+ "sha256": "9c27eee03d50f4ff00fb60a1551a03d7311dbed257b7b943b3ff5d13eb30c083"
31
+ }
32
+ },
33
+ "parties": {
34
+ "checker": {
35
+ "name": "invinoveritas",
36
+ "key_id": "invinoveritas-checker-a4cecab2"
37
+ },
38
+ "producer": {
39
+ "name": "Example Vendor (self-test)",
40
+ "key_id": "example-vendor-producer-7f31aa02"
41
+ }
42
+ },
43
+ "independence": "independent",
44
+ "verdict": "pass"
45
+ }
46
+ },
47
+ "bindings_sidecar": {
48
+ "version": "invinoveritas.work-acceptance-bindings/v0",
49
+ "receipt_sha256": "4c7273afcdb5333586b0a41c4238a968f3dedc23f66f71c5a1928ff3d279ab3d",
50
+ "criteria_id": "d74ad7d7492908f1146b233e89db6ca205065c67f02d33758ff0aa9f8b9e02fc",
51
+ "bindings": {
52
+ "x402_receipt_commitment": "0xe04421456465df1012f416b72aece3401f9e3d5be5bd78280324a031efe1d40f",
53
+ "x402_authorization_nonce": "0x43457c8e57b7c4ff92d716350c61c88c8f3b644d09d7c813d5a7ee6450a843da"
54
+ }
55
+ }
56
+ },
57
+ "receipt": {
58
+ "format": "eip712",
59
+ "authorizationNonce": "0x43457c8e57b7c4ff92d716350c61c88c8f3b644d09d7c813d5a7ee6450a843da",
60
+ "authorizationCommitment": "0xe04421456465df1012f416b72aece3401f9e3d5be5bd78280324a031efe1d40f"
61
+ },
62
+ "receiptSha256": "4c7273afcdb5333586b0a41c4238a968f3dedc23f66f71c5a1928ff3d279ab3d",
63
+ "deliverableSha256": "9c27eee03d50f4ff00fb60a1551a03d7311dbed257b7b943b3ff5d13eb30c083"
64
+ },
65
+ "expected": {
66
+ "valid": true,
67
+ "reason_code": "LINKED",
68
+ "compared": [
69
+ "COMMITMENT",
70
+ "AUTHORIZATION"
71
+ ]
72
+ },
73
+ "limits": [
74
+ "Linking the payment to the acceptance record does not prove the payment settled. That is the receipt-to-settlement question and it needs a chain read."
75
+ ]
76
+ }
@@ -0,0 +1,69 @@
1
+ # The settlement-link vectors, in Kanon's shape
2
+
3
+ This directory is generated. Do not edit the files by hand; run
4
+ `node tools/naar-kanon-vorm.mjs` and commit the result. `npm test` checks that what is
5
+ on disk is byte-identical to what the converter would write today, so a stale copy
6
+ cannot sit here unnoticed.
7
+
8
+ ## Why it exists
9
+
10
+ The author of the Kanon vectors wrote in
11
+ [x402#3396](https://github.com/x402-foundation/x402/issues/3396) on 17 October 2026:
12
+
13
+ > if you want receipt-to-settlement vectors in Kanon's shape, I'm open to that, with the
14
+ > part that needs a chain read marked as such.
15
+
16
+ This is that. The source corpus lives in `../settlement-link` and is the same twelve
17
+ cases; these are the same cases in their field layout, with the chain-reading one
18
+ marked.
19
+
20
+ ## What these cover, and what they do not
21
+
22
+ Kanon covers the buyer's authorization **before** settlement: validity window, signature
23
+ malleability, domain replay across chain and contract, nonce reuse, amount. These cover
24
+ the other end: does a salted commitment inside a **signed receipt** open onto exactly one
25
+ settlement on chain. The two sets nowhere overlap, which is why this is a complement and
26
+ not a merge, and why every file here carries `verifies: "receipt-to-settlement"`.
27
+
28
+ That also means Kanon's existing runner cannot execute these as they stand. Their input
29
+ is `payload.authorization`; ours is a receipt plus a salt plus an asset. The shape is
30
+ theirs so the two corpora can sit in one place and be read by one reader. The content is
31
+ a different axis, and pretending otherwise would waste somebody's afternoon.
32
+
33
+ ## The two fields we added, both additive
34
+
35
+ - `verifies` — which axis the vector tests. Without it, two corpora in one directory are
36
+ indistinguishable.
37
+ - `requires_chain_read` — present and `true` on exactly one vector. The other eleven run
38
+ offline, with no account, no key and no network. A runner that does not know this field
39
+ ignores it; a runner that does knows to expect an RPC.
40
+
41
+ ## The one place the shape does not fit, and it is the interesting one
42
+
43
+ Kanon's `expected` is `{ valid, reason_code }`. Three of our four outcomes map onto that
44
+ cleanly:
45
+
46
+ | our outcome | expected |
47
+ |---|---|
48
+ | `LINKED` | `valid: true`, `reason_code: LINKED` |
49
+ | `NOT_LINKED` | `valid: false`, `reason_code: NOT_LINKED` |
50
+ | `AMBIGUOUS` | `valid: false`, `reason_code: AMBIGUOUS` |
51
+ | `NOT_MEASURED` | **no `valid` at all**, `measurable: false` |
52
+
53
+ The fourth has no slot, and we left the hole visible rather than filling it. A receipt
54
+ whose candidate set could not be obtained has not been rejected: our chain read did not
55
+ happen, and that says nothing about the receipt. Writing `valid: false` there would
56
+ blame an honest seller for our own failed RPC. A missing field is a question for a
57
+ reader; a false is an accusation.
58
+
59
+ That is the third-outcome gap the same thread discussed, and the Kanon author said in
60
+ the same comment he would look at adding an optional third outcome without weakening the
61
+ accept and reject cases. One file in this directory is what that gap looks like in
62
+ practice: `no-candidates-not-measured.json`.
63
+
64
+ ## Provenance
65
+
66
+ The Kanon vectors themselves are in `../kanon`, pinned at commit `0d5a7461e996`,
67
+ Apache-2.0, redistributed unmodified with attribution. The vectors here are ours, built
68
+ from three real payments on Base Sepolia; the receipts, salts and nonces in them are real
69
+ and the commitments re-derive from the signed receipts rather than being copied in.
@@ -0,0 +1,43 @@
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
+ "verifies": "receipt-to-settlement",
9
+ "encodes": "The chain-reading half of the walk, marked as such.",
10
+ "provenance": [
11
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
12
+ "EIP-3009"
13
+ ],
14
+ "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.",
15
+ "requires_chain_read": true,
16
+ "input": {
17
+ "receipt": {
18
+ "format": "eip712",
19
+ "payload": {
20
+ "version": 2,
21
+ "network": "eip155:84532",
22
+ "resourceUrl": "https://api.tokenizen.nl/api/paid/verify-ownership",
23
+ "payer": "0x87f344b6Ec48A936373d774D3De8649DEEB66Ee1",
24
+ "issuedAt": 1792187030,
25
+ "transaction": "",
26
+ "authorizationMethod": "eip3009",
27
+ "authorizationCommitment": "0x5c35a61a09a566a9c284fad18a70ac2ddfff34d01d2a3ce776ce94e8d5b38ff8"
28
+ },
29
+ "signature": "0x78615cb22986af0aad6e08b74ca78773bb58ece21660e84e4b35b3e5c1841b0075238ca9bfdf0f969ad3f2903af8f391fafd1b3fc7ebbf1fb5c949a3948fccde1c"
30
+ },
31
+ "authorizationSalt": "0xea85b2473363fd8fff1ccdd7f8638be8b47c7c05023e3e2e3f81bebb085ba2ad",
32
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
33
+ },
34
+ "context": {
35
+ "rpc": "any Base Sepolia endpoint that serves eth_getLogs over the window around the receipt issuedAt",
36
+ "expected_candidate_count": 2,
37
+ "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."
38
+ },
39
+ "expected": {
40
+ "valid": true,
41
+ "reason_code": "LINKED"
42
+ }
43
+ }
@@ -0,0 +1,42 @@
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
+ "verifies": "receipt-to-settlement",
9
+ "encodes": "An incomplete check must be distinguishable from a failed one.",
10
+ "provenance": [
11
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
12
+ "EIP-3009"
13
+ ],
14
+ "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.",
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
+ "measurable": false,
39
+ "reason_code": "NOT_MEASURED",
40
+ "note": "Deliberately carries no `valid`. The candidate set could not be obtained, so this says nothing about the receipt. Writing valid:false here would blame an honest seller for a failed chain read. This is the third-outcome gap discussed in x402#3396."
41
+ }
42
+ }
@@ -0,0 +1,47 @@
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
+ "verifies": "receipt-to-settlement",
9
+ "encodes": "Receipt-to-settlement binding verified offline against a supplied candidate set.",
10
+ "provenance": [
11
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
12
+ "EIP-3009"
13
+ ],
14
+ "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.",
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
+ "valid": true,
45
+ "reason_code": "LINKED"
46
+ }
47
+ }
@@ -0,0 +1,47 @@
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
+ "verifies": "receipt-to-settlement",
9
+ "encodes": "Receipt-to-settlement binding verified offline against a supplied candidate set.",
10
+ "provenance": [
11
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
12
+ "EIP-3009"
13
+ ],
14
+ "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.",
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
+ "valid": true,
45
+ "reason_code": "LINKED"
46
+ }
47
+ }
@@ -0,0 +1,47 @@
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
+ "verifies": "receipt-to-settlement",
9
+ "encodes": "Receipt-to-settlement binding verified offline against a supplied candidate set.",
10
+ "provenance": [
11
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
12
+ "EIP-3009"
13
+ ],
14
+ "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.",
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
+ "valid": true,
45
+ "reason_code": "LINKED"
46
+ }
47
+ }
@@ -0,0 +1,76 @@
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
+ "verifies": "receipt-to-settlement",
9
+ "encodes": "Disambiguation of two otherwise indistinguishable payments.",
10
+ "provenance": [
11
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
12
+ "EIP-3009"
13
+ ],
14
+ "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.",
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
+ "valid": true,
74
+ "reason_code": "LINKED"
75
+ }
76
+ }
@@ -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
+ "verifies": "receipt-to-settlement",
9
+ "encodes": "A candidate that is not this payment must not satisfy the commitment.",
10
+ "provenance": [
11
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
12
+ "EIP-3009"
13
+ ],
14
+ "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.",
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
+ "valid": false,
44
+ "reason_code": "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
+ "verifies": "receipt-to-settlement",
9
+ "encodes": "A candidate that is not this payment must not satisfy the commitment.",
10
+ "provenance": [
11
+ "x402 offer-receipt extension section 5.6.4, as proposed in pull request 3827 at commit 0e492d6 and not on main",
12
+ "EIP-3009"
13
+ ],
14
+ "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.",
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
+ "valid": false,
44
+ "reason_code": "NOT_LINKED"
45
+ }
46
+ }