@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 @@
1
+ {"version":3,"file":"create-invoice.d.ts","sourceRoot":"","sources":["../../src/core/create-invoice.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAE7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAE3E,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAiE9B"}
@@ -0,0 +1,66 @@
1
+ // src/core/create-invoice.ts
2
+ import { randomBytes } from 'node:crypto';
3
+ import QRCode from 'qrcode';
4
+ import { mintMacaroon } from '../macaroon.js';
5
+ /**
6
+ * Framework-agnostic invoice creation handler.
7
+ *
8
+ * Validates the requested amount against configured tiers (if any),
9
+ * creates a new Lightning invoice, mints a macaroon, stores everything,
10
+ * and returns structured result data.
11
+ */
12
+ export async function handleCreateInvoice(deps, request) {
13
+ try {
14
+ const requestedAmount = request.amountSats ?? deps.defaultAmount;
15
+ if (!Number.isInteger(requestedAmount) || requestedAmount < 1) {
16
+ return { success: false, error: 'amountSats must be a positive integer' };
17
+ }
18
+ // Find matching tier or validate amount
19
+ let creditSats = requestedAmount;
20
+ if (deps.tiers.length > 0) {
21
+ const tier = deps.tiers.find(t => t.amountSats === requestedAmount);
22
+ if (!tier) {
23
+ return {
24
+ success: false,
25
+ error: 'Invalid amount. Choose from available tiers.',
26
+ tiers: deps.tiers,
27
+ };
28
+ }
29
+ creditSats = tier.creditSats;
30
+ }
31
+ let paymentHash;
32
+ let bolt11;
33
+ if (deps.backend) {
34
+ const invoice = await deps.backend.createInvoice(requestedAmount, `toll-booth: ${creditSats} sats credit`);
35
+ paymentHash = invoice.paymentHash;
36
+ bolt11 = invoice.bolt11;
37
+ }
38
+ else {
39
+ // Cashu-only mode: synthetic payment hash
40
+ paymentHash = randomBytes(32).toString('hex');
41
+ }
42
+ const macaroon = mintMacaroon(deps.rootKey, paymentHash, creditSats);
43
+ const statusToken = randomBytes(32).toString('hex');
44
+ deps.storage.storeInvoice(paymentHash, bolt11 ?? '', creditSats, macaroon, statusToken);
45
+ const qrSvg = bolt11
46
+ ? await QRCode.toString(`lightning:${bolt11}`.toUpperCase(), { type: 'svg', margin: 2 })
47
+ : undefined;
48
+ return {
49
+ success: true,
50
+ data: {
51
+ bolt11: bolt11 ?? '',
52
+ paymentHash,
53
+ paymentUrl: `/invoice-status/${paymentHash}?token=${statusToken}`,
54
+ amountSats: requestedAmount,
55
+ creditSats,
56
+ macaroon,
57
+ qrSvg: qrSvg ?? '',
58
+ },
59
+ };
60
+ }
61
+ catch (err) {
62
+ console.error('[toll-booth] create invoice error:', err instanceof Error ? err.message : err);
63
+ return { success: false, error: 'Failed to create invoice' };
64
+ }
65
+ }
66
+ //# sourceMappingURL=create-invoice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-invoice.js","sourceRoot":"","sources":["../../src/core/create-invoice.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,MAAM,MAAM,QAAQ,CAAA;AAG3B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAW7C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAuB,EACvB,OAA6B;IAE7B,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,aAAa,CAAA;QAEhE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;YAC9D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,uCAAuC,EAAE,CAAA;QAC3E,CAAC;QAED,wCAAwC;QACxC,IAAI,UAAU,GAAG,eAAe,CAAA;QAChC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,eAAe,CAAC,CAAA;YACnE,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,8CAA8C;oBACrD,KAAK,EAAE,IAAI,CAAC,KAAK;iBAClB,CAAA;YACH,CAAC;YACD,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;QAC9B,CAAC;QAED,IAAI,WAAmB,CAAA;QACvB,IAAI,MAA0B,CAAA;QAE9B,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAC9C,eAAe,EACf,eAAe,UAAU,cAAc,CACxC,CAAA;YACD,WAAW,GAAG,OAAO,CAAC,WAAW,CAAA;YACjC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QACzB,CAAC;aAAM,CAAC;YACN,0CAA0C;YAC1C,WAAW,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC/C,CAAC;QAED,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,CAAC,CAAA;QACpE,MAAM,WAAW,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAEnD,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,IAAI,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAA;QAEvF,MAAM,KAAK,GAAG,MAAM;YAClB,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CACnB,aAAa,MAAM,EAAE,CAAC,WAAW,EAAE,EACnC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAC3B;YACH,CAAC,CAAC,SAAS,CAAA;QAEb,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,MAAM,EAAE,MAAM,IAAI,EAAE;gBACpB,WAAW;gBACX,UAAU,EAAE,mBAAmB,WAAW,UAAU,WAAW,EAAE;gBACjE,UAAU,EAAE,eAAe;gBAC3B,UAAU;gBACV,QAAQ;gBACR,KAAK,EAAE,KAAK,IAAI,EAAE;aACnB;SACF,CAAA;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QAC7F,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAA;IAC9D,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { LightningBackend, CreditTier } from '../types.js';
2
+ import type { StorageBackend } from '../storage/interface.js';
3
+ import type { InvoiceStatusResult } from './types.js';
4
+ export interface InvoiceStatusDeps {
5
+ backend?: LightningBackend;
6
+ storage: StorageBackend;
7
+ tiers?: CreditTier[];
8
+ nwcEnabled?: boolean;
9
+ cashuEnabled?: boolean;
10
+ }
11
+ /**
12
+ * Framework-agnostic invoice status check.
13
+ * Returns structured data suitable for JSON responses.
14
+ */
15
+ export declare function handleInvoiceStatus(deps: InvoiceStatusDeps, paymentHash: string, statusToken?: string): Promise<InvoiceStatusResult>;
16
+ /**
17
+ * Framework-agnostic invoice status rendered as HTML.
18
+ * Returns the HTML string and appropriate HTTP status code.
19
+ */
20
+ export declare function renderInvoiceStatusHtml(deps: InvoiceStatusDeps, paymentHash: string, statusToken?: string): Promise<{
21
+ html: string;
22
+ status: number;
23
+ }>;
24
+ //# sourceMappingURL=invoice-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-status.d.ts","sourceRoot":"","sources":["../../src/core/invoice-status.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAE7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAErD,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,iBAAiB,EACvB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,mBAAmB,CAAC,CA2B9B;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,iBAAiB,EACvB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAqC3C"}
@@ -0,0 +1,74 @@
1
+ import { renderPaymentPage, renderErrorPage } from '../payment-page.js';
2
+ /**
3
+ * Framework-agnostic invoice status check.
4
+ * Returns structured data suitable for JSON responses.
5
+ */
6
+ export async function handleInvoiceStatus(deps, paymentHash, statusToken) {
7
+ const invoice = statusToken
8
+ ? deps.storage.getInvoiceForStatus(paymentHash, statusToken)
9
+ : undefined;
10
+ if (!invoice) {
11
+ return { found: false, paid: false };
12
+ }
13
+ // In Cashu-only mode (no backend), check settlement status from storage
14
+ if (!deps.backend) {
15
+ const settled = deps.storage.isSettled(paymentHash);
16
+ return {
17
+ found: true,
18
+ paid: settled,
19
+ tokenSuffix: settled ? deps.storage.getSettlementSecret(paymentHash) : undefined,
20
+ invoice,
21
+ };
22
+ }
23
+ const status = await deps.backend.checkInvoice(paymentHash);
24
+ return {
25
+ found: true,
26
+ paid: status.paid,
27
+ preimage: status.preimage,
28
+ tokenSuffix: status.preimage ?? (status.paid ? deps.storage.getSettlementSecret(paymentHash) : undefined),
29
+ invoice,
30
+ };
31
+ }
32
+ /**
33
+ * Framework-agnostic invoice status rendered as HTML.
34
+ * Returns the HTML string and appropriate HTTP status code.
35
+ */
36
+ export async function renderInvoiceStatusHtml(deps, paymentHash, statusToken) {
37
+ try {
38
+ const invoice = statusToken
39
+ ? deps.storage.getInvoiceForStatus(paymentHash, statusToken)
40
+ : undefined;
41
+ if (!invoice) {
42
+ return {
43
+ html: renderErrorPage({
44
+ paymentHash,
45
+ message: 'This invoice was not found. It may have expired or the payment hash is incorrect.',
46
+ }),
47
+ status: 404,
48
+ };
49
+ }
50
+ const status = deps.backend
51
+ ? await deps.backend.checkInvoice(paymentHash)
52
+ : { paid: deps.storage.isSettled(paymentHash), preimage: undefined };
53
+ const html = await renderPaymentPage({
54
+ invoice,
55
+ paid: status.paid,
56
+ preimage: status.preimage,
57
+ tokenSuffix: status.preimage ?? (status.paid ? deps.storage.getSettlementSecret(paymentHash) : undefined),
58
+ tiers: deps.tiers ?? [],
59
+ nwcEnabled: deps.nwcEnabled ?? false,
60
+ cashuEnabled: deps.cashuEnabled ?? false,
61
+ });
62
+ return { html, status: 200 };
63
+ }
64
+ catch {
65
+ return {
66
+ html: renderErrorPage({
67
+ paymentHash,
68
+ message: 'Failed to check invoice status. Please try again.',
69
+ }),
70
+ status: 502,
71
+ };
72
+ }
73
+ }
74
+ //# sourceMappingURL=invoice-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-status.js","sourceRoot":"","sources":["../../src/core/invoice-status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { StorageBackend } from '../storage/interface.js';
2
+ import type { NwcPayRequest, NwcPayResult } from './types.js';
3
+ export interface NwcPayDeps {
4
+ nwcPay: (nwcUri: string, bolt11: string) => Promise<string>;
5
+ storage: StorageBackend;
6
+ }
7
+ export declare function handleNwcPay(deps: NwcPayDeps, request: NwcPayRequest): Promise<NwcPayResult>;
8
+ //# sourceMappingURL=nwc-pay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nwc-pay.d.ts","sourceRoot":"","sources":["../../src/core/nwc-pay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAG7D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3D,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAuBvB"}
@@ -0,0 +1,23 @@
1
+ import { PAYMENT_HASH_RE } from './types.js';
2
+ export async function handleNwcPay(deps, request) {
3
+ try {
4
+ const { nwcUri, bolt11, paymentHash, statusToken } = request;
5
+ if (typeof nwcUri !== 'string' || !nwcUri ||
6
+ typeof bolt11 !== 'string' || !bolt11 ||
7
+ !PAYMENT_HASH_RE.test(paymentHash) ||
8
+ typeof statusToken !== 'string' || !statusToken) {
9
+ return { success: false, error: 'Invalid request: nwcUri, bolt11, paymentHash and statusToken required', status: 400 };
10
+ }
11
+ const invoice = deps.storage.getInvoiceForStatus(paymentHash, statusToken);
12
+ if (!invoice || invoice.bolt11 !== bolt11) {
13
+ return { success: false, error: 'Unknown invoice or invoice mismatch', status: 400 };
14
+ }
15
+ const preimage = await deps.nwcPay(nwcUri, invoice.bolt11);
16
+ return { success: true, preimage };
17
+ }
18
+ catch (err) {
19
+ console.error('[toll-booth] NWC payment error:', err instanceof Error ? err.message : err);
20
+ return { success: false, error: 'NWC payment failed', status: 500 };
21
+ }
22
+ }
23
+ //# sourceMappingURL=nwc-pay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nwc-pay.js","sourceRoot":"","sources":["../../src/core/nwc-pay.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAO5C,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAgB,EAChB,OAAsB;IAEtB,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,OAAO,CAAA;QAC5D,IACE,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM;YACrC,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM;YACrC,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;YAClC,OAAO,WAAW,KAAK,QAAQ,IAAI,CAAC,WAAW,EAC/C,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,uEAAuE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAA;QACxH,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;QAC1E,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,qCAAqC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAA;QACtF,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QAC1D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QAC1F,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,GAAG,EAAE,CAAA;IACrE,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { FreeTier } from '../free-tier.js';
2
+ import type { TollBoothRequest, TollBoothResult, TollBoothCoreConfig } from './types.js';
3
+ export interface TollBoothEngine {
4
+ handle(req: TollBoothRequest): Promise<TollBoothResult>;
5
+ freeTier: FreeTier | null;
6
+ upstream: string;
7
+ }
8
+ export declare function createTollBooth(config: TollBoothCoreConfig): TollBoothEngine;
9
+ //# sourceMappingURL=toll-booth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toll-booth.d.ts","sourceRoot":"","sources":["../../src/core/toll-booth.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAExF,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IACvD,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CA6H5E"}
@@ -0,0 +1,172 @@
1
+ // src/core/toll-booth.ts
2
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
3
+ import { mintMacaroon, verifyMacaroon } from '../macaroon.js';
4
+ import { FreeTier } from '../free-tier.js';
5
+ export function createTollBooth(config) {
6
+ const defaultAmount = config.defaultInvoiceAmount ?? 1000;
7
+ const upstream = config.upstream.replace(/\/$/, '');
8
+ const freeTier = config.freeTier ? new FreeTier(config.freeTier.requestsPerDay) : null;
9
+ return {
10
+ freeTier,
11
+ upstream,
12
+ async handle(req) {
13
+ const start = Date.now();
14
+ const path = req.path;
15
+ const pricedCost = config.pricing[path];
16
+ // Unpriced routes: pass through unless strictPricing is enabled
17
+ if (pricedCost === undefined && !config.strictPricing) {
18
+ return { action: 'pass', upstream, headers: {} };
19
+ }
20
+ // Effective cost: explicit pricing, or defaultInvoiceAmount when strictPricing
21
+ const cost = pricedCost ?? defaultAmount;
22
+ // Check for L402 Authorisation header
23
+ const authHeader = req.headers['authorization'] ?? req.headers['Authorization'];
24
+ if (authHeader?.startsWith('L402 ')) {
25
+ const result = handleL402Auth(authHeader, config.rootKey, config.storage, cost, defaultAmount);
26
+ if (result.authorised) {
27
+ if (result.creditedAmount) {
28
+ config.onPayment?.({
29
+ timestamp: new Date().toISOString(),
30
+ paymentHash: result.paymentHash,
31
+ amountSats: result.creditedAmount,
32
+ });
33
+ }
34
+ config.onRequest?.({
35
+ timestamp: new Date().toISOString(),
36
+ endpoint: path,
37
+ satsDeducted: cost,
38
+ remainingBalance: result.remaining,
39
+ latencyMs: Date.now() - start,
40
+ authenticated: true,
41
+ clientIp: req.ip,
42
+ });
43
+ return {
44
+ action: 'proxy',
45
+ upstream,
46
+ headers: { 'X-Credit-Balance': String(result.remaining) },
47
+ creditBalance: result.remaining,
48
+ };
49
+ }
50
+ // Fall through to issue a new challenge if authorisation failed
51
+ }
52
+ // Check free tier
53
+ if (freeTier) {
54
+ const check = freeTier.check(req.ip);
55
+ if (check.allowed) {
56
+ config.onRequest?.({
57
+ timestamp: new Date().toISOString(),
58
+ endpoint: path,
59
+ satsDeducted: 0,
60
+ remainingBalance: 0,
61
+ latencyMs: Date.now() - start,
62
+ authenticated: false,
63
+ clientIp: req.ip,
64
+ });
65
+ return {
66
+ action: 'proxy',
67
+ upstream,
68
+ headers: { 'X-Free-Remaining': String(check.remaining) },
69
+ freeRemaining: check.remaining,
70
+ };
71
+ }
72
+ }
73
+ // Issue L402 challenge
74
+ let paymentHash;
75
+ let bolt11;
76
+ if (config.backend) {
77
+ const invoice = await config.backend.createInvoice(defaultAmount, `toll-booth: ${defaultAmount} sats credit`);
78
+ paymentHash = invoice.paymentHash;
79
+ bolt11 = invoice.bolt11;
80
+ }
81
+ else {
82
+ // Cashu-only mode: synthetic payment hash (no Lightning invoice)
83
+ paymentHash = randomBytes(32).toString('hex');
84
+ }
85
+ const macaroon = mintMacaroon(config.rootKey, paymentHash, defaultAmount);
86
+ const statusToken = randomBytes(32).toString('hex');
87
+ // Store invoice for payment page (bolt11 is empty in Cashu-only mode)
88
+ config.storage.storeInvoice(paymentHash, bolt11 ?? '', defaultAmount, macaroon, statusToken);
89
+ config.onChallenge?.({
90
+ timestamp: new Date().toISOString(),
91
+ endpoint: path,
92
+ amountSats: defaultAmount,
93
+ clientIp: req.ip,
94
+ });
95
+ const headers = bolt11
96
+ ? { 'WWW-Authenticate': `L402 macaroon="${macaroon}", invoice="${bolt11}"` }
97
+ : { 'WWW-Authenticate': `L402 macaroon="${macaroon}"` };
98
+ const body = {
99
+ error: 'Payment required',
100
+ macaroon,
101
+ payment_hash: paymentHash,
102
+ payment_url: `/invoice-status/${paymentHash}?token=${statusToken}`,
103
+ amount_sats: defaultAmount,
104
+ };
105
+ if (bolt11)
106
+ body.invoice = bolt11;
107
+ return {
108
+ action: 'challenge',
109
+ status: 402,
110
+ headers,
111
+ body,
112
+ };
113
+ },
114
+ };
115
+ }
116
+ function handleL402Auth(authHeader, rootKey, storage, cost, defaultAmount) {
117
+ try {
118
+ const token = authHeader.slice(5); // Remove "L402 "
119
+ const colonIdx = token.lastIndexOf(':');
120
+ if (colonIdx === -1)
121
+ return { authorised: false, remaining: 0 };
122
+ const macaroonBase64 = token.slice(0, colonIdx);
123
+ const preimage = token.slice(colonIdx + 1);
124
+ const result = verifyMacaroon(rootKey, macaroonBase64);
125
+ if (!result.valid || !result.paymentHash)
126
+ return { authorised: false, remaining: 0 };
127
+ // Verify suffix proof:
128
+ // - Lightning path: suffix is the real preimage (sha256(preimage) == payment hash)
129
+ // - Cashu path: suffix matches the settlement secret stored at redemption time
130
+ const settlementSecret = storage.getSettlementSecret(result.paymentHash);
131
+ const hasValidLightningPreimage = isValidLightningPreimage(preimage, result.paymentHash);
132
+ const hasValidSettlementSecret = settlementSecret !== undefined
133
+ && preimage.length === settlementSecret.length
134
+ && timingSafeEqual(Buffer.from(preimage), Buffer.from(settlementSecret));
135
+ if (!hasValidLightningPreimage && !hasValidSettlementSecret) {
136
+ return { authorised: false, remaining: 0 };
137
+ }
138
+ // Check if this payment hash has already been settled (Lightning or Cashu)
139
+ const alreadySettled = storage.isSettled(result.paymentHash);
140
+ let creditedAmount;
141
+ if (!alreadySettled) {
142
+ // First-time settlement must be proven with a real preimage hash match.
143
+ if (!hasValidLightningPreimage)
144
+ return { authorised: false, remaining: 0 };
145
+ // Atomically settle and credit (handles concurrent requests, crash-safe).
146
+ // Store the preimage as settlement secret so subsequent requests can verify
147
+ // via either sha256(preimage)==hash or direct secret comparison.
148
+ const amount = result.creditBalance ?? defaultAmount;
149
+ if (storage.settleWithCredit(result.paymentHash, amount, preimage)) {
150
+ creditedAmount = amount;
151
+ }
152
+ }
153
+ // Debit credit for this request
154
+ const debit = storage.debit(result.paymentHash, cost);
155
+ if (!debit.success)
156
+ return { authorised: false, remaining: debit.remaining };
157
+ return { authorised: true, remaining: debit.remaining, paymentHash: result.paymentHash, creditedAmount };
158
+ }
159
+ catch (err) {
160
+ console.error('[toll-booth] L402 auth error:', err instanceof Error ? err.message : err);
161
+ return { authorised: false, remaining: 0 };
162
+ }
163
+ }
164
+ function isValidLightningPreimage(preimage, paymentHash) {
165
+ if (!/^[0-9a-f]{64}$/.test(preimage))
166
+ return false;
167
+ const computedHash = createHash('sha256')
168
+ .update(Buffer.from(preimage, 'hex'))
169
+ .digest();
170
+ return timingSafeEqual(computedHash, Buffer.from(paymentHash, 'hex'));
171
+ }
172
+ //# sourceMappingURL=toll-booth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toll-booth.js","sourceRoot":"","sources":["../../src/core/toll-booth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ import type { LightningBackend, CreditTier, PaymentEvent, RequestEvent, ChallengeEvent } from '../types.js';
2
+ import type { StorageBackend, StoredInvoice } from '../storage/interface.js';
3
+ /** Matches a valid 64-char lowercase hex payment hash. */
4
+ export declare const PAYMENT_HASH_RE: RegExp;
5
+ export interface TollBoothRequest {
6
+ method: string;
7
+ path: string;
8
+ headers: Record<string, string | undefined>;
9
+ ip: string;
10
+ body?: ReadableStream | null;
11
+ }
12
+ export type TollBoothResult = {
13
+ action: 'proxy';
14
+ upstream: string;
15
+ headers: Record<string, string>;
16
+ creditBalance?: number;
17
+ freeRemaining?: number;
18
+ } | {
19
+ action: 'challenge';
20
+ status: 402;
21
+ headers: Record<string, string>;
22
+ body: Record<string, unknown>;
23
+ } | {
24
+ action: 'pass';
25
+ upstream: string;
26
+ headers: Record<string, string>;
27
+ };
28
+ export interface TollBoothCoreConfig {
29
+ /** Lightning backend. Optional when Cashu-only mode is used. */
30
+ backend?: LightningBackend;
31
+ storage: StorageBackend;
32
+ pricing: Record<string, number>;
33
+ upstream: string;
34
+ defaultInvoiceAmount?: number;
35
+ strictPricing?: boolean;
36
+ rootKey: string;
37
+ freeTier?: {
38
+ requestsPerDay: number;
39
+ };
40
+ creditTiers?: CreditTier[];
41
+ onPayment?: (event: PaymentEvent) => void;
42
+ onRequest?: (event: RequestEvent) => void;
43
+ onChallenge?: (event: ChallengeEvent) => void;
44
+ }
45
+ export interface CreateInvoiceRequest {
46
+ amountSats?: number;
47
+ }
48
+ export interface CreateInvoiceResult {
49
+ success: boolean;
50
+ error?: string;
51
+ tiers?: CreditTier[];
52
+ data?: {
53
+ bolt11: string;
54
+ paymentHash: string;
55
+ paymentUrl: string;
56
+ amountSats: number;
57
+ creditSats: number;
58
+ macaroon: string;
59
+ qrSvg: string;
60
+ };
61
+ }
62
+ export interface InvoiceStatusResult {
63
+ found: boolean;
64
+ paid: boolean;
65
+ preimage?: string;
66
+ tokenSuffix?: string;
67
+ invoice?: StoredInvoice;
68
+ }
69
+ export interface NwcPayRequest {
70
+ nwcUri: string;
71
+ bolt11: string;
72
+ paymentHash: string;
73
+ statusToken: string;
74
+ }
75
+ export type NwcPayResult = {
76
+ success: true;
77
+ preimage: string;
78
+ } | {
79
+ success: false;
80
+ error: string;
81
+ status: 400 | 500;
82
+ };
83
+ export interface CashuRedeemRequest {
84
+ token: string;
85
+ paymentHash: string;
86
+ statusToken: string;
87
+ }
88
+ export type CashuRedeemResult = {
89
+ success: true;
90
+ credited: number;
91
+ tokenSuffix: string;
92
+ } | {
93
+ success: false;
94
+ state: 'pending';
95
+ retryAfterMs: number;
96
+ } | {
97
+ success: false;
98
+ error: string;
99
+ status: 400 | 500;
100
+ };
101
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Matches a valid 64-char lowercase hex payment hash. */
2
+ export const PAYMENT_HASH_RE = /^[0-9a-f]{64}$/;
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAIA,0DAA0D;AAC1D,MAAM,CAAC,MAAM,eAAe,GAAG,gBAAgB,CAAA"}
@@ -0,0 +1,14 @@
1
+ export interface FreeTierResult {
2
+ allowed: boolean;
3
+ remaining: number;
4
+ }
5
+ export declare class FreeTier {
6
+ private readonly requestsPerDay;
7
+ private counters;
8
+ private currentDate;
9
+ constructor(requestsPerDay: number);
10
+ /** Reset all counters (e.g. via admin endpoint). */
11
+ reset(): void;
12
+ check(ip: string): FreeTierResult;
13
+ }
14
+ //# sourceMappingURL=free-tier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"free-tier.d.ts","sourceRoot":"","sources":["../src/free-tier.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB;AAKD,qBAAa,QAAQ;IAIP,OAAO,CAAC,QAAQ,CAAC,cAAc;IAH3C,OAAO,CAAC,QAAQ,CAAqD;IACrE,OAAO,CAAC,WAAW,CAAwC;gBAE9B,cAAc,EAAE,MAAM;IAMnD,oDAAoD;IACpD,KAAK,IAAI,IAAI;IAIb,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc;CA2BlC"}
@@ -0,0 +1,41 @@
1
+ // src/free-tier.ts
2
+ /** Maximum number of distinct IPs tracked before new IPs are denied. */
3
+ const MAX_TRACKED_IPS = 100_000;
4
+ export class FreeTier {
5
+ requestsPerDay;
6
+ counters = new Map();
7
+ currentDate = new Date().toISOString().slice(0, 10);
8
+ constructor(requestsPerDay) {
9
+ this.requestsPerDay = requestsPerDay;
10
+ if (!Number.isInteger(requestsPerDay) || requestsPerDay < 1) {
11
+ throw new RangeError(`requestsPerDay must be a positive integer, got ${requestsPerDay}`);
12
+ }
13
+ }
14
+ /** Reset all counters (e.g. via admin endpoint). */
15
+ reset() {
16
+ this.counters.clear();
17
+ }
18
+ check(ip) {
19
+ const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
20
+ // Evict all stale entries when the date rolls over
21
+ if (this.currentDate !== today) {
22
+ this.counters.clear();
23
+ this.currentDate = today;
24
+ }
25
+ const entry = this.counters.get(ip);
26
+ if (!entry || entry.date !== today) {
27
+ // Prevent unbounded memory growth from IP-spoofed requests
28
+ if (!entry && this.counters.size >= MAX_TRACKED_IPS) {
29
+ return { allowed: false, remaining: 0 };
30
+ }
31
+ this.counters.set(ip, { count: 1, date: today });
32
+ return { allowed: true, remaining: this.requestsPerDay - 1 };
33
+ }
34
+ if (entry.count >= this.requestsPerDay) {
35
+ return { allowed: false, remaining: 0 };
36
+ }
37
+ entry.count++;
38
+ return { allowed: true, remaining: this.requestsPerDay - entry.count };
39
+ }
40
+ }
41
+ //# sourceMappingURL=free-tier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"free-tier.js","sourceRoot":"","sources":["../src/free-tier.ts"],"names":[],"mappings":"AAAA,mBAAmB;AAOnB,wEAAwE;AACxE,MAAM,eAAe,GAAG,OAAO,CAAA;AAE/B,MAAM,OAAO,QAAQ;IAIU;IAHrB,QAAQ,GAAG,IAAI,GAAG,EAA2C,CAAA;IAC7D,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAE3D,YAA6B,cAAsB;QAAtB,mBAAc,GAAd,cAAc,CAAQ;QACjD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,UAAU,CAAC,kDAAkD,cAAc,EAAE,CAAC,CAAA;QAC1F,CAAC;IACH,CAAC;IAED,oDAAoD;IACpD,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;IACvB,CAAC;IAED,KAAK,CAAC,EAAU;QACd,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA,CAAC,aAAa;QAEjE,mDAAmD;QACnD,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;YAC/B,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;YACrB,IAAI,CAAC,WAAW,GAAG,KAAK,CAAA;QAC1B,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAEnC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACnC,2DAA2D;YAC3D,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,eAAe,EAAE,CAAC;gBACpD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAA;YACzC,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;YAChD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,cAAc,GAAG,CAAC,EAAE,CAAA;QAC9D,CAAC;QAED,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACvC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAA;QACzC,CAAC;QAED,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;IACxE,CAAC;CACF"}