@layr-labs/ecloud-cli 0.4.0-dev.1 → 0.4.0-dev.3
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 -0
- package/bin/dev.js +0 -0
- package/dist/commands/auth/generate.js +0 -0
- package/dist/commands/auth/login.js +0 -0
- package/dist/commands/auth/logout.js +0 -0
- package/dist/commands/auth/migrate.js +0 -0
- package/dist/commands/auth/whoami.js +1 -0
- package/dist/commands/auth/whoami.js.map +1 -1
- package/dist/commands/billing/__tests__/status.test.js +89 -22
- package/dist/commands/billing/__tests__/status.test.js.map +1 -1
- package/dist/commands/billing/__tests__/subscribe.test.js +88 -20
- package/dist/commands/billing/__tests__/subscribe.test.js.map +1 -1
- package/dist/commands/billing/__tests__/top-up.test.js +139 -201
- package/dist/commands/billing/__tests__/top-up.test.js.map +1 -1
- package/dist/commands/billing/cancel.js +88 -19
- package/dist/commands/billing/cancel.js.map +1 -1
- package/dist/commands/billing/status.js +89 -21
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/subscribe.js +88 -19
- package/dist/commands/billing/subscribe.js.map +1 -1
- package/dist/commands/billing/top-up.js +102 -91
- package/dist/commands/billing/top-up.js.map +1 -1
- package/dist/commands/compute/app/configure/tls.js +0 -0
- package/dist/commands/compute/app/create.js +1 -0
- package/dist/commands/compute/app/create.js.map +1 -1
- package/dist/commands/compute/app/deploy.js +38 -24
- package/dist/commands/compute/app/deploy.js.map +1 -1
- package/dist/commands/compute/app/info.js +2 -1
- package/dist/commands/compute/app/info.js.map +1 -1
- package/dist/commands/compute/app/list.js +2 -1
- package/dist/commands/compute/app/list.js.map +1 -1
- package/dist/commands/compute/app/logs.js +5 -8
- package/dist/commands/compute/app/logs.js.map +1 -1
- package/dist/commands/compute/app/profile/set.js +5 -8
- package/dist/commands/compute/app/profile/set.js.map +1 -1
- package/dist/commands/compute/app/releases.js +2 -1
- package/dist/commands/compute/app/releases.js.map +1 -1
- package/dist/commands/compute/app/start.js +5 -8
- package/dist/commands/compute/app/start.js.map +1 -1
- package/dist/commands/compute/app/stop.js +5 -8
- package/dist/commands/compute/app/stop.js.map +1 -1
- package/dist/commands/compute/app/terminate.js +5 -8
- package/dist/commands/compute/app/terminate.js.map +1 -1
- package/dist/commands/compute/app/upgrade.js +36 -14
- package/dist/commands/compute/app/upgrade.js.map +1 -1
- package/dist/commands/compute/build/info.js +9 -12
- package/dist/commands/compute/build/info.js.map +1 -1
- package/dist/commands/compute/build/list.js +9 -12
- package/dist/commands/compute/build/list.js.map +1 -1
- package/dist/commands/compute/build/logs.js +9 -12
- package/dist/commands/compute/build/logs.js.map +1 -1
- package/dist/commands/compute/build/status.js +9 -12
- package/dist/commands/compute/build/status.js.map +1 -1
- package/dist/commands/compute/build/submit.js +32 -10
- package/dist/commands/compute/build/submit.js.map +1 -1
- package/dist/commands/compute/build/verify.js +9 -12
- package/dist/commands/compute/build/verify.js.map +1 -1
- package/dist/commands/compute/environment/list.js +0 -0
- package/dist/commands/compute/environment/set.js +1 -0
- package/dist/commands/compute/environment/set.js.map +1 -1
- package/dist/commands/compute/environment/show.js +0 -0
- package/dist/commands/compute/undelegate.js +5 -8
- package/dist/commands/compute/undelegate.js.map +1 -1
- package/dist/commands/telemetry/disable.js +0 -0
- package/dist/commands/telemetry/enable.js +0 -0
- package/dist/commands/telemetry/status.js +0 -0
- package/dist/commands/upgrade.js +0 -0
- package/dist/commands/version.js +0 -0
- package/package.json +15 -14
- package/LICENSE +0 -7
|
@@ -12,9 +12,7 @@ import {
|
|
|
12
12
|
createBillingModule,
|
|
13
13
|
createBuildModule,
|
|
14
14
|
getEnvironmentConfig as getEnvironmentConfig3,
|
|
15
|
-
requirePrivateKey
|
|
16
|
-
getPrivateKeyWithSource,
|
|
17
|
-
addHexPrefix as addHexPrefix2
|
|
15
|
+
requirePrivateKey
|
|
18
16
|
} from "@layr-labs/ecloud-sdk";
|
|
19
17
|
|
|
20
18
|
// src/flags.ts
|
|
@@ -169,6 +167,7 @@ var CONFIG_DIR = path2.join(os2.homedir(), ".eigenx");
|
|
|
169
167
|
var APPS_DIR = path2.join(CONFIG_DIR, "apps");
|
|
170
168
|
|
|
171
169
|
// src/utils/prompts.ts
|
|
170
|
+
import { execSync } from "child_process";
|
|
172
171
|
async function getPrivateKeyInteractive(privateKey) {
|
|
173
172
|
if (privateKey) {
|
|
174
173
|
if (!validatePrivateKeyFormat(privateKey)) {
|
|
@@ -176,8 +175,8 @@ async function getPrivateKeyInteractive(privateKey) {
|
|
|
176
175
|
}
|
|
177
176
|
return privateKey;
|
|
178
177
|
}
|
|
179
|
-
const { getPrivateKeyWithSource
|
|
180
|
-
const result = await
|
|
178
|
+
const { getPrivateKeyWithSource } = await import("@layr-labs/ecloud-sdk");
|
|
179
|
+
const result = await getPrivateKeyWithSource({ privateKey: void 0 });
|
|
181
180
|
if (result) {
|
|
182
181
|
return result.key;
|
|
183
182
|
}
|
|
@@ -196,6 +195,50 @@ async function getPrivateKeyInteractive(privateKey) {
|
|
|
196
195
|
});
|
|
197
196
|
return key.trim();
|
|
198
197
|
}
|
|
198
|
+
async function getEnvironmentInteractive(environment) {
|
|
199
|
+
if (environment) {
|
|
200
|
+
try {
|
|
201
|
+
getEnvironmentConfig2(environment);
|
|
202
|
+
if (!isEnvironmentAvailable(environment)) {
|
|
203
|
+
throw new Error(`Environment ${environment} is not available in this build`);
|
|
204
|
+
}
|
|
205
|
+
return environment;
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const availableEnvs = getAvailableEnvironments();
|
|
210
|
+
let defaultEnv;
|
|
211
|
+
const configDefaultEnv = getDefaultEnvironment();
|
|
212
|
+
if (configDefaultEnv && availableEnvs.includes(configDefaultEnv)) {
|
|
213
|
+
try {
|
|
214
|
+
getEnvironmentConfig2(configDefaultEnv);
|
|
215
|
+
defaultEnv = configDefaultEnv;
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const choices = [];
|
|
220
|
+
if (availableEnvs.includes("sepolia")) {
|
|
221
|
+
choices.push({ name: "sepolia - Ethereum Sepolia testnet", value: "sepolia" });
|
|
222
|
+
}
|
|
223
|
+
if (availableEnvs.includes("sepolia-dev")) {
|
|
224
|
+
choices.push({ name: "sepolia-dev - Ethereum Sepolia testnet (dev)", value: "sepolia-dev" });
|
|
225
|
+
}
|
|
226
|
+
if (availableEnvs.includes("mainnet-alpha")) {
|
|
227
|
+
choices.push({
|
|
228
|
+
name: "mainnet-alpha - Ethereum mainnet (\u26A0\uFE0F uses real funds)",
|
|
229
|
+
value: "mainnet-alpha"
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (choices.length === 0) {
|
|
233
|
+
throw new Error("No environments available in this build");
|
|
234
|
+
}
|
|
235
|
+
const env = await select({
|
|
236
|
+
message: "Select environment:",
|
|
237
|
+
choices,
|
|
238
|
+
default: defaultEnv
|
|
239
|
+
});
|
|
240
|
+
return env;
|
|
241
|
+
}
|
|
199
242
|
var MAX_IMAGE_SIZE = 4 * 1024 * 1024;
|
|
200
243
|
|
|
201
244
|
// src/flags.ts
|
|
@@ -222,38 +265,41 @@ var commonFlags = {
|
|
|
222
265
|
default: false
|
|
223
266
|
})
|
|
224
267
|
};
|
|
268
|
+
async function validateCommonFlags(flags, options) {
|
|
269
|
+
flags["environment"] = await getEnvironmentInteractive(flags["environment"]);
|
|
270
|
+
if (options?.requirePrivateKey !== false) {
|
|
271
|
+
flags["private-key"] = await getPrivateKeyInteractive(flags["private-key"]);
|
|
272
|
+
}
|
|
273
|
+
return flags;
|
|
274
|
+
}
|
|
225
275
|
|
|
226
276
|
// src/client.ts
|
|
227
|
-
import { createWalletClient, custom } from "viem";
|
|
228
|
-
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
229
277
|
async function createBillingClient(flags) {
|
|
230
|
-
|
|
278
|
+
flags = await validateCommonFlags(flags);
|
|
279
|
+
const environment = flags.environment;
|
|
280
|
+
const environmentConfig = getEnvironmentConfig3(environment);
|
|
281
|
+
const rpcUrl = flags["rpc-url"] || environmentConfig.billingRPCURL || environmentConfig.defaultRPCURL;
|
|
282
|
+
const { key: privateKey, source } = await requirePrivateKey({
|
|
231
283
|
privateKey: flags["private-key"]
|
|
232
284
|
});
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
})
|
|
285
|
+
if (flags.verbose) {
|
|
286
|
+
console.log(`Using private key from: ${source}`);
|
|
287
|
+
}
|
|
288
|
+
const { walletClient, publicClient } = createViemClients({
|
|
289
|
+
privateKey,
|
|
290
|
+
rpcUrl,
|
|
291
|
+
environment
|
|
242
292
|
});
|
|
243
293
|
return createBillingModule({
|
|
244
|
-
verbose: flags.verbose
|
|
294
|
+
verbose: flags.verbose,
|
|
245
295
|
walletClient,
|
|
296
|
+
publicClient,
|
|
297
|
+
environment,
|
|
246
298
|
skipTelemetry: true
|
|
247
|
-
// CLI already has telemetry, skip SDK telemetry
|
|
248
299
|
});
|
|
249
300
|
}
|
|
250
301
|
|
|
251
302
|
// src/commands/billing/top-up.ts
|
|
252
|
-
import {
|
|
253
|
-
getEnvironmentConfig as getEnvironmentConfig4,
|
|
254
|
-
USDCCreditsABI,
|
|
255
|
-
ERC20ABI
|
|
256
|
-
} from "@layr-labs/ecloud-sdk";
|
|
257
303
|
import { formatUnits } from "viem";
|
|
258
304
|
import chalk2 from "chalk";
|
|
259
305
|
import { input as input2 } from "@inquirer/prompts";
|
|
@@ -343,19 +389,7 @@ var BillingTopUp = class _BillingTopUp extends Command {
|
|
|
343
389
|
return withTelemetry(this, async () => {
|
|
344
390
|
const { flags } = await this.parse(_BillingTopUp);
|
|
345
391
|
const billing = await createBillingClient(flags);
|
|
346
|
-
const
|
|
347
|
-
const { publicClient, walletClient, address: walletAddress } = createViemClients({
|
|
348
|
-
privateKey,
|
|
349
|
-
rpcUrl: flags["rpc-url"],
|
|
350
|
-
environment: flags.environment
|
|
351
|
-
});
|
|
352
|
-
const environmentConfig = getEnvironmentConfig4(flags.environment);
|
|
353
|
-
const usdcCreditsAddress = environmentConfig.usdcCreditsAddress;
|
|
354
|
-
if (!usdcCreditsAddress) {
|
|
355
|
-
this.error(
|
|
356
|
-
`USDCCredits contract is not configured for environment "${flags.environment}". USDC credit purchasing is only available on environments with a deployed USDCCredits contract.`
|
|
357
|
-
);
|
|
358
|
-
}
|
|
392
|
+
const walletAddress = billing.address;
|
|
359
393
|
const targetAccount = flags.account ?? walletAddress;
|
|
360
394
|
this.log(`
|
|
361
395
|
${chalk2.bold("Purchase EigenCompute credits")}`);
|
|
@@ -365,34 +399,22 @@ ${chalk2.bold("Purchase EigenCompute credits")}`);
|
|
|
365
399
|
if (targetAccount !== walletAddress) {
|
|
366
400
|
this.log(` ${chalk2.bold("Target:")} ${targetAccount}`);
|
|
367
401
|
}
|
|
368
|
-
let
|
|
402
|
+
let baselineTotal;
|
|
369
403
|
try {
|
|
370
404
|
const status = await billing.getStatus({
|
|
371
405
|
productId: flags.product
|
|
372
406
|
});
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
407
|
+
const remaining = status.remainingCredits ?? 0;
|
|
408
|
+
const applied = status.creditsApplied ?? 0;
|
|
409
|
+
baselineTotal = remaining + applied;
|
|
410
|
+
if (status.remainingCredits !== void 0) {
|
|
411
|
+
this.log(` ${chalk2.bold("Credits:")} ${chalk2.cyan(`$${status.remainingCredits.toFixed(2)}`)}`);
|
|
376
412
|
}
|
|
377
413
|
} catch {
|
|
378
414
|
this.debug("Could not fetch current credit balance");
|
|
379
415
|
}
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
abi: USDCCreditsABI,
|
|
383
|
-
functionName: "usdc"
|
|
384
|
-
});
|
|
385
|
-
const minimumPurchase = await publicClient.readContract({
|
|
386
|
-
address: usdcCreditsAddress,
|
|
387
|
-
abi: USDCCreditsABI,
|
|
388
|
-
functionName: "minimumPurchase"
|
|
389
|
-
});
|
|
390
|
-
const usdcBalance = await publicClient.readContract({
|
|
391
|
-
address: usdcAddress,
|
|
392
|
-
abi: ERC20ABI,
|
|
393
|
-
functionName: "balanceOf",
|
|
394
|
-
args: [walletAddress]
|
|
395
|
-
});
|
|
416
|
+
const onChainState = await billing.getTopUpInfo();
|
|
417
|
+
const { usdcBalance, minimumPurchase } = onChainState;
|
|
396
418
|
const balanceFormatted = formatUnits(usdcBalance, 6);
|
|
397
419
|
this.log(` ${chalk2.bold("USDC:")} ${balanceFormatted} USDC`);
|
|
398
420
|
if (usdcBalance === BigInt(0)) {
|
|
@@ -429,36 +451,11 @@ ${chalk2.yellow(" No USDC in wallet.")}`);
|
|
|
429
451
|
}
|
|
430
452
|
this.log(`
|
|
431
453
|
Purchasing ${chalk2.bold(`$${amountFloat.toFixed(2)}`)} in credits...`);
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
functionName: "allowance",
|
|
436
|
-
args: [walletAddress, usdcCreditsAddress]
|
|
437
|
-
});
|
|
438
|
-
if (currentAllowance < amountRaw) {
|
|
439
|
-
this.log(chalk2.gray(" Approving USDC spend..."));
|
|
440
|
-
const approveTx = await walletClient.writeContract({
|
|
441
|
-
address: usdcAddress,
|
|
442
|
-
abi: ERC20ABI,
|
|
443
|
-
functionName: "approve",
|
|
444
|
-
args: [usdcCreditsAddress, amountRaw],
|
|
445
|
-
chain: walletClient.chain,
|
|
446
|
-
account: walletClient.account
|
|
447
|
-
});
|
|
448
|
-
await publicClient.waitForTransactionReceipt({ hash: approveTx });
|
|
449
|
-
this.log(` ${chalk2.green("\u2713")} Approved`);
|
|
450
|
-
}
|
|
451
|
-
this.log(chalk2.gray(" Submitting credit purchase..."));
|
|
452
|
-
const purchaseTx = await walletClient.writeContract({
|
|
453
|
-
address: usdcCreditsAddress,
|
|
454
|
-
abi: USDCCreditsABI,
|
|
455
|
-
functionName: "purchaseCreditsFor",
|
|
456
|
-
args: [amountRaw, targetAccount],
|
|
457
|
-
chain: walletClient.chain,
|
|
458
|
-
account: walletClient.account
|
|
454
|
+
const { txHash } = await billing.topUp({
|
|
455
|
+
amount: amountRaw,
|
|
456
|
+
account: targetAccount
|
|
459
457
|
});
|
|
460
|
-
|
|
461
|
-
this.log(` ${chalk2.green("\u2713")} Transaction confirmed: ${receipt.transactionHash}`);
|
|
458
|
+
this.log(` ${chalk2.green("\u2713")} Transaction confirmed: ${txHash}`);
|
|
462
459
|
this.log(chalk2.gray("\n Waiting for credits to appear..."));
|
|
463
460
|
const startTime = Date.now();
|
|
464
461
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
@@ -467,11 +464,25 @@ ${chalk2.yellow(" No USDC in wallet.")}`);
|
|
|
467
464
|
const status = await billing.getStatus({
|
|
468
465
|
productId: flags.product
|
|
469
466
|
});
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
467
|
+
const remaining = status.remainingCredits ?? 0;
|
|
468
|
+
const applied = status.creditsApplied ?? 0;
|
|
469
|
+
const currentTotal = remaining + applied;
|
|
470
|
+
this.debug(`Poll: remaining=${remaining}, applied=${applied}, total=${currentTotal}, baseline=${baselineTotal}`);
|
|
471
|
+
if (baselineTotal === void 0 || currentTotal > baselineTotal) {
|
|
472
|
+
const creditsAdded = baselineTotal !== void 0 ? currentTotal - baselineTotal : void 0;
|
|
473
|
+
const isMatched = creditsAdded !== void 0 && Math.abs(creditsAdded - amountFloat * 2) < 0.01;
|
|
474
|
+
const appliedFromTopUp = creditsAdded !== void 0 ? creditsAdded - remaining : 0;
|
|
475
|
+
this.log(`
|
|
476
|
+
${chalk2.green("\u2713")} Credits received: ${chalk2.cyan(`$${(creditsAdded ?? amountFloat).toFixed(2)}`)}`);
|
|
477
|
+
if (isMatched) {
|
|
478
|
+
this.log(` ${chalk2.green("\u2713")} Includes $${amountFloat.toFixed(2)} match bonus!`);
|
|
479
|
+
}
|
|
480
|
+
if (remaining > 0) {
|
|
481
|
+
this.log(` Remaining balance: ${chalk2.cyan(`$${remaining.toFixed(2)}`)}`);
|
|
482
|
+
}
|
|
483
|
+
if (appliedFromTopUp > 0) {
|
|
484
|
+
this.log(` ${chalk2.gray(`$${appliedFromTopUp.toFixed(2)} applied to current bill`)}`);
|
|
485
|
+
}
|
|
475
486
|
this.log();
|
|
476
487
|
return;
|
|
477
488
|
}
|
|
@@ -494,95 +505,47 @@ import { input as input3 } from "@inquirer/prompts";
|
|
|
494
505
|
vi.mock("../../../client", () => ({
|
|
495
506
|
createBillingClient: vi.fn()
|
|
496
507
|
}));
|
|
497
|
-
vi.mock("../../../utils/viemClients", () => ({
|
|
498
|
-
createViemClients: vi.fn()
|
|
499
|
-
}));
|
|
500
508
|
vi.mock("../../../telemetry", () => ({
|
|
501
509
|
withTelemetry: vi.fn((_cmd, fn) => fn())
|
|
502
510
|
}));
|
|
503
511
|
vi.mock("@inquirer/prompts", () => ({
|
|
504
512
|
input: vi.fn()
|
|
505
513
|
}));
|
|
506
|
-
vi.mock("@layr-labs/ecloud-sdk", async () => {
|
|
507
|
-
const actual = await vi.importActual("@layr-labs/ecloud-sdk");
|
|
508
|
-
return {
|
|
509
|
-
...actual,
|
|
510
|
-
getEnvironmentConfig: vi.fn().mockReturnValue({
|
|
511
|
-
name: "sepolia",
|
|
512
|
-
build: "dev",
|
|
513
|
-
chainID: BigInt(11155111),
|
|
514
|
-
appControllerAddress: "0xa86DC1C47cb2518327fB4f9A1627F51966c83B92",
|
|
515
|
-
permissionControllerAddress: "0x44632dfBdCb6D3E21EF613B0ca8A6A0c618F5a37",
|
|
516
|
-
erc7702DelegatorAddress: "0x63c0c19a282a1b52b07dd5a65b58948a07dae32b",
|
|
517
|
-
kmsServerURL: "http://10.128.0.57:8080",
|
|
518
|
-
userApiServerURL: "https://userapi-compute-sepolia-dev.eigencloud.xyz",
|
|
519
|
-
defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
520
|
-
usdcCreditsAddress: "0xbdA3897c3A428763B59015C64AB766c288C97376"
|
|
521
|
-
})
|
|
522
|
-
};
|
|
523
|
-
});
|
|
524
514
|
var WALLET_ADDRESS = "0x1234567890abcdef1234567890abcdef12345678";
|
|
525
|
-
var USDC_ADDRESS = "0xUSDCAddress0000000000000000000000000000";
|
|
526
|
-
var USDC_CREDITS_ADDRESS = "0xbdA3897c3A428763B59015C64AB766c288C97376";
|
|
527
515
|
var TX_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
|
|
528
516
|
describe("ecloud billing top-up", () => {
|
|
529
517
|
let logOutput;
|
|
530
518
|
let mockBilling;
|
|
531
|
-
let mockPublicClient;
|
|
532
|
-
let mockWalletClient;
|
|
533
519
|
beforeEach(() => {
|
|
534
520
|
vi.useFakeTimers();
|
|
535
521
|
vi.clearAllMocks();
|
|
536
522
|
logOutput = [];
|
|
537
523
|
mockBilling = {
|
|
538
524
|
address: WALLET_ADDRESS,
|
|
539
|
-
getStatus: vi.fn()
|
|
525
|
+
getStatus: vi.fn(),
|
|
526
|
+
getTopUpInfo: vi.fn(),
|
|
527
|
+
topUp: vi.fn()
|
|
540
528
|
};
|
|
541
529
|
createBillingClient.mockResolvedValue(mockBilling);
|
|
542
|
-
mockPublicClient = {
|
|
543
|
-
readContract: vi.fn(),
|
|
544
|
-
waitForTransactionReceipt: vi.fn().mockResolvedValue({
|
|
545
|
-
transactionHash: TX_HASH
|
|
546
|
-
})
|
|
547
|
-
};
|
|
548
|
-
mockWalletClient = {
|
|
549
|
-
writeContract: vi.fn().mockResolvedValue(TX_HASH),
|
|
550
|
-
chain: { id: 11155111 },
|
|
551
|
-
account: { address: WALLET_ADDRESS }
|
|
552
|
-
};
|
|
553
|
-
createViemClients.mockReturnValue({
|
|
554
|
-
publicClient: mockPublicClient,
|
|
555
|
-
walletClient: mockWalletClient,
|
|
556
|
-
address: WALLET_ADDRESS,
|
|
557
|
-
chain: { id: 11155111 }
|
|
558
|
-
});
|
|
559
530
|
input3.mockResolvedValue("50");
|
|
560
531
|
});
|
|
561
532
|
afterEach(() => {
|
|
562
533
|
vi.useRealTimers();
|
|
563
534
|
});
|
|
564
|
-
function
|
|
535
|
+
function setupOnChainState(overrides = {}) {
|
|
565
536
|
const {
|
|
566
|
-
usdcAddress =
|
|
537
|
+
usdcAddress = "0xUSDCAddress0000000000000000000000000000",
|
|
567
538
|
minimumPurchase = BigInt(1e6),
|
|
568
539
|
// 1 USDC
|
|
569
|
-
|
|
540
|
+
usdcBalance = BigInt(1e8),
|
|
570
541
|
// 100 USDC
|
|
571
|
-
|
|
542
|
+
currentAllowance = BigInt(0)
|
|
572
543
|
} = overrides;
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
return Promise.resolve(minimumPurchase);
|
|
579
|
-
case "balanceOf":
|
|
580
|
-
return Promise.resolve(balanceOf);
|
|
581
|
-
case "allowance":
|
|
582
|
-
return Promise.resolve(allowance);
|
|
583
|
-
default:
|
|
584
|
-
return Promise.reject(new Error(`Unexpected readContract: ${functionName}`));
|
|
585
|
-
}
|
|
544
|
+
mockBilling.getTopUpInfo.mockResolvedValue({
|
|
545
|
+
usdcAddress,
|
|
546
|
+
minimumPurchase,
|
|
547
|
+
usdcBalance,
|
|
548
|
+
currentAllowance
|
|
586
549
|
});
|
|
587
550
|
}
|
|
588
551
|
function createCommand(flags = {}) {
|
|
@@ -602,8 +565,9 @@ describe("ecloud billing top-up", () => {
|
|
|
602
565
|
});
|
|
603
566
|
return cmd;
|
|
604
567
|
}
|
|
605
|
-
it("happy path: sufficient balance,
|
|
606
|
-
|
|
568
|
+
it("happy path: sufficient balance, purchase succeeds", async () => {
|
|
569
|
+
setupOnChainState();
|
|
570
|
+
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
607
571
|
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
|
|
608
572
|
const cmd = createCommand({ amount: "50" });
|
|
609
573
|
const promise = cmd.run();
|
|
@@ -615,27 +579,17 @@ describe("ecloud billing top-up", () => {
|
|
|
615
579
|
expect(fullOutput).toContain(WALLET_ADDRESS);
|
|
616
580
|
expect(fullOutput).toContain("$10.00");
|
|
617
581
|
expect(fullOutput).toContain("100 USDC");
|
|
618
|
-
expect(fullOutput).toContain("
|
|
619
|
-
expect(fullOutput).toContain("Approved");
|
|
620
|
-
expect(fullOutput).toContain("Submitting credit purchase");
|
|
582
|
+
expect(fullOutput).toContain("Purchasing");
|
|
621
583
|
expect(fullOutput).toContain("Transaction confirmed");
|
|
622
584
|
expect(fullOutput).toContain("Credits received");
|
|
623
585
|
expect(fullOutput).toContain("$60.00");
|
|
624
|
-
expect(
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
})
|
|
629
|
-
);
|
|
630
|
-
expect(mockWalletClient.writeContract).toHaveBeenCalledWith(
|
|
631
|
-
expect.objectContaining({
|
|
632
|
-
functionName: "purchaseCreditsFor",
|
|
633
|
-
args: [BigInt(5e7), WALLET_ADDRESS]
|
|
634
|
-
})
|
|
635
|
-
);
|
|
586
|
+
expect(mockBilling.topUp).toHaveBeenCalledWith({
|
|
587
|
+
amount: BigInt(5e7),
|
|
588
|
+
account: WALLET_ADDRESS
|
|
589
|
+
});
|
|
636
590
|
});
|
|
637
591
|
it("zero USDC balance: exits with fund wallet message", async () => {
|
|
638
|
-
|
|
592
|
+
setupOnChainState({ usdcBalance: BigInt(0) });
|
|
639
593
|
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "inactive" });
|
|
640
594
|
const cmd = createCommand({ amount: "50" });
|
|
641
595
|
await cmd.run();
|
|
@@ -643,17 +597,18 @@ describe("ecloud billing top-up", () => {
|
|
|
643
597
|
expect(fullOutput).toContain("No USDC in wallet");
|
|
644
598
|
expect(fullOutput).toContain("Send USDC on Sepolia to");
|
|
645
599
|
expect(fullOutput).toContain(WALLET_ADDRESS);
|
|
646
|
-
expect(
|
|
600
|
+
expect(mockBilling.topUp).not.toHaveBeenCalled();
|
|
647
601
|
});
|
|
648
602
|
it("below minimum purchase: shows error", async () => {
|
|
649
|
-
|
|
603
|
+
setupOnChainState({ minimumPurchase: BigInt(1e7) });
|
|
650
604
|
mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "inactive" });
|
|
651
605
|
const cmd = createCommand({ amount: "5" });
|
|
652
606
|
await expect(cmd.run()).rejects.toThrow("Minimum purchase is 10 USDC");
|
|
653
607
|
});
|
|
654
|
-
it("--account flag: passes different address to
|
|
608
|
+
it("--account flag: passes different address to topUp", async () => {
|
|
655
609
|
const targetAccount = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
|
656
|
-
|
|
610
|
+
setupOnChainState();
|
|
611
|
+
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
657
612
|
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
|
|
658
613
|
const cmd = createCommand({ amount: "50", account: targetAccount });
|
|
659
614
|
const promise = cmd.run();
|
|
@@ -663,33 +618,14 @@ describe("ecloud billing top-up", () => {
|
|
|
663
618
|
await promise;
|
|
664
619
|
const fullOutput = logOutput.join("\n");
|
|
665
620
|
expect(fullOutput).toContain(targetAccount);
|
|
666
|
-
expect(
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
})
|
|
671
|
-
);
|
|
672
|
-
});
|
|
673
|
-
it("allowance already sufficient: skips approve step", async () => {
|
|
674
|
-
setupContractReads({ allowance: BigInt(1e8) });
|
|
675
|
-
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
|
|
676
|
-
const cmd = createCommand({ amount: "50" });
|
|
677
|
-
const promise = cmd.run();
|
|
678
|
-
for (let i = 0; i < 10; i++) {
|
|
679
|
-
await vi.advanceTimersByTimeAsync(5e3);
|
|
680
|
-
}
|
|
681
|
-
await promise;
|
|
682
|
-
const fullOutput = logOutput.join("\n");
|
|
683
|
-
expect(fullOutput).not.toContain("Approving USDC spend");
|
|
684
|
-
expect(mockWalletClient.writeContract).toHaveBeenCalledTimes(1);
|
|
685
|
-
expect(mockWalletClient.writeContract).toHaveBeenCalledWith(
|
|
686
|
-
expect.objectContaining({
|
|
687
|
-
functionName: "purchaseCreditsFor"
|
|
688
|
-
})
|
|
689
|
-
);
|
|
621
|
+
expect(mockBilling.topUp).toHaveBeenCalledWith({
|
|
622
|
+
amount: BigInt(5e7),
|
|
623
|
+
account: targetAccount
|
|
624
|
+
});
|
|
690
625
|
});
|
|
691
626
|
it("billing API poll timeout: shows timeout message", async () => {
|
|
692
|
-
|
|
627
|
+
setupOnChainState();
|
|
628
|
+
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
693
629
|
mockBilling.getStatus.mockResolvedValue({
|
|
694
630
|
subscriptionStatus: "active",
|
|
695
631
|
remainingCredits: 10
|
|
@@ -703,7 +639,8 @@ describe("ecloud billing top-up", () => {
|
|
|
703
639
|
expect(fullOutput).toContain("ecloud billing status");
|
|
704
640
|
});
|
|
705
641
|
it("uses --amount flag when provided (skips prompt)", async () => {
|
|
706
|
-
|
|
642
|
+
setupOnChainState();
|
|
643
|
+
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
707
644
|
mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "inactive" }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 100 });
|
|
708
645
|
const cmd = createCommand({ amount: "100" });
|
|
709
646
|
const promise = cmd.run();
|
|
@@ -714,14 +651,15 @@ describe("ecloud billing top-up", () => {
|
|
|
714
651
|
expect(input3).not.toHaveBeenCalled();
|
|
715
652
|
});
|
|
716
653
|
it("does not fail if status check errors", async () => {
|
|
717
|
-
|
|
654
|
+
setupOnChainState();
|
|
655
|
+
mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
|
|
718
656
|
mockBilling.getStatus.mockRejectedValue(new Error("API unavailable"));
|
|
719
657
|
const cmd = createCommand({ amount: "50" });
|
|
720
658
|
const promise = cmd.run();
|
|
721
659
|
await vi.advanceTimersByTimeAsync(2e5);
|
|
722
660
|
await promise;
|
|
723
661
|
const fullOutput = logOutput.join("\n");
|
|
724
|
-
expect(fullOutput).toContain("
|
|
662
|
+
expect(fullOutput).toContain("Purchasing");
|
|
725
663
|
expect(fullOutput).toContain("Transaction confirmed");
|
|
726
664
|
expect(fullOutput).toContain("Credits haven't appeared yet");
|
|
727
665
|
});
|