@getalby/lightning-tools 7.0.2 → 8.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.
Files changed (44) hide show
  1. package/README.md +108 -28
  2. package/dist/cjs/402/l402.cjs +157 -0
  3. package/dist/cjs/402/l402.cjs.map +1 -0
  4. package/dist/cjs/402/mpp.cjs +179 -0
  5. package/dist/cjs/402/mpp.cjs.map +1 -0
  6. package/dist/cjs/402/x402.cjs +1320 -0
  7. package/dist/cjs/402/x402.cjs.map +1 -0
  8. package/dist/cjs/402.cjs +1694 -0
  9. package/dist/cjs/402.cjs.map +1 -0
  10. package/dist/cjs/bolt11.cjs +534 -518
  11. package/dist/cjs/bolt11.cjs.map +1 -1
  12. package/dist/cjs/index.cjs +811 -453
  13. package/dist/cjs/index.cjs.map +1 -1
  14. package/dist/cjs/lnurl.cjs +22 -7
  15. package/dist/cjs/lnurl.cjs.map +1 -1
  16. package/dist/esm/402/l402.js +150 -0
  17. package/dist/esm/402/l402.js.map +1 -0
  18. package/dist/esm/402/mpp.js +177 -0
  19. package/dist/esm/402/mpp.js.map +1 -0
  20. package/dist/esm/402/x402.js +1318 -0
  21. package/dist/esm/402/x402.js.map +1 -0
  22. package/dist/esm/402.js +1683 -0
  23. package/dist/esm/402.js.map +1 -0
  24. package/dist/esm/bolt11.js +534 -519
  25. package/dist/esm/bolt11.js.map +1 -1
  26. package/dist/esm/index.js +803 -451
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/lnurl.js +22 -7
  29. package/dist/esm/lnurl.js.map +1 -1
  30. package/dist/lightning-tools.umd.js +3 -3
  31. package/dist/lightning-tools.umd.js.map +1 -1
  32. package/dist/types/402/l402.d.ts +51 -0
  33. package/dist/types/402/mpp.d.ts +26 -0
  34. package/dist/types/402/x402.d.ts +13 -0
  35. package/dist/types/402.d.ts +78 -0
  36. package/dist/types/bolt11.d.ts +6 -1
  37. package/dist/types/index.d.ts +76 -28
  38. package/dist/types/lnurl.d.ts +2 -0
  39. package/package.json +20 -5
  40. package/dist/cjs/l402.cjs +0 -93
  41. package/dist/cjs/l402.cjs.map +0 -1
  42. package/dist/esm/l402.js +0 -87
  43. package/dist/esm/l402.js.map +0 -1
  44. package/dist/types/l402.d.ts +0 -35
@@ -1,5 +1,312 @@
1
1
  'use strict';
2
2
 
