@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":"payment-page.js","sourceRoot":"","sources":["../src/payment-page.ts"],"names":[],"mappings":"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"}
|
package/dist/stats.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { PaymentEvent, RequestEvent, ChallengeEvent } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Aggregate statistics snapshot from a toll-booth instance.
|
|
4
|
+
*
|
|
5
|
+
* All counters are in-memory and reset on restart — no PII is stored.
|
|
6
|
+
*/
|
|
7
|
+
export interface BoothStats {
|
|
8
|
+
/** ISO 8601 timestamp when the booth started. */
|
|
9
|
+
upSince: string;
|
|
10
|
+
requests: {
|
|
11
|
+
/** Total requests handled (authenticated + free tier). */
|
|
12
|
+
total: number;
|
|
13
|
+
/** Requests authenticated with a valid L402 token. */
|
|
14
|
+
authenticated: number;
|
|
15
|
+
/** Requests served under the free tier allowance. */
|
|
16
|
+
freeTier: number;
|
|
17
|
+
/** 402 challenges issued (invoices created). */
|
|
18
|
+
challenged: number;
|
|
19
|
+
};
|
|
20
|
+
revenue: {
|
|
21
|
+
/** Lightning invoices settled via L402 preimage. */
|
|
22
|
+
invoicesPaid: number;
|
|
23
|
+
/** Payments made via Nostr Wallet Connect. */
|
|
24
|
+
nwcPayments: number;
|
|
25
|
+
/** Payments made via Cashu token redemption. */
|
|
26
|
+
cashuRedemptions: number;
|
|
27
|
+
/** Total satoshis credited to balances. */
|
|
28
|
+
totalCredited: number;
|
|
29
|
+
/** Total satoshis consumed by requests. */
|
|
30
|
+
totalConsumed: number;
|
|
31
|
+
};
|
|
32
|
+
/** Per-endpoint breakdown of usage. */
|
|
33
|
+
endpoints: Record<string, {
|
|
34
|
+
requests: number;
|
|
35
|
+
satsConsumed: number;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Lightweight in-memory statistics collector for toll-booth.
|
|
40
|
+
*
|
|
41
|
+
* Aggregates request, payment, and challenge events into counters
|
|
42
|
+
* with no personally identifiable information. Resets on process restart.
|
|
43
|
+
*/
|
|
44
|
+
export declare class StatsCollector {
|
|
45
|
+
private readonly upSince;
|
|
46
|
+
private requests;
|
|
47
|
+
private revenue;
|
|
48
|
+
private endpoints;
|
|
49
|
+
constructor();
|
|
50
|
+
/** Record a proxied request (authenticated or free tier). */
|
|
51
|
+
recordRequest(event: RequestEvent): void;
|
|
52
|
+
/** Record a Lightning invoice settlement (credit granted). */
|
|
53
|
+
recordPayment(event: PaymentEvent): void;
|
|
54
|
+
/** Record a 402 challenge issued. */
|
|
55
|
+
recordChallenge(_event: ChallengeEvent): void;
|
|
56
|
+
/** Record a successful NWC payment. */
|
|
57
|
+
recordNwcPayment(amountSats: number): void;
|
|
58
|
+
/** Record a successful Cashu token redemption. */
|
|
59
|
+
recordCashuRedemption(amountSats: number): void;
|
|
60
|
+
/** Return a frozen snapshot of current statistics. */
|
|
61
|
+
snapshot(): BoothStats;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAK5E;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IAEf,QAAQ,EAAE;QACR,0DAA0D;QAC1D,KAAK,EAAE,MAAM,CAAA;QACb,sDAAsD;QACtD,aAAa,EAAE,MAAM,CAAA;QACrB,qDAAqD;QACrD,QAAQ,EAAE,MAAM,CAAA;QAChB,gDAAgD;QAChD,UAAU,EAAE,MAAM,CAAA;KACnB,CAAA;IAED,OAAO,EAAE;QACP,oDAAoD;QACpD,YAAY,EAAE,MAAM,CAAA;QACpB,8CAA8C;QAC9C,WAAW,EAAE,MAAM,CAAA;QACnB,gDAAgD;QAChD,gBAAgB,EAAE,MAAM,CAAA;QACxB,2CAA2C;QAC3C,aAAa,EAAE,MAAM,CAAA;QACrB,2CAA2C;QAC3C,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;IAED,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACtE;AAED;;;;;GAKG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAA6D;IAC7E,OAAO,CAAC,OAAO,CAMd;IACD,OAAO,CAAC,SAAS,CAAgE;;IAMjF,6DAA6D;IAC7D,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAmBxC,8DAA8D;IAC9D,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAKxC,qCAAqC;IACrC,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAI7C,uCAAuC;IACvC,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAK1C,kDAAkD;IAClD,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAK/C,sDAAsD;IACtD,QAAQ,IAAI,UAAU;CAavB"}
|
package/dist/stats.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Maximum number of distinct endpoint paths tracked in stats. */
|
|
2
|
+
const MAX_TRACKED_ENDPOINTS = 1000;
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight in-memory statistics collector for toll-booth.
|
|
5
|
+
*
|
|
6
|
+
* Aggregates request, payment, and challenge events into counters
|
|
7
|
+
* with no personally identifiable information. Resets on process restart.
|
|
8
|
+
*/
|
|
9
|
+
export class StatsCollector {
|
|
10
|
+
upSince;
|
|
11
|
+
requests = { total: 0, authenticated: 0, freeTier: 0, challenged: 0 };
|
|
12
|
+
revenue = {
|
|
13
|
+
invoicesPaid: 0,
|
|
14
|
+
nwcPayments: 0,
|
|
15
|
+
cashuRedemptions: 0,
|
|
16
|
+
totalCredited: 0,
|
|
17
|
+
totalConsumed: 0,
|
|
18
|
+
};
|
|
19
|
+
endpoints = new Map();
|
|
20
|
+
constructor() {
|
|
21
|
+
this.upSince = new Date().toISOString();
|
|
22
|
+
}
|
|
23
|
+
/** Record a proxied request (authenticated or free tier). */
|
|
24
|
+
recordRequest(event) {
|
|
25
|
+
this.requests.total++;
|
|
26
|
+
if (event.authenticated) {
|
|
27
|
+
this.requests.authenticated++;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
this.requests.freeTier++;
|
|
31
|
+
}
|
|
32
|
+
const existing = this.endpoints.get(event.endpoint);
|
|
33
|
+
if (existing) {
|
|
34
|
+
existing.requests++;
|
|
35
|
+
existing.satsConsumed += event.satsDeducted;
|
|
36
|
+
}
|
|
37
|
+
else if (this.endpoints.size < MAX_TRACKED_ENDPOINTS) {
|
|
38
|
+
this.endpoints.set(event.endpoint, { requests: 1, satsConsumed: event.satsDeducted });
|
|
39
|
+
}
|
|
40
|
+
this.revenue.totalConsumed += event.satsDeducted;
|
|
41
|
+
}
|
|
42
|
+
/** Record a Lightning invoice settlement (credit granted). */
|
|
43
|
+
recordPayment(event) {
|
|
44
|
+
this.revenue.invoicesPaid++;
|
|
45
|
+
this.revenue.totalCredited += event.amountSats;
|
|
46
|
+
}
|
|
47
|
+
/** Record a 402 challenge issued. */
|
|
48
|
+
recordChallenge(_event) {
|
|
49
|
+
this.requests.challenged++;
|
|
50
|
+
}
|
|
51
|
+
/** Record a successful NWC payment. */
|
|
52
|
+
recordNwcPayment(amountSats) {
|
|
53
|
+
this.revenue.nwcPayments++;
|
|
54
|
+
this.revenue.totalCredited += amountSats;
|
|
55
|
+
}
|
|
56
|
+
/** Record a successful Cashu token redemption. */
|
|
57
|
+
recordCashuRedemption(amountSats) {
|
|
58
|
+
this.revenue.cashuRedemptions++;
|
|
59
|
+
this.revenue.totalCredited += amountSats;
|
|
60
|
+
}
|
|
61
|
+
/** Return a frozen snapshot of current statistics. */
|
|
62
|
+
snapshot() {
|
|
63
|
+
const endpoints = {};
|
|
64
|
+
for (const [path, counters] of this.endpoints) {
|
|
65
|
+
endpoints[path] = { ...counters };
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
upSince: this.upSince,
|
|
69
|
+
requests: { ...this.requests },
|
|
70
|
+
revenue: { ...this.revenue },
|
|
71
|
+
endpoints,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAGA,kEAAkE;AAClE,MAAM,qBAAqB,GAAG,IAAI,CAAA;AAuClC;;;;;GAKG;AACH,MAAM,OAAO,cAAc;IACR,OAAO,CAAQ;IACxB,QAAQ,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;IACrE,OAAO,GAAG;QAChB,YAAY,EAAE,CAAC;QACf,WAAW,EAAE,CAAC;QACd,gBAAgB,EAAE,CAAC;QACnB,aAAa,EAAE,CAAC;QAChB,aAAa,EAAE,CAAC;KACjB,CAAA;IACO,SAAS,GAAG,IAAI,GAAG,EAAsD,CAAA;IAEjF;QACE,IAAI,CAAC,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACzC,CAAC;IAED,6DAA6D;IAC7D,aAAa,CAAC,KAAmB;QAC/B,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;QACrB,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;YACxB,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAA;QAC/B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAA;QAC1B,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;QACnD,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,QAAQ,EAAE,CAAA;YACnB,QAAQ,CAAC,YAAY,IAAI,KAAK,CAAC,YAAY,CAAA;QAC7C,CAAC;aAAM,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,qBAAqB,EAAE,CAAC;YACvD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAA;QACvF,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC,YAAY,CAAA;IAClD,CAAC;IAED,8DAA8D;IAC9D,aAAa,CAAC,KAAmB;QAC/B,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAA;QAC3B,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC,UAAU,CAAA;IAChD,CAAC;IAED,qCAAqC;IACrC,eAAe,CAAC,MAAsB;QACpC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAA;IAC5B,CAAC;IAED,uCAAuC;IACvC,gBAAgB,CAAC,UAAkB;QACjC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAA;QAC1B,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,UAAU,CAAA;IAC1C,CAAC;IAED,kDAAkD;IAClD,qBAAqB,CAAC,UAAkB;QACtC,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAA;QAC/B,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,UAAU,CAAA;IAC1C,CAAC;IAED,sDAAsD;IACtD,QAAQ;QACN,MAAM,SAAS,GAA+D,EAAE,CAAA;QAChF,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC9C,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAA;QACnC,CAAC;QAED,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE;YAC9B,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE;YAC5B,SAAS;SACV,CAAA;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface DebitResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
remaining: number;
|
|
4
|
+
}
|
|
5
|
+
export interface StoredInvoice {
|
|
6
|
+
paymentHash: string;
|
|
7
|
+
bolt11: string;
|
|
8
|
+
amountSats: number;
|
|
9
|
+
macaroon: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PendingClaim {
|
|
13
|
+
paymentHash: string;
|
|
14
|
+
token: string;
|
|
15
|
+
claimedAt: string;
|
|
16
|
+
}
|
|
17
|
+
export interface StorageBackend {
|
|
18
|
+
credit(paymentHash: string, amount: number): void;
|
|
19
|
+
debit(paymentHash: string, amount: number): DebitResult;
|
|
20
|
+
balance(paymentHash: string): number;
|
|
21
|
+
/** Atomically mark a payment hash as settled. Returns true if newly settled, false if already was. */
|
|
22
|
+
settle(paymentHash: string): boolean;
|
|
23
|
+
/** Check whether a payment hash has been settled. */
|
|
24
|
+
isSettled(paymentHash: string): boolean;
|
|
25
|
+
/** Atomically settle and credit in one operation. Returns true if newly settled, false if already was. */
|
|
26
|
+
settleWithCredit(paymentHash: string, amount: number, settlementSecret?: string): boolean;
|
|
27
|
+
/** Optional secret required for non-preimage L402 authorisation after settlement (e.g. Cashu flow). */
|
|
28
|
+
getSettlementSecret(paymentHash: string): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Write-ahead claim with an exclusive lease before an irreversible external call.
|
|
31
|
+
* Returns true if newly claimed, false if already claimed or settled.
|
|
32
|
+
* Sets a lease that expires after `leaseMs` milliseconds (default 30000).
|
|
33
|
+
* The claim persists across restarts for crash recovery.
|
|
34
|
+
*/
|
|
35
|
+
claimForRedeem(paymentHash: string, token: string, leaseMs?: number): boolean;
|
|
36
|
+
/** Returns all claims that were never settled (for crash recovery on startup). */
|
|
37
|
+
pendingClaims(): PendingClaim[];
|
|
38
|
+
/**
|
|
39
|
+
* Atomically acquire an exclusive recovery lease on an existing pending claim.
|
|
40
|
+
* Only succeeds if the claim exists, is not settled, and the previous lease has expired.
|
|
41
|
+
* Returns the claim if the lease was acquired, undefined otherwise.
|
|
42
|
+
*/
|
|
43
|
+
tryAcquireRecoveryLease(paymentHash: string, leaseMs: number): PendingClaim | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Extend an active lease on a pending claim.
|
|
46
|
+
* Returns true if extended, false if missing/settled/expired.
|
|
47
|
+
*/
|
|
48
|
+
extendRecoveryLease(paymentHash: string, leaseMs: number): boolean;
|
|
49
|
+
storeInvoice(paymentHash: string, bolt11: string, amountSats: number, macaroon: string, statusToken: string): void;
|
|
50
|
+
getInvoice(paymentHash: string): StoredInvoice | undefined;
|
|
51
|
+
getInvoiceForStatus(paymentHash: string, statusToken: string): StoredInvoice | undefined;
|
|
52
|
+
/** Delete invoices older than maxAgeMs. Returns the number of deleted rows. */
|
|
53
|
+
pruneExpiredInvoices(maxAgeMs: number): number;
|
|
54
|
+
/** Delete zero-balance credits and aged settlements/claims. Returns total deleted rows. */
|
|
55
|
+
pruneStaleRecords(maxAgeMs: number): number;
|
|
56
|
+
close(): void;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/storage/interface.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACjD,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAAA;IACvD,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAA;IACpC,sGAAsG;IACtG,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAA;IACpC,qDAAqD;IACrD,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAA;IACvC,0GAA0G;IAC1G,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACzF,uGAAuG;IACvG,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IAC5D;;;;;OAKG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC7E,kFAAkF;IAClF,aAAa,IAAI,YAAY,EAAE,CAAA;IAC/B;;;;OAIG;IACH,uBAAuB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAA;IACvF;;;OAGG;IACH,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;IAClE,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAClH,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAA;IAC1D,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAA;IACxF,+EAA+E;IAC/E,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9C,2FAA2F;IAC3F,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3C,KAAK,IAAI,IAAI,CAAA;CACd"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/storage/interface.ts"],"names":[],"mappings":"AAAA,2BAA2B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/storage/memory.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAA4C,MAAM,gBAAgB,CAAA;AAe9F,wBAAgB,aAAa,IAAI,cAAc,CAqI9C"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const DEFAULT_LEASE_MS = 30_000;
|
|
2
|
+
function toStoredInvoice(record) {
|
|
3
|
+
return {
|
|
4
|
+
paymentHash: record.paymentHash,
|
|
5
|
+
bolt11: record.bolt11,
|
|
6
|
+
amountSats: record.amountSats,
|
|
7
|
+
macaroon: record.macaroon,
|
|
8
|
+
createdAt: record.createdAt,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function memoryStorage() {
|
|
12
|
+
const balances = new Map();
|
|
13
|
+
const invoices = new Map();
|
|
14
|
+
const settled = new Map();
|
|
15
|
+
const claims = new Map();
|
|
16
|
+
return {
|
|
17
|
+
credit(paymentHash, amount) {
|
|
18
|
+
const current = balances.get(paymentHash) ?? 0;
|
|
19
|
+
balances.set(paymentHash, current + amount);
|
|
20
|
+
},
|
|
21
|
+
debit(paymentHash, amount) {
|
|
22
|
+
const current = balances.get(paymentHash) ?? 0;
|
|
23
|
+
if (current < amount) {
|
|
24
|
+
return { success: false, remaining: current };
|
|
25
|
+
}
|
|
26
|
+
const remaining = current - amount;
|
|
27
|
+
balances.set(paymentHash, remaining);
|
|
28
|
+
return { success: true, remaining };
|
|
29
|
+
},
|
|
30
|
+
balance(paymentHash) {
|
|
31
|
+
return balances.get(paymentHash) ?? 0;
|
|
32
|
+
},
|
|
33
|
+
settle(paymentHash) {
|
|
34
|
+
if (settled.has(paymentHash))
|
|
35
|
+
return false;
|
|
36
|
+
settled.set(paymentHash, undefined);
|
|
37
|
+
return true;
|
|
38
|
+
},
|
|
39
|
+
isSettled(paymentHash) {
|
|
40
|
+
return settled.has(paymentHash);
|
|
41
|
+
},
|
|
42
|
+
settleWithCredit(paymentHash, amount, settlementSecret) {
|
|
43
|
+
if (settled.has(paymentHash))
|
|
44
|
+
return false;
|
|
45
|
+
settled.set(paymentHash, settlementSecret);
|
|
46
|
+
claims.delete(paymentHash);
|
|
47
|
+
const current = balances.get(paymentHash) ?? 0;
|
|
48
|
+
balances.set(paymentHash, current + amount);
|
|
49
|
+
return true;
|
|
50
|
+
},
|
|
51
|
+
getSettlementSecret(paymentHash) {
|
|
52
|
+
return settled.get(paymentHash);
|
|
53
|
+
},
|
|
54
|
+
claimForRedeem(paymentHash, token, leaseMs) {
|
|
55
|
+
if (settled.has(paymentHash) || claims.has(paymentHash))
|
|
56
|
+
return false;
|
|
57
|
+
claims.set(paymentHash, {
|
|
58
|
+
token,
|
|
59
|
+
claimedAt: new Date().toISOString(),
|
|
60
|
+
leaseExpiresAt: Date.now() + (leaseMs ?? DEFAULT_LEASE_MS),
|
|
61
|
+
});
|
|
62
|
+
return true;
|
|
63
|
+
},
|
|
64
|
+
pendingClaims() {
|
|
65
|
+
return Array.from(claims.entries())
|
|
66
|
+
.filter(([paymentHash]) => !settled.has(paymentHash))
|
|
67
|
+
.map(([paymentHash, { token, claimedAt }]) => ({
|
|
68
|
+
paymentHash, token, claimedAt,
|
|
69
|
+
}));
|
|
70
|
+
},
|
|
71
|
+
tryAcquireRecoveryLease(paymentHash, leaseMs) {
|
|
72
|
+
if (settled.has(paymentHash))
|
|
73
|
+
return undefined;
|
|
74
|
+
const claim = claims.get(paymentHash);
|
|
75
|
+
if (!claim)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (Date.now() < claim.leaseExpiresAt)
|
|
78
|
+
return undefined;
|
|
79
|
+
// Lease expired — acquire it
|
|
80
|
+
claim.leaseExpiresAt = Date.now() + leaseMs;
|
|
81
|
+
return { paymentHash, token: claim.token, claimedAt: claim.claimedAt };
|
|
82
|
+
},
|
|
83
|
+
extendRecoveryLease(paymentHash, leaseMs) {
|
|
84
|
+
if (settled.has(paymentHash))
|
|
85
|
+
return false;
|
|
86
|
+
const claim = claims.get(paymentHash);
|
|
87
|
+
if (!claim)
|
|
88
|
+
return false;
|
|
89
|
+
if (Date.now() >= claim.leaseExpiresAt)
|
|
90
|
+
return false;
|
|
91
|
+
claim.leaseExpiresAt = Date.now() + leaseMs;
|
|
92
|
+
return true;
|
|
93
|
+
},
|
|
94
|
+
storeInvoice(paymentHash, bolt11, amountSats, macaroon, statusToken) {
|
|
95
|
+
if (invoices.has(paymentHash))
|
|
96
|
+
return;
|
|
97
|
+
invoices.set(paymentHash, {
|
|
98
|
+
paymentHash,
|
|
99
|
+
bolt11,
|
|
100
|
+
amountSats,
|
|
101
|
+
macaroon,
|
|
102
|
+
statusToken,
|
|
103
|
+
createdAt: new Date().toISOString(),
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
getInvoice(paymentHash) {
|
|
107
|
+
const invoice = invoices.get(paymentHash);
|
|
108
|
+
return invoice ? toStoredInvoice(invoice) : undefined;
|
|
109
|
+
},
|
|
110
|
+
getInvoiceForStatus(paymentHash, statusToken) {
|
|
111
|
+
const invoice = invoices.get(paymentHash);
|
|
112
|
+
if (!invoice || invoice.statusToken !== statusToken)
|
|
113
|
+
return undefined;
|
|
114
|
+
return toStoredInvoice(invoice);
|
|
115
|
+
},
|
|
116
|
+
pruneExpiredInvoices(maxAgeMs) {
|
|
117
|
+
const cutoff = new Date(Date.now() - maxAgeMs).toISOString();
|
|
118
|
+
let pruned = 0;
|
|
119
|
+
for (const [hash, inv] of invoices) {
|
|
120
|
+
if (inv.createdAt < cutoff) {
|
|
121
|
+
invoices.delete(hash);
|
|
122
|
+
pruned++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return pruned;
|
|
126
|
+
},
|
|
127
|
+
pruneStaleRecords(_maxAgeMs) {
|
|
128
|
+
// Memory storage is for testing only — no long-running pruning needed
|
|
129
|
+
return 0;
|
|
130
|
+
},
|
|
131
|
+
close() {
|
|
132
|
+
balances.clear();
|
|
133
|
+
invoices.clear();
|
|
134
|
+
settled.clear();
|
|
135
|
+
claims.clear();
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/storage/memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/storage/sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAA4C,MAAM,gBAAgB,CAAA;AAI9F,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,cAAc,CAgV1E"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// src/storage/sqlite.ts
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
const DEFAULT_LEASE_MS = 30_000;
|
|
4
|
+
export function sqliteStorage(config) {
|
|
5
|
+
const db = new Database(config?.path ?? ':memory:');
|
|
6
|
+
db.pragma('journal_mode = WAL');
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS credits (
|
|
9
|
+
payment_hash TEXT PRIMARY KEY,
|
|
10
|
+
balance INTEGER NOT NULL DEFAULT 0,
|
|
11
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
12
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
13
|
+
)
|
|
14
|
+
`);
|
|
15
|
+
db.exec(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS invoices (
|
|
17
|
+
payment_hash TEXT PRIMARY KEY,
|
|
18
|
+
bolt11 TEXT NOT NULL,
|
|
19
|
+
amount_sats INTEGER NOT NULL,
|
|
20
|
+
macaroon TEXT NOT NULL,
|
|
21
|
+
status_token TEXT NOT NULL,
|
|
22
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
23
|
+
)
|
|
24
|
+
`);
|
|
25
|
+
db.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS settlements (
|
|
27
|
+
payment_hash TEXT PRIMARY KEY,
|
|
28
|
+
settlement_secret TEXT,
|
|
29
|
+
settled_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
30
|
+
)
|
|
31
|
+
`);
|
|
32
|
+
db.exec(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS claims (
|
|
34
|
+
payment_hash TEXT PRIMARY KEY,
|
|
35
|
+
token TEXT NOT NULL,
|
|
36
|
+
claimed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
37
|
+
lease_expires_at TEXT
|
|
38
|
+
)
|
|
39
|
+
`);
|
|
40
|
+
// Migration: add lease_expires_at column if upgrading from older schema
|
|
41
|
+
try {
|
|
42
|
+
db.exec('ALTER TABLE claims ADD COLUMN lease_expires_at TEXT');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Column already exists — ignore
|
|
46
|
+
}
|
|
47
|
+
// Migration: add status_token column and backfill older rows.
|
|
48
|
+
try {
|
|
49
|
+
db.exec('ALTER TABLE invoices ADD COLUMN status_token TEXT');
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Column already exists — ignore
|
|
53
|
+
}
|
|
54
|
+
db.exec(`
|
|
55
|
+
UPDATE invoices
|
|
56
|
+
SET status_token = lower(hex(randomblob(32)))
|
|
57
|
+
WHERE status_token IS NULL OR status_token = ''
|
|
58
|
+
`);
|
|
59
|
+
// Migration: add settlement_secret column to older schemas.
|
|
60
|
+
try {
|
|
61
|
+
db.exec('ALTER TABLE settlements ADD COLUMN settlement_secret TEXT');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Column already exists — ignore
|
|
65
|
+
}
|
|
66
|
+
const stmtCredit = db.prepare(`
|
|
67
|
+
INSERT INTO credits (payment_hash, balance)
|
|
68
|
+
VALUES (?, ?)
|
|
69
|
+
ON CONFLICT(payment_hash) DO UPDATE SET
|
|
70
|
+
balance = balance + excluded.balance,
|
|
71
|
+
updated_at = datetime('now')
|
|
72
|
+
`);
|
|
73
|
+
const stmtDebit = db.prepare(`
|
|
74
|
+
UPDATE credits SET balance = balance - ?, updated_at = datetime('now')
|
|
75
|
+
WHERE payment_hash = ? AND balance >= ?
|
|
76
|
+
`);
|
|
77
|
+
const stmtBalance = db.prepare('SELECT balance FROM credits WHERE payment_hash = ?');
|
|
78
|
+
const stmtStoreInvoice = db.prepare(`
|
|
79
|
+
INSERT OR IGNORE INTO invoices (payment_hash, bolt11, amount_sats, macaroon, status_token)
|
|
80
|
+
VALUES (?, ?, ?, ?, ?)
|
|
81
|
+
`);
|
|
82
|
+
const stmtGetInvoice = db.prepare('SELECT payment_hash, bolt11, amount_sats, macaroon, created_at FROM invoices WHERE payment_hash = ?');
|
|
83
|
+
const stmtGetInvoiceForStatus = db.prepare(`SELECT payment_hash, bolt11, amount_sats, macaroon, created_at
|
|
84
|
+
FROM invoices
|
|
85
|
+
WHERE payment_hash = ? AND status_token = ?`);
|
|
86
|
+
const stmtSettle = db.prepare('INSERT OR IGNORE INTO settlements (payment_hash, settlement_secret) VALUES (?, ?)');
|
|
87
|
+
const stmtIsSettled = db.prepare('SELECT 1 FROM settlements WHERE payment_hash = ?');
|
|
88
|
+
const stmtGetSettlementSecret = db.prepare('SELECT settlement_secret FROM settlements WHERE payment_hash = ?');
|
|
89
|
+
const stmtClaim = db.prepare('INSERT OR IGNORE INTO claims (payment_hash, token, lease_expires_at) VALUES (?, ?, ?)');
|
|
90
|
+
const stmtPendingClaims = db.prepare(`
|
|
91
|
+
SELECT c.payment_hash, c.token, c.claimed_at
|
|
92
|
+
FROM claims c
|
|
93
|
+
LEFT JOIN settlements s ON c.payment_hash = s.payment_hash
|
|
94
|
+
WHERE s.payment_hash IS NULL
|
|
95
|
+
`);
|
|
96
|
+
const stmtDeleteClaim = db.prepare('DELETE FROM claims WHERE payment_hash = ?');
|
|
97
|
+
const stmtTryAcquireRecoveryLease = db.prepare(`
|
|
98
|
+
UPDATE claims
|
|
99
|
+
SET lease_expires_at = ?
|
|
100
|
+
WHERE payment_hash = ?
|
|
101
|
+
AND (lease_expires_at IS NULL OR datetime(lease_expires_at) <= datetime('now'))
|
|
102
|
+
AND payment_hash NOT IN (SELECT payment_hash FROM settlements)
|
|
103
|
+
`);
|
|
104
|
+
const stmtExtendRecoveryLease = db.prepare(`
|
|
105
|
+
UPDATE claims
|
|
106
|
+
SET lease_expires_at = ?
|
|
107
|
+
WHERE payment_hash = ?
|
|
108
|
+
AND datetime(lease_expires_at) > datetime('now')
|
|
109
|
+
AND payment_hash NOT IN (SELECT payment_hash FROM settlements)
|
|
110
|
+
`);
|
|
111
|
+
const stmtGetClaim = db.prepare('SELECT payment_hash, token, claimed_at FROM claims WHERE payment_hash = ?');
|
|
112
|
+
const stmtPruneInvoices = db.prepare(`
|
|
113
|
+
DELETE FROM invoices
|
|
114
|
+
WHERE datetime(created_at) <= datetime('now', '-' || ? || ' seconds')
|
|
115
|
+
`);
|
|
116
|
+
const stmtPruneZeroCredits = db.prepare(`
|
|
117
|
+
DELETE FROM credits
|
|
118
|
+
WHERE balance <= 0
|
|
119
|
+
AND datetime(updated_at) <= datetime('now', '-' || ? || ' seconds')
|
|
120
|
+
`);
|
|
121
|
+
const stmtPruneSettlements = db.prepare(`
|
|
122
|
+
DELETE FROM settlements
|
|
123
|
+
WHERE datetime(settled_at) <= datetime('now', '-' || ? || ' seconds')
|
|
124
|
+
`);
|
|
125
|
+
const stmtPruneClaims = db.prepare(`
|
|
126
|
+
DELETE FROM claims
|
|
127
|
+
WHERE payment_hash IN (SELECT payment_hash FROM settlements)
|
|
128
|
+
OR datetime(claimed_at) <= datetime('now', '-' || ? || ' seconds')
|
|
129
|
+
`);
|
|
130
|
+
const txnClaimForRedeem = db.transaction((paymentHash, token, leaseExpiresAt) => {
|
|
131
|
+
// Reject if already settled
|
|
132
|
+
if (stmtIsSettled.get(paymentHash))
|
|
133
|
+
return false;
|
|
134
|
+
// Try to claim (INSERT OR IGNORE — fails silently if already claimed)
|
|
135
|
+
const r = stmtClaim.run(paymentHash, token, leaseExpiresAt);
|
|
136
|
+
return r.changes > 0;
|
|
137
|
+
});
|
|
138
|
+
const txnSettleWithCredit = db.transaction((paymentHash, amount, settlementSecret) => {
|
|
139
|
+
const r = stmtSettle.run(paymentHash, settlementSecret ?? null);
|
|
140
|
+
if (r.changes > 0) {
|
|
141
|
+
stmtCredit.run(paymentHash, amount);
|
|
142
|
+
stmtDeleteClaim.run(paymentHash);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
});
|
|
147
|
+
const txnTryAcquireRecoveryLease = db.transaction((paymentHash, leaseExpiresAt) => {
|
|
148
|
+
const r = stmtTryAcquireRecoveryLease.run(leaseExpiresAt, paymentHash);
|
|
149
|
+
if (r.changes === 0)
|
|
150
|
+
return undefined;
|
|
151
|
+
const row = stmtGetClaim.get(paymentHash);
|
|
152
|
+
if (!row)
|
|
153
|
+
return undefined;
|
|
154
|
+
return {
|
|
155
|
+
paymentHash: row.payment_hash,
|
|
156
|
+
token: row.token,
|
|
157
|
+
claimedAt: row.claimed_at,
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
const txnDebit = db.transaction((paymentHash, amount) => {
|
|
161
|
+
const row = stmtBalance.get(paymentHash);
|
|
162
|
+
const current = row?.balance ?? 0;
|
|
163
|
+
if (current < amount) {
|
|
164
|
+
return { success: false, remaining: current };
|
|
165
|
+
}
|
|
166
|
+
const result = stmtDebit.run(amount, paymentHash, amount);
|
|
167
|
+
if (result.changes === 0) {
|
|
168
|
+
return { success: false, remaining: current };
|
|
169
|
+
}
|
|
170
|
+
return { success: true, remaining: current - amount };
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
credit(paymentHash, amount) {
|
|
174
|
+
stmtCredit.run(paymentHash, amount);
|
|
175
|
+
},
|
|
176
|
+
debit(paymentHash, amount) {
|
|
177
|
+
return txnDebit(paymentHash, amount);
|
|
178
|
+
},
|
|
179
|
+
balance(paymentHash) {
|
|
180
|
+
const row = stmtBalance.get(paymentHash);
|
|
181
|
+
return row?.balance ?? 0;
|
|
182
|
+
},
|
|
183
|
+
settle(paymentHash) {
|
|
184
|
+
const result = stmtSettle.run(paymentHash, null);
|
|
185
|
+
return result.changes > 0;
|
|
186
|
+
},
|
|
187
|
+
isSettled(paymentHash) {
|
|
188
|
+
return !!stmtIsSettled.get(paymentHash);
|
|
189
|
+
},
|
|
190
|
+
settleWithCredit(paymentHash, amount, settlementSecret) {
|
|
191
|
+
return txnSettleWithCredit(paymentHash, amount, settlementSecret);
|
|
192
|
+
},
|
|
193
|
+
getSettlementSecret(paymentHash) {
|
|
194
|
+
const row = stmtGetSettlementSecret.get(paymentHash);
|
|
195
|
+
return row?.settlement_secret ?? undefined;
|
|
196
|
+
},
|
|
197
|
+
claimForRedeem(paymentHash, token, leaseMs) {
|
|
198
|
+
const ms = leaseMs ?? DEFAULT_LEASE_MS;
|
|
199
|
+
const leaseExpiresAt = new Date(Date.now() + ms).toISOString();
|
|
200
|
+
return txnClaimForRedeem(paymentHash, token, leaseExpiresAt);
|
|
201
|
+
},
|
|
202
|
+
pendingClaims() {
|
|
203
|
+
const rows = stmtPendingClaims.all();
|
|
204
|
+
return rows.map((r) => ({
|
|
205
|
+
paymentHash: r.payment_hash,
|
|
206
|
+
token: r.token,
|
|
207
|
+
claimedAt: r.claimed_at,
|
|
208
|
+
}));
|
|
209
|
+
},
|
|
210
|
+
tryAcquireRecoveryLease(paymentHash, leaseMs) {
|
|
211
|
+
const leaseExpiresAt = new Date(Date.now() + leaseMs).toISOString();
|
|
212
|
+
return txnTryAcquireRecoveryLease(paymentHash, leaseExpiresAt);
|
|
213
|
+
},
|
|
214
|
+
extendRecoveryLease(paymentHash, leaseMs) {
|
|
215
|
+
const leaseExpiresAt = new Date(Date.now() + leaseMs).toISOString();
|
|
216
|
+
const result = stmtExtendRecoveryLease.run(leaseExpiresAt, paymentHash);
|
|
217
|
+
return result.changes > 0;
|
|
218
|
+
},
|
|
219
|
+
storeInvoice(paymentHash, bolt11, amountSats, macaroon, statusToken) {
|
|
220
|
+
stmtStoreInvoice.run(paymentHash, bolt11, amountSats, macaroon, statusToken);
|
|
221
|
+
},
|
|
222
|
+
getInvoice(paymentHash) {
|
|
223
|
+
const row = stmtGetInvoice.get(paymentHash);
|
|
224
|
+
if (!row)
|
|
225
|
+
return undefined;
|
|
226
|
+
return {
|
|
227
|
+
paymentHash: row.payment_hash,
|
|
228
|
+
bolt11: row.bolt11,
|
|
229
|
+
amountSats: row.amount_sats,
|
|
230
|
+
macaroon: row.macaroon,
|
|
231
|
+
createdAt: row.created_at,
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
getInvoiceForStatus(paymentHash, statusToken) {
|
|
235
|
+
const row = stmtGetInvoiceForStatus.get(paymentHash, statusToken);
|
|
236
|
+
if (!row)
|
|
237
|
+
return undefined;
|
|
238
|
+
return {
|
|
239
|
+
paymentHash: row.payment_hash,
|
|
240
|
+
bolt11: row.bolt11,
|
|
241
|
+
amountSats: row.amount_sats,
|
|
242
|
+
macaroon: row.macaroon,
|
|
243
|
+
createdAt: row.created_at,
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
pruneExpiredInvoices(maxAgeMs) {
|
|
247
|
+
const maxAgeSecs = Math.floor(maxAgeMs / 1000);
|
|
248
|
+
const result = stmtPruneInvoices.run(maxAgeSecs);
|
|
249
|
+
return result.changes;
|
|
250
|
+
},
|
|
251
|
+
pruneStaleRecords(maxAgeMs) {
|
|
252
|
+
const maxAgeSecs = Math.floor(maxAgeMs / 1000);
|
|
253
|
+
let total = 0;
|
|
254
|
+
total += stmtPruneZeroCredits.run(maxAgeSecs).changes;
|
|
255
|
+
total += stmtPruneSettlements.run(maxAgeSecs).changes;
|
|
256
|
+
total += stmtPruneClaims.run(maxAgeSecs).changes;
|
|
257
|
+
return total;
|
|
258
|
+
},
|
|
259
|
+
close() {
|
|
260
|
+
db.close();
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=sqlite.js.map
|