@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/index.js CHANGED
@@ -152,7 +152,6 @@ var ENV_SOURCE_SCRIPT_NAME = "compute-source-env.sh";
152
152
  var KMS_CLIENT_BINARY_NAME = "kms-client";
153
153
  var KMS_SIGNING_KEY_NAME = "kms-signing-public-key.pem";
154
154
  var TLS_KEYGEN_BINARY_NAME = "tls-keygen";
155
- var DRAIN_WATCHER_BINARY_NAME = "ecloud-drain-watcher";
156
155
  var CADDYFILE_NAME = "Caddyfile";
157
156
  var LAYERED_BUILD_DIR_PREFIX = "ecloud-layered-build";
158
157
 
@@ -439,12 +438,14 @@ USER root
439
438
  {{/if}}
440
439
 
441
440
  # Copy core TEE components
441
+ # CA bundle for kms-client / tls-keygen to validate HTTPS calls to
442
+ # eigencloud.xyz endpoints. Bundled at a non-standard path and consumed
443
+ # only via SSL_CERT_FILE in compute-source-env.sh, so the user's
444
+ # /etc/ssl/ is never touched.
445
+ COPY --from=alpine:3.20.10 /etc/ssl/certs/ca-certificates.crt /usr/local/share/eigenx-ca-certs.crt
442
446
  COPY compute-source-env.sh /usr/local/bin/
443
447
  COPY kms-client /usr/local/bin/
444
448
  COPY kms-signing-public-key.pem /usr/local/bin/
