@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.
@@ -1,3 +1,4 @@
1
- export { A as AppModule, i as AppModuleConfig, C as ComputeModule, a as ComputeModuleConfig, j as createAppModule, d as createComputeModule, e as encodeStartAppData, f as encodeStopAppData, g as encodeTerminateAppData } from './compute-DwReciJz.cjs';
1
+ export { A as AppModule, v as AppModuleConfig, C as ComputeModule, a as ComputeModuleConfig, x as createAppModule, i as createComputeModule, j as encodeStartAppData, k as encodeStopAppData, l as encodeTerminateAppData } from './compute-B-V7Dbj9.cjs';
2
2
  import 'viem';
3
3
  import './index-BVnxNfqb.cjs';
4
+ import './validation-D3yIUF-Z.cjs';
package/dist/compute.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- export { A as AppModule, i as AppModuleConfig, C as ComputeModule, a as ComputeModuleConfig, j as createAppModule, d as createComputeModule, e as encodeStartAppData, f as encodeStopAppData, g as encodeTerminateAppData } from './compute-BqouWhdQ.js';
1
+ export { A as AppModule, v as AppModuleConfig, C as ComputeModule, a as ComputeModuleConfig, x as createAppModule, i as createComputeModule, j as encodeStartAppData, k as encodeStopAppData, l as encodeTerminateAppData } from './compute-BJ_sqrKn.js';
2
2
  import 'viem';
3
3
  import './index-BVnxNfqb.js';
