s402 0.3.0 → 0.5.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.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "s402",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
- "description": "s402 — Chain-agnostic HTTP 402 wire format. Types, HTTP encoding, and scheme registry for five payment schemes. Wire-compatible with x402. Zero runtime dependencies.",
5
+ "description": "s402 — Chain-agnostic HTTP 402 wire format. Types, HTTP encoding, and scheme registry for six payment schemes. Wire-compatible with x402. Zero runtime dependencies.",
6
6
  "license": "Apache-2.0",
7
7
  "author": "SweeInc <daniel@sweeinc.com> (https://s402-protocol.org)",
8
8
  "repository": {
@@ -89,6 +89,13 @@
89
89
  },
90
90
  "default": "./dist/receipts.mjs"
91
91
  },
92
+ "./extensions": {
93
+ "import": {
94
+ "types": "./dist/extensions.d.mts",
95
+ "default": "./dist/extensions.mjs"
96
+ },
97
+ "default": "./dist/extensions.mjs"
98
+ },
92
99
  "./test-utils": {
93
100
  "import": {
94
101
  "types": "./dist/test-utils.d.mts",
@@ -73,6 +73,48 @@
73
73
  },
74
74
  "shouldReject": false
75
75
  },
76
+ {
77
+ "description": "Upto requirements body with estimatedAmount",
78
+ "input": {
79
+ "type": "requirements",
80
+ "value": {
81
+ "s402Version": "1",
82
+ "accepts": [
83
+ "upto"
84
+ ],
85
+ "network": "sui:mainnet",
86
+ "asset": "0x2::sui::SUI",
87
+ "amount": "1000000",
88
+ "payTo": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
89
+ "upto": {
90
+ "maxAmount": "10000000",
91
+ "settlementDeadlineMs": "1700000000000",
92
+ "usageReportUrl": "https://api.example.com/usage",
93
+ "estimatedAmount": "7500000"
94
+ }
95
+ }
96
+ },
97
+ "expected": {
98
+ "body": "{\"s402Version\":\"1\",\"accepts\":[\"upto\"],\"network\":\"sui:mainnet\",\"asset\":\"0x2::sui::SUI\",\"amount\":\"1000000\",\"payTo\":\"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\",\"upto\":{\"maxAmount\":\"10000000\",\"settlementDeadlineMs\":\"1700000000000\",\"usageReportUrl\":\"https://api.example.com/usage\",\"estimatedAmount\":\"7500000\"}}",
99
+ "decoded": {
100
+ "s402Version": "1",
101
+ "accepts": [
102
+ "upto"
103
+ ],
104
+ "network": "sui:mainnet",
105
+ "asset": "0x2::sui::SUI",
106
+ "amount": "1000000",
107
+ "payTo": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
108
+ "upto": {
109
+ "maxAmount": "10000000",
110
+ "settlementDeadlineMs": "1700000000000",
111
+ "usageReportUrl": "https://api.example.com/usage",
112
+ "estimatedAmount": "7500000"
113
+ }
114
+ }
115
+ },
116
+ "shouldReject": false
117
+ },
76
118
  {
77
119
  "description": "Payload body encode/decode",
78
120
  "input": {
@@ -18,7 +18,8 @@
18
18
  "network": "sui:mainnet",
19
19
  "asset": "0x2::sui::SUI",
20
20
  "amount": "1000000",
21
- "payTo": "0xrecipient123"
21
+ "payTo": "0xrecipient123",
22
+ "expiresAt": 1700000060000
22
23
  },
23
24
  "shouldReject": false
24
25
  },
@@ -47,6 +47,39 @@
47
47
  },
48
48
  "shouldReject": false
49
49
  },
