@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.js CHANGED
@@ -148,7 +148,6 @@ var ENV_SOURCE_SCRIPT_NAME = "compute-source-env.sh";
148
148
  var KMS_CLIENT_BINARY_NAME = "kms-client";
149
149
  var KMS_SIGNING_KEY_NAME = "kms-signing-public-key.pem";
150
150
  var TLS_KEYGEN_BINARY_NAME = "tls-keygen";
151
- var DRAIN_WATCHER_BINARY_NAME = "ecloud-drain-watcher";
152
151
  var CADDYFILE_NAME = "Caddyfile";
153
152
  var LAYERED_BUILD_DIR_PREFIX = "ecloud-layered-build";
154
153
 
@@ -435,12 +434,14 @@ USER root
435
434
  {{/if}}
436
435
 
437
436
  # Copy core TEE components
437
+ # CA bundle for kms-client / tls-keygen to validate HTTPS calls to
438
+ # eigencloud.xyz endpoints. Bundled at a non-standard path and consumed
439
+ # only via SSL_CERT_FILE in compute-source-env.sh, so the user's
440
+ # /etc/ssl/ is never touched.
441
+ COPY --from=alpine:3.20.10 /etc/ssl/certs/ca-certificates.crt /usr/local/share/eigenx-ca-certs.crt
438
442
  COPY compute-source-env.sh /usr/local/bin/
439
443
  COPY kms-client /usr/local/bin/
440
444
  COPY kms-signing-public-key.pem /usr/local/bin/
