@layr-labs/ecloud-cli 1.0.0-devep7 → 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.
Files changed (84) hide show
  1. package/VERSION +2 -2
  2. package/dist/commands/admin/admins/add.js +411 -0
  3. package/dist/commands/admin/admins/add.js.map +1 -0
  4. package/dist/commands/admin/admins/list.js +414 -0
  5. package/dist/commands/admin/admins/list.js.map +1 -0
  6. package/dist/commands/admin/admins/remove.js +409 -0
  7. package/dist/commands/admin/admins/remove.js.map +1 -0
  8. package/dist/commands/admin/coupons/create.js +424 -0
  9. package/dist/commands/admin/coupons/create.js.map +1 -0
  10. package/dist/commands/admin/coupons/deactivate.js +409 -0
  11. package/dist/commands/admin/coupons/deactivate.js.map +1 -0
  12. package/dist/commands/admin/coupons/get.js +418 -0
  13. package/dist/commands/admin/coupons/get.js.map +1 -0
  14. package/dist/commands/admin/coupons/list.js +442 -0
  15. package/dist/commands/admin/coupons/list.js.map +1 -0
  16. package/dist/commands/admin/coupons/redeem.js +413 -0
  17. package/dist/commands/admin/coupons/redeem.js.map +1 -0
  18. package/dist/commands/auth/whoami.js +7 -1
  19. package/dist/commands/auth/whoami.js.map +1 -1
  20. package/dist/commands/billing/__tests__/status.test.js +82 -4
  21. package/dist/commands/billing/__tests__/status.test.js.map +1 -1
  22. package/dist/commands/billing/__tests__/subscribe.test.js +10 -2
  23. package/dist/commands/billing/__tests__/subscribe.test.js.map +1 -1
  24. package/dist/commands/billing/__tests__/top-up.test.js +565 -13
  25. package/dist/commands/billing/__tests__/top-up.test.js.map +1 -1
  26. package/dist/commands/billing/cancel.js +11 -4
  27. package/dist/commands/billing/cancel.js.map +1 -1
  28. package/dist/commands/billing/list-cards.js +10 -2
  29. package/dist/commands/billing/list-cards.js.map +1 -1
  30. package/dist/commands/billing/redeem-coupon.js +421 -0
  31. package/dist/commands/billing/redeem-coupon.js.map +1 -0
  32. package/dist/commands/billing/status.js +36 -2
  33. package/dist/commands/billing/status.js.map +1 -1
  34. package/dist/commands/billing/subscribe.js +10 -2
  35. package/dist/commands/billing/subscribe.js.map +1 -1
  36. package/dist/commands/billing/top-up.js +392 -11
  37. package/dist/commands/billing/top-up.js.map +1 -1
  38. package/dist/commands/compute/app/__tests__/status.test.js +1017 -0
  39. package/dist/commands/compute/app/__tests__/status.test.js.map +1 -0
  40. package/dist/commands/compute/app/configure/tls.js +70 -30
  41. package/dist/commands/compute/app/configure/tls.js.map +1 -1
  42. package/dist/commands/compute/app/create.js.map +1 -1
  43. package/dist/commands/compute/app/deploy.js +301 -68
  44. package/dist/commands/compute/app/deploy.js.map +1 -1
  45. package/dist/commands/compute/app/info.js +8 -2
  46. package/dist/commands/compute/app/info.js.map +1 -1
  47. package/dist/commands/compute/app/list.js +8 -2
  48. package/dist/commands/compute/app/list.js.map +1 -1
  49. package/dist/commands/compute/app/logs.js +12 -4
  50. package/dist/commands/compute/app/logs.js.map +1 -1
  51. package/dist/commands/compute/app/profile/set.js +12 -4
  52. package/dist/commands/compute/app/profile/set.js.map +1 -1
  53. package/dist/commands/compute/app/releases.js +8 -2
  54. package/dist/commands/compute/app/releases.js.map +1 -1
  55. package/dist/commands/compute/app/start.js +16 -8
  56. package/dist/commands/compute/app/start.js.map +1 -1
  57. package/dist/commands/compute/app/status.js +845 -0
  58. package/dist/commands/compute/app/status.js.map +1 -0
  59. package/dist/commands/compute/app/stop.js +16 -8
  60. package/dist/commands/compute/app/stop.js.map +1 -1
  61. package/dist/commands/compute/app/terminate.js +16 -8
  62. package/dist/commands/compute/app/terminate.js.map +1 -1
  63. package/dist/commands/compute/app/upgrade.js +298 -67
  64. package/dist/commands/compute/app/upgrade.js.map +1 -1
  65. package/dist/commands/compute/build/info.js +9 -2
  66. package/dist/commands/compute/build/info.js.map +1 -1
  67. package/dist/commands/compute/build/list.js +9 -2
  68. package/dist/commands/compute/build/list.js.map +1 -1
  69. package/dist/commands/compute/build/logs.js +9 -2
  70. package/dist/commands/compute/build/logs.js.map +1 -1
  71. package/dist/commands/compute/build/status.js +9 -2
  72. package/dist/commands/compute/build/status.js.map +1 -1
  73. package/dist/commands/compute/build/submit.js +9 -2
  74. package/dist/commands/compute/build/submit.js.map +1 -1
  75. package/dist/commands/compute/build/verify.js +9 -2
  76. package/dist/commands/compute/build/verify.js.map +1 -1
  77. package/dist/commands/compute/environment/set.js.map +1 -1
  78. package/dist/commands/compute/undelegate.js +12 -4
  79. package/dist/commands/compute/undelegate.js.map +1 -1
  80. package/dist/hooks/init/__tests__/version-check.test.js +32 -2
  81. package/dist/hooks/init/__tests__/version-check.test.js.map +1 -1
  82. package/dist/hooks/init/version-check.js +6 -1
  83. package/dist/hooks/init/version-check.js.map +1 -1
  84. package/package.json +11 -2