50
+ {
51
+ "description": "Decode upto payload with settlementCeiling",
52
+ "input": {
53
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJ1cHRvIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRYQjBiMTkwZUE9PSIsInNpZ25hdHVyZSI6ImRYQjBiMTl6YVdjPSIsIm1heEFtb3VudCI6IjEwMDAwMDAwIiwic2V0dGxlbWVudENlaWxpbmciOiI4MDAwMDAwIn19"
54
+ },
55
+ "expected": {
56
+ "s402Version": "1",
57
+ "scheme": "upto",
58
+ "payload": {
59
+ "transaction": "dXB0b190eA==",
60
+ "signature": "dXB0b19zaWc=",
61
+ "maxAmount": "10000000",
62
+ "settlementCeiling": "8000000"
63
+ }
64
+ },
65
+ "shouldReject": false
66
+ },
67
+ {
68
+ "description": "Decode upto payload without settlementCeiling",
69
+ "input": {
70
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJ1cHRvIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRYQjBiMTkwZUE9PSIsInNpZ25hdHVyZSI6ImRYQjBiMTl6YVdjPSIsIm1heEFtb3VudCI6IjUwMDAwMDAifX0="
71
+ },
72
+ "expected": {
73
+ "s402Version": "1",
74
+ "scheme": "upto",
75
+ "payload": {
76
+ "transaction": "dXB0b190eA==",
77
+ "signature": "dXB0b19zaWc=",
78
+ "maxAmount": "5000000"
79
+ }
80
+ },
81
+ "shouldReject": false
82
+ },
50
83
  {
51
84
  "description": "Decode stream payload",
52
85
  "input": {
@@ -31,6 +31,39 @@
31
31
  },
32
32
  "shouldReject": false
33
33
  },
34
+ {
35
+ "description": "Upto payload with maxAmount and settlementCeiling",
36
+ "input": {
37
+ "s402Version": "1",
38
+ "scheme": "upto",
39
+ "payload": {
40
+ "transaction": "dXB0b190eA==",
41
+ "signature": "dXB0b19zaWc=",
42
+ "maxAmount": "10000000",
43
+ "settlementCeiling": "8000000"
44
+ }
45
+ },
46
+ "expected": {
47
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJ1cHRvIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRYQjBiMTkwZUE9PSIsInNpZ25hdHVyZSI6ImRYQjBiMTl6YVdjPSIsIm1heEFtb3VudCI6IjEwMDAwMDAwIiwic2V0dGxlbWVudENlaWxpbmciOiI4MDAwMDAwIn19"
48
+ },
49
+ "shouldReject": false
50
+ },
51
+ {
52
+ "description": "Upto payload without settlementCeiling (backwards compatible)",
53
+ "input": {
54
+ "s402Version": "1",
55
+ "scheme": "upto",
56
+ "payload": {
57
+ "transaction": "dXB0b190eA==",
58
+ "signature": "dXB0b19zaWc=",
59
+ "maxAmount": "5000000"
60
+ }
61
+ },
62
+ "expected": {
63
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJ1cHRvIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRYQjBiMTkwZUE9PSIsInNpZ25hdHVyZSI6ImRYQjBiMTl6YVdjPSIsIm1heEFtb3VudCI6IjUwMDAwMDAifX0="
64
+ },
65
+ "shouldReject": false
66
+ },
34
67
  {
35
68
  "description": "Stream payload",
36
69
  "input": {
@@ -38,6 +38,50 @@
38
38
  },
39
39
  "shouldReject": false
40
40
  },
