@layr-labs/ecloud-sdk 1.0.0-devep8 → 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.
package/dist/compute.cjs CHANGED
@@ -167,7 +167,7 @@ __export(compute_exports, {
167
167
  module.exports = __toCommonJS(compute_exports);
168
168
 
169
169
  // src/client/modules/compute/app/index.ts
170
- var import_viem6 = require("viem");
170
+ var import_viem7 = require("viem");
171
171
 
172
172
  // src/client/common/docker/build.ts
173
173
  var child_process = __toESM(require("child_process"), 1);
@@ -183,7 +183,6 @@ var ENV_SOURCE_SCRIPT_NAME = "compute-source-env.sh";
183
183
  var KMS_CLIENT_BINARY_NAME = "kms-client";
184
184
  var KMS_SIGNING_KEY_NAME = "kms-signing-public-key.pem";
185
185
  var TLS_KEYGEN_BINARY_NAME = "tls-keygen";
186
- var DRAIN_WATCHER_BINARY_NAME = "ecloud-drain-watcher";
187
186
  var CADDYFILE_NAME = "Caddyfile";
188
187
  var LAYERED_BUILD_DIR_PREFIX = "ecloud-layered-build";
189
188
 
@@ -470,12 +469,14 @@ USER root
470
469
  {{/if}}
471
470
 
472
471
  # Copy core TEE components
472
+ # CA bundle for kms-client / tls-keygen to validate HTTPS calls to
473
+ # eigencloud.xyz endpoints. Bundled at a non-standard path and consumed
474
+ # only via SSL_CERT_FILE in compute-source-env.sh, so the user's
475
+ # /etc/ssl/ is never touched.
476
+ COPY --from=alpine:3.20.10 /etc/ssl/certs/ca-certificates.crt /usr/local/share/eigenx-ca-certs.crt
473
477
  COPY compute-source-env.sh /usr/local/bin/
474
478
  COPY kms-client /usr/local/bin/
475
479
  COPY kms-signing-public-key.pem /usr/local/bin/
476
- {{#if includeDrainWatcher}}
477
- COPY ecloud-drain-watcher /usr/local/bin/
478
- {{/if}}
479
480
 
480
481
  {{#if includeTLS}}
481
482
  # Copy Caddy from official image
@@ -489,8 +490,7 @@ COPY Caddyfile /etc/caddy/
489
490
  {{#if originalUser}}
490
491
  # Make binaries executable (755 for executables, 644 for keys)
491
492
  RUN chmod 755 /usr/local/bin/compute-source-env.sh \\
492
- && chmod 755 /usr/local/bin/kms-client{{#if includeDrainWatcher}} \\
493
- && chmod 755 /usr/local/bin/ecloud-drain-watcher{{/if}}{{#if includeTLS}} \\
493
+ && chmod 755 /usr/local/bin/kms-client{{#if includeTLS}} \\
494
494
  && chmod 755 /usr/local/bin/tls-keygen \\
495
495
  && chmod 755 /usr/local/bin/caddy{{/if}} \\
496
496
  && chmod 644 /usr/local/bin/kms-signing-public-key.pem
@@ -500,8 +500,7 @@ ENV __ECLOUD_ORIGINAL_USER={{originalUser}}
500
500
  {{else}}
501
501
  # Make binaries executable (preserve existing permissions, just add execute)
502
502
  RUN chmod +x /usr/local/bin/compute-source-env.sh \\
503
- && chmod +x /usr/local/bin/kms-client{{#if includeDrainWatcher}} \\
504
- && chmod +x /usr/local/bin/ecloud-drain-watcher{{/if}}{{#if includeTLS}} \\
503
+ && chmod +x /usr/local/bin/kms-client{{#if includeTLS}} \\
505
504
  && chmod +x /usr/local/bin/tls-keygen{{/if}}
506
505
  {{/if}}
507
506
 
@@ -514,36 +513,8 @@ LABEL tee.launch_policy.log_redirect={{logRedirect}}
514
513
  LABEL tee.launch_policy.monitoring_memory_allow={{resourceUsageAllow}}
515
514
  {{/if}}
516
515
 
517
- # Allow-list the envvars the ecloud-platform sets via GCE \`tee-env-*\`
518
- # metadata. Without this label, Confidential Space's launcher rejects
519
- # any \`tee-env-*\` override at container-start with
520
- # "env var {...} is not allowed to be overridden on this image" and
521
- # exits with code 1 \u2014 which terminates the VM before the entrypoint
522
- # ever runs. User-supplied env vars flow through KMS (not tee-env-*)
523
- # and don't need to be listed here.
524
- #
525
- # Entries:
526
- # - ECLOUD_PD_EXPECTED set on PD-backed apps so the entrypoint
527
- # (compute-source-env.sh) knows to wait for
528
- # the persistent disk before exec'ing the
529
- # user workload.
530
- # - ECLOUD_PLATFORM_HOST the platform-routed hostname
531
- # (<addr>.<env>.eigencloud.xyz) so the
532
- # entrypoint's setup_tls can issue an ACME
533
- # cert for it. Injected by the CLI into
534
- # publicEnv at deploy/upgrade time and
535
- # propagated by ecloud-platform's
536
- # compute.go as a tee-env-* metadata key.
537
- #
538
- # The CS launcher parses this label as a comma-separated list
539
- # (go-tpm-tools/launcher/spec/launch_policy.go:185 \u2014 strings.Split
540
- # on ","). Quotes are not required; keep the value bare for
541
- # consistency with compute-tee's and eigenx-kms's existing labels.
542
- LABEL tee.launch_policy.allow_env_override=ECLOUD_PD_EXPECTED,ECLOUD_PLATFORM_HOST
543
-
544
516
  LABEL eigenx_cli_version={{ecloudCLIVersion}}
545
517
  LABEL eigenx_vm_image=eigen
546
- LABEL eigenx_container_contract=v1
547
518
 
548
519
  {{#if includeTLS}}
549
520
  # Expose both HTTP and HTTPS ports for Caddy
@@ -565,54 +536,11 @@ var import_handlebars2 = __toESM(require("handlebars"), 1);
565
536
 
566
537
  // src/client/common/templates/compute-source-env.sh.tmpl
567
538
  var compute_source_env_sh_default = `#!/bin/sh
568
- # EigenCompute container entrypoint script
569
- # This script handles KMS secret fetching, TLS setup, and privilege dropping
570
- # before executing the user's application.
571
- #
572
- # Handlebars template variables (replaced at build time by the CLI):
573
- # kmsServerURL - URL of the KMS server
574
- # userAPIURL - URL of the user API (ecloud-platform)
575
- # The KMS signing public key is copied into the image as
576
- # /usr/local/bin/kms-signing-public-key.pem at layer-build time by the CLI.
577
- #
578
- # ecloud-platform divergence from compute-tee:
579
- # This script emits ECLOUD_READY / ECLOUD_FAIL / ECLOUD_AWAITING_USERDATA /
580
- # ECLOUD_DETACHED markers to stdout at key lifecycle points. The GCP
581
- # provisioner's serial-console watcher in ecloud-platform
582
- # (pkg/services/infraService/providers/gcp/compute.go) parses those
583
- # markers to gate "VM ready" and to coordinate the prewarm-detach
584
- # upgrade flow. Without the markers, the platform's waitForStartupReady
585
- # times out at ~10 minutes per deploy, rollback fires, and the VM is
586
- # deleted \u2014 seen in dev on 2026-05-04 with an older copy of this
587
- # template that lacked the markers.
588
- #
589
- # Prewarm-detach contract:
590
- # - If ECLOUD_PD_EXPECTED=1 and /mnt/disks/userdata is not present at boot,
591
- # emit ECLOUD_AWAITING_USERDATA and wait until the disk is attached.
592
- # - On SIGTERM (drain-requested), forward to child, wait for exit, sync
593
- # + unmount /mnt/disks/userdata, emit ECLOUD_DETACHED, exit.
594
- # - ECLOUD_READY is emitted once runtime is bootstrapped (same as before).
595
- # - ECLOUD_FAIL is emitted on any unrecoverable setup error.
596
- # Keep the markers on any line that resolves a lifecycle outcome.
597
- #
598
- # This file is kept in lockstep with
599
- # ecloud-platform/pkg/services/buildService/assets/compute-source-env.sh.tmpl
600
- # \u2014 if you change one, change the other. Differences vs the platform copy
601
- # are intentionally minimal:
602
- # - Handlebars placeholders use the CLI's naming (kmsServerURL,
603
- # userAPIURL) rather than the platform's (KMS_SERVER_URL,
604
- # USER_API_URL). (See top of file for real placeholder syntax \u2014
605
- # not repeated here so Handlebars doesn't expand it in this comment.)
606
- # - KMS signing key is read from a file the CLI copies into the image,
607
- # not heredoc-embedded in the script, because the CLI's image
608
- # layering writes it as a separate file (kms-signing-public-key.pem).
609
- # - TLS binary is \`tls-keygen\` (CLI-bundled) not \`tls-client\`.
610
-
611
539
  echo "compute-source-env.sh: Running setup script..."
612
540
 
613
541
  # Fetch and source environment variables from KMS
614
542
  echo "Fetching secrets from KMS..."
615
- if /usr/local/bin/kms-client \\
543
+ if SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt /usr/local/bin/kms-client \\
616
544
  --kms-server-url "{{kmsServerURL}}" \\
617
545
  --kms-signing-key-file /usr/local/bin/kms-signing-public-key.pem \\
618
546
  --userapi-url "{{userAPIURL}}" \\
@@ -623,187 +551,93 @@ if /usr/local/bin/kms-client \\
623
551
  else
624
552
  echo "compute-source-env.sh: ERROR - Failed to fetch environment variables from KMS"
625
553
  echo "compute-source-env.sh: Exiting - cannot start user workload without KMS secrets"
626
- echo "ECLOUD_FAIL kms_bootstrap"
627
554
  exit 1
628
555
  fi
629
556
 
630
- # issue_cert_for runs tls-keygen for a single hostname and copies the
631
- # produced fullchain/privkey into $1's output directory ($2). Returns
632
- # 0 on success, non-zero on any failure (caller decides whether that's
633
- # fatal). tls-keygen writes to /run/tls/{fullchain,privkey}.pem by
634
- # default; we move those to a per-host subdirectory so the two-site
635
- # Caddyfile can select the right cert by path.
636
- issue_cert_for() {
637
- local host="$1"
638
- local cert_dir="$2"
639
- local mnemonic="$3"
640
- local challenge="$4"
641
- local staging_flag="$5"
642
-
643
- echo "compute-source-env.sh: Obtaining TLS certificate for $host (challenge=$challenge)..."
644
- # Remove any stale default outputs from a prior tls-keygen run
645
- rm -f /run/tls/fullchain.pem /run/tls/privkey.pem
646
- if ! MNEMONIC="$mnemonic" DOMAIN="$host" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
647
- -challenge "$challenge" \\
648
- $staging_flag; then
649
- return 1
650
- fi
651
- mkdir -p "$cert_dir"
652
- mv /run/tls/fullchain.pem "$cert_dir/fullchain.pem"
653
- mv /run/tls/privkey.pem "$cert_dir/privkey.pem"
654
- echo "compute-source-env.sh: Cert for $host written to $cert_dir"
655
- }
656
-
657
- # Setup TLS: issues certs for
658
- # - ECLOUD_PLATFORM_HOST (platform-routed <addr>.<env>.eigencloud.xyz),
659
- # when the CLI/platform has set it
660
- # - DOMAIN (user-supplied custom domain), when set and non-localhost
661
- #
662
- # No client-side DNS precheck. Earlier versions tried to gate ACME on
663
- # "does this hostname resolve to my external IP" but that's wrong for
664
- # the platform-routing model (DNS points at the shared nginx NLB, not
665
- # the VM) and was preventing cert issuance on the production path.
666
- # tls-client (eigencompute-containers/tls-client) does its own DNS
667
- # poll before calling ACME and surfaces a clear error when challenges
668
- # can't reach the VM, which is the right place for that check \u2014
669
- # attempting it here from inside the VM cannot tell platform-routed
670
- # from compute-tee-routed apps.
557
+ # Setup TLS if tls-keygen is present (which means TLS was configured at build time)
671
558
  setup_tls() {
672
559
  # If tls-keygen isn't present, TLS wasn't configured during build
673
560
  if [ ! -x /usr/local/bin/tls-keygen ]; then
674
561
  echo "compute-source-env.sh: TLS not configured (no tls-keygen binary)"
675
562
  return 0
676
563
  fi
677
-
678
- local platform_host="\${ECLOUD_PLATFORM_HOST:-}"
679
- local user_domain="\${DOMAIN:-}"
564
+
565
+ local domain="\${DOMAIN:-}"
680
566
  local mnemonic="\${MNEMONIC:-}"
681
-
682
- # Normalize "localhost" DOMAIN to unset \u2014 compute-tee apps often
683
- # set DOMAIN=localhost as a sentinel for "no custom domain".
684
- if [ "$user_domain" = "localhost" ]; then
685
- user_domain=""
686
- fi
687
-
688
- # If the user set DOMAIN to the same value as the platform
689
- # hostname, collapse them. Without this we'd burn a second ACME
690
- # issuance for the exact same name (each VM boot counts against
691
- # Let's Encrypt's 5-certs-per-domain-per-week limit), and Caddy
692
- # would then refuse to start because two site blocks bound to
693
- # the same address is a config error. Also unset DOMAIN so the
694
- # DOMAIN site block in the Caddyfile falls back to its
695
- # "localhost.user.invalid" placeholder and stays dormant.
696
- if [ -n "$platform_host" ] && [ "$user_domain" = "$platform_host" ]; then
697
- echo "compute-source-env.sh: DOMAIN matches ECLOUD_PLATFORM_HOST; skipping duplicate cert"
698
- user_domain=""
699
- unset DOMAIN
700
- fi
701
-
702
- if [ -z "$platform_host" ] && [ -z "$user_domain" ]; then
703
- echo "compute-source-env.sh: TLS skipped (neither ECLOUD_PLATFORM_HOST nor DOMAIN set)"
704
- return 0
567
+
568
+ # Since tls-keygen is present, TLS is expected - validate requirements
569
+ if [ -z "$domain" ] || [ "$domain" = "localhost" ]; then
570
+ echo "compute-source-env.sh: ERROR - TLS binary present but DOMAIN not configured or is localhost"
571
+ echo "compute-source-env.sh: Set DOMAIN environment variable to a valid domain"
572
+ exit 1
705
573
  fi
706
-
574
+
707
575
  if [ -z "$mnemonic" ]; then
708
- echo "compute-source-env.sh: ERROR - TLS requested but MNEMONIC not available"
709
- echo "ECLOUD_FAIL tls_mnemonic_missing"
576
+ echo "compute-source-env.sh: ERROR - TLS binary present but MNEMONIC not available"
577
+ echo "compute-source-env.sh: Cannot obtain TLS certificate without mnemonic"
710
578
  exit 1
711
579
  fi
712
-
580
+
713
581
  if [ ! -x /usr/local/bin/caddy ]; then
714
- echo "compute-source-env.sh: ERROR - TLS requested but Caddy not found"
715
- echo "ECLOUD_FAIL tls_caddy_missing"
582
+ echo "compute-source-env.sh: ERROR - TLS binary present but Caddy not found"
716
583
  exit 1
717
584
  fi
718
-
585
+
586
+ echo "compute-source-env.sh: Setting up TLS for domain: $domain"
587
+
588
+ # Obtain TLS certificate using ACME
589
+ # Default to http-01, but allow override via ACME_CHALLENGE env var
719
590
  local challenge="\${ACME_CHALLENGE:-http-01}"
591
+
592
+ # Check if we should use staging (for testing)
720
593
  local staging_flag=""
721
594
  if [ "\${ACME_STAGING:-false}" = "true" ]; then
722
595
  staging_flag="-staging"
723
- echo "compute-source-env.sh: Using Let's Encrypt STAGING environment"
724
- fi
725
-
726
- local certs_issued=0
727
-
728
- if [ -n "$platform_host" ]; then
729
- if issue_cert_for "$platform_host" "/run/tls/platform" "$mnemonic" "$challenge" "$staging_flag"; then
730
- certs_issued=$((certs_issued + 1))
731
- else
732
- echo "compute-source-env.sh: ERROR - failed to issue cert for platform host $platform_host"
733
- echo "ECLOUD_FAIL tls_setup"
734
- exit 1
735
- fi
736
- fi
737
-
738
- if [ -n "$user_domain" ]; then
739
- if issue_cert_for "$user_domain" "/run/tls/domain" "$mnemonic" "$challenge" "$staging_flag"; then
740
- certs_issued=$((certs_issued + 1))
741
- else
742
- echo "compute-source-env.sh: ERROR - failed to issue cert for user domain $user_domain"
743
- echo "ECLOUD_FAIL tls_setup"
744
- exit 1
745
- fi
596
+ echo "compute-source-env.sh: Using Let's Encrypt STAGING environment (certificates won't be trusted)"
746
597
  fi
747
-
748
- if [ "$certs_issued" -eq 0 ]; then
749
- echo "compute-source-env.sh: no certs issued, skipping Caddy (app will serve plaintext until DNS is wired)"
750
- return 0
598
+
599
+ echo "compute-source-env.sh: Obtaining TLS certificate using $challenge challenge..."
600
+ # Pass the API URL for certificate persistence
601
+ if ! SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt \\
602
+ MNEMONIC="$mnemonic" DOMAIN="$domain" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
603
+ -challenge "$challenge" \\
604
+ $staging_flag; then
605
+ echo "compute-source-env.sh: ERROR - Failed to obtain TLS certificate"
606
+ echo "compute-source-env.sh: Certificate issuance failed for $domain"
607
+ exit 1
751
608
  fi
752
-
753
- # Caddy's validate step checks that every \`tls <cert> <key>\` file
754
- # exists, even on site blocks bound to dormant placeholder
755
- # hostnames. The default Caddyfile declares both a platform site
756
- # and a user-domain site; when only one is configured, the other
757
- # block's cert paths are never populated and validate fails with
758
- # "Invalid Caddyfile". Point the unused block at the issued
759
- # block's cert files so validate passes \u2014 the dormant block can't
760
- # receive real traffic (its hostname falls back to
761
- # localhost.{platform,user}.invalid, which Caddy routes by SNI and
762
- # never matches public traffic), so the symlink is never actually
763
- # presented. Skipped when a user-supplied Caddyfile is in use,
764
- # since we don't know what cert paths it references.
765
- if [ -d /run/tls/platform ] && [ ! -e /run/tls/domain/fullchain.pem ]; then
766
- mkdir -p /run/tls/domain
767
- ln -sf /run/tls/platform/fullchain.pem /run/tls/domain/fullchain.pem
768
- ln -sf /run/tls/platform/privkey.pem /run/tls/domain/privkey.pem
769
- elif [ -d /run/tls/domain ] && [ ! -e /run/tls/platform/fullchain.pem ]; then
770
- mkdir -p /run/tls/platform
771
- ln -sf /run/tls/domain/fullchain.pem /run/tls/platform/fullchain.pem
772
- ln -sf /run/tls/domain/privkey.pem /run/tls/platform/privkey.pem
609
+
610
+ echo "compute-source-env.sh: TLS certificate obtained successfully"
611
+
612
+ # Validate Caddyfile before starting
613
+ if ! /usr/local/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile 2>/dev/null; then
614
+ echo "compute-source-env.sh: ERROR - Invalid Caddyfile"
615
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
616
+ exit 1
773
617
  fi
774
-
775
- # Validate Caddyfile before starting. Don't redirect stderr \u2014 when
776
- # validate fails, Caddy's diagnostic is the only signal that lands
777
- # in ReadinessError.SerialTail, so silencing it leaves operators
778
- # staring at a bare "tls_invalid_caddyfile" with no detail.
779
- if [ -f /etc/caddy/Caddyfile ]; then
780
- if ! /usr/local/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile; then
781
- echo "compute-source-env.sh: ERROR - Invalid Caddyfile"
782
- echo "ECLOUD_FAIL tls_invalid_caddyfile"
618
+
619
+ # Start Caddy in background
620
+ echo "compute-source-env.sh: Starting Caddy reverse proxy..."
621
+
622
+ # Check if Caddy logs should be enabled
623
+ if [ "\${ENABLE_CADDY_LOGS:-false}" = "true" ]; then
624
+ if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1; then
625
+ echo "compute-source-env.sh: ERROR - Failed to start Caddy"
626
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
783
627
  exit 1
784
628
  fi
785
-
786
- echo "compute-source-env.sh: Starting Caddy reverse proxy..."
787
- if [ "\${ENABLE_CADDY_LOGS:-false}" = "true" ]; then
788
- if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1; then
789
- echo "compute-source-env.sh: ERROR - Failed to start Caddy"
790
- echo "ECLOUD_FAIL tls_caddy_start"
791
- exit 1
792
- fi
793
- else
794
- if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
795
- echo "compute-source-env.sh: ERROR - Failed to start Caddy"
796
- echo "ECLOUD_FAIL tls_caddy_start"
797
- exit 1
798
- fi
799
- fi
800
-
801
- sleep 2
802
- echo "compute-source-env.sh: Caddy started successfully"
803
629
  else
804
- echo "compute-source-env.sh: No Caddyfile found, skipping Caddy"
630
+ # Redirect Caddy output to /dev/null to silence logs
631
+ if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
632
+ echo "compute-source-env.sh: ERROR - Failed to start Caddy"
633
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
634
+ exit 1
635
+ fi
805
636
  fi
806
-
637
+
638
+ # Give Caddy a moment to fully initialize
639
+ sleep 2
640
+ echo "compute-source-env.sh: Caddy started successfully"
807
641
  return 0
808
642
  }
809
643
 
@@ -814,309 +648,15 @@ setup_tls
814
648
  export KMS_SERVER_URL="{{kmsServerURL}}"
815
649
  export KMS_PUBLIC_KEY="$(cat /usr/local/bin/kms-signing-public-key.pem)"
816
650
 
817
- # \u2500\u2500 Prewarm-detach: wait for PD if expected \u2500\u2500
818
- # Orchestrator sets ECLOUD_PD_EXPECTED=1 on apps using StorageBackend=pd.
819
- # When the prewarm path is used, the new VM boots WITHOUT the disk; we
820
- # signal awaiting-userdata and poll until the disk is attached.
821
- USERDATA_MOUNT="/mnt/disks/userdata"
822
- USERDATA_DEV="/dev/disk/by-id/google-persistent_storage_1"
823
-
824
- # userdata_mount_backing_dev resolves the kernel device that currently
825
- # backs $USERDATA_MOUNT (or empty string if not mounted). The output is
826
- # canonicalized via readlink so we can compare against $USERDATA_DEV
827
- # without caring whether the mount table records the symlinked
828
- # /dev/disk/by-id/... path or the underlying /dev/sdb-style path.
829
- #
830
- # Tries findmnt first (clean structured output), falls back to parsing
831
- # /proc/self/mountinfo so we don't add a hard dependency on util-linux
832
- # being present in the image.
833
- userdata_mount_backing_dev() {
834
- if command -v findmnt >/dev/null 2>&1; then
835
- local src
836
- src=$(findmnt -n -o SOURCE -- "$USERDATA_MOUNT" 2>/dev/null) || return 0
837
- [ -n "$src" ] || return 0
838
- readlink -f "$src" 2>/dev/null || echo "$src"
839
- return 0
840
- fi
841
- # /proc/self/mountinfo line shape (kernel docs): mount-id parent-id
842
- # major:minor root mount-point ... So column 5 is the mount point and
843
- # the device path lives in the optional fields after the "-" separator.
844
- # We just need the major:minor \u2192 device mapping; awk + a /sys lookup
845
- # is cleaner than parsing the mountinfo's optional-field section.
846
- local maj_min dev_basename
847
- maj_min=$(awk -v mp="$USERDATA_MOUNT" '$5 == mp { print $3 }' /proc/self/mountinfo 2>/dev/null | head -1)
848
- [ -n "$maj_min" ] || return 0
849
- dev_basename=$(readlink "/sys/dev/block/\${maj_min}" 2>/dev/null | awk -F/ '{print $NF}')
850
- [ -n "$dev_basename" ] || return 0
851
- readlink -f "/dev/\${dev_basename}" 2>/dev/null || echo "/dev/\${dev_basename}"
852
- }
853
-
854
- # expected_userdata_dev resolves $USERDATA_DEV through any symlinks so
855
- # string-compare against userdata_mount_backing_dev is meaningful even
856
- # when the kernel hands us a /dev/nvme0n1-style path while
857
- # /dev/disk/by-id/google-persistent_storage_1 is the symlink GCE
858
- # publishes.
859
- expected_userdata_dev() {
860
- if [ -e "$USERDATA_DEV" ]; then
861
- readlink -f "$USERDATA_DEV" 2>/dev/null || echo "$USERDATA_DEV"
862
- else
863
- echo "$USERDATA_DEV"
864
- fi
865
- }
866
-
867
- wait_for_userdata() {
868
- if [ "\${ECLOUD_PD_EXPECTED:-0}" != "1" ]; then
869
- return 0
870
- fi
871
- if mountpoint -q "$USERDATA_MOUNT" 2>/dev/null; then
872
- # The mount slot is taken \u2014 but is it taken by the PD we're
873
- # about to attach, or by something else? The Confidential Space
874
- # TEE runtime's SetupSecondaryEncryptedVolume step probes for a
875
- # secondary GCE disk once at boot, doesn't see it (because the
876
- # orchestrator hasn't called AttachPD yet \u2014 the contract says
877
- # the new VM signals awaiting-userdata FIRST), and falls back
878
- # to mounting an encrypted folder on the BOOT disk at this same
879
- # path. If we accept that mount as-is, the user app boots on a
880
- # fresh empty filesystem and the orchestrator's late AttachPD
881
- # silently lands on a VM that's already serving from boot disk.
882
- # See docs/solutions/2026-05-15-prewarm-detach-pd-preservation-boot-race.md
883
- # in ecloud-platform.
884
- local backing expected
885
- backing=$(userdata_mount_backing_dev)
886
- expected=$(expected_userdata_dev)
887
- if [ -n "$backing" ] && [ "$backing" = "$expected" ]; then
888
- echo "compute-source-env.sh: userdata already mounted at $USERDATA_MOUNT (backed by $backing)"
889
- return 0
890
- fi
891
- # Mount slot taken, but NOT by our PD. This is the runtime
892
- # premount on boot disk. Try to recover by lazy-unmounting (the
893
- # filesystem is fresh and unused \u2014 no app has written to it
894
- # yet), then fall through to the normal wait-for-PD loop.
895
- echo "compute-source-env.sh: WARNING - $USERDATA_MOUNT premounted by TEE runtime fallback (backing=\${backing:-unknown}, expected=$expected); attempting recovery"
896
- if ! umount -l "$USERDATA_MOUNT" 2>/dev/null; then
897
- # Lazy unmount should never fail under normal circumstances \u2014
898
- # it doesn't wait for open files, just detaches the namespace.
899
- # If it did fail, we have no path forward: the orchestrator's
900
- # strict-mode WaitAwaitingUserdata gate (ecloud-platform PR
901
- # #174) catches our ECLOUD_FAIL within one poll tick and
902
- # rolls back, leaving the user's data safe on the old VM.
903
- echo "ECLOUD_FAIL pd_runtime_premount_unrecoverable"
904
- exit 1
905
- fi
906
- echo "compute-source-env.sh: unmounted runtime fallback at $USERDATA_MOUNT, waiting for real PD"
907
- fi
908
- # Refuse to proceed if the tools we need for safe first-attach
909
- # detection are missing. Without blkid we cannot tell an empty new
910
- # disk from an already-formatted one \u2014 running mkfs.ext4 on the
911
- # latter would destroy data.
912
- if ! command -v blkid >/dev/null 2>&1; then
913
- echo "ECLOUD_FAIL pd_tools_missing"
914
- exit 1
915
- fi
916
- echo "ECLOUD_AWAITING_USERDATA"
917
- echo "compute-source-env.sh: waiting for PD at $USERDATA_DEV..."
918
- # Poll for up to 10 minutes (120 * 5s). The orchestrator's overall
919
- # attach timeout is shorter; the ceiling here just bounds the wait
920
- # for manual / diagnostic scenarios.
921
- local i=0
922
- local mount_failures=0
923
- while [ "$i" -lt 120 ]; do
924
- if [ -e "$USERDATA_DEV" ]; then
925
- mkdir -p "$USERDATA_MOUNT"
926
- if mount -o noatime "$USERDATA_DEV" "$USERDATA_MOUNT" 2>/dev/null; then
927
- echo "compute-source-env.sh: PD mounted at $USERDATA_MOUNT"
928
- return 0
929
- fi
930
- # Disk present but mount failed. Check whether it has a
931
- # recognized filesystem. \`blkid -s TYPE -o value\` prints the
932
- # FS type (empty if none). We only mkfs when there is
933
- # demonstrably NO filesystem \u2014 never on the basis of blkid
934
- # returning non-zero alone, which could mean "blkid missing"
935
- # or "device busy".
936
- local fstype
937
- fstype=$(blkid -s TYPE -o value "$USERDATA_DEV" 2>/dev/null)
938
- if [ -z "$fstype" ]; then
939
- echo "compute-source-env.sh: formatting $USERDATA_DEV (first attach)"
940
- mkfs.ext4 -F -L eclouddata "$USERDATA_DEV" >/dev/null 2>&1 || {
941
- echo "ECLOUD_FAIL pd_mkfs_failed"
942
- exit 1
943
- }
944
- mount -o noatime "$USERDATA_DEV" "$USERDATA_MOUNT" || {
945
- echo "ECLOUD_FAIL pd_mount_after_format_failed"
946
- exit 1
947
- }
948
- return 0
949
- fi
950
- # Disk has a filesystem but mount still failed. Give it a
951
- # few retries to cover transient cases (device busy, udev
952
- # still settling), but don't pretend this is an attach
953
- # timeout if it persists.
954
- mount_failures=$((mount_failures + 1))
955
- if [ "$mount_failures" -ge 6 ]; then
956
- echo "ECLOUD_FAIL pd_mount_failed"
957
- exit 1
958
- fi
959
- else
960
- # Device disappeared (e.g. udev re-enumeration between
961
- # attach and mount). Reset the consecutive-failure counter
962
- # so only true back-to-back mount failures trip
963
- # pd_mount_failed; a device blip should not steal retries.
964
- mount_failures=0
965
- fi
966
- i=$((i + 1))
967
- sleep 5
968
- done
969
- echo "ECLOUD_FAIL pd_attach_timeout"
970
- exit 1
971
- }
972
-
973
- wait_for_userdata
974
-
975
- # \u2500\u2500 Prewarm-detach: install SIGTERM handler for graceful drain \u2500\u2500
976
- # Orchestrator signals drain by setting the instance metadata key
977
- # ECLOUD_DRAIN_REQUESTED=1, which a host-level agent translates into
978
- # SIGTERM on PID 1. On SIGTERM we:
979
- # 1. Forward to the child (wakes the user's app for graceful exit)
980
- # 2. Wait for child exit
981
- # 3. Sync + unmount the PD
982
- # 4. Emit ECLOUD_DETACHED so the orchestrator can proceed to detach
983
- CHILD_PID=""
984
- _DRAIN_IN_PROGRESS=0
985
-
986
- drain_handler() {
987
- # Guard against re-entry if SIGTERM arrives twice (e.g. both the
988
- # drain_watcher and an external signal fire in quick succession).
989
- if [ "$_DRAIN_IN_PROGRESS" = "1" ]; then
990
- return 0
991
- fi
992
- _DRAIN_IN_PROGRESS=1
993
- echo "compute-source-env.sh: received drain signal, forwarding to child pgid=$CHILD_PID"
994
- if [ -n "$CHILD_PID" ]; then
995
- # Send to the process group so intermediate wrappers (su, sh -c,
996
- # etc.) don't swallow the signal. The leading \`-\` targets the
997
- # pgid, which equals the direct child's pid for a shell-backgrounded
998
- # process. Fall back to the pid alone if pgid signaling fails
999
- # (e.g. kernel older than 3.9 or PID namespace edge cases).
1000
- kill -TERM -"$CHILD_PID" 2>/dev/null || kill -TERM "$CHILD_PID" 2>/dev/null || true
1001
- # Give the app up to 30s to exit cleanly.
1002
- local i=0
1003
- while [ "$i" -lt 30 ] && kill -0 "$CHILD_PID" 2>/dev/null; do
1004
- i=$((i + 1))
1005
- sleep 1
1006
- done
1007
- if kill -0 "$CHILD_PID" 2>/dev/null; then
1008
- echo "compute-source-env.sh: child did not exit in 30s, sending SIGKILL"
1009
- kill -KILL -"$CHILD_PID" 2>/dev/null || kill -KILL "$CHILD_PID" 2>/dev/null || true
1010
- # Reap the process so its in-flight I/O is flushed to the
1011
- # filesystem before we sync + unmount. SIGKILL schedules
1012
- # death; wait guarantees it's complete.
1013
- wait "$CHILD_PID" 2>/dev/null || true
1014
- fi
1015
- fi
1016
- if [ "\${ECLOUD_PD_EXPECTED:-0}" = "1" ] && mountpoint -q "$USERDATA_MOUNT" 2>/dev/null; then
1017
- sync
1018
- if umount "$USERDATA_MOUNT" 2>/dev/null; then
1019
- echo "compute-source-env.sh: unmounted $USERDATA_MOUNT cleanly"
1020
- else
1021
- # Force lazy unmount as last resort \u2014 orchestrator still needs
1022
- # the DETACHED signal to proceed.
1023
- umount -l "$USERDATA_MOUNT" 2>/dev/null || true
1024
- echo "compute-source-env.sh: WARNING - used lazy unmount on $USERDATA_MOUNT"
1025
- fi
1026
- # ECLOUD_DETACHED is strictly a PD-lifecycle signal. Only emit
1027
- # it when we actually had a PD mount in play, so serial-log
1028
- # parsers and alerting for non-PD apps don't see spurious
1029
- # lifecycle markers on routine container SIGTERM.
1030
- echo "ECLOUD_DETACHED"
1031
- fi
1032
- # Always exit 0: drain is a managed shutdown and the orchestrator
1033
- # waits on ECLOUD_DETACHED, not the container exit code. Forwarding
1034
- # the child's exit status here would make a crash-during-drain look
1035
- # like a drain failure to whatever reads the container exit code.
1036
- exit 0
1037
- }
1038
- trap drain_handler TERM
1039
-
1040
- # \u2500\u2500 Prewarm-detach: background drain watcher \u2500\u2500
1041
- # Container metadata delivery in Confidential Space is limited, so we
1042
- # poll the instance metadata server for ECLOUD_DRAIN_REQUESTED and
1043
- # raise SIGTERM on ourselves when it flips to "1".
1044
- #
1045
- # Try wget first (present in most Alpine bases), fall back to curl.
1046
- # If neither is present, drain watcher is disabled \u2014 the orchestrator
1047
- # will hit its drain timeout and fail the upgrade explicitly, which is
1048
- # the correct behavior (we cannot silently ignore a drain request).
1049
- _fetch_drain_flag() {
1050
- local url="http://metadata.google.internal/computeMetadata/v1/instance/attributes/ECLOUD_DRAIN_REQUESTED"
1051
- if command -v wget >/dev/null 2>&1; then
1052
- wget -q --tries=1 --timeout=2 --header='Metadata-Flavor: Google' -O - "$url" 2>/dev/null
1053
- elif command -v curl >/dev/null 2>&1; then
1054
- curl -sf --max-time 2 -H 'Metadata-Flavor: Google' "$url" 2>/dev/null
1055
- else
1056
- return 2
1057
- fi
1058
- }
1059
-
1060
- drain_watcher() {
1061
- # Preflight: confirm we have an HTTP client
1062
- if ! _fetch_drain_flag >/dev/null 2>&1; then
1063
- # Either no http client available OR metadata server not
1064
- # responding yet. If no client, give up and log; otherwise the
1065
- # loop below will retry.
1066
- if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then
1067
- echo "compute-source-env.sh: WARNING - no wget/curl; drain_watcher disabled"
1068
- return 0
1069
- fi
1070
- fi
1071
- while true; do
1072
- local v
1073
- v=$(_fetch_drain_flag || true)
1074
- if [ "$v" = "1" ]; then
1075
- echo "compute-source-env.sh: drain_watcher saw ECLOUD_DRAIN_REQUESTED=1, signaling PID 1"
1076
- # The CS launcher runs this script directly as PID 1, so
1077
- # kill -TERM 1 delivers SIGTERM to the shell that installed
1078
- # the drain_handler trap. If the launch mechanism ever
1079
- # wraps this script in another process, this assumption
1080
- # breaks and drain will silently no-op \u2014 audit here.
1081
- kill -TERM 1 2>/dev/null || true
1082
- return 0
1083
- fi
1084
- sleep 2
1085
- done
1086
- }
1087
-
1088
- if [ "\${ECLOUD_PD_EXPECTED:-0}" = "1" ]; then
1089
- # Assumption: the orchestrator only flips ECLOUD_DRAIN_REQUESTED=1
1090
- # after observing ECLOUD_AWAITING_USERDATA (old VM) or
1091
- # ECLOUD_READY (new VM), so CHILD_PID is always set by the time
1092
- # drain_handler fires. If drain somehow arrived in the tiny window
1093
- # between this watcher spawn and CHILD_PID assignment below,
1094
- # drain_handler would skip the child-kill branch and still emit
1095
- # ECLOUD_DETACHED \u2014 harmless because there's nothing to drain yet.
1096
- if [ -x /usr/local/bin/ecloud-drain-watcher ]; then
1097
- /usr/local/bin/ecloud-drain-watcher &
1098
- else
1099
- drain_watcher &
1100
- fi
1101
- fi
1102
-
1103
651
  echo "compute-source-env.sh: Environment sourced."
1104
- echo "ECLOUD_READY runtime_bootstrapped"
1105
652
 
1106
653
  # Drop privileges to original user for the application command
1107
654
  if [ -n "$__ECLOUD_ORIGINAL_USER" ] && [ "$(id -u)" = "0" ]; then
1108
655
  echo "compute-source-env.sh: Dropping privileges to user: $__ECLOUD_ORIGINAL_USER"
1109
- # Must background the child so our trap can fire; exec replaces PID 1.
1110
- su -s /bin/sh "$__ECLOUD_ORIGINAL_USER" -c 'exec "$@"' -- sh "$@" &
1111
- CHILD_PID=$!
1112
- wait "$CHILD_PID"
1113
- exit $?
656
+ exec su -s /bin/sh "$__ECLOUD_ORIGINAL_USER" -c 'exec "$@"' -- sh "$@"
1114
657
  fi
1115
658
 
1116
- "$@" &
1117
- CHILD_PID=$!
1118
- wait "$CHILD_PID"
1119
- exit $?
659
+ exec "$@"
1120
660
  `;
1121
661
 
1122
662
  // src/client/common/templates/scriptTemplate.ts
@@ -1125,14 +665,6 @@ function processScriptTemplate(data) {
1125
665
  return template(data);
1126
666
  }
1127
667
 
1128
- // src/client/common/templates/Caddyfile.default.tmpl
1129
- var Caddyfile_default_default = '# Caddy configuration for automatic HTTPS\n#\n# Two sites can be configured at runtime via env vars that\n# ecloud-platform / the CLI inject into tee-env metadata:\n#\n# ECLOUD_PLATFORM_HOST \u2014 the platform-derived hostname\n# (<addr>.<env>.eigencloud.xyz). Always set\n# for platform-routed apps. Cert at\n# /run/tls/platform/fullchain.pem.\n# DOMAIN \u2014 optional user-supplied custom domain.\n# Cert at /run/tls/domain/fullchain.pem.\n#\n# When a variable is unset, Caddy substitutes the default listed\n# after the colon. We use distinct defaults per site so each block\n# binds to a unique name at bootstrap \u2014 both blocks overlapping\n# on "localhost" would cause Caddy to reject the config.\n{$ECLOUD_PLATFORM_HOST:localhost.platform.invalid} {\n tls /run/tls/platform/fullchain.pem /run/tls/platform/privkey.pem\n\n reverse_proxy localhost:{$APP_PORT:3000} {\n health_uri /health\n health_interval 30s\n health_timeout 5s\n health_status 200\n }\n\n header {\n X-Content-Type-Options "nosniff"\n X-Frame-Options "DENY"\n X-XSS-Protection "1; mode=block"\n Referrer-Policy "strict-origin-when-cross-origin"\n -Server\n }\n\n log {\n output stdout\n format console\n level INFO\n }\n\n request_body {\n max_size 10MB\n }\n}\n\n{$DOMAIN:localhost.user.invalid} {\n tls /run/tls/domain/fullchain.pem /run/tls/domain/privkey.pem\n\n reverse_proxy localhost:{$APP_PORT:3000} {\n health_uri /health\n health_interval 30s\n health_timeout 5s\n health_status 200\n }\n\n header {\n X-Content-Type-Options "nosniff"\n X-Frame-Options "DENY"\n X-XSS-Protection "1; mode=block"\n Referrer-Policy "strict-origin-when-cross-origin"\n -Server\n }\n\n log {\n output stdout\n format console\n level INFO\n }\n\n request_body {\n max_size 10MB\n }\n}\n\n# HTTP endpoint (optional, for health checks or redirects)\n:80 {\n # Redirect to HTTPS for any real (non-placeholder) host\n @for_domain expression {host} != "localhost.platform.invalid" && {host} != "localhost.user.invalid" && {host} != "localhost"\n redir @for_domain https://{host}{uri} permanent\n\n # Health check endpoint (always available via HTTP)\n handle /health {\n respond "OK" 200\n }\n}\n';
1130
-
1131
- // src/client/common/templates/caddyfileTemplate.ts
1132
- function getDefaultCaddyfile() {
1133
- return Caddyfile_default_default;
1134
- }
1135
-
1136
668
  // keys/mainnet-alpha/prod/kms-encryption-public-key.pem
1137
669
  var kms_encryption_public_key_default = "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0kHU86k17ofCIGcJKDcf\nAFurFhSLeWmOL0bwWLCeVnTPG0MMHtJOq+woE0XXSWw6lzm+jzavBBTwKde1dgal\nAp91vULAZFMUpiUdd2dNUVtvU89qW0Pgf1Eu5FDj7BkY/SnyECbWJM4ga0BmpiGy\nnQwLNN9mMGhjVoVLn2zwEGZ7JzS9Nz11EZKO/k/9DcO6LaoIFmKuvVf3jl6lvZg8\naeA0LoZXjkycHlRUt/kfKwZnhakUaYHP1ksV7ZNmolS5GYDTSKGB2KPPNR1s4/Xu\nu8zeEFC8HuGRU8XuuBeaAunitnGhbNVREUNJGff6HZOGB6CIFNXjbQETeZ3p5uro\n0v+hd1QqQYBv7+DEaMCmGnJNGAyIMr2mn4vr7wGsIj0HonlSHmQ8rmdUhL2ocNTc\nLhKgZiZmBuDpSbFW/r53R2G7CHcqaqGeUBnT54QCH4zsYKw0/4dOtwFxQpTyBf9/\n+k+KaWEJYKkx9d9OzKGyAvzrTDVOFoajddiJ6LPvRlMdOUQr3hl4IAC0/nh9lhHq\nD0R+i5WAU96TkdAe7B7iTGH2D22k0KUPR6Q9W3aF353SLxQAMPNrgG4QQufAdRJn\nAF+8ntun5TkTqjTWRSwAsUJZ1z4wb96DympWJbDi0OciJRZ3Fz3j9+amC43yCHGg\naaEMjdt35ewbztUSc04F10MCAwEAAQ==\n-----END PUBLIC KEY-----";
1138
670
 
@@ -1308,9 +840,15 @@ async function layerLocalImage(options, logger) {
1308
840
  const imageConfig = await extractImageConfig(docker, sourceImageRef);
1309
841
  const originalCmd = imageConfig.cmd.length > 0 ? imageConfig.cmd : imageConfig.entrypoint;
1310
842
  const originalUser = imageConfig.user;
1311
- const includeTLS = true;
1312
- const drainWatcherSource = findBinary("ecloud-drain-watcher-linux-amd64");
1313
- const includeDrainWatcher = fs.existsSync(drainWatcherSource);
843
+ let includeTLS = false;
844
+ if (envFilePath && fs.existsSync(envFilePath)) {
845
+ const envContent = fs.readFileSync(envFilePath, "utf-8");
846
+ const domainMatch = envContent.match(/^DOMAIN=(.+)$/m);
847
+ if (domainMatch && domainMatch[1] && domainMatch[1] !== "localhost") {
848
+ includeTLS = true;
849
+ logger.debug(`Found DOMAIN=${domainMatch[1]} in ${envFilePath}, including TLS components`);
850
+ }
851
+ }
1314
852
  const layeredDockerfileContent = processDockerfileTemplate({
1315
853
  baseImage: sourceImageRef,
1316
854
  originalCmd: JSON.stringify(originalCmd),
@@ -1318,9 +856,8 @@ async function layerLocalImage(options, logger) {
1318
856
  logRedirect,
1319
857
  resourceUsageAllow,
1320
858
  includeTLS,
1321
- ecloudCLIVersion: "0.1.0",
859
+ ecloudCLIVersion: "0.1.0"
1322
860
  // TODO: Get from package.json
1323
- includeDrainWatcher
1324
861
  });
1325
862
  const scriptContent = processScriptTemplate({
1326
863
  kmsServerURL: environmentConfig.kmsServerURL,
@@ -1330,8 +867,7 @@ async function layerLocalImage(options, logger) {
1330
867
  environmentConfig,
1331
868
  layeredDockerfileContent,
1332
869
  scriptContent,
1333
- includeTLS,
1334
- includeDrainWatcher ? drainWatcherSource : void 0
870
+ includeTLS
1335
871
  // logger
1336
872
  );
1337
873
  try {
@@ -1346,7 +882,7 @@ async function layerLocalImage(options, logger) {
1346
882
  fs.rmSync(tempDir, { recursive: true, force: true });
1347
883
  }
1348
884
  }
1349
- async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileContent, scriptContent, includeTLS, drainWatcherSource) {
885
+ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileContent, scriptContent, includeTLS) {
1350
886
  const tempDir = fs.mkdtempSync(path2.join(os.tmpdir(), LAYERED_BUILD_DIR_PREFIX));
1351
887
  try {
1352
888
  const layeredDockerfilePath = path2.join(tempDir, LAYERED_DOCKERFILE_NAME);
@@ -1370,11 +906,6 @@ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileCo
1370
906
  }
1371
907
  fs.copyFileSync(kmsClientSource, kmsClientPath);
1372
908
  fs.chmodSync(kmsClientPath, 493);
1373
- if (drainWatcherSource && fs.existsSync(drainWatcherSource)) {
1374
- const drainWatcherPath = path2.join(tempDir, DRAIN_WATCHER_BINARY_NAME);
1375
- fs.copyFileSync(drainWatcherSource, drainWatcherPath);
1376
- fs.chmodSync(drainWatcherPath, 493);
1377
- }
1378
909
  if (includeTLS) {
1379
910
  const tlsKeygenPath = path2.join(tempDir, TLS_KEYGEN_BINARY_NAME);
1380
911
  const tlsKeygenSource = findBinary("tls-keygen-linux-amd64");
@@ -1385,13 +916,15 @@ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileCo
1385
916
  }
1386
917
  fs.copyFileSync(tlsKeygenSource, tlsKeygenPath);
1387
918
  fs.chmodSync(tlsKeygenPath, 493);
1388
- const userCaddyfilePath = path2.join(process.cwd(), CADDYFILE_NAME);
1389
- const destCaddyfilePath = path2.join(tempDir, CADDYFILE_NAME);
1390
- if (fs.existsSync(userCaddyfilePath)) {
1391
- fs.copyFileSync(userCaddyfilePath, destCaddyfilePath);
1392
- fs.chmodSync(destCaddyfilePath, 420);
919
+ const caddyfilePath = path2.join(process.cwd(), CADDYFILE_NAME);
920
+ if (fs.existsSync(caddyfilePath)) {
921
+ const caddyfileContent = fs.readFileSync(caddyfilePath);
922
+ const destCaddyfilePath = path2.join(tempDir, CADDYFILE_NAME);
923
+ fs.writeFileSync(destCaddyfilePath, caddyfileContent, { mode: 420 });
1393
924
  } else {
1394
- fs.writeFileSync(destCaddyfilePath, getDefaultCaddyfile(), { mode: 420 });
925
+ throw new Error(
926
+ "TLS is enabled (DOMAIN is set) but Caddyfile not found. Run configure TLS to set up TLS configuration"
927
+ );
1395
928
  }
1396
929
  }
1397
930
  return tempDir;
@@ -1459,16 +992,7 @@ async function extractDigestFromSinglePlatform(manifest, imageRef) {
1459
992
  architecture: inspectData[0].Architecture
1460
993
  } : null;
1461
994
  if (!config) {
1462
- if (manifest.config?.digest) {
1463
- const digest = hexStringToBytes32(manifest.config.digest);
1464
- const registry = extractRegistryName(imageRef);
1465
- return {
1466
- digest,
1467
- registry,
1468
- platform: DOCKER_PLATFORM
1469
- };
1470
- }
1471
- throw new Error(`Could not determine platform for ${imageRef}`);
995
+ throw createPlatformErrorMessage(imageRef, ["unknown (could not determine architecture)"]);
1472
996
  }
1473
997
  const platform2 = `${config.os}/${config.architecture}`;
1474
998
  if (platform2 === DOCKER_PLATFORM) {
@@ -1540,14 +1064,11 @@ Image: ${imageRef}
1540
1064
  Found platform(s): ${platforms.join(", ")}
1541
1065
  Required platform: ${DOCKER_PLATFORM}
1542
1066
 
1543
- To fix this issue:
1544
- 1. Manual fix:
1545
- a. Rebuild your image with the correct platform:
1546
- docker build --platform ${DOCKER_PLATFORM} -t ${imageRef} .
1547
- b. Push the rebuilt image to your remote registry:
1548
- docker push ${imageRef}
1549
-
1550
- 2. Or use the SDK to build with the correct platform automatically.`;
1067
+ To fix, either:
1068
+ 1. Rebuild the image for ${DOCKER_PLATFORM} and push it:
1069
+ docker buildx build --platform ${DOCKER_PLATFORM} -t ${imageRef} --push .
1070
+ 2. Or use a verifiable build (--verifiable --repo <repo> --commit <sha>), which
1071
+ builds server-side and needs no local Docker.`;
1551
1072
  return new Error(errorMsg);
1552
1073
  }
1553
1074
 
@@ -1652,6 +1173,8 @@ async function prepareRelease(options, logger) {
1652
1173
  logger.info(`Waiting ${REGISTRY_PROPAGATION_WAIT_SECONDS} seconds for registry propagation...`);
1653
1174
  await new Promise((resolve2) => setTimeout(resolve2, REGISTRY_PROPAGATION_WAIT_SECONDS * 1e3));
1654
1175
  } else {
1176
+ logger.info("Verifying image platform (linux/amd64)...");
1177
+ await getImageDigestAndName(imageRef);
1655
1178
  logger.info("Checking if image needs layering...");
1656
1179
  finalImageRef = await layerRemoteImageIfNeeded(
1657
1180
  {
@@ -1810,6 +1333,13 @@ function extractRegistryNameNoDocker(imageRef) {
1810
1333
  var import_viem = require("viem");
1811
1334
 
1812
1335
  // src/client/common/types/index.ts
1336
+ var EMPTY_CONTAINER_POLICY = {
1337
+ args: [],
1338
+ cmdOverride: [],
1339
+ env: [],
1340
+ envOverride: [],
1341
+ restartPolicy: ""
1342
+ };
1813
1343
  var noopLogger = {
1814
1344
  debug: () => {
1815
1345
  },
@@ -3050,32 +2580,1577 @@ async function executeBatch(options, logger = noopLogger) {
3050
2580
  } else {
3051
2581
  revertReason = callError.message || "Unknown reason";
3052
2582
  }
3053
- }
3054
- throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2583
+ }
2584
+ throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2585
+ }
2586
+ return hash;
2587
+ }
2588
+
2589
+ // src/client/common/contract/caller.ts
2590
+ var import_viem3 = require("viem");
2591
+
2592
+ // src/client/common/utils/helpers.ts
2593
+ var import_viem2 = require("viem");
2594
+ var import_chains2 = require("viem/chains");
2595
+ var import_accounts = require("viem/accounts");
2596
+ function getChainFromID(chainID, fallback2 = import_chains2.sepolia) {
2597
+ const id = Number(chainID);
2598
+ return (0, import_viem2.extractChain)({ chains: SUPPORTED_CHAINS, id }) || fallback2;
2599
+ }
2600
+ function addHexPrefix(value) {
2601
+ return value.startsWith("0x") ? value : `0x${value}`;
2602
+ }
2603
+ function stripHexPrefix(value) {
2604
+ return value.startsWith("0x") ? value.slice(2) : value;
2605
+ }
2606
+
2607
+ // src/client/common/abis/AppController.json
2608
+ var AppController_default = [
2609
+ {
2610
+ type: "constructor",
2611
+ inputs: [
2612
+ {
2613
+ name: "_version",
2614
+ type: "string",
2615
+ internalType: "string"
2616
+ },
2617
+ {
2618
+ name: "_permissionController",
2619
+ type: "address",
2620
+ internalType: "contractIPermissionController"
2621
+ },
2622
+ {
2623
+ name: "_releaseManager",
2624
+ type: "address",
2625
+ internalType: "contractIReleaseManager"
2626
+ },
2627
+ {
2628
+ name: "_computeAVSRegistrar",
2629
+ type: "address",
2630
+ internalType: "contractIComputeAVSRegistrar"
2631
+ },
2632
+ {
2633
+ name: "_computeOperator",
2634
+ type: "address",
2635
+ internalType: "contractIComputeOperator"
2636
+ },
2637
+ {
2638
+ name: "_appBeacon",
2639
+ type: "address",
2640
+ internalType: "contractIBeacon"
2641
+ }
2642
+ ],
2643
+ stateMutability: "nonpayable"
2644
+ },
2645
+ {
2646
+ type: "function",
2647
+ name: "API_PERMISSION_TYPEHASH",
2648
+ inputs: [],
2649
+ outputs: [
2650
+ {
2651
+ name: "",
2652
+ type: "bytes32",
2653
+ internalType: "bytes32"
2654
+ }
2655
+ ],
2656
+ stateMutability: "view"
2657
+ },
2658
+ {
2659
+ type: "function",
2660
+ name: "appBeacon",
2661
+ inputs: [],
2662
+ outputs: [
2663
+ {
2664
+ name: "",
2665
+ type: "address",
2666
+ internalType: "contractIBeacon"
2667
+ }
2668
+ ],
2669
+ stateMutability: "view"
2670
+ },
2671
+ {
2672
+ type: "function",
2673
+ name: "calculateApiPermissionDigestHash",
2674
+ inputs: [
2675
+ {
2676
+ name: "permission",
2677
+ type: "bytes4",
2678
+ internalType: "bytes4"
2679
+ },
2680
+ {
2681
+ name: "expiry",
2682
+ type: "uint256",
2683
+ internalType: "uint256"
2684
+ }
2685
+ ],
2686
+ outputs: [
2687
+ {
2688
+ name: "",
2689
+ type: "bytes32",
2690
+ internalType: "bytes32"
2691
+ }
2692
+ ],
2693
+ stateMutability: "view"
2694
+ },
2695
+ {
2696
+ type: "function",
2697
+ name: "calculateAppId",
2698
+ inputs: [
2699
+ {
2700
+ name: "deployer",
2701
+ type: "address",
2702
+ internalType: "address"
2703
+ },
2704
+ {
2705
+ name: "salt",
2706
+ type: "bytes32",
2707
+ internalType: "bytes32"
2708
+ }
2709
+ ],
2710
+ outputs: [
2711
+ {
2712
+ name: "",
2713
+ type: "address",
2714
+ internalType: "contractIApp"
2715
+ }
2716
+ ],
2717
+ stateMutability: "view"
2718
+ },
2719
+ {
2720
+ type: "function",
2721
+ name: "computeAVSRegistrar",
2722
+ inputs: [],
2723
+ outputs: [
2724
+ {
2725
+ name: "",
2726
+ type: "address",
2727
+ internalType: "contractIComputeAVSRegistrar"
2728
+ }
2729
+ ],
2730
+ stateMutability: "view"
2731
+ },
2732
+ {
2733
+ type: "function",
2734
+ name: "computeOperator",
2735
+ inputs: [],
2736
+ outputs: [
2737
+ {
2738
+ name: "",
2739
+ type: "address",
2740
+ internalType: "contractIComputeOperator"
2741
+ }
2742
+ ],
2743
+ stateMutability: "view"
2744
+ },
2745
+ {
2746
+ type: "function",
2747
+ name: "confirmUpgrade",
2748
+ inputs: [
2749
+ {
2750
+ name: "app",
2751
+ type: "address",
2752
+ internalType: "contractIApp"
2753
+ }
2754
+ ],
2755
+ outputs: [],
2756
+ stateMutability: "nonpayable"
2757
+ },
2758
+ {
2759
+ type: "function",
2760
+ name: "createApp",
2761
+ inputs: [
2762
+ {
2763
+ name: "salt",
2764
+ type: "bytes32",
2765
+ internalType: "bytes32"
2766
+ },
2767
+ {
2768
+ name: "release",
2769
+ type: "tuple",
2770
+ internalType: "structIAppController.Release",
2771
+ components: [
2772
+ {
2773
+ name: "rmsRelease",
2774
+ type: "tuple",
2775
+ internalType: "structIReleaseManagerTypes.Release",
2776
+ components: [
2777
+ {
2778
+ name: "artifacts",
2779
+ type: "tuple[]",
2780
+ internalType: "structIReleaseManagerTypes.Artifact[]",
2781
+ components: [
2782
+ {
2783
+ name: "digest",
2784
+ type: "bytes32",
2785
+ internalType: "bytes32"
2786
+ },
2787
+ {
2788
+ name: "registry",
2789
+ type: "string",
2790
+ internalType: "string"
2791
+ }
2792
+ ]
2793
+ },
2794
+ {
2795
+ name: "upgradeByTime",
2796
+ type: "uint32",
2797
+ internalType: "uint32"
2798
+ }
2799
+ ]
2800
+ },
2801
+ {
2802
+ name: "publicEnv",
2803
+ type: "bytes",
2804
+ internalType: "bytes"
2805
+ },
2806
+ {
2807
+ name: "encryptedEnv",
2808
+ type: "bytes",
2809
+ internalType: "bytes"
2810
+ },
2811
+ {
2812
+ name: "containerPolicy",
2813
+ type: "tuple",
2814
+ internalType: "structIAppController.ContainerPolicy",
2815
+ components: [
2816
+ {
2817
+ name: "args",
2818
+ type: "string[]",
2819
+ internalType: "string[]"
2820
+ },
2821
+ {
2822
+ name: "cmdOverride",
2823
+ type: "string[]",
2824
+ internalType: "string[]"
2825
+ },
2826
+ {
2827
+ name: "env",
2828
+ type: "tuple[]",
2829
+ internalType: "structIAppController.EnvVar[]",
2830
+ components: [
2831
+ {
2832
+ name: "key",
2833
+ type: "string",
2834
+ internalType: "string"
2835
+ },
2836
+ {
2837
+ name: "value",
2838
+ type: "string",
2839
+ internalType: "string"
2840
+ }
2841
+ ]
2842
+ },
2843
+ {
2844
+ name: "envOverride",
2845
+ type: "tuple[]",
2846
+ internalType: "structIAppController.EnvVar[]",
2847
+ components: [
2848
+ {
2849
+ name: "key",
2850
+ type: "string",
2851
+ internalType: "string"
2852
+ },
2853
+ {
2854
+ name: "value",
2855
+ type: "string",
2856
+ internalType: "string"
2857
+ }
2858
+ ]
2859
+ },
2860
+ {
2861
+ name: "restartPolicy",
2862
+ type: "string",
2863
+ internalType: "string"
2864
+ }
2865
+ ]
2866
+ }
2867
+ ]
2868
+ }
2869
+ ],
2870
+ outputs: [
2871
+ {
2872
+ name: "app",
2873
+ type: "address",
2874
+ internalType: "contractIApp"
2875
+ }
2876
+ ],
2877
+ stateMutability: "nonpayable"
2878
+ },
2879
+ {
2880
+ type: "function",
2881
+ name: "createAppWithIsolatedBilling",
2882
+ inputs: [
2883
+ {
2884
+ name: "salt",
2885
+ type: "bytes32",
2886
+ internalType: "bytes32"
2887
+ },
2888
+ {
2889
+ name: "release",
2890
+ type: "tuple",
2891
+ internalType: "structIAppController.Release",
2892
+ components: [
2893
+ {
2894
+ name: "rmsRelease",
2895
+ type: "tuple",
2896
+ internalType: "structIReleaseManagerTypes.Release",
2897
+ components: [
2898
+ {
2899
+ name: "artifacts",
2900
+ type: "tuple[]",
2901
+ internalType: "structIReleaseManagerTypes.Artifact[]",
2902
+ components: [
2903
+ {
2904
+ name: "digest",
2905
+ type: "bytes32",
2906
+ internalType: "bytes32"
2907
+ },
2908
+ {
2909
+ name: "registry",
2910
+ type: "string",
2911
+ internalType: "string"
2912
+ }
2913
+ ]
2914
+ },
2915
+ {
2916
+ name: "upgradeByTime",
2917
+ type: "uint32",
2918
+ internalType: "uint32"
2919
+ }
2920
+ ]
2921
+ },
2922
+ {
2923
+ name: "publicEnv",
2924
+ type: "bytes",
2925
+ internalType: "bytes"
2926
+ },
2927
+ {
2928
+ name: "encryptedEnv",
2929
+ type: "bytes",
2930
+ internalType: "bytes"
2931
+ },
2932
+ {
2933
+ name: "containerPolicy",
2934
+ type: "tuple",
2935
+ internalType: "structIAppController.ContainerPolicy",
2936
+ components: [
2937
+ {
2938
+ name: "args",
2939
+ type: "string[]",
2940
+ internalType: "string[]"
2941
+ },
2942
+ {
2943
+ name: "cmdOverride",
2944
+ type: "string[]",
2945
+ internalType: "string[]"
2946
+ },
2947
+ {
2948
+ name: "env",
2949
+ type: "tuple[]",
2950
+ internalType: "structIAppController.EnvVar[]",
2951
+ components: [
2952
+ {
2953
+ name: "key",
2954
+ type: "string",
2955
+ internalType: "string"
2956
+ },
2957
+ {
2958
+ name: "value",
2959
+ type: "string",
2960
+ internalType: "string"
2961
+ }
2962
+ ]
2963
+ },
2964
+ {
2965
+ name: "envOverride",
2966
+ type: "tuple[]",
2967
+ internalType: "structIAppController.EnvVar[]",
2968
+ components: [
2969
+ {
2970
+ name: "key",
2971
+ type: "string",
2972
+ internalType: "string"
2973
+ },
2974
+ {
2975
+ name: "value",
2976
+ type: "string",
2977
+ internalType: "string"
2978
+ }
2979
+ ]
2980
+ },
2981
+ {
2982
+ name: "restartPolicy",
2983
+ type: "string",
2984
+ internalType: "string"
2985
+ }
2986
+ ]
2987
+ }
2988
+ ]
2989
+ }
2990
+ ],
2991
+ outputs: [
2992
+ {
2993
+ name: "app",
2994
+ type: "address",
2995
+ internalType: "contractIApp"
2996
+ }
2997
+ ],
2998
+ stateMutability: "nonpayable"
2999
+ },
3000
+ {
3001
+ type: "function",
3002
+ name: "createEmptyApp",
3003
+ inputs: [
3004
+ {
3005
+ name: "salt",
3006
+ type: "bytes32",
3007
+ internalType: "bytes32"
3008
+ }
3009
+ ],
3010
+ outputs: [
3011
+ {
3012
+ name: "app",
3013
+ type: "address",
3014
+ internalType: "contractIApp"
3015
+ }
3016
+ ],
3017
+ stateMutability: "nonpayable"
3018
+ },
3019
+ {
3020
+ type: "function",
3021
+ name: "createEmptyAppWithIsolatedBilling",
3022
+ inputs: [
3023
+ {
3024
+ name: "salt",
3025
+ type: "bytes32",
3026
+ internalType: "bytes32"
3027
+ }
3028
+ ],
3029
+ outputs: [
3030
+ {
3031
+ name: "app",
3032
+ type: "address",
3033
+ internalType: "contractIApp"
3034
+ }
3035
+ ],
3036
+ stateMutability: "nonpayable"
3037
+ },
3038
+ {
3039
+ type: "function",
3040
+ name: "domainSeparator",
3041
+ inputs: [],
3042
+ outputs: [
3043
+ {
3044
+ name: "",
3045
+ type: "bytes32",
3046
+ internalType: "bytes32"
3047
+ }
3048
+ ],
3049
+ stateMutability: "view"
3050
+ },
3051
+ {
3052
+ type: "function",
3053
+ name: "getActiveAppCount",
3054
+ inputs: [
3055
+ {
3056
+ name: "user",
3057
+ type: "address",
3058
+ internalType: "address"
3059
+ }
3060
+ ],
3061
+ outputs: [
3062
+ {
3063
+ name: "",
3064
+ type: "uint32",
3065
+ internalType: "uint32"
3066
+ }
3067
+ ],
3068
+ stateMutability: "view"
3069
+ },
3070
+ {
3071
+ type: "function",
3072
+ name: "getAppCreator",
3073
+ inputs: [
3074
+ {
3075
+ name: "app",
3076
+ type: "address",
3077
+ internalType: "contractIApp"
3078
+ }
3079
+ ],
3080
+ outputs: [
3081
+ {
3082
+ name: "",
3083
+ type: "address",
3084
+ internalType: "address"
3085
+ }
3086
+ ],
3087
+ stateMutability: "view"
3088
+ },
3089
+ {
3090
+ type: "function",
3091
+ name: "getAppLatestReleaseBlockNumber",
3092
+ inputs: [
3093
+ {
3094
+ name: "app",
3095
+ type: "address",
3096
+ internalType: "contractIApp"
3097
+ }
3098
+ ],
3099
+ outputs: [
3100
+ {
3101
+ name: "",
3102
+ type: "uint32",
3103
+ internalType: "uint32"
3104
+ }
3105
+ ],
3106
+ stateMutability: "view"
3107
+ },
3108
+ {
3109
+ type: "function",
3110
+ name: "getAppOperatorSetId",
3111
+ inputs: [
3112
+ {
3113
+ name: "app",
3114
+ type: "address",
3115
+ internalType: "contractIApp"
3116
+ }
3117
+ ],
3118
+ outputs: [
3119
+ {
3120
+ name: "",
3121
+ type: "uint32",
3122
+ internalType: "uint32"
3123
+ }
3124
+ ],
3125
+ stateMutability: "view"
3126
+ },
3127
+ {
3128
+ type: "function",
3129
+ name: "getAppPendingReleaseBlockNumber",
3130
+ inputs: [
3131
+ {
3132
+ name: "app",
3133
+ type: "address",
3134
+ internalType: "contractIApp"
3135
+ }
3136
+ ],
3137
+ outputs: [
3138
+ {
3139
+ name: "",
3140
+ type: "uint32",
3141
+ internalType: "uint32"
3142
+ }
3143
+ ],
3144
+ stateMutability: "view"
3145
+ },
3146
+ {
3147
+ type: "function",
3148
+ name: "getAppStatus",
3149
+ inputs: [
3150
+ {
3151
+ name: "app",
3152
+ type: "address",
3153
+ internalType: "contractIApp"
3154
+ }
3155
+ ],
3156
+ outputs: [
3157
+ {
3158
+ name: "",
3159
+ type: "uint8",
3160
+ internalType: "enumIAppController.AppStatus"
3161
+ }
3162
+ ],
3163
+ stateMutability: "view"
3164
+ },
3165
+ {
3166
+ type: "function",
3167
+ name: "getApps",
3168
+ inputs: [
3169
+ {
3170
+ name: "offset",
3171
+ type: "uint256",
3172
+ internalType: "uint256"
3173
+ },
3174
+ {
3175
+ name: "limit",
3176
+ type: "uint256",
3177
+ internalType: "uint256"
3178
+ }
3179
+ ],
3180
+ outputs: [
3181
+ {
3182
+ name: "apps",
3183
+ type: "address[]",
3184
+ internalType: "contractIApp[]"
3185
+ },
3186
+ {
3187
+ name: "appConfigsMem",
3188
+ type: "tuple[]",
3189
+ internalType: "structIAppController.AppConfig[]",
3190
+ components: [
3191
+ {
3192
+ name: "creator",
3193
+ type: "address",
3194
+ internalType: "address"
3195
+ },
3196
+ {
3197
+ name: "operatorSetId",
3198
+ type: "uint32",
3199
+ internalType: "uint32"
3200
+ },
3201
+ {
3202
+ name: "latestReleaseBlockNumber",
3203
+ type: "uint32",
3204
+ internalType: "uint32"
3205
+ },
3206
+ {
3207
+ name: "pendingReleaseBlockNumber",
3208
+ type: "uint32",
3209
+ internalType: "uint32"
3210
+ },
3211
+ {
3212
+ name: "status",
3213
+ type: "uint8",
3214
+ internalType: "enumIAppController.AppStatus"
3215
+ }
3216
+ ]
3217
+ }
3218
+ ],
3219
+ stateMutability: "view"
3220
+ },
3221
+ {
3222
+ type: "function",
3223
+ name: "getAppsByBillingAccount",
3224
+ inputs: [
3225
+ {
3226
+ name: "account",
3227
+ type: "address",
3228
+ internalType: "address"
3229
+ },
3230
+ {
3231
+ name: "offset",
3232
+ type: "uint256",
3233
+ internalType: "uint256"
3234
+ },
3235
+ {
3236
+ name: "limit",
3237
+ type: "uint256",
3238
+ internalType: "uint256"
3239
+ }
3240
+ ],
3241
+ outputs: [
3242
+ {
3243
+ name: "apps",
3244
+ type: "address[]",
3245
+ internalType: "contractIApp[]"
3246
+ },
3247
+ {
3248
+ name: "appConfigsMem",
3249
+ type: "tuple[]",
3250
+ internalType: "structIAppController.AppConfig[]",
3251
+ components: [
3252
+ {
3253
+ name: "creator",
3254
+ type: "address",
3255
+ internalType: "address"
3256
+ },
3257
+ {
3258
+ name: "operatorSetId",
3259
+ type: "uint32",
3260
+ internalType: "uint32"
3261
+ },
3262
+ {
3263
+ name: "latestReleaseBlockNumber",
3264
+ type: "uint32",
3265
+ internalType: "uint32"
3266
+ },
3267
+ {
3268
+ name: "pendingReleaseBlockNumber",
3269
+ type: "uint32",
3270
+ internalType: "uint32"
3271
+ },
3272
+ {
3273
+ name: "status",
3274
+ type: "uint8",
3275
+ internalType: "enumIAppController.AppStatus"
3276
+ }
3277
+ ]
3278
+ }
3279
+ ],
3280
+ stateMutability: "view"
3281
+ },
3282
+ {
3283
+ type: "function",
3284
+ name: "getAppsByCreator",
3285
+ inputs: [
3286
+ {
3287
+ name: "creator",
3288
+ type: "address",
3289
+ internalType: "address"
3290
+ },
3291
+ {
3292
+ name: "offset",
3293
+ type: "uint256",
3294
+ internalType: "uint256"
3295
+ },
3296
+ {
3297
+ name: "limit",
3298
+ type: "uint256",
3299
+ internalType: "uint256"
3300
+ }
3301
+ ],
3302
+ outputs: [
3303
+ {
3304
+ name: "apps",
3305
+ type: "address[]",
3306
+ internalType: "contractIApp[]"
3307
+ },
3308
+ {
3309
+ name: "appConfigsMem",
3310
+ type: "tuple[]",
3311
+ internalType: "structIAppController.AppConfig[]",
3312
+ components: [
3313
+ {
3314
+ name: "creator",
3315
+ type: "address",
3316
+ internalType: "address"
3317
+ },
3318
+ {
3319
+ name: "operatorSetId",
3320
+ type: "uint32",
3321
+ internalType: "uint32"
3322
+ },
3323
+ {
3324
+ name: "latestReleaseBlockNumber",
3325
+ type: "uint32",
3326
+ internalType: "uint32"
3327
+ },
3328
+ {
3329
+ name: "pendingReleaseBlockNumber",
3330
+ type: "uint32",
3331
+ internalType: "uint32"
3332
+ },
3333
+ {
3334
+ name: "status",
3335
+ type: "uint8",
3336
+ internalType: "enumIAppController.AppStatus"
3337
+ }
3338
+ ]
3339
+ }
3340
+ ],
3341
+ stateMutability: "view"
3342
+ },
3343
+ {
3344
+ type: "function",
3345
+ name: "getAppsByDeveloper",
3346
+ inputs: [
3347
+ {
3348
+ name: "developer",
3349
+ type: "address",
3350
+ internalType: "address"
3351
+ },
3352
+ {
3353
+ name: "offset",
3354
+ type: "uint256",
3355
+ internalType: "uint256"
3356
+ },
3357
+ {
3358
+ name: "limit",
3359
+ type: "uint256",
3360
+ internalType: "uint256"
3361
+ }
3362
+ ],
3363
+ outputs: [
3364
+ {
3365
+ name: "apps",
3366
+ type: "address[]",
3367
+ internalType: "contractIApp[]"
3368
+ },
3369
+ {
3370
+ name: "appConfigsMem",
3371
+ type: "tuple[]",
3372
+ internalType: "structIAppController.AppConfig[]",
3373
+ components: [
3374
+ {
3375
+ name: "creator",
3376
+ type: "address",
3377
+ internalType: "address"
3378
+ },
3379
+ {
3380
+ name: "operatorSetId",
3381
+ type: "uint32",
3382
+ internalType: "uint32"
3383
+ },
3384
+ {
3385
+ name: "latestReleaseBlockNumber",
3386
+ type: "uint32",
3387
+ internalType: "uint32"
3388
+ },
3389
+ {
3390
+ name: "pendingReleaseBlockNumber",
3391
+ type: "uint32",
3392
+ internalType: "uint32"
3393
+ },
3394
+ {
3395
+ name: "status",
3396
+ type: "uint8",
3397
+ internalType: "enumIAppController.AppStatus"
3398
+ }
3399
+ ]
3400
+ }
3401
+ ],
3402
+ stateMutability: "view"
3403
+ },
3404
+ {
3405
+ type: "function",
3406
+ name: "getBillingAccount",
3407
+ inputs: [
3408
+ {
3409
+ name: "app",
3410
+ type: "address",
3411
+ internalType: "contractIApp"
3412
+ }
3413
+ ],
3414
+ outputs: [
3415
+ {
3416
+ name: "",
3417
+ type: "address",
3418
+ internalType: "address"
3419
+ }
3420
+ ],
3421
+ stateMutability: "view"
3422
+ },
3423
+ {
3424
+ type: "function",
3425
+ name: "getBillingType",
3426
+ inputs: [
3427
+ {
3428
+ name: "app",
3429
+ type: "address",
3430
+ internalType: "contractIApp"
3431
+ }
3432
+ ],
3433
+ outputs: [
3434
+ {
3435
+ name: "",
3436
+ type: "uint8",
3437
+ internalType: "enumIAppController.BillingType"
3438
+ }
3439
+ ],
3440
+ stateMutability: "view"
3441
+ },
3442
+ {
3443
+ type: "function",
3444
+ name: "getMaxActiveAppsPerUser",
3445
+ inputs: [
3446
+ {
3447
+ name: "user",
3448
+ type: "address",
3449
+ internalType: "address"
3450
+ }
3451
+ ],
3452
+ outputs: [
3453
+ {
3454
+ name: "",
3455
+ type: "uint32",
3456
+ internalType: "uint32"
3457
+ }
3458
+ ],
3459
+ stateMutability: "view"
3460
+ },
3461
+ {
3462
+ type: "function",
3463
+ name: "globalActiveAppCount",
3464
+ inputs: [],
3465
+ outputs: [
3466
+ {
3467
+ name: "",
3468
+ type: "uint32",
3469
+ internalType: "uint32"
3470
+ }
3471
+ ],
3472
+ stateMutability: "view"
3473
+ },
3474
+ {
3475
+ type: "function",
3476
+ name: "initialize",
3477
+ inputs: [
3478
+ {
3479
+ name: "admin",
3480
+ type: "address",
3481
+ internalType: "address"
3482
+ }
3483
+ ],
3484
+ outputs: [],
3485
+ stateMutability: "nonpayable"
3486
+ },
3487
+ {
3488
+ type: "function",
3489
+ name: "maxGlobalActiveApps",
3490
+ inputs: [],
3491
+ outputs: [
3492
+ {
3493
+ name: "",
3494
+ type: "uint32",
3495
+ internalType: "uint32"
3496
+ }
3497
+ ],
3498
+ stateMutability: "view"
3499
+ },
3500
+ {
3501
+ type: "function",
3502
+ name: "permissionController",
3503
+ inputs: [],
3504
+ outputs: [
3505
+ {
3506
+ name: "",
3507
+ type: "address",
3508
+ internalType: "contractIPermissionController"
3509
+ }
3510
+ ],
3511
+ stateMutability: "view"
3512
+ },
3513
+ {
3514
+ type: "function",
3515
+ name: "releaseManager",
3516
+ inputs: [],
3517
+ outputs: [
3518
+ {
3519
+ name: "",
3520
+ type: "address",
3521
+ internalType: "contractIReleaseManager"
3522
+ }
3523
+ ],
3524
+ stateMutability: "view"
3525
+ },
3526
+ {
3527
+ type: "function",
3528
+ name: "setMaxActiveAppsPerUser",
3529
+ inputs: [
3530
+ {
3531
+ name: "user",
3532
+ type: "address",
3533
+ internalType: "address"
3534
+ },
3535
+ {
3536
+ name: "limit",
3537
+ type: "uint32",
3538
+ internalType: "uint32"
3539
+ }
3540
+ ],
3541
+ outputs: [],
3542
+ stateMutability: "nonpayable"
3543
+ },
3544
+ {
3545
+ type: "function",
3546
+ name: "setMaxGlobalActiveApps",
3547
+ inputs: [
3548
+ {
3549
+ name: "limit",
3550
+ type: "uint32",
3551
+ internalType: "uint32"
3552
+ }
3553
+ ],
3554
+ outputs: [],
3555
+ stateMutability: "nonpayable"
3556
+ },
3557
+ {
3558
+ type: "function",
3559
+ name: "startApp",
3560
+ inputs: [
3561
+ {
3562
+ name: "app",
3563
+ type: "address",
3564
+ internalType: "contractIApp"
3565
+ }
3566
+ ],
3567
+ outputs: [],
3568
+ stateMutability: "nonpayable"
3569
+ },
3570
+ {
3571
+ type: "function",
3572
+ name: "stopApp",
3573
+ inputs: [
3574
+ {
3575
+ name: "app",
3576
+ type: "address",
3577
+ internalType: "contractIApp"
3578
+ }
3579
+ ],
3580
+ outputs: [],
3581
+ stateMutability: "nonpayable"
3582
+ },
3583
+ {
3584
+ type: "function",
3585
+ name: "suspend",
3586
+ inputs: [
3587
+ {
3588
+ name: "account",
3589
+ type: "address",
3590
+ internalType: "address"
3591
+ },
3592
+ {
3593
+ name: "apps",
3594
+ type: "address[]",
3595
+ internalType: "contractIApp[]"
3596
+ }
3597
+ ],
3598
+ outputs: [],
3599
+ stateMutability: "nonpayable"
3600
+ },
3601
+ {
3602
+ type: "function",
3603
+ name: "terminateApp",
3604
+ inputs: [
3605
+ {
3606
+ name: "app",
3607
+ type: "address",
3608
+ internalType: "contractIApp"
3609
+ }
3610
+ ],
3611
+ outputs: [],
3612
+ stateMutability: "nonpayable"
3613
+ },
3614
+ {
3615
+ type: "function",
3616
+ name: "terminateAppByAdmin",
3617
+ inputs: [
3618
+ {
3619
+ name: "app",
3620
+ type: "address",
3621
+ internalType: "contractIApp"
3622
+ }
3623
+ ],
3624
+ outputs: [],
3625
+ stateMutability: "nonpayable"
3626
+ },
3627
+ {
3628
+ type: "function",
3629
+ name: "updateAppMetadataURI",
3630
+ inputs: [
3631
+ {
3632
+ name: "app",
3633
+ type: "address",
3634
+ internalType: "contractIApp"
3635
+ },
3636
+ {
3637
+ name: "metadataURI",
3638
+ type: "string",
3639
+ internalType: "string"
3640
+ }
3641
+ ],
3642
+ outputs: [],
3643
+ stateMutability: "nonpayable"
3644
+ },
3645
+ {
3646
+ type: "function",
3647
+ name: "upgradeApp",
3648
+ inputs: [
3649
+ {
3650
+ name: "app",
3651
+ type: "address",
3652
+ internalType: "contractIApp"
3653
+ },
3654
+ {
3655
+ name: "release",
3656
+ type: "tuple",
3657
+ internalType: "structIAppController.Release",
3658
+ components: [
3659
+ {
3660
+ name: "rmsRelease",
3661
+ type: "tuple",
3662
+ internalType: "structIReleaseManagerTypes.Release",
3663
+ components: [
3664
+ {
3665
+ name: "artifacts",
3666
+ type: "tuple[]",
3667
+ internalType: "structIReleaseManagerTypes.Artifact[]",
3668
+ components: [
3669
+ {
3670
+ name: "digest",
3671
+ type: "bytes32",
3672
+ internalType: "bytes32"
3673
+ },
3674
+ {
3675
+ name: "registry",
3676
+ type: "string",
3677
+ internalType: "string"
3678
+ }
3679
+ ]
3680
+ },
3681
+ {
3682
+ name: "upgradeByTime",
3683
+ type: "uint32",
3684
+ internalType: "uint32"
3685
+ }
3686
+ ]
3687
+ },
3688
+ {
3689
+ name: "publicEnv",
3690
+ type: "bytes",
3691
+ internalType: "bytes"
3692
+ },
3693
+ {
3694
+ name: "encryptedEnv",
3695
+ type: "bytes",
3696
+ internalType: "bytes"
3697
+ },
3698
+ {
3699
+ name: "containerPolicy",
3700
+ type: "tuple",
3701
+ internalType: "structIAppController.ContainerPolicy",
3702
+ components: [
3703
+ {
3704
+ name: "args",
3705
+ type: "string[]",
3706
+ internalType: "string[]"
3707
+ },
3708
+ {
3709
+ name: "cmdOverride",
3710
+ type: "string[]",
3711
+ internalType: "string[]"
3712
+ },
3713
+ {
3714
+ name: "env",
3715
+ type: "tuple[]",
3716
+ internalType: "structIAppController.EnvVar[]",
3717
+ components: [
3718
+ {
3719
+ name: "key",
3720
+ type: "string",
3721
+ internalType: "string"
3722
+ },
3723
+ {
3724
+ name: "value",
3725
+ type: "string",
3726
+ internalType: "string"
3727
+ }
3728
+ ]
3729
+ },
3730
+ {
3731
+ name: "envOverride",
3732
+ type: "tuple[]",
3733
+ internalType: "structIAppController.EnvVar[]",
3734
+ components: [
3735
+ {
3736
+ name: "key",
3737
+ type: "string",
3738
+ internalType: "string"
3739
+ },
3740
+ {
3741
+ name: "value",
3742
+ type: "string",
3743
+ internalType: "string"
3744
+ }
3745
+ ]
3746
+ },
3747
+ {
3748
+ name: "restartPolicy",
3749
+ type: "string",
3750
+ internalType: "string"
3751
+ }
3752
+ ]
3753
+ }
3754
+ ]
3755
+ }
3756
+ ],
3757
+ outputs: [
3758
+ {
3759
+ name: "",
3760
+ type: "uint256",
3761
+ internalType: "uint256"
3762
+ }
3763
+ ],
3764
+ stateMutability: "nonpayable"
3765
+ },
3766
+ {
3767
+ type: "function",
3768
+ name: "version",
3769
+ inputs: [],
3770
+ outputs: [
3771
+ {
3772
+ name: "",
3773
+ type: "string",
3774
+ internalType: "string"
3775
+ }
3776
+ ],
3777
+ stateMutability: "view"
3778
+ },
3779
+ {
3780
+ type: "event",
3781
+ name: "AppCreated",
3782
+ inputs: [
3783
+ {
3784
+ name: "creator",
3785
+ type: "address",
3786
+ indexed: true,
3787
+ internalType: "address"
3788
+ },
3789
+ {
3790
+ name: "app",
3791
+ type: "address",
3792
+ indexed: true,
3793
+ internalType: "contractIApp"
3794
+ },
3795
+ {
3796
+ name: "operatorSetId",
3797
+ type: "uint32",
3798
+ indexed: false,
3799
+ internalType: "uint32"
3800
+ }
3801
+ ],
3802
+ anonymous: false
3803
+ },
3804
+ {
3805
+ type: "event",
3806
+ name: "AppMetadataURIUpdated",
3807
+ inputs: [
3808
+ {
3809
+ name: "app",
3810
+ type: "address",
3811
+ indexed: true,
3812
+ internalType: "contractIApp"
3813
+ },
3814
+ {
3815
+ name: "metadataURI",
3816
+ type: "string",
3817
+ indexed: false,
3818
+ internalType: "string"
3819
+ }
3820
+ ],
3821
+ anonymous: false
3822
+ },
3823
+ {
3824
+ type: "event",
3825
+ name: "AppStarted",
3826
+ inputs: [
3827
+ {
3828
+ name: "app",
3829
+ type: "address",
3830
+ indexed: true,
3831
+ internalType: "contractIApp"
3832
+ }
3833
+ ],
3834
+ anonymous: false
3835
+ },
3836
+ {
3837
+ type: "event",
3838
+ name: "AppStopped",
3839
+ inputs: [
3840
+ {
3841
+ name: "app",
3842
+ type: "address",
3843
+ indexed: true,
3844
+ internalType: "contractIApp"
3845
+ }
3846
+ ],
3847
+ anonymous: false
3848
+ },
3849
+ {
3850
+ type: "event",
3851
+ name: "AppSuspended",
3852
+ inputs: [
3853
+ {
3854
+ name: "app",
3855
+ type: "address",
3856
+ indexed: true,
3857
+ internalType: "contractIApp"
3858
+ }
3859
+ ],
3860
+ anonymous: false
3861
+ },
3862
+ {
3863
+ type: "event",
3864
+ name: "AppTerminated",
3865
+ inputs: [
3866
+ {
3867
+ name: "app",
3868
+ type: "address",
3869
+ indexed: true,
3870
+ internalType: "contractIApp"
3871
+ }
3872
+ ],
3873
+ anonymous: false
3874
+ },
3875
+ {
3876
+ type: "event",
3877
+ name: "AppTerminatedByAdmin",
3878
+ inputs: [
3879
+ {
3880
+ name: "app",
3881
+ type: "address",
3882
+ indexed: true,
3883
+ internalType: "contractIApp"
3884
+ }
3885
+ ],
3886
+ anonymous: false
3887
+ },
3888
+ {
3889
+ type: "event",
3890
+ name: "AppUpgraded",
3891
+ inputs: [
3892
+ {
3893
+ name: "app",
3894
+ type: "address",
3895
+ indexed: true,
3896
+ internalType: "contractIApp"
3897
+ },
3898
+ {
3899
+ name: "rmsReleaseId",
3900
+ type: "uint256",
3901
+ indexed: false,
3902
+ internalType: "uint256"
3903
+ },
3904
+ {
3905
+ name: "release",
3906
+ type: "tuple",
3907
+ indexed: false,
3908
+ internalType: "structIAppController.Release",
3909
+ components: [
3910
+ {
3911
+ name: "rmsRelease",
3912
+ type: "tuple",
3913
+ internalType: "structIReleaseManagerTypes.Release",
3914
+ components: [
3915
+ {
3916
+ name: "artifacts",
3917
+ type: "tuple[]",
3918
+ internalType: "structIReleaseManagerTypes.Artifact[]",
3919
+ components: [
3920
+ {
3921
+ name: "digest",
3922
+ type: "bytes32",
3923
+ internalType: "bytes32"
3924
+ },
3925
+ {
3926
+ name: "registry",
3927
+ type: "string",
3928
+ internalType: "string"
3929
+ }
3930
+ ]
3931
+ },
3932
+ {
3933
+ name: "upgradeByTime",
3934
+ type: "uint32",
3935
+ internalType: "uint32"
3936
+ }
3937
+ ]
3938
+ },
3939
+ {
3940
+ name: "publicEnv",
3941
+ type: "bytes",
3942
+ internalType: "bytes"
3943
+ },
3944
+ {
3945
+ name: "encryptedEnv",
3946
+ type: "bytes",
3947
+ internalType: "bytes"
3948
+ },
3949
+ {
3950
+ name: "containerPolicy",
3951
+ type: "tuple",
3952
+ internalType: "structIAppController.ContainerPolicy",
3953
+ components: [
3954
+ {
3955
+ name: "args",
3956
+ type: "string[]",
3957
+ internalType: "string[]"
3958
+ },
3959
+ {
3960
+ name: "cmdOverride",
3961
+ type: "string[]",
3962
+ internalType: "string[]"
3963
+ },
3964
+ {
3965
+ name: "env",
3966
+ type: "tuple[]",
3967
+ internalType: "structIAppController.EnvVar[]",
3968
+ components: [
3969
+ {
3970
+ name: "key",
3971
+ type: "string",
3972
+ internalType: "string"
3973
+ },
3974
+ {
3975
+ name: "value",
3976
+ type: "string",
3977
+ internalType: "string"
3978
+ }
3979
+ ]
3980
+ },
3981
+ {
3982
+ name: "envOverride",
3983
+ type: "tuple[]",
3984
+ internalType: "structIAppController.EnvVar[]",
3985
+ components: [
3986
+ {
3987
+ name: "key",
3988
+ type: "string",
3989
+ internalType: "string"
3990
+ },
3991
+ {
3992
+ name: "value",
3993
+ type: "string",
3994
+ internalType: "string"
3995
+ }
3996
+ ]
3997
+ },
3998
+ {
3999
+ name: "restartPolicy",
4000
+ type: "string",
4001
+ internalType: "string"
4002
+ }
4003
+ ]
4004
+ }
4005
+ ]
4006
+ }
4007
+ ],
4008
+ anonymous: false
4009
+ },
4010
+ {
4011
+ type: "event",
4012
+ name: "GlobalMaxActiveAppsSet",
4013
+ inputs: [
4014
+ {
4015
+ name: "limit",
4016
+ type: "uint32",
4017
+ indexed: false,
4018
+ internalType: "uint32"
4019
+ }
4020
+ ],
4021
+ anonymous: false
4022
+ },
4023
+ {
4024
+ type: "event",
4025
+ name: "Initialized",
4026
+ inputs: [
4027
+ {
4028
+ name: "version",
4029
+ type: "uint8",
4030
+ indexed: false,
4031
+ internalType: "uint8"
4032
+ }
4033
+ ],
4034
+ anonymous: false
4035
+ },
4036
+ {
4037
+ type: "event",
4038
+ name: "MaxActiveAppsSet",
4039
+ inputs: [
4040
+ {
4041
+ name: "user",
4042
+ type: "address",
4043
+ indexed: true,
4044
+ internalType: "address"
4045
+ },
4046
+ {
4047
+ name: "limit",
4048
+ type: "uint32",
4049
+ indexed: false,
4050
+ internalType: "uint32"
4051
+ }
4052
+ ],
4053
+ anonymous: false
4054
+ },
4055
+ {
4056
+ type: "event",
4057
+ name: "UpgradeConfirmed",
4058
+ inputs: [
4059
+ {
4060
+ name: "app",
4061
+ type: "address",
4062
+ indexed: true,
4063
+ internalType: "contractIApp"
4064
+ },
4065
+ {
4066
+ name: "pendingReleaseBlockNumber",
4067
+ type: "uint32",
4068
+ indexed: false,
4069
+ internalType: "uint32"
4070
+ }
4071
+ ],
4072
+ anonymous: false
4073
+ },
4074
+ {
4075
+ type: "error",
4076
+ name: "AccountHasActiveApps",
4077
+ inputs: []
4078
+ },
4079
+ {
4080
+ type: "error",
4081
+ name: "AppAlreadyExists",
4082
+ inputs: []
4083
+ },
4084
+ {
4085
+ type: "error",
4086
+ name: "AppDoesNotExist",
4087
+ inputs: []
4088
+ },
4089
+ {
4090
+ type: "error",
4091
+ name: "GlobalMaxActiveAppsExceeded",
4092
+ inputs: []
4093
+ },
4094
+ {
4095
+ type: "error",
4096
+ name: "InvalidAppStatus",
4097
+ inputs: []
4098
+ },
4099
+ {
4100
+ type: "error",
4101
+ name: "InvalidPermissions",
4102
+ inputs: []
4103
+ },
4104
+ {
4105
+ type: "error",
4106
+ name: "InvalidReleaseMetadataURI",
4107
+ inputs: []
4108
+ },
4109
+ {
4110
+ type: "error",
4111
+ name: "InvalidShortString",
4112
+ inputs: []
4113
+ },
4114
+ {
4115
+ type: "error",
4116
+ name: "InvalidSignature",
4117
+ inputs: []
4118
+ },
4119
+ {
4120
+ type: "error",
4121
+ name: "MaxActiveAppsExceeded",
4122
+ inputs: []
4123
+ },
4124
+ {
4125
+ type: "error",
4126
+ name: "MoreThanOneArtifact",
4127
+ inputs: []
4128
+ },
4129
+ {
4130
+ type: "error",
4131
+ name: "NoPendingUpgrade",
4132
+ inputs: []
4133
+ },
4134
+ {
4135
+ type: "error",
4136
+ name: "SignatureExpired",
4137
+ inputs: []
4138
+ },
4139
+ {
4140
+ type: "error",
4141
+ name: "StringTooLong",
4142
+ inputs: [
4143
+ {
4144
+ name: "str",
4145
+ type: "string",
4146
+ internalType: "string"
4147
+ }
4148
+ ]
3055
4149
  }
3056
- return hash;
3057
- }
3058
-
3059
- // src/client/common/contract/caller.ts
3060
- var import_viem3 = require("viem");
3061
-
3062
- // src/client/common/utils/helpers.ts
3063
- var import_viem2 = require("viem");
3064
- var import_chains2 = require("viem/chains");
3065
- var import_accounts = require("viem/accounts");
3066
- function getChainFromID(chainID, fallback2 = import_chains2.sepolia) {
3067
- const id = Number(chainID);
3068
- return (0, import_viem2.extractChain)({ chains: SUPPORTED_CHAINS, id }) || fallback2;
3069
- }
3070
- function addHexPrefix(value) {
3071
- return value.startsWith("0x") ? value : `0x${value}`;
3072
- }
3073
- function stripHexPrefix(value) {
3074
- return value.startsWith("0x") ? value.slice(2) : value;
3075
- }
4150
+ ];
3076
4151
 
3077
- // src/client/common/abis/AppController.json
3078
- var AppController_default = [
4152
+ // src/client/common/abis/AppController.v1_4.json
4153
+ var AppController_v1_4_default = [
3079
4154
  {
3080
4155
  type: "constructor",
3081
4156
  inputs: [
@@ -4759,6 +5834,38 @@ var PermissionController_default = [
4759
5834
  ];
4760
5835
 
4761
5836
  // src/client/common/contract/caller.ts
5837
+ function appControllerAbiFor(environmentConfig) {
5838
+ return environmentConfig.releaseAbiVersion === "v1.4" ? AppController_v1_4_default : AppController_default;
5839
+ }
5840
+ function supportsContainerPolicy(environmentConfig) {
5841
+ return environmentConfig.releaseAbiVersion !== "v1.4";
5842
+ }
5843
+ function containerPolicyForViem(policy = EMPTY_CONTAINER_POLICY) {
5844
+ return {
5845
+ args: policy.args,
5846
+ cmdOverride: policy.cmdOverride,
5847
+ env: policy.env.map((e) => ({ key: e.key, value: e.value })),
5848
+ envOverride: policy.envOverride.map((e) => ({ key: e.key, value: e.value })),
5849
+ restartPolicy: policy.restartPolicy
5850
+ };
5851
+ }
5852
+ function releaseForViem(release, environmentConfig) {
5853
+ const base = {
5854
+ rmsRelease: {
5855
+ artifacts: release.rmsRelease.artifacts.map((artifact) => ({
5856
+ digest: `0x${(0, import_viem3.bytesToHex)(artifact.digest).slice(2).padStart(64, "0")}`,
5857
+ registry: artifact.registry
5858
+ })),
5859
+ upgradeByTime: release.rmsRelease.upgradeByTime
5860
+ },
5861
+ publicEnv: (0, import_viem3.bytesToHex)(release.publicEnv),
5862
+ encryptedEnv: (0, import_viem3.bytesToHex)(release.encryptedEnv)
5863
+ };
5864
+ if (!supportsContainerPolicy(environmentConfig)) {
5865
+ return base;
5866
+ }
5867
+ return { ...base, containerPolicy: containerPolicyForViem(release.containerPolicy) };
5868
+ }
4762
5869
  function formatETH(wei) {
4763
5870
  const eth = Number(wei) / 1e18;
4764
5871
  const costStr = eth.toFixed(6);
@@ -4775,7 +5882,7 @@ async function calculateAppID(options) {
4775
5882
  const saltHex = `0x${paddedSaltHex}`;
4776
5883
  const appID = await publicClient.readContract({
4777
5884
  address: environmentConfig.appControllerAddress,
4778
- abi: AppController_default,
5885
+ abi: appControllerAbiFor(environmentConfig),
4779
5886
  functionName: "calculateAppId",
4780
5887
  args: [ownerAddress, saltHex]
4781
5888
  });
@@ -4799,22 +5906,12 @@ async function prepareDeployBatch(options, logger = noopLogger) {
4799
5906
  const saltHexString = (0, import_viem3.bytesToHex)(salt).slice(2);
4800
5907
  const paddedSaltHex = saltHexString.padStart(64, "0");
4801
5908
  const saltHex = `0x${paddedSaltHex}`;
4802
- const releaseForViem = {
4803
- rmsRelease: {
4804
- artifacts: release.rmsRelease.artifacts.map((artifact) => ({
4805
- digest: `0x${(0, import_viem3.bytesToHex)(artifact.digest).slice(2).padStart(64, "0")}`,
4806
- registry: artifact.registry
4807
- })),
4808
- upgradeByTime: release.rmsRelease.upgradeByTime
4809
- },
4810
- publicEnv: (0, import_viem3.bytesToHex)(release.publicEnv),
4811
- encryptedEnv: (0, import_viem3.bytesToHex)(release.encryptedEnv)
4812
- };
5909
+ const release_ = releaseForViem(release, environmentConfig);
4813
5910
  const functionName = options.billTo === "app" ? "createAppWithIsolatedBilling" : "createApp";
4814
5911
  const createData = (0, import_viem3.encodeFunctionData)({
4815
- abi: AppController_default,
5912
+ abi: appControllerAbiFor(environmentConfig),
4816
5913
  functionName,
4817
- args: [saltHex, releaseForViem]
5914
+ args: [saltHex, release_]
4818
5915
  });
4819
5916
  const acceptAdminData = (0, import_viem3.encodeFunctionData)({
4820
5917
  abi: PermissionController_default,
@@ -4902,21 +5999,11 @@ async function prepareUpgradeBatch(options) {
4902
5999
  publicLogs,
4903
6000
  needsPermissionChange
4904
6001
  } = options;
4905
- const releaseForViem = {
4906
- rmsRelease: {
4907
- artifacts: release.rmsRelease.artifacts.map((artifact) => ({
4908
- digest: `0x${(0, import_viem3.bytesToHex)(artifact.digest).slice(2).padStart(64, "0")}`,
4909
- registry: artifact.registry
4910
- })),
4911
- upgradeByTime: release.rmsRelease.upgradeByTime
4912
- },
4913
- publicEnv: (0, import_viem3.bytesToHex)(release.publicEnv),
4914
- encryptedEnv: (0, import_viem3.bytesToHex)(release.encryptedEnv)
4915
- };
6002
+ const release_ = releaseForViem(release, environmentConfig);
4916
6003
  const upgradeData = (0, import_viem3.encodeFunctionData)({
4917
- abi: AppController_default,
6004
+ abi: appControllerAbiFor(environmentConfig),
4918
6005
  functionName: "upgradeApp",
4919
- args: [appID, releaseForViem]
6006
+ args: [appID, release_]
4920
6007
  });
4921
6008
  const executions = [
4922
6009
  {
@@ -5050,7 +6137,7 @@ ${pendingMessage}`);
5050
6137
  if (callError.data) {
5051
6138
  try {
5052
6139
  const decoded = (0, import_viem3.decodeErrorResult)({
5053
- abi: AppController_default,
6140
+ abi: appControllerAbiFor(environmentConfig),
5054
6141
  data: callError.data
5055
6142
  });
5056
6143
  const formattedError = formatAppControllerError(decoded);
@@ -5103,7 +6190,7 @@ function formatAppControllerError(decoded) {
5103
6190
  async function getActiveAppCount(publicClient, environmentConfig, user) {
5104
6191
  const count = await publicClient.readContract({
5105
6192
  address: environmentConfig.appControllerAddress,
5106
- abi: AppController_default,
6193
+ abi: appControllerAbiFor(environmentConfig),
5107
6194
  functionName: "getActiveAppCount",
5108
6195
  args: [user]
5109
6196
  });
@@ -5112,7 +6199,7 @@ async function getActiveAppCount(publicClient, environmentConfig, user) {
5112
6199
  async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
5113
6200
  const quota = await publicClient.readContract({
5114
6201
  address: environmentConfig.appControllerAddress,
5115
- abi: AppController_default,
6202
+ abi: appControllerAbiFor(environmentConfig),
5116
6203
  functionName: "getMaxActiveAppsPerUser",
5117
6204
  args: [user]
5118
6205
  });
@@ -5121,7 +6208,7 @@ async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
5121
6208
  async function getBillingType(publicClient, environmentConfig, app) {
5122
6209
  const result = await publicClient.readContract({
5123
6210
  address: environmentConfig.appControllerAddress,
5124
- abi: AppController_default,
6211
+ abi: appControllerAbiFor(environmentConfig),
5125
6212
  functionName: "getBillingType",
5126
6213
  args: [app]
5127
6214
  });
@@ -5130,7 +6217,7 @@ async function getBillingType(publicClient, environmentConfig, app) {
5130
6217
  async function getAppsByBillingAccount(publicClient, environmentConfig, account, offset, limit) {
5131
6218
  const result = await publicClient.readContract({
5132
6219
  address: environmentConfig.appControllerAddress,
5133
- abi: AppController_default,
6220
+ abi: appControllerAbiFor(environmentConfig),
5134
6221
  functionName: "getAppsByBillingAccount",
5135
6222
  args: [account, offset, limit]
5136
6223
  });
@@ -5184,12 +6271,41 @@ async function undelegate(options, logger = noopLogger) {
5184
6271
  return hash;
5185
6272
  }
5186
6273
 
6274
+ // src/client/common/gas/insufficientGas.ts
6275
+ var import_viem4 = require("viem");
6276
+ var InsufficientGasError = class extends Error {
6277
+ constructor(args) {
6278
+ const requiredEth = (0, import_viem4.formatEther)(args.requiredWei);
6279
+ const availableEth = (0, import_viem4.formatEther)(args.availableWei);
6280
+ super(
6281
+ `Insufficient ETH for gas: wallet ${args.address} has ${availableEth} ETH but this transaction needs ~${requiredEth} ETH.
6282
+ Compute credits do not pay on-chain gas \u2014 fund the wallet with ETH and retry.`
6283
+ );
6284
+ this.name = "InsufficientGasError";
6285
+ this.address = args.address;
6286
+ this.requiredWei = args.requiredWei;
6287
+ this.availableWei = args.availableWei;
6288
+ this.requiredEth = requiredEth;
6289
+ this.availableEth = availableEth;
6290
+ }
6291
+ };
6292
+ async function assertSufficientGas(args) {
6293
+ const availableWei = await args.publicClient.getBalance({ address: args.address });
6294
+ if (availableWei < args.gasEstimate.maxCostWei) {
6295
+ throw new InsufficientGasError({
6296
+ address: args.address,
6297
+ requiredWei: args.gasEstimate.maxCostWei,
6298
+ availableWei
6299
+ });
6300
+ }
6301
+ }
6302
+
5187
6303
  // src/client/common/utils/userapi.ts
5188
6304
  var import_axios2 = __toESM(require("axios"), 1);
5189
6305
 
5190
6306
  // src/client/common/utils/auth.ts
5191
- var import_viem4 = require("viem");
5192
- var APP_CONTROLLER_ABI = (0, import_viem4.parseAbi)([
6307
+ var import_viem5 = require("viem");
6308
+ var APP_CONTROLLER_ABI = (0, import_viem5.parseAbi)([
5193
6309
  "function calculateApiPermissionDigestHash(bytes4 permission, uint256 expiry) view returns (bytes32)"
5194
6310
  ]);
5195
6311
  async function calculatePermissionSignature(options) {
@@ -5210,43 +6326,13 @@ async function calculatePermissionSignature(options) {
5210
6326
  });
5211
6327
  return { signature, digest };
5212
6328
  }
5213
- var generateBillingSigData = (product, expiry) => {
5214
- return {
5215
- domain: {
5216
- name: "EigenCloud Billing API",
5217
- version: "1"
5218
- },
5219
- types: {
5220
- BillingAuth: [
5221
- { name: "product", type: "string" },
5222
- { name: "expiry", type: "uint256" }
5223
- ]
5224
- },
5225
- primaryType: "BillingAuth",
5226
- message: {
5227
- product,
5228
- expiry
5229
- }
5230
- };
5231
- };
5232
- async function calculateBillingAuthSignature(options) {
5233
- const { walletClient, product, expiry } = options;
5234
- const account = walletClient.account;
5235
- if (!account) {
5236
- throw new Error("WalletClient must have an account attached");
5237
- }
5238
- const signature = await walletClient.signTypedData({
5239
- account,
5240
- ...generateBillingSigData(product, expiry)
5241
- });
5242
- return { signature, expiry };
5243
- }
5244
6329
 
5245
6330
  // src/client/common/utils/retry.ts
5246
6331
  var import_axios = __toESM(require("axios"), 1);
5247
6332
  var MAX_RETRIES = 5;
5248
6333
  var INITIAL_BACKOFF_MS = 1e3;
5249
6334
  var MAX_BACKOFF_MS = 3e4;
6335
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
5250
6336
  function sleep(ms) {
5251
6337
  return new Promise((resolve2) => setTimeout(resolve2, ms));
5252
6338
  }
@@ -5266,7 +6352,7 @@ async function requestWithRetry(config) {
5266
6352
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
5267
6353
  const res = await (0, import_axios.default)({ ...config, validateStatus: () => true });
5268
6354
  lastResponse = res;
5269
- if (res.status !== 429) {
6355
+ if (!RETRYABLE_STATUSES.has(res.status)) {
5270
6356
  return res;
5271
6357
  }
5272
6358
  if (attempt < MAX_RETRIES) {
@@ -5295,7 +6381,7 @@ var CanViewAppLogsPermission = "0x2fd3f2fe";
5295
6381
  var CanViewSensitiveAppInfoPermission = "0x0e67b22f";
5296
6382
  var CanUpdateAppProfilePermission = "0x036fef61";
5297
6383
  function getDefaultClientId() {
5298
- const version = true ? "1.0.0-devep8" : "0.0.0";
6384
+ const version = true ? "1.0.0" : "0.0.0";
5299
6385
  return `ecloud-sdk/v${version}`;
5300
6386
  }
5301
6387
  var UserApiClient = class {
@@ -5330,7 +6416,6 @@ var UserApiClient = class {
5330
6416
  status: app.app_status,
5331
6417
  ip: app.ip,
5332
6418
  machineType: app.machine_type,
5333
- hostname: app.hostname,
5334
6419
  profile: app.profile,
5335
6420
  metrics: app.metrics,
5336
6421
  evmAddresses,
@@ -5396,28 +6481,6 @@ var UserApiClient = class {
5396
6481
  status: app.app_status || app.App_Status || ""
5397
6482
  }));
5398
6483
  }
5399
- /**
5400
- * Get deployments for an app from the gRPC-gateway endpoint.
5401
- * Returns deployment records including upgrade_phase for tracking upgrade progress.
5402
- *
5403
- * Endpoint: GET /v1/apps/:appAddress/deployments
5404
- */
5405
- async getDeployments(appAddress) {
5406
- const endpoint = `${this.config.userApiServerURL}/v1/apps/${appAddress}/deployments`;
5407
- const response = await this.makeEIP712AuthenticatedRequest(endpoint);
5408
- const result = await response.json();
5409
- const deployments = result.deployments || [];
5410
- return deployments.map((dep) => ({
5411
- id: dep.id || "",
5412
- externalId: dep.external_id || dep.externalId || "",
5413
- endpoint: dep.endpoint || "",
5414
- releaseId: dep.release_id || dep.releaseId || "",
5415
- upgradePhase: dep.upgrade_phase || dep.upgradePhase || "",
5416
- replacesDeploymentId: dep.replaces_deployment_id || dep.replacesDeploymentId || "",
5417
- createdAt: dep.created_at || dep.createdAt || "",
5418
- updatedAt: dep.updated_at || dep.updatedAt || ""
5419
- }));
5420
- }
5421
6484
  /**
5422
6485
  * Upload app profile information with optional image
5423
6486
  *
@@ -5551,48 +6614,6 @@ Please check:
5551
6614
  "X-eigenx-expiry": expiry.toString()
5552
6615
  };
5553
6616
  }
5554
- /**
5555
- * Make an EIP-712 authenticated request to the gRPC-gateway endpoints.
5556
- * Uses the billing/compute auth pattern: Authorization + X-Account + X-Expiry headers.
5557
- */
5558
- async makeEIP712AuthenticatedRequest(url) {
5559
- const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
5560
- const { signature } = await calculateBillingAuthSignature({
5561
- walletClient: this.walletClient,
5562
- product: "compute",
5563
- expiry
5564
- });
5565
- const headers = {
5566
- Authorization: `Bearer ${signature}`,
5567
- "X-Account": this.address,
5568
- "X-Expiry": expiry.toString(),
5569
- "x-client-id": this.clientId
5570
- };
5571
- try {
5572
- const response = await requestWithRetry({
5573
- method: "GET",
5574
- url,
5575
- headers,
5576
- maxRedirects: 0,
5577
- withCredentials: true
5578
- });
5579
- const status = response.status;
5580
- if (status < 200 || status >= 300) {
5581
- const body = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
5582
- throw new Error(`gRPC-gateway request failed: ${status} - ${body}`);
5583
- }
5584
- return {
5585
- json: async () => response.data,
5586
- text: async () => typeof response.data === "string" ? response.data : JSON.stringify(response.data)
5587
- };
5588
- } catch (error) {
5589
- if (error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED") || error.message?.includes("ENOTFOUND") || error.cause) {
5590
- const cause = error.cause?.message || error.cause || error.message;
5591
- throw new Error(`Failed to connect to API at ${url}: ${cause}`);
5592
- }
5593
- throw error;
5594
- }
5595
- }
5596
6617
  // ==========================================================================
5597
6618
  // SIWE Session Management
5598
6619
  // ==========================================================================
@@ -5698,8 +6719,36 @@ function transformAppRelease(raw) {
5698
6719
 
5699
6720
  // src/client/common/contract/watcher.ts
5700
6721
  var WATCH_POLL_INTERVAL_SECONDS = 5;
6722
+ var WATCH_HEARTBEAT_INTERVAL_SECONDS = 30;
6723
+ var WATCH_DEFAULT_TIMEOUT_SECONDS = 10 * 60;
5701
6724
  var APP_STATUS_RUNNING = "Running";
5702
6725
  var APP_STATUS_FAILED = "Failed";
6726
+ var WatchTimeoutError = class extends Error {
6727
+ constructor(args) {
6728
+ super(
6729
+ args.message ?? `Timed out after ${args.elapsedSeconds}s waiting for app ${args.appId} (last status: ${args.lastStatus ?? "unknown"})`
6730
+ );
6731
+ this.name = "WatchTimeoutError";
6732
+ this.appId = args.appId;
6733
+ this.elapsedSeconds = args.elapsedSeconds;
6734
+ this.lastStatus = args.lastStatus;
6735
+ this.timeoutSeconds = args.timeoutSeconds;
6736
+ }
6737
+ };
6738
+ function resolveWatchTimeoutSeconds(explicit) {
6739
+ if (typeof explicit === "number" && Number.isFinite(explicit) && explicit > 0) {
6740
+ return Math.floor(explicit);
6741
+ }
6742
+ const raw = process.env.ECLOUD_WATCH_TIMEOUT_SECONDS;
6743
+ if (raw === void 0 || raw === "") {
6744
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6745
+ }
6746
+ const parsed = Number(raw);
6747
+ if (!Number.isFinite(parsed) || parsed <= 0) {
6748
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6749
+ }
6750
+ return Math.floor(parsed);
6751
+ }
5703
6752
  async function watchUntilRunning(options, logger) {
5704
6753
  const { walletClient, publicClient, environmentConfig, appId } = options;
5705
6754
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
@@ -5730,8 +6779,20 @@ async function watchUntilRunning(options, logger) {
5730
6779
  return false;
5731
6780
  };
5732
6781
  const startTime = Date.now();
6782
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
5733
6783
  let lastLoggedStatus;
6784
+ let lastHeartbeatAt = startTime;
5734
6785
  while (true) {
6786
+ const elapsedMs = Date.now() - startTime;
6787
+ const elapsed = Math.round(elapsedMs / 1e3);
6788
+ if (elapsed >= timeoutSeconds) {
6789
+ throw new WatchTimeoutError({
6790
+ appId,
6791
+ elapsedSeconds: elapsed,
6792
+ lastStatus: lastLoggedStatus,
6793
+ timeoutSeconds
6794
+ });
6795
+ }
5735
6796
  try {
5736
6797
  const info = await userApiClient.getInfos([appId], 1);
5737
6798
  if (info.length === 0) {
@@ -5741,62 +6802,35 @@ async function watchUntilRunning(options, logger) {
5741
6802
  const appInfo = info[0];
5742
6803
  const currentStatus = appInfo.status;
5743
6804
  const currentIP = appInfo.ip || "";
5744
- const elapsed = Math.round((Date.now() - startTime) / 1e3);
5745
6805
  if (currentStatus !== lastLoggedStatus) {
5746
6806
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
5747
6807
  lastLoggedStatus = currentStatus;
6808
+ lastHeartbeatAt = Date.now();
6809
+ } else if (Date.now() - lastHeartbeatAt >= WATCH_HEARTBEAT_INTERVAL_SECONDS * 1e3) {
6810
+ logger.info(`Status: ${currentStatus} (${elapsed}s)`);
6811
+ lastHeartbeatAt = Date.now();
5748
6812
  }
5749
6813
  if (stopCondition(currentStatus, currentIP)) {
5750
6814
  return currentIP || void 0;
5751
6815
  }
5752
6816
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5753
6817
  } catch (error) {
6818
+ if (error instanceof WatchTimeoutError) {
6819
+ throw error;
6820
+ }
5754
6821
  logger.warn(`Failed to fetch app info: ${error.message}`);
5755
6822
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5756
6823
  }
5757
6824
  }
5758
6825
  }
5759
6826
  var APP_STATUS_STOPPED = "Stopped";
5760
- var UPGRADE_PHASE_LABELS = {
5761
- provisioning: "Provisioning new instance",
5762
- health_check: "Running health checks",
5763
- draining: "Switching traffic & draining old instance",
5764
- complete: "Complete",
5765
- rolling_back: "Rolling back",
5766
- rollback_done: "Rollback complete",
5767
- db_handoff: "Database handoff",
5768
- // Prewarm-detach phases
5769
- awaiting_userdata: "Waiting for instance readiness",
5770
- draining_old: "Draining old instance",
5771
- detached: "Detaching storage",
5772
- attached_to_new: "Attaching storage to new instance",
5773
- finalizing: "Finalizing new instance",
5774
- flipping: "Switching traffic",
5775
- teardown_old: "Cleaning up old instance"
5776
- };
5777
- async function fetchUpgradePhase(userApiClient, appId) {
5778
- try {
5779
- const deployments = await userApiClient.getDeployments(appId);
5780
- if (deployments.length === 0) return void 0;
5781
- const sorted = [...deployments].sort((a, b) => {
5782
- const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0;
5783
- const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0;
5784
- return tb - ta;
5785
- });
5786
- return sorted[0].upgradePhase || void 0;
5787
- } catch {
5788
- return void 0;
5789
- }
5790
- }
5791
6827
  async function watchUntilUpgradeComplete(options, logger) {
5792
6828
  const { walletClient, publicClient, environmentConfig, appId } = options;
6829
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
5793
6830
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
5794
6831
  let initialStatus;
5795
6832
  let initialIP;
5796
6833
  let hasChanged = false;
5797
- const startTime = Date.now();
5798
- let lastLoggedStatus;
5799
- let lastLoggedPhase;
5800
6834
  const stopCondition = (status, ip) => {
5801
6835
  if (!initialStatus) {
5802
6836
  initialStatus = status;
@@ -5830,37 +6864,44 @@ async function watchUntilUpgradeComplete(options, logger) {
5830
6864
  }
5831
6865
  return false;
5832
6866
  };
6867
+ const startTime = Date.now();
6868
+ const deadline = startTime + timeoutSeconds * 1e3;
6869
+ let lastLoggedStatus;
6870
+ let lastObservedStatus;
5833
6871
  while (true) {
6872
+ if (Date.now() >= deadline) {
6873
+ const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
6874
+ throw new WatchTimeoutError({
6875
+ appId,
6876
+ lastStatus: lastObservedStatus,
6877
+ elapsedSeconds,
6878
+ timeoutSeconds
6879
+ });
6880
+ }
5834
6881
  try {
5835
- const [infoResult, upgradePhase] = await Promise.all([
5836
- userApiClient.getInfos([appId], 1),
5837
- fetchUpgradePhase(userApiClient, appId)
5838
- ]);
5839
- if (infoResult.length === 0) {
6882
+ const info = await userApiClient.getInfos([appId], 1);
6883
+ if (info.length === 0) {
5840
6884
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5841
6885
  continue;
5842
6886
  }
5843
- const appInfo = infoResult[0];
6887
+ const appInfo = info[0];
5844
6888
  const currentStatus = appInfo.status;
5845
6889
  const currentIP = appInfo.ip || "";
6890
+ lastObservedStatus = currentStatus;
5846
6891
  const elapsed = Math.round((Date.now() - startTime) / 1e3);
5847
- if (upgradePhase && upgradePhase !== lastLoggedPhase) {
5848
- const label = UPGRADE_PHASE_LABELS[upgradePhase] || upgradePhase;
5849
- logger.info(`Phase: ${label} (${elapsed}s)`);
5850
- lastLoggedPhase = upgradePhase;
5851
- }
5852
- if (!upgradePhase && currentStatus !== lastLoggedStatus) {
6892
+ if (currentStatus !== lastLoggedStatus) {
5853
6893
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
5854
6894
  lastLoggedStatus = currentStatus;
5855
6895
  }
5856
6896
  if (stopCondition(currentStatus, currentIP)) {
5857
- const totalElapsed = Math.round((Date.now() - startTime) / 1e3);
5858
- logger.info(`Upgrade completed in ${totalElapsed}s`);
5859
6897
  return;
5860
6898
  }
5861
6899
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5862
6900
  } catch (error) {
5863
- if (error.message?.includes("entered")) {
6901
+ if (error instanceof WatchTimeoutError) {
6902
+ throw error;
6903
+ }
6904
+ if (typeof error?.message === "string" && error.message.includes("Failed")) {
5864
6905
  throw error;
5865
6906
  }
5866
6907
  logger.warn(`Failed to fetch app info: ${error.message}`);
@@ -5874,7 +6915,7 @@ function sleep2(ms) {
5874
6915
 
5875
6916
  // src/client/common/utils/validation.ts
5876
6917
  var import_fs = __toESM(require("fs"), 1);
5877
- var import_viem5 = require("viem");
6918
+ var import_viem6 = require("viem");
5878
6919
  function validateAppName(name) {
5879
6920
  if (!name) {
5880
6921
  throw new Error("App name cannot be empty");
@@ -5922,7 +6963,7 @@ function validateAppID(appID) {
5922
6963
  throw new Error("App ID is required");
5923
6964
  }
5924
6965
  const normalized = typeof appID === "string" ? addHexPrefix(appID) : appID;
5925
- if ((0, import_viem5.isAddress)(normalized)) {
6966
+ if ((0, import_viem6.isAddress)(normalized)) {
5926
6967
  return normalized;
5927
6968
  }
5928
6969
  throw new Error(`Invalid app ID: '${appID}' is not a valid address`);
@@ -5971,14 +7012,13 @@ var ChainAddresses = {
5971
7012
  PermissionController: "0x44632dfBdCb6D3E21EF613B0ca8A6A0c618F5a37"
5972
7013
  }
5973
7014
  };
5974
- var PLATFORM_ENV_TESTNET_SEPOLIA = "testnet-sepolia";
5975
- var PLATFORM_ENV_MAINNET_ETHEREUM = "mainnet-ethereum";
5976
- var DEFAULT_APP_BASE_DOMAIN = "eigencloud.xyz";
5977
7015
  var ENVIRONMENTS = {
5978
7016
  "sepolia-dev": {
5979
7017
  name: "sepolia",
5980
7018
  build: "dev",
5981
7019
  appControllerAddress: "0xa86DC1C47cb2518327fB4f9A1627F51966c83B92",
7020
+ releaseAbiVersion: "v1.5",
7021
+ // AppController upgraded to v1.5.x (containerPolicy)
5982
7022
  permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
5983
7023
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
5984
7024
  kmsServerURL: "http://10.128.0.57:8080",
@@ -5986,14 +7026,14 @@ var ENVIRONMENTS = {
5986
7026
  defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com",
5987
7027
  usdcCreditsAddress: "0xbdA3897c3A428763B59015C64AB766c288C97376",
5988
7028
  baseUsdcCreditsAddress: "0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac",
5989
- baseRPCURL: "https://base-sepolia-rpc.publicnode.com",
5990
- platformEnv: PLATFORM_ENV_TESTNET_SEPOLIA,
5991
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7029
+ baseRPCURL: "https://base-sepolia-rpc.publicnode.com"
5992
7030
  },
5993
7031
  sepolia: {
5994
7032
  name: "sepolia",
5995
7033
  build: "prod",
5996
7034
  appControllerAddress: "0x0dd810a6ffba6a9820a10d97b659f07d8d23d4E2",
7035
+ releaseAbiVersion: "v1.4",
7036
+ // prod still on AppController v1.4.0 (3-field Release)
5997
7037
  permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
5998
7038
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
5999
7039
  kmsServerURL: "http://10.128.15.203:8080",
@@ -6002,22 +7042,20 @@ var ENVIRONMENTS = {
6002
7042
  billingRPCURL: "https://ethereum-rpc.publicnode.com",
6003
7043
  usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d",
6004
7044
  baseUsdcCreditsAddress: "0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac",
6005
- baseRPCURL: "https://base-sepolia-rpc.publicnode.com",
6006
- platformEnv: PLATFORM_ENV_TESTNET_SEPOLIA,
6007
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7045
+ baseRPCURL: "https://base-sepolia-rpc.publicnode.com"
6008
7046
  },
6009
7047
  "mainnet-alpha": {
6010
7048
  name: "mainnet-alpha",
6011
7049
  build: "prod",
6012
7050
  appControllerAddress: "0xc38d35Fc995e75342A21CBd6D770305b142Fbe67",
7051
+ releaseAbiVersion: "v1.4",
7052
+ // prod still on AppController v1.4.0 (3-field Release)
6013
7053
  permissionControllerAddress: ChainAddresses[MAINNET_CHAIN_ID].PermissionController,
6014
7054
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
6015
7055
  kmsServerURL: "http://10.128.0.2:8080",
6016
7056
  userApiServerURL: "https://userapi-compute.eigencloud.xyz",
6017
7057
  defaultRPCURL: "https://ethereum-rpc.publicnode.com",
6018
- usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d",
6019
- platformEnv: PLATFORM_ENV_MAINNET_ETHEREUM,
6020
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7058
+ usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d"
6021
7059
  }
6022
7060
  };
6023
7061
  var CHAIN_ID_TO_ENVIRONMENT = {
@@ -6061,7 +7099,7 @@ function getEnvironmentConfig(environment, chainID) {
6061
7099
  };
6062
7100
  }
6063
7101
  function getBuildType() {
6064
- const buildTimeType = true ? "dev"?.toLowerCase() : void 0;
7102
+ const buildTimeType = true ? "prod"?.toLowerCase() : void 0;
6065
7103
  const runtimeType = process.env.BUILD_TYPE?.toLowerCase();
6066
7104
  const buildType = buildTimeType || runtimeType;
6067
7105
  if (buildType === "dev") {
@@ -6412,6 +7450,11 @@ async function prepareDeployFromVerifiableBuild(options, logger = defaultLogger)
6412
7450
  executions: batch.executions,
6413
7451
  authorizationList
6414
7452
  });
7453
+ await assertSufficientGas({
7454
+ publicClient: batch.publicClient,
7455
+ address: batch.walletClient.account.address,
7456
+ gasEstimate
7457
+ });
6415
7458
  const data = {
6416
7459
  appId: batch.appId,
6417
7460
  salt: batch.salt,
@@ -6670,6 +7713,11 @@ async function prepareDeploy(options, logger = defaultLogger) {
6670
7713
  executions: batch.executions,
6671
7714
  authorizationList
6672
7715
  });
7716
+ await assertSufficientGas({
7717
+ publicClient: batch.publicClient,
7718
+ address: batch.walletClient.account.address,
7719
+ gasEstimate
7720
+ });
6673
7721
  const data = {
6674
7722
  appId: batch.appId,
6675
7723
  salt: batch.salt,
@@ -6706,7 +7754,7 @@ async function executeDeploy(options) {
6706
7754
  }
6707
7755
  );
6708
7756
  }
6709
- async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
7757
+ async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
6710
7758
  return withSDKTelemetry(
6711
7759
  {
6712
7760
  functionName: "watchDeployment",
@@ -6722,7 +7770,8 @@ async function watchDeployment(appId, walletClient, publicClient, environmentCon
6722
7770
  walletClient,
6723
7771
  publicClient,
6724
7772
  environmentConfig,
6725
- appId
7773
+ appId,
7774
+ timeoutSeconds: opts?.timeoutSeconds
6726
7775
  },
6727
7776
  logger
6728
7777
  );
@@ -6818,6 +7867,11 @@ async function prepareUpgradeFromVerifiableBuild(options, logger = defaultLogger
6818
7867
  executions: batch.executions,
6819
7868
  authorizationList
6820
7869
  });
7870
+ await assertSufficientGas({
7871
+ publicClient: batch.publicClient,
7872
+ address: batch.walletClient.account.address,
7873
+ gasEstimate
7874
+ });
6821
7875
  const data = {
6822
7876
  appId: batch.appId,
6823
7877
  executions: batch.executions,
@@ -7007,6 +8061,11 @@ async function prepareUpgrade(options, logger = defaultLogger) {
7007
8061
  executions: batch.executions,
7008
8062
  authorizationList
7009
8063
  });
8064
+ await assertSufficientGas({
8065
+ publicClient: batch.publicClient,
8066
+ address: batch.walletClient.account.address,
8067
+ gasEstimate
8068
+ });
7010
8069
  const data = {
7011
8070
  appId: batch.appId,
7012
8071
  executions: batch.executions,
@@ -7041,7 +8100,7 @@ async function executeUpgrade(options) {
7041
8100
  }
7042
8101
  );
7043
8102
  }
7044
- async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
8103
+ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
7045
8104
  return withSDKTelemetry(
7046
8105
  {
7047
8106
  functionName: "watchUpgrade",
@@ -7057,7 +8116,8 @@ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig
7057
8116
  walletClient,
7058
8117
  publicClient,
7059
8118
  environmentConfig,
7060
- appId
8119
+ appId,
8120
+ timeoutSeconds: opts?.timeoutSeconds
7061
8121
  },
7062
8122
  logger
7063
8123
  );
@@ -7715,27 +8775,27 @@ async function logs(options, walletClient, publicClient, environmentConfig, logg
7715
8775
  }
7716
8776
 
7717
8777
  // src/client/modules/compute/app/index.ts
7718
- var CONTROLLER_ABI = (0, import_viem6.parseAbi)([
8778
+ var CONTROLLER_ABI = (0, import_viem7.parseAbi)([
7719
8779
  "function startApp(address appId)",
7720
8780
  "function stopApp(address appId)",
7721
8781
  "function terminateApp(address appId)"
7722
8782
  ]);
7723
8783
  function encodeStartAppData(appId) {
7724
- return (0, import_viem6.encodeFunctionData)({
8784
+ return (0, import_viem7.encodeFunctionData)({
7725
8785
  abi: CONTROLLER_ABI,
7726
8786
  functionName: "startApp",
7727
8787
  args: [appId]
7728
8788
  });
7729
8789
  }
7730
8790
  function encodeStopAppData(appId) {
7731
- return (0, import_viem6.encodeFunctionData)({
8791
+ return (0, import_viem7.encodeFunctionData)({
7732
8792
  abi: CONTROLLER_ABI,
7733
8793
  functionName: "stopApp",
7734
8794
  args: [appId]
7735
8795
  });
7736
8796
  }
7737
8797
  function encodeTerminateAppData(appId) {
7738
- return (0, import_viem6.encodeFunctionData)({
8798
+ return (0, import_viem7.encodeFunctionData)({
7739
8799
  abi: CONTROLLER_ABI,
7740
8800
  functionName: "terminateApp",
7741
8801
  args: [appId]
@@ -7749,7 +8809,7 @@ function createAppModule(ctx) {
7749
8809
  }
7750
8810
  const account = walletClient.account;
7751
8811
  const environment = getEnvironmentConfig(ctx.environment);
7752
- const logger = getLogger(ctx.verbose);
8812
+ const logger = ctx.logger ?? getLogger(ctx.verbose);
7753
8813
  return {
7754
8814
  async create(opts) {
7755
8815
  return createApp(opts, logger);
@@ -7861,14 +8921,15 @@ function createAppModule(ctx) {
7861
8921
  imageRef: result.imageRef
7862
8922
  };
7863
8923
  },
7864
- async watchDeployment(appId) {
8924
+ async watchDeployment(appId, opts) {
7865
8925
  return watchDeployment(
7866
8926
  appId,
7867
8927
  walletClient,
7868
8928
  publicClient,
7869
8929
  environment,
7870
8930
  logger,
7871
- skipTelemetry
8931
+ skipTelemetry,
8932
+ opts
7872
8933
  );
7873
8934
  },
7874
8935
  // Granular upgrade control
@@ -7926,8 +8987,16 @@ function createAppModule(ctx) {
7926
8987
  imageRef: result.imageRef
7927
8988
  };
7928
8989
  },
7929
- async watchUpgrade(appId) {
7930
- return watchUpgrade(appId, walletClient, publicClient, environment, logger, skipTelemetry);
8990
+ async watchUpgrade(appId, opts) {
8991
+ return watchUpgrade(
8992
+ appId,
8993
+ walletClient,
8994
+ publicClient,
8995
+ environment,
8996
+ logger,
8997
+ skipTelemetry,
8998
+ opts
8999
+ );
7931
9000
  },
7932
9001
  // Profile management
7933
9002
  async setProfile(appId, profile) {
@@ -7978,7 +9047,7 @@ function createAppModule(ctx) {
7978
9047
  },
7979
9048
  async () => {
7980
9049
  const pendingMessage = `Starting app ${appId}...`;
7981
- const data = (0, import_viem6.encodeFunctionData)({
9050
+ const data = (0, import_viem7.encodeFunctionData)({
7982
9051
  abi: CONTROLLER_ABI,
7983
9052
  functionName: "startApp",
7984
9053
  args: [appId]
@@ -8010,7 +9079,7 @@ function createAppModule(ctx) {
8010
9079
  },
8011
9080
  async () => {
8012
9081
  const pendingMessage = `Stopping app ${appId}...`;
8013
- const data = (0, import_viem6.encodeFunctionData)({
9082
+ const data = (0, import_viem7.encodeFunctionData)({
8014
9083
  abi: CONTROLLER_ABI,
8015
9084
  functionName: "stopApp",
8016
9085
  args: [appId]
@@ -8042,7 +9111,7 @@ function createAppModule(ctx) {
8042
9111
  },
8043
9112
  async () => {
8044
9113
  const pendingMessage = `Terminating app ${appId}...`;
8045
- const data = (0, import_viem6.encodeFunctionData)({
9114
+ const data = (0, import_viem7.encodeFunctionData)({
8046
9115
  abi: CONTROLLER_ABI,
8047
9116
  functionName: "terminateApp",
8048
9117
  args: [appId]