3
+ function bytes(b, ...lengths) {
4
+ if (!(b instanceof Uint8Array))
5
+ throw new Error('Expected Uint8Array');
6
+ if (lengths.length > 0 && !lengths.includes(b.length))
7
+ throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
8
+ }
9
+ function exists(instance, checkFinished = true) {
10
+ if (instance.destroyed)
11
+ throw new Error('Hash instance has been destroyed');
12
+ if (checkFinished && instance.finished)
13
+ throw new Error('Hash#digest() has already been called');
14
+ }
15
+ function output(out, instance) {
16
+ bytes(out);
17
+ const min = instance.outputLen;
18
+ if (out.length < min) {
19
+ throw new Error(`digestInto() expects output buffer of length at least ${min}`);
20
+ }
21
+ }
22
+
23
+ /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
24
+ // We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
25
+ // node.js versions earlier than v19 don't declare it in global scope.
26
+ // For node.js, package.json#exports field mapping rewrites import
27
+ // from `crypto` to `cryptoNode`, which imports native module.
28
+ // Makes the utils un-importable in browsers without a bundler.
29
+ // Once node.js 18 is deprecated, we can just drop the import.
30
+ const u8a = (a) => a instanceof Uint8Array;
31
+ // Cast array to view
32
+ const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
33
+ // The rotate right (circular right shift) operation for uint32
34
+ const rotr = (word, shift) => (word << (32 - shift)) | (word >>> shift);
35
+ // big-endian hardware is rare. Just in case someone still decides to run hashes:
36
+ // early-throw an error because we don't support BE yet.
37
+ const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
38
+ if (!isLE)
39
+ throw new Error('Non little-endian hardware is not supported');
40
+ const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
41
+ /**
42
+ * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
43
+ */
44
+ function bytesToHex(bytes) {
45
+ if (!u8a(bytes))
46
+ throw new Error('Uint8Array expected');
47
+ // pre-caching improves the speed 6x
48
+ let hex = '';
49
+ for (let i = 0; i < bytes.length; i++) {
50
+ hex += hexes[bytes[i]];
51
+ }
52
+ return hex;
53
+ }
54
+ /**
55
+ * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
56
+ */
57
+ function utf8ToBytes(str) {
58
+ if (typeof str !== 'string')
59
+ throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
60
+ return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
61
+ }
62
+ /**
63
+ * Normalizes (non-hex) string or Uint8Array to Uint8Array.
64
+ * Warning: when Uint8Array is passed, it would NOT get copied.
65
+ * Keep in mind for future mutable operations.
66
+ */
67
+ function toBytes(data) {
68
+ if (typeof data === 'string')
69
+ data = utf8ToBytes(data);
70
+ if (!u8a(data))
71
+ throw new Error(`expected Uint8Array, got ${typeof data}`);
72
+ return data;
73
+ }
74
+ // For runtime check if class implements interface
75
+ class Hash {
76
+ // Safe version that clones internal state
77
+ clone() {
78
+ return this._cloneInto();
79
+ }
80
+ }
81
+ function wrapConstructor(hashCons) {
82
+ const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
83
+ const tmp = hashCons();
84
+ hashC.outputLen = tmp.outputLen;
85
+ hashC.blockLen = tmp.blockLen;
86
+ hashC.create = () => hashCons();
87
+ return hashC;
88
+ }
89
+
90
+ // Polyfill for Safari 14
91
+ function setBigUint64(view, byteOffset, value, isLE) {
92
+ if (typeof view.setBigUint64 === 'function')
93
+ return view.setBigUint64(byteOffset, value, isLE);
94
+ const _32n = BigInt(32);
95
+ const _u32_max = BigInt(0xffffffff);
96
+ const wh = Number((value >> _32n) & _u32_max);
97
+ const wl = Number(value & _u32_max);
98
+ const h = isLE ? 4 : 0;
99
+ const l = isLE ? 0 : 4;
100
+ view.setUint32(byteOffset + h, wh, isLE);
101
+ view.setUint32(byteOffset + l, wl, isLE);
102
+ }
103
+ // Base SHA2 class (RFC 6234)
104
+ class SHA2 extends Hash {
105
+ constructor(blockLen, outputLen, padOffset, isLE) {
106
+ super();
107
+ this.blockLen = blockLen;
108
+ this.outputLen = outputLen;
109
+ this.padOffset = padOffset;
110
+ this.isLE = isLE;
111
+ this.finished = false;
112
+ this.length = 0;
113
+ this.pos = 0;
114
+ this.destroyed = false;
115
+ this.buffer = new Uint8Array(blockLen);
116
+ this.view = createView(this.buffer);
117
+ }
118
+ update(data) {
119
+ exists(this);
120
+ const { view, buffer, blockLen } = this;
121
+ data = toBytes(data);
122
+ const len = data.length;
123
+ for (let pos = 0; pos < len;) {
124
+ const take = Math.min(blockLen - this.pos, len - pos);
125
+ // Fast path: we have at least one block in input, cast it to view and process
126
+ if (take === blockLen) {
127
+ const dataView = createView(data);
128
+ for (; blockLen <= len - pos; pos += blockLen)
129
+ this.process(dataView, pos);
130
+ continue;
131
+ }
132
+ buffer.set(data.subarray(pos, pos + take), this.pos);
133
+ this.pos += take;
134
+ pos += take;
135
+ if (this.pos === blockLen) {
136
+ this.process(view, 0);
137
+ this.pos = 0;
138
+ }
139
+ }
140
+ this.length += data.length;
141
+ this.roundClean();
142
+ return this;
143
+ }
144
+ digestInto(out) {
145
+ exists(this);
146
+ output(out, this);
147
+ this.finished = true;
148
+ // Padding
149
+ // We can avoid allocation of buffer for padding completely if it
150
+ // was previously not allocated here. But it won't change performance.
151
+ const { buffer, view, blockLen, isLE } = this;
152
+ let { pos } = this;
153
+ // append the bit '1' to the message
154
+ buffer[pos++] = 0b10000000;
155
+ this.buffer.subarray(pos).fill(0);
156
+ // we have less than padOffset left in buffer, so we cannot put length in current block, need process it and pad again
157
+ if (this.padOffset > blockLen - pos) {
158
+ this.process(view, 0);
159
+ pos = 0;
160
+ }
161
+ // Pad until full block byte with zeros
162
+ for (let i = pos; i < blockLen; i++)
163
+ buffer[i] = 0;
164
+ // Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
165
+ // You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
166
+ // So we just write lowest 64 bits of that value.
167
+ setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
168
+ this.process(view, 0);
169
+ const oview = createView(out);
170
+ const len = this.outputLen;
171
+ // NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
172
+ if (len % 4)
173
+ throw new Error('_sha2: outputLen should be aligned to 32bit');
174
+ const outLen = len / 4;
175
+ const state = this.get();
176
+ if (outLen > state.length)
177
+ throw new Error('_sha2: outputLen bigger than state');
178
+ for (let i = 0; i < outLen; i++)
179
+ oview.setUint32(4 * i, state[i], isLE);
180
+ }
181
+ digest() {
182
+ const { buffer, outputLen } = this;
183
+ this.digestInto(buffer);
184
+ const res = buffer.slice(0, outputLen);
185
+ this.destroy();
186
+ return res;
187
+ }
188
+ _cloneInto(to) {
189
+ to || (to = new this.constructor());
190
+ to.set(...this.get());
191
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
192
+ to.length = length;
193
+ to.pos = pos;
194
+ to.finished = finished;
195
+ to.destroyed = destroyed;
196
+ if (length % blockLen)
197
+ to.buffer.set(buffer);
198
+ return to;
199
+ }
200
+ }
201
+
202
+ // SHA2-256 need to try 2^128 hashes to execute birthday attack.
203
+ // BTC network is doing 2^67 hashes/sec as per early 2023.
204
+ // Choice: a ? b : c
205
+ const Chi = (a, b, c) => (a & b) ^ (~a & c);
206
+ // Majority function, true if any two inpust is true
207
+ const Maj = (a, b, c) => (a & b) ^ (a & c) ^ (b & c);
208
+ // Round constants:
209
+ // first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311)
210
+ // prettier-ignore
211
+ const SHA256_K = /* @__PURE__ */ new Uint32Array([
212
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
213
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
214
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
215
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
216
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
217
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
218
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
219
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
220
+ ]);
221
+ // Initial state (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
222
+ // prettier-ignore
223
+ const IV = /* @__PURE__ */ new Uint32Array([
224
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
225
+ ]);
226
+ // Temporary buffer, not used to store anything between runs
227
+ // Named this way because it matches specification.
228
+ const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
229
+ class SHA256 extends SHA2 {
230
+ constructor() {
231
+ super(64, 32, 8, false);
232
+ // We cannot use array here since array allows indexing by variable
233
+ // which means optimizer/compiler cannot use registers.
234
+ this.A = IV[0] | 0;
235
+ this.B = IV[1] | 0;
236
+ this.C = IV[2] | 0;
237
+ this.D = IV[3] | 0;
238
+ this.E = IV[4] | 0;
239
+ this.F = IV[5] | 0;
240
+ this.G = IV[6] | 0;
241
+ this.H = IV[7] | 0;
242
+ }
243
+ get() {
244
+ const { A, B, C, D, E, F, G, H } = this;
245
+ return [A, B, C, D, E, F, G, H];
246
+ }
247
+ // prettier-ignore
248
+ set(A, B, C, D, E, F, G, H) {
249
+ this.A = A | 0;
250
+ this.B = B | 0;
251
+ this.C = C | 0;
252
+ this.D = D | 0;
253
+ this.E = E | 0;
254
+ this.F = F | 0;
255
+ this.G = G | 0;
256
+ this.H = H | 0;
257
+ }
258
+ process(view, offset) {
259
+ // Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
260
+ for (let i = 0; i < 16; i++, offset += 4)
261
+ SHA256_W[i] = view.getUint32(offset, false);
262
+ for (let i = 16; i < 64; i++) {
263
+ const W15 = SHA256_W[i - 15];
264
+ const W2 = SHA256_W[i - 2];
265
+ const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);
266
+ const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);
267
+ SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;
268
+ }
269
+ // Compression function main loop, 64 rounds
270
+ let { A, B, C, D, E, F, G, H } = this;
271
+ for (let i = 0; i < 64; i++) {
272
+ const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
273
+ const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;
274
+ const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
275
+ const T2 = (sigma0 + Maj(A, B, C)) | 0;
276
+ H = G;
277
+ G = F;
278
+ F = E;
279
+ E = (D + T1) | 0;
280
+ D = C;
281
+ C = B;
282
+ B = A;
283
+ A = (T1 + T2) | 0;
284
+ }
285
+ // Add the compressed chunk to the current hash value
286
+ A = (A + this.A) | 0;
287
+ B = (B + this.B) | 0;
288
+ C = (C + this.C) | 0;
289
+ D = (D + this.D) | 0;
290
+ E = (E + this.E) | 0;
291
+ F = (F + this.F) | 0;
292
+ G = (G + this.G) | 0;
293
+ H = (H + this.H) | 0;
294
+ this.set(A, B, C, D, E, F, G, H);
295
+ }
296
+ roundClean() {
297
+ SHA256_W.fill(0);
298
+ }
299
+ destroy() {
300
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
301
+ this.buffer.fill(0);
302
+ }
303
+ }
304
+ /**
305
+ * SHA2-256 hash function
306
+ * @param message - data that would be hashed
307
+ */
308
+ const sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256());
309
+
3
310
  var lib = {};
4
311
 
5
312
  var hasRequiredLib;