41
+ {
42
+ "description": "Decode upto scheme with estimatedAmount",
43
+ "input": {
44
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJhY2NlcHRzIjpbInVwdG8iXSwibmV0d29yayI6InN1aTptYWlubmV0IiwiYXNzZXQiOiIweDI6OnN1aTo6U1VJIiwiYW1vdW50IjoiMTAwMDAwMCIsInBheVRvIjoiMHhhYmNkZWYxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwIiwidXB0byI6eyJtYXhBbW91bnQiOiIxMDAwMDAwMCIsInNldHRsZW1lbnREZWFkbGluZU1zIjoiMTcwMDAwMDAwMDAwMCIsInVzYWdlUmVwb3J0VXJsIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20vdXNhZ2UiLCJlc3RpbWF0ZWRBbW91bnQiOiI3NTAwMDAwIn19"
45
+ },
46
+ "expected": {
47
+ "s402Version": "1",
48
+ "accepts": [
49
+ "upto"
50
+ ],
51
+ "network": "sui:mainnet",
52
+ "asset": "0x2::sui::SUI",
53
+ "amount": "1000000",
54
+ "payTo": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
55
+ "upto": {
56
+ "maxAmount": "10000000",
57
+ "settlementDeadlineMs": "1700000000000",
58
+ "usageReportUrl": "https://api.example.com/usage",
59
+ "estimatedAmount": "7500000"
60
+ }
61
+ },
62
+ "shouldReject": false
63
+ },
64
+ {
65
+ "description": "Decode upto scheme minimal",
66
+ "input": {
67
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJhY2NlcHRzIjpbInVwdG8iXSwibmV0d29yayI6InN1aTptYWlubmV0IiwiYXNzZXQiOiIweDI6OnN1aTo6U1VJIiwiYW1vdW50IjoiMTAwMDAwMCIsInBheVRvIjoiMHhhYmNkZWYxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwIiwidXB0byI6eyJtYXhBbW91bnQiOiI1MDAwMDAwIiwic2V0dGxlbWVudERlYWRsaW5lTXMiOiIxNzAwMDAwMDAwMDAwIn19"
68
+ },
69
+ "expected": {
70
+ "s402Version": "1",
71
+ "accepts": [
72
+ "upto"
73
+ ],
74
+ "network": "sui:mainnet",
75
+ "asset": "0x2::sui::SUI",
76
+ "amount": "1000000",
77
+ "payTo": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
78
+ "upto": {
79
+ "maxAmount": "5000000",
80
+ "settlementDeadlineMs": "1700000000000"
81
+ }
82
+ },
83
+ "shouldReject": false
84
+ },
41
85
  {
42
86
  "description": "Decode escrow scheme",
43
87
  "input": {
@@ -38,6 +38,50 @@
38
38
  },
39
39
  "shouldReject": false
40
40
  },
41
+ {
42
+ "description": "Upto scheme with estimatedAmount",
43
+ "input": {
44
+ "s402Version": "1",
45
+ "accepts": [
46
+ "upto"
47
+ ],
48
+ "network": "sui:mainnet",
49
+ "asset": "0x2::sui::SUI",
50
+ "amount": "1000000",
51
+ "payTo": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
52
+ "upto": {
53
+ "maxAmount": "10000000",
54
+ "settlementDeadlineMs": "1700000000000",
55
+ "usageReportUrl": "https://api.example.com/usage",
56
+ "estimatedAmount": "7500000"
57
+ }
58
+ },
59
+ "expected": {
60
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJhY2NlcHRzIjpbInVwdG8iXSwibmV0d29yayI6InN1aTptYWlubmV0IiwiYXNzZXQiOiIweDI6OnN1aTo6U1VJIiwiYW1vdW50IjoiMTAwMDAwMCIsInBheVRvIjoiMHhhYmNkZWYxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwIiwidXB0byI6eyJtYXhBbW91bnQiOiIxMDAwMDAwMCIsInNldHRsZW1lbnREZWFkbGluZU1zIjoiMTcwMDAwMDAwMDAwMCIsInVzYWdlUmVwb3J0VXJsIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20vdXNhZ2UiLCJlc3RpbWF0ZWRBbW91bnQiOiI3NTAwMDAwIn19"
61
+ },
62
+ "shouldReject": false
63
+ },
64
+ {
65
+ "description": "Upto scheme minimal (no estimatedAmount)",
66
+ "input": {
67
+ "s402Version": "1",
68
+ "accepts": [
69
+ "upto"
70
+ ],
71
+ "network": "sui:mainnet",
72
+ "asset": "0x2::sui::SUI",
73
+ "amount": "1000000",
74
+ "payTo": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
75
+ "upto": {
76
+ "maxAmount": "5000000",
77
+ "settlementDeadlineMs": "1700000000000"
78
+ }
79
+ },
80
+ "expected": {
81
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJhY2NlcHRzIjpbInVwdG8iXSwibmV0d29yayI6InN1aTptYWlubmV0IiwiYXNzZXQiOiIweDI6OnN1aTo6U1VJIiwiYW1vdW50IjoiMTAwMDAwMCIsInBheVRvIjoiMHhhYmNkZWYxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwIiwidXB0byI6eyJtYXhBbW91bnQiOiI1MDAwMDAwIiwic2V0dGxlbWVudERlYWRsaW5lTXMiOiIxNzAwMDAwMDAwMDAwIn19"
82
+ },
83
+ "shouldReject": false
84
+ },
41
85
  {
42
86
  "description": "Escrow scheme with extras",
43
87
  "input": {
@@ -123,6 +123,58 @@
123
123
  },
124
124
  "shouldReject": false
125
125
  },