@@ -8,6 +8,7 @@ import {
8
8
  createComputeModule,
9
9
  createBillingModule,
10
10
  createBuildModule,
11
+ createAdminModule,
11
12
  getEnvironmentConfig as getEnvironmentConfig3,
12
13
  requirePrivateKey
13
14
  } from "@layr-labs/ecloud-sdk";
@@ -247,7 +248,7 @@ var commonFlags = {
247
248
  required: false,
248
249
  description: "Deployment environment to use",
249
250
  env: "ECLOUD_ENV",
250
- default: async () => getDefaultEnvironment() || (getBuildType2() === "dev" ? "sepolia-dev" : "sepolia")
251
+ default: async () => getDefaultEnvironment() || (getBuildType2() === "dev" ? "sepolia-dev" : "mainnet-alpha")
251
252
  }),
252
253
  "private-key": Flags.string({
253
254
  required: false,
@@ -277,6 +278,12 @@ var commonFlags = {
277
278
  nonce: Flags.string({
278
279
  required: false,
279
280
  description: 'Override transaction nonce (integer or "latest" to replace a stuck transaction)'
281
+ }),
282
+ "non-interactive": Flags.boolean({
283
+ required: false,
284
+ description: "Assume non-interactive mode: default safe prompts and error all-at-once on missing required inputs",
285
+ env: "ECLOUD_NON_INTERACTIVE",
286
+ default: false
280
287
  })
281
288
  };
282
289
  async function validateCommonFlags(flags, options) {
@@ -309,7 +316,8 @@ async function createBillingClient(flags) {
309
316
  walletClient,
310
317
  publicClient,
311
318
  environment,
312
- skipTelemetry: true
319
+ skipTelemetry: true,
320
+ privateKey
313
321
  });
314
322
  }
315
323
 
@@ -377,22 +385,299 @@ async function withTelemetry(command, action) {
377
385
  }
378
386
  }
379
387
 
