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