4
+ import './validation-D3yIUF-Z.js';
package/dist/compute.js CHANGED
@@ -421,7 +421,74 @@ var PushPermissionError = class extends Error {
421
421
  import Handlebars from "handlebars";
422
422
 
423
423
  // src/client/common/templates/Dockerfile.layered.tmpl
424
- 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';
424
+ var Dockerfile_layered_default = `{{#if includeTLS}}
425
+ # Get Caddy from official image
426
+ FROM caddy:2.10.2-alpine AS caddy
427
+ {{/if}}
428
+
429
+ FROM {{baseImage}}
430
+
431
+ {{#if originalUser}}
432
+ # Switch to root to perform setup (base image has non-root USER: {{originalUser}})
433
+ USER root
434
+ {{/if}}
435
+
436
+ # Copy core TEE components
437
+ # CA bundle for kms-client / tls-keygen to validate HTTPS calls to
438
+ # eigencloud.xyz endpoints. Bundled at a non-standard path and consumed
439
+ # only via SSL_CERT_FILE in compute-source-env.sh, so the user's
440
+ # /etc/ssl/ is never touched.
441
+ COPY --from=alpine:3.20.10 /etc/ssl/certs/ca-certificates.crt /usr/local/share/eigenx-ca-certs.crt
442
+ COPY compute-source-env.sh /usr/local/bin/
443
+ COPY kms-client /usr/local/bin/
444
+ COPY kms-signing-public-key.pem /usr/local/bin/
445
+
446
+ {{#if includeTLS}}
447
+ # Copy Caddy from official image
448
+ COPY --from=caddy /usr/bin/caddy /usr/local/bin/caddy
449
+
450
+ # Copy TLS components
451
+ COPY tls-keygen /usr/local/bin/
452
+ COPY Caddyfile /etc/caddy/
453
+ {{/if}}
454
+
455
+ {{#if originalUser}}
456
+ # Make binaries executable (755 for executables, 644 for keys)
457
+ RUN chmod 755 /usr/local/bin/compute-source-env.sh \\
458
+ && chmod 755 /usr/local/bin/kms-client{{#if includeTLS}} \\
459
+ && chmod 755 /usr/local/bin/tls-keygen \\
460
+ && chmod 755 /usr/local/bin/caddy{{/if}} \\
461
+ && chmod 644 /usr/local/bin/kms-signing-public-key.pem
462
+
463
+ # Store original user - entrypoint will drop privileges to this user after TEE setup
464
+ ENV __ECLOUD_ORIGINAL_USER={{originalUser}}
465
+ {{else}}
466
+ # Make binaries executable (preserve existing permissions, just add execute)
467
+ RUN chmod +x /usr/local/bin/compute-source-env.sh \\
468
+ && chmod +x /usr/local/bin/kms-client{{#if includeTLS}} \\
469
+ && chmod +x /usr/local/bin/tls-keygen{{/if}}
470
+ {{/if}}
471
+
472
+ {{#if logRedirect}}
473
+
474
+ LABEL tee.launch_policy.log_redirect={{logRedirect}}
475
+ {{/if}}
476
+ {{#if resourceUsageAllow}}
477
+
478
+ LABEL tee.launch_policy.monitoring_memory_allow={{resourceUsageAllow}}
479
+ {{/if}}
480
+
481
+ LABEL eigenx_cli_version={{ecloudCLIVersion}}
482
+ LABEL eigenx_vm_image=eigen
483
+
484
+ {{#if includeTLS}}
485
+ # Expose both HTTP and HTTPS ports for Caddy
486
+ EXPOSE 80 443
487
+ {{/if}}
488
+
489
+ ENTRYPOINT ["/usr/local/bin/compute-source-env.sh"]
490
+ CMD {{{originalCmd}}}
491
+ `;
425
492
 
426
493
  // src/client/common/templates/dockerfileTemplate.ts
427
494
  function processDockerfileTemplate(data) {
@@ -438,7 +505,7 @@ echo "compute-source-env.sh: Running setup script..."
438
505
 
439
506
  # Fetch and source environment variables from KMS
440
507
  echo "Fetching secrets from KMS..."
441
- if /usr/local/bin/kms-client \\
508
+ if SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt /usr/local/bin/kms-client \\
442
509
  --kms-server-url "{{kmsServerURL}}" \\
443
510
  --kms-signing-key-file /usr/local/bin/kms-signing-public-key.pem \\
444
511
  --userapi-url "{{userAPIURL}}" \\
@@ -496,7 +563,8 @@ setup_tls() {
496
563
 
497
564
  echo "compute-source-env.sh: Obtaining TLS certificate using $challenge challenge..."
498
565
  # Pass the API URL for certificate persistence
499
- if ! MNEMONIC="$mnemonic" DOMAIN="$domain" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
566
+ if ! SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt \\
567
+ MNEMONIC="$mnemonic" DOMAIN="$domain" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
500
568
  -challenge "$challenge" \\
501
569
  $staging_flag; then
502
570
  echo "compute-source-env.sh: ERROR - Failed to obtain TLS certificate"
@@ -888,16 +956,7 @@ async function extractDigestFromSinglePlatform(manifest, imageRef) {
888
956
  architecture: inspectData[0].Architecture
889
957
  } : null;
890
958
  if (!config) {
891
- if (manifest.config?.digest) {
892
- const digest = hexStringToBytes32(manifest.config.digest);
893
- const registry = extractRegistryName(imageRef);
894
- return {
895
- digest,
896
- registry,
897
- platform: DOCKER_PLATFORM
898
- };
899
- }
900
- throw new Error(`Could not determine platform for ${imageRef}`);
959
+ throw createPlatformErrorMessage(imageRef, ["unknown (could not determine architecture)"]);
901
960
  }
902
961
  const platform2 = `${config.os}/${config.architecture}`;
903
962
  if (platform2 === DOCKER_PLATFORM) {
@@ -969,14 +1028,11 @@ Image: ${imageRef}
969
1028
  Found platform(s): ${platforms.join(", ")}
970
1029
  Required platform: ${DOCKER_PLATFORM}
971
1030
 
972
- To fix this issue:
973
- 1. Manual fix:
974
- a. Rebuild your image with the correct platform:
975
- docker build --platform ${DOCKER_PLATFORM} -t ${imageRef} .
976
- b. Push the rebuilt image to your remote registry:
977
- docker push ${imageRef}
978
-
979
- 2. Or use the SDK to build with the correct platform automatically.`;
1031
+ To fix, either:
1032
+ 1. Rebuild the image for ${DOCKER_PLATFORM} and push it:
1033
+ docker buildx build --platform ${DOCKER_PLATFORM} -t ${imageRef} --push .
1034
+ 2. Or use a verifiable build (--verifiable --repo <repo> --commit <sha>), which
1035
+ builds server-side and needs no local Docker.`;
980
1036
  return new Error(errorMsg);
981
1037
  }
982
1038
 
@@ -1081,6 +1137,8 @@ async function prepareRelease(options, logger) {
1081
1137
  logger.info(`Waiting ${REGISTRY_PROPAGATION_WAIT_SECONDS} seconds for registry propagation...`);
1082
1138
  await new Promise((resolve2) => setTimeout(resolve2, REGISTRY_PROPAGATION_WAIT_SECONDS * 1e3));
1083
1139
  } else {
1140
+ logger.info("Verifying image platform (linux/amd64)...");
1141
+ await getImageDigestAndName(imageRef);
1084
1142
  logger.info("Checking if image needs layering...");
1085
1143
  finalImageRef = await layerRemoteImageIfNeeded(
1086
1144
  {
@@ -6177,6 +6235,35 @@ async function undelegate(options, logger = noopLogger) {
6177
6235
  return hash;
6178
6236
  }
6179
6237
 
6238
+ // src/client/common/gas/insufficientGas.ts
6239
+ import { formatEther } from "viem";
6240
+ var InsufficientGasError = class extends Error {
6241
+ constructor(args) {
6242
+ const requiredEth = formatEther(args.requiredWei);
6243
+ const availableEth = formatEther(args.availableWei);
6244
+ super(
6245
+ `Insufficient ETH for gas: wallet ${args.address} has ${availableEth} ETH but this transaction needs ~${requiredEth} ETH.
6246
+ Compute credits do not pay on-chain gas \u2014 fund the wallet with ETH and retry.`
6247
+ );
6248
+ this.name = "InsufficientGasError";
6249
+ this.address = args.address;
6250
+ this.requiredWei = args.requiredWei;
6251
+ this.availableWei = args.availableWei;
6252
+ this.requiredEth = requiredEth;
6253
+ this.availableEth = availableEth;
6254
+ }
6255
+ };
6256
+ async function assertSufficientGas(args) {
6257
+ const availableWei = await args.publicClient.getBalance({ address: args.address });
6258
+ if (availableWei < args.gasEstimate.maxCostWei) {
6259
+ throw new InsufficientGasError({
6260
+ address: args.address,
6261
+ requiredWei: args.gasEstimate.maxCostWei,
6262
+ availableWei
6263
+ });
6264
+ }
6265
+ }
6266
+
6180
6267
  // src/client/common/utils/userapi.ts
6181
6268
  import axios2 from "axios";
6182
6269
 
@@ -6209,6 +6296,7 @@ import axios from "axios";
6209
6296
  var MAX_RETRIES = 5;
6210
6297
  var INITIAL_BACKOFF_MS = 1e3;
6211
6298
  var MAX_BACKOFF_MS = 3e4;
6299
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
6212
6300
  function sleep(ms) {
6213
6301
  return new Promise((resolve2) => setTimeout(resolve2, ms));
6214
6302
  }
@@ -6228,7 +6316,7 @@ async function requestWithRetry(config) {
6228
6316
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
6229
6317
  const res = await axios({ ...config, validateStatus: () => true });
6230
6318
  lastResponse = res;
6231
- if (res.status !== 429) {
6319
+ if (!RETRYABLE_STATUSES.has(res.status)) {
6232
6320
  return res;
6233
6321
  }
6234
6322
  if (attempt < MAX_RETRIES) {
@@ -6257,7 +6345,7 @@ var CanViewAppLogsPermission = "0x2fd3f2fe";
6257
6345
  var CanViewSensitiveAppInfoPermission = "0x0e67b22f";
6258
6346
  var CanUpdateAppProfilePermission = "0x036fef61";
6259
6347
  function getDefaultClientId() {
6260
- const version = true ? "1.0.0-dev.7" : "0.0.0";
6348
+ const version = true ? "1.0.0-dev.8" : "0.0.0";
6261
6349
  return `ecloud-sdk/v${version}`;
6262
6350
  }
6263
6351
  var UserApiClient = class {
@@ -6595,8 +6683,36 @@ function transformAppRelease(raw) {
6595
6683
 
6596
6684
  // src/client/common/contract/watcher.ts
6597
6685
  var WATCH_POLL_INTERVAL_SECONDS = 5;
6686
+ var WATCH_HEARTBEAT_INTERVAL_SECONDS = 30;
6687
+ var WATCH_DEFAULT_TIMEOUT_SECONDS = 10 * 60;
6598
6688
  var APP_STATUS_RUNNING = "Running";
6599
6689
  var APP_STATUS_FAILED = "Failed";
6690
+ var WatchTimeoutError = class extends Error {
6691
+ constructor(args) {
6692
+ super(
6693
+ args.message ?? `Timed out after ${args.elapsedSeconds}s waiting for app ${args.appId} (last status: ${args.lastStatus ?? "unknown"})`
6694
+ );
6695
+ this.name = "WatchTimeoutError";
6696
+ this.appId = args.appId;
6697
+ this.elapsedSeconds = args.elapsedSeconds;
6698
+ this.lastStatus = args.lastStatus;
6699
+ this.timeoutSeconds = args.timeoutSeconds;
6700
+ }
6701
+ };
6702
+ function resolveWatchTimeoutSeconds(explicit) {
6703
+ if (typeof explicit === "number" && Number.isFinite(explicit) && explicit > 0) {
6704
+ return Math.floor(explicit);
6705
+ }
6706
+ const raw = process.env.ECLOUD_WATCH_TIMEOUT_SECONDS;
6707
+ if (raw === void 0 || raw === "") {
6708
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6709
+ }
6710
+ const parsed = Number(raw);
6711
+ if (!Number.isFinite(parsed) || parsed <= 0) {
6712
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6713
+ }
6714
+ return Math.floor(parsed);
6715
+ }
6600
6716
  async function watchUntilRunning(options, logger) {
6601
6717
  const { walletClient, publicClient, environmentConfig, appId } = options;
6602
6718
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
@@ -6627,8 +6743,20 @@ async function watchUntilRunning(options, logger) {
6627
6743
  return false;
6628
6744
  };
6629
6745
  const startTime = Date.now();
6746
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
6630
6747
  let lastLoggedStatus;
6748
+ let lastHeartbeatAt = startTime;
6631
6749
  while (true) {
6750
+ const elapsedMs = Date.now() - startTime;
6751
+ const elapsed = Math.round(elapsedMs / 1e3);
6752
+ if (elapsed >= timeoutSeconds) {
6753
+ throw new WatchTimeoutError({
6754
+ appId,
6755
+ elapsedSeconds: elapsed,
6756
+ lastStatus: lastLoggedStatus,
6757
+ timeoutSeconds
6758
+ });
6759
+ }
6632
6760
  try {
6633
6761
  const info = await userApiClient.getInfos([appId], 1);
6634
6762
  if (info.length === 0) {
@@ -6638,16 +6766,22 @@ async function watchUntilRunning(options, logger) {
6638
6766
  const appInfo = info[0];
6639
6767
  const currentStatus = appInfo.status;
6640
6768
  const currentIP = appInfo.ip || "";
6641
- const elapsed = Math.round((Date.now() - startTime) / 1e3);
6642
6769
  if (currentStatus !== lastLoggedStatus) {
6643
6770
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
6644
6771
  lastLoggedStatus = currentStatus;
6772
+ lastHeartbeatAt = Date.now();
6773
+ } else if (Date.now() - lastHeartbeatAt >= WATCH_HEARTBEAT_INTERVAL_SECONDS * 1e3) {
6774
+ logger.info(`Status: ${currentStatus} (${elapsed}s)`);
6775
+ lastHeartbeatAt = Date.now();
6645
6776
  }
6646
6777
  if (stopCondition(currentStatus, currentIP)) {
6647
6778
  return currentIP || void 0;
6648
6779
  }
6649
6780
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
6650
6781
  } catch (error) {
6782
+ if (error instanceof WatchTimeoutError) {
6783
+ throw error;
6784
+ }
6651
6785
  logger.warn(`Failed to fetch app info: ${error.message}`);
6652
6786
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
6653
6787
  }
@@ -6656,6 +6790,7 @@ async function watchUntilRunning(options, logger) {
6656
6790
  var APP_STATUS_STOPPED = "Stopped";
6657
6791
  async function watchUntilUpgradeComplete(options, logger) {
6658
6792
  const { walletClient, publicClient, environmentConfig, appId } = options;
6793
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
6659
6794
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
6660
6795
  let initialStatus;
6661
6796
  let initialIP;
@@ -6693,7 +6828,20 @@ async function watchUntilUpgradeComplete(options, logger) {
6693
6828
  }
6694
6829
  return false;
6695
6830
  };
6831
+ const startTime = Date.now();
6832
+ const deadline = startTime + timeoutSeconds * 1e3;
6833
+ let lastLoggedStatus;
6834
+ let lastObservedStatus;
6696
6835
  while (true) {
6836
+ if (Date.now() >= deadline) {
6837
+ const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
6838
+ throw new WatchTimeoutError({
6839
+ appId,
6840
+ lastStatus: lastObservedStatus,
6841
+ elapsedSeconds,
6842
+ timeoutSeconds
6843
+ });
6844
+ }
6697
6845
  try {
6698
6846
  const info = await userApiClient.getInfos([appId], 1);
6699
6847
  if (info.length === 0) {
@@ -6703,11 +6851,23 @@ async function watchUntilUpgradeComplete(options, logger) {
6703
6851
  const appInfo = info[0];
6704
6852
  const currentStatus = appInfo.status;
6705
6853
  const currentIP = appInfo.ip || "";
6854
+ lastObservedStatus = currentStatus;
6855
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
6856
+ if (currentStatus !== lastLoggedStatus) {
6857
+ logger.info(`Status: ${currentStatus} (${elapsed}s)`);
6858
+ lastLoggedStatus = currentStatus;
6859
+ }
6706
6860
  if (stopCondition(currentStatus, currentIP)) {
6707
6861
  return;
6708
6862
  }
6709
6863
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
6710
6864
  } catch (error) {
6865
+ if (error instanceof WatchTimeoutError) {
6866
+ throw error;
6867
+ }
6868
+ if (typeof error?.message === "string" && error.message.includes("Failed")) {
6869
+ throw error;
6870
+ }
6711
6871
  logger.warn(`Failed to fetch app info: ${error.message}`);
6712
6872
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
6713
6873
  }
@@ -7254,6 +7414,11 @@ async function prepareDeployFromVerifiableBuild(options, logger = defaultLogger)
7254
7414
  executions: batch.executions,
7255
7415
  authorizationList
7256
7416
  });
7417
+ await assertSufficientGas({
7418
+ publicClient: batch.publicClient,
7419
+ address: batch.walletClient.account.address,
7420
+ gasEstimate
7421
+ });
7257
7422
  const data = {
7258
7423
  appId: batch.appId,
7259
7424
  salt: batch.salt,
@@ -7512,6 +7677,11 @@ async function prepareDeploy(options, logger = defaultLogger) {
7512
7677
  executions: batch.executions,
7513
7678
  authorizationList
7514
7679
  });
7680
+ await assertSufficientGas({
7681
+ publicClient: batch.publicClient,
7682
+ address: batch.walletClient.account.address,
7683
+ gasEstimate
7684
+ });
7515
7685
  const data = {
7516
7686
  appId: batch.appId,
7517
7687
  salt: batch.salt,
@@ -7548,7 +7718,7 @@ async function executeDeploy(options) {
7548
7718
  }
7549
7719
  );
7550
7720
  }
7551
- async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
7721
+ async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
7552
7722
  return withSDKTelemetry(
7553
7723
  {
7554
7724
  functionName: "watchDeployment",
@@ -7564,7 +7734,8 @@ async function watchDeployment(appId, walletClient, publicClient, environmentCon
7564
7734
  walletClient,
7565
7735
  publicClient,
7566
7736
  environmentConfig,
7567
- appId
7737
+ appId,
7738
+ timeoutSeconds: opts?.timeoutSeconds
7568
7739
  },
7569
7740
  logger
7570
7741
  );
@@ -7660,6 +7831,11 @@ async function prepareUpgradeFromVerifiableBuild(options, logger = defaultLogger
7660
7831
  executions: batch.executions,
7661
7832
  authorizationList
7662
7833
  });
7834
+ await assertSufficientGas({
7835
+ publicClient: batch.publicClient,
7836
+ address: batch.walletClient.account.address,
7837
+ gasEstimate
7838
+ });
7663
7839
  const data = {
7664
7840
  appId: batch.appId,
7665
7841
  executions: batch.executions,
@@ -7849,6 +8025,11 @@ async function prepareUpgrade(options, logger = defaultLogger) {
7849
8025
  executions: batch.executions,
7850
8026
  authorizationList
7851
8027
  });
8028
+ await assertSufficientGas({
8029
+ publicClient: batch.publicClient,
8030
+ address: batch.walletClient.account.address,
8031
+ gasEstimate
8032
+ });
7852
8033
  const data = {
7853
8034
  appId: batch.appId,
7854
8035
  executions: batch.executions,
@@ -7883,7 +8064,7 @@ async function executeUpgrade(options) {
7883
8064
  }
7884
8065
  );
7885
8066
  }
7886
- async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
8067
+ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
7887
8068
  return withSDKTelemetry(
7888
8069
  {
7889
8070
  functionName: "watchUpgrade",
@@ -7899,7 +8080,8 @@ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig
7899
8080
  walletClient,
7900
8081
  publicClient,
7901
8082
  environmentConfig,
7902
- appId
8083
+ appId,
8084
+ timeoutSeconds: opts?.timeoutSeconds
7903
8085
  },
7904
8086
  logger
7905
8087
  );
@@ -8591,7 +8773,7 @@ function createAppModule(ctx) {
8591
8773
  }
8592
8774
  const account = walletClient.account;
8593
8775
  const environment = getEnvironmentConfig(ctx.environment);
8594
- const logger = getLogger(ctx.verbose);
8776
+ const logger = ctx.logger ?? getLogger(ctx.verbose);
8595
8777
  return {
8596
8778
  async create(opts) {
8597
8779
  return createApp(opts, logger);
@@ -8703,14 +8885,15 @@ function createAppModule(ctx) {
8703
8885
  imageRef: result.imageRef
8704
8886
  };
8705
8887
  },
8706
- async watchDeployment(appId) {
8888
+ async watchDeployment(appId, opts) {
8707
8889
  return watchDeployment(
8708
8890
  appId,
8709
8891
  walletClient,
8710
8892
  publicClient,
8711
8893
  environment,
8712
8894
  logger,
8713
- skipTelemetry
8895
+ skipTelemetry,
8896
+ opts
8714
8897
  );
8715
8898
  },
8716
8899
  // Granular upgrade control
@@ -8768,8 +8951,16 @@ function createAppModule(ctx) {
8768
8951
  imageRef: result.imageRef
8769
8952
  };
8770
8953
  },
8771
- async watchUpgrade(appId) {
8772
- return watchUpgrade(appId, walletClient, publicClient, environment, logger, skipTelemetry);
8954
+ async watchUpgrade(appId, opts) {
8955
+ return watchUpgrade(
8956
+ appId,
8957
+ walletClient,
8958
+ publicClient,
8959
+ environment,
8960
+ logger,
8961
+ skipTelemetry,
8962
+ opts
8963
+ );
8773
8964
  },
8774
8965
  // Profile management
8775
8966
  async setProfile(appId, profile) {