388
+ // src/commands/billing/top-up.ts
389
+ import {
390
+ getBillingEnvironmentConfig,
391
+ getBuildType as getBuildType4,
392
+ requirePrivateKey as requirePrivateKey2,
393
+ addHexPrefix as addHexPrefix2
394
+ } from "@layr-labs/ecloud-sdk";
395
+ import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
396
+
397
+ // src/x402/client.ts
398
+ import { randomBytes as randomBytes2 } from "crypto";
399
+
400
+ // src/x402/types.ts
401
+ var USDC_DOMAINS = {
402
+ "eip155:84532": { name: "USDC", version: "2" },
403
+ // Base Sepolia
404
+ "eip155:8453": { name: "USD Coin", version: "2" }
405
+ // Base mainnet
406
+ };
407
+ function usdcDomainForNetwork(network, extra) {
408
+ const extraName = typeof extra?.name === "string" ? extra.name : void 0;
409
+ const extraVersion = typeof extra?.version === "string" ? extra.version : void 0;
410
+ if (extraName && extraVersion) {
411
+ return { name: extraName, version: extraVersion };
412
+ }
413
+ const fromTable = USDC_DOMAINS[network];
414
+ if (fromTable) {
415
+ return { name: extraName ?? fromTable.name, version: extraVersion ?? fromTable.version };
416
+ }
417
+ throw new Error(
418
+ `Cannot determine USDC EIP-712 domain for unknown x402 network "${network}"; the challenge must supply extra.name and extra.version.`
419
+ );
420
+ }
421
+
422
+ // src/x402/client.ts
423
+ var DEFAULT_TIMEOUT_MS = 6e4;
424
+ var X402Error = class extends Error {
425
+ status;
426
+ constructor(message, status) {
427
+ super(message);
428
+ this.name = "X402Error";
429
+ this.status = status;
430
+ }
431
+ };
432
+ function chainIdFromNetwork(network) {
433
+ const m = /^eip155:(\d+)$/.exec(network);
434
+ if (!m) {
435
+ throw new X402Error(`Unsupported x402 network "${network}" (expected eip155:<chainId>)`);
436
+ }
437
+ return Number(m[1]);
438
+ }
439
+ function parseChallenge(body) {
440
+ const reqs = body?.accepts?.[0];
441
+ if (!reqs) {
442
+ throw new X402Error("x402 challenge had no payment requirements (accepts was empty)");
443
+ }
444
+ if (reqs.scheme !== "exact") {
445
+ throw new X402Error(`Unsupported x402 scheme "${reqs.scheme}" (expected "exact")`);
446
+ }
447
+ chainIdFromNetwork(reqs.network);
448
+ const paymentId = reqs.extra?.paymentId;
449
+ if (typeof paymentId !== "string" || paymentId.length === 0) {
450
+ throw new X402Error("x402 challenge requirements missing extra.paymentId");
451
+ }
452
+ return reqs;
453
+ }
454
+ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
455
+ TransferWithAuthorization: [
456
+ { name: "from", type: "address" },
457
+ { name: "to", type: "address" },
458
+ { name: "value", type: "uint256" },
459
+ { name: "validAfter", type: "uint256" },
460
+ { name: "validBefore", type: "uint256" },
461
+ { name: "nonce", type: "bytes32" }
462
+ ]
463
+ };
464
+ async function buildSignedPayload(reqs, account, nowSeconds, randomNonce) {
465
+ const chainId = chainIdFromNetwork(reqs.network);
466
+ const domainInfo = usdcDomainForNetwork(reqs.network, reqs.extra);
467
+ const validAfter = nowSeconds - 600;
468
+ const validBefore = nowSeconds + 3600;
469
+ const nonce = randomNonce();
470
+ const authorization = {
471
+ from: account.address,
472
+ to: reqs.payTo,
473
+ value: reqs.amount,
474
+ // signed verbatim — never recomputed
475
+ validAfter: String(validAfter),
476
+ validBefore: String(validBefore),
477
+ nonce
478
+ };
479
+ const signature = await account.signTypedData({
480
+ domain: {
481
+ name: domainInfo.name,
482
+ version: domainInfo.version,
483
+ chainId,
484
+ verifyingContract: reqs.asset
485
+ },
486
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
487
+ primaryType: "TransferWithAuthorization",
488
+ message: {
489
+ from: account.address,
490
+ to: reqs.payTo,
491
+ value: BigInt(reqs.amount),
492
+ validAfter: BigInt(validAfter),
493
+ validBefore: BigInt(validBefore),
494
+ nonce
495
+ }
496
+ });
497
+ return {
498
+ x402Version: 2,
499
+ payload: { signature, authorization },
500
+ accepted: reqs
501
+ // echoed verbatim; carries extra.paymentId
502
+ };
503
+ }
504
+ function encodePaymentHeader(payload) {
505
+ return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
506
+ }
507
+ function createNonce() {
508
+ return "0x" + randomBytes2(32).toString("hex");
509
+ }
510
+ function decodeReceipt(headerValue) {
511
+ if (!headerValue) return void 0;
512
+ try {
513
+ return JSON.parse(Buffer.from(headerValue, "base64").toString("utf8"));
514
+ } catch {
515
+ return void 0;
516
+ }
517
+ }
518
+ async function readBody(resp) {
519
+ const text = await resp.text();
520
+ try {
521
+ return text ? JSON.parse(text) : {};
522
+ } catch {
523
+ return { error: text };
524
+ }
525
+ }
526
+ function describeFetchError(err, url, timeoutMs) {
527
+ if (err instanceof X402Error) return err;
528
+ const name = err?.name;
529
+ if (name === "AbortError" || name === "TimeoutError") {
530
+ return new X402Error(`x402 request to ${url} timed out after ${timeoutMs}ms`);
531
+ }
532
+ const cause = err?.cause;
533
+ const code = cause?.code ?? err?.code;
534
+ const detail = [code, cause?.message ?? err?.message].filter(Boolean).join(": ") || String(err);
535
+ let hint = "";
536
+ switch (code) {
537
+ case "ENOTFOUND":
538
+ case "EAI_AGAIN":
539
+ hint = " \u2014 the platform host could not be resolved; check --api-url / ECLOUD_API_URL";
540
+ break;
541
+ case "ECONNREFUSED":
542
+ hint = " \u2014 the platform host refused the connection; is the API URL correct and reachable?";
543
+ break;
544
+ case "ETIMEDOUT":
545
+ case "ECONNRESET":
546
+ hint = " \u2014 the connection dropped; the host may be unreachable from this network";
547
+ break;
548
+ }
549
+ return new X402Error(`x402 request to ${url} failed: ${detail}${hint}`);
550
+ }
551
+ async function purchaseCreditsX402(opts) {
552
+ const doFetch = opts.fetchImpl ?? fetch;
553
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
554
+ const body = JSON.stringify({ amountCents: opts.amountCents });
555
+ const log = (msg) => {
556
+ if (opts.verbose) console.error(`[x402] ${msg}`);
557
+ };
558
+ const request = async (label, headers) => {
559
+ const controller = new AbortController();
560
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
561
+ const startedAt = Date.now();
562
+ log(`${label}: POST ${opts.url}`);
563
+ try {
564
+ const resp = await doFetch(opts.url, { method: "POST", headers, body, signal: controller.signal });
565
+ log(`${label}: \u2190 HTTP ${resp.status} (${Date.now() - startedAt}ms)`);
566
+ return resp;
567
+ } catch (err) {
568
+ log(
569
+ `${label}: transport error: ${err?.name ?? "Error"}: ${err?.message ?? err}` + (err?.cause ? ` (cause: ${err.cause.code ?? ""} ${err.cause.message ?? ""})` : "")
570
+ );
571
+ throw describeFetchError(err, opts.url, timeoutMs);
572
+ } finally {
573
+ clearTimeout(timer);
574
+ }
575
+ };
576
+ log(`endpoint ${opts.url} \u2014 requesting ${opts.amountCents} cents of credits`);
577
+ const challengeResp = await request("challenge", { "Content-Type": "application/json" });
578
+ if (challengeResp.status !== 402) {
579
+ const errBody = await readBody(challengeResp);
580
+ log(
581
+ `challenge: expected 402, got ${challengeResp.status}; body: ${JSON.stringify(errBody)}`
582
+ );
583
+ throw new X402Error(
584
+ errBody?.error ?? `x402 challenge failed (HTTP ${challengeResp.status})`,
585
+ challengeResp.status
586
+ );
587
+ }
588
+ const reqs = parseChallenge(await readBody(challengeResp));
589
+ log(
590
+ `challenge: scheme=${reqs.scheme} network=${reqs.network} asset=${reqs.asset} amount=${reqs.amount} payTo=${reqs.payTo} paymentId=${reqs.extra?.paymentId}`
591
+ );
592
+ const payload = await buildSignedPayload(
593
+ reqs,
594
+ opts.account,
595
+ Math.floor(Date.now() / 1e3),
596
+ createNonce
597
+ );
598
+ const header = encodePaymentHeader(payload);
599
+ log(
600
+ `settle: signed authorization from=${payload.payload.authorization.from} validBefore=${payload.payload.authorization.validBefore}; sending X-PAYMENT (${header.length} chars)`
601
+ );
602
+ const settleResp = await request("settle", {
603
+ "Content-Type": "application/json",
604
+ "X-PAYMENT": header
605
+ });
606
+ if (settleResp.status !== 201) {
607
+ const errBody = await readBody(settleResp);
608
+ log(`settle: expected 201, got ${settleResp.status}; body: ${JSON.stringify(errBody)}`);
609
+ const serverMsg = typeof errBody?.error === "string" ? `: ${errBody.error}` : "";
610
+ switch (settleResp.status) {
611
+ case 402:
612
+ throw new X402Error(`x402 payment verification failed or the pending purchase expired${serverMsg}`, 402);
613
+ case 404:
614
+ throw new X402Error(`x402 target not found, or the pending purchase expired${serverMsg}`, 404);
615
+ case 409:
616
+ throw new X402Error(`x402 amount or target mismatch with the pending purchase${serverMsg}`, 409);
617
+ case 502:
618
+ throw new X402Error(`x402 settlement failed at the payment facilitator${serverMsg}`, 502);
619
+ default:
620
+ throw new X402Error(`x402 settlement failed (HTTP ${settleResp.status})${serverMsg}`, settleResp.status);
621
+ }
622
+ }
623
+ const ok = await readBody(settleResp);
624
+ const receipt = decodeReceipt(settleResp.headers.get("x-payment-response"));
625
+ const txHash = ok.txHash ?? receipt?.transaction ?? "";
626
+ const targetAddress = typeof ok.appId === "string" ? ok.appId : typeof ok.creatorAddr === "string" ? ok.creatorAddr : void 0;
627
+ const targetType = typeof ok.appId === "string" ? "app" : typeof ok.creatorAddr === "string" ? "creator" : void 0;
628
+ log(
629
+ `settle: 201 \u2014 txHash=${txHash} creditedCents=${ok.creditedCents} target=${targetType ?? "?"}:${targetAddress ?? "?"}`
630
+ );
631
+ return {
632
+ txHash,
633
+ paymentId: ok.paymentId ?? reqs.extra?.paymentId ?? "",
634
+ creditedCents: typeof ok.creditedCents === "number" ? ok.creditedCents : opts.amountCents,
635
+ targetType,
636
+ targetAddress
637
+ };
638
+ }
639
+
380
640
  // src/commands/billing/top-up.ts
