bolt12-utils 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/bech32.d.ts +31 -0
  2. package/dist/bech32.d.ts.map +1 -0
  3. package/dist/bech32.js +161 -0
  4. package/dist/bech32.js.map +1 -0
  5. package/dist/bigsize.d.ts +22 -0
  6. package/dist/bigsize.d.ts.map +1 -0
  7. package/dist/bigsize.js +87 -0
  8. package/dist/bigsize.js.map +1 -0
  9. package/dist/fields.d.ts +61 -0
  10. package/dist/fields.d.ts.map +1 -0
  11. package/dist/fields.js +99 -0
  12. package/dist/fields.js.map +1 -0
  13. package/dist/generated.d.ts +179 -0
  14. package/dist/generated.d.ts.map +1 -0
  15. package/dist/generated.js +565 -0
  16. package/dist/generated.js.map +1 -0
  17. package/dist/index.d.ts +47 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +125 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/merkle.d.ts +55 -0
  22. package/dist/merkle.d.ts.map +1 -0
  23. package/dist/merkle.js +144 -0
  24. package/dist/merkle.js.map +1 -0
  25. package/dist/offer.d.ts +45 -0
  26. package/dist/offer.d.ts.map +1 -0
  27. package/dist/offer.js +288 -0
  28. package/dist/offer.js.map +1 -0
  29. package/dist/payer_proof.d.ts +89 -0
  30. package/dist/payer_proof.d.ts.map +1 -0
  31. package/dist/payer_proof.js +576 -0
  32. package/dist/payer_proof.js.map +1 -0
  33. package/dist/tlv.d.ts +26 -0
  34. package/dist/tlv.d.ts.map +1 -0
  35. package/dist/tlv.js +65 -0
  36. package/dist/tlv.js.map +1 -0
  37. package/dist/utils.d.ts +12 -0
  38. package/dist/utils.d.ts.map +1 -0
  39. package/dist/utils.js +52 -0
  40. package/dist/utils.js.map +1 -0
  41. package/package.json +47 -0
  42. package/src/bech32.ts +187 -0
  43. package/src/bigsize.ts +97 -0
  44. package/src/fields.ts +147 -0
  45. package/src/generated.ts +697 -0
  46. package/src/index.ts +132 -0
  47. package/src/merkle.ts +163 -0
  48. package/src/offer.ts +328 -0
  49. package/src/payer_proof.ts +727 -0
  50. package/src/tlv.ts +75 -0
  51. package/src/utils.ts +49 -0
