@layr-labs/ecloud-sdk 1.0.0-dev.7 → 1.0.0-dev.8

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/dist/index.js CHANGED
@@ -425,7 +425,74 @@ var PushPermissionError = class extends Error {
425
425
  import Handlebars from "handlebars";
426
426
 
427
427
  // src/client/common/templates/Dockerfile.layered.tmpl
428
- var Dockerfile_layered_default = '{{#if includeTLS}}\n# Get Caddy from official image\nFROM caddy:2.10.2-alpine AS caddy\n{{/if}}\n\nFROM {{baseImage}}\n\n{{#if originalUser}}\n# Switch to root to perform setup (base image has non-root USER: {{originalUser}})\nUSER root\n{{/if}}\n\n# Copy core TEE components\nCOPY compute-source-env.sh /usr/local/bin/\nCOPY kms-client /usr/local/bin/\nCOPY kms-signing-public-key.pem /usr/local/bin/\n\n{{#if includeTLS}}\n# Copy Caddy from official image\nCOPY --from=caddy /usr/bin/caddy /usr/local/bin/caddy\n\n# Copy TLS components\nCOPY tls-keygen /usr/local/bin/\nCOPY Caddyfile /etc/caddy/\n{{/if}}\n\n{{#if originalUser}}\n# Make binaries executable (755 for executables, 644 for keys)\nRUN chmod 755 /usr/local/bin/compute-source-env.sh \\\n && chmod 755 /usr/local/bin/kms-client{{#if includeTLS}} \\\n && chmod 755 /usr/local/bin/tls-keygen \\\n && chmod 755 /usr/local/bin/caddy{{/if}} \\\n && chmod 644 /usr/local/bin/kms-signing-public-key.pem\n\n# Store original user - entrypoint will drop privileges to this user after TEE setup\nENV __ECLOUD_ORIGINAL_USER={{originalUser}}\n{{else}}\n# Make binaries executable (preserve existing permissions, just add execute)\nRUN chmod +x /usr/local/bin/compute-source-env.sh \\\n && chmod +x /usr/local/bin/kms-client{{#if includeTLS}} \\\n && chmod +x /usr/local/bin/tls-keygen{{/if}}\n{{/if}}\n\n{{#if logRedirect}}\n\nLABEL tee.launch_policy.log_redirect={{logRedirect}}\n{{/if}}\n{{#if resourceUsageAllow}}\n\nLABEL tee.launch_policy.monitoring_memory_allow={{resourceUsageAllow}}\n{{/if}}\n\nLABEL eigenx_cli_version={{ecloudCLIVersion}}\nLABEL eigenx_vm_image=eigen\n\n{{#if includeTLS}}\n# Expose both HTTP and HTTPS ports for Caddy\nEXPOSE 80 443\n{{/if}}\n\nENTRYPOINT ["/usr/local/bin/compute-source-env.sh"]\nCMD {{{originalCmd}}}\n';
428
+ var Dockerfile_layered_default = `{{#if includeTLS}}
429
+ # Get Caddy from official image
430
+ FROM caddy:2.10.2-alpine AS caddy
431
+ {{/if}}
432
+
433
+ FROM {{baseImage}}
434
+
435
+ {{#if originalUser}}
436
+ # Switch to root to perform setup (base image has non-root USER: {{originalUser}})
437
+ USER root
438
+ {{/if}}
439
+
440
+ # Copy core TEE components
441
+ # CA bundle for kms-client / tls-keygen to validate HTTPS calls to
442
+ # eigencloud.xyz endpoints. Bundled at a non-standard path and consumed
443
+ # only via SSL_CERT_FILE in compute-source-env.sh, so the user's
444
+ # /etc/ssl/ is never touched.
445
+ COPY --from=alpine:3.20.10 /etc/ssl/certs/ca-certificates.crt /usr/local/share/eigenx-ca-certs.crt
446
+ COPY compute-source-env.sh /usr/local/bin/
447
+ COPY kms-client /usr/local/bin/
448
+ COPY kms-signing-public-key.pem /usr/local/bin/
449
+
450
+ {{#if includeTLS}}
451
+ # Copy Caddy from official image
452
+ COPY --from=caddy /usr/bin/caddy /usr/local/bin/caddy
453
+
454
+ # Copy TLS components
455
+ COPY tls-keygen /usr/local/bin/
456
+ COPY Caddyfile /etc/caddy/
457
+ {{/if}}
458
+
459
+ {{#if originalUser}}
460
+ # Make binaries executable (755 for executables, 644 for keys)
461
+ RUN chmod 755 /usr/local/bin/compute-source-env.sh \\
462
+ && chmod 755 /usr/local/bin/kms-client{{#if includeTLS}} \\
463
+ && chmod 755 /usr/local/bin/tls-keygen \\
464
+ && chmod 755 /usr/local/bin/caddy{{/if}} \\
465
+ && chmod 644 /usr/local/bin/kms-signing-public-key.pem
466
+
467
+ # Store original user - entrypoint will drop privileges to this user after TEE setup
468
+ ENV __ECLOUD_ORIGINAL_USER={{originalUser}}
469
+ {{else}}
470
+ # Make binaries executable (preserve existing permissions, just add execute)
471
+ RUN chmod +x /usr/local/bin/compute-source-env.sh \\
472
+ && chmod +x /usr/local/bin/kms-client{{#if includeTLS}} \\
473
+ && chmod +x /usr/local/bin/tls-keygen{{/if}}
474
+ {{/if}}
475
+
476
+ {{#if logRedirect}}
477
+
478
+ LABEL tee.launch_policy.log_redirect={{logRedirect}}
479
+ {{/if}}
480
+ {{#if resourceUsageAllow}}
481
+
482
+ LABEL tee.launch_policy.monitoring_memory_allow={{resourceUsageAllow}}
483
+ {{/if}}
484
+
485
+ LABEL eigenx_cli_version={{ecloudCLIVersion}}
486
+ LABEL eigenx_vm_image=eigen
487
+
488
+ {{#if includeTLS}}
489
+ # Expose both HTTP and HTTPS ports for Caddy
490
+ EXPOSE 80 443
491
+ {{/if}}
492
+
493
+ ENTRYPOINT ["/usr/local/bin/compute-source-env.sh"]
494
+ CMD {{{originalCmd}}}
495
+ `;
429
496
 
430
497
  // src/client/common/templates/dockerfileTemplate.ts
431
498
  function processDockerfileTemplate(data) {
@@ -442,7 +509,7 @@ echo "compute-source-env.sh: Running setup script..."
442
509
 
443
510
  # Fetch and source environment variables from KMS
444
511
  echo "Fetching secrets from KMS..."
445
- if /usr/local/bin/kms-client \\
512
+ if SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt /usr/local/bin/kms-client \\
446
513
  --kms-server-url "{{kmsServerURL}}" \\
447
514
  --kms-signing-key-file /usr/local/bin/kms-signing-public-key.pem \\
448
515
  --userapi-url "{{userAPIURL}}" \\
@@ -500,7 +567,8 @@ setup_tls() {
500
567
 
501
568
  echo "compute-source-env.sh: Obtaining TLS certificate using $challenge challenge..."
502
569
  # Pass the API URL for certificate persistence
503
- if ! MNEMONIC="$mnemonic" DOMAIN="$domain" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
570
+ if ! SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt \\
571
+ MNEMONIC="$mnemonic" DOMAIN="$domain" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
504
572
  -challenge "$challenge" \\
505
573
  $staging_flag; then
506
574
  echo "compute-source-env.sh: ERROR - Failed to obtain TLS certificate"
@@ -892,16 +960,7 @@ async function extractDigestFromSinglePlatform(manifest, imageRef) {
892
960
  architecture: inspectData[0].Architecture
893
961
  } : null;
894
962
  if (!config) {
895
- if (manifest.config?.digest) {
896
- const digest = hexStringToBytes32(manifest.config.digest);
897
- const registry = extractRegistryName(imageRef);
898
- return {
899
- digest,
900
- registry,
901
- platform: DOCKER_PLATFORM
902
- };
903
- }
904
- throw new Error(`Could not determine platform for ${imageRef}`);
963
+ throw createPlatformErrorMessage(imageRef, ["unknown (could not determine architecture)"]);
905
964
  }
906
965
  const platform2 = `${config.os}/${config.architecture}`;
907
966
  if (platform2 === DOCKER_PLATFORM) {
@@ -973,14 +1032,11 @@ Image: ${imageRef}
973
1032
  Found platform(s): ${platforms.join(", ")}
974
1033
  Required platform: ${DOCKER_PLATFORM}
975
1034
 
976
- To fix this issue:
977
- 1. Manual fix:
978
- a. Rebuild your image with the correct platform:
979
- docker build --platform ${DOCKER_PLATFORM} -t ${imageRef} .
980
- b. Push the rebuilt image to your remote registry:
981
- docker push ${imageRef}
982
-
983
- 2. Or use the SDK to build with the correct platform automatically.`;
1035
+ To fix, either:
1036
+ 1. Rebuild the image for ${DOCKER_PLATFORM} and push it:
1037
+ docker buildx build --platform ${DOCKER_PLATFORM} -t ${imageRef} --push .
1038
+ 2. Or use a verifiable build (--verifiable --repo <repo> --commit <sha>), which
1039
+ builds server-side and needs no local Docker.`;
984
1040
  return new Error(errorMsg);
985
1041
  }
986
1042
 
@@ -1085,6 +1141,8 @@ async function prepareRelease(options, logger) {
1085
1141
  logger.info(`Waiting ${REGISTRY_PROPAGATION_WAIT_SECONDS} seconds for registry propagation...`);
1086
1142
  await new Promise((resolve2) => setTimeout(resolve2, REGISTRY_PROPAGATION_WAIT_SECONDS * 1e3));
1087
1143
  } else {
1144
+ logger.info("Verifying image platform (linux/amd64)...");
1145
+ await getImageDigestAndName(imageRef);
1088
1146
  logger.info("Checking if image needs layering...");
1089
1147
  finalImageRef = await layerRemoteImageIfNeeded(
1090
1148
  {
@@ -6290,6 +6348,35 @@ async function undelegate(options, logger = noopLogger) {
6290
6348
  return hash;
6291
6349
  }
6292
6350
 
6351
+ // src/client/common/gas/insufficientGas.ts
6352
+ import { formatEther } from "viem";
6353
+ var InsufficientGasError = class extends Error {
6354
+ constructor(args) {
6355
+ const requiredEth = formatEther(args.requiredWei);
6356
+ const availableEth = formatEther(args.availableWei);
6357
+ super(
6358
+ `Insufficient ETH for gas: wallet ${args.address} has ${availableEth} ETH but this transaction needs ~${requiredEth} ETH.
6359
+ Compute credits do not pay on-chain gas \u2014 fund the wallet with ETH and retry.`
6360
+ );
6361
+ this.name = "InsufficientGasError";
6362
+ this.address = args.address;
6363
+ this.requiredWei = args.requiredWei;
6364
+ this.availableWei = args.availableWei;
6365
+ this.requiredEth = requiredEth;
6366
+ this.availableEth = availableEth;
6367
+ }
6368
+ };
6369
+ async function assertSufficientGas(args) {
6370
+ const availableWei = await args.publicClient.getBalance({ address: args.address });
6371
+ if (availableWei < args.gasEstimate.maxCostWei) {
6372
+ throw new InsufficientGasError({
6373
+ address: args.address,
6374
+ requiredWei: args.gasEstimate.maxCostWei,
6375
+ availableWei
6376
+ });
6377
+ }
6378
+ }
6379
+
6293
6380
  // src/client/common/utils/userapi.ts
6294
6381
  import axios2 from "axios";
6295
6382
 
@@ -6353,6 +6440,7 @@ import axios from "axios";
6353
6440
  var MAX_RETRIES = 5;
6354
6441
  var INITIAL_BACKOFF_MS = 1e3;
6355
6442
  var MAX_BACKOFF_MS = 3e4;
6443
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
6356
6444
  function sleep(ms) {
6357
6445
  return new Promise((resolve2) => setTimeout(resolve2, ms));
6358
6446
  }
@@ -6372,7 +6460,7 @@ async function requestWithRetry(config) {
6372
6460
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
6373
6461
  const res = await axios({ ...config, validateStatus: () => true });
6374
6462
  lastResponse = res;
6375
- if (res.status !== 429) {
6463
+ if (!RETRYABLE_STATUSES.has(res.status)) {
6376
6464
  return res;
6377
6465
  }
6378
6466
  if (attempt < MAX_RETRIES) {
@@ -6401,7 +6489,7 @@ var CanViewAppLogsPermission = "0x2fd3f2fe";
6401
6489
  var CanViewSensitiveAppInfoPermission = "0x0e67b22f";
6402
6490
  var CanUpdateAppProfilePermission = "0x036fef61";
6403
6491
  function getDefaultClientId() {
6404
- const version = true ? "1.0.0-dev.7" : "0.0.0";
6492
+ const version = true ? "1.0.0-dev.8" : "0.0.0";
6405
6493
  return `ecloud-sdk/v${version}`;
6406
6494
  }
6407
6495
  var UserApiClient = class {
@@ -6739,8 +6827,36 @@ function transformAppRelease(raw) {
6739
6827
 
6740
6828
  // src/client/common/contract/watcher.ts
6741
6829
  var WATCH_POLL_INTERVAL_SECONDS = 5;
6830
+ var WATCH_HEARTBEAT_INTERVAL_SECONDS = 30;
6831
+ var WATCH_DEFAULT_TIMEOUT_SECONDS = 10 * 60;
6742
6832
  var APP_STATUS_RUNNING = "Running";
6743
6833
  var APP_STATUS_FAILED = "Failed";
6834
+ var WatchTimeoutError = class extends Error {
6835
+ constructor(args) {
6836
+ super(
6837
+ args.message ?? `Timed out after ${args.elapsedSeconds}s waiting for app ${args.appId} (last status: ${args.lastStatus ?? "unknown"})`
6838
+ );
6839
+ this.name = "WatchTimeoutError";
6840
+ this.appId = args.appId;
6841
+ this.elapsedSeconds = args.elapsedSeconds;
6842
+ this.lastStatus = args.lastStatus;
6843
+ this.timeoutSeconds = args.timeoutSeconds;
6844
+ }
6845
+ };
6846
+ function resolveWatchTimeoutSeconds(explicit) {
6847
+ if (typeof explicit === "number" && Number.isFinite(explicit) && explicit > 0) {
6848
+ return Math.floor(explicit);
6849
+ }
6850
+ const raw = process.env.ECLOUD_WATCH_TIMEOUT_SECONDS;
6851
+ if (raw === void 0 || raw === "") {
6852
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6853
+ }
6854
+ const parsed = Number(raw);
6855
+ if (!Number.isFinite(parsed) || parsed <= 0) {
6856
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6857
+ }
6858
+ return Math.floor(parsed);
6859
+ }
6744
6860
  async function watchUntilRunning(options, logger) {
6745
6861
  const { walletClient, publicClient, environmentConfig, appId } = options;
6746
6862
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
@@ -6771,8 +6887,20 @@ async function watchUntilRunning(options, logger) {
6771
6887
  return false;
6772
6888
  };
6773
6889
  const startTime = Date.now();
6890
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
6774
6891
  let lastLoggedStatus;
6892
+ let lastHeartbeatAt = startTime;
6775
6893
  while (true) {
6894
+ const elapsedMs = Date.now() - startTime;
6895
+ const elapsed = Math.round(elapsedMs / 1e3);
6896
+ if (elapsed >= timeoutSeconds) {
6897
+ throw new WatchTimeoutError({
6898
+ appId,
6899
+ elapsedSeconds: elapsed,
6900
+ lastStatus: lastLoggedStatus,
6901
+ timeoutSeconds
6902
+ });
6903
+ }
6776
6904
  try {
6777
6905
  const info = await userApiClient.getInfos([appId], 1);
6778
6906
  if (info.length === 0) {
@@ -6782,16 +6910,22 @@ async function watchUntilRunning(options, logger) {
6782
6910
  const appInfo = info[0];
6783
6911
  const currentStatus = appInfo.status;
6784
6912
  const currentIP = appInfo.ip || "";
6785
- const elapsed = Math.round((Date.now() - startTime) / 1e3);
6786
6913
  if (currentStatus !== lastLoggedStatus) {
6787
6914
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
6788
6915
  lastLoggedStatus = currentStatus;
6916
+ lastHeartbeatAt = Date.now();
6917
+ } else if (Date.now() - lastHeartbeatAt >= WATCH_HEARTBEAT_INTERVAL_SECONDS * 1e3) {
6918
+ logger.info(`Status: ${currentStatus} (${elapsed}s)`);
6919
+ lastHeartbeatAt = Date.now();
6789
6920
  }
6790
6921
  if (stopCondition(currentStatus, currentIP)) {
6791
6922
  return currentIP || void 0;
6792
6923
  }
6793
6924
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
6794
6925
  } catch (error) {
6926
+ if (error instanceof WatchTimeoutError) {
6927
+ throw error;
6928
+ }
6795
6929
  logger.warn(`Failed to fetch app info: ${error.message}`);
6796
6930
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
6797
6931
  }
@@ -6800,6 +6934,7 @@ async function watchUntilRunning(options, logger) {
6800
6934
  var APP_STATUS_STOPPED = "Stopped";
6801
6935
  async function watchUntilUpgradeComplete(options, logger) {
6802
6936
  const { walletClient, publicClient, environmentConfig, appId } = options;
6937
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
6803
6938
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
6804
6939
  let initialStatus;
6805
6940
  let initialIP;
@@ -6837,7 +6972,20 @@ async function watchUntilUpgradeComplete(options, logger) {
6837
6972
  }
6838
6973
  return false;
6839
6974
  };
6975
+ const startTime = Date.now();
6976
+ const deadline = startTime + timeoutSeconds * 1e3;
6977
+ let lastLoggedStatus;
6978
+ let lastObservedStatus;
6840
6979
  while (true) {
6980
+ if (Date.now() >= deadline) {
6981
+ const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
6982
+ throw new WatchTimeoutError({
6983
+ appId,
6984
+ lastStatus: lastObservedStatus,
6985
+ elapsedSeconds,
6986
+ timeoutSeconds
6987
+ });
6988
+ }
6841
6989
  try {
6842
6990
  const info = await userApiClient.getInfos([appId], 1);
6843
6991
  if (info.length === 0) {
@@ -6847,11 +6995,23 @@ async function watchUntilUpgradeComplete(options, logger) {
6847
6995
  const appInfo = info[0];
6848
6996
  const currentStatus = appInfo.status;
6849
6997
  const currentIP = appInfo.ip || "";
6998
+ lastObservedStatus = currentStatus;
6999
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
7000
+ if (currentStatus !== lastLoggedStatus) {
7001
+ logger.info(`Status: ${currentStatus} (${elapsed}s)`);
7002
+ lastLoggedStatus = currentStatus;
7003
+ }
6850
7004
  if (stopCondition(currentStatus, currentIP)) {
6851
7005
  return;
6852
7006
  }
6853
7007
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
6854
7008
  } catch (error) {
7009
+ if (error instanceof WatchTimeoutError) {
7010
+ throw error;
7011
+ }
7012
+ if (typeof error?.message === "string" && error.message.includes("Failed")) {
7013
+ throw error;
7014
+ }
6855
7015
  logger.warn(`Failed to fetch app info: ${error.message}`);
6856
7016
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
6857
7017
  }
@@ -8072,6 +8232,11 @@ async function prepareDeployFromVerifiableBuild(options, logger = defaultLogger)
8072
8232
  executions: batch.executions,
8073
8233
  authorizationList
8074
8234
  });
8235
+ await assertSufficientGas({
8236
+ publicClient: batch.publicClient,
8237
+ address: batch.walletClient.account.address,
8238
+ gasEstimate
8239
+ });
8075
8240
  const data = {
8076
8241
  appId: batch.appId,
8077
8242
  salt: batch.salt,
@@ -8330,6 +8495,11 @@ async function prepareDeploy(options, logger = defaultLogger) {
8330
8495
  executions: batch.executions,
8331
8496
  authorizationList
8332
8497
  });
8498
+ await assertSufficientGas({
8499
+ publicClient: batch.publicClient,
8500
+ address: batch.walletClient.account.address,
8501
+ gasEstimate
8502
+ });
8333
8503
  const data = {
8334
8504
  appId: batch.appId,
8335
8505
  salt: batch.salt,
@@ -8366,7 +8536,7 @@ async function executeDeploy(options) {
8366
8536
  }
8367
8537
  );
8368
8538
  }
8369
- async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
8539
+ async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
8370
8540
  return withSDKTelemetry(
8371
8541
  {
8372
8542
  functionName: "watchDeployment",
@@ -8382,7 +8552,8 @@ async function watchDeployment(appId, walletClient, publicClient, environmentCon
8382
8552
  walletClient,
8383
8553
  publicClient,
8384
8554
  environmentConfig,
8385
- appId
8555
+ appId,
8556
+ timeoutSeconds: opts?.timeoutSeconds
8386
8557
  },
8387
8558
  logger
8388
8559
  );
@@ -8478,6 +8649,11 @@ async function prepareUpgradeFromVerifiableBuild(options, logger = defaultLogger
8478
8649
  executions: batch.executions,
8479
8650
  authorizationList
8480
8651
  });
8652
+ await assertSufficientGas({
8653
+ publicClient: batch.publicClient,
8654
+ address: batch.walletClient.account.address,
8655
+ gasEstimate
8656
+ });
8481
8657
  const data = {
8482
8658
  appId: batch.appId,
8483
8659
  executions: batch.executions,
@@ -8667,6 +8843,11 @@ async function prepareUpgrade(options, logger = defaultLogger) {
8667
8843
  executions: batch.executions,
8668
8844
  authorizationList
8669
8845
  });
8846
+ await assertSufficientGas({
8847
+ publicClient: batch.publicClient,
8848
+ address: batch.walletClient.account.address,
8849
+ gasEstimate
8850
+ });
8670
8851
  const data = {
8671
8852
  appId: batch.appId,
8672
8853
  executions: batch.executions,
@@ -8701,7 +8882,7 @@ async function executeUpgrade(options) {
8701
8882
  }
8702
8883
  );
8703
8884
  }
8704
- async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
8885
+ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
8705
8886
  return withSDKTelemetry(
8706
8887
  {
8707
8888
  functionName: "watchUpgrade",
@@ -8717,7 +8898,8 @@ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig
8717
8898
  walletClient,
8718
8899
  publicClient,
8719
8900
  environmentConfig,
8720
- appId
8901
+ appId,
8902
+ timeoutSeconds: opts?.timeoutSeconds
8721
8903
  },
8722
8904
  logger
8723
8905
  );
@@ -9409,7 +9591,7 @@ function createAppModule(ctx) {
9409
9591
  }
9410
9592
  const account = walletClient.account;
9411
9593
  const environment = getEnvironmentConfig(ctx.environment);
9412
- const logger = getLogger(ctx.verbose);
9594
+ const logger = ctx.logger ?? getLogger(ctx.verbose);
9413
9595
  return {
9414
9596
  async create(opts) {
9415
9597
  return createApp(opts, logger);
@@ -9521,14 +9703,15 @@ function createAppModule(ctx) {
9521
9703
  imageRef: result.imageRef
9522
9704
  };
9523
9705
  },
9524
- async watchDeployment(appId) {
9706
+ async watchDeployment(appId, opts) {
9525
9707
  return watchDeployment(
9526
9708
  appId,
9527
9709
  walletClient,
9528
9710
  publicClient,
9529
9711
  environment,
9530
9712
  logger,
9531
- skipTelemetry
9713
+ skipTelemetry,
9714
+ opts
9532
9715
  );
9533
9716
  },
9534
9717
  // Granular upgrade control
@@ -9586,8 +9769,16 @@ function createAppModule(ctx) {
9586
9769
  imageRef: result.imageRef
9587
9770
  };
9588
9771
  },
9589
- async watchUpgrade(appId) {
9590
- return watchUpgrade(appId, walletClient, publicClient, environment, logger, skipTelemetry);
9772
+ async watchUpgrade(appId, opts) {
9773
+ return watchUpgrade(
9774
+ appId,
9775
+ walletClient,
9776
+ publicClient,
9777
+ environment,
9778
+ logger,
9779
+ skipTelemetry,
9780
+ opts
9781
+ );
9591
9782
  },
9592
9783
  // Profile management
9593
9784
  async setProfile(appId, profile) {
@@ -11100,6 +11291,7 @@ export {
11100
11291
  EMPTY_CONTAINER_POLICY,
11101
11292
  ERC20_default as ERC20ABI,
11102
11293
  ForbiddenError,
11294
+ InsufficientGasError,
11103
11295
  JwtProvider,
11104
11296
  NoopClient,
11105
11297
  NotFoundError,
@@ -11109,9 +11301,12 @@ export {
11109
11301
  TimeoutError,
11110
11302
  USDCCredits_default as USDCCreditsABI,
11111
11303
  UserApiClient,
11304
+ WATCH_DEFAULT_TIMEOUT_SECONDS,
11305
+ WatchTimeoutError,
11112
11306
  addHexPrefix,
11113
11307
  addMetric,
11114
11308
  addMetricWithDimensions,
11309
+ assertSufficientGas,
11115
11310
  assertValidFilePath,
11116
11311
  assertValidImageReference,
11117
11312
  assertValidPrivateKey,