126
+ {
127
+ "description": "Upto requirements with estimatedAmount roundtrip",
128
+ "input": {
129
+ "type": "requirements",
130
+ "transport": "header",
131
+ "value": {
132
+ "s402Version": "1",
133
+ "accepts": [
134
+ "upto"
135
+ ],
136
+ "network": "sui:mainnet",
137
+ "asset": "0x2::sui::SUI",
138
+ "amount": "1000000",
139
+ "payTo": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
140
+ "upto": {
141
+ "maxAmount": "10000000",
142
+ "settlementDeadlineMs": "1700000000000",
143
+ "usageReportUrl": "https://api.example.com/usage",
144
+ "estimatedAmount": "7500000"
145
+ }
146
+ }
147
+ },
148
+ "expected": {
149
+ "firstEncode": "eyJzNDAyVmVyc2lvbiI6IjEiLCJhY2NlcHRzIjpbInVwdG8iXSwibmV0d29yayI6InN1aTptYWlubmV0IiwiYXNzZXQiOiIweDI6OnN1aTo6U1VJIiwiYW1vdW50IjoiMTAwMDAwMCIsInBheVRvIjoiMHhhYmNkZWYxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwIiwidXB0byI6eyJtYXhBbW91bnQiOiIxMDAwMDAwMCIsInNldHRsZW1lbnREZWFkbGluZU1zIjoiMTcwMDAwMDAwMDAwMCIsInVzYWdlUmVwb3J0VXJsIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20vdXNhZ2UiLCJlc3RpbWF0ZWRBbW91bnQiOiI3NTAwMDAwIn19",
150
+ "reEncode": "eyJzNDAyVmVyc2lvbiI6IjEiLCJhY2NlcHRzIjpbInVwdG8iXSwibmV0d29yayI6InN1aTptYWlubmV0IiwiYXNzZXQiOiIweDI6OnN1aTo6U1VJIiwiYW1vdW50IjoiMTAwMDAwMCIsInBheVRvIjoiMHhhYmNkZWYxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3ODkwIiwidXB0byI6eyJtYXhBbW91bnQiOiIxMDAwMDAwMCIsInNldHRsZW1lbnREZWFkbGluZU1zIjoiMTcwMDAwMDAwMDAwMCIsInVzYWdlUmVwb3J0VXJsIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20vdXNhZ2UiLCJlc3RpbWF0ZWRBbW91bnQiOiI3NTAwMDAwIn19",
151
+ "identical": true
152
+ },
153
+ "shouldReject": false
154
+ },
155
+ {
156
+ "description": "Upto payload with settlementCeiling roundtrip",
157
+ "input": {
158
+ "type": "payload",
159
+ "transport": "header",
160
+ "value": {
161
+ "s402Version": "1",
162
+ "scheme": "upto",
163
+ "payload": {
164
+ "transaction": "dXB0b190eA==",
165
+ "signature": "dXB0b19zaWc=",
166
+ "maxAmount": "10000000",
167
+ "settlementCeiling": "8000000"
168
+ }
169
+ }
170
+ },
171
+ "expected": {
172
+ "firstEncode": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJ1cHRvIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRYQjBiMTkwZUE9PSIsInNpZ25hdHVyZSI6ImRYQjBiMTl6YVdjPSIsIm1heEFtb3VudCI6IjEwMDAwMDAwIiwic2V0dGxlbWVudENlaWxpbmciOiI4MDAwMDAwIn19",
173
+ "reEncode": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJ1cHRvIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRYQjBiMTkwZUE9PSIsInNpZ25hdHVyZSI6ImRYQjBiMTl6YVdjPSIsIm1heEFtb3VudCI6IjEwMDAwMDAwIiwic2V0dGxlbWVudENlaWxpbmciOiI4MDAwMDAwIn19",
174
+ "identical": true
175
+ },
176
+ "shouldReject": false
177
+ },
126
178
  {
127
179
  "description": "Complex requirements with extensions roundtrip",
128
180
  "input": {
@@ -61,6 +61,20 @@
61
61
  },
62
62
  "shouldReject": false
63
63
  },