441
- {{#if includeDrainWatcher}}
442
- COPY ecloud-drain-watcher /usr/local/bin/
443
- {{/if}}
444
445
 
445
446
  {{#if includeTLS}}
446
447
  # Copy Caddy from official image
@@ -454,8 +455,7 @@ COPY Caddyfile /etc/caddy/
454
455
  {{#if originalUser}}
455
456
  # Make binaries executable (755 for executables, 644 for keys)
456
457
  RUN chmod 755 /usr/local/bin/compute-source-env.sh \\
457
- && chmod 755 /usr/local/bin/kms-client{{#if includeDrainWatcher}} \\
458
- && chmod 755 /usr/local/bin/ecloud-drain-watcher{{/if}}{{#if includeTLS}} \\
458
+ && chmod 755 /usr/local/bin/kms-client{{#if includeTLS}} \\
459
459
  && chmod 755 /usr/local/bin/tls-keygen \\
460
460
  && chmod 755 /usr/local/bin/caddy{{/if}} \\
461
461
  && chmod 644 /usr/local/bin/kms-signing-public-key.pem
@@ -465,8 +465,7 @@ ENV __ECLOUD_ORIGINAL_USER={{originalUser}}
465
465
  {{else}}
466
466
  # Make binaries executable (preserve existing permissions, just add execute)
467
467
  RUN chmod +x /usr/local/bin/compute-source-env.sh \\
468
- && chmod +x /usr/local/bin/kms-client{{#if includeDrainWatcher}} \\
469
- && chmod +x /usr/local/bin/ecloud-drain-watcher{{/if}}{{#if includeTLS}} \\
468
+ && chmod +x /usr/local/bin/kms-client{{#if includeTLS}} \\
470
469
  && chmod +x /usr/local/bin/tls-keygen{{/if}}
471
470
  {{/if}}
472
471
 
@@ -479,36 +478,8 @@ LABEL tee.launch_policy.log_redirect={{logRedirect}}
479
478
  LABEL tee.launch_policy.monitoring_memory_allow={{resourceUsageAllow}}
480
479
  {{/if}}
481
480
 
482
- # Allow-list the envvars the ecloud-platform sets via GCE \`tee-env-*\`
483
- # metadata. Without this label, Confidential Space's launcher rejects
484
- # any \`tee-env-*\` override at container-start with
485
- # "env var {...} is not allowed to be overridden on this image" and
486
- # exits with code 1 \u2014 which terminates the VM before the entrypoint
487
- # ever runs. User-supplied env vars flow through KMS (not tee-env-*)
488
- # and don't need to be listed here.
489
- #
490
- # Entries:
491
- # - ECLOUD_PD_EXPECTED set on PD-backed apps so the entrypoint
492
- # (compute-source-env.sh) knows to wait for
493
- # the persistent disk before exec'ing the
494
- # user workload.
495
- # - ECLOUD_PLATFORM_HOST the platform-routed hostname
496
- # (<addr>.<env>.eigencloud.xyz) so the
497
- # entrypoint's setup_tls can issue an ACME
498
- # cert for it. Injected by the CLI into
499
- # publicEnv at deploy/upgrade time and
500
- # propagated by ecloud-platform's
501
- # compute.go as a tee-env-* metadata key.
502
- #
503
- # The CS launcher parses this label as a comma-separated list
504
- # (go-tpm-tools/launcher/spec/launch_policy.go:185 \u2014 strings.Split
505
- # on ","). Quotes are not required; keep the value bare for
506
- # consistency with compute-tee's and eigenx-kms's existing labels.
507
- LABEL tee.launch_policy.allow_env_override=ECLOUD_PD_EXPECTED,ECLOUD_PLATFORM_HOST
508
-
509
481
  LABEL eigenx_cli_version={{ecloudCLIVersion}}
510
482
  LABEL eigenx_vm_image=eigen
511
- LABEL eigenx_container_contract=v1
512
483
 
513
484
  {{#if includeTLS}}
514
485
  # Expose both HTTP and HTTPS ports for Caddy
@@ -530,54 +501,11 @@ import Handlebars2 from "handlebars";
530
501
 
531
502
  // src/client/common/templates/compute-source-env.sh.tmpl
532
503
  var compute_source_env_sh_default = `#!/bin/sh
533
- # EigenCompute container entrypoint script
534
- # This script handles KMS secret fetching, TLS setup, and privilege dropping
535
- # before executing the user's application.
536
- #
537
- # Handlebars template variables (replaced at build time by the CLI):
538
- # kmsServerURL - URL of the KMS server
539
- # userAPIURL - URL of the user API (ecloud-platform)
540
- # The KMS signing public key is copied into the image as
541
- # /usr/local/bin/kms-signing-public-key.pem at layer-build time by the CLI.
542
- #
543
- # ecloud-platform divergence from compute-tee:
544
- # This script emits ECLOUD_READY / ECLOUD_FAIL / ECLOUD_AWAITING_USERDATA /
545
- # ECLOUD_DETACHED markers to stdout at key lifecycle points. The GCP
546
- # provisioner's serial-console watcher in ecloud-platform
547
- # (pkg/services/infraService/providers/gcp/compute.go) parses those
548
- # markers to gate "VM ready" and to coordinate the prewarm-detach
549
- # upgrade flow. Without the markers, the platform's waitForStartupReady
550
- # times out at ~10 minutes per deploy, rollback fires, and the VM is
551
- # deleted \u2014 seen in dev on 2026-05-04 with an older copy of this
552
- # template that lacked the markers.
553
- #
554
- # Prewarm-detach contract:
555
- # - If ECLOUD_PD_EXPECTED=1 and /mnt/disks/userdata is not present at boot,
556
- # emit ECLOUD_AWAITING_USERDATA and wait until the disk is attached.
557
- # - On SIGTERM (drain-requested), forward to child, wait for exit, sync
558
- # + unmount /mnt/disks/userdata, emit ECLOUD_DETACHED, exit.
559
- # - ECLOUD_READY is emitted once runtime is bootstrapped (same as before).
560
- # - ECLOUD_FAIL is emitted on any unrecoverable setup error.
561
- # Keep the markers on any line that resolves a lifecycle outcome.
562
- #
563
- # This file is kept in lockstep with
564
- # ecloud-platform/pkg/services/buildService/assets/compute-source-env.sh.tmpl
565
- # \u2014 if you change one, change the other. Differences vs the platform copy
566
- # are intentionally minimal:
567
- # - Handlebars placeholders use the CLI's naming (kmsServerURL,
568
- # userAPIURL) rather than the platform's (KMS_SERVER_URL,
569
- # USER_API_URL). (See top of file for real placeholder syntax \u2014
570
- # not repeated here so Handlebars doesn't expand it in this comment.)
571
- # - KMS signing key is read from a file the CLI copies into the image,
572
- # not heredoc-embedded in the script, because the CLI's image
573
- # layering writes it as a separate file (kms-signing-public-key.pem).
574
- # - TLS binary is \`tls-keygen\` (CLI-bundled) not \`tls-client\`.
575
-
576
504
  echo "compute-source-env.sh: Running setup script..."
577
505
 
578
506
  # Fetch and source environment variables from KMS
579
507
  echo "Fetching secrets from KMS..."
580
- if /usr/local/bin/kms-client \\
508
+ if SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt /usr/local/bin/kms-client \\
581
509
  --kms-server-url "{{kmsServerURL}}" \\
582
510
  --kms-signing-key-file /usr/local/bin/kms-signing-public-key.pem \\
583
511
  --userapi-url "{{userAPIURL}}" \\
@@ -588,187 +516,93 @@ if /usr/local/bin/kms-client \\
588
516
  else
589
517
  echo "compute-source-env.sh: ERROR - Failed to fetch environment variables from KMS"
590
518
  echo "compute-source-env.sh: Exiting - cannot start user workload without KMS secrets"
591
- echo "ECLOUD_FAIL kms_bootstrap"
592
519
  exit 1
593
520
  fi
594
521
 
595
- # issue_cert_for runs tls-keygen for a single hostname and copies the
596
- # produced fullchain/privkey into $1's output directory ($2). Returns
597
- # 0 on success, non-zero on any failure (caller decides whether that's
598
- # fatal). tls-keygen writes to /run/tls/{fullchain,privkey}.pem by
599
- # default; we move those to a per-host subdirectory so the two-site
600
- # Caddyfile can select the right cert by path.
601
- issue_cert_for() {
602
- local host="$1"
603
- local cert_dir="$2"
604
- local mnemonic="$3"
605
- local challenge="$4"
606
- local staging_flag="$5"
607
-
608
- echo "compute-source-env.sh: Obtaining TLS certificate for $host (challenge=$challenge)..."
609
- # Remove any stale default outputs from a prior tls-keygen run
610
- rm -f /run/tls/fullchain.pem /run/tls/privkey.pem
611
- if ! MNEMONIC="$mnemonic" DOMAIN="$host" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
612
- -challenge "$challenge" \\
613
- $staging_flag; then
614
- return 1
615
- fi
616
- mkdir -p "$cert_dir"
617
- mv /run/tls/fullchain.pem "$cert_dir/fullchain.pem"
618
- mv /run/tls/privkey.pem "$cert_dir/privkey.pem"
619
- echo "compute-source-env.sh: Cert for $host written to $cert_dir"
620
- }
621
-
622
- # Setup TLS: issues certs for
623
- # - ECLOUD_PLATFORM_HOST (platform-routed <addr>.<env>.eigencloud.xyz),
624
- # when the CLI/platform has set it
625
- # - DOMAIN (user-supplied custom domain), when set and non-localhost
626
- #
627
- # No client-side DNS precheck. Earlier versions tried to gate ACME on
628
- # "does this hostname resolve to my external IP" but that's wrong for
629
- # the platform-routing model (DNS points at the shared nginx NLB, not
630
- # the VM) and was preventing cert issuance on the production path.
631
- # tls-client (eigencompute-containers/tls-client) does its own DNS
632
- # poll before calling ACME and surfaces a clear error when challenges
633
- # can't reach the VM, which is the right place for that check \u2014
634
- # attempting it here from inside the VM cannot tell platform-routed
635
- # from compute-tee-routed apps.
522
+ # Setup TLS if tls-keygen is present (which means TLS was configured at build time)
636
523
  setup_tls() {
637
524
  # If tls-keygen isn't present, TLS wasn't configured during build
638
525
  if [ ! -x /usr/local/bin/tls-keygen ]; then
639
526
  echo "compute-source-env.sh: TLS not configured (no tls-keygen binary)"
640
527
  return 0
641
528
  fi
642
-
643
- local platform_host="\${ECLOUD_PLATFORM_HOST:-}"
644
- local user_domain="\${DOMAIN:-}"
529
+
530
+ local domain="\${DOMAIN:-}"
645
531
  local mnemonic="\${MNEMONIC:-}"
646
-
647
- # Normalize "localhost" DOMAIN to unset \u2014 compute-tee apps often
648
- # set DOMAIN=localhost as a sentinel for "no custom domain".
649
- if [ "$user_domain" = "localhost" ]; then
650
- user_domain=""
651
- fi
652
-
653
- # If the user set DOMAIN to the same value as the platform
654
- # hostname, collapse them. Without this we'd burn a second ACME
655
- # issuance for the exact same name (each VM boot counts against
656
- # Let's Encrypt's 5-certs-per-domain-per-week limit), and Caddy
657
- # would then refuse to start because two site blocks bound to
658
- # the same address is a config error. Also unset DOMAIN so the
659
- # DOMAIN site block in the Caddyfile falls back to its
660
- # "localhost.user.invalid" placeholder and stays dormant.
661
- if [ -n "$platform_host" ] && [ "$user_domain" = "$platform_host" ]; then
662
- echo "compute-source-env.sh: DOMAIN matches ECLOUD_PLATFORM_HOST; skipping duplicate cert"
663
- user_domain=""
664
- unset DOMAIN
665
- fi
666
-
667
- if [ -z "$platform_host" ] && [ -z "$user_domain" ]; then
668
- echo "compute-source-env.sh: TLS skipped (neither ECLOUD_PLATFORM_HOST nor DOMAIN set)"
669
- return 0
532
+
533
+ # Since tls-keygen is present, TLS is expected - validate requirements
534
+ if [ -z "$domain" ] || [ "$domain" = "localhost" ]; then
535
+ echo "compute-source-env.sh: ERROR - TLS binary present but DOMAIN not configured or is localhost"
536
+ echo "compute-source-env.sh: Set DOMAIN environment variable to a valid domain"
537
+ exit 1
670
538
  fi
671
-
539
+
672
540
  if [ -z "$mnemonic" ]; then
673
- echo "compute-source-env.sh: ERROR - TLS requested but MNEMONIC not available"
674
- echo "ECLOUD_FAIL tls_mnemonic_missing"
541
+ echo "compute-source-env.sh: ERROR - TLS binary present but MNEMONIC not available"
542
+ echo "compute-source-env.sh: Cannot obtain TLS certificate without mnemonic"
675
543
  exit 1
676
544
  fi
677
-
545
+
678
546
  if [ ! -x /usr/local/bin/caddy ]; then
679
- echo "compute-source-env.sh: ERROR - TLS requested but Caddy not found"
680
- echo "ECLOUD_FAIL tls_caddy_missing"
547
+ echo "compute-source-env.sh: ERROR - TLS binary present but Caddy not found"
681
548
  exit 1
682
549
  fi
683
-
550
+
551
+ echo "compute-source-env.sh: Setting up TLS for domain: $domain"
552
+
553
+ # Obtain TLS certificate using ACME
554
+ # Default to http-01, but allow override via ACME_CHALLENGE env var
684
555
  local challenge="\${ACME_CHALLENGE:-http-01}"
556
+
557
+ # Check if we should use staging (for testing)
685
558
  local staging_flag=""
686
559
  if [ "\${ACME_STAGING:-false}" = "true" ]; then
687
560
  staging_flag="-staging"
688
- echo "compute-source-env.sh: Using Let's Encrypt STAGING environment"
689
- fi
690
-
691
- local certs_issued=0
692
-
693
- if [ -n "$platform_host" ]; then
694
- if issue_cert_for "$platform_host" "/run/tls/platform" "$mnemonic" "$challenge" "$staging_flag"; then
695
- certs_issued=$((certs_issued + 1))
696
- else
697
- echo "compute-source-env.sh: ERROR - failed to issue cert for platform host $platform_host"
698
- echo "ECLOUD_FAIL tls_setup"
699
- exit 1
700
- fi
701
- fi
702
-
703
- if [ -n "$user_domain" ]; then
704
- if issue_cert_for "$user_domain" "/run/tls/domain" "$mnemonic" "$challenge" "$staging_flag"; then
705
- certs_issued=$((certs_issued + 1))
706
- else
707
- echo "compute-source-env.sh: ERROR - failed to issue cert for user domain $user_domain"
708
- echo "ECLOUD_FAIL tls_setup"
709
- exit 1
710
- fi
561
+ echo "compute-source-env.sh: Using Let's Encrypt STAGING environment (certificates won't be trusted)"
711
562
  fi
712
-
713
- if [ "$certs_issued" -eq 0 ]; then
714
- echo "compute-source-env.sh: no certs issued, skipping Caddy (app will serve plaintext until DNS is wired)"
715
- return 0
563
+
564
+ echo "compute-source-env.sh: Obtaining TLS certificate using $challenge challenge..."
565
+ # Pass the API URL for certificate persistence
566
+ if ! SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt \\
567
+ MNEMONIC="$mnemonic" DOMAIN="$domain" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
568
+ -challenge "$challenge" \\
569
+ $staging_flag; then
570
+ echo "compute-source-env.sh: ERROR - Failed to obtain TLS certificate"
571
+ echo "compute-source-env.sh: Certificate issuance failed for $domain"
572
+ exit 1
716
573
  fi
717
-
718
- # Caddy's validate step checks that every \`tls <cert> <key>\` file
719
- # exists, even on site blocks bound to dormant placeholder
720
- # hostnames. The default Caddyfile declares both a platform site
721
- # and a user-domain site; when only one is configured, the other
722
- # block's cert paths are never populated and validate fails with
723
- # "Invalid Caddyfile". Point the unused block at the issued
724
- # block's cert files so validate passes \u2014 the dormant block can't
725
- # receive real traffic (its hostname falls back to
726
- # localhost.{platform,user}.invalid, which Caddy routes by SNI and
727
- # never matches public traffic), so the symlink is never actually
728
- # presented. Skipped when a user-supplied Caddyfile is in use,
729
- # since we don't know what cert paths it references.
730
- if [ -d /run/tls/platform ] && [ ! -e /run/tls/domain/fullchain.pem ]; then
731
- mkdir -p /run/tls/domain
732
- ln -sf /run/tls/platform/fullchain.pem /run/tls/domain/fullchain.pem
733
- ln -sf /run/tls/platform/privkey.pem /run/tls/domain/privkey.pem
734
- elif [ -d /run/tls/domain ] && [ ! -e /run/tls/platform/fullchain.pem ]; then
735
- mkdir -p /run/tls/platform
736
- ln -sf /run/tls/domain/fullchain.pem /run/tls/platform/fullchain.pem
737
- ln -sf /run/tls/domain/privkey.pem /run/tls/platform/privkey.pem
574
+
575
+ echo "compute-source-env.sh: TLS certificate obtained successfully"
576
+
577
+ # Validate Caddyfile before starting
578
+ if ! /usr/local/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile 2>/dev/null; then
579
+ echo "compute-source-env.sh: ERROR - Invalid Caddyfile"
580
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
581
+ exit 1
738
582
  fi
739
-
740
- # Validate Caddyfile before starting. Don't redirect stderr \u2014 when
741
- # validate fails, Caddy's diagnostic is the only signal that lands
742
- # in ReadinessError.SerialTail, so silencing it leaves operators
743
- # staring at a bare "tls_invalid_caddyfile" with no detail.
744
- if [ -f /etc/caddy/Caddyfile ]; then
745
- if ! /usr/local/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile; then
746
- echo "compute-source-env.sh: ERROR - Invalid Caddyfile"
747
- echo "ECLOUD_FAIL tls_invalid_caddyfile"
583
+
584
+ # Start Caddy in background
585
+ echo "compute-source-env.sh: Starting Caddy reverse proxy..."
586
+
587
+ # Check if Caddy logs should be enabled
588
+ if [ "\${ENABLE_CADDY_LOGS:-false}" = "true" ]; then
589
+ if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1; then
590
+ echo "compute-source-env.sh: ERROR - Failed to start Caddy"
591
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
748
592
  exit 1
749
593
  fi
750
-
751
- echo "compute-source-env.sh: Starting Caddy reverse proxy..."
752
- if [ "\${ENABLE_CADDY_LOGS:-false}" = "true" ]; then
753
- if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1; then
754
- echo "compute-source-env.sh: ERROR - Failed to start Caddy"
755
- echo "ECLOUD_FAIL tls_caddy_start"
756
- exit 1
757
- fi
758
- else
759
- if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
760
- echo "compute-source-env.sh: ERROR - Failed to start Caddy"
761
- echo "ECLOUD_FAIL tls_caddy_start"
762
- exit 1
763
- fi
764
- fi
765
-
766
- sleep 2
767
- echo "compute-source-env.sh: Caddy started successfully"
768
594
  else
769
- echo "compute-source-env.sh: No Caddyfile found, skipping Caddy"
595
+ # Redirect Caddy output to /dev/null to silence logs
596
+ if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
597
+ echo "compute-source-env.sh: ERROR - Failed to start Caddy"
598
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
599
+ exit 1
600
+ fi
770
601
  fi
771
-
602
+
603
+ # Give Caddy a moment to fully initialize
604
+ sleep 2
605
+ echo "compute-source-env.sh: Caddy started successfully"
772
606
  return 0
773
607
  }
774
608
 
@@ -779,309 +613,15 @@ setup_tls
779
613
  export KMS_SERVER_URL="{{kmsServerURL}}"
780
614
  export KMS_PUBLIC_KEY="$(cat /usr/local/bin/kms-signing-public-key.pem)"
781
615
 
782
- # \u2500\u2500 Prewarm-detach: wait for PD if expected \u2500\u2500
783
- # Orchestrator sets ECLOUD_PD_EXPECTED=1 on apps using StorageBackend=pd.
784
- # When the prewarm path is used, the new VM boots WITHOUT the disk; we
785
- # signal awaiting-userdata and poll until the disk is attached.
786
- USERDATA_MOUNT="/mnt/disks/userdata"
787
- USERDATA_DEV="/dev/disk/by-id/google-persistent_storage_1"
788
-
789
- # userdata_mount_backing_dev resolves the kernel device that currently
790
- # backs $USERDATA_MOUNT (or empty string if not mounted). The output is
791
- # canonicalized via readlink so we can compare against $USERDATA_DEV
792
- # without caring whether the mount table records the symlinked
793
- # /dev/disk/by-id/... path or the underlying /dev/sdb-style path.
794
- #
795
- # Tries findmnt first (clean structured output), falls back to parsing
796
- # /proc/self/mountinfo so we don't add a hard dependency on util-linux
797
- # being present in the image.
798
- userdata_mount_backing_dev() {
799
- if command -v findmnt >/dev/null 2>&1; then
800
- local src
801
- src=$(findmnt -n -o SOURCE -- "$USERDATA_MOUNT" 2>/dev/null) || return 0
802
- [ -n "$src" ] || return 0
803
- readlink -f "$src" 2>/dev/null || echo "$src"
804
- return 0
805
- fi
806
- # /proc/self/mountinfo line shape (kernel docs): mount-id parent-id
807
- # major:minor root mount-point ... So column 5 is the mount point and
808
- # the device path lives in the optional fields after the "-" separator.
809
- # We just need the major:minor \u2192 device mapping; awk + a /sys lookup
810
- # is cleaner than parsing the mountinfo's optional-field section.
811
- local maj_min dev_basename
812
- maj_min=$(awk -v mp="$USERDATA_MOUNT" '$5 == mp { print $3 }' /proc/self/mountinfo 2>/dev/null | head -1)
813
- [ -n "$maj_min" ] || return 0
814
- dev_basename=$(readlink "/sys/dev/block/\${maj_min}" 2>/dev/null | awk -F/ '{print $NF}')
815
- [ -n "$dev_basename" ] || return 0
816
- readlink -f "/dev/\${dev_basename}" 2>/dev/null || echo "/dev/\${dev_basename}"
817
- }
818
-
819
- # expected_userdata_dev resolves $USERDATA_DEV through any symlinks so
820
- # string-compare against userdata_mount_backing_dev is meaningful even
821
- # when the kernel hands us a /dev/nvme0n1-style path while
822
- # /dev/disk/by-id/google-persistent_storage_1 is the symlink GCE
823
- # publishes.
824
- expected_userdata_dev() {
825
- if [ -e "$USERDATA_DEV" ]; then
826
- readlink -f "$USERDATA_DEV" 2>/dev/null || echo "$USERDATA_DEV"
827
- else
828
- echo "$USERDATA_DEV"
829
- fi
830
- }
831
-
832
- wait_for_userdata() {
833
- if [ "\${ECLOUD_PD_EXPECTED:-0}" != "1" ]; then
834
- return 0
835
- fi
836
- if mountpoint -q "$USERDATA_MOUNT" 2>/dev/null; then
837
- # The mount slot is taken \u2014 but is it taken by the PD we're
838
- # about to attach, or by something else? The Confidential Space
839
- # TEE runtime's SetupSecondaryEncryptedVolume step probes for a
840
- # secondary GCE disk once at boot, doesn't see it (because the
841
- # orchestrator hasn't called AttachPD yet \u2014 the contract says
842
- # the new VM signals awaiting-userdata FIRST), and falls back
843
- # to mounting an encrypted folder on the BOOT disk at this same
844
- # path. If we accept that mount as-is, the user app boots on a
845
- # fresh empty filesystem and the orchestrator's late AttachPD
846
- # silently lands on a VM that's already serving from boot disk.
847
- # See docs/solutions/2026-05-15-prewarm-detach-pd-preservation-boot-race.md
848
- # in ecloud-platform.
849
- local backing expected
850
- backing=$(userdata_mount_backing_dev)
851
- expected=$(expected_userdata_dev)
852
- if [ -n "$backing" ] && [ "$backing" = "$expected" ]; then
853
- echo "compute-source-env.sh: userdata already mounted at $USERDATA_MOUNT (backed by $backing)"
854
- return 0
855
- fi
856
- # Mount slot taken, but NOT by our PD. This is the runtime
857
- # premount on boot disk. Try to recover by lazy-unmounting (the
858
- # filesystem is fresh and unused \u2014 no app has written to it
859
- # yet), then fall through to the normal wait-for-PD loop.
860
- echo "compute-source-env.sh: WARNING - $USERDATA_MOUNT premounted by TEE runtime fallback (backing=\${backing:-unknown}, expected=$expected); attempting recovery"
861
- if ! umount -l "$USERDATA_MOUNT" 2>/dev/null; then
862
- # Lazy unmount should never fail under normal circumstances \u2014
863
- # it doesn't wait for open files, just detaches the namespace.
864
- # If it did fail, we have no path forward: the orchestrator's
865
- # strict-mode WaitAwaitingUserdata gate (ecloud-platform PR
866
- # #174) catches our ECLOUD_FAIL within one poll tick and
867
- # rolls back, leaving the user's data safe on the old VM.
868
- echo "ECLOUD_FAIL pd_runtime_premount_unrecoverable"
869
- exit 1
870
- fi
871
- echo "compute-source-env.sh: unmounted runtime fallback at $USERDATA_MOUNT, waiting for real PD"
872
- fi
873
- # Refuse to proceed if the tools we need for safe first-attach
874
- # detection are missing. Without blkid we cannot tell an empty new
875
- # disk from an already-formatted one \u2014 running mkfs.ext4 on the
876
- # latter would destroy data.
877
- if ! command -v blkid >/dev/null 2>&1; then
878
- echo "ECLOUD_FAIL pd_tools_missing"
879
- exit 1
880
- fi
881
- echo "ECLOUD_AWAITING_USERDATA"
882
- echo "compute-source-env.sh: waiting for PD at $USERDATA_DEV..."
883
- # Poll for up to 10 minutes (120 * 5s). The orchestrator's overall
884
- # attach timeout is shorter; the ceiling here just bounds the wait
885
- # for manual / diagnostic scenarios.
886
- local i=0
887
- local mount_failures=0
888
- while [ "$i" -lt 120 ]; do
889
- if [ -e "$USERDATA_DEV" ]; then
890
- mkdir -p "$USERDATA_MOUNT"
891
- if mount -o noatime "$USERDATA_DEV" "$USERDATA_MOUNT" 2>/dev/null; then
892
- echo "compute-source-env.sh: PD mounted at $USERDATA_MOUNT"
893
- return 0
894
- fi
895
- # Disk present but mount failed. Check whether it has a
896
- # recognized filesystem. \`blkid -s TYPE -o value\` prints the
897
- # FS type (empty if none). We only mkfs when there is
898
- # demonstrably NO filesystem \u2014 never on the basis of blkid
899
- # returning non-zero alone, which could mean "blkid missing"
900
- # or "device busy".
901
- local fstype
902
- fstype=$(blkid -s TYPE -o value "$USERDATA_DEV" 2>/dev/null)
903
- if [ -z "$fstype" ]; then
904
- echo "compute-source-env.sh: formatting $USERDATA_DEV (first attach)"
905
- mkfs.ext4 -F -L eclouddata "$USERDATA_DEV" >/dev/null 2>&1 || {
906
- echo "ECLOUD_FAIL pd_mkfs_failed"
907
- exit 1
908
- }
909
- mount -o noatime "$USERDATA_DEV" "$USERDATA_MOUNT" || {
910
- echo "ECLOUD_FAIL pd_mount_after_format_failed"
911
- exit 1
912
- }
913
- return 0
914
- fi
915
- # Disk has a filesystem but mount still failed. Give it a
916
- # few retries to cover transient cases (device busy, udev
917
- # still settling), but don't pretend this is an attach
918
- # timeout if it persists.
919
- mount_failures=$((mount_failures + 1))
920
- if [ "$mount_failures" -ge 6 ]; then
921
- echo "ECLOUD_FAIL pd_mount_failed"
922
- exit 1
923
- fi
924
- else
925
- # Device disappeared (e.g. udev re-enumeration between
926
- # attach and mount). Reset the consecutive-failure counter
927
- # so only true back-to-back mount failures trip
928
- # pd_mount_failed; a device blip should not steal retries.
929
- mount_failures=0
930
- fi
931
- i=$((i + 1))
932
- sleep 5
933
- done
934
- echo "ECLOUD_FAIL pd_attach_timeout"
935
- exit 1
936
- }
937
-
938
- wait_for_userdata
939
-
940
- # \u2500\u2500 Prewarm-detach: install SIGTERM handler for graceful drain \u2500\u2500
941
- # Orchestrator signals drain by setting the instance metadata key
942
- # ECLOUD_DRAIN_REQUESTED=1, which a host-level agent translates into
943
- # SIGTERM on PID 1. On SIGTERM we:
944
- # 1. Forward to the child (wakes the user's app for graceful exit)
945
- # 2. Wait for child exit
946
- # 3. Sync + unmount the PD
947
- # 4. Emit ECLOUD_DETACHED so the orchestrator can proceed to detach
948
- CHILD_PID=""
949
- _DRAIN_IN_PROGRESS=0
950
-
951
- drain_handler() {
952
- # Guard against re-entry if SIGTERM arrives twice (e.g. both the
953
- # drain_watcher and an external signal fire in quick succession).
954
- if [ "$_DRAIN_IN_PROGRESS" = "1" ]; then
955
- return 0
956
- fi
957
- _DRAIN_IN_PROGRESS=1
958
- echo "compute-source-env.sh: received drain signal, forwarding to child pgid=$CHILD_PID"
959
- if [ -n "$CHILD_PID" ]; then
960
- # Send to the process group so intermediate wrappers (su, sh -c,
961
- # etc.) don't swallow the signal. The leading \`-\` targets the
962
- # pgid, which equals the direct child's pid for a shell-backgrounded
963
- # process. Fall back to the pid alone if pgid signaling fails
964
- # (e.g. kernel older than 3.9 or PID namespace edge cases).
965
- kill -TERM -"$CHILD_PID" 2>/dev/null || kill -TERM "$CHILD_PID" 2>/dev/null || true
966
- # Give the app up to 30s to exit cleanly.
967
- local i=0
968
- while [ "$i" -lt 30 ] && kill -0 "$CHILD_PID" 2>/dev/null; do
969
- i=$((i + 1))
970
- sleep 1
971
- done
972
- if kill -0 "$CHILD_PID" 2>/dev/null; then
973
- echo "compute-source-env.sh: child did not exit in 30s, sending SIGKILL"
974
- kill -KILL -"$CHILD_PID" 2>/dev/null || kill -KILL "$CHILD_PID" 2>/dev/null || true
975
- # Reap the process so its in-flight I/O is flushed to the
976
- # filesystem before we sync + unmount. SIGKILL schedules
977
- # death; wait guarantees it's complete.
978
- wait "$CHILD_PID" 2>/dev/null || true
979
- fi
980
- fi
981
- if [ "\${ECLOUD_PD_EXPECTED:-0}" = "1" ] && mountpoint -q "$USERDATA_MOUNT" 2>/dev/null; then
982
- sync
983
- if umount "$USERDATA_MOUNT" 2>/dev/null; then
984
- echo "compute-source-env.sh: unmounted $USERDATA_MOUNT cleanly"
985
- else
986
- # Force lazy unmount as last resort \u2014 orchestrator still needs
987
- # the DETACHED signal to proceed.
988
- umount -l "$USERDATA_MOUNT" 2>/dev/null || true
989
- echo "compute-source-env.sh: WARNING - used lazy unmount on $USERDATA_MOUNT"
990
- fi
991
- # ECLOUD_DETACHED is strictly a PD-lifecycle signal. Only emit
992
- # it when we actually had a PD mount in play, so serial-log
993
- # parsers and alerting for non-PD apps don't see spurious
994
- # lifecycle markers on routine container SIGTERM.
995
- echo "ECLOUD_DETACHED"
996
- fi
997
- # Always exit 0: drain is a managed shutdown and the orchestrator
998
- # waits on ECLOUD_DETACHED, not the container exit code. Forwarding
999
- # the child's exit status here would make a crash-during-drain look
1000
- # like a drain failure to whatever reads the container exit code.
1001
- exit 0
1002
- }
1003
- trap drain_handler TERM
1004
-
1005
- # \u2500\u2500 Prewarm-detach: background drain watcher \u2500\u2500
1006
- # Container metadata delivery in Confidential Space is limited, so we
1007
- # poll the instance metadata server for ECLOUD_DRAIN_REQUESTED and
1008
- # raise SIGTERM on ourselves when it flips to "1".
1009
- #
1010
- # Try wget first (present in most Alpine bases), fall back to curl.
1011
- # If neither is present, drain watcher is disabled \u2014 the orchestrator
1012
- # will hit its drain timeout and fail the upgrade explicitly, which is
1013
- # the correct behavior (we cannot silently ignore a drain request).
1014
- _fetch_drain_flag() {
1015
- local url="http://metadata.google.internal/computeMetadata/v1/instance/attributes/ECLOUD_DRAIN_REQUESTED"
1016
- if command -v wget >/dev/null 2>&1; then
1017
- wget -q --tries=1 --timeout=2 --header='Metadata-Flavor: Google' -O - "$url" 2>/dev/null
1018
- elif command -v curl >/dev/null 2>&1; then
1019
- curl -sf --max-time 2 -H 'Metadata-Flavor: Google' "$url" 2>/dev/null
1020
- else
1021
- return 2
1022
- fi
1023
- }
1024
-
1025
- drain_watcher() {
1026
- # Preflight: confirm we have an HTTP client
1027
- if ! _fetch_drain_flag >/dev/null 2>&1; then
1028
- # Either no http client available OR metadata server not
1029
- # responding yet. If no client, give up and log; otherwise the
1030
- # loop below will retry.
1031
- if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then
1032
- echo "compute-source-env.sh: WARNING - no wget/curl; drain_watcher disabled"
1033
- return 0
1034
- fi
1035
- fi
1036
- while true; do
1037
- local v
1038
- v=$(_fetch_drain_flag || true)
1039
- if [ "$v" = "1" ]; then
1040
- echo "compute-source-env.sh: drain_watcher saw ECLOUD_DRAIN_REQUESTED=1, signaling PID 1"
1041
- # The CS launcher runs this script directly as PID 1, so
1042
- # kill -TERM 1 delivers SIGTERM to the shell that installed
1043
- # the drain_handler trap. If the launch mechanism ever
1044
- # wraps this script in another process, this assumption
1045
- # breaks and drain will silently no-op \u2014 audit here.
1046
- kill -TERM 1 2>/dev/null || true
1047
- return 0
1048
- fi
1049
- sleep 2
1050
- done
1051
- }
1052
-
1053
- if [ "\${ECLOUD_PD_EXPECTED:-0}" = "1" ]; then
1054
- # Assumption: the orchestrator only flips ECLOUD_DRAIN_REQUESTED=1
1055
- # after observing ECLOUD_AWAITING_USERDATA (old VM) or
1056
- # ECLOUD_READY (new VM), so CHILD_PID is always set by the time
1057
- # drain_handler fires. If drain somehow arrived in the tiny window
1058
- # between this watcher spawn and CHILD_PID assignment below,
1059
- # drain_handler would skip the child-kill branch and still emit
1060
- # ECLOUD_DETACHED \u2014 harmless because there's nothing to drain yet.
1061
- if [ -x /usr/local/bin/ecloud-drain-watcher ]; then
1062
- /usr/local/bin/ecloud-drain-watcher &
1063
- else
1064
- drain_watcher &
1065
- fi
1066
- fi
1067
-
1068
616
  echo "compute-source-env.sh: Environment sourced."
1069
- echo "ECLOUD_READY runtime_bootstrapped"
1070
617
 
1071
618
  # Drop privileges to original user for the application command
1072
619
  if [ -n "$__ECLOUD_ORIGINAL_USER" ] && [ "$(id -u)" = "0" ]; then
1073
620
  echo "compute-source-env.sh: Dropping privileges to user: $__ECLOUD_ORIGINAL_USER"
1074
- # Must background the child so our trap can fire; exec replaces PID 1.
1075
- su -s /bin/sh "$__ECLOUD_ORIGINAL_USER" -c 'exec "$@"' -- sh "$@" &
1076
- CHILD_PID=$!
1077
- wait "$CHILD_PID"
1078
- exit $?
621
+ exec su -s /bin/sh "$__ECLOUD_ORIGINAL_USER" -c 'exec "$@"' -- sh "$@"
1079
622
  fi
1080
623
 
1081
- "$@" &
1082
- CHILD_PID=$!
1083
- wait "$CHILD_PID"
1084
- exit $?
624
+ exec "$@"
1085
625
  `;
1086
626
 
1087
627
  // src/client/common/templates/scriptTemplate.ts
@@ -1090,14 +630,6 @@ function processScriptTemplate(data) {
1090
630
  return template(data);
1091
631
  }
1092
632
 
1093
- // src/client/common/templates/Caddyfile.default.tmpl
1094
- 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';
1095
-
1096
- // src/client/common/templates/caddyfileTemplate.ts
1097
- function getDefaultCaddyfile() {
1098
- return Caddyfile_default_default;
1099
- }
1100
-
1101
633
  // keys/mainnet-alpha/prod/kms-encryption-public-key.pem
1102
634
  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-----";
1103
635
 
@@ -1272,9 +804,15 @@ async function layerLocalImage(options, logger) {
1272
804
  const imageConfig = await extractImageConfig(docker, sourceImageRef);
1273
805
  const originalCmd = imageConfig.cmd.length > 0 ? imageConfig.cmd : imageConfig.entrypoint;
1274
806
  const originalUser = imageConfig.user;
1275
- const includeTLS = true;
1276
- const drainWatcherSource = findBinary("ecloud-drain-watcher-linux-amd64");
1277
- const includeDrainWatcher = fs.existsSync(drainWatcherSource);
807
+ let includeTLS = false;
808
+ if (envFilePath && fs.existsSync(envFilePath)) {
809
+ const envContent = fs.readFileSync(envFilePath, "utf-8");
810
+ const domainMatch = envContent.match(/^DOMAIN=(.+)$/m);
811
+ if (domainMatch && domainMatch[1] && domainMatch[1] !== "localhost") {
812
+ includeTLS = true;
813
+ logger.debug(`Found DOMAIN=${domainMatch[1]} in ${envFilePath}, including TLS components`);
814
+ }
815
+ }
1278
816
  const layeredDockerfileContent = processDockerfileTemplate({
1279
817
  baseImage: sourceImageRef,
1280
818
  originalCmd: JSON.stringify(originalCmd),
@@ -1282,9 +820,8 @@ async function layerLocalImage(options, logger) {
1282
820
  logRedirect,
1283
821
  resourceUsageAllow,
1284
822
  includeTLS,
1285
- ecloudCLIVersion: "0.1.0",
823
+ ecloudCLIVersion: "0.1.0"
1286
824
  // TODO: Get from package.json
1287
- includeDrainWatcher
1288
825
  });
1289
826
  const scriptContent = processScriptTemplate({
1290
827
  kmsServerURL: environmentConfig.kmsServerURL,
@@ -1294,8 +831,7 @@ async function layerLocalImage(options, logger) {
1294
831
  environmentConfig,
1295
832
  layeredDockerfileContent,
1296
833
  scriptContent,
1297
- includeTLS,
1298
- includeDrainWatcher ? drainWatcherSource : void 0
834
+ includeTLS
1299
835
  // logger
1300
836
  );
1301
837
  try {
@@ -1310,7 +846,7 @@ async function layerLocalImage(options, logger) {
1310
846
  fs.rmSync(tempDir, { recursive: true, force: true });
1311
847
  }
1312
848
  }
1313
- async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileContent, scriptContent, includeTLS, drainWatcherSource) {
849
+ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileContent, scriptContent, includeTLS) {
1314
850
  const tempDir = fs.mkdtempSync(path2.join(os.tmpdir(), LAYERED_BUILD_DIR_PREFIX));
1315
851
  try {
1316
852
  const layeredDockerfilePath = path2.join(tempDir, LAYERED_DOCKERFILE_NAME);
@@ -1334,11 +870,6 @@ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileCo
1334
870
  }
1335
871
  fs.copyFileSync(kmsClientSource, kmsClientPath);
1336
872
  fs.chmodSync(kmsClientPath, 493);
1337
- if (drainWatcherSource && fs.existsSync(drainWatcherSource)) {
1338
- const drainWatcherPath = path2.join(tempDir, DRAIN_WATCHER_BINARY_NAME);
1339
- fs.copyFileSync(drainWatcherSource, drainWatcherPath);
1340
- fs.chmodSync(drainWatcherPath, 493);
1341
- }
1342
873
  if (includeTLS) {
1343
874
  const tlsKeygenPath = path2.join(tempDir, TLS_KEYGEN_BINARY_NAME);
1344
875
  const tlsKeygenSource = findBinary("tls-keygen-linux-amd64");
@@ -1349,13 +880,15 @@ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileCo
1349
880
  }
1350
881
  fs.copyFileSync(tlsKeygenSource, tlsKeygenPath);
1351
882
  fs.chmodSync(tlsKeygenPath, 493);
1352
- const userCaddyfilePath = path2.join(process.cwd(), CADDYFILE_NAME);
1353
- const destCaddyfilePath = path2.join(tempDir, CADDYFILE_NAME);
1354
- if (fs.existsSync(userCaddyfilePath)) {
1355
- fs.copyFileSync(userCaddyfilePath, destCaddyfilePath);
1356
- fs.chmodSync(destCaddyfilePath, 420);
883
+ const caddyfilePath = path2.join(process.cwd(), CADDYFILE_NAME);
884
+ if (fs.existsSync(caddyfilePath)) {
885
+ const caddyfileContent = fs.readFileSync(caddyfilePath);
886
+ const destCaddyfilePath = path2.join(tempDir, CADDYFILE_NAME);
887
+ fs.writeFileSync(destCaddyfilePath, caddyfileContent, { mode: 420 });
1357
888
  } else {
1358
- fs.writeFileSync(destCaddyfilePath, getDefaultCaddyfile(), { mode: 420 });
889
+ throw new Error(
890
+ "TLS is enabled (DOMAIN is set) but Caddyfile not found. Run configure TLS to set up TLS configuration"
891
+ );
1359
892
  }
1360
893
  }
1361
894
  return tempDir;
@@ -1423,16 +956,7 @@ async function extractDigestFromSinglePlatform(manifest, imageRef) {
1423
956
  architecture: inspectData[0].Architecture
1424
957
  } : null;
1425
958
  if (!config) {
1426
- if (manifest.config?.digest) {
1427
- const digest = hexStringToBytes32(manifest.config.digest);
1428
- const registry = extractRegistryName(imageRef);
1429
- return {
1430
- digest,
1431
- registry,
1432
- platform: DOCKER_PLATFORM
1433
- };
1434
- }
1435
- throw new Error(`Could not determine platform for ${imageRef}`);
959
+ throw createPlatformErrorMessage(imageRef, ["unknown (could not determine architecture)"]);
1436
960
  }
1437
961
  const platform2 = `${config.os}/${config.architecture}`;
1438
962
  if (platform2 === DOCKER_PLATFORM) {
@@ -1504,14 +1028,11 @@ Image: ${imageRef}
1504
1028
  Found platform(s): ${platforms.join(", ")}
1505
1029
  Required platform: ${DOCKER_PLATFORM}
1506
1030
 
1507
- To fix this issue:
1508
- 1. Manual fix:
1509
- a. Rebuild your image with the correct platform:
1510
- docker build --platform ${DOCKER_PLATFORM} -t ${imageRef} .
1511
- b. Push the rebuilt image to your remote registry:
1512
- docker push ${imageRef}
1513
-
1514
- 2. Or use the SDK to build with the correct platform automatically.`;
1031
+ To fix, either:
1032
+ 1. Rebuild the image for ${DOCKER_PLATFORM} and push it:
1033
+ docker buildx build --platform ${DOCKER_PLATFORM} -t ${imageRef} --push .
1034
+ 2. Or use a verifiable build (--verifiable --repo <repo> --commit <sha>), which
1035
+ builds server-side and needs no local Docker.`;
1515
1036
  return new Error(errorMsg);
1516
1037
  }
1517
1038
 
@@ -1616,6 +1137,8 @@ async function prepareRelease(options, logger) {
1616
1137
  logger.info(`Waiting ${REGISTRY_PROPAGATION_WAIT_SECONDS} seconds for registry propagation...`);
1617
1138
  await new Promise((resolve2) => setTimeout(resolve2, REGISTRY_PROPAGATION_WAIT_SECONDS * 1e3));
1618
1139
  } else {
1140
+ logger.info("Verifying image platform (linux/amd64)...");
1141
+ await getImageDigestAndName(imageRef);
1619
1142
  logger.info("Checking if image needs layering...");
1620
1143
  finalImageRef = await layerRemoteImageIfNeeded(
1621
1144
  {
@@ -1774,6 +1297,13 @@ function extractRegistryNameNoDocker(imageRef) {
1774
1297
  import { encodeFunctionData, encodeAbiParameters, decodeErrorResult } from "viem";
1775
1298
 
1776
1299
  // src/client/common/types/index.ts
1300
+ var EMPTY_CONTAINER_POLICY = {
1301
+ args: [],
1302
+ cmdOverride: [],
1303
+ env: [],
1304
+ envOverride: [],
1305
+ restartPolicy: ""
1306
+ };
1777
1307
  var noopLogger = {
1778
1308
  debug: () => {
1779
1309
  },
@@ -3014,32 +2544,1577 @@ async function executeBatch(options, logger = noopLogger) {
3014
2544
  } else {
3015
2545
  revertReason = callError.message || "Unknown reason";
3016
2546
  }
3017
- }
3018
- throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2547
+ }
2548
+ throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2549
+ }
2550
+ return hash;
2551
+ }
2552
+
2553
+ // src/client/common/contract/caller.ts
2554
+ import { encodeFunctionData as encodeFunctionData2, decodeErrorResult as decodeErrorResult2, bytesToHex } from "viem";
2555
+
2556
+ // src/client/common/utils/helpers.ts
2557
+ import { extractChain, createPublicClient, createWalletClient, http, fallback } from "viem";
2558
+ import { sepolia as sepolia2 } from "viem/chains";
2559
+ import { privateKeyToAccount } from "viem/accounts";
2560
+ function getChainFromID(chainID, fallback2 = sepolia2) {
2561
+ const id = Number(chainID);
2562
+ return extractChain({ chains: SUPPORTED_CHAINS, id }) || fallback2;
2563
+ }
2564
+ function addHexPrefix(value) {
2565
+ return value.startsWith("0x") ? value : `0x${value}`;
2566
+ }
2567
+ function stripHexPrefix(value) {
2568
+ return value.startsWith("0x") ? value.slice(2) : value;
2569
+ }
2570
+
2571
+ // src/client/common/abis/AppController.json
2572
+ var AppController_default = [
2573
+ {
2574
+ type: "constructor",
2575
+ inputs: [
2576
+ {
2577
+ name: "_version",
2578
+ type: "string",
2579
+ internalType: "string"
2580
+ },
2581
+ {
2582
+ name: "_permissionController",
2583
+ type: "address",
2584
+ internalType: "contractIPermissionController"
2585
+ },
2586
+ {
2587
+ name: "_releaseManager",
2588
+ type: "address",
2589
+ internalType: "contractIReleaseManager"
2590
+ },
2591
+ {
2592
+ name: "_computeAVSRegistrar",
2593
+ type: "address",
2594
+ internalType: "contractIComputeAVSRegistrar"
2595
+ },
2596
+ {
2597
+ name: "_computeOperator",
2598
+ type: "address",
2599
+ internalType: "contractIComputeOperator"
2600
+ },
2601
+ {
2602
+ name: "_appBeacon",
2603
+ type: "address",
2604
+ internalType: "contractIBeacon"
2605
+ }
2606
+ ],
2607
+ stateMutability: "nonpayable"
2608
+ },
2609
+ {
2610
+ type: "function",
2611
+ name: "API_PERMISSION_TYPEHASH",
2612
+ inputs: [],
2613
+ outputs: [
2614
+ {
2615
+ name: "",
2616
+ type: "bytes32",
2617
+ internalType: "bytes32"
2618
+ }
2619
+ ],
2620
+ stateMutability: "view"
2621
+ },
2622
+ {
2623
+ type: "function",
2624
+ name: "appBeacon",
2625
+ inputs: [],
2626
+ outputs: [
2627
+ {
2628
+ name: "",
2629
+ type: "address",
2630
+ internalType: "contractIBeacon"
2631
+ }
2632
+ ],
2633
+ stateMutability: "view"
2634
+ },
2635
+ {
2636
+ type: "function",
2637
+ name: "calculateApiPermissionDigestHash",
2638
+ inputs: [
2639
+ {
2640
+ name: "permission",
2641
+ type: "bytes4",
2642
+ internalType: "bytes4"
2643
+ },
2644
+ {
2645
+ name: "expiry",
2646
+ type: "uint256",
2647
+ internalType: "uint256"
2648
+ }
2649
+ ],
2650
+ outputs: [
2651
+ {
2652
+ name: "",
2653
+ type: "bytes32",
2654
+ internalType: "bytes32"
2655
+ }
2656
+ ],
2657
+ stateMutability: "view"
2658
+ },
2659
+ {
2660
+ type: "function",
2661
+ name: "calculateAppId",
2662
+ inputs: [
2663
+ {
2664
+ name: "deployer",
2665
+ type: "address",
2666
+ internalType: "address"
2667
+ },
2668
+ {
2669
+ name: "salt",
2670
+ type: "bytes32",
2671
+ internalType: "bytes32"
2672
+ }
2673
+ ],
2674
+ outputs: [
2675
+ {
2676
+ name: "",
2677
+ type: "address",
2678
+ internalType: "contractIApp"
2679
+ }
2680
+ ],
2681
+ stateMutability: "view"
2682
+ },
2683
+ {
2684
+ type: "function",
2685
+ name: "computeAVSRegistrar",
2686
+ inputs: [],
2687
+ outputs: [
2688
+ {
2689
+ name: "",
2690
+ type: "address",
2691
+ internalType: "contractIComputeAVSRegistrar"
2692
+ }
2693
+ ],
2694
+ stateMutability: "view"
2695
+ },
2696
+ {
2697
+ type: "function",
2698
+ name: "computeOperator",
2699
+ inputs: [],
2700
+ outputs: [
2701
+ {
2702
+ name: "",
2703
+ type: "address",
2704
+ internalType: "contractIComputeOperator"
2705
+ }
2706
+ ],
2707
+ stateMutability: "view"
2708
+ },
2709
+ {
2710
+ type: "function",
2711
+ name: "confirmUpgrade",
2712
+ inputs: [
2713
+ {
2714
+ name: "app",
2715
+ type: "address",
2716
+ internalType: "contractIApp"
2717
+ }
2718
+ ],
2719
+ outputs: [],
2720
+ stateMutability: "nonpayable"
2721
+ },
2722
+ {
2723
+ type: "function",
2724
+ name: "createApp",
2725
+ inputs: [
2726
+ {
2727
+ name: "salt",
2728
+ type: "bytes32",
2729
+ internalType: "bytes32"
2730
+ },
2731
+ {
2732
+ name: "release",
2733
+ type: "tuple",
2734
+ internalType: "structIAppController.Release",
2735
+ components: [
2736
+ {
2737
+ name: "rmsRelease",
2738
+ type: "tuple",
2739
+ internalType: "structIReleaseManagerTypes.Release",
2740
+ components: [
2741
+ {
2742
+ name: "artifacts",
2743
+ type: "tuple[]",
2744
+ internalType: "structIReleaseManagerTypes.Artifact[]",
2745
+ components: [
2746
+ {
2747
+ name: "digest",
2748
+ type: "bytes32",
2749
+ internalType: "bytes32"
2750
+ },
2751
+ {
2752
+ name: "registry",
2753
+ type: "string",
2754
+ internalType: "string"
2755
+ }
2756
+ ]
2757
+ },
2758
+ {
2759
+ name: "upgradeByTime",
2760
+ type: "uint32",
2761
+ internalType: "uint32"
2762
+ }
2763
+ ]
2764
+ },
2765
+ {
2766
+ name: "publicEnv",
2767
+ type: "bytes",
2768
+ internalType: "bytes"
2769
+ },
2770
+ {
2771
+ name: "encryptedEnv",
2772
+ type: "bytes",
2773
+ internalType: "bytes"
2774
+ },
2775
+ {
2776
+ name: "containerPolicy",
2777
+ type: "tuple",
2778
+ internalType: "structIAppController.ContainerPolicy",
2779
+ components: [
2780
+ {
2781
+ name: "args",
2782
+ type: "string[]",
2783
+ internalType: "string[]"
2784
+ },
2785
+ {
2786
+ name: "cmdOverride",
2787
+ type: "string[]",
2788
+ internalType: "string[]"
2789
+ },
2790
+ {
2791
+ name: "env",
2792
+ type: "tuple[]",
2793
+ internalType: "structIAppController.EnvVar[]",
2794
+ components: [
2795
+ {
2796
+ name: "key",
2797
+ type: "string",
2798
+ internalType: "string"
2799
+ },
2800
+ {
2801
+ name: "value",
2802
+ type: "string",
2803
+ internalType: "string"
2804
+ }
2805
+ ]
2806
+ },
2807
+ {
2808
+ name: "envOverride",
2809
+ type: "tuple[]",
2810
+ internalType: "structIAppController.EnvVar[]",
2811
+ components: [
2812
+ {
2813
+ name: "key",
2814
+ type: "string",
2815
+ internalType: "string"
2816
+ },
2817
+ {
2818
+ name: "value",
2819
+ type: "string",
2820
+ internalType: "string"
2821
+ }
2822
+ ]
2823
+ },
2824
+ {
2825
+ name: "restartPolicy",
2826
+ type: "string",
2827
+ internalType: "string"
2828
+ }
2829
+ ]
2830
+ }
2831
+ ]
2832
+ }
2833
+ ],
2834
+ outputs: [
2835
+ {
2836
+ name: "app",
2837
+ type: "address",
2838
+ internalType: "contractIApp"
2839
+ }
2840
+ ],
2841
+ stateMutability: "nonpayable"
2842
+ },
2843
+ {
2844
+ type: "function",
2845
+ name: "createAppWithIsolatedBilling",
2846
+ inputs: [
2847
+ {
2848
+ name: "salt",
2849
+ type: "bytes32",
2850
+ internalType: "bytes32"
2851
+ },
2852
+ {
2853
+ name: "release",
2854
+ type: "tuple",
2855
+ internalType: "structIAppController.Release",
2856
+ components: [
2857
+ {
2858
+ name: "rmsRelease",
2859
+ type: "tuple",
2860
+ internalType: "structIReleaseManagerTypes.Release",
2861
+ components: [
2862
+ {
2863
+ name: "artifacts",
2864
+ type: "tuple[]",
2865
+ internalType: "structIReleaseManagerTypes.Artifact[]",
2866
+ components: [
2867
+ {
2868
+ name: "digest",
2869
+ type: "bytes32",
2870
+ internalType: "bytes32"
2871
+ },
2872
+ {
2873
+ name: "registry",
2874
+ type: "string",
2875
+ internalType: "string"
2876
+ }
2877
+ ]
2878
+ },
2879
+ {
2880
+ name: "upgradeByTime",
2881
+ type: "uint32",
2882
+ internalType: "uint32"
2883
+ }
2884
+ ]
2885
+ },
2886
+ {
2887
+ name: "publicEnv",
2888
+ type: "bytes",
2889
+ internalType: "bytes"
2890
+ },
2891
+ {
2892
+ name: "encryptedEnv",
2893
+ type: "bytes",
2894
+ internalType: "bytes"
2895
+ },
2896
+ {
2897
+ name: "containerPolicy",
2898
+ type: "tuple",
2899
+ internalType: "structIAppController.ContainerPolicy",
2900
+ components: [
2901
+ {
2902
+ name: "args",
2903
+ type: "string[]",
2904
+ internalType: "string[]"
2905
+ },
2906
+ {
2907
+ name: "cmdOverride",
2908
+ type: "string[]",
2909
+ internalType: "string[]"
2910
+ },
2911
+ {
2912
+ name: "env",
2913
+ type: "tuple[]",
2914
+ internalType: "structIAppController.EnvVar[]",
2915
+ components: [
2916
+ {
2917
+ name: "key",
2918
+ type: "string",
2919
+ internalType: "string"
2920
+ },
2921
+ {
2922
+ name: "value",
2923
+ type: "string",
2924
+ internalType: "string"
2925
+ }
2926
+ ]
2927
+ },
2928
+ {
2929
+ name: "envOverride",
2930
+ type: "tuple[]",
2931
+ internalType: "structIAppController.EnvVar[]",
2932
+ components: [
2933
+ {
2934
+ name: "key",
2935
+ type: "string",
2936
+ internalType: "string"
2937
+ },
2938
+ {
2939
+ name: "value",
2940
+ type: "string",
2941
+ internalType: "string"
2942
+ }
2943
+ ]
2944
+ },
2945
+ {
2946
+ name: "restartPolicy",
2947
+ type: "string",
2948
+ internalType: "string"
2949
+ }
2950
+ ]
2951
+ }
2952
+ ]
2953
+ }
2954
+ ],
2955
+ outputs: [
2956
+ {
2957
+ name: "app",
2958
+ type: "address",
2959
+ internalType: "contractIApp"
2960
+ }
2961
+ ],
2962
+ stateMutability: "nonpayable"
2963
+ },
2964
+ {
2965
+ type: "function",
2966
+ name: "createEmptyApp",
2967
+ inputs: [
2968
+ {
2969
+ name: "salt",
2970
+ type: "bytes32",
2971
+ internalType: "bytes32"
2972
+ }
2973
+ ],
2974
+ outputs: [
2975
+ {
2976
+ name: "app",
2977
+ type: "address",
2978
+ internalType: "contractIApp"
2979
+ }
2980
+ ],
2981
+ stateMutability: "nonpayable"
2982
+ },
2983
+ {
2984
+ type: "function",
2985
+ name: "createEmptyAppWithIsolatedBilling",
2986
+ inputs: [
2987
+ {
2988
+ name: "salt",
2989
+ type: "bytes32",
2990
+ internalType: "bytes32"
2991
+ }
2992
+ ],
2993
+ outputs: [
2994
+ {
2995
+ name: "app",
2996
+ type: "address",
2997
+ internalType: "contractIApp"
2998
+ }
2999
+ ],
3000
+ stateMutability: "nonpayable"
3001
+ },
3002
+ {
3003
+ type: "function",
3004
+ name: "domainSeparator",
3005
+ inputs: [],
3006
+ outputs: [
3007
+ {
3008
+ name: "",
3009
+ type: "bytes32",
3010
+ internalType: "bytes32"
3011
+ }
3012
+ ],
3013
+ stateMutability: "view"
3014
+ },
3015
+ {
3016
+ type: "function",
3017
+ name: "getActiveAppCount",
3018
+ inputs: [
3019
+ {
3020
+ name: "user",
3021
+ type: "address",
3022
+ internalType: "address"
3023
+ }
3024
+ ],
3025
+ outputs: [
3026
+ {
3027
+ name: "",
3028
+ type: "uint32",
3029
+ internalType: "uint32"
3030
+ }
3031
+ ],
3032
+ stateMutability: "view"
3033
+ },
3034
+ {
3035
+ type: "function",
3036
+ name: "getAppCreator",
3037
+ inputs: [
3038
+ {
3039
+ name: "app",
3040
+ type: "address",
3041
+ internalType: "contractIApp"
3042
+ }
3043
+ ],
3044
+ outputs: [
3045
+ {
3046
+ name: "",
3047
+ type: "address",
3048
+ internalType: "address"
3049
+ }
3050
+ ],
3051
+ stateMutability: "view"
3052
+ },
3053
+ {
3054
+ type: "function",
3055
+ name: "getAppLatestReleaseBlockNumber",
3056
+ inputs: [
3057
+ {
3058
+ name: "app",
3059
+ type: "address",
3060
+ internalType: "contractIApp"
3061
+ }
3062
+ ],
3063
+ outputs: [
3064
+ {
3065
+ name: "",
3066
+ type: "uint32",
3067
+ internalType: "uint32"
3068
+ }
3069
+ ],
3070
+ stateMutability: "view"
3071
+ },
3072
+ {
3073
+ type: "function",
3074
+ name: "getAppOperatorSetId",
3075
+ inputs: [
3076
+ {
3077
+ name: "app",
3078
+ type: "address",
3079
+ internalType: "contractIApp"
3080
+ }
3081
+ ],
3082
+ outputs: [
3083
+ {
3084
+ name: "",
3085
+ type: "uint32",
3086
+ internalType: "uint32"
3087
+ }
3088
+ ],
3089
+ stateMutability: "view"
3090
+ },
3091
+ {
3092
+ type: "function",
3093
+ name: "getAppPendingReleaseBlockNumber",
3094
+ inputs: [
3095
+ {
3096
+ name: "app",
3097
+ type: "address",
3098
+ internalType: "contractIApp"
3099
+ }
3100
+ ],
3101
+ outputs: [
3102
+ {
3103
+ name: "",
3104
+ type: "uint32",
3105
+ internalType: "uint32"
3106
+ }
3107
+ ],
3108
+ stateMutability: "view"
3109
+ },
3110
+ {
3111
+ type: "function",
3112
+ name: "getAppStatus",
3113
+ inputs: [
3114
+ {
3115
+ name: "app",
3116
+ type: "address",
3117
+ internalType: "contractIApp"
3118
+ }
3119
+ ],
3120
+ outputs: [
3121
+ {
3122
+ name: "",
3123
+ type: "uint8",
3124
+ internalType: "enumIAppController.AppStatus"
3125
+ }
3126
+ ],
3127
+ stateMutability: "view"
3128
+ },
3129
+ {
3130
+ type: "function",
3131
+ name: "getApps",
3132
+ inputs: [
3133
+ {
3134
+ name: "offset",
3135
+ type: "uint256",
3136
+ internalType: "uint256"
3137
+ },
3138
+ {
3139
+ name: "limit",
3140
+ type: "uint256",
3141
+ internalType: "uint256"
3142
+ }
3143
+ ],
3144
+ outputs: [
3145
+ {
3146
+ name: "apps",
3147
+ type: "address[]",
3148
+ internalType: "contractIApp[]"
3149
+ },
3150
+ {
3151
+ name: "appConfigsMem",
3152
+ type: "tuple[]",
3153
+ internalType: "structIAppController.AppConfig[]",
3154
+ components: [
3155
+ {
3156
+ name: "creator",
3157
+ type: "address",
3158
+ internalType: "address"
3159
+ },
3160
+ {
3161
+ name: "operatorSetId",
3162
+ type: "uint32",
3163
+ internalType: "uint32"
3164
+ },
3165
+ {
3166
+ name: "latestReleaseBlockNumber",
3167
+ type: "uint32",
3168
+ internalType: "uint32"
3169
+ },
3170
+ {
3171
+ name: "pendingReleaseBlockNumber",
3172
+ type: "uint32",
3173
+ internalType: "uint32"
3174
+ },
3175
+ {
3176
+ name: "status",
3177
+ type: "uint8",
3178
+ internalType: "enumIAppController.AppStatus"
3179
+ }
3180
+ ]
3181
+ }
3182
+ ],
3183
+ stateMutability: "view"
3184
+ },
3185
+ {
3186
+ type: "function",
3187
+ name: "getAppsByBillingAccount",
3188
+ inputs: [
3189
+ {
3190
+ name: "account",
3191
+ type: "address",
3192
+ internalType: "address"
3193
+ },
3194
+ {
3195
+ name: "offset",
3196
+ type: "uint256",
3197
+ internalType: "uint256"
3198
+ },
3199
+ {
3200
+ name: "limit",
3201
+ type: "uint256",
3202
+ internalType: "uint256"
3203
+ }
3204
+ ],
3205
+ outputs: [
3206
+ {
3207
+ name: "apps",
3208
+ type: "address[]",
3209
+ internalType: "contractIApp[]"
3210
+ },
3211
+ {
3212
+ name: "appConfigsMem",
3213
+ type: "tuple[]",
3214
+ internalType: "structIAppController.AppConfig[]",
3215
+ components: [
3216
+ {
3217
+ name: "creator",
3218
+ type: "address",
3219
+ internalType: "address"
3220
+ },
3221
+ {
3222
+ name: "operatorSetId",
3223
+ type: "uint32",
3224
+ internalType: "uint32"
3225
+ },
3226
+ {
3227
+ name: "latestReleaseBlockNumber",
3228
+ type: "uint32",
3229
+ internalType: "uint32"
3230
+ },
3231
+ {
3232
+ name: "pendingReleaseBlockNumber",
3233
+ type: "uint32",
3234
+ internalType: "uint32"
3235
+ },
3236
+ {
3237
+ name: "status",
3238
+ type: "uint8",
3239
+ internalType: "enumIAppController.AppStatus"
3240
+ }
3241
+ ]
3242
+ }
3243
+ ],
3244
+ stateMutability: "view"
3245
+ },
3246
+ {
3247
+ type: "function",
3248
+ name: "getAppsByCreator",
3249
+ inputs: [
3250
+ {
3251
+ name: "creator",
3252
+ type: "address",
3253
+ internalType: "address"
3254
+ },
3255
+ {
3256
+ name: "offset",
3257
+ type: "uint256",
3258
+ internalType: "uint256"
3259
+ },
3260
+ {
3261
+ name: "limit",
3262
+ type: "uint256",
3263
+ internalType: "uint256"
3264
+ }
3265
+ ],
3266
+ outputs: [
3267
+ {
3268
+ name: "apps",
3269
+ type: "address[]",
3270
+ internalType: "contractIApp[]"
3271
+ },
3272
+ {
3273
+ name: "appConfigsMem",
3274
+ type: "tuple[]",
3275
+ internalType: "structIAppController.AppConfig[]",
3276
+ components: [
3277
+ {
3278
+ name: "creator",
3279
+ type: "address",
3280
+ internalType: "address"
3281
+ },
3282
+ {
3283
+ name: "operatorSetId",
3284
+ type: "uint32",
3285
+ internalType: "uint32"
3286
+ },
3287
+ {
3288
+ name: "latestReleaseBlockNumber",
3289
+ type: "uint32",
3290
+ internalType: "uint32"
3291
+ },
3292
+ {
3293
+ name: "pendingReleaseBlockNumber",
3294
+ type: "uint32",
3295
+ internalType: "uint32"
3296
+ },
3297
+ {
3298
+ name: "status",
3299
+ type: "uint8",
3300
+ internalType: "enumIAppController.AppStatus"
3301
+ }
3302
+ ]
3303
+ }
3304
+ ],
3305
+ stateMutability: "view"
3306
+ },
3307
+ {
3308
+ type: "function",
3309
+ name: "getAppsByDeveloper",
3310
+ inputs: [
3311
+ {
3312
+ name: "developer",
3313
+ type: "address",
3314
+ internalType: "address"
3315
+ },
3316
+ {
3317
+ name: "offset",
3318
+ type: "uint256",
3319
+ internalType: "uint256"
3320
+ },
3321
+ {
3322
+ name: "limit",
3323
+ type: "uint256",
3324
+ internalType: "uint256"
3325
+ }
3326
+ ],
3327
+ outputs: [
3328
+ {
3329
+ name: "apps",
3330
+ type: "address[]",
3331
+ internalType: "contractIApp[]"
3332
+ },
3333
+ {
3334
+ name: "appConfigsMem",
3335
+ type: "tuple[]",
3336
+ internalType: "structIAppController.AppConfig[]",
3337
+ components: [
3338
+ {
3339
+ name: "creator",
3340
+ type: "address",
3341
+ internalType: "address"
3342
+ },
3343
+ {
3344
+ name: "operatorSetId",
3345
+ type: "uint32",
3346
+ internalType: "uint32"
3347
+ },
3348
+ {
3349
+ name: "latestReleaseBlockNumber",
3350
+ type: "uint32",
3351
+ internalType: "uint32"
3352
+ },
3353
+ {
3354
+ name: "pendingReleaseBlockNumber",
3355
+ type: "uint32",
3356
+ internalType: "uint32"
3357
+ },
3358
+ {
3359
+ name: "status",
3360
+ type: "uint8",
3361
+ internalType: "enumIAppController.AppStatus"
3362
+ }
3363
+ ]
3364
+ }
3365
+ ],
3366
+ stateMutability: "view"
3367
+ },
3368
+ {
3369
+ type: "function",
3370
+ name: "getBillingAccount",
3371
+ inputs: [
3372
+ {
3373
+ name: "app",
3374
+ type: "address",
3375
+ internalType: "contractIApp"
3376
+ }
3377
+ ],
3378
+ outputs: [
3379
+ {
3380
+ name: "",
3381
+ type: "address",
3382
+ internalType: "address"
3383
+ }
3384
+ ],
3385
+ stateMutability: "view"
3386
+ },
3387
+ {
3388
+ type: "function",
3389
+ name: "getBillingType",
3390
+ inputs: [
3391
+ {
3392
+ name: "app",
3393
+ type: "address",
3394
+ internalType: "contractIApp"
3395
+ }
3396
+ ],
3397
+ outputs: [
3398
+ {
3399
+ name: "",
3400
+ type: "uint8",
3401
+ internalType: "enumIAppController.BillingType"
3402
+ }
3403
+ ],
3404
+ stateMutability: "view"
3405
+ },
3406
+ {
3407
+ type: "function",
3408
+ name: "getMaxActiveAppsPerUser",
3409
+ inputs: [
3410
+ {
3411
+ name: "user",
3412
+ type: "address",
3413
+ internalType: "address"
3414
+ }
3415
+ ],
3416
+ outputs: [
3417
+ {
3418
+ name: "",
3419
+ type: "uint32",
3420
+ internalType: "uint32"
3421
+ }
3422
+ ],
3423
+ stateMutability: "view"
3424
+ },
3425
+ {
3426
+ type: "function",
3427
+ name: "globalActiveAppCount",
3428
+ inputs: [],
3429
+ outputs: [
3430
+ {
3431
+ name: "",
3432
+ type: "uint32",
3433
+ internalType: "uint32"
3434
+ }
3435
+ ],
3436
+ stateMutability: "view"
3437
+ },
3438
+ {
3439
+ type: "function",
3440
+ name: "initialize",
3441
+ inputs: [
3442
+ {
3443
+ name: "admin",
3444
+ type: "address",
3445
+ internalType: "address"
3446
+ }
3447
+ ],
3448
+ outputs: [],
3449
+ stateMutability: "nonpayable"
3450
+ },
3451
+ {
3452
+ type: "function",
3453
+ name: "maxGlobalActiveApps",
3454
+ inputs: [],
3455
+ outputs: [
3456
+ {
3457
+ name: "",
3458
+ type: "uint32",
3459
+ internalType: "uint32"
3460
+ }
3461
+ ],
3462
+ stateMutability: "view"
3463
+ },
3464
+ {
3465
+ type: "function",
3466
+ name: "permissionController",
3467
+ inputs: [],
3468
+ outputs: [
3469
+ {
3470
+ name: "",
3471
+ type: "address",
3472
+ internalType: "contractIPermissionController"
3473
+ }
3474
+ ],
3475
+ stateMutability: "view"
3476
+ },
3477
+ {
3478
+ type: "function",
3479
+ name: "releaseManager",
3480
+ inputs: [],
3481
+ outputs: [
3482
+ {
3483
+ name: "",
3484
+ type: "address",
3485
+ internalType: "contractIReleaseManager"
3486
+ }
3487
+ ],
3488
+ stateMutability: "view"
3489
+ },
3490
+ {
3491
+ type: "function",
3492
+ name: "setMaxActiveAppsPerUser",
3493
+ inputs: [
3494
+ {
3495
+ name: "user",
3496
+ type: "address",
3497
+ internalType: "address"
3498
+ },
3499
+ {
3500
+ name: "limit",
3501
+ type: "uint32",
3502
+ internalType: "uint32"
3503
+ }
3504
+ ],
3505
+ outputs: [],
3506
+ stateMutability: "nonpayable"
3507
+ },
3508
+ {
3509
+ type: "function",
3510
+ name: "setMaxGlobalActiveApps",
3511
+ inputs: [
3512
+ {
3513
+ name: "limit",
3514
+ type: "uint32",
3515
+ internalType: "uint32"
3516
+ }
3517
+ ],
3518
+ outputs: [],
3519
+ stateMutability: "nonpayable"
3520
+ },
3521
+ {
3522
+ type: "function",
3523
+ name: "startApp",
3524
+ inputs: [
3525
+ {
3526
+ name: "app",
3527
+ type: "address",
3528
+ internalType: "contractIApp"
3529
+ }
3530
+ ],
3531
+ outputs: [],
3532
+ stateMutability: "nonpayable"
3533
+ },
3534
+ {
3535
+ type: "function",
3536
+ name: "stopApp",
3537
+ inputs: [
3538
+ {
3539
+ name: "app",
3540
+ type: "address",
3541
+ internalType: "contractIApp"
3542
+ }
3543
+ ],
3544
+ outputs: [],
3545
+ stateMutability: "nonpayable"
3546
+ },
3547
+ {
3548
+ type: "function",
3549
+ name: "suspend",
3550
+ inputs: [
3551
+ {
3552
+ name: "account",
3553
+ type: "address",
3554
+ internalType: "address"
3555
+ },
3556
+ {
3557
+ name: "apps",
3558
+ type: "address[]",
3559
+ internalType: "contractIApp[]"
3560
+ }
3561
+ ],
3562
+ outputs: [],
3563
+ stateMutability: "nonpayable"
3564
+ },
3565
+ {
3566
+ type: "function",
3567
+ name: "terminateApp",
3568
+ inputs: [
3569
+ {
3570
+ name: "app",
3571
+ type: "address",
3572
+ internalType: "contractIApp"
3573
+ }
3574
+ ],
3575
+ outputs: [],
3576
+ stateMutability: "nonpayable"
3577
+ },
3578
+ {
3579
+ type: "function",
3580
+ name: "terminateAppByAdmin",
3581
+ inputs: [
3582
+ {
3583
+ name: "app",
3584
+ type: "address",
3585
+ internalType: "contractIApp"
3586
+ }
3587
+ ],
3588
+ outputs: [],
3589
+ stateMutability: "nonpayable"
3590
+ },
3591
+ {
3592
+ type: "function",
3593
+ name: "updateAppMetadataURI",
3594
+ inputs: [
3595
+ {
3596
+ name: "app",
3597
+ type: "address",
3598
+ internalType: "contractIApp"
3599
+ },
3600
+ {
3601
+ name: "metadataURI",
3602
+ type: "string",
3603
+ internalType: "string"
3604
+ }
3605
+ ],
3606
+ outputs: [],
3607
+ stateMutability: "nonpayable"
3608
+ },
3609
+ {
3610
+ type: "function",
3611
+ name: "upgradeApp",
3612
+ inputs: [
3613
+ {
3614
+ name: "app",
3615
+ type: "address",
3616
+ internalType: "contractIApp"
3617
+ },
3618
+ {
3619
+ name: "release",
3620
+ type: "tuple",
3621
+ internalType: "structIAppController.Release",
3622
+ components: [
3623
+ {
3624
+ name: "rmsRelease",
3625
+ type: "tuple",
3626
+ internalType: "structIReleaseManagerTypes.Release",
3627
+ components: [
3628
+ {
3629
+ name: "artifacts",
3630
+ type: "tuple[]",
3631
+ internalType: "structIReleaseManagerTypes.Artifact[]",
3632
+ components: [
3633
+ {
3634
+ name: "digest",
3635
+ type: "bytes32",
3636
+ internalType: "bytes32"
3637
+ },
3638
+ {
3639
+ name: "registry",
3640
+ type: "string",
3641
+ internalType: "string"
3642
+ }
3643
+ ]
3644
+ },
3645
+ {
3646
+ name: "upgradeByTime",
3647
+ type: "uint32",
3648
+ internalType: "uint32"
3649
+ }
3650
+ ]
3651
+ },
3652
+ {
3653
+ name: "publicEnv",
3654
+ type: "bytes",
3655
+ internalType: "bytes"
3656
+ },
3657
+ {
3658
+ name: "encryptedEnv",
3659
+ type: "bytes",
3660
+ internalType: "bytes"
3661
+ },
3662
+ {
3663
+ name: "containerPolicy",
3664
+ type: "tuple",
3665
+ internalType: "structIAppController.ContainerPolicy",
3666
+ components: [
3667
+ {
3668
+ name: "args",
3669
+ type: "string[]",
3670
+ internalType: "string[]"
3671
+ },
3672
+ {
3673
+ name: "cmdOverride",
3674
+ type: "string[]",
3675
+ internalType: "string[]"
3676
+ },
3677
+ {
3678
+ name: "env",
3679
+ type: "tuple[]",
3680
+ internalType: "structIAppController.EnvVar[]",
3681
+ components: [
3682
+ {
3683
+ name: "key",
3684
+ type: "string",
3685
+ internalType: "string"
3686
+ },
3687
+ {
3688
+ name: "value",
3689
+ type: "string",
3690
+ internalType: "string"
3691
+ }
3692
+ ]
3693
+ },
3694
+ {
3695
+ name: "envOverride",
3696
+ type: "tuple[]",
3697
+ internalType: "structIAppController.EnvVar[]",
3698
+ components: [
3699
+ {
3700
+ name: "key",
3701
+ type: "string",
3702
+ internalType: "string"
3703
+ },
3704
+ {
3705
+ name: "value",
3706
+ type: "string",
3707
+ internalType: "string"
3708
+ }
3709
+ ]
3710
+ },
3711
+ {
3712
+ name: "restartPolicy",
3713
+ type: "string",
3714
+ internalType: "string"
3715
+ }
3716
+ ]
3717
+ }
3718
+ ]
3719
+ }
3720
+ ],
3721
+ outputs: [
3722
+ {
3723
+ name: "",
3724
+ type: "uint256",
3725
+ internalType: "uint256"
3726
+ }
3727
+ ],
3728
+ stateMutability: "nonpayable"
3729
+ },
3730
+ {
3731
+ type: "function",
3732
+ name: "version",
3733
+ inputs: [],
3734
+ outputs: [
3735
+ {
3736
+ name: "",
3737
+ type: "string",
3738
+ internalType: "string"
3739
+ }
3740
+ ],
3741
+ stateMutability: "view"
3742
+ },
3743
+ {
3744
+ type: "event",
3745
+ name: "AppCreated",
3746
+ inputs: [
3747
+ {
3748
+ name: "creator",
3749
+ type: "address",
3750
+ indexed: true,
3751
+ internalType: "address"
3752
+ },
3753
+ {
3754
+ name: "app",
3755
+ type: "address",
3756
+ indexed: true,
3757
+ internalType: "contractIApp"
3758
+ },
3759
+ {
3760
+ name: "operatorSetId",
3761
+ type: "uint32",
3762
+ indexed: false,
3763
+ internalType: "uint32"
3764
+ }
3765
+ ],
3766
+ anonymous: false
3767
+ },
3768
+ {
3769
+ type: "event",
3770
+ name: "AppMetadataURIUpdated",
3771
+ inputs: [
3772
+ {
3773
+ name: "app",
3774
+ type: "address",
3775
+ indexed: true,
3776
+ internalType: "contractIApp"
3777
+ },
3778
+ {
3779
+ name: "metadataURI",
3780
+ type: "string",
3781
+ indexed: false,
3782
+ internalType: "string"
3783
+ }
3784
+ ],
3785
+ anonymous: false
3786
+ },
3787
+ {
3788
+ type: "event",
3789
+ name: "AppStarted",
3790
+ inputs: [
3791
+ {
3792
+ name: "app",
3793
+ type: "address",
3794
+ indexed: true,
3795
+ internalType: "contractIApp"
3796
+ }
3797
+ ],
3798
+ anonymous: false
3799
+ },
3800
+ {
3801
+ type: "event",
3802
+ name: "AppStopped",
3803
+ inputs: [
3804
+ {
3805
+ name: "app",
3806
+ type: "address",
3807
+ indexed: true,
3808
+ internalType: "contractIApp"
3809
+ }
3810
+ ],
3811
+ anonymous: false
3812
+ },
3813
+ {
3814
+ type: "event",
3815
+ name: "AppSuspended",
3816
+ inputs: [
3817
+ {
3818
+ name: "app",
3819
+ type: "address",
3820
+ indexed: true,
3821
+ internalType: "contractIApp"
3822
+ }
3823
+ ],
3824
+ anonymous: false
3825
+ },
3826
+ {
3827
+ type: "event",
3828
+ name: "AppTerminated",
3829
+ inputs: [
3830
+ {
3831
+ name: "app",
3832
+ type: "address",
3833
+ indexed: true,
3834
+ internalType: "contractIApp"
3835
+ }
3836
+ ],
3837
+ anonymous: false
3838
+ },
3839
+ {
3840
+ type: "event",
3841
+ name: "AppTerminatedByAdmin",
3842
+ inputs: [
3843
+ {
3844
+ name: "app",
3845
+ type: "address",
3846
+ indexed: true,
3847
+ internalType: "contractIApp"
3848
+ }
3849
+ ],
3850
+ anonymous: false
3851
+ },
3852
+ {
3853
+ type: "event",
3854
+ name: "AppUpgraded",
3855
+ inputs: [
3856
+ {
3857
+ name: "app",
3858
+ type: "address",
3859
+ indexed: true,
3860
+ internalType: "contractIApp"
3861
+ },
3862
+ {
3863
+ name: "rmsReleaseId",
3864
+ type: "uint256",
3865
+ indexed: false,
3866
+ internalType: "uint256"
3867
+ },
3868
+ {
3869
+ name: "release",
3870
+ type: "tuple",
3871
+ indexed: false,
3872
+ internalType: "structIAppController.Release",
3873
+ components: [
3874
+ {
3875
+ name: "rmsRelease",
3876
+ type: "tuple",
3877
+ internalType: "structIReleaseManagerTypes.Release",
3878
+ components: [
3879
+ {
3880
+ name: "artifacts",
3881
+ type: "tuple[]",
3882
+ internalType: "structIReleaseManagerTypes.Artifact[]",
3883
+ components: [
3884
+ {
3885
+ name: "digest",
3886
+ type: "bytes32",
3887
+ internalType: "bytes32"
3888
+ },
3889
+ {
3890
+ name: "registry",
3891
+ type: "string",
3892
+ internalType: "string"
3893
+ }
3894
+ ]
3895
+ },
3896
+ {
3897
+ name: "upgradeByTime",
3898
+ type: "uint32",
3899
+ internalType: "uint32"
3900
+ }
3901
+ ]
3902
+ },
3903
+ {
3904
+ name: "publicEnv",
3905
+ type: "bytes",
3906
+ internalType: "bytes"
3907
+ },
3908
+ {
3909
+ name: "encryptedEnv",
3910
+ type: "bytes",
3911
+ internalType: "bytes"
3912
+ },
3913
+ {
3914
+ name: "containerPolicy",
3915
+ type: "tuple",
3916
+ internalType: "structIAppController.ContainerPolicy",
3917
+ components: [
3918
+ {
3919
+ name: "args",
3920
+ type: "string[]",
3921
+ internalType: "string[]"
3922
+ },
3923
+ {
3924
+ name: "cmdOverride",
3925
+ type: "string[]",
3926
+ internalType: "string[]"
3927
+ },
3928
+ {
3929
+ name: "env",
3930
+ type: "tuple[]",
3931
+ internalType: "structIAppController.EnvVar[]",
3932
+ components: [
3933
+ {
3934
+ name: "key",
3935
+ type: "string",
3936
+ internalType: "string"
3937
+ },
3938
+ {
3939
+ name: "value",
3940
+ type: "string",
3941
+ internalType: "string"
3942
+ }
3943
+ ]
3944
+ },
3945
+ {
3946
+ name: "envOverride",
3947
+ type: "tuple[]",
3948
+ internalType: "structIAppController.EnvVar[]",
3949
+ components: [
3950
+ {
3951
+ name: "key",
3952
+ type: "string",
3953
+ internalType: "string"
3954
+ },
3955
+ {
3956
+ name: "value",
3957
+ type: "string",
3958
+ internalType: "string"
3959
+ }
3960
+ ]
3961
+ },
3962
+ {
3963
+ name: "restartPolicy",
3964
+ type: "string",
3965
+ internalType: "string"
3966
+ }
3967
+ ]
3968
+ }
3969
+ ]
3970
+ }
3971
+ ],
3972
+ anonymous: false
3973
+ },
3974
+ {
3975
+ type: "event",
3976
+ name: "GlobalMaxActiveAppsSet",
3977
+ inputs: [
3978
+ {
3979
+ name: "limit",
3980
+ type: "uint32",
3981
+ indexed: false,
3982
+ internalType: "uint32"
3983
+ }
3984
+ ],
3985
+ anonymous: false
3986
+ },
3987
+ {
3988
+ type: "event",
3989
+ name: "Initialized",
3990
+ inputs: [
3991
+ {
3992
+ name: "version",
3993
+ type: "uint8",
3994
+ indexed: false,
3995
+ internalType: "uint8"
3996
+ }
3997
+ ],
3998
+ anonymous: false
3999
+ },
4000
+ {
4001
+ type: "event",
4002
+ name: "MaxActiveAppsSet",
4003
+ inputs: [
4004
+ {
4005
+ name: "user",
4006
+ type: "address",
4007
+ indexed: true,
4008
+ internalType: "address"
4009
+ },
4010
+ {
4011
+ name: "limit",
4012
+ type: "uint32",
4013
+ indexed: false,
4014
+ internalType: "uint32"
4015
+ }
4016
+ ],
4017
+ anonymous: false
4018
+ },
4019
+ {
4020
+ type: "event",
4021
+ name: "UpgradeConfirmed",
4022
+ inputs: [
4023
+ {
4024
+ name: "app",
4025
+ type: "address",
4026
+ indexed: true,
4027
+ internalType: "contractIApp"
4028
+ },
4029
+ {
4030
+ name: "pendingReleaseBlockNumber",
4031
+ type: "uint32",
4032
+ indexed: false,
4033
+ internalType: "uint32"
4034
+ }
4035
+ ],
4036
+ anonymous: false
4037
+ },
4038
+ {
4039
+ type: "error",
4040
+ name: "AccountHasActiveApps",
4041
+ inputs: []
4042
+ },
4043
+ {
4044
+ type: "error",
4045
+ name: "AppAlreadyExists",
4046
+ inputs: []
4047
+ },
4048
+ {
4049
+ type: "error",
4050
+ name: "AppDoesNotExist",
4051
+ inputs: []
4052
+ },
4053
+ {
4054
+ type: "error",
4055
+ name: "GlobalMaxActiveAppsExceeded",
4056
+ inputs: []
4057
+ },
4058
+ {
4059
+ type: "error",
4060
+ name: "InvalidAppStatus",
4061
+ inputs: []
4062
+ },
4063
+ {
4064
+ type: "error",
4065
+ name: "InvalidPermissions",
4066
+ inputs: []
4067
+ },
4068
+ {
4069
+ type: "error",
4070
+ name: "InvalidReleaseMetadataURI",
4071
+ inputs: []
4072
+ },
4073
+ {
4074
+ type: "error",
4075
+ name: "InvalidShortString",
4076
+ inputs: []
4077
+ },
4078
+ {
4079
+ type: "error",
4080
+ name: "InvalidSignature",
4081
+ inputs: []
4082
+ },
4083
+ {
4084
+ type: "error",
4085
+ name: "MaxActiveAppsExceeded",
4086
+ inputs: []
4087
+ },
4088
+ {
4089
+ type: "error",
4090
+ name: "MoreThanOneArtifact",
4091
+ inputs: []
4092
+ },
4093
+ {
4094
+ type: "error",
4095
+ name: "NoPendingUpgrade",
4096
+ inputs: []
4097
+ },
4098
+ {
4099
+ type: "error",
4100
+ name: "SignatureExpired",
4101
+ inputs: []
4102
+ },
4103
+ {
4104
+ type: "error",
4105
+ name: "StringTooLong",
4106
+ inputs: [
4107
+ {
4108
+ name: "str",
4109
+ type: "string",
4110
+ internalType: "string"
4111
+ }
4112
+ ]
3019
4113
  }
3020
- return hash;
3021
- }
3022
-
3023
- // src/client/common/contract/caller.ts
3024
- import { encodeFunctionData as encodeFunctionData2, decodeErrorResult as decodeErrorResult2, bytesToHex } from "viem";
3025
-
3026
- // src/client/common/utils/helpers.ts
3027
- import { extractChain, createPublicClient, createWalletClient, http, fallback } from "viem";
3028
- import { sepolia as sepolia2 } from "viem/chains";
3029
- import { privateKeyToAccount } from "viem/accounts";
3030
- function getChainFromID(chainID, fallback2 = sepolia2) {
3031
- const id = Number(chainID);
3032
- return extractChain({ chains: SUPPORTED_CHAINS, id }) || fallback2;
3033
- }
3034
- function addHexPrefix(value) {
3035
- return value.startsWith("0x") ? value : `0x${value}`;
3036
- }
3037
- function stripHexPrefix(value) {
3038
- return value.startsWith("0x") ? value.slice(2) : value;
3039
- }
4114
+ ];
3040
4115
 
3041
- // src/client/common/abis/AppController.json
3042
- var AppController_default = [
4116
+ // src/client/common/abis/AppController.v1_4.json
4117
+ var AppController_v1_4_default = [
3043
4118
  {
3044
4119
  type: "constructor",
3045
4120
  inputs: [
@@ -4723,6 +5798,38 @@ var PermissionController_default = [
4723
5798
  ];
4724
5799
 
4725
5800
  // src/client/common/contract/caller.ts
5801
+ function appControllerAbiFor(environmentConfig) {
5802
+ return environmentConfig.releaseAbiVersion === "v1.4" ? AppController_v1_4_default : AppController_default;
5803
+ }
5804
+ function supportsContainerPolicy(environmentConfig) {
5805
+ return environmentConfig.releaseAbiVersion !== "v1.4";
5806
+ }
5807
+ function containerPolicyForViem(policy = EMPTY_CONTAINER_POLICY) {
5808
+ return {
5809
+ args: policy.args,
5810
+ cmdOverride: policy.cmdOverride,
5811
+ env: policy.env.map((e) => ({ key: e.key, value: e.value })),
5812
+ envOverride: policy.envOverride.map((e) => ({ key: e.key, value: e.value })),
5813
+ restartPolicy: policy.restartPolicy
5814
+ };
5815
+ }
5816
+ function releaseForViem(release, environmentConfig) {
5817
+ const base = {
5818
+ rmsRelease: {
5819
+ artifacts: release.rmsRelease.artifacts.map((artifact) => ({
5820
+ digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
5821
+ registry: artifact.registry
5822
+ })),
5823
+ upgradeByTime: release.rmsRelease.upgradeByTime
5824
+ },
5825
+ publicEnv: bytesToHex(release.publicEnv),
5826
+ encryptedEnv: bytesToHex(release.encryptedEnv)
5827
+ };
5828
+ if (!supportsContainerPolicy(environmentConfig)) {
5829
+ return base;
5830
+ }
5831
+ return { ...base, containerPolicy: containerPolicyForViem(release.containerPolicy) };
5832
+ }
4726
5833
  function formatETH(wei) {
4727
5834
  const eth = Number(wei) / 1e18;
4728
5835
  const costStr = eth.toFixed(6);
@@ -4739,7 +5846,7 @@ async function calculateAppID(options) {
4739
5846
  const saltHex = `0x${paddedSaltHex}`;
4740
5847
  const appID = await publicClient.readContract({
4741
5848
  address: environmentConfig.appControllerAddress,
4742
- abi: AppController_default,
5849
+ abi: appControllerAbiFor(environmentConfig),
4743
5850
  functionName: "calculateAppId",
4744
5851
  args: [ownerAddress, saltHex]
4745
5852
  });
@@ -4763,22 +5870,12 @@ async function prepareDeployBatch(options, logger = noopLogger) {
4763
5870
  const saltHexString = bytesToHex(salt).slice(2);
4764
5871
  const paddedSaltHex = saltHexString.padStart(64, "0");
4765
5872
  const saltHex = `0x${paddedSaltHex}`;
4766
- const releaseForViem = {
4767
- rmsRelease: {
4768
- artifacts: release.rmsRelease.artifacts.map((artifact) => ({
4769
- digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
4770
- registry: artifact.registry
4771
- })),
4772
- upgradeByTime: release.rmsRelease.upgradeByTime
4773
- },
4774
- publicEnv: bytesToHex(release.publicEnv),
4775
- encryptedEnv: bytesToHex(release.encryptedEnv)
4776
- };
5873
+ const release_ = releaseForViem(release, environmentConfig);
4777
5874
  const functionName = options.billTo === "app" ? "createAppWithIsolatedBilling" : "createApp";
4778
5875
  const createData = encodeFunctionData2({
4779
- abi: AppController_default,
5876
+ abi: appControllerAbiFor(environmentConfig),
4780
5877
  functionName,
4781
- args: [saltHex, releaseForViem]
5878
+ args: [saltHex, release_]
4782
5879
  });
4783
5880
  const acceptAdminData = encodeFunctionData2({
4784
5881
  abi: PermissionController_default,
@@ -4866,21 +5963,11 @@ async function prepareUpgradeBatch(options) {
4866
5963
  publicLogs,
4867
5964
  needsPermissionChange
4868
5965
  } = options;
4869
- const releaseForViem = {
4870
- rmsRelease: {
4871
- artifacts: release.rmsRelease.artifacts.map((artifact) => ({
4872
- digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
4873
- registry: artifact.registry
4874
- })),
4875
- upgradeByTime: release.rmsRelease.upgradeByTime
4876
- },
4877
- publicEnv: bytesToHex(release.publicEnv),
4878
- encryptedEnv: bytesToHex(release.encryptedEnv)
4879
- };
5966
+ const release_ = releaseForViem(release, environmentConfig);
4880
5967
  const upgradeData = encodeFunctionData2({
4881
- abi: AppController_default,
5968
+ abi: appControllerAbiFor(environmentConfig),
4882
5969
  functionName: "upgradeApp",
4883
- args: [appID, releaseForViem]
5970
+ args: [appID, release_]
4884
5971
  });
4885
5972
  const executions = [
4886
5973
  {
@@ -5014,7 +6101,7 @@ ${pendingMessage}`);
5014
6101
  if (callError.data) {
5015
6102
  try {
5016
6103
  const decoded = decodeErrorResult2({
5017
- abi: AppController_default,
6104
+ abi: appControllerAbiFor(environmentConfig),
5018
6105
  data: callError.data
5019
6106
  });
5020
6107
  const formattedError = formatAppControllerError(decoded);
@@ -5067,7 +6154,7 @@ function formatAppControllerError(decoded) {
5067
6154
  async function getActiveAppCount(publicClient, environmentConfig, user) {
5068
6155
  const count = await publicClient.readContract({
5069
6156
  address: environmentConfig.appControllerAddress,
5070
- abi: AppController_default,
6157
+ abi: appControllerAbiFor(environmentConfig),
5071
6158
  functionName: "getActiveAppCount",
5072
6159
  args: [user]
5073
6160
  });
@@ -5076,7 +6163,7 @@ async function getActiveAppCount(publicClient, environmentConfig, user) {
5076
6163
  async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
5077
6164
  const quota = await publicClient.readContract({
5078
6165
  address: environmentConfig.appControllerAddress,
5079
- abi: AppController_default,
6166
+ abi: appControllerAbiFor(environmentConfig),
5080
6167
  functionName: "getMaxActiveAppsPerUser",
5081
6168
  args: [user]
5082
6169
  });
@@ -5085,7 +6172,7 @@ async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
5085
6172
  async function getBillingType(publicClient, environmentConfig, app) {
5086
6173
  const result = await publicClient.readContract({
5087
6174
  address: environmentConfig.appControllerAddress,
5088
- abi: AppController_default,
6175
+ abi: appControllerAbiFor(environmentConfig),
5089
6176
  functionName: "getBillingType",
5090
6177
  args: [app]
5091
6178
  });
@@ -5094,7 +6181,7 @@ async function getBillingType(publicClient, environmentConfig, app) {
5094
6181
  async function getAppsByBillingAccount(publicClient, environmentConfig, account, offset, limit) {
5095
6182
  const result = await publicClient.readContract({
5096
6183
  address: environmentConfig.appControllerAddress,
5097
- abi: AppController_default,
6184
+ abi: appControllerAbiFor(environmentConfig),
5098
6185
  functionName: "getAppsByBillingAccount",
5099
6186
  args: [account, offset, limit]
5100
6187
  });
@@ -5148,6 +6235,35 @@ async function undelegate(options, logger = noopLogger) {
5148
6235
  return hash;
5149
6236
  }
5150
6237
 
6238
+ // src/client/common/gas/insufficientGas.ts
6239
+ import { formatEther } from "viem";
6240
+ var InsufficientGasError = class extends Error {
6241
+ constructor(args) {
6242
+ const requiredEth = formatEther(args.requiredWei);
6243
+ const availableEth = formatEther(args.availableWei);
6244
+ super(
6245
+ `Insufficient ETH for gas: wallet ${args.address} has ${availableEth} ETH but this transaction needs ~${requiredEth} ETH.
6246
+ Compute credits do not pay on-chain gas \u2014 fund the wallet with ETH and retry.`
6247
+ );
6248
+ this.name = "InsufficientGasError";
6249
+ this.address = args.address;
6250
+ this.requiredWei = args.requiredWei;
6251
+ this.availableWei = args.availableWei;
6252
+ this.requiredEth = requiredEth;
6253
+ this.availableEth = availableEth;
6254
+ }
6255
+ };
6256
+ async function assertSufficientGas(args) {
6257
+ const availableWei = await args.publicClient.getBalance({ address: args.address });
6258
+ if (availableWei < args.gasEstimate.maxCostWei) {
6259
+ throw new InsufficientGasError({
6260
+ address: args.address,
6261
+ requiredWei: args.gasEstimate.maxCostWei,
6262
+ availableWei
6263
+ });
6264
+ }
6265
+ }
6266
+
5151
6267
  // src/client/common/utils/userapi.ts
5152
6268
  import axios2 from "axios";
5153
6269
 
@@ -5174,43 +6290,13 @@ async function calculatePermissionSignature(options) {
5174
6290
  });
5175
6291
  return { signature, digest };
5176
6292
  }
5177
- var generateBillingSigData = (product, expiry) => {
5178
- return {
5179
- domain: {
5180
- name: "EigenCloud Billing API",
5181
- version: "1"
5182
- },
5183
- types: {
5184
- BillingAuth: [
5185
- { name: "product", type: "string" },
5186
- { name: "expiry", type: "uint256" }
5187
- ]
5188
- },
5189
- primaryType: "BillingAuth",
5190
- message: {
5191
- product,
5192
- expiry
5193
- }
5194
- };
5195
- };
5196
- async function calculateBillingAuthSignature(options) {
5197
- const { walletClient, product, expiry } = options;
5198
- const account = walletClient.account;
5199
- if (!account) {
5200
- throw new Error("WalletClient must have an account attached");
5201
- }
5202
- const signature = await walletClient.signTypedData({
5203
- account,
5204
- ...generateBillingSigData(product, expiry)
5205
- });
5206
- return { signature, expiry };
5207
- }
5208
6293
 
5209
6294
  // src/client/common/utils/retry.ts
5210
6295
  import axios from "axios";
5211
6296
  var MAX_RETRIES = 5;
5212
6297
  var INITIAL_BACKOFF_MS = 1e3;
5213
6298
  var MAX_BACKOFF_MS = 3e4;
6299
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
5214
6300
  function sleep(ms) {
5215
6301
  return new Promise((resolve2) => setTimeout(resolve2, ms));
5216
6302
  }
@@ -5230,7 +6316,7 @@ async function requestWithRetry(config) {
5230
6316
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
5231
6317
  const res = await axios({ ...config, validateStatus: () => true });
5232
6318
  lastResponse = res;
5233
- if (res.status !== 429) {
6319
+ if (!RETRYABLE_STATUSES.has(res.status)) {
5234
6320
  return res;
5235
6321
  }
5236
6322
  if (attempt < MAX_RETRIES) {
@@ -5259,7 +6345,7 @@ var CanViewAppLogsPermission = "0x2fd3f2fe";
5259
6345
  var CanViewSensitiveAppInfoPermission = "0x0e67b22f";
5260
6346
  var CanUpdateAppProfilePermission = "0x036fef61";
5261
6347
  function getDefaultClientId() {
5262
- const version = true ? "1.0.0-devep8" : "0.0.0";
6348
+ const version = true ? "1.0.0" : "0.0.0";
5263
6349
  return `ecloud-sdk/v${version}`;
5264
6350
  }
5265
6351
  var UserApiClient = class {
@@ -5294,7 +6380,6 @@ var UserApiClient = class {
5294
6380
  status: app.app_status,
5295
6381
  ip: app.ip,
5296
6382
  machineType: app.machine_type,
5297
- hostname: app.hostname,
5298
6383
  profile: app.profile,
5299
6384
  metrics: app.metrics,
5300
6385
  evmAddresses,
@@ -5360,28 +6445,6 @@ var UserApiClient = class {
5360
6445
  status: app.app_status || app.App_Status || ""
5361
6446
  }));
5362
6447
  }
5363
- /**
5364
- * Get deployments for an app from the gRPC-gateway endpoint.
5365
- * Returns deployment records including upgrade_phase for tracking upgrade progress.
5366
- *
5367
- * Endpoint: GET /v1/apps/:appAddress/deployments
5368
- */
5369
- async getDeployments(appAddress) {
5370
- const endpoint = `${this.config.userApiServerURL}/v1/apps/${appAddress}/deployments`;
5371
- const response = await this.makeEIP712AuthenticatedRequest(endpoint);
5372
- const result = await response.json();
5373
- const deployments = result.deployments || [];
5374
- return deployments.map((dep) => ({
5375
- id: dep.id || "",
5376
- externalId: dep.external_id || dep.externalId || "",
5377
- endpoint: dep.endpoint || "",
5378
- releaseId: dep.release_id || dep.releaseId || "",
5379
- upgradePhase: dep.upgrade_phase || dep.upgradePhase || "",
5380
- replacesDeploymentId: dep.replaces_deployment_id || dep.replacesDeploymentId || "",
5381
- createdAt: dep.created_at || dep.createdAt || "",
5382
- updatedAt: dep.updated_at || dep.updatedAt || ""
5383
- }));
5384
- }
5385
6448
  /**
5386
6449
  * Upload app profile information with optional image
5387
6450
  *
@@ -5515,48 +6578,6 @@ Please check:
5515
6578
  "X-eigenx-expiry": expiry.toString()
5516
6579
  };
5517
6580
  }
5518
- /**
5519
- * Make an EIP-712 authenticated request to the gRPC-gateway endpoints.
5520
- * Uses the billing/compute auth pattern: Authorization + X-Account + X-Expiry headers.
5521
- */
5522
- async makeEIP712AuthenticatedRequest(url) {
5523
- const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
5524
- const { signature } = await calculateBillingAuthSignature({
5525
- walletClient: this.walletClient,
5526
- product: "compute",
5527
- expiry
5528
- });
5529
- const headers = {
5530
- Authorization: `Bearer ${signature}`,
5531
- "X-Account": this.address,
5532
- "X-Expiry": expiry.toString(),
5533
- "x-client-id": this.clientId
5534
- };
5535
- try {
5536
- const response = await requestWithRetry({
5537
- method: "GET",
5538
- url,
5539
- headers,
5540
- maxRedirects: 0,
5541
- withCredentials: true
5542
- });
5543
- const status = response.status;
5544
- if (status < 200 || status >= 300) {
5545
- const body = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
5546
- throw new Error(`gRPC-gateway request failed: ${status} - ${body}`);
5547
- }
5548
- return {
5549
- json: async () => response.data,
5550
- text: async () => typeof response.data === "string" ? response.data : JSON.stringify(response.data)
5551
- };
5552
- } catch (error) {
5553
- if (error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED") || error.message?.includes("ENOTFOUND") || error.cause) {
5554
- const cause = error.cause?.message || error.cause || error.message;
5555
- throw new Error(`Failed to connect to API at ${url}: ${cause}`);
5556
- }
5557
- throw error;
5558
- }
5559
- }
5560
6581
  // ==========================================================================
5561
6582
  // SIWE Session Management
5562
6583
  // ==========================================================================
@@ -5662,8 +6683,36 @@ function transformAppRelease(raw) {
5662
6683
 
5663
6684
  // src/client/common/contract/watcher.ts
5664
6685
  var WATCH_POLL_INTERVAL_SECONDS = 5;
6686
+ var WATCH_HEARTBEAT_INTERVAL_SECONDS = 30;
6687
+ var WATCH_DEFAULT_TIMEOUT_SECONDS = 10 * 60;
5665
6688
  var APP_STATUS_RUNNING = "Running";
5666
6689
  var APP_STATUS_FAILED = "Failed";
6690
+ var WatchTimeoutError = class extends Error {
6691
+ constructor(args) {
6692
+ super(
6693
+ args.message ?? `Timed out after ${args.elapsedSeconds}s waiting for app ${args.appId} (last status: ${args.lastStatus ?? "unknown"})`
6694
+ );
6695
+ this.name = "WatchTimeoutError";
6696
+ this.appId = args.appId;
6697
+ this.elapsedSeconds = args.elapsedSeconds;
6698
+ this.lastStatus = args.lastStatus;
6699
+ this.timeoutSeconds = args.timeoutSeconds;
6700
+ }
6701
+ };
6702
+ function resolveWatchTimeoutSeconds(explicit) {
6703
+ if (typeof explicit === "number" && Number.isFinite(explicit) && explicit > 0) {
6704
+ return Math.floor(explicit);
6705
+ }
6706
+ const raw = process.env.ECLOUD_WATCH_TIMEOUT_SECONDS;
6707
+ if (raw === void 0 || raw === "") {
6708
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6709
+ }
6710
+ const parsed = Number(raw);
6711
+ if (!Number.isFinite(parsed) || parsed <= 0) {
6712
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6713
+ }
6714
+ return Math.floor(parsed);
6715
+ }
5667
6716
  async function watchUntilRunning(options, logger) {
5668
6717
  const { walletClient, publicClient, environmentConfig, appId } = options;
5669
6718
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
@@ -5694,8 +6743,20 @@ async function watchUntilRunning(options, logger) {
5694
6743
  return false;
5695
6744
  };
5696
6745
  const startTime = Date.now();
6746
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
5697
6747
  let lastLoggedStatus;
6748
+ let lastHeartbeatAt = startTime;
5698
6749
  while (true) {
6750
+ const elapsedMs = Date.now() - startTime;
6751
+ const elapsed = Math.round(elapsedMs / 1e3);
6752
+ if (elapsed >= timeoutSeconds) {
6753
+ throw new WatchTimeoutError({
6754
+ appId,
6755
+ elapsedSeconds: elapsed,
6756
+ lastStatus: lastLoggedStatus,
6757
+ timeoutSeconds
6758
+ });
6759
+ }
5699
6760
  try {
5700
6761
  const info = await userApiClient.getInfos([appId], 1);
5701
6762
  if (info.length === 0) {
@@ -5705,62 +6766,35 @@ async function watchUntilRunning(options, logger) {
5705
6766
  const appInfo = info[0];
5706
6767
  const currentStatus = appInfo.status;
5707
6768
  const currentIP = appInfo.ip || "";
5708
- const elapsed = Math.round((Date.now() - startTime) / 1e3);
5709
6769
  if (currentStatus !== lastLoggedStatus) {
5710
6770
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
5711
6771
  lastLoggedStatus = currentStatus;
6772
+ lastHeartbeatAt = Date.now();
6773
+ } else if (Date.now() - lastHeartbeatAt >= WATCH_HEARTBEAT_INTERVAL_SECONDS * 1e3) {
6774
+ logger.info(`Status: ${currentStatus} (${elapsed}s)`);
6775
+ lastHeartbeatAt = Date.now();
5712
6776
  }
5713
6777
  if (stopCondition(currentStatus, currentIP)) {
5714
6778
  return currentIP || void 0;
5715
6779
  }
5716
6780
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5717
6781
  } catch (error) {
6782
+ if (error instanceof WatchTimeoutError) {
6783
+ throw error;
6784
+ }
5718
6785
  logger.warn(`Failed to fetch app info: ${error.message}`);
5719
6786
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5720
6787
  }
5721
6788
  }
5722
6789
  }
5723
6790
  var APP_STATUS_STOPPED = "Stopped";
5724
- var UPGRADE_PHASE_LABELS = {
5725
- provisioning: "Provisioning new instance",
5726
- health_check: "Running health checks",
5727
- draining: "Switching traffic & draining old instance",
5728
- complete: "Complete",
5729
- rolling_back: "Rolling back",
5730
- rollback_done: "Rollback complete",
5731
- db_handoff: "Database handoff",
5732
- // Prewarm-detach phases
5733
- awaiting_userdata: "Waiting for instance readiness",
5734
- draining_old: "Draining old instance",
5735
- detached: "Detaching storage",
5736
- attached_to_new: "Attaching storage to new instance",
5737
- finalizing: "Finalizing new instance",
5738
- flipping: "Switching traffic",
5739
- teardown_old: "Cleaning up old instance"
5740
- };
5741
- async function fetchUpgradePhase(userApiClient, appId) {
5742
- try {
5743
- const deployments = await userApiClient.getDeployments(appId);
5744
- if (deployments.length === 0) return void 0;
5745
- const sorted = [...deployments].sort((a, b) => {
5746
- const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0;
5747
- const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0;
5748
- return tb - ta;
5749
- });
5750
- return sorted[0].upgradePhase || void 0;
5751
- } catch {
5752
- return void 0;
5753
- }
5754
- }
5755
6791
  async function watchUntilUpgradeComplete(options, logger) {
5756
6792
  const { walletClient, publicClient, environmentConfig, appId } = options;
6793
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
5757
6794
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
5758
6795
  let initialStatus;
5759
6796
  let initialIP;
5760
6797
  let hasChanged = false;
5761
- const startTime = Date.now();
5762
- let lastLoggedStatus;
5763
- let lastLoggedPhase;
5764
6798
  const stopCondition = (status, ip) => {
5765
6799
  if (!initialStatus) {
5766
6800
  initialStatus = status;
@@ -5794,37 +6828,44 @@ async function watchUntilUpgradeComplete(options, logger) {
5794
6828
  }
5795
6829
  return false;
5796
6830
  };
6831
+ const startTime = Date.now();
6832
+ const deadline = startTime + timeoutSeconds * 1e3;
6833
+ let lastLoggedStatus;
6834
+ let lastObservedStatus;
5797
6835
  while (true) {
6836
+ if (Date.now() >= deadline) {
6837
+ const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
6838
+ throw new WatchTimeoutError({
6839
+ appId,
6840
+ lastStatus: lastObservedStatus,
6841
+ elapsedSeconds,
6842
+ timeoutSeconds
6843
+ });
6844
+ }
5798
6845
  try {
5799
- const [infoResult, upgradePhase] = await Promise.all([
5800
- userApiClient.getInfos([appId], 1),
5801
- fetchUpgradePhase(userApiClient, appId)
5802
- ]);
5803
- if (infoResult.length === 0) {
6846
+ const info = await userApiClient.getInfos([appId], 1);
6847
+ if (info.length === 0) {
5804
6848
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5805
6849
  continue;
5806
6850
  }
5807
- const appInfo = infoResult[0];
6851
+ const appInfo = info[0];
5808
6852
  const currentStatus = appInfo.status;
5809
6853
  const currentIP = appInfo.ip || "";
6854
+ lastObservedStatus = currentStatus;
5810
6855
  const elapsed = Math.round((Date.now() - startTime) / 1e3);
5811
- if (upgradePhase && upgradePhase !== lastLoggedPhase) {
5812
- const label = UPGRADE_PHASE_LABELS[upgradePhase] || upgradePhase;
5813
- logger.info(`Phase: ${label} (${elapsed}s)`);
5814
- lastLoggedPhase = upgradePhase;
5815
- }
5816
- if (!upgradePhase && currentStatus !== lastLoggedStatus) {
6856
+ if (currentStatus !== lastLoggedStatus) {
5817
6857
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
5818
6858
  lastLoggedStatus = currentStatus;
5819
6859
  }
5820
6860
  if (stopCondition(currentStatus, currentIP)) {
5821
- const totalElapsed = Math.round((Date.now() - startTime) / 1e3);
5822
- logger.info(`Upgrade completed in ${totalElapsed}s`);
5823
6861
  return;
5824
6862
  }
5825
6863
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5826
6864
  } catch (error) {
5827
- if (error.message?.includes("entered")) {
6865
+ if (error instanceof WatchTimeoutError) {
6866
+ throw error;
6867
+ }
6868
+ if (typeof error?.message === "string" && error.message.includes("Failed")) {
5828
6869
  throw error;
5829
6870
  }
5830
6871
  logger.warn(`Failed to fetch app info: ${error.message}`);
@@ -5935,14 +6976,13 @@ var ChainAddresses = {
5935
6976
  PermissionController: "0x44632dfBdCb6D3E21EF613B0ca8A6A0c618F5a37"
5936
6977
  }
5937
6978
  };
5938
- var PLATFORM_ENV_TESTNET_SEPOLIA = "testnet-sepolia";
5939
- var PLATFORM_ENV_MAINNET_ETHEREUM = "mainnet-ethereum";
5940
- var DEFAULT_APP_BASE_DOMAIN = "eigencloud.xyz";
5941
6979
  var ENVIRONMENTS = {
5942
6980
  "sepolia-dev": {
5943
6981
  name: "sepolia",
5944
6982
  build: "dev",
5945
6983
  appControllerAddress: "0xa86DC1C47cb2518327fB4f9A1627F51966c83B92",
6984
+ releaseAbiVersion: "v1.5",
6985
+ // AppController upgraded to v1.5.x (containerPolicy)
5946
6986
  permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
5947
6987
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
5948
6988
  kmsServerURL: "http://10.128.0.57:8080",
@@ -5950,14 +6990,14 @@ var ENVIRONMENTS = {
5950
6990
  defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com",
5951
6991
  usdcCreditsAddress: "0xbdA3897c3A428763B59015C64AB766c288C97376",
5952
6992
  baseUsdcCreditsAddress: "0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac",
5953
- baseRPCURL: "https://base-sepolia-rpc.publicnode.com",
5954
- platformEnv: PLATFORM_ENV_TESTNET_SEPOLIA,
5955
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
6993
+ baseRPCURL: "https://base-sepolia-rpc.publicnode.com"
5956
6994
  },
5957
6995
  sepolia: {
5958
6996
  name: "sepolia",
5959
6997
  build: "prod",
5960
6998
  appControllerAddress: "0x0dd810a6ffba6a9820a10d97b659f07d8d23d4E2",
6999
+ releaseAbiVersion: "v1.4",
7000
+ // prod still on AppController v1.4.0 (3-field Release)
5961
7001
  permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
5962
7002
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
5963
7003
  kmsServerURL: "http://10.128.15.203:8080",
@@ -5966,22 +7006,20 @@ var ENVIRONMENTS = {
5966
7006
  billingRPCURL: "https://ethereum-rpc.publicnode.com",
5967
7007
  usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d",
5968
7008
  baseUsdcCreditsAddress: "0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac",
5969
- baseRPCURL: "https://base-sepolia-rpc.publicnode.com",
5970
- platformEnv: PLATFORM_ENV_TESTNET_SEPOLIA,
5971
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7009
+ baseRPCURL: "https://base-sepolia-rpc.publicnode.com"
5972
7010
  },
5973
7011
  "mainnet-alpha": {
5974
7012
  name: "mainnet-alpha",
5975
7013
  build: "prod",
5976
7014
  appControllerAddress: "0xc38d35Fc995e75342A21CBd6D770305b142Fbe67",
7015
+ releaseAbiVersion: "v1.4",
7016
+ // prod still on AppController v1.4.0 (3-field Release)
5977
7017
  permissionControllerAddress: ChainAddresses[MAINNET_CHAIN_ID].PermissionController,
5978
7018
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
5979
7019
  kmsServerURL: "http://10.128.0.2:8080",
5980
7020
  userApiServerURL: "https://userapi-compute.eigencloud.xyz",
5981
7021
  defaultRPCURL: "https://ethereum-rpc.publicnode.com",
5982
- usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d",
5983
- platformEnv: PLATFORM_ENV_MAINNET_ETHEREUM,
5984
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7022
+ usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d"
5985
7023
  }
5986
7024
  };
5987
7025
  var CHAIN_ID_TO_ENVIRONMENT = {
@@ -6025,7 +7063,7 @@ function getEnvironmentConfig(environment, chainID) {
6025
7063
  };
6026
7064
  }
6027
7065
  function getBuildType() {
6028
- const buildTimeType = true ? "dev"?.toLowerCase() : void 0;
7066
+ const buildTimeType = true ? "prod"?.toLowerCase() : void 0;
6029
7067
  const runtimeType = process.env.BUILD_TYPE?.toLowerCase();
6030
7068
  const buildType = buildTimeType || runtimeType;
6031
7069
  if (buildType === "dev") {
@@ -6376,6 +7414,11 @@ async function prepareDeployFromVerifiableBuild(options, logger = defaultLogger)
6376
7414
  executions: batch.executions,
6377
7415
  authorizationList
6378
7416
  });
7417
+ await assertSufficientGas({
7418
+ publicClient: batch.publicClient,
7419
+ address: batch.walletClient.account.address,
7420
+ gasEstimate
7421
+ });
6379
7422
  const data = {
6380
7423
  appId: batch.appId,
6381
7424
  salt: batch.salt,
@@ -6634,6 +7677,11 @@ async function prepareDeploy(options, logger = defaultLogger) {
6634
7677
  executions: batch.executions,
6635
7678
  authorizationList
6636
7679
  });
7680
+ await assertSufficientGas({
7681
+ publicClient: batch.publicClient,
7682
+ address: batch.walletClient.account.address,
7683
+ gasEstimate
7684
+ });
6637
7685
  const data = {
6638
7686
  appId: batch.appId,
6639
7687
  salt: batch.salt,
@@ -6670,7 +7718,7 @@ async function executeDeploy(options) {
6670
7718
  }
6671
7719
  );
6672
7720
  }
6673
- async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
7721
+ async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
6674
7722
  return withSDKTelemetry(
6675
7723
  {
6676
7724
  functionName: "watchDeployment",
@@ -6686,7 +7734,8 @@ async function watchDeployment(appId, walletClient, publicClient, environmentCon
6686
7734
  walletClient,
6687
7735
  publicClient,
6688
7736
  environmentConfig,
6689
- appId
7737
+ appId,
7738
+ timeoutSeconds: opts?.timeoutSeconds
6690
7739
  },
6691
7740
  logger
6692
7741
  );
@@ -6782,6 +7831,11 @@ async function prepareUpgradeFromVerifiableBuild(options, logger = defaultLogger
6782
7831
  executions: batch.executions,
6783
7832
  authorizationList
6784
7833
  });
7834
+ await assertSufficientGas({
7835
+ publicClient: batch.publicClient,
7836
+ address: batch.walletClient.account.address,
7837
+ gasEstimate
7838
+ });
6785
7839
  const data = {
6786
7840
  appId: batch.appId,
6787
7841
  executions: batch.executions,
@@ -6971,6 +8025,11 @@ async function prepareUpgrade(options, logger = defaultLogger) {
6971
8025
  executions: batch.executions,
6972
8026
  authorizationList
6973
8027
  });
8028
+ await assertSufficientGas({
8029
+ publicClient: batch.publicClient,
8030
+ address: batch.walletClient.account.address,
8031
+ gasEstimate
8032
+ });
6974
8033
  const data = {
6975
8034
  appId: batch.appId,
6976
8035
  executions: batch.executions,
@@ -7005,7 +8064,7 @@ async function executeUpgrade(options) {
7005
8064
  }
7006
8065
  );
7007
8066
  }
7008
- async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
8067
+ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
7009
8068
  return withSDKTelemetry(
7010
8069
  {
7011
8070
  functionName: "watchUpgrade",
@@ -7021,7 +8080,8 @@ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig
7021
8080
  walletClient,
7022
8081
  publicClient,
7023
8082
  environmentConfig,
7024
- appId
8083
+ appId,
8084
+ timeoutSeconds: opts?.timeoutSeconds
7025
8085
  },
7026
8086
  logger
7027
8087
  );
@@ -7713,7 +8773,7 @@ function createAppModule(ctx) {
7713
8773
  }
7714
8774
  const account = walletClient.account;
7715
8775
  const environment = getEnvironmentConfig(ctx.environment);
7716
- const logger = getLogger(ctx.verbose);
8776
+ const logger = ctx.logger ?? getLogger(ctx.verbose);
7717
8777
  return {
7718
8778
  async create(opts) {
7719
8779
  return createApp(opts, logger);
@@ -7825,14 +8885,15 @@ function createAppModule(ctx) {
7825
8885
  imageRef: result.imageRef
7826
8886
  };
7827
8887
  },
7828
- async watchDeployment(appId) {
8888
+ async watchDeployment(appId, opts) {
7829
8889
  return watchDeployment(
7830
8890
  appId,
7831
8891
  walletClient,
7832
8892
  publicClient,
7833
8893
  environment,
7834
8894
  logger,
7835
- skipTelemetry
8895
+ skipTelemetry,
8896
+ opts
7836
8897
  );
7837
8898
  },
7838
8899
  // Granular upgrade control
@@ -7890,8 +8951,16 @@ function createAppModule(ctx) {
7890
8951
  imageRef: result.imageRef
7891
8952
  };
7892
8953
  },
7893
- async watchUpgrade(appId) {
7894
- return watchUpgrade(appId, walletClient, publicClient, environment, logger, skipTelemetry);
8954
+ async watchUpgrade(appId, opts) {
8955
+ return watchUpgrade(
8956
+ appId,
8957
+ walletClient,
8958
+ publicClient,
8959
+ environment,
8960
+ logger,
8961
+ skipTelemetry,
8962
+ opts
8963
+ );
7895
8964
  },
7896
8965
  // Profile management
7897
8966
  async setProfile(appId, profile) {