@@ -767,409 +1074,121 @@ function requireBolt11 () {
767
1074
  name: 'signature',
768
1075
  letters: letters.slice(0, 104),
769
1076
  value: hex.encode(bech32.fromWordsUnsafe(sigWords))
770
- });
771
- letters = letters.slice(104);
772
-
773
- // checksum
774
- sections.push({
775
- name: 'checksum',
776
- letters: letters
777
- });
778
-
779
- let result = {
780
- paymentRequest,
781
- sections,
782
-
783
- get expiry() {
784
- let exp = sections.find(s => s.name === 'expiry');
785
- if (exp) return getValue('timestamp') + exp.value
786
- },
787
-
788
- get route_hints() {
789
- return sections.filter(s => s.name === 'route_hint').map(s => s.value)
790
- }
791
- };
792
-
793
- for (let name in TAGCODES) {
794
- if (name === 'route_hint') {
795
- // route hints can be multiple, so this won't work for them
796
- continue
797
- }
798
-
799
- Object.defineProperty(result, name, {
800
- get() {
801
- return getValue(name)
802
- }
803
- });
804
- }
805
-
806
- return result
807
-
808
- function getValue(name) {
809
- let section = sections.find(s => s.name === name);
810
- return section ? section.value : undefined
811
- }
812
- }
813
-
814
- bolt11 = {
815
- decode,
816
- hrpToMillisat
817
- };
818
- return bolt11;
819
- }
820
-
821
- var bolt11Exports = requireBolt11();
822
-
823
- // from https://stackoverflow.com/a/50868276
824
- const fromHexString = (hexString) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
825
- const decodeInvoice = (paymentRequest) => {
826
- if (!paymentRequest)
827
- return null;
828
- try {
829
- const decoded = bolt11Exports.decode(paymentRequest);
830
- if (!decoded || !decoded.sections)
831
- return null;
832
- const hashTag = decoded.sections.find((value) => value.name === "payment_hash");
833
- if (hashTag?.name !== "payment_hash" || !hashTag.value)
834
- return null;
835
- const paymentHash = hashTag.value;
836
- let satoshi = 0;
837
- const amountTag = decoded.sections.find((value) => value.name === "amount");
838
- if (amountTag?.name === "amount" && amountTag.value) {
839
- satoshi = parseInt(amountTag.value) / 1000; // millisats
840
- }
841
- const timestampTag = decoded.sections.find((value) => value.name === "timestamp");
842
- if (timestampTag?.name !== "timestamp" || !timestampTag.value)
843
- return null;
844
- const timestamp = timestampTag.value;
845
- let expiry;
846
- const expiryTag = decoded.sections.find((value) => value.name === "expiry");
847
- if (expiryTag?.name === "expiry") {
848
- expiry = expiryTag.value;
849
- }
850
- const descriptionTag = decoded.sections.find((value) => value.name === "description");
851
- const description = descriptionTag?.name === "description"
852
- ? descriptionTag?.value
853
- : undefined;
854
- return {
855
- paymentHash,
856
- satoshi,
857
- timestamp,
858
- expiry,
859
- description,
860
- };
861
- }
862
- catch {
863
- return null;
864
- }
865
- };
866
-
867
- function bytes(b, ...lengths) {
868
- if (!(b instanceof Uint8Array))
869
- throw new Error('Expected Uint8Array');
870
- if (lengths.length > 0 && !lengths.includes(b.length))
871
- throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
872
- }
873
- function exists(instance, checkFinished = true) {
874
- if (instance.destroyed)
875
- throw new Error('Hash instance has been destroyed');
876
- if (checkFinished && instance.finished)
877
- throw new Error('Hash#digest() has already been called');
878
- }
879
- function output(out, instance) {
880
- bytes(out);
881
- const min = instance.outputLen;
882
- if (out.length < min) {
883
- throw new Error(`digestInto() expects output buffer of length at least ${min}`);
884
- }
885
- }
1077
+ });
1078
+ letters = letters.slice(104);
886
1079
 
887
- /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
888
- // We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
889
- // node.js versions earlier than v19 don't declare it in global scope.
890
- // For node.js, package.json#exports field mapping rewrites import
891
- // from `crypto` to `cryptoNode`, which imports native module.
892
- // Makes the utils un-importable in browsers without a bundler.
893
- // Once node.js 18 is deprecated, we can just drop the import.
894
- const u8a = (a) => a instanceof Uint8Array;
895
- // Cast array to view
896
- const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
897
- // The rotate right (circular right shift) operation for uint32
898
- const rotr = (word, shift) => (word << (32 - shift)) | (word >>> shift);
899
- // big-endian hardware is rare. Just in case someone still decides to run hashes:
900
- // early-throw an error because we don't support BE yet.
901
- const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
902
- if (!isLE)
903
- throw new Error('Non little-endian hardware is not supported');
904
- const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
905
- /**
906
- * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
907
- */
908
- function bytesToHex(bytes) {
909
- if (!u8a(bytes))
910
- throw new Error('Uint8Array expected');
911
- // pre-caching improves the speed 6x
912
- let hex = '';
913
- for (let i = 0; i < bytes.length; i++) {
914
- hex += hexes[bytes[i]];
915
- }
916
- return hex;
917
- }
918
- /**
919
- * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
920
- */
921
- function utf8ToBytes(str) {
922
- if (typeof str !== 'string')
923
- throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
924
- return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
925
- }
926
- /**
927
- * Normalizes (non-hex) string or Uint8Array to Uint8Array.
928
- * Warning: when Uint8Array is passed, it would NOT get copied.
929
- * Keep in mind for future mutable operations.
930
- */
931
- function toBytes(data) {
932
- if (typeof data === 'string')
933
- data = utf8ToBytes(data);
934
- if (!u8a(data))
935
- throw new Error(`expected Uint8Array, got ${typeof data}`);
936
- return data;
937
- }
938
- // For runtime check if class implements interface
939
- class Hash {
940
- // Safe version that clones internal state
941
- clone() {
942
- return this._cloneInto();
943
- }
944
- }
945
- function wrapConstructor(hashCons) {
946
- const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
947
- const tmp = hashCons();
948
- hashC.outputLen = tmp.outputLen;
949
- hashC.blockLen = tmp.blockLen;
950
- hashC.create = () => hashCons();
951
- return hashC;
952
- }
1080
+ // checksum
1081
+ sections.push({
1082
+ name: 'checksum',
1083
+ letters: letters
1084
+ });
953
1085
 