445
- {{#if includeDrainWatcher}}
446
- COPY ecloud-drain-watcher /usr/local/bin/
447
- {{/if}}
448
449
 
449
450
  {{#if includeTLS}}
450
451
  # Copy Caddy from official image
@@ -458,8 +459,7 @@ COPY Caddyfile /etc/caddy/
458
459
  {{#if originalUser}}
459
460
  # Make binaries executable (755 for executables, 644 for keys)
460
461
  RUN chmod 755 /usr/local/bin/compute-source-env.sh \\
461
- && chmod 755 /usr/local/bin/kms-client{{#if includeDrainWatcher}} \\
462
- && chmod 755 /usr/local/bin/ecloud-drain-watcher{{/if}}{{#if includeTLS}} \\
462
+ && chmod 755 /usr/local/bin/kms-client{{#if includeTLS}} \\
463
463
  && chmod 755 /usr/local/bin/tls-keygen \\
464
464
  && chmod 755 /usr/local/bin/caddy{{/if}} \\
465
465
  && chmod 644 /usr/local/bin/kms-signing-public-key.pem
@@ -469,8 +469,7 @@ ENV __ECLOUD_ORIGINAL_USER={{originalUser}}
469
469
  {{else}}
470
470
  # Make binaries executable (preserve existing permissions, just add execute)
471
471
  RUN chmod +x /usr/local/bin/compute-source-env.sh \\
472
- && chmod +x /usr/local/bin/kms-client{{#if includeDrainWatcher}} \\
473
- && chmod +x /usr/local/bin/ecloud-drain-watcher{{/if}}{{#if includeTLS}} \\
472
+ && chmod +x /usr/local/bin/kms-client{{#if includeTLS}} \\
474
473
  && chmod +x /usr/local/bin/tls-keygen{{/if}}
475
474
  {{/if}}
476
475
 
@@ -483,36 +482,8 @@ LABEL tee.launch_policy.log_redirect={{logRedirect}}
483
482
  LABEL tee.launch_policy.monitoring_memory_allow={{resourceUsageAllow}}
484
483
  {{/if}}
485
484
 
486
- # Allow-list the envvars the ecloud-platform sets via GCE \`tee-env-*\`
487
- # metadata. Without this label, Confidential Space's launcher rejects
488
- # any \`tee-env-*\` override at container-start with
489
- # "env var {...} is not allowed to be overridden on this image" and
490
- # exits with code 1 \u2014 which terminates the VM before the entrypoint
491
- # ever runs. User-supplied env vars flow through KMS (not tee-env-*)
492
- # and don't need to be listed here.
493
- #
494
- # Entries:
495
- # - ECLOUD_PD_EXPECTED set on PD-backed apps so the entrypoint
496
- # (compute-source-env.sh) knows to wait for
497
- # the persistent disk before exec'ing the
498
- # user workload.
499
- # - ECLOUD_PLATFORM_HOST the platform-routed hostname
500
- # (<addr>.<env>.eigencloud.xyz) so the
501
- # entrypoint's setup_tls can issue an ACME
502
- # cert for it. Injected by the CLI into
503
- # publicEnv at deploy/upgrade time and
504
- # propagated by ecloud-platform's
505
- # compute.go as a tee-env-* metadata key.
506
- #
507
- # The CS launcher parses this label as a comma-separated list
508
- # (go-tpm-tools/launcher/spec/launch_policy.go:185 \u2014 strings.Split
509
- # on ","). Quotes are not required; keep the value bare for
510
- # consistency with compute-tee's and eigenx-kms's existing labels.
511
- LABEL tee.launch_policy.allow_env_override=ECLOUD_PD_EXPECTED,ECLOUD_PLATFORM_HOST
512
-
513
485
  LABEL eigenx_cli_version={{ecloudCLIVersion}}
514
486
  LABEL eigenx_vm_image=eigen
515
- LABEL eigenx_container_contract=v1
516
487
 
517
488
  {{#if includeTLS}}
518
489
  # Expose both HTTP and HTTPS ports for Caddy
@@ -534,54 +505,11 @@ import Handlebars2 from "handlebars";
534
505
 
535
506
  // src/client/common/templates/compute-source-env.sh.tmpl
536
507
  var compute_source_env_sh_default = `#!/bin/sh
537
- # EigenCompute container entrypoint script
538
- # This script handles KMS secret fetching, TLS setup, and privilege dropping
539
- # before executing the user's application.
540
- #
541
- # Handlebars template variables (replaced at build time by the CLI):
542
- # kmsServerURL - URL of the KMS server
543
- # userAPIURL - URL of the user API (ecloud-platform)
544
- # The KMS signing public key is copied into the image as
545
- # /usr/local/bin/kms-signing-public-key.pem at layer-build time by the CLI.
546
- #
547
- # ecloud-platform divergence from compute-tee:
548
- # This script emits ECLOUD_READY / ECLOUD_FAIL / ECLOUD_AWAITING_USERDATA /
549
- # ECLOUD_DETACHED markers to stdout at key lifecycle points. The GCP
550
- # provisioner's serial-console watcher in ecloud-platform
551
- # (pkg/services/infraService/providers/gcp/compute.go) parses those
552
- # markers to gate "VM ready" and to coordinate the prewarm-detach
553
- # upgrade flow. Without the markers, the platform's waitForStartupReady
554
- # times out at ~10 minutes per deploy, rollback fires, and the VM is
555
- # deleted \u2014 seen in dev on 2026-05-04 with an older copy of this
556
- # template that lacked the markers.
557
- #
558
- # Prewarm-detach contract:
559
- # - If ECLOUD_PD_EXPECTED=1 and /mnt/disks/userdata is not present at boot,
560
- # emit ECLOUD_AWAITING_USERDATA and wait until the disk is attached.
561
- # - On SIGTERM (drain-requested), forward to child, wait for exit, sync
562
- # + unmount /mnt/disks/userdata, emit ECLOUD_DETACHED, exit.
563
- # - ECLOUD_READY is emitted once runtime is bootstrapped (same as before).
564
- # - ECLOUD_FAIL is emitted on any unrecoverable setup error.
565
- # Keep the markers on any line that resolves a lifecycle outcome.
566
- #
567
- # This file is kept in lockstep with
568
- # ecloud-platform/pkg/services/buildService/assets/compute-source-env.sh.tmpl
569
- # \u2014 if you change one, change the other. Differences vs the platform copy
570
- # are intentionally minimal:
571
- # - Handlebars placeholders use the CLI's naming (kmsServerURL,
572
- # userAPIURL) rather than the platform's (KMS_SERVER_URL,
573
- # USER_API_URL). (See top of file for real placeholder syntax \u2014
574
- # not repeated here so Handlebars doesn't expand it in this comment.)
575
- # - KMS signing key is read from a file the CLI copies into the image,
576
- # not heredoc-embedded in the script, because the CLI's image
577
- # layering writes it as a separate file (kms-signing-public-key.pem).
578
- # - TLS binary is \`tls-keygen\` (CLI-bundled) not \`tls-client\`.
579
-
580
508
  echo "compute-source-env.sh: Running setup script..."
581
509
 
582
510
  # Fetch and source environment variables from KMS
583
511
  echo "Fetching secrets from KMS..."
584
- if /usr/local/bin/kms-client \\
512
+ if SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt /usr/local/bin/kms-client \\
585
513
  --kms-server-url "{{kmsServerURL}}" \\
586
514
  --kms-signing-key-file /usr/local/bin/kms-signing-public-key.pem \\
587
515
  --userapi-url "{{userAPIURL}}" \\
@@ -592,187 +520,93 @@ if /usr/local/bin/kms-client \\
592
520
  else
593
521
  echo "compute-source-env.sh: ERROR - Failed to fetch environment variables from KMS"
594
522
  echo "compute-source-env.sh: Exiting - cannot start user workload without KMS secrets"
595
- echo "ECLOUD_FAIL kms_bootstrap"
596
523
  exit 1
597
524
  fi
598
525
 
599
- # issue_cert_for runs tls-keygen for a single hostname and copies the
600
- # produced fullchain/privkey into $1's output directory ($2). Returns
601
- # 0 on success, non-zero on any failure (caller decides whether that's
602
- # fatal). tls-keygen writes to /run/tls/{fullchain,privkey}.pem by
603
- # default; we move those to a per-host subdirectory so the two-site
604
- # Caddyfile can select the right cert by path.
605
- issue_cert_for() {
606
- local host="$1"
607
- local cert_dir="$2"
608
- local mnemonic="$3"
609
- local challenge="$4"
610
- local staging_flag="$5"
611
-
612
- echo "compute-source-env.sh: Obtaining TLS certificate for $host (challenge=$challenge)..."
613
- # Remove any stale default outputs from a prior tls-keygen run
614
- rm -f /run/tls/fullchain.pem /run/tls/privkey.pem
615
- if ! MNEMONIC="$mnemonic" DOMAIN="$host" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
616
- -challenge "$challenge" \\
617
- $staging_flag; then
618
- return 1
619
- fi
620
- mkdir -p "$cert_dir"
621
- mv /run/tls/fullchain.pem "$cert_dir/fullchain.pem"
622
- mv /run/tls/privkey.pem "$cert_dir/privkey.pem"
623
- echo "compute-source-env.sh: Cert for $host written to $cert_dir"
624
- }
625
-
626
- # Setup TLS: issues certs for
627
- # - ECLOUD_PLATFORM_HOST (platform-routed <addr>.<env>.eigencloud.xyz),
628
- # when the CLI/platform has set it
629
- # - DOMAIN (user-supplied custom domain), when set and non-localhost
630
- #
631
- # No client-side DNS precheck. Earlier versions tried to gate ACME on
632
- # "does this hostname resolve to my external IP" but that's wrong for
633
- # the platform-routing model (DNS points at the shared nginx NLB, not
634
- # the VM) and was preventing cert issuance on the production path.
635
- # tls-client (eigencompute-containers/tls-client) does its own DNS
636
- # poll before calling ACME and surfaces a clear error when challenges
637
- # can't reach the VM, which is the right place for that check \u2014
638
- # attempting it here from inside the VM cannot tell platform-routed
639
- # from compute-tee-routed apps.
526
+ # Setup TLS if tls-keygen is present (which means TLS was configured at build time)
640
527
  setup_tls() {
641
528
  # If tls-keygen isn't present, TLS wasn't configured during build
642
529
  if [ ! -x /usr/local/bin/tls-keygen ]; then
643
530
  echo "compute-source-env.sh: TLS not configured (no tls-keygen binary)"
644
531
  return 0
645
532
  fi
646
-
647
- local platform_host="\${ECLOUD_PLATFORM_HOST:-}"
648
- local user_domain="\${DOMAIN:-}"
533
+
534
+ local domain="\${DOMAIN:-}"
649
535
  local mnemonic="\${MNEMONIC:-}"
650
-
651
- # Normalize "localhost" DOMAIN to unset \u2014 compute-tee apps often
652
- # set DOMAIN=localhost as a sentinel for "no custom domain".
653
- if [ "$user_domain" = "localhost" ]; then
654
- user_domain=""
655
- fi
656
-
657
- # If the user set DOMAIN to the same value as the platform
658
- # hostname, collapse them. Without this we'd burn a second ACME
659
- # issuance for the exact same name (each VM boot counts against
660
- # Let's Encrypt's 5-certs-per-domain-per-week limit), and Caddy
661
- # would then refuse to start because two site blocks bound to
662
- # the same address is a config error. Also unset DOMAIN so the
663
- # DOMAIN site block in the Caddyfile falls back to its
664
- # "localhost.user.invalid" placeholder and stays dormant.
665
- if [ -n "$platform_host" ] && [ "$user_domain" = "$platform_host" ]; then
666
- echo "compute-source-env.sh: DOMAIN matches ECLOUD_PLATFORM_HOST; skipping duplicate cert"
667
- user_domain=""
668
- unset DOMAIN
669
- fi
670
-
671
- if [ -z "$platform_host" ] && [ -z "$user_domain" ]; then
672
- echo "compute-source-env.sh: TLS skipped (neither ECLOUD_PLATFORM_HOST nor DOMAIN set)"
673
- return 0
536
+
537
+ # Since tls-keygen is present, TLS is expected - validate requirements
538
+ if [ -z "$domain" ] || [ "$domain" = "localhost" ]; then
539
+ echo "compute-source-env.sh: ERROR - TLS binary present but DOMAIN not configured or is localhost"
540
+ echo "compute-source-env.sh: Set DOMAIN environment variable to a valid domain"
541
+ exit 1
674
542
  fi
675
-
543
+
676
544
  if [ -z "$mnemonic" ]; then
677
- echo "compute-source-env.sh: ERROR - TLS requested but MNEMONIC not available"
678
- echo "ECLOUD_FAIL tls_mnemonic_missing"
545
+ echo "compute-source-env.sh: ERROR - TLS binary present but MNEMONIC not available"
546
+ echo "compute-source-env.sh: Cannot obtain TLS certificate without mnemonic"
679
547
  exit 1
680
548
  fi
681
-
549
+
682
550
  if [ ! -x /usr/local/bin/caddy ]; then
683
- echo "compute-source-env.sh: ERROR - TLS requested but Caddy not found"
684
- echo "ECLOUD_FAIL tls_caddy_missing"
551
+ echo "compute-source-env.sh: ERROR - TLS binary present but Caddy not found"
685
552
  exit 1
686
553
  fi
687
-
554
+
555
+ echo "compute-source-env.sh: Setting up TLS for domain: $domain"
556
+
557
+ # Obtain TLS certificate using ACME
558
+ # Default to http-01, but allow override via ACME_CHALLENGE env var
688
559
  local challenge="\${ACME_CHALLENGE:-http-01}"
560
+
561
+ # Check if we should use staging (for testing)
689
562
  local staging_flag=""
690
563
  if [ "\${ACME_STAGING:-false}" = "true" ]; then
691
564
  staging_flag="-staging"
692
- echo "compute-source-env.sh: Using Let's Encrypt STAGING environment"
693
- fi
694
-
695
- local certs_issued=0
696
-
697
- if [ -n "$platform_host" ]; then
698
- if issue_cert_for "$platform_host" "/run/tls/platform" "$mnemonic" "$challenge" "$staging_flag"; then
699
- certs_issued=$((certs_issued + 1))
700
- else
701
- echo "compute-source-env.sh: ERROR - failed to issue cert for platform host $platform_host"
702
- echo "ECLOUD_FAIL tls_setup"
703
- exit 1
704
- fi
705
- fi
706
-
707
- if [ -n "$user_domain" ]; then
708
- if issue_cert_for "$user_domain" "/run/tls/domain" "$mnemonic" "$challenge" "$staging_flag"; then
709
- certs_issued=$((certs_issued + 1))
710
- else
711
- echo "compute-source-env.sh: ERROR - failed to issue cert for user domain $user_domain"
712
- echo "ECLOUD_FAIL tls_setup"
713
- exit 1
714
- fi
565
+ echo "compute-source-env.sh: Using Let's Encrypt STAGING environment (certificates won't be trusted)"
715
566
  fi
716
-
717
- if [ "$certs_issued" -eq 0 ]; then
718
- echo "compute-source-env.sh: no certs issued, skipping Caddy (app will serve plaintext until DNS is wired)"
719
- return 0
567
+
568
+ echo "compute-source-env.sh: Obtaining TLS certificate using $challenge challenge..."
569
+ # Pass the API URL for certificate persistence
570
+ if ! SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt \\
571
+ MNEMONIC="$mnemonic" DOMAIN="$domain" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
572
+ -challenge "$challenge" \\
573
+ $staging_flag; then
574
+ echo "compute-source-env.sh: ERROR - Failed to obtain TLS certificate"
575
+ echo "compute-source-env.sh: Certificate issuance failed for $domain"
576
+ exit 1
720
577
  fi
721
-
722
- # Caddy's validate step checks that every \`tls <cert> <key>\` file
723
- # exists, even on site blocks bound to dormant placeholder
724
- # hostnames. The default Caddyfile declares both a platform site
725
- # and a user-domain site; when only one is configured, the other
726
- # block's cert paths are never populated and validate fails with
727
- # "Invalid Caddyfile". Point the unused block at the issued
728
- # block's cert files so validate passes \u2014 the dormant block can't
729
- # receive real traffic (its hostname falls back to
730
- # localhost.{platform,user}.invalid, which Caddy routes by SNI and
731
- # never matches public traffic), so the symlink is never actually
732
- # presented. Skipped when a user-supplied Caddyfile is in use,
733
- # since we don't know what cert paths it references.
734
- if [ -d /run/tls/platform ] && [ ! -e /run/tls/domain/fullchain.pem ]; then
735
- mkdir -p /run/tls/domain
736
- ln -sf /run/tls/platform/fullchain.pem /run/tls/domain/fullchain.pem
737
- ln -sf /run/tls/platform/privkey.pem /run/tls/domain/privkey.pem
738
- elif [ -d /run/tls/domain ] && [ ! -e /run/tls/platform/fullchain.pem ]; then
739
- mkdir -p /run/tls/platform
740
- ln -sf /run/tls/domain/fullchain.pem /run/tls/platform/fullchain.pem
741
- ln -sf /run/tls/domain/privkey.pem /run/tls/platform/privkey.pem
578
+
579
+ echo "compute-source-env.sh: TLS certificate obtained successfully"
580
+
581
+ # Validate Caddyfile before starting
582
+ if ! /usr/local/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile 2>/dev/null; then
583
+ echo "compute-source-env.sh: ERROR - Invalid Caddyfile"
584
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
585
+ exit 1
742
586
  fi
743
-
744
- # Validate Caddyfile before starting. Don't redirect stderr \u2014 when
745
- # validate fails, Caddy's diagnostic is the only signal that lands
746
- # in ReadinessError.SerialTail, so silencing it leaves operators
747
- # staring at a bare "tls_invalid_caddyfile" with no detail.
748
- if [ -f /etc/caddy/Caddyfile ]; then
749
- if ! /usr/local/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile; then
750
- echo "compute-source-env.sh: ERROR - Invalid Caddyfile"
751
- echo "ECLOUD_FAIL tls_invalid_caddyfile"
587
+
588
+ # Start Caddy in background
589
+ echo "compute-source-env.sh: Starting Caddy reverse proxy..."
590
+
591
+ # Check if Caddy logs should be enabled
592
+ if [ "\${ENABLE_CADDY_LOGS:-false}" = "true" ]; then
593
+ if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1; then
594
+ echo "compute-source-env.sh: ERROR - Failed to start Caddy"
595
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
752
596
  exit 1
753
597
  fi
754
-
755
- echo "compute-source-env.sh: Starting Caddy reverse proxy..."
756
- if [ "\${ENABLE_CADDY_LOGS:-false}" = "true" ]; then
757
- if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1; then
758
- echo "compute-source-env.sh: ERROR - Failed to start Caddy"
759
- echo "ECLOUD_FAIL tls_caddy_start"
760
- exit 1
761
- fi
762
- else
763
- if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
764
- echo "compute-source-env.sh: ERROR - Failed to start Caddy"
765
- echo "ECLOUD_FAIL tls_caddy_start"
766
- exit 1
767
- fi
768
- fi
769
-
770
- sleep 2
771
- echo "compute-source-env.sh: Caddy started successfully"
772
598
  else
773
- echo "compute-source-env.sh: No Caddyfile found, skipping Caddy"
599
+ # Redirect Caddy output to /dev/null to silence logs
600
+ if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
601
+ echo "compute-source-env.sh: ERROR - Failed to start Caddy"
602
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
603
+ exit 1
604
+ fi
774
605
  fi
775
-
606
+
607
+ # Give Caddy a moment to fully initialize
608
+ sleep 2
609
+ echo "compute-source-env.sh: Caddy started successfully"
776
610
  return 0
777
611
  }
778
612
 
@@ -783,309 +617,15 @@ setup_tls
783
617
  export KMS_SERVER_URL="{{kmsServerURL}}"
784
618
  export KMS_PUBLIC_KEY="$(cat /usr/local/bin/kms-signing-public-key.pem)"
785
619
 
786
- # \u2500\u2500 Prewarm-detach: wait for PD if expected \u2500\u2500
787
- # Orchestrator sets ECLOUD_PD_EXPECTED=1 on apps using StorageBackend=pd.
788
- # When the prewarm path is used, the new VM boots WITHOUT the disk; we
789
- # signal awaiting-userdata and poll until the disk is attached.
790
- USERDATA_MOUNT="/mnt/disks/userdata"
791
- USERDATA_DEV="/dev/disk/by-id/google-persistent_storage_1"
792
-
793
- # userdata_mount_backing_dev resolves the kernel device that currently
794
- # backs $USERDATA_MOUNT (or empty string if not mounted). The output is
795
- # canonicalized via readlink so we can compare against $USERDATA_DEV
796
- # without caring whether the mount table records the symlinked
797
- # /dev/disk/by-id/... path or the underlying /dev/sdb-style path.
798
- #
799
- # Tries findmnt first (clean structured output), falls back to parsing
800
- # /proc/self/mountinfo so we don't add a hard dependency on util-linux
801
- # being present in the image.
802
- userdata_mount_backing_dev() {
803
- if command -v findmnt >/dev/null 2>&1; then
804
- local src
805
- src=$(findmnt -n -o SOURCE -- "$USERDATA_MOUNT" 2>/dev/null) || return 0
806
- [ -n "$src" ] || return 0
807
- readlink -f "$src" 2>/dev/null || echo "$src"
808
- return 0
809
- fi
810
- # /proc/self/mountinfo line shape (kernel docs): mount-id parent-id
811
- # major:minor root mount-point ... So column 5 is the mount point and
812
- # the device path lives in the optional fields after the "-" separator.
813
- # We just need the major:minor \u2192 device mapping; awk + a /sys lookup
814
- # is cleaner than parsing the mountinfo's optional-field section.
815
- local maj_min dev_basename
816
- maj_min=$(awk -v mp="$USERDATA_MOUNT" '$5 == mp { print $3 }' /proc/self/mountinfo 2>/dev/null | head -1)
817
- [ -n "$maj_min" ] || return 0
818
- dev_basename=$(readlink "/sys/dev/block/\${maj_min}" 2>/dev/null | awk -F/ '{print $NF}')
819
- [ -n "$dev_basename" ] || return 0
820
- readlink -f "/dev/\${dev_basename}" 2>/dev/null || echo "/dev/\${dev_basename}"
821
- }
822
-
823
- # expected_userdata_dev resolves $USERDATA_DEV through any symlinks so
824
- # string-compare against userdata_mount_backing_dev is meaningful even
825
- # when the kernel hands us a /dev/nvme0n1-style path while
826
- # /dev/disk/by-id/google-persistent_storage_1 is the symlink GCE
827
- # publishes.
828
- expected_userdata_dev() {
829
- if [ -e "$USERDATA_DEV" ]; then
830
- readlink -f "$USERDATA_DEV" 2>/dev/null || echo "$USERDATA_DEV"
831
- else
832
- echo "$USERDATA_DEV"
833
- fi
834
- }
835
-
836
- wait_for_userdata() {
837
- if [ "\${ECLOUD_PD_EXPECTED:-0}" != "1" ]; then
838
- return 0
839
- fi
840
- if mountpoint -q "$USERDATA_MOUNT" 2>/dev/null; then
841
- # The mount slot is taken \u2014 but is it taken by the PD we're
842
- # about to attach, or by something else? The Confidential Space
843
- # TEE runtime's SetupSecondaryEncryptedVolume step probes for a
844
- # secondary GCE disk once at boot, doesn't see it (because the
845
- # orchestrator hasn't called AttachPD yet \u2014 the contract says
846
- # the new VM signals awaiting-userdata FIRST), and falls back
847
- # to mounting an encrypted folder on the BOOT disk at this same
848
- # path. If we accept that mount as-is, the user app boots on a
849
- # fresh empty filesystem and the orchestrator's late AttachPD
850
- # silently lands on a VM that's already serving from boot disk.
851
- # See docs/solutions/2026-05-15-prewarm-detach-pd-preservation-boot-race.md
852
- # in ecloud-platform.
853
- local backing expected
854
- backing=$(userdata_mount_backing_dev)
855
- expected=$(expected_userdata_dev)
856
- if [ -n "$backing" ] && [ "$backing" = "$expected" ]; then
857
- echo "compute-source-env.sh: userdata already mounted at $USERDATA_MOUNT (backed by $backing)"
858
- return 0
859
- fi
860
- # Mount slot taken, but NOT by our PD. This is the runtime
861
- # premount on boot disk. Try to recover by lazy-unmounting (the
862
- # filesystem is fresh and unused \u2014 no app has written to it
863
- # yet), then fall through to the normal wait-for-PD loop.
864
- echo "compute-source-env.sh: WARNING - $USERDATA_MOUNT premounted by TEE runtime fallback (backing=\${backing:-unknown}, expected=$expected); attempting recovery"
865
- if ! umount -l "$USERDATA_MOUNT" 2>/dev/null; then
866
- # Lazy unmount should never fail under normal circumstances \u2014
867
- # it doesn't wait for open files, just detaches the namespace.
868
- # If it did fail, we have no path forward: the orchestrator's
869
- # strict-mode WaitAwaitingUserdata gate (ecloud-platform PR
870
- # #174) catches our ECLOUD_FAIL within one poll tick and
871
- # rolls back, leaving the user's data safe on the old VM.
872
- echo "ECLOUD_FAIL pd_runtime_premount_unrecoverable"
873
- exit 1
874
- fi
875
- echo "compute-source-env.sh: unmounted runtime fallback at $USERDATA_MOUNT, waiting for real PD"
876
- fi
877
- # Refuse to proceed if the tools we need for safe first-attach
878
- # detection are missing. Without blkid we cannot tell an empty new
879
- # disk from an already-formatted one \u2014 running mkfs.ext4 on the
880
- # latter would destroy data.
881
- if ! command -v blkid >/dev/null 2>&1; then
882
- echo "ECLOUD_FAIL pd_tools_missing"
883
- exit 1
884
- fi
885
- echo "ECLOUD_AWAITING_USERDATA"
886
- echo "compute-source-env.sh: waiting for PD at $USERDATA_DEV..."
887
- # Poll for up to 10 minutes (120 * 5s). The orchestrator's overall
888
- # attach timeout is shorter; the ceiling here just bounds the wait
889
- # for manual / diagnostic scenarios.
890
- local i=0
891
- local mount_failures=0
892
- while [ "$i" -lt 120 ]; do
893
- if [ -e "$USERDATA_DEV" ]; then
894
- mkdir -p "$USERDATA_MOUNT"
895
- if mount -o noatime "$USERDATA_DEV" "$USERDATA_MOUNT" 2>/dev/null; then
896
- echo "compute-source-env.sh: PD mounted at $USERDATA_MOUNT"
897
- return 0
898
- fi
899
- # Disk present but mount failed. Check whether it has a
900
- # recognized filesystem. \`blkid -s TYPE -o value\` prints the
901
- # FS type (empty if none). We only mkfs when there is
902
- # demonstrably NO filesystem \u2014 never on the basis of blkid
903
- # returning non-zero alone, which could mean "blkid missing"
904
- # or "device busy".
905
- local fstype
906
- fstype=$(blkid -s TYPE -o value "$USERDATA_DEV" 2>/dev/null)
907
- if [ -z "$fstype" ]; then
908
- echo "compute-source-env.sh: formatting $USERDATA_DEV (first attach)"
909
- mkfs.ext4 -F -L eclouddata "$USERDATA_DEV" >/dev/null 2>&1 || {
910
- echo "ECLOUD_FAIL pd_mkfs_failed"
911
- exit 1
912
- }
913
- mount -o noatime "$USERDATA_DEV" "$USERDATA_MOUNT" || {
914
- echo "ECLOUD_FAIL pd_mount_after_format_failed"
915
- exit 1
916
- }
917
- return 0
918
- fi
919
- # Disk has a filesystem but mount still failed. Give it a
920
- # few retries to cover transient cases (device busy, udev
921
- # still settling), but don't pretend this is an attach
922
- # timeout if it persists.
923
- mount_failures=$((mount_failures + 1))
924
- if [ "$mount_failures" -ge 6 ]; then
925
- echo "ECLOUD_FAIL pd_mount_failed"
926
- exit 1
927
- fi
928
- else
929
- # Device disappeared (e.g. udev re-enumeration between
930
- # attach and mount). Reset the consecutive-failure counter
931
- # so only true back-to-back mount failures trip
932
- # pd_mount_failed; a device blip should not steal retries.
933
- mount_failures=0
934
- fi
935
- i=$((i + 1))
936
- sleep 5
937
- done
938
- echo "ECLOUD_FAIL pd_attach_timeout"
939
- exit 1
940
- }
941
-
942
- wait_for_userdata
943
-
944
- # \u2500\u2500 Prewarm-detach: install SIGTERM handler for graceful drain \u2500\u2500
945
- # Orchestrator signals drain by setting the instance metadata key
946
- # ECLOUD_DRAIN_REQUESTED=1, which a host-level agent translates into
947
- # SIGTERM on PID 1. On SIGTERM we:
948
- # 1. Forward to the child (wakes the user's app for graceful exit)
949
- # 2. Wait for child exit
950
- # 3. Sync + unmount the PD
951
- # 4. Emit ECLOUD_DETACHED so the orchestrator can proceed to detach
952
- CHILD_PID=""
953
- _DRAIN_IN_PROGRESS=0
954
-
955
- drain_handler() {
956
- # Guard against re-entry if SIGTERM arrives twice (e.g. both the
957
- # drain_watcher and an external signal fire in quick succession).
958
- if [ "$_DRAIN_IN_PROGRESS" = "1" ]; then
959
- return 0
960
- fi
961
- _DRAIN_IN_PROGRESS=1
962
- echo "compute-source-env.sh: received drain signal, forwarding to child pgid=$CHILD_PID"
963
- if [ -n "$CHILD_PID" ]; then
964
- # Send to the process group so intermediate wrappers (su, sh -c,
965
- # etc.) don't swallow the signal. The leading \`-\` targets the
966
- # pgid, which equals the direct child's pid for a shell-backgrounded
967
- # process. Fall back to the pid alone if pgid signaling fails
968
- # (e.g. kernel older than 3.9 or PID namespace edge cases).
969
- kill -TERM -"$CHILD_PID" 2>/dev/null || kill -TERM "$CHILD_PID" 2>/dev/null || true
970
- # Give the app up to 30s to exit cleanly.
971
- local i=0
972
- while [ "$i" -lt 30 ] && kill -0 "$CHILD_PID" 2>/dev/null; do
973
- i=$((i + 1))
974
- sleep 1
975
- done
976
- if kill -0 "$CHILD_PID" 2>/dev/null; then
977
- echo "compute-source-env.sh: child did not exit in 30s, sending SIGKILL"
978
- kill -KILL -"$CHILD_PID" 2>/dev/null || kill -KILL "$CHILD_PID" 2>/dev/null || true
979
- # Reap the process so its in-flight I/O is flushed to the
980
- # filesystem before we sync + unmount. SIGKILL schedules
981
- # death; wait guarantees it's complete.
982
- wait "$CHILD_PID" 2>/dev/null || true
983
- fi
984
- fi
985
- if [ "\${ECLOUD_PD_EXPECTED:-0}" = "1" ] && mountpoint -q "$USERDATA_MOUNT" 2>/dev/null; then
986
- sync
987
- if umount "$USERDATA_MOUNT" 2>/dev/null; then
988
- echo "compute-source-env.sh: unmounted $USERDATA_MOUNT cleanly"
989
- else
990
- # Force lazy unmount as last resort \u2014 orchestrator still needs
991
- # the DETACHED signal to proceed.
992
- umount -l "$USERDATA_MOUNT" 2>/dev/null || true
993
- echo "compute-source-env.sh: WARNING - used lazy unmount on $USERDATA_MOUNT"
994
- fi
995
- # ECLOUD_DETACHED is strictly a PD-lifecycle signal. Only emit
996
- # it when we actually had a PD mount in play, so serial-log
997
- # parsers and alerting for non-PD apps don't see spurious
998
- # lifecycle markers on routine container SIGTERM.
999
- echo "ECLOUD_DETACHED"
1000
- fi
1001
- # Always exit 0: drain is a managed shutdown and the orchestrator
1002
- # waits on ECLOUD_DETACHED, not the container exit code. Forwarding
1003
- # the child's exit status here would make a crash-during-drain look
1004
- # like a drain failure to whatever reads the container exit code.
1005
- exit 0
1006
- }
1007
- trap drain_handler TERM
1008
-
1009
- # \u2500\u2500 Prewarm-detach: background drain watcher \u2500\u2500
1010
- # Container metadata delivery in Confidential Space is limited, so we
1011
- # poll the instance metadata server for ECLOUD_DRAIN_REQUESTED and
1012
- # raise SIGTERM on ourselves when it flips to "1".
1013
- #
1014
- # Try wget first (present in most Alpine bases), fall back to curl.
1015
- # If neither is present, drain watcher is disabled \u2014 the orchestrator
1016
- # will hit its drain timeout and fail the upgrade explicitly, which is
1017
- # the correct behavior (we cannot silently ignore a drain request).
1018
- _fetch_drain_flag() {
1019
- local url="http://metadata.google.internal/computeMetadata/v1/instance/attributes/ECLOUD_DRAIN_REQUESTED"
1020
- if command -v wget >/dev/null 2>&1; then
1021
- wget -q --tries=1 --timeout=2 --header='Metadata-Flavor: Google' -O - "$url" 2>/dev/null
1022
- elif command -v curl >/dev/null 2>&1; then
1023
- curl -sf --max-time 2 -H 'Metadata-Flavor: Google' "$url" 2>/dev/null
1024
- else
1025
- return 2
1026
- fi
1027
- }
1028
-
1029
- drain_watcher() {
1030
- # Preflight: confirm we have an HTTP client
1031
- if ! _fetch_drain_flag >/dev/null 2>&1; then
1032
- # Either no http client available OR metadata server not
1033
- # responding yet. If no client, give up and log; otherwise the
1034
- # loop below will retry.
1035
- if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then
1036
- echo "compute-source-env.sh: WARNING - no wget/curl; drain_watcher disabled"
1037
- return 0
1038
- fi
1039
- fi
1040
- while true; do
1041
- local v
1042
- v=$(_fetch_drain_flag || true)
1043
- if [ "$v" = "1" ]; then
1044
- echo "compute-source-env.sh: drain_watcher saw ECLOUD_DRAIN_REQUESTED=1, signaling PID 1"
1045
- # The CS launcher runs this script directly as PID 1, so
1046
- # kill -TERM 1 delivers SIGTERM to the shell that installed
1047
- # the drain_handler trap. If the launch mechanism ever
1048
- # wraps this script in another process, this assumption
1049
- # breaks and drain will silently no-op \u2014 audit here.
1050
- kill -TERM 1 2>/dev/null || true
1051
- return 0
1052
- fi
1053
- sleep 2
1054
- done
1055
- }
1056
-
1057
- if [ "\${ECLOUD_PD_EXPECTED:-0}" = "1" ]; then
1058
- # Assumption: the orchestrator only flips ECLOUD_DRAIN_REQUESTED=1
1059
- # after observing ECLOUD_AWAITING_USERDATA (old VM) or
1060
- # ECLOUD_READY (new VM), so CHILD_PID is always set by the time
1061
- # drain_handler fires. If drain somehow arrived in the tiny window
1062
- # between this watcher spawn and CHILD_PID assignment below,
1063
- # drain_handler would skip the child-kill branch and still emit
1064
- # ECLOUD_DETACHED \u2014 harmless because there's nothing to drain yet.
1065
- if [ -x /usr/local/bin/ecloud-drain-watcher ]; then
1066
- /usr/local/bin/ecloud-drain-watcher &
1067
- else
1068
- drain_watcher &
1069
- fi
1070
- fi
1071
-
1072
620
  echo "compute-source-env.sh: Environment sourced."
1073
- echo "ECLOUD_READY runtime_bootstrapped"
1074
621
 
1075
622
  # Drop privileges to original user for the application command
1076
623
  if [ -n "$__ECLOUD_ORIGINAL_USER" ] && [ "$(id -u)" = "0" ]; then
1077
624
  echo "compute-source-env.sh: Dropping privileges to user: $__ECLOUD_ORIGINAL_USER"
1078
- # Must background the child so our trap can fire; exec replaces PID 1.
1079
- su -s /bin/sh "$__ECLOUD_ORIGINAL_USER" -c 'exec "$@"' -- sh "$@" &
1080
- CHILD_PID=$!
1081
- wait "$CHILD_PID"
1082
- exit $?
625
+ exec su -s /bin/sh "$__ECLOUD_ORIGINAL_USER" -c 'exec "$@"' -- sh "$@"
1083
626
  fi
1084
627
 
1085
- "$@" &
1086
- CHILD_PID=$!
1087
- wait "$CHILD_PID"
1088
- exit $?
628
+ exec "$@"
1089
629
  `;
1090
630
 
1091
631
  // src/client/common/templates/scriptTemplate.ts
@@ -1094,14 +634,6 @@ function processScriptTemplate(data) {
1094
634
  return template(data);
1095
635
  }
1096
636
 
1097
- // src/client/common/templates/Caddyfile.default.tmpl
1098
- 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';
1099
-
1100
- // src/client/common/templates/caddyfileTemplate.ts
1101
- function getDefaultCaddyfile() {
1102
- return Caddyfile_default_default;
1103
- }
1104
-
1105
637
  // keys/mainnet-alpha/prod/kms-encryption-public-key.pem
1106
638
  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-----";
1107
639
 
@@ -1276,9 +808,15 @@ async function layerLocalImage(options, logger) {
1276
808
  const imageConfig = await extractImageConfig(docker, sourceImageRef);
1277
809
  const originalCmd = imageConfig.cmd.length > 0 ? imageConfig.cmd : imageConfig.entrypoint;
1278
810
  const originalUser = imageConfig.user;
1279
- const includeTLS = true;
1280
- const drainWatcherSource = findBinary("ecloud-drain-watcher-linux-amd64");
1281
- const includeDrainWatcher = fs.existsSync(drainWatcherSource);
811
+ let includeTLS = false;
812
+ if (envFilePath && fs.existsSync(envFilePath)) {
813
+ const envContent = fs.readFileSync(envFilePath, "utf-8");
814
+ const domainMatch = envContent.match(/^DOMAIN=(.+)$/m);
815
+ if (domainMatch && domainMatch[1] && domainMatch[1] !== "localhost") {
816
+ includeTLS = true;
817
+ logger.debug(`Found DOMAIN=${domainMatch[1]} in ${envFilePath}, including TLS components`);
818
+ }
819
+ }
1282
820
  const layeredDockerfileContent = processDockerfileTemplate({
1283
821
  baseImage: sourceImageRef,
1284
822
  originalCmd: JSON.stringify(originalCmd),
@@ -1286,9 +824,8 @@ async function layerLocalImage(options, logger) {
1286
824
  logRedirect,
1287
825
  resourceUsageAllow,
1288
826
  includeTLS,
1289
- ecloudCLIVersion: "0.1.0",
827
+ ecloudCLIVersion: "0.1.0"
1290
828
  // TODO: Get from package.json
1291
- includeDrainWatcher
1292
829
  });
1293
830
  const scriptContent = processScriptTemplate({
1294
831
  kmsServerURL: environmentConfig.kmsServerURL,
@@ -1298,8 +835,7 @@ async function layerLocalImage(options, logger) {
1298
835
  environmentConfig,
1299
836
  layeredDockerfileContent,
1300
837
  scriptContent,
1301
- includeTLS,
1302
- includeDrainWatcher ? drainWatcherSource : void 0
838
+ includeTLS
1303
839
  // logger
1304
840
  );
1305
841
  try {
@@ -1314,7 +850,7 @@ async function layerLocalImage(options, logger) {
1314
850
  fs.rmSync(tempDir, { recursive: true, force: true });
1315
851
  }
1316
852
  }
1317
- async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileContent, scriptContent, includeTLS, drainWatcherSource) {
853
+ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileContent, scriptContent, includeTLS) {
1318
854
  const tempDir = fs.mkdtempSync(path2.join(os.tmpdir(), LAYERED_BUILD_DIR_PREFIX));
1319
855
  try {
1320
856
  const layeredDockerfilePath = path2.join(tempDir, LAYERED_DOCKERFILE_NAME);
@@ -1338,11 +874,6 @@ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileCo
1338
874
  }
1339
875
  fs.copyFileSync(kmsClientSource, kmsClientPath);
1340
876
  fs.chmodSync(kmsClientPath, 493);
1341
- if (drainWatcherSource && fs.existsSync(drainWatcherSource)) {
1342
- const drainWatcherPath = path2.join(tempDir, DRAIN_WATCHER_BINARY_NAME);
1343
- fs.copyFileSync(drainWatcherSource, drainWatcherPath);
1344
- fs.chmodSync(drainWatcherPath, 493);
1345
- }
1346
877
  if (includeTLS) {
1347
878
  const tlsKeygenPath = path2.join(tempDir, TLS_KEYGEN_BINARY_NAME);
1348
879
  const tlsKeygenSource = findBinary("tls-keygen-linux-amd64");
@@ -1353,13 +884,15 @@ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileCo
1353
884
  }
1354
885
  fs.copyFileSync(tlsKeygenSource, tlsKeygenPath);
1355
886
  fs.chmodSync(tlsKeygenPath, 493);
1356
- const userCaddyfilePath = path2.join(process.cwd(), CADDYFILE_NAME);
1357
- const destCaddyfilePath = path2.join(tempDir, CADDYFILE_NAME);
1358
- if (fs.existsSync(userCaddyfilePath)) {
1359
- fs.copyFileSync(userCaddyfilePath, destCaddyfilePath);
1360
- fs.chmodSync(destCaddyfilePath, 420);
887
+ const caddyfilePath = path2.join(process.cwd(), CADDYFILE_NAME);
888
+ if (fs.existsSync(caddyfilePath)) {
889
+ const caddyfileContent = fs.readFileSync(caddyfilePath);
890
+ const destCaddyfilePath = path2.join(tempDir, CADDYFILE_NAME);
891
+ fs.writeFileSync(destCaddyfilePath, caddyfileContent, { mode: 420 });
1361
892
  } else {
1362
- fs.writeFileSync(destCaddyfilePath, getDefaultCaddyfile(), { mode: 420 });
893
+ throw new Error(
894
+ "TLS is enabled (DOMAIN is set) but Caddyfile not found. Run configure TLS to set up TLS configuration"
895
+ );
1363
896
  }
1364
897
  }
1365
898
  return tempDir;
@@ -1427,16 +960,7 @@ async function extractDigestFromSinglePlatform(manifest, imageRef) {
1427
960
  architecture: inspectData[0].Architecture
1428
961
  } : null;
1429
962
  if (!config) {
1430
- if (manifest.config?.digest) {
1431
- const digest = hexStringToBytes32(manifest.config.digest);
1432
- const registry = extractRegistryName(imageRef);
1433
- return {
1434
- digest,
1435
- registry,
1436
- platform: DOCKER_PLATFORM
1437
- };
1438
- }
1439
- throw new Error(`Could not determine platform for ${imageRef}`);
963
+ throw createPlatformErrorMessage(imageRef, ["unknown (could not determine architecture)"]);
1440
964
  }
1441
965
  const platform2 = `${config.os}/${config.architecture}`;
1442
966
  if (platform2 === DOCKER_PLATFORM) {
@@ -1508,14 +1032,11 @@ Image: ${imageRef}
1508
1032
  Found platform(s): ${platforms.join(", ")}
1509
1033
  Required platform: ${DOCKER_PLATFORM}
1510
1034
 
1511
- To fix this issue:
1512
- 1. Manual fix:
1513
- a. Rebuild your image with the correct platform:
1514
- docker build --platform ${DOCKER_PLATFORM} -t ${imageRef} .
1515
- b. Push the rebuilt image to your remote registry:
1516
- docker push ${imageRef}
1517
-
1518
- 2. Or use the SDK to build with the correct platform automatically.`;
1035
+ To fix, either:
1036
+ 1. Rebuild the image for ${DOCKER_PLATFORM} and push it:
1037
+ docker buildx build --platform ${DOCKER_PLATFORM} -t ${imageRef} --push .
1038
+ 2. Or use a verifiable build (--verifiable --repo <repo> --commit <sha>), which
1039
+ builds server-side and needs no local Docker.`;
1519
1040
  return new Error(errorMsg);
1520
1041
  }
1521
1042
 
@@ -1620,6 +1141,8 @@ async function prepareRelease(options, logger) {
1620
1141
  logger.info(`Waiting ${REGISTRY_PROPAGATION_WAIT_SECONDS} seconds for registry propagation...`);
1621
1142
  await new Promise((resolve2) => setTimeout(resolve2, REGISTRY_PROPAGATION_WAIT_SECONDS * 1e3));
1622
1143
  } else {
1144
+ logger.info("Verifying image platform (linux/amd64)...");
1145
+ await getImageDigestAndName(imageRef);
1623
1146
  logger.info("Checking if image needs layering...");
1624
1147
  finalImageRef = await layerRemoteImageIfNeeded(
1625
1148
  {
@@ -1778,6 +1301,13 @@ function extractRegistryNameNoDocker(imageRef) {
1778
1301
  import { encodeFunctionData, encodeAbiParameters, decodeErrorResult } from "viem";
1779
1302
 
1780
1303
  // src/client/common/types/index.ts
1304
+ var EMPTY_CONTAINER_POLICY = {
1305
+ args: [],
1306
+ cmdOverride: [],
1307
+ env: [],
1308
+ envOverride: [],
1309
+ restartPolicy: ""
1310
+ };
1781
1311
  var noopLogger = {
1782
1312
  debug: () => {
1783
1313
  },
@@ -3018,49 +2548,1594 @@ async function executeBatch(options, logger = noopLogger) {
3018
2548
  } else {
3019
2549
  revertReason = callError.message || "Unknown reason";
3020
2550
  }
3021
- }
3022
- throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2551
+ }
2552
+ throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2553
+ }
2554
+ return hash;
2555
+ }
2556
+
2557
+ // src/client/common/contract/caller.ts
2558
+ import { encodeFunctionData as encodeFunctionData2, decodeErrorResult as decodeErrorResult2, bytesToHex } from "viem";
2559
+
2560
+ // src/client/common/utils/helpers.ts
2561
+ import { extractChain, createPublicClient, createWalletClient, http, fallback } from "viem";
2562
+ import { sepolia as sepolia2 } from "viem/chains";
2563
+ import { privateKeyToAccount } from "viem/accounts";
2564
+ function getChainFromID(chainID, fallback2 = sepolia2) {
2565
+ const id = Number(chainID);
2566
+ return extractChain({ chains: SUPPORTED_CHAINS, id }) || fallback2;
2567
+ }
2568
+ function createClients(options) {
2569
+ const { privateKey, rpcUrl, chainId } = options;
2570
+ const privateKeyHex = addHexPrefix(privateKey);
2571
+ const account = privateKeyToAccount(privateKeyHex);
2572
+ const chain = getChainFromID(chainId);
2573
+ const transport = typeof rpcUrl === "string" ? http(rpcUrl) : fallback(rpcUrl.map((url) => http(url)));
2574
+ const publicClient = createPublicClient({
2575
+ chain,
2576
+ transport
2577
+ });
2578
+ const walletClient = createWalletClient({
2579
+ account,
2580
+ chain,
2581
+ transport
2582
+ });
2583
+ return { walletClient, publicClient };
2584
+ }
2585
+ function addHexPrefix(value) {
2586
+ return value.startsWith("0x") ? value : `0x${value}`;
2587
+ }
2588
+ function stripHexPrefix(value) {
2589
+ return value.startsWith("0x") ? value.slice(2) : value;
2590
+ }
2591
+
2592
+ // src/client/common/abis/AppController.json
2593
+ var AppController_default = [
2594
+ {
2595
+ type: "constructor",
2596
+ inputs: [
2597
+ {
2598
+ name: "_version",
2599
+ type: "string",
2600
+ internalType: "string"
2601
+ },
2602
+ {
2603
+ name: "_permissionController",
2604
+ type: "address",
2605
+ internalType: "contractIPermissionController"
2606
+ },
2607
+ {
2608
+ name: "_releaseManager",
2609
+ type: "address",
2610
+ internalType: "contractIReleaseManager"
2611
+ },
2612
+ {
2613
+ name: "_computeAVSRegistrar",
2614
+ type: "address",
2615
+ internalType: "contractIComputeAVSRegistrar"
2616
+ },
2617
+ {
2618
+ name: "_computeOperator",
2619
+ type: "address",
2620
+ internalType: "contractIComputeOperator"
2621
+ },
2622
+ {
2623
+ name: "_appBeacon",
2624
+ type: "address",
2625
+ internalType: "contractIBeacon"
2626
+ }
2627
+ ],
2628
+ stateMutability: "nonpayable"
2629
+ },
2630
+ {
2631
+ type: "function",
2632
+ name: "API_PERMISSION_TYPEHASH",
2633
+ inputs: [],
2634
+ outputs: [
2635
+ {
2636
+ name: "",
2637
+ type: "bytes32",
2638
+ internalType: "bytes32"
2639
+ }
2640
+ ],
2641
+ stateMutability: "view"
2642
+ },
2643
+ {
2644
+ type: "function",
2645
+ name: "appBeacon",
2646
+ inputs: [],
2647
+ outputs: [
2648
+ {
2649
+ name: "",
2650
+ type: "address",
2651
+ internalType: "contractIBeacon"
2652
+ }
2653
+ ],
2654
+ stateMutability: "view"
2655
+ },
2656
+ {
2657
+ type: "function",
2658
+ name: "calculateApiPermissionDigestHash",
2659
+ inputs: [
2660
+ {
2661
+ name: "permission",
2662
+ type: "bytes4",
2663
+ internalType: "bytes4"
2664
+ },
2665
+ {
2666
+ name: "expiry",
2667
+ type: "uint256",
2668
+ internalType: "uint256"
2669
+ }
2670
+ ],
2671
+ outputs: [
2672
+ {
2673
+ name: "",
2674
+ type: "bytes32",
2675
+ internalType: "bytes32"
2676
+ }
2677
+ ],
2678
+ stateMutability: "view"
2679
+ },
2680
+ {
2681
+ type: "function",
2682
+ name: "calculateAppId",
2683
+ inputs: [
2684
+ {
2685
+ name: "deployer",
2686
+ type: "address",
2687
+ internalType: "address"
2688
+ },
2689
+ {
2690
+ name: "salt",
2691
+ type: "bytes32",
2692
+ internalType: "bytes32"
2693
+ }
2694
+ ],
2695
+ outputs: [
2696
+ {
2697
+ name: "",
2698
+ type: "address",
2699
+ internalType: "contractIApp"
2700
+ }
2701
+ ],
2702
+ stateMutability: "view"
2703
+ },
2704
+ {
2705
+ type: "function",
2706
+ name: "computeAVSRegistrar",
2707
+ inputs: [],
2708
+ outputs: [
2709
+ {
2710
+ name: "",
2711
+ type: "address",
2712
+ internalType: "contractIComputeAVSRegistrar"
2713
+ }
2714
+ ],
2715
+ stateMutability: "view"
2716
+ },
2717
+ {
2718
+ type: "function",
2719
+ name: "computeOperator",
2720
+ inputs: [],
2721
+ outputs: [
2722
+ {
2723
+ name: "",
2724
+ type: "address",
2725
+ internalType: "contractIComputeOperator"
2726
+ }
2727
+ ],
2728
+ stateMutability: "view"
2729
+ },
2730
+ {
2731
+ type: "function",
2732
+ name: "confirmUpgrade",
2733
+ inputs: [
2734
+ {
2735
+ name: "app",
2736
+ type: "address",
2737
+ internalType: "contractIApp"
2738
+ }
2739
+ ],
2740
+ outputs: [],
2741
+ stateMutability: "nonpayable"
2742
+ },
2743
+ {
2744
+ type: "function",
2745
+ name: "createApp",
2746
+ inputs: [
2747
+ {
2748
+ name: "salt",
2749
+ type: "bytes32",
2750
+ internalType: "bytes32"
2751
+ },
2752
+ {
2753
+ name: "release",
2754
+ type: "tuple",
2755
+ internalType: "structIAppController.Release",
2756
+ components: [
2757
+ {
2758
+ name: "rmsRelease",
2759
+ type: "tuple",
2760
+ internalType: "structIReleaseManagerTypes.Release",
2761
+ components: [
2762
+ {
2763
+ name: "artifacts",
2764
+ type: "tuple[]",
2765
+ internalType: "structIReleaseManagerTypes.Artifact[]",
2766
+ components: [
2767
+ {
2768
+ name: "digest",
2769
+ type: "bytes32",
2770
+ internalType: "bytes32"
2771
+ },
2772
+ {
2773
+ name: "registry",
2774
+ type: "string",
2775
+ internalType: "string"
2776
+ }
2777
+ ]
2778
+ },
2779
+ {
2780
+ name: "upgradeByTime",
2781
+ type: "uint32",
2782
+ internalType: "uint32"
2783
+ }
2784
+ ]
2785
+ },
2786
+ {
2787
+ name: "publicEnv",
2788
+ type: "bytes",
2789
+ internalType: "bytes"
2790
+ },
2791
+ {
2792
+ name: "encryptedEnv",
2793
+ type: "bytes",
2794
+ internalType: "bytes"
2795
+ },
2796
+ {
2797
+ name: "containerPolicy",
2798
+ type: "tuple",
2799
+ internalType: "structIAppController.ContainerPolicy",
2800
+ components: [
2801
+ {
2802
+ name: "args",
2803
+ type: "string[]",
2804
+ internalType: "string[]"
2805
+ },
2806
+ {
2807
+ name: "cmdOverride",
2808
+ type: "string[]",
2809
+ internalType: "string[]"
2810
+ },
2811
+ {
2812
+ name: "env",
2813
+ type: "tuple[]",
2814
+ internalType: "structIAppController.EnvVar[]",
2815
+ components: [
2816
+ {
2817
+ name: "key",
2818
+ type: "string",
2819
+ internalType: "string"
2820
+ },
2821
+ {
2822
+ name: "value",
2823
+ type: "string",
2824
+ internalType: "string"
2825
+ }
2826
+ ]
2827
+ },
2828
+ {
2829
+ name: "envOverride",
2830
+ type: "tuple[]",
2831
+ internalType: "structIAppController.EnvVar[]",
2832
+ components: [
2833
+ {
2834
+ name: "key",
2835
+ type: "string",
2836
+ internalType: "string"
2837
+ },
2838
+ {
2839
+ name: "value",
2840
+ type: "string",
2841
+ internalType: "string"
2842
+ }
2843
+ ]
2844
+ },
2845
+ {
2846
+ name: "restartPolicy",
2847
+ type: "string",
2848
+ internalType: "string"
2849
+ }
2850
+ ]
2851
+ }
2852
+ ]
2853
+ }
2854
+ ],
2855
+ outputs: [
2856
+ {
2857
+ name: "app",
2858
+ type: "address",
2859
+ internalType: "contractIApp"
2860
+ }
2861
+ ],
2862
+ stateMutability: "nonpayable"
2863
+ },
2864
+ {
2865
+ type: "function",
2866
+ name: "createAppWithIsolatedBilling",
2867
+ inputs: [
2868
+ {
2869
+ name: "salt",
2870
+ type: "bytes32",
2871
+ internalType: "bytes32"
2872
+ },
2873
+ {
2874
+ name: "release",
2875
+ type: "tuple",
2876
+ internalType: "structIAppController.Release",
2877
+ components: [
2878
+ {
2879
+ name: "rmsRelease",
2880
+ type: "tuple",
2881
+ internalType: "structIReleaseManagerTypes.Release",
2882
+ components: [
2883
+ {
2884
+ name: "artifacts",
2885
+ type: "tuple[]",
2886
+ internalType: "structIReleaseManagerTypes.Artifact[]",
2887
+ components: [
2888
+ {
2889
+ name: "digest",
2890
+ type: "bytes32",
2891
+ internalType: "bytes32"
2892
+ },
2893
+ {
2894
+ name: "registry",
2895
+ type: "string",
2896
+ internalType: "string"
2897
+ }
2898
+ ]
2899
+ },
2900
+ {
2901
+ name: "upgradeByTime",
2902
+ type: "uint32",
2903
+ internalType: "uint32"
2904
+ }
2905
+ ]
2906
+ },
2907
+ {
2908
+ name: "publicEnv",
2909
+ type: "bytes",
2910
+ internalType: "bytes"
2911
+ },
2912
+ {
2913
+ name: "encryptedEnv",
2914
+ type: "bytes",
2915
+ internalType: "bytes"
2916
+ },
2917
+ {
2918
+ name: "containerPolicy",
2919
+ type: "tuple",
2920
+ internalType: "structIAppController.ContainerPolicy",
2921
+ components: [
2922
+ {
2923
+ name: "args",
2924
+ type: "string[]",
2925
+ internalType: "string[]"
2926
+ },
2927
+ {
2928
+ name: "cmdOverride",
2929
+ type: "string[]",
2930
+ internalType: "string[]"
2931
+ },
2932
+ {
2933
+ name: "env",
2934
+ type: "tuple[]",
2935
+ internalType: "structIAppController.EnvVar[]",
2936
+ components: [
2937
+ {
2938
+ name: "key",
2939
+ type: "string",
2940
+ internalType: "string"
2941
+ },
2942
+ {
2943
+ name: "value",
2944
+ type: "string",
2945
+ internalType: "string"
2946
+ }
2947
+ ]
2948
+ },
2949
+ {
2950
+ name: "envOverride",
2951
+ type: "tuple[]",
2952
+ internalType: "structIAppController.EnvVar[]",
2953
+ components: [
2954
+ {
2955
+ name: "key",
2956
+ type: "string",
2957
+ internalType: "string"
2958
+ },
2959
+ {
2960
+ name: "value",
2961
+ type: "string",
2962
+ internalType: "string"
2963
+ }
2964
+ ]
2965
+ },
2966
+ {
2967
+ name: "restartPolicy",
2968
+ type: "string",
2969
+ internalType: "string"
2970
+ }
2971
+ ]
2972
+ }
2973
+ ]
2974
+ }
2975
+ ],
2976
+ outputs: [
2977
+ {
2978
+ name: "app",
2979
+ type: "address",
2980
+ internalType: "contractIApp"
2981
+ }
2982
+ ],
2983
+ stateMutability: "nonpayable"
2984
+ },
2985
+ {
2986
+ type: "function",
2987
+ name: "createEmptyApp",
2988
+ inputs: [
2989
+ {
2990
+ name: "salt",
2991
+ type: "bytes32",
2992
+ internalType: "bytes32"
2993
+ }
2994
+ ],
2995
+ outputs: [
2996
+ {
2997
+ name: "app",
2998
+ type: "address",
2999
+ internalType: "contractIApp"
3000
+ }
3001
+ ],
3002
+ stateMutability: "nonpayable"
3003
+ },
3004
+ {
3005
+ type: "function",
3006
+ name: "createEmptyAppWithIsolatedBilling",
3007
+ inputs: [
3008
+ {
3009
+ name: "salt",
3010
+ type: "bytes32",
3011
+ internalType: "bytes32"
3012
+ }
3013
+ ],
3014
+ outputs: [
3015
+ {
3016
+ name: "app",
3017
+ type: "address",
3018
+ internalType: "contractIApp"
3019
+ }
3020
+ ],
3021
+ stateMutability: "nonpayable"
3022
+ },
3023
+ {
3024
+ type: "function",
3025
+ name: "domainSeparator",
3026
+ inputs: [],
3027
+ outputs: [
3028
+ {
3029
+ name: "",
3030
+ type: "bytes32",
3031
+ internalType: "bytes32"
3032
+ }
3033
+ ],
3034
+ stateMutability: "view"
3035
+ },
3036
+ {
3037
+ type: "function",
3038
+ name: "getActiveAppCount",
3039
+ inputs: [
3040
+ {
3041
+ name: "user",
3042
+ type: "address",
3043
+ internalType: "address"
3044
+ }
3045
+ ],
3046
+ outputs: [
3047
+ {
3048
+ name: "",
3049
+ type: "uint32",
3050
+ internalType: "uint32"
3051
+ }
3052
+ ],
3053
+ stateMutability: "view"
3054
+ },
3055
+ {
3056
+ type: "function",
3057
+ name: "getAppCreator",
3058
+ inputs: [
3059
+ {
3060
+ name: "app",
3061
+ type: "address",
3062
+ internalType: "contractIApp"
3063
+ }
3064
+ ],
3065
+ outputs: [
3066
+ {
3067
+ name: "",
3068
+ type: "address",
3069
+ internalType: "address"
3070
+ }
3071
+ ],
3072
+ stateMutability: "view"
3073
+ },
3074
+ {
3075
+ type: "function",
3076
+ name: "getAppLatestReleaseBlockNumber",
3077
+ inputs: [
3078
+ {
3079
+ name: "app",
3080
+ type: "address",
3081
+ internalType: "contractIApp"
3082
+ }
3083
+ ],
3084
+ outputs: [
3085
+ {
3086
+ name: "",
3087
+ type: "uint32",
3088
+ internalType: "uint32"
3089
+ }
3090
+ ],
3091
+ stateMutability: "view"
3092
+ },
3093
+ {
3094
+ type: "function",
3095
+ name: "getAppOperatorSetId",
3096
+ inputs: [
3097
+ {
3098
+ name: "app",
3099
+ type: "address",
3100
+ internalType: "contractIApp"
3101
+ }
3102
+ ],
3103
+ outputs: [
3104
+ {
3105
+ name: "",
3106
+ type: "uint32",
3107
+ internalType: "uint32"
3108
+ }
3109
+ ],
3110
+ stateMutability: "view"
3111
+ },
3112
+ {
3113
+ type: "function",
3114
+ name: "getAppPendingReleaseBlockNumber",
3115
+ inputs: [
3116
+ {
3117
+ name: "app",
3118
+ type: "address",
3119
+ internalType: "contractIApp"
3120
+ }
3121
+ ],
3122
+ outputs: [
3123
+ {
3124
+ name: "",
3125
+ type: "uint32",
3126
+ internalType: "uint32"
3127
+ }
3128
+ ],
3129
+ stateMutability: "view"
3130
+ },
3131
+ {
3132
+ type: "function",
3133
+ name: "getAppStatus",
3134
+ inputs: [
3135
+ {
3136
+ name: "app",
3137
+ type: "address",
3138
+ internalType: "contractIApp"
3139
+ }
3140
+ ],
3141
+ outputs: [
3142
+ {
3143
+ name: "",
3144
+ type: "uint8",
3145
+ internalType: "enumIAppController.AppStatus"
3146
+ }
3147
+ ],
3148
+ stateMutability: "view"
3149
+ },
3150
+ {
3151
+ type: "function",
3152
+ name: "getApps",
3153
+ inputs: [
3154
+ {
3155
+ name: "offset",
3156
+ type: "uint256",
3157
+ internalType: "uint256"
3158
+ },
3159
+ {
3160
+ name: "limit",
3161
+ type: "uint256",
3162
+ internalType: "uint256"
3163
+ }
3164
+ ],
3165
+ outputs: [
3166
+ {
3167
+ name: "apps",
3168
+ type: "address[]",
3169
+ internalType: "contractIApp[]"
3170
+ },
3171
+ {
3172
+ name: "appConfigsMem",
3173
+ type: "tuple[]",
3174
+ internalType: "structIAppController.AppConfig[]",
3175
+ components: [
3176
+ {
3177
+ name: "creator",
3178
+ type: "address",
3179
+ internalType: "address"
3180
+ },
3181
+ {
3182
+ name: "operatorSetId",
3183
+ type: "uint32",
3184
+ internalType: "uint32"
3185
+ },
3186
+ {
3187
+ name: "latestReleaseBlockNumber",
3188
+ type: "uint32",
3189
+ internalType: "uint32"
3190
+ },
3191
+ {
3192
+ name: "pendingReleaseBlockNumber",
3193
+ type: "uint32",
3194
+ internalType: "uint32"
3195
+ },
3196
+ {
3197
+ name: "status",
3198
+ type: "uint8",
3199
+ internalType: "enumIAppController.AppStatus"
3200
+ }
3201
+ ]
3202
+ }
3203
+ ],
3204
+ stateMutability: "view"
3205
+ },
3206
+ {
3207
+ type: "function",
3208
+ name: "getAppsByBillingAccount",
3209
+ inputs: [
3210
+ {
3211
+ name: "account",
3212
+ type: "address",
3213
+ internalType: "address"
3214
+ },
3215
+ {
3216
+ name: "offset",
3217
+ type: "uint256",
3218
+ internalType: "uint256"
3219
+ },
3220
+ {
3221
+ name: "limit",
3222
+ type: "uint256",
3223
+ internalType: "uint256"
3224
+ }
3225
+ ],
3226
+ outputs: [
3227
+ {
3228
+ name: "apps",
3229
+ type: "address[]",
3230
+ internalType: "contractIApp[]"
3231
+ },
3232
+ {
3233
+ name: "appConfigsMem",
3234
+ type: "tuple[]",
3235
+ internalType: "structIAppController.AppConfig[]",
3236
+ components: [
3237
+ {
3238
+ name: "creator",
3239
+ type: "address",
3240
+ internalType: "address"
3241
+ },
3242
+ {
3243
+ name: "operatorSetId",
3244
+ type: "uint32",
3245
+ internalType: "uint32"
3246
+ },
3247
+ {
3248
+ name: "latestReleaseBlockNumber",
3249
+ type: "uint32",
3250
+ internalType: "uint32"
3251
+ },
3252
+ {
3253
+ name: "pendingReleaseBlockNumber",
3254
+ type: "uint32",
3255
+ internalType: "uint32"
3256
+ },
3257
+ {
3258
+ name: "status",
3259
+ type: "uint8",
3260
+ internalType: "enumIAppController.AppStatus"
3261
+ }
3262
+ ]
3263
+ }
3264
+ ],
3265
+ stateMutability: "view"
3266
+ },
3267
+ {
3268
+ type: "function",
3269
+ name: "getAppsByCreator",
3270
+ inputs: [
3271
+ {
3272
+ name: "creator",
3273
+ type: "address",
3274
+ internalType: "address"
3275
+ },
3276
+ {
3277
+ name: "offset",
3278
+ type: "uint256",
3279
+ internalType: "uint256"
3280
+ },
3281
+ {
3282
+ name: "limit",
3283
+ type: "uint256",
3284
+ internalType: "uint256"
3285
+ }
3286
+ ],
3287
+ outputs: [
3288
+ {
3289
+ name: "apps",
3290
+ type: "address[]",
3291
+ internalType: "contractIApp[]"
3292
+ },
3293
+ {
3294
+ name: "appConfigsMem",
3295
+ type: "tuple[]",
3296
+ internalType: "structIAppController.AppConfig[]",
3297
+ components: [
3298
+ {
3299
+ name: "creator",
3300
+ type: "address",
3301
+ internalType: "address"
3302
+ },
3303
+ {
3304
+ name: "operatorSetId",
3305
+ type: "uint32",
3306
+ internalType: "uint32"
3307
+ },
3308
+ {
3309
+ name: "latestReleaseBlockNumber",
3310
+ type: "uint32",
3311
+ internalType: "uint32"
3312
+ },
3313
+ {
3314
+ name: "pendingReleaseBlockNumber",
3315
+ type: "uint32",
3316
+ internalType: "uint32"
3317
+ },
3318
+ {
3319
+ name: "status",
3320
+ type: "uint8",
3321
+ internalType: "enumIAppController.AppStatus"
3322
+ }
3323
+ ]
3324
+ }
3325
+ ],
3326
+ stateMutability: "view"
3327
+ },
3328
+ {
3329
+ type: "function",
3330
+ name: "getAppsByDeveloper",
3331
+ inputs: [
3332
+ {
3333
+ name: "developer",
3334
+ type: "address",
3335
+ internalType: "address"
3336
+ },
3337
+ {
3338
+ name: "offset",
3339
+ type: "uint256",
3340
+ internalType: "uint256"
3341
+ },
3342
+ {
3343
+ name: "limit",
3344
+ type: "uint256",
3345
+ internalType: "uint256"
3346
+ }
3347
+ ],
3348
+ outputs: [
3349
+ {
3350
+ name: "apps",
3351
+ type: "address[]",
3352
+ internalType: "contractIApp[]"
3353
+ },
3354
+ {
3355
+ name: "appConfigsMem",
3356
+ type: "tuple[]",
3357
+ internalType: "structIAppController.AppConfig[]",
3358
+ components: [
3359
+ {
3360
+ name: "creator",
3361
+ type: "address",
3362
+ internalType: "address"
3363
+ },
3364
+ {
3365
+ name: "operatorSetId",
3366
+ type: "uint32",
3367
+ internalType: "uint32"
3368
+ },
3369
+ {
3370
+ name: "latestReleaseBlockNumber",
3371
+ type: "uint32",
3372
+ internalType: "uint32"
3373
+ },
3374
+ {
3375
+ name: "pendingReleaseBlockNumber",
3376
+ type: "uint32",
3377
+ internalType: "uint32"
3378
+ },
3379
+ {
3380
+ name: "status",
3381
+ type: "uint8",
3382
+ internalType: "enumIAppController.AppStatus"
3383
+ }
3384
+ ]
3385
+ }
3386
+ ],
3387
+ stateMutability: "view"
3388
+ },
3389
+ {
3390
+ type: "function",
3391
+ name: "getBillingAccount",
3392
+ inputs: [
3393
+ {
3394
+ name: "app",
3395
+ type: "address",
3396
+ internalType: "contractIApp"
3397
+ }
3398
+ ],
3399
+ outputs: [
3400
+ {
3401
+ name: "",
3402
+ type: "address",
3403
+ internalType: "address"
3404
+ }
3405
+ ],
3406
+ stateMutability: "view"
3407
+ },
3408
+ {
3409
+ type: "function",
3410
+ name: "getBillingType",
3411
+ inputs: [
3412
+ {
3413
+ name: "app",
3414
+ type: "address",
3415
+ internalType: "contractIApp"
3416
+ }
3417
+ ],
3418
+ outputs: [
3419
+ {
3420
+ name: "",
3421
+ type: "uint8",
3422
+ internalType: "enumIAppController.BillingType"
3423
+ }
3424
+ ],
3425
+ stateMutability: "view"
3426
+ },
3427
+ {
3428
+ type: "function",
3429
+ name: "getMaxActiveAppsPerUser",
3430
+ inputs: [
3431
+ {
3432
+ name: "user",
3433
+ type: "address",
3434
+ internalType: "address"
3435
+ }
3436
+ ],
3437
+ outputs: [
3438
+ {
3439
+ name: "",
3440
+ type: "uint32",
3441
+ internalType: "uint32"
3442
+ }
3443
+ ],
3444
+ stateMutability: "view"
3445
+ },
3446
+ {
3447
+ type: "function",
3448
+ name: "globalActiveAppCount",
3449
+ inputs: [],
3450
+ outputs: [
3451
+ {
3452
+ name: "",
3453
+ type: "uint32",
3454
+ internalType: "uint32"
3455
+ }
3456
+ ],
3457
+ stateMutability: "view"
3458
+ },
3459
+ {
3460
+ type: "function",
3461
+ name: "initialize",
3462
+ inputs: [
3463
+ {
3464
+ name: "admin",
3465
+ type: "address",
3466
+ internalType: "address"
3467
+ }
3468
+ ],
3469
+ outputs: [],
3470
+ stateMutability: "nonpayable"
3471
+ },
3472
+ {
3473
+ type: "function",
3474
+ name: "maxGlobalActiveApps",
3475
+ inputs: [],
3476
+ outputs: [
3477
+ {
3478
+ name: "",
3479
+ type: "uint32",
3480
+ internalType: "uint32"
3481
+ }
3482
+ ],
3483
+ stateMutability: "view"
3484
+ },
3485
+ {
3486
+ type: "function",
3487
+ name: "permissionController",
3488
+ inputs: [],
3489
+ outputs: [
3490
+ {
3491
+ name: "",
3492
+ type: "address",
3493
+ internalType: "contractIPermissionController"
3494
+ }
3495
+ ],
3496
+ stateMutability: "view"
3497
+ },
3498
+ {
3499
+ type: "function",
3500
+ name: "releaseManager",
3501
+ inputs: [],
3502
+ outputs: [
3503
+ {
3504
+ name: "",
3505
+ type: "address",
3506
+ internalType: "contractIReleaseManager"
3507
+ }
3508
+ ],
3509
+ stateMutability: "view"
3510
+ },
3511
+ {
3512
+ type: "function",
3513
+ name: "setMaxActiveAppsPerUser",
3514
+ inputs: [
3515
+ {
3516
+ name: "user",
3517
+ type: "address",
3518
+ internalType: "address"
3519
+ },
3520
+ {
3521
+ name: "limit",
3522
+ type: "uint32",
3523
+ internalType: "uint32"
3524
+ }
3525
+ ],
3526
+ outputs: [],
3527
+ stateMutability: "nonpayable"
3528
+ },
3529
+ {
3530
+ type: "function",
3531
+ name: "setMaxGlobalActiveApps",
3532
+ inputs: [
3533
+ {
3534
+ name: "limit",
3535
+ type: "uint32",
3536
+ internalType: "uint32"
3537
+ }
3538
+ ],
3539
+ outputs: [],
3540
+ stateMutability: "nonpayable"
3541
+ },
3542
+ {
3543
+ type: "function",
3544
+ name: "startApp",
3545
+ inputs: [
3546
+ {
3547
+ name: "app",
3548
+ type: "address",
3549
+ internalType: "contractIApp"
3550
+ }
3551
+ ],
3552
+ outputs: [],
3553
+ stateMutability: "nonpayable"
3554
+ },
3555
+ {
3556
+ type: "function",
3557
+ name: "stopApp",
3558
+ inputs: [
3559
+ {
3560
+ name: "app",
3561
+ type: "address",
3562
+ internalType: "contractIApp"
3563
+ }
3564
+ ],
3565
+ outputs: [],
3566
+ stateMutability: "nonpayable"
3567
+ },
3568
+ {
3569
+ type: "function",
3570
+ name: "suspend",
3571
+ inputs: [
3572
+ {
3573
+ name: "account",
3574
+ type: "address",
3575
+ internalType: "address"
3576
+ },
3577
+ {
3578
+ name: "apps",
3579
+ type: "address[]",
3580
+ internalType: "contractIApp[]"
3581
+ }
3582
+ ],
3583
+ outputs: [],
3584
+ stateMutability: "nonpayable"
3585
+ },
3586
+ {
3587
+ type: "function",
3588
+ name: "terminateApp",
3589
+ inputs: [
3590
+ {
3591
+ name: "app",
3592
+ type: "address",
3593
+ internalType: "contractIApp"
3594
+ }
3595
+ ],
3596
+ outputs: [],
3597
+ stateMutability: "nonpayable"
3598
+ },
3599
+ {
3600
+ type: "function",
3601
+ name: "terminateAppByAdmin",
3602
+ inputs: [
3603
+ {
3604
+ name: "app",
3605
+ type: "address",
3606
+ internalType: "contractIApp"
3607
+ }
3608
+ ],
3609
+ outputs: [],
3610
+ stateMutability: "nonpayable"
3611
+ },
3612
+ {
3613
+ type: "function",
3614
+ name: "updateAppMetadataURI",
3615
+ inputs: [
3616
+ {
3617
+ name: "app",
3618
+ type: "address",
3619
+ internalType: "contractIApp"
3620
+ },
3621
+ {
3622
+ name: "metadataURI",
3623
+ type: "string",
3624
+ internalType: "string"
3625
+ }
3626
+ ],
3627
+ outputs: [],
3628
+ stateMutability: "nonpayable"
3629
+ },
3630
+ {
3631
+ type: "function",
3632
+ name: "upgradeApp",
3633
+ inputs: [
3634
+ {
3635
+ name: "app",
3636
+ type: "address",
3637
+ internalType: "contractIApp"
3638
+ },
3639
+ {
3640
+ name: "release",
3641
+ type: "tuple",
3642
+ internalType: "structIAppController.Release",
3643
+ components: [
3644
+ {
3645
+ name: "rmsRelease",
3646
+ type: "tuple",
3647
+ internalType: "structIReleaseManagerTypes.Release",
3648
+ components: [
3649
+ {
3650
+ name: "artifacts",
3651
+ type: "tuple[]",
3652
+ internalType: "structIReleaseManagerTypes.Artifact[]",
3653
+ components: [
3654
+ {
3655
+ name: "digest",
3656
+ type: "bytes32",
3657
+ internalType: "bytes32"
3658
+ },
3659
+ {
3660
+ name: "registry",
3661
+ type: "string",
3662
+ internalType: "string"
3663
+ }
3664
+ ]
3665
+ },
3666
+ {
3667
+ name: "upgradeByTime",
3668
+ type: "uint32",
3669
+ internalType: "uint32"
3670
+ }
3671
+ ]
3672
+ },
3673
+ {
3674
+ name: "publicEnv",
3675
+ type: "bytes",
3676
+ internalType: "bytes"
3677
+ },
3678
+ {
3679
+ name: "encryptedEnv",
3680
+ type: "bytes",
3681
+ internalType: "bytes"
3682
+ },
3683
+ {
3684
+ name: "containerPolicy",
3685
+ type: "tuple",
3686
+ internalType: "structIAppController.ContainerPolicy",
3687
+ components: [
3688
+ {
3689
+ name: "args",
3690
+ type: "string[]",
3691
+ internalType: "string[]"
3692
+ },
3693
+ {
3694
+ name: "cmdOverride",
3695
+ type: "string[]",
3696
+ internalType: "string[]"
3697
+ },
3698
+ {
3699
+ name: "env",
3700
+ type: "tuple[]",
3701
+ internalType: "structIAppController.EnvVar[]",
3702
+ components: [
3703
+ {
3704
+ name: "key",
3705
+ type: "string",
3706
+ internalType: "string"
3707
+ },
3708
+ {
3709
+ name: "value",
3710
+ type: "string",
3711
+ internalType: "string"
3712
+ }
3713
+ ]
3714
+ },
3715
+ {
3716
+ name: "envOverride",
3717
+ type: "tuple[]",
3718
+ internalType: "structIAppController.EnvVar[]",
3719
+ components: [
3720
+ {
3721
+ name: "key",
3722
+ type: "string",
3723
+ internalType: "string"
3724
+ },
3725
+ {
3726
+ name: "value",
3727
+ type: "string",
3728
+ internalType: "string"
3729
+ }
3730
+ ]
3731
+ },
3732
+ {
3733
+ name: "restartPolicy",
3734
+ type: "string",
3735
+ internalType: "string"
3736
+ }
3737
+ ]
3738
+ }
3739
+ ]
3740
+ }
3741
+ ],
3742
+ outputs: [
3743
+ {
3744
+ name: "",
3745
+ type: "uint256",
3746
+ internalType: "uint256"
3747
+ }
3748
+ ],
3749
+ stateMutability: "nonpayable"
3750
+ },
3751
+ {
3752
+ type: "function",
3753
+ name: "version",
3754
+ inputs: [],
3755
+ outputs: [
3756
+ {
3757
+ name: "",
3758
+ type: "string",
3759
+ internalType: "string"
3760
+ }
3761
+ ],
3762
+ stateMutability: "view"
3763
+ },
3764
+ {
3765
+ type: "event",
3766
+ name: "AppCreated",
3767
+ inputs: [
3768
+ {
3769
+ name: "creator",
3770
+ type: "address",
3771
+ indexed: true,
3772
+ internalType: "address"
3773
+ },
3774
+ {
3775
+ name: "app",
3776
+ type: "address",
3777
+ indexed: true,
3778
+ internalType: "contractIApp"
3779
+ },
3780
+ {
3781
+ name: "operatorSetId",
3782
+ type: "uint32",
3783
+ indexed: false,
3784
+ internalType: "uint32"
3785
+ }
3786
+ ],
3787
+ anonymous: false
3788
+ },
3789
+ {
3790
+ type: "event",
3791
+ name: "AppMetadataURIUpdated",
3792
+ inputs: [
3793
+ {
3794
+ name: "app",
3795
+ type: "address",
3796
+ indexed: true,
3797
+ internalType: "contractIApp"
3798
+ },
3799
+ {
3800
+ name: "metadataURI",
3801
+ type: "string",
3802
+ indexed: false,
3803
+ internalType: "string"
3804
+ }
3805
+ ],
3806
+ anonymous: false
3807
+ },
3808
+ {
3809
+ type: "event",
3810
+ name: "AppStarted",
3811
+ inputs: [
3812
+ {
3813
+ name: "app",
3814
+ type: "address",
3815
+ indexed: true,
3816
+ internalType: "contractIApp"
3817
+ }
3818
+ ],
3819
+ anonymous: false
3820
+ },
3821
+ {
3822
+ type: "event",
3823
+ name: "AppStopped",
3824
+ inputs: [
3825
+ {
3826
+ name: "app",
3827
+ type: "address",
3828
+ indexed: true,
3829
+ internalType: "contractIApp"
3830
+ }
3831
+ ],
3832
+ anonymous: false
3833
+ },
3834
+ {
3835
+ type: "event",
3836
+ name: "AppSuspended",
3837
+ inputs: [
3838
+ {
3839
+ name: "app",
3840
+ type: "address",
3841
+ indexed: true,
3842
+ internalType: "contractIApp"
3843
+ }
3844
+ ],
3845
+ anonymous: false
3846
+ },
3847
+ {
3848
+ type: "event",
3849
+ name: "AppTerminated",
3850
+ inputs: [
3851
+ {
3852
+ name: "app",
3853
+ type: "address",
3854
+ indexed: true,
3855
+ internalType: "contractIApp"
3856
+ }
3857
+ ],
3858
+ anonymous: false
3859
+ },
3860
+ {
3861
+ type: "event",
3862
+ name: "AppTerminatedByAdmin",
3863
+ inputs: [
3864
+ {
3865
+ name: "app",
3866
+ type: "address",
3867
+ indexed: true,
3868
+ internalType: "contractIApp"
3869
+ }
3870
+ ],
3871
+ anonymous: false
3872
+ },
3873
+ {
3874
+ type: "event",
3875
+ name: "AppUpgraded",
3876
+ inputs: [
3877
+ {
3878
+ name: "app",
3879
+ type: "address",
3880
+ indexed: true,
3881
+ internalType: "contractIApp"
3882
+ },
3883
+ {
3884
+ name: "rmsReleaseId",
3885
+ type: "uint256",
3886
+ indexed: false,
3887
+ internalType: "uint256"
3888
+ },
3889
+ {
3890
+ name: "release",
3891
+ type: "tuple",
3892
+ indexed: false,
3893
+ internalType: "structIAppController.Release",
3894
+ components: [
3895
+ {
3896
+ name: "rmsRelease",
3897
+ type: "tuple",
3898
+ internalType: "structIReleaseManagerTypes.Release",
3899
+ components: [
3900
+ {
3901
+ name: "artifacts",
3902
+ type: "tuple[]",
3903
+ internalType: "structIReleaseManagerTypes.Artifact[]",
3904
+ components: [
3905
+ {
3906
+ name: "digest",
3907
+ type: "bytes32",
3908
+ internalType: "bytes32"
3909
+ },
3910
+ {
3911
+ name: "registry",
3912
+ type: "string",
3913
+ internalType: "string"
3914
+ }
3915
+ ]
3916
+ },
3917
+ {
3918
+ name: "upgradeByTime",
3919
+ type: "uint32",
3920
+ internalType: "uint32"
3921
+ }
3922
+ ]
3923
+ },
3924
+ {
3925
+ name: "publicEnv",
3926
+ type: "bytes",
3927
+ internalType: "bytes"
3928
+ },
3929
+ {
3930
+ name: "encryptedEnv",
3931
+ type: "bytes",
3932
+ internalType: "bytes"
3933
+ },
3934
+ {
3935
+ name: "containerPolicy",
3936
+ type: "tuple",
3937
+ internalType: "structIAppController.ContainerPolicy",
3938
+ components: [
3939
+ {
3940
+ name: "args",
3941
+ type: "string[]",
3942
+ internalType: "string[]"
3943
+ },
3944
+ {
3945
+ name: "cmdOverride",
3946
+ type: "string[]",
3947
+ internalType: "string[]"
3948
+ },
3949
+ {
3950
+ name: "env",
3951
+ type: "tuple[]",
3952
+ internalType: "structIAppController.EnvVar[]",
3953
+ components: [
3954
+ {
3955
+ name: "key",
3956
+ type: "string",
3957
+ internalType: "string"
3958
+ },
3959
+ {
3960
+ name: "value",
3961
+ type: "string",
3962
+ internalType: "string"
3963
+ }
3964
+ ]
3965
+ },
3966
+ {
3967
+ name: "envOverride",
3968
+ type: "tuple[]",
3969
+ internalType: "structIAppController.EnvVar[]",
3970
+ components: [
3971
+ {
3972
+ name: "key",
3973
+ type: "string",
3974
+ internalType: "string"
3975
+ },
3976
+ {
3977
+ name: "value",
3978
+ type: "string",
3979
+ internalType: "string"
3980
+ }
3981
+ ]
3982
+ },
3983
+ {
3984
+ name: "restartPolicy",
3985
+ type: "string",
3986
+ internalType: "string"
3987
+ }
3988
+ ]
3989
+ }
3990
+ ]
3991
+ }
3992
+ ],
3993
+ anonymous: false
3994
+ },
3995
+ {
3996
+ type: "event",
3997
+ name: "GlobalMaxActiveAppsSet",
3998
+ inputs: [
3999
+ {
4000
+ name: "limit",
4001
+ type: "uint32",
4002
+ indexed: false,
4003
+ internalType: "uint32"
4004
+ }
4005
+ ],
4006
+ anonymous: false
4007
+ },
4008
+ {
4009
+ type: "event",
4010
+ name: "Initialized",
4011
+ inputs: [
4012
+ {
4013
+ name: "version",
4014
+ type: "uint8",
4015
+ indexed: false,
4016
+ internalType: "uint8"
4017
+ }
4018
+ ],
4019
+ anonymous: false
4020
+ },
4021
+ {
4022
+ type: "event",
4023
+ name: "MaxActiveAppsSet",
4024
+ inputs: [
4025
+ {
4026
+ name: "user",
4027
+ type: "address",
4028
+ indexed: true,
4029
+ internalType: "address"
4030
+ },
4031
+ {
4032
+ name: "limit",
4033
+ type: "uint32",
4034
+ indexed: false,
4035
+ internalType: "uint32"
4036
+ }
4037
+ ],
4038
+ anonymous: false
4039
+ },
4040
+ {
4041
+ type: "event",
4042
+ name: "UpgradeConfirmed",
4043
+ inputs: [
4044
+ {
4045
+ name: "app",
4046
+ type: "address",
4047
+ indexed: true,
4048
+ internalType: "contractIApp"
4049
+ },
4050
+ {
4051
+ name: "pendingReleaseBlockNumber",
4052
+ type: "uint32",
4053
+ indexed: false,
4054
+ internalType: "uint32"
4055
+ }
4056
+ ],
4057
+ anonymous: false
4058
+ },
4059
+ {
4060
+ type: "error",
4061
+ name: "AccountHasActiveApps",
4062
+ inputs: []
4063
+ },
4064
+ {
4065
+ type: "error",
4066
+ name: "AppAlreadyExists",
4067
+ inputs: []
4068
+ },
4069
+ {
4070
+ type: "error",
4071
+ name: "AppDoesNotExist",
4072
+ inputs: []
4073
+ },
4074
+ {
4075
+ type: "error",
4076
+ name: "GlobalMaxActiveAppsExceeded",
4077
+ inputs: []
4078
+ },
4079
+ {
4080
+ type: "error",
4081
+ name: "InvalidAppStatus",
4082
+ inputs: []
4083
+ },
4084
+ {
4085
+ type: "error",
4086
+ name: "InvalidPermissions",
4087
+ inputs: []
4088
+ },
4089
+ {
4090
+ type: "error",
4091
+ name: "InvalidReleaseMetadataURI",
4092
+ inputs: []
4093
+ },
4094
+ {
4095
+ type: "error",
4096
+ name: "InvalidShortString",
4097
+ inputs: []
4098
+ },
4099
+ {
4100
+ type: "error",
4101
+ name: "InvalidSignature",
4102
+ inputs: []
4103
+ },
4104
+ {
4105
+ type: "error",
4106
+ name: "MaxActiveAppsExceeded",
4107
+ inputs: []
4108
+ },
4109
+ {
4110
+ type: "error",
4111
+ name: "MoreThanOneArtifact",
4112
+ inputs: []
4113
+ },
4114
+ {
4115
+ type: "error",
4116
+ name: "NoPendingUpgrade",
4117
+ inputs: []
4118
+ },
4119
+ {
4120
+ type: "error",
4121
+ name: "SignatureExpired",
4122
+ inputs: []
4123
+ },
4124
+ {
4125
+ type: "error",
4126
+ name: "StringTooLong",
4127
+ inputs: [
4128
+ {
4129
+ name: "str",
4130
+ type: "string",
4131
+ internalType: "string"
4132
+ }
4133
+ ]
3023
4134
  }
3024
- return hash;
3025
- }
3026
-
3027
- // src/client/common/contract/caller.ts
3028
- import { encodeFunctionData as encodeFunctionData2, decodeErrorResult as decodeErrorResult2, bytesToHex } from "viem";
3029
-
3030
- // src/client/common/utils/helpers.ts
3031
- import { extractChain, createPublicClient, createWalletClient, http, fallback } from "viem";
3032
- import { sepolia as sepolia2 } from "viem/chains";
3033
- import { privateKeyToAccount } from "viem/accounts";
3034
- function getChainFromID(chainID, fallback2 = sepolia2) {
3035
- const id = Number(chainID);
3036
- return extractChain({ chains: SUPPORTED_CHAINS, id }) || fallback2;
3037
- }
3038
- function createClients(options) {
3039
- const { privateKey, rpcUrl, chainId } = options;
3040
- const privateKeyHex = addHexPrefix(privateKey);
3041
- const account = privateKeyToAccount(privateKeyHex);
3042
- const chain = getChainFromID(chainId);
3043
- const transport = typeof rpcUrl === "string" ? http(rpcUrl) : fallback(rpcUrl.map((url) => http(url)));
3044
- const publicClient = createPublicClient({
3045
- chain,
3046
- transport
3047
- });
3048
- const walletClient = createWalletClient({
3049
- account,
3050
- chain,
3051
- transport
3052
- });
3053
- return { walletClient, publicClient };
3054
- }
3055
- function addHexPrefix(value) {
3056
- return value.startsWith("0x") ? value : `0x${value}`;
3057
- }
3058
- function stripHexPrefix(value) {
3059
- return value.startsWith("0x") ? value.slice(2) : value;
3060
- }
4135
+ ];
3061
4136
 
3062
- // src/client/common/abis/AppController.json
3063
- var AppController_default = [
4137
+ // src/client/common/abis/AppController.v1_4.json
4138
+ var AppController_v1_4_default = [
3064
4139
  {
3065
4140
  type: "constructor",
3066
4141
  inputs: [
@@ -4744,6 +5819,38 @@ var PermissionController_default = [
4744
5819
  ];
4745
5820
 
4746
5821
  // src/client/common/contract/caller.ts
5822
+ function appControllerAbiFor(environmentConfig) {
5823
+ return environmentConfig.releaseAbiVersion === "v1.4" ? AppController_v1_4_default : AppController_default;
5824
+ }
5825
+ function supportsContainerPolicy(environmentConfig) {
5826
+ return environmentConfig.releaseAbiVersion !== "v1.4";
5827
+ }
5828
+ function containerPolicyForViem(policy = EMPTY_CONTAINER_POLICY) {
5829
+ return {
5830
+ args: policy.args,
5831
+ cmdOverride: policy.cmdOverride,
5832
+ env: policy.env.map((e) => ({ key: e.key, value: e.value })),
5833
+ envOverride: policy.envOverride.map((e) => ({ key: e.key, value: e.value })),
5834
+ restartPolicy: policy.restartPolicy
5835
+ };
5836
+ }
5837
+ function releaseForViem(release, environmentConfig) {
5838
+ const base = {
5839
+ rmsRelease: {
5840
+ artifacts: release.rmsRelease.artifacts.map((artifact) => ({
5841
+ digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
5842
+ registry: artifact.registry
5843
+ })),
5844
+ upgradeByTime: release.rmsRelease.upgradeByTime
5845
+ },
5846
+ publicEnv: bytesToHex(release.publicEnv),
5847
+ encryptedEnv: bytesToHex(release.encryptedEnv)
5848
+ };
5849
+ if (!supportsContainerPolicy(environmentConfig)) {
5850
+ return base;
5851
+ }
5852
+ return { ...base, containerPolicy: containerPolicyForViem(release.containerPolicy) };
5853
+ }
4747
5854
  function formatETH(wei) {
4748
5855
  const eth = Number(wei) / 1e18;
4749
5856
  const costStr = eth.toFixed(6);
@@ -4781,7 +5888,7 @@ async function calculateAppID(options) {
4781
5888
  const saltHex = `0x${paddedSaltHex}`;
4782
5889
  const appID = await publicClient.readContract({
4783
5890
  address: environmentConfig.appControllerAddress,
4784
- abi: AppController_default,
5891
+ abi: appControllerAbiFor(environmentConfig),
4785
5892
  functionName: "calculateAppId",
4786
5893
  args: [ownerAddress, saltHex]
4787
5894
  });
@@ -4805,22 +5912,12 @@ async function prepareDeployBatch(options, logger = noopLogger) {
4805
5912
  const saltHexString = bytesToHex(salt).slice(2);
4806
5913
  const paddedSaltHex = saltHexString.padStart(64, "0");
4807
5914
  const saltHex = `0x${paddedSaltHex}`;
4808
- const releaseForViem = {
4809
- rmsRelease: {
4810
- artifacts: release.rmsRelease.artifacts.map((artifact) => ({
4811
- digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
4812
- registry: artifact.registry
4813
- })),
4814
- upgradeByTime: release.rmsRelease.upgradeByTime
4815
- },
4816
- publicEnv: bytesToHex(release.publicEnv),
4817
- encryptedEnv: bytesToHex(release.encryptedEnv)
4818
- };
5915
+ const release_ = releaseForViem(release, environmentConfig);
4819
5916
  const functionName = options.billTo === "app" ? "createAppWithIsolatedBilling" : "createApp";
4820
5917
  const createData = encodeFunctionData2({
4821
- abi: AppController_default,
5918
+ abi: appControllerAbiFor(environmentConfig),
4822
5919
  functionName,
4823
- args: [saltHex, releaseForViem]
5920
+ args: [saltHex, release_]
4824
5921
  });
4825
5922
  const acceptAdminData = encodeFunctionData2({
4826
5923
  abi: PermissionController_default,
@@ -4908,21 +6005,11 @@ async function prepareUpgradeBatch(options) {
4908
6005
  publicLogs,
4909
6006
  needsPermissionChange
4910
6007
  } = options;
4911
- const releaseForViem = {
4912
- rmsRelease: {
4913
- artifacts: release.rmsRelease.artifacts.map((artifact) => ({
4914
- digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
4915
- registry: artifact.registry
4916
- })),
4917
- upgradeByTime: release.rmsRelease.upgradeByTime
4918
- },
4919
- publicEnv: bytesToHex(release.publicEnv),
4920
- encryptedEnv: bytesToHex(release.encryptedEnv)
4921
- };
6008
+ const release_ = releaseForViem(release, environmentConfig);
4922
6009
  const upgradeData = encodeFunctionData2({
4923
- abi: AppController_default,
6010
+ abi: appControllerAbiFor(environmentConfig),
4924
6011
  functionName: "upgradeApp",
4925
- args: [appID, releaseForViem]
6012
+ args: [appID, release_]
4926
6013
  });
4927
6014
  const executions = [
4928
6015
  {
@@ -5056,7 +6143,7 @@ ${pendingMessage}`);
5056
6143
  if (callError.data) {
5057
6144
  try {
5058
6145
  const decoded = decodeErrorResult2({
5059
- abi: AppController_default,
6146
+ abi: appControllerAbiFor(environmentConfig),
5060
6147
  data: callError.data
5061
6148
  });
5062
6149
  const formattedError = formatAppControllerError(decoded);
@@ -5109,7 +6196,7 @@ function formatAppControllerError(decoded) {
5109
6196
  async function getActiveAppCount(publicClient, environmentConfig, user) {
5110
6197
  const count = await publicClient.readContract({
5111
6198
  address: environmentConfig.appControllerAddress,
5112
- abi: AppController_default,
6199
+ abi: appControllerAbiFor(environmentConfig),
5113
6200
  functionName: "getActiveAppCount",
5114
6201
  args: [user]
5115
6202
  });
@@ -5118,7 +6205,7 @@ async function getActiveAppCount(publicClient, environmentConfig, user) {
5118
6205
  async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
5119
6206
  const quota = await publicClient.readContract({
5120
6207
  address: environmentConfig.appControllerAddress,
5121
- abi: AppController_default,
6208
+ abi: appControllerAbiFor(environmentConfig),
5122
6209
  functionName: "getMaxActiveAppsPerUser",
5123
6210
  args: [user]
5124
6211
  });
@@ -5127,7 +6214,7 @@ async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
5127
6214
  async function getAppsByDeveloper(publicClient, environmentConfig, developer, offset, limit) {
5128
6215
  const result = await publicClient.readContract({
5129
6216
  address: environmentConfig.appControllerAddress,
5130
- abi: AppController_default,
6217
+ abi: appControllerAbiFor(environmentConfig),
5131
6218
  functionName: "getAppsByDeveloper",
5132
6219
  args: [developer, offset, limit]
5133
6220
  });
@@ -5139,7 +6226,7 @@ async function getAppsByDeveloper(publicClient, environmentConfig, developer, of
5139
6226
  async function getBillingType(publicClient, environmentConfig, app) {
5140
6227
  const result = await publicClient.readContract({
5141
6228
  address: environmentConfig.appControllerAddress,
5142
- abi: AppController_default,
6229
+ abi: appControllerAbiFor(environmentConfig),
5143
6230
  functionName: "getBillingType",
5144
6231
  args: [app]
5145
6232
  });
@@ -5148,7 +6235,7 @@ async function getBillingType(publicClient, environmentConfig, app) {
5148
6235
  async function getAppsByBillingAccount(publicClient, environmentConfig, account, offset, limit) {
5149
6236
  const result = await publicClient.readContract({
5150
6237
  address: environmentConfig.appControllerAddress,
5151
- abi: AppController_default,
6238
+ abi: appControllerAbiFor(environmentConfig),
5152
6239
  functionName: "getAppsByBillingAccount",
5153
6240
  args: [account, offset, limit]
5154
6241
  });
@@ -5182,7 +6269,7 @@ async function getAppLatestReleaseBlockNumbers(publicClient, environmentConfig,
5182
6269
  appIDs.map(
5183
6270
  (appID) => publicClient.readContract({
5184
6271
  address: environmentConfig.appControllerAddress,
5185
- abi: AppController_default,
6272
+ abi: appControllerAbiFor(environmentConfig),
5186
6273
  functionName: "getAppLatestReleaseBlockNumber",
5187
6274
  args: [appID]
5188
6275
  }).catch(() => null)
@@ -5261,6 +6348,35 @@ async function undelegate(options, logger = noopLogger) {
5261
6348
  return hash;
5262
6349
  }
5263
6350
 
6351
+ // src/client/common/gas/insufficientGas.ts
6352
+ import { formatEther } from "viem";
6353
+ var InsufficientGasError = class extends Error {
6354
+ constructor(args) {
6355
+ const requiredEth = formatEther(args.requiredWei);
6356
+ const availableEth = formatEther(args.availableWei);
6357
+ super(
6358
+ `Insufficient ETH for gas: wallet ${args.address} has ${availableEth} ETH but this transaction needs ~${requiredEth} ETH.
6359
+ Compute credits do not pay on-chain gas \u2014 fund the wallet with ETH and retry.`
6360
+ );
6361
+ this.name = "InsufficientGasError";
6362
+ this.address = args.address;
6363
+ this.requiredWei = args.requiredWei;
6364
+ this.availableWei = args.availableWei;
6365
+ this.requiredEth = requiredEth;
6366
+ this.availableEth = availableEth;
6367
+ }
6368
+ };
6369
+ async function assertSufficientGas(args) {
6370
+ const availableWei = await args.publicClient.getBalance({ address: args.address });
6371
+ if (availableWei < args.gasEstimate.maxCostWei) {
6372
+ throw new InsufficientGasError({
6373
+ address: args.address,
6374
+ requiredWei: args.gasEstimate.maxCostWei,
6375
+ availableWei
6376
+ });
6377
+ }
6378
+ }
6379
+
5264
6380
  // src/client/common/utils/userapi.ts
5265
6381
  import axios2 from "axios";
5266
6382
 
@@ -5324,6 +6440,7 @@ import axios from "axios";
5324
6440
  var MAX_RETRIES = 5;
5325
6441
  var INITIAL_BACKOFF_MS = 1e3;
5326
6442
  var MAX_BACKOFF_MS = 3e4;
6443
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
5327
6444
  function sleep(ms) {
5328
6445
  return new Promise((resolve2) => setTimeout(resolve2, ms));
5329
6446
  }
@@ -5343,7 +6460,7 @@ async function requestWithRetry(config) {
5343
6460
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
5344
6461
  const res = await axios({ ...config, validateStatus: () => true });
5345
6462
  lastResponse = res;
5346
- if (res.status !== 429) {
6463
+ if (!RETRYABLE_STATUSES.has(res.status)) {
5347
6464
  return res;
5348
6465
  }
5349
6466
  if (attempt < MAX_RETRIES) {
@@ -5372,7 +6489,7 @@ var CanViewAppLogsPermission = "0x2fd3f2fe";
5372
6489
  var CanViewSensitiveAppInfoPermission = "0x0e67b22f";
5373
6490
  var CanUpdateAppProfilePermission = "0x036fef61";
5374
6491
  function getDefaultClientId() {
5375
- const version = true ? "1.0.0-devep8" : "0.0.0";
6492
+ const version = true ? "1.0.0" : "0.0.0";
5376
6493
  return `ecloud-sdk/v${version}`;
5377
6494
  }
5378
6495
  var UserApiClient = class {
@@ -5407,7 +6524,6 @@ var UserApiClient = class {
5407
6524
  status: app.app_status,
5408
6525
  ip: app.ip,
5409
6526
  machineType: app.machine_type,
5410
- hostname: app.hostname,
5411
6527
  profile: app.profile,
5412
6528
  metrics: app.metrics,
5413
6529
  evmAddresses,
@@ -5473,28 +6589,6 @@ var UserApiClient = class {
5473
6589
  status: app.app_status || app.App_Status || ""
5474
6590
  }));
5475
6591
  }
5476
- /**
5477
- * Get deployments for an app from the gRPC-gateway endpoint.
5478
- * Returns deployment records including upgrade_phase for tracking upgrade progress.
5479
- *
5480
- * Endpoint: GET /v1/apps/:appAddress/deployments
5481
- */
5482
- async getDeployments(appAddress) {
5483
- const endpoint = `${this.config.userApiServerURL}/v1/apps/${appAddress}/deployments`;
5484
- const response = await this.makeEIP712AuthenticatedRequest(endpoint);
5485
- const result = await response.json();
5486
- const deployments = result.deployments || [];
5487
- return deployments.map((dep) => ({
5488
- id: dep.id || "",
5489
- externalId: dep.external_id || dep.externalId || "",
5490
- endpoint: dep.endpoint || "",
5491
- releaseId: dep.release_id || dep.releaseId || "",
5492
- upgradePhase: dep.upgrade_phase || dep.upgradePhase || "",
5493
- replacesDeploymentId: dep.replaces_deployment_id || dep.replacesDeploymentId || "",
5494
- createdAt: dep.created_at || dep.createdAt || "",
5495
- updatedAt: dep.updated_at || dep.updatedAt || ""
5496
- }));
5497
- }
5498
6592
  /**
5499
6593
  * Upload app profile information with optional image
5500
6594
  *
@@ -5628,48 +6722,6 @@ Please check:
5628
6722
  "X-eigenx-expiry": expiry.toString()
5629
6723
  };
5630
6724
  }
5631
- /**
5632
- * Make an EIP-712 authenticated request to the gRPC-gateway endpoints.
5633
- * Uses the billing/compute auth pattern: Authorization + X-Account + X-Expiry headers.
5634
- */
5635
- async makeEIP712AuthenticatedRequest(url) {
5636
- const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
5637
- const { signature } = await calculateBillingAuthSignature({
5638
- walletClient: this.walletClient,
5639
- product: "compute",
5640
- expiry
5641
- });
5642
- const headers = {
5643
- Authorization: `Bearer ${signature}`,
5644
- "X-Account": this.address,
5645
- "X-Expiry": expiry.toString(),
5646
- "x-client-id": this.clientId
5647
- };
5648
- try {
5649
- const response = await requestWithRetry({
5650
- method: "GET",
5651
- url,
5652
- headers,
5653
- maxRedirects: 0,
5654
- withCredentials: true
5655
- });
5656
- const status = response.status;
5657
- if (status < 200 || status >= 300) {
5658
- const body = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
5659
- throw new Error(`gRPC-gateway request failed: ${status} - ${body}`);
5660
- }
5661
- return {
5662
- json: async () => response.data,
5663
- text: async () => typeof response.data === "string" ? response.data : JSON.stringify(response.data)
5664
- };
5665
- } catch (error) {
5666
- if (error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED") || error.message?.includes("ENOTFOUND") || error.cause) {
5667
- const cause = error.cause?.message || error.cause || error.message;
5668
- throw new Error(`Failed to connect to API at ${url}: ${cause}`);
5669
- }
5670
- throw error;
5671
- }
5672
- }
5673
6725
  // ==========================================================================
5674
6726
  // SIWE Session Management
5675
6727
  // ==========================================================================
@@ -5775,8 +6827,36 @@ function transformAppRelease(raw) {
5775
6827
 
5776
6828
  // src/client/common/contract/watcher.ts
5777
6829
  var WATCH_POLL_INTERVAL_SECONDS = 5;
6830
+ var WATCH_HEARTBEAT_INTERVAL_SECONDS = 30;
6831
+ var WATCH_DEFAULT_TIMEOUT_SECONDS = 10 * 60;
5778
6832
  var APP_STATUS_RUNNING = "Running";
5779
6833
  var APP_STATUS_FAILED = "Failed";
6834
+ var WatchTimeoutError = class extends Error {
6835
+ constructor(args) {
6836
+ super(
6837
+ args.message ?? `Timed out after ${args.elapsedSeconds}s waiting for app ${args.appId} (last status: ${args.lastStatus ?? "unknown"})`
6838
+ );
6839
+ this.name = "WatchTimeoutError";
6840
+ this.appId = args.appId;
6841
+ this.elapsedSeconds = args.elapsedSeconds;
6842
+ this.lastStatus = args.lastStatus;
6843
+ this.timeoutSeconds = args.timeoutSeconds;
6844
+ }
6845
+ };
6846
+ function resolveWatchTimeoutSeconds(explicit) {
6847
+ if (typeof explicit === "number" && Number.isFinite(explicit) && explicit > 0) {
6848
+ return Math.floor(explicit);
6849
+ }
6850
+ const raw = process.env.ECLOUD_WATCH_TIMEOUT_SECONDS;
6851
+ if (raw === void 0 || raw === "") {
6852
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6853
+ }
6854
+ const parsed = Number(raw);
6855
+ if (!Number.isFinite(parsed) || parsed <= 0) {
6856
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6857
+ }
6858
+ return Math.floor(parsed);
6859
+ }
5780
6860
  async function watchUntilRunning(options, logger) {
5781
6861
  const { walletClient, publicClient, environmentConfig, appId } = options;
5782
6862
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
@@ -5807,8 +6887,20 @@ async function watchUntilRunning(options, logger) {
5807
6887
  return false;
5808
6888
  };
5809
6889
  const startTime = Date.now();
6890
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
5810
6891
  let lastLoggedStatus;
6892
+ let lastHeartbeatAt = startTime;
5811
6893
  while (true) {
6894
+ const elapsedMs = Date.now() - startTime;
6895
+ const elapsed = Math.round(elapsedMs / 1e3);
6896
+ if (elapsed >= timeoutSeconds) {
6897
+ throw new WatchTimeoutError({
6898
+ appId,
6899
+ elapsedSeconds: elapsed,
6900
+ lastStatus: lastLoggedStatus,
6901
+ timeoutSeconds
6902
+ });
6903
+ }
5812
6904
  try {
5813
6905
  const info = await userApiClient.getInfos([appId], 1);
5814
6906
  if (info.length === 0) {
@@ -5818,62 +6910,35 @@ async function watchUntilRunning(options, logger) {
5818
6910
  const appInfo = info[0];
5819
6911
  const currentStatus = appInfo.status;
5820
6912
  const currentIP = appInfo.ip || "";
5821
- const elapsed = Math.round((Date.now() - startTime) / 1e3);
5822
6913
  if (currentStatus !== lastLoggedStatus) {
5823
6914
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
5824
6915
  lastLoggedStatus = currentStatus;
6916
+ lastHeartbeatAt = Date.now();
6917
+ } else if (Date.now() - lastHeartbeatAt >= WATCH_HEARTBEAT_INTERVAL_SECONDS * 1e3) {
6918
+ logger.info(`Status: ${currentStatus} (${elapsed}s)`);
6919
+ lastHeartbeatAt = Date.now();
5825
6920
  }
5826
6921
  if (stopCondition(currentStatus, currentIP)) {
5827
6922
  return currentIP || void 0;
5828
6923
  }
5829
6924
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5830
6925
  } catch (error) {
6926
+ if (error instanceof WatchTimeoutError) {
6927
+ throw error;
6928
+ }
5831
6929
  logger.warn(`Failed to fetch app info: ${error.message}`);
5832
6930
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5833
6931
  }
5834
6932
  }
5835
6933
  }
5836
6934
  var APP_STATUS_STOPPED = "Stopped";
5837
- var UPGRADE_PHASE_LABELS = {
5838
- provisioning: "Provisioning new instance",
5839
- health_check: "Running health checks",
5840
- draining: "Switching traffic & draining old instance",
5841
- complete: "Complete",
5842
- rolling_back: "Rolling back",
5843
- rollback_done: "Rollback complete",
5844
- db_handoff: "Database handoff",
5845
- // Prewarm-detach phases
5846
- awaiting_userdata: "Waiting for instance readiness",
5847
- draining_old: "Draining old instance",
5848
- detached: "Detaching storage",
5849
- attached_to_new: "Attaching storage to new instance",
5850
- finalizing: "Finalizing new instance",
5851
- flipping: "Switching traffic",
5852
- teardown_old: "Cleaning up old instance"
5853
- };
5854
- async function fetchUpgradePhase(userApiClient, appId) {
5855
- try {
5856
- const deployments = await userApiClient.getDeployments(appId);
5857
- if (deployments.length === 0) return void 0;
5858
- const sorted = [...deployments].sort((a, b) => {
5859
- const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0;
5860
- const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0;
5861
- return tb - ta;
5862
- });
5863
- return sorted[0].upgradePhase || void 0;
5864
- } catch {
5865
- return void 0;
5866
- }
5867
- }
5868
6935
  async function watchUntilUpgradeComplete(options, logger) {
5869
6936
  const { walletClient, publicClient, environmentConfig, appId } = options;
6937
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
5870
6938
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
5871
6939
  let initialStatus;
5872
6940
  let initialIP;
5873
6941
  let hasChanged = false;
5874
- const startTime = Date.now();
5875
- let lastLoggedStatus;
5876
- let lastLoggedPhase;
5877
6942
  const stopCondition = (status, ip) => {
5878
6943
  if (!initialStatus) {
5879
6944
  initialStatus = status;
@@ -5907,37 +6972,44 @@ async function watchUntilUpgradeComplete(options, logger) {
5907
6972
  }
5908
6973
  return false;
5909
6974
  };
6975
+ const startTime = Date.now();
6976
+ const deadline = startTime + timeoutSeconds * 1e3;
6977
+ let lastLoggedStatus;
6978
+ let lastObservedStatus;
5910
6979
  while (true) {
6980
+ if (Date.now() >= deadline) {
6981
+ const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
6982
+ throw new WatchTimeoutError({
6983
+ appId,
6984
+ lastStatus: lastObservedStatus,
6985
+ elapsedSeconds,
6986
+ timeoutSeconds
6987
+ });
6988
+ }
5911
6989
  try {
5912
- const [infoResult, upgradePhase] = await Promise.all([
5913
- userApiClient.getInfos([appId], 1),
5914
- fetchUpgradePhase(userApiClient, appId)
5915
- ]);
5916
- if (infoResult.length === 0) {
6990
+ const info = await userApiClient.getInfos([appId], 1);
6991
+ if (info.length === 0) {
5917
6992
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5918
6993
  continue;
5919
6994
  }
5920
- const appInfo = infoResult[0];
6995
+ const appInfo = info[0];
5921
6996
  const currentStatus = appInfo.status;
5922
6997
  const currentIP = appInfo.ip || "";
6998
+ lastObservedStatus = currentStatus;
5923
6999
  const elapsed = Math.round((Date.now() - startTime) / 1e3);
5924
- if (upgradePhase && upgradePhase !== lastLoggedPhase) {
5925
- const label = UPGRADE_PHASE_LABELS[upgradePhase] || upgradePhase;
5926
- logger.info(`Phase: ${label} (${elapsed}s)`);
5927
- lastLoggedPhase = upgradePhase;
5928
- }
5929
- if (!upgradePhase && currentStatus !== lastLoggedStatus) {
7000
+ if (currentStatus !== lastLoggedStatus) {
5930
7001
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
5931
7002
  lastLoggedStatus = currentStatus;
5932
7003
  }
5933
7004
  if (stopCondition(currentStatus, currentIP)) {
5934
- const totalElapsed = Math.round((Date.now() - startTime) / 1e3);
5935
- logger.info(`Upgrade completed in ${totalElapsed}s`);
5936
7005
  return;
5937
7006
  }
5938
7007
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5939
7008
  } catch (error) {
5940
- if (error.message?.includes("entered")) {
7009
+ if (error instanceof WatchTimeoutError) {
7010
+ throw error;
7011
+ }
7012
+ if (typeof error?.message === "string" && error.message.includes("Failed")) {
5941
7013
  throw error;
5942
7014
  }
5943
7015
  logger.warn(`Failed to fetch app info: ${error.message}`);
@@ -6268,14 +7340,13 @@ var BILLING_ENVIRONMENTS = {
6268
7340
  billingApiServerURL: "https://billingapi.eigencloud.xyz"
6269
7341
  }
6270
7342
  };
6271
- var PLATFORM_ENV_TESTNET_SEPOLIA = "testnet-sepolia";
6272
- var PLATFORM_ENV_MAINNET_ETHEREUM = "mainnet-ethereum";
6273
- var DEFAULT_APP_BASE_DOMAIN = "eigencloud.xyz";
6274
7343
  var ENVIRONMENTS = {
6275
7344
  "sepolia-dev": {
6276
7345
  name: "sepolia",
6277
7346
  build: "dev",
6278
7347
  appControllerAddress: "0xa86DC1C47cb2518327fB4f9A1627F51966c83B92",
7348
+ releaseAbiVersion: "v1.5",
7349
+ // AppController upgraded to v1.5.x (containerPolicy)
6279
7350
  permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
6280
7351
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
6281
7352
  kmsServerURL: "http://10.128.0.57:8080",
@@ -6283,14 +7354,14 @@ var ENVIRONMENTS = {
6283
7354
  defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com",
6284
7355
  usdcCreditsAddress: "0xbdA3897c3A428763B59015C64AB766c288C97376",
6285
7356
  baseUsdcCreditsAddress: "0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac",
6286
- baseRPCURL: "https://base-sepolia-rpc.publicnode.com",
6287
- platformEnv: PLATFORM_ENV_TESTNET_SEPOLIA,
6288
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7357
+ baseRPCURL: "https://base-sepolia-rpc.publicnode.com"
6289
7358
  },
6290
7359
  sepolia: {
6291
7360
  name: "sepolia",
6292
7361
  build: "prod",
6293
7362
  appControllerAddress: "0x0dd810a6ffba6a9820a10d97b659f07d8d23d4E2",
7363
+ releaseAbiVersion: "v1.4",
7364
+ // prod still on AppController v1.4.0 (3-field Release)
6294
7365
  permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
6295
7366
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
6296
7367
  kmsServerURL: "http://10.128.15.203:8080",
@@ -6299,22 +7370,20 @@ var ENVIRONMENTS = {
6299
7370
  billingRPCURL: "https://ethereum-rpc.publicnode.com",
6300
7371
  usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d",
6301
7372
  baseUsdcCreditsAddress: "0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac",
6302
- baseRPCURL: "https://base-sepolia-rpc.publicnode.com",
6303
- platformEnv: PLATFORM_ENV_TESTNET_SEPOLIA,
6304
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7373
+ baseRPCURL: "https://base-sepolia-rpc.publicnode.com"
6305
7374
  },
6306
7375
  "mainnet-alpha": {
6307
7376
  name: "mainnet-alpha",
6308
7377
  build: "prod",
6309
7378
  appControllerAddress: "0xc38d35Fc995e75342A21CBd6D770305b142Fbe67",
7379
+ releaseAbiVersion: "v1.4",
7380
+ // prod still on AppController v1.4.0 (3-field Release)
6310
7381
  permissionControllerAddress: ChainAddresses[MAINNET_CHAIN_ID].PermissionController,
6311
7382
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
6312
7383
  kmsServerURL: "http://10.128.0.2:8080",
6313
7384
  userApiServerURL: "https://userapi-compute.eigencloud.xyz",
6314
7385
  defaultRPCURL: "https://ethereum-rpc.publicnode.com",
6315
- usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d",
6316
- platformEnv: PLATFORM_ENV_MAINNET_ETHEREUM,
6317
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7386
+ usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d"
6318
7387
  }
6319
7388
  };
6320
7389
  var CHAIN_ID_TO_ENVIRONMENT = {
@@ -6374,7 +7443,7 @@ function getBillingEnvironmentConfig(build) {
6374
7443
  };
6375
7444
  }
6376
7445
  function getBuildType() {
6377
- const buildTimeType = true ? "dev"?.toLowerCase() : void 0;
7446
+ const buildTimeType = true ? "prod"?.toLowerCase() : void 0;
6378
7447
  const runtimeType = process.env.BUILD_TYPE?.toLowerCase();
6379
7448
  const buildType = buildTimeType || runtimeType;
6380
7449
  if (buildType === "dev") {
@@ -6691,6 +7760,63 @@ var BillingApiClient = class {
6691
7760
  return resp.json();
6692
7761
  }
6693
7762
  // ==========================================================================
7763
+ // Admin - Coupon Methods
7764
+ // ==========================================================================
7765
+ async createCoupon(amountCents) {
7766
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons`;
7767
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { amountCents });
7768
+ return resp.json();
7769
+ }
7770
+ async listCoupons(opts) {
7771
+ const params = new URLSearchParams();
7772
+ if (opts?.offset !== void 0) params.set("offset", opts.offset.toString());
7773
+ if (opts?.limit !== void 0) params.set("limit", opts.limit.toString());
7774
+ if (opts?.active !== void 0) params.set("active", opts.active.toString());
7775
+ if (opts?.redeemed !== void 0) params.set("redeemed", opts.redeemed.toString());
7776
+ const qs = params.toString();
7777
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons${qs ? `?${qs}` : ""}`;
7778
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", "compute");
7779
+ return resp.json();
7780
+ }
7781
+ async getCoupon(id) {
7782
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons/${id}`;
7783
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", "compute");
7784
+ return resp.json();
7785
+ }
7786
+ async deactivateCoupon(id) {
7787
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons/${id}/deactivate`;
7788
+ await this.makeAuthenticatedRequest(endpoint, "POST", "compute");
7789
+ }
7790
+ async redeemCouponForUser(id, address) {
7791
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons/${id}/redeem`;
7792
+ await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { address });
7793
+ }
7794
+ // ==========================================================================
7795
+ // Admin - Admin Management Methods
7796
+ // ==========================================================================
7797
+ async addAdmin(address) {
7798
+ const endpoint = `${this.config.billingApiServerURL}/admin/admins`;
7799
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { address });
7800
+ return resp.json();
7801
+ }
7802
+ async removeAdmin(address) {
7803
+ const endpoint = `${this.config.billingApiServerURL}/admin/admins/${address}`;
7804
+ await this.makeAuthenticatedRequest(endpoint, "DELETE", "compute");
7805
+ }
7806
+ async listAdmins() {
7807
+ const endpoint = `${this.config.billingApiServerURL}/admin/admins`;
7808
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", "compute");
7809
+ return resp.json();
7810
+ }
7811
+ // ==========================================================================
7812
+ // User - Coupon Redemption
7813
+ // ==========================================================================
7814
+ async redeemCoupon(code) {
7815
+ const endpoint = `${this.config.billingApiServerURL}/v1/coupons/redeem`;
7816
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { code });
7817
+ return resp.json();
7818
+ }
7819
+ // ==========================================================================
6694
7820
  // Internal Methods
6695
7821
  // ==========================================================================
6696
7822
  /**
@@ -7106,6 +8232,11 @@ async function prepareDeployFromVerifiableBuild(options, logger = defaultLogger)
7106
8232
  executions: batch.executions,
7107
8233
  authorizationList
7108
8234
  });
8235
+ await assertSufficientGas({
8236
+ publicClient: batch.publicClient,
8237
+ address: batch.walletClient.account.address,
8238
+ gasEstimate
8239
+ });
7109
8240
  const data = {
7110
8241
  appId: batch.appId,
7111
8242
  salt: batch.salt,
@@ -7364,6 +8495,11 @@ async function prepareDeploy(options, logger = defaultLogger) {
7364
8495
  executions: batch.executions,
7365
8496
  authorizationList
7366
8497
  });
8498
+ await assertSufficientGas({
8499
+ publicClient: batch.publicClient,
8500
+ address: batch.walletClient.account.address,
8501
+ gasEstimate
8502
+ });
7367
8503
  const data = {
7368
8504
  appId: batch.appId,
7369
8505
  salt: batch.salt,
@@ -7400,7 +8536,7 @@ async function executeDeploy(options) {
7400
8536
  }
7401
8537
  );
7402
8538
  }
7403
- async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
8539
+ async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
7404
8540
  return withSDKTelemetry(
7405
8541
  {
7406
8542
  functionName: "watchDeployment",
@@ -7416,7 +8552,8 @@ async function watchDeployment(appId, walletClient, publicClient, environmentCon
7416
8552
  walletClient,
7417
8553
  publicClient,
7418
8554
  environmentConfig,
7419
- appId
8555
+ appId,
8556
+ timeoutSeconds: opts?.timeoutSeconds
7420
8557
  },
7421
8558
  logger
7422
8559
  );
@@ -7512,6 +8649,11 @@ async function prepareUpgradeFromVerifiableBuild(options, logger = defaultLogger
7512
8649
  executions: batch.executions,
7513
8650
  authorizationList
7514
8651
  });
8652
+ await assertSufficientGas({
8653
+ publicClient: batch.publicClient,
8654
+ address: batch.walletClient.account.address,
8655
+ gasEstimate
8656
+ });
7515
8657
  const data = {
7516
8658
  appId: batch.appId,
7517
8659
  executions: batch.executions,
@@ -7701,6 +8843,11 @@ async function prepareUpgrade(options, logger = defaultLogger) {
7701
8843
  executions: batch.executions,
7702
8844
  authorizationList
7703
8845
  });
8846
+ await assertSufficientGas({
8847
+ publicClient: batch.publicClient,
8848
+ address: batch.walletClient.account.address,
8849
+ gasEstimate
8850
+ });
7704
8851
  const data = {
7705
8852
  appId: batch.appId,
7706
8853
  executions: batch.executions,
@@ -7735,7 +8882,7 @@ async function executeUpgrade(options) {
7735
8882
  }
7736
8883
  );
7737
8884
  }
7738
- async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
8885
+ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
7739
8886
  return withSDKTelemetry(
7740
8887
  {
7741
8888
  functionName: "watchUpgrade",
@@ -7751,7 +8898,8 @@ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig
7751
8898
  walletClient,
7752
8899
  publicClient,
7753
8900
  environmentConfig,
7754
- appId
8901
+ appId,
8902
+ timeoutSeconds: opts?.timeoutSeconds
7755
8903
  },
7756
8904
  logger
7757
8905
  );
@@ -8443,7 +9591,7 @@ function createAppModule(ctx) {
8443
9591
  }
8444
9592
  const account = walletClient.account;
8445
9593
  const environment = getEnvironmentConfig(ctx.environment);
8446
- const logger = getLogger(ctx.verbose);
9594
+ const logger = ctx.logger ?? getLogger(ctx.verbose);
8447
9595
  return {
8448
9596
  async create(opts) {
8449
9597
  return createApp(opts, logger);
@@ -8555,14 +9703,15 @@ function createAppModule(ctx) {
8555
9703
  imageRef: result.imageRef
8556
9704
  };
8557
9705
  },
8558
- async watchDeployment(appId) {
9706
+ async watchDeployment(appId, opts) {
8559
9707
  return watchDeployment(
8560
9708
  appId,
8561
9709
  walletClient,
8562
9710
  publicClient,
8563
9711
  environment,
8564
9712
  logger,
8565
- skipTelemetry
9713
+ skipTelemetry,
9714
+ opts
8566
9715
  );
8567
9716
  },
8568
9717
  // Granular upgrade control
@@ -8620,8 +9769,16 @@ function createAppModule(ctx) {
8620
9769
  imageRef: result.imageRef
8621
9770
  };
8622
9771
  },
8623
- async watchUpgrade(appId) {
8624
- return watchUpgrade(appId, walletClient, publicClient, environment, logger, skipTelemetry);
9772
+ async watchUpgrade(appId, opts) {
9773
+ return watchUpgrade(
9774
+ appId,
9775
+ walletClient,
9776
+ publicClient,
9777
+ environment,
9778
+ logger,
9779
+ skipTelemetry,
9780
+ opts
9781
+ );
8625
9782
  },
8626
9783
  // Profile management
8627
9784
  async setProfile(appId, profile) {
@@ -9111,6 +10268,9 @@ function createBillingModule(config) {
9111
10268
  },
9112
10269
  hasBaseSupport() {
9113
10270
  return !!baseUsdcCreditsAddress && !!baseRPCURL;
10271
+ },
10272
+ async redeemCoupon(code) {
10273
+ return billingApi.redeemCoupon(code);
9114
10274
  }
9115
10275
  };
9116
10276
  return module;
@@ -9546,6 +10706,44 @@ function transformVerifyResult(raw) {
9546
10706
  };
9547
10707
  }
9548
10708
 
10709
+ // src/client/modules/admin/index.ts
10710
+ function createAdminModule(config) {
10711
+ const { verbose = false, walletClient } = config;
10712
+ if (!walletClient.account) {
10713
+ throw new Error("WalletClient must have an account attached");
10714
+ }
10715
+ const address = walletClient.account.address;
10716
+ const billingEnvConfig = getBillingEnvironmentConfig(getBuildType());
10717
+ const billingApi = new BillingApiClient(billingEnvConfig, walletClient, { verbose });
10718
+ return {
10719
+ address,
10720
+ async createCoupon(amountCents) {
10721
+ return billingApi.createCoupon(amountCents);
10722
+ },
10723
+ async listCoupons(opts) {
10724
+ return billingApi.listCoupons(opts);
10725
+ },
10726
+ async getCoupon(id) {
10727
+ return billingApi.getCoupon(id);
10728
+ },
10729
+ async deactivateCoupon(id) {
10730
+ return billingApi.deactivateCoupon(id);
10731
+ },
10732
+ async redeemCouponForUser(id, userAddress) {
10733
+ return billingApi.redeemCouponForUser(id, userAddress);
10734
+ },
10735
+ async addAdmin(adminAddress) {
10736
+ return billingApi.addAdmin(adminAddress);
10737
+ },
10738
+ async removeAdmin(adminAddress) {
10739
+ return billingApi.removeAdmin(adminAddress);
10740
+ },
10741
+ async listAdmins() {
10742
+ return billingApi.listAdmins();
10743
+ }
10744
+ };
10745
+ }
10746
+
9549
10747
  // src/client/common/auth/keyring.ts
9550
10748
  import { AsyncEntry, findCredentials } from "@napi-rs/keyring";
9551
10749
  import { privateKeyToAddress } from "viem/accounts";
@@ -10090,8 +11288,10 @@ export {
10090
11288
  BuildError,
10091
11289
  BuildFailedError,
10092
11290
  ConflictError,
11291
+ EMPTY_CONTAINER_POLICY,
10093
11292
  ERC20_default as ERC20ABI,
10094
11293
  ForbiddenError,
11294
+ InsufficientGasError,
10095
11295
  JwtProvider,
10096
11296
  NoopClient,
10097
11297
  NotFoundError,
@@ -10101,14 +11301,18 @@ export {
10101
11301
  TimeoutError,
10102
11302
  USDCCredits_default as USDCCreditsABI,
10103
11303
  UserApiClient,
11304
+ WATCH_DEFAULT_TIMEOUT_SECONDS,
11305
+ WatchTimeoutError,
10104
11306
  addHexPrefix,
10105
11307
  addMetric,
10106
11308
  addMetricWithDimensions,
11309
+ assertSufficientGas,
10107
11310
  assertValidFilePath,
10108
11311
  assertValidImageReference,
10109
11312
  assertValidPrivateKey,
10110
11313
  calculateAppID,
10111
11314
  checkERC7702Delegation,
11315
+ createAdminModule,
10112
11316
  createApp,
10113
11317
  createAppEnvironment,
10114
11318
  createBillingModule,