@@ -0,0 +1,697 @@
1
+ /**
2
+ * @fileoverview Auto-generated BOLT12 TLV types, constants, and field extractors.
3
+ * @generated from specs/bolt12.csv by tools/generate-ts.ts
4
+ *
5
+ * DO NOT EDIT — changes will be overwritten on next generation.
6
+ *
7
+ * Re-generate with:
8
+ * npx tsx tools/generate-ts.ts --spec specs/bolt12.csv --output js/src/generated.ts
9
+ *
10
+ * Source: lightning-rfc/12-offer-encoding.md (https://github.com/lightning/bolts)
11
+ */
12
+
13
+ /* eslint-disable */
14
+
15
+ import type { TlvRecord } from './tlv.js';
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Wire encoding/decoding helpers
19
+ // ---------------------------------------------------------------------------
20
+
21
+ const utf8Encoder = new TextEncoder();
22
+ const utf8Decoder = new TextDecoder('utf-8', { fatal: false });
23
+
24
+ /** Read a truncated big-endian unsigned integer. */
25
+ function readTU(data: Uint8Array): bigint {
26
+ if (data.length === 0) return 0n;
27
+ let val = 0n;
28
+ for (let i = 0; i < data.length; i++) {
29
+ val = (val << 8n) | BigInt(data[i]);
30
+ }
31
+ return val;
32
+ }
33
+
34
+ /** Write a truncated unsigned integer (variable length, big-endian). */
35
+ function writeTU(val: bigint): Uint8Array {
36
+ if (val === 0n) return new Uint8Array(0);
37
+ const bytes: number[] = [];
38
+ let v = val;
39
+ while (v > 0n) {
40
+ bytes.unshift(Number(v & 0xffn));
41
+ v >>= 8n;
42
+ }
43
+ return new Uint8Array(bytes);
44
+ }
45
+
46
+ /** Read a fixed-width big-endian unsigned integer. */
47
+ function readU(data: Uint8Array, offset: number, len: number): [bigint, number] {
48
+ let val = 0n;
49
+ for (let i = 0; i < len; i++) {
50
+ val = (val << 8n) | BigInt(data[offset + i]);
51
+ }
52
+ return [val, offset + len];
53
+ }
54
+
55
+ /** Convert bytes to lowercase hex string. */
56
+ function toHex(buf: Uint8Array): string {
57
+ let hex = '';
58
+ for (let i = 0; i < buf.length; i++) {
59
+ hex += buf[i].toString(16).padStart(2, '0');
60
+ }
61
+ return hex;
62
+ }
63
+
64
+ /** Subtype: blinded_payinfo */
65
+ export interface BlindedPayinfo {
66
+ fee_base_msat: number;
67
+ fee_proportional_millionths: number;
68
+ cltv_expiry_delta: number;
69
+ htlc_minimum_msat: bigint;
70
+ htlc_maximum_msat: bigint;
71
+ features: Uint8Array;
72
+ }
73
+
74
+ /** Subtype: fallback_address */
75
+ export interface FallbackAddress {
76
+ version: number;
77
+ address: Uint8Array;
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // offer TLV type constants
82
+ // ---------------------------------------------------------------------------
83
+
84
+ export const OFFER_CHAINS = 2n;
85
+ export const OFFER_METADATA = 4n;
86
+ export const OFFER_CURRENCY = 6n;
87
+ export const OFFER_AMOUNT = 8n;
88
+ export const OFFER_DESCRIPTION = 10n;
89
+ export const OFFER_FEATURES = 12n;
90
+ export const OFFER_ABSOLUTE_EXPIRY = 14n;
91
+ export const OFFER_PATHS = 16n;
92
+ export const OFFER_ISSUER = 18n;
93
+ export const OFFER_QUANTITY_MAX = 20n;
94
+ export const OFFER_ISSUER_ID = 22n;
95
+
96
+ export const KNOWN_OFFER_TYPES = new Set<bigint>([
97
+ OFFER_CHAINS,
98
+ OFFER_METADATA,
99
+ OFFER_CURRENCY,
100
+ OFFER_AMOUNT,
101
+ OFFER_DESCRIPTION,
102
+ OFFER_FEATURES,
103
+ OFFER_ABSOLUTE_EXPIRY,
104
+ OFFER_PATHS,
105
+ OFFER_ISSUER,
106
+ OFFER_QUANTITY_MAX,
107
+ OFFER_ISSUER_ID,
108
+ ]);
109
+
110
+ /** Map from TLV type number to field name for offer. */
111
+ export const OFFER_TLV_NAMES: ReadonlyMap<bigint, string> = new Map([
112
+ [2n, 'offer_chains'],
113
+ [4n, 'offer_metadata'],
114
+ [6n, 'offer_currency'],
115
+ [8n, 'offer_amount'],
116
+ [10n, 'offer_description'],
117
+ [12n, 'offer_features'],
118
+ [14n, 'offer_absolute_expiry'],
119
+ [16n, 'offer_paths'],
120
+ [18n, 'offer_issuer'],
121
+ [20n, 'offer_quantity_max'],
122
+ [22n, 'offer_issuer_id'],
123
+ ]);
124
+
125
+ /** Typed fields for a decoded offer. */
126
+ export interface OfferFields {
127
+ offer_chains?: Uint8Array[];
128
+ offer_metadata?: Uint8Array;
129
+ offer_currency?: string;
130
+ offer_amount?: bigint;
131
+ offer_description?: string;
132
+ offer_features?: Uint8Array;
133
+ offer_absolute_expiry?: bigint;
134
+ offer_paths?: Uint8Array;
135
+ offer_issuer?: string;
136
+ offer_quantity_max?: bigint;
137
+ offer_issuer_id?: Uint8Array;
138
+ /** Raw TLV records for advanced access. */
139
+ records: TlvRecord[];
140
+ }
141
+
142
+ /**
143
+ * Extract typed fields from offer TLV records.
144
+ */
145
+ export function extractOfferFields(records: TlvRecord[]): OfferFields {
146
+ const fields: Partial<Omit<OfferFields, 'records'>> = {};
147
+
148
+ for (const rec of records) {
149
+ switch (rec.type) {
150
+ case OFFER_CHAINS: {
151
+ {
152
+ const chains: Uint8Array[] = [];
153
+ for (let i = 0; i < rec.value.length; i += 32) {
154
+ chains.push(rec.value.slice(i, i + 32));
155
+ }
156
+ fields.offer_chains = chains;
157
+ }
158
+ break;
159
+ }
160
+ case OFFER_METADATA: {
161
+ fields.offer_metadata = rec.value;
162
+ break;
163
+ }
164
+ case OFFER_CURRENCY: {
165
+ fields.offer_currency = utf8Decoder.decode(rec.value);
166
+ break;
167
+ }
168
+ case OFFER_AMOUNT: {
169
+ fields.offer_amount = readTU(rec.value);
170
+ break;
171
+ }
172
+ case OFFER_DESCRIPTION: {
173
+ fields.offer_description = utf8Decoder.decode(rec.value);
174
+ break;
175
+ }
176
+ case OFFER_FEATURES: {
177
+ fields.offer_features = rec.value;
178
+ break;
179
+ }
180
+ case OFFER_ABSOLUTE_EXPIRY: {
181
+ fields.offer_absolute_expiry = readTU(rec.value);
182
+ break;
183
+ }
184
+ case OFFER_PATHS: {
185
+ fields.offer_paths = rec.value; // blinded_path[] — raw bytes
186
+ break;
187
+ }
188
+ case OFFER_ISSUER: {
189
+ fields.offer_issuer = utf8Decoder.decode(rec.value);
190
+ break;
191
+ }
192
+ case OFFER_QUANTITY_MAX: {
193
+ fields.offer_quantity_max = readTU(rec.value);
194
+ break;
195
+ }
196
+ case OFFER_ISSUER_ID: {
197
+ fields.offer_issuer_id = rec.value.slice(0, 33);
198
+ break;
199
+ }
200
+ }
201
+ }
202
+
203
+ return { ...fields, records } as OfferFields;
204
+ }
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // invoice_request TLV type constants
208
+ // ---------------------------------------------------------------------------
209
+
210
+ export const INVREQ_METADATA = 0n;
211
+ export const INVREQ_CHAIN = 80n;
212
+ export const INVREQ_AMOUNT = 82n;
213
+ export const INVREQ_FEATURES = 84n;
214
+ export const INVREQ_QUANTITY = 86n;
215
+ export const INVREQ_PAYER_ID = 88n;
216
+ export const INVREQ_PAYER_NOTE = 89n;
217
+ export const INVREQ_PATHS = 90n;
218
+ export const INVREQ_BIP_353_NAME = 91n;
219
+ export const SIGNATURE = 240n;
220
+
221
+ export const KNOWN_INVOICE_REQUEST_TYPES = new Set<bigint>([
222
+ INVREQ_METADATA,
223
+ INVREQ_CHAIN,
224
+ INVREQ_AMOUNT,
225
+ INVREQ_FEATURES,
226
+ INVREQ_QUANTITY,
227
+ INVREQ_PAYER_ID,
228
+ INVREQ_PAYER_NOTE,
229
+ INVREQ_PATHS,
230
+ INVREQ_BIP_353_NAME,
231
+ SIGNATURE,
232
+ ]);
233
+
234
+ /** Map from TLV type number to field name for invoice_request. */
235
+ export const INVOICE_REQUEST_TLV_NAMES: ReadonlyMap<bigint, string> = new Map([
236
+ [0n, 'invreq_metadata'],
237
+ [2n, 'offer_chains'],
238
+ [4n, 'offer_metadata'],
239
+ [6n, 'offer_currency'],
240
+ [8n, 'offer_amount'],
241
+ [10n, 'offer_description'],
242
+ [12n, 'offer_features'],
243
+ [14n, 'offer_absolute_expiry'],
244
+ [16n, 'offer_paths'],
245
+ [18n, 'offer_issuer'],
246
+ [20n, 'offer_quantity_max'],
247
+ [22n, 'offer_issuer_id'],
248
+ [80n, 'invreq_chain'],
249
+ [82n, 'invreq_amount'],
250
+ [84n, 'invreq_features'],
251
+ [86n, 'invreq_quantity'],
252
+ [88n, 'invreq_payer_id'],
253
+ [89n, 'invreq_payer_note'],
254
+ [90n, 'invreq_paths'],
255
+ [91n, 'invreq_bip_353_name'],
256
+ [240n, 'signature'],
257
+ ]);
258
+
259
+ /** Typed fields for a decoded invoice_request. */
260
+ export interface InvoiceRequestFields {
261
+ invreq_metadata?: Uint8Array;
262
+ offer_chains?: Uint8Array[];
263
+ offer_metadata?: Uint8Array;
264
+ offer_currency?: string;
265
+ offer_amount?: bigint;
266
+ offer_description?: string;
267
+ offer_features?: Uint8Array;
268
+ offer_absolute_expiry?: bigint;
269
+ offer_paths?: Uint8Array;
270
+ offer_issuer?: string;
271
+ offer_quantity_max?: bigint;
272
+ offer_issuer_id?: Uint8Array;
273
+ invreq_chain?: Uint8Array;
274
+ invreq_amount?: bigint;
275
+ invreq_features?: Uint8Array;
276
+ invreq_quantity?: bigint;
277
+ invreq_payer_id?: Uint8Array;
278
+ invreq_payer_note?: string;
279
+ invreq_paths?: Uint8Array;
280
+ invreq_bip_353_name?: {
281
+ name: Uint8Array;
282
+ domain: Uint8Array;
283
+ };
284
+ signature?: Uint8Array;
285
+ /** Raw TLV records for advanced access. */
286
+ records: TlvRecord[];
287
+ }
288
+
289
+ /**
290
+ * Extract typed fields from invoice_request TLV records.
291
+ */
292
+ export function extractInvoiceRequestFields(records: TlvRecord[]): InvoiceRequestFields {
293
+ const fields: Partial<Omit<InvoiceRequestFields, 'records'>> = {};
294
+
295
+ for (const rec of records) {
296
+ switch (rec.type) {
297
+ case INVREQ_METADATA: {
298
+ fields.invreq_metadata = rec.value;
299
+ break;
300
+ }
301
+ case OFFER_CHAINS: {
302
+ {
303
+ const chains: Uint8Array[] = [];
304
+ for (let i = 0; i < rec.value.length; i += 32) {
305
+ chains.push(rec.value.slice(i, i + 32));
306
+ }
307
+ fields.offer_chains = chains;
308
+ }
309
+ break;
310
+ }
311
+ case OFFER_METADATA: {
312
+ fields.offer_metadata = rec.value;
313
+ break;
314
+ }
315
+ case OFFER_CURRENCY: {
316
+ fields.offer_currency = utf8Decoder.decode(rec.value);
317
+ break;
318
+ }
319
+ case OFFER_AMOUNT: {
320
+ fields.offer_amount = readTU(rec.value);
321
+ break;
322
+ }
323
+ case OFFER_DESCRIPTION: {
324
+ fields.offer_description = utf8Decoder.decode(rec.value);
325
+ break;
326
+ }
327
+ case OFFER_FEATURES: {
328
+ fields.offer_features = rec.value;
329
+ break;
330
+ }
331
+ case OFFER_ABSOLUTE_EXPIRY: {
332
+ fields.offer_absolute_expiry = readTU(rec.value);
333
+ break;
334
+ }
335
+ case OFFER_PATHS: {
336
+ fields.offer_paths = rec.value; // blinded_path[] — raw bytes
337
+ break;
338
+ }
339
+ case OFFER_ISSUER: {
340
+ fields.offer_issuer = utf8Decoder.decode(rec.value);
341
+ break;
342
+ }
343
+ case OFFER_QUANTITY_MAX: {
344
+ fields.offer_quantity_max = readTU(rec.value);
345
+ break;
346
+ }
347
+ case OFFER_ISSUER_ID: {
348
+ fields.offer_issuer_id = rec.value.slice(0, 33);
349
+ break;
350
+ }
351
+ case INVREQ_CHAIN: {
352
+ fields.invreq_chain = rec.value.slice(0, 32);
353
+ break;
354
+ }
355
+ case INVREQ_AMOUNT: {
356
+ fields.invreq_amount = readTU(rec.value);
357
+ break;
358
+ }
359
+ case INVREQ_FEATURES: {
360
+ fields.invreq_features = rec.value;
361
+ break;
362
+ }
363
+ case INVREQ_QUANTITY: {
364
+ fields.invreq_quantity = readTU(rec.value);
365
+ break;
366
+ }
367
+ case INVREQ_PAYER_ID: {
368
+ fields.invreq_payer_id = rec.value.slice(0, 33);
369
+ break;
370
+ }
371
+ case INVREQ_PAYER_NOTE: {
372
+ fields.invreq_payer_note = utf8Decoder.decode(rec.value);
373
+ break;
374
+ }
375
+ case INVREQ_PATHS: {
376
+ fields.invreq_paths = rec.value; // blinded_path[] — raw bytes
377
+ break;
378
+ }
379
+ case INVREQ_BIP_353_NAME: {
380
+ let _off = 0;
381
+ const name_len = rec.value[_off]; _off += 1;
382
+ const _name = rec.value.slice(_off, _off + name_len); _off += name_len;
383
+ const domain_len = rec.value[_off]; _off += 1;
384
+ const _domain = rec.value.slice(_off, _off + domain_len); _off += domain_len;
385
+ fields.invreq_bip_353_name = { name: _name, domain: _domain };
386
+ break;
387
+ }
388
+ case SIGNATURE: {
389
+ fields.signature = rec.value.slice(0, 64);
390
+ break;
391
+ }
392
+ }
393
+ }
394
+
395
+ return { ...fields, records } as InvoiceRequestFields;
396
+ }
397
+
398
+ // ---------------------------------------------------------------------------
399
+ // invoice TLV type constants
400
+ // ---------------------------------------------------------------------------
401
+
402
+ export const INVOICE_PATHS = 160n;
403
+ export const INVOICE_BLINDEDPAY = 162n;
404
+ export const INVOICE_CREATED_AT = 164n;
405
+ export const INVOICE_RELATIVE_EXPIRY = 166n;
406
+ export const INVOICE_PAYMENT_HASH = 168n;
407
+ export const INVOICE_AMOUNT = 170n;
408
+ export const INVOICE_FALLBACKS = 172n;
409
+ export const INVOICE_FEATURES = 174n;
410
+ export const INVOICE_NODE_ID = 176n;
411
+
412
+ export const KNOWN_INVOICE_TYPES = new Set<bigint>([
413
+ INVOICE_PATHS,
414
+ INVOICE_BLINDEDPAY,
415
+ INVOICE_CREATED_AT,
416
+ INVOICE_RELATIVE_EXPIRY,
417
+ INVOICE_PAYMENT_HASH,
418
+ INVOICE_AMOUNT,
419
+ INVOICE_FALLBACKS,
420
+ INVOICE_FEATURES,
421
+ INVOICE_NODE_ID,
422
+ ]);
423
+
424
+ /** Map from TLV type number to field name for invoice. */
425
+ export const INVOICE_TLV_NAMES: ReadonlyMap<bigint, string> = new Map([
426
+ [0n, 'invreq_metadata'],
427
+ [2n, 'offer_chains'],
428
+ [4n, 'offer_metadata'],
429
+ [6n, 'offer_currency'],
430
+ [8n, 'offer_amount'],
431
+ [10n, 'offer_description'],
432
+ [12n, 'offer_features'],
433
+ [14n, 'offer_absolute_expiry'],
434
+ [16n, 'offer_paths'],
435
+ [18n, 'offer_issuer'],
436
+ [20n, 'offer_quantity_max'],
437
+ [22n, 'offer_issuer_id'],
438
+ [80n, 'invreq_chain'],
439
+ [82n, 'invreq_amount'],
440
+ [84n, 'invreq_features'],
441
+ [86n, 'invreq_quantity'],
442
+ [88n, 'invreq_payer_id'],
443
+ [89n, 'invreq_payer_note'],
444
+ [90n, 'invreq_paths'],
445
+ [91n, 'invreq_bip_353_name'],
446
+ [160n, 'invoice_paths'],
447
+ [162n, 'invoice_blindedpay'],
448
+ [164n, 'invoice_created_at'],
449
+ [166n, 'invoice_relative_expiry'],
450
+ [168n, 'invoice_payment_hash'],
451
+ [170n, 'invoice_amount'],
452
+ [172n, 'invoice_fallbacks'],
453
+ [174n, 'invoice_features'],
454
+ [176n, 'invoice_node_id'],
455
+ [240n, 'signature'],
456
+ ]);
457
+
458
+ /** Typed fields for a decoded invoice. */
459
+ export interface InvoiceFields {
460
+ invreq_metadata?: Uint8Array;
461
+ offer_chains?: Uint8Array[];
462
+ offer_metadata?: Uint8Array;
463
+ offer_currency?: string;
464
+ offer_amount?: bigint;
465
+ offer_description?: string;
466
+ offer_features?: Uint8Array;
467
+ offer_absolute_expiry?: bigint;
468
+ offer_paths?: Uint8Array;
469
+ offer_issuer?: string;
470
+ offer_quantity_max?: bigint;
471
+ offer_issuer_id?: Uint8Array;
472
+ invreq_chain?: Uint8Array;
473
+ invreq_amount?: bigint;
474
+ invreq_features?: Uint8Array;
475
+ invreq_quantity?: bigint;
476
+ invreq_payer_id?: Uint8Array;
477
+ invreq_payer_note?: string;
478
+ invreq_paths?: Uint8Array;
479
+ invreq_bip_353_name?: {
480
+ name: Uint8Array;
481
+ domain: Uint8Array;
482
+ };
483
+ invoice_paths?: Uint8Array;
484
+ invoice_blindedpay?: Uint8Array;
485
+ invoice_created_at?: bigint;
486
+ invoice_relative_expiry?: bigint;
487
+ invoice_payment_hash?: Uint8Array;
488
+ invoice_amount?: bigint;
489
+ invoice_fallbacks?: Uint8Array;
490
+ invoice_features?: Uint8Array;
491
+ invoice_node_id?: Uint8Array;
492
+ signature?: Uint8Array;
493
+ /** Raw TLV records for advanced access. */
494
+ records: TlvRecord[];
495
+ }
496
+
497
+ /**
498
+ * Extract typed fields from invoice TLV records.
499
+ */
500
+ export function extractInvoiceFields(records: TlvRecord[]): InvoiceFields {
501
+ const fields: Partial<Omit<InvoiceFields, 'records'>> = {};
502
+
503
+ for (const rec of records) {
504
+ switch (rec.type) {
505
+ case INVREQ_METADATA: {
506
+ fields.invreq_metadata = rec.value;
507
+ break;
508
+ }
509
+ case OFFER_CHAINS: {
510
+ {
511
+ const chains: Uint8Array[] = [];
512
+ for (let i = 0; i < rec.value.length; i += 32) {
513
+ chains.push(rec.value.slice(i, i + 32));
514
+ }
515
+ fields.offer_chains = chains;
516
+ }
517
+ break;
518
+ }
519
+ case OFFER_METADATA: {
520
+ fields.offer_metadata = rec.value;
521
+ break;
522
+ }
523
+ case OFFER_CURRENCY: {
524
+ fields.offer_currency = utf8Decoder.decode(rec.value);
525
+ break;
526
+ }
527
+ case OFFER_AMOUNT: {
528
+ fields.offer_amount = readTU(rec.value);
529
+ break;
530
+ }
531
+ case OFFER_DESCRIPTION: {
532
+ fields.offer_description = utf8Decoder.decode(rec.value);
533
+ break;
534
+ }
535
+ case OFFER_FEATURES: {
536
+ fields.offer_features = rec.value;
537
+ break;
538
+ }
539
+ case OFFER_ABSOLUTE_EXPIRY: {
540
+ fields.offer_absolute_expiry = readTU(rec.value);
541
+ break;
542
+ }
543
+ case OFFER_PATHS: {
544
+ fields.offer_paths = rec.value; // blinded_path[] — raw bytes
545
+ break;
546
+ }
547
+ case OFFER_ISSUER: {
548
+ fields.offer_issuer = utf8Decoder.decode(rec.value);
549
+ break;
550
+ }
551
+ case OFFER_QUANTITY_MAX: {
552
+ fields.offer_quantity_max = readTU(rec.value);
553
+ break;
554
+ }
555
+ case OFFER_ISSUER_ID: {
556
+ fields.offer_issuer_id = rec.value.slice(0, 33);
557
+ break;
558
+ }
559
+ case INVREQ_CHAIN: {
560
+ fields.invreq_chain = rec.value.slice(0, 32);
561
+ break;
562
+ }
563
+ case INVREQ_AMOUNT: {
564
+ fields.invreq_amount = readTU(rec.value);
565
+ break;
566
+ }
567
+ case INVREQ_FEATURES: {
568
+ fields.invreq_features = rec.value;
569
+ break;
570
+ }
571
+ case INVREQ_QUANTITY: {
572
+ fields.invreq_quantity = readTU(rec.value);
573
+ break;
574
+ }
575
+ case INVREQ_PAYER_ID: {
576
+ fields.invreq_payer_id = rec.value.slice(0, 33);
577
+ break;
578
+ }
579
+ case INVREQ_PAYER_NOTE: {
580
+ fields.invreq_payer_note = utf8Decoder.decode(rec.value);
581
+ break;
582
+ }
583
+ case INVREQ_PATHS: {
584
+ fields.invreq_paths = rec.value; // blinded_path[] — raw bytes
585
+ break;
586
+ }
587
+ case INVREQ_BIP_353_NAME: {
588
+ let _off = 0;
589
+ const name_len = rec.value[_off]; _off += 1;
590
+ const _name = rec.value.slice(_off, _off + name_len); _off += name_len;
591
+ const domain_len = rec.value[_off]; _off += 1;
592
+ const _domain = rec.value.slice(_off, _off + domain_len); _off += domain_len;
593
+ fields.invreq_bip_353_name = { name: _name, domain: _domain };
594
+ break;
595
+ }
596
+ case INVOICE_PATHS: {
597
+ fields.invoice_paths = rec.value; // blinded_path[] — raw bytes
598
+ break;
599
+ }
600
+ case INVOICE_BLINDEDPAY: {
601
+ fields.invoice_blindedpay = rec.value; // blinded_payinfo[] — raw bytes
602
+ break;
603
+ }
604
+ case INVOICE_CREATED_AT: {
605
+ fields.invoice_created_at = readTU(rec.value);
606
+ break;
607
+ }
608
+ case INVOICE_RELATIVE_EXPIRY: {
609
+ fields.invoice_relative_expiry = readTU(rec.value);
610
+ break;
611
+ }
612
+ case INVOICE_PAYMENT_HASH: {
613
+ fields.invoice_payment_hash = rec.value.slice(0, 32);
614
+ break;
615
+ }
616
+ case INVOICE_AMOUNT: {
617
+ fields.invoice_amount = readTU(rec.value);
618
+ break;
619
+ }
620
+ case INVOICE_FALLBACKS: {
621
+ fields.invoice_fallbacks = rec.value; // fallback_address[] — raw bytes
622
+ break;
623
+ }
624
+ case INVOICE_FEATURES: {
625
+ fields.invoice_features = rec.value;
626
+ break;
627
+ }
628
+ case INVOICE_NODE_ID: {
629
+ fields.invoice_node_id = rec.value.slice(0, 33);
630
+ break;
631
+ }
632
+ case SIGNATURE: {
633
+ fields.signature = rec.value.slice(0, 64);
634
+ break;
635
+ }
636
+ }
637
+ }
638
+
639
+ return { ...fields, records } as InvoiceFields;
640
+ }
641
+
642
+ // ---------------------------------------------------------------------------
643
+ // invoice_error TLV type constants
644
+ // ---------------------------------------------------------------------------
645
+
646
+ export const ERRONEOUS_FIELD = 1n;
647
+ export const SUGGESTED_VALUE = 3n;
648
+ export const ERROR = 5n;
649
+
650
+ export const KNOWN_INVOICE_ERROR_TYPES = new Set<bigint>([
651
+ ERRONEOUS_FIELD,
652
+ SUGGESTED_VALUE,
653
+ ERROR,
654
+ ]);
655
+
656
+ /** Map from TLV type number to field name for invoice_error. */
657
+ export const INVOICE_ERROR_TLV_NAMES: ReadonlyMap<bigint, string> = new Map([
658
+ [1n, 'erroneous_field'],
659
+ [3n, 'suggested_value'],
660
+ [5n, 'error'],
661
+ ]);
662
+
663
+ /** Typed fields for a decoded invoice_error. */
664
+ export interface InvoiceErrorFields {
665
+ erroneous_field?: bigint;
666
+ suggested_value?: Uint8Array;
667
+ error?: string;
668
+ /** Raw TLV records for advanced access. */
669
+ records: TlvRecord[];
670
+ }
671
+
672
+ /**
673
+ * Extract typed fields from invoice_error TLV records.
674
+ */
675
+ export function extractInvoiceErrorFields(records: TlvRecord[]): InvoiceErrorFields {
676
+ const fields: Partial<Omit<InvoiceErrorFields, 'records'>> = {};
677
+
678
+ for (const rec of records) {
679
+ switch (rec.type) {
680
+ case ERRONEOUS_FIELD: {
681
+ fields.erroneous_field = readTU(rec.value);
682
+ break;
683
+ }
684
+ case SUGGESTED_VALUE: {
685
+ fields.suggested_value = rec.value;
686
+ break;
687
+ }
688
+ case ERROR: {
689
+ fields.error = utf8Decoder.decode(rec.value);
690
+ break;
691
+ }
692
+ }
693
+ }
694
+
695
+ return { ...fields, records } as InvoiceErrorFields;
696
+ }
697
+