381
641
  var POLL_INTERVAL_MS = 5e3;
382
642
  var POLL_TIMEOUT_MS = 3 * 60 * 1e3;
643
+ function resolveX402Target(flags, walletAddress) {
644
+ if (flags.app && flags.creator) {
645
+ throw new Error("--app and --creator are mutually exclusive");
646
+ }
647
+ if (flags.app) return { type: "app", address: flags.app };
648
+ if (flags.creator) return { type: "creator", address: flags.creator };
649
+ return { type: "creator", address: walletAddress };
650
+ }
651
+ function buildX402Url(baseUrl, target) {
652
+ const base = baseUrl.replace(/\/+$/, "");
653
+ const segment = target.type === "app" ? "apps" : "creators";
654
+ return `${base}/${segment}/${target.address}/x402-credits`;
655
+ }
656
+ function resolveX402BaseUrl(flags) {
657
+ const fromFlag = flags["api-url"];
658
+ if (fromFlag) return fromFlag.replace(/\/+$/, "");
659
+ const { billingApiServerURL } = getBillingEnvironmentConfig(getBuildType4());
660
+ if (billingApiServerURL) return billingApiServerURL.replace(/\/+$/, "");
661
+ throw new Error(
662
+ "No billing API URL configured; pass --api-url or set ECLOUD_API_URL / ECLOUD_BILLING_API_URL."
663
+ );
664
+ }
383
665
  var BillingTopUp = class _BillingTopUp extends Command {
384
- static description = "Purchase EigenCompute credits with USDC or credit card";
666
+ static description = "Purchase EigenCompute credits with USDC, credit card, or x402";
385
667
  static examples = [
386
668
  "<%= config.bin %> billing top-up",
387
669
  "<%= config.bin %> billing top-up --method usdc --amount 50",
388
- "<%= config.bin %> billing top-up --method card --amount 25"
670
+ "<%= config.bin %> billing top-up --method card --amount 25",
671
+ "<%= config.bin %> billing top-up --method x402 --amount 50",
672
+ "<%= config.bin %> billing top-up --method x402 --amount 50 --app 0xApp...",
673
+ "<%= config.bin %> billing top-up --method x402 --amount 50 --creator 0xCreator..."
389
674
  ];
390
675
  static flags = {
391
676
  ...commonFlags,
392
677
  method: Flags2.string({
393
678
  required: false,
394
- description: "Payment method: usdc (on-chain) or card (credit card)",
395
- options: ["usdc", "card"]
679
+ description: "Payment method: usdc (on-chain), card (credit card), or x402 (USDC over x402)",
680
+ options: ["usdc", "card", "x402"]
396
681
  }),
397
682
  amount: Flags2.string({
398
683
  required: false,
@@ -408,6 +693,24 @@ var BillingTopUp = class _BillingTopUp extends Command {
408
693
  default: "compute",
409
694
  options: ["compute"],
410
695
  env: "ECLOUD_PRODUCT_ID"
696
+ }),
697
+ chain: Flags2.string({
698
+ required: false,
699
+ description: "Blockchain network for USDC payment: ethereum or base",
700
+ options: ["ethereum", "base"]
701
+ }),
702
+ creator: Flags2.string({
703
+ required: false,
704
+ description: "x402: creator address to credit (defaults to your wallet). Mutually exclusive with --app."
705
+ }),
706
+ app: Flags2.string({
707
+ required: false,
708
+ description: "x402: app address to credit. Mutually exclusive with --creator."
709
+ }),
710
+ "api-url": Flags2.string({
711
+ required: false,
712
+ description: "x402: override the billing API base URL (defaults to the environment's billing API)",
713
+ env: "ECLOUD_API_URL"
411
714
  })
412
715
  };
413
716
  async run() {
@@ -440,25 +743,39 @@ ${chalk2.bold("Purchase EigenCompute credits")}`);
440
743
  message: "How would you like to pay?",
441
744
  choices: [
442
745
  { value: "card", name: "Credit card" },
443
- { value: "usdc", name: "USDC (on-chain)" }
746
+ { value: "usdc", name: "USDC (on-chain)" },
747
+ { value: "x402", name: "USDC (x402)" }
444
748
  ]
445
749
  });
446
750
  if (method === "usdc") {
447
751
  await this.handleUsdc(billing, flags, walletAddress, targetAccount, baselineTotal);
752
+ } else if (method === "x402") {
753
+ await this.handleX402(billing, flags, walletAddress, baselineTotal);
448
754
  } else {
449
755
  await this.handleCard(billing, flags, baselineTotal);
450
756
  }
451
757
  });
452
758
  }
453
759
  async handleUsdc(billing, flags, walletAddress, targetAccount, baselineTotal) {
454
- const onChainState = await billing.getTopUpInfo();
760
+ let selectedChain = "ethereum";
761
+ if (billing.hasBaseSupport()) {
762
+ selectedChain = flags.chain ?? await select2({
763
+ message: "Which network?",
764
+ choices: [
765
+ { value: "ethereum", name: "Ethereum" },
766
+ { value: "base", name: "Base" }
767
+ ]
768
+ });
769
+ }
770
+ const onChainState = await billing.getTopUpInfo({ chain: selectedChain });
455
771
  const { usdcBalance, minimumPurchase } = onChainState;
456
772
  const balanceFormatted = formatUnits(usdcBalance, 6);
457
773
  this.log(` ${chalk2.bold("USDC:")} ${balanceFormatted} USDC`);
458
774
  if (usdcBalance === BigInt(0)) {
775
+ const networkName = selectedChain === "base" ? "Base Sepolia" : "Sepolia";
459
776
  this.log(`
460
777
  ${chalk2.yellow(" No USDC in wallet.")}`);
461
- this.log(` Send USDC on Sepolia to: ${chalk2.cyan(walletAddress)}`);
778
+ this.log(` Send USDC on ${networkName} to: ${chalk2.cyan(walletAddress)}`);
462
779
  this.log(` Then re-run: ${chalk2.cyan("ecloud billing top-up")}
463
780
  `);
464
781
  return;
@@ -491,7 +808,8 @@ ${chalk2.yellow(" No USDC in wallet.")}`);
491
808
  Purchasing ${chalk2.bold(`$${amountFloat.toFixed(2)}`)} in credits...`);
492
809
  const { txHash } = await billing.topUp({
493
810
  amount: amountRaw,
494
- account: targetAccount
811
+ account: targetAccount,
812
+ chain: selectedChain
495
813
  });
496
814
  this.log(` ${chalk2.green("\u2713")} Transaction confirmed: ${txHash}`);
497
815
  await this.pollForCredits(billing, flags, baselineTotal, amountFloat);
@@ -546,6 +864,66 @@ ${chalk2.yellow(" No USDC in wallet.")}`);
546
864
  }
