@layr-labs/ecloud-sdk 1.0.0-devep7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -143,8 +143,8 @@ import * as child_process from "child_process";
143
143
  import { promisify } from "util";
144
144
 
145
145
  // src/client/common/constants.ts
146
- import { sepolia, mainnet } from "viem/chains";
147
- var SUPPORTED_CHAINS = [mainnet, sepolia];
146
+ import { sepolia, mainnet, baseSepolia } from "viem/chains";
147
+ var SUPPORTED_CHAINS = [mainnet, sepolia, baseSepolia];
148
148
  var DOCKER_PLATFORM = "linux/amd64";
149
149
  var REGISTRY_PROPAGATION_WAIT_SECONDS = 3;
150
150
  var LAYERED_DOCKERFILE_NAME = "Dockerfile.eigencompute";
@@ -152,7 +152,6 @@ var ENV_SOURCE_SCRIPT_NAME = "compute-source-env.sh";
152
152
  var KMS_CLIENT_BINARY_NAME = "kms-client";
153
153
  var KMS_SIGNING_KEY_NAME = "kms-signing-public-key.pem";
154
154
  var TLS_KEYGEN_BINARY_NAME = "tls-keygen";
155
- var DRAIN_WATCHER_BINARY_NAME = "ecloud-drain-watcher";
156
155
  var CADDYFILE_NAME = "Caddyfile";
157
156
  var LAYERED_BUILD_DIR_PREFIX = "ecloud-layered-build";
158
157
 
@@ -439,12 +438,14 @@ USER root
439
438
  {{/if}}
440
439
 
441
440
  # Copy core TEE components
441
+ # CA bundle for kms-client / tls-keygen to validate HTTPS calls to
442
+ # eigencloud.xyz endpoints. Bundled at a non-standard path and consumed
443
+ # only via SSL_CERT_FILE in compute-source-env.sh, so the user's
444
+ # /etc/ssl/ is never touched.
445
+ COPY --from=alpine:3.20.10 /etc/ssl/certs/ca-certificates.crt /usr/local/share/eigenx-ca-certs.crt
442
446
  COPY compute-source-env.sh /usr/local/bin/
443
447
  COPY kms-client /usr/local/bin/
444
448
  COPY kms-signing-public-key.pem /usr/local/bin/
