bsv-x402 0.2.0 → 0.3.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/dist/index.cjs CHANGED
@@ -26,7 +26,9 @@ __export(index_exports, {
26
26
  RateLimiter: () => RateLimiter,
27
27
  TIER_PRESETS: () => TIER_PRESETS,
28
28
  WalletTwoFactorProvider: () => WalletTwoFactorProvider,
29
+ constructBrc105Proof: () => constructBrc105Proof,
29
30
  createX402Fetch: () => createX402Fetch,
31
+ parseBrc105Challenge: () => parseBrc105Challenge,
30
32
  parseChallenge: () => parseChallenge,
31
33
  resolveSitePolicy: () => resolveSitePolicy,
32
34
  resolveSpendLimits: () => resolveSpendLimits,
@@ -34,6 +36,527 @@ __export(index_exports, {
34
36
  });
35
37
  module.exports = __toCommonJS(index_exports);
36
38
 
39
+ // src/brc105-challenge.ts
40
+ function parseBrc105Challenge(response) {
41
+ const version = response.headers.get("x-bsv-payment-version");
42
+ if (version === null) {
43
+ throw new Error("BRC-105: missing x-bsv-payment-version header");
44
+ }
45
+ if (version !== "1.0") {
46
+ throw new Error(`BRC-105: unsupported version "${version}", expected "1.0"`);
47
+ }
48
+ const satoshisRaw = response.headers.get("x-bsv-payment-satoshis-required");
49
+ if (satoshisRaw === null) {
50
+ throw new Error("BRC-105: missing x-bsv-payment-satoshis-required header");
51
+ }
52
+ const satoshisRequired = Number(satoshisRaw);
53
+ if (!Number.isFinite(satoshisRequired) || !Number.isInteger(satoshisRequired) || satoshisRequired <= 0) {
54
+ throw new Error("BRC-105: satoshis-required must be a positive integer");
55
+ }
56
+ const serverIdentityKey = response.headers.get("x-bsv-auth-identity-key");
57
+ if (serverIdentityKey === null || serverIdentityKey.length === 0) {
58
+ throw new Error("BRC-105: missing or empty x-bsv-auth-identity-key header");
59
+ }
60
+ if (!/^0[23][0-9a-fA-F]{64}$/.test(serverIdentityKey)) {
61
+ throw new Error("BRC-105: x-bsv-auth-identity-key must be a 33-byte compressed public key (hex)");
62
+ }
63
+ const derivationPrefix = response.headers.get("x-bsv-payment-derivation-prefix");
64
+ if (derivationPrefix === null || derivationPrefix.length === 0) {
65
+ throw new Error("BRC-105: missing or empty x-bsv-payment-derivation-prefix header");
66
+ }
67
+ return {
68
+ version,
69
+ satoshisRequired,
70
+ serverIdentityKey,
71
+ derivationPrefix
72
+ };
73
+ }
74
+
75
+ // src/brc105-proof.ts
76
+ function bytesToHex(bytes) {
77
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
78
+ }
79
+ function hexToBytes(hex) {
80
+ if (hex.length % 2 !== 0) throw new Error("Hex string must have even length");
81
+ const bytes = new Uint8Array(hex.length / 2);
82
+ for (let i = 0; i < hex.length; i += 2) {
83
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
84
+ }
85
+ return bytes;
86
+ }
87
+ function bytesToBase64(bytes) {
88
+ let binary = "";
89
+ for (const b of bytes) binary += String.fromCharCode(b);
90
+ return btoa(binary);
91
+ }
92
+ function numberArrayToBase64(arr) {
93
+ return bytesToBase64(new Uint8Array(arr));
94
+ }
95
+ var RIPEMD160_CONSTANTS = {
96
+ // Round constants for left and right paths
97
+ KL: [0, 1518500249, 1859775393, 2400959708, 2840853838],
98
+ KR: [1352829926, 1548603684, 1836072691, 2053994217, 0],
99
+ // Message schedule (word index per step)
100
+ RL: [
101
+ 0,
102
+ 1,
103
+ 2,
104
+ 3,
105
+ 4,
106
+ 5,
107
+ 6,
108
+ 7,
109
+ 8,
110
+ 9,
111
+ 10,
112
+ 11,
113
+ 12,
114
+ 13,
115
+ 14,
116
+ 15,
117
+ 7,
118
+ 4,
119
+ 13,
120
+ 1,
121
+ 10,
122
+ 6,
123
+ 15,
124
+ 3,
125
+ 12,
126
+ 0,
127
+ 9,
128
+ 5,
129
+ 2,
130
+ 14,
131
+ 11,
132
+ 8,
133
+ 3,
134
+ 10,
135
+ 14,
136
+ 4,
137
+ 9,
138
+ 15,
139
+ 8,
140
+ 1,
141
+ 2,
142
+ 7,
143
+ 0,
144
+ 6,
145
+ 13,
146
+ 11,
147
+ 5,
148
+ 12,
149
+ 1,
150
+ 9,
151
+ 11,
152
+ 10,
153
+ 0,
154
+ 8,
155
+ 12,
156
+ 4,
157
+ 13,
158
+ 3,
159
+ 7,
160
+ 15,
161
+ 14,
162
+ 5,
163
+ 6,
164
+ 2,
165
+ 4,
166
+ 0,
167
+ 5,
168
+ 9,
169
+ 7,
170
+ 12,
171
+ 2,
172
+ 10,
173
+ 14,
174
+ 1,
175
+ 3,
176
+ 8,
177
+ 11,
178
+ 6,
179
+ 15,
180
+ 13
181
+ ],
182
+ RR: [
183
+ 5,
184
+ 14,
185
+ 7,
186
+ 0,
187
+ 9,
188
+ 2,
189
+ 11,
190
+ 4,
191
+ 13,
192
+ 6,
193
+ 15,
194
+ 8,
195
+ 1,
196
+ 10,
197
+ 3,
198
+ 12,
199
+ 6,
200
+ 11,
201
+ 3,
202
+ 7,
203
+ 0,
204
+ 13,
205
+ 5,
206
+ 10,
207
+ 14,
208
+ 15,
209
+ 8,
210
+ 12,
211
+ 4,
212
+ 9,
213
+ 1,
214
+ 2,
215
+ 15,
216
+ 5,
217
+ 1,
218
+ 3,
219
+ 7,
220
+ 14,
221
+ 6,
222
+ 9,
223
+ 11,
224
+ 8,
225
+ 12,
226
+ 2,
227
+ 10,
228
+ 0,
229
+ 4,
230
+ 13,
231
+ 8,
232
+ 6,
233
+ 4,
234
+ 1,
235
+ 3,
236
+ 11,
237
+ 15,
238
+ 0,
239
+ 5,
240
+ 12,
241
+ 2,
242
+ 13,
243
+ 9,
244
+ 7,
245
+ 10,
246
+ 14,
247
+ 12,
248
+ 15,
249
+ 10,
250
+ 4,
251
+ 1,
252
+ 5,
253
+ 8,
254
+ 7,
255
+ 6,
256
+ 2,
257
+ 13,
258
+ 14,
259
+ 0,
260
+ 3,
261
+ 9,
262
+ 11
263
+ ],
264
+ // Rotation amounts
265
+ SL: [
266
+ 11,
267
+ 14,
268
+ 15,
269
+ 12,
270
+ 5,
271
+ 8,
272
+ 7,
273
+ 9,
274
+ 11,
275
+ 13,
276
+ 14,
277
+ 15,
278
+ 6,
279
+ 7,
280
+ 9,
281
+ 8,
282
+ 7,
283
+ 6,
284
+ 8,
285
+ 13,
286
+ 11,
287
+ 9,
288
+ 7,
289
+ 15,
290
+ 7,
291
+ 12,
292
+ 15,
293
+ 9,
294
+ 11,
295
+ 7,
296
+ 13,
297
+ 12,
298
+ 11,
299
+ 13,
300
+ 6,
301
+ 7,
302
+ 14,
303
+ 9,
304
+ 13,
305
+ 15,
306
+ 14,
307
+ 8,
308
+ 13,
309
+ 6,
310
+ 5,
311
+ 12,
312
+ 7,
313
+ 5,
314
+ 11,
315
+ 12,
316
+ 14,
317
+ 15,
318
+ 14,
319
+ 15,
320
+ 9,
321
+ 8,
322
+ 9,
323
+ 14,
324
+ 5,
325
+ 6,
326
+ 8,
327
+ 6,
328
+ 5,
329
+ 12,
330
+ 9,
331
+ 15,
332
+ 5,
333
+ 11,
334
+ 6,
335
+ 8,
336
+ 13,
337
+ 12,
338
+ 5,
339
+ 12,
340
+ 13,
341
+ 14,
342
+ 11,
343
+ 8,
344
+ 5,
345
+ 6
346
+ ],
347
+ SR: [
348
+ 8,
349
+ 9,
350
+ 9,
351
+ 11,
352
+ 13,
353
+ 15,
354
+ 15,
355
+ 5,
356
+ 7,
357
+ 7,
358
+ 8,
359
+ 11,
360
+ 14,
361
+ 14,
362
+ 12,
363
+ 6,
364
+ 9,
365
+ 13,
366
+ 15,
367
+ 7,
368
+ 12,
369
+ 8,
370
+ 9,
371
+ 11,
372
+ 7,
373
+ 7,
374
+ 12,
375
+ 7,
376
+ 6,
377
+ 15,
378
+ 13,
379
+ 11,
380
+ 9,
381
+ 7,
382
+ 15,
383
+ 11,
384
+ 8,
385
+ 6,
386
+ 6,
387
+ 14,
388
+ 12,
389
+ 13,
390
+ 5,
391
+ 14,
392
+ 13,
393
+ 13,
394
+ 7,
395
+ 5,
396
+ 15,
397
+ 5,
398
+ 8,
399
+ 11,
400
+ 14,
401
+ 14,
402
+ 6,
403
+ 14,
404
+ 6,
405
+ 9,
406
+ 12,
407
+ 9,
408
+ 12,
409
+ 5,
410
+ 15,
411
+ 8,
412
+ 8,
413
+ 5,
414
+ 12,
415
+ 9,
416
+ 12,
417
+ 5,
418
+ 14,
419
+ 6,
420
+ 8,
421
+ 13,
422
+ 6,
423
+ 5,
424
+ 15,
425
+ 13,
426
+ 11,
427
+ 11
428
+ ]
429
+ };
430
+ function rotl32(x, n) {
431
+ return (x << n | x >>> 32 - n) >>> 0;
432
+ }
433
+ function f(j, x, y, z) {
434
+ if (j < 16) return (x ^ y ^ z) >>> 0;
435
+ if (j < 32) return (x & y | ~x & z) >>> 0;
436
+ if (j < 48) return ((x | ~y) ^ z) >>> 0;
437
+ if (j < 64) return (x & z | y & ~z) >>> 0;
438
+ return (x ^ (y | ~z)) >>> 0;
439
+ }
440
+ function ripemd160(message) {
441
+ const msgLen = message.length;
442
+ const bitLen = msgLen * 8;
443
+ const padLen = (55 - msgLen % 64 + 64) % 64 + 1;
444
+ const padded = new Uint8Array(msgLen + padLen + 8);
445
+ padded.set(message);
446
+ padded[msgLen] = 128;
447
+ const view = new DataView(padded.buffer);
448
+ view.setUint32(padded.length - 8, bitLen >>> 0, true);
449
+ view.setUint32(padded.length - 4, bitLen / 4294967296 >>> 0, true);
450
+ let h0 = 1732584193;
451
+ let h1 = 4023233417;
452
+ let h2 = 2562383102;
453
+ let h3 = 271733878;
454
+ let h4 = 3285377520;
455
+ const { KL, KR, RL, RR, SL, SR } = RIPEMD160_CONSTANTS;
456
+ for (let offset = 0; offset < padded.length; offset += 64) {
457
+ const w = new Uint32Array(16);
458
+ for (let i = 0; i < 16; i++) {
459
+ w[i] = view.getUint32(offset + i * 4, true);
460
+ }
461
+ let al = h0, bl = h1, cl = h2, dl = h3, el = h4;
462
+ let ar = h0, br = h1, cr = h2, dr = h3, er = h4;
463
+ for (let j = 0; j < 80; j++) {
464
+ const round = j >>> 4;
465
+ let t2 = al + f(j, bl, cl, dl) + w[RL[j]] + KL[round] >>> 0;
466
+ t2 = rotl32(t2, SL[j]) + el >>> 0;
467
+ al = el;
468
+ el = dl;
469
+ dl = rotl32(cl, 10);
470
+ cl = bl;
471
+ bl = t2;
472
+ t2 = ar + f(79 - j, br, cr, dr) + w[RR[j]] + KR[round] >>> 0;
473
+ t2 = rotl32(t2, SR[j]) + er >>> 0;
474
+ ar = er;
475
+ er = dr;
476
+ dr = rotl32(cr, 10);
477
+ cr = br;
478
+ br = t2;
479
+ }
480
+ const t = h1 + cl + dr >>> 0;
481
+ h1 = h2 + dl + er >>> 0;
482
+ h2 = h3 + el + ar >>> 0;
483
+ h3 = h4 + al + br >>> 0;
484
+ h4 = h0 + bl + cr >>> 0;
485
+ h0 = t;
486
+ }
487
+ const digest = new Uint8Array(20);
488
+ const dv = new DataView(digest.buffer);
489
+ dv.setUint32(0, h0, true);
490
+ dv.setUint32(4, h1, true);
491
+ dv.setUint32(8, h2, true);
492
+ dv.setUint32(12, h3, true);
493
+ dv.setUint32(16, h4, true);
494
+ return digest;
495
+ }
496
+ async function hash160(data) {
497
+ const sha256 = new Uint8Array(await crypto.subtle.digest("SHA-256", data));
498
+ return ripemd160(sha256);
499
+ }
500
+ async function pubkeyToP2PKHLockingScript(pubkeyHex) {
501
+ const pubkeyBytes = hexToBytes(pubkeyHex);
502
+ const pubkeyHash = await hash160(pubkeyBytes);
503
+ return `76a914${bytesToHex(pubkeyHash)}88ac`;
504
+ }
505
+ async function createDerivationSuffix(wallet) {
506
+ const randomBytes = new Uint8Array(16);
507
+ crypto.getRandomValues(randomBytes);
508
+ const firstHalf = Array.from(randomBytes);
509
+ const keyID = new TextDecoder().decode(randomBytes);
510
+ const { hmac } = await wallet.createHmac({
511
+ data: firstHalf,
512
+ protocolID: [2, "server hmac"],
513
+ keyID,
514
+ counterparty: "self"
515
+ });
516
+ const nonceBytes = [...firstHalf, ...hmac];
517
+ return numberArrayToBase64(nonceBytes);
518
+ }
519
+ async function constructBrc105Proof(challenge, wallet) {
520
+ const derivationSuffix = await createDerivationSuffix(wallet);
521
+ const keyID = `${challenge.derivationPrefix} ${derivationSuffix}`;
522
+ const { publicKey: derivedPublicKey } = await wallet.getPublicKey({
523
+ protocolID: [2, "3241645161d8"],
524
+ keyID,
525
+ counterparty: challenge.serverIdentityKey
526
+ });
527
+ const lockingScript = await pubkeyToP2PKHLockingScript(derivedPublicKey);
528
+ const result = await wallet.createAction({
529
+ description: "BRC-105 payment",
530
+ outputs: [{
531
+ satoshis: challenge.satoshisRequired,
532
+ lockingScript,
533
+ description: "BRC-105 payment output",
534
+ customInstructions: JSON.stringify({
535
+ derivationPrefix: challenge.derivationPrefix,
536
+ derivationSuffix,
537
+ payee: challenge.serverIdentityKey
538
+ })
539
+ }],
540
+ options: {
541
+ randomizeOutputs: false
542
+ }
543
+ });
544
+ let transactionBase64;
545
+ if (result.tx && Array.isArray(result.tx) && result.tx.length > 0) {
546
+ transactionBase64 = numberArrayToBase64(result.tx);
547
+ } else if (result.rawTx && typeof result.rawTx === "string" && result.rawTx.length > 0) {
548
+ transactionBase64 = bytesToBase64(hexToBytes(result.rawTx));
549
+ } else {
550
+ throw new Error("Wallet returned no transaction data (neither tx nor rawTx)");
551
+ }
552
+ return {
553
+ derivationPrefix: challenge.derivationPrefix,
554
+ derivationSuffix,
555
+ transaction: transactionBase64,
556
+ txid: result.txid
557
+ };
558
+ }
559
+
37
560
  // src/challenge.ts
38
561
  function parseChallenge(header) {
39
562
  let parsed;
@@ -250,28 +773,29 @@ var RateLimiter = class {
250
773
  this.broken = state?.circuitBroken ?? false;
251
774
  this.now = now ?? Date.now;
252
775
  }
253
- check(challenge, origin) {
776
+ check(request, origin) {
254
777
  if (this.broken) {
255
778
  return { action: "block", reason: "Circuit breaker tripped \u2014 call resetLimits() to clear", severity: "trip" };
256
779
  }
257
- if (!Number.isFinite(challenge.amount) || !Number.isInteger(challenge.amount) || challenge.amount <= 0) {
780
+ const amount = request.amount;
781
+ if (!Number.isFinite(amount) || !Number.isInteger(amount) || amount <= 0) {
258
782
  return { action: "block", reason: "Invalid transaction amount rejected", severity: "reject" };
259
783
  }
260
- if (challenge.amount > BFG_PER_TX_CEILING_SATOSHIS) {
784
+ if (amount > BFG_PER_TX_CEILING_SATOSHIS) {
261
785
  return { action: "block", reason: `Exceeds BFG per-tx ceiling (${BFG_PER_TX_CEILING_SATOSHIS} sats)`, severity: "reject" };
262
786
  }
263
787
  const dayAgo = this.now() - WINDOW_MS.day;
264
788
  const dailyTotal = this.sumSatoshis(dayAgo);
265
- if (dailyTotal + challenge.amount > BFG_DAILY_CEILING_SATOSHIS) {
789
+ if (dailyTotal + amount > BFG_DAILY_CEILING_SATOSHIS) {
266
790
  return { action: "block", reason: `Exceeds BFG daily ceiling (${BFG_DAILY_CEILING_SATOSHIS} sats)`, severity: "trip" };
267
791
  }
268
- if (challenge.amount > this.limits.perTxMaxSatoshis) {
792
+ if (amount > this.limits.perTxMaxSatoshis) {
269
793
  return { action: "block", reason: `Exceeds per-tx limit (${this.limits.perTxMaxSatoshis} sats)`, severity: "reject" };
270
794
  }
271
795
  const isCustomSite = this.hasCustomPolicy(origin);
272
796
  const effectiveLimits = this.effectiveWindows(origin);
273
797
  const effectivePerTx = this.effectivePerTxMax(origin);
274
- if (effectivePerTx !== void 0 && challenge.amount > effectivePerTx) {
798
+ if (effectivePerTx !== void 0 && amount > effectivePerTx) {
275
799
  return { action: "block", reason: `Exceeds per-tx limit for ${origin} (${effectivePerTx} sats)`, severity: "reject" };
276
800
  }
277
801
  let yellowLight;
@@ -280,19 +804,19 @@ var RateLimiter = class {
280
804
  const windowEntries = this.entriesInWindow(cutoff, isCustomSite ? origin : void 0);
281
805
  const totalSats = windowEntries.reduce((sum, e) => sum + e.satoshis, 0);
282
806
  const totalTx = windowEntries.length;
283
- if (totalSats + challenge.amount > wl.maxSatoshis) {
807
+ if (totalSats + amount > wl.maxSatoshis) {
284
808
  return { action: "block", reason: `Exceeds ${wl.window} sats limit (${wl.maxSatoshis})`, severity: "window" };
285
809
  }
286
810
  if (totalTx + 1 > wl.maxTransactions) {
287
811
  return { action: "block", reason: `Exceeds ${wl.window} tx count limit (${wl.maxTransactions})`, severity: "window" };
288
812
  }
289
- if (this.limits.yellowLightThreshold < 1 && !yellowLight && totalSats + challenge.amount > wl.maxSatoshis * this.limits.yellowLightThreshold) {
813
+ if (this.limits.yellowLightThreshold < 1 && !yellowLight && totalSats + amount > wl.maxSatoshis * this.limits.yellowLightThreshold) {
290
814
  yellowLight = {
291
815
  origin,
292
816
  currentSpend: totalSats,
293
817
  limit: wl.maxSatoshis,
294
818
  window: wl.window,
295
- challenge
819
+ challenge: request
296
820
  };
297
821
  }
298
822
  }
@@ -567,6 +1091,8 @@ function createX402Fetch(config = {}) {
567
1091
  const storage = config.storage ?? new LocalStorageAdapter();
568
1092
  const twoFactor = config.twoFactorProvider;
569
1093
  const constructProof = config.proofConstructor ?? defaultConstructProof;
1094
+ const brc105ProofConstructor = config.brc105ProofConstructor;
1095
+ const brc105Wallet = config.brc105Wallet;
570
1096
  const nowFn = config.now ?? Date.now;
571
1097
  const mutex = createMutex();
572
1098
  const needs2fa = limits.require2fa;
@@ -588,76 +1114,135 @@ function createX402Fetch(config = {}) {
588
1114
  await storage.save(rl.getState());
589
1115
  await storage.saveSitePolicies(limits.sitePolicies);
590
1116
  }
591
- const fetchFn = async function x402Fetch2(input, init) {
592
- const response = await fetch(input, init);
593
- if (response.status !== 402) return response;
594
- const challengeHeader = response.headers.get("X402-Challenge");
595
- if (!challengeHeader) return response;
596
- let challenge;
597
- try {
598
- challenge = parseChallenge(challengeHeader);
599
- } catch {
600
- return response;
601
- }
602
- const origin = extractOrigin(input);
1117
+ async function handlePaymentFlow(originalResponse, input, init, origin, amount, protocol, buildProof, retryWithProof, makeLedgerEntry) {
603
1118
  return mutex(async () => {
604
1119
  const rl = await ensureInitialised();
605
1120
  const sitePolicy = await resolveSitePolicy(origin, limits, twoFactor);
606
- if (sitePolicy.action === "block") return response;
1121
+ if (sitePolicy.action === "block") return originalResponse;
607
1122
  if (!limits.sitePolicies[origin]) {
608
1123
  limits.sitePolicies[origin] = sitePolicy;
609
1124
  await storage.saveSitePolicies(limits.sitePolicies);
610
1125
  }
611
- const result = rl.check(challenge, origin);
1126
+ const spendCheckable = { amount, origin, protocol };
1127
+ const result = rl.check(spendCheckable, origin);
612
1128
  if (result.action === "block") {
613
1129
  if (result.severity === "trip") {
614
1130
  rl.trip();
615
1131
  await persist(rl);
616
1132
  config.onLimitReached?.(result.reason);
617
- return response;
1133
+ return originalResponse;
618
1134
  }
619
1135
  if (result.severity === "window" && twoFactor) {
620
1136
  config.onLimitReached?.(result.reason);
621
1137
  const override = await twoFactor.verify({
622
1138
  type: "limit-override",
623
- amount: challenge.amount,
1139
+ amount,
624
1140
  origin,
625
1141
  reason: result.reason
626
1142
  });
627
1143
  if (override) {
628
1144
  } else {
629
- return response;
1145
+ return originalResponse;
630
1146
  }
631
1147
  } else {
632
1148
  config.onLimitReached?.(result.reason);
633
- return response;
1149
+ return originalResponse;
634
1150
  }
635
1151
  }
636
1152
  if (result.action === "yellow-light") {
637
1153
  const proceed = config.onYellowLight ? await config.onYellowLight(result.detail) : false;
638
- if (!proceed) return response;
1154
+ if (!proceed) return originalResponse;
639
1155
  }
640
- if (limits.require2fa.onHighValueTx && challenge.amount > limits.require2fa.highValueThreshold) {
641
- if (!twoFactor) return response;
1156
+ if (limits.require2fa.onHighValueTx && amount > limits.require2fa.highValueThreshold) {
1157
+ if (!twoFactor) return originalResponse;
642
1158
  const verified = await twoFactor.verify({
643
1159
  type: "high-value-tx",
644
- amount: challenge.amount,
1160
+ amount,
645
1161
  origin
646
1162
  });
647
- if (!verified) return response;
1163
+ if (!verified) return originalResponse;
648
1164
  }
649
- const proof = await constructProof(challenge);
650
- rl.record({
651
- timestamp: nowFn(),
652
- origin,
653
- satoshis: challenge.amount,
654
- txid: proof.txid
655
- });
1165
+ let proof;
1166
+ try {
1167
+ proof = await buildProof();
1168
+ } catch {
1169
+ return originalResponse;
1170
+ }
1171
+ rl.record(makeLedgerEntry(proof));
656
1172
  await persist(rl);
657
- const headers = new Headers(init?.headers);
658
- headers.set("X402-Proof", JSON.stringify(proof));
659
- return fetch(input, { ...init, headers });
1173
+ return retryWithProof(proof);
660
1174
  });
1175
+ }
1176
+ const fetchFn = async function x402Fetch2(input, init) {
1177
+ const response = await fetch(input, init);
1178
+ if (response.status !== 402) return response;
1179
+ const origin = extractOrigin(input);
1180
+ const challengeHeader = response.headers.get("X402-Challenge");
1181
+ if (challengeHeader) {
1182
+ let challenge;
1183
+ try {
1184
+ challenge = parseChallenge(challengeHeader);
1185
+ } catch {
1186
+ return response;
1187
+ }
1188
+ return handlePaymentFlow(
1189
+ response,
1190
+ input,
1191
+ init,
1192
+ origin,
1193
+ challenge.amount,
1194
+ "x402",
1195
+ async () => constructProof(challenge),
1196
+ (proof) => {
1197
+ const headers = new Headers(init?.headers);
1198
+ headers.set("X402-Proof", JSON.stringify(proof));
1199
+ return fetch(input, { ...init, headers });
1200
+ },
1201
+ (proof) => ({
1202
+ timestamp: nowFn(),
1203
+ origin,
1204
+ satoshis: challenge.amount,
1205
+ txid: proof.txid
1206
+ })
1207
+ );
1208
+ }
1209
+ const brc105Version = response.headers.get("x-bsv-payment-version");
1210
+ if (brc105Version) {
1211
+ if (!brc105ProofConstructor && !brc105Wallet) return response;
1212
+ let brc105Challenge;
1213
+ try {
1214
+ brc105Challenge = parseBrc105Challenge(response);
1215
+ } catch {
1216
+ return response;
1217
+ }
1218
+ return handlePaymentFlow(
1219
+ response,
1220
+ input,
1221
+ init,
1222
+ origin,
1223
+ brc105Challenge.satoshisRequired,
1224
+ "brc105",
1225
+ async () => {
1226
+ if (brc105ProofConstructor) {
1227
+ return brc105ProofConstructor(brc105Challenge);
1228
+ }
1229
+ return constructBrc105Proof(brc105Challenge, brc105Wallet);
1230
+ },
1231
+ (proof) => {
1232
+ const headers = new Headers(init?.headers);
1233
+ headers.set("x-bsv-payment", JSON.stringify(proof));
1234
+ return fetch(input, { ...init, headers });
1235
+ },
1236
+ (proof) => ({
1237
+ timestamp: nowFn(),
1238
+ origin,
1239
+ satoshis: brc105Challenge.satoshisRequired,
1240
+ txid: proof.txid,
1241
+ protocol: "brc105"
1242
+ })
1243
+ );
1244
+ }
1245
+ return response;
661
1246
  };
662
1247
  fetchFn.resetLimits = async () => {
663
1248
  const rl = await ensureInitialised();
@@ -762,7 +1347,9 @@ Allow this payment of ${action.amount} sats to ${action.origin}?`;
762
1347
  RateLimiter,
763
1348
  TIER_PRESETS,
764
1349
  WalletTwoFactorProvider,
1350
+ constructBrc105Proof,
765
1351
  createX402Fetch,
1352
+ parseBrc105Challenge,
766
1353
  parseChallenge,
767
1354
  resolveSitePolicy,
768
1355
  resolveSpendLimits,