547
865
  await this.pollForCredits(billing, flags, baselineTotal, dollars);
548
866
  }
867
+ async handleX402(billing, flags, walletAddress, baselineTotal) {
868
+ const MINIMUM_DOLLARS = 5;
869
+ const target = resolveX402Target(
870
+ { creator: flags.creator, app: flags.app },
871
+ walletAddress
872
+ );
873
+ const targetIsSelf = target.address.toLowerCase() === walletAddress.toLowerCase();
874
+ const baseUrl = resolveX402BaseUrl({ "api-url": flags["api-url"] });
875
+ const url = buildX402Url(baseUrl, target);
876
+ if (flags.verbose) {
877
+ console.error(`[x402] billing API base URL: ${baseUrl}`);
878
+ console.error(`[x402] endpoint: ${url}`);
879
+ console.error(`[x402] target: ${target.type} ${target.address} (self=${targetIsSelf})`);
880
+ }
881
+ const amountStr = flags.amount ?? await input2({
882
+ message: `How many dollars of credits to purchase? (minimum: $${MINIMUM_DOLLARS})`,
883
+ validate: (val) => {
884
+ const n = parseInt(val, 10);
885
+ if (isNaN(n) || n <= 0) return "Enter a positive whole number";
886
+ if (n.toString() !== val.trim()) return "Enter a whole dollar amount (no cents)";
887
+ if (n < MINIMUM_DOLLARS) return `Minimum purchase is $${MINIMUM_DOLLARS}`;
888
+ return true;
889
+ }
890
+ });
891
+ const dollars = parseInt(amountStr, 10);
892
+ if (isNaN(dollars) || dollars < MINIMUM_DOLLARS) {
893
+ this.error(`Minimum purchase is $${MINIMUM_DOLLARS}`);
894
+ }
895
+ const amountCents = dollars * 100;
896
+ if (target.type === "app") {
897
+ this.log(` ${chalk2.bold("Crediting app:")} ${target.address}`);
898
+ } else if (!targetIsSelf) {
899
+ this.log(` ${chalk2.bold("Crediting creator:")} ${target.address}`);
900
+ }
901
+ const { key } = await requirePrivateKey2({ privateKey: flags["private-key"] });
902
+ const account = privateKeyToAccount4(addHexPrefix2(key));
903
+ this.log(`
904
+ Purchasing ${chalk2.bold(`$${dollars}`)} in credits over x402...`);
905
+ const result = await purchaseCreditsX402({
906
+ url,
907
+ amountCents,
908
+ account,
909
+ verbose: flags.verbose
910
+ });
911
+ this.log(` ${chalk2.green("\u2713")} x402 payment settled`);
912
+ this.log(` ${chalk2.bold("Transaction:")} ${result.txHash}`);
913
+ this.log(` ${chalk2.bold("Payment ID:")} ${result.paymentId}`);
914
+ this.log(
915
+ ` ${chalk2.bold("Credits added:")} ${chalk2.cyan(`$${(result.creditedCents / 100).toFixed(2)}`)}`
916
+ );
917
+ if (targetIsSelf) {
918
+ await this.pollForCredits(billing, flags, baselineTotal, dollars);
919
+ } else {
920
+ this.log(
921
+ `
922
+ Credits applied to ${target.type} ${target.address}. Check that account's balance with: ecloud billing status
923
+ `
924
+ );
925
+ }
926
+ }
549
927
  async pollForCredits(billing, flags, baselineTotal, amountPurchased) {
550
928
  this.log(chalk2.gray("\n Waiting for credits to appear..."));
551
929
  const startTime = Date.now();
@@ -586,6 +964,9 @@ ${chalk2.yellow(" No USDC in wallet.")}`);
586
964
  }
587
965
  };
588
966
  export {
589
- BillingTopUp as default
967
+ buildX402Url,
968
+ BillingTopUp as default,
969
+ resolveX402BaseUrl,
970
+ resolveX402Target
590
971
  };
591
972
  //# sourceMappingURL=top-up.js.map