445
- {{#if includeDrainWatcher}}
446
- COPY ecloud-drain-watcher /usr/local/bin/
447
- {{/if}}
448
449
 
449
450
  {{#if includeTLS}}
450
451
  # Copy Caddy from official image
@@ -458,8 +459,7 @@ COPY Caddyfile /etc/caddy/
458
459
  {{#if originalUser}}
459
460
  # Make binaries executable (755 for executables, 644 for keys)
460
461
  RUN chmod 755 /usr/local/bin/compute-source-env.sh \\
461
- && chmod 755 /usr/local/bin/kms-client{{#if includeDrainWatcher}} \\
462
- && chmod 755 /usr/local/bin/ecloud-drain-watcher{{/if}}{{#if includeTLS}} \\
462
+ && chmod 755 /usr/local/bin/kms-client{{#if includeTLS}} \\
463
463
  && chmod 755 /usr/local/bin/tls-keygen \\
464
464
  && chmod 755 /usr/local/bin/caddy{{/if}} \\
465
465
  && chmod 644 /usr/local/bin/kms-signing-public-key.pem
@@ -469,8 +469,7 @@ ENV __ECLOUD_ORIGINAL_USER={{originalUser}}
469
469
  {{else}}
470
470
  # Make binaries executable (preserve existing permissions, just add execute)
471
471
  RUN chmod +x /usr/local/bin/compute-source-env.sh \\
472
- && chmod +x /usr/local/bin/kms-client{{#if includeDrainWatcher}} \\
473
- && chmod +x /usr/local/bin/ecloud-drain-watcher{{/if}}{{#if includeTLS}} \\
472
+ && chmod +x /usr/local/bin/kms-client{{#if includeTLS}} \\
474
473
  && chmod +x /usr/local/bin/tls-keygen{{/if}}
475
474
  {{/if}}
476
475
 
@@ -483,36 +482,8 @@ LABEL tee.launch_policy.log_redirect={{logRedirect}}
483
482
  LABEL tee.launch_policy.monitoring_memory_allow={{resourceUsageAllow}}
484
483
  {{/if}}
485
484
 
486
- # Allow-list the envvars the ecloud-platform sets via GCE \`tee-env-*\`
487
- # metadata. Without this label, Confidential Space's launcher rejects
488
- # any \`tee-env-*\` override at container-start with
489
- # "env var {...} is not allowed to be overridden on this image" and
490
- # exits with code 1 \u2014 which terminates the VM before the entrypoint
491
- # ever runs. User-supplied env vars flow through KMS (not tee-env-*)
492
- # and don't need to be listed here.
493
- #
494
- # Entries:
495
- # - ECLOUD_PD_EXPECTED set on PD-backed apps so the entrypoint
496
- # (compute-source-env.sh) knows to wait for
497
- # the persistent disk before exec'ing the
498
- # user workload.
499
- # - ECLOUD_PLATFORM_HOST the platform-routed hostname
500
- # (<addr>.<env>.eigencloud.xyz) so the
501
- # entrypoint's setup_tls can issue an ACME
502
- # cert for it. Injected by the CLI into
503
- # publicEnv at deploy/upgrade time and
504
- # propagated by ecloud-platform's
505
- # compute.go as a tee-env-* metadata key.
506
- #
507
- # The CS launcher parses this label as a comma-separated list
508
- # (go-tpm-tools/launcher/spec/launch_policy.go:185 \u2014 strings.Split
509
- # on ","). Quotes are not required; keep the value bare for
510
- # consistency with compute-tee's and eigenx-kms's existing labels.
511
- LABEL tee.launch_policy.allow_env_override=ECLOUD_PD_EXPECTED,ECLOUD_PLATFORM_HOST
512
-
513
485
  LABEL eigenx_cli_version={{ecloudCLIVersion}}
514
486
  LABEL eigenx_vm_image=eigen
515
- LABEL eigenx_container_contract=v1
516
487
 
517
488
  {{#if includeTLS}}
518
489
  # Expose both HTTP and HTTPS ports for Caddy
@@ -534,54 +505,11 @@ import Handlebars2 from "handlebars";
534
505
 
535
506
  // src/client/common/templates/compute-source-env.sh.tmpl
536
507
  var compute_source_env_sh_default = `#!/bin/sh
537
- # EigenCompute container entrypoint script
538
- # This script handles KMS secret fetching, TLS setup, and privilege dropping
539
- # before executing the user's application.
540
- #
541
- # Handlebars template variables (replaced at build time by the CLI):
542
- # kmsServerURL - URL of the KMS server
543
- # userAPIURL - URL of the user API (ecloud-platform)
544
- # The KMS signing public key is copied into the image as
545
- # /usr/local/bin/kms-signing-public-key.pem at layer-build time by the CLI.
546
- #
547
- # ecloud-platform divergence from compute-tee:
548
- # This script emits ECLOUD_READY / ECLOUD_FAIL / ECLOUD_AWAITING_USERDATA /
549
- # ECLOUD_DETACHED markers to stdout at key lifecycle points. The GCP
550
- # provisioner's serial-console watcher in ecloud-platform
551
- # (pkg/services/infraService/providers/gcp/compute.go) parses those
552
- # markers to gate "VM ready" and to coordinate the prewarm-detach
553
- # upgrade flow. Without the markers, the platform's waitForStartupReady
554
- # times out at ~10 minutes per deploy, rollback fires, and the VM is
555
- # deleted \u2014 seen in dev on 2026-05-04 with an older copy of this
556
- # template that lacked the markers.
557
- #
558
- # Prewarm-detach contract:
559
- # - If ECLOUD_PD_EXPECTED=1 and /mnt/disks/userdata is not present at boot,
560
- # emit ECLOUD_AWAITING_USERDATA and wait until the disk is attached.
561
- # - On SIGTERM (drain-requested), forward to child, wait for exit, sync
562
- # + unmount /mnt/disks/userdata, emit ECLOUD_DETACHED, exit.
563
- # - ECLOUD_READY is emitted once runtime is bootstrapped (same as before).
564
- # - ECLOUD_FAIL is emitted on any unrecoverable setup error.
565
- # Keep the markers on any line that resolves a lifecycle outcome.
566
- #
567
- # This file is kept in lockstep with
568
- # ecloud-platform/pkg/services/buildService/assets/compute-source-env.sh.tmpl
569
- # \u2014 if you change one, change the other. Differences vs the platform copy
570
- # are intentionally minimal:
571
- # - Handlebars placeholders use the CLI's naming (kmsServerURL,
572
- # userAPIURL) rather than the platform's (KMS_SERVER_URL,
573
- # USER_API_URL). (See top of file for real placeholder syntax \u2014
574
- # not repeated here so Handlebars doesn't expand it in this comment.)
575
- # - KMS signing key is read from a file the CLI copies into the image,
576
- # not heredoc-embedded in the script, because the CLI's image
577
- # layering writes it as a separate file (kms-signing-public-key.pem).
578
- # - TLS binary is \`tls-keygen\` (CLI-bundled) not \`tls-client\`.
579
-
580
508
  echo "compute-source-env.sh: Running setup script..."
581
509
 
582
510
  # Fetch and source environment variables from KMS
583
511
  echo "Fetching secrets from KMS..."
584
- if /usr/local/bin/kms-client \\
512
+ if SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt /usr/local/bin/kms-client \\
585
513
  --kms-server-url "{{kmsServerURL}}" \\
586
514
  --kms-signing-key-file /usr/local/bin/kms-signing-public-key.pem \\
587
515
  --userapi-url "{{userAPIURL}}" \\
@@ -592,187 +520,93 @@ if /usr/local/bin/kms-client \\
592
520
  else
593
521
  echo "compute-source-env.sh: ERROR - Failed to fetch environment variables from KMS"
594
522
  echo "compute-source-env.sh: Exiting - cannot start user workload without KMS secrets"
595
- echo "ECLOUD_FAIL kms_bootstrap"
596
523
  exit 1
597
524
  fi
598
525
 
599
- # issue_cert_for runs tls-keygen for a single hostname and copies the
600
- # produced fullchain/privkey into $1's output directory ($2). Returns
601
- # 0 on success, non-zero on any failure (caller decides whether that's
602
- # fatal). tls-keygen writes to /run/tls/{fullchain,privkey}.pem by
603
- # default; we move those to a per-host subdirectory so the two-site
604
- # Caddyfile can select the right cert by path.
605
- issue_cert_for() {
606
- local host="$1"
607
- local cert_dir="$2"
608
- local mnemonic="$3"
609
- local challenge="$4"
610
- local staging_flag="$5"
611
-
612
- echo "compute-source-env.sh: Obtaining TLS certificate for $host (challenge=$challenge)..."
613
- # Remove any stale default outputs from a prior tls-keygen run
614
- rm -f /run/tls/fullchain.pem /run/tls/privkey.pem
615
- if ! MNEMONIC="$mnemonic" DOMAIN="$host" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
616
- -challenge "$challenge" \\
617
- $staging_flag; then
618
- return 1
619
- fi
620
- mkdir -p "$cert_dir"
621
- mv /run/tls/fullchain.pem "$cert_dir/fullchain.pem"
622
- mv /run/tls/privkey.pem "$cert_dir/privkey.pem"
623
- echo "compute-source-env.sh: Cert for $host written to $cert_dir"
624
- }
625
-
626
- # Setup TLS: issues certs for
627
- # - ECLOUD_PLATFORM_HOST (platform-routed <addr>.<env>.eigencloud.xyz),
628
- # when the CLI/platform has set it
629
- # - DOMAIN (user-supplied custom domain), when set and non-localhost
630
- #
631
- # No client-side DNS precheck. Earlier versions tried to gate ACME on
632
- # "does this hostname resolve to my external IP" but that's wrong for
633
- # the platform-routing model (DNS points at the shared nginx NLB, not
634
- # the VM) and was preventing cert issuance on the production path.
635
- # tls-client (eigencompute-containers/tls-client) does its own DNS
636
- # poll before calling ACME and surfaces a clear error when challenges
637
- # can't reach the VM, which is the right place for that check \u2014
638
- # attempting it here from inside the VM cannot tell platform-routed
639
- # from compute-tee-routed apps.
526
+ # Setup TLS if tls-keygen is present (which means TLS was configured at build time)
640
527
  setup_tls() {
641
528
  # If tls-keygen isn't present, TLS wasn't configured during build
642
529
  if [ ! -x /usr/local/bin/tls-keygen ]; then
643
530
  echo "compute-source-env.sh: TLS not configured (no tls-keygen binary)"
644
531
  return 0
645
532
  fi
646
-
647
- local platform_host="\${ECLOUD_PLATFORM_HOST:-}"
648
- local user_domain="\${DOMAIN:-}"
533
+
534
+ local domain="\${DOMAIN:-}"
649
535
  local mnemonic="\${MNEMONIC:-}"
650
-
651
- # Normalize "localhost" DOMAIN to unset \u2014 compute-tee apps often
652
- # set DOMAIN=localhost as a sentinel for "no custom domain".
653
- if [ "$user_domain" = "localhost" ]; then
654
- user_domain=""
655
- fi
656
-
657
- # If the user set DOMAIN to the same value as the platform
658
- # hostname, collapse them. Without this we'd burn a second ACME
659
- # issuance for the exact same name (each VM boot counts against
660
- # Let's Encrypt's 5-certs-per-domain-per-week limit), and Caddy
661
- # would then refuse to start because two site blocks bound to
662
- # the same address is a config error. Also unset DOMAIN so the
663
- # DOMAIN site block in the Caddyfile falls back to its
664
- # "localhost.user.invalid" placeholder and stays dormant.
665
- if [ -n "$platform_host" ] && [ "$user_domain" = "$platform_host" ]; then
666
- echo "compute-source-env.sh: DOMAIN matches ECLOUD_PLATFORM_HOST; skipping duplicate cert"
667
- user_domain=""
668
- unset DOMAIN
669
- fi
670
-
671
- if [ -z "$platform_host" ] && [ -z "$user_domain" ]; then
672
- echo "compute-source-env.sh: TLS skipped (neither ECLOUD_PLATFORM_HOST nor DOMAIN set)"
673
- return 0
536
+
537
+ # Since tls-keygen is present, TLS is expected - validate requirements
538
+ if [ -z "$domain" ] || [ "$domain" = "localhost" ]; then
539
+ echo "compute-source-env.sh: ERROR - TLS binary present but DOMAIN not configured or is localhost"
540
+ echo "compute-source-env.sh: Set DOMAIN environment variable to a valid domain"
541
+ exit 1
674
542
  fi
675
-
543
+
676
544
  if [ -z "$mnemonic" ]; then
677
- echo "compute-source-env.sh: ERROR - TLS requested but MNEMONIC not available"
678
- echo "ECLOUD_FAIL tls_mnemonic_missing"
545
+ echo "compute-source-env.sh: ERROR - TLS binary present but MNEMONIC not available"
546
+ echo "compute-source-env.sh: Cannot obtain TLS certificate without mnemonic"
679
547
  exit 1
680
548
  fi
681
-
549
+
682
550
  if [ ! -x /usr/local/bin/caddy ]; then
683
- echo "compute-source-env.sh: ERROR - TLS requested but Caddy not found"
684
- echo "ECLOUD_FAIL tls_caddy_missing"
551
+ echo "compute-source-env.sh: ERROR - TLS binary present but Caddy not found"
685
552
  exit 1
686
553
  fi
687
-
554
+
555
+ echo "compute-source-env.sh: Setting up TLS for domain: $domain"
556
+
557
+ # Obtain TLS certificate using ACME
558
+ # Default to http-01, but allow override via ACME_CHALLENGE env var
688
559
  local challenge="\${ACME_CHALLENGE:-http-01}"
560
+
561
+ # Check if we should use staging (for testing)
689
562
  local staging_flag=""
690
563
  if [ "\${ACME_STAGING:-false}" = "true" ]; then
691
564
  staging_flag="-staging"
692
- echo "compute-source-env.sh: Using Let's Encrypt STAGING environment"
693
- fi
694
-
695
- local certs_issued=0
696
-
697
- if [ -n "$platform_host" ]; then
698
- if issue_cert_for "$platform_host" "/run/tls/platform" "$mnemonic" "$challenge" "$staging_flag"; then
699
- certs_issued=$((certs_issued + 1))
700
- else
701
- echo "compute-source-env.sh: ERROR - failed to issue cert for platform host $platform_host"
702
- echo "ECLOUD_FAIL tls_setup"
703
- exit 1
704
- fi
705
- fi
706
-
707
- if [ -n "$user_domain" ]; then
708
- if issue_cert_for "$user_domain" "/run/tls/domain" "$mnemonic" "$challenge" "$staging_flag"; then
709
- certs_issued=$((certs_issued + 1))
710
- else
711
- echo "compute-source-env.sh: ERROR - failed to issue cert for user domain $user_domain"
712
- echo "ECLOUD_FAIL tls_setup"
713
- exit 1
714
- fi
565
+ echo "compute-source-env.sh: Using Let's Encrypt STAGING environment (certificates won't be trusted)"
715
566
  fi
716
-
717
- if [ "$certs_issued" -eq 0 ]; then
718
- echo "compute-source-env.sh: no certs issued, skipping Caddy (app will serve plaintext until DNS is wired)"
719
- return 0
567
+
568
+ echo "compute-source-env.sh: Obtaining TLS certificate using $challenge challenge..."
569
+ # Pass the API URL for certificate persistence
570
+ if ! SSL_CERT_FILE=/usr/local/share/eigenx-ca-certs.crt \\
571
+ MNEMONIC="$mnemonic" DOMAIN="$domain" API_URL="{{userAPIURL}}" /usr/local/bin/tls-keygen \\
572
+ -challenge "$challenge" \\
573
+ $staging_flag; then
574
+ echo "compute-source-env.sh: ERROR - Failed to obtain TLS certificate"
575
+ echo "compute-source-env.sh: Certificate issuance failed for $domain"
576
+ exit 1
720
577
  fi
721
-
722
- # Caddy's validate step checks that every \`tls <cert> <key>\` file
723
- # exists, even on site blocks bound to dormant placeholder
724
- # hostnames. The default Caddyfile declares both a platform site
725
- # and a user-domain site; when only one is configured, the other
726
- # block's cert paths are never populated and validate fails with
727
- # "Invalid Caddyfile". Point the unused block at the issued
728
- # block's cert files so validate passes \u2014 the dormant block can't
729
- # receive real traffic (its hostname falls back to
730
- # localhost.{platform,user}.invalid, which Caddy routes by SNI and
731
- # never matches public traffic), so the symlink is never actually
732
- # presented. Skipped when a user-supplied Caddyfile is in use,
733
- # since we don't know what cert paths it references.
734
- if [ -d /run/tls/platform ] && [ ! -e /run/tls/domain/fullchain.pem ]; then
735
- mkdir -p /run/tls/domain
736
- ln -sf /run/tls/platform/fullchain.pem /run/tls/domain/fullchain.pem
737
- ln -sf /run/tls/platform/privkey.pem /run/tls/domain/privkey.pem
738
- elif [ -d /run/tls/domain ] && [ ! -e /run/tls/platform/fullchain.pem ]; then
739
- mkdir -p /run/tls/platform
740
- ln -sf /run/tls/domain/fullchain.pem /run/tls/platform/fullchain.pem
741
- ln -sf /run/tls/domain/privkey.pem /run/tls/platform/privkey.pem
578
+
579
+ echo "compute-source-env.sh: TLS certificate obtained successfully"
580
+
581
+ # Validate Caddyfile before starting
582
+ if ! /usr/local/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile 2>/dev/null; then
583
+ echo "compute-source-env.sh: ERROR - Invalid Caddyfile"
584
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
585
+ exit 1
742
586
  fi
743
-
744
- # Validate Caddyfile before starting. Don't redirect stderr \u2014 when
745
- # validate fails, Caddy's diagnostic is the only signal that lands
746
- # in ReadinessError.SerialTail, so silencing it leaves operators
747
- # staring at a bare "tls_invalid_caddyfile" with no detail.
748
- if [ -f /etc/caddy/Caddyfile ]; then
749
- if ! /usr/local/bin/caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile; then
750
- echo "compute-source-env.sh: ERROR - Invalid Caddyfile"
751
- echo "ECLOUD_FAIL tls_invalid_caddyfile"
587
+
588
+ # Start Caddy in background
589
+ echo "compute-source-env.sh: Starting Caddy reverse proxy..."
590
+
591
+ # Check if Caddy logs should be enabled
592
+ if [ "\${ENABLE_CADDY_LOGS:-false}" = "true" ]; then
593
+ if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1; then
594
+ echo "compute-source-env.sh: ERROR - Failed to start Caddy"
595
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
752
596
  exit 1
753
597
  fi
754
-
755
- echo "compute-source-env.sh: Starting Caddy reverse proxy..."
756
- if [ "\${ENABLE_CADDY_LOGS:-false}" = "true" ]; then
757
- if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1; then
758
- echo "compute-source-env.sh: ERROR - Failed to start Caddy"
759
- echo "ECLOUD_FAIL tls_caddy_start"
760
- exit 1
761
- fi
762
- else
763
- if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
764
- echo "compute-source-env.sh: ERROR - Failed to start Caddy"
765
- echo "ECLOUD_FAIL tls_caddy_start"
766
- exit 1
767
- fi
768
- fi
769
-
770
- sleep 2
771
- echo "compute-source-env.sh: Caddy started successfully"
772
598
  else
773
- echo "compute-source-env.sh: No Caddyfile found, skipping Caddy"
599
+ # Redirect Caddy output to /dev/null to silence logs
600
+ if ! /usr/local/bin/caddy start --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null 2>&1; then
601
+ echo "compute-source-env.sh: ERROR - Failed to start Caddy"
602
+ echo "compute-source-env.sh: TLS was requested (DOMAIN=$domain) but setup failed"
603
+ exit 1
604
+ fi
774
605
  fi
775
-
606
+
607
+ # Give Caddy a moment to fully initialize
608
+ sleep 2
609
+ echo "compute-source-env.sh: Caddy started successfully"
776
610
  return 0
777
611
  }
778
612
 
@@ -783,233 +617,15 @@ setup_tls
783
617
  export KMS_SERVER_URL="{{kmsServerURL}}"
784
618
  export KMS_PUBLIC_KEY="$(cat /usr/local/bin/kms-signing-public-key.pem)"
785
619
 
786
- # \u2500\u2500 Prewarm-detach: wait for PD if expected \u2500\u2500
787
- # Orchestrator sets ECLOUD_PD_EXPECTED=1 on apps using StorageBackend=pd.
788
- # When the prewarm path is used, the new VM boots WITHOUT the disk; we
789
- # signal awaiting-userdata and poll until the disk is attached.
790
- USERDATA_MOUNT="/mnt/disks/userdata"
791
- USERDATA_DEV="/dev/disk/by-id/google-persistent_storage_1"
792
-
793
- wait_for_userdata() {
794
- if [ "\${ECLOUD_PD_EXPECTED:-0}" != "1" ]; then
795
- return 0
796
- fi
797
- if mountpoint -q "$USERDATA_MOUNT" 2>/dev/null; then
798
- echo "compute-source-env.sh: userdata already mounted at $USERDATA_MOUNT"
799
- return 0
800
- fi
801
- # Refuse to proceed if the tools we need for safe first-attach
802
- # detection are missing. Without blkid we cannot tell an empty new
803
- # disk from an already-formatted one \u2014 running mkfs.ext4 on the
804
- # latter would destroy data.
805
- if ! command -v blkid >/dev/null 2>&1; then
806
- echo "ECLOUD_FAIL pd_tools_missing"
807
- exit 1
808
- fi
809
- echo "ECLOUD_AWAITING_USERDATA"
810
- echo "compute-source-env.sh: waiting for PD at $USERDATA_DEV..."
811
- # Poll for up to 10 minutes (120 * 5s). The orchestrator's overall
812
- # attach timeout is shorter; the ceiling here just bounds the wait
813
- # for manual / diagnostic scenarios.
814
- local i=0
815
- local mount_failures=0
816
- while [ "$i" -lt 120 ]; do
817
- if [ -e "$USERDATA_DEV" ]; then
818
- mkdir -p "$USERDATA_MOUNT"
819
- if mount -o noatime "$USERDATA_DEV" "$USERDATA_MOUNT" 2>/dev/null; then
820
- echo "compute-source-env.sh: PD mounted at $USERDATA_MOUNT"
821
- return 0
822
- fi
823
- # Disk present but mount failed. Check whether it has a
824
- # recognized filesystem. \`blkid -s TYPE -o value\` prints the
825
- # FS type (empty if none). We only mkfs when there is
826
- # demonstrably NO filesystem \u2014 never on the basis of blkid
827
- # returning non-zero alone, which could mean "blkid missing"
828
- # or "device busy".
829
- local fstype
830
- fstype=$(blkid -s TYPE -o value "$USERDATA_DEV" 2>/dev/null)
831
- if [ -z "$fstype" ]; then
832
- echo "compute-source-env.sh: formatting $USERDATA_DEV (first attach)"
833
- mkfs.ext4 -F -L eclouddata "$USERDATA_DEV" >/dev/null 2>&1 || {
834
- echo "ECLOUD_FAIL pd_mkfs_failed"
835
- exit 1
836
- }
837
- mount -o noatime "$USERDATA_DEV" "$USERDATA_MOUNT" || {
838
- echo "ECLOUD_FAIL pd_mount_after_format_failed"
839
- exit 1
840
- }
841
- return 0
842
- fi
843
- # Disk has a filesystem but mount still failed. Give it a
844
- # few retries to cover transient cases (device busy, udev
845
- # still settling), but don't pretend this is an attach
846
- # timeout if it persists.
847
- mount_failures=$((mount_failures + 1))
848
- if [ "$mount_failures" -ge 6 ]; then
849
- echo "ECLOUD_FAIL pd_mount_failed"
850
- exit 1
851
- fi
852
- else
853
- # Device disappeared (e.g. udev re-enumeration between
854
- # attach and mount). Reset the consecutive-failure counter
855
- # so only true back-to-back mount failures trip
856
- # pd_mount_failed; a device blip should not steal retries.
857
- mount_failures=0
858
- fi
859
- i=$((i + 1))
860
- sleep 5
861
- done
862
- echo "ECLOUD_FAIL pd_attach_timeout"
863
- exit 1
864
- }
865
-
866
- wait_for_userdata
867
-
868
- # \u2500\u2500 Prewarm-detach: install SIGTERM handler for graceful drain \u2500\u2500
869
- # Orchestrator signals drain by setting the instance metadata key
870
- # ECLOUD_DRAIN_REQUESTED=1, which a host-level agent translates into
871
- # SIGTERM on PID 1. On SIGTERM we:
872
- # 1. Forward to the child (wakes the user's app for graceful exit)
873
- # 2. Wait for child exit
874
- # 3. Sync + unmount the PD
875
- # 4. Emit ECLOUD_DETACHED so the orchestrator can proceed to detach
876
- CHILD_PID=""
877
- _DRAIN_IN_PROGRESS=0
878
-
879
- drain_handler() {
880
- # Guard against re-entry if SIGTERM arrives twice (e.g. both the
881
- # drain_watcher and an external signal fire in quick succession).
882
- if [ "$_DRAIN_IN_PROGRESS" = "1" ]; then
883
- return 0
884
- fi
885
- _DRAIN_IN_PROGRESS=1
886
- echo "compute-source-env.sh: received drain signal, forwarding to child pgid=$CHILD_PID"
887
- if [ -n "$CHILD_PID" ]; then
888
- # Send to the process group so intermediate wrappers (su, sh -c,
889
- # etc.) don't swallow the signal. The leading \`-\` targets the
890
- # pgid, which equals the direct child's pid for a shell-backgrounded
891
- # process. Fall back to the pid alone if pgid signaling fails
892
- # (e.g. kernel older than 3.9 or PID namespace edge cases).
893
- kill -TERM -"$CHILD_PID" 2>/dev/null || kill -TERM "$CHILD_PID" 2>/dev/null || true
894
- # Give the app up to 30s to exit cleanly.
895
- local i=0
896
- while [ "$i" -lt 30 ] && kill -0 "$CHILD_PID" 2>/dev/null; do
897
- i=$((i + 1))
898
- sleep 1
899
- done
900
- if kill -0 "$CHILD_PID" 2>/dev/null; then
901
- echo "compute-source-env.sh: child did not exit in 30s, sending SIGKILL"
902
- kill -KILL -"$CHILD_PID" 2>/dev/null || kill -KILL "$CHILD_PID" 2>/dev/null || true
903
- # Reap the process so its in-flight I/O is flushed to the
904
- # filesystem before we sync + unmount. SIGKILL schedules
905
- # death; wait guarantees it's complete.
906
- wait "$CHILD_PID" 2>/dev/null || true
907
- fi
908
- fi
909
- if [ "\${ECLOUD_PD_EXPECTED:-0}" = "1" ] && mountpoint -q "$USERDATA_MOUNT" 2>/dev/null; then
910
- sync
911
- if umount "$USERDATA_MOUNT" 2>/dev/null; then
912
- echo "compute-source-env.sh: unmounted $USERDATA_MOUNT cleanly"
913
- else
914
- # Force lazy unmount as last resort \u2014 orchestrator still needs
915
- # the DETACHED signal to proceed.
916
- umount -l "$USERDATA_MOUNT" 2>/dev/null || true
917
- echo "compute-source-env.sh: WARNING - used lazy unmount on $USERDATA_MOUNT"
918
- fi
919
- # ECLOUD_DETACHED is strictly a PD-lifecycle signal. Only emit
920
- # it when we actually had a PD mount in play, so serial-log
921
- # parsers and alerting for non-PD apps don't see spurious
922
- # lifecycle markers on routine container SIGTERM.
923
- echo "ECLOUD_DETACHED"
924
- fi
925
- # Always exit 0: drain is a managed shutdown and the orchestrator
926
- # waits on ECLOUD_DETACHED, not the container exit code. Forwarding
927
- # the child's exit status here would make a crash-during-drain look
928
- # like a drain failure to whatever reads the container exit code.
929
- exit 0
930
- }
931
- trap drain_handler TERM
932
-
933
- # \u2500\u2500 Prewarm-detach: background drain watcher \u2500\u2500
934
- # Container metadata delivery in Confidential Space is limited, so we
935
- # poll the instance metadata server for ECLOUD_DRAIN_REQUESTED and
936
- # raise SIGTERM on ourselves when it flips to "1".
937
- #
938
- # Try wget first (present in most Alpine bases), fall back to curl.
939
- # If neither is present, drain watcher is disabled \u2014 the orchestrator
940
- # will hit its drain timeout and fail the upgrade explicitly, which is
941
- # the correct behavior (we cannot silently ignore a drain request).
942
- _fetch_drain_flag() {
943
- local url="http://metadata.google.internal/computeMetadata/v1/instance/attributes/ECLOUD_DRAIN_REQUESTED"
944
- if command -v wget >/dev/null 2>&1; then
945
- wget -q --tries=1 --timeout=2 --header='Metadata-Flavor: Google' -O - "$url" 2>/dev/null
946
- elif command -v curl >/dev/null 2>&1; then
947
- curl -sf --max-time 2 -H 'Metadata-Flavor: Google' "$url" 2>/dev/null
948
- else
949
- return 2
950
- fi
951
- }
952
-
953
- drain_watcher() {
954
- # Preflight: confirm we have an HTTP client
955
- if ! _fetch_drain_flag >/dev/null 2>&1; then
956
- # Either no http client available OR metadata server not
957
- # responding yet. If no client, give up and log; otherwise the
958
- # loop below will retry.
959
- if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then
960
- echo "compute-source-env.sh: WARNING - no wget/curl; drain_watcher disabled"
961
- return 0
962
- fi
963
- fi
964
- while true; do
965
- local v
966
- v=$(_fetch_drain_flag || true)
967
- if [ "$v" = "1" ]; then
968
- echo "compute-source-env.sh: drain_watcher saw ECLOUD_DRAIN_REQUESTED=1, signaling PID 1"
969
- # The CS launcher runs this script directly as PID 1, so
970
- # kill -TERM 1 delivers SIGTERM to the shell that installed
971
- # the drain_handler trap. If the launch mechanism ever
972
- # wraps this script in another process, this assumption
973
- # breaks and drain will silently no-op \u2014 audit here.
974
- kill -TERM 1 2>/dev/null || true
975
- return 0
976
- fi
977
- sleep 2
978
- done
979
- }
980
-
981
- if [ "\${ECLOUD_PD_EXPECTED:-0}" = "1" ]; then
982
- # Assumption: the orchestrator only flips ECLOUD_DRAIN_REQUESTED=1
983
- # after observing ECLOUD_AWAITING_USERDATA (old VM) or
984
- # ECLOUD_READY (new VM), so CHILD_PID is always set by the time
985
- # drain_handler fires. If drain somehow arrived in the tiny window
986
- # between this watcher spawn and CHILD_PID assignment below,
987
- # drain_handler would skip the child-kill branch and still emit
988
- # ECLOUD_DETACHED \u2014 harmless because there's nothing to drain yet.
989
- if [ -x /usr/local/bin/ecloud-drain-watcher ]; then
990
- /usr/local/bin/ecloud-drain-watcher &
991
- else
992
- drain_watcher &
993
- fi
994
- fi
995
-
996
620
  echo "compute-source-env.sh: Environment sourced."
997
- echo "ECLOUD_READY runtime_bootstrapped"
998
621
 
999
622
  # Drop privileges to original user for the application command
1000
623
  if [ -n "$__ECLOUD_ORIGINAL_USER" ] && [ "$(id -u)" = "0" ]; then
1001
624
  echo "compute-source-env.sh: Dropping privileges to user: $__ECLOUD_ORIGINAL_USER"
1002
- # Must background the child so our trap can fire; exec replaces PID 1.
1003
- su -s /bin/sh "$__ECLOUD_ORIGINAL_USER" -c 'exec "$@"' -- sh "$@" &
1004
- CHILD_PID=$!
1005
- wait "$CHILD_PID"
1006
- exit $?
625
+ exec su -s /bin/sh "$__ECLOUD_ORIGINAL_USER" -c 'exec "$@"' -- sh "$@"
1007
626
  fi
1008
627
 
1009
- "$@" &
1010
- CHILD_PID=$!
1011
- wait "$CHILD_PID"
1012
- exit $?
628
+ exec "$@"
1013
629
  `;
1014
630
 
1015
631
  // src/client/common/templates/scriptTemplate.ts
@@ -1018,14 +634,6 @@ function processScriptTemplate(data) {
1018
634
  return template(data);
1019
635
  }
1020
636
 
1021
- // src/client/common/templates/Caddyfile.default.tmpl
1022
- 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';
1023
-
1024
- // src/client/common/templates/caddyfileTemplate.ts
1025
- function getDefaultCaddyfile() {
1026
- return Caddyfile_default_default;
1027
- }
1028
-
1029
637
  // keys/mainnet-alpha/prod/kms-encryption-public-key.pem
1030
638
  var kms_encryption_public_key_default = "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0kHU86k17ofCIGcJKDcf\nAFurFhSLeWmOL0bwWLCeVnTPG0MMHtJOq+woE0XXSWw6lzm+jzavBBTwKde1dgal\nAp91vULAZFMUpiUdd2dNUVtvU89qW0Pgf1Eu5FDj7BkY/SnyECbWJM4ga0BmpiGy\nnQwLNN9mMGhjVoVLn2zwEGZ7JzS9Nz11EZKO/k/9DcO6LaoIFmKuvVf3jl6lvZg8\naeA0LoZXjkycHlRUt/kfKwZnhakUaYHP1ksV7ZNmolS5GYDTSKGB2KPPNR1s4/Xu\nu8zeEFC8HuGRU8XuuBeaAunitnGhbNVREUNJGff6HZOGB6CIFNXjbQETeZ3p5uro\n0v+hd1QqQYBv7+DEaMCmGnJNGAyIMr2mn4vr7wGsIj0HonlSHmQ8rmdUhL2ocNTc\nLhKgZiZmBuDpSbFW/r53R2G7CHcqaqGeUBnT54QCH4zsYKw0/4dOtwFxQpTyBf9/\n+k+KaWEJYKkx9d9OzKGyAvzrTDVOFoajddiJ6LPvRlMdOUQr3hl4IAC0/nh9lhHq\nD0R+i5WAU96TkdAe7B7iTGH2D22k0KUPR6Q9W3aF353SLxQAMPNrgG4QQufAdRJn\nAF+8ntun5TkTqjTWRSwAsUJZ1z4wb96DympWJbDi0OciJRZ3Fz3j9+amC43yCHGg\naaEMjdt35ewbztUSc04F10MCAwEAAQ==\n-----END PUBLIC KEY-----";
1031
639
 
@@ -1200,9 +808,15 @@ async function layerLocalImage(options, logger) {
1200
808
  const imageConfig = await extractImageConfig(docker, sourceImageRef);
1201
809
  const originalCmd = imageConfig.cmd.length > 0 ? imageConfig.cmd : imageConfig.entrypoint;
1202
810
  const originalUser = imageConfig.user;
1203
- const includeTLS = true;
1204
- const drainWatcherSource = findBinary("ecloud-drain-watcher-linux-amd64");
1205
- const includeDrainWatcher = fs.existsSync(drainWatcherSource);
811
+ let includeTLS = false;
812
+ if (envFilePath && fs.existsSync(envFilePath)) {
813
+ const envContent = fs.readFileSync(envFilePath, "utf-8");
814
+ const domainMatch = envContent.match(/^DOMAIN=(.+)$/m);
815
+ if (domainMatch && domainMatch[1] && domainMatch[1] !== "localhost") {
816
+ includeTLS = true;
817
+ logger.debug(`Found DOMAIN=${domainMatch[1]} in ${envFilePath}, including TLS components`);
818
+ }
819
+ }
1206
820
  const layeredDockerfileContent = processDockerfileTemplate({
1207
821
  baseImage: sourceImageRef,
1208
822
  originalCmd: JSON.stringify(originalCmd),
@@ -1210,9 +824,8 @@ async function layerLocalImage(options, logger) {
1210
824
  logRedirect,
1211
825
  resourceUsageAllow,
1212
826
  includeTLS,
1213
- ecloudCLIVersion: "0.1.0",
827
+ ecloudCLIVersion: "0.1.0"
1214
828
  // TODO: Get from package.json
1215
- includeDrainWatcher
1216
829
  });
1217
830
  const scriptContent = processScriptTemplate({
1218
831
  kmsServerURL: environmentConfig.kmsServerURL,
@@ -1222,8 +835,7 @@ async function layerLocalImage(options, logger) {
1222
835
  environmentConfig,
1223
836
  layeredDockerfileContent,
1224
837
  scriptContent,
1225
- includeTLS,
1226
- includeDrainWatcher ? drainWatcherSource : void 0
838
+ includeTLS
1227
839
  // logger
1228
840
  );
1229
841
  try {
@@ -1238,7 +850,7 @@ async function layerLocalImage(options, logger) {
1238
850
  fs.rmSync(tempDir, { recursive: true, force: true });
1239
851
  }
1240
852
  }
1241
- async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileContent, scriptContent, includeTLS, drainWatcherSource) {
853
+ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileContent, scriptContent, includeTLS) {
1242
854
  const tempDir = fs.mkdtempSync(path2.join(os.tmpdir(), LAYERED_BUILD_DIR_PREFIX));
1243
855
  try {
1244
856
  const layeredDockerfilePath = path2.join(tempDir, LAYERED_DOCKERFILE_NAME);
@@ -1262,11 +874,6 @@ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileCo
1262
874
  }
1263
875
  fs.copyFileSync(kmsClientSource, kmsClientPath);
1264
876
  fs.chmodSync(kmsClientPath, 493);
1265
- if (drainWatcherSource && fs.existsSync(drainWatcherSource)) {
1266
- const drainWatcherPath = path2.join(tempDir, DRAIN_WATCHER_BINARY_NAME);
1267
- fs.copyFileSync(drainWatcherSource, drainWatcherPath);
1268
- fs.chmodSync(drainWatcherPath, 493);
1269
- }
1270
877
  if (includeTLS) {
1271
878
  const tlsKeygenPath = path2.join(tempDir, TLS_KEYGEN_BINARY_NAME);
1272
879
  const tlsKeygenSource = findBinary("tls-keygen-linux-amd64");
@@ -1277,13 +884,15 @@ async function setupLayeredBuildDirectory(environmentConfig, layeredDockerfileCo
1277
884
  }
1278
885
  fs.copyFileSync(tlsKeygenSource, tlsKeygenPath);
1279
886
  fs.chmodSync(tlsKeygenPath, 493);
1280
- const userCaddyfilePath = path2.join(process.cwd(), CADDYFILE_NAME);
1281
- const destCaddyfilePath = path2.join(tempDir, CADDYFILE_NAME);
1282
- if (fs.existsSync(userCaddyfilePath)) {
1283
- fs.copyFileSync(userCaddyfilePath, destCaddyfilePath);
1284
- fs.chmodSync(destCaddyfilePath, 420);
887
+ const caddyfilePath = path2.join(process.cwd(), CADDYFILE_NAME);
888
+ if (fs.existsSync(caddyfilePath)) {
889
+ const caddyfileContent = fs.readFileSync(caddyfilePath);
890
+ const destCaddyfilePath = path2.join(tempDir, CADDYFILE_NAME);
891
+ fs.writeFileSync(destCaddyfilePath, caddyfileContent, { mode: 420 });
1285
892
  } else {
1286
- fs.writeFileSync(destCaddyfilePath, getDefaultCaddyfile(), { mode: 420 });
893
+ throw new Error(
894
+ "TLS is enabled (DOMAIN is set) but Caddyfile not found. Run configure TLS to set up TLS configuration"
895
+ );
1287
896
  }
1288
897
  }
1289
898
  return tempDir;
@@ -1351,16 +960,7 @@ async function extractDigestFromSinglePlatform(manifest, imageRef) {
1351
960
  architecture: inspectData[0].Architecture
1352
961
  } : null;
1353
962
  if (!config) {
1354
- if (manifest.config?.digest) {
1355
- const digest = hexStringToBytes32(manifest.config.digest);
1356
- const registry = extractRegistryName(imageRef);
1357
- return {
1358
- digest,
1359
- registry,
1360
- platform: DOCKER_PLATFORM
1361
- };
1362
- }
1363
- throw new Error(`Could not determine platform for ${imageRef}`);
963
+ throw createPlatformErrorMessage(imageRef, ["unknown (could not determine architecture)"]);
1364
964
  }
1365
965
  const platform2 = `${config.os}/${config.architecture}`;
1366
966
  if (platform2 === DOCKER_PLATFORM) {
@@ -1432,14 +1032,11 @@ Image: ${imageRef}
1432
1032
  Found platform(s): ${platforms.join(", ")}
1433
1033
  Required platform: ${DOCKER_PLATFORM}
1434
1034
 
1435
- To fix this issue:
1436
- 1. Manual fix:
1437
- a. Rebuild your image with the correct platform:
1438
- docker build --platform ${DOCKER_PLATFORM} -t ${imageRef} .
1439
- b. Push the rebuilt image to your remote registry:
1440
- docker push ${imageRef}
1441
-
1442
- 2. Or use the SDK to build with the correct platform automatically.`;
1035
+ To fix, either:
1036
+ 1. Rebuild the image for ${DOCKER_PLATFORM} and push it:
1037
+ docker buildx build --platform ${DOCKER_PLATFORM} -t ${imageRef} --push .
1038
+ 2. Or use a verifiable build (--verifiable --repo <repo> --commit <sha>), which
1039
+ builds server-side and needs no local Docker.`;
1443
1040
  return new Error(errorMsg);
1444
1041
  }
1445
1042
 
@@ -1544,6 +1141,8 @@ async function prepareRelease(options, logger) {
1544
1141
  logger.info(`Waiting ${REGISTRY_PROPAGATION_WAIT_SECONDS} seconds for registry propagation...`);
1545
1142
  await new Promise((resolve2) => setTimeout(resolve2, REGISTRY_PROPAGATION_WAIT_SECONDS * 1e3));
1546
1143
  } else {
1144
+ logger.info("Verifying image platform (linux/amd64)...");
1145
+ await getImageDigestAndName(imageRef);
1547
1146
  logger.info("Checking if image needs layering...");
1548
1147
  finalImageRef = await layerRemoteImageIfNeeded(
1549
1148
  {
@@ -1702,6 +1301,13 @@ function extractRegistryNameNoDocker(imageRef) {
1702
1301
  import { encodeFunctionData, encodeAbiParameters, decodeErrorResult } from "viem";
1703
1302
 
1704
1303
  // src/client/common/types/index.ts
1304
+ var EMPTY_CONTAINER_POLICY = {
1305
+ args: [],
1306
+ cmdOverride: [],
1307
+ env: [],
1308
+ envOverride: [],
1309
+ restartPolicy: ""
1310
+ };
1705
1311
  var noopLogger = {
1706
1312
  debug: () => {
1707
1313
  },
@@ -2942,49 +2548,1594 @@ async function executeBatch(options, logger = noopLogger) {
2942
2548
  } else {
2943
2549
  revertReason = callError.message || "Unknown reason";
2944
2550
  }
2945
- }
2946
- throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2551
+ }
2552
+ throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2553
+ }
2554
+ return hash;
2555
+ }
2556
+
2557
+ // src/client/common/contract/caller.ts
2558
+ import { encodeFunctionData as encodeFunctionData2, decodeErrorResult as decodeErrorResult2, bytesToHex } from "viem";
2559
+
2560
+ // src/client/common/utils/helpers.ts
2561
+ import { extractChain, createPublicClient, createWalletClient, http, fallback } from "viem";
2562
+ import { sepolia as sepolia2 } from "viem/chains";
2563
+ import { privateKeyToAccount } from "viem/accounts";
2564
+ function getChainFromID(chainID, fallback2 = sepolia2) {
2565
+ const id = Number(chainID);
2566
+ return extractChain({ chains: SUPPORTED_CHAINS, id }) || fallback2;
2567
+ }
2568
+ function createClients(options) {
2569
+ const { privateKey, rpcUrl, chainId } = options;
2570
+ const privateKeyHex = addHexPrefix(privateKey);
2571
+ const account = privateKeyToAccount(privateKeyHex);
2572
+ const chain = getChainFromID(chainId);
2573
+ const transport = typeof rpcUrl === "string" ? http(rpcUrl) : fallback(rpcUrl.map((url) => http(url)));
2574
+ const publicClient = createPublicClient({
2575
+ chain,
2576
+ transport
2577
+ });
2578
+ const walletClient = createWalletClient({
2579
+ account,
2580
+ chain,
2581
+ transport
2582
+ });
2583
+ return { walletClient, publicClient };
2584
+ }
2585
+ function addHexPrefix(value) {
2586
+ return value.startsWith("0x") ? value : `0x${value}`;
2587
+ }
2588
+ function stripHexPrefix(value) {
2589
+ return value.startsWith("0x") ? value.slice(2) : value;
2590
+ }
2591
+
2592
+ // src/client/common/abis/AppController.json
2593
+ var AppController_default = [
2594
+ {
2595
+ type: "constructor",
2596
+ inputs: [
2597
+ {
2598
+ name: "_version",
2599
+ type: "string",
2600
+ internalType: "string"
2601
+ },
2602
+ {
2603
+ name: "_permissionController",
2604
+ type: "address",
2605
+ internalType: "contractIPermissionController"
2606
+ },
2607
+ {
2608
+ name: "_releaseManager",
2609
+ type: "address",
2610
+ internalType: "contractIReleaseManager"
2611
+ },
2612
+ {
2613
+ name: "_computeAVSRegistrar",
2614
+ type: "address",
2615
+ internalType: "contractIComputeAVSRegistrar"
2616
+ },
2617
+ {
2618
+ name: "_computeOperator",
2619
+ type: "address",
2620
+ internalType: "contractIComputeOperator"
2621
+ },
2622
+ {
2623
+ name: "_appBeacon",
2624
+ type: "address",
2625
+ internalType: "contractIBeacon"
2626
+ }
2627
+ ],
2628
+ stateMutability: "nonpayable"
2629
+ },
2630
+ {
2631
+ type: "function",
2632
+ name: "API_PERMISSION_TYPEHASH",
2633
+ inputs: [],
2634
+ outputs: [
2635
+ {
2636
+ name: "",
2637
+ type: "bytes32",
2638
+ internalType: "bytes32"
2639
+ }
2640
+ ],
2641
+ stateMutability: "view"
2642
+ },
2643
+ {
2644
+ type: "function",
2645
+ name: "appBeacon",
2646
+ inputs: [],
2647
+ outputs: [
2648
+ {
2649
+ name: "",
2650
+ type: "address",
2651
+ internalType: "contractIBeacon"
2652
+ }
2653
+ ],
2654
+ stateMutability: "view"
2655
+ },
2656
+ {
2657
+ type: "function",
2658
+ name: "calculateApiPermissionDigestHash",
2659
+ inputs: [
2660
+ {
2661
+ name: "permission",
2662
+ type: "bytes4",
2663
+ internalType: "bytes4"
2664
+ },
2665
+ {
2666
+ name: "expiry",
2667
+ type: "uint256",
2668
+ internalType: "uint256"
2669
+ }
2670
+ ],
2671
+ outputs: [
2672
+ {
2673
+ name: "",
2674
+ type: "bytes32",
2675
+ internalType: "bytes32"
2676
+ }
2677
+ ],
2678
+ stateMutability: "view"
2679
+ },
2680
+ {
2681
+ type: "function",
2682
+ name: "calculateAppId",
2683
+ inputs: [
2684
+ {
2685
+ name: "deployer",
2686
+ type: "address",
2687
+ internalType: "address"
2688
+ },
2689
+ {
2690
+ name: "salt",
2691
+ type: "bytes32",
2692
+ internalType: "bytes32"
2693
+ }
2694
+ ],
2695
+ outputs: [
2696
+ {
2697
+ name: "",
2698
+ type: "address",
2699
+ internalType: "contractIApp"
2700
+ }
2701
+ ],
2702
+ stateMutability: "view"
2703
+ },
2704
+ {
2705
+ type: "function",
2706
+ name: "computeAVSRegistrar",
2707
+ inputs: [],
2708
+ outputs: [
2709
+ {
2710
+ name: "",
2711
+ type: "address",
2712
+ internalType: "contractIComputeAVSRegistrar"
2713
+ }
2714
+ ],
2715
+ stateMutability: "view"
2716
+ },
2717
+ {
2718
+ type: "function",
2719
+ name: "computeOperator",
2720
+ inputs: [],
2721
+ outputs: [
2722
+ {
2723
+ name: "",
2724
+ type: "address",
2725
+ internalType: "contractIComputeOperator"
2726
+ }
2727
+ ],
2728
+ stateMutability: "view"
2729
+ },
2730
+ {
2731
+ type: "function",
2732
+ name: "confirmUpgrade",
2733
+ inputs: [
2734
+ {
2735
+ name: "app",
2736
+ type: "address",
2737
+ internalType: "contractIApp"
2738
+ }
2739
+ ],
2740
+ outputs: [],
2741
+ stateMutability: "nonpayable"
2742
+ },
2743
+ {
2744
+ type: "function",
2745
+ name: "createApp",
2746
+ inputs: [
2747
+ {
2748
+ name: "salt",
2749
+ type: "bytes32",
2750
+ internalType: "bytes32"
2751
+ },
2752
+ {
2753
+ name: "release",
2754
+ type: "tuple",
2755
+ internalType: "structIAppController.Release",
2756
+ components: [
2757
+ {
2758
+ name: "rmsRelease",
2759
+ type: "tuple",
2760
+ internalType: "structIReleaseManagerTypes.Release",
2761
+ components: [
2762
+ {
2763
+ name: "artifacts",
2764
+ type: "tuple[]",
2765
+ internalType: "structIReleaseManagerTypes.Artifact[]",
2766
+ components: [
2767
+ {
2768
+ name: "digest",
2769
+ type: "bytes32",
2770
+ internalType: "bytes32"
2771
+ },
2772
+ {
2773
+ name: "registry",
2774
+ type: "string",
2775
+ internalType: "string"
2776
+ }
2777
+ ]
2778
+ },
2779
+ {
2780
+ name: "upgradeByTime",
2781
+ type: "uint32",
2782
+ internalType: "uint32"
2783
+ }
2784
+ ]
2785
+ },
2786
+ {
2787
+ name: "publicEnv",
2788
+ type: "bytes",
2789
+ internalType: "bytes"
2790
+ },
2791
+ {
2792
+ name: "encryptedEnv",
2793
+ type: "bytes",
2794
+ internalType: "bytes"
2795
+ },
2796
+ {
2797
+ name: "containerPolicy",
2798
+ type: "tuple",
2799
+ internalType: "structIAppController.ContainerPolicy",
2800
+ components: [
2801
+ {
2802
+ name: "args",
2803
+ type: "string[]",
2804
+ internalType: "string[]"
2805
+ },
2806
+ {
2807
+ name: "cmdOverride",
2808
+ type: "string[]",
2809
+ internalType: "string[]"
2810
+ },
2811
+ {
2812
+ name: "env",
2813
+ type: "tuple[]",
2814
+ internalType: "structIAppController.EnvVar[]",
2815
+ components: [
2816
+ {
2817
+ name: "key",
2818
+ type: "string",
2819
+ internalType: "string"
2820
+ },
2821
+ {
2822
+ name: "value",
2823
+ type: "string",
2824
+ internalType: "string"
2825
+ }
2826
+ ]
2827
+ },
2828
+ {
2829
+ name: "envOverride",
2830
+ type: "tuple[]",
2831
+ internalType: "structIAppController.EnvVar[]",
2832
+ components: [
2833
+ {
2834
+ name: "key",
2835
+ type: "string",
2836
+ internalType: "string"
2837
+ },
2838
+ {
2839
+ name: "value",
2840
+ type: "string",
2841
+ internalType: "string"
2842
+ }
2843
+ ]
2844
+ },
2845
+ {
2846
+ name: "restartPolicy",
2847
+ type: "string",
2848
+ internalType: "string"
2849
+ }
2850
+ ]
2851
+ }
2852
+ ]
2853
+ }
2854
+ ],
2855
+ outputs: [
2856
+ {
2857
+ name: "app",
2858
+ type: "address",
2859
+ internalType: "contractIApp"
2860
+ }
2861
+ ],
2862
+ stateMutability: "nonpayable"
2863
+ },
2864
+ {
2865
+ type: "function",
2866
+ name: "createAppWithIsolatedBilling",
2867
+ inputs: [
2868
+ {
2869
+ name: "salt",
2870
+ type: "bytes32",
2871
+ internalType: "bytes32"
2872
+ },
2873
+ {
2874
+ name: "release",
2875
+ type: "tuple",
2876
+ internalType: "structIAppController.Release",
2877
+ components: [
2878
+ {
2879
+ name: "rmsRelease",
2880
+ type: "tuple",
2881
+ internalType: "structIReleaseManagerTypes.Release",
2882
+ components: [
2883
+ {
2884
+ name: "artifacts",
2885
+ type: "tuple[]",
2886
+ internalType: "structIReleaseManagerTypes.Artifact[]",
2887
+ components: [
2888
+ {
2889
+ name: "digest",
2890
+ type: "bytes32",
2891
+ internalType: "bytes32"
2892
+ },
2893
+ {
2894
+ name: "registry",
2895
+ type: "string",
2896
+ internalType: "string"
2897
+ }
2898
+ ]
2899
+ },
2900
+ {
2901
+ name: "upgradeByTime",
2902
+ type: "uint32",
2903
+ internalType: "uint32"
2904
+ }
2905
+ ]
2906
+ },
2907
+ {
2908
+ name: "publicEnv",
2909
+ type: "bytes",
2910
+ internalType: "bytes"
2911
+ },
2912
+ {
2913
+ name: "encryptedEnv",
2914
+ type: "bytes",
2915
+ internalType: "bytes"
2916
+ },
2917
+ {
2918
+ name: "containerPolicy",
2919
+ type: "tuple",
2920
+ internalType: "structIAppController.ContainerPolicy",
2921
+ components: [
2922
+ {
2923
+ name: "args",
2924
+ type: "string[]",
2925
+ internalType: "string[]"
2926
+ },
2927
+ {
2928
+ name: "cmdOverride",
2929
+ type: "string[]",
2930
+ internalType: "string[]"
2931
+ },
2932
+ {
2933
+ name: "env",
2934
+ type: "tuple[]",
2935
+ internalType: "structIAppController.EnvVar[]",
2936
+ components: [
2937
+ {
2938
+ name: "key",
2939
+ type: "string",
2940
+ internalType: "string"
2941
+ },
2942
+ {
2943
+ name: "value",
2944
+ type: "string",
2945
+ internalType: "string"
2946
+ }
2947
+ ]
2948
+ },
2949
+ {
2950
+ name: "envOverride",
2951
+ type: "tuple[]",
2952
+ internalType: "structIAppController.EnvVar[]",
2953
+ components: [
2954
+ {
2955
+ name: "key",
2956
+ type: "string",
2957
+ internalType: "string"
2958
+ },
2959
+ {
2960
+ name: "value",
2961
+ type: "string",
2962
+ internalType: "string"
2963
+ }
2964
+ ]
2965
+ },
2966
+ {
2967
+ name: "restartPolicy",
2968
+ type: "string",
2969
+ internalType: "string"
2970
+ }
2971
+ ]
2972
+ }
2973
+ ]
2974
+ }
2975
+ ],
2976
+ outputs: [
2977
+ {
2978
+ name: "app",
2979
+ type: "address",
2980
+ internalType: "contractIApp"
2981
+ }
2982
+ ],
2983
+ stateMutability: "nonpayable"
2984
+ },
2985
+ {
2986
+ type: "function",
2987
+ name: "createEmptyApp",
2988
+ inputs: [
2989
+ {
2990
+ name: "salt",
2991
+ type: "bytes32",
2992
+ internalType: "bytes32"
2993
+ }
2994
+ ],
2995
+ outputs: [
2996
+ {
2997
+ name: "app",
2998
+ type: "address",
2999
+ internalType: "contractIApp"
3000
+ }
3001
+ ],
3002
+ stateMutability: "nonpayable"
3003
+ },
3004
+ {
3005
+ type: "function",
3006
+ name: "createEmptyAppWithIsolatedBilling",
3007
+ inputs: [
3008
+ {
3009
+ name: "salt",
3010
+ type: "bytes32",
3011
+ internalType: "bytes32"
3012
+ }
3013
+ ],
3014
+ outputs: [
3015
+ {
3016
+ name: "app",
3017
+ type: "address",
3018
+ internalType: "contractIApp"
3019
+ }
3020
+ ],
3021
+ stateMutability: "nonpayable"
3022
+ },
3023
+ {
3024
+ type: "function",
3025
+ name: "domainSeparator",
3026
+ inputs: [],
3027
+ outputs: [
3028
+ {
3029
+ name: "",
3030
+ type: "bytes32",
3031
+ internalType: "bytes32"
3032
+ }
3033
+ ],
3034
+ stateMutability: "view"
3035
+ },
3036
+ {
3037
+ type: "function",
3038
+ name: "getActiveAppCount",
3039
+ inputs: [
3040
+ {
3041
+ name: "user",
3042
+ type: "address",
3043
+ internalType: "address"
3044
+ }
3045
+ ],
3046
+ outputs: [
3047
+ {
3048
+ name: "",
3049
+ type: "uint32",
3050
+ internalType: "uint32"
3051
+ }
3052
+ ],
3053
+ stateMutability: "view"
3054
+ },
3055
+ {
3056
+ type: "function",
3057
+ name: "getAppCreator",
3058
+ inputs: [
3059
+ {
3060
+ name: "app",
3061
+ type: "address",
3062
+ internalType: "contractIApp"
3063
+ }
3064
+ ],
3065
+ outputs: [
3066
+ {
3067
+ name: "",
3068
+ type: "address",
3069
+ internalType: "address"
3070
+ }
3071
+ ],
3072
+ stateMutability: "view"
3073
+ },
3074
+ {
3075
+ type: "function",
3076
+ name: "getAppLatestReleaseBlockNumber",
3077
+ inputs: [
3078
+ {
3079
+ name: "app",
3080
+ type: "address",
3081
+ internalType: "contractIApp"
3082
+ }
3083
+ ],
3084
+ outputs: [
3085
+ {
3086
+ name: "",
3087
+ type: "uint32",
3088
+ internalType: "uint32"
3089
+ }
3090
+ ],
3091
+ stateMutability: "view"
3092
+ },
3093
+ {
3094
+ type: "function",
3095
+ name: "getAppOperatorSetId",
3096
+ inputs: [
3097
+ {
3098
+ name: "app",
3099
+ type: "address",
3100
+ internalType: "contractIApp"
3101
+ }
3102
+ ],
3103
+ outputs: [
3104
+ {
3105
+ name: "",
3106
+ type: "uint32",
3107
+ internalType: "uint32"
3108
+ }
3109
+ ],
3110
+ stateMutability: "view"
3111
+ },
3112
+ {
3113
+ type: "function",
3114
+ name: "getAppPendingReleaseBlockNumber",
3115
+ inputs: [
3116
+ {
3117
+ name: "app",
3118
+ type: "address",
3119
+ internalType: "contractIApp"
3120
+ }
3121
+ ],
3122
+ outputs: [
3123
+ {
3124
+ name: "",
3125
+ type: "uint32",
3126
+ internalType: "uint32"
3127
+ }
3128
+ ],
3129
+ stateMutability: "view"
3130
+ },
3131
+ {
3132
+ type: "function",
3133
+ name: "getAppStatus",
3134
+ inputs: [
3135
+ {
3136
+ name: "app",
3137
+ type: "address",
3138
+ internalType: "contractIApp"
3139
+ }
3140
+ ],
3141
+ outputs: [
3142
+ {
3143
+ name: "",
3144
+ type: "uint8",
3145
+ internalType: "enumIAppController.AppStatus"
3146
+ }
3147
+ ],
3148
+ stateMutability: "view"
3149
+ },
3150
+ {
3151
+ type: "function",
3152
+ name: "getApps",
3153
+ inputs: [
3154
+ {
3155
+ name: "offset",
3156
+ type: "uint256",
3157
+ internalType: "uint256"
3158
+ },
3159
+ {
3160
+ name: "limit",
3161
+ type: "uint256",
3162
+ internalType: "uint256"
3163
+ }
3164
+ ],
3165
+ outputs: [
3166
+ {
3167
+ name: "apps",
3168
+ type: "address[]",
3169
+ internalType: "contractIApp[]"
3170
+ },
3171
+ {
3172
+ name: "appConfigsMem",
3173
+ type: "tuple[]",
3174
+ internalType: "structIAppController.AppConfig[]",
3175
+ components: [
3176
+ {
3177
+ name: "creator",
3178
+ type: "address",
3179
+ internalType: "address"
3180
+ },
3181
+ {
3182
+ name: "operatorSetId",
3183
+ type: "uint32",
3184
+ internalType: "uint32"
3185
+ },
3186
+ {
3187
+ name: "latestReleaseBlockNumber",
3188
+ type: "uint32",
3189
+ internalType: "uint32"
3190
+ },
3191
+ {
3192
+ name: "pendingReleaseBlockNumber",
3193
+ type: "uint32",
3194
+ internalType: "uint32"
3195
+ },
3196
+ {
3197
+ name: "status",
3198
+ type: "uint8",
3199
+ internalType: "enumIAppController.AppStatus"
3200
+ }
3201
+ ]
3202
+ }
3203
+ ],
3204
+ stateMutability: "view"
3205
+ },
3206
+ {
3207
+ type: "function",
3208
+ name: "getAppsByBillingAccount",
3209
+ inputs: [
3210
+ {
3211
+ name: "account",
3212
+ type: "address",
3213
+ internalType: "address"
3214
+ },
3215
+ {
3216
+ name: "offset",
3217
+ type: "uint256",
3218
+ internalType: "uint256"
3219
+ },
3220
+ {
3221
+ name: "limit",
3222
+ type: "uint256",
3223
+ internalType: "uint256"
3224
+ }
3225
+ ],
3226
+ outputs: [
3227
+ {
3228
+ name: "apps",
3229
+ type: "address[]",
3230
+ internalType: "contractIApp[]"
3231
+ },
3232
+ {
3233
+ name: "appConfigsMem",
3234
+ type: "tuple[]",
3235
+ internalType: "structIAppController.AppConfig[]",
3236
+ components: [
3237
+ {
3238
+ name: "creator",
3239
+ type: "address",
3240
+ internalType: "address"
3241
+ },
3242
+ {
3243
+ name: "operatorSetId",
3244
+ type: "uint32",
3245
+ internalType: "uint32"
3246
+ },
3247
+ {
3248
+ name: "latestReleaseBlockNumber",
3249
+ type: "uint32",
3250
+ internalType: "uint32"
3251
+ },
3252
+ {
3253
+ name: "pendingReleaseBlockNumber",
3254
+ type: "uint32",
3255
+ internalType: "uint32"
3256
+ },
3257
+ {
3258
+ name: "status",
3259
+ type: "uint8",
3260
+ internalType: "enumIAppController.AppStatus"
3261
+ }
3262
+ ]
3263
+ }
3264
+ ],
3265
+ stateMutability: "view"
3266
+ },
3267
+ {
3268
+ type: "function",
3269
+ name: "getAppsByCreator",
3270
+ inputs: [
3271
+ {
3272
+ name: "creator",
3273
+ type: "address",
3274
+ internalType: "address"
3275
+ },
3276
+ {
3277
+ name: "offset",
3278
+ type: "uint256",
3279
+ internalType: "uint256"
3280
+ },
3281
+ {
3282
+ name: "limit",
3283
+ type: "uint256",
3284
+ internalType: "uint256"
3285
+ }
3286
+ ],
3287
+ outputs: [
3288
+ {
3289
+ name: "apps",
3290
+ type: "address[]",
3291
+ internalType: "contractIApp[]"
3292
+ },
3293
+ {
3294
+ name: "appConfigsMem",
3295
+ type: "tuple[]",
3296
+ internalType: "structIAppController.AppConfig[]",
3297
+ components: [
3298
+ {
3299
+ name: "creator",
3300
+ type: "address",
3301
+ internalType: "address"
3302
+ },
3303
+ {
3304
+ name: "operatorSetId",
3305
+ type: "uint32",
3306
+ internalType: "uint32"
3307
+ },
3308
+ {
3309
+ name: "latestReleaseBlockNumber",
3310
+ type: "uint32",
3311
+ internalType: "uint32"
3312
+ },
3313
+ {
3314
+ name: "pendingReleaseBlockNumber",
3315
+ type: "uint32",
3316
+ internalType: "uint32"
3317
+ },
3318
+ {
3319
+ name: "status",
3320
+ type: "uint8",
3321
+ internalType: "enumIAppController.AppStatus"
3322
+ }
3323
+ ]
3324
+ }
3325
+ ],
3326
+ stateMutability: "view"
3327
+ },
3328
+ {
3329
+ type: "function",
3330
+ name: "getAppsByDeveloper",
3331
+ inputs: [
3332
+ {
3333
+ name: "developer",
3334
+ type: "address",
3335
+ internalType: "address"
3336
+ },
3337
+ {
3338
+ name: "offset",
3339
+ type: "uint256",
3340
+ internalType: "uint256"
3341
+ },
3342
+ {
3343
+ name: "limit",
3344
+ type: "uint256",
3345
+ internalType: "uint256"
3346
+ }
3347
+ ],
3348
+ outputs: [
3349
+ {
3350
+ name: "apps",
3351
+ type: "address[]",
3352
+ internalType: "contractIApp[]"
3353
+ },
3354
+ {
3355
+ name: "appConfigsMem",
3356
+ type: "tuple[]",
3357
+ internalType: "structIAppController.AppConfig[]",
3358
+ components: [
3359
+ {
3360
+ name: "creator",
3361
+ type: "address",
3362
+ internalType: "address"
3363
+ },
3364
+ {
3365
+ name: "operatorSetId",
3366
+ type: "uint32",
3367
+ internalType: "uint32"
3368
+ },
3369
+ {
3370
+ name: "latestReleaseBlockNumber",
3371
+ type: "uint32",
3372
+ internalType: "uint32"
3373
+ },
3374
+ {
3375
+ name: "pendingReleaseBlockNumber",
3376
+ type: "uint32",
3377
+ internalType: "uint32"
3378
+ },
3379
+ {
3380
+ name: "status",
3381
+ type: "uint8",
3382
+ internalType: "enumIAppController.AppStatus"
3383
+ }
3384
+ ]
3385
+ }
3386
+ ],
3387
+ stateMutability: "view"
3388
+ },
3389
+ {
3390
+ type: "function",
3391
+ name: "getBillingAccount",
3392
+ inputs: [
3393
+ {
3394
+ name: "app",
3395
+ type: "address",
3396
+ internalType: "contractIApp"
3397
+ }
3398
+ ],
3399
+ outputs: [
3400
+ {
3401
+ name: "",
3402
+ type: "address",
3403
+ internalType: "address"
3404
+ }
3405
+ ],
3406
+ stateMutability: "view"
3407
+ },
3408
+ {
3409
+ type: "function",
3410
+ name: "getBillingType",
3411
+ inputs: [
3412
+ {
3413
+ name: "app",
3414
+ type: "address",
3415
+ internalType: "contractIApp"
3416
+ }
3417
+ ],
3418
+ outputs: [
3419
+ {
3420
+ name: "",
3421
+ type: "uint8",
3422
+ internalType: "enumIAppController.BillingType"
3423
+ }
3424
+ ],
3425
+ stateMutability: "view"
3426
+ },
3427
+ {
3428
+ type: "function",
3429
+ name: "getMaxActiveAppsPerUser",
3430
+ inputs: [
3431
+ {
3432
+ name: "user",
3433
+ type: "address",
3434
+ internalType: "address"
3435
+ }
3436
+ ],
3437
+ outputs: [
3438
+ {
3439
+ name: "",
3440
+ type: "uint32",
3441
+ internalType: "uint32"
3442
+ }
3443
+ ],
3444
+ stateMutability: "view"
3445
+ },
3446
+ {
3447
+ type: "function",
3448
+ name: "globalActiveAppCount",
3449
+ inputs: [],
3450
+ outputs: [
3451
+ {
3452
+ name: "",
3453
+ type: "uint32",
3454
+ internalType: "uint32"
3455
+ }
3456
+ ],
3457
+ stateMutability: "view"
3458
+ },
3459
+ {
3460
+ type: "function",
3461
+ name: "initialize",
3462
+ inputs: [
3463
+ {
3464
+ name: "admin",
3465
+ type: "address",
3466
+ internalType: "address"
3467
+ }
3468
+ ],
3469
+ outputs: [],
3470
+ stateMutability: "nonpayable"
3471
+ },
3472
+ {
3473
+ type: "function",
3474
+ name: "maxGlobalActiveApps",
3475
+ inputs: [],
3476
+ outputs: [
3477
+ {
3478
+ name: "",
3479
+ type: "uint32",
3480
+ internalType: "uint32"
3481
+ }
3482
+ ],
3483
+ stateMutability: "view"
3484
+ },
3485
+ {
3486
+ type: "function",
3487
+ name: "permissionController",
3488
+ inputs: [],
3489
+ outputs: [
3490
+ {
3491
+ name: "",
3492
+ type: "address",
3493
+ internalType: "contractIPermissionController"
3494
+ }
3495
+ ],
3496
+ stateMutability: "view"
3497
+ },
3498
+ {
3499
+ type: "function",
3500
+ name: "releaseManager",
3501
+ inputs: [],
3502
+ outputs: [
3503
+ {
3504
+ name: "",
3505
+ type: "address",
3506
+ internalType: "contractIReleaseManager"
3507
+ }
3508
+ ],
3509
+ stateMutability: "view"
3510
+ },
3511
+ {
3512
+ type: "function",
3513
+ name: "setMaxActiveAppsPerUser",
3514
+ inputs: [
3515
+ {
3516
+ name: "user",
3517
+ type: "address",
3518
+ internalType: "address"
3519
+ },
3520
+ {
3521
+ name: "limit",
3522
+ type: "uint32",
3523
+ internalType: "uint32"
3524
+ }
3525
+ ],
3526
+ outputs: [],
3527
+ stateMutability: "nonpayable"
3528
+ },
3529
+ {
3530
+ type: "function",
3531
+ name: "setMaxGlobalActiveApps",
3532
+ inputs: [
3533
+ {
3534
+ name: "limit",
3535
+ type: "uint32",
3536
+ internalType: "uint32"
3537
+ }
3538
+ ],
3539
+ outputs: [],
3540
+ stateMutability: "nonpayable"
3541
+ },
3542
+ {
3543
+ type: "function",
3544
+ name: "startApp",
3545
+ inputs: [
3546
+ {
3547
+ name: "app",
3548
+ type: "address",
3549
+ internalType: "contractIApp"
3550
+ }
3551
+ ],
3552
+ outputs: [],
3553
+ stateMutability: "nonpayable"
3554
+ },
3555
+ {
3556
+ type: "function",
3557
+ name: "stopApp",
3558
+ inputs: [
3559
+ {
3560
+ name: "app",
3561
+ type: "address",
3562
+ internalType: "contractIApp"
3563
+ }
3564
+ ],
3565
+ outputs: [],
3566
+ stateMutability: "nonpayable"
3567
+ },
3568
+ {
3569
+ type: "function",
3570
+ name: "suspend",
3571
+ inputs: [
3572
+ {
3573
+ name: "account",
3574
+ type: "address",
3575
+ internalType: "address"
3576
+ },
3577
+ {
3578
+ name: "apps",
3579
+ type: "address[]",
3580
+ internalType: "contractIApp[]"
3581
+ }
3582
+ ],
3583
+ outputs: [],
3584
+ stateMutability: "nonpayable"
3585
+ },
3586
+ {
3587
+ type: "function",
3588
+ name: "terminateApp",
3589
+ inputs: [
3590
+ {
3591
+ name: "app",
3592
+ type: "address",
3593
+ internalType: "contractIApp"
3594
+ }
3595
+ ],
3596
+ outputs: [],
3597
+ stateMutability: "nonpayable"
3598
+ },
3599
+ {
3600
+ type: "function",
3601
+ name: "terminateAppByAdmin",
3602
+ inputs: [
3603
+ {
3604
+ name: "app",
3605
+ type: "address",
3606
+ internalType: "contractIApp"
3607
+ }
3608
+ ],
3609
+ outputs: [],
3610
+ stateMutability: "nonpayable"
3611
+ },
3612
+ {
3613
+ type: "function",
3614
+ name: "updateAppMetadataURI",
3615
+ inputs: [
3616
+ {
3617
+ name: "app",
3618
+ type: "address",
3619
+ internalType: "contractIApp"
3620
+ },
3621
+ {
3622
+ name: "metadataURI",
3623
+ type: "string",
3624
+ internalType: "string"
3625
+ }
3626
+ ],
3627
+ outputs: [],
3628
+ stateMutability: "nonpayable"
3629
+ },
3630
+ {
3631
+ type: "function",
3632
+ name: "upgradeApp",
3633
+ inputs: [
3634
+ {
3635
+ name: "app",
3636
+ type: "address",
3637
+ internalType: "contractIApp"
3638
+ },
3639
+ {
3640
+ name: "release",
3641
+ type: "tuple",
3642
+ internalType: "structIAppController.Release",
3643
+ components: [
3644
+ {
3645
+ name: "rmsRelease",
3646
+ type: "tuple",
3647
+ internalType: "structIReleaseManagerTypes.Release",
3648
+ components: [
3649
+ {
3650
+ name: "artifacts",
3651
+ type: "tuple[]",
3652
+ internalType: "structIReleaseManagerTypes.Artifact[]",
3653
+ components: [
3654
+ {
3655
+ name: "digest",
3656
+ type: "bytes32",
3657
+ internalType: "bytes32"
3658
+ },
3659
+ {
3660
+ name: "registry",
3661
+ type: "string",
3662
+ internalType: "string"
3663
+ }
3664
+ ]
3665
+ },
3666
+ {
3667
+ name: "upgradeByTime",
3668
+ type: "uint32",
3669
+ internalType: "uint32"
3670
+ }
3671
+ ]
3672
+ },
3673
+ {
3674
+ name: "publicEnv",
3675
+ type: "bytes",
3676
+ internalType: "bytes"
3677
+ },
3678
+ {
3679
+ name: "encryptedEnv",
3680
+ type: "bytes",
3681
+ internalType: "bytes"
3682
+ },
3683
+ {
3684
+ name: "containerPolicy",
3685
+ type: "tuple",
3686
+ internalType: "structIAppController.ContainerPolicy",
3687
+ components: [
3688
+ {
3689
+ name: "args",
3690
+ type: "string[]",
3691
+ internalType: "string[]"
3692
+ },
3693
+ {
3694
+ name: "cmdOverride",
3695
+ type: "string[]",
3696
+ internalType: "string[]"
3697
+ },
3698
+ {
3699
+ name: "env",
3700
+ type: "tuple[]",
3701
+ internalType: "structIAppController.EnvVar[]",
3702
+ components: [
3703
+ {
3704
+ name: "key",
3705
+ type: "string",
3706
+ internalType: "string"
3707
+ },
3708
+ {
3709
+ name: "value",
3710
+ type: "string",
3711
+ internalType: "string"
3712
+ }
3713
+ ]
3714
+ },
3715
+ {
3716
+ name: "envOverride",
3717
+ type: "tuple[]",
3718
+ internalType: "structIAppController.EnvVar[]",
3719
+ components: [
3720
+ {
3721
+ name: "key",
3722
+ type: "string",
3723
+ internalType: "string"
3724
+ },
3725
+ {
3726
+ name: "value",
3727
+ type: "string",
3728
+ internalType: "string"
3729
+ }
3730
+ ]
3731
+ },
3732
+ {
3733
+ name: "restartPolicy",
3734
+ type: "string",
3735
+ internalType: "string"
3736
+ }
3737
+ ]
3738
+ }
3739
+ ]
3740
+ }
3741
+ ],
3742
+ outputs: [
3743
+ {
3744
+ name: "",
3745
+ type: "uint256",
3746
+ internalType: "uint256"
3747
+ }
3748
+ ],
3749
+ stateMutability: "nonpayable"
3750
+ },
3751
+ {
3752
+ type: "function",
3753
+ name: "version",
3754
+ inputs: [],
3755
+ outputs: [
3756
+ {
3757
+ name: "",
3758
+ type: "string",
3759
+ internalType: "string"
3760
+ }
3761
+ ],
3762
+ stateMutability: "view"
3763
+ },
3764
+ {
3765
+ type: "event",
3766
+ name: "AppCreated",
3767
+ inputs: [
3768
+ {
3769
+ name: "creator",
3770
+ type: "address",
3771
+ indexed: true,
3772
+ internalType: "address"
3773
+ },
3774
+ {
3775
+ name: "app",
3776
+ type: "address",
3777
+ indexed: true,
3778
+ internalType: "contractIApp"
3779
+ },
3780
+ {
3781
+ name: "operatorSetId",
3782
+ type: "uint32",
3783
+ indexed: false,
3784
+ internalType: "uint32"
3785
+ }
3786
+ ],
3787
+ anonymous: false
3788
+ },
3789
+ {
3790
+ type: "event",
3791
+ name: "AppMetadataURIUpdated",
3792
+ inputs: [
3793
+ {
3794
+ name: "app",
3795
+ type: "address",
3796
+ indexed: true,
3797
+ internalType: "contractIApp"
3798
+ },
3799
+ {
3800
+ name: "metadataURI",
3801
+ type: "string",
3802
+ indexed: false,
3803
+ internalType: "string"
3804
+ }
3805
+ ],
3806
+ anonymous: false
3807
+ },
3808
+ {
3809
+ type: "event",
3810
+ name: "AppStarted",
3811
+ inputs: [
3812
+ {
3813
+ name: "app",
3814
+ type: "address",
3815
+ indexed: true,
3816
+ internalType: "contractIApp"
3817
+ }
3818
+ ],
3819
+ anonymous: false
3820
+ },
3821
+ {
3822
+ type: "event",
3823
+ name: "AppStopped",
3824
+ inputs: [
3825
+ {
3826
+ name: "app",
3827
+ type: "address",
3828
+ indexed: true,
3829
+ internalType: "contractIApp"
3830
+ }
3831
+ ],
3832
+ anonymous: false
3833
+ },
3834
+ {
3835
+ type: "event",
3836
+ name: "AppSuspended",
3837
+ inputs: [
3838
+ {
3839
+ name: "app",
3840
+ type: "address",
3841
+ indexed: true,
3842
+ internalType: "contractIApp"
3843
+ }
3844
+ ],
3845
+ anonymous: false
3846
+ },
3847
+ {
3848
+ type: "event",
3849
+ name: "AppTerminated",
3850
+ inputs: [
3851
+ {
3852
+ name: "app",
3853
+ type: "address",
3854
+ indexed: true,
3855
+ internalType: "contractIApp"
3856
+ }
3857
+ ],
3858
+ anonymous: false
3859
+ },
3860
+ {
3861
+ type: "event",
3862
+ name: "AppTerminatedByAdmin",
3863
+ inputs: [
3864
+ {
3865
+ name: "app",
3866
+ type: "address",
3867
+ indexed: true,
3868
+ internalType: "contractIApp"
3869
+ }
3870
+ ],
3871
+ anonymous: false
3872
+ },
3873
+ {
3874
+ type: "event",
3875
+ name: "AppUpgraded",
3876
+ inputs: [
3877
+ {
3878
+ name: "app",
3879
+ type: "address",
3880
+ indexed: true,
3881
+ internalType: "contractIApp"
3882
+ },
3883
+ {
3884
+ name: "rmsReleaseId",
3885
+ type: "uint256",
3886
+ indexed: false,
3887
+ internalType: "uint256"
3888
+ },
3889
+ {
3890
+ name: "release",
3891
+ type: "tuple",
3892
+ indexed: false,
3893
+ internalType: "structIAppController.Release",
3894
+ components: [
3895
+ {
3896
+ name: "rmsRelease",
3897
+ type: "tuple",
3898
+ internalType: "structIReleaseManagerTypes.Release",
3899
+ components: [
3900
+ {
3901
+ name: "artifacts",
3902
+ type: "tuple[]",
3903
+ internalType: "structIReleaseManagerTypes.Artifact[]",
3904
+ components: [
3905
+ {
3906
+ name: "digest",
3907
+ type: "bytes32",
3908
+ internalType: "bytes32"
3909
+ },
3910
+ {
3911
+ name: "registry",
3912
+ type: "string",
3913
+ internalType: "string"
3914
+ }
3915
+ ]
3916
+ },
3917
+ {
3918
+ name: "upgradeByTime",
3919
+ type: "uint32",
3920
+ internalType: "uint32"
3921
+ }
3922
+ ]
3923
+ },
3924
+ {
3925
+ name: "publicEnv",
3926
+ type: "bytes",
3927
+ internalType: "bytes"
3928
+ },
3929
+ {
3930
+ name: "encryptedEnv",
3931
+ type: "bytes",
3932
+ internalType: "bytes"
3933
+ },
3934
+ {
3935
+ name: "containerPolicy",
3936
+ type: "tuple",
3937
+ internalType: "structIAppController.ContainerPolicy",
3938
+ components: [
3939
+ {
3940
+ name: "args",
3941
+ type: "string[]",
3942
+ internalType: "string[]"
3943
+ },
3944
+ {
3945
+ name: "cmdOverride",
3946
+ type: "string[]",
3947
+ internalType: "string[]"
3948
+ },
3949
+ {
3950
+ name: "env",
3951
+ type: "tuple[]",
3952
+ internalType: "structIAppController.EnvVar[]",
3953
+ components: [
3954
+ {
3955
+ name: "key",
3956
+ type: "string",
3957
+ internalType: "string"
3958
+ },
3959
+ {
3960
+ name: "value",
3961
+ type: "string",
3962
+ internalType: "string"
3963
+ }
3964
+ ]
3965
+ },
3966
+ {
3967
+ name: "envOverride",
3968
+ type: "tuple[]",
3969
+ internalType: "structIAppController.EnvVar[]",
3970
+ components: [
3971
+ {
3972
+ name: "key",
3973
+ type: "string",
3974
+ internalType: "string"
3975
+ },
3976
+ {
3977
+ name: "value",
3978
+ type: "string",
3979
+ internalType: "string"
3980
+ }
3981
+ ]
3982
+ },
3983
+ {
3984
+ name: "restartPolicy",
3985
+ type: "string",
3986
+ internalType: "string"
3987
+ }
3988
+ ]
3989
+ }
3990
+ ]
3991
+ }
3992
+ ],
3993
+ anonymous: false
3994
+ },
3995
+ {
3996
+ type: "event",
3997
+ name: "GlobalMaxActiveAppsSet",
3998
+ inputs: [
3999
+ {
4000
+ name: "limit",
4001
+ type: "uint32",
4002
+ indexed: false,
4003
+ internalType: "uint32"
4004
+ }
4005
+ ],
4006
+ anonymous: false
4007
+ },
4008
+ {
4009
+ type: "event",
4010
+ name: "Initialized",
4011
+ inputs: [
4012
+ {
4013
+ name: "version",
4014
+ type: "uint8",
4015
+ indexed: false,
4016
+ internalType: "uint8"
4017
+ }
4018
+ ],
4019
+ anonymous: false
4020
+ },
4021
+ {
4022
+ type: "event",
4023
+ name: "MaxActiveAppsSet",
4024
+ inputs: [
4025
+ {
4026
+ name: "user",
4027
+ type: "address",
4028
+ indexed: true,
4029
+ internalType: "address"
4030
+ },
4031
+ {
4032
+ name: "limit",
4033
+ type: "uint32",
4034
+ indexed: false,
4035
+ internalType: "uint32"
4036
+ }
4037
+ ],
4038
+ anonymous: false
4039
+ },
4040
+ {
4041
+ type: "event",
4042
+ name: "UpgradeConfirmed",
4043
+ inputs: [
4044
+ {
4045
+ name: "app",
4046
+ type: "address",
4047
+ indexed: true,
4048
+ internalType: "contractIApp"
4049
+ },
4050
+ {
4051
+ name: "pendingReleaseBlockNumber",
4052
+ type: "uint32",
4053
+ indexed: false,
4054
+ internalType: "uint32"
4055
+ }
4056
+ ],
4057
+ anonymous: false
4058
+ },
4059
+ {
4060
+ type: "error",
4061
+ name: "AccountHasActiveApps",
4062
+ inputs: []
4063
+ },
4064
+ {
4065
+ type: "error",
4066
+ name: "AppAlreadyExists",
4067
+ inputs: []
4068
+ },
4069
+ {
4070
+ type: "error",
4071
+ name: "AppDoesNotExist",
4072
+ inputs: []
4073
+ },
4074
+ {
4075
+ type: "error",
4076
+ name: "GlobalMaxActiveAppsExceeded",
4077
+ inputs: []
4078
+ },
4079
+ {
4080
+ type: "error",
4081
+ name: "InvalidAppStatus",
4082
+ inputs: []
4083
+ },
4084
+ {
4085
+ type: "error",
4086
+ name: "InvalidPermissions",
4087
+ inputs: []
4088
+ },
4089
+ {
4090
+ type: "error",
4091
+ name: "InvalidReleaseMetadataURI",
4092
+ inputs: []
4093
+ },
4094
+ {
4095
+ type: "error",
4096
+ name: "InvalidShortString",
4097
+ inputs: []
4098
+ },
4099
+ {
4100
+ type: "error",
4101
+ name: "InvalidSignature",
4102
+ inputs: []
4103
+ },
4104
+ {
4105
+ type: "error",
4106
+ name: "MaxActiveAppsExceeded",
4107
+ inputs: []
4108
+ },
4109
+ {
4110
+ type: "error",
4111
+ name: "MoreThanOneArtifact",
4112
+ inputs: []
4113
+ },
4114
+ {
4115
+ type: "error",
4116
+ name: "NoPendingUpgrade",
4117
+ inputs: []
4118
+ },
4119
+ {
4120
+ type: "error",
4121
+ name: "SignatureExpired",
4122
+ inputs: []
4123
+ },
4124
+ {
4125
+ type: "error",
4126
+ name: "StringTooLong",
4127
+ inputs: [
4128
+ {
4129
+ name: "str",
4130
+ type: "string",
4131
+ internalType: "string"
4132
+ }
4133
+ ]
2947
4134
  }
2948
- return hash;
2949
- }
2950
-
2951
- // src/client/common/contract/caller.ts
2952
- import { encodeFunctionData as encodeFunctionData2, decodeErrorResult as decodeErrorResult2, bytesToHex } from "viem";
2953
-
2954
- // src/client/common/utils/helpers.ts
2955
- import { extractChain, createPublicClient, createWalletClient, http, fallback } from "viem";
2956
- import { sepolia as sepolia2 } from "viem/chains";
2957
- import { privateKeyToAccount } from "viem/accounts";
2958
- function getChainFromID(chainID, fallback2 = sepolia2) {
2959
- const id = Number(chainID);
2960
- return extractChain({ chains: SUPPORTED_CHAINS, id }) || fallback2;
2961
- }
2962
- function createClients(options) {
2963
- const { privateKey, rpcUrl, chainId } = options;
2964
- const privateKeyHex = addHexPrefix(privateKey);
2965
- const account = privateKeyToAccount(privateKeyHex);
2966
- const chain = getChainFromID(chainId);
2967
- const transport = typeof rpcUrl === "string" ? http(rpcUrl) : fallback(rpcUrl.map((url) => http(url)));
2968
- const publicClient = createPublicClient({
2969
- chain,
2970
- transport
2971
- });
2972
- const walletClient = createWalletClient({
2973
- account,
2974
- chain,
2975
- transport
2976
- });
2977
- return { walletClient, publicClient };
2978
- }
2979
- function addHexPrefix(value) {
2980
- return value.startsWith("0x") ? value : `0x${value}`;
2981
- }
2982
- function stripHexPrefix(value) {
2983
- return value.startsWith("0x") ? value.slice(2) : value;
2984
- }
4135
+ ];
2985
4136
 
2986
- // src/client/common/abis/AppController.json
2987
- var AppController_default = [
4137
+ // src/client/common/abis/AppController.v1_4.json
4138
+ var AppController_v1_4_default = [
2988
4139
  {
2989
4140
  type: "constructor",
2990
4141
  inputs: [
@@ -4668,6 +5819,38 @@ var PermissionController_default = [
4668
5819
  ];
4669
5820
 
4670
5821
  // src/client/common/contract/caller.ts
5822
+ function appControllerAbiFor(environmentConfig) {
5823
+ return environmentConfig.releaseAbiVersion === "v1.4" ? AppController_v1_4_default : AppController_default;
5824
+ }
5825
+ function supportsContainerPolicy(environmentConfig) {
5826
+ return environmentConfig.releaseAbiVersion !== "v1.4";
5827
+ }
5828
+ function containerPolicyForViem(policy = EMPTY_CONTAINER_POLICY) {
5829
+ return {
5830
+ args: policy.args,
5831
+ cmdOverride: policy.cmdOverride,
5832
+ env: policy.env.map((e) => ({ key: e.key, value: e.value })),
5833
+ envOverride: policy.envOverride.map((e) => ({ key: e.key, value: e.value })),
5834
+ restartPolicy: policy.restartPolicy
5835
+ };
5836
+ }
5837
+ function releaseForViem(release, environmentConfig) {
5838
+ const base = {
5839
+ rmsRelease: {
5840
+ artifacts: release.rmsRelease.artifacts.map((artifact) => ({
5841
+ digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
5842
+ registry: artifact.registry
5843
+ })),
5844
+ upgradeByTime: release.rmsRelease.upgradeByTime
5845
+ },
5846
+ publicEnv: bytesToHex(release.publicEnv),
5847
+ encryptedEnv: bytesToHex(release.encryptedEnv)
5848
+ };
5849
+ if (!supportsContainerPolicy(environmentConfig)) {
5850
+ return base;
5851
+ }
5852
+ return { ...base, containerPolicy: containerPolicyForViem(release.containerPolicy) };
5853
+ }
4671
5854
  function formatETH(wei) {
4672
5855
  const eth = Number(wei) / 1e18;
4673
5856
  const costStr = eth.toFixed(6);
@@ -4705,7 +5888,7 @@ async function calculateAppID(options) {
4705
5888
  const saltHex = `0x${paddedSaltHex}`;
4706
5889
  const appID = await publicClient.readContract({
4707
5890
  address: environmentConfig.appControllerAddress,
4708
- abi: AppController_default,
5891
+ abi: appControllerAbiFor(environmentConfig),
4709
5892
  functionName: "calculateAppId",
4710
5893
  args: [ownerAddress, saltHex]
4711
5894
  });
@@ -4729,22 +5912,12 @@ async function prepareDeployBatch(options, logger = noopLogger) {
4729
5912
  const saltHexString = bytesToHex(salt).slice(2);
4730
5913
  const paddedSaltHex = saltHexString.padStart(64, "0");
4731
5914
  const saltHex = `0x${paddedSaltHex}`;
4732
- const releaseForViem = {
4733
- rmsRelease: {
4734
- artifacts: release.rmsRelease.artifacts.map((artifact) => ({
4735
- digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
4736
- registry: artifact.registry
4737
- })),
4738
- upgradeByTime: release.rmsRelease.upgradeByTime
4739
- },
4740
- publicEnv: bytesToHex(release.publicEnv),
4741
- encryptedEnv: bytesToHex(release.encryptedEnv)
4742
- };
5915
+ const release_ = releaseForViem(release, environmentConfig);
4743
5916
  const functionName = options.billTo === "app" ? "createAppWithIsolatedBilling" : "createApp";
4744
5917
  const createData = encodeFunctionData2({
4745
- abi: AppController_default,
5918
+ abi: appControllerAbiFor(environmentConfig),
4746
5919
  functionName,
4747
- args: [saltHex, releaseForViem]
5920
+ args: [saltHex, release_]
4748
5921
  });
4749
5922
  const acceptAdminData = encodeFunctionData2({
4750
5923
  abi: PermissionController_default,
@@ -4832,21 +6005,11 @@ async function prepareUpgradeBatch(options) {
4832
6005
  publicLogs,
4833
6006
  needsPermissionChange
4834
6007
  } = options;
4835
- const releaseForViem = {
4836
- rmsRelease: {
4837
- artifacts: release.rmsRelease.artifacts.map((artifact) => ({
4838
- digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
4839
- registry: artifact.registry
4840
- })),
4841
- upgradeByTime: release.rmsRelease.upgradeByTime
4842
- },
4843
- publicEnv: bytesToHex(release.publicEnv),
4844
- encryptedEnv: bytesToHex(release.encryptedEnv)
4845
- };
6008
+ const release_ = releaseForViem(release, environmentConfig);
4846
6009
  const upgradeData = encodeFunctionData2({
4847
- abi: AppController_default,
6010
+ abi: appControllerAbiFor(environmentConfig),
4848
6011
  functionName: "upgradeApp",
4849
- args: [appID, releaseForViem]
6012
+ args: [appID, release_]
4850
6013
  });
4851
6014
  const executions = [
4852
6015
  {
@@ -4980,7 +6143,7 @@ ${pendingMessage}`);
4980
6143
  if (callError.data) {
4981
6144
  try {
4982
6145
  const decoded = decodeErrorResult2({
4983
- abi: AppController_default,
6146
+ abi: appControllerAbiFor(environmentConfig),
4984
6147
  data: callError.data
4985
6148
  });
4986
6149
  const formattedError = formatAppControllerError(decoded);
@@ -5033,7 +6196,7 @@ function formatAppControllerError(decoded) {
5033
6196
  async function getActiveAppCount(publicClient, environmentConfig, user) {
5034
6197
  const count = await publicClient.readContract({
5035
6198
  address: environmentConfig.appControllerAddress,
5036
- abi: AppController_default,
6199
+ abi: appControllerAbiFor(environmentConfig),
5037
6200
  functionName: "getActiveAppCount",
5038
6201
  args: [user]
5039
6202
  });
@@ -5042,7 +6205,7 @@ async function getActiveAppCount(publicClient, environmentConfig, user) {
5042
6205
  async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
5043
6206
  const quota = await publicClient.readContract({
5044
6207
  address: environmentConfig.appControllerAddress,
5045
- abi: AppController_default,
6208
+ abi: appControllerAbiFor(environmentConfig),
5046
6209
  functionName: "getMaxActiveAppsPerUser",
5047
6210
  args: [user]
5048
6211
  });
@@ -5051,7 +6214,7 @@ async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
5051
6214
  async function getAppsByDeveloper(publicClient, environmentConfig, developer, offset, limit) {
5052
6215
  const result = await publicClient.readContract({
5053
6216
  address: environmentConfig.appControllerAddress,
5054
- abi: AppController_default,
6217
+ abi: appControllerAbiFor(environmentConfig),
5055
6218
  functionName: "getAppsByDeveloper",
5056
6219
  args: [developer, offset, limit]
5057
6220
  });
@@ -5063,7 +6226,7 @@ async function getAppsByDeveloper(publicClient, environmentConfig, developer, of
5063
6226
  async function getBillingType(publicClient, environmentConfig, app) {
5064
6227
  const result = await publicClient.readContract({
5065
6228
  address: environmentConfig.appControllerAddress,
5066
- abi: AppController_default,
6229
+ abi: appControllerAbiFor(environmentConfig),
5067
6230
  functionName: "getBillingType",
5068
6231
  args: [app]
5069
6232
  });
@@ -5072,7 +6235,7 @@ async function getBillingType(publicClient, environmentConfig, app) {
5072
6235
  async function getAppsByBillingAccount(publicClient, environmentConfig, account, offset, limit) {
5073
6236
  const result = await publicClient.readContract({
5074
6237
  address: environmentConfig.appControllerAddress,
5075
- abi: AppController_default,
6238
+ abi: appControllerAbiFor(environmentConfig),
5076
6239
  functionName: "getAppsByBillingAccount",
5077
6240
  args: [account, offset, limit]
5078
6241
  });
@@ -5106,7 +6269,7 @@ async function getAppLatestReleaseBlockNumbers(publicClient, environmentConfig,
5106
6269
  appIDs.map(
5107
6270
  (appID) => publicClient.readContract({
5108
6271
  address: environmentConfig.appControllerAddress,
5109
- abi: AppController_default,
6272
+ abi: appControllerAbiFor(environmentConfig),
5110
6273
  functionName: "getAppLatestReleaseBlockNumber",
5111
6274
  args: [appID]
5112
6275
  }).catch(() => null)
@@ -5185,6 +6348,35 @@ async function undelegate(options, logger = noopLogger) {
5185
6348
  return hash;
5186
6349
  }
5187
6350
 
6351
+ // src/client/common/gas/insufficientGas.ts
6352
+ import { formatEther } from "viem";
6353
+ var InsufficientGasError = class extends Error {
6354
+ constructor(args) {
6355
+ const requiredEth = formatEther(args.requiredWei);
6356
+ const availableEth = formatEther(args.availableWei);
6357
+ super(
6358
+ `Insufficient ETH for gas: wallet ${args.address} has ${availableEth} ETH but this transaction needs ~${requiredEth} ETH.
6359
+ Compute credits do not pay on-chain gas \u2014 fund the wallet with ETH and retry.`
6360
+ );
6361
+ this.name = "InsufficientGasError";
6362
+ this.address = args.address;
6363
+ this.requiredWei = args.requiredWei;
6364
+ this.availableWei = args.availableWei;
6365
+ this.requiredEth = requiredEth;
6366
+ this.availableEth = availableEth;
6367
+ }
6368
+ };
6369
+ async function assertSufficientGas(args) {
6370
+ const availableWei = await args.publicClient.getBalance({ address: args.address });
6371
+ if (availableWei < args.gasEstimate.maxCostWei) {
6372
+ throw new InsufficientGasError({
6373
+ address: args.address,
6374
+ requiredWei: args.gasEstimate.maxCostWei,
6375
+ availableWei
6376
+ });
6377
+ }
6378
+ }
6379
+
5188
6380
  // src/client/common/utils/userapi.ts
5189
6381
  import axios2 from "axios";
5190
6382
 
@@ -5248,6 +6440,7 @@ import axios from "axios";
5248
6440
  var MAX_RETRIES = 5;
5249
6441
  var INITIAL_BACKOFF_MS = 1e3;
5250
6442
  var MAX_BACKOFF_MS = 3e4;
6443
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
5251
6444
  function sleep(ms) {
5252
6445
  return new Promise((resolve2) => setTimeout(resolve2, ms));
5253
6446
  }
@@ -5267,7 +6460,7 @@ async function requestWithRetry(config) {
5267
6460
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
5268
6461
  const res = await axios({ ...config, validateStatus: () => true });
5269
6462
  lastResponse = res;
5270
- if (res.status !== 429) {
6463
+ if (!RETRYABLE_STATUSES.has(res.status)) {
5271
6464
  return res;
5272
6465
  }
5273
6466
  if (attempt < MAX_RETRIES) {
@@ -5296,7 +6489,7 @@ var CanViewAppLogsPermission = "0x2fd3f2fe";
5296
6489
  var CanViewSensitiveAppInfoPermission = "0x0e67b22f";
5297
6490
  var CanUpdateAppProfilePermission = "0x036fef61";
5298
6491
  function getDefaultClientId() {
5299
- const version = true ? "1.0.0-devep7" : "0.0.0";
6492
+ const version = true ? "1.0.0" : "0.0.0";
5300
6493
  return `ecloud-sdk/v${version}`;
5301
6494
  }
5302
6495
  var UserApiClient = class {
@@ -5331,7 +6524,6 @@ var UserApiClient = class {
5331
6524
  status: app.app_status,
5332
6525
  ip: app.ip,
5333
6526
  machineType: app.machine_type,
5334
- hostname: app.hostname,
5335
6527
  profile: app.profile,
5336
6528
  metrics: app.metrics,
5337
6529
  evmAddresses,
@@ -5397,28 +6589,6 @@ var UserApiClient = class {
5397
6589
  status: app.app_status || app.App_Status || ""
5398
6590
  }));
5399
6591
  }
5400
- /**
5401
- * Get deployments for an app from the gRPC-gateway endpoint.
5402
- * Returns deployment records including upgrade_phase for tracking upgrade progress.
5403
- *
5404
- * Endpoint: GET /v1/apps/:appAddress/deployments
5405
- */
5406
- async getDeployments(appAddress) {
5407
- const endpoint = `${this.config.userApiServerURL}/v1/apps/${appAddress}/deployments`;
5408
- const response = await this.makeEIP712AuthenticatedRequest(endpoint);
5409
- const result = await response.json();
5410
- const deployments = result.deployments || [];
5411
- return deployments.map((dep) => ({
5412
- id: dep.id || "",
5413
- externalId: dep.external_id || dep.externalId || "",
5414
- endpoint: dep.endpoint || "",
5415
- releaseId: dep.release_id || dep.releaseId || "",
5416
- upgradePhase: dep.upgrade_phase || dep.upgradePhase || "",
5417
- replacesDeploymentId: dep.replaces_deployment_id || dep.replacesDeploymentId || "",
5418
- createdAt: dep.created_at || dep.createdAt || "",
5419
- updatedAt: dep.updated_at || dep.updatedAt || ""
5420
- }));
5421
- }
5422
6592
  /**
5423
6593
  * Upload app profile information with optional image
5424
6594
  *
@@ -5552,48 +6722,6 @@ Please check:
5552
6722
  "X-eigenx-expiry": expiry.toString()
5553
6723
  };
5554
6724
  }
5555
- /**
5556
- * Make an EIP-712 authenticated request to the gRPC-gateway endpoints.
5557
- * Uses the billing/compute auth pattern: Authorization + X-Account + X-Expiry headers.
5558
- */
5559
- async makeEIP712AuthenticatedRequest(url) {
5560
- const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
5561
- const { signature } = await calculateBillingAuthSignature({
5562
- walletClient: this.walletClient,
5563
- product: "compute",
5564
- expiry
5565
- });
5566
- const headers = {
5567
- Authorization: `Bearer ${signature}`,
5568
- "X-Account": this.address,
5569
- "X-Expiry": expiry.toString(),
5570
- "x-client-id": this.clientId
5571
- };
5572
- try {
5573
- const response = await requestWithRetry({
5574
- method: "GET",
5575
- url,
5576
- headers,
5577
- maxRedirects: 0,
5578
- withCredentials: true
5579
- });
5580
- const status = response.status;
5581
- if (status < 200 || status >= 300) {
5582
- const body = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
5583
- throw new Error(`gRPC-gateway request failed: ${status} - ${body}`);
5584
- }
5585
- return {
5586
- json: async () => response.data,
5587
- text: async () => typeof response.data === "string" ? response.data : JSON.stringify(response.data)
5588
- };
5589
- } catch (error) {
5590
- if (error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED") || error.message?.includes("ENOTFOUND") || error.cause) {
5591
- const cause = error.cause?.message || error.cause || error.message;
5592
- throw new Error(`Failed to connect to API at ${url}: ${cause}`);
5593
- }
5594
- throw error;
5595
- }
5596
- }
5597
6725
  // ==========================================================================
5598
6726
  // SIWE Session Management
5599
6727
  // ==========================================================================
@@ -5699,8 +6827,36 @@ function transformAppRelease(raw) {
5699
6827
 
5700
6828
  // src/client/common/contract/watcher.ts
5701
6829
  var WATCH_POLL_INTERVAL_SECONDS = 5;
6830
+ var WATCH_HEARTBEAT_INTERVAL_SECONDS = 30;
6831
+ var WATCH_DEFAULT_TIMEOUT_SECONDS = 10 * 60;
5702
6832
  var APP_STATUS_RUNNING = "Running";
5703
6833
  var APP_STATUS_FAILED = "Failed";
6834
+ var WatchTimeoutError = class extends Error {
6835
+ constructor(args) {
6836
+ super(
6837
+ args.message ?? `Timed out after ${args.elapsedSeconds}s waiting for app ${args.appId} (last status: ${args.lastStatus ?? "unknown"})`
6838
+ );
6839
+ this.name = "WatchTimeoutError";
6840
+ this.appId = args.appId;
6841
+ this.elapsedSeconds = args.elapsedSeconds;
6842
+ this.lastStatus = args.lastStatus;
6843
+ this.timeoutSeconds = args.timeoutSeconds;
6844
+ }
6845
+ };
6846
+ function resolveWatchTimeoutSeconds(explicit) {
6847
+ if (typeof explicit === "number" && Number.isFinite(explicit) && explicit > 0) {
6848
+ return Math.floor(explicit);
6849
+ }
6850
+ const raw = process.env.ECLOUD_WATCH_TIMEOUT_SECONDS;
6851
+ if (raw === void 0 || raw === "") {
6852
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6853
+ }
6854
+ const parsed = Number(raw);
6855
+ if (!Number.isFinite(parsed) || parsed <= 0) {
6856
+ return WATCH_DEFAULT_TIMEOUT_SECONDS;
6857
+ }
6858
+ return Math.floor(parsed);
6859
+ }
5704
6860
  async function watchUntilRunning(options, logger) {
5705
6861
  const { walletClient, publicClient, environmentConfig, appId } = options;
5706
6862
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
@@ -5731,8 +6887,20 @@ async function watchUntilRunning(options, logger) {
5731
6887
  return false;
5732
6888
  };
5733
6889
  const startTime = Date.now();
6890
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
5734
6891
  let lastLoggedStatus;
6892
+ let lastHeartbeatAt = startTime;
5735
6893
  while (true) {
6894
+ const elapsedMs = Date.now() - startTime;
6895
+ const elapsed = Math.round(elapsedMs / 1e3);
6896
+ if (elapsed >= timeoutSeconds) {
6897
+ throw new WatchTimeoutError({
6898
+ appId,
6899
+ elapsedSeconds: elapsed,
6900
+ lastStatus: lastLoggedStatus,
6901
+ timeoutSeconds
6902
+ });
6903
+ }
5736
6904
  try {
5737
6905
  const info = await userApiClient.getInfos([appId], 1);
5738
6906
  if (info.length === 0) {
@@ -5742,62 +6910,35 @@ async function watchUntilRunning(options, logger) {
5742
6910
  const appInfo = info[0];
5743
6911
  const currentStatus = appInfo.status;
5744
6912
  const currentIP = appInfo.ip || "";
5745
- const elapsed = Math.round((Date.now() - startTime) / 1e3);
5746
6913
  if (currentStatus !== lastLoggedStatus) {
5747
6914
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
5748
6915
  lastLoggedStatus = currentStatus;
6916
+ lastHeartbeatAt = Date.now();
6917
+ } else if (Date.now() - lastHeartbeatAt >= WATCH_HEARTBEAT_INTERVAL_SECONDS * 1e3) {
6918
+ logger.info(`Status: ${currentStatus} (${elapsed}s)`);
6919
+ lastHeartbeatAt = Date.now();
5749
6920
  }
5750
6921
  if (stopCondition(currentStatus, currentIP)) {
5751
6922
  return currentIP || void 0;
5752
6923
  }
5753
6924
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5754
6925
  } catch (error) {
6926
+ if (error instanceof WatchTimeoutError) {
6927
+ throw error;
6928
+ }
5755
6929
  logger.warn(`Failed to fetch app info: ${error.message}`);
5756
6930
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5757
6931
  }
5758
6932
  }
5759
6933
  }
5760
6934
  var APP_STATUS_STOPPED = "Stopped";
5761
- var UPGRADE_PHASE_LABELS = {
5762
- provisioning: "Provisioning new instance",
5763
- health_check: "Running health checks",
5764
- draining: "Switching traffic & draining old instance",
5765
- complete: "Complete",
5766
- rolling_back: "Rolling back",
5767
- rollback_done: "Rollback complete",
5768
- db_handoff: "Database handoff",
5769
- // Prewarm-detach phases
5770
- awaiting_userdata: "Waiting for instance readiness",
5771
- draining_old: "Draining old instance",
5772
- detached: "Detaching storage",
5773
- attached_to_new: "Attaching storage to new instance",
5774
- finalizing: "Finalizing new instance",
5775
- flipping: "Switching traffic",
5776
- teardown_old: "Cleaning up old instance"
5777
- };
5778
- async function fetchUpgradePhase(userApiClient, appId) {
5779
- try {
5780
- const deployments = await userApiClient.getDeployments(appId);
5781
- if (deployments.length === 0) return void 0;
5782
- const sorted = [...deployments].sort((a, b) => {
5783
- const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0;
5784
- const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0;
5785
- return tb - ta;
5786
- });
5787
- return sorted[0].upgradePhase || void 0;
5788
- } catch {
5789
- return void 0;
5790
- }
5791
- }
5792
6935
  async function watchUntilUpgradeComplete(options, logger) {
5793
6936
  const { walletClient, publicClient, environmentConfig, appId } = options;
6937
+ const timeoutSeconds = resolveWatchTimeoutSeconds(options.timeoutSeconds);
5794
6938
  const userApiClient = new UserApiClient(environmentConfig, walletClient, publicClient);
5795
6939
  let initialStatus;
5796
6940
  let initialIP;
5797
6941
  let hasChanged = false;
5798
- const startTime = Date.now();
5799
- let lastLoggedStatus;
5800
- let lastLoggedPhase;
5801
6942
  const stopCondition = (status, ip) => {
5802
6943
  if (!initialStatus) {
5803
6944
  initialStatus = status;
@@ -5831,37 +6972,44 @@ async function watchUntilUpgradeComplete(options, logger) {
5831
6972
  }
5832
6973
  return false;
5833
6974
  };
6975
+ const startTime = Date.now();
6976
+ const deadline = startTime + timeoutSeconds * 1e3;
6977
+ let lastLoggedStatus;
6978
+ let lastObservedStatus;
5834
6979
  while (true) {
6980
+ if (Date.now() >= deadline) {
6981
+ const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
6982
+ throw new WatchTimeoutError({
6983
+ appId,
6984
+ lastStatus: lastObservedStatus,
6985
+ elapsedSeconds,
6986
+ timeoutSeconds
6987
+ });
6988
+ }
5835
6989
  try {
5836
- const [infoResult, upgradePhase] = await Promise.all([
5837
- userApiClient.getInfos([appId], 1),
5838
- fetchUpgradePhase(userApiClient, appId)
5839
- ]);
5840
- if (infoResult.length === 0) {
6990
+ const info = await userApiClient.getInfos([appId], 1);
6991
+ if (info.length === 0) {
5841
6992
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5842
6993
  continue;
5843
6994
  }
5844
- const appInfo = infoResult[0];
6995
+ const appInfo = info[0];
5845
6996
  const currentStatus = appInfo.status;
5846
6997
  const currentIP = appInfo.ip || "";
6998
+ lastObservedStatus = currentStatus;
5847
6999
  const elapsed = Math.round((Date.now() - startTime) / 1e3);
5848
- if (upgradePhase && upgradePhase !== lastLoggedPhase) {
5849
- const label = UPGRADE_PHASE_LABELS[upgradePhase] || upgradePhase;
5850
- logger.info(`Phase: ${label} (${elapsed}s)`);
5851
- lastLoggedPhase = upgradePhase;
5852
- }
5853
- if (!upgradePhase && currentStatus !== lastLoggedStatus) {
7000
+ if (currentStatus !== lastLoggedStatus) {
5854
7001
  logger.info(`Status: ${currentStatus} (${elapsed}s)`);
5855
7002
  lastLoggedStatus = currentStatus;
5856
7003
  }
5857
7004
  if (stopCondition(currentStatus, currentIP)) {
5858
- const totalElapsed = Math.round((Date.now() - startTime) / 1e3);
5859
- logger.info(`Upgrade completed in ${totalElapsed}s`);
5860
7005
  return;
5861
7006
  }
5862
7007
  await sleep2(WATCH_POLL_INTERVAL_SECONDS * 1e3);
5863
7008
  } catch (error) {
5864
- if (error.message?.includes("entered")) {
7009
+ if (error instanceof WatchTimeoutError) {
7010
+ throw error;
7011
+ }
7012
+ if (typeof error?.message === "string" && error.message.includes("Failed")) {
5865
7013
  throw error;
5866
7014
  }
5867
7015
  logger.warn(`Failed to fetch app info: ${error.message}`);
@@ -6172,6 +7320,7 @@ function validateLogsParams(params) {
6172
7320
  // src/client/common/config/environment.ts
6173
7321
  var SEPOLIA_CHAIN_ID = 11155111;
6174
7322
  var MAINNET_CHAIN_ID = 1;
7323
+ var BASE_SEPOLIA_CHAIN_ID = 84532;
6175
7324
  var CommonAddresses = {
6176
7325
  ERC7702Delegator: "0x63c0c19a282a1b52b07dd5a65b58948a07dae32b"
6177
7326
  };
@@ -6191,27 +7340,28 @@ var BILLING_ENVIRONMENTS = {
6191
7340
  billingApiServerURL: "https://billingapi.eigencloud.xyz"
6192
7341
  }
6193
7342
  };
6194
- var PLATFORM_ENV_TESTNET_SEPOLIA = "testnet-sepolia";
6195
- var PLATFORM_ENV_MAINNET_ETHEREUM = "mainnet-ethereum";
6196
- var DEFAULT_APP_BASE_DOMAIN = "eigencloud.xyz";
6197
7343
  var ENVIRONMENTS = {
6198
7344
  "sepolia-dev": {
6199
7345
  name: "sepolia",
6200
7346
  build: "dev",
6201
7347
  appControllerAddress: "0xa86DC1C47cb2518327fB4f9A1627F51966c83B92",
7348
+ releaseAbiVersion: "v1.5",
7349
+ // AppController upgraded to v1.5.x (containerPolicy)
6202
7350
  permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
6203
7351
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
6204
7352
  kmsServerURL: "http://10.128.0.57:8080",
6205
7353
  userApiServerURL: "https://userapi-compute-sepolia-dev.eigencloud.xyz",
6206
7354
  defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com",
6207
7355
  usdcCreditsAddress: "0xbdA3897c3A428763B59015C64AB766c288C97376",
6208
- platformEnv: PLATFORM_ENV_TESTNET_SEPOLIA,
6209
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7356
+ baseUsdcCreditsAddress: "0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac",
7357
+ baseRPCURL: "https://base-sepolia-rpc.publicnode.com"
6210
7358
  },
6211
7359
  sepolia: {
6212
7360
  name: "sepolia",
6213
7361
  build: "prod",
6214
7362
  appControllerAddress: "0x0dd810a6ffba6a9820a10d97b659f07d8d23d4E2",
7363
+ releaseAbiVersion: "v1.4",
7364
+ // prod still on AppController v1.4.0 (3-field Release)
6215
7365
  permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,
6216
7366
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
6217
7367
  kmsServerURL: "http://10.128.15.203:8080",
@@ -6219,21 +7369,21 @@ var ENVIRONMENTS = {
6219
7369
  defaultRPCURL: "https://ethereum-sepolia-rpc.publicnode.com",
6220
7370
  billingRPCURL: "https://ethereum-rpc.publicnode.com",
6221
7371
  usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d",
6222
- platformEnv: PLATFORM_ENV_TESTNET_SEPOLIA,
6223
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7372
+ baseUsdcCreditsAddress: "0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac",
7373
+ baseRPCURL: "https://base-sepolia-rpc.publicnode.com"
6224
7374
  },
6225
7375
  "mainnet-alpha": {
6226
7376
  name: "mainnet-alpha",
6227
7377
  build: "prod",
6228
7378
  appControllerAddress: "0xc38d35Fc995e75342A21CBd6D770305b142Fbe67",
7379
+ releaseAbiVersion: "v1.4",
7380
+ // prod still on AppController v1.4.0 (3-field Release)
6229
7381
  permissionControllerAddress: ChainAddresses[MAINNET_CHAIN_ID].PermissionController,
6230
7382
  erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,
6231
7383
  kmsServerURL: "http://10.128.0.2:8080",
6232
7384
  userApiServerURL: "https://userapi-compute.eigencloud.xyz",
6233
7385
  defaultRPCURL: "https://ethereum-rpc.publicnode.com",
6234
- usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d",
6235
- platformEnv: PLATFORM_ENV_MAINNET_ETHEREUM,
6236
- appBaseDomain: DEFAULT_APP_BASE_DOMAIN
7386
+ usdcCreditsAddress: "0xed9c88640ca9149Bd9f7ee6620074af10F2E145d"
6237
7387
  }
6238
7388
  };
6239
7389
  var CHAIN_ID_TO_ENVIRONMENT = {
@@ -6293,7 +7443,7 @@ function getBillingEnvironmentConfig(build) {
6293
7443
  };
6294
7444
  }
6295
7445
  function getBuildType() {
6296
- const buildTimeType = true ? "dev"?.toLowerCase() : void 0;
7446
+ const buildTimeType = true ? "prod"?.toLowerCase() : void 0;
6297
7447
  const runtimeType = process.env.BUILD_TYPE?.toLowerCase();
6298
7448
  const buildType = buildTimeType || runtimeType;
6299
7449
  if (buildType === "dev") {
@@ -6610,6 +7760,63 @@ var BillingApiClient = class {
6610
7760
  return resp.json();
6611
7761
  }
6612
7762
  // ==========================================================================
7763
+ // Admin - Coupon Methods
7764
+ // ==========================================================================
7765
+ async createCoupon(amountCents) {
7766
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons`;
7767
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { amountCents });
7768
+ return resp.json();
7769
+ }
7770
+ async listCoupons(opts) {
7771
+ const params = new URLSearchParams();
7772
+ if (opts?.offset !== void 0) params.set("offset", opts.offset.toString());
7773
+ if (opts?.limit !== void 0) params.set("limit", opts.limit.toString());
7774
+ if (opts?.active !== void 0) params.set("active", opts.active.toString());
7775
+ if (opts?.redeemed !== void 0) params.set("redeemed", opts.redeemed.toString());
7776
+ const qs = params.toString();
7777
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons${qs ? `?${qs}` : ""}`;
7778
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", "compute");
7779
+ return resp.json();
7780
+ }
7781
+ async getCoupon(id) {
7782
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons/${id}`;
7783
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", "compute");
7784
+ return resp.json();
7785
+ }
7786
+ async deactivateCoupon(id) {
7787
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons/${id}/deactivate`;
7788
+ await this.makeAuthenticatedRequest(endpoint, "POST", "compute");
7789
+ }
7790
+ async redeemCouponForUser(id, address) {
7791
+ const endpoint = `${this.config.billingApiServerURL}/admin/coupons/${id}/redeem`;
7792
+ await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { address });
7793
+ }
7794
+ // ==========================================================================
7795
+ // Admin - Admin Management Methods
7796
+ // ==========================================================================
7797
+ async addAdmin(address) {
7798
+ const endpoint = `${this.config.billingApiServerURL}/admin/admins`;
7799
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { address });
7800
+ return resp.json();
7801
+ }
7802
+ async removeAdmin(address) {
7803
+ const endpoint = `${this.config.billingApiServerURL}/admin/admins/${address}`;
7804
+ await this.makeAuthenticatedRequest(endpoint, "DELETE", "compute");
7805
+ }
7806
+ async listAdmins() {
7807
+ const endpoint = `${this.config.billingApiServerURL}/admin/admins`;
7808
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", "compute");
7809
+ return resp.json();
7810
+ }
7811
+ // ==========================================================================
7812
+ // User - Coupon Redemption
7813
+ // ==========================================================================
7814
+ async redeemCoupon(code) {
7815
+ const endpoint = `${this.config.billingApiServerURL}/v1/coupons/redeem`;
7816
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", "compute", { code });
7817
+ return resp.json();
7818
+ }
7819
+ // ==========================================================================
6613
7820
  // Internal Methods
6614
7821
  // ==========================================================================
6615
7822
  /**
@@ -7025,6 +8232,11 @@ async function prepareDeployFromVerifiableBuild(options, logger = defaultLogger)
7025
8232
  executions: batch.executions,
7026
8233
  authorizationList
7027
8234
  });
8235
+ await assertSufficientGas({
8236
+ publicClient: batch.publicClient,
8237
+ address: batch.walletClient.account.address,
8238
+ gasEstimate
8239
+ });
7028
8240
  const data = {
7029
8241
  appId: batch.appId,
7030
8242
  salt: batch.salt,
@@ -7283,6 +8495,11 @@ async function prepareDeploy(options, logger = defaultLogger) {
7283
8495
  executions: batch.executions,
7284
8496
  authorizationList
7285
8497
  });
8498
+ await assertSufficientGas({
8499
+ publicClient: batch.publicClient,
8500
+ address: batch.walletClient.account.address,
8501
+ gasEstimate
8502
+ });
7286
8503
  const data = {
7287
8504
  appId: batch.appId,
7288
8505
  salt: batch.salt,
@@ -7319,7 +8536,7 @@ async function executeDeploy(options) {
7319
8536
  }
7320
8537
  );
7321
8538
  }
7322
- async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
8539
+ async function watchDeployment(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
7323
8540
  return withSDKTelemetry(
7324
8541
  {
7325
8542
  functionName: "watchDeployment",
@@ -7335,7 +8552,8 @@ async function watchDeployment(appId, walletClient, publicClient, environmentCon
7335
8552
  walletClient,
7336
8553
  publicClient,
7337
8554
  environmentConfig,
7338
- appId
8555
+ appId,
8556
+ timeoutSeconds: opts?.timeoutSeconds
7339
8557
  },
7340
8558
  logger
7341
8559
  );
@@ -7431,6 +8649,11 @@ async function prepareUpgradeFromVerifiableBuild(options, logger = defaultLogger
7431
8649
  executions: batch.executions,
7432
8650
  authorizationList
7433
8651
  });
8652
+ await assertSufficientGas({
8653
+ publicClient: batch.publicClient,
8654
+ address: batch.walletClient.account.address,
8655
+ gasEstimate
8656
+ });
7434
8657
  const data = {
7435
8658
  appId: batch.appId,
7436
8659
  executions: batch.executions,
@@ -7620,6 +8843,11 @@ async function prepareUpgrade(options, logger = defaultLogger) {
7620
8843
  executions: batch.executions,
7621
8844
  authorizationList
7622
8845
  });
8846
+ await assertSufficientGas({
8847
+ publicClient: batch.publicClient,
8848
+ address: batch.walletClient.account.address,
8849
+ gasEstimate
8850
+ });
7623
8851
  const data = {
7624
8852
  appId: batch.appId,
7625
8853
  executions: batch.executions,
@@ -7654,7 +8882,7 @@ async function executeUpgrade(options) {
7654
8882
  }
7655
8883
  );
7656
8884
  }
7657
- async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry) {
8885
+ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig, logger = defaultLogger, skipTelemetry, opts) {
7658
8886
  return withSDKTelemetry(
7659
8887
  {
7660
8888
  functionName: "watchUpgrade",
@@ -7670,7 +8898,8 @@ async function watchUpgrade(appId, walletClient, publicClient, environmentConfig
7670
8898
  walletClient,
7671
8899
  publicClient,
7672
8900
  environmentConfig,
7673
- appId
8901
+ appId,
8902
+ timeoutSeconds: opts?.timeoutSeconds
7674
8903
  },
7675
8904
  logger
7676
8905
  );
@@ -8362,7 +9591,7 @@ function createAppModule(ctx) {
8362
9591
  }
8363
9592
  const account = walletClient.account;
8364
9593
  const environment = getEnvironmentConfig(ctx.environment);
8365
- const logger = getLogger(ctx.verbose);
9594
+ const logger = ctx.logger ?? getLogger(ctx.verbose);
8366
9595
  return {
8367
9596
  async create(opts) {
8368
9597
  return createApp(opts, logger);
@@ -8474,14 +9703,15 @@ function createAppModule(ctx) {
8474
9703
  imageRef: result.imageRef
8475
9704
  };
8476
9705
  },
8477
- async watchDeployment(appId) {
9706
+ async watchDeployment(appId, opts) {
8478
9707
  return watchDeployment(
8479
9708
  appId,
8480
9709
  walletClient,
8481
9710
  publicClient,
8482
9711
  environment,
8483
9712
  logger,
8484
- skipTelemetry
9713
+ skipTelemetry,
9714
+ opts
8485
9715
  );
8486
9716
  },
8487
9717
  // Granular upgrade control
@@ -8539,8 +9769,16 @@ function createAppModule(ctx) {
8539
9769
  imageRef: result.imageRef
8540
9770
  };
8541
9771
  },
8542
- async watchUpgrade(appId) {
8543
- return watchUpgrade(appId, walletClient, publicClient, environment, logger, skipTelemetry);
9772
+ async watchUpgrade(appId, opts) {
9773
+ return watchUpgrade(
9774
+ appId,
9775
+ walletClient,
9776
+ publicClient,
9777
+ environment,
9778
+ logger,
9779
+ skipTelemetry,
9780
+ opts
9781
+ );
8544
9782
  },
8545
9783
  // Profile management
8546
9784
  async setProfile(appId, profile) {
@@ -8815,7 +10053,7 @@ var ERC20_default = [
8815
10053
 
8816
10054
  // src/client/modules/billing/index.ts
8817
10055
  function createBillingModule(config) {
8818
- const { verbose = false, skipTelemetry = false, walletClient, publicClient, environment } = config;
10056
+ const { verbose = false, skipTelemetry = false, walletClient, publicClient, environment, privateKey } = config;
8819
10057
  if (!walletClient.account) {
8820
10058
  throw new Error("WalletClient must have an account attached");
8821
10059
  }
@@ -8824,35 +10062,69 @@ function createBillingModule(config) {
8824
10062
  const billingEnvConfig = getBillingEnvironmentConfig(getBuildType());
8825
10063
  const billingApi = new BillingApiClient(billingEnvConfig, walletClient, { verbose });
8826
10064
  const environmentConfig = getEnvironmentConfig(environment);
8827
- const usdcCreditsAddress = environmentConfig.usdcCreditsAddress;
8828
- if (!usdcCreditsAddress) {
10065
+ if (!environmentConfig.usdcCreditsAddress) {
8829
10066
  throw new Error(`USDCCredits contract address not configured for environment "${environment}"`);
8830
10067
  }
10068
+ const usdcCreditsAddress = environmentConfig.usdcCreditsAddress;
10069
+ const baseUsdcCreditsAddress = environmentConfig.baseUsdcCreditsAddress;
10070
+ const baseRPCURL = environmentConfig.baseRPCURL;
10071
+ function resolveChainConfig(chain) {
10072
+ if (chain === "base") {
10073
+ if (!baseUsdcCreditsAddress || !baseRPCURL) {
10074
+ throw new Error(`Base chain not configured for environment "${environment}"`);
10075
+ }
10076
+ if (!privateKey) {
10077
+ throw new Error("Private key required for Base chain transactions");
10078
+ }
10079
+ const baseClients = createClients({
10080
+ privateKey,
10081
+ rpcUrl: baseRPCURL,
10082
+ chainId: BigInt(BASE_SEPOLIA_CHAIN_ID)
10083
+ });
10084
+ return {
10085
+ pub: baseClients.publicClient,
10086
+ wallet: baseClients.walletClient,
10087
+ creditsAddress: baseUsdcCreditsAddress,
10088
+ envConfig: {
10089
+ ...environmentConfig,
10090
+ chainID: BigInt(BASE_SEPOLIA_CHAIN_ID),
10091
+ defaultRPCURL: baseRPCURL
10092
+ }
10093
+ };
10094
+ }
10095
+ return {
10096
+ pub: publicClient,
10097
+ wallet: walletClient,
10098
+ creditsAddress: usdcCreditsAddress,
10099
+ envConfig: environmentConfig
10100
+ };
10101
+ }
8831
10102
  const module = {
8832
10103
  address,
8833
- async getTopUpInfo() {
8834
- const usdcAddress = await publicClient.readContract({
8835
- address: usdcCreditsAddress,
10104
+ async getTopUpInfo(opts) {
10105
+ const { pub, creditsAddress } = resolveChainConfig(opts?.chain);
10106
+ const usdcAddress = await pub.readContract({
10107
+ address: creditsAddress,
8836
10108
  abi: USDCCredits_default,
8837
10109
  functionName: "usdc"
8838
10110
  });
8839
10111
  const [minimumPurchase, usdcBalance, currentAllowance] = await Promise.all([
8840
- publicClient.readContract({
8841
- address: usdcCreditsAddress,
10112
+ pub.readContract({
10113
+ address: creditsAddress,
8842
10114
  abi: USDCCredits_default,
8843
10115
  functionName: "minimumPurchase"
8844
10116
  }),
8845
- publicClient.readContract({
10117
+ pub.readContract({
8846
10118
  address: usdcAddress,
8847
10119
  abi: ERC20_default,
8848
10120
  functionName: "balanceOf",
8849
10121
  args: [address]
8850
10122
  }),
8851
- publicClient.readContract({
10123
+ pub.readContract({
8852
10124
  address: usdcAddress,
8853
10125
  abi: ERC20_default,
8854
10126
  functionName: "allowance",
8855
- args: [address, usdcCreditsAddress]
10127
+ args: [address, creditsAddress]
8856
10128
  })
8857
10129
  ]);
8858
10130
  return { usdcAddress, minimumPurchase, usdcBalance, currentAllowance };
@@ -8862,11 +10134,12 @@ function createBillingModule(config) {
8862
10134
  {
8863
10135
  functionName: "topUp",
8864
10136
  skipTelemetry,
8865
- properties: { amount: opts.amount.toString() }
10137
+ properties: { amount: opts.amount.toString(), chain: opts.chain || "ethereum" }
8866
10138
  },
8867
10139
  async () => {
8868
10140
  const targetAccount = opts.account ?? address;
8869
- const { usdcAddress, currentAllowance } = await module.getTopUpInfo();
10141
+ const { pub, wallet, creditsAddress, envConfig } = resolveChainConfig(opts.chain);
10142
+ const { usdcAddress, currentAllowance } = await module.getTopUpInfo({ chain: opts.chain });
8870
10143
  const executions = [];
8871
10144
  if (currentAllowance < opts.amount) {
8872
10145
  executions.push({
@@ -8875,12 +10148,12 @@ function createBillingModule(config) {
8875
10148
  callData: encodeFunctionData4({
8876
10149
  abi: ERC20_default,
8877
10150
  functionName: "approve",
8878
- args: [usdcCreditsAddress, opts.amount]
10151
+ args: [creditsAddress, opts.amount]
8879
10152
  })
8880
10153
  });
8881
10154
  }
8882
10155
  executions.push({
8883
- target: usdcCreditsAddress,
10156
+ target: creditsAddress,
8884
10157
  value: 0n,
8885
10158
  callData: encodeFunctionData4({
8886
10159
  abi: USDCCredits_default,
@@ -8890,9 +10163,9 @@ function createBillingModule(config) {
8890
10163
  });
8891
10164
  const txHash = await executeBatch(
8892
10165
  {
8893
- walletClient,
8894
- publicClient,
8895
- environmentConfig,
10166
+ walletClient: wallet,
10167
+ publicClient: pub,
10168
+ environmentConfig: envConfig,
8896
10169
  executions,
8897
10170
  pendingMessage: "Submitting credit purchase..."
8898
10171
  },
@@ -8992,6 +10265,12 @@ function createBillingModule(config) {
8992
10265
  },
8993
10266
  async purchaseCredits(amountCents, paymentMethodId) {
8994
10267
  return billingApi.purchaseCredits(amountCents, paymentMethodId);
10268
+ },
10269
+ hasBaseSupport() {
10270
+ return !!baseUsdcCreditsAddress && !!baseRPCURL;
10271
+ },
10272
+ async redeemCoupon(code) {
10273
+ return billingApi.redeemCoupon(code);
8995
10274
  }
8996
10275
  };
8997
10276
  return module;
@@ -9427,6 +10706,44 @@ function transformVerifyResult(raw) {
9427
10706
  };
9428
10707
  }
9429
10708
 
10709
+ // src/client/modules/admin/index.ts
10710
+ function createAdminModule(config) {
10711
+ const { verbose = false, walletClient } = config;
10712
+ if (!walletClient.account) {
10713
+ throw new Error("WalletClient must have an account attached");
10714
+ }
10715
+ const address = walletClient.account.address;
10716
+ const billingEnvConfig = getBillingEnvironmentConfig(getBuildType());
10717
+ const billingApi = new BillingApiClient(billingEnvConfig, walletClient, { verbose });
10718
+ return {
10719
+ address,
10720
+ async createCoupon(amountCents) {
10721
+ return billingApi.createCoupon(amountCents);
10722
+ },
10723
+ async listCoupons(opts) {
10724
+ return billingApi.listCoupons(opts);
10725
+ },
10726
+ async getCoupon(id) {
10727
+ return billingApi.getCoupon(id);
10728
+ },
10729
+ async deactivateCoupon(id) {
10730
+ return billingApi.deactivateCoupon(id);
10731
+ },
10732
+ async redeemCouponForUser(id, userAddress) {
10733
+ return billingApi.redeemCouponForUser(id, userAddress);
10734
+ },
10735
+ async addAdmin(adminAddress) {
10736
+ return billingApi.addAdmin(adminAddress);
10737
+ },
10738
+ async removeAdmin(adminAddress) {
10739
+ return billingApi.removeAdmin(adminAddress);
10740
+ },
10741
+ async listAdmins() {
10742
+ return billingApi.listAdmins();
10743
+ }
10744
+ };
10745
+ }
10746
+
9430
10747
  // src/client/common/auth/keyring.ts
9431
10748
  import { AsyncEntry, findCredentials } from "@napi-rs/keyring";
9432
10749
  import { privateKeyToAddress } from "viem/accounts";
@@ -9964,14 +11281,17 @@ var JwtProvider = class {
9964
11281
  export {
9965
11282
  AttestClient,
9966
11283
  AuthRequiredError,
11284
+ BASE_SEPOLIA_CHAIN_ID,
9967
11285
  BUILD_STATUS,
9968
11286
  BadRequestError,
9969
11287
  BillingApiClient,
9970
11288
  BuildError,
9971
11289
  BuildFailedError,
9972
11290
  ConflictError,
11291
+ EMPTY_CONTAINER_POLICY,
9973
11292
  ERC20_default as ERC20ABI,
9974
11293
  ForbiddenError,
11294
+ InsufficientGasError,
9975
11295
  JwtProvider,
9976
11296
  NoopClient,
9977
11297
  NotFoundError,
@@ -9981,14 +11301,18 @@ export {
9981
11301
  TimeoutError,
9982
11302
  USDCCredits_default as USDCCreditsABI,
9983
11303
  UserApiClient,
11304
+ WATCH_DEFAULT_TIMEOUT_SECONDS,
11305
+ WatchTimeoutError,
9984
11306
  addHexPrefix,
9985
11307
  addMetric,
9986
11308
  addMetricWithDimensions,
11309
+ assertSufficientGas,
9987
11310
  assertValidFilePath,
9988
11311
  assertValidImageReference,
9989
11312
  assertValidPrivateKey,
9990
11313
  calculateAppID,
9991
11314
  checkERC7702Delegation,
11315
+ createAdminModule,
9992
11316
  createApp,
9993
11317
  createAppEnvironment,
9994
11318
  createBillingModule,