64
+ {
65
+ "description": "Decode success with actualAmount and depositId (upto)",
66
+ "input": {
67
+ "header": "eyJzdWNjZXNzIjp0cnVlLCJ0eERpZ2VzdCI6InVwdG9fZGVjb2RlX2RpZ2VzdCIsImFjdHVhbEFtb3VudCI6Ijc1MDAwMDAiLCJkZXBvc2l0SWQiOiIweGRlcG9zaXRfZGVjb2RlXzEyMyIsImZpbmFsaXR5TXMiOjI1MH0="
68
+ },
69
+ "expected": {
70
+ "success": true,
71
+ "txDigest": "upto_decode_digest",
72
+ "finalityMs": 250,
73
+ "actualAmount": "7500000",
74
+ "depositId": "0xdeposit_decode_123"
75
+ },
76
+ "shouldReject": false
77
+ },
64
78
  {
65
79
  "description": "Decode strips unknown top-level keys from settle response",
66
80
  "input": {
@@ -61,5 +61,33 @@
61
61
  "header": "eyJzdWNjZXNzIjp0cnVlLCJ0eERpZ2VzdCI6InByZXAxMjMiLCJiYWxhbmNlSWQiOiIweGJhbGFuY2U3ODkiLCJmaW5hbGl0eU1zIjozMDB9"
62
62
  },
63
63
  "shouldReject": false
64
+ },
65
+ {
66
+ "description": "Success with actualAmount and depositId (upto)",
67
+ "input": {
68
+ "success": true,
69
+ "txDigest": "upto_digest_abc",
70
+ "actualAmount": "7500000",
71
+ "depositId": "0xdeposit_upto_123",
72
+ "finalityMs": 250
73
+ },
74
+ "expected": {
75
+ "header": "eyJzdWNjZXNzIjp0cnVlLCJ0eERpZ2VzdCI6InVwdG9fZGlnZXN0X2FiYyIsImFjdHVhbEFtb3VudCI6Ijc1MDAwMDAiLCJkZXBvc2l0SWQiOiIweGRlcG9zaXRfdXB0b18xMjMiLCJmaW5hbGl0eU1zIjoyNTB9"
76
+ },
77
+ "shouldReject": false
78
+ },
79
+ {
80
+ "description": "Success with actualAmount at zero (upto full refund)",
81
+ "input": {
82
+ "success": true,
83
+ "txDigest": "upto_refund_xyz",
84
+ "actualAmount": "0",
85
+ "depositId": "0xdeposit_refund_456",
86
+ "finalityMs": 180
87
+ },
88
+ "expected": {
89
+ "header": "eyJzdWNjZXNzIjp0cnVlLCJ0eERpZ2VzdCI6InVwdG9fcmVmdW5kX3h5eiIsImFjdHVhbEFtb3VudCI6IjAiLCJkZXBvc2l0SWQiOiIweGRlcG9zaXRfcmVmdW5kXzQ1NiIsImZpbmFsaXR5TXMiOjE4MH0="
90
+ },
91
+ "shouldReject": false
64
92
  }
65
93
  ]
