@layr-labs/ecloud-cli 1.0.0-dev.3 → 1.0.0-devep1
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/VERSION +2 -2
- package/dist/commands/billing/__tests__/status.test.js +23 -4
- package/dist/commands/billing/__tests__/status.test.js.map +1 -1
- package/dist/commands/billing/__tests__/top-up.test.js +89 -273
- package/dist/commands/billing/__tests__/top-up.test.js.map +1 -1
- package/dist/commands/billing/status.js +23 -4
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/top-up.js +79 -151
- package/dist/commands/billing/top-up.js.map +1 -1
- package/dist/commands/compute/app/deploy.js +1 -1
- package/dist/commands/compute/app/deploy.js.map +1 -1
- package/dist/commands/compute/app/info.js +1 -1
- package/dist/commands/compute/app/info.js.map +1 -1
- package/dist/commands/compute/app/list.js +1 -1
- package/dist/commands/compute/app/list.js.map +1 -1
- package/dist/commands/compute/app/logs.js +1 -1
- package/dist/commands/compute/app/logs.js.map +1 -1
- package/dist/commands/compute/app/profile/set.js +1 -1
- package/dist/commands/compute/app/profile/set.js.map +1 -1
- package/dist/commands/compute/app/releases.js +1 -1
- package/dist/commands/compute/app/releases.js.map +1 -1
- package/dist/commands/compute/app/start.js +1 -1
- package/dist/commands/compute/app/start.js.map +1 -1
- package/dist/commands/compute/app/stop.js +1 -1
- package/dist/commands/compute/app/stop.js.map +1 -1
- package/dist/commands/compute/app/terminate.js +1 -1
- package/dist/commands/compute/app/terminate.js.map +1 -1
- package/dist/commands/compute/app/upgrade.js +1 -1
- package/dist/commands/compute/app/upgrade.js.map +1 -1
- package/dist/commands/compute/build/info.js +1 -1
- package/dist/commands/compute/build/info.js.map +1 -1
- package/dist/commands/compute/build/list.js +1 -1
- package/dist/commands/compute/build/list.js.map +1 -1
- package/dist/commands/compute/build/logs.js +1 -1
- package/dist/commands/compute/build/logs.js.map +1 -1
- package/dist/commands/compute/build/status.js +1 -1
- package/dist/commands/compute/build/status.js.map +1 -1
- package/dist/commands/compute/build/submit.js +1 -1
- package/dist/commands/compute/build/submit.js.map +1 -1
- package/dist/commands/compute/build/verify.js +1 -1
- package/dist/commands/compute/build/verify.js.map +1 -1
- package/dist/commands/compute/undelegate.js +1 -1
- package/dist/commands/compute/undelegate.js.map +1 -1
- package/dist/hooks/init/__tests__/version-check.test.js +1 -1
- package/dist/hooks/init/__tests__/version-check.test.js.map +1 -1
- package/dist/hooks/init/version-check.js +1 -1
- package/dist/hooks/init/version-check.js.map +1 -1
- package/package.json +2 -2
- package/dist/commands/billing/list-cards.js +0 -409
- package/dist/commands/billing/list-cards.js.map +0 -1
|
@@ -319,8 +319,7 @@ async function createBillingClient(flags) {
|
|
|
319
319
|
// src/commands/billing/top-up.ts
|
|
320
320
|
import { formatUnits } from "viem";
|
|
321
321
|
import chalk2 from "chalk";
|
|
322
|
-
import { input as input2
|
|
323
|
-
import open from "open";
|
|
322
|
+
import { input as input2 } from "@inquirer/prompts";
|
|
324
323
|
|
|
325
324
|
// src/telemetry.ts
|
|
326
325
|
import {
|
|
@@ -384,22 +383,12 @@ async function withTelemetry(command, action) {
|
|
|
384
383
|
var POLL_INTERVAL_MS = 5e3;
|
|
385
384
|
var POLL_TIMEOUT_MS = 3 * 60 * 1e3;
|
|
386
385
|
var BillingTopUp = class _BillingTopUp extends Command {
|
|
387
|
-
static description = "Purchase EigenCompute credits with USDC
|
|
388
|
-
static examples = [
|
|
389
|
-
"<%= config.bin %> billing top-up",
|
|
390
|
-
"<%= config.bin %> billing top-up --method usdc --amount 50",
|
|
391
|
-
"<%= config.bin %> billing top-up --method card --amount 25"
|
|
392
|
-
];
|
|
386
|
+
static description = "Purchase EigenCompute credits with USDC";
|
|
393
387
|
static flags = {
|
|
394
388
|
...commonFlags,
|
|
395
|
-
method: Flags2.string({
|
|
396
|
-
required: false,
|
|
397
|
-
description: "Payment method: usdc (on-chain) or card (credit card)",
|
|
398
|
-
options: ["usdc", "card"]
|
|
399
|
-
}),
|
|
400
389
|
amount: Flags2.string({
|
|
401
390
|
required: false,
|
|
402
|
-
description: "Amount to spend (
|
|
391
|
+
description: "Amount of USDC to spend (e.g., '50')"
|
|
403
392
|
}),
|
|
404
393
|
account: Flags2.string({
|
|
405
394
|
required: false,
|
|
@@ -435,162 +424,101 @@ ${chalk2.bold("Purchase EigenCompute credits")}`);
|
|
|
435
424
|
const remaining = status.remainingCredits ?? 0;
|
|
436
425
|
const applied = status.creditsApplied ?? 0;
|
|
437
426
|
baselineTotal = remaining + applied;
|
|
438
|
-
|
|
427
|
+
if (status.remainingCredits !== void 0) {
|
|
428
|
+
this.log(` ${chalk2.bold("Credits:")} ${chalk2.cyan(`$${status.remainingCredits.toFixed(2)}`)}`);
|
|
429
|
+
}
|
|
439
430
|
} catch {
|
|
440
431
|
this.debug("Could not fetch current credit balance");
|
|
441
432
|
}
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
});
|
|
449
|
-
if (method === "usdc") {
|
|
450
|
-
await this.handleUsdc(billing, flags, walletAddress, targetAccount, baselineTotal);
|
|
451
|
-
} else {
|
|
452
|
-
await this.handleCard(billing, flags, baselineTotal);
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
async handleUsdc(billing, flags, walletAddress, targetAccount, baselineTotal) {
|
|
457
|
-
const onChainState = await billing.getTopUpInfo();
|
|
458
|
-
const { usdcBalance, minimumPurchase } = onChainState;
|
|
459
|
-
const balanceFormatted = formatUnits(usdcBalance, 6);
|
|
460
|
-
this.log(` ${chalk2.bold("USDC:")} ${balanceFormatted} USDC`);
|
|
461
|
-
if (usdcBalance === BigInt(0)) {
|
|
462
|
-
this.log(`
|
|
433
|
+
const onChainState = await billing.getTopUpInfo();
|
|
434
|
+
const { usdcBalance, minimumPurchase } = onChainState;
|
|
435
|
+
const balanceFormatted = formatUnits(usdcBalance, 6);
|
|
436
|
+
this.log(` ${chalk2.bold("USDC:")} ${balanceFormatted} USDC`);
|
|
437
|
+
if (usdcBalance === BigInt(0)) {
|
|
438
|
+
this.log(`
|
|
463
439
|
${chalk2.yellow(" No USDC in wallet.")}`);
|
|
464
|
-
|
|
465
|
-
|
|
440
|
+
this.log(` Send USDC on Sepolia to: ${chalk2.cyan(walletAddress)}`);
|
|
441
|
+
this.log(` Then re-run: ${chalk2.cyan("ecloud billing top-up")}
|
|
466
442
|
`);
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
const minimumFormatted = formatUnits(minimumPurchase, 6);
|
|
470
|
-
const amountStr = flags.amount ?? await input2({
|
|
471
|
-
message: `How much USDC to spend on credits? (minimum: ${minimumFormatted})`,
|
|
472
|
-
validate: (val) => {
|
|
473
|
-
const n = parseFloat(val);
|
|
474
|
-
if (isNaN(n) || n <= 0) return "Enter a positive number";
|
|
475
|
-
const raw = BigInt(Math.round(n * 1e6));
|
|
476
|
-
if (raw < minimumPurchase)
|
|
477
|
-
return `Minimum purchase is ${minimumFormatted} USDC`;
|
|
478
|
-
if (raw > usdcBalance)
|
|
479
|
-
return `Insufficient balance. You have ${balanceFormatted} USDC`;
|
|
480
|
-
return true;
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
const amountFloat = parseFloat(amountStr);
|
|
484
|
-
const amountRaw = BigInt(Math.round(amountFloat * 1e6));
|
|
485
|
-
if (amountRaw < minimumPurchase) {
|
|
486
|
-
this.error(`Minimum purchase is ${minimumFormatted} USDC`);
|
|
487
|
-
}
|
|
488
|
-
if (amountRaw > usdcBalance) {
|
|
489
|
-
this.error(
|
|
490
|
-
`Insufficient USDC balance. You have ${balanceFormatted} USDC but requested ${amountFloat.toFixed(2)}`
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
this.log(`
|
|
494
|
-
Purchasing ${chalk2.bold(`$${amountFloat.toFixed(2)}`)} in credits...`);
|
|
495
|
-
const { txHash } = await billing.topUp({
|
|
496
|
-
amount: amountRaw,
|
|
497
|
-
account: targetAccount
|
|
498
|
-
});
|
|
499
|
-
this.log(` ${chalk2.green("\u2713")} Transaction confirmed: ${txHash}`);
|
|
500
|
-
await this.pollForCredits(billing, flags, baselineTotal, amountFloat);
|
|
501
|
-
}
|
|
502
|
-
async handleCard(billing, flags, baselineTotal) {
|
|
503
|
-
const MINIMUM_DOLLARS = 5;
|
|
504
|
-
const amountStr = flags.amount ?? await input2({
|
|
505
|
-
message: `How many dollars of credits to purchase? (minimum: $${MINIMUM_DOLLARS})`,
|
|
506
|
-
validate: (val) => {
|
|
507
|
-
const n = parseInt(val, 10);
|
|
508
|
-
if (isNaN(n) || n <= 0) return "Enter a positive whole number";
|
|
509
|
-
if (n.toString() !== val.trim()) return "Enter a whole dollar amount (no cents)";
|
|
510
|
-
if (n < MINIMUM_DOLLARS) return `Minimum purchase is $${MINIMUM_DOLLARS}`;
|
|
511
|
-
return true;
|
|
443
|
+
return;
|
|
512
444
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
choices.push({ value: "new", name: "Add a new card" });
|
|
527
|
-
const selection = await select2({
|
|
528
|
-
message: "Which card would you like to use?",
|
|
529
|
-
choices
|
|
445
|
+
const minimumFormatted = formatUnits(minimumPurchase, 6);
|
|
446
|
+
const amountStr = flags.amount ?? await input2({
|
|
447
|
+
message: `How much USDC to spend on credits? (minimum: ${minimumFormatted})`,
|
|
448
|
+
validate: (val) => {
|
|
449
|
+
const n = parseFloat(val);
|
|
450
|
+
if (isNaN(n) || n <= 0) return "Enter a positive number";
|
|
451
|
+
const raw = BigInt(Math.round(n * 1e6));
|
|
452
|
+
if (raw < minimumPurchase)
|
|
453
|
+
return `Minimum purchase is ${minimumFormatted} USDC`;
|
|
454
|
+
if (raw > usdcBalance)
|
|
455
|
+
return `Insufficient balance. You have ${balanceFormatted} USDC`;
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
530
458
|
});
|
|
531
|
-
|
|
532
|
-
|
|
459
|
+
const amountFloat = parseFloat(amountStr);
|
|
460
|
+
const amountRaw = BigInt(Math.round(amountFloat * 1e6));
|
|
461
|
+
if (amountRaw < minimumPurchase) {
|
|
462
|
+
this.error(`Minimum purchase is ${minimumFormatted} USDC`);
|
|
533
463
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const result = await billing.purchaseCredits(amountCents, paymentMethodId);
|
|
538
|
-
if (result.checkoutUrl) {
|
|
539
|
-
this.log(`
|
|
540
|
-
${chalk2.cyan(result.checkoutUrl)}`);
|
|
541
|
-
this.log(chalk2.gray(" Opening checkout in browser..."));
|
|
542
|
-
await open(result.checkoutUrl);
|
|
543
|
-
} else if (result.checkoutSessionId) {
|
|
544
|
-
this.error(
|
|
545
|
-
"Checkout session created but no URL was returned. Please contact support."
|
|
546
|
-
);
|
|
547
|
-
} else {
|
|
548
|
-
this.log(` ${chalk2.green("\u2713")} Payment submitted`);
|
|
549
|
-
}
|
|
550
|
-
await this.pollForCredits(billing, flags, baselineTotal, dollars);
|
|
551
|
-
}
|
|
552
|
-
async pollForCredits(billing, flags, baselineTotal, amountPurchased) {
|
|
553
|
-
this.log(chalk2.gray("\n Waiting for credits to appear..."));
|
|
554
|
-
const startTime = Date.now();
|
|
555
|
-
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
556
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
557
|
-
try {
|
|
558
|
-
const status = await billing.getStatus({
|
|
559
|
-
productId: flags.product
|
|
560
|
-
});
|
|
561
|
-
const remaining = status.remainingCredits ?? 0;
|
|
562
|
-
const applied = status.creditsApplied ?? 0;
|
|
563
|
-
const currentTotal = remaining + applied;
|
|
564
|
-
this.debug(
|
|
565
|
-
`Poll: remaining=${remaining}, applied=${applied}, total=${currentTotal}, baseline=${baselineTotal}`
|
|
464
|
+
if (amountRaw > usdcBalance) {
|
|
465
|
+
this.error(
|
|
466
|
+
`Insufficient USDC balance. You have ${balanceFormatted} USDC but requested ${amountFloat.toFixed(2)}`
|
|
566
467
|
);
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
468
|
+
}
|
|
469
|
+
this.log(`
|
|
470
|
+
Purchasing ${chalk2.bold(`$${amountFloat.toFixed(2)}`)} in credits...`);
|
|
471
|
+
const { txHash } = await billing.topUp({
|
|
472
|
+
amount: amountRaw,
|
|
473
|
+
account: targetAccount
|
|
474
|
+
});
|
|
475
|
+
this.log(` ${chalk2.green("\u2713")} Transaction confirmed: ${txHash}`);
|
|
476
|
+
this.log(chalk2.gray("\n Waiting for credits to appear..."));
|
|
477
|
+
const startTime = Date.now();
|
|
478
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
479
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
480
|
+
try {
|
|
481
|
+
const status = await billing.getStatus({
|
|
482
|
+
productId: flags.product
|
|
483
|
+
});
|
|
484
|
+
const remaining = status.remainingCredits ?? 0;
|
|
485
|
+
const applied = status.creditsApplied ?? 0;
|
|
486
|
+
const currentTotal = remaining + applied;
|
|
487
|
+
this.debug(`Poll: remaining=${remaining}, applied=${applied}, total=${currentTotal}, baseline=${baselineTotal}`);
|
|
488
|
+
if (baselineTotal === void 0 || currentTotal > baselineTotal) {
|
|
489
|
+
const creditsAdded = baselineTotal !== void 0 ? currentTotal - baselineTotal : void 0;
|
|
490
|
+
const isMatched = creditsAdded !== void 0 && Math.abs(creditsAdded - amountFloat * 2) < 0.01;
|
|
491
|
+
const appliedFromTopUp = creditsAdded !== void 0 ? creditsAdded - remaining : 0;
|
|
492
|
+
this.log(`
|
|
493
|
+
${chalk2.green("\u2713")} Credits received: ${chalk2.cyan(`$${(creditsAdded ?? amountFloat).toFixed(2)}`)}`);
|
|
494
|
+
if (isMatched) {
|
|
495
|
+
this.log(` ${chalk2.green("\u2713")} Includes $${amountFloat.toFixed(2)} match bonus!`);
|
|
496
|
+
}
|
|
497
|
+
if (remaining > 0) {
|
|
498
|
+
this.log(` Remaining balance: ${chalk2.cyan(`$${remaining.toFixed(2)}`)}`);
|
|
499
|
+
}
|
|
500
|
+
if (appliedFromTopUp > 0) {
|
|
501
|
+
this.log(` ${chalk2.gray(`$${appliedFromTopUp.toFixed(2)} applied to current bill`)}`);
|
|
502
|
+
}
|
|
503
|
+
this.log();
|
|
504
|
+
return;
|
|
575
505
|
}
|
|
576
|
-
|
|
577
|
-
|
|
506
|
+
} catch {
|
|
507
|
+
this.debug("Error polling for credit balance");
|
|
578
508
|
}
|
|
579
|
-
} catch {
|
|
580
|
-
this.debug("Error polling for credit balance");
|
|
581
509
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
`
|
|
510
|
+
this.log(
|
|
511
|
+
`
|
|
585
512
|
${chalk2.yellow("\u26A0")} Credits haven't appeared yet. This can take a few minutes.`
|
|
586
|
-
|
|
587
|
-
|
|
513
|
+
);
|
|
514
|
+
this.log(` ${chalk2.gray("Check your balance:")} ecloud billing status
|
|
588
515
|
`);
|
|
516
|
+
});
|
|
589
517
|
}
|
|
590
518
|
};
|
|
591
519
|
|
|
592
520
|
// src/commands/billing/__tests__/top-up.test.ts
|
|
593
|
-
import { input as input3
|
|
521
|
+
import { input as input3 } from "@inquirer/prompts";
|
|
594
522
|
vi.mock("../../../client", () => ({
|
|
595
523
|
createBillingClient: vi.fn()
|
|
596
524
|
}));
|
|
@@ -598,11 +526,7 @@ vi.mock("../../../telemetry", () => ({
|
|
|
598
526
|
withTelemetry: vi.fn((_cmd, fn) => fn())
|
|
599
527
|
}));
|
|
600
528
|
vi.mock("@inquirer/prompts", () => ({
|
|
601
|
-
input: vi.fn()
|
|
602
|
-
select: vi.fn()
|
|
603
|
-
}));
|
|
604
|
-
vi.mock("open", () => ({
|
|
605
|
-
default: vi.fn()
|
|
529
|
+
input: vi.fn()
|
|
606
530
|
}));
|
|
607
531
|
var WALLET_ADDRESS = "0x1234567890abcdef1234567890abcdef12345678";
|
|
608
532
|
var TX_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
|
|
@@ -617,9 +541,7 @@ describe("ecloud billing top-up", () => {
|
|
|
617
541
|
address: WALLET_ADDRESS,
|
|
618
542
|
getStatus: vi.fn(),
|
|
619
543
|
getTopUpInfo: vi.fn(),
|
|
620
|
-
topUp: vi.fn()
|
|
621
|
-
getPaymentMethods: vi.fn(),
|
|
622
|
-
purchaseCredits: vi.fn()
|
|
544
|
+
topUp: vi.fn()
|
|
623
545
|
};
|
|
624
546
|
createBillingClient.mockResolvedValue(mockBilling);
|
|
625
547
|
input3.mockResolvedValue("50");
|
|
@@ -664,7 +586,7 @@ describe("ecloud billing top-up", () => {
|
|
|
664
586
|
setupOnChainState();
|
|
665
587
|
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
666
588
|
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
|
|
667
|
-
const cmd = createCommand({ amount: "50"
|
|
589
|
+
const cmd = createCommand({ amount: "50" });
|
|
668
590
|
const promise = cmd.run();
|
|
669
591
|
for (let i = 0; i < 10; i++) {
|
|
670
592
|
await vi.advanceTimersByTimeAsync(5e3);
|
|
@@ -686,7 +608,7 @@ describe("ecloud billing top-up", () => {
|
|
|
686
608
|
it("zero USDC balance: exits with fund wallet message", async () => {
|
|
687
609
|
setupOnChainState({ usdcBalance: BigInt(0) });
|
|
688
610
|
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "inactive" });
|
|
689
|
-
const cmd = createCommand({ amount: "50"
|
|
611
|
+
const cmd = createCommand({ amount: "50" });
|
|
690
612
|
await cmd.run();
|
|
691
613
|
const fullOutput = logOutput.join("\n");
|
|
692
614
|
expect(fullOutput).toContain("No USDC in wallet");
|
|
@@ -697,7 +619,7 @@ describe("ecloud billing top-up", () => {
|
|
|
697
619
|
it("below minimum purchase: shows error", async () => {
|
|
698
620
|
setupOnChainState({ minimumPurchase: BigInt(1e7) });
|
|
699
621
|
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "inactive" });
|
|
700
|
-
const cmd = createCommand({ amount: "5"
|
|
622
|
+
const cmd = createCommand({ amount: "5" });
|
|
701
623
|
await expect(cmd.run()).rejects.toThrow("Minimum purchase is 10 USDC");
|
|
702
624
|
});
|
|
703
625
|
it("--account flag: passes different address to topUp", async () => {
|
|
@@ -705,7 +627,7 @@ describe("ecloud billing top-up", () => {
|
|
|
705
627
|
setupOnChainState();
|
|
706
628
|
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
707
629
|
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
|
|
708
|
-
const cmd = createCommand({ amount: "50",
|
|
630
|
+
const cmd = createCommand({ amount: "50", account: targetAccount });
|
|
709
631
|
const promise = cmd.run();
|
|
710
632
|
for (let i = 0; i < 10; i++) {
|
|
711
633
|
await vi.advanceTimersByTimeAsync(5e3);
|
|
@@ -725,7 +647,7 @@ describe("ecloud billing top-up", () => {
|
|
|
725
647
|
subscriptionStatus: "active",
|
|
726
648
|
remainingCredits: 10
|
|
727
649
|
});
|
|
728
|
-
const cmd = createCommand({ amount: "50"
|
|
650
|
+
const cmd = createCommand({ amount: "50" });
|
|
729
651
|
const promise = cmd.run();
|
|
730
652
|
await vi.advanceTimersByTimeAsync(2e5);
|
|
731
653
|
await promise;
|
|
@@ -737,7 +659,7 @@ describe("ecloud billing top-up", () => {
|
|
|
737
659
|
setupOnChainState();
|
|
738
660
|
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
739
661
|
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "inactive" }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 100 });
|
|
740
|
-
const cmd = createCommand({ amount: "100"
|
|
662
|
+
const cmd = createCommand({ amount: "100" });
|
|
741
663
|
const promise = cmd.run();
|
|
742
664
|
for (let i = 0; i < 10; i++) {
|
|
743
665
|
await vi.advanceTimersByTimeAsync(5e3);
|
|
@@ -749,7 +671,7 @@ describe("ecloud billing top-up", () => {
|
|
|
749
671
|
setupOnChainState();
|
|
750
672
|
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
751
673
|
mockBilling.getStatus.mockRejectedValue(new Error("API unavailable"));
|
|
752
|
-
const cmd = createCommand({ amount: "50"
|
|
674
|
+
const cmd = createCommand({ amount: "50" });
|
|
753
675
|
const promise = cmd.run();
|
|
754
676
|
await vi.advanceTimersByTimeAsync(2e5);
|
|
755
677
|
await promise;
|
|
@@ -758,111 +680,5 @@ describe("ecloud billing top-up", () => {
|
|
|
758
680
|
expect(fullOutput).toContain("Transaction confirmed");
|
|
759
681
|
expect(fullOutput).toContain("Credits haven't appeared yet");
|
|
760
682
|
});
|
|
761
|
-
it("credit card: charges selected card on file", async () => {
|
|
762
|
-
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 35 });
|
|
763
|
-
mockBilling.getPaymentMethods.mockResolvedValue({
|
|
764
|
-
paymentMethods: [
|
|
765
|
-
{
|
|
766
|
-
id: "029641fc-3e5c-11f1-986c-5601121cbf6d",
|
|
767
|
-
stripePaymentMethodId: "pm_1ABC1234",
|
|
768
|
-
brand: "visa",
|
|
769
|
-
last4: "1234",
|
|
770
|
-
createdAt: "2026-04-20T15:00:00Z"
|
|
771
|
-
},
|
|
772
|
-
{
|
|
773
|
-
id: "139752fd-4e6d-22f2-a97d-6712232dcg7e",
|
|
774
|
-
stripePaymentMethodId: "pm_2DEF5678",
|
|
775
|
-
brand: "mastercard",
|
|
776
|
-
last4: "5678",
|
|
777
|
-
createdAt: "2026-04-21T10:00:00Z"
|
|
778
|
-
}
|
|
779
|
-
]
|
|
780
|
-
});
|
|
781
|
-
mockBilling.purchaseCredits.mockResolvedValue({
|
|
782
|
-
purchaseId: "a1b2c3d4",
|
|
783
|
-
amountCents: "2500"
|
|
784
|
-
});
|
|
785
|
-
select3.mockResolvedValue("029641fc-3e5c-11f1-986c-5601121cbf6d");
|
|
786
|
-
const cmd = createCommand({ amount: "25", method: "card" });
|
|
787
|
-
const promise = cmd.run();
|
|
788
|
-
for (let i = 0; i < 10; i++) {
|
|
789
|
-
await vi.advanceTimersByTimeAsync(5e3);
|
|
790
|
-
}
|
|
791
|
-
await promise;
|
|
792
|
-
const fullOutput = logOutput.join("\n");
|
|
793
|
-
expect(mockBilling.purchaseCredits).toHaveBeenCalledWith(2500, "029641fc-3e5c-11f1-986c-5601121cbf6d");
|
|
794
|
-
expect(fullOutput).toContain("Payment submitted");
|
|
795
|
-
expect(fullOutput).toContain("Credits received");
|
|
796
|
-
});
|
|
797
|
-
it("credit card: opens checkout when user selects add new card", async () => {
|
|
798
|
-
const openMock = (await import("open")).default;
|
|
799
|
-
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "active", remainingCredits: 10 });
|
|
800
|
-
mockBilling.getPaymentMethods.mockResolvedValue({
|
|
801
|
-
paymentMethods: [
|
|
802
|
-
{
|
|
803
|
-
id: "029641fc-3e5c-11f1-986c-5601121cbf6d",
|
|
804
|
-
stripePaymentMethodId: "pm_1ABC1234",
|
|
805
|
-
brand: "visa",
|
|
806
|
-
last4: "1234",
|
|
807
|
-
createdAt: "2026-04-20T15:00:00Z"
|
|
808
|
-
}
|
|
809
|
-
]
|
|
810
|
-
});
|
|
811
|
-
mockBilling.purchaseCredits.mockResolvedValue({
|
|
812
|
-
checkoutSessionId: "cs_test_abc123",
|
|
813
|
-
checkoutUrl: "https://checkout.stripe.com/test",
|
|
814
|
-
amountCents: "2500"
|
|
815
|
-
});
|
|
816
|
-
select3.mockResolvedValue("new");
|
|
817
|
-
const cmd = createCommand({ amount: "25", method: "card" });
|
|
818
|
-
const promise = cmd.run();
|
|
819
|
-
await vi.advanceTimersByTimeAsync(2e5);
|
|
820
|
-
await promise;
|
|
821
|
-
const fullOutput = logOutput.join("\n");
|
|
822
|
-
expect(mockBilling.purchaseCredits).toHaveBeenCalledWith(2500, void 0);
|
|
823
|
-
expect(openMock).toHaveBeenCalledWith("https://checkout.stripe.com/test");
|
|
824
|
-
expect(fullOutput).toContain("https://checkout.stripe.com/test");
|
|
825
|
-
});
|
|
826
|
-
it("credit card: opens checkout when no card on file", async () => {
|
|
827
|
-
const openMock = (await import("open")).default;
|
|
828
|
-
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "active", remainingCredits: 10 });
|
|
829
|
-
mockBilling.getPaymentMethods.mockResolvedValue({ paymentMethods: [] });
|
|
830
|
-
mockBilling.purchaseCredits.mockResolvedValue({
|
|
831
|
-
checkoutSessionId: "cs_test_abc123",
|
|
832
|
-
checkoutUrl: "https://checkout.stripe.com/test",
|
|
833
|
-
amountCents: "5000"
|
|
834
|
-
});
|
|
835
|
-
const cmd = createCommand({ amount: "50", method: "card" });
|
|
836
|
-
const promise = cmd.run();
|
|
837
|
-
await vi.advanceTimersByTimeAsync(2e5);
|
|
838
|
-
await promise;
|
|
839
|
-
const fullOutput = logOutput.join("\n");
|
|
840
|
-
expect(select3).not.toHaveBeenCalled();
|
|
841
|
-
expect(mockBilling.purchaseCredits).toHaveBeenCalledWith(5e3, void 0);
|
|
842
|
-
expect(openMock).toHaveBeenCalledWith("https://checkout.stripe.com/test");
|
|
843
|
-
expect(fullOutput).toContain("https://checkout.stripe.com/test");
|
|
844
|
-
});
|
|
845
|
-
it("credit card: rejects amount below $5 minimum", async () => {
|
|
846
|
-
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "active", remainingCredits: 10 });
|
|
847
|
-
const cmd = createCommand({ amount: "3", method: "card" });
|
|
848
|
-
await expect(cmd.run()).rejects.toThrow("Minimum purchase is $5");
|
|
849
|
-
});
|
|
850
|
-
it("credit card: --method and --amount flags skip prompts", async () => {
|
|
851
|
-
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
|
|
852
|
-
mockBilling.getPaymentMethods.mockResolvedValue({ paymentMethods: [] });
|
|
853
|
-
mockBilling.purchaseCredits.mockResolvedValue({
|
|
854
|
-
checkoutSessionId: "cs_test_abc123",
|
|
855
|
-
checkoutUrl: "https://checkout.stripe.com/test",
|
|
856
|
-
amountCents: "5000"
|
|
857
|
-
});
|
|
858
|
-
const cmd = createCommand({ amount: "50", method: "card" });
|
|
859
|
-
const promise = cmd.run();
|
|
860
|
-
for (let i = 0; i < 10; i++) {
|
|
861
|
-
await vi.advanceTimersByTimeAsync(5e3);
|
|
862
|
-
}
|
|
863
|
-
await promise;
|
|
864
|
-
expect(select3).not.toHaveBeenCalled();
|
|
865
|
-
expect(input3).not.toHaveBeenCalled();
|
|
866
|
-
});
|
|
867
683
|
});
|
|
868
684
|
//# sourceMappingURL=top-up.test.js.map
|