@thecryptodonkey/toll-booth 1.0.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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/adapters/express.d.ts +51 -0
  4. package/dist/adapters/express.d.ts.map +1 -0
  5. package/dist/adapters/express.js +237 -0
  6. package/dist/adapters/express.js.map +1 -0
  7. package/dist/adapters/proxy-headers.d.ts +7 -0
  8. package/dist/adapters/proxy-headers.d.ts.map +1 -0
  9. package/dist/adapters/proxy-headers.js +58 -0
  10. package/dist/adapters/proxy-headers.js.map +1 -0
  11. package/dist/adapters/web-standard.d.ts +60 -0
  12. package/dist/adapters/web-standard.d.ts.map +1 -0
  13. package/dist/adapters/web-standard.js +252 -0
  14. package/dist/adapters/web-standard.js.map +1 -0
  15. package/dist/backends/alby.d.ts +25 -0
  16. package/dist/backends/alby.d.ts.map +1 -0
  17. package/dist/backends/alby.js +137 -0
  18. package/dist/backends/alby.js.map +1 -0
  19. package/dist/backends/cln.d.ts +22 -0
  20. package/dist/backends/cln.d.ts.map +1 -0
  21. package/dist/backends/cln.js +55 -0
  22. package/dist/backends/cln.js.map +1 -0
  23. package/dist/backends/lnbits.d.ts +23 -0
  24. package/dist/backends/lnbits.d.ts.map +1 -0
  25. package/dist/backends/lnbits.js +58 -0
  26. package/dist/backends/lnbits.js.map +1 -0
  27. package/dist/backends/lnd.d.ts +21 -0
  28. package/dist/backends/lnd.d.ts.map +1 -0
  29. package/dist/backends/lnd.js +59 -0
  30. package/dist/backends/lnd.js.map +1 -0
  31. package/dist/backends/phoenixd.d.ts +19 -0
  32. package/dist/backends/phoenixd.d.ts.map +1 -0
  33. package/dist/backends/phoenixd.js +59 -0
  34. package/dist/backends/phoenixd.js.map +1 -0
  35. package/dist/booth.d.ts +54 -0
  36. package/dist/booth.d.ts.map +1 -0
  37. package/dist/booth.js +200 -0
  38. package/dist/booth.js.map +1 -0
  39. package/dist/core/cashu-redeem.d.ts +9 -0
  40. package/dist/core/cashu-redeem.d.ts.map +1 -0
  41. package/dist/core/cashu-redeem.js +85 -0
  42. package/dist/core/cashu-redeem.js.map +1 -0
  43. package/dist/core/create-invoice.d.ts +19 -0
  44. package/dist/core/create-invoice.d.ts.map +1 -0
  45. package/dist/core/create-invoice.js +66 -0
  46. package/dist/core/create-invoice.js.map +1 -0
  47. package/dist/core/invoice-status.d.ts +24 -0
  48. package/dist/core/invoice-status.d.ts.map +1 -0
  49. package/dist/core/invoice-status.js +74 -0
  50. package/dist/core/invoice-status.js.map +1 -0
  51. package/dist/core/nwc-pay.d.ts +8 -0
  52. package/dist/core/nwc-pay.d.ts.map +1 -0
  53. package/dist/core/nwc-pay.js +23 -0
  54. package/dist/core/nwc-pay.js.map +1 -0
  55. package/dist/core/toll-booth.d.ts +9 -0
  56. package/dist/core/toll-booth.d.ts.map +1 -0
  57. package/dist/core/toll-booth.js +172 -0
  58. package/dist/core/toll-booth.js.map +1 -0
  59. package/dist/core/types.d.ts +101 -0
  60. package/dist/core/types.d.ts.map +1 -0
  61. package/dist/core/types.js +3 -0
  62. package/dist/core/types.js.map +1 -0
  63. package/dist/free-tier.d.ts +14 -0
  64. package/dist/free-tier.d.ts.map +1 -0
  65. package/dist/free-tier.js +41 -0
  66. package/dist/free-tier.js.map +1 -0
  67. package/dist/index.d.ts +38 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +27 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/macaroon.d.ts +39 -0
  72. package/dist/macaroon.d.ts.map +1 -0
  73. package/dist/macaroon.js +111 -0
  74. package/dist/macaroon.js.map +1 -0
  75. package/dist/payment-page.d.ts +18 -0
  76. package/dist/payment-page.d.ts.map +1 -0
  77. package/dist/payment-page.js +391 -0
  78. package/dist/payment-page.js.map +1 -0
  79. package/dist/stats.d.ts +63 -0
  80. package/dist/stats.d.ts.map +1 -0
  81. package/dist/stats.js +75 -0
  82. package/dist/stats.js.map +1 -0
  83. package/dist/storage/interface.d.ts +58 -0
  84. package/dist/storage/interface.d.ts.map +1 -0
  85. package/dist/storage/interface.js +3 -0
  86. package/dist/storage/interface.js.map +1 -0
  87. package/dist/storage/memory.d.ts +3 -0
  88. package/dist/storage/memory.d.ts.map +1 -0
  89. package/dist/storage/memory.js +139 -0
  90. package/dist/storage/memory.js.map +1 -0
  91. package/dist/storage/sqlite.d.ts +6 -0
  92. package/dist/storage/sqlite.d.ts.map +1 -0
  93. package/dist/storage/sqlite.js +264 -0
  94. package/dist/storage/sqlite.js.map +1 -0
  95. package/dist/types.d.ts +198 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +8 -0
  98. package/dist/types.js.map +1 -0
  99. package/llms.txt +91 -0
  100. package/package.json +100 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Lightning backend adapter for LNbits.
