@lizard-build/cli 4.0.23 → 4.0.24

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.
@@ -0,0 +1,110 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
+ import { api, APIError, apiErrorFrom, errorLink, isPaymentRequired, setAccessToken, setBaseURL } from "../../src/lib/api.js";
3
+
4
+ const fetchMock = vi.fn();
5
+
6
+ function reply(status: number, body: unknown) {
7
+ return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
8
+ }
9
+
10
+ const proBody = {
11
+ error: "INSUFFICIENT_CREDITS",
12
+ code: "PAYMENT_REQUIRED",
13
+ status: "trial_available",
14
+ message: "Start your 7-day Pro trial with $5 in credits to deploy. No charge today, then $19/month.",
15
+ subscribeUrl: "https://lizard.build/profile/account-billing?subscribe=1",
16
+ billingUrl: "https://lizard.build/profile/account-billing",
17
+ topupUrl: "https://lizard.build/profile/account-credits",
18
+ balanceCents: 0,
19
+ availableCents: 100,
20
+ };
21
+
22
+ beforeEach(() => {
23
+ fetchMock.mockReset();
24
+ vi.stubGlobal("fetch", fetchMock);
25
+ setBaseURL("https://lizard.build");
26
+ setAccessToken("test-token");
27
+ });
28
+ afterEach(() => vi.unstubAllGlobals());
29
+
30
+ describe("402 from a create path", () => {
31
+ test("prints the platform's sentence and the trial link", async () => {
32
+ fetchMock.mockResolvedValue(reply(402, proBody));
33
+ const err = await api.post("/api/projects/p1/apps", {}).catch((e) => e);
34
+ expect(err).toBeInstanceOf(APIError);
35
+ expect(err.status).toBe(402);
36
+ expect(err.code).toBe("PAYMENT_REQUIRED");
37
+ expect(err.message).toBe(
38
+ `${proBody.message}\n Start your trial: ${proBody.subscribeUrl} (or run \`lizard billing start\`)`,
39
+ );
40
+ expect(err.body).toEqual(proBody);
41
+ expect(isPaymentRequired(err)).toBe(true);
42
+ });
43
+
44
+ test("trial already used links Start Pro", () => {
45
+ const err = apiErrorFrom(402, "Payment Required", {
46
+ ...proBody, status: "subscription_required", message: "Start Pro ($19/month, taxes included) to deploy.",
47
+ });
48
+ expect(err.message).toBe(
49
+ `Start Pro ($19/month, taxes included) to deploy.\n Start Pro: ${proBody.subscribeUrl} (or run \`lizard billing start\`)`,
50
+ );
51
+ });
52
+
53
+ test.each(["past_due", "paused"])("%s links Billing", (status) => {
54
+ const err = apiErrorFrom(402, "Payment Required", { ...proBody, status, message: "A payment failed." });
55
+ expect(err.message).toBe(`A payment failed.\n Billing: ${proBody.billingUrl}`);
56
+ });
57
+
58
+ test("used trial credits link Billing and start-now", () => {
59
+ const err = apiErrorFrom(402, "Payment Required", { ...proBody, status: "trial_credits_used", message: "Your trial credits are used up." });
60
+ expect(err.message).toBe(
61
+ `Your trial credits are used up.\n Billing: ${proBody.billingUrl} (or run \`lizard billing start-now\`)`,
62
+ );
63
+ });
64
+
65
+ test("old prepaid credits accounts link the Credits page", () => {
66
+ const err = apiErrorFrom(402, "Payment Required", { ...proBody, status: "frozen", message: "Your credits are used up." });
67
+ expect(err.message).toBe(`Your credits are used up.\n Add credits: ${proBody.topupUrl}`);
68
+ });
69
+
70
+ test("an older server's body (no code, no billingUrl) still gets a link", () => {
71
+ const old = {
72
+ error: "INSUFFICIENT_CREDITS", status: "credits_required", message: "Add credits to keep deploying.",
73
+ topupUrl: "https://lizard.build/profile/account-credits", balanceCents: 0, availableCents: 0,
74
+ };
75
+ const err = apiErrorFrom(402, "Payment Required", old);
76
+ expect(err.code).toBe("INSUFFICIENT_CREDITS");
77
+ expect(isPaymentRequired(err)).toBe(true);
78
+ expect(err.message).toBe(`Add credits to keep deploying.\n Add credits: ${old.topupUrl}`);
79
+ });
80
+
81
+ test("a code-only 402 body is recognized by code alone", () => {
82
+ const err = apiErrorFrom(402, "Payment Required", { code: "PAYMENT_REQUIRED", message: "Start Pro.", billingUrl: proBody.billingUrl });
83
+ expect(isPaymentRequired(err)).toBe(true);
84
+ expect(err.message).toBe(`Start Pro.\n Billing: ${proBody.billingUrl}`);
85
+ });
86
+ });
87
+
88
+ describe("other errors with a link", () => {
89
+ test("an unpaid invoice links the invoice", () => {
90
+ const err = apiErrorFrom(402, "Payment Required", {
91
+ error: "UNPAID_INVOICE", message: "Pay your open invoice before starting Pro again", invoiceUrl: "https://invoice.stripe.com/i/abc",
92
+ });
93
+ expect(err.code).toBe("UNPAID_INVOICE");
94
+ expect(isPaymentRequired(err)).toBe(false);
95
+ expect(err.message).toBe("Pay your open invoice before starting Pro again\n Pay the open invoice: https://invoice.stripe.com/i/abc");
96
+ });
97
+
98
+ test("closed credits endpoints link Billing", () => {
99
+ const err = apiErrorFrom(409, "Conflict", {
100
+ error: "CREDITS_NOT_AVAILABLE", message: "Credits are replaced by the Pro plan. Open Billing to start Pro.", billingUrl: proBody.billingUrl,
101
+ });
102
+ expect(err.message).toBe(`Credits are replaced by the Pro plan. Open Billing to start Pro.\n Billing: ${proBody.billingUrl}`);
103
+ });
104
+
105
+ test("plain errors are unchanged", () => {
106
+ expect(apiErrorFrom(404, "Not Found", { error: "App not found" }).message).toBe("App not found");
107
+ expect(apiErrorFrom(500, "Internal Server Error", null).message).toBe("Internal Server Error");
108
+ expect(errorLink({ error: "x", billingUrl: "javascript:alert(1)" })).toBeNull();
109
+ });
110
+ });
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerCredits(program: Command): void;
@@ -1,331 +0,0 @@
1
- import { dollarsToCents, getX402Quote, payX402, getX402Status } from "../lib/x402.js";
2
- import chalk from "chalk";
3
- import open from "open";
4
- import * as p from "@clack/prompts";
5
- import { api, withQuery, APIError } from "../lib/api.js";
6
- import { success, isJSONMode, printJSON, table, isTTY, timeAgo } from "../lib/format.js";
7
- // `lizard credits` — account-scoped prepaid balance (LIZARD-177). Unlike
8
- // most commands this never takes --project/--service: billing belongs to
9
- // the user, not a workspace. See docs/credits-api.md on the platform repo
10
- // for the contract this is built against.
11
- function fmtCents(cents) {
12
- const sign = cents < 0 ? "-" : "";
13
- return `${sign}$${(Math.abs(cents) / 100).toFixed(2)}`;
14
- }
15
- /** Accepts "10", "10.5", "$10.50" -> cents. Throws on anything else. */
16
- function parseDollarsToCents(input) {
17
- const cleaned = input.trim().replace(/^\$/, "");
18
- const value = Number(cleaned);
19
- if (!Number.isFinite(value) || value <= 0) {
20
- throw new Error(`Invalid amount: "${input}". Use a positive dollar amount, e.g. 10 or 10.50.`);
21
- }
22
- return Math.round(value * 100);
23
- }
24
- function statusLabel(status) {
25
- switch (status) {
26
- case "active": return chalk.green(status);
27
- case "grace": return chalk.yellow(status);
28
- case "frozen": return chalk.red(status);
29
- default: return status;
30
- }
31
- }
32
- async function printBalance() {
33
- const view = await api.get("/api/billing/balance");
34
- if (isJSONMode()) {
35
- printJSON(view);
36
- return view;
37
- }
38
- console.log(`${chalk.bold(fmtCents(view.balanceCents))} ${statusLabel(view.status)} ${chalk.dim(`(${view.plan})`)}`);
39
- if (view.neverFreeze) {
40
- console.log(chalk.dim("Exempt from billing freeze — balance can go negative with no enforcement."));
41
- }
42
- else {
43
- if (view.overdraftLimitCents != null) {
44
- console.log(chalk.dim(`Overdraft limit: `) + fmtCents(-view.overdraftLimitCents));
45
- }
46
- if (view.availableCents != null) {
47
- console.log(chalk.dim(`Available before freeze: `) + fmtCents(view.availableCents));
48
- }
49
- if (view.graceSince) {
50
- const deadline = view.graceSince + view.graceHours * 3_600_000;
51
- const hoursLeft = Math.max(0, Math.round((deadline - Date.now()) / 3_600_000));
52
- console.log(chalk.yellow(`In grace since ${timeAgo(view.graceSince)} — freezes in ~${hoursLeft}h if unpaid.`));
53
- }
54
- }
55
- if (view.expiringCents > 0 && view.expiringAt) {
56
- console.log(chalk.dim(`Expiring: `) + `${fmtCents(view.expiringCents)} by ${new Date(view.expiringAt).toLocaleDateString()}`);
57
- }
58
- if (view.hourlyRateCents > 0) {
59
- const rate = chalk.dim(`(~${fmtCents(view.hourlyRateCents)}/hr)`);
60
- if (view.runwayHours != null) {
61
- const days = (view.runwayHours / 24).toFixed(1);
62
- console.log(chalk.dim(`Runway: `) + `~${view.runwayHours}h (${days}d) at current usage ${rate}`);
63
- }
64
- else {
65
- console.log(chalk.dim(`Current usage rate: `) + rate);
66
- }
67
- }
68
- console.log(chalk.dim("Auto top-up: ") +
69
- (view.autoTopup.enabled
70
- ? `on — tops up ${fmtCents(view.autoTopup.amountCents)} when balance drops below ${fmtCents(view.autoTopup.thresholdCents)}`
71
- : "off"));
72
- if (view.autoTopup.disabledReason) {
73
- console.log(chalk.yellow(` disabled: ${view.autoTopup.disabledReason}`));
74
- }
75
- if (view.pendingPromo) {
76
- console.log(chalk.dim("Pending promo: ") + `${view.pendingPromo.code} (${fmtCents(view.pendingPromo.creditCents)})`);
77
- }
78
- return view;
79
- }
80
- export function registerCredits(program) {
81
- const cmd = program
82
- .command("credits")
83
- .alias("billing")
84
- .description("Account balance, usage, and top-ups (prepaid credits)")
85
- .action(async () => {
86
- await printBalance();
87
- });
88
- cmd
89
- .command("balance")
90
- .description("Show current balance, status, and runway")
91
- .action(async () => {
92
- await printBalance();
93
- });
94
- cmd
95
- .command("transactions")
96
- .alias("ledger")
97
- .description("List recent balance transactions")
98
- .option("-l, --limit <n>", "Max results (1-100)", "20")
99
- .option("-c, --cursor <cursor>", "Pagination cursor from a previous call")
100
- .option("-u, --usage", "Include daily usage-deduction rows")
101
- .action(async (opts) => {
102
- const page = await api.get(withQuery("/api/billing/transactions", {
103
- limit: opts.limit,
104
- cursor: opts.cursor,
105
- includeUsage: opts.usage ? "1" : undefined,
106
- }));
107
- if (isJSONMode()) {
108
- printJSON(page);
109
- return;
110
- }
111
- if (page.items.length === 0) {
112
- console.log("No transactions.");
113
- return;
114
- }
115
- table(["When", "Kind", "Amount", "Balance after", "Description"], page.items.map((t) => [
116
- timeAgo(t.createdAt),
117
- t.kind,
118
- t.amountCents >= 0 ? chalk.green(fmtCents(t.amountCents)) : chalk.red(fmtCents(t.amountCents)),
119
- t.balanceAfterCents != null ? fmtCents(t.balanceAfterCents) : "—",
120
- t.description ?? "",
121
- ]));
122
- if (page.nextCursor) {
123
- console.log(chalk.dim(`\nMore results: lizard credits transactions --cursor ${page.nextCursor}`));
124
- }
125
- });
126
- // ── Payment methods ───────────────────────────────────────────────
127
- const pm = cmd.command("payment-methods").alias("pm").description("Manage saved payment methods");
128
- pm.command("list")
129
- .description("List saved payment methods")
130
- .action(async () => {
131
- const { items } = await api.get("/api/billing/payment-methods");
132
- if (isJSONMode()) {
133
- printJSON(items);
134
- return;
135
- }
136
- if (items.length === 0) {
137
- console.log("No saved payment methods. Add one with `lizard credits payment-methods add`.");
138
- return;
139
- }
140
- table(["ID", "Card", "Expires", "Default"], items.map((m) => [m.id, `${m.brand} •••• ${m.last4}`, `${m.expMonth}/${m.expYear}`, m.isDefault ? "yes" : ""]));
141
- });
142
- pm.command("add")
143
- .description("Save a new card via a Stripe Checkout link")
144
- .option("--return-url <url>", "Where to redirect after saving the card")
145
- .option("--no-open", "Print the link instead of opening a browser")
146
- .action(async (opts) => {
147
- const { url } = await api.post("/api/billing/payment-methods/setup", {
148
- returnUrl: opts.returnUrl,
149
- });
150
- if (isJSONMode()) {
151
- printJSON({ url });
152
- return;
153
- }
154
- console.log(`Open this link to save a card:\n${chalk.cyan(url)}`);
155
- if (opts.open !== false && isTTY())
156
- await open(url).catch(() => { });
157
- });
158
- pm.command("remove <id>")
159
- .alias("rm")
160
- .description("Remove a saved payment method")
161
- .option("-y, --yes", "Skip confirmation")
162
- .action(async (id, opts) => {
163
- if (!opts.yes) {
164
- if (!isTTY())
165
- throw new Error("Use -y to confirm in non-interactive mode");
166
- const confirm = await p.confirm({ message: `Remove payment method ${chalk.bold(id)}?` });
167
- if (p.isCancel(confirm) || !confirm)
168
- process.exit(5);
169
- }
170
- const out = await api.delete(`/api/billing/payment-methods/${encodeURIComponent(id)}`);
171
- if (isJSONMode()) {
172
- printJSON(out);
173
- }
174
- else {
175
- success("Payment method removed");
176
- }
177
- });
178
- // ── Top up ────────────────────────────────────────────────────────
179
- cmd
180
- .command("topup <amount>")
181
- .alias("purchase")
182
- .description("Buy credits (amount in dollars, e.g. `lizard credits topup 20`)")
183
- .option("--method <method>", "card, crypto, or x402", "card")
184
- .option("--quote", "Show an x402 quote without paying")
185
- .option("--max-total <amount>", "Maximum x402 payment including fees, in dollars")
186
- .option("--request-id <uuid>", "Reuse this ID to resume an x402 purchase")
187
- .option("-y, --yes", "Confirm the x402 payment within --max-total")
188
- .option("--return-url <url>", "Where to redirect after payment")
189
- .option("--no-open", "Print the checkout link instead of opening a browser")
190
- .action(async (amount, opts) => {
191
- if (opts.method === "x402") {
192
- const creditCents = dollarsToCents(amount);
193
- const quote = await getX402Quote(creditCents);
194
- if (opts.quote) {
195
- if (isJSONMode())
196
- printJSON(quote);
197
- else
198
- console.log(`Credits ${fmtCents(quote.creditCents)}, fee ${fmtCents(quote.feeCents)}, total ${fmtCents(quote.totalCents)} USDC on Base`);
199
- return;
200
- }
201
- if (!opts.maxTotal)
202
- throw new Error("Use --max-total to set a payment limit including fees.");
203
- const limit = dollarsToCents(opts.maxTotal);
204
- if (quote.totalCents > limit)
205
- throw new Error("Payment exceeds --max-total.");
206
- if (!opts.yes) {
207
- if (!isTTY())
208
- throw new Error("Use --yes and --max-total to authorize an x402 payment.");
209
- const confirmed = await p.confirm({ message: `Pay ${fmtCents(quote.totalCents)} USDC for ${fmtCents(quote.creditCents)} credits?` });
210
- if (p.isCancel(confirmed) || !confirmed)
211
- throw new Error("Payment cancelled.");
212
- }
213
- const result = await payX402(creditCents, limit, quote, opts.requestId);
214
- if (isJSONMode())
215
- printJSON(result);
216
- else
217
- console.log(`Payment ${result.status}. Attempt: ${result.attemptId}. Request: ${result.requestId}`);
218
- return;
219
- }
220
- if (opts.quote || opts.maxTotal || opts.requestId)
221
- throw new Error("--quote, --max-total, and --request-id require --method x402.");
222
- const creditCents = parseDollarsToCents(amount);
223
- if (opts.method !== "card" && opts.method !== "crypto") {
224
- throw new Error(`--method must be "card", "crypto", or "x402", got "${opts.method}"`);
225
- }
226
- let out;
227
- try {
228
- out = await api.post("/api/billing/purchase", {
229
- creditCents,
230
- paymentMethod: opts.method,
231
- returnUrl: opts.returnUrl,
232
- });
233
- }
234
- catch (e) {
235
- if (e instanceof APIError && e.status === 409 && e.message === "CREDITS_NOT_ENABLED") {
236
- throw new Error("Credits purchases aren't available yet on this account.");
237
- }
238
- throw e;
239
- }
240
- if (isJSONMode()) {
241
- printJSON(out);
242
- return;
243
- }
244
- if (!out.url)
245
- throw new Error("No checkout URL returned.");
246
- console.log(`Complete payment (${fmtCents(creditCents)}) at:\n${chalk.cyan(out.url)}`);
247
- if (opts.open !== false && isTTY())
248
- await open(out.url).catch(() => { });
249
- });
250
- cmd.command("payment-status <attempt-id>")
251
- .description("Check or recover an x402 credit purchase")
252
- .action(async (attemptId) => {
253
- const result = await getX402Status(attemptId);
254
- if (isJSONMode())
255
- printJSON(result);
256
- else
257
- console.log(`Payment ${result.status}. Attempt: ${result.attemptId}`);
258
- });
259
- // ── Auto top-up ───────────────────────────────────────────────────
260
- const auto = cmd.command("auto-topup").description("Show or configure automatic top-up");
261
- auto.action(async () => {
262
- const settings = await api.get("/api/billing/auto-topup");
263
- if (isJSONMode()) {
264
- printJSON(settings);
265
- return;
266
- }
267
- console.log(JSON.stringify(settings, null, 2));
268
- });
269
- auto
270
- .command("set")
271
- .description("Configure automatic top-up")
272
- .requiredOption("--payment-method <id...>", "Payment method ID(s) to charge, in priority order")
273
- .option("--threshold <amount>", "Top up when balance drops below this (dollars)", "5")
274
- .option("--amount <amount>", "How much to add per top-up (dollars)", "20")
275
- .option("--disable", "Turn auto top-up off instead of configuring it")
276
- .action(async (opts) => {
277
- const body = {
278
- enabled: !opts.disable,
279
- thresholdCents: parseDollarsToCents(opts.threshold),
280
- amountCents: parseDollarsToCents(opts.amount),
281
- paymentMethodIds: opts.paymentMethod,
282
- };
283
- const out = await api.put("/api/billing/auto-topup", body);
284
- if (isJSONMode()) {
285
- printJSON(out);
286
- return;
287
- }
288
- success(opts.disable ? "Auto top-up disabled" : "Auto top-up configured");
289
- });
290
- auto
291
- .command("run")
292
- .description("Manually trigger an auto top-up check now")
293
- .action(async () => {
294
- try {
295
- const out = await api.post("/api/billing/auto-topup/run", {});
296
- if (isJSONMode()) {
297
- printJSON(out);
298
- return;
299
- }
300
- if (out.ok) {
301
- success(`Topped up ${fmtCents(out.creditedCents ?? 0)} — new balance ${fmtCents(out.balanceCents ?? 0)}`);
302
- }
303
- }
304
- catch (e) {
305
- if (e instanceof APIError && e.status === 429) {
306
- throw new Error(`Auto top-up rate-limited: ${e.body?.reason ?? e.message}`);
307
- }
308
- throw e;
309
- }
310
- });
311
- // ── Promo codes ───────────────────────────────────────────────────
312
- cmd
313
- .command("promo <code>")
314
- .description("Redeem a promo code")
315
- .action(async (code) => {
316
- try {
317
- const out = await api.post("/api/billing/promo/redeem", { code });
318
- if (isJSONMode()) {
319
- printJSON(out);
320
- return;
321
- }
322
- success(`Redeemed ${fmtCents(out.creditCents)} — new balance ${fmtCents(out.balanceCents)}`);
323
- }
324
- catch (e) {
325
- if (e instanceof APIError)
326
- throw new Error(e.body?.message ?? e.message);
327
- throw e;
328
- }
329
- });
330
- }
331
- //# sourceMappingURL=credits.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"credits.js","sourceRoot":"","sources":["../../src/commands/credits.ts"],"names":[],"mappings":"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"}
@@ -1,23 +0,0 @@
1
- import type { PaymentRequired } from '@x402/core/types';
2
- export interface X402Quote {
3
- creditCents: number;
4
- feeCents: number;
5
- totalCents: number;
6
- network: string;
7
- asset: string;
8
- payTo: string;
9
- }
10
- export interface X402Result extends X402Quote {
11
- requestId: string;
12
- attemptId: string;
13
- status: string;
14
- transaction?: string | null;
15
- paymentIntentId?: string | null;
16
- }
17
- export declare function dollarsToCents(value: string): number;
18
- export declare function paymentOrigin(): string;
19
- export declare function validateQuote(q: X402Quote, creditCents: number, maxTotalCents?: number): void;
20
- export declare function getX402Quote(creditCents: number): Promise<X402Quote>;
21
- export declare function validateChallenge(value: PaymentRequired, q: X402Quote, origin: string): void;
22
- export declare function payX402(creditCents: number, maxTotalCents: number, expected: X402Quote, requestId?: string): Promise<X402Result>;
23
- export declare function getX402Status(attemptId: string): Promise<X402Result>;
package/dist/lib/x402.js DELETED
@@ -1,148 +0,0 @@
1
- import { createHash, randomUUID } from 'node:crypto';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import os from 'node:os';
5
- import { APIError, getBaseURL, getRequestToken } from './api.js';
6
- const NETWORK = 'eip155:8453';
7
- const ASSET = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
8
- const ENDPOINT = '/api/billing/purchase/x402';
9
- class PaymentInputError extends Error {
10
- }
11
- export function dollarsToCents(value) {
12
- if (!/^\d+(\.\d{1,2})?$/.test(value))
13
- throw new Error('Use a positive dollar amount with at most two decimal places.');
14
- const [whole, fraction = ''] = value.split('.');
15
- const cents = Number(whole) * 100 + Number(fraction.padEnd(2, '0'));
16
- if (!Number.isSafeInteger(cents) || cents <= 0)
17
- throw new Error('Invalid dollar amount.');
18
- return cents;
19
- }
20
- export function paymentOrigin() {
21
- const url = new URL(getBaseURL());
22
- const trusted = process.env.LIZARD_X402_TRUSTED_ORIGIN || 'https://lizard.build';
23
- if (url.protocol !== 'https:' || url.origin !== trusted || url.username || url.password || url.pathname !== '/' || url.search || url.hash) {
24
- throw new Error('x402 requires a trusted HTTPS API origin. Set LIZARD_X402_TRUSTED_ORIGIN explicitly for another server.');
25
- }
26
- return url.origin;
27
- }
28
- async function request(method, route, body, signature) {
29
- const token = getRequestToken();
30
- if (!token)
31
- throw new APIError(401, 'Sign in before buying credits.');
32
- const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
33
- if (signature)
34
- headers['PAYMENT-SIGNATURE'] = signature;
35
- return fetch(paymentOrigin() + route, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), redirect: 'error', signal: AbortSignal.timeout(45_000) });
36
- }
37
- async function json(res) {
38
- const value = await res.json();
39
- if (!res.ok)
40
- throw new APIError(res.status, value.error || value.message || 'Payment request failed.', value.error, value);
41
- return value;
42
- }
43
- export function validateQuote(q, creditCents, maxTotalCents = Number.MAX_SAFE_INTEGER) {
44
- if (!Number.isSafeInteger(q.creditCents) || !Number.isSafeInteger(q.feeCents) || !Number.isSafeInteger(q.totalCents) ||
45
- q.creditCents !== creditCents || q.feeCents < 0 || q.totalCents !== q.creditCents + q.feeCents || q.totalCents > maxTotalCents ||
46
- q.network !== NETWORK || q.asset.toLowerCase() !== ASSET.toLowerCase() || !/^0x[0-9a-fA-F]{40}$/.test(q.payTo))
47
- throw new Error('Payment quote is invalid or exceeds --max-total.');
48
- }
49
- export async function getX402Quote(creditCents) {
50
- const q = await json(await request('POST', `${ENDPOINT}/quote`, { creditCents }));
51
- validateQuote(q, creditCents);
52
- return q;
53
- }
54
- export function validateChallenge(value, q, origin) {
55
- const r = value.accepts?.[0];
56
- if (value.x402Version !== 2 || value.accepts?.length !== 1 || value.resource?.url !== origin + ENDPOINT || !r ||
57
- r.scheme !== 'exact' || r.network !== NETWORK || r.asset.toLowerCase() !== ASSET.toLowerCase() ||
58
- r.payTo.toLowerCase() !== q.payTo.toLowerCase() || r.amount !== String(BigInt(q.totalCents) * 10000n) ||
59
- r.maxTimeoutSeconds <= 0 || r.maxTimeoutSeconds > 300 ||
60
- (value.extensions && Object.keys(value.extensions).length > 0) ||
61
- (r.extra?.assetTransferMethod !== undefined && r.extra.assetTransferMethod !== 'eip3009') ||
62
- Object.keys(r.extra || {}).some(key => !['name', 'version', 'assetTransferMethod'].includes(key)) || r.extra?.name !== 'USD Coin' || r.extra?.version !== '2')
63
- throw new Error('Untrusted x402 payment challenge.');
64
- }
65
- function journalPath(creditCents, requestId) {
66
- if (requestId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(requestId))
67
- throw new Error('--request-id must be a UUID.');
68
- const account = createHash('sha256').update(paymentOrigin() + '\0' + getRequestToken()).digest('hex');
69
- const dir = path.join(process.env.LIZARD_HOME || os.homedir(), '.lizard', 'payments', account);
70
- fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
71
- const pending = path.join(dir, `pending-${creditCents}.json`);
72
- if (requestId && fs.existsSync(pending)) {
73
- const previous = JSON.parse(fs.readFileSync(pending, 'utf8'));
74
- if (previous.requestId === requestId)
75
- return pending;
76
- }
77
- return path.join(dir, requestId || `pending-${creditCents}.json`);
78
- }
79
- function persist(file, state) {
80
- const temporary = `${file}.${randomUUID()}.tmp`;
81
- fs.writeFileSync(temporary, JSON.stringify(state), { mode: 0o600, flag: 'wx' });
82
- fs.renameSync(temporary, file);
83
- }
84
- export async function payX402(creditCents, maxTotalCents, expected, requestId) {
85
- validateQuote(expected, creditCents, maxTotalCents);
86
- const origin = paymentOrigin();
87
- const file = journalPath(creditCents, requestId);
88
- let state = { requestId: requestId || randomUUID(), creditCents, maxTotalCents, origin };
89
- try {
90
- fs.writeFileSync(file, JSON.stringify(state), { flag: 'wx', mode: 0o600 });
91
- }
92
- catch (e) {
93
- if (e.code !== 'EEXIST')
94
- throw e;
95
- state = JSON.parse(fs.readFileSync(file, 'utf8'));
96
- if (state.origin !== origin || state.creditCents !== creditCents || state.maxTotalCents > maxTotalCents)
97
- throw new Error('Saved payment differs. Resume with the original amount and limit.');
98
- if (state.result?.status === 'paid') {
99
- if (requestId)
100
- return state.result;
101
- throw new Error(`Previous payment ${state.requestId} is paid. Use a new --request-id UUID for another purchase of this amount.`);
102
- }
103
- }
104
- try {
105
- let response = await request('POST', ENDPOINT, { creditCents, requestId: state.requestId }, state.signature);
106
- if (response.status === 402 && !state.signature) {
107
- const header = response.headers.get('PAYMENT-REQUIRED');
108
- if (!header || header.length > 16_384)
109
- throw new Error('Server returned 402 without a valid x402 challenge.');
110
- const challenge = JSON.parse(Buffer.from(header, 'base64').toString('utf8'));
111
- const body = await response.json();
112
- validateQuote(body, creditCents, Math.min(state.maxTotalCents, maxTotalCents));
113
- if (body.payTo.toLowerCase() !== expected.payTo.toLowerCase() || body.totalCents !== expected.totalCents)
114
- throw new Error('Quote changed. Request a fresh quote before paying.');
115
- validateChallenge(challenge, body, origin);
116
- const key = process.env.LIZARD_X402_PRIVATE_KEY;
117
- if (!key || !/^0x[0-9a-fA-F]{64}$/.test(key))
118
- throw new PaymentInputError('Set LIZARD_X402_PRIVATE_KEY to a funded Base wallet key through your secret manager.');
119
- const [{ x402Client }, { ExactEvmScheme }, { privateKeyToAccount }] = await Promise.all([
120
- import('@x402/core/client'), import('@x402/evm/exact/client'), import('viem/accounts'),
121
- ]);
122
- const signer = privateKeyToAccount(key);
123
- const client = new x402Client().register(NETWORK, new ExactEvmScheme(signer))
124
- .setSpendControls({ maxAmountPerPayment: `$${(Math.min(state.maxTotalCents, maxTotalCents) / 100).toFixed(2)}` });
125
- const payload = await client.createPaymentPayload(challenge);
126
- state.signature = Buffer.from(JSON.stringify(payload)).toString('base64');
127
- state.result = body;
128
- persist(file, state); // Persist the authorization before sending it anywhere.
129
- response = await request('POST', ENDPOINT, { creditCents, requestId: state.requestId }, state.signature);
130
- }
131
- const result = await json(response);
132
- state.result = result;
133
- persist(file, state);
134
- return result;
135
- }
136
- catch (e) {
137
- if (e instanceof APIError && e.code === 'QUOTE_EXPIRED')
138
- throw new Error('Quote expired before payment was accepted. Use a new --request-id UUID.');
139
- const message = e instanceof APIError || e instanceof PaymentInputError ? e.message : 'Payment did not finish. No new payment was started.';
140
- throw new Error(`${message} Resume with --request-id ${state.requestId}. Saved state: ${file}`);
141
- }
142
- }
143
- export async function getX402Status(attemptId) {
144
- if (!/^[0-9a-f-]{36}$/i.test(attemptId))
145
- throw new Error('Payment attempt ID must be a UUID.');
146
- return json(await request('GET', `${ENDPOINT}/${attemptId}`));
147
- }
148
- //# sourceMappingURL=x402.js.map