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