954
- // Polyfill for Safari 14
955
- function setBigUint64(view, byteOffset, value, isLE) {
956
- if (typeof view.setBigUint64 === 'function')
957
- return view.setBigUint64(byteOffset, value, isLE);
958
- const _32n = BigInt(32);
959
- const _u32_max = BigInt(0xffffffff);
960
- const wh = Number((value >> _32n) & _u32_max);
961
- const wl = Number(value & _u32_max);
962
- const h = isLE ? 4 : 0;
963
- const l = isLE ? 0 : 4;
964
- view.setUint32(byteOffset + h, wh, isLE);
965
- view.setUint32(byteOffset + l, wl, isLE);
966
- }
967
- // Base SHA2 class (RFC 6234)
968
- class SHA2 extends Hash {
969
- constructor(blockLen, outputLen, padOffset, isLE) {
970
- super();
971
- this.blockLen = blockLen;
972
- this.outputLen = outputLen;
973
- this.padOffset = padOffset;
974
- this.isLE = isLE;
975
- this.finished = false;
976
- this.length = 0;
977
- this.pos = 0;
978
- this.destroyed = false;
979
- this.buffer = new Uint8Array(blockLen);
980
- this.view = createView(this.buffer);
981
- }
982
- update(data) {
983
- exists(this);
984
- const { view, buffer, blockLen } = this;
985
- data = toBytes(data);
986
- const len = data.length;
987
- for (let pos = 0; pos < len;) {
988
- const take = Math.min(blockLen - this.pos, len - pos);
989
- // Fast path: we have at least one block in input, cast it to view and process
990
- if (take === blockLen) {
991
- const dataView = createView(data);
992
- for (; blockLen <= len - pos; pos += blockLen)
993
- this.process(dataView, pos);
994
- continue;
995
- }
996
- buffer.set(data.subarray(pos, pos + take), this.pos);
997
- this.pos += take;
998
- pos += take;
999
- if (this.pos === blockLen) {
1000
- this.process(view, 0);
1001
- this.pos = 0;
1002
- }
1003
- }
1004
- this.length += data.length;
1005
- this.roundClean();
1006
- return this;
1007
- }
1008
- digestInto(out) {
1009
- exists(this);
1010
- output(out, this);
1011
- this.finished = true;
1012
- // Padding
1013
- // We can avoid allocation of buffer for padding completely if it
1014
- // was previously not allocated here. But it won't change performance.
1015
- const { buffer, view, blockLen, isLE } = this;
1016
- let { pos } = this;
1017
- // append the bit '1' to the message
1018
- buffer[pos++] = 0b10000000;
1019
- this.buffer.subarray(pos).fill(0);
1020
- // we have less than padOffset left in buffer, so we cannot put length in current block, need process it and pad again
1021
- if (this.padOffset > blockLen - pos) {
1022
- this.process(view, 0);
1023
- pos = 0;
1024
- }
1025
- // Pad until full block byte with zeros
1026
- for (let i = pos; i < blockLen; i++)
1027
- buffer[i] = 0;
1028
- // Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
1029
- // You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
1030
- // So we just write lowest 64 bits of that value.
1031
- setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
1032
- this.process(view, 0);
1033
- const oview = createView(out);
1034
- const len = this.outputLen;
1035
- // NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
1036
- if (len % 4)
1037
- throw new Error('_sha2: outputLen should be aligned to 32bit');
1038
- const outLen = len / 4;
1039
- const state = this.get();
1040
- if (outLen > state.length)
1041
- throw new Error('_sha2: outputLen bigger than state');
1042
- for (let i = 0; i < outLen; i++)
1043
- oview.setUint32(4 * i, state[i], isLE);
1044
- }
1045
- digest() {
1046
- const { buffer, outputLen } = this;
1047
- this.digestInto(buffer);
1048
- const res = buffer.slice(0, outputLen);
1049
- this.destroy();
1050
- return res;
1051
- }
1052
- _cloneInto(to) {
1053
- to || (to = new this.constructor());
1054
- to.set(...this.get());
1055
- const { blockLen, buffer, length, finished, destroyed, pos } = this;
1056
- to.length = length;
1057
- to.pos = pos;
1058
- to.finished = finished;
1059
- to.destroyed = destroyed;
1060
- if (length % blockLen)
1061
- to.buffer.set(buffer);
1062
- return to;
1063
- }
1064
- }
1086
+ let result = {
1087
+ paymentRequest,
1088
+ sections,
1065
1089
 
1066
- // SHA2-256 need to try 2^128 hashes to execute birthday attack.
1067
- // BTC network is doing 2^67 hashes/sec as per early 2023.
1068
- // Choice: a ? b : c
1069
- const Chi = (a, b, c) => (a & b) ^ (~a & c);
1070
- // Majority function, true if any two inpust is true
1071
- const Maj = (a, b, c) => (a & b) ^ (a & c) ^ (b & c);
1072
- // Round constants:
1073
- // first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311)
1074
- // prettier-ignore
1075
- const SHA256_K = /* @__PURE__ */ new Uint32Array([
1076
- 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
1077
- 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
1078
- 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
1079
- 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
1080
- 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
1081
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
1082
- 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
1083
- 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
1084
- ]);
1085
- // Initial state (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
1086
- // prettier-ignore
1087
- const IV = /* @__PURE__ */ new Uint32Array([
1088
- 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
1089
- ]);
1090
- // Temporary buffer, not used to store anything between runs
1091
- // Named this way because it matches specification.
1092
- const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
1093
- class SHA256 extends SHA2 {
1094
- constructor() {
1095
- super(64, 32, 8, false);
1096
- // We cannot use array here since array allows indexing by variable
1097
- // which means optimizer/compiler cannot use registers.
1098
- this.A = IV[0] | 0;
1099
- this.B = IV[1] | 0;
1100
- this.C = IV[2] | 0;
1101
- this.D = IV[3] | 0;
1102
- this.E = IV[4] | 0;
1103
- this.F = IV[5] | 0;
1104
- this.G = IV[6] | 0;
1105
- this.H = IV[7] | 0;
1106
- }
1107
- get() {
1108
- const { A, B, C, D, E, F, G, H } = this;
1109
- return [A, B, C, D, E, F, G, H];
1110
- }
1111
- // prettier-ignore
1112
- set(A, B, C, D, E, F, G, H) {
1113
- this.A = A | 0;
1114
- this.B = B | 0;
1115
- this.C = C | 0;
1116
- this.D = D | 0;
1117
- this.E = E | 0;
1118
- this.F = F | 0;
1119
- this.G = G | 0;
1120
- this.H = H | 0;
1121
- }
1122
- process(view, offset) {
1123
- // Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
1124
- for (let i = 0; i < 16; i++, offset += 4)
1125
- SHA256_W[i] = view.getUint32(offset, false);
1126
- for (let i = 16; i < 64; i++) {
1127
- const W15 = SHA256_W[i - 15];
1128
- const W2 = SHA256_W[i - 2];
1129
- const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);
1130
- const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);
1131
- SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;
1090
+ get expiry() {
1091
+ let exp = sections.find(s => s.name === 'expiry');
1092
+ if (exp) return getValue('timestamp') + exp.value
1093
+ },
1094
+
1095
+ get route_hints() {
1096
+ return sections.filter(s => s.name === 'route_hint').map(s => s.value)
1097
+ }
1098
+ };
1099
+
1100
+ for (let name in TAGCODES) {
1101
+ if (name === 'route_hint') {
1102
+ // route hints can be multiple, so this won't work for them
1103
+ continue
1104
+ }
1105
+
1106
+ Object.defineProperty(result, name, {
1107
+ get() {
1108
+ return getValue(name)
1109
+ }
1110
+ });
1111
+ }
1112
+
1113
+ return result
1114
+
1115
+ function getValue(name) {
1116
+ let section = sections.find(s => s.name === name);
1117
+ return section ? section.value : undefined
1118
+ }
1119
+ }
1120
+
1121
+ bolt11 = {
1122
+ decode,
1123
+ hrpToMillisat
1124
+ };
1125
+ return bolt11;
1126
+ }
1127
+
1128
+ var bolt11Exports = requireBolt11();
1129
+
1130
+ // from https://stackoverflow.com/a/50868276
1131
+ const fromHexString = (hexString) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
1132
+ const decodeInvoice = (paymentRequest) => {
1133
+ if (!paymentRequest)
1134
+ return null;
1135
+ try {
1136
+ const decoded = bolt11Exports.decode(paymentRequest);
1137
+ if (!decoded || !decoded.sections)
1138
+ return null;
1139
+ const hashTag = decoded.sections.find((value) => value.name === "payment_hash");
1140
+ if (hashTag?.name !== "payment_hash" || !hashTag.value)
1141
+ return null;
1142
+ const paymentHash = hashTag.value;
1143
+ let satoshi = 0;
1144
+ let millisatoshi = 0;
1145
+ let amountRaw = "0";
1146
+ const amountTag = decoded.sections.find((value) => value.name === "amount");
1147
+ if (amountTag?.name === "amount" && amountTag.value) {
1148
+ amountRaw = amountTag.value;
1149
+ millisatoshi = parseInt(amountTag.value);
1150
+ satoshi = parseInt(amountTag.value) / 1000; // millisats
1132
1151
  }
1133
- // Compression function main loop, 64 rounds
1134
- let { A, B, C, D, E, F, G, H } = this;
1135
- for (let i = 0; i < 64; i++) {
1136
- const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
1137
- const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;
1138
- const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
1139
- const T2 = (sigma0 + Maj(A, B, C)) | 0;
1140
- H = G;
1141
- G = F;
1142
- F = E;
1143
- E = (D + T1) | 0;
1144
- D = C;
1145
- C = B;
1146
- B = A;
1147
- A = (T1 + T2) | 0;
1152
+ const timestampTag = decoded.sections.find((value) => value.name === "timestamp");
1153
+ if (timestampTag?.name !== "timestamp" || !timestampTag.value)
1154
+ return null;
1155
+ const timestamp = timestampTag.value;
1156
+ let expiry;
1157
+ const expiryTag = decoded.sections.find((value) => value.name === "expiry");
1158
+ if (expiryTag?.name === "expiry") {
1159
+ expiry = expiryTag.value;
1148
1160
  }
1149
- // Add the compressed chunk to the current hash value
1150
- A = (A + this.A) | 0;
1151
- B = (B + this.B) | 0;
1152
- C = (C + this.C) | 0;
1153
- D = (D + this.D) | 0;
1154
- E = (E + this.E) | 0;
1155
- F = (F + this.F) | 0;
1156
- G = (G + this.G) | 0;
1157
- H = (H + this.H) | 0;
1158
- this.set(A, B, C, D, E, F, G, H);
1161
+ const descriptionTag = decoded.sections.find((value) => value.name === "description");
1162
+ const description = descriptionTag?.name === "description"
1163
+ ? descriptionTag?.value
1164
+ : undefined;
1165
+ return {
1166
+ paymentHash,
1167
+ satoshi,
1168
+ millisatoshi,
1169
+ amountRaw,
1170
+ timestamp,
1171
+ expiry,
1172
+ description,
1173
+ };
1159
1174
  }
1160
- roundClean() {
1161
- SHA256_W.fill(0);
1175
+ catch {
1176
+ return null;
1162
1177
  }
1163
- destroy() {
1164
- this.set(0, 0, 0, 0, 0, 0, 0, 0);
1165
- this.buffer.fill(0);
1178
+ };
1179
+ function validatePreimage(preimage, paymentHash) {
1180
+ try {
1181
+ if (!/^[0-9a-fA-F]{64}$/.test(preimage))
1182
+ return false;
1183
+ if (!/^[0-9a-fA-F]{64}$/.test(paymentHash))
1184
+ return false;
1185
+ const preimageHash = bytesToHex(sha256(fromHexString(preimage)));
1186
+ return paymentHash === preimageHash;
1187
+ }
1188
+ catch {
1189
+ return false;
1166
1190
  }
1167
1191
  }
1168
- /**
1169
- * SHA2-256 hash function
1170
- * @param message - data that would be hashed
1171
- */
1172
- const sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256());
1173
1192
 
1174
1193
  class Invoice {
1175
1194
  constructor(args) {
@@ -1183,6 +1202,8 @@ class Invoice {
1183
1202
  }
1184
1203
  this.paymentHash = decodedInvoice.paymentHash;
1185
1204
  this.satoshi = decodedInvoice.satoshi;
1205
+ this.millisatoshi = decodedInvoice.millisatoshi;
1206
+ this.amountRaw = decodedInvoice.amountRaw;
1186
1207
  this.timestamp = decodedInvoice.timestamp;
1187
1208
  this.expiry = decodedInvoice.expiry;
1188
1209
  this.createdDate = new Date(this.timestamp * 1000);
@@ -1207,13 +1228,7 @@ class Invoice {
1207
1228
  validatePreimage(preimage) {
1208
1229
  if (!preimage || !this.paymentHash)
1209
1230
  return false;
1210
- try {
1211
- const preimageHash = bytesToHex(sha256(fromHexString(preimage)));
1212
- return this.paymentHash === preimageHash;
1213
- }
1214
- catch {
1215
- return false;
1216
- }
1231
+ return validatePreimage(preimage, this.paymentHash);
1217
1232
  }
1218
1233
  async verifyPayment() {
1219
1234
  try {
@@ -1659,24 +1674,23 @@ class LightningAddress {
1659
1674
  }
1660
1675
  }
1661
1676
 
1662
- class MemoryStorage {
1663
- constructor(initial) {
1664
- this.storage = initial || {};
1665
- }
1666
- getItem(key) {
1667
- return this.storage[key];
1668
- }
1669
- setItem(key, value) {
1670
- this.storage[key] = value;
1671
- }
1672
- }
1673
- class NoStorage {
1674
- constructor(initial) { }
1675
- getItem(key) {
1676
- return null;
1677
- }
1678
- setItem(key, value) { }
1677
+ function createGuardedWallet(wallet, maxAmountSats) {
1678
+ return {
1679
+ payInvoice: async (args) => {
1680
+ const invoice = new Invoice({ pr: args.invoice });
1681
+ if (invoice.satoshi > maxAmountSats) {
1682
+ throw new Error(`Invoice amount (${invoice.satoshi} sats) exceeds maxAmount (${maxAmountSats} sats)`);
1683
+ }
1684
+ return wallet.payInvoice(args);
1685
+ },
1686
+ };
1679
1687
  }
1688
+
1689
+ /**
1690
+ * Client: parse "www-authenticate" header from server response
1691
+ * @param input
1692
+ * @returns details from the header value (token or macaroon, invoice)
1693
+ */
1680
1694
  const parseL402 = (input) => {
1681
1695
  // Remove the L402 and LSAT identifiers
1682
1696
  const string = input.replace("L402", "").replace("LSAT", "").trim();
@@ -1691,58 +1705,396 @@ const parseL402 = (input) => {
1691
1705
  // Value is either match[3] (double-quoted), match[4] (single-quoted), or match[5] (unquoted)
1692
1706
  keyValuePairs[match[1]] = match[3] || match[4] || match[5];
1693
1707
  }
1708
+ if (!keyValuePairs["token"] && keyValuePairs["macaroon"]) {
1709
+ // fallback to old naming
1710
+ keyValuePairs["token"] = keyValuePairs["macaroon"];
1711
+ delete keyValuePairs["macaroon"];
1712
+ }
1713
+ if (!("token" in keyValuePairs) ||
1714
+ typeof keyValuePairs["token"] !== "string") {
1715
+ throw new Error("No macaroon or token found in www-authenticate header");
1716
+ }
1717
+ if (!("invoice" in keyValuePairs) ||
1718
+ typeof keyValuePairs["invoice"] !== "string") {
1719
+ throw new Error("No invoice found in www-authenticate header");
1720
+ }
1694
1721
  return keyValuePairs;
1695
1722
  };
1696
- const makeAuthenticateHeader = (args) => {
1697
- const key = args.key || "L402";
1698
- return `${key} macaroon="${args.macaroon}", invoice="${args.invoice}"`;
1699
- };
1700
1723
 
1701
- const memoryStorage = new MemoryStorage();
1702
- const HEADER_KEY = "L402";
1703
- const fetchWithL402 = async (url, fetchArgs, options) => {
1704
- if (!options) {
1705
- options = {};
1724
+ const handleL402Payment = async (l402Header, url, fetchArgs, headers, wallet) => {
1725
+ const details = parseL402(l402Header);
1726
+ const token = details.token || details.macaroon;
1727
+ const invoice = details.invoice;
1728
+ if (!token) {
1729
+ throw new Error("L402: missing token/macaroon in WWW-Authenticate header");
1706
1730
  }
1707
- const headerKey = options.headerKey || HEADER_KEY;
1731
+ if (!invoice) {
1732
+ throw new Error("L402: missing invoice in WWW-Authenticate header");
1733
+ }
1734
+ const invResp = await wallet.payInvoice({ invoice });
1735
+ headers.set("Authorization", `L402 ${token}:${invResp.preimage}`);
1736
+ return fetch(url, fetchArgs);
1737
+ };
1738
+ const fetchWithL402 = async (url, fetchArgs, options) => {
1708
1739
  const wallet = options.wallet;
1709
1740
  if (!wallet) {
1710
1741
  throw new Error("wallet is missing");
1711
1742
  }
1712
- const store = options.store || memoryStorage;
1713
1743
  if (!fetchArgs) {
1714
1744
  fetchArgs = {};
1715
1745
  }
1716
1746
  fetchArgs.cache = "no-store";
1717
1747
  fetchArgs.mode = "cors";
1718
- if (!fetchArgs.headers) {
1719
- fetchArgs.headers = {};
1748
+ const headers = new Headers(fetchArgs.headers ?? undefined);
1749
+ fetchArgs.headers = headers;
1750
+ const initResp = await fetch(url, fetchArgs);
1751
+ const header = initResp.headers.get("www-authenticate");
1752
+ if (!header) {
1753
+ return initResp;
1754
+ }
1755
+ return handleL402Payment(header, url, fetchArgs, headers, wallet);
1756
+ };
1757
+
1758
+ const buildX402PaymentSignature = (scheme, network, invoice, requirements) => {
1759
+ const json = JSON.stringify({
1760
+ x402Version: 2,
1761
+ scheme,
1762
+ network,
1763
+ payload: { invoice },
1764
+ accepted: requirements,
1765
+ });
1766
+ // btoa only handles latin1; encode via UTF-8 to be safe
1767
+ return btoa(unescape(encodeURIComponent(json)));
1768
+ };
1769
+
1770
+ const handleX402Payment = async (x402Header, url, fetchArgs, headers, wallet) => {
1771
+ let parsed;
1772
+ try {
1773
+ parsed = JSON.parse(decodeURIComponent(escape(atob(x402Header))));
1774
+ }
1775
+ catch (_) {
1776
+ throw new Error("x402: invalid PAYMENT-REQUIRED header (not valid base64-encoded JSON)");
1777
+ }
1778
+ if (!Array.isArray(parsed.accepts) || parsed.accepts.length === 0) {
1779
+ throw new Error("x402: PAYMENT-REQUIRED header contains no payment options");
1780
+ }
1781
+ const requirements = parsed.accepts.find((e) => {
1782
+ return e.extra?.paymentMethod === "lightning";
1783
+ });
1784
+ if (!requirements) {
1785
+ throw new Error("x402: unsupported x402 network, only Bitcoin lightning network is supported.");
1720
1786
  }
1721
- const cachedL402Data = store.getItem(url);
1722
- if (cachedL402Data) {
1723
- const data = JSON.parse(cachedL402Data);
1724
- fetchArgs.headers["Authorization"] =
1725
- `${headerKey} ${data.token}:${data.preimage}`;
1726
- return await fetch(url, fetchArgs);
1787
+ if (!requirements.extra?.invoice) {
1788
+ throw new Error("x402: payment requirements missing lightning invoice");
1727
1789
  }
1728
- fetchArgs.headers["Accept-Authenticate"] = headerKey;
1790
+ const invoice = new Invoice({ pr: requirements.extra.invoice });
1791
+ if (invoice.amountRaw != requirements.amount) {
1792
+ throw new Error(`Invalid invoice amount: ${invoice.amountRaw}. expected ${requirements.amount}`);
1793
+ }
1794
+ await wallet.payInvoice({ invoice: invoice.paymentRequest });
1795
+ headers.set("payment-signature", buildX402PaymentSignature(requirements.scheme, requirements.network, invoice.paymentRequest, requirements));
1796
+ return fetch(url, fetchArgs);
1797
+ };
1798
+ const fetchWithX402 = async (url, fetchArgs, options) => {
1799
+ const wallet = options.wallet;
1800
+ if (!fetchArgs) {
1801
+ fetchArgs = {};
1802
+ }
1803
+ fetchArgs.cache = "no-store";
1804
+ fetchArgs.mode = "cors";
1805
+ const headers = new Headers(fetchArgs.headers ?? undefined);
1806
+ fetchArgs.headers = headers;
1729
1807
  const initResp = await fetch(url, fetchArgs);
1730
- const header = initResp.headers.get("www-authenticate");
1808
+ const header = initResp.headers.get("PAYMENT-REQUIRED");
1731
1809
  if (!header) {
1732
1810
  return initResp;
1733
1811
  }
1734
- const details = parseL402(header);
1735
- const token = details.token || details.macaroon;
1736
- const inv = details.invoice;
1737
- const invResp = await wallet.sendPayment(inv);
1738
- store.setItem(url, JSON.stringify({
1739
- token: token,
1740
- preimage: invResp.preimage,
1741
- }));
1742
- fetchArgs.headers["Authorization"] =
1743
- `${headerKey} ${token}:${invResp.preimage}`;
1744
- return await fetch(url, fetchArgs);
1812
+ return handleX402Payment(header, url, fetchArgs, headers, wallet);
1813
+ };
1814
+
1815
+ /**
1816
+ * Parse a `WWW-Authenticate: Payment …` header produced by a
1817
+ * draft-lightning-charge-00 server. Expected format:
1818
+ *
1819
+ * Payment id="<id>", realm="<realm>", method="lightning",
1820
+ * intent="charge", request="<base64url>" [, expires="<rfc3339>"]
1821
+ *
1822
+ * Returns null when the header is not a Payment lightning/charge challenge.
1823
+ */
1824
+ const parseMppChallenge = (header) => {
1825
+ if (!header.trimStart().toLowerCase().startsWith("payment")) {
1826
+ return null;
1827
+ }
1828
+ const rest = header
1829
+ .slice(header.toLowerCase().indexOf("payment") + "payment".length)
1830
+ .trim();
1831
+ const result = {};
1832
+ const regex = /(\w+)=("([^"]*)"|'([^']*)'|([^,\s]*))/g;
1833
+ let match;
1834
+ while ((match = regex.exec(rest)) !== null) {
1835
+ result[match[1]] = match[3] ?? match[4] ?? match[5] ?? "";
1836
+ }
1837
+ if (result.method !== "lightning" ||
1838
+ result.intent !== "charge" ||
1839
+ !result.id ||
1840
+ !result.realm ||
1841
+ !result.request) {
1842
+ return null;
1843
+ }
1844
+ return {
1845
+ id: result.id,
1846
+ realm: result.realm,
1847
+ method: result.method,
1848
+ intent: result.intent,
1849
+ request: result.request,
1850
+ ...(result.expires ? { expires: result.expires } : {}),
1851
+ };
1852
+ };
1853
+ /** Decode a base64url string (no padding required) to a UTF-8 string. */
1854
+ const decodeBase64url = (input) => {
1855
+ const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
1856
+ const binary = atob(base64);
1857
+ const bytes = new Uint8Array(binary.length);
1858
+ for (let i = 0; i < binary.length; i++) {
1859
+ bytes[i] = binary.charCodeAt(i);
1860
+ }
1861
+ return new TextDecoder("utf-8").decode(bytes);
1862
+ };
1863
+ /** Encode a UTF-8 string to base64url without padding. */
1864
+ const encodeBase64url = (input) => {
1865
+ const bytes = new TextEncoder().encode(input);
1866
+ let binary = "";
1867
+ for (let i = 0; i < bytes.length; i++) {
1868
+ binary += String.fromCharCode(bytes[i]);
1869
+ }
1870
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1871
+ };
1872
+ /**
1873
+ * JSON Canonicalization Scheme (RFC 8785).
1874
+ * Produces compact JSON with object keys sorted lexicographically.
1875
+ */
1876
+ const jcs = (value) => {
1877
+ if (value === null || typeof value !== "object") {
1878
+ return JSON.stringify(value);
1879
+ }
1880
+ if (Array.isArray(value)) {
1881
+ return "[" + value.map(jcs).join(",") + "]";
1882
+ }
1883
+ const keys = Object.keys(value).sort();
1884
+ return ("{" +
1885
+ keys
1886
+ .map((k) => JSON.stringify(k) + ":" + jcs(value[k]))
1887
+ .join(",") +
1888
+ "}");
1889
+ };
1890
+ /**
1891
+ * Build the base64url-encoded credential token for the `Authorization` header.
1892
+ *
1893
+ * Per the spec the credential is a JCS-serialised JSON object that echoes all
1894
+ * challenge auth-params (id, realm, method, intent, request, expires) and
1895
+ * carries the HTLC preimage that proves payment:
1896
+ *
1897
+ * {
1898
+ * "challenge": { "id": "…", "intent": "charge",
1899
+ * "method": "lightning", "realm": "…", "request": "…" },
1900
+ * "payload": { "preimage": "<64-char lowercase hex>" }
1901
+ * }
1902
+ *
1903
+ * Keys are sorted lexicographically at every level per JCS.
1904
+ */
1905
+ const buildMppCredential = (challenge, preimage, source) => {
1906
+ const challengeEcho = {
1907
+ id: challenge.id,
1908
+ intent: challenge.intent,
1909
+ method: challenge.method,
1910
+ realm: challenge.realm,
1911
+ request: challenge.request,
1912
+ };
1913
+ if (challenge.expires) {
1914
+ challengeEcho.expires = challenge.expires;
1915
+ }
1916
+ const credential = {
1917
+ challenge: challengeEcho,
1918
+ payload: { preimage },
1919
+ };
1920
+ return encodeBase64url(jcs(credential));
1921
+ };
1922
+
1923
+ /**
1924
+ * Handle a `WWW-Authenticate: Payment …` challenge produced by a
1925
+ * draft-lightning-charge-00 server.
1926
+ *
1927
+ * Flow:
1928
+ * 1. Parse the challenge from the header.
1929
+ * 2. Decode the `request` auth-param to find the BOLT11 invoice.
1930
+ * 3. Pay the invoice via the wallet; receive the HTLC preimage.
1931
+ * 4. Build the `Authorization: Payment <credential>` header.
1932
+ * 5. Retry the original request with the credential.
1933
+ */
1934
+ const handleMppChargePayment = async (wwwAuthHeader, url, fetchArgs, headers, wallet) => {
1935
+ const challenge = parseMppChallenge(wwwAuthHeader);
1936
+ if (!challenge) {
1937
+ throw new Error("mpp: invalid or unsupported WWW-Authenticate challenge (expected Payment method=lightning intent=charge)");
1938
+ }
1939
+ let request;
1940
+ try {
1941
+ request = JSON.parse(decodeBase64url(challenge.request));
1942
+ }
1943
+ catch (_) {
1944
+ throw new Error("mpp: invalid request auth-param (not valid base64url-encoded JSON)");
1945
+ }
1946
+ const invoice = request.methodDetails?.invoice;
1947
+ if (!invoice) {
1948
+ throw new Error("mpp: missing invoice in charge request");
1949
+ }
1950
+ const invResp = await wallet.payInvoice({ invoice });
1951
+ // Per spec: Authorization: Payment <base64url-token> (single token, no wrapper)
1952
+ const credential = buildMppCredential(challenge, invResp.preimage);
1953
+ headers.set("Authorization", `Payment ${credential}`);
1954
+ return fetch(url, fetchArgs);
1955
+ };
1956
+ /**
1957
+ * Fetch a resource protected by the draft-lightning-charge-00 payment
1958
+ * authentication protocol.
1959
+ *
1960
+ * On a `402 Payment Required` response that carries a
1961
+ * `WWW-Authenticate: Payment method="lightning" intent="charge" …` header
1962
+ * the function pays the embedded BOLT11 invoice and retries with the
1963
+ * resulting preimage as the credential.
1964
+ *
1965
+ * Note: lightning-charge uses consume-once challenge semantics – each
1966
+ * challenge embeds a fresh invoice, so paid credentials cannot be reused.
1967
+ * The `store` option is accepted for API consistency but is not used.
1968
+ */
1969
+ const fetchWithMpp = async (url, fetchArgs, options) => {
1970
+ const wallet = options.wallet;
1971
+ if (!wallet) {
1972
+ throw new Error("wallet is missing");
1973
+ }
1974
+ if (!fetchArgs) {
1975
+ fetchArgs = {};
1976
+ }
1977
+ fetchArgs.cache = "no-store";
1978
+ fetchArgs.mode = "cors";
1979
+ const headers = new Headers(fetchArgs.headers ?? undefined);
1980
+ fetchArgs.headers = headers;
1981
+ const initResp = await fetch(url, fetchArgs);
1982
+ const wwwAuthHeader = initResp.headers.get("www-authenticate");
1983
+ if (!wwwAuthHeader ||
1984
+ !wwwAuthHeader.trimStart().toLowerCase().startsWith("payment")) {
1985
+ return initResp;
1986
+ }
1987
+ return handleMppChargePayment(wwwAuthHeader, url, fetchArgs, headers, wallet);
1988
+ };
1989
+
1990
+ const fetch402 = async (url, fetchArgs, options) => {
1991
+ const wallet = options.maxAmount
1992
+ ? createGuardedWallet(options.wallet, options.maxAmount)
1993
+ : options.wallet;
1994
+ if (!fetchArgs) {
1995
+ fetchArgs = {};
1996
+ }
1997
+ fetchArgs.cache = "no-store";
1998
+ fetchArgs.mode = "cors";
1999
+ const headers = new Headers(fetchArgs.headers ?? undefined);
2000
+ fetchArgs.headers = headers;
2001
+ const initResp = await fetch(url, fetchArgs);
2002
+ const wwwAuthHeader = initResp.headers.get("www-authenticate");
2003
+ if (wwwAuthHeader) {
2004
+ const trimmed = wwwAuthHeader.trimStart().toLowerCase();
2005
+ if (trimmed.startsWith("payment")) {
2006
+ return handleMppChargePayment(wwwAuthHeader, url, fetchArgs, headers, wallet);
2007
+ }
2008
+ if (trimmed.startsWith("l402") || trimmed.startsWith("lsat")) {
2009
+ return handleL402Payment(wwwAuthHeader, url, fetchArgs, headers, wallet);
2010
+ }
2011
+ throw new Error(`fetch402: unsupported WWW-Authenticate scheme: ${wwwAuthHeader}`);
2012
+ }
2013
+ const x402Header = initResp.headers.get("PAYMENT-REQUIRED");
2014
+ if (x402Header) {
2015
+ return handleX402Payment(x402Header, url, fetchArgs, headers, wallet);
2016
+ }
2017
+ return initResp;
2018
+ };
2019
+
2020
+ async function issueL402Macaroon(secret, paymentHash, params) {
2021
+ if (params !== undefined &&
2022
+ Object.prototype.hasOwnProperty.call(params, "paymentHash")) {
2023
+ throw new Error("paymentHash is reserved");
2024
+ }
2025
+ const payload = { ...params, paymentHash };
2026
+ const encoded = Buffer.from(JSON.stringify(payload)).toString("base64url");
2027
+ const mac = await sign(secret, encoded);
2028
+ return `${encoded}.${mac}`;
2029
+ }
2030
+ async function verifyL402Macaroon(secret, token) {
2031
+ const { timingSafeEqual } = await import('crypto');
2032
+ const dotIndex = token.lastIndexOf(".");
2033
+ if (dotIndex === -1)
2034
+ throw new Error("Invalid macaroon token");
2035
+ const encoded = token.slice(0, dotIndex);
2036
+ const mac = token.slice(dotIndex + 1);
2037
+ // Constant-time comparison to prevent timing attacks
2038
+ const expectedMac = await sign(secret, encoded);
2039
+ try {
2040
+ if (!timingSafeEqual(Buffer.from(mac, "hex"), Buffer.from(expectedMac, "hex"))) {
2041
+ throw new Error("Invalid macaroon token");
2042
+ }
2043
+ }
2044
+ catch (e) {
2045
+ throw new Error("Invalid macaroon token");
2046
+ }
2047
+ try {
2048
+ const parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
2049
+ if (parsed === null ||
2050
+ typeof parsed !== "object" ||
2051
+ Array.isArray(parsed) ||
2052
+ typeof parsed.paymentHash !== "string") {
2053
+ throw new Error("Invalid macaroon payload");
2054
+ }
2055
+ return parsed;
2056
+ }
2057
+ catch {
2058
+ throw new Error("Invalid macaroon token");
2059
+ }
2060
+ }
2061
+ async function sign(secret, payload) {
2062
+ const { createHmac } = await import('crypto');
2063
+ return createHmac("sha256", secret).update(payload).digest("hex");
2064
+ }
2065
+
2066
+ /**
2067
+ * Server: create a WWW-Authenticate header for a given macaroon and invoice
2068
+ * @param args the macaroon/token and invoice generated for the client's request
2069
+ * @returns the header value
2070
+ */
2071
+ const makeL402AuthenticateHeader = (args) => {
2072
+ if (!args.token) {
2073
+ throw new Error("token must be provided");
2074
+ }
2075
+ return `L402 version="0" token="${args.token}", invoice="${args.invoice}"`;
1745
2076
  };
2077
+ /**
2078
+ * Server: parse "authorization" header sent from client
2079
+ * @param input value from authorization header
2080
+ * @returns the macaroon and preimage
2081
+ */
2082
+ function parseL402Authorization(input) {
2083
+ // Backwards compat: LSAT was the former name of L402
2084
+ const normalized = input.replace(/^LSAT /, "L402 ");
2085
+ const prefix = "L402 ";
2086
+ if (!normalized.startsWith(prefix))
2087
+ return null;
2088
+ const credentials = normalized.slice(prefix.length);
2089
+ const colonIndex = credentials.indexOf(":");
2090
+ if (colonIndex === -1) {
2091
+ throw new Error("Invalid authorization header value");
2092
+ }
2093
+ return {
2094
+ token: credentials.slice(0, colonIndex),
2095
+ preimage: credentials.slice(colonIndex + 1),
2096
+ };
2097
+ }
1746
2098
 
1747
2099
  const numSatsInBtc = 100000000;
1748
2100
  const getFiatCurrencies = async () => {
@@ -1797,10 +2149,12 @@ exports.DEFAULT_PROXY = DEFAULT_PROXY;
1797
2149
  exports.Invoice = Invoice;
1798
2150
  exports.LN_ADDRESS_REGEX = LN_ADDRESS_REGEX;
1799
2151
  exports.LightningAddress = LightningAddress;
1800
- exports.MemoryStorage = MemoryStorage;
1801
- exports.NoStorage = NoStorage;
2152
+ exports.createGuardedWallet = createGuardedWallet;
1802
2153
  exports.decodeInvoice = decodeInvoice;
2154
+ exports.fetch402 = fetch402;
1803
2155
  exports.fetchWithL402 = fetchWithL402;
2156
+ exports.fetchWithMpp = fetchWithMpp;
2157
+ exports.fetchWithX402 = fetchWithX402;
1804
2158
  exports.fromHexString = fromHexString;
1805
2159
  exports.generateZapEvent = generateZapEvent;
1806
2160
  exports.getEventHash = getEventHash;
@@ -1811,12 +2165,16 @@ exports.getFormattedFiatValue = getFormattedFiatValue;
1811
2165
  exports.getSatoshiValue = getSatoshiValue;
1812
2166
  exports.isUrl = isUrl;
1813
2167
  exports.isValidAmount = isValidAmount;
1814
- exports.makeAuthenticateHeader = makeAuthenticateHeader;
2168
+ exports.issueL402Macaroon = issueL402Macaroon;
2169
+ exports.makeL402AuthenticateHeader = makeL402AuthenticateHeader;
1815
2170
  exports.parseKeysendResponse = parseKeysendResponse;
1816
2171
  exports.parseL402 = parseL402;
2172
+ exports.parseL402Authorization = parseL402Authorization;
1817
2173
  exports.parseLnUrlPayResponse = parseLnUrlPayResponse;
1818
2174
  exports.parseNostrResponse = parseNostrResponse;
1819
2175
  exports.sendBoostagram = sendBoostagram;
1820
2176
  exports.serializeEvent = serializeEvent;
1821
2177
  exports.validateEvent = validateEvent;
2178
+ exports.validatePreimage = validatePreimage;
2179
+ exports.verifyL402Macaroon = verifyL402Macaroon;
1822
2180
  //# sourceMappingURL=index.cjs.map