@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.
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/adapters/express.d.ts +51 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +237 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/proxy-headers.d.ts +7 -0
- package/dist/adapters/proxy-headers.d.ts.map +1 -0
- package/dist/adapters/proxy-headers.js +58 -0
- package/dist/adapters/proxy-headers.js.map +1 -0
- package/dist/adapters/web-standard.d.ts +60 -0
- package/dist/adapters/web-standard.d.ts.map +1 -0
- package/dist/adapters/web-standard.js +252 -0
- package/dist/adapters/web-standard.js.map +1 -0
- package/dist/backends/alby.d.ts +25 -0
- package/dist/backends/alby.d.ts.map +1 -0
- package/dist/backends/alby.js +137 -0
- package/dist/backends/alby.js.map +1 -0
- package/dist/backends/cln.d.ts +22 -0
- package/dist/backends/cln.d.ts.map +1 -0
- package/dist/backends/cln.js +55 -0
- package/dist/backends/cln.js.map +1 -0
- package/dist/backends/lnbits.d.ts +23 -0
- package/dist/backends/lnbits.d.ts.map +1 -0
- package/dist/backends/lnbits.js +58 -0
- package/dist/backends/lnbits.js.map +1 -0
- package/dist/backends/lnd.d.ts +21 -0
- package/dist/backends/lnd.d.ts.map +1 -0
- package/dist/backends/lnd.js +59 -0
- package/dist/backends/lnd.js.map +1 -0
- package/dist/backends/phoenixd.d.ts +19 -0
- package/dist/backends/phoenixd.d.ts.map +1 -0
- package/dist/backends/phoenixd.js +59 -0
- package/dist/backends/phoenixd.js.map +1 -0
- package/dist/booth.d.ts +54 -0
- package/dist/booth.d.ts.map +1 -0
- package/dist/booth.js +200 -0
- package/dist/booth.js.map +1 -0
- package/dist/core/cashu-redeem.d.ts +9 -0
- package/dist/core/cashu-redeem.d.ts.map +1 -0
- package/dist/core/cashu-redeem.js +85 -0
- package/dist/core/cashu-redeem.js.map +1 -0
- package/dist/core/create-invoice.d.ts +19 -0
- package/dist/core/create-invoice.d.ts.map +1 -0
- package/dist/core/create-invoice.js +66 -0
- package/dist/core/create-invoice.js.map +1 -0
- package/dist/core/invoice-status.d.ts +24 -0
- package/dist/core/invoice-status.d.ts.map +1 -0
- package/dist/core/invoice-status.js +74 -0
- package/dist/core/invoice-status.js.map +1 -0
- package/dist/core/nwc-pay.d.ts +8 -0
- package/dist/core/nwc-pay.d.ts.map +1 -0
- package/dist/core/nwc-pay.js +23 -0
- package/dist/core/nwc-pay.js.map +1 -0
- package/dist/core/toll-booth.d.ts +9 -0
- package/dist/core/toll-booth.d.ts.map +1 -0
- package/dist/core/toll-booth.js +172 -0
- package/dist/core/toll-booth.js.map +1 -0
- package/dist/core/types.d.ts +101 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/free-tier.d.ts +14 -0
- package/dist/free-tier.d.ts.map +1 -0
- package/dist/free-tier.js +41 -0
- package/dist/free-tier.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/macaroon.d.ts +39 -0
- package/dist/macaroon.d.ts.map +1 -0
- package/dist/macaroon.js +111 -0
- package/dist/macaroon.js.map +1 -0
- package/dist/payment-page.d.ts +18 -0
- package/dist/payment-page.d.ts.map +1 -0
- package/dist/payment-page.js +391 -0
- package/dist/payment-page.js.map +1 -0
- package/dist/stats.d.ts +63 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +75 -0
- package/dist/stats.js.map +1 -0
- package/dist/storage/interface.d.ts +58 -0
- package/dist/storage/interface.d.ts.map +1 -0
- package/dist/storage/interface.js +3 -0
- package/dist/storage/interface.js.map +1 -0
- package/dist/storage/memory.d.ts +3 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +139 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/sqlite.d.ts +6 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +264 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/types.d.ts +198 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/llms.txt +91 -0
- 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":"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"}
|
|
@@ -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 @@
|
|
|
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"}
|