@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.cjs CHANGED
@@ -171,8 +171,10 @@ __export(index_exports, {
171
171
  BuildError: () => BuildError,
172
172
  BuildFailedError: () => BuildFailedError,
173
173
  ConflictError: () => ConflictError,
174
+ EMPTY_CONTAINER_POLICY: () => EMPTY_CONTAINER_POLICY,
174
175
  ERC20ABI: () => ERC20_default,
175
176
  ForbiddenError: () => ForbiddenError,
177
+ InsufficientGasError: () => InsufficientGasError,
176
178
  JwtProvider: () => JwtProvider,
177
179
  NoopClient: () => NoopClient,
178
180
  NotFoundError: () => NotFoundError,
@@ -182,14 +184,18 @@ __export(index_exports, {
182
184
  TimeoutError: () => TimeoutError,
183
185
  USDCCreditsABI: () => USDCCredits_default,
184
186
  UserApiClient: () => UserApiClient,
187
+ WATCH_DEFAULT_TIMEOUT_SECONDS: () => WATCH_DEFAULT_TIMEOUT_SECONDS,
188
+ WatchTimeoutError: () => WatchTimeoutError,
185
189
  addHexPrefix: () => addHexPrefix,
186
190
  addMetric: () => addMetric,
187
191
  addMetricWithDimensions: () => addMetricWithDimensions,
192
+ assertSufficientGas: () => assertSufficientGas,
188
193
  assertValidFilePath: () => assertValidFilePath,
189
194
  assertValidImageReference: () => assertValidImageReference,
190
195
  assertValidPrivateKey: () => assertValidPrivateKey,
191
196
  calculateAppID: () => calculateAppID,
192
197
  checkERC7702Delegation: () => checkERC7702Delegation,
198
+ createAdminModule: () => createAdminModule,
193
199
  createApp: () => createApp,
194
200
  createAppEnvironment: () => createAppEnvironment,
195
201
  createBillingModule: () => createBillingModule,
@@ -285,7 +291,7 @@ __export(index_exports, {
285
291
  module.exports = __toCommonJS(index_exports);
286
292
 
287
293
  // src/client/modules/compute/app/index.ts
288
- var import_viem6 = require("viem");
294
+ var import_viem7 = require("viem");
289
295
 
290
296
  // src/client/common/docker/build.ts
291
297
  var child_process = __toESM(require("child_process"), 1);
@@ -301,7 +307,6 @@ var ENV_SOURCE_SCRIPT_NAME = "compute-source-env.sh";
301
307
  var KMS_CLIENT_BINARY_NAME = "kms-client";
302
308
  var KMS_SIGNING_KEY_NAME = "kms-signing-public-key.pem";
303
309
  var TLS_KEYGEN_BINARY_NAME = "tls-keygen";
304
- var DRAIN_WATCHER_BINARY_NAME = "ecloud-drain-watcher";
305
310
  var CADDYFILE_NAME = "Caddyfile";
306
311
  var LAYERED_BUILD_DIR_PREFIX = "ecloud-layered-build";
307
312
 
@@ -588,12 +593,14 @@ USER root
588
593
  {{/if}}
589
594
 
590
595
  # Copy core TEE components
596
+ # CA bundle for kms-client / tls-keygen to validate HTTPS calls to
597
+ # eigencloud.xyz endpoints. Bundled at a non-standard path and consumed
598
+ # only via SSL_CERT_FILE in compute-source-env.sh, so the user's
599
+ # /etc/ssl/ is never touched.
600
+ COPY --from=alpine:3.20.10 /etc/ssl/certs/ca-certificates.crt /usr/local/share/eigenx-ca-certs.crt
591
601
  COPY compute-source-env.sh /usr/local/bin/
592
602
  COPY kms-client /usr/local/bin/
593
603
  COPY kms-signing-public-key.pem /usr/local/bin/
594
- {{#if includeDrainWatcher}}
595
- COPY ecloud-drain-watcher /usr/local/bin/
596
- {{/if}}
597
604
 
598
605
  {{#if includeTLS}}
599
606
  # Copy Caddy from official image
@@ -607,8 +614,7 @@ COPY Caddyfile /etc/caddy/
607
614
  {{#if originalUser}}
608
615
  # Make binaries executable (755 for executables, 644 for keys)
609
616
  RUN chmod 755 /usr/local/bin/compute-source-env.sh \\
610
- && chmod 755 /usr/local/bin/kms-client{{#if includeDrainWatcher}} \\
611
- && chmod 755 /usr/local/bin/ecloud-drain-watcher{{/if}}{{#if includeTLS}} \\
617
+ && chmod 755 /usr/local/bin/kms-client{{#if includeTLS}} \\
612
618
  && chmod 755 /usr/local/bin/tls-keygen \\
613
619
  && chmod 755 /usr/local/bin/caddy{{/if}} \\
614
620
  && chmod 644 /usr/local/bin/kms-signing-public-key.pem
@@ -618,8 +624,7 @@ ENV __ECLOUD_ORIGINAL_USER={{originalUser}}
618
624
  {{else}}
619
625
  # Make binaries executable (preserve existing permissions, just add execute)
620
626
  RUN chmod +x /usr/local/bin/compute-source-env.sh \\
621
- && chmod +x /usr/local/bin/kms-client{{#if includeDrainWatcher}} \\
622
- && chmod +x /usr/local/bin/ecloud-drain-watcher{{/if}}{{#if includeTLS}} \\
627
+ && chmod +x /usr/local/bin/kms-client{{#if includeTLS}} \\
623
628
  && chmod +x /usr/local/bin/tls-keygen{{/if}}
624
629
  {{/if}}
625
630
 
@@ -632,36 +637,8 @@ LABEL tee.launch_policy.log_redirect={{logRedirect}}
632
637
  LABEL tee.launch_policy.monitoring_memory_allow={{resourceUsageAllow}}
633
638
  {{/if}}
634
639
 
635
- # Allow-list the envvars the ecloud-platform sets via GCE \`tee-env-*\`
636
- # metadata. Without this label, Confidential Space's launcher rejects
637
- # any \`tee-env-*\` override at container-start with
638
- # "env var {...} is not allowed to be overridden on this image" and
639
- # exits with code 1 \u2014 which terminates the VM before the entrypoint
640
- # ever runs. User-supplied env vars flow through KMS (not tee-env-*)
641
- # and don't need to be listed here.
642
- #
643
- # Entries:
644
- # - ECLOUD_PD_EXPECTED set on PD-backed apps so the entrypoint
645
- # (compute-source-env.sh) knows to wait for
646
- # the persistent disk before exec'ing the
647
- # user workload.
648
- # - ECLOUD_PLATFORM_HOST the platform-routed hostname
649
- # (<addr>.<env>.eigencloud.xyz) so the
650
- # entrypoint's setup_tls can issue an ACME
651
- # cert for it. Injected by the CLI into
652
- # publicEnv at deploy/upgrade time and
653
- # propagated by ecloud-platform's
654
- # compute.go as a tee-env-* metadata key.
655
- #
656
- # The CS launcher parses this label as a comma-separated list
657
- # (go-tpm-tools/launcher/spec/launch_policy.go:185 \u2014 strings.Split
658
- # on ","). Quotes are not required; keep the value bare for
659
- # consistency with compute-tee's and eigenx-kms's existing labels.
660
- LABEL tee.launch_policy.allow_env_override=ECLOUD_PD_EXPECTED,ECLOUD_PLATFORM_HOST
661
-
662
640
  LABEL eigenx_cli_version={{ecloudCLIVersion}}
663
641
  LABEL eigenx_vm_image=eigen
664
- LABEL eigenx_container_contract=v1
665
642
 
666
643
  {{#if includeTLS}}
667
644
  # Expose both HTTP and HTTPS ports for Caddy
@@ -683,54 +660,11 @@ var import_handlebars2 = __toESM(require("handlebars"), 1);
683
660
 
684
661
  // src/client/common/templates/compute-source-env.sh.tmpl
685
662
  var compute_source_env_sh_default = `#!/bin/sh
686
- # EigenCompute container entrypoint script
687
- # This script handles KMS secret fetching, TLS setup, and privilege dropping
688
- # before executing the user's application.
689
- #
690
- # Handlebars template variables (replaced at build time by the CLI):
691
- # kmsServerURL - URL of the KMS server
692
- # userAPIURL - URL of the user API (ecloud-platform)
693
- # The KMS signing public key is copied into the image as
694
- # /usr/local/bin/kms-signing-public-key.pem at layer-build time by the CLI.
695
- #
696
- # ecloud-platform divergence from compute-tee:
697
- # This script emits ECLOUD_READY / ECLOUD_FAIL / ECLOUD_AWAITING_USERDATA /
698
- # ECLOUD_DETACHED markers to stdout at key lifecycle points. The GCP
699
- # provisioner's serial-console watcher in ecloud-platform
700
- # (pkg/services/infraService/providers/gcp/compute.go) parses those
701
- # markers to gate "VM ready" and to coordinate the prewarm-detach
702
- # upgrade flow. Without the markers, the platform's waitForStartupReady
703
- # times out at ~10 minutes per deploy, rollback fires, and the VM is
704
- # deleted \u2014 seen in dev on 2026-05-04 with an older copy of this
705
- # template that lacked the markers.
706
- #
707
- # Prewarm-detach contract:
708
- # - If ECLOUD_PD_EXPECTED=1 and /mnt/disks/userdata is not present at boot,
709
- # emit ECLOUD_AWAITING_USERDATA and wait until the disk is attached.
710
- # - On SIGTERM (drain-requested), forward to child, wait for exit, sync
711
- # + unmount /mnt/disks/userdata, emit ECLOUD_DETACHED, exit.
712
- # - ECLOUD_READY is emitted once runtime is bootstrapped (same as before).
713
- # - ECLOUD_FAIL is emitted on any unrecoverable setup error.
714
- # Keep the markers on any line that resolves a lifecycle outcome.
715
- #
716
- # This file is kept in lockstep with
717
- # ecloud-platform/pkg/services/buildService/assets/compute-source-env.sh.tmpl
718
- # \u2014 if you change one, change the other. Differences vs the platform copy
719
- # are intentionally minimal:
720
- # - Handlebars placeholders use the CLI's naming (kmsServerURL,
721
- # userAPIURL) rather than the platform's (KMS_SERVER_URL,
722
- # USER_API_URL). (See top of file for real placeholder syntax \u2014
723
- # not repeated here so Handlebars doesn't expand it in this comment.)
724
- # - KMS signing key is read from a file the CLI copies into the image,
725
- # not heredoc-embedded in the script, because the CLI's image
726
- # layering writes it as a separate file (kms-signing-public-key.pem).
727
- # - TLS binary is \`tls-keygen\` (CLI-bundled) not \`tls-client\`.
728
-
729
663
  echo "compute-source-env.sh: Running setup script..."
730
664
 
731
665
  # Fetch and source environment variables from KMS
732
666
  echo "Fetching secrets from KMS..."
733
- if /usr/local/bin/kms-client \\
667
+ if SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt /usr/local/bin/kms-client \\
734
668
  --kms-server-url "{{kmsServerURL}}" \\
735
669
  --kms-signing-key-file /usr/local/bin/kms-signing-public-key.pem \\
736
670
  --userapi-url "{{userAPIURL}}" \\
@@ -741,187 +675,93 @@ if /usr/local/bin/kms-client \\
741
675
  else
742
676
  echo "compute-source-env.sh: ERROR - Failed to fetch environment variables from KMS"
743
677
  echo "compute-source-env.sh: Exiting - cannot start user workload without KMS secrets"
744
- echo "ECLOUD_FAIL kms_bootstrap"
745
678
  exit 1
746
679
  fi
747
680
 
748
- # issue_cert_for runs tls-keygen for a single hostname and copies the
749
- # produced fullchain/privkey into $1's output directory ($2). Returns
750
- # 0 on success, non-zero on any failure (caller decides whether that's
751
- # fatal). tls-keygen writes to /run/tls/{fullchain,privkey}.pem by
752
- # default; we move those to a per-host subdirectory so the two-site
753
- # Caddyfile can select the right cert by path.
754
- issue_cert_for() {
755
- local host="$1"
756
- local cert_dir="$2"
757
- local mnemonic="$3"
758
- local challenge="$4"
759
- local staging_flag="$5"
760
-
761
- echo "compute-source-env.sh: Obtaining TLS certificate for $host (challenge=$challenge)..."
762
- # Remove any stale default outputs from a prior tls-keygen run
763
- rm -f /run/tls/fullchain.pem /run/tls/privkey.pem
764
- if ! MNEMONIC="$mnemonic" DOMAIN="$host" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
765
- -challenge "$challenge" \\
766
- $staging_flag; then
767
- return 1
768
- fi
769
- mkdir -p "$cert_dir"
770
- mv /run/tls/fullchain.pem "$cert_dir/fullchain.pem"
771
- mv /run/tls/privkey.pem "$cert_dir/privkey.pem"
772
- echo "compute-source-env.sh: Cert for $host written to $cert_dir"
773
- }
774
-
775
- # Setup TLS: issues certs for
776
- # - ECLOUD_PLATFORM_HOST (platform-routed <addr>.<env>.eigencloud.xyz),
777
- # when the CLI/platform has set it
778
- # - DOMAIN (user-supplied custom domain), when set and non-localhost
779
- #
780
- # No client-side DNS precheck. Earlier versions tried to gate ACME on
781
- # "does this hostname resolve to my external IP" but that's wrong for
782
- # the platform-routing model (DNS points at the shared nginx NLB, not
783
- # the VM) and was preventing cert issuance on the production path.
784
- # tls-client (eigencompute-containers/tls-client) does its own DNS
785
- # poll before calling ACME and surfaces a clear error when challenges
786
- # can't reach the VM, which is the right place for that check \u2014
787
- # attempting it here from inside the VM cannot tell platform-routed
788
- # from compute-tee-routed apps.
681
+ # Setup TLS if tls-keygen is present (which means TLS was configured at build time)
789
682
  setup_tls() {
790
683
  # If tls-keygen isn't present, TLS wasn't configured during build
791
684
  if [ ! -x /usr/local/bin/tls-keygen ]; then
792
685
  echo "compute-source-env.sh: TLS not configured (no tls-keygen binary)"
793
686
  return 0
794
687
  fi
795
-
796
- local platform_host="\${ECLOUD_PLATFORM_HOST:-}"
797
- local user_domain="\${DOMAIN:-}"
688
+
689
+ local domain="\${DOMAIN:-}"
798
690
  local mnemonic="\${MNEMONIC:-}"
799
-
800
- # Normalize "localhost" DOMAIN to unset \u2014 compute-tee apps often
801
- # set DOMAIN=localhost as a sentinel for "no custom domain".
802
- if [ "$user_domain" = "localhost" ]; then
803
- user_domain=""
804
- fi
805
-
806
- # If the user set DOMAIN to the same value as the platform
807
- # hostname, collapse them. Without this we'd burn a second ACME
808
- # issuance for the exact same name (each VM boot counts against
809
- # Let's Encrypt's 5-certs-per-domain-per-week limit), and Caddy
810
- # would then refuse to start because two site blocks bound to
811
- # the same address is a config error. Also unset DOMAIN so the
812
- # DOMAIN site block in the Caddyfile falls back to its
813
- # "localhost.user.invalid" placeholder and stays dormant.
814
- if [ -n "$platform_host" ] && [ "$user_domain" = "$platform_host" ]; then
815
- echo "compute-source-env.sh: DOMAIN matches ECLOUD_PLATFORM_HOST; skipping duplicate cert"
816
- user_domain=""
817
- unset DOMAIN
818
- fi
819
-
820
- if [ -z "$platform_host" ] && [ -z "$user_domain" ]; then
821
- echo "compute-source-env.sh: TLS skipped (neither ECLOUD_PLATFORM_HOST nor DOMAIN set)"
822
- return 0
691
+
692
+ # Since tls-keygen is present, TLS is expected - validate requirements
693
+ if [ -z "$domain" ] || [ "$domain" = "localhost" ]; then
694
+ echo "compute-source-env.sh: ERROR - TLS binary present but DOMAIN not configured or is localhost"
695
+ echo "compute-source-env.sh: Set DOMAIN environment variable to a valid domain"
696
+ exit 1
823
697
  fi
824
-
698
+
825
699
  if [ -z "$mnemonic" ]; then
826
- echo "compute-source-env.sh: ERROR - TLS requested but MNEMONIC not available"
827
- echo "ECLOUD_FAIL tls_mnemonic_missing"
700
+ echo "compute-source-env.sh: ERROR - TLS binary present but MNEMONIC not available"
701
+ echo "compute-source-env.sh: Cannot obtain TLS certificate without mnemonic"
828
702
  exit 1
829
703
  fi
830
-
704
+
831
705
  if [ ! -x /usr/local/bin/caddy ]; then
832
- echo "compute-source-env.sh: ERROR - TLS requested but Caddy not found"
833
- echo "ECLOUD_FAIL tls_caddy_missing"
706
+ echo "compute-source-env.sh: ERROR - TLS binary present but Caddy not found"
834
707
  exit 1
835
708
  fi
836
-
709
+
710
+ echo "compute-source-env.sh: Setting up TLS for domain: $domain"
711
+
712
+ # Obtain TLS certificate using ACME
713
+ # Default to http-01, but allow override via ACME_CHALLENGE env var
837
714
  local challenge="\${ACME_CHALLENGE:-http-01}"
715
+
716
+ # Check if we should use staging (for testing)
838
717
  local staging_flag=""
839
718
  if [ "\${ACME_STAGING:-false}" = "true" ]; then
840
719
  staging_flag="-staging"
841
- echo "compute-source-env.sh: Using Let's Encrypt STAGING environment"
842
- fi
843
-
844
- local certs_issued=0
845
-
846
- if [ -n "$platform_host" ]; then
847
- if issue_cert_for "$platform_host" "/run/tls/platform" "$mnemonic" "$challenge" "$staging_flag"; then
848
- certs_issued=$((certs_issued + 1))
849
- else
850
- echo "compute-source-env.sh: ERROR - failed to issue cert for platform host $platform_host"
851
- echo "ECLOUD_FAIL tls_setup"
852
- exit 1
853
- fi
854
- fi
855
-
856
- if [ -n "$user_domain" ]; then
857
- if issue_cert_for "$user_domain" "/run/tls/domain" "$mnemonic" "$challenge" "$staging_flag"; then
858
- certs_issued=$((certs_issued + 1))
859
- else
860
- echo "compute-source-env.sh: ERROR - failed to issue cert for user domain $user_domain"
861
- echo "ECLOUD_FAIL tls_setup"
862
- exit 1
863
- fi
720
+ echo "compute-source-env.sh: Using Let's Encrypt STAGING environment (certificates won't be trusted)"
864
721
  fi
865
-
866
- if [ "$certs_issued" -eq 0 ]; then
867
- echo "compute-source-env.sh: no certs issued, skipping Caddy (app will serve plaintext until DNS is wired)"
868
- return 0
722
+
723
+ echo "compute-source-env.sh: Obtaining TLS certificate using $challenge challenge..."
724
+ # Pass the API URL for certificate persistence
725
+ if ! SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt \\
726
+ MNEMONIC="$mnemonic" DOMAIN="$domain" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
727
+ -challenge "$challenge" \\
728
+ $staging_flag; then
729
+ echo "compute-source-env.sh: ERROR - Failed to obtain TLS certificate"
730
+ echo "compute-source-env.sh: Certificate issuance failed for $domain"
731
+ exit 1
869
732
  fi
870
-
871
- # Caddy's validate step checks that every \`tls <cert> <key>\` file
872
- # exists, even on site blocks bound to dormant placeholder
873
- # hostnames. The default Caddyfile declares both a platform site
874
- # and a user-domain site; when only one is configured, the other
875
- # block's cert paths are never populated and validate fails with
876
- # "Invalid Caddyfile". Point the unused block at the issued
877
- # block's cert files so validate passes \u2014 the dormant block can't
878
- # receive real traffic (its hostname falls back to
879
- # localhost.{platform,user}.invalid, which Caddy routes by SNI and
880
- # never matches public traffic), so the symlink is never actually
881
- # presented. Skipped when a user-supplied Caddyfile is in use,
882
- # since we don't know what cert paths it references.
883
- if [ -d /run/tls/platform ] && [ ! -e /run/tls/domain/fullchain.pem ]; then
884
- mkdir -p /run/tls/domain
885
- ln -sf /run/tls/platform/fullchain.pem /run/tls/domain/fullchain.pem
886
- ln -sf /run/tls/platform/privkey.pem /run/tls/domain/privkey.pem
887
- elif [ -d /run/tls/domain ] && [ ! -e /run/tls/platform/fullchain.pem ]; then
888
- mkdir -p /run/tls/platform
889
- ln -sf /run/tls/domain/fullchain.pem /run/tls/platform/fullchain.pem
890
- ln -sf /run/tls/domain/privkey.pem /run/tls/platform/privkey.pem
733
+
734
+ echo "compute-source-env.sh: TLS certificate obtained successfully"
735
+
736
+ # Validate Caddyfile before starting
737
+ if ! /usr/local/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile 2>/dev/null; then
738
+ echo "compute-source-env.sh: ERROR - Invalid Caddyfile"
739
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
740
+ exit 1
891
741
  fi
892
-
893
- # Validate Caddyfile before starting. Don't redirect stderr \u2014 when
894
- # validate fails, Caddy's diagnostic is the only signal that lands
895
- # in ReadinessError.SerialTail, so silencing it leaves operators
896
- # staring at a bare "tls_invalid_caddyfile" with no detail.
897
- if [ -f /etc/caddy/Caddyfile ]; then
898
- if ! /usr/local/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile; then
899
- echo "compute-source-env.sh: ERROR - Invalid Caddyfile"
900
- echo "ECLOUD_FAIL tls_invalid_caddyfile"
742
+
743
+ # Start Caddy in background
744
+ echo "compute-source-env.sh: Starting Caddy reverse proxy..."
745
+
746
+ # Check if Caddy logs should be enabled
747
+ if [ "\${ENABLE_CADDY_LOGS:-false}" = "true" ]; then
748
+ if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1; then
749
+ echo "compute-source-env.sh: ERROR - Failed to start Caddy"
750
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
901
751
  exit 1
902
752
  fi
903
-
904
- echo "compute-source-env.sh: Starting Caddy reverse proxy..."
905
- if [ "\${ENABLE_CADDY_LOGS:-false}" = "true" ]; then
906
- if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1; then
907
- echo "compute-source-env.sh: ERROR - Failed to start Caddy"
908
- echo "ECLOUD_FAIL tls_caddy_start"
909
- exit 1
910
- fi
911
- else
912
- if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
913
- echo "compute-source-env.sh: ERROR - Failed to start Caddy"
914
- echo "ECLOUD_FAIL tls_caddy_start"
915
- exit 1
916
- fi
917
- fi
918
-
919
- sleep 2
920
- echo "compute-source-env.sh: Caddy started successfully"
921
753
  else
922
- echo "compute-source-env.sh: No Caddyfile found, skipping Caddy"
754
+ # Redirect Caddy output to /dev/null to silence logs
755
+ if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
756
+ echo "compute-source-env.sh: ERROR - Failed to start Caddy"
757
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
758
+ exit 1
759
+ fi
923
760
  fi
924
-
761
+
762
+ # Give Caddy a moment to fully initialize
763
+ sleep 2
764
+ echo "compute-source-env.sh: Caddy started successfully"
925
765
  return 0
926
766
  }
927
767
 
@@ -932,309 +772,15 @@ setup_tls
932
772
  export KMS_SERVER_URL="{{kmsServerURL}}"
933
773
  export KMS_PUBLIC_KEY="$(cat /usr/local/bin/kms-signing-public-key.pem)"
934
774
 
935
- # \u2500\u2500 Prewarm-detach: wait for PD if expected \u2500\u2500
936
- # Orchestrator sets ECLOUD_PD_EXPECTED=1 on apps using StorageBackend=pd.
937
- # When the prewarm path is used, the new VM boots WITHOUT the disk; we
938
- # signal awaiting-userdata and poll until the disk is attached.
939
- USERDATA_MOUNT="/mnt/disks/userdata"
940
- USERDATA_DEV="/dev/disk/by-id/google-persistent_storage_1"
941
-
942
- # userdata_mount_backing_dev resolves the kernel device that currently
943
- # backs $USERDATA_MOUNT (or empty string if not mounted). The output is
944
- # canonicalized via readlink so we can compare against $USERDATA_DEV
945
- # without caring whether the mount table records the symlinked
946
- # /dev/disk/by-id/... path or the underlying /dev/sdb-style path.
947
- #
948
- # Tries findmnt first (clean structured output), falls back to parsing
949
- # /proc/self/mountinfo so we don't add a hard dependency on util-linux
950
- # being present in the image.
951
- userdata_mount_backing_dev() {
952
- if command -v findmnt >/dev/null 2>&1; then
953
- local src
954
- src=$(findmnt -n -o SOURCE -- "$USERDATA_MOUNT" 2>/dev/null) || return 0
955
- [ -n "$src" ] || return 0
956
- readlink -f "$src" 2>/dev/null || echo "$src"
957
- return 0
958
- fi
959
- # /proc/self/mountinfo line shape (kernel docs): mount-id parent-id
960
- # major:minor root mount-point ... So column 5 is the mount point and
961
- # the device path lives in the optional fields after the "-" separator.
962
- # We just need the major:minor \u2192 device mapping; awk + a /sys lookup
963
- # is cleaner than parsing the mountinfo's optional-field section.
964
- local maj_min dev_basename
965
- maj_min=$(awk -v mp="$USERDATA_MOUNT" '$5 == mp { print $3 }' /proc/self/mountinfo 2>/dev/null | head -1)
966
- [ -n "$maj_min" ] || return 0
967
- dev_basename=$(readlink "/sys/dev/block/\${maj_min}" 2>/dev/null | awk -F/ '{print $NF}')
968
- [ -n "$dev_basename" ] || return 0
969
- readlink -f "/dev/\${dev_basename}" 2>/dev/null || echo "/dev/\${dev_basename}"
970
- }
971
-
972
- # expected_userdata_dev resolves $USERDATA_DEV through any symlinks so
973
- # string-compare against userdata_mount_backing_dev is meaningful even
974
- # when the kernel hands us a /dev/nvme0n1-style path while
975
- # /dev/disk/by-id/google-persistent_storage_1 is the symlink GCE
976
- # publishes.
977
- expected_userdata_dev() {
978
- if [ -e "$USERDATA_DEV" ]; then
979
- readlink -f "$USERDATA_DEV" 2>/dev/null || echo "$USERDATA_DEV"
980
- else
981
- echo "$USERDATA_DEV"
982
- fi
983
- }
984
-
985
- wait_for_userdata() {
986
- if [ "\${ECLOUD_PD_EXPECTED:-0}" != "1" ]; then
987
- return 0
988
- fi
989
- if mountpoint -q "$USERDATA_MOUNT" 2>/dev/null; then
990
- # The mount slot is taken \u2014 but is it taken by the PD we're
991
- # about to attach, or by something else? The Confidential Space
992
- # TEE runtime's SetupSecondaryEncryptedVolume step probes for a
993
- # secondary GCE disk once at boot, doesn't see it (because the
994
- # orchestrator hasn't called AttachPD yet \u2014 the contract says
995
- # the new VM signals awaiting-userdata FIRST), and falls back
996
- # to mounting an encrypted folder on the BOOT disk at this same
997
- # path. If we accept that mount as-is, the user app boots on a
998
- # fresh empty filesystem and the orchestrator's late AttachPD
999
- # silently lands on a VM that's already serving from boot disk.
1000
- # See docs/solutions/2026-05-15-prewarm-detach-pd-preservation-boot-race.md
1001
- # in ecloud-platform.
1002
- local backing expected
1003
- backing=$(userdata_mount_backing_dev)
1004
- expected=$(expected_userdata_dev)
1005
- if [ -n "$backing" ] && [ "$backing" = "$expected" ]; then
1006
- echo "compute-source-env.sh: userdata already mounted at $USERDATA_MOUNT (backed by $backing)"
1007
- return 0
1008
- fi
1009
- # Mount slot taken, but NOT by our PD. This is the runtime
1010
- # premount on boot disk. Try to recover by lazy-unmounting (the
1011
- # filesystem is fresh and unused \u2014 no app has written to it
1012
- # yet), then fall through to the normal wait-for-PD loop.
1013
- echo "compute-source-env.sh: WARNING - $USERDATA_MOUNT premounted by TEE runtime fallback (backing=\${backing:-unknown}, expected=$expected); attempting recovery"
1014
- if ! umount -l "$USERDATA_MOUNT" 2>/dev/null; then
1015
- # Lazy unmount should never fail under normal circumstances \u2014
1016
- # it doesn't wait for open files, just detaches the namespace.
1017
- # If it did fail, we have no path forward: the orchestrator's
1018
- # strict-mode WaitAwaitingUserdata gate (ecloud-platform PR
1019
- # #174) catches our ECLOUD_FAIL within one poll tick and
1020
- # rolls back, leaving the user's data safe on the old VM.
1021
- echo "ECLOUD_FAIL pd_runtime_premount_unrecoverable"
1022
- exit 1
1023
- fi
1024
- echo "compute-source-env.sh: unmounted runtime fallback at $USERDATA_MOUNT, waiting for real PD"
1025
- fi
1026
- # Refuse to proceed if the tools we need for safe first-attach
1027
- # detection are missing. Without blkid we cannot tell an empty new
1028
- # disk from an already-formatted one \u2014 running mkfs.ext4 on the
1029
- # latter would destroy data.
1030
- if ! command -v blkid >/dev/null 2>&1; then
1031
- echo "ECLOUD_FAIL pd_tools_missing"
1032
- exit 1
1033
- fi
1034
- echo "ECLOUD_AWAITING_USERDATA"
1035
- echo "compute-source-env.sh: waiting for PD at $USERDATA_DEV..."
1036
- # Poll for up to 10 minutes (120 * 5s). The orchestrator's overall
1037
- # attach timeout is shorter; the ceiling here just bounds the wait
1038
- # for manual / diagnostic scenarios.
1039
- local i=0
1040
- local mount_failures=0
1041
- while [ "$i" -lt 120 ]; do
1042
- if [ -e "$USERDATA_DEV" ]; then
1043
- mkdir -p "$USERDATA_MOUNT"
1044
- if mount -o noatime "$USERDATA_DEV" "$USERDATA_MOUNT" 2>/dev/null; then
1045
- echo "compute-source-env.sh: PD mounted at $USERDATA_MOUNT"
1046
- return 0
1047
- fi
1048
- # Disk present but mount failed. Check whether it has a
1049
- # recognized filesystem. \`blkid -s TYPE -o value\` prints the
1050
- # FS type (empty if none). We only mkfs when there is
1051
- # demonstrably NO filesystem \u2014 never on the basis of blkid
1052
- # returning non-zero alone, which could mean "blkid missing"
1053
- # or "device busy".
1054
- local fstype
1055
- fstype=$(blkid -s TYPE -o value "$USERDATA_DEV" 2>/dev/null)
1056
- if [ -z "$fstype" ]; then
1057
- echo "compute-source-env.sh: formatting $USERDATA_DEV (first attach)"
1058
- mkfs.ext4 -F -L eclouddata "$USERDATA_DEV" >/dev/null 2>&1 || {
1059
- echo "ECLOUD_FAIL pd_mkfs_failed"
1060
- exit 1
1061
- }
1062
- mount -o noatime "$USERDATA_DEV" "$USERDATA_MOUNT" || {
1063
- echo "ECLOUD_FAIL pd_mount_after_format_failed"
1064
- exit 1
1065
- }
1066
- return 0
1067
- fi
1068
- # Disk has a filesystem but mount still failed. Give it a
1069
- # few retries to cover transient cases (device busy, udev
1070
- # still settling), but don't pretend this is an attach
1071
- # timeout if it persists.
1072
- mount_failures=$((mount_failures + 1))
1073
- if [ "$mount_failures" -ge 6 ]; then
1074
- echo "ECLOUD_FAIL pd_mount_failed"
1075
- exit 1
1076
- fi
1077
- else
1078
- # Device disappeared (e.g. udev re-enumeration between
1079
- # attach and mount). Reset the consecutive-failure counter
1080
- # so only true back-to-back mount failures trip
1081
- # pd_mount_failed; a device blip should not steal retries.
1082
- mount_failures=0
1083
- fi
1084
- i=$((i + 1))
1085
- sleep 5
1086
- done
1087
- echo "ECLOUD_FAIL pd_attach_timeout"
1088
- exit 1
1089
- }
1090
-
1091
- wait_for_userdata
1092
-
1093
- # \u2500\u2500 Prewarm-detach: install SIGTERM handler for graceful drain \u2500\u2500
1094
- # Orchestrator signals drain by setting the instance metadata key
1095
- # ECLOUD_DRAIN_REQUESTED=1, which a host-level agent translates into
1096
- # SIGTERM on PID 1. On SIGTERM we:
1097
- # 1. Forward to the child (wakes the user's app for graceful exit)
1098
- # 2. Wait for child exit
1099
- # 3. Sync + unmount the PD
1100
- # 4. Emit ECLOUD_DETACHED so the orchestrator can proceed to detach
1101
- CHILD_PID=""
1102
- _DRAIN_IN_PROGRESS=0
1103
-
1104
- drain_handler() {
1105
- # Guard against re-entry if SIGTERM arrives twice (e.g. both the
1106
- # drain_watcher and an external signal fire in quick succession).
1107
- if [ "$_DRAIN_IN_PROGRESS" = "1" ]; then
1108
- return 0
1109
- fi
1110
- _DRAIN_IN_PROGRESS=1
1111
- echo "compute-source-env.sh: received drain signal, forwarding to child pgid=$CHILD_PID"
1112
- if [ -n "$CHILD_PID" ]; then
1113
- # Send to the process group so intermediate wrappers (su, sh -c,
1114
- # etc.) don't swallow the signal. The leading \`-\` targets the
1115
- # pgid, which equals the direct child's pid for a shell-backgrounded
1116
- # process. Fall back to the pid alone if pgid signaling fails
1117
- # (e.g. kernel older than 3.9 or PID namespace edge cases).
1118
- kill -TERM -"$CHILD_PID" 2>/dev/null || kill -TERM "$CHILD_PID" 2>/dev/null || true
1119
- # Give the app up to 30s to exit cleanly.
1120
- local i=0
1121
- while [ "$i" -lt 30 ] && kill -0 "$CHILD_PID" 2>/dev/null; do
1122
- i=$((i + 1))
1123
- sleep 1
1124
- done
1125
- if kill -0 "$CHILD_PID" 2>/dev/null; then
1126
- echo "compute-source-env.sh: child did not exit in 30s, sending SIGKILL"
1127
- kill -KILL -"$CHILD_PID" 2>/dev/null || kill -KILL "$CHILD_PID" 2>/dev/null || true
1128
- # Reap the process so its in-flight I/O is flushed to the
1129
- # filesystem before we sync + unmount. SIGKILL schedules
1130
- # death; wait guarantees it's complete.
1131
- wait "$CHILD_PID" 2>/dev/null || true
1132
- fi
1133
- fi
1134
- if [ "\${ECLOUD_PD_EXPECTED:-0}" = "1" ] && mountpoint -q "$USERDATA_MOUNT" 2>/dev/null; then
1135
- sync
1136
- if umount "$USERDATA_MOUNT" 2>/dev/null; then
1137
- echo "compute-source-env.sh: unmounted $USERDATA_MOUNT cleanly"
1138
- else
1139
- # Force lazy unmount as last resort \u2014 orchestrator still needs
1140
- # the DETACHED signal to proceed.
1141
- umount -l "$USERDATA_MOUNT" 2>/dev/null || true
1142
- echo "compute-source-env.sh: WARNING - used lazy unmount on $USERDATA_MOUNT"
1143
- fi
1144
- # ECLOUD_DETACHED is strictly a PD-lifecycle signal. Only emit
1145
- # it when we actually had a PD mount in play, so serial-log
1146
- # parsers and alerting for non-PD apps don't see spurious
1147
- # lifecycle markers on routine container SIGTERM.
1148
- echo "ECLOUD_DETACHED"
1149
- fi
1150
- # Always exit 0: drain is a managed shutdown and the orchestrator
1151
- # waits on ECLOUD_DETACHED, not the container exit code. Forwarding
1152
- # the child's exit status here would make a crash-during-drain look
1153
- # like a drain failure to whatever reads the container exit code.
1154
- exit 0
1155
- }
1156
- trap drain_handler TERM
1157
-
1158
- # \u2500\u2500 Prewarm-detach: background drain watcher \u2500\u2500
1159
- # Container metadata delivery in Confidential Space is limited, so we
1160
- # poll the instance metadata server for ECLOUD_DRAIN_REQUESTED and
1161
- # raise SIGTERM on ourselves when it flips to "1".
1162
- #
1163
- # Try wget first (present in most Alpine bases), fall back to curl.
1164
- # If neither is present, drain watcher is disabled \u2014 the orchestrator
1165
- # will hit its drain timeout and fail the upgrade explicitly, which is
1166
- # the correct behavior (we cannot silently ignore a drain request).
1167
- _fetch_drain_flag() {
1168
- local url="http://metadata.google.internal/computeMetadata/v1/instance/attributes/ECLOUD_DRAIN_REQUESTED"
1169
- if command -v wget >/dev/null 2>&1; then
1170
- wget -q --tries=1 --timeout=2 --header='Metadata-Flavor: Google' -O - "$url" 2>/dev/null
1171
- elif command -v curl >/dev/null 2>&1; then
1172
- curl -sf --max-time 2 -H 'Metadata-Flavor: Google' "$url" 2>/dev/null
1173
- else
1174
- return 2
1175
- fi
1176
- }
1177
-
1178
- drain_watcher() {
1179
- # Preflight: confirm we have an HTTP client
1180
- if ! _fetch_drain_flag >/dev/null 2>&1; then
1181
- # Either no http client available OR metadata server not
1182
- # responding yet. If no client, give up and log; otherwise the
1183
- # loop below will retry.
1184
- if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then
1185
- echo "compute-source-env.sh: WARNING - no wget/curl; drain_watcher disabled"
1186
- return 0
1187
- fi
1188
- fi
1189
- while true; do
1190
- local v
1191
- v=$(_fetch_drain_flag || true)
1192
- if [ "$v" = "1" ]; then
1193
- echo "compute-source-env.sh: drain_watcher saw ECLOUD_DRAIN_REQUESTED=1, signaling PID 1"
1194
- # The CS launcher runs this script directly as PID 1, so
1195
- # kill -TERM 1 delivers SIGTERM to the shell that installed
1196
- # the drain_handler trap. If the launch mechanism ever
1197
- # wraps this script in another process, this assumption
1198
- # breaks and drain will silently no-op \u2014 audit here.
1199
- kill -TERM 1 2>/dev/null || true
1200
- return 0
1201
- fi
1202
- sleep 2
1203
- done
1204
- }
1205
-
1206
- if [ "\${ECLOUD_PD_EXPECTED:-0}" = "1" ]; then
1207
- # Assumption: the orchestrator only flips ECLOUD_DRAIN_REQUESTED=1
1208
- # after observing ECLOUD_AWAITING_USERDATA (old VM) or
1209
- # ECLOUD_READY (new VM), so CHILD_PID is always set by the time
1210
- # drain_handler fires. If drain somehow arrived in the tiny window
1211
- # between this watcher spawn and CHILD_PID assignment below,
1212
- # drain_handler would skip the child-kill branch and still emit
1213
- # ECLOUD_DETACHED \u2014 harmless because there's nothing to drain yet.
1214
- if [ -x /usr/local/bin/ecloud-drain-watcher ]; then
1215
- /usr/local/bin/ecloud-drain-watcher &
1216
- else
1217
- drain_watcher &
1218
- fi
1219
- fi
1220
-
1221
775
  echo "compute-source-env.sh: Environment sourced."
1222
- echo "ECLOUD_READY runtime_bootstrapped"
1223
776
 
1224
777
  # Drop privileges to original user for the application command
1225
778
  if [ -n "$__ECLOUD_ORIGINAL_USER" ] && [ "$(id -u)" = "0" ]; then
1226
779
  echo "compute-source-env.sh: Dropping privileges to user: $__ECLOUD_ORIGINAL_USER"
1227
- # Must background the child so our trap can fire; exec replaces PID 1.
1228
- su -s /bin/sh "$__ECLOUD_ORIGINAL_USER" -c 'exec "$@"' -- sh "$@" &
1229
- CHILD_PID=$!
1230
- wait "$CHILD_PID"
1231
- exit $?
780
+ exec su -s /bin/sh "$__ECLOUD_ORIGINAL_USER" -c 'exec "$@"' -- sh "$@"
1232
781
  fi
1233
782
 
1234
- "$@" &
1235
- CHILD_PID=$!
1236
- wait "$CHILD_PID"
1237
- exit $?
783
+ exec "$@"
1238
784
  `;
1239
785
 
1240
786
  // src/client/common/templates/scriptTemplate.ts
@@ -1243,14 +789,6 @@ function processScriptTemplate(data) {
1243
789
  return template(data);
1244
790
  }
1245
791
 
1246
- // src/client/common/templates/Caddyfile.default.tmpl
1247
- 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';
1248
-
1249
- // src/client/common/templates/caddyfileTemplate.ts
1250
- function getDefaultCaddyfile() {
1251
- return Caddyfile_default_default;
1252
- }
1253
-
1254
792
  // keys/mainnet-alpha/prod/kms-encryption-public-key.pem
1255
793
  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-----";
1256
794
 
@@ -1426,9 +964,15 @@ async function layerLocalImage(options, logger) {
1426
964
  const imageConfig = await extractImageConfig(docker, sourceImageRef);
1427
965
  const originalCmd = imageConfig.cmd.length > 0 ? imageConfig.cmd : imageConfig.entrypoint;
1428
966
  const originalUser = imageConfig.user;
1429
- const includeTLS = true;
1430
- const drainWatcherSource = findBinary("ecloud-drain-watcher-linux-amd64");
1431
- const includeDrainWatcher = fs.existsSync(drainWatcherSource);
967
+ let includeTLS = false;
968
+ if (envFilePath && fs.existsSync(envFilePath)) {
969
+ const envContent = fs.readFileSync(envFilePath, "utf-8");
970
+ const domainMatch = envContent.match(/^DOMAIN=(.+)$/m);
971
+ if (domainMatch && domainMatch[1] && domainMatch[1] !== "localhost") {
972
+ includeTLS = true;
973
+ logger.debug(`Found DOMAIN=${domainMatch[1]} in ${envFilePath}, including TLS components`);
974
+ }
975
+ }
1432
976
  const layeredDockerfileContent = processDockerfileTemplate({
1433
977
  baseImage: sourceImageRef,
1434
978
  originalCmd: JSON.stringify(originalCmd),
@@ -1436,9 +980,8 @@ async function layerLocalImage(options, logger) {
1436
980
  logRedirect,
1437
981
  resourceUsageAllow,
1438
982
  includeTLS,
1439
- ecloudCLIVersion: "0.1.0",
983
+ ecloudCLIVersion: "0.1.0"
1440
984
  // TODO: Get from package.json
1441
- includeDrainWatcher
1442
985
  });
1443
986
  const scriptContent = processScriptTemplate({
1444
987
  kmsServerURL: environmentConfig.kmsServerURL,
@@ -1448,8 +991,7 @@ async function layerLocalImage(options, logger) {
1448
991
  environmentConfig,
1449
992
  layeredDockerfileContent,
1450
993
  scriptContent,
1451
- includeTLS,
1452
- includeDrainWatcher ? drainWatcherSource : void 0
994
+ includeTLS
1453
995
  // logger
1454
996
  );
1455
997
  try {
@@ -1464,7 +1006,7 @@ async function layerLocalImage(options, logger) {
1464
1006
  fs.rmSync(tempDir, { recursive: true, force: true });
1465
1007
  }
1466
1008
  }
1467
- async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileContent, scriptContent, includeTLS, drainWatcherSource) {
1009
+ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileContent, scriptContent, includeTLS) {
1468
1010
  const tempDir = fs.mkdtempSync(path2.join(os.tmpdir(), LAYERED_BUILD_DIR_PREFIX));
1469
1011
  try {
1470
1012
  const layeredDockerfilePath = path2.join(tempDir, LAYERED_DOCKERFILE_NAME);
@@ -1488,11 +1030,6 @@ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileCo
1488
1030
  }
1489
1031
  fs.copyFileSync(kmsClientSource, kmsClientPath);
1490
1032
  fs.chmodSync(kmsClientPath, 493);
1491
- if (drainWatcherSource && fs.existsSync(drainWatcherSource)) {
1492
- const drainWatcherPath = path2.join(tempDir, DRAIN_WATCHER_BINARY_NAME);
1493
- fs.copyFileSync(drainWatcherSource, drainWatcherPath);
1494
- fs.chmodSync(drainWatcherPath, 493);
1495
- }
1496
1033
  if (includeTLS) {
1497
1034
  const tlsKeygenPath = path2.join(tempDir, TLS_KEYGEN_BINARY_NAME);
1498
1035
  const tlsKeygenSource = findBinary("tls-keygen-linux-amd64");
@@ -1503,13 +1040,15 @@ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileCo
1503
1040
  }
1504
1041
  fs.copyFileSync(tlsKeygenSource, tlsKeygenPath);
1505
1042
  fs.chmodSync(tlsKeygenPath, 493);
1506
- const userCaddyfilePath = path2.join(process.cwd(), CADDYFILE_NAME);
1507
- const destCaddyfilePath = path2.join(tempDir, CADDYFILE_NAME);
1508
- if (fs.existsSync(userCaddyfilePath)) {
1509
- fs.copyFileSync(userCaddyfilePath, destCaddyfilePath);
1510
- fs.chmodSync(destCaddyfilePath, 420);
1043
+ const caddyfilePath = path2.join(process.cwd(), CADDYFILE_NAME);
1044
+ if (fs.existsSync(caddyfilePath)) {
1045
+ const caddyfileContent = fs.readFileSync(caddyfilePath);
1046
+ const destCaddyfilePath = path2.join(tempDir, CADDYFILE_NAME);
1047
+ fs.writeFileSync(destCaddyfilePath, caddyfileContent, { mode: 420 });
1511
1048
  } else {
1512
- fs.writeFileSync(destCaddyfilePath, getDefaultCaddyfile(), { mode: 420 });
1049
+ throw new Error(
1050
+ "TLS is enabled (DOMAIN is set) but Caddyfile not found. Run configure TLS to set up TLS configuration"
1051
+ );
1513
1052
  }
1514
1053
  }
1515
1054
  return tempDir;
@@ -1577,16 +1116,7 @@ async function extractDigestFromSinglePlatform(manifest, imageRef) {
1577
1116
  architecture: inspectData[0].Architecture
1578
1117
  } : null;
1579
1118
  if (!config) {
1580
- if (manifest.config?.digest) {
1581
- const digest = hexStringToBytes32(manifest.config.digest);
1582
- const registry = extractRegistryName(imageRef);
1583
- return {
1584
- digest,
1585
- registry,
1586
- platform: DOCKER_PLATFORM
1587
- };
1588
- }
1589
- throw new Error(`Could not determine platform for ${imageRef}`);
1119
+ throw createPlatformErrorMessage(imageRef, ["unknown (could not determine architecture)"]);
1590
1120
  }
1591
1121
  const platform2 = `${config.os}/${config.architecture}`;
1592
1122
  if (platform2 === DOCKER_PLATFORM) {
@@ -1658,14 +1188,11 @@ Image: ${imageRef}
1658
1188
  Found platform(s): ${platforms.join(", ")}
1659
1189
  Required platform: ${DOCKER_PLATFORM}
1660
1190
 
1661
- To fix this issue:
1662
- 1. Manual fix:
1663
- a. Rebuild your image with the correct platform:
1664
- docker build --platform ${DOCKER_PLATFORM} -t ${imageRef} .
1665
- b. Push the rebuilt image to your remote registry:
1666
- docker push ${imageRef}
1667
-
1668
- 2. Or use the SDK to build with the correct platform automatically.`;
1191
+ To fix, either:
1192
+ 1. Rebuild the image for ${DOCKER_PLATFORM} and push it:
1193
+ docker buildx build --platform ${DOCKER_PLATFORM} -t ${imageRef} --push .
1194
+ 2. Or use a verifiable build (--verifiable --repo <repo> --commit <sha>), which
1195
+ builds server-side and needs no local Docker.`;
1669
1196
  return new Error(errorMsg);
1670
1197
  }
1671
1198
 
@@ -1770,6 +1297,8 @@ async function prepareRelease(options, logger) {
1770
1297
  logger.info(`Waiting ${REGISTRY_PROPAGATION_WAIT_SECONDS} seconds for registry propagation...`);
1771
1298
  await new Promise((resolve2) => setTimeout(resolve2, REGISTRY_PROPAGATION_WAIT_SECONDS * 1e3));
1772
1299
  } else {
1300
+ logger.info("Verifying image platform (linux/amd64)...");
1301
+ await getImageDigestAndName(imageRef);
1773
1302
  logger.info("Checking if image needs layering...");
1774
1303
  finalImageRef = await layerRemoteImageIfNeeded(
1775
1304
  {
@@ -1928,6 +1457,13 @@ function extractRegistryNameNoDocker(imageRef) {
1928
1457
  var import_viem = require("viem");
1929
1458
 
1930
1459
  // src/client/common/types/index.ts
1460
+ var EMPTY_CONTAINER_POLICY = {
1461
+ args: [],
1462
+ cmdOverride: [],
1463
+ env: [],
1464
+ envOverride: [],
1465
+ restartPolicy: ""
1466
+ };
1931
1467
  var noopLogger = {
1932
1468
  debug: () => {
1933
1469
  },
@@ -3168,49 +2704,1594 @@ async function executeBatch(options, logger = noopLogger) {
3168
2704
  } else {
3169
2705
  revertReason = callError.message || "Unknown reason";
3170
2706
  }
3171
- }
3172
- throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2707
+ }
2708
+ throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2709
+ }
2710
+ return hash;
2711
+ }
2712
+
2713
+ // src/client/common/contract/caller.ts
2714
+ var import_viem3 = require("viem");
2715
+
2716
+ // src/client/common/utils/helpers.ts
2717
+ var import_viem2 = require("viem");
2718
+ var import_chains2 = require("viem/chains");
2719
+ var import_accounts = require("viem/accounts");
2720
+ function getChainFromID(chainID, fallback2 = import_chains2.sepolia) {
2721
+ const id = Number(chainID);
2722
+ return (0, import_viem2.extractChain)({ chains: SUPPORTED_CHAINS, id }) || fallback2;
2723
+ }
2724
+ function createClients(options) {
2725
+ const { privateKey, rpcUrl, chainId } = options;
2726
+ const privateKeyHex = addHexPrefix(privateKey);
2727
+ const account = (0, import_accounts.privateKeyToAccount)(privateKeyHex);
2728
+ const chain = getChainFromID(chainId);
2729
+ const transport = typeof rpcUrl === "string" ? (0, import_viem2.http)(rpcUrl) : (0, import_viem2.fallback)(rpcUrl.map((url) => (0, import_viem2.http)(url)));
2730
+ const publicClient = (0, import_viem2.createPublicClient)({
2731
+ chain,
2732
+ transport
2733
+ });
2734
+ const walletClient = (0, import_viem2.createWalletClient)({
2735
+ account,
2736
+ chain,
2737
+ transport
2738
+ });
2739
+ return { walletClient, publicClient };
2740
+ }
2741
+ function addHexPrefix(value) {
2742
+ return value.startsWith("0x") ? value : `0x${value}`;
2743
+ }
2744
+ function stripHexPrefix(value) {
2745
+ return value.startsWith("0x") ? value.slice(2) : value;
2746
+ }
2747
+
2748
+ // src/client/common/abis/AppController.json
2749
+ var AppController_default = [
2750
+ {
2751
+ type: "constructor",
2752
+ inputs: [
2753
+ {
2754
+ name: "_version",
2755
+ type: "string",
2756
+ internalType: "string"
2757
+ },
2758
+ {
2759
+ name: "_permissionController",
2760
+ type: "address",
2761
+ internalType: "contractIPermissionController"
2762
+ },
2763
+ {
2764
+ name: "_releaseManager",
2765
+ type: "address",
2766
+ internalType: "contractIReleaseManager"
2767
+ },
2768
+ {
2769
+ name: "_computeAVSRegistrar",
2770
+ type: "address",
2771
+ internalType: "contractIComputeAVSRegistrar"
2772
+ },
2773
+ {
2774
+ name: "_computeOperator",
2775
+ type: "address",
2776
+ internalType: "contractIComputeOperator"
2777
+ },
2778
+ {
2779
+ name: "_appBeacon",
2780
+ type: "address",
2781
+ internalType: "contractIBeacon"
2782
+ }
2783
+ ],
2784
+ stateMutability: "nonpayable"
2785
+ },
2786
+ {
2787
+ type: "function",
2788
+ name: "API_PERMISSION_TYPEHASH",
2789
+ inputs: [],
2790
+ outputs: [
2791
+ {
2792
+ name: "",
2793
+ type: "bytes32",
2794
+ internalType: "bytes32"
2795
+ }
2796
+ ],
2797
+ stateMutability: "view"
2798
+ },
2799
+ {
2800
+ type: "function",
2801
+ name: "appBeacon",
2802
+ inputs: [],
2803
+ outputs: [
2804
+ {
2805
+ name: "",
2806
+ type: "address",
2807
+ internalType: "contractIBeacon"
2808
+ }
2809
+ ],
2810
+ stateMutability: "view"
2811
+ },
2812
+ {
2813
+ type: "function",
2814
+ name: "calculateApiPermissionDigestHash",
2815
+ inputs: [
2816
+ {
2817
+ name: "permission",
2818
+ type: "bytes4",
2819
+ internalType: "bytes4"
2820
+ },
2821
+ {
2822
+ name: "expiry",
2823
+ type: "uint256",
2824
+ internalType: "uint256"
2825
+ }
2826
+ ],
2827
+ outputs: [
2828
+ {
2829
+ name: "",
2830
+ type: "bytes32",
2831
+ internalType: "bytes32"
2832
+ }
2833
+ ],
2834
+ stateMutability: "view"
2835
+ },
2836
+ {
2837
+ type: "function",
2838
+ name: "calculateAppId",
2839
+ inputs: [
2840
+ {
2841
+ name: "deployer",
2842
+ type: "address",
2843
+ internalType: "address"
2844
+ },
2845
+ {
2846
+ name: "salt",
2847
+ type: "bytes32",
2848
+ internalType: "bytes32"
2849
+ }
2850
+ ],
2851
+ outputs: [
2852
+ {
2853
+ name: "",
2854
+ type: "address",
2855
+ internalType: "contractIApp"
2856
+ }
2857
+ ],
2858
+ stateMutability: "view"
2859
+ },
2860
+ {
2861
+ type: "function",
2862
+ name: "computeAVSRegistrar",
2863
+ inputs: [],
2864
+ outputs: [
2865
+ {
2866
+ name: "",
2867
+ type: "address",
2868
+ internalType: "contractIComputeAVSRegistrar"
2869
+ }
2870
+ ],
2871
+ stateMutability: "view"
2872
+ },
2873
+ {
2874
+ type: "function",
2875
+ name: "computeOperator",
2876
+ inputs: [],
2877
+ outputs: [
2878
+ {
2879
+ name: "",
2880
+ type: "address",
2881
+ internalType: "contractIComputeOperator"
2882
+ }
2883
+ ],
2884
+ stateMutability: "view"
2885
+ },
2886
+ {
2887
+ type: "function",
2888
+ name: "confirmUpgrade",
2889
+ inputs: [
2890
+ {
2891
+ name: "app",
2892
+ type: "address",
2893
+ internalType: "contractIApp"
2894
+ }
2895
+ ],
2896
+ outputs: [],
2897
+ stateMutability: "nonpayable"
2898
+ },
2899
+ {
2900
+ type: "function",
2901
+ name: "createApp",
2902
+ inputs: [
2903
+ {
2904
+ name: "salt",
2905
+ type: "bytes32",
2906
+ internalType: "bytes32"
2907
+ },
2908
+ {
2909
+ name: "release",
2910
+ type: "tuple",
2911
+ internalType: "structIAppController.Release",
2912
+ components: [
2913
+ {
2914
+ name: "rmsRelease",
2915
+ type: "tuple",
2916
+ internalType: "structIReleaseManagerTypes.Release",
2917
+ components: [
2918
+ {
2919
+ name: "artifacts",
2920
+ type: "tuple[]",
2921
+ internalType: "structIReleaseManagerTypes.Artifact[]",
2922
+ components: [
2923
+ {
2924
+ name: "digest",
2925
+ type: "bytes32",
2926
+ internalType: "bytes32"
2927
+ },
2928
+ {
2929
+ name: "registry",
2930
+ type: "string",
2931
+ internalType: "string"
2932
+ }
2933
+ ]
2934
+ },
2935
+ {
2936
+ name: "upgradeByTime",
2937
+ type: "uint32",
2938
+ internalType: "uint32"
2939
+ }
2940
+ ]
2941
+ },
2942
+ {
2943
+ name: "publicEnv",
2944
+ type: "bytes",
2945
+ internalType: "bytes"
2946
+ },
2947
+ {
2948
+ name: "encryptedEnv",
2949
+ type: "bytes",
2950
+ internalType: "bytes"
2951
+ },
2952
+ {
2953
+ name: "containerPolicy",
2954
+ type: "tuple",
2955
+ internalType: "structIAppController.ContainerPolicy",
2956
+ components: [
2957
+ {
2958
+ name: "args",
2959
+ type: "string[]",
2960
+ internalType: "string[]"
2961
+ },
2962
+ {
2963
+ name: "cmdOverride",
2964
+ type: "string[]",
2965
+ internalType: "string[]"
2966
+ },
2967
+ {
2968
+ name: "env",
2969
+ type: "tuple[]",
2970
+ internalType: "structIAppController.EnvVar[]",
2971
+ components: [
2972
+ {
2973
+ name: "key",
2974
+ type: "string",
2975
+ internalType: "string"
2976
+ },
2977
+ {
2978
+ name: "value",
2979
+ type: "string",
2980
+ internalType: "string"
2981
+ }
2982
+ ]
2983
+ },
2984
+ {
2985
+ name: "envOverride",
2986
+ type: "tuple[]",
2987
+ internalType: "structIAppController.EnvVar[]",
2988
+ components: [
2989
+ {
2990
+ name: "key",
2991
+ type: "string",
2992
+ internalType: "string"
2993
+ },
2994
+ {
2995
+ name: "value",
2996
+ type: "string",
2997
+ internalType: "string"
2998
+ }
2999
+ ]
3000
+ },
3001
+ {
3002
+ name: "restartPolicy",
3003
+ type: "string",
3004
+ internalType: "string"
3005
+ }
3006
+ ]
3007
+ }
3008
+ ]
3009
+ }
3010
+ ],
3011
+ outputs: [
3012
+ {
3013
+ name: "app",
3014
+ type: "address",
3015
+ internalType: "contractIApp"
3016
+ }
3017
+ ],
3018
+ stateMutability: "nonpayable"
3019
+ },
3020
+ {
3021
+ type: "function",
3022
+ name: "createAppWithIsolatedBilling",
3023
+ inputs: [
3024
+ {
3025
+ name: "salt",
3026
+ type: "bytes32",
3027
+ internalType: "bytes32"
3028
+ },
3029
+ {
3030
+ name: "release",
3031
+ type: "tuple",
3032
+ internalType: "structIAppController.Release",
3033
+ components: [
3034
+ {
3035
+ name: "rmsRelease",
3036
+ type: "tuple",
3037
+ internalType: "structIReleaseManagerTypes.Release",
3038
+ components: [
3039
+ {
3040
+ name: "artifacts",
3041
+ type: "tuple[]",
3042
+ internalType: "structIReleaseManagerTypes.Artifact[]",
3043
+ components: [
3044
+ {
3045
+ name: "digest",
3046
+ type: "bytes32",
3047
+ internalType: "bytes32"
3048
+ },
3049
+ {
3050
+ name: "registry",
3051
+ type: "string",
3052
+ internalType: "string"
3053
+ }
3054
+ ]
3055
+ },
3056
+ {
3057
+ name: "upgradeByTime",
3058
+ type: "uint32",
3059
+ internalType: "uint32"
3060
+ }
3061
+ ]
3062
+ },
3063
+ {
3064
+ name: "publicEnv",
3065
+ type: "bytes",
3066
+ internalType: "bytes"
3067
+ },
3068
+ {
3069
+ name: "encryptedEnv",
3070
+ type: "bytes",
3071
+ internalType: "bytes"
3072
+ },
3073
+ {
3074
+ name: "containerPolicy",
3075
+ type: "tuple",
3076
+ internalType: "structIAppController.ContainerPolicy",
3077
+ components: [
3078
+ {
3079
+ name: "args",
3080
+ type: "string[]",
3081
+ internalType: "string[]"
3082
+ },
3083
+ {
3084
+ name: "cmdOverride",
3085
+ type: "string[]",
3086
+ internalType: "string[]"
3087
+ },
3088
+ {
3089
+ name: "env",
3090
+ type: "tuple[]",
3091
+ internalType: "structIAppController.EnvVar[]",
3092
+ components: [
3093
+ {
3094
+ name: "key",
3095
+ type: "string",
3096
+ internalType: "string"
3097
+ },
3098
+ {
3099
+ name: "value",
3100
+ type: "string",
3101
+ internalType: "string"
3102
+ }
3103
+ ]
3104
+ },
3105
+ {
3106
+ name: "envOverride",
3107
+ type: "tuple[]",
3108
+ internalType: "structIAppController.EnvVar[]",
3109
+ components: [
3110
+ {
3111
+ name: "key",
3112
+ type: "string",
3113
+ internalType: "string"
3114
+ },
3115
+ {
3116
+ name: "value",
3117
+ type: "string",
3118
+ internalType: "string"
3119
+ }
3120
+ ]
3121
+ },
3122
+ {
3123
+ name: "restartPolicy",
3124
+ type: "string",
3125
+ internalType: "string"
3126
+ }
3127
+ ]
3128
+ }
3129
+ ]
3130
+ }
3131
+ ],
3132
+ outputs: [
3133
+ {
3134
+ name: "app",
3135
+ type: "address",
3136
+ internalType: "contractIApp"
3137
+ }
3138
+ ],
3139
+ stateMutability: "nonpayable"
3140
+ },
3141
+ {
3142
+ type: "function",
3143
+ name: "createEmptyApp",
3144
+ inputs: [
3145
+ {
3146
+ name: "salt",
3147
+ type: "bytes32",
3148
+ internalType: "bytes32"
3149
+ }
3150
+ ],
3151
+ outputs: [
3152
+ {
3153
+ name: "app",
3154
+ type: "address",
3155
+ internalType: "contractIApp"
3156
+ }
3157
+ ],
3158
+ stateMutability: "nonpayable"
3159
+ },
3160
+ {
3161
+ type: "function",
3162
+ name: "createEmptyAppWithIsolatedBilling",
3163
+ inputs: [
3164
+ {
3165
+ name: "salt",
3166
+ type: "bytes32",
3167
+ internalType: "bytes32"
3168
+ }
3169
+ ],
3170
+ outputs: [
3171
+ {
3172
+ name: "app",
3173
+ type: "address",
3174
+ internalType: "contractIApp"
3175
+ }
3176
+ ],
3177
+ stateMutability: "nonpayable"
3178
+ },
3179
+ {
3180
+ type: "function",
3181
+ name: "domainSeparator",
3182
+ inputs: [],
3183
+ outputs: [
3184
+ {
3185
+ name: "",
3186
+ type: "bytes32",
3187
+ internalType: "bytes32"
3188
+ }
3189
+ ],
3190
+ stateMutability: "view"
3191
+ },
3192
+ {
3193
+ type: "function",
3194
+ name: "getActiveAppCount",
3195
+ inputs: [
3196
+ {
3197
+ name: "user",
3198
+ type: "address",
3199
+ internalType: "address"
3200
+ }
3201
+ ],
3202
+ outputs: [
3203
+ {
3204
+ name: "",
3205
+ type: "uint32",
3206
+ internalType: "uint32"
3207
+ }
3208
+ ],
3209
+ stateMutability: "view"
3210
+ },
3211
+ {
3212
+ type: "function",
3213
+ name: "getAppCreator",
3214
+ inputs: [
3215
+ {
3216
+ name: "app",
3217
+ type: "address",
3218
+ internalType: "contractIApp"
3219
+ }
3220
+ ],
3221
+ outputs: [
3222
+ {
3223
+ name: "",
3224
+ type: "address",
3225
+ internalType: "address"
3226
+ }
3227
+ ],
3228
+ stateMutability: "view"
3229
+ },
3230
+ {
3231
+ type: "function",
3232
+ name: "getAppLatestReleaseBlockNumber",
3233
+ inputs: [
3234
+ {
3235
+ name: "app",
3236
+ type: "address",
3237
+ internalType: "contractIApp"
3238
+ }
3239
+ ],
3240
+ outputs: [
3241
+ {
3242
+ name: "",
3243
+ type: "uint32",
3244
+ internalType: "uint32"
3245
+ }
3246
+ ],
3247
+ stateMutability: "view"
3248
+ },
3249
+ {
3250
+ type: "function",
3251
+ name: "getAppOperatorSetId",
3252
+ inputs: [
3253
+ {
3254
+ name: "app",
3255
+ type: "address",
3256
+ internalType: "contractIApp"
3257
+ }
3258
+ ],
3259
+ outputs: [
3260
+ {
3261
+ name: "",
3262
+ type: "uint32",
3263
+ internalType: "uint32"
3264
+ }
3265
+ ],
3266
+ stateMutability: "view"
3267
+ },
3268
+ {
3269
+ type: "function",
3270
+ name: "getAppPendingReleaseBlockNumber",
3271
+ inputs: [
3272
+ {
3273
+ name: "app",
3274
+ type: "address",
3275
+ internalType: "contractIApp"
3276
+ }
3277
+ ],
3278
+ outputs: [
3279
+ {
3280
+ name: "",
3281
+ type: "uint32",
3282
+ internalType: "uint32"
3283
+ }
3284
+ ],
3285
+ stateMutability: "view"
3286
+ },
3287
+ {
3288
+ type: "function",
3289
+ name: "getAppStatus",
3290
+ inputs: [
3291
+ {
3292
+ name: "app",
3293
+ type: "address",
3294
+ internalType: "contractIApp"
3295
+ }
3296
+ ],
3297
+ outputs: [
3298
+ {
3299
+ name: "",
3300
+ type: "uint8",
3301
+ internalType: "enumIAppController.AppStatus"
3302
+ }
3303
+ ],
3304
+ stateMutability: "view"
3305
+ },
3306
+ {
3307
+ type: "function",
3308
+ name: "getApps",
3309
+ inputs: [
3310
+ {
3311
+ name: "offset",
3312
+ type: "uint256",
3313
+ internalType: "uint256"
3314
+ },
3315
+ {
3316
+ name: "limit",
3317
+ type: "uint256",
3318
+ internalType: "uint256"
3319
+ }
3320
+ ],
3321
+ outputs: [
3322
+ {
3323
+ name: "apps",
3324
+ type: "address[]",
3325
+ internalType: "contractIApp[]"
3326
+ },
3327
+ {
3328
+ name: "appConfigsMem",
3329
+ type: "tuple[]",
3330
+ internalType: "structIAppController.AppConfig[]",
3331
+ components: [
3332
+ {
3333
+ name: "creator",
3334
+ type: "address",
3335
+ internalType: "address"
3336
+ },
3337
+ {
3338
+ name: "operatorSetId",
3339
+ type: "uint32",
3340
+ internalType: "uint32"
3341
+ },
3342
+ {
3343
+ name: "latestReleaseBlockNumber",
3344
+ type: "uint32",
3345
+ internalType: "uint32"
3346
+ },
3347
+ {
3348
+ name: "pendingReleaseBlockNumber",
3349
+ type: "uint32",
3350
+ internalType: "uint32"
3351
+ },
3352
+ {
3353
+ name: "status",
3354
+ type: "uint8",
3355
+ internalType: "enumIAppController.AppStatus"
3356
+ }
3357
+ ]
3358
+ }
3359
+ ],
3360
+ stateMutability: "view"
3361
+ },
3362
+ {
3363
+ type: "function",
3364
+ name: "getAppsByBillingAccount",
3365
+ inputs: [
3366
+ {
3367
+ name: "account",
3368
+ type: "address",
3369
+ internalType: "address"
3370
+ },
3371
+ {
3372
+ name: "offset",
3373
+ type: "uint256",
3374
+ internalType: "uint256"
3375
+ },
3376
+ {
3377
+ name: "limit",
3378
+ type: "uint256",
3379
+ internalType: "uint256"
3380
+ }
3381
+ ],
3382
+ outputs: [
3383
+ {
3384
+ name: "apps",
3385
+ type: "address[]",
3386
+ internalType: "contractIApp[]"
3387
+ },
3388
+ {
3389
+ name: "appConfigsMem",
3390
+ type: "tuple[]",
3391
+ internalType: "structIAppController.AppConfig[]",
3392
+ components: [
3393
+ {
3394
+ name: "creator",
3395
+ type: "address",
3396
+ internalType: "address"
3397
+ },
3398
+ {
3399
+ name: "operatorSetId",
3400
+ type: "uint32",
3401
+ internalType: "uint32"
3402
+ },
3403
+ {
3404
+ name: "latestReleaseBlockNumber",
3405
+ type: "uint32",
3406
+ internalType: "uint32"
3407
+ },
3408
+ {
3409
+ name: "pendingReleaseBlockNumber",
3410
+ type: "uint32",
3411
+ internalType: "uint32"
3412
+ },
3413
+ {
3414
+ name: "status",
3415
+ type: "uint8",
3416
+ internalType: "enumIAppController.AppStatus"
3417
+ }
3418
+ ]
3419
+ }
3420
+ ],
3421
+ stateMutability: "view"
3422
+ },
3423
+ {
3424
+ type: "function",
3425
+ name: "getAppsByCreator",
3426
+ inputs: [
3427
+ {
3428
+ name: "creator",
3429
+ type: "address",
3430
+ internalType: "address"
3431
+ },
3432
+ {
3433
+ name: "offset",
3434
+ type: "uint256",
3435
+ internalType: "uint256"
3436
+ },
3437
+ {
3438
+ name: "limit",
3439
+ type: "uint256",
3440
+ internalType: "uint256"
3441
+ }
3442
+ ],
3443
+ outputs: [
3444
+ {
3445
+ name: "apps",
3446
+ type: "address[]",
3447
+ internalType: "contractIApp[]"
3448
+ },
3449
+ {
3450
+ name: "appConfigsMem",
3451
+ type: "tuple[]",
3452
+ internalType: "structIAppController.AppConfig[]",
3453
+ components: [
3454
+ {
3455
+ name: "creator",
3456
+ type: "address",
3457
+ internalType: "address"
3458
+ },
3459
+ {
3460
+ name: "operatorSetId",
3461
+ type: "uint32",
3462
+ internalType: "uint32"
3463
+ },
3464
+ {
3465
+ name: "latestReleaseBlockNumber",
3466
+ type: "uint32",
3467
+ internalType: "uint32"
3468
+ },
3469
+ {
3470
+ name: "pendingReleaseBlockNumber",
3471
+ type: "uint32",
3472
+ internalType: "uint32"
3473
+ },
3474
+ {
3475
+ name: "status",
3476
+ type: "uint8",
3477
+ internalType: "enumIAppController.AppStatus"
3478
+ }
3479
+ ]
3480
+ }
3481
+ ],
3482
+ stateMutability: "view"
3483
+ },
3484
+ {
3485
+ type: "function",
3486
+ name: "getAppsByDeveloper",
3487
+ inputs: [
3488
+ {
3489
+ name: "developer",
3490
+ type: "address",
3491
+ internalType: "address"
3492
+ },
3493
+ {
3494
+ name: "offset",
3495
+ type: "uint256",
3496
+ internalType: "uint256"
3497
+ },
3498
+ {
3499
+ name: "limit",
3500
+ type: "uint256",
3501
+ internalType: "uint256"
3502
+ }
3503
+ ],
3504
+ outputs: [
3505
+ {
3506
+ name: "apps",
3507
+ type: "address[]",
3508
+ internalType: "contractIApp[]"
3509
+ },
3510
+ {
3511
+ name: "appConfigsMem",
3512
+ type: "tuple[]",
3513
+ internalType: "structIAppController.AppConfig[]",
3514
+ components: [
3515
+ {
3516
+ name: "creator",
3517
+ type: "address",
3518
+ internalType: "address"
3519
+ },
3520
+ {
3521
+ name: "operatorSetId",
3522
+ type: "uint32",
3523
+ internalType: "uint32"
3524
+ },
3525
+ {
3526
+ name: "latestReleaseBlockNumber",
3527
+ type: "uint32",
3528
+ internalType: "uint32"
3529
+ },
3530
+ {
3531
+ name: "pendingReleaseBlockNumber",
3532
+ type: "uint32",
3533
+ internalType: "uint32"
3534
+ },
3535
+ {
3536
+ name: "status",
3537
+ type: "uint8",
3538
+ internalType: "enumIAppController.AppStatus"
3539
+ }
3540
+ ]
3541
+ }
3542
+ ],
3543
+ stateMutability: "view"
3544
+ },
3545
+ {
3546
+ type: "function",
3547
+ name: "getBillingAccount",
3548
+ inputs: [
3549
+ {
3550
+ name: "app",
3551
+ type: "address",
3552
+ internalType: "contractIApp"
3553
+ }
3554
+ ],
3555
+ outputs: [
3556
+ {
3557
+ name: "",
3558
+ type: "address",
3559
+ internalType: "address"
3560
+ }
3561
+ ],
3562
+ stateMutability: "view"
3563
+ },
3564
+ {
3565
+ type: "function",
3566
+ name: "getBillingType",
3567
+ inputs: [
3568
+ {
3569
+ name: "app",
3570
+ type: "address",
3571
+ internalType: "contractIApp"
3572
+ }
3573
+ ],
3574
+ outputs: [
3575
+ {
3576
+ name: "",
3577
+ type: "uint8",
3578
+ internalType: "enumIAppController.BillingType"
3579
+ }
3580
+ ],
3581
+ stateMutability: "view"
3582
+ },
3583
+ {
3584
+ type: "function",
3585
+ name: "getMaxActiveAppsPerUser",
3586
+ inputs: [
3587
+ {
3588
+ name: "user",
3589
+ type: "address",
3590
+ internalType: "address"
3591
+ }
3592
+ ],
3593
+ outputs: [
3594
+ {
3595
+ name: "",
3596
+ type: "uint32",
3597
+ internalType: "uint32"
3598
+ }
3599
+ ],
3600
+ stateMutability: "view"
3601
+ },
3602
+ {
3603
+ type: "function",
3604
+ name: "globalActiveAppCount",
3605
+ inputs: [],
3606
+ outputs: [
3607
+ {
3608
+ name: "",
3609
+ type: "uint32",
3610
+ internalType: "uint32"
3611
+ }
3612
+ ],
3613
+ stateMutability: "view"
3614
+ },
3615
+ {
3616
+ type: "function",
3617
+ name: "initialize",
3618
+ inputs: [
3619
+ {
3620
+ name: "admin",
3621
+ type: "address",
3622
+ internalType: "address"
3623
+ }
3624
+ ],
3625
+ outputs: [],
3626
+ stateMutability: "nonpayable"
3627
+ },
3628
+ {
3629
+ type: "function",
3630
+ name: "maxGlobalActiveApps",
3631
+ inputs: [],
3632
+ outputs: [
3633
+ {
3634
+ name: "",
3635
+ type: "uint32",
3636
+ internalType: "uint32"
3637
+ }
3638
+ ],
3639
+ stateMutability: "view"
3640
+ },
3641
+ {
3642
+ type: "function",
3643
+ name: "permissionController",
3644
+ inputs: [],
3645
+ outputs: [
3646
+ {
3647
+ name: "",
3648
+ type: "address",
3649
+ internalType: "contractIPermissionController"
3650
+ }
3651
+ ],
3652
+ stateMutability: "view"
3653
+ },
3654
+ {
3655
+ type: "function",
3656
+ name: "releaseManager",
3657
+ inputs: [],
3658
+ outputs: [
3659
+ {
3660
+ name: "",
3661
+ type: "address",
3662
+ internalType: "contractIReleaseManager"
3663
+ }
3664
+ ],
3665
+ stateMutability: "view"
3666
+ },
3667
+ {
3668
+ type: "function",
3669
+ name: "setMaxActiveAppsPerUser",
3670
+ inputs: [
3671
+ {
3672
+ name: "user",
3673
+ type: "address",
3674
+ internalType: "address"
3675
+ },
3676
+ {
3677
+ name: "limit",
3678
+ type: "uint32",
3679
+ internalType: "uint32"
3680
+ }
3681
+ ],
3682
+ outputs: [],
3683
+ stateMutability: "nonpayable"
3684
+ },
3685
+ {
3686
+ type: "function",
3687
+ name: "setMaxGlobalActiveApps",
3688
+ inputs: [
3689
+ {
3690
+ name: "limit",
3691
+ type: "uint32",
3692
+ internalType: "uint32"
3693
+ }
3694
+ ],
3695
+ outputs: [],
3696
+ stateMutability: "nonpayable"
3697
+ },
3698
+ {
3699
+ type: "function",
3700
+ name: "startApp",
3701
+ inputs: [
3702
+ {
3703
+ name: "app",
3704
+ type: "address",
3705
+ internalType: "contractIApp"
3706
+ }
3707
+ ],
3708
+ outputs: [],
3709
+ stateMutability: "nonpayable"
3710
+ },
3711
+ {
3712
+ type: "function",
3713
+ name: "stopApp",
3714
+ inputs: [
3715
+ {
3716
+ name: "app",
3717
+ type: "address",
3718
+ internalType: "contractIApp"
3719
+ }
3720
+ ],
3721
+ outputs: [],
3722
+ stateMutability: "nonpayable"
3723
+ },
3724
+ {
3725
+ type: "function",
3726
+ name: "suspend",
3727
+ inputs: [
3728
+ {
3729
+ name: "account",
3730
+ type: "address",
3731
+ internalType: "address"
3732
+ },
3733
+ {
3734
+ name: "apps",
3735
+ type: "address[]",
3736
+ internalType: "contractIApp[]"
3737
+ }
3738
+ ],
3739
+ outputs: [],
3740
+ stateMutability: "nonpayable"
3741
+ },
3742
+ {
3743
+ type: "function",
3744
+ name: "terminateApp",
3745
+ inputs: [
3746
+ {
3747
+ name: "app",
3748
+ type: "address",
3749
+ internalType: "contractIApp"
3750
+ }
3751
+ ],
3752
+ outputs: [],
3753
+ stateMutability: "nonpayable"
3754
+ },
3755
+ {
3756
+ type: "function",
3757
+ name: "terminateAppByAdmin",
3758
+ inputs: [
3759
+ {
3760
+ name: "app",
3761
+ type: "address",
3762
+ internalType: "contractIApp"
3763
+ }
3764
+ ],
3765
+ outputs: [],
3766
+ stateMutability: "nonpayable"
3767
+ },
3768
+ {
3769
+ type: "function",
3770
+ name: "updateAppMetadataURI",
3771
+ inputs: [
3772
+ {
3773
+ name: "app",
3774
+ type: "address",
3775
+ internalType: "contractIApp"
3776
+ },
3777
+ {
3778
+ name: "metadataURI",
3779
+ type: "string",
3780
+ internalType: "string"
3781
+ }
3782
+ ],
3783
+ outputs: [],
3784
+ stateMutability: "nonpayable"
3785
+ },
3786
+ {
3787
+ type: "function",
3788
+ name: "upgradeApp",
3789
+ inputs: [
3790
+ {
3791
+ name: "app",
3792
+ type: "address",
3793
+ internalType: "contractIApp"
3794
+ },
3795
+ {
3796
+ name: "release",
3797
+ type: "tuple",
3798
+ internalType: "structIAppController.Release",
3799
+ components: [
3800
+ {
3801
+ name: "rmsRelease",
3802
+ type: "tuple",
3803
+ internalType: "structIReleaseManagerTypes.Release",
3804
+ components: [
3805
+ {
3806
+ name: "artifacts",
3807
+ type: "tuple[]",
3808
+ internalType: "structIReleaseManagerTypes.Artifact[]",
3809
+ components: [
3810
+ {
3811
+ name: "digest",
3812
+ type: "bytes32",
3813
+ internalType: "bytes32"
3814
+ },
3815
+ {
3816
+ name: "registry",
3817
+ type: "string",
3818
+ internalType: "string"
3819
+ }
3820
+ ]
3821
+ },
3822
+ {
3823
+ name: "upgradeByTime",
3824
+ type: "uint32",
3825
+ internalType: "uint32"
3826
+ }
3827
+ ]
3828
+ },
3829
+ {
3830
+ name: "publicEnv",
3831
+ type: "bytes",
3832
+ internalType: "bytes"
3833
+ },
3834
+ {
3835
+ name: "encryptedEnv",
3836
+ type: "bytes",
3837
+ internalType: "bytes"
3838
+ },
3839
+ {
3840
+ name: "containerPolicy",
3841
+ type: "tuple",
3842
+ internalType: "structIAppController.ContainerPolicy",
3843
+ components: [
3844
+ {
3845
+ name: "args",
3846
+ type: "string[]",
3847
+ internalType: "string[]"
3848
+ },
3849
+ {
3850
+ name: "cmdOverride",
3851
+ type: "string[]",
3852
+ internalType: "string[]"
3853
+ },
3854
+ {
3855
+ name: "env",
3856
+ type: "tuple[]",
3857
+ internalType: "structIAppController.EnvVar[]",
3858
+ components: [
3859
+ {
3860
+ name: "key",
3861
+ type: "string",
3862
+ internalType: "string"
3863
+ },
3864
+ {
3865
+ name: "value",
3866
+ type: "string",
3867
+ internalType: "string"
3868
+ }
3869
+ ]
3870
+ },
3871
+ {
3872
+ name: "envOverride",
3873
+ type: "tuple[]",
3874
+ internalType: "structIAppController.EnvVar[]",
3875
+ components: [
3876
+ {
3877
+ name: "key",
3878
+ type: "string",
3879
+ internalType: "string"
3880
+ },
3881
+ {
3882
+ name: "value",
3883
+ type: "string",
3884
+ internalType: "string"
3885
+ }
3886
+ ]
3887
+ },
3888
+ {
3889
+ name: "restartPolicy",
3890
+ type: "string",
3891
+ internalType: "string"
3892
+ }
3893
+ ]
3894
+ }
3895
+ ]
3896
+ }
3897
+ ],
3898
+ outputs: [
3899
+ {
3900
+ name: "",
3901
+ type: "uint256",
3902
+ internalType: "uint256"
3903
+ }
3904
+ ],
3905
+ stateMutability: "nonpayable"
3906
+ },
3907
+ {
3908
+ type: "function",
3909
+ name: "version",
3910
+ inputs: [],
3911
+ outputs: [
3912
+ {
3913
+ name: "",
3914
+ type: "string",
3915
+ internalType: "string"
3916
+ }
3917
+ ],
3918
+ stateMutability: "view"
3919
+ },
3920
+ {
3921
+ type: "event",
3922
+ name: "AppCreated",
3923
+ inputs: [
3924
+ {
3925
+ name: "creator",
3926
+ type: "address",
3927
+ indexed: true,
3928
+ internalType: "address"
3929
+ },
3930
+ {
3931
+ name: "app",
3932
+ type: "address",
3933
+ indexed: true,
3934
+ internalType: "contractIApp"
3935
+ },
3936
+ {
3937
+ name: "operatorSetId",
3938
+ type: "uint32",
3939
+ indexed: false,
3940
+ internalType: "uint32"
3941
+ }
3942
+ ],
3943
+ anonymous: false
3944
+ },
3945
+ {
3946
+ type: "event",
3947
+ name: "AppMetadataURIUpdated",
3948
+ inputs: [
3949
+ {
3950
+ name: "app",
3951
+ type: "address",
3952
+ indexed: true,
3953
+ internalType: "contractIApp"
3954
+ },
3955
+ {
3956
+ name: "metadataURI",
3957
+ type: "string",
3958
+ indexed: false,
3959
+ internalType: "string"
3960
+ }
3961
+ ],
3962
+ anonymous: false
3963
+ },
3964
+ {
3965
+ type: "event",
3966
+ name: "AppStarted",
3967
+ inputs: [
3968
+ {
3969
+ name: "app",
3970
+ type: "address",
3971
+ indexed: true,
3972
+ internalType: "contractIApp"
3973
+ }
3974
+ ],
3975
+ anonymous: false
3976
+ },
3977
+ {
3978
+ type: "event",
3979
+ name: "AppStopped",
3980
+ inputs: [
3981
+ {
3982
+ name: "app",
3983
+ type: "address",
3984
+ indexed: true,
3985
+ internalType: "contractIApp"
3986
+ }
3987
+ ],
3988
+ anonymous: false
3989
+ },
3990
+ {
3991
+ type: "event",
3992
+ name: "AppSuspended",
3993
+ inputs: [
3994
+ {
3995
+ name: "app",
3996
+ type: "address",
3997
+ indexed: true,
3998
+ internalType: "contractIApp"
3999
+ }
4000
+ ],
4001
+ anonymous: false
4002
+ },
4003
+ {
4004
+ type: "event",
4005
+ name: "AppTerminated",
4006
+ inputs: [
4007
+ {
4008
+ name: "app",
4009
+ type: "address",
4010
+ indexed: true,
4011
+ internalType: "contractIApp"
4012
+ }
4013
+ ],
4014
+ anonymous: false
4015
+ },
4016
+ {
4017
+ type: "event",
4018
+ name: "AppTerminatedByAdmin",
4019
+ inputs: [
4020
+ {
4021
+ name: "app",
4022
+ type: "address",
4023
+ indexed: true,
4024
+ internalType: "contractIApp"
4025
+ }
4026
+ ],
4027
+ anonymous: false
4028
+ },
4029
+ {
4030
+ type: "event",
4031
+ name: "AppUpgraded",
4032
+ inputs: [
4033
+ {
4034
+ name: "app",
4035
+ type: "address",
4036
+ indexed: true,
4037
+ internalType: "contractIApp"
4038
+ },
4039
+ {
4040
+ name: "rmsReleaseId",
4041
+ type: "uint256",
4042
+ indexed: false,
4043
+ internalType: "uint256"
4044
+ },
4045
+ {
4046
+ name: "release",
4047
+ type: "tuple",
4048
+ indexed: false,
4049
+ internalType: "structIAppController.Release",
4050
+ components: [
4051
+ {
4052
+ name: "rmsRelease",
4053
+ type: "tuple",
4054
+ internalType: "structIReleaseManagerTypes.Release",
4055
+ components: [
4056
+ {
4057
+ name: "artifacts",
4058
+ type: "tuple[]",
4059
+ internalType: "structIReleaseManagerTypes.Artifact[]",
4060
+ components: [
4061
+ {
4062
+ name: "digest",
4063
+ type: "bytes32",
4064
+ internalType: "bytes32"
4065
+ },
4066
+ {
4067
+ name: "registry",
4068
+ type: "string",
4069
+ internalType: "string"
4070
+ }
4071
+ ]
4072
+ },
4073
+ {
4074
+ name: "upgradeByTime",
4075
+ type: "uint32",
4076
+ internalType: "uint32"
4077
+ }
4078
+ ]
4079
+ },
4080
+ {
4081
+ name: "publicEnv",
4082
+ type: "bytes",
4083
+ internalType: "bytes"
4084
+ },
4085
+ {
4086
+ name: "encryptedEnv",
4087
+ type: "bytes",
4088
+ internalType: "bytes"
4089
+ },
4090
+ {
4091
+ name: "containerPolicy",
4092
+ type: "tuple",
4093
+ internalType: "structIAppController.ContainerPolicy",
4094
+ components: [
4095
+ {
4096
+ name: "args",
4097
+ type: "string[]",
4098
+ internalType: "string[]"
4099
+ },
4100
+ {
4101
+ name: "cmdOverride",
4102
+ type: "string[]",
4103
+ internalType: "string[]"
4104
+ },
4105
+ {
4106
+ name: "env",
4107
+ type: "tuple[]",
4108
+ internalType: "structIAppController.EnvVar[]",
4109
+ components: [
4110
+ {
4111
+ name: "key",
4112
+ type: "string",
4113
+ internalType: "string"
4114
+ },
4115
+ {
4116
+ name: "value",
4117
+ type: "string",
4118
+ internalType: "string"
4119
+ }
4120
+ ]
4121
+ },
4122
+ {
4123
+ name: "envOverride",
4124
+ type: "tuple[]",
4125
+ internalType: "structIAppController.EnvVar[]",
4126
+ components: [
4127
+ {
4128
+ name: "key",
4129
+ type: "string",
4130
+ internalType: "string"
4131
+ },
4132
+ {
4133
+ name: "value",
4134
+ type: "string",
4135
+ internalType: "string"
4136
+ }
4137
+ ]
4138
+ },
4139
+ {
4140
+ name: "restartPolicy",
4141
+ type: "string",
4142
+ internalType: "string"
4143
+ }
4144
+ ]
4145
+ }
4146
+ ]
4147
+ }
4148
+ ],
4149
+ anonymous: false
4150
+ },
4151
+ {
4152
+ type: "event",
4153
+ name: "GlobalMaxActiveAppsSet",
4154
+ inputs: [
4155
+ {
4156
+ name: "limit",
4157
+ type: "uint32",
4158
+ indexed: false,
4159
+ internalType: "uint32"
4160
+ }
4161
+ ],
4162
+ anonymous: false
4163
+ },
4164
+ {
4165
+ type: "event",
4166
+ name: "Initialized",
4167
+ inputs: [
4168
+ {
4169
+ name: "version",
4170
+ type: "uint8",
4171
+ indexed: false,
4172
+ internalType: "uint8"
4173
+ }
4174
+ ],
4175
+ anonymous: false
4176
+ },
4177
+ {
4178
+ type: "event",
4179
+ name: "MaxActiveAppsSet",
4180
+ inputs: [
4181
+ {
4182
+ name: "user",
4183
+ type: "address",
4184
+ indexed: true,
4185
+ internalType: "address"
4186
+ },
4187
+ {
4188
+ name: "limit",
4189
+ type: "uint32",
4190
+ indexed: false,
4191
+ internalType: "uint32"
4192
+ }
4193
+ ],
4194
+ anonymous: false
4195
+ },
4196
+ {
4197
+ type: "event",
4198
+ name: "UpgradeConfirmed",
4199
+ inputs: [
4200
+ {
4201
+ name: "app",
4202
+ type: "address",
4203
+ indexed: true,
4204
+ internalType: "contractIApp"
4205
+ },
4206
+ {
4207
+ name: "pendingReleaseBlockNumber",
4208
+ type: "uint32",
4209
+ indexed: false,
4210
+ internalType: "uint32"
4211
+ }
4212
+ ],
4213
+ anonymous: false
4214
+ },
4215
+ {
4216
+ type: "error",
4217
+ name: "AccountHasActiveApps",
4218
+ inputs: []
4219
+ },
4220
+ {
4221
+ type: "error",
4222
+ name: "AppAlreadyExists",
4223
+ inputs: []
4224
+ },
4225
+ {
4226
+ type: "error",
4227
+ name: "AppDoesNotExist",
4228
+ inputs: []
4229
+ },
4230
+ {
4231
+ type: "error",
4232
+ name: "GlobalMaxActiveAppsExceeded",
4233
+ inputs: []
4234
+ },
4235
+ {
4236
+ type: "error",
4237
+ name: "InvalidAppStatus",
4238
+ inputs: []
4239
+ },
4240
+ {
4241
+ type: "error",
4242
+ name: "InvalidPermissions",
4243
+ inputs: []
4244
+ },
4245
+ {
4246
+ type: "error",
4247
+ name: "InvalidReleaseMetadataURI",
4248
+ inputs: []
4249
+ },
4250
+ {
4251
+ type: "error",
4252
+ name: "InvalidShortString",
4253
+ inputs: []
4254
+ },
4255
+ {
4256
+ type: "error",
4257
+ name: "InvalidSignature",
4258
+ inputs: []
4259
+ },
4260
+ {
4261
+ type: "error",
4262
+ name: "MaxActiveAppsExceeded",
4263
+ inputs: []
4264
+ },
4265
+ {
4266
+ type: "error",
4267
+ name: "MoreThanOneArtifact",
4268
+ inputs: []
4269
+ },
4270
+ {
4271
+ type: "error",
4272
+ name: "NoPendingUpgrade",
4273
+ inputs: []
4274
+ },
4275
+ {
4276
+ type: "error",
4277
+ name: "SignatureExpired",
4278
+ inputs: []
4279
+ },
4280
+ {
4281
+ type: "error",
4282
+ name: "StringTooLong",
4283
+ inputs: [
4284
+ {
4285
+ name: "str",
4286
+ type: "string",
4287
+ internalType: "string"
4288
+ }
4289
+ ]
3173
4290
  }
3174
- return hash;
3175
- }
3176
-
3177
- // src/client/common/contract/caller.ts
3178
- var import_viem3 = require("viem");
3179
-
3180
- // src/client/common/utils/helpers.ts
3181
- var import_viem2 = require("viem");
3182
- var import_chains2 = require("viem/chains");
3183
- var import_accounts = require("viem/accounts");
3184
- function getChainFromID(chainID, fallback2 = import_chains2.sepolia) {
3185
- const id = Number(chainID);
3186
- return (0, import_viem2.extractChain)({ chains: SUPPORTED_CHAINS, id }) || fallback2;
3187
- }
3188
- function createClients(options) {
3189
- const { privateKey, rpcUrl, chainId } = options;
3190
- const privateKeyHex = addHexPrefix(privateKey);
3191
- const account = (0, import_accounts.privateKeyToAccount)(privateKeyHex);
3192
- const chain = getChainFromID(chainId);
3193
- const transport = typeof rpcUrl === "string" ? (0, import_viem2.http)(rpcUrl) : (0, import_viem2.fallback)(rpcUrl.map((url) => (0, import_viem2.http)(url)));
3194
- const publicClient = (0, import_viem2.createPublicClient)({
3195
- chain,
3196
- transport
3197
- });
3198
- const walletClient = (0, import_viem2.createWalletClient)({
3199
- account,
3200
- chain,
3201
- transport
3202
- });
3203
- return { walletClient, publicClient };
3204
- }
3205
- function addHexPrefix(value) {
3206
- return value.startsWith("0x") ? value : `0x${value}`;
3207
- }
3208
- function stripHexPrefix(value) {
3209
- return value.startsWith("0x") ? value.slice(2) : value;
3210
- }
4291
+ ];
3211
4292
 
3212
- // src/client/common/abis/AppController.json
3213
- var AppController_default = [
4293
+ // src/client/common/abis/AppController.v1_4.json
4294
+ var AppController_v1_4_default = [
3214
4295
  {
3215
4296
  type: "constructor",
3216
4297
  inputs: [
@@ -4894,6 +5975,38 @@ var PermissionController_default = [
4894
5975
  ];
4895
5976
 
4896
5977
  // src/client/common/contract/caller.ts
5978
+ function appControllerAbiFor(environmentConfig) {
5979
+ return environmentConfig.releaseAbiVersion === "v1.4" ? AppController_v1_4_default : AppController_default;
5980
+ }
5981
+ function supportsContainerPolicy(environmentConfig) {
5982
+ return environmentConfig.releaseAbiVersion !== "v1.4";
5983
+ }
5984
+ function containerPolicyForViem(policy = EMPTY_CONTAINER_POLICY) {
5985
+ return {
5986
+ args: policy.args,
5987
+ cmdOverride: policy.cmdOverride,
5988
+ env: policy.env.map((e) => ({ key: e.key, value: e.value })),
5989
+ envOverride: policy.envOverride.map((e) => ({ key: e.key, value: e.value })),
5990
+ restartPolicy: policy.restartPolicy
5991
+ };
5992
+ }
5993
+ function releaseForViem(release, environmentConfig) {
5994
+ const base = {
5995
+ rmsRelease: {
5996
+ artifacts: release.rmsRelease.artifacts.map((artifact) => ({
5997
+ digest: `0x${(0, import_viem3.bytesToHex)(artifact.digest).slice(2).padStart(64, "0")}`,
5998
+ registry: artifact.registry
5999
+ })),
6000
+ upgradeByTime: release.rmsRelease.upgradeByTime
6001
+ },
6002
+ publicEnv: (0, import_viem3.bytesToHex)(release.publicEnv),
6003
+ encryptedEnv: (0, import_viem3.bytesToHex)(release.encryptedEnv)
6004
+ };
6005
+ if (!supportsContainerPolicy(environmentConfig)) {
6006
+ return base;
6007
+ }
6008
+ return { ...base, containerPolicy: containerPolicyForViem(release.containerPolicy) };
6009
+ }
4897
6010
  function formatETH(wei) {
4898
6011
  const eth = Number(wei) / 1e18;
4899
6012
  const costStr = eth.toFixed(6);
@@ -4931,7 +6044,7 @@ async function calculateAppID(options) {
4931
6044
  const saltHex = `0x${paddedSaltHex}`;
4932
6045
  const appID = await publicClient.readContract({
4933
6046
  address: environmentConfig.appControllerAddress,
4934
- abi: AppController_default,
6047
+ abi: appControllerAbiFor(environmentConfig),
4935
6048
  functionName: "calculateAppId",
4936
6049
  args: [ownerAddress, saltHex]
4937
6050
  });
@@ -4955,22 +6068,12 @@ async function prepareDeployBatch(options, logger = noopLogger) {
4955
6068
  const saltHexString = (0, import_viem3.bytesToHex)(salt).slice(2);
4956
6069
  const paddedSaltHex = saltHexString.padStart(64, "0");
4957
6070
  const saltHex = `0x${paddedSaltHex}`;
4958
- const releaseForViem = {
4959
- rmsRelease: {
4960
- artifacts: release.rmsRelease.artifacts.map((artifact) => ({
4961
- digest: `0x${(0, import_viem3.bytesToHex)(artifact.digest).slice(2).padStart(64, "0")}`,
4962
- registry: artifact.registry
4963
- })),
4964
- upgradeByTime: release.rmsRelease.upgradeByTime
4965
- },
4966
- publicEnv: (0, import_viem3.bytesToHex)(release.publicEnv),
4967
- encryptedEnv: (0, import_viem3.bytesToHex)(release.encryptedEnv)
4968
- };
6071
+ const release_ = releaseForViem(release, environmentConfig);
4969
6072
  const functionName = options.billTo === "app" ? "createAppWithIsolatedBilling" : "createApp";
4970
6073
  const createData = (0, import_viem3.encodeFunctionData)({
4971
- abi: AppController_default,
6074
+ abi: appControllerAbiFor(environmentConfig),
4972
6075
  functionName,
4973
- args: [saltHex, releaseForViem]
6076
+ args: [saltHex, release_]
4974
6077
  });
4975
6078
  const acceptAdminData = (0, import_viem3.encodeFunctionData)({
4976
6079
  abi: PermissionController_default,
@@ -5058,21 +6161,11 @@ async function prepareUpgradeBatch(options) {
5058
6161
  publicLogs,
5059
6162
  needsPermissionChange
5060
6163
  } = options;
5061
- const releaseForViem = {
5062
- rmsRelease: {
5063
- artifacts: release.rmsRelease.artifacts.map((artifact) => ({
5064
- digest: `0x${(0, import_viem3.bytesToHex)(artifact.digest).slice(2).padStart(64, "0")}`,
5065
- registry: artifact.registry
5066
- })),
5067
- upgradeByTime: release.rmsRelease.upgradeByTime
5068
- },
5069
- publicEnv: (0, import_viem3.bytesToHex)(release.publicEnv),
5070
- encryptedEnv: (0, import_viem3.bytesToHex)(release.encryptedEnv)
5071
- };
6164
+ const release_ = releaseForViem(release, environmentConfig);
5072
6165
  const upgradeData = (0, import_viem3.encodeFunctionData)({
5073
- abi: AppController_default,
6166
+ abi: appControllerAbiFor(environmentConfig),
5074
6167
  functionName: "upgradeApp",
5075
- args: [appID, releaseForViem]
6168
+ args: [appID, release_]
5076
6169
  });
5077
6170
  const executions = [
5078
6171
  {
@@ -5206,7 +6299,7 @@ ${pendingMessage}`);
5206
6299
  if (callError.data) {
5207
6300
  try {
5208
6301
  const decoded = (0, import_viem3.decodeErrorResult)({
5209
- abi: AppController_default,
6302
+ abi: appControllerAbiFor(environmentConfig),
5210
6303
  data: callError.data
5211
6304
  });
5212
6305
  const formattedError = formatAppControllerError(decoded);
@@ -5259,7 +6352,7 @@ function formatAppControllerError(decoded) {
5259
6352
  async function getActiveAppCount(publicClient, environmentConfig, user) {
5260
6353
  const count = await publicClient.readContract({
5261
6354
  address: environmentConfig.appControllerAddress,
5262
- abi: AppController_default,
6355
+ abi: appControllerAbiFor(environmentConfig),
5263
6356
  functionName: "getActiveAppCount",
5264
6357
  args: [user]
5265
6358
  });
@@ -5268,7 +6361,7 @@ async function getActiveAppCount(publicClient, environmentConfig, user) {
5268
6361
  async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
5269
6362
  const quota = await publicClient.readContract({
5270
6363
  address: environmentConfig.appControllerAddress,
5271
- abi: AppController_default,
6364
+ abi: appControllerAbiFor(environmentConfig),
5272
6365
  functionName: "getMaxActiveAppsPerUser",
5273
6366
  args: [user]
5274
6367
  });
@@ -5277,7 +6370,7 @@ async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
5277
6370
  async function getAppsByDeveloper(publicClient, environmentConfig, developer, offset, limit) {
5278
6371
  const result = await publicClient.readContract({
5279
6372
  address: environmentConfig.appControllerAddress,
5280
- abi: AppController_default,
6373
+ abi: appControllerAbiFor(environmentConfig),
5281
6374
  functionName: "getAppsByDeveloper",
5282
6375
  args: [developer, offset, limit]
5283
6376
  });
@@ -5289,7 +6382,7 @@ async function getAppsByDeveloper(publicClient, environmentConfig, developer, of
5289
6382
  async function getBillingType(publicClient, environmentConfig, app) {
5290
6383
  const result = await publicClient.readContract({
5291
6384
  address: environmentConfig.appControllerAddress,
5292
- abi: AppController_default,
6385
+ abi: appControllerAbiFor(environmentConfig),
5293
6386
  functionName: "getBillingType",
5294
6387
  args: [app]
5295
6388
  });
@@ -5298,7 +6391,7 @@ async function getBillingType(publicClient, environmentConfig, app) {
5298
6391
  async function getAppsByBillingAccount(publicClient, environmentConfig, account, offset, limit) {
5299
6392
  const result = await publicClient.readContract({
5300
6393
  address: environmentConfig.appControllerAddress,
5301
- abi: AppController_default,
6394
+ abi: appControllerAbiFor(environmentConfig),
5302
6395
  functionName: "getAppsByBillingAccount",
5303
6396
  args: [account, offset, limit]
5304
6397
  });
@@ -5332,7 +6425,7 @@ async function getAppLatestReleaseBlockNumbers(publicClient, environmentConfig,
5332
6425
  appIDs.map(
5333
6426
  (appID) => publicClient.readContract({
5334
6427
  address: environmentConfig.appControllerAddress,
5335
- abi: AppController_default,
6428
+ abi: appControllerAbiFor(environmentConfig),
5336
6429
  functionName: "getAppLatestReleaseBlockNumber",
5337
6430
  args: [appID]
5338
6431
  }).catch(() => null)
@@ -5411,12 +6504,41 @@ async function undelegate(options, logger = noopLogger) {
5411
6504
  return hash;
5412
6505
  }
5413
6506
 
6507
+ // src/client/common/gas/insufficientGas.ts
6508
+ var import_viem4 = require("viem");
6509
+ var InsufficientGasError = class extends Error {
6510
+ constructor(args) {
6511
+ const requiredEth = (0, import_viem4.formatEther)(args.requiredWei);
6512
+ const availableEth = (0, import_viem4.formatEther)(args.availableWei);
6513
+ super(
6514
+ `Insufficient ETH for gas: wallet ${args.address} has ${availableEth} ETH but this transaction needs ~${requiredEth} ETH.
6515
+ Compute credits do not pay on-chain gas \u2014 fund the wallet with ETH and retry.`
6516
+ );
6517
+ this.name = "InsufficientGasError";
6518
+ this.address = args.address;
6519
+ this.requiredWei = args.requiredWei;
6520
+ this.availableWei = args.availableWei;
6521
+ this.requiredEth = requiredEth;
6522
+ this.availableEth = availableEth;
6523
+ }
6524
+ };
6525
+ async function assertSufficientGas(args) {
6526
+ const availableWei = await args.publicClient.getBalance({ address: args.address });
6527
+ if (availableWei < args.gasEstimate.maxCostWei) {
6528
+ throw new InsufficientGasError({
6529
+ address: args.address,
6530
+ requiredWei: args.gasEstimate.maxCostWei,
6531
+ availableWei
6532
+ });
6533
+ }
6534
+ }
6535
+
5414
6536
  // src/client/common/utils/userapi.ts
5415
6537
  var import_axios2 = __toESM(require("axios"), 1);
5416
6538
 
5417
6539
  // src/client/common/utils/auth.ts
5418
- var import_viem4 = require("viem");
5419
- var APP_CONTROLLER_ABI = (0, import_viem4.parseAbi)([
6540
+ var import_viem5 = require("viem");
6541
+ var APP_CONTROLLER_ABI = (0, import_viem5.parseAbi)([
5420
6542
  "function calculateApiPermissionDigestHash(bytes4 permission, uint256 expiry) view returns (bytes32)"
5421
6543
  ]);
5422
6544
  async function calculatePermissionSignature(options) {
@@ -5474,6 +6596,7 @@ var import_axios = __toESM(require("axios"), 1);
5474
6596
  var MAX_RETRIES = 5;
5475
6597
  var INITIAL_BACKOFF_MS = 1e3;
5476
6598
  var MAX_BACKOFF_MS = 3e4;
6599
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
5477
6600
  function sleep(ms) {
5478
6601
  return new Promise((resolve2) => setTimeout(resolve2, ms));
5479
6602
  }
@@ -5493,7 +6616,7 @@ async function requestWithRetry(config) {
5493
6616
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
5494
6617
  const res = await (0, import_axios.default)({ ...config, validateStatus: () => true });
5495
6618
  lastResponse = res;
5496
- if (res.status !== 429) {
6619
+ if (!RETRYABLE_STATUSES.has(res.status)) {
5497
6620
  return res;
5498
6621
  }
5499
6622
  if (attempt < MAX_RETRIES) {
@@ -5522,7 +6645,7 @@ var CanViewAppLogsPermission = "0x2fd3f2fe";
5522
6645
  var CanViewSensitiveAppInfoPermission = "0x0e67b22f";
5523
6646
  var CanUpdateAppProfilePermission = "0x036fef61";
5524
6647
  function getDefaultClientId() {
5525
- const version = true ? "1.0.0-devep8" : "0.0.0";
6648
+ const version = true ? "1.0.0" : "0.0.0";
5526
6649
  return `ecloud-sdk/v${version}`;
5527
6650
  }
5528
6651
  var UserApiClient = class {
@@ -5557,7 +6680,6 @@ var UserApiClient = class {
5557
6680
  status: app.app_status,
5558
6681
  ip: app.ip,
5559
6682
  machineType: app.machine_type,
5560
- hostname: app.hostname,
5561
6683
  profile: app.profile,
5562
6684
  metrics: app.metrics,
5563
6685
  evmAddresses,
@@ -5623,28 +6745,6 @@ var UserApiClient = class {
5623
6745
  status: app.app_status || app.App_Status || ""
5624
6746
  }));
5625
6747
  }
5626
- /**
5627
- * Get deployments for an app from the gRPC-gateway endpoint.
5628
- * Returns deployment records including upgrade_phase for tracking upgrade progress.
5629
- *
5630
- * Endpoint: GET /v1/apps/:appAddress/deployments
5631
- */
5632
- async getDeployments(appAddress) {
5633
- const endpoint = `${this.config.userApiServerURL}/v1/apps/${appAddress}/deployments`;
5634
- const response = await this.makeEIP712AuthenticatedRequest(endpoint);
5635
- const result = await response.json();
5636
- const deployments = result.deployments || [];
5637
- return deployments.map((dep) => ({
5638
- id: dep.id || "",
5639
- externalId: dep.external_id || dep.externalId || "",
5640
- endpoint: dep.endpoint || "",
5641
- releaseId: dep.release_id || dep.releaseId || "",
5642
- upgradePhase: dep.upgrade_phase || dep.upgradePhase || "",
5643
- replacesDeploymentId: dep.replaces_deployment_id || dep.replacesDeploymentId || "",
5644
- createdAt: dep.created_at || dep.createdAt || "",
5645
- updatedAt: dep.updated_at || dep.updatedAt || ""
5646
- }));
5647
- }
5648
6748
  /**
5649
6749
  * Upload app profile information with optional image
5650
6750
  *
@@ -5778,48 +6878,6 @@ Please check:
5778
6878
  "X-eigenx-expiry": expiry.toString()
5779
6879
  };
5780
6880
  }
5781
- /**
5782
- * Make an EIP-712 authenticated request to the gRPC-gateway endpoints.
5783
- * Uses the billing/compute auth pattern: Authorization + X-Account + X-Expiry headers.
5784
- */
5785
- async makeEIP712AuthenticatedRequest(url) {
5786
- const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
5787
- const { signature } = await calculateBillingAuthSignature({
5788
- walletClient: this.walletClient,
5789
- product: "compute",
5790
- expiry
5791
- });
5792
- const headers = {
5793
- Authorization: `Bearer ${signature}`,
5794
- "X-Account": this.address,
5795
- "X-Expiry": expiry.toString(),
5796
- "x-client-id": this.clientId
5797
- };
5798
- try {
5799
- const response = await requestWithRetry({
5800
- method: "GET",
5801
- url,
5802
- headers,
5803
- maxRedirects: 0,
5804
- withCredentials: true
5805
- });
5806
- const status = response.status;
5807
- if (status < 200 || status >= 300) {
5808
- const body = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
5809
- throw new Error(`gRPC-gateway request failed: ${status} - ${body}`);
5810
- }
5811
- return {
5812
- json: async () => response.data,
5813
- text: async () => typeof response.data === "string" ? response.data : JSON.stringify(response.data)
5814
- };
5815
- } catch (error) {
5816
- if (error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED") || error.message?.includes("ENOTFOUND") || error.cause) {
5817
- const cause = error.cause?.message || error.cause || error.message;
5818
- throw new Error(`Failed to connect to API at ${url}: ${cause}`);
5819
- }
5820
- throw error;
5821
- }
5822
- }
5823
6881
  // ==========================================================================
5824
6882
  // SIWE Session Management
5825
6883
  // ==========================================================================
@@ -5925,8 +6983,36 @@ function transformAppRelease(raw) {
5925
6983
 
5926
6984
  // src/client/common/contract/watcher.ts
5927
6985
  var WATCH_POLL_INTERVAL_SECONDS = 5;
6986
+ var WATCH_HEARTBEAT_INTERVAL_SECONDS = 30;
6987
+ var WATCH_DEFAULT_TIMEOUT_SECONDS = 10 * 60;
5928
6988
  var APP_STATUS_RUNNING = "Running";
5929
6989
  var APP_STATUS_FAILED = "Failed";
6990
+ var WatchTimeoutError = class extends Error {
6991
+ constructor(args) {
6992
+ super(
6993
+ args.message ?? `Timed out after ${args.elapsedSeconds}s waiting for app ${args.appId} (last status: ${args.lastStatus ?? "unknown"})`
6994
+ );
6995
+ this.name = "WatchTimeoutError";
6996
+ this.appId = args.appId;
6997
+ this.elapsedSeconds = args.elapsedSeconds;
6998
+ this.lastStatus = args.lastStatus;
6999
+ this.timeoutSeconds = args.timeoutSeconds;
7000
+ }
7001
+ };
7002
+ function resolveWatchTimeoutSeconds(explicit) {
7003
+ if (typeof explicit === "number" && Number.isFinite(explicit) && explicit > 0) {
7004
+ return Math.floor(explicit);
7005
+ }
7006
+ const raw = process.env.ECLOUD_WATCH_TIMEOUT_SECONDS;
7007
+ if (raw === void 0 || raw === "") {
7008
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
7009
+ }
7010
+ const parsed = Number(raw);
7011
+ if (!Number.isFinite(parsed) || parsed <= 0) {
7012
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
7013
+ }
7014
+ return Math.floor(parsed);
7015
+ }
5930
7016
  async function watchUntilRunning(options, logger) {
5931
7017
  const { walletClient, publicClient, environmentConfig, appId } = options;
5932
7018
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
@@ -5957,8 +7043,20 @@ async function watchUntilRunning(options, logger) {
5957
7043
  return false;
5958
7044
  };
5959
7045
  const startTime = Date.now();
7046
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
5960
7047
  let lastLoggedStatus;
7048
+ let lastHeartbeatAt = startTime;
5961
7049
  while (true) {
7050
+ const elapsedMs = Date.now() - startTime;
7051
+ const elapsed = Math.round(elapsedMs / 1e3);
7052
+ if (elapsed >= timeoutSeconds) {
7053
+ throw new WatchTimeoutError({
7054
+ appId,
7055
+ elapsedSeconds: elapsed,
7056
+ lastStatus: lastLoggedStatus,
7057
+ timeoutSeconds
7058
+ });
7059
+ }
5962
7060
  try {
5963
7061
  const info = await userApiClient.getInfos([appId], 1);
5964
7062
  if (info.length === 0) {
@@ -5968,62 +7066,35 @@ async function watchUntilRunning(options, logger) {
5968
7066
  const appInfo = info[0];
5969
7067
  const currentStatus = appInfo.status;
5970
7068
  const currentIP = appInfo.ip || "";
5971
- const elapsed = Math.round((Date.now() - startTime) / 1e3);
5972
7069
  if (currentStatus !== lastLoggedStatus) {
5973
7070
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
5974
7071
  lastLoggedStatus = currentStatus;
7072
+ lastHeartbeatAt = Date.now();
7073
+ } else if (Date.now() - lastHeartbeatAt >= WATCH_HEARTBEAT_INTERVAL_SECONDS * 1e3) {
7074
+ logger.info(`Status: ${currentStatus} (${elapsed}s)`);
7075
+ lastHeartbeatAt = Date.now();
5975
7076
  }
5976
7077
  if (stopCondition(currentStatus, currentIP)) {
5977
7078
  return currentIP || void 0;
5978
7079
  }
5979
7080
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5980
7081
  } catch (error) {
7082
+ if (error instanceof WatchTimeoutError) {
7083
+ throw error;
7084
+ }
5981
7085
  logger.warn(`Failed to fetch app info: ${error.message}`);
5982
7086
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5983
7087
  }
5984
7088
  }
5985
7089
  }
5986
7090
  var APP_STATUS_STOPPED = "Stopped";
5987
- var UPGRADE_PHASE_LABELS = {
5988
- provisioning: "Provisioning new instance",
5989
- health_check: "Running health checks",
5990
- draining: "Switching traffic & draining old instance",
5991
- complete: "Complete",
5992
- rolling_back: "Rolling back",
5993
- rollback_done: "Rollback complete",
5994
- db_handoff: "Database handoff",
5995
- // Prewarm-detach phases
5996
- awaiting_userdata: "Waiting for instance readiness",
5997
- draining_old: "Draining old instance",
5998
- detached: "Detaching storage",
5999
- attached_to_new: "Attaching storage to new instance",
6000
- finalizing: "Finalizing new instance",
6001
- flipping: "Switching traffic",
6002
- teardown_old: "Cleaning up old instance"
6003
- };
6004
- async function fetchUpgradePhase(userApiClient, appId) {
6005
- try {
6006
- const deployments = await userApiClient.getDeployments(appId);
6007
- if (deployments.length === 0) return void 0;
6008
- const sorted = [...deployments].sort((a, b) => {
6009
- const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0;
6010
- const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0;
6011
- return tb - ta;
6012
- });
6013
- return sorted[0].upgradePhase || void 0;
6014
- } catch {
6015
- return void 0;
6016
- }
6017
- }
6018
7091
  async function watchUntilUpgradeComplete(options, logger) {
6019
7092
  const { walletClient, publicClient, environmentConfig, appId } = options;
7093
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
6020
7094
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
6021
7095
  let initialStatus;
6022
7096
  let initialIP;
6023
7097
  let hasChanged = false;
6024
- const startTime = Date.now();
6025
- let lastLoggedStatus;
6026
- let lastLoggedPhase;
6027
7098
  const stopCondition = (status, ip) => {
6028
7099
  if (!initialStatus) {
6029
7100
  initialStatus = status;
@@ -6057,37 +7128,44 @@ async function watchUntilUpgradeComplete(options, logger) {
6057
7128
  }
6058
7129
  return false;
6059
7130
  };
7131
+ const startTime = Date.now();
7132
+ const deadline = startTime + timeoutSeconds * 1e3;
7133
+ let lastLoggedStatus;
7134
+ let lastObservedStatus;
6060
7135
  while (true) {
7136
+ if (Date.now() >= deadline) {
7137
+ const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
7138
+ throw new WatchTimeoutError({
7139
+ appId,
7140
+ lastStatus: lastObservedStatus,
7141
+ elapsedSeconds,
7142
+ timeoutSeconds
7143
+ });
7144
+ }
6061
7145
  try {
6062
- const [infoResult, upgradePhase] = await Promise.all([
6063
- userApiClient.getInfos([appId], 1),
6064
- fetchUpgradePhase(userApiClient, appId)
6065
- ]);
6066
- if (infoResult.length === 0) {
7146
+ const info = await userApiClient.getInfos([appId], 1);
7147
+ if (info.length === 0) {
6067
7148
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
6068
7149
  continue;
6069
7150
  }
6070
- const appInfo = infoResult[0];
7151
+ const appInfo = info[0];
6071
7152
  const currentStatus = appInfo.status;
6072
7153
  const currentIP = appInfo.ip || "";
7154
+ lastObservedStatus = currentStatus;
6073
7155
  const elapsed = Math.round((Date.now() - startTime) / 1e3);
6074
- if (upgradePhase && upgradePhase !== lastLoggedPhase) {
6075
- const label = UPGRADE_PHASE_LABELS[upgradePhase] || upgradePhase;
6076
- logger.info(`Phase: ${label} (${elapsed}s)`);
6077
- lastLoggedPhase = upgradePhase;
6078
- }
6079
- if (!upgradePhase && currentStatus !== lastLoggedStatus) {
7156
+ if (currentStatus !== lastLoggedStatus) {
6080
7157
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
6081
7158
  lastLoggedStatus = currentStatus;
6082
7159
  }
6083
7160
  if (stopCondition(currentStatus, currentIP)) {
6084
- const totalElapsed = Math.round((Date.now() - startTime) / 1e3);
6085
- logger.info(`Upgrade completed in ${totalElapsed}s`);
6086
7161
  return;
6087
7162
  }
6088
7163
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
6089
7164
  } catch (error) {
6090
- if (error.message?.includes("entered")) {
7165
+ if (error instanceof WatchTimeoutError) {
7166
+ throw error;
7167
+ }
7168
+ if (typeof error?.message === "string" && error.message.includes("Failed")) {
6091
7169
  throw error;
6092
7170
  }
6093
7171
  logger.warn(`Failed to fetch app info: ${error.message}`);
@@ -6102,7 +7180,7 @@ function sleep2(ms) {
6102
7180
  // src/client/common/utils/validation.ts
6103
7181
  var import_fs = __toESM(require("fs"), 1);
6104
7182
  var import_path = __toESM(require("path"), 1);
6105
- var import_viem5 = require("viem");
7183
+ var import_viem6 = require("viem");
6106
7184
  function validateAppName(name) {
6107
7185
  if (!name) {
6108
7186
  throw new Error("App name cannot be empty");
@@ -6254,7 +7332,7 @@ function validateAppID(appID) {
6254
7332
  throw new Error("App ID is required");
6255
7333
  }
6256
7334
  const normalized = typeof appID === "string" ? addHexPrefix(appID) : appID;
6257
- if ((0, import_viem5.isAddress)(normalized)) {
7335
+ if ((0, import_viem6.isAddress)(normalized)) {
6258
7336
  return normalized;
6259
7337
  }
6260
7338
  throw new Error(`Invalid app ID: '${appID}' is not a valid address`);
@@ -6418,14 +7496,13 @@ var BILLING_ENVIRONMENTS = {
6418
7496
  billingApiServerURL: "https://billingapi.eigencloud.xyz"
6419
7497
  }
6420
7498
  };
6421
- var PLATFORM_ENV_TESTNET_SEPOLIA = "testnet-sepolia";
6422
- var PLATFORM_ENV_MAINNET_ETHEREUM = "mainnet-ethereum";
6423
- var DEFAULT_APP_BASE_DOMAIN = "eigencloud.xyz";
6424
7499
  var ENVIRONMENTS = {
6425
7500
  "sepolia-dev": {
6426
7501
  name: "sepolia",
6427
7502
  build: "dev",
6428
7503
  appControllerAddress: "0xa86DC1C47cb2518327fB4f9A1627F51966c83B92",
7504
+ releaseAbiVersion: "v1.5",
7505
+ // AppController upgraded to v1.5.x (containerPolicy)
6429
7506
  permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
6430
7507
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
6431
7508
  kmsServerURL: "http://10.128.0.57:8080",
@@ -6433,14 +7510,14 @@ var ENVIRONMENTS = {
6433
7510
  defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com",
6434
7511
  usdcCreditsAddress: "0xbdA3897c3A428763B59015C64AB766c288C97376",
6435
7512
  baseUsdcCreditsAddress: "0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac",
6436
- baseRPCURL: "https://base-sepolia-rpc.publicnode.com",
6437
- platformEnv: PLATFORM_ENV_TESTNET_SEPOLIA,
6438
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7513
+ baseRPCURL: "https://base-sepolia-rpc.publicnode.com"
6439
7514
  },
6440
7515
  sepolia: {
6441
7516
  name: "sepolia",
6442
7517
  build: "prod",
6443
7518
  appControllerAddress: "0x0dd810a6ffba6a9820a10d97b659f07d8d23d4E2",
7519
+ releaseAbiVersion: "v1.4",
7520
+ // prod still on AppController v1.4.0 (3-field Release)
6444
7521
  permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
6445
7522
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
6446
7523
  kmsServerURL: "http://10.128.15.203:8080",
@@ -6449,22 +7526,20 @@ var ENVIRONMENTS = {
6449
7526
  billingRPCURL: "https://ethereum-rpc.publicnode.com",
6450
7527
  usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d",
6451
7528
  baseUsdcCreditsAddress: "0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac",
6452
- baseRPCURL: "https://base-sepolia-rpc.publicnode.com",
6453
- platformEnv: PLATFORM_ENV_TESTNET_SEPOLIA,
6454
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7529
+ baseRPCURL: "https://base-sepolia-rpc.publicnode.com"
6455
7530
  },
6456
7531
  "mainnet-alpha": {
6457
7532
  name: "mainnet-alpha",
6458
7533
  build: "prod",
6459
7534
  appControllerAddress: "0xc38d35Fc995e75342A21CBd6D770305b142Fbe67",
7535
+ releaseAbiVersion: "v1.4",
7536
+ // prod still on AppController v1.4.0 (3-field Release)
6460
7537
  permissionControllerAddress: ChainAddresses[MAINNET_CHAIN_ID].PermissionController,
6461
7538
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
6462
7539
  kmsServerURL: "http://10.128.0.2:8080",
6463
7540
  userApiServerURL: "https://userapi-compute.eigencloud.xyz",
6464
7541
  defaultRPCURL: "https://ethereum-rpc.publicnode.com",
6465
- usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d",
6466
- platformEnv: PLATFORM_ENV_MAINNET_ETHEREUM,
6467
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7542
+ usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d"
6468
7543
  }
6469
7544
  };
6470
7545
  var CHAIN_ID_TO_ENVIRONMENT = {
@@ -6524,7 +7599,7 @@ function getBillingEnvironmentConfig(build) {
6524
7599
  };
6525
7600
  }
6526
7601
  function getBuildType() {
6527
- const buildTimeType = true ? "dev"?.toLowerCase() : void 0;
7602
+ const buildTimeType = true ? "prod"?.toLowerCase() : void 0;
6528
7603
  const runtimeType = process.env.BUILD_TYPE?.toLowerCase();
6529
7604
  const buildType = buildTimeType || runtimeType;
6530
7605
  if (buildType === "dev") {
@@ -6841,6 +7916,63 @@ var BillingApiClient = class {
6841
7916
  return resp.json();
6842
7917
  }
6843
7918
  // ==========================================================================
7919
+ // Admin - Coupon Methods
7920
+ // ==========================================================================
7921
+ async createCoupon(amountCents) {
7922
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons`;
7923
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { amountCents });
7924
+ return resp.json();
7925
+ }
7926
+ async listCoupons(opts) {
7927
+ const params = new URLSearchParams();
7928
+ if (opts?.offset !== void 0) params.set("offset", opts.offset.toString());
7929
+ if (opts?.limit !== void 0) params.set("limit", opts.limit.toString());
7930
+ if (opts?.active !== void 0) params.set("active", opts.active.toString());
7931
+ if (opts?.redeemed !== void 0) params.set("redeemed", opts.redeemed.toString());
7932
+ const qs = params.toString();
7933
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons${qs ? `?${qs}` : ""}`;
7934
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", "compute");
7935
+ return resp.json();
7936
+ }
7937
+ async getCoupon(id) {
7938
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons/${id}`;
7939
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", "compute");
7940
+ return resp.json();
7941
+ }
7942
+ async deactivateCoupon(id) {
7943
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons/${id}/deactivate`;
7944
+ await this.makeAuthenticatedRequest(endpoint, "POST", "compute");
7945
+ }
7946
+ async redeemCouponForUser(id, address) {
7947
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons/${id}/redeem`;
7948
+ await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { address });
7949
+ }
7950
+ // ==========================================================================
7951
+ // Admin - Admin Management Methods
7952
+ // ==========================================================================
7953
+ async addAdmin(address) {
7954
+ const endpoint = `${this.config.billingApiServerURL}/admin/admins`;
7955
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { address });
7956
+ return resp.json();
7957
+ }
7958
+ async removeAdmin(address) {
7959
+ const endpoint = `${this.config.billingApiServerURL}/admin/admins/${address}`;
7960
+ await this.makeAuthenticatedRequest(endpoint, "DELETE", "compute");
7961
+ }
7962
+ async listAdmins() {
7963
+ const endpoint = `${this.config.billingApiServerURL}/admin/admins`;
7964
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", "compute");
7965
+ return resp.json();
7966
+ }
7967
+ // ==========================================================================
7968
+ // User - Coupon Redemption
7969
+ // ==========================================================================
7970
+ async redeemCoupon(code) {
7971
+ const endpoint = `${this.config.billingApiServerURL}/v1/coupons/redeem`;
7972
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { code });
7973
+ return resp.json();
7974
+ }
7975
+ // ==========================================================================
6844
7976
  // Internal Methods
6845
7977
  // ==========================================================================
6846
7978
  /**
@@ -7256,6 +8388,11 @@ async function prepareDeployFromVerifiableBuild(options, logger = defaultLogger)
7256
8388
  executions: batch.executions,
7257
8389
  authorizationList
7258
8390
  });
8391
+ await assertSufficientGas({
8392
+ publicClient: batch.publicClient,
8393
+ address: batch.walletClient.account.address,
8394
+ gasEstimate
8395
+ });
7259
8396
  const data = {
7260
8397
  appId: batch.appId,
7261
8398
  salt: batch.salt,
@@ -7514,6 +8651,11 @@ async function prepareDeploy(options, logger = defaultLogger) {
7514
8651
  executions: batch.executions,
7515
8652
  authorizationList
7516
8653
  });
8654
+ await assertSufficientGas({
8655
+ publicClient: batch.publicClient,
8656
+ address: batch.walletClient.account.address,
8657
+ gasEstimate
8658
+ });
7517
8659
  const data = {
7518
8660
  appId: batch.appId,
7519
8661
  salt: batch.salt,
@@ -7550,7 +8692,7 @@ async function executeDeploy(options) {
7550
8692
  }
7551
8693
  );
7552
8694
  }
7553
- async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
8695
+ async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
7554
8696
  return withSDKTelemetry(
7555
8697
  {
7556
8698
  functionName: "watchDeployment",
@@ -7566,7 +8708,8 @@ async function watchDeployment(appId, walletClient, publicClient, environmentCon
7566
8708
  walletClient,
7567
8709
  publicClient,
7568
8710
  environmentConfig,
7569
- appId
8711
+ appId,
8712
+ timeoutSeconds: opts?.timeoutSeconds
7570
8713
  },
7571
8714
  logger
7572
8715
  );
@@ -7662,6 +8805,11 @@ async function prepareUpgradeFromVerifiableBuild(options, logger = defaultLogger
7662
8805
  executions: batch.executions,
7663
8806
  authorizationList
7664
8807
  });
8808
+ await assertSufficientGas({
8809
+ publicClient: batch.publicClient,
8810
+ address: batch.walletClient.account.address,
8811
+ gasEstimate
8812
+ });
7665
8813
  const data = {
7666
8814
  appId: batch.appId,
7667
8815
  executions: batch.executions,
@@ -7851,6 +8999,11 @@ async function prepareUpgrade(options, logger = defaultLogger) {
7851
8999
  executions: batch.executions,
7852
9000
  authorizationList
7853
9001
  });
9002
+ await assertSufficientGas({
9003
+ publicClient: batch.publicClient,
9004
+ address: batch.walletClient.account.address,
9005
+ gasEstimate
9006
+ });
7854
9007
  const data = {
7855
9008
  appId: batch.appId,
7856
9009
  executions: batch.executions,
@@ -7885,7 +9038,7 @@ async function executeUpgrade(options) {
7885
9038
  }
7886
9039
  );
7887
9040
  }
7888
- async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
9041
+ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
7889
9042
  return withSDKTelemetry(
7890
9043
  {
7891
9044
  functionName: "watchUpgrade",
@@ -7901,7 +9054,8 @@ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig
7901
9054
  walletClient,
7902
9055
  publicClient,
7903
9056
  environmentConfig,
7904
- appId
9057
+ appId,
9058
+ timeoutSeconds: opts?.timeoutSeconds
7905
9059
  },
7906
9060
  logger
7907
9061
  );
@@ -8559,27 +9713,27 @@ async function logs(options, walletClient, publicClient, environmentConfig, logg
8559
9713
  }
8560
9714
 
8561
9715
  // src/client/modules/compute/app/index.ts
8562
- var CONTROLLER_ABI = (0, import_viem6.parseAbi)([
9716
+ var CONTROLLER_ABI = (0, import_viem7.parseAbi)([
8563
9717
  "function startApp(address appId)",
8564
9718
  "function stopApp(address appId)",
8565
9719
  "function terminateApp(address appId)"
8566
9720
  ]);
8567
9721
  function encodeStartAppData(appId) {
8568
- return (0, import_viem6.encodeFunctionData)({
9722
+ return (0, import_viem7.encodeFunctionData)({
8569
9723
  abi: CONTROLLER_ABI,
8570
9724
  functionName: "startApp",
8571
9725
  args: [appId]
8572
9726
  });
8573
9727
  }
8574
9728
  function encodeStopAppData(appId) {
8575
- return (0, import_viem6.encodeFunctionData)({
9729
+ return (0, import_viem7.encodeFunctionData)({
8576
9730
  abi: CONTROLLER_ABI,
8577
9731
  functionName: "stopApp",
8578
9732
  args: [appId]
8579
9733
  });
8580
9734
  }
8581
9735
  function encodeTerminateAppData(appId) {
8582
- return (0, import_viem6.encodeFunctionData)({
9736
+ return (0, import_viem7.encodeFunctionData)({
8583
9737
  abi: CONTROLLER_ABI,
8584
9738
  functionName: "terminateApp",
8585
9739
  args: [appId]
@@ -8593,7 +9747,7 @@ function createAppModule(ctx) {
8593
9747
  }
8594
9748
  const account = walletClient.account;
8595
9749
  const environment = getEnvironmentConfig(ctx.environment);
8596
- const logger = getLogger(ctx.verbose);
9750
+ const logger = ctx.logger ?? getLogger(ctx.verbose);
8597
9751
  return {
8598
9752
  async create(opts) {
8599
9753
  return createApp(opts, logger);
@@ -8705,14 +9859,15 @@ function createAppModule(ctx) {
8705
9859
  imageRef: result.imageRef
8706
9860
  };
8707
9861
  },
8708
- async watchDeployment(appId) {
9862
+ async watchDeployment(appId, opts) {
8709
9863
  return watchDeployment(
8710
9864
  appId,
8711
9865
  walletClient,
8712
9866
  publicClient,
8713
9867
  environment,
8714
9868
  logger,
8715
- skipTelemetry
9869
+ skipTelemetry,
9870
+ opts
8716
9871
  );
8717
9872
  },
8718
9873
  // Granular upgrade control
@@ -8770,8 +9925,16 @@ function createAppModule(ctx) {
8770
9925
  imageRef: result.imageRef
8771
9926
  };
8772
9927
  },
8773
- async watchUpgrade(appId) {
8774
- return watchUpgrade(appId, walletClient, publicClient, environment, logger, skipTelemetry);
9928
+ async watchUpgrade(appId, opts) {
9929
+ return watchUpgrade(
9930
+ appId,
9931
+ walletClient,
9932
+ publicClient,
9933
+ environment,
9934
+ logger,
9935
+ skipTelemetry,
9936
+ opts
9937
+ );
8775
9938
  },
8776
9939
  // Profile management
8777
9940
  async setProfile(appId, profile) {
@@ -8822,7 +9985,7 @@ function createAppModule(ctx) {
8822
9985
  },
8823
9986
  async () => {
8824
9987
  const pendingMessage = `Starting app ${appId}...`;
8825
- const data = (0, import_viem6.encodeFunctionData)({
9988
+ const data = (0, import_viem7.encodeFunctionData)({
8826
9989
  abi: CONTROLLER_ABI,
8827
9990
  functionName: "startApp",
8828
9991
  args: [appId]
@@ -8854,7 +10017,7 @@ function createAppModule(ctx) {
8854
10017
  },
8855
10018
  async () => {
8856
10019
  const pendingMessage = `Stopping app ${appId}...`;
8857
- const data = (0, import_viem6.encodeFunctionData)({
10020
+ const data = (0, import_viem7.encodeFunctionData)({
8858
10021
  abi: CONTROLLER_ABI,
8859
10022
  functionName: "stopApp",
8860
10023
  args: [appId]
@@ -8886,7 +10049,7 @@ function createAppModule(ctx) {
8886
10049
  },
8887
10050
  async () => {
8888
10051
  const pendingMessage = `Terminating app ${appId}...`;
8889
- const data = (0, import_viem6.encodeFunctionData)({
10052
+ const data = (0, import_viem7.encodeFunctionData)({
8890
10053
  abi: CONTROLLER_ABI,
8891
10054
  functionName: "terminateApp",
8892
10055
  args: [appId]
@@ -8953,7 +10116,7 @@ function createComputeModule(config) {
8953
10116
  }
8954
10117
 
8955
10118
  // src/client/modules/billing/index.ts
8956
- var import_viem7 = require("viem");
10119
+ var import_viem8 = require("viem");
8957
10120
 
8958
10121
  // src/client/common/abis/USDCCredits.json
8959
10122
  var USDCCredits_default = [
@@ -9138,7 +10301,7 @@ function createBillingModule(config) {
9138
10301
  executions.push({
9139
10302
  target: usdcAddress,
9140
10303
  value: 0n,
9141
- callData: (0, import_viem7.encodeFunctionData)({
10304
+ callData: (0, import_viem8.encodeFunctionData)({
9142
10305
  abi: ERC20_default,
9143
10306
  functionName: "approve",
9144
10307
  args: [creditsAddress, opts.amount]
@@ -9148,7 +10311,7 @@ function createBillingModule(config) {
9148
10311
  executions.push({
9149
10312
  target: creditsAddress,
9150
10313
  value: 0n,
9151
- callData: (0, import_viem7.encodeFunctionData)({
10314
+ callData: (0, import_viem8.encodeFunctionData)({
9152
10315
  abi: USDCCredits_default,
9153
10316
  functionName: "purchaseCreditsFor",
9154
10317
  args: [opts.amount, targetAccount]
@@ -9261,6 +10424,9 @@ function createBillingModule(config) {
9261
10424
  },
9262
10425
  hasBaseSupport() {
9263
10426
  return !!baseUsdcCreditsAddress && !!baseRPCURL;
10427
+ },
10428
+ async redeemCoupon(code) {
10429
+ return billingApi.redeemCoupon(code);
9264
10430
  }
9265
10431
  };
9266
10432
  return module2;
@@ -9696,6 +10862,44 @@ function transformVerifyResult(raw) {
9696
10862
  };
9697
10863
  }
9698
10864
 
10865
+ // src/client/modules/admin/index.ts
10866
+ function createAdminModule(config) {
10867
+ const { verbose = false, walletClient } = config;
10868
+ if (!walletClient.account) {
10869
+ throw new Error("WalletClient must have an account attached");
10870
+ }
10871
+ const address = walletClient.account.address;
10872
+ const billingEnvConfig = getBillingEnvironmentConfig(getBuildType());
10873
+ const billingApi = new BillingApiClient(billingEnvConfig, walletClient, { verbose });
10874
+ return {
10875
+ address,
10876
+ async createCoupon(amountCents) {
10877
+ return billingApi.createCoupon(amountCents);
10878
+ },
10879
+ async listCoupons(opts) {
10880
+ return billingApi.listCoupons(opts);
10881
+ },
10882
+ async getCoupon(id) {
10883
+ return billingApi.getCoupon(id);
10884
+ },
10885
+ async deactivateCoupon(id) {
10886
+ return billingApi.deactivateCoupon(id);
10887
+ },
10888
+ async redeemCouponForUser(id, userAddress) {
10889
+ return billingApi.redeemCouponForUser(id, userAddress);
10890
+ },
10891
+ async addAdmin(adminAddress) {
10892
+ return billingApi.addAdmin(adminAddress);
10893
+ },
10894
+ async removeAdmin(adminAddress) {
10895
+ return billingApi.removeAdmin(adminAddress);
10896
+ },
10897
+ async listAdmins() {
10898
+ return billingApi.listAdmins();
10899
+ }
10900
+ };
10901
+ }
10902
+
9699
10903
  // src/client/common/auth/keyring.ts
9700
10904
  var import_keyring = require("@napi-rs/keyring");
9701
10905
  var import_accounts2 = require("viem/accounts");
@@ -10241,8 +11445,10 @@ var JwtProvider = class {
10241
11445
  BuildError,
10242
11446
  BuildFailedError,
10243
11447
  ConflictError,
11448
+ EMPTY_CONTAINER_POLICY,
10244
11449
  ERC20ABI,
10245
11450
  ForbiddenError,
11451
+ InsufficientGasError,
10246
11452
  JwtProvider,
10247
11453
  NoopClient,
10248
11454
  NotFoundError,
@@ -10252,14 +11458,18 @@ var JwtProvider = class {
10252
11458
  TimeoutError,
10253
11459
  USDCCreditsABI,
10254
11460
  UserApiClient,
11461
+ WATCH_DEFAULT_TIMEOUT_SECONDS,
11462
+ WatchTimeoutError,
10255
11463
  addHexPrefix,
10256
11464
  addMetric,
10257
11465
  addMetricWithDimensions,
11466
+ assertSufficientGas,
10258
11467
  assertValidFilePath,
10259
11468
  assertValidImageReference,
10260
11469
  assertValidPrivateKey,
10261
11470
  calculateAppID,
10262
11471
  checkERC7702Delegation,
11472
+ createAdminModule,
10263
11473
  createApp,
10264
11474
  createAppEnvironment,
10265
11475
  createBillingModule,