@@ -0,0 +1,180 @@
1
+ [
2
+ {
3
+ "description": "Matching digest — verified is true, digests equal",
4
+ "payload": {
5
+ "s402Version": "1",
6
+ "scheme": "exact",
7
+ "payload": {
8
+ "transaction": "dHgtYnl0ZXMtaGVyZQ==",
9
+ "signature": "c2lnLWJ5dGVzLWhlcmU="
10
+ }
11
+ },
12
+ "settleResponse": {
13
+ "success": true,
14
+ "txDigest": "KNOWN_DIGEST_ABC123"
15
+ },
16
+ "expectedShape": {
17
+ "verified": "boolean",
18
+ "expectedDigest": "string",
19
+ "actualDigest": "string|null"
20
+ },
21
+ "invariants": [
22
+ "IF verified === true THEN expectedDigest === actualDigest",
23
+ "actualDigest MUST equal settleResponse.txDigest"
24
+ ],
25
+ "notes": "The implementation derives expectedDigest from payload.payload.transaction using a chain-specific algorithm. If the facilitator returned the correct digest, verified MUST be true."
26
+ },
27
+ {
28
+ "description": "Mismatched digest — verified is false with DIGEST_MISMATCH reason",
29
+ "payload": {
30
+ "s402Version": "1",
31
+ "scheme": "exact",
32
+ "payload": {
33
+ "transaction": "dHgtYnl0ZXMtaGVyZQ==",
34
+ "signature": "c2lnLWJ5dGVzLWhlcmU="
35
+ }
36
+ },
37
+ "settleResponse": {
38
+ "success": true,
39
+ "txDigest": "WRONG_DIGEST_XYZ789"
40
+ },
41
+ "expectedShape": {
42
+ "verified": "boolean",
43
+ "expectedDigest": "string",
44
+ "actualDigest": "string|null"
45
+ },
46
+ "invariants": [
47
+ "verified MUST be false",
48
+ "expectedDigest MUST NOT equal actualDigest",
49
+ "actualDigest MUST equal settleResponse.txDigest",
50
+ "reason SHOULD contain 'DIGEST_MISMATCH' or indicate the mismatch"
51
+ ],
52
+ "notes": "A malicious facilitator returns a real but unrelated digest. The client detects the mismatch locally without any RPC call."
53
+ },
54
+ {
55
+ "description": "Settle failed — no txDigest available",
56
+ "payload": {
57
+ "s402Version": "1",
58
+ "scheme": "exact",
59
+ "payload": {
60
+ "transaction": "dHgtYnl0ZXMtaGVyZQ==",
61
+ "signature": "c2lnLWJ5dGVzLWhlcmU="
62
+ }
63
+ },
64
+ "settleResponse": {
65
+ "success": false,
66
+ "error": "Insufficient gas"
67
+ },
68
+ "expectedShape": {
69
+ "verified": "boolean",
70
+ "expectedDigest": "string",
71
+ "actualDigest": "string|null"
72
+ },
73
+ "invariants": [
74
+ "verified MUST be false",
75
+ "actualDigest MUST be null (no digest was returned)",
76
+ "reason SHOULD indicate no digest was available"
77
+ ],
78
+ "notes": "When settlement fails, there is no digest to verify. The implementation should handle this gracefully rather than throwing."
79
+ },
80
+ {
81
+ "description": "Settle succeeded but txDigest is undefined",
82
+ "payload": {
83
+ "s402Version": "1",
84
+ "scheme": "exact",
85
+ "payload": {
86
+ "transaction": "dHgtYnl0ZXMtaGVyZQ==",
87
+ "signature": "c2lnLWJ5dGVzLWhlcmU="
88
+ }
89
+ },
90
+ "settleResponse": {
91
+ "success": true
92
+ },
93
+ "expectedShape": {
94
+ "verified": "boolean",
95
+ "expectedDigest": "string",
96
+ "actualDigest": "string|null"
97
+ },
98
+ "invariants": [
99
+ "verified MUST be false",
100
+ "actualDigest MUST be null",
101
+ "reason SHOULD indicate missing digest"
102
+ ],
103
+ "notes": "Edge case: facilitator reports success but omits the digest. This should be treated as unverifiable — the client cannot confirm settlement."
104
+ },
105
+ {
106
+ "description": "Invalid base64 in payload transaction bytes",
107
+ "payload": {
108
+ "s402Version": "1",
109
+ "scheme": "exact",
110
+ "payload": {
111
+ "transaction": "!!!NOT-BASE64!!!",
112
+ "signature": "c2lnLWJ5dGVzLWhlcmU="
113
+ }
114
+ },
115
+ "settleResponse": {
116
+ "success": true,
117
+ "txDigest": "SOME_DIGEST"
118
+ },
119
+ "expectedShape": {
120
+ "verified": "boolean",
121
+ "expectedDigest": "string",
122
+ "actualDigest": "string|null"
123
+ },
124
+ "invariants": [
125
+ "verified MUST be false",
126
+ "MUST NOT throw — return a verification result with reason"
127
+ ],
128
+ "notes": "Malformed input must fail gracefully. Implementations MUST catch decode errors and return { verified: false } rather than propagating exceptions."
129
+ },
130
+ {
131
+ "description": "Stream scheme — same verification contract applies",
132
+ "payload": {
133
+ "s402Version": "1",
134
+ "scheme": "stream",
135
+ "payload": {
136
+ "transaction": "c3RyZWFtLXR4LWJ5dGVz",
137
+ "signature": "c3RyZWFtLXNpZw=="
138
+ }
139
+ },
140
+ "settleResponse": {
141
+ "success": true,
142
+ "txDigest": "STREAM_DIGEST_123"
143
+ },
144
+ "expectedShape": {
145
+ "verified": "boolean",
146
+ "expectedDigest": "string",
147
+ "actualDigest": "string|null"
148
+ },
149
+ "invariants": [
150
+ "IF verified === true THEN expectedDigest === actualDigest",
151
+ "actualDigest MUST equal settleResponse.txDigest"
152
+ ],
153
+ "notes": "The S8 invariant applies to ALL client-signed schemes, not just exact. Stream, escrow, prepaid, and unlock-TX1 all sign full transactions before sending to the facilitator."
154
+ },
155
+ {
156
+ "description": "Scheme that cannot verify locally (e.g. unlock-TX2)",
157
+ "payload": {
158
+ "s402Version": "1",
159
+ "scheme": "unlock",
160
+ "payload": {
161
+ "transaction": "",
162
+ "signature": ""
163
+ }
164
+ },
165
+ "settleResponse": {
166
+ "success": true,
167
+ "txDigest": "FACILITATOR_BUILT_TX"
168
+ },
169
+ "expectedShape": {
170
+ "verified": "boolean",
171
+ "expectedDigest": "string",
172
+ "actualDigest": "string|null"
173
+ },
174
+ "invariants": [
175
+ "verified MUST be false",
176
+ "reason MUST be present and explain why verification is not possible"
177
+ ],
178
+ "notes": "For schemes where the facilitator constructs the transaction (e.g. unlock phase 2), the client has no signed bytes to derive a digest from. The implementation MUST return { verified: false } with a reason, not throw."
179
+ }
180
+ ]
@@ -175,6 +175,75 @@
175
175
  "shouldReject": true,
