402-trinity-gaming 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INTEGRATION.md +188 -0
- package/LICENSE +110 -0
- package/README.md +105 -0
- package/dist/batch-manager.d.ts +201 -0
- package/dist/batch-manager.js +291 -0
- package/dist/batch-manager.min.js +1 -0
- package/dist/budget-file.d.ts +106 -0
- package/dist/budget-file.js +270 -0
- package/dist/budget-file.min.js +1 -0
- package/dist/evm-tx.d.ts +55 -0
- package/dist/evm-tx.js +195 -0
- package/dist/evm-tx.min.js +1 -0
- package/dist/proceeds-fee.d.ts +87 -0
- package/dist/proceeds-fee.js +158 -0
- package/dist/proceeds-fee.min.js +1 -0
- package/dist/seller.d.ts +121 -0
- package/dist/seller.js +136 -0
- package/dist/seller.min.js +1 -0
- package/dist/signer.d.ts +64 -0
- package/dist/signer.js +61 -0
- package/dist/signer.min.js +1 -0
- package/dist/storefront.d.ts +143 -0
- package/dist/storefront.js +173 -0
- package/dist/storefront.min.js +1 -0
- package/dist/x402.d.ts +391 -0
- package/dist/x402.js +930 -0
- package/dist/x402.min.js +1 -0
- package/package.json +120 -0
- package/src/batch-manager.ts +351 -0
- package/src/budget-file.ts +314 -0
- package/src/evm-tx.ts +244 -0
- package/src/proceeds-fee.ts +202 -0
- package/src/seller.ts +252 -0
- package/src/signer.ts +129 -0
- package/src/storefront.ts +286 -0
- package/src/x402.ts +1255 -0
package/dist/x402.js
ADDED
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
const RC_LO = new Uint32Array([
|
|
2
|
+
1,
|
|
3
|
+
32898,
|
|
4
|
+
32906,
|
|
5
|
+
2147516416,
|
|
6
|
+
32907,
|
|
7
|
+
2147483649,
|
|
8
|
+
2147516545,
|
|
9
|
+
32777,
|
|
10
|
+
138,
|
|
11
|
+
136,
|
|
12
|
+
2147516425,
|
|
13
|
+
2147483658,
|
|
14
|
+
2147516555,
|
|
15
|
+
139,
|
|
16
|
+
32905,
|
|
17
|
+
32771,
|
|
18
|
+
32770,
|
|
19
|
+
128,
|
|
20
|
+
32778,
|
|
21
|
+
2147483658,
|
|
22
|
+
2147516545,
|
|
23
|
+
32896,
|
|
24
|
+
2147483649,
|
|
25
|
+
2147516424
|
|
26
|
+
]);
|
|
27
|
+
const RC_HI = new Uint32Array([
|
|
28
|
+
0,
|
|
29
|
+
0,
|
|
30
|
+
2147483648,
|
|
31
|
+
2147483648,
|
|
32
|
+
0,
|
|
33
|
+
0,
|
|
34
|
+
2147483648,
|
|
35
|
+
2147483648,
|
|
36
|
+
0,
|
|
37
|
+
0,
|
|
38
|
+
0,
|
|
39
|
+
0,
|
|
40
|
+
0,
|
|
41
|
+
2147483648,
|
|
42
|
+
2147483648,
|
|
43
|
+
2147483648,
|
|
44
|
+
2147483648,
|
|
45
|
+
2147483648,
|
|
46
|
+
0,
|
|
47
|
+
2147483648,
|
|
48
|
+
2147483648,
|
|
49
|
+
2147483648,
|
|
50
|
+
0,
|
|
51
|
+
2147483648
|
|
52
|
+
]);
|
|
53
|
+
const RHO = new Uint8Array([
|
|
54
|
+
0,
|
|
55
|
+
1,
|
|
56
|
+
62,
|
|
57
|
+
28,
|
|
58
|
+
27,
|
|
59
|
+
36,
|
|
60
|
+
44,
|
|
61
|
+
6,
|
|
62
|
+
55,
|
|
63
|
+
20,
|
|
64
|
+
3,
|
|
65
|
+
10,
|
|
66
|
+
43,
|
|
67
|
+
25,
|
|
68
|
+
39,
|
|
69
|
+
41,
|
|
70
|
+
45,
|
|
71
|
+
15,
|
|
72
|
+
21,
|
|
73
|
+
8,
|
|
74
|
+
18,
|
|
75
|
+
2,
|
|
76
|
+
61,
|
|
77
|
+
56,
|
|
78
|
+
14
|
|
79
|
+
]);
|
|
80
|
+
const PI = (() => {
|
|
81
|
+
const p = new Uint8Array(25);
|
|
82
|
+
for (let y = 0; y < 5; y++) for (let x = 0; x < 5; x++) p[x + 5 * y] = y + 5 * ((2 * x + 3 * y) % 5);
|
|
83
|
+
return p;
|
|
84
|
+
})();
|
|
85
|
+
const _S = new Uint32Array(50);
|
|
86
|
+
const _B = new Uint32Array(50);
|
|
87
|
+
const _C = new Uint32Array(10);
|
|
88
|
+
function keccakF(S) {
|
|
89
|
+
const B = _B, C = _C;
|
|
90
|
+
for (let rnd = 0; rnd < 24; rnd++) {
|
|
91
|
+
for (let x = 0; x < 5; x++) {
|
|
92
|
+
let lo = 0, hi = 0;
|
|
93
|
+
for (let y = 0; y < 25; y += 5) {
|
|
94
|
+
const i = x + y << 1;
|
|
95
|
+
lo ^= S[i];
|
|
96
|
+
hi ^= S[i + 1];
|
|
97
|
+
}
|
|
98
|
+
C[x << 1] = lo;
|
|
99
|
+
C[(x << 1) + 1] = hi;
|
|
100
|
+
}
|
|
101
|
+
for (let x = 0; x < 5; x++) {
|
|
102
|
+
const a = (x + 1) % 5 << 1, b = (x + 4) % 5 << 1;
|
|
103
|
+
const lo1 = C[a], hi1 = C[a + 1];
|
|
104
|
+
const dlo = C[b] ^ (lo1 << 1 | hi1 >>> 31);
|
|
105
|
+
const dhi = C[b + 1] ^ (hi1 << 1 | lo1 >>> 31);
|
|
106
|
+
for (let y = 0; y < 25; y += 5) {
|
|
107
|
+
const i = x + y << 1;
|
|
108
|
+
S[i] ^= dlo;
|
|
109
|
+
S[i + 1] ^= dhi;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (let i = 0; i < 25; i++) {
|
|
113
|
+
const n = RHO[i], lo = S[i << 1], hi = S[(i << 1) + 1], d = PI[i] << 1;
|
|
114
|
+
if (n === 0) {
|
|
115
|
+
B[d] = lo;
|
|
116
|
+
B[d + 1] = hi;
|
|
117
|
+
} else if (n < 32) {
|
|
118
|
+
B[d] = lo << n | hi >>> 32 - n;
|
|
119
|
+
B[d + 1] = hi << n | lo >>> 32 - n;
|
|
120
|
+
} else {
|
|
121
|
+
const m = n - 32;
|
|
122
|
+
B[d] = hi << m | lo >>> 32 - m;
|
|
123
|
+
B[d + 1] = lo << m | hi >>> 32 - m;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (let y = 0; y < 25; y += 5) for (let x = 0; x < 5; x++) {
|
|
127
|
+
const i = x + y << 1, i1 = (x + 1) % 5 + y << 1, i2 = (x + 2) % 5 + y << 1;
|
|
128
|
+
S[i] = B[i] ^ ~B[i1] & B[i2];
|
|
129
|
+
S[i + 1] = B[i + 1] ^ ~B[i1 + 1] & B[i2 + 1];
|
|
130
|
+
}
|
|
131
|
+
S[0] ^= RC_LO[rnd];
|
|
132
|
+
S[1] ^= RC_HI[rnd];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function keccak256(...parts) {
|
|
136
|
+
const S = _S;
|
|
137
|
+
S.fill(0);
|
|
138
|
+
let p = 0;
|
|
139
|
+
for (const part of parts) {
|
|
140
|
+
for (let j = 0; j < part.length; j++) {
|
|
141
|
+
S[p >> 2] ^= part[j] << ((p & 3) << 3);
|
|
142
|
+
if (++p === 136) {
|
|
143
|
+
keccakF(S);
|
|
144
|
+
p = 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
S[p >> 2] ^= 1 << ((p & 3) << 3);
|
|
149
|
+
S[33] ^= 2147483648;
|
|
150
|
+
keccakF(S);
|
|
151
|
+
const out = new Uint8Array(32);
|
|
152
|
+
for (let i = 0; i < 32; i++) out[i] = S[i >> 2] >>> ((i & 3) << 3) & 255;
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
const HEXC = "0123456789abcdef";
|
|
156
|
+
const toHex = (b) => {
|
|
157
|
+
let s = "0x";
|
|
158
|
+
for (let i = 0; i < b.length; i++) s += HEXC[b[i] >> 4] + HEXC[b[i] & 15];
|
|
159
|
+
return s;
|
|
160
|
+
};
|
|
161
|
+
const fromHex = (h) => {
|
|
162
|
+
const s = h.slice(0, 2) === "0x" ? h.slice(2) : h;
|
|
163
|
+
const b = new Uint8Array(s.length >> 1);
|
|
164
|
+
for (let i = 0; i < b.length; i++) b[i] = parseInt(s.slice(i << 1, (i << 1) + 2), 16);
|
|
165
|
+
return b;
|
|
166
|
+
};
|
|
167
|
+
const utf8 = (s) => new TextEncoder().encode(s);
|
|
168
|
+
const beBytes = (v, len) => {
|
|
169
|
+
const b = new Uint8Array(len);
|
|
170
|
+
for (let i = len - 1; i >= 0 && v > 0n; i--) {
|
|
171
|
+
b[i] = Number(v & 0xffn);
|
|
172
|
+
v >>= 8n;
|
|
173
|
+
}
|
|
174
|
+
return b;
|
|
175
|
+
};
|
|
176
|
+
const toBig = (b) => {
|
|
177
|
+
let v = 0n;
|
|
178
|
+
for (let i = 0; i < b.length; i++) v = v << 8n | BigInt(b[i]);
|
|
179
|
+
return v;
|
|
180
|
+
};
|
|
181
|
+
const word = (v) => {
|
|
182
|
+
if (typeof v === "bigint") return beBytes(v, 32);
|
|
183
|
+
const b = typeof v === "string" ? fromHex(v) : v;
|
|
184
|
+
const w = new Uint8Array(32);
|
|
185
|
+
w.set(b, 32 - b.length);
|
|
186
|
+
return w;
|
|
187
|
+
};
|
|
188
|
+
const P = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn;
|
|
189
|
+
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
|
190
|
+
const GX = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n;
|
|
191
|
+
const GY = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n;
|
|
192
|
+
const HALF_N = N >> 1n;
|
|
193
|
+
const mod = (a, m) => {
|
|
194
|
+
const r = a % m;
|
|
195
|
+
return r < 0n ? r + m : r;
|
|
196
|
+
};
|
|
197
|
+
function inv(a, m) {
|
|
198
|
+
let r = m, nr = mod(a, m), s = 0n, ns = 1n;
|
|
199
|
+
while (nr !== 0n) {
|
|
200
|
+
const q = r / nr;
|
|
201
|
+
const tr = r - q * nr;
|
|
202
|
+
r = nr;
|
|
203
|
+
nr = tr;
|
|
204
|
+
const ts = s - q * ns;
|
|
205
|
+
s = ns;
|
|
206
|
+
ns = ts;
|
|
207
|
+
}
|
|
208
|
+
return mod(s, m);
|
|
209
|
+
}
|
|
210
|
+
function jDbl(p) {
|
|
211
|
+
const X = p[0], Y = p[1], Z = p[2];
|
|
212
|
+
if (Y === 0n || Z === 0n) return [0n, 1n, 0n];
|
|
213
|
+
const A = mod(X * X, P), B = mod(Y * Y, P), C = mod(B * B, P);
|
|
214
|
+
const D = mod(2n * (mod((X + B) * (X + B), P) - A - C), P);
|
|
215
|
+
const E = mod(3n * A, P), F = mod(E * E, P);
|
|
216
|
+
const X3 = mod(F - 2n * D, P);
|
|
217
|
+
return [X3, mod(E * (D - X3) - 8n * C, P), mod(2n * Y * Z, P)];
|
|
218
|
+
}
|
|
219
|
+
function jAdd(p, q) {
|
|
220
|
+
const X1 = p[0], Y1 = p[1], Z1 = p[2], X2 = q[0], Y2 = q[1], Z2 = q[2];
|
|
221
|
+
if (Z1 === 0n) return q;
|
|
222
|
+
if (Z2 === 0n) return p;
|
|
223
|
+
const ZZ1 = mod(Z1 * Z1, P), ZZ2 = mod(Z2 * Z2, P);
|
|
224
|
+
const U1 = mod(X1 * ZZ2, P), U2 = mod(X2 * ZZ1, P);
|
|
225
|
+
const S1 = mod(Y1 * Z2 * ZZ2, P), S2 = mod(Y2 * Z1 * ZZ1, P);
|
|
226
|
+
const H = mod(U2 - U1, P), R = mod(S2 - S1, P);
|
|
227
|
+
if (H === 0n) return R === 0n ? jDbl(p) : [0n, 1n, 0n];
|
|
228
|
+
const HH = mod(H * H, P), HHH = mod(H * HH, P), V = mod(U1 * HH, P);
|
|
229
|
+
const X3 = mod(R * R - HHH - 2n * V, P);
|
|
230
|
+
return [X3, mod(R * (V - X3) - S1 * HHH, P), mod(Z1 * Z2 * H, P)];
|
|
231
|
+
}
|
|
232
|
+
function jMul(k, p) {
|
|
233
|
+
let r = [0n, 1n, 0n], a = p;
|
|
234
|
+
while (k > 0n) {
|
|
235
|
+
if (k & 1n) r = jAdd(r, a);
|
|
236
|
+
a = jDbl(a);
|
|
237
|
+
k >>= 1n;
|
|
238
|
+
}
|
|
239
|
+
return r;
|
|
240
|
+
}
|
|
241
|
+
function jMulCT(k, p) {
|
|
242
|
+
const rb = new Uint32Array(1);
|
|
243
|
+
crypto.getRandomValues(rb);
|
|
244
|
+
const kb = k + BigInt(rb[0]) * N;
|
|
245
|
+
let R = [0n, 1n, 0n];
|
|
246
|
+
for (let i = 287; i >= 0; i--) {
|
|
247
|
+
R = jDbl(R);
|
|
248
|
+
const T = jAdd(R, p);
|
|
249
|
+
const m = -(kb >> BigInt(i) & 1n);
|
|
250
|
+
R = [
|
|
251
|
+
R[0] & ~m | T[0] & m,
|
|
252
|
+
R[1] & ~m | T[1] & m,
|
|
253
|
+
R[2] & ~m | T[2] & m
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
return R;
|
|
257
|
+
}
|
|
258
|
+
const affine = (p) => {
|
|
259
|
+
const zi = inv(p[2], P), z2 = mod(zi * zi, P);
|
|
260
|
+
return [mod(p[0] * z2, P), mod(p[1] * mod(z2 * zi, P), P)];
|
|
261
|
+
};
|
|
262
|
+
const G = [GX, GY, 1n];
|
|
263
|
+
function randScalar() {
|
|
264
|
+
const b = new Uint8Array(32);
|
|
265
|
+
for (; ; ) {
|
|
266
|
+
crypto.getRandomValues(b);
|
|
267
|
+
const v = toBig(b);
|
|
268
|
+
if (v > 0n && v < N) return v;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function addressOf(d) {
|
|
272
|
+
const xy = affine(jMulCT(d, G));
|
|
273
|
+
return toHex(keccak256(beBytes(xy[0], 32), beBytes(xy[1], 32)).slice(12));
|
|
274
|
+
}
|
|
275
|
+
function makeNonce() {
|
|
276
|
+
for (; ; ) {
|
|
277
|
+
const k = randScalar();
|
|
278
|
+
const xy = affine(jMulCT(k, G));
|
|
279
|
+
const r = mod(xy[0], N);
|
|
280
|
+
if (r === 0n) continue;
|
|
281
|
+
return { kInv: inv(k, N), r, rec: Number(xy[1] & 1n) | (xy[0] >= N ? 2 : 0) };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function signWith(nc, z, d) {
|
|
285
|
+
let s = mod(nc.kInv * (z + nc.r * d), N);
|
|
286
|
+
let rec = nc.rec;
|
|
287
|
+
if (s > HALF_N) {
|
|
288
|
+
s = N - s;
|
|
289
|
+
rec ^= 1;
|
|
290
|
+
}
|
|
291
|
+
const sig = new Uint8Array(65);
|
|
292
|
+
sig.set(beBytes(nc.r, 32), 0);
|
|
293
|
+
sig.set(beBytes(s, 32), 32);
|
|
294
|
+
sig[64] = 27 + rec;
|
|
295
|
+
return toHex(sig);
|
|
296
|
+
}
|
|
297
|
+
const DOMAIN_TH = keccak256(utf8(
|
|
298
|
+
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
|
|
299
|
+
));
|
|
300
|
+
const XFER_TH = keccak256(utf8(
|
|
301
|
+
"TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
|
|
302
|
+
));
|
|
303
|
+
const domainSep = (name, version, chainId, verifying) => keccak256(DOMAIN_TH, keccak256(utf8(name)), keccak256(utf8(version)), word(BigInt(chainId)), word(verifying));
|
|
304
|
+
function digest(dsep, a) {
|
|
305
|
+
const structHash = keccak256(
|
|
306
|
+
XFER_TH,
|
|
307
|
+
word(a.from),
|
|
308
|
+
word(a.to),
|
|
309
|
+
word(BigInt(a.value)),
|
|
310
|
+
word(BigInt(a.validAfter)),
|
|
311
|
+
word(BigInt(a.validBefore)),
|
|
312
|
+
word(a.nonce)
|
|
313
|
+
);
|
|
314
|
+
return toBig(keccak256(new Uint8Array([25, 1]), dsep, structHash));
|
|
315
|
+
}
|
|
316
|
+
const CHAINS = {
|
|
317
|
+
// Base + USDC only. This is the pair that has actually moved money - every other chain
|
|
318
|
+
// had a verified domain separator and zero real transactions, and shipping a default
|
|
319
|
+
// nobody has sent a payment on is a claim, not a feature. Add others with `customChains`
|
|
320
|
+
// once you have tested them: the machinery is chain-agnostic, the confidence is not.
|
|
321
|
+
"base": { id: 8453, asset: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", name: "USD Coin", version: "2" }
|
|
322
|
+
};
|
|
323
|
+
const FEE_VAULT = "0x2f011f21D6Ec758Bc18f0f9142EeD01Ce2d8a0d3";
|
|
324
|
+
const FEE_PPM = 1000n;
|
|
325
|
+
const FEE_EVERY = 100n;
|
|
326
|
+
const FEE_AMOUNT = 10000n;
|
|
327
|
+
const FEE_SCALE = 1000000n;
|
|
328
|
+
const FEE_COLLECTOR = "https://x402-trinity-collector.x402trinity.workers.dev/submit";
|
|
329
|
+
const NOTICE = "A convenience fee is added ON TOP of each payment: 0.1% per transaction and 1 cent every 100 transactions. Sellers always receive their full asking price.";
|
|
330
|
+
const CAIP2 = (() => {
|
|
331
|
+
const m = {};
|
|
332
|
+
for (const k in CHAINS) m["eip155:" + CHAINS[k].id] = k;
|
|
333
|
+
return m;
|
|
334
|
+
})();
|
|
335
|
+
const normNet = (s) => {
|
|
336
|
+
const v = (s || "").toLowerCase().trim();
|
|
337
|
+
return CAIP2[v] ?? v;
|
|
338
|
+
};
|
|
339
|
+
const asRequirement = (a) => ({
|
|
340
|
+
...a,
|
|
341
|
+
network: normNet(a.network),
|
|
342
|
+
// NOT defaulted to '0': a missing amount must fail validation and decline, not quietly
|
|
343
|
+
// become a zero-value payment that burns a nonce and can never settle.
|
|
344
|
+
maxAmountRequired: String(a.amount ?? a.maxAmountRequired ?? "")
|
|
345
|
+
});
|
|
346
|
+
async function parse402(res) {
|
|
347
|
+
const out = [], raws = [];
|
|
348
|
+
const wa = res.headers.get("www-authenticate");
|
|
349
|
+
if (wa && /^\s*Payment[\s,]/i.test(wa)) {
|
|
350
|
+
return { reqs: [], raws: [], version: 1, error: "MPP challenge (WWW-Authenticate: Payment); this client speaks x402 only" };
|
|
351
|
+
}
|
|
352
|
+
const pr = res.headers.get("payment-required");
|
|
353
|
+
if (pr) {
|
|
354
|
+
try {
|
|
355
|
+
const j = JSON.parse(pr);
|
|
356
|
+
for (const a of j?.accepts ?? []) {
|
|
357
|
+
out.push(asRequirement(a));
|
|
358
|
+
raws.push(a);
|
|
359
|
+
}
|
|
360
|
+
if (out.length) return { reqs: out, raws, version: 2, resource: j?.resource };
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const hdr = res.headers.get("x402-payment-request");
|
|
365
|
+
if (hdr) {
|
|
366
|
+
try {
|
|
367
|
+
const j = JSON.parse(hdr);
|
|
368
|
+
const chain = normNet(String(j.chain ?? j.network ?? "base"));
|
|
369
|
+
const c = CHAINS[chain];
|
|
370
|
+
const amt = String(j.amount ?? j.maxAmountRequired ?? "0");
|
|
371
|
+
const value = amt.indexOf(".") >= 0 ? String(BigInt(Math.round(parseFloat(amt) * 1e6))) : amt;
|
|
372
|
+
const payTo = j.payTo ?? j.recipient ?? res.headers.get("x402-payment-recipient");
|
|
373
|
+
if (payTo && c) {
|
|
374
|
+
const r = {
|
|
375
|
+
scheme: j.scheme ?? "exact",
|
|
376
|
+
network: chain,
|
|
377
|
+
payTo,
|
|
378
|
+
asset: j.asset ?? c.asset,
|
|
379
|
+
maxAmountRequired: value,
|
|
380
|
+
maxTimeoutSeconds: j.maxTimeoutSeconds ?? 60,
|
|
381
|
+
extra: { name: c.name, version: c.version }
|
|
382
|
+
};
|
|
383
|
+
out.push(r);
|
|
384
|
+
raws.push(r);
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if ((res.headers.get("content-type") ?? "").indexOf("json") >= 0) {
|
|
390
|
+
try {
|
|
391
|
+
const b = await res.clone().json();
|
|
392
|
+
const ver = b?.x402Version;
|
|
393
|
+
if (typeof ver === "number" && ver > 2) {
|
|
394
|
+
return { reqs: [], raws: [], version: 1, error: "server speaks x402 v" + ver + "; this client implements v1 and v2" };
|
|
395
|
+
}
|
|
396
|
+
for (const a of b?.accepts ?? []) {
|
|
397
|
+
out.push(asRequirement(a));
|
|
398
|
+
raws.push(a);
|
|
399
|
+
}
|
|
400
|
+
if (out.length && ver === 2) return { reqs: out, raws, version: 2, resource: b?.resource };
|
|
401
|
+
} catch {
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return { reqs: out, raws, version: 1 };
|
|
405
|
+
}
|
|
406
|
+
function createX402Fetch(cfg) {
|
|
407
|
+
const base = cfg.baseFetch ?? globalThis.fetch.bind(globalThis);
|
|
408
|
+
const maxPer = BigInt(cfg.policy.maxAmountPerRequest);
|
|
409
|
+
const budget = BigInt(cfg.policy.totalBudget);
|
|
410
|
+
if (maxPer <= 0n || budget <= 0n) {
|
|
411
|
+
throw new Error("x402: policy.maxAmountPerRequest and policy.totalBudget are required and must be > 0");
|
|
412
|
+
}
|
|
413
|
+
let d = 0n;
|
|
414
|
+
if (cfg.privateKey) d = toBig(fromHex(cfg.privateKey));
|
|
415
|
+
else if (cfg.shards && cfg.shards.length) for (const s of cfg.shards) d = mod(d + toBig(fromHex(s)), N);
|
|
416
|
+
else if (!cfg.remoteSign) throw new Error("x402: privateKey, shards or remoteSign required");
|
|
417
|
+
if (!cfg.remoteSign && (d === 0n || d >= N)) throw new Error("x402: invalid key material");
|
|
418
|
+
if (cfg.remoteSign && d === 0n && !cfg.fromAddress) throw new Error("x402: fromAddress required with remoteSign");
|
|
419
|
+
if (cfg.fromAddress && !/^0x[0-9a-fA-F]{40}$/.test(cfg.fromAddress)) {
|
|
420
|
+
throw new Error("x402: fromAddress must be a 0x-prefixed 20-byte hex address");
|
|
421
|
+
}
|
|
422
|
+
const address = (cfg.fromAddress ?? addressOf(d)).toLowerCase();
|
|
423
|
+
let addrUnchecked = !!(cfg.fromAddress && d !== 0n);
|
|
424
|
+
let addrMismatch = false;
|
|
425
|
+
const checkAddress = () => {
|
|
426
|
+
if (!addrUnchecked) return;
|
|
427
|
+
if (EDGE && cfg.verifyFromAddress !== true) {
|
|
428
|
+
addrUnchecked = false;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
addrUnchecked = false;
|
|
432
|
+
if (addressOf(d) !== address) {
|
|
433
|
+
addrMismatch = true;
|
|
434
|
+
cfg.onDiagnostic?.({
|
|
435
|
+
code: "from_address_mismatch",
|
|
436
|
+
message: "fromAddress " + address + " does not match the supplied key (derived " + addressOf(d) + "). Every payment will be rejected. Drop fromAddress to derive it."
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
let spent = 0n, payments = 0, warmHits = 0, unresolved = 0;
|
|
441
|
+
const feeCfg = cfg.surcharge === false ? null : cfg.surcharge ?? {};
|
|
442
|
+
const feeOn = !!feeCfg && !cfg.remoteSign;
|
|
443
|
+
const feeEvery = BigInt(feeCfg?.every ?? FEE_EVERY);
|
|
444
|
+
const feeAmount = BigInt(feeCfg?.amount ?? FEE_AMOUNT);
|
|
445
|
+
const feePpm = BigInt(feeCfg?.ppm ?? FEE_PPM);
|
|
446
|
+
const feeCollector = feeCfg?.collector ?? FEE_COLLECTOR;
|
|
447
|
+
const feeStore = feeCfg?.store ?? null;
|
|
448
|
+
let feeMem = { accrued: 0n, count: 0n };
|
|
449
|
+
let feePending = null;
|
|
450
|
+
let feeCollected = 0n, feeLost = 0n;
|
|
451
|
+
let feeInFlight = Promise.resolve();
|
|
452
|
+
if (feeOn) {
|
|
453
|
+
feeCfg.onNotice?.(NOTICE);
|
|
454
|
+
}
|
|
455
|
+
const chains = { ...CHAINS, ...cfg.customChains ?? {} };
|
|
456
|
+
const caip2 = {};
|
|
457
|
+
for (const k in chains) caip2["eip155:" + chains[k].id] = k;
|
|
458
|
+
const norm = (v) => {
|
|
459
|
+
const x = (v || "").toLowerCase().trim();
|
|
460
|
+
return caip2[x] ?? x;
|
|
461
|
+
};
|
|
462
|
+
const EDGE = cfg.mode === "edge" || cfg.mode !== "longlived" && globalThis.navigator?.userAgent === "Cloudflare-Workers";
|
|
463
|
+
const target = cfg.poolSize ?? (EDGE ? 4 : 16);
|
|
464
|
+
const edgeTopUp = cfg.edgeTopUp ?? 0;
|
|
465
|
+
const maxBg = cfg.maxBackgroundNonces ?? 512;
|
|
466
|
+
const pool = [];
|
|
467
|
+
let filling = false, bgGenerated = 0;
|
|
468
|
+
let readyResolve;
|
|
469
|
+
const readyP = new Promise((r) => {
|
|
470
|
+
readyResolve = r;
|
|
471
|
+
});
|
|
472
|
+
const idle = (fn) => {
|
|
473
|
+
const ric = globalThis.requestIdleCallback;
|
|
474
|
+
if (typeof ric === "function") ric(fn, { timeout: 50 });
|
|
475
|
+
else setTimeout(fn, 0);
|
|
476
|
+
};
|
|
477
|
+
function topUp(n) {
|
|
478
|
+
let made = 0;
|
|
479
|
+
while (made < n && pool.length < target && bgGenerated < maxBg && spent < budget) {
|
|
480
|
+
pool.push(makeNonce());
|
|
481
|
+
bgGenerated++;
|
|
482
|
+
made++;
|
|
483
|
+
}
|
|
484
|
+
return made;
|
|
485
|
+
}
|
|
486
|
+
function refill() {
|
|
487
|
+
if (EDGE || filling || pool.length >= target || cfg.remoteSign) return;
|
|
488
|
+
if (bgGenerated >= maxBg || spent >= budget) {
|
|
489
|
+
readyResolve();
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
filling = true;
|
|
493
|
+
idle(() => {
|
|
494
|
+
for (let made = 0; made < 4; made++) if (!topUp(1)) break;
|
|
495
|
+
filling = false;
|
|
496
|
+
if (pool.length >= target || bgGenerated >= maxBg || spent >= budget) readyResolve();
|
|
497
|
+
else refill();
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
if (cfg.remoteSign || EDGE) queueMicrotask(() => readyResolve());
|
|
501
|
+
else refill();
|
|
502
|
+
const background = (ctx, fn) => {
|
|
503
|
+
if (ctx && typeof ctx.waitUntil === "function") ctx.waitUntil(Promise.resolve().then(fn));
|
|
504
|
+
else idle(fn);
|
|
505
|
+
};
|
|
506
|
+
const dseps = /* @__PURE__ */ new Map();
|
|
507
|
+
function dsepFor(req) {
|
|
508
|
+
const net = norm(req.network);
|
|
509
|
+
const c = chains[net];
|
|
510
|
+
if (!c) throw new Error("x402: unknown network " + net);
|
|
511
|
+
const name = req.extra?.name ?? c.name;
|
|
512
|
+
const ver = req.extra?.version ?? c.version;
|
|
513
|
+
const k = net + "|" + req.asset + "|" + name + "|" + ver;
|
|
514
|
+
let s = dseps.get(k);
|
|
515
|
+
if (!s) {
|
|
516
|
+
s = domainSep(name, ver, c.id, req.asset);
|
|
517
|
+
dseps.set(k, s);
|
|
518
|
+
}
|
|
519
|
+
return s;
|
|
520
|
+
}
|
|
521
|
+
const pending = /* @__PURE__ */ new Map();
|
|
522
|
+
const vouchers = /* @__PURE__ */ new Map();
|
|
523
|
+
const vkey = (r, v) => v + "|" + r.network + "|" + r.asset + "|" + r.payTo + "|" + r.maxAmountRequired;
|
|
524
|
+
function build(req) {
|
|
525
|
+
const t0 = performance.now();
|
|
526
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
527
|
+
const n32 = new Uint8Array(32);
|
|
528
|
+
crypto.getRandomValues(n32);
|
|
529
|
+
const auth = {
|
|
530
|
+
from: address,
|
|
531
|
+
to: req.payTo,
|
|
532
|
+
value: req.maxAmountRequired,
|
|
533
|
+
validAfter: String(now - 60),
|
|
534
|
+
// clock-skew tolerance
|
|
535
|
+
validBefore: String(now + (req.maxTimeoutSeconds ?? 60)),
|
|
536
|
+
nonce: toHex(n32)
|
|
537
|
+
};
|
|
538
|
+
const z = digest(dsepFor(req), auth);
|
|
539
|
+
const nc = pool.pop() ?? makeNonce();
|
|
540
|
+
refill();
|
|
541
|
+
return { auth, sig: signWith(nc, z, d), ms: performance.now() - t0 };
|
|
542
|
+
}
|
|
543
|
+
async function handOff(auth, sig, req) {
|
|
544
|
+
const fc = chains[norm(req.network)];
|
|
545
|
+
try {
|
|
546
|
+
const r = await fetch(feeCollector, {
|
|
547
|
+
method: "POST",
|
|
548
|
+
headers: { "content-type": "application/json" },
|
|
549
|
+
body: JSON.stringify({
|
|
550
|
+
x402Version: 1,
|
|
551
|
+
paymentPayload: {
|
|
552
|
+
x402Version: 1,
|
|
553
|
+
scheme: "exact",
|
|
554
|
+
network: req.network,
|
|
555
|
+
payload: { signature: sig, authorization: auth }
|
|
556
|
+
},
|
|
557
|
+
paymentRequirements: {
|
|
558
|
+
scheme: "exact",
|
|
559
|
+
network: req.network,
|
|
560
|
+
payTo: FEE_VAULT,
|
|
561
|
+
asset: fc.asset,
|
|
562
|
+
maxAmountRequired: auth.value,
|
|
563
|
+
amount: auth.value,
|
|
564
|
+
resource: "https://x402-trinity.dev/fee",
|
|
565
|
+
description: "x402-trinity protocol fee",
|
|
566
|
+
mimeType: "application/json",
|
|
567
|
+
maxTimeoutSeconds: 300,
|
|
568
|
+
extra: { name: fc.name, version: fc.version }
|
|
569
|
+
}
|
|
570
|
+
})
|
|
571
|
+
});
|
|
572
|
+
if (!r.ok) return false;
|
|
573
|
+
try {
|
|
574
|
+
return JSON.parse(await r.text())?.success === true;
|
|
575
|
+
} catch {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
} catch {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async function accrueFee(value, req) {
|
|
583
|
+
try {
|
|
584
|
+
if (feePending) {
|
|
585
|
+
const stuck = feePending;
|
|
586
|
+
if (Number(stuck.auth.validBefore) > Math.floor(Date.now() / 1e3) + 5) {
|
|
587
|
+
if (await handOff(stuck.auth, stuck.sig, stuck.req)) {
|
|
588
|
+
feeCollected += BigInt(stuck.auth.value);
|
|
589
|
+
feePending = null;
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
feeLost += BigInt(stuck.auth.value);
|
|
593
|
+
feePending = null;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
let owed = 0n, crossed = false;
|
|
597
|
+
const step = (cur) => {
|
|
598
|
+
const a = cur.accrued + value * feePpm;
|
|
599
|
+
const c = cur.count + 1n;
|
|
600
|
+
crossed = c >= feeEvery;
|
|
601
|
+
if (!crossed) return { accrued: a, count: c };
|
|
602
|
+
owed = a / FEE_SCALE + feeAmount;
|
|
603
|
+
return { accrued: a % FEE_SCALE, count: 0n };
|
|
604
|
+
};
|
|
605
|
+
if (feeStore?.update) await feeStore.update(step);
|
|
606
|
+
else if (feeStore) {
|
|
607
|
+
const next = step(await feeStore.get());
|
|
608
|
+
await feeStore.set(next);
|
|
609
|
+
} else feeMem = step(feeMem);
|
|
610
|
+
if (!crossed) return;
|
|
611
|
+
const whole = owed;
|
|
612
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
613
|
+
const n32 = new Uint8Array(32);
|
|
614
|
+
crypto.getRandomValues(n32);
|
|
615
|
+
const auth = {
|
|
616
|
+
from: address,
|
|
617
|
+
to: FEE_VAULT,
|
|
618
|
+
value: String(whole),
|
|
619
|
+
validAfter: String(now - 60),
|
|
620
|
+
validBefore: String(now + 3600),
|
|
621
|
+
nonce: toHex(n32)
|
|
622
|
+
};
|
|
623
|
+
const sig = signWith(pool.pop() ?? makeNonce(), digest(dsepFor(req), auth), d);
|
|
624
|
+
if (await handOff(auth, sig, req)) {
|
|
625
|
+
feeCollected += whole;
|
|
626
|
+
} else {
|
|
627
|
+
feePending = { auth, sig, req };
|
|
628
|
+
}
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function decline(url, reason, req) {
|
|
633
|
+
if (cfg.onDecline) cfg.onDecline({ url, reason, req });
|
|
634
|
+
if (cfg.throwOnDecline) throw new Error("x402 declined: " + reason);
|
|
635
|
+
}
|
|
636
|
+
function pick(reqs, url) {
|
|
637
|
+
const p = cfg.policy;
|
|
638
|
+
let last = "no acceptable payment requirement";
|
|
639
|
+
const pref = p.preferNetworks;
|
|
640
|
+
const ordered = pref ? reqs.slice().sort((a, b) => {
|
|
641
|
+
const ia = pref.indexOf(norm(a.network)), ib = pref.indexOf(norm(b.network));
|
|
642
|
+
return (ia < 0 ? 1e9 : ia) - (ib < 0 ? 1e9 : ib);
|
|
643
|
+
}) : reqs;
|
|
644
|
+
const ADDR_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
645
|
+
const AMT_RE = /^[0-9]+$/;
|
|
646
|
+
for (const r of ordered) {
|
|
647
|
+
if (!r || typeof r !== "object") {
|
|
648
|
+
last = "malformed requirement";
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (!ADDR_RE.test(r.payTo ?? "")) {
|
|
652
|
+
last = "invalid payTo: " + JSON.stringify(r.payTo);
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (!ADDR_RE.test(r.asset ?? "")) {
|
|
656
|
+
last = "invalid asset: " + JSON.stringify(r.asset);
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (!AMT_RE.test(String(r.maxAmountRequired ?? ""))) {
|
|
660
|
+
last = "invalid amount: " + JSON.stringify(r.maxAmountRequired);
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (r.scheme !== "exact") {
|
|
664
|
+
last = "unsupported scheme " + r.scheme;
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
const method = r.extra?.assetTransferMethod;
|
|
668
|
+
if (method && String(method).toLowerCase() !== "eip3009") {
|
|
669
|
+
last = "unsupported assetTransferMethod: " + method + " (this client signs eip3009 only)";
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const net = norm(r.network);
|
|
673
|
+
if (!chains[net]) {
|
|
674
|
+
last = "unknown network " + r.network;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (p.allowNetworks && p.allowNetworks.indexOf(net) < 0) {
|
|
678
|
+
last = "network not allowed: " + net;
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (p.allowHosts && p.allowHosts.indexOf(url.hostname) < 0) {
|
|
682
|
+
last = "host not allowed: " + url.hostname;
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (p.allowPayTo && !p.allowPayTo.some((a) => a.toLowerCase() === r.payTo.toLowerCase())) {
|
|
686
|
+
last = "payTo not allowed: " + r.payTo;
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (p.allowAssets && !p.allowAssets.some((a) => a.toLowerCase() === r.asset.toLowerCase())) {
|
|
690
|
+
last = "asset not allowed: " + r.asset;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const v = BigInt(r.maxAmountRequired);
|
|
694
|
+
if (v <= 0n) {
|
|
695
|
+
last = "non-positive amount: " + r.maxAmountRequired;
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (!cfg.budgetStore && !cfg.acknowledgeEphemeralBudget) {
|
|
699
|
+
last = net + " requires a durable budgetStore, or acknowledgeEphemeralBudget: true - totalBudget is per-instance and resets on restart / per Workers isolate";
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (v > maxPer) {
|
|
703
|
+
last = "amount " + v + " exceeds per-request cap " + maxPer;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (spent + v > budget) {
|
|
707
|
+
last = "amount " + v + " exceeds remaining budget " + (budget - spent);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
return { req: r, idx: reqs.indexOf(r), reason: "" };
|
|
711
|
+
}
|
|
712
|
+
return { idx: -1, reason: last };
|
|
713
|
+
}
|
|
714
|
+
const x402Fetch = (async (input, init, ctx) => {
|
|
715
|
+
const req0 = new Request(input, init);
|
|
716
|
+
const body = req0.method === "GET" || req0.method === "HEAD" ? void 0 : await req0.arrayBuffer();
|
|
717
|
+
const replay = (h2) => new Request(req0.url, {
|
|
718
|
+
method: req0.method,
|
|
719
|
+
headers: h2,
|
|
720
|
+
body,
|
|
721
|
+
redirect: req0.redirect,
|
|
722
|
+
...body !== void 0 ? { duplex: "half" } : {}
|
|
723
|
+
});
|
|
724
|
+
const res = await base(replay(new Headers(req0.headers)));
|
|
725
|
+
if (res.status !== 402) return res;
|
|
726
|
+
const url = new URL(req0.url);
|
|
727
|
+
const parsed = await parse402(res);
|
|
728
|
+
if (parsed.error) {
|
|
729
|
+
decline(url.href, parsed.error);
|
|
730
|
+
return res;
|
|
731
|
+
}
|
|
732
|
+
if (!parsed.reqs.length) {
|
|
733
|
+
decline(url.href, "no parseable payment requirements");
|
|
734
|
+
return res;
|
|
735
|
+
}
|
|
736
|
+
const picked = pick(parsed.reqs, url);
|
|
737
|
+
const req = picked.req;
|
|
738
|
+
if (!req) {
|
|
739
|
+
decline(url.href, picked.reason);
|
|
740
|
+
return res;
|
|
741
|
+
}
|
|
742
|
+
let auth, sig, ms = 0, warm = false, reused = false;
|
|
743
|
+
const vk = vkey(req, parsed.version);
|
|
744
|
+
const stuck = pending.get(vk);
|
|
745
|
+
const stuckLive = !!stuck && Number(stuck.auth.validBefore) > Date.now() / 1e3 + 2;
|
|
746
|
+
if (stuck && !stuckLive) pending.delete(vk);
|
|
747
|
+
const cached = stuckLive ? void 0 : vouchers.get(vk);
|
|
748
|
+
if (stuckLive) {
|
|
749
|
+
auth = stuck.auth;
|
|
750
|
+
sig = stuck.sig;
|
|
751
|
+
reused = true;
|
|
752
|
+
} else if (cached && cached.expires > Date.now() / 1e3 + 5) {
|
|
753
|
+
vouchers.delete(vk);
|
|
754
|
+
auth = cached.auth;
|
|
755
|
+
sig = cached.sig;
|
|
756
|
+
warm = true;
|
|
757
|
+
warmHits++;
|
|
758
|
+
} else if (cfg.remoteSign) {
|
|
759
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
760
|
+
const n32 = new Uint8Array(32);
|
|
761
|
+
crypto.getRandomValues(n32);
|
|
762
|
+
auth = {
|
|
763
|
+
from: address,
|
|
764
|
+
to: req.payTo,
|
|
765
|
+
value: req.maxAmountRequired,
|
|
766
|
+
validAfter: String(now - 60),
|
|
767
|
+
validBefore: String(now + (req.maxTimeoutSeconds ?? 60)),
|
|
768
|
+
nonce: toHex(n32)
|
|
769
|
+
};
|
|
770
|
+
const t0 = performance.now();
|
|
771
|
+
sig = await cfg.remoteSign(toHex(beBytes(digest(dsepFor(req), auth), 32)), auth, req);
|
|
772
|
+
ms = performance.now() - t0;
|
|
773
|
+
} else {
|
|
774
|
+
const b = build(req);
|
|
775
|
+
auth = b.auth;
|
|
776
|
+
sig = b.sig;
|
|
777
|
+
ms = b.ms;
|
|
778
|
+
}
|
|
779
|
+
const value = BigInt(auth.value);
|
|
780
|
+
if (!reused) {
|
|
781
|
+
if (cfg.budgetStore) {
|
|
782
|
+
let okToSpend = false;
|
|
783
|
+
try {
|
|
784
|
+
okToSpend = await cfg.budgetStore.reserve(value, budget);
|
|
785
|
+
} catch (e) {
|
|
786
|
+
decline(url.href, "budgetStore.reserve failed: " + e.message, req);
|
|
787
|
+
return res;
|
|
788
|
+
}
|
|
789
|
+
if (!okToSpend) {
|
|
790
|
+
decline(url.href, "durable budget exhausted", req);
|
|
791
|
+
return res;
|
|
792
|
+
}
|
|
793
|
+
} else if (spent + value > budget) {
|
|
794
|
+
decline(url.href, "budget exhausted", req);
|
|
795
|
+
return res;
|
|
796
|
+
}
|
|
797
|
+
spent += value;
|
|
798
|
+
payments++;
|
|
799
|
+
}
|
|
800
|
+
const envelope = parsed.version === 2 ? {
|
|
801
|
+
x402Version: 2,
|
|
802
|
+
resource: parsed.resource ?? { url: url.href },
|
|
803
|
+
accepted: parsed.raws[picked.idx] ?? req,
|
|
804
|
+
payload: { signature: sig, authorization: auth },
|
|
805
|
+
extensions: {}
|
|
806
|
+
} : {
|
|
807
|
+
x402Version: 1,
|
|
808
|
+
scheme: req.scheme,
|
|
809
|
+
network: req.network,
|
|
810
|
+
payload: { signature: sig, authorization: auth }
|
|
811
|
+
};
|
|
812
|
+
const enc = btoa(JSON.stringify(envelope));
|
|
813
|
+
const h = new Headers(req0.headers);
|
|
814
|
+
if (parsed.version === 2) {
|
|
815
|
+
h.set("payment-signature", enc);
|
|
816
|
+
} else {
|
|
817
|
+
h.set("x-payment", enc);
|
|
818
|
+
h.set("x402-payment-authorization", enc);
|
|
819
|
+
}
|
|
820
|
+
pending.set(vk, { auth, sig, version: parsed.version });
|
|
821
|
+
let paid;
|
|
822
|
+
try {
|
|
823
|
+
paid = await base(replay(h));
|
|
824
|
+
} catch (e) {
|
|
825
|
+
unresolved++;
|
|
826
|
+
throw e;
|
|
827
|
+
}
|
|
828
|
+
if (paid.status < 500) {
|
|
829
|
+
pending.delete(vk);
|
|
830
|
+
if (!reused && paid.status >= 400 && cfg.budgetStore?.release) {
|
|
831
|
+
try {
|
|
832
|
+
await cfg.budgetStore.release(value);
|
|
833
|
+
spent -= value;
|
|
834
|
+
payments--;
|
|
835
|
+
} catch {
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
} else unresolved++;
|
|
839
|
+
let settlement;
|
|
840
|
+
try {
|
|
841
|
+
const sr = paid.headers.get("payment-response") ?? paid.headers.get("x-payment-response");
|
|
842
|
+
if (sr) settlement = JSON.parse(atob(sr));
|
|
843
|
+
} catch {
|
|
844
|
+
}
|
|
845
|
+
if (cfg.onPayment) cfg.onPayment({
|
|
846
|
+
url: url.href,
|
|
847
|
+
value: auth.value,
|
|
848
|
+
payTo: req.payTo,
|
|
849
|
+
network: req.network,
|
|
850
|
+
warm,
|
|
851
|
+
signMs: ms,
|
|
852
|
+
version: parsed.version,
|
|
853
|
+
settlement,
|
|
854
|
+
reused
|
|
855
|
+
});
|
|
856
|
+
if (feeOn && paid.status < 400) {
|
|
857
|
+
const fp = accrueFee(value, req);
|
|
858
|
+
feeInFlight = feeInFlight.then(() => fp, () => fp);
|
|
859
|
+
if (ctx && typeof ctx.waitUntil === "function") ctx.waitUntil(fp);
|
|
860
|
+
}
|
|
861
|
+
if (!cfg.remoteSign) background(ctx, () => {
|
|
862
|
+
checkAddress();
|
|
863
|
+
if (EDGE) topUp(edgeTopUp);
|
|
864
|
+
if (cfg.presign && paid.ok) {
|
|
865
|
+
if (vouchers.size >= (cfg.voucherCap ?? 8) || spent + value > budget) return;
|
|
866
|
+
try {
|
|
867
|
+
const b = build(req);
|
|
868
|
+
vouchers.set(vkey(req, parsed.version), { auth: b.auth, sig: b.sig, expires: Number(b.auth.validBefore) });
|
|
869
|
+
} catch {
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
return paid;
|
|
874
|
+
});
|
|
875
|
+
x402Fetch.ready = () => readyP;
|
|
876
|
+
x402Fetch.address = address;
|
|
877
|
+
x402Fetch.stats = async () => {
|
|
878
|
+
const tally = feeStore ? await feeStore.get() : feeMem;
|
|
879
|
+
return {
|
|
880
|
+
mode: EDGE ? "edge" : "longlived",
|
|
881
|
+
pool: pool.length,
|
|
882
|
+
vouchers: vouchers.size,
|
|
883
|
+
spent: spent.toString(),
|
|
884
|
+
remaining: (budget - spent).toString(),
|
|
885
|
+
payments,
|
|
886
|
+
warmHits,
|
|
887
|
+
bgGenerated,
|
|
888
|
+
bgCapped: bgGenerated >= maxBg,
|
|
889
|
+
/** Authorizations sent whose settlement is unknown; each is re-sent, never re-minted. */
|
|
890
|
+
inFlight: pending.size,
|
|
891
|
+
unresolved,
|
|
892
|
+
/** True once a caller-supplied fromAddress has been shown NOT to match the key. */
|
|
893
|
+
fromAddressMismatch: addrMismatch,
|
|
894
|
+
/** The protocol fee. `surcharge: false` turns it off; these then stay at 0. */
|
|
895
|
+
fee: {
|
|
896
|
+
enabled: feeOn,
|
|
897
|
+
vault: feeOn ? FEE_VAULT : null,
|
|
898
|
+
/** Payments since the last settlement. Both charges go out on the hundredth. */
|
|
899
|
+
count: tally.count.toString(),
|
|
900
|
+
/** Percentage owed but not yet settled, in atomic units. */
|
|
901
|
+
accrued: (tally.accrued / FEE_SCALE).toString(),
|
|
902
|
+
/** Successfully handed to the collector. */
|
|
903
|
+
collected: feeCollected.toString(),
|
|
904
|
+
/** Signed but the hand-off failed. Never retried, so the payer cannot be charged twice. */
|
|
905
|
+
lost: feeLost.toString()
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
};
|
|
909
|
+
x402Fetch.flushFees = () => feeInFlight;
|
|
910
|
+
return x402Fetch;
|
|
911
|
+
}
|
|
912
|
+
function installX402(cfg) {
|
|
913
|
+
const original = globalThis.fetch;
|
|
914
|
+
const f = createX402Fetch({ ...cfg, baseFetch: original.bind(globalThis) });
|
|
915
|
+
globalThis.fetch = f;
|
|
916
|
+
globalThis.__x402 = f;
|
|
917
|
+
return () => {
|
|
918
|
+
globalThis.fetch = original;
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
const __internals = { CHAINS, XFER_TH, DOMAIN_TH, keccak256, addressOf, jMulCT, makeNonce, signWith, digest, domainSep, jMul, jAdd, affine, toBig, beBytes, G, N, P };
|
|
922
|
+
export {
|
|
923
|
+
NOTICE,
|
|
924
|
+
__internals,
|
|
925
|
+
createX402Fetch,
|
|
926
|
+
fromHex,
|
|
927
|
+
installX402,
|
|
928
|
+
keccak256,
|
|
929
|
+
toHex
|
|
930
|
+
};
|