@thecryptodonkey/toll-booth 1.1.2 → 1.2.1
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/README.md +71 -23
- package/dist/adapters/express.d.ts +15 -2
- package/dist/adapters/express.d.ts.map +1 -1
- package/dist/adapters/express.js +62 -16
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/hono.d.ts +62 -0
- package/dist/adapters/hono.d.ts.map +1 -0
- package/dist/adapters/hono.js +202 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/proxy-headers.d.ts.map +1 -1
- package/dist/adapters/proxy-headers.js +1 -0
- package/dist/adapters/proxy-headers.js.map +1 -1
- package/dist/adapters/web-standard.d.ts +9 -1
- package/dist/adapters/web-standard.d.ts.map +1 -1
- package/dist/adapters/web-standard.js +44 -9
- package/dist/adapters/web-standard.js.map +1 -1
- package/dist/backends/cln.d.ts.map +1 -1
- package/dist/backends/cln.js +5 -2
- package/dist/backends/cln.js.map +1 -1
- package/dist/backends/lnbits.d.ts.map +1 -1
- package/dist/backends/lnbits.js +5 -2
- package/dist/backends/lnbits.js.map +1 -1
- package/dist/backends/lnd.d.ts.map +1 -1
- package/dist/backends/lnd.js +4 -1
- package/dist/backends/lnd.js.map +1 -1
- package/dist/backends/phoenixd.d.ts.map +1 -1
- package/dist/backends/phoenixd.js +5 -2
- package/dist/backends/phoenixd.js.map +1 -1
- package/dist/booth.d.ts.map +1 -1
- package/dist/booth.js +36 -6
- package/dist/booth.js.map +1 -1
- package/dist/core/cashu-redeem.d.ts.map +1 -1
- package/dist/core/cashu-redeem.js +15 -5
- package/dist/core/cashu-redeem.js.map +1 -1
- package/dist/core/create-invoice.d.ts +1 -0
- package/dist/core/create-invoice.d.ts.map +1 -1
- package/dist/core/create-invoice.js +29 -3
- package/dist/core/create-invoice.js.map +1 -1
- package/dist/core/invoice-status.d.ts.map +1 -1
- package/dist/core/invoice-status.js +24 -11
- package/dist/core/invoice-status.js.map +1 -1
- package/dist/core/l402-rail.d.ts +11 -0
- package/dist/core/l402-rail.d.ts.map +1 -0
- package/dist/core/l402-rail.js +94 -0
- package/dist/core/l402-rail.js.map +1 -0
- package/dist/core/nwc-pay.d.ts.map +1 -1
- package/dist/core/nwc-pay.js +8 -4
- package/dist/core/nwc-pay.js.map +1 -1
- package/dist/core/payment-rail.d.ts +40 -0
- package/dist/core/payment-rail.d.ts.map +1 -0
- package/dist/core/payment-rail.js +13 -0
- package/dist/core/payment-rail.js.map +1 -0
- package/dist/core/toll-booth.d.ts +2 -1
- package/dist/core/toll-booth.d.ts.map +1 -1
- package/dist/core/toll-booth.js +143 -120
- package/dist/core/toll-booth.js.map +1 -1
- package/dist/core/types.d.ts +17 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/x402-rail.d.ts +4 -0
- package/dist/core/x402-rail.d.ts.map +1 -0
- package/dist/core/x402-rail.js +65 -0
- package/dist/core/x402-rail.js.map +1 -0
- package/dist/core/x402-types.d.ts +29 -0
- package/dist/core/x402-types.d.ts.map +1 -0
- package/dist/core/x402-types.js +7 -0
- package/dist/core/x402-types.js.map +1 -0
- package/dist/demo.js +6 -6
- package/dist/demo.js.map +1 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/macaroon.d.ts +18 -2
- package/dist/macaroon.d.ts.map +1 -1
- package/dist/macaroon.js +61 -8
- package/dist/macaroon.js.map +1 -1
- package/dist/payment-page.d.ts.map +1 -1
- package/dist/payment-page.js +8 -1
- package/dist/payment-page.js.map +1 -1
- package/dist/stats.d.ts.map +1 -1
- package/dist/stats.js +4 -2
- package/dist/stats.js.map +1 -1
- package/dist/storage/interface.d.ts +10 -5
- package/dist/storage/interface.d.ts.map +1 -1
- package/dist/storage/interface.js +0 -1
- package/dist/storage/interface.js.map +1 -1
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +52 -15
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +138 -28
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types.d.ts +23 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +22 -5
package/dist/core/toll-booth.js
CHANGED
|
@@ -1,55 +1,129 @@
|
|
|
1
1
|
// src/core/toll-booth.ts
|
|
2
|
-
import {
|
|
3
|
-
import { mintMacaroon, verifyMacaroon } from '../macaroon.js';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
4
3
|
import { FreeTier } from '../free-tier.js';
|
|
4
|
+
import { createL402Rail } from './l402-rail.js';
|
|
5
|
+
import { normalisePricingTable } from './payment-rail.js';
|
|
5
6
|
export function createTollBooth(config) {
|
|
6
7
|
const defaultAmount = config.defaultInvoiceAmount ?? 1000;
|
|
7
8
|
const upstream = config.upstream.replace(/\/$/, '');
|
|
8
9
|
const freeTier = config.freeTier ? new FreeTier(config.freeTier.requestsPerDay) : null;
|
|
10
|
+
const storage = config.storage;
|
|
11
|
+
// Booth always provides explicit rails. This fallback exists for direct
|
|
12
|
+
// createTollBooth() users who don't pass rails (backward compat).
|
|
13
|
+
const rails = config.rails ?? [
|
|
14
|
+
createL402Rail({
|
|
15
|
+
rootKey: config.rootKey,
|
|
16
|
+
storage,
|
|
17
|
+
defaultAmount,
|
|
18
|
+
backend: config.backend,
|
|
19
|
+
}),
|
|
20
|
+
];
|
|
21
|
+
const normalisedPricing = config.normalisedPricing ?? normalisePricingTable(config.pricing ?? {});
|
|
22
|
+
const MAX_ESTIMATED_COSTS = 10_000;
|
|
23
|
+
const MAX_AGE_MS = 60_000;
|
|
24
|
+
const estimatedCosts = new Map();
|
|
9
25
|
return {
|
|
10
26
|
freeTier,
|
|
11
27
|
upstream,
|
|
12
28
|
async handle(req) {
|
|
13
29
|
const start = Date.now();
|
|
14
30
|
const path = req.path;
|
|
15
|
-
const
|
|
31
|
+
const pricedEntry = config.pricing[path];
|
|
16
32
|
// Unpriced routes: pass through unless strictPricing is enabled
|
|
17
|
-
if (
|
|
33
|
+
if (pricedEntry === undefined && !config.strictPricing) {
|
|
18
34
|
return { action: 'pass', upstream, headers: {} };
|
|
19
35
|
}
|
|
20
|
-
//
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
// Pricing for this route, normalised to PriceInfo
|
|
37
|
+
const priceInfo = typeof pricedEntry === 'number'
|
|
38
|
+
? { sats: pricedEntry }
|
|
39
|
+
: (pricedEntry ?? { sats: defaultAmount });
|
|
40
|
+
// Try each rail
|
|
41
|
+
for (const rail of rails) {
|
|
42
|
+
if (rail.detect(req)) {
|
|
43
|
+
const result = await Promise.resolve(rail.verify(req));
|
|
44
|
+
if (result.authenticated) {
|
|
45
|
+
// Pick cost in the rail's currency
|
|
46
|
+
const cost = result.currency === 'usd'
|
|
47
|
+
? (priceInfo.usd ?? 0)
|
|
48
|
+
: (priceInfo.sats ?? defaultAmount);
|
|
49
|
+
// Per-request replay protection: reject if already settled
|
|
50
|
+
if (result.mode === 'per-request') {
|
|
51
|
+
if (storage.isSettled(result.paymentId)) {
|
|
52
|
+
break; // fall through to challenge
|
|
53
|
+
}
|
|
54
|
+
if (!storage.settle(result.paymentId)) {
|
|
55
|
+
break; // lost race; another request settled first
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Engine handles debit for credit mode
|
|
59
|
+
if (result.mode === 'credit' && result.paymentId && cost > 0) {
|
|
60
|
+
const debit = storage.debit(result.paymentId, cost, result.currency);
|
|
61
|
+
if (!debit.success) {
|
|
62
|
+
// Insufficient balance — fall through to challenge
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const remaining = result.mode === 'credit'
|
|
67
|
+
? storage.balance(result.paymentId, result.currency)
|
|
68
|
+
: undefined;
|
|
69
|
+
// Fire onPayment exactly once per paymentHash (first time seen)
|
|
70
|
+
if (result.paymentId && !estimatedCosts.has(result.paymentId)) {
|
|
71
|
+
const creditedAmount = (remaining ?? 0) + cost;
|
|
72
|
+
config.onPayment?.({
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
paymentHash: result.paymentId,
|
|
75
|
+
amountSats: creditedAmount,
|
|
76
|
+
currency: result.currency,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Track estimated cost with currency for reconciliation
|
|
80
|
+
if (result.paymentId) {
|
|
81
|
+
// Evict stale entries
|
|
82
|
+
if (estimatedCosts.size >= MAX_ESTIMATED_COSTS) {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
for (const [key, entry] of estimatedCosts) {
|
|
85
|
+
if (now - entry.ts > MAX_AGE_MS)
|
|
86
|
+
estimatedCosts.delete(key);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
estimatedCosts.set(result.paymentId, { cost, ts: Date.now(), currency: result.currency });
|
|
90
|
+
}
|
|
91
|
+
// Build response headers
|
|
92
|
+
const headers = {};
|
|
93
|
+
if (remaining !== undefined) {
|
|
94
|
+
headers['X-Credit-Balance'] = String(remaining);
|
|
95
|
+
}
|
|
96
|
+
if (result.customCaveats) {
|
|
97
|
+
for (const [key, value] of Object.entries(result.customCaveats)) {
|
|
98
|
+
if (/^[a-zA-Z0-9_]+$/.test(key)) {
|
|
99
|
+
headers[`X-Toll-Caveat-${key.charAt(0).toUpperCase() + key.slice(1)}`] = value.replace(/[\r\n]/g, '');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
config.onRequest?.({
|
|
29
104
|
timestamp: new Date().toISOString(),
|
|
30
|
-
|
|
31
|
-
|
|
105
|
+
endpoint: path,
|
|
106
|
+
satsDeducted: cost,
|
|
107
|
+
remainingBalance: remaining ?? 0,
|
|
108
|
+
latencyMs: Date.now() - start,
|
|
109
|
+
authenticated: true,
|
|
110
|
+
clientIp: req.ip,
|
|
111
|
+
currency: result.currency,
|
|
32
112
|
});
|
|
113
|
+
return {
|
|
114
|
+
action: 'proxy',
|
|
115
|
+
upstream,
|
|
116
|
+
headers,
|
|
117
|
+
paymentHash: result.paymentId,
|
|
118
|
+
estimatedCost: cost,
|
|
119
|
+
creditBalance: remaining,
|
|
120
|
+
};
|
|
33
121
|
}
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
};
|
|
122
|
+
// Rail detected credentials but verification failed — fall through to challenge
|
|
123
|
+
break;
|
|
49
124
|
}
|
|
50
|
-
// Fall through to issue a new challenge if authorisation failed
|
|
51
125
|
}
|
|
52
|
-
//
|
|
126
|
+
// No rail authenticated — check free tier
|
|
53
127
|
if (freeTier) {
|
|
54
128
|
const check = freeTier.check(req.ip);
|
|
55
129
|
if (check.allowed) {
|
|
@@ -70,103 +144,52 @@ export function createTollBooth(config) {
|
|
|
70
144
|
};
|
|
71
145
|
}
|
|
72
146
|
}
|
|
73
|
-
// Issue
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
147
|
+
// Issue multi-rail challenge
|
|
148
|
+
const challengeHeaders = {};
|
|
149
|
+
const challengeBody = {};
|
|
150
|
+
const normalisedPrice = normalisedPricing[req.path] ?? { sats: defaultAmount };
|
|
151
|
+
for (const rail of rails) {
|
|
152
|
+
if (rail.canChallenge && !rail.canChallenge(normalisedPrice))
|
|
153
|
+
continue;
|
|
154
|
+
const fragment = await rail.challenge(req.path, normalisedPrice);
|
|
155
|
+
Object.assign(challengeHeaders, fragment.headers);
|
|
156
|
+
Object.assign(challengeBody, fragment.body);
|
|
80
157
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
158
|
+
challengeBody.message = 'Payment required.';
|
|
159
|
+
// Store invoice data from L402 rail if present
|
|
160
|
+
const l402Data = challengeBody.l402;
|
|
161
|
+
if (l402Data?.payment_hash) {
|
|
162
|
+
const paymentHash = l402Data.payment_hash;
|
|
163
|
+
const statusToken = randomBytes(32).toString('hex');
|
|
164
|
+
storage.storeInvoice(paymentHash, l402Data.invoice ?? '', defaultAmount, l402Data.macaroon, statusToken, req.ip);
|
|
165
|
+
l402Data.payment_url = `/invoice-status/${paymentHash}?token=${statusToken}`;
|
|
166
|
+
l402Data.status_token = statusToken;
|
|
84
167
|
}
|
|
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
168
|
config.onChallenge?.({
|
|
90
169
|
timestamp: new Date().toISOString(),
|
|
91
170
|
endpoint: path,
|
|
92
171
|
amountSats: defaultAmount,
|
|
93
172
|
clientIp: req.ip,
|
|
94
173
|
});
|
|
95
|
-
|
|
96
|
-
? { 'WWW-Authenticate': `L402 macaroon="${macaroon}", invoice="${bolt11}"`, 'X-Powered-By': 'toll-booth' }
|
|
97
|
-
: { 'WWW-Authenticate': `L402 macaroon="${macaroon}"`, 'X-Powered-By': 'toll-booth' };
|
|
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
|
-
};
|
|
174
|
+
return { action: 'challenge', status: 402, headers: challengeHeaders, body: challengeBody };
|
|
113
175
|
},
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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;
|
|
176
|
+
reconcile(paymentHash, actualCost) {
|
|
177
|
+
const entry = estimatedCosts.get(paymentHash);
|
|
178
|
+
if (entry === undefined) {
|
|
179
|
+
return { adjusted: false, newBalance: storage.balance(paymentHash), delta: 0 };
|
|
151
180
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
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'));
|
|
181
|
+
const delta = entry.cost - actualCost;
|
|
182
|
+
if (delta === 0) {
|
|
183
|
+
return { adjusted: false, newBalance: storage.balance(paymentHash, entry.currency), delta: 0 };
|
|
184
|
+
}
|
|
185
|
+
const newBalance = storage.adjustCredits(paymentHash, delta, entry.currency);
|
|
186
|
+
if (delta < 0) {
|
|
187
|
+
const unit = entry.currency === 'usd' ? 'cents' : 'sats';
|
|
188
|
+
console.warn(`[toll-booth] Reconciliation: additional charge of ${-delta} ${unit} for ${paymentHash}, new balance ${newBalance}`);
|
|
189
|
+
}
|
|
190
|
+
estimatedCosts.delete(paymentHash);
|
|
191
|
+
return { adjusted: true, newBalance, delta };
|
|
192
|
+
},
|
|
193
|
+
};
|
|
171
194
|
}
|
|
172
195
|
//# sourceMappingURL=toll-booth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toll-booth.js","sourceRoot":"","sources":["../../src/core/toll-booth.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"toll-booth.js","sourceRoot":"","sources":["../../src/core/toll-booth.ts"],"names":[],"mappings":"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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LightningBackend, CreditTier, PaymentEvent, RequestEvent, ChallengeEvent } from '../types.js';
|
|
2
2
|
import type { StorageBackend, StoredInvoice } from '../storage/interface.js';
|
|
3
|
+
import type { PaymentRail, PriceInfo, PricingEntry } from './payment-rail.js';
|
|
3
4
|
/** Matches a valid 64-char lowercase hex payment hash. */
|
|
4
5
|
export declare const PAYMENT_HASH_RE: RegExp;
|
|
5
6
|
export interface TollBoothRequest {
|
|
@@ -13,11 +14,13 @@ export type TollBoothResult = {
|
|
|
13
14
|
action: 'proxy';
|
|
14
15
|
upstream: string;
|
|
15
16
|
headers: Record<string, string>;
|
|
17
|
+
paymentHash?: string;
|
|
18
|
+
estimatedCost?: number;
|
|
16
19
|
creditBalance?: number;
|
|
17
20
|
freeRemaining?: number;
|
|
18
21
|
} | {
|
|
19
22
|
action: 'challenge';
|
|
20
|
-
status: 402;
|
|
23
|
+
status: 401 | 402;
|
|
21
24
|
headers: Record<string, string>;
|
|
22
25
|
body: Record<string, unknown>;
|
|
23
26
|
} | {
|
|
@@ -25,11 +28,16 @@ export type TollBoothResult = {
|
|
|
25
28
|
upstream: string;
|
|
26
29
|
headers: Record<string, string>;
|
|
27
30
|
};
|
|
31
|
+
export interface ReconcileResult {
|
|
32
|
+
adjusted: boolean;
|
|
33
|
+
newBalance: number;
|
|
34
|
+
delta: number;
|
|
35
|
+
}
|
|
28
36
|
export interface TollBoothCoreConfig {
|
|
29
37
|
/** Lightning backend. Optional when Cashu-only mode is used. */
|
|
30
38
|
backend?: LightningBackend;
|
|
31
39
|
storage: StorageBackend;
|
|
32
|
-
pricing: Record<string,
|
|
40
|
+
pricing: Record<string, PricingEntry>;
|
|
33
41
|
upstream: string;
|
|
34
42
|
defaultInvoiceAmount?: number;
|
|
35
43
|
strictPricing?: boolean;
|
|
@@ -38,16 +46,23 @@ export interface TollBoothCoreConfig {
|
|
|
38
46
|
requestsPerDay: number;
|
|
39
47
|
};
|
|
40
48
|
creditTiers?: CreditTier[];
|
|
49
|
+
rails?: PaymentRail[];
|
|
50
|
+
normalisedPricing?: Record<string, PriceInfo>;
|
|
41
51
|
onPayment?: (event: PaymentEvent) => void;
|
|
42
52
|
onRequest?: (event: RequestEvent) => void;
|
|
43
53
|
onChallenge?: (event: ChallengeEvent) => void;
|
|
44
54
|
}
|
|
45
55
|
export interface CreateInvoiceRequest {
|
|
46
56
|
amountSats?: number;
|
|
57
|
+
caveats?: string[];
|
|
58
|
+
/** Injected by adapter; not client-settable. Used for invoice rate limiting. */
|
|
59
|
+
clientIp?: string;
|
|
47
60
|
}
|
|
48
61
|
export interface CreateInvoiceResult {
|
|
49
62
|
success: boolean;
|
|
50
63
|
error?: string;
|
|
64
|
+
/** HTTP status code hint for the adapter. Defaults to 400 on error. */
|
|
65
|
+
status?: number;
|
|
51
66
|
tiers?: CreditTier[];
|
|
52
67
|
data?: {
|
|
53
68
|
bolt11: string;
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC3G,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC3G,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC5E,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAE7E,0DAA0D;AAC1D,eAAO,MAAM,eAAe,QAAmB,CAAA;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC3C,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACpK;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC1G;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAA;AAEzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,CAAA;IACrC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;IAC1B,KAAK,CAAC,EAAE,WAAW,EAAE,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAC7C,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IACzC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IACzC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;CAC9C;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,IAAI,CAAC,EAAE;QACL,MAAM,EAAE,MAAM,CAAA;QACd,WAAW,EAAE,MAAM,CAAA;QACnB,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAA;CAAE,CAAA;AAExD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAA;CAAE,CAAA"}
|
package/dist/core/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAKA,0DAA0D;AAC1D,MAAM,CAAC,MAAM,eAAe,GAAG,gBAAgB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x402-rail.d.ts","sourceRoot":"","sources":["../../src/core/x402-rail.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAkD,MAAM,mBAAmB,CAAA;AACpG,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,iBAAiB,CAAA;AAGlE,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,CA+ElE"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { DEFAULT_USDC_ASSETS } from './x402-types.js';
|
|
2
|
+
export function createX402Rail(config) {
|
|
3
|
+
const { receiverAddress, network, asset = DEFAULT_USDC_ASSETS[network], facilitator, creditMode = true, facilitatorUrl, storage, } = config;
|
|
4
|
+
return {
|
|
5
|
+
type: 'x402',
|
|
6
|
+
creditSupported: true,
|
|
7
|
+
canChallenge(price) {
|
|
8
|
+
return price.usd !== undefined;
|
|
9
|
+
},
|
|
10
|
+
detect(req) {
|
|
11
|
+
return req.headers['x-payment'] !== undefined;
|
|
12
|
+
},
|
|
13
|
+
async challenge(_route, price) {
|
|
14
|
+
return {
|
|
15
|
+
headers: { 'X-Payment-Required': 'x402' },
|
|
16
|
+
body: {
|
|
17
|
+
x402: {
|
|
18
|
+
receiver: receiverAddress,
|
|
19
|
+
network,
|
|
20
|
+
asset,
|
|
21
|
+
amount_usd: price.usd,
|
|
22
|
+
...(facilitatorUrl && { facilitator: facilitatorUrl }),
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
async verify(req) {
|
|
28
|
+
const raw = req.headers['x-payment'];
|
|
29
|
+
if (!raw) {
|
|
30
|
+
return { authenticated: false, paymentId: '', mode: 'per-request', currency: 'usd' };
|
|
31
|
+
}
|
|
32
|
+
let payload;
|
|
33
|
+
try {
|
|
34
|
+
payload = JSON.parse(raw);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { authenticated: false, paymentId: '', mode: 'per-request', currency: 'usd' };
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const result = await facilitator.verify(payload);
|
|
41
|
+
if (!result.valid) {
|
|
42
|
+
return { authenticated: false, paymentId: result.txHash || '', mode: 'per-request', currency: 'usd' };
|
|
43
|
+
}
|
|
44
|
+
// Credit mode: persist balance to storage (mirrors L402 rail's settleWithCredit)
|
|
45
|
+
if (creditMode && storage && !storage.isSettled(result.txHash)) {
|
|
46
|
+
storage.settleWithCredit(result.txHash, result.amount, undefined, 'usd');
|
|
47
|
+
}
|
|
48
|
+
const creditBalance = creditMode && storage
|
|
49
|
+
? storage.balance(result.txHash, 'usd')
|
|
50
|
+
: (creditMode ? result.amount : undefined);
|
|
51
|
+
return {
|
|
52
|
+
authenticated: true,
|
|
53
|
+
paymentId: result.txHash,
|
|
54
|
+
mode: creditMode ? 'credit' : 'per-request',
|
|
55
|
+
creditBalance,
|
|
56
|
+
currency: 'usd',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return { authenticated: false, paymentId: '', mode: 'per-request', currency: 'usd' };
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=x402-rail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x402-rail.js","sourceRoot":"","sources":["../../src/core/x402-rail.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,UAAU,cAAc,CAAC,MAAsB;IACnD,MAAM,EACJ,eAAe,EACf,OAAO,EACP,KAAK,GAAG,mBAAmB,CAAC,OAAO,CAAC,EACpC,WAAW,EACX,UAAU,GAAG,IAAI,EACjB,cAAc,EACd,OAAO,GACR,GAAG,MAAM,CAAA;IAEV,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,eAAe,EAAE,IAAI;QAErB,YAAY,CAAC,KAAgB;YAC3B,OAAO,KAAK,CAAC,GAAG,KAAK,SAAS,CAAA;QAChC,CAAC;QAED,MAAM,CAAC,GAAqB;YAC1B,OAAO,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,SAAS,CAAA;QAC/C,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,MAAc,EAAE,KAAgB;YAC9C,OAAO;gBACL,OAAO,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE;gBACzC,IAAI,EAAE;oBACJ,IAAI,EAAE;wBACJ,QAAQ,EAAE,eAAe;wBACzB,OAAO;wBACP,KAAK;wBACL,UAAU,EAAE,KAAK,CAAC,GAAG;wBACrB,GAAG,CAAC,cAAc,IAAI,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC;qBACvD;iBACF;aACF,CAAA;QACH,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,GAAqB;YAChC,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;YACpC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;YACtF,CAAC;YAED,IAAI,OAAoB,CAAA;YACxB,IAAI,CAAC;gBACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;YACtF,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;gBAChD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;oBAClB,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;gBACvG,CAAC;gBAED,iFAAiF;gBACjF,IAAI,UAAU,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC/D,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,CAAA;gBAC1E,CAAC;gBAED,MAAM,aAAa,GAAG,UAAU,IAAI,OAAO;oBACzC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;oBACvC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;gBAE5C,OAAO;oBACL,aAAa,EAAE,IAAI;oBACnB,SAAS,EAAE,MAAM,CAAC,MAAM;oBACxB,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa;oBAC3C,aAAa;oBACb,QAAQ,EAAE,KAAK;iBAChB,CAAA;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;YACtF,CAAC;QACH,CAAC;KAEF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface X402Payment {
|
|
2
|
+
signature: string;
|
|
3
|
+
sender: string;
|
|
4
|
+
amount: number;
|
|
5
|
+
network: string;
|
|
6
|
+
nonce: string;
|
|
7
|
+
}
|
|
8
|
+
export interface X402VerifyResult {
|
|
9
|
+
valid: boolean;
|
|
10
|
+
txHash: string;
|
|
11
|
+
amount: number;
|
|
12
|
+
sender: string;
|
|
13
|
+
}
|
|
14
|
+
export interface X402Facilitator {
|
|
15
|
+
verify(payload: X402Payment): Promise<X402VerifyResult>;
|
|
16
|
+
}
|
|
17
|
+
export interface X402RailConfig {
|
|
18
|
+
receiverAddress: string;
|
|
19
|
+
network: string;
|
|
20
|
+
asset?: string;
|
|
21
|
+
facilitator: X402Facilitator;
|
|
22
|
+
creditMode?: boolean;
|
|
23
|
+
facilitatorUrl?: string;
|
|
24
|
+
/** Storage backend — required for credit mode to persist balances. Injected by Booth. */
|
|
25
|
+
storage?: import('../storage/interface.js').StorageBackend;
|
|
26
|
+
}
|
|
27
|
+
/** Default USDC contract addresses by network */
|
|
28
|
+
export declare const DEFAULT_USDC_ASSETS: Record<string, string>;
|
|
29
|
+
//# sourceMappingURL=x402-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x402-types.d.ts","sourceRoot":"","sources":["../../src/core/x402-types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;CACxD;AAED,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,eAAe,CAAA;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,yFAAyF;IACzF,OAAO,CAAC,EAAE,OAAO,yBAAyB,EAAE,cAAc,CAAA;CAC3D;AAED,iDAAiD;AACjD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAItD,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Default USDC contract addresses by network */
|
|
2
|
+
export const DEFAULT_USDC_ASSETS = {
|
|
3
|
+
'base': '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
4
|
+
'base-sepolia': '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
5
|
+
'polygon': '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=x402-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x402-types.js","sourceRoot":"","sources":["../../src/core/x402-types.ts"],"names":[],"mappings":"AA8BA,iDAAiD;AACjD,MAAM,CAAC,MAAM,mBAAmB,GAA2B;IACzD,MAAM,EAAE,4CAA4C;IACpD,cAAc,EAAE,4CAA4C;IAC5D,SAAS,EAAE,4CAA4C;CACxD,CAAA"}
|
package/dist/demo.js
CHANGED
|
@@ -122,8 +122,8 @@ export async function startDemo() {
|
|
|
122
122
|
storage,
|
|
123
123
|
rootKey,
|
|
124
124
|
upstream: upstreamUrl,
|
|
125
|
-
pricing: { '/api/joke':
|
|
126
|
-
defaultInvoiceAmount:
|
|
125
|
+
pricing: { '/api/joke': 21 },
|
|
126
|
+
defaultInvoiceAmount: 21,
|
|
127
127
|
freeTier: { requestsPerDay: 3 },
|
|
128
128
|
onRequest: (event) => {
|
|
129
129
|
if (event.authenticated) {
|
|
@@ -150,7 +150,7 @@ export async function startDemo() {
|
|
|
150
150
|
storage,
|
|
151
151
|
rootKey,
|
|
152
152
|
tiers: [],
|
|
153
|
-
defaultAmount:
|
|
153
|
+
defaultAmount: 21,
|
|
154
154
|
});
|
|
155
155
|
const invoiceStatusHandler = createWebStandardInvoiceStatusHandler({
|
|
156
156
|
backend,
|
|
@@ -183,8 +183,8 @@ export async function startDemo() {
|
|
|
183
183
|
console.log('');
|
|
184
184
|
console.log(` ${YELLOW}This is a demo - the mock payment will auto-settle in ~1s.${RESET}`);
|
|
185
185
|
console.log(` ${DIM}Wait a moment, then check the terminal for your curl command.${RESET}`);
|
|
186
|
-
if (body.payment_url) {
|
|
187
|
-
console.log(` ${DIM}Payment page: http://localhost:${port}${body.payment_url}${RESET}`);
|
|
186
|
+
if (body.l402?.payment_url) {
|
|
187
|
+
console.log(` ${DIM}Payment page: http://localhost:${port}${body.l402.payment_url}${RESET}`);
|
|
188
188
|
}
|
|
189
189
|
console.log('');
|
|
190
190
|
}
|
|
@@ -210,7 +210,7 @@ export async function startDemo() {
|
|
|
210
210
|
console.log('');
|
|
211
211
|
console.log(` ${BOLD}Pricing${RESET}`);
|
|
212
212
|
console.log(` ${DIM}Route${RESET} ${DIM}Cost${RESET} ${DIM}Free tier${RESET}`);
|
|
213
|
-
console.log(` /api/joke
|
|
213
|
+
console.log(` /api/joke 21 sats 3 reqs/day`);
|
|
214
214
|
console.log('');
|
|
215
215
|
console.log(` ${BOLD}Try it:${RESET}`);
|
|
216
216
|
console.log(` ${DIM}$${RESET} curl http://localhost:${port}/api/joke`);
|