176
176
  "expectedErrorCode": "INVALID_PAYLOAD"
177
177
  },
178
+ {
179
+ "description": "Rejects upto.estimatedAmount exceeding maxAmount",
180
+ "input": {
181
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJhY2NlcHRzIjpbInVwdG8iXSwibmV0d29yayI6InN1aTptYWlubmV0IiwiYXNzZXQiOiJTVUkiLCJhbW91bnQiOiIxMDAwIiwicGF5VG8iOiIweGFiYyIsInVwdG8iOnsibWF4QW1vdW50IjoiNTAwMDAwMCIsInNldHRsZW1lbnREZWFkbGluZU1zIjoiMTcwMDAwMDAwMDAwMCIsImVzdGltYXRlZEFtb3VudCI6IjUwMDAwMDEifX0="
182
+ },
183
+ "shouldReject": true,
184
+ "expectedErrorCode": "INVALID_PAYLOAD"
185
+ },
186
+ {
187
+ "description": "Rejects upto payload with settlementCeiling of zero",
188
+ "input": {
189
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJ1cHRvIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRIZz0iLCJzaWduYXR1cmUiOiJjMmxuIiwibWF4QW1vdW50IjoiMTAwMDAwMCIsInNldHRsZW1lbnRDZWlsaW5nIjoiMCJ9fQ==",
190
+ "decodeAs": "payload"
191
+ },
192
+ "shouldReject": true,
193
+ "expectedErrorCode": "INVALID_PAYLOAD"
194
+ },
195
+ {
196
+ "description": "Rejects upto payload with settlementCeiling exceeding maxAmount",
197
+ "input": {
198
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJ1cHRvIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRIZz0iLCJzaWduYXR1cmUiOiJjMmxuIiwibWF4QW1vdW50IjoiMTAwMDAwMCIsInNldHRsZW1lbnRDZWlsaW5nIjoiMTAwMDAwMSJ9fQ==",
199
+ "decodeAs": "payload"
200
+ },
201
+ "shouldReject": true,
202
+ "expectedErrorCode": "INVALID_PAYLOAD"
203
+ },
204
+ {
205
+ "description": "Rejects upto.estimatedAmount as number (must be string)",
206
+ "input": {
207
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJhY2NlcHRzIjpbInVwdG8iXSwibmV0d29yayI6InN1aTptYWlubmV0IiwiYXNzZXQiOiJTVUkiLCJhbW91bnQiOiIxMDAwIiwicGF5VG8iOiIweGFiYyIsInVwdG8iOnsibWF4QW1vdW50IjoiNTAwMDAwMCIsInNldHRsZW1lbnREZWFkbGluZU1zIjoiMTcwMDAwMDAwMDAwMCIsImVzdGltYXRlZEFtb3VudCI6MTIzfX0="
208
+ },
209
+ "shouldReject": true,
210
+ "expectedErrorCode": "INVALID_PAYLOAD"
211
+ },
212
+ {
213
+ "description": "Rejects upto payload with numeric settlementCeiling (must be string)",
214
+ "input": {
215
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJ1cHRvIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRIZz0iLCJzaWduYXR1cmUiOiJjMmxuIiwibWF4QW1vdW50IjoiMTAwMDAwMCIsInNldHRsZW1lbnRDZWlsaW5nIjo1MDAwMDB9fQ==",
216
+ "decodeAs": "payload"
217
+ },
218
+ "shouldReject": true,
219
+ "expectedErrorCode": "INVALID_PAYLOAD"
220
+ },
221
+ {
222
+ "description": "Rejects prepaid payload with negative ratePerCall",
223
+ "input": {
224
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJwcmVwYWlkIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRIZz0iLCJzaWduYXR1cmUiOiJjMmxuIiwicmF0ZVBlckNhbGwiOiItNSJ9fQ==",
225
+ "decodeAs": "payload"
226
+ },
227
+ "shouldReject": true,
228
+ "expectedErrorCode": "INVALID_PAYLOAD"
229
+ },
230
+ {
231
+ "description": "Rejects prepaid payload with non-numeric maxCalls",
232
+ "input": {
233
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJzY2hlbWUiOiJwcmVwYWlkIiwicGF5bG9hZCI6eyJ0cmFuc2FjdGlvbiI6ImRIZz0iLCJzaWduYXR1cmUiOiJjMmxuIiwicmF0ZVBlckNhbGwiOiIxMDAiLCJtYXhDYWxscyI6ImFiYyJ9fQ==",
234
+ "decodeAs": "payload"
235
+ },
236
+ "shouldReject": true,
237
+ "expectedErrorCode": "INVALID_PAYLOAD"
238
+ },
239
+ {
240
+ "description": "Rejects mandate.minPerTx with leading zeros",
241
+ "input": {
242
+ "header": "eyJzNDAyVmVyc2lvbiI6IjEiLCJhY2NlcHRzIjpbImV4YWN0Il0sIm5ldHdvcmsiOiJzdWk6bWFpbm5ldCIsImFzc2V0IjoiU1VJIiwiYW1vdW50IjoiMTAwMCIsInBheVRvIjoiMHhhYmMiLCJtYW5kYXRlIjp7InJlcXVpcmVkIjp0cnVlLCJtaW5QZXJUeCI6IjAwNyJ9fQ=="
243
+ },
244
+ "shouldReject": true,
245
+ "expectedErrorCode": "INVALID_PAYLOAD"
246
+ },
178
247
  {
179
248
  "description": "Rejects invalid base64 header",
180
249
  "input": {