3
+ *
4
+ * Uses the LNbits Payments API with `X-Api-Key` header authentication.
5
+ * Works with any LNbits instance — self-hosted or hosted (legend.lnbits.com).
6
+ *
7
+ * LNbits abstracts over multiple funding sources (LND, CLN, Phoenixd,
8
+ * LndHub, etc.), so this backend supports any node type that LNbits
9
+ * connects to.
10
+ *
11
+ * @see https://lnbits.com/
12
+ */
13
+ export function lnbitsBackend(config) {
14
+ const baseUrl = config.url.replace(/\/$/, '');
15
+ const timeoutMs = config.timeout ?? 30_000;
16
+ const headers = {
17
+ 'X-Api-Key': config.apiKey,
18
+ 'Content-Type': 'application/json',
19
+ };
20
+ return {
21
+ async createInvoice(amountSats, memo) {
22
+ const res = await fetch(`${baseUrl}/api/v1/payments`, {
23
+ method: 'POST',
24
+ headers,
25
+ body: JSON.stringify({
26
+ out: false,
27
+ amount: amountSats,
28
+ memo: memo ?? 'toll-booth payment',
29
+ }),
30
+ signal: AbortSignal.timeout(timeoutMs),
31
+ });
32
+ if (!res.ok) {
33
+ const text = await res.text().catch(() => '');
34
+ throw new Error(`LNbits createInvoice failed (${res.status}): ${text}`);
35
+ }
36
+ const data = await res.json();
37
+ return { bolt11: data.payment_request, paymentHash: data.payment_hash };
38
+ },
39
+ async checkInvoice(paymentHash) {
40
+ const res = await fetch(`${baseUrl}/api/v1/payments/${paymentHash}`, {
41
+ headers: { 'X-Api-Key': config.apiKey },
42
+ signal: AbortSignal.timeout(timeoutMs),
43
+ });
44
+ if (res.status === 404)
45
+ return { paid: false };
46
+ if (!res.ok) {
47
+ const text = await res.text().catch(() => '');
48
+ throw new Error(`LNbits checkInvoice failed (${res.status}): ${text}`);
49
+ }
50
+ const data = await res.json();
51
+ return {
52
+ paid: data.paid,
53
+ preimage: data.paid ? data.preimage : undefined,
54
+ };
55
+ },
56
+ };
57
+ }
58
+ //# sourceMappingURL=lnbits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lnbits.js","sourceRoot":"","sources":["../../src/backends/lnbits.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,MAAoB;IAChD,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IAC7C,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAA;IAC1C,MAAM,OAAO,GAA2B;QACtC,WAAW,EAAE,MAAM,CAAC,MAAM;QAC1B,cAAc,EAAE,kBAAkB;KACnC,CAAA;IAED,OAAO;QACL,KAAK,CAAC,aAAa,CAAC,UAAkB,EAAE,IAAa;YACnD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,kBAAkB,EAAE;gBACpD,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,GAAG,EAAE,KAAK;oBACV,MAAM,EAAE,UAAU;oBAClB,IAAI,EAAE,IAAI,IAAI,oBAAoB;iBACnC,CAAC;gBACF,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;aACvC,CAAC,CAAA;YAEF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;gBAC7C,MAAM,IAAI,KAAK,CAAC,gCAAgC,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAA;YACzE,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAuD,CAAA;YAClF,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,EAAE,IAAI,CAAC,YAAY,EAAE,CAAA;QACzE,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,WAAmB;YACpC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,oBAAoB,WAAW,EAAE,EAAE;gBACnE,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;gBACvC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;aACvC,CAAC,CAAA;YAEF,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;YAE9C,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;gBAC7C,MAAM,IAAI,KAAK,CAAC,+BAA+B,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAA;YACxE,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA0C,CAAA;YACrE,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;aAChD,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { LightningBackend } from '../types.js';
2
+ export interface LndConfig {
3
+ /** LND REST API URL (e.g. https://localhost:8080) */
4
+ url: string;
5
+ /** Admin macaroon as hex string */
6
+ macaroon?: string;
7
+ /** Path to admin.macaroon file (alternative to hex) */
8
+ macaroonPath?: string;
9
+ /** Request timeout in ms (default: 30000) */
10
+ timeout?: number;
11
+ }
12
+ /**
13
+ * Lightning backend adapter for LND's REST API.
14
+ *
15
+ * Authenticates via the `Grpc-Metadata-macaroon` header using a hex-encoded
16
+ * admin macaroon. The macaroon can be provided directly or read from a file.
17
+ *
18
+ * @see https://lightning.engineering/api-docs/api/lnd/
19
+ */
20
+ export declare function lndBackend(config: LndConfig): LightningBackend;
21
+ //# sourceMappingURL=lnd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lnd.d.ts","sourceRoot":"","sources":["../../src/backends/lnd.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAA0B,MAAM,aAAa,CAAA;AAG3E,MAAM,WAAW,SAAS;IACxB,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAA;IACX,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,gBAAgB,CAsD9D"}
@@ -0,0 +1,59 @@
1
+ import { readFileSync } from 'node:fs';
2
+ /**
3
+ * Lightning backend adapter for LND's REST API.
4
+ *
5
+ * Authenticates via the `Grpc-Metadata-macaroon` header using a hex-encoded
6
+ * admin macaroon. The macaroon can be provided directly or read from a file.
7
+ *
8
+ * @see https://lightning.engineering/api-docs/api/lnd/
9
+ */
10
+ export function lndBackend(config) {
11
+ const baseUrl = config.url.replace(/\/$/, '');
12
+ const timeoutMs = config.timeout ?? 30_000;
13
+ let macaroonHex;
14
+ if (config.macaroon) {
15
+ macaroonHex = config.macaroon;
16
+ }
17
+ else if (config.macaroonPath) {
18
+ macaroonHex = readFileSync(config.macaroonPath).toString('hex');
19
+ }
20
+ else {
21
+ throw new Error('LND backend requires either macaroon (hex) or macaroonPath');
22
+ }
23
+ return {
24
+ async createInvoice(amountSats, memo) {
25
+ const res = await fetch(`${baseUrl}/v1/invoices`, {
26
+ method: 'POST',
27
+ headers: {
28
+ 'Grpc-Metadata-macaroon': macaroonHex,
29
+ 'Content-Type': 'application/json',
30
+ },
31
+ body: JSON.stringify({ value: String(amountSats), memo: memo ?? '' }),
32
+ signal: AbortSignal.timeout(timeoutMs),
33
+ });
34
+ if (!res.ok) {
35
+ const text = await res.text().catch(() => '');
36
+ throw new Error(`LND createinvoice failed (${res.status}): ${text}`);
37
+ }
38
+ const data = await res.json();
39
+ const paymentHash = Buffer.from(data.r_hash, 'base64').toString('hex');
40
+ return { bolt11: data.payment_request, paymentHash };
41
+ },
42
+ async checkInvoice(paymentHash) {
43
+ const res = await fetch(`${baseUrl}/v1/invoice/${paymentHash}`, {
44
+ headers: { 'Grpc-Metadata-macaroon': macaroonHex },
45
+ signal: AbortSignal.timeout(timeoutMs),
46
+ });
47
+ if (!res.ok)
48
+ return { paid: false };
49
+ const data = await res.json();
50
+ return {
51
+ paid: data.settled,
52
+ preimage: data.settled && data.r_preimage
53
+ ? Buffer.from(data.r_preimage, 'base64').toString('hex')
54
+ : undefined,
55
+ };
56
+ },
57
+ };
58
+ }
59
+ //# sourceMappingURL=lnd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lnd.js","sourceRoot":"","sources":["../../src/backends/lnd.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAatC;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,MAAiB;IAC1C,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IAC7C,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAA;IAE1C,IAAI,WAAmB,CAAA;IACvB,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAA;IAC/B,CAAC;SAAM,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QAC/B,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IACjE,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAA;IAC/E,CAAC;IAED,OAAO;QACL,KAAK,CAAC,aAAa,CAAC,UAAkB,EAAE,IAAa;YACnD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,cAAc,EAAE;gBAChD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,wBAAwB,EAAE,WAAW;oBACrC,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC;gBACrE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;aACvC,CAAC,CAAA;YAEF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;gBAC7C,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAA;YACtE,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAiD,CAAA;YAC5E,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YAEtE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,EAAE,CAAA;QACtD,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,WAAmB;YACpC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,eAAe,WAAW,EAAE,EAAE;gBAC9D,OAAO,EAAE,EAAE,wBAAwB,EAAE,WAAW,EAAE;gBAClD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;aACvC,CAAC,CAAA;YAEF,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;YAEnC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA+C,CAAA;YAE1E,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,OAAO;gBAClB,QAAQ,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,UAAU;oBACvC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;oBACxD,CAAC,CAAC,SAAS;aACd,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { LightningBackend } from '../types.js';
2
+ export interface PhoenixdConfig {
3
+ url: string;
4
+ password: string;
5
+ /** Request timeout in ms (default: 30000) */
6
+ timeout?: number;
7
+ }
8
+ /**
9
+ * Lightning backend adapter for Phoenixd's HTTP API.
10
+ *
11
+ * Phoenixd uses HTTP Basic auth with an empty username — the Authorization
12
+ * header encodes `:password` (note the leading colon) in base64.
13
+ * The createinvoice endpoint expects an `application/x-www-form-urlencoded`
14
+ * POST body, not JSON.
15
+ *
16
+ * @see https://phoenix.acinq.co/server/api
17
+ */
18
+ export declare function phoenixdBackend(config: PhoenixdConfig): LightningBackend;
19
+ //# sourceMappingURL=phoenixd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phoenixd.d.ts","sourceRoot":"","sources":["../../src/backends/phoenixd.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAA0B,MAAM,aAAa,CAAA;AAE3E,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,gBAAgB,CAqDxE"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Lightning backend adapter for Phoenixd's HTTP API.
3
+ *
4
+ * Phoenixd uses HTTP Basic auth with an empty username — the Authorization
5
+ * header encodes `:password` (note the leading colon) in base64.
6
+ * The createinvoice endpoint expects an `application/x-www-form-urlencoded`
7
+ * POST body, not JSON.
8
+ *
9
+ * @see https://phoenix.acinq.co/server/api
10
+ */
11
+ export function phoenixdBackend(config) {
12
+ const baseUrl = config.url.replace(/\/$/, '');
13
+ const authHeader = 'Basic ' + Buffer.from(`:${config.password}`).toString('base64');
14
+ const timeoutMs = config.timeout ?? 30_000;
15
+ return {
16
+ async createInvoice(amountSats, memo) {
17
+ const body = new URLSearchParams();
18
+ body.set('amountSat', String(amountSats));
19
+ if (memo)
20
+ body.set('description', memo);
21
+ body.set('externalId', '');
22
+ const res = await fetch(`${baseUrl}/createinvoice`, {
23
+ method: 'POST',
24
+ headers: {
25
+ 'Authorization': authHeader,
26
+ 'Content-Type': 'application/x-www-form-urlencoded',
27
+ },
28
+ body,
29
+ signal: AbortSignal.timeout(timeoutMs),
30
+ });
31
+ if (!res.ok) {
32
+ const text = await res.text().catch(() => '');
33
+ throw new Error(`Phoenixd createinvoice failed (${res.status}): ${text}`);
34
+ }
35
+ const data = await res.json();
36
+ return { bolt11: data.serialized, paymentHash: data.paymentHash };
37
+ },
38
+ async checkInvoice(paymentHash) {
39
+ const res = await fetch(`${baseUrl}/payments/incoming/${paymentHash}`, {
40
+ headers: { 'Authorization': authHeader },
41
+ signal: AbortSignal.timeout(timeoutMs),
42
+ });
43
+ // 404 = invoice not found (normal for unknown hashes)
44
+ if (res.status === 404)
45
+ return { paid: false };
46
+ // Auth failures and server errors must propagate so health checks detect them
47
+ if (!res.ok) {
48
+ const text = await res.text().catch(() => '');
49
+ throw new Error(`Phoenixd checkInvoice failed (${res.status}): ${text}`);
50
+ }
51
+ const data = await res.json();
52
+ return {
53
+ paid: data.isPaid,
54
+ preimage: data.isPaid ? data.preimage : undefined,
55
+ };
56
+ },
57
+ };
58
+ }
59
+ //# sourceMappingURL=phoenixd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phoenixd.js","sourceRoot":"","sources":["../../src/backends/phoenixd.ts"],"names":[],"mappings":"AASA;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,MAAsB;IACpD,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IAC7C,MAAM,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACnF,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAA;IAE1C,OAAO;QACL,KAAK,CAAC,aAAa,CAAC,UAAkB,EAAE,IAAa;YACnD,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAA;YAClC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAA;YACzC,IAAI,IAAI;gBAAE,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;YACvC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;YAE1B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,gBAAgB,EAAE;gBAClD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,eAAe,EAAE,UAAU;oBAC3B,cAAc,EAAE,mCAAmC;iBACpD;gBACD,IAAI;gBACJ,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;aACvC,CAAC,CAAA;YAEF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;gBAC7C,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAA;YAC3E,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAiD,CAAA;YAC5E,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAA;QACnE,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,WAAmB;YACpC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,sBAAsB,WAAW,EAAE,EAAE;gBACrE,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE;gBACxC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;aACvC,CAAC,CAAA;YAEF,sDAAsD;YACtD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;YAE9C,8EAA8E;YAC9E,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;gBAC7C,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAA;YAC1E,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA4C,CAAA;YACvE,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;aAClD,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { BoothConfig, EventHandler } from './types.js';
2
+ import type { StorageBackend } from './storage/interface.js';
3
+ import { StatsCollector } from './stats.js';
4
+ export type AdapterType = 'express' | 'web-standard';
5
+ export interface BoothOptions extends BoothConfig {
6
+ adapter: AdapterType;
7
+ storage?: StorageBackend;
8
+ }
9
+ /**
10
+ * Encapsulates the middleware, invoice-status handler, create-invoice handler,
11
+ * and wallet adapter endpoints with shared internal state.
12
+ *
13
+ * The `adapter` option selects the framework integration:
14
+ * - `'express'` — Express middleware and handlers
15
+ * - `'web-standard'` — Web Standards (Request/Response) handlers
16
+ *
17
+ * ```typescript
18
+ * const booth = new Booth({ adapter: 'express', ...config })
19
+ * app.get('/invoice-status/:paymentHash', booth.invoiceStatusHandler)
20
+ * app.post('/create-invoice', booth.createInvoiceHandler)
21
+ * app.use('/*', booth.middleware)
22
+ * ```
23
+ */
24
+ export declare class Booth {
25
+ readonly middleware: unknown;
26
+ readonly invoiceStatusHandler: unknown;
27
+ readonly createInvoiceHandler: unknown;
28
+ readonly nwcPayHandler?: unknown;
29
+ readonly cashuRedeemHandler?: unknown;
30
+ /** Aggregate usage statistics. Resets on restart. */
31
+ readonly stats: StatsCollector;
32
+ private readonly storage;
33
+ private readonly engine;
34
+ private readonly rootKey;
35
+ private readonly redeemCashu?;
36
+ private readonly pruneTimer?;
37
+ constructor(config: BoothOptions & EventHandler);
38
+ /** Reset free-tier counters for all IPs. */
39
+ resetFreeTier(): void;
40
+ /**
41
+ * Recover Cashu redemptions that were claimed but never settled (crash recovery).
42
+ * Automatically called on startup when Cashu is enabled. Can also be called
43
+ * manually. This requires an idempotent `redeemCashu` implementation keyed by
44
+ * `paymentHash`. For each pending claim, retries the redeem call:
45
+ * - If recovery lease is acquired: attempts redeem
46
+ * - On success: settles with the credited amount
47
+ * - On failure: leaves the claim pending for the next recovery attempt
48
+ *
49
+ * Returns the number of successfully recovered claims.
50
+ */
51
+ recoverPendingClaims(redeemFn: (token: string, paymentHash: string) => Promise<number>): Promise<number>;
52
+ close(): void;
53
+ }
54
+ //# sourceMappingURL=booth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"booth.d.ts","sourceRoot":"","sources":["../src/booth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAM5D,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAmB3C,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,cAAc,CAAA;AAEpD,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,OAAO,EAAE,WAAW,CAAA;IACpB,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,KAAK;IAChB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAA;IACtC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAA;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;IAChC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAErC,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAE9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAyD;IACtF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAgC;gBAEhD,MAAM,EAAE,YAAY,GAAG,YAAY;IAgI/C,4CAA4C;IAC5C,aAAa,IAAI,IAAI;IAIrB;;;;;;;;;;OAUG;IACG,oBAAoB,CACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAChE,OAAO,CAAC,MAAM,CAAC;IA6BlB,KAAK,IAAI,IAAI;CAId"}
package/dist/booth.js ADDED
@@ -0,0 +1,200 @@
1
+ import { createTollBooth } from './core/toll-booth.js';
2
+ import { sqliteStorage } from './storage/sqlite.js';
3
+ import { StatsCollector } from './stats.js';
4
+ import { randomBytes } from 'node:crypto';
5
+ import { REDEEM_LEASE_MS } from './core/cashu-redeem.js';
6
+ import { createExpressMiddleware, createExpressInvoiceStatusHandler, createExpressCreateInvoiceHandler, createExpressNwcHandler, createExpressCashuHandler, } from './adapters/express.js';
7
+ import { createWebStandardMiddleware, createWebStandardInvoiceStatusHandler, createWebStandardCreateInvoiceHandler, createWebStandardNwcHandler, createWebStandardCashuHandler, } from './adapters/web-standard.js';
8
+ /**
9
+ * Encapsulates the middleware, invoice-status handler, create-invoice handler,
10
+ * and wallet adapter endpoints with shared internal state.
11
+ *
12
+ * The `adapter` option selects the framework integration:
13
+ * - `'express'` — Express middleware and handlers
14
+ * - `'web-standard'` — Web Standards (Request/Response) handlers
15
+ *
16
+ * ```typescript
17
+ * const booth = new Booth({ adapter: 'express', ...config })
18
+ * app.get('/invoice-status/:paymentHash', booth.invoiceStatusHandler)
19
+ * app.post('/create-invoice', booth.createInvoiceHandler)
20
+ * app.use('/*', booth.middleware)
21
+ * ```
22
+ */
23
+ export class Booth {
24
+ middleware;
25
+ invoiceStatusHandler;
26
+ createInvoiceHandler;
27
+ nwcPayHandler;
28
+ cashuRedeemHandler;
29
+ /** Aggregate usage statistics. Resets on restart. */
30
+ stats;
31
+ storage;
32
+ engine;
33
+ rootKey;
34
+ redeemCashu;
35
+ pruneTimer;
36
+ constructor(config) {
37
+ if (!config.backend && !config.redeemCashu) {
38
+ throw new Error('At least one payment method required: provide a Lightning backend, redeemCashu callback, or both');
39
+ }
40
+ const rootKeyInput = config.rootKey ?? randomBytes(32).toString('hex');
41
+ if (!/^[0-9a-fA-F]{64}$/.test(rootKeyInput)) {
42
+ throw new Error('rootKey must be exactly 64 hex characters (32 bytes)');
43
+ }
44
+ this.rootKey = rootKeyInput;
45
+ if (config.storage && config.dbPath) {
46
+ throw new Error('Provide either storage or dbPath, not both');
47
+ }
48
+ this.storage = config.storage ?? sqliteStorage({ path: config.dbPath ?? './toll-booth.db' });
49
+ this.stats = new StatsCollector();
50
+ const defaultAmount = config.defaultInvoiceAmount ?? 1000;
51
+ // Wire stats collection while preserving user-provided callbacks
52
+ const userOnPayment = config.onPayment;
53
+ const userOnRequest = config.onRequest;
54
+ const userOnChallenge = config.onChallenge;
55
+ const stats = this.stats;
56
+ this.engine = createTollBooth({
57
+ backend: config.backend,
58
+ storage: this.storage,
59
+ pricing: config.pricing,
60
+ upstream: config.upstream,
61
+ defaultInvoiceAmount: defaultAmount,
62
+ rootKey: this.rootKey,
63
+ freeTier: config.freeTier,
64
+ strictPricing: config.strictPricing,
65
+ creditTiers: config.creditTiers,
66
+ onPayment: (event) => {
67
+ stats.recordPayment(event);
68
+ userOnPayment?.(event);
69
+ },
70
+ onRequest: (event) => {
71
+ stats.recordRequest(event);
72
+ userOnRequest?.(event);
73
+ },
74
+ onChallenge: (event) => {
75
+ stats.recordChallenge(event);
76
+ userOnChallenge?.(event);
77
+ },
78
+ });
79
+ const createInvoiceDeps = {
80
+ backend: config.backend,
81
+ storage: this.storage,
82
+ rootKey: this.rootKey,
83
+ tiers: config.creditTiers ?? [],
84
+ defaultAmount,
85
+ };
86
+ const invoiceStatusDeps = {
87
+ backend: config.backend,
88
+ storage: this.storage,
89
+ tiers: config.creditTiers,
90
+ nwcEnabled: !!config.nwcPayInvoice,
91
+ cashuEnabled: !!config.redeemCashu,
92
+ };
93
+ const upstream = config.upstream.replace(/\/$/, '');
94
+ const adapterConfig = {
95
+ engine: this.engine,
96
+ upstream,
97
+ trustProxy: config.trustProxy,
98
+ getClientIp: config.getClientIp,
99
+ responseHeaders: config.responseHeaders,
100
+ upstreamTimeout: config.upstreamTimeout,
101
+ };
102
+ const nwcPayDeps = config.nwcPayInvoice
103
+ ? { nwcPay: config.nwcPayInvoice, storage: this.storage }
104
+ : undefined;
105
+ const cashuRedeemDeps = config.redeemCashu
106
+ ? { redeem: config.redeemCashu, storage: this.storage }
107
+ : undefined;
108
+ switch (config.adapter) {
109
+ case 'express':
110
+ this.middleware = createExpressMiddleware(adapterConfig);
111
+ this.invoiceStatusHandler = createExpressInvoiceStatusHandler(invoiceStatusDeps);
112
+ this.createInvoiceHandler = createExpressCreateInvoiceHandler(createInvoiceDeps);
113
+ if (nwcPayDeps)
114
+ this.nwcPayHandler = createExpressNwcHandler(nwcPayDeps);
115
+ if (cashuRedeemDeps) {
116
+ this.redeemCashu = config.redeemCashu;
117
+ this.cashuRedeemHandler = createExpressCashuHandler(cashuRedeemDeps);
118
+ }
119
+ break;
120
+ case 'web-standard':
121
+ this.middleware = createWebStandardMiddleware(adapterConfig);
122
+ this.invoiceStatusHandler = createWebStandardInvoiceStatusHandler(invoiceStatusDeps);
123
+ this.createInvoiceHandler = createWebStandardCreateInvoiceHandler(createInvoiceDeps);
124
+ if (nwcPayDeps)
125
+ this.nwcPayHandler = createWebStandardNwcHandler(nwcPayDeps);
126
+ if (cashuRedeemDeps) {
127
+ this.redeemCashu = config.redeemCashu;
128
+ this.cashuRedeemHandler = createWebStandardCashuHandler(cashuRedeemDeps);
129
+ }
130
+ break;
131
+ }
132
+ // Invoice expiry pruning
133
+ const maxAge = config.invoiceMaxAgeMs ?? 86_400_000; // 24 hours
134
+ if (maxAge > 0) {
135
+ const timer = setInterval(() => {
136
+ this.storage.pruneExpiredInvoices(maxAge);
137
+ this.storage.pruneStaleRecords(maxAge);
138
+ }, 3_600_000); // every hour
139
+ timer.unref();
140
+ this.pruneTimer = timer;
141
+ }
142
+ // Auto-recover any pending Cashu claims from a previous crash
143
+ if (this.redeemCashu) {
144
+ const fn = this.redeemCashu;
145
+ this.recoverPendingClaims(fn).catch(() => {
146
+ // Recovery failures are non-fatal; claims stay pending for next restart
147
+ });
148
+ }
149
+ }
150
+ /** Reset free-tier counters for all IPs. */
151
+ resetFreeTier() {
152
+ this.engine.freeTier?.reset();
153
+ }
154
+ /**
155
+ * Recover Cashu redemptions that were claimed but never settled (crash recovery).
156
+ * Automatically called on startup when Cashu is enabled. Can also be called
157
+ * manually. This requires an idempotent `redeemCashu` implementation keyed by
158
+ * `paymentHash`. For each pending claim, retries the redeem call:
159
+ * - If recovery lease is acquired: attempts redeem
160
+ * - On success: settles with the credited amount
161
+ * - On failure: leaves the claim pending for the next recovery attempt
162
+ *
163
+ * Returns the number of successfully recovered claims.
164
+ */
165
+ async recoverPendingClaims(redeemFn) {
166
+ const claims = this.storage.pendingClaims();
167
+ let recovered = 0;
168
+ const renewIntervalMs = Math.max(1_000, Math.floor(REDEEM_LEASE_MS / 2));
169
+ for (const claim of claims) {
170
+ // Respect active leases so startup recovery does not race in-flight requests
171
+ // (or another process already handling this claim).
172
+ const leasedClaim = this.storage.tryAcquireRecoveryLease(claim.paymentHash, REDEEM_LEASE_MS);
173
+ if (!leasedClaim)
174
+ continue;
175
+ const timer = setInterval(() => {
176
+ this.storage.extendRecoveryLease(leasedClaim.paymentHash, REDEEM_LEASE_MS);
177
+ }, renewIntervalMs);
178
+ try {
179
+ const credited = await redeemFn(leasedClaim.token, leasedClaim.paymentHash);
180
+ if (this.storage.settleWithCredit(leasedClaim.paymentHash, credited)) {
181
+ recovered++;
182
+ }
183
+ }
184
+ catch {
185
+ // Transient failure (network, mint outage) — leave claim pending
186
+ // for the next recovery attempt. Do NOT settle with 0.
187
+ }
188
+ finally {
189
+ clearInterval(timer);
190
+ }
191
+ }
192
+ return recovered;
193
+ }
194
+ close() {
195
+ if (this.pruneTimer)
196
+ clearInterval(this.pruneTimer);
197
+ this.storage.close();
198
+ }
199
+ }
200
+ //# sourceMappingURL=booth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"booth.js","sourceRoot":"","sources":["../src/booth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { StorageBackend } from '../storage/interface.js';
2
+ import type { CashuRedeemRequest, CashuRedeemResult } from './types.js';
3
+ export declare const REDEEM_LEASE_MS = 30000;
4
+ export interface CashuRedeemDeps {
5
+ redeem: (token: string, paymentHash: string) => Promise<number>;
6
+ storage: StorageBackend;
7
+ }
8
+ export declare function handleCashuRedeem(deps: CashuRedeemDeps, request: CashuRedeemRequest): Promise<CashuRedeemResult>;
9
+ //# sourceMappingURL=cashu-redeem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cashu-redeem.d.ts","sourceRoot":"","sources":["../../src/core/cashu-redeem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAGvE,eAAO,MAAM,eAAe,QAAS,CAAA;AAGrC,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC/D,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC,CA8E5B"}
@@ -0,0 +1,85 @@
1
+ import { PAYMENT_HASH_RE } from './types.js';
2
+ export const REDEEM_LEASE_MS = 30_000;
3
+ const REDEEM_LEASE_RENEW_MS = Math.max(1_000, Math.floor(REDEEM_LEASE_MS / 2));
4
+ export async function handleCashuRedeem(deps, request) {
5
+ try {
6
+ const { token, paymentHash, statusToken } = request;
7
+ if (typeof token !== 'string' || !token ||
8
+ !PAYMENT_HASH_RE.test(paymentHash) ||
9
+ typeof statusToken !== 'string' || !statusToken) {
10
+ return { success: false, error: 'Invalid request: token, paymentHash, and statusToken required', status: 400 };
11
+ }
12
+ const invoice = deps.storage.getInvoiceForStatus(paymentHash, statusToken);
13
+ if (!invoice) {
14
+ return { success: false, error: 'Unknown payment hash or invalid status token', status: 400 };
15
+ }
16
+ // Fast path: already settled
17
+ if (deps.storage.isSettled(paymentHash)) {
18
+ return {
19
+ success: true,
20
+ credited: 0,
21
+ tokenSuffix: deps.storage.getSettlementSecret(paymentHash) ?? '',
22
+ };
23
+ }
24
+ // Try to claim exclusively before the irreversible external redeem call.
25
+ if (!deps.storage.claimForRedeem(paymentHash, token, REDEEM_LEASE_MS)) {
26
+ // Already settled — idempotent success
27
+ if (deps.storage.isSettled(paymentHash)) {
28
+ return {
29
+ success: true,
30
+ credited: 0,
31
+ tokenSuffix: deps.storage.getSettlementSecret(paymentHash) ?? '',
32
+ };
33
+ }
34
+ // Retry an expired lease. This is only correct when the redeem
35
+ // implementation is idempotent for the same paymentHash.
36
+ const pendingClaim = deps.storage.tryAcquireRecoveryLease(paymentHash, REDEEM_LEASE_MS);
37
+ if (pendingClaim) {
38
+ try {
39
+ const credited = await withLeaseKeepAlive(deps.storage, paymentHash, () => deps.redeem(pendingClaim.token, paymentHash));
40
+ const settlementSecret = globalThis.crypto.randomUUID();
41
+ const newlySettled = deps.storage.settleWithCredit(paymentHash, credited, settlementSecret);
42
+ return {
43
+ success: true,
44
+ credited: newlySettled ? credited : 0,
45
+ tokenSuffix: newlySettled ? settlementSecret : deps.storage.getSettlementSecret(paymentHash) ?? '',
46
+ };
47
+ }
48
+ catch {
49
+ return { success: false, state: 'pending', retryAfterMs: 2000 };
50
+ }
51
+ }
52
+ return { success: false, state: 'pending', retryAfterMs: 2000 };
53
+ }
54
+ // We hold the exclusive claim — call the external mint
55
+ try {
56
+ const credited = await withLeaseKeepAlive(deps.storage, paymentHash, () => deps.redeem(token, paymentHash));
57
+ const settlementSecret = globalThis.crypto.randomUUID();
58
+ const newlySettled = deps.storage.settleWithCredit(paymentHash, credited, settlementSecret);
59
+ return {
60
+ success: true,
61
+ credited: newlySettled ? credited : 0,
62
+ tokenSuffix: newlySettled ? settlementSecret : deps.storage.getSettlementSecret(paymentHash) ?? '',
63
+ };
64
+ }
65
+ catch {
66
+ return { success: false, state: 'pending', retryAfterMs: 2000 };
67
+ }
68
+ }
69
+ catch (err) {
70
+ console.error('[toll-booth] Cashu redeem error:', err instanceof Error ? err.message : err);
71
+ return { success: false, error: 'Cashu redemption failed', status: 500 };
72
+ }
73
+ }
74
+ async function withLeaseKeepAlive(storage, paymentHash, fn) {
75
+ const timer = setInterval(() => {
76
+ storage.extendRecoveryLease(paymentHash, REDEEM_LEASE_MS);
77
+ }, REDEEM_LEASE_RENEW_MS);
78
+ try {
79
+ return await fn();
80
+ }
81
+ finally {
82
+ clearInterval(timer);
83
+ }
84
+ }
85
+ //# sourceMappingURL=cashu-redeem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cashu-redeem.js","sourceRoot":"","sources":["../../src/core/cashu-redeem.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { LightningBackend, CreditTier } from '../types.js';
2
+ import type { StorageBackend } from '../storage/interface.js';
3
+ import type { CreateInvoiceRequest, CreateInvoiceResult } from './types.js';
4
+ export interface CreateInvoiceDeps {
5
+ backend?: LightningBackend;
6
+ storage: StorageBackend;
7
+ rootKey: string;
8
+ tiers: CreditTier[];
9
+ defaultAmount: number;
10
+ }
11
+ /**
12
+ * Framework-agnostic invoice creation handler.
13
+ *
14
+ * Validates the requested amount against configured tiers (if any),
15
+ * creates a new Lightning invoice, mints a macaroon, stores everything,
16
+ * and returns structured result data.
17
+ */
18
+ export declare function handleCreateInvoice(deps: CreateInvoiceDeps, request: CreateInvoiceRequest): Promise<CreateInvoiceResult>;
19
+ //# sourceMappingURL=create-invoice.d.ts.map