@layr-labs/ecloud-sdk 0.2.1-dev → 0.2.2-dev

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/browser.js CHANGED
@@ -1,3 +1,15 @@
1
+ // src/client/common/types/index.ts
2
+ var noopLogger = {
3
+ debug: () => {
4
+ },
5
+ info: () => {
6
+ },
7
+ warn: () => {
8
+ },
9
+ error: () => {
10
+ }
11
+ };
12
+
1
13
  // src/client/common/config/environment.ts
2
14
  var SEPOLIA_CHAIN_ID = 11155111;
3
15
  var MAINNET_CHAIN_ID = 1;
@@ -12,6 +24,14 @@ var ChainAddresses = {
12
24
  PermissionController: "0x44632dfBdCb6D3E21EF613B0ca8A6A0c618F5a37"
13
25
  }
14
26
  };
27
+ var BILLING_ENVIRONMENTS = {
28
+ dev: {
29
+ billingApiServerURL: "https://billingapi-dev.eigencloud.xyz"
30
+ },
31
+ prod: {
32
+ billingApiServerURL: "https://billingapi.eigencloud.xyz"
33
+ }
34
+ };
15
35
  var ENVIRONMENTS = {
16
36
  "sepolia-dev": {
17
37
  name: "sepolia",
@@ -70,6 +90,13 @@ function getEnvironmentConfig(environment, chainID) {
70
90
  chainID: BigInt(resolvedChainID)
71
91
  };
72
92
  }
93
+ function getBillingEnvironmentConfig(build) {
94
+ const config = BILLING_ENVIRONMENTS[build];
95
+ if (!config) {
96
+ throw new Error(`Unknown billing environment: ${build}`);
97
+ }
98
+ return config;
99
+ }
73
100
  function getBuildType() {
74
101
  const buildTimeType = true ? "dev"?.toLowerCase() : void 0;
75
102
  const runtimeType = process.env.BUILD_TYPE?.toLowerCase();
@@ -103,8 +130,13 @@ import { privateKeyToAccount } from "viem/accounts";
103
130
 
104
131
  // src/client/common/constants.ts
105
132
  import { sepolia, mainnet } from "viem/chains";
133
+ var SUPPORTED_CHAINS = [mainnet, sepolia];
106
134
 
107
135
  // src/client/common/utils/helpers.ts
136
+ function getChainFromID(chainID, fallback = sepolia2) {
137
+ const id = Number(chainID);
138
+ return extractChain({ chains: SUPPORTED_CHAINS, id }) || fallback;
139
+ }
108
140
  function addHexPrefix(value) {
109
141
  return value.startsWith("0x") ? value : `0x${value}`;
110
142
  }
@@ -338,6 +370,160 @@ async function calculatePermissionSignature(options) {
338
370
  });
339
371
  return { signature, digest };
340
372
  }
373
+ var generateBillingSigData = (product, expiry) => {
374
+ return {
375
+ domain: {
376
+ name: "EigenCloud Billing API",
377
+ version: "1"
378
+ },
379
+ types: {
380
+ BillingAuth: [
381
+ { name: "product", type: "string" },
382
+ { name: "expiry", type: "uint256" }
383
+ ]
384
+ },
385
+ primaryType: "BillingAuth",
386
+ message: {
387
+ product,
388
+ expiry
389
+ }
390
+ };
391
+ };
392
+ async function calculateBillingAuthSignature(options) {
393
+ const { walletClient, product, expiry } = options;
394
+ const account = walletClient.account;
395
+ if (!account) {
396
+ throw new Error("WalletClient must have an account attached");
397
+ }
398
+ const signature = await walletClient.signTypedData({
399
+ account,
400
+ ...generateBillingSigData(product, expiry)
401
+ });
402
+ return { signature, expiry };
403
+ }
404
+
405
+ // src/client/common/auth/session.ts
406
+ var SessionError = class extends Error {
407
+ constructor(message, code, statusCode) {
408
+ super(message);
409
+ this.code = code;
410
+ this.statusCode = statusCode;
411
+ this.name = "SessionError";
412
+ }
413
+ };
414
+ function stripHexPrefix2(hex) {
415
+ return hex.startsWith("0x") ? hex.slice(2) : hex;
416
+ }
417
+ async function parseErrorResponse(response) {
418
+ try {
419
+ const data = await response.json();
420
+ return data.error || response.statusText;
421
+ } catch {
422
+ return response.statusText;
423
+ }
424
+ }
425
+ async function loginToComputeApi(config, request) {
426
+ let response;
427
+ try {
428
+ response = await fetch(`${config.baseUrl}/auth/siwe/login`, {
429
+ method: "POST",
430
+ credentials: "include",
431
+ // Include cookies for session management
432
+ headers: {
433
+ "Content-Type": "application/json"
434
+ },
435
+ body: JSON.stringify({
436
+ message: request.message,
437
+ signature: stripHexPrefix2(request.signature)
438
+ })
439
+ });
440
+ } catch (error) {
441
+ throw new SessionError(
442
+ `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,
443
+ "NETWORK_ERROR"
444
+ );
445
+ }
446
+ if (!response.ok) {
447
+ const errorMessage = await parseErrorResponse(response);
448
+ const status = response.status;
449
+ if (status === 400) {
450
+ if (errorMessage.toLowerCase().includes("siwe")) {
451
+ throw new SessionError(`Invalid SIWE message: ${errorMessage}`, "INVALID_MESSAGE", status);
452
+ }
453
+ throw new SessionError(`Bad request: ${errorMessage}`, "INVALID_MESSAGE", status);
454
+ }
455
+ if (status === 401) {
456
+ throw new SessionError(`Invalid signature: ${errorMessage}`, "INVALID_SIGNATURE", status);
457
+ }
458
+ throw new SessionError(`Login failed: ${errorMessage}`, "UNKNOWN", status);
459
+ }
460
+ const data = await response.json();
461
+ return {
462
+ success: data.success,
463
+ address: data.address
464
+ };
465
+ }
466
+ async function getComputeApiSession(config) {
467
+ let response;
468
+ try {
469
+ response = await fetch(`${config.baseUrl}/auth/session`, {
470
+ method: "GET",
471
+ credentials: "include",
472
+ // Include cookies for session management
473
+ headers: {
474
+ "Content-Type": "application/json"
475
+ }
476
+ });
477
+ } catch {
478
+ return {
479
+ authenticated: false
480
+ };
481
+ }
482
+ if (response.status === 401) {
483
+ return {
484
+ authenticated: false
485
+ };
486
+ }
487
+ if (!response.ok) {
488
+ const errorMessage = await parseErrorResponse(response);
489
+ throw new SessionError(`Failed to get session: ${errorMessage}`, "UNKNOWN", response.status);
490
+ }
491
+ const data = await response.json();
492
+ return {
493
+ authenticated: data.authenticated,
494
+ address: data.address,
495
+ chainId: data.chain_id
496
+ };
497
+ }
498
+ async function logoutFromComputeApi(config) {
499
+ let response;
500
+ try {
501
+ response = await fetch(`${config.baseUrl}/auth/logout`, {
502
+ method: "POST",
503
+ credentials: "include",
504
+ // Include cookies for session management
505
+ headers: {
506
+ "Content-Type": "application/json"
507
+ }
508
+ });
509
+ } catch (error) {
510
+ throw new SessionError(
511
+ `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,
512
+ "NETWORK_ERROR"
513
+ );
514
+ }
515
+ if (response.status === 401) {
516
+ return;
517
+ }
518
+ if (!response.ok) {
519
+ const errorMessage = await parseErrorResponse(response);
520
+ throw new SessionError(`Logout failed: ${errorMessage}`, "UNKNOWN", response.status);
521
+ }
522
+ }
523
+ async function isSessionValid(config) {
524
+ const session = await getComputeApiSession(config);
525
+ return session.authenticated;
526
+ }
341
527
 
342
528
  // src/client/common/utils/userapi.ts
343
529
  function isJsonObject(value) {
@@ -356,15 +542,16 @@ var CanViewAppLogsPermission = "0x2fd3f2fe";
356
542
  var CanViewSensitiveAppInfoPermission = "0x0e67b22f";
357
543
  var CanUpdateAppProfilePermission = "0x036fef61";
358
544
  function getDefaultClientId() {
359
- const version = true ? "0.2.1-dev" : "0.0.0";
545
+ const version = true ? "0.2.2-dev" : "0.0.0";
360
546
  return `ecloud-sdk/v${version}`;
361
547
  }
362
548
  var UserApiClient = class {
363
- constructor(config, walletClient, publicClient, clientId) {
549
+ constructor(config, walletClient, publicClient, options) {
364
550
  this.config = config;
365
551
  this.walletClient = walletClient;
366
552
  this.publicClient = publicClient;
367
- this.clientId = clientId || getDefaultClientId();
553
+ this.clientId = options?.clientId || getDefaultClientId();
554
+ this.useSession = options?.useSession ?? false;
368
555
  }
369
556
  /**
370
557
  * Get the address of the connected wallet
@@ -452,7 +639,7 @@ var UserApiClient = class {
452
639
  const apps = result.apps || result.Apps || [];
453
640
  return apps.map((app, i) => ({
454
641
  address: app.address || appIDs[i],
455
- status: app.status || app.Status || ""
642
+ status: app.app_status || app.App_Status || ""
456
643
  }));
457
644
  }
458
645
  /**
@@ -484,9 +671,11 @@ var UserApiClient = class {
484
671
  const headers = {
485
672
  "x-client-id": this.clientId
486
673
  };
487
- const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
488
- const authHeaders = await this.generateAuthHeaders(CanUpdateAppProfilePermission, expiry);
489
- Object.assign(headers, authHeaders);
674
+ if (!this.useSession) {
675
+ const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
676
+ const authHeaders = await this.generateAuthHeaders(CanUpdateAppProfilePermission, expiry);
677
+ Object.assign(headers, authHeaders);
678
+ }
490
679
  try {
491
680
  const response = await axios.post(endpoint, formData, {
492
681
  headers,
@@ -495,8 +684,10 @@ var UserApiClient = class {
495
684
  // Don't throw on any status
496
685
  maxContentLength: Infinity,
497
686
  // Allow large file uploads
498
- maxBodyLength: Infinity
687
+ maxBodyLength: Infinity,
499
688
  // Allow large file uploads
689
+ withCredentials: true
690
+ // Include cookies for session auth
500
691
  });
501
692
  const status = response.status;
502
693
  if (status !== 200 && status !== 201) {
@@ -530,7 +721,7 @@ Please check:
530
721
  const headers = {
531
722
  "x-client-id": this.clientId
532
723
  };
533
- if (permission) {
724
+ if (permission && !this.useSession) {
534
725
  const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
535
726
  const authHeaders = await this.generateAuthHeaders(permission, expiry);
536
727
  Object.assign(headers, authHeaders);
@@ -539,8 +730,10 @@ Please check:
539
730
  const response = await axios.get(url, {
540
731
  headers,
541
732
  maxRedirects: 0,
542
- validateStatus: () => true
733
+ validateStatus: () => true,
543
734
  // Don't throw on any status
735
+ withCredentials: true
736
+ // Include cookies for session auth
544
737
  });
545
738
  const status = response.status;
546
739
  const statusText = status >= 200 && status < 300 ? "OK" : "Error";
@@ -582,6 +775,65 @@ Please check:
582
775
  "X-eigenx-expiry": expiry.toString()
583
776
  };
584
777
  }
778
+ // ==========================================================================
779
+ // SIWE Session Management
780
+ // ==========================================================================
781
+ /**
782
+ * Login to the compute API using SIWE (Sign-In with Ethereum)
783
+ *
784
+ * This establishes a session with the compute API by verifying the SIWE message
785
+ * and signature. On success, a session cookie is set in the browser.
786
+ *
787
+ * @param request - Login request containing SIWE message and signature
788
+ * @returns Login result with the authenticated address
789
+ *
790
+ * @example
791
+ * ```typescript
792
+ * import { createSiweMessage } from "@layr-labs/ecloud-sdk/browser";
793
+ *
794
+ * const { message } = createSiweMessage({
795
+ * address: userAddress,
796
+ * chainId: 11155111,
797
+ * domain: window.location.host,
798
+ * uri: window.location.origin,
799
+ * });
800
+ *
801
+ * const signature = await signMessageAsync({ message });
802
+ * const result = await client.siweLogin({ message, signature });
803
+ * ```
804
+ */
805
+ async siweLogin(request) {
806
+ return loginToComputeApi({ baseUrl: this.config.userApiServerURL }, request);
807
+ }
808
+ /**
809
+ * Logout from the compute API
810
+ *
811
+ * This destroys the current session and clears the session cookie.
812
+ *
813
+ * @example
814
+ * ```typescript
815
+ * await client.siweLogout();
816
+ * ```
817
+ */
818
+ async siweLogout() {
819
+ return logoutFromComputeApi({ baseUrl: this.config.userApiServerURL });
820
+ }
821
+ /**
822
+ * Get the current SIWE session status from the compute API
823
+ *
824
+ * @returns Session information including authentication status and address
825
+ *
826
+ * @example
827
+ * ```typescript
828
+ * const session = await client.getSiweSession();
829
+ * if (session.authenticated) {
830
+ * console.log(`Logged in as ${session.address}`);
831
+ * }
832
+ * ```
833
+ */
834
+ async getSiweSession() {
835
+ return getComputeApiSession({ baseUrl: this.config.userApiServerURL });
836
+ }
585
837
  };
586
838
  function transformAppReleaseBuild(raw) {
587
839
  if (!isJsonObject(raw)) return void 0;
@@ -626,6 +878,241 @@ function transformAppRelease(raw) {
626
878
  };
627
879
  }
628
880
 
881
+ // src/client/common/utils/billingapi.ts
882
+ import axios2 from "axios";
883
+ var BillingApiClient = class {
884
+ constructor(config, walletClient) {
885
+ this.config = config;
886
+ this.walletClient = walletClient;
887
+ }
888
+ /**
889
+ * Get the address of the connected wallet
890
+ */
891
+ get address() {
892
+ const account = this.walletClient.account;
893
+ if (!account) {
894
+ throw new Error("WalletClient must have an account attached");
895
+ }
896
+ return account.address;
897
+ }
898
+ async createSubscription(productId = "compute", options) {
899
+ const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
900
+ const body = options ? {
901
+ success_url: options.successUrl,
902
+ cancel_url: options.cancelUrl
903
+ } : void 0;
904
+ const resp = await this.makeAuthenticatedRequest(endpoint, "POST", productId, body);
905
+ return resp.json();
906
+ }
907
+ async getSubscription(productId = "compute") {
908
+ const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
909
+ const resp = await this.makeAuthenticatedRequest(endpoint, "GET", productId);
910
+ return resp.json();
911
+ }
912
+ async cancelSubscription(productId = "compute") {
913
+ const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;
914
+ await this.makeAuthenticatedRequest(endpoint, "DELETE", productId);
915
+ }
916
+ /**
917
+ * Make an authenticated request to the billing API
918
+ */
919
+ async makeAuthenticatedRequest(url, method, productId, body) {
920
+ const expiry = BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
921
+ const { signature } = await calculateBillingAuthSignature({
922
+ walletClient: this.walletClient,
923
+ product: productId,
924
+ expiry
925
+ });
926
+ const headers = {
927
+ Authorization: `Bearer ${signature}`,
928
+ "X-Account": this.address,
929
+ "X-Expiry": expiry.toString()
930
+ };
931
+ if (body) {
932
+ headers["Content-Type"] = "application/json";
933
+ }
934
+ try {
935
+ const response = await axios2({
936
+ method,
937
+ url,
938
+ headers,
939
+ data: body,
940
+ timeout: 3e4,
941
+ maxRedirects: 0,
942
+ validateStatus: () => true
943
+ // Don't throw on any status
944
+ });
945
+ const status = response.status;
946
+ const statusText = status >= 200 && status < 300 ? "OK" : "Error";
947
+ if (status < 200 || status >= 300) {
948
+ const body2 = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
949
+ throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${body2}`);
950
+ }
951
+ return {
952
+ json: async () => response.data,
953
+ text: async () => typeof response.data === "string" ? response.data : JSON.stringify(response.data)
954
+ };
955
+ } catch (error) {
956
+ if (error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED") || error.message?.includes("ENOTFOUND") || error.cause) {
957
+ const cause = error.cause?.message || error.cause || error.message;
958
+ throw new Error(
959
+ `Failed to connect to BillingAPI at ${url}: ${cause}
960
+ Please check:
961
+ 1. Your internet connection
962
+ 2. The API server is accessible: ${this.config.billingApiServerURL}
963
+ 3. Firewall/proxy settings`
964
+ );
965
+ }
966
+ throw error;
967
+ }
968
+ }
969
+ };
970
+
971
+ // src/client/common/utils/buildapi.ts
972
+ import axios3 from "axios";
973
+ var MAX_RETRIES = 5;
974
+ var INITIAL_BACKOFF_MS = 1e3;
975
+ var MAX_BACKOFF_MS = 3e4;
976
+ async function sleep(ms) {
977
+ return new Promise((resolve) => setTimeout(resolve, ms));
978
+ }
979
+ function getRetryDelay(res, attempt) {
980
+ const retryAfter = res.headers["retry-after"];
981
+ if (retryAfter) {
982
+ const seconds = parseInt(retryAfter, 10);
983
+ if (!isNaN(seconds)) {
984
+ return Math.min(seconds * 1e3, MAX_BACKOFF_MS);
985
+ }
986
+ }
987
+ return Math.min(INITIAL_BACKOFF_MS * Math.pow(2, attempt), MAX_BACKOFF_MS);
988
+ }
989
+ async function requestWithRetry(config) {
990
+ let lastResponse;
991
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
992
+ const res = await axios3({ ...config, validateStatus: () => true });
993
+ lastResponse = res;
994
+ if (res.status !== 429) {
995
+ return res;
996
+ }
997
+ if (attempt < MAX_RETRIES) {
998
+ const delay = getRetryDelay(res, attempt);
999
+ await sleep(delay);
1000
+ }
1001
+ }
1002
+ return lastResponse;
1003
+ }
1004
+ var BuildApiClient = class {
1005
+ constructor(options) {
1006
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
1007
+ this.clientId = options.clientId;
1008
+ this.walletClient = options.walletClient;
1009
+ }
1010
+ /**
1011
+ * Get the address of the connected wallet
1012
+ */
1013
+ get address() {
1014
+ const account = this.walletClient?.account;
1015
+ if (!account) {
1016
+ throw new Error("WalletClient must have an account attached");
1017
+ }
1018
+ return account.address;
1019
+ }
1020
+ async submitBuild(payload) {
1021
+ return this.authenticatedJsonRequest("/builds", "POST", payload);
1022
+ }
1023
+ async getBuild(buildId) {
1024
+ return this.publicJsonRequest(`/builds/${encodeURIComponent(buildId)}`);
1025
+ }
1026
+ async getBuildByDigest(digest) {
1027
+ return this.publicJsonRequest(`/builds/image/${encodeURIComponent(digest)}`);
1028
+ }
1029
+ async verify(identifier) {
1030
+ return this.publicJsonRequest(`/builds/verify/${encodeURIComponent(identifier)}`);
1031
+ }
1032
+ async getLogs(buildId) {
1033
+ return this.authenticatedTextRequest(`/builds/${encodeURIComponent(buildId)}/logs`);
1034
+ }
1035
+ async listBuilds(params) {
1036
+ const res = await requestWithRetry({
1037
+ url: `${this.baseUrl}/builds`,
1038
+ method: "GET",
1039
+ params,
1040
+ headers: this.clientId ? { "x-client-id": this.clientId } : void 0,
1041
+ timeout: 6e4
1042
+ });
1043
+ if (res.status < 200 || res.status >= 300) throw buildApiHttpError(res);
1044
+ return res.data;
1045
+ }
1046
+ async publicJsonRequest(path) {
1047
+ const res = await requestWithRetry({
1048
+ url: `${this.baseUrl}${path}`,
1049
+ method: "GET",
1050
+ headers: this.clientId ? { "x-client-id": this.clientId } : void 0,
1051
+ timeout: 6e4
1052
+ });
1053
+ if (res.status < 200 || res.status >= 300) throw buildApiHttpError(res);
1054
+ return res.data;
1055
+ }
1056
+ async authenticatedJsonRequest(path, method, body) {
1057
+ if (!this.walletClient?.account) {
1058
+ throw new Error("WalletClient with account required for authenticated requests");
1059
+ }
1060
+ const headers = {
1061
+ "Content-Type": "application/json"
1062
+ };
1063
+ if (this.clientId) headers["x-client-id"] = this.clientId;
1064
+ const expiry = BigInt(Math.floor(Date.now() / 1e3) + 60);
1065
+ const { signature } = await calculateBillingAuthSignature({
1066
+ walletClient: this.walletClient,
1067
+ product: "compute",
1068
+ expiry
1069
+ });
1070
+ headers.Authorization = `Bearer ${signature}`;
1071
+ headers["X-eigenx-expiry"] = expiry.toString();
1072
+ headers["X-Account"] = this.address;
1073
+ const res = await requestWithRetry({
1074
+ url: `${this.baseUrl}${path}`,
1075
+ method,
1076
+ headers,
1077
+ data: body,
1078
+ timeout: 6e4
1079
+ });
1080
+ if (res.status < 200 || res.status >= 300) throw buildApiHttpError(res);
1081
+ return res.data;
1082
+ }
1083
+ async authenticatedTextRequest(path) {
1084
+ if (!this.walletClient?.account) {
1085
+ throw new Error("WalletClient with account required for authenticated requests");
1086
+ }
1087
+ const headers = {};
1088
+ if (this.clientId) headers["x-client-id"] = this.clientId;
1089
+ const expiry = BigInt(Math.floor(Date.now() / 1e3) + 60);
1090
+ const { signature } = await calculateBillingAuthSignature({
1091
+ walletClient: this.walletClient,
1092
+ product: "compute",
1093
+ expiry
1094
+ });
1095
+ headers.Authorization = `Bearer ${signature}`;
1096
+ headers["X-eigenx-expiry"] = expiry.toString();
1097
+ headers["X-Account"] = this.address;
1098
+ const res = await requestWithRetry({
1099
+ url: `${this.baseUrl}${path}`,
1100
+ method: "GET",
1101
+ headers,
1102
+ timeout: 6e4,
1103
+ responseType: "text"
1104
+ });
1105
+ if (res.status < 200 || res.status >= 300) throw buildApiHttpError(res);
1106
+ return typeof res.data === "string" ? res.data : JSON.stringify(res.data);
1107
+ }
1108
+ };
1109
+ function buildApiHttpError(res) {
1110
+ const status = res.status;
1111
+ const body = typeof res.data === "string" ? res.data : res.data ? JSON.stringify(res.data) : "";
1112
+ const url = res.config?.url ? ` ${res.config.url}` : "";
1113
+ return new Error(`BuildAPI request failed: ${status}${url} - ${body || "Unknown error"}`);
1114
+ }
1115
+
629
1116
  // src/client/common/contract/eip7702.ts
630
1117
  import { encodeFunctionData, encodeAbiParameters, decodeErrorResult } from "viem";
631
1118
 
@@ -1706,13 +2193,99 @@ async function estimateBatchGas(options) {
1706
2193
  maxCostEth: formatETH(maxCostWei)
1707
2194
  };
1708
2195
  }
2196
+ async function checkERC7702Delegation(publicClient, account, delegatorAddress) {
2197
+ const code = await publicClient.getCode({ address: account });
2198
+ if (!code) {
2199
+ return false;
2200
+ }
2201
+ const expectedCode = `0xef0100${delegatorAddress.slice(2)}`;
2202
+ return code.toLowerCase() === expectedCode.toLowerCase();
2203
+ }
2204
+ async function executeBatch(options, logger = noopLogger) {
2205
+ const { walletClient, publicClient, environmentConfig, executions, pendingMessage, gas } = options;
2206
+ const account = walletClient.account;
2207
+ if (!account) {
2208
+ throw new Error("Wallet client must have an account");
2209
+ }
2210
+ const chain = walletClient.chain;
2211
+ if (!chain) {
2212
+ throw new Error("Wallet client must have a chain");
2213
+ }
2214
+ const executeBatchData = encodeExecuteBatchData(executions);
2215
+ const isDelegated2 = await checkERC7702Delegation(
2216
+ publicClient,
2217
+ account.address,
2218
+ environmentConfig.erc7702DelegatorAddress
2219
+ );
2220
+ let authorizationList = [];
2221
+ if (!isDelegated2) {
2222
+ const transactionNonce = await publicClient.getTransactionCount({
2223
+ address: account.address,
2224
+ blockTag: "pending"
2225
+ });
2226
+ const chainId = await publicClient.getChainId();
2227
+ const authorizationNonce = transactionNonce + 1;
2228
+ logger.debug("Using wallet client signing for EIP-7702 authorization");
2229
+ const signedAuthorization = await walletClient.signAuthorization({
2230
+ account: account.address,
2231
+ contractAddress: environmentConfig.erc7702DelegatorAddress,
2232
+ chainId,
2233
+ nonce: Number(authorizationNonce)
2234
+ });
2235
+ authorizationList = [signedAuthorization];
2236
+ }
2237
+ if (pendingMessage) {
2238
+ logger.info(pendingMessage);
2239
+ }
2240
+ const txRequest = {
2241
+ account: walletClient.account,
2242
+ chain,
2243
+ to: account.address,
2244
+ data: executeBatchData,
2245
+ value: 0n
2246
+ };
2247
+ if (authorizationList.length > 0) {
2248
+ txRequest.authorizationList = authorizationList;
2249
+ }
2250
+ if (gas?.maxFeePerGas) {
2251
+ txRequest.maxFeePerGas = gas.maxFeePerGas;
2252
+ }
2253
+ if (gas?.maxPriorityFeePerGas) {
2254
+ txRequest.maxPriorityFeePerGas = gas.maxPriorityFeePerGas;
2255
+ }
2256
+ const hash = await walletClient.sendTransaction(txRequest);
2257
+ logger.info(`Transaction sent: ${hash}`);
2258
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
2259
+ if (receipt.status === "reverted") {
2260
+ let revertReason = "Unknown reason";
2261
+ try {
2262
+ await publicClient.call({
2263
+ to: account.address,
2264
+ data: executeBatchData,
2265
+ account: account.address
2266
+ });
2267
+ } catch (callError) {
2268
+ if (callError.data) {
2269
+ try {
2270
+ const decoded = decodeErrorResult({
2271
+ abi: ERC7702Delegator_default,
2272
+ data: callError.data
2273
+ });
2274
+ revertReason = `${decoded.errorName}: ${JSON.stringify(decoded.args)}`;
2275
+ } catch {
2276
+ revertReason = callError.message || "Unknown reason";
2277
+ }
2278
+ } else {
2279
+ revertReason = callError.message || "Unknown reason";
2280
+ }
2281
+ }
2282
+ throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);
2283
+ }
2284
+ return hash;
2285
+ }
1709
2286
 
1710
2287
  // src/client/common/contract/caller.ts
1711
- import {
1712
- encodeFunctionData as encodeFunctionData2,
1713
- decodeErrorResult as decodeErrorResult2,
1714
- bytesToHex
1715
- } from "viem";
2288
+ import { encodeFunctionData as encodeFunctionData2, decodeErrorResult as decodeErrorResult2, bytesToHex } from "viem";
1716
2289
 
1717
2290
  // src/client/common/abis/AppController.json
1718
2291
  var AppController_default = [
@@ -2762,57 +3335,1049 @@ var AppController_default = [
2762
3335
  }
2763
3336
  ];
2764
3337
 
2765
- // src/client/common/contract/caller.ts
2766
- function formatETH(wei) {
2767
- const eth = Number(wei) / 1e18;
2768
- const costStr = eth.toFixed(6);
2769
- const trimmed = costStr.replace(/\.?0+$/, "");
2770
- if (trimmed === "0" && wei > 0n) {
2771
- return "<0.000001";
2772
- }
2773
- return trimmed;
2774
- }
2775
- async function estimateTransactionGas(options) {
2776
- const { publicClient, from, to, data, value = 0n } = options;
2777
- const fees = await publicClient.estimateFeesPerGas();
2778
- const gasLimit = await publicClient.estimateGas({
2779
- account: from,
2780
- to,
2781
- data,
2782
- value
2783
- });
2784
- const maxFeePerGas = fees.maxFeePerGas;
2785
- const maxPriorityFeePerGas = fees.maxPriorityFeePerGas;
2786
- const maxCostWei = gasLimit * maxFeePerGas;
2787
- const maxCostEth = formatETH(maxCostWei);
2788
- return {
2789
- gasLimit,
2790
- maxFeePerGas,
2791
- maxPriorityFeePerGas,
2792
- maxCostWei,
2793
- maxCostEth
2794
- };
2795
- }
2796
- async function getActiveAppCount(publicClient, environmentConfig, user) {
2797
- const count = await publicClient.readContract({
2798
- address: environmentConfig.appControllerAddress,
2799
- abi: AppController_default,
2800
- functionName: "getActiveAppCount",
2801
- args: [user]
2802
- });
2803
- return Number(count);
2804
- }
2805
- async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
2806
- const quota = await publicClient.readContract({
2807
- address: environmentConfig.appControllerAddress,
2808
- abi: AppController_default,
2809
- functionName: "getMaxActiveAppsPerUser",
2810
- args: [user]
2811
- });
2812
- return Number(quota);
2813
- }
2814
- async function getAppsByCreator(publicClient, environmentConfig, creator, offset, limit) {
2815
- const result = await publicClient.readContract({
3338
+ // src/client/common/abis/PermissionController.json
3339
+ var PermissionController_default = [
3340
+ {
3341
+ type: "function",
3342
+ name: "acceptAdmin",
3343
+ inputs: [
3344
+ {
3345
+ name: "account",
3346
+ type: "address",
3347
+ internalType: "address"
3348
+ }
3349
+ ],
3350
+ outputs: [],
3351
+ stateMutability: "nonpayable"
3352
+ },
3353
+ {
3354
+ type: "function",
3355
+ name: "addPendingAdmin",
3356
+ inputs: [
3357
+ {
3358
+ name: "account",
3359
+ type: "address",
3360
+ internalType: "address"
3361
+ },
3362
+ {
3363
+ name: "admin",
3364
+ type: "address",
3365
+ internalType: "address"
3366
+ }
3367
+ ],
3368
+ outputs: [],
3369
+ stateMutability: "nonpayable"
3370
+ },
3371
+ {
3372
+ type: "function",
3373
+ name: "canCall",
3374
+ inputs: [
3375
+ {
3376
+ name: "account",
3377
+ type: "address",
3378
+ internalType: "address"
3379
+ },
3380
+ {
3381
+ name: "caller",
3382
+ type: "address",
3383
+ internalType: "address"
3384
+ },
3385
+ {
3386
+ name: "target",
3387
+ type: "address",
3388
+ internalType: "address"
3389
+ },
3390
+ {
3391
+ name: "selector",
3392
+ type: "bytes4",
3393
+ internalType: "bytes4"
3394
+ }
3395
+ ],
3396
+ outputs: [
3397
+ {
3398
+ name: "",
3399
+ type: "bool",
3400
+ internalType: "bool"
3401
+ }
3402
+ ],
3403
+ stateMutability: "nonpayable"
3404
+ },
3405
+ {
3406
+ type: "function",
3407
+ name: "getAdmins",
3408
+ inputs: [
3409
+ {
3410
+ name: "account",
3411
+ type: "address",
3412
+ internalType: "address"
3413
+ }
3414
+ ],
3415
+ outputs: [
3416
+ {
3417
+ name: "",
3418
+ type: "address[]",
3419
+ internalType: "address[]"
3420
+ }
3421
+ ],
3422
+ stateMutability: "view"
3423
+ },
3424
+ {
3425
+ type: "function",
3426
+ name: "getAppointeePermissions",
3427
+ inputs: [
3428
+ {
3429
+ name: "account",
3430
+ type: "address",
3431
+ internalType: "address"
3432
+ },
3433
+ {
3434
+ name: "appointee",
3435
+ type: "address",
3436
+ internalType: "address"
3437
+ }
3438
+ ],
3439
+ outputs: [
3440
+ {
3441
+ name: "",
3442
+ type: "address[]",
3443
+ internalType: "address[]"
3444
+ },
3445
+ {
3446
+ name: "",
3447
+ type: "bytes4[]",
3448
+ internalType: "bytes4[]"
3449
+ }
3450
+ ],
3451
+ stateMutability: "nonpayable"
3452
+ },
3453
+ {
3454
+ type: "function",
3455
+ name: "getAppointees",
3456
+ inputs: [
3457
+ {
3458
+ name: "account",
3459
+ type: "address",
3460
+ internalType: "address"
3461
+ },
3462
+ {
3463
+ name: "target",
3464
+ type: "address",
3465
+ internalType: "address"
3466
+ },
3467
+ {
3468
+ name: "selector",
3469
+ type: "bytes4",
3470
+ internalType: "bytes4"
3471
+ }
3472
+ ],
3473
+ outputs: [
3474
+ {
3475
+ name: "",
3476
+ type: "address[]",
3477
+ internalType: "address[]"
3478
+ }
3479
+ ],
3480
+ stateMutability: "nonpayable"
3481
+ },
3482
+ {
3483
+ type: "function",
3484
+ name: "getPendingAdmins",
3485
+ inputs: [
3486
+ {
3487
+ name: "account",
3488
+ type: "address",
3489
+ internalType: "address"
3490
+ }
3491
+ ],
3492
+ outputs: [
3493
+ {
3494
+ name: "",
3495
+ type: "address[]",
3496
+ internalType: "address[]"
3497
+ }
3498
+ ],
3499
+ stateMutability: "view"
3500
+ },
3501
+ {
3502
+ type: "function",
3503
+ name: "isAdmin",
3504
+ inputs: [
3505
+ {
3506
+ name: "account",
3507
+ type: "address",
3508
+ internalType: "address"
3509
+ },
3510
+ {
3511
+ name: "caller",
3512
+ type: "address",
3513
+ internalType: "address"
3514
+ }
3515
+ ],
3516
+ outputs: [
3517
+ {
3518
+ name: "",
3519
+ type: "bool",
3520
+ internalType: "bool"
3521
+ }
3522
+ ],
3523
+ stateMutability: "view"
3524
+ },
3525
+ {
3526
+ type: "function",
3527
+ name: "isPendingAdmin",
3528
+ inputs: [
3529
+ {
3530
+ name: "account",
3531
+ type: "address",
3532
+ internalType: "address"
3533
+ },
3534
+ {
3535
+ name: "pendingAdmin",
3536
+ type: "address",
3537
+ internalType: "address"
3538
+ }
3539
+ ],
3540
+ outputs: [
3541
+ {
3542
+ name: "",
3543
+ type: "bool",
3544
+ internalType: "bool"
3545
+ }
3546
+ ],
3547
+ stateMutability: "view"
3548
+ },
3549
+ {
3550
+ type: "function",
3551
+ name: "removeAdmin",
3552
+ inputs: [
3553
+ {
3554
+ name: "account",
3555
+ type: "address",
3556
+ internalType: "address"
3557
+ },
3558
+ {
3559
+ name: "admin",
3560
+ type: "address",
3561
+ internalType: "address"
3562
+ }
3563
+ ],
3564
+ outputs: [],
3565
+ stateMutability: "nonpayable"
3566
+ },
3567
+ {
3568
+ type: "function",
3569
+ name: "removeAppointee",
3570
+ inputs: [
3571
+ {
3572
+ name: "account",
3573
+ type: "address",
3574
+ internalType: "address"
3575
+ },
3576
+ {
3577
+ name: "appointee",
3578
+ type: "address",
3579
+ internalType: "address"
3580
+ },
3581
+ {
3582
+ name: "target",
3583
+ type: "address",
3584
+ internalType: "address"
3585
+ },
3586
+ {
3587
+ name: "selector",
3588
+ type: "bytes4",
3589
+ internalType: "bytes4"
3590
+ }
3591
+ ],
3592
+ outputs: [],
3593
+ stateMutability: "nonpayable"
3594
+ },
3595
+ {
3596
+ type: "function",
3597
+ name: "removePendingAdmin",
3598
+ inputs: [
3599
+ {
3600
+ name: "account",
3601
+ type: "address",
3602
+ internalType: "address"
3603
+ },
3604
+ {
3605
+ name: "admin",
3606
+ type: "address",
3607
+ internalType: "address"
3608
+ }
3609
+ ],
3610
+ outputs: [],
3611
+ stateMutability: "nonpayable"
3612
+ },
3613
+ {
3614
+ type: "function",
3615
+ name: "setAppointee",
3616
+ inputs: [
3617
+ {
3618
+ name: "account",
3619
+ type: "address",
3620
+ internalType: "address"
3621
+ },
3622
+ {
3623
+ name: "appointee",
3624
+ type: "address",
3625
+ internalType: "address"
3626
+ },
3627
+ {
3628
+ name: "target",
3629
+ type: "address",
3630
+ internalType: "address"
3631
+ },
3632
+ {
3633
+ name: "selector",
3634
+ type: "bytes4",
3635
+ internalType: "bytes4"
3636
+ }
3637
+ ],
3638
+ outputs: [],
3639
+ stateMutability: "nonpayable"
3640
+ },
3641
+ {
3642
+ type: "function",
3643
+ name: "version",
3644
+ inputs: [],
3645
+ outputs: [
3646
+ {
3647
+ name: "",
3648
+ type: "string",
3649
+ internalType: "string"
3650
+ }
3651
+ ],
3652
+ stateMutability: "view"
3653
+ },
3654
+ {
3655
+ type: "event",
3656
+ name: "AdminRemoved",
3657
+ inputs: [
3658
+ {
3659
+ name: "account",
3660
+ type: "address",
3661
+ indexed: true,
3662
+ internalType: "address"
3663
+ },
3664
+ {
3665
+ name: "admin",
3666
+ type: "address",
3667
+ indexed: false,
3668
+ internalType: "address"
3669
+ }
3670
+ ],
3671
+ anonymous: false
3672
+ },
3673
+ {
3674
+ type: "event",
3675
+ name: "AdminSet",
3676
+ inputs: [
3677
+ {
3678
+ name: "account",
3679
+ type: "address",
3680
+ indexed: true,
3681
+ internalType: "address"
3682
+ },
3683
+ {
3684
+ name: "admin",
3685
+ type: "address",
3686
+ indexed: false,
3687
+ internalType: "address"
3688
+ }
3689
+ ],
3690
+ anonymous: false
3691
+ },
3692
+ {
3693
+ type: "event",
3694
+ name: "AppointeeRemoved",
3695
+ inputs: [
3696
+ {
3697
+ name: "account",
3698
+ type: "address",
3699
+ indexed: true,
3700
+ internalType: "address"
3701
+ },
3702
+ {
3703
+ name: "appointee",
3704
+ type: "address",
3705
+ indexed: true,
3706
+ internalType: "address"
3707
+ },
3708
+ {
3709
+ name: "target",
3710
+ type: "address",
3711
+ indexed: false,
3712
+ internalType: "address"
3713
+ },
3714
+ {
3715
+ name: "selector",
3716
+ type: "bytes4",
3717
+ indexed: false,
3718
+ internalType: "bytes4"
3719
+ }
3720
+ ],
3721
+ anonymous: false
3722
+ },
3723
+ {
3724
+ type: "event",
3725
+ name: "AppointeeSet",
3726
+ inputs: [
3727
+ {
3728
+ name: "account",
3729
+ type: "address",
3730
+ indexed: true,
3731
+ internalType: "address"
3732
+ },
3733
+ {
3734
+ name: "appointee",
3735
+ type: "address",
3736
+ indexed: true,
3737
+ internalType: "address"
3738
+ },
3739
+ {
3740
+ name: "target",
3741
+ type: "address",
3742
+ indexed: false,
3743
+ internalType: "address"
3744
+ },
3745
+ {
3746
+ name: "selector",
3747
+ type: "bytes4",
3748
+ indexed: false,
3749
+ internalType: "bytes4"
3750
+ }
3751
+ ],
3752
+ anonymous: false
3753
+ },
3754
+ {
3755
+ type: "event",
3756
+ name: "PendingAdminAdded",
3757
+ inputs: [
3758
+ {
3759
+ name: "account",
3760
+ type: "address",
3761
+ indexed: true,
3762
+ internalType: "address"
3763
+ },
3764
+ {
3765
+ name: "admin",
3766
+ type: "address",
3767
+ indexed: false,
3768
+ internalType: "address"
3769
+ }
3770
+ ],
3771
+ anonymous: false
3772
+ },
3773
+ {
3774
+ type: "event",
3775
+ name: "PendingAdminRemoved",
3776
+ inputs: [
3777
+ {
3778
+ name: "account",
3779
+ type: "address",
3780
+ indexed: true,
3781
+ internalType: "address"
3782
+ },
3783
+ {
3784
+ name: "admin",
3785
+ type: "address",
3786
+ indexed: false,
3787
+ internalType: "address"
3788
+ }
3789
+ ],
3790
+ anonymous: false
3791
+ },
3792
+ {
3793
+ type: "error",
3794
+ name: "AdminAlreadyPending",
3795
+ inputs: []
3796
+ },
3797
+ {
3798
+ type: "error",
3799
+ name: "AdminAlreadySet",
3800
+ inputs: []
3801
+ },
3802
+ {
3803
+ type: "error",
3804
+ name: "AdminNotPending",
3805
+ inputs: []
3806
+ },
3807
+ {
3808
+ type: "error",
3809
+ name: "AdminNotSet",
3810
+ inputs: []
3811
+ },
3812
+ {
3813
+ type: "error",
3814
+ name: "AppointeeAlreadySet",
3815
+ inputs: []
3816
+ },
3817
+ {
3818
+ type: "error",
3819
+ name: "AppointeeNotSet",
3820
+ inputs: []
3821
+ },
3822
+ {
3823
+ type: "error",
3824
+ name: "CannotHaveZeroAdmins",
3825
+ inputs: []
3826
+ },
3827
+ {
3828
+ type: "error",
3829
+ name: "NotAdmin",
3830
+ inputs: []
3831
+ }
3832
+ ];
3833
+
3834
+ // src/client/common/contract/caller.ts
3835
+ function formatETH(wei) {
3836
+ const eth = Number(wei) / 1e18;
3837
+ const costStr = eth.toFixed(6);
3838
+ const trimmed = costStr.replace(/\.?0+$/, "");
3839
+ if (trimmed === "0" && wei > 0n) {
3840
+ return "<0.000001";
3841
+ }
3842
+ return trimmed;
3843
+ }
3844
+ async function estimateTransactionGas(options) {
3845
+ const { publicClient, from, to, data, value = 0n } = options;
3846
+ const fees = await publicClient.estimateFeesPerGas();
3847
+ const gasLimit = await publicClient.estimateGas({
3848
+ account: from,
3849
+ to,
3850
+ data,
3851
+ value
3852
+ });
3853
+ const maxFeePerGas = fees.maxFeePerGas;
3854
+ const maxPriorityFeePerGas = fees.maxPriorityFeePerGas;
3855
+ const maxCostWei = gasLimit * maxFeePerGas;
3856
+ const maxCostEth = formatETH(maxCostWei);
3857
+ return {
3858
+ gasLimit,
3859
+ maxFeePerGas,
3860
+ maxPriorityFeePerGas,
3861
+ maxCostWei,
3862
+ maxCostEth
3863
+ };
3864
+ }
3865
+ async function calculateAppID(options) {
3866
+ const { publicClient, environmentConfig, ownerAddress, salt } = options;
3867
+ const saltHexString = bytesToHex(salt).slice(2);
3868
+ const paddedSaltHex = saltHexString.padStart(64, "0");
3869
+ const saltHex = `0x${paddedSaltHex}`;
3870
+ const appID = await publicClient.readContract({
3871
+ address: environmentConfig.appControllerAddress,
3872
+ abi: AppController_default,
3873
+ functionName: "calculateAppId",
3874
+ args: [ownerAddress, saltHex]
3875
+ });
3876
+ return appID;
3877
+ }
3878
+ async function prepareDeployBatch(options, logger = noopLogger) {
3879
+ const { walletClient, publicClient, environmentConfig, salt, release, publicLogs } = options;
3880
+ const account = walletClient.account;
3881
+ if (!account) {
3882
+ throw new Error("WalletClient must have an account attached");
3883
+ }
3884
+ logger.info("Calculating app ID...");
3885
+ const appId = await calculateAppID({
3886
+ publicClient,
3887
+ environmentConfig,
3888
+ ownerAddress: account.address,
3889
+ salt
3890
+ });
3891
+ logger.debug(`App ID calculated: ${appId}`);
3892
+ logger.debug(`This address will be used for acceptAdmin call`);
3893
+ const saltHexString = bytesToHex(salt).slice(2);
3894
+ const paddedSaltHex = saltHexString.padStart(64, "0");
3895
+ const saltHex = `0x${paddedSaltHex}`;
3896
+ const releaseForViem = {
3897
+ rmsRelease: {
3898
+ artifacts: release.rmsRelease.artifacts.map((artifact) => ({
3899
+ digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
3900
+ registry: artifact.registry
3901
+ })),
3902
+ upgradeByTime: release.rmsRelease.upgradeByTime
3903
+ },
3904
+ publicEnv: bytesToHex(release.publicEnv),
3905
+ encryptedEnv: bytesToHex(release.encryptedEnv)
3906
+ };
3907
+ const createData = encodeFunctionData2({
3908
+ abi: AppController_default,
3909
+ functionName: "createApp",
3910
+ args: [saltHex, releaseForViem]
3911
+ });
3912
+ const acceptAdminData = encodeFunctionData2({
3913
+ abi: PermissionController_default,
3914
+ functionName: "acceptAdmin",
3915
+ args: [appId]
3916
+ });
3917
+ const executions = [
3918
+ {
3919
+ target: environmentConfig.appControllerAddress,
3920
+ value: 0n,
3921
+ callData: createData
3922
+ },
3923
+ {
3924
+ target: environmentConfig.permissionControllerAddress,
3925
+ value: 0n,
3926
+ callData: acceptAdminData
3927
+ }
3928
+ ];
3929
+ if (publicLogs) {
3930
+ const anyoneCanViewLogsData = encodeFunctionData2({
3931
+ abi: PermissionController_default,
3932
+ functionName: "setAppointee",
3933
+ args: [
3934
+ appId,
3935
+ "0x493219d9949348178af1f58740655951a8cd110c",
3936
+ // AnyoneCanCallAddress
3937
+ "0x57ee1fb74c1087e26446abc4fb87fd8f07c43d8d",
3938
+ // ApiPermissionsTarget
3939
+ "0x2fd3f2fe"
3940
+ // CanViewAppLogsPermission
3941
+ ]
3942
+ });
3943
+ executions.push({
3944
+ target: environmentConfig.permissionControllerAddress,
3945
+ value: 0n,
3946
+ callData: anyoneCanViewLogsData
3947
+ });
3948
+ }
3949
+ return {
3950
+ appId,
3951
+ salt,
3952
+ executions,
3953
+ walletClient,
3954
+ publicClient,
3955
+ environmentConfig
3956
+ };
3957
+ }
3958
+ async function executeDeployBatch(data, context, gas, logger = noopLogger) {
3959
+ const pendingMessage = "Deploying new app...";
3960
+ const txHash = await executeBatch(
3961
+ {
3962
+ walletClient: context.walletClient,
3963
+ publicClient: context.publicClient,
3964
+ environmentConfig: context.environmentConfig,
3965
+ executions: data.executions,
3966
+ pendingMessage,
3967
+ gas
3968
+ },
3969
+ logger
3970
+ );
3971
+ return { appId: data.appId, txHash };
3972
+ }
3973
+ async function deployApp(options, logger = noopLogger) {
3974
+ const prepared = await prepareDeployBatch(options, logger);
3975
+ const data = {
3976
+ appId: prepared.appId,
3977
+ salt: prepared.salt,
3978
+ executions: prepared.executions
3979
+ };
3980
+ const context = {
3981
+ walletClient: prepared.walletClient,
3982
+ publicClient: prepared.publicClient,
3983
+ environmentConfig: prepared.environmentConfig
3984
+ };
3985
+ return executeDeployBatch(data, context, options.gas, logger);
3986
+ }
3987
+ function supportsEIP7702(walletClient) {
3988
+ const account = walletClient.account;
3989
+ if (!account) return false;
3990
+ return account.type === "local";
3991
+ }
3992
+ async function executeDeploySequential(options, logger = noopLogger) {
3993
+ const { walletClient, publicClient, environmentConfig, data, publicLogs, onProgress } = options;
3994
+ const account = walletClient.account;
3995
+ if (!account) {
3996
+ throw new Error("WalletClient must have an account attached");
3997
+ }
3998
+ const chain = getChainFromID(environmentConfig.chainID);
3999
+ const txHashes = {
4000
+ createApp: "0x",
4001
+ acceptAdmin: "0x"
4002
+ };
4003
+ logger.info("Step 1/3: Creating app...");
4004
+ onProgress?.("createApp");
4005
+ const createAppExecution = data.executions[0];
4006
+ const createAppHash = await walletClient.sendTransaction({
4007
+ account,
4008
+ to: createAppExecution.target,
4009
+ data: createAppExecution.callData,
4010
+ value: createAppExecution.value,
4011
+ chain
4012
+ });
4013
+ logger.info(`createApp transaction sent: ${createAppHash}`);
4014
+ const createAppReceipt = await publicClient.waitForTransactionReceipt({ hash: createAppHash });
4015
+ if (createAppReceipt.status === "reverted") {
4016
+ throw new Error(`createApp transaction reverted: ${createAppHash}`);
4017
+ }
4018
+ txHashes.createApp = createAppHash;
4019
+ logger.info(`createApp confirmed in block ${createAppReceipt.blockNumber}`);
4020
+ logger.info("Step 2/3: Accepting admin role...");
4021
+ onProgress?.("acceptAdmin", createAppHash);
4022
+ const acceptAdminExecution = data.executions[1];
4023
+ const acceptAdminHash = await walletClient.sendTransaction({
4024
+ account,
4025
+ to: acceptAdminExecution.target,
4026
+ data: acceptAdminExecution.callData,
4027
+ value: acceptAdminExecution.value,
4028
+ chain
4029
+ });
4030
+ logger.info(`acceptAdmin transaction sent: ${acceptAdminHash}`);
4031
+ const acceptAdminReceipt = await publicClient.waitForTransactionReceipt({
4032
+ hash: acceptAdminHash
4033
+ });
4034
+ if (acceptAdminReceipt.status === "reverted") {
4035
+ throw new Error(`acceptAdmin transaction reverted: ${acceptAdminHash}`);
4036
+ }
4037
+ txHashes.acceptAdmin = acceptAdminHash;
4038
+ logger.info(`acceptAdmin confirmed in block ${acceptAdminReceipt.blockNumber}`);
4039
+ if (publicLogs && data.executions.length > 2) {
4040
+ logger.info("Step 3/3: Setting public logs permission...");
4041
+ onProgress?.("setPublicLogs", acceptAdminHash);
4042
+ const setAppointeeExecution = data.executions[2];
4043
+ const setAppointeeHash = await walletClient.sendTransaction({
4044
+ account,
4045
+ to: setAppointeeExecution.target,
4046
+ data: setAppointeeExecution.callData,
4047
+ value: setAppointeeExecution.value,
4048
+ chain
4049
+ });
4050
+ logger.info(`setAppointee transaction sent: ${setAppointeeHash}`);
4051
+ const setAppointeeReceipt = await publicClient.waitForTransactionReceipt({
4052
+ hash: setAppointeeHash
4053
+ });
4054
+ if (setAppointeeReceipt.status === "reverted") {
4055
+ throw new Error(`setAppointee transaction reverted: ${setAppointeeHash}`);
4056
+ }
4057
+ txHashes.setPublicLogs = setAppointeeHash;
4058
+ logger.info(`setAppointee confirmed in block ${setAppointeeReceipt.blockNumber}`);
4059
+ }
4060
+ onProgress?.("complete", txHashes.setPublicLogs || txHashes.acceptAdmin);
4061
+ logger.info(`Deployment complete! App ID: ${data.appId}`);
4062
+ return {
4063
+ appId: data.appId,
4064
+ txHashes
4065
+ };
4066
+ }
4067
+ async function supportsEIP5792(walletClient) {
4068
+ try {
4069
+ if (typeof walletClient.getCapabilities !== "function") {
4070
+ return false;
4071
+ }
4072
+ const account = walletClient.account;
4073
+ if (!account) return false;
4074
+ const capabilities = await walletClient.getCapabilities({
4075
+ account: account.address
4076
+ });
4077
+ return capabilities !== null && capabilities !== void 0 && Object.keys(capabilities).length > 0;
4078
+ } catch {
4079
+ return false;
4080
+ }
4081
+ }
4082
+ async function executeDeployBatched(options, logger = noopLogger) {
4083
+ const { walletClient, environmentConfig, data, publicLogs, onProgress } = options;
4084
+ const account = walletClient.account;
4085
+ if (!account) {
4086
+ throw new Error("WalletClient must have an account attached");
4087
+ }
4088
+ const chain = getChainFromID(environmentConfig.chainID);
4089
+ const calls = data.executions.map(
4090
+ (execution) => ({
4091
+ to: execution.target,
4092
+ data: execution.callData,
4093
+ value: execution.value
4094
+ })
4095
+ );
4096
+ const filteredCalls = publicLogs ? calls : calls.slice(0, 2);
4097
+ logger.info(`Deploying with EIP-5792 sendCalls (${filteredCalls.length} calls)...`);
4098
+ onProgress?.("createApp");
4099
+ try {
4100
+ const { id: batchId } = await walletClient.sendCalls({
4101
+ account,
4102
+ chain,
4103
+ calls: filteredCalls,
4104
+ forceAtomic: true
4105
+ });
4106
+ logger.info(`Batch submitted with ID: ${batchId}`);
4107
+ onProgress?.("acceptAdmin");
4108
+ let status;
4109
+ let attempts = 0;
4110
+ const maxAttempts = 120;
4111
+ while (attempts < maxAttempts) {
4112
+ try {
4113
+ status = await walletClient.getCallsStatus({ id: batchId });
4114
+ if (status.status === "success" || status.status === "confirmed") {
4115
+ logger.info(`Batch confirmed with ${status.receipts?.length || 0} receipts`);
4116
+ break;
4117
+ }
4118
+ if (status.status === "failed" || status.status === "reverted") {
4119
+ throw new Error(`Batch transaction failed: ${status.status}`);
4120
+ }
4121
+ } catch (statusError) {
4122
+ if (statusError.message?.includes("not supported")) {
4123
+ logger.warn("getCallsStatus not supported, waiting for chain confirmation...");
4124
+ await new Promise((resolve) => setTimeout(resolve, 15e3));
4125
+ break;
4126
+ }
4127
+ throw statusError;
4128
+ }
4129
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
4130
+ attempts++;
4131
+ }
4132
+ if (attempts >= maxAttempts) {
4133
+ throw new Error("Timeout waiting for batch confirmation");
4134
+ }
4135
+ if (publicLogs) {
4136
+ onProgress?.("setPublicLogs");
4137
+ }
4138
+ onProgress?.("complete");
4139
+ const receipts = (status?.receipts || []).map((r) => ({
4140
+ transactionHash: r.transactionHash || r.hash
4141
+ }));
4142
+ logger.info(`Deployment complete! App ID: ${data.appId}`);
4143
+ return {
4144
+ appId: data.appId,
4145
+ batchId,
4146
+ receipts
4147
+ };
4148
+ } catch (error) {
4149
+ if (error.message?.includes("not supported") || error.message?.includes("wallet_sendCalls") || error.code === -32601) {
4150
+ throw new Error("EIP5792_NOT_SUPPORTED");
4151
+ }
4152
+ throw error;
4153
+ }
4154
+ }
4155
+ async function prepareUpgradeBatch(options) {
4156
+ const {
4157
+ walletClient,
4158
+ publicClient,
4159
+ environmentConfig,
4160
+ appID,
4161
+ release,
4162
+ publicLogs,
4163
+ needsPermissionChange
4164
+ } = options;
4165
+ const releaseForViem = {
4166
+ rmsRelease: {
4167
+ artifacts: release.rmsRelease.artifacts.map((artifact) => ({
4168
+ digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, "0")}`,
4169
+ registry: artifact.registry
4170
+ })),
4171
+ upgradeByTime: release.rmsRelease.upgradeByTime
4172
+ },
4173
+ publicEnv: bytesToHex(release.publicEnv),
4174
+ encryptedEnv: bytesToHex(release.encryptedEnv)
4175
+ };
4176
+ const upgradeData = encodeFunctionData2({
4177
+ abi: AppController_default,
4178
+ functionName: "upgradeApp",
4179
+ args: [appID, releaseForViem]
4180
+ });
4181
+ const executions = [
4182
+ {
4183
+ target: environmentConfig.appControllerAddress,
4184
+ value: 0n,
4185
+ callData: upgradeData
4186
+ }
4187
+ ];
4188
+ if (needsPermissionChange) {
4189
+ if (publicLogs) {
4190
+ const addLogsData = encodeFunctionData2({
4191
+ abi: PermissionController_default,
4192
+ functionName: "setAppointee",
4193
+ args: [
4194
+ appID,
4195
+ "0x493219d9949348178af1f58740655951a8cd110c",
4196
+ // AnyoneCanCallAddress
4197
+ "0x57ee1fb74c1087e26446abc4fb87fd8f07c43d8d",
4198
+ // ApiPermissionsTarget
4199
+ "0x2fd3f2fe"
4200
+ // CanViewAppLogsPermission
4201
+ ]
4202
+ });
4203
+ executions.push({
4204
+ target: environmentConfig.permissionControllerAddress,
4205
+ value: 0n,
4206
+ callData: addLogsData
4207
+ });
4208
+ } else {
4209
+ const removeLogsData = encodeFunctionData2({
4210
+ abi: PermissionController_default,
4211
+ functionName: "removeAppointee",
4212
+ args: [
4213
+ appID,
4214
+ "0x493219d9949348178af1f58740655951a8cd110c",
4215
+ // AnyoneCanCallAddress
4216
+ "0x57ee1fb74c1087e26446abc4fb87fd8f07c43d8d",
4217
+ // ApiPermissionsTarget
4218
+ "0x2fd3f2fe"
4219
+ // CanViewAppLogsPermission
4220
+ ]
4221
+ });
4222
+ executions.push({
4223
+ target: environmentConfig.permissionControllerAddress,
4224
+ value: 0n,
4225
+ callData: removeLogsData
4226
+ });
4227
+ }
4228
+ }
4229
+ return {
4230
+ appId: appID,
4231
+ executions,
4232
+ walletClient,
4233
+ publicClient,
4234
+ environmentConfig
4235
+ };
4236
+ }
4237
+ async function executeUpgradeBatch(data, context, gas, logger = noopLogger) {
4238
+ const pendingMessage = `Upgrading app ${data.appId}...`;
4239
+ const txHash = await executeBatch(
4240
+ {
4241
+ walletClient: context.walletClient,
4242
+ publicClient: context.publicClient,
4243
+ environmentConfig: context.environmentConfig,
4244
+ executions: data.executions,
4245
+ pendingMessage,
4246
+ gas
4247
+ },
4248
+ logger
4249
+ );
4250
+ return txHash;
4251
+ }
4252
+ async function upgradeApp(options, logger = noopLogger) {
4253
+ const prepared = await prepareUpgradeBatch(options);
4254
+ const data = {
4255
+ appId: prepared.appId,
4256
+ executions: prepared.executions
4257
+ };
4258
+ const context = {
4259
+ walletClient: prepared.walletClient,
4260
+ publicClient: prepared.publicClient,
4261
+ environmentConfig: prepared.environmentConfig
4262
+ };
4263
+ return executeUpgradeBatch(data, context, options.gas, logger);
4264
+ }
4265
+ async function sendAndWaitForTransaction(options, logger = noopLogger) {
4266
+ const {
4267
+ walletClient,
4268
+ publicClient,
4269
+ environmentConfig,
4270
+ to,
4271
+ data,
4272
+ value = 0n,
4273
+ pendingMessage,
4274
+ txDescription,
4275
+ gas
4276
+ } = options;
4277
+ const account = walletClient.account;
4278
+ if (!account) {
4279
+ throw new Error("WalletClient must have an account attached");
4280
+ }
4281
+ const chain = getChainFromID(environmentConfig.chainID);
4282
+ if (pendingMessage) {
4283
+ logger.info(`
4284
+ ${pendingMessage}`);
4285
+ }
4286
+ const hash = await walletClient.sendTransaction({
4287
+ account,
4288
+ to,
4289
+ data,
4290
+ value,
4291
+ ...gas?.maxFeePerGas && { maxFeePerGas: gas.maxFeePerGas },
4292
+ ...gas?.maxPriorityFeePerGas && {
4293
+ maxPriorityFeePerGas: gas.maxPriorityFeePerGas
4294
+ },
4295
+ chain
4296
+ });
4297
+ logger.info(`Transaction sent: ${hash}`);
4298
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
4299
+ if (receipt.status === "reverted") {
4300
+ let revertReason = "Unknown reason";
4301
+ try {
4302
+ await publicClient.call({
4303
+ to,
4304
+ data,
4305
+ account: account.address
4306
+ });
4307
+ } catch (callError) {
4308
+ if (callError.data) {
4309
+ try {
4310
+ const decoded = decodeErrorResult2({
4311
+ abi: AppController_default,
4312
+ data: callError.data
4313
+ });
4314
+ const formattedError = formatAppControllerError(decoded);
4315
+ revertReason = formattedError.message;
4316
+ } catch {
4317
+ revertReason = callError.message || "Unknown reason";
4318
+ }
4319
+ } else {
4320
+ revertReason = callError.message || "Unknown reason";
4321
+ }
4322
+ }
4323
+ logger.error(`${txDescription} transaction (hash: ${hash}) reverted: ${revertReason}`);
4324
+ throw new Error(`${txDescription} transaction (hash: ${hash}) reverted: ${revertReason}`);
4325
+ }
4326
+ return hash;
4327
+ }
4328
+ function formatAppControllerError(decoded) {
4329
+ const errorName = decoded.errorName;
4330
+ switch (errorName) {
4331
+ case "MaxActiveAppsExceeded":
4332
+ return new Error(
4333
+ "you have reached your app deployment limit. To request access or increase your limit, please visit https://onboarding.eigencloud.xyz/ or reach out to the Eigen team"
4334
+ );
4335
+ case "GlobalMaxActiveAppsExceeded":
4336
+ return new Error(
4337
+ "the platform has reached the maximum number of active apps. please try again later"
4338
+ );
4339
+ case "InvalidPermissions":
4340
+ return new Error("you don't have permission to perform this operation");
4341
+ case "AppAlreadyExists":
4342
+ return new Error("an app with this owner and salt already exists");
4343
+ case "AppDoesNotExist":
4344
+ return new Error("the specified app does not exist");
4345
+ case "InvalidAppStatus":
4346
+ return new Error("the app is in an invalid state for this operation");
4347
+ case "MoreThanOneArtifact":
4348
+ return new Error("only one artifact is allowed per release");
4349
+ case "InvalidSignature":
4350
+ return new Error("invalid signature provided");
4351
+ case "SignatureExpired":
4352
+ return new Error("the provided signature has expired");
4353
+ case "InvalidReleaseMetadataURI":
4354
+ return new Error("invalid release metadata URI provided");
4355
+ case "InvalidShortString":
4356
+ return new Error("invalid short string format");
4357
+ default:
4358
+ return new Error(`contract error: ${errorName}`);
4359
+ }
4360
+ }
4361
+ async function getActiveAppCount(publicClient, environmentConfig, user) {
4362
+ const count = await publicClient.readContract({
4363
+ address: environmentConfig.appControllerAddress,
4364
+ abi: AppController_default,
4365
+ functionName: "getActiveAppCount",
4366
+ args: [user]
4367
+ });
4368
+ return Number(count);
4369
+ }
4370
+ async function getMaxActiveAppsPerUser(publicClient, environmentConfig, user) {
4371
+ const quota = await publicClient.readContract({
4372
+ address: environmentConfig.appControllerAddress,
4373
+ abi: AppController_default,
4374
+ functionName: "getMaxActiveAppsPerUser",
4375
+ args: [user]
4376
+ });
4377
+ return Number(quota);
4378
+ }
4379
+ async function getAppsByCreator(publicClient, environmentConfig, creator, offset, limit) {
4380
+ const result = await publicClient.readContract({
2816
4381
  address: environmentConfig.appControllerAddress,
2817
4382
  abi: AppController_default,
2818
4383
  functionName: "getAppsByCreator",
@@ -2840,7 +4405,13 @@ async function getAllAppsByDeveloper(publicClient, env, developer, pageSize = 10
2840
4405
  const allApps = [];
2841
4406
  const allConfigs = [];
2842
4407
  while (true) {
2843
- const { apps, appConfigs } = await getAppsByDeveloper(publicClient, env, developer, offset, pageSize);
4408
+ const { apps, appConfigs } = await getAppsByDeveloper(
4409
+ publicClient,
4410
+ env,
4411
+ developer,
4412
+ offset,
4413
+ pageSize
4414
+ );
2844
4415
  if (apps.length === 0) break;
2845
4416
  allApps.push(...apps);
2846
4417
  allConfigs.push(...appConfigs);
@@ -2852,6 +4423,74 @@ async function getAllAppsByDeveloper(publicClient, env, developer, pageSize = 10
2852
4423
  appConfigs: allConfigs
2853
4424
  };
2854
4425
  }
4426
+ async function suspend(options, logger = noopLogger) {
4427
+ const { walletClient, publicClient, environmentConfig, account, apps } = options;
4428
+ const suspendData = encodeFunctionData2({
4429
+ abi: AppController_default,
4430
+ functionName: "suspend",
4431
+ args: [account, apps]
4432
+ });
4433
+ const pendingMessage = `Suspending ${apps.length} app(s)...`;
4434
+ return sendAndWaitForTransaction(
4435
+ {
4436
+ walletClient,
4437
+ publicClient,
4438
+ environmentConfig,
4439
+ to: environmentConfig.appControllerAddress,
4440
+ data: suspendData,
4441
+ pendingMessage,
4442
+ txDescription: "Suspend"
4443
+ },
4444
+ logger
4445
+ );
4446
+ }
4447
+ async function isDelegated(options) {
4448
+ const { publicClient, environmentConfig, address } = options;
4449
+ return checkERC7702Delegation(
4450
+ publicClient,
4451
+ address,
4452
+ environmentConfig.erc7702DelegatorAddress
4453
+ );
4454
+ }
4455
+ async function undelegate(options, logger = noopLogger) {
4456
+ const { walletClient, publicClient, environmentConfig } = options;
4457
+ const account = walletClient.account;
4458
+ if (!account) {
4459
+ throw new Error("WalletClient must have an account attached");
4460
+ }
4461
+ const chain = getChainFromID(environmentConfig.chainID);
4462
+ const transactionNonce = await publicClient.getTransactionCount({
4463
+ address: account.address,
4464
+ blockTag: "pending"
4465
+ });
4466
+ const chainId = await publicClient.getChainId();
4467
+ const authorizationNonce = BigInt(transactionNonce) + 1n;
4468
+ logger.debug("Signing undelegate authorization");
4469
+ const signedAuthorization = await walletClient.signAuthorization({
4470
+ contractAddress: "0x0000000000000000000000000000000000000000",
4471
+ chainId,
4472
+ nonce: Number(authorizationNonce),
4473
+ account
4474
+ });
4475
+ const authorizationList = [signedAuthorization];
4476
+ const hash = await walletClient.sendTransaction({
4477
+ account,
4478
+ to: account.address,
4479
+ // Send to self
4480
+ data: "0x",
4481
+ // Empty data
4482
+ value: 0n,
4483
+ authorizationList,
4484
+ chain
4485
+ });
4486
+ logger.info(`Transaction sent: ${hash}`);
4487
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
4488
+ if (receipt.status === "reverted") {
4489
+ logger.error(`Undelegate transaction (hash: ${hash}) reverted`);
4490
+ throw new Error(`Undelegate transaction (hash: ${hash}) reverted`);
4491
+ }
4492
+ return hash;
4493
+ }
2855
4494
 
2856
4495
  // src/client/common/contract/encoders.ts
2857
4496
  import { parseAbi as parseAbi2, encodeFunctionData as encodeFunctionData3 } from "viem";
@@ -2881,32 +4520,395 @@ function encodeTerminateAppData(appId) {
2881
4520
  args: [appId]
2882
4521
  });
2883
4522
  }
4523
+
4524
+ // src/client/common/auth/siwe.ts
4525
+ import { SiweMessage, generateNonce as siweGenerateNonce } from "siwe";
4526
+ var generateNonce = siweGenerateNonce;
4527
+ function createSiweMessage(params) {
4528
+ const now = /* @__PURE__ */ new Date();
4529
+ const nonce = params.nonce || generateNonce();
4530
+ const issuedAt = params.issuedAt || now;
4531
+ const expirationTime = params.expirationTime || new Date(now.getTime() + 24 * 60 * 60 * 1e3);
4532
+ const siweMessage = new SiweMessage({
4533
+ domain: params.domain,
4534
+ address: params.address,
4535
+ statement: params.statement,
4536
+ uri: params.uri,
4537
+ version: "1",
4538
+ chainId: params.chainId,
4539
+ nonce,
4540
+ issuedAt: issuedAt.toISOString(),
4541
+ expirationTime: expirationTime.toISOString(),
4542
+ notBefore: params.notBefore?.toISOString(),
4543
+ requestId: params.requestId,
4544
+ resources: params.resources
4545
+ });
4546
+ return {
4547
+ message: siweMessage.prepareMessage(),
4548
+ params: {
4549
+ address: params.address,
4550
+ chainId: params.chainId,
4551
+ domain: params.domain,
4552
+ uri: params.uri,
4553
+ nonce,
4554
+ issuedAt,
4555
+ statement: params.statement,
4556
+ expirationTime,
4557
+ notBefore: params.notBefore,
4558
+ requestId: params.requestId,
4559
+ resources: params.resources
4560
+ }
4561
+ };
4562
+ }
4563
+ function parseSiweMessage(message) {
4564
+ try {
4565
+ const siweMessage = new SiweMessage(message);
4566
+ return {
4567
+ address: siweMessage.address,
4568
+ chainId: siweMessage.chainId,
4569
+ domain: siweMessage.domain,
4570
+ uri: siweMessage.uri,
4571
+ nonce: siweMessage.nonce,
4572
+ statement: siweMessage.statement,
4573
+ issuedAt: siweMessage.issuedAt ? new Date(siweMessage.issuedAt) : void 0,
4574
+ expirationTime: siweMessage.expirationTime ? new Date(siweMessage.expirationTime) : void 0,
4575
+ notBefore: siweMessage.notBefore ? new Date(siweMessage.notBefore) : void 0,
4576
+ requestId: siweMessage.requestId,
4577
+ resources: siweMessage.resources
4578
+ };
4579
+ } catch {
4580
+ return null;
4581
+ }
4582
+ }
4583
+ function isSiweMessageExpired(params) {
4584
+ if (!params.expirationTime) return false;
4585
+ return /* @__PURE__ */ new Date() > params.expirationTime;
4586
+ }
4587
+ function isSiweMessageNotYetValid(params) {
4588
+ if (!params.notBefore) return false;
4589
+ return /* @__PURE__ */ new Date() < params.notBefore;
4590
+ }
4591
+
4592
+ // src/client/common/hooks/useComputeSession.ts
4593
+ import { useCallback, useEffect, useRef, useState } from "react";
4594
+ function useComputeSession(config) {
4595
+ const {
4596
+ baseUrl,
4597
+ refreshInterval = 6e4,
4598
+ // 1 minute default
4599
+ checkOnMount = true,
4600
+ onSessionExpired,
4601
+ onSessionRefreshed,
4602
+ onError
4603
+ } = config;
4604
+ const [session, setSession] = useState(null);
4605
+ const [isLoading, setIsLoading] = useState(checkOnMount);
4606
+ const [error, setError] = useState(null);
4607
+ const wasAuthenticatedRef = useRef(false);
4608
+ const isMountedRef = useRef(true);
4609
+ const refreshIntervalRef = useRef(null);
4610
+ const apiConfig = { baseUrl };
4611
+ const checkSession = useCallback(async () => {
4612
+ try {
4613
+ const sessionInfo = await getComputeApiSession(apiConfig);
4614
+ if (!isMountedRef.current) {
4615
+ return sessionInfo;
4616
+ }
4617
+ setSession(sessionInfo);
4618
+ setError(null);
4619
+ if (wasAuthenticatedRef.current && !sessionInfo.authenticated) {
4620
+ onSessionExpired?.();
4621
+ }
4622
+ wasAuthenticatedRef.current = sessionInfo.authenticated;
4623
+ if (sessionInfo.authenticated) {
4624
+ onSessionRefreshed?.(sessionInfo);
4625
+ }
4626
+ return sessionInfo;
4627
+ } catch (err) {
4628
+ if (!isMountedRef.current) {
4629
+ throw err;
4630
+ }
4631
+ const sessionError = err instanceof SessionError ? err : new SessionError(`Failed to check session: ${String(err)}`, "UNKNOWN");
4632
+ setError(sessionError);
4633
+ onError?.(sessionError);
4634
+ const fallbackSession = { authenticated: false };
4635
+ setSession(fallbackSession);
4636
+ return fallbackSession;
4637
+ }
4638
+ }, [baseUrl, onSessionExpired, onSessionRefreshed, onError]);
4639
+ const refresh = useCallback(async () => {
4640
+ setIsLoading(true);
4641
+ try {
4642
+ return await checkSession();
4643
+ } finally {
4644
+ if (isMountedRef.current) {
4645
+ setIsLoading(false);
4646
+ }
4647
+ }
4648
+ }, [checkSession]);
4649
+ const login = useCallback(
4650
+ async (params, signMessage) => {
4651
+ setIsLoading(true);
4652
+ setError(null);
4653
+ try {
4654
+ let domain = params.domain;
4655
+ let uri = params.uri;
4656
+ if (typeof window !== "undefined") {
4657
+ domain = domain || window.location.host;
4658
+ uri = uri || window.location.origin;
4659
+ }
4660
+ if (!domain || !uri) {
4661
+ throw new SessionError(
4662
+ "domain and uri are required when not in browser environment",
4663
+ "INVALID_MESSAGE"
4664
+ );
4665
+ }
4666
+ const siweMessage = createSiweMessage({
4667
+ ...params,
4668
+ domain,
4669
+ uri,
4670
+ statement: params.statement || "Sign in to EigenCloud Compute API"
4671
+ });
4672
+ const signature = await signMessage({ message: siweMessage.message });
4673
+ await loginToComputeApi(apiConfig, {
4674
+ message: siweMessage.message,
4675
+ signature
4676
+ });
4677
+ const sessionInfo = await checkSession();
4678
+ if (!isMountedRef.current) {
4679
+ return sessionInfo;
4680
+ }
4681
+ wasAuthenticatedRef.current = sessionInfo.authenticated;
4682
+ return sessionInfo;
4683
+ } catch (err) {
4684
+ if (!isMountedRef.current) {
4685
+ throw err;
4686
+ }
4687
+ const sessionError = err instanceof SessionError ? err : new SessionError(`Login failed: ${String(err)}`, "UNKNOWN");
4688
+ setError(sessionError);
4689
+ onError?.(sessionError);
4690
+ throw sessionError;
4691
+ } finally {
4692
+ if (isMountedRef.current) {
4693
+ setIsLoading(false);
4694
+ }
4695
+ }
4696
+ },
4697
+ [baseUrl, checkSession, onError]
4698
+ );
4699
+ const logout = useCallback(async () => {
4700
+ setIsLoading(true);
4701
+ setError(null);
4702
+ try {
4703
+ await logoutFromComputeApi(apiConfig);
4704
+ if (!isMountedRef.current) {
4705
+ return;
4706
+ }
4707
+ const newSession = { authenticated: false };
4708
+ setSession(newSession);
4709
+ wasAuthenticatedRef.current = false;
4710
+ } catch (err) {
4711
+ if (!isMountedRef.current) {
4712
+ throw err;
4713
+ }
4714
+ const sessionError = err instanceof SessionError ? err : new SessionError(`Logout failed: ${String(err)}`, "UNKNOWN");
4715
+ setError(sessionError);
4716
+ onError?.(sessionError);
4717
+ setSession({ authenticated: false });
4718
+ wasAuthenticatedRef.current = false;
4719
+ } finally {
4720
+ if (isMountedRef.current) {
4721
+ setIsLoading(false);
4722
+ }
4723
+ }
4724
+ }, [baseUrl, onError]);
4725
+ const clearError = useCallback(() => {
4726
+ setError(null);
4727
+ }, []);
4728
+ useEffect(() => {
4729
+ isMountedRef.current = true;
4730
+ if (checkOnMount) {
4731
+ checkSession().finally(() => {
4732
+ if (isMountedRef.current) {
4733
+ setIsLoading(false);
4734
+ }
4735
+ });
4736
+ }
4737
+ return () => {
4738
+ isMountedRef.current = false;
4739
+ };
4740
+ }, [checkOnMount, checkSession]);
4741
+ useEffect(() => {
4742
+ if (refreshInterval <= 0) {
4743
+ return;
4744
+ }
4745
+ if (refreshIntervalRef.current) {
4746
+ clearInterval(refreshIntervalRef.current);
4747
+ }
4748
+ refreshIntervalRef.current = setInterval(() => {
4749
+ if (wasAuthenticatedRef.current) {
4750
+ checkSession();
4751
+ }
4752
+ }, refreshInterval);
4753
+ return () => {
4754
+ if (refreshIntervalRef.current) {
4755
+ clearInterval(refreshIntervalRef.current);
4756
+ refreshIntervalRef.current = null;
4757
+ }
4758
+ };
4759
+ }, [refreshInterval, checkSession]);
4760
+ return {
4761
+ session,
4762
+ isLoading,
4763
+ error,
4764
+ isAuthenticated: session?.authenticated ?? false,
4765
+ login,
4766
+ logout,
4767
+ refresh,
4768
+ clearError
4769
+ };
4770
+ }
4771
+
4772
+ // src/client/common/encryption/kms.ts
4773
+ import { importSPKI, CompactEncrypt } from "jose";
4774
+ function getAppProtectedHeaders(appID) {
4775
+ return {
4776
+ "x-eigenx-app-id": appID
4777
+ };
4778
+ }
4779
+ async function encryptRSAOAEPAndAES256GCM(encryptionKeyPEM, plaintext, protectedHeaders) {
4780
+ const pemString = typeof encryptionKeyPEM === "string" ? encryptionKeyPEM : encryptionKeyPEM.toString("utf-8");
4781
+ const publicKey = await importSPKI(pemString, "RSA-OAEP-256", {
4782
+ extractable: true
4783
+ });
4784
+ const header = {
4785
+ alg: "RSA-OAEP-256",
4786
+ // Key encryption algorithm (SHA-256)
4787
+ enc: "A256GCM",
4788
+ // Content encryption algorithm
4789
+ ...protectedHeaders || {}
4790
+ // Add custom protected headers
4791
+ };
4792
+ const plaintextBytes = new Uint8Array(plaintext);
4793
+ const jwe = await new CompactEncrypt(plaintextBytes).setProtectedHeader(header).encrypt(publicKey);
4794
+ return jwe;
4795
+ }
4796
+
4797
+ // keys/mainnet-alpha/prod/kms-encryption-public-key.pem
4798
+ 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-----";
4799
+
4800
+ // keys/mainnet-alpha/prod/kms-signing-public-key.pem
4801
+ var kms_signing_public_key_default = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfxbhXJjH4D0DH/iW5/rK1HzWS+f9\nEyooZTrCYjCfezuOEmRuOWNaZLvwXN8SdzrvjWA7gSvOS85hLzp4grANRQ==\n-----END PUBLIC KEY-----";
4802
+
4803
+ // keys/sepolia/dev/kms-encryption-public-key.pem
4804
+ var kms_encryption_public_key_default2 = "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr/vqttU6aXX35HtsXavU\n5teysunDzZB3HyaFM4qcuRnqj+70KxqLOwZsERN5SwZ/56Jm8T2ds1CcXsQCMUMw\n+MPlsF6KMGfzghLtYHONwvKLnn+U9y886aAay6W8a0A7O7YCZehNYD3kQnCXjOIc\nMj6v8AEvMw+w/lNabjRXnwSBMKVIGp/cSL0hGwt8fGoC3TsxQN9opzvU1Z4rAw9K\na119l6dlPnqezDva378TCaXDjqKe/jSZOI1CcYpaSK2SJ+95Wbvte5j3lXbg1oT2\n0rXeJUHEJ68QxMtJplfw0Sg+Ek4CUJ2c/kbdg0u7sIIO5wcB4WHL/Lfbw2XPmcBI\nt0r0EC575D3iHF/aI01Ms2IRA0GDeHnNcr5FJLWJljTjNLEt4tFITrXwBe1Ealm3\nNCxamApl5bBSwQ72Gb5fiQFwB8Fl2/XG3wfGTFInFEvWE4c/H8dtu1wHTsyEFZcG\nB47IkD5GBSZq90Hd9xuZva55dxGpqUVrEJO88SqHGP9Oa+HLTYdEe5AR5Hitw4Mu\ndk1cCH+X5OqY9dfpdoCNbKAM0N2SJvNAnDTU2JKGYheXrnDslXR6atBmU5gDkH+W\nQVryDYl9xbwWIACMQsAQjrrtKw5xqJ4V89+06FN/wyEVF7KWAcJ4AhKiVnCvLqzb\nBbISc+gOkRsefhCDJVPEKDkCAwEAAQ==\n-----END PUBLIC KEY-----";
4805
+
4806
+ // keys/sepolia/dev/kms-signing-public-key.pem
4807
+ var kms_signing_public_key_default2 = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb2Q88/cxdic2xi4jS2V0dtYHjLwq\n4wVFBFmaY8TTXoMXNggKEdU6PuE8EovocVKMpw3SIlaM27z9uxksNVL2xw==\n-----END PUBLIC KEY-----\n";
4808
+
4809
+ // keys/sepolia/prod/kms-encryption-public-key.pem
4810
+ var kms_encryption_public_key_default3 = "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApDvk8pAivkgtiC5li5MP\nxMTJDduTeorBl18ynrooTxp2BwwgPwXfXbJaCA0qRubvc0aO2uh2VDrPM27CqMLH\no2S9YLtpLii4A1Nl7SE/MdWKWdG6v94xNGpc2YyPP7yWtHfqOkgDWp8sokl3Uq/9\nMS0pjUaI7RyS5boCTy8Qw90BxGMpucjOmqm+luw4EdPWZCrgriUR2bbGRRgAmrT1\nK4ou4IgPp799r120hwHbCWxnOvLdQdpiv2507b900xS/3yZahhnHCAn66146LU/f\nBrRpQKSM0qSpktXrrc9MH/ru2VLR5cGLp89ZcZMQA9cRGglWM5XWVY3Ti2TPJ6Kd\nAn1d7qNkGJaSdVa3x3HkOf6c6HeTyqis5/L/6L+PFhUsTRbmKg1FtwD+3xxdyf7h\nabFxryE9rv+WatHL6r6z5ztV0znJ/Fpfs5A45FWA6pfb28fA59RGpi/DQ8RxgdCH\nnZRNvdz8dTgRaXSPgkfGXBcCFqb/QhFmad7XbWDthGzfhbPOxNPtiaGRQ1Dr/Pgq\nn0ugdLbRQLmDOAFgaQcnr0U4y1TUlWJnvoZMETkVN7gmITtXA4F324ALT7Rd+Lgk\nHikW5vG+NjAEwXfPsK0YzT+VbHd7o1lbru9UxiDlN03XVEkz/oRQi47CvSTo3FSr\n5dB4lz8kov3UUcNJfQFZolMCAwEAAQ==\n-----END PUBLIC KEY-----";
4811
+
4812
+ // keys/sepolia/prod/kms-signing-public-key.pem
4813
+ var kms_signing_public_key_default3 = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsk6ZdmmvBqFfKHs+1cYjIemRGN7h\n1NatIEitFRyx+3q8wmTJ9LknTE1FwWBLcCNTseJDti8Rh+SaVxfGOyJuuA==\n-----END PUBLIC KEY-----";
4814
+
4815
+ // src/client/common/utils/keys.ts
4816
+ var KEYS = {
4817
+ "mainnet-alpha": {
4818
+ prod: {
4819
+ encryption: kms_encryption_public_key_default,
4820
+ signing: kms_signing_public_key_default
4821
+ }
4822
+ },
4823
+ sepolia: {
4824
+ dev: {
4825
+ encryption: kms_encryption_public_key_default2,
4826
+ signing: kms_signing_public_key_default2
4827
+ },
4828
+ prod: {
4829
+ encryption: kms_encryption_public_key_default3,
4830
+ signing: kms_signing_public_key_default3
4831
+ }
4832
+ }
4833
+ };
4834
+ function getKMSKeysForEnvironment(environment, build = "prod") {
4835
+ const envKeys = KEYS[environment];
4836
+ if (!envKeys) {
4837
+ throw new Error(`No keys found for environment: ${environment}`);
4838
+ }
4839
+ const buildKeys = envKeys[build];
4840
+ if (!buildKeys) {
4841
+ throw new Error(`No keys found for environment: ${environment}, build: ${build}`);
4842
+ }
4843
+ return {
4844
+ encryptionKey: Buffer.from(buildKeys.encryption),
4845
+ signingKey: Buffer.from(buildKeys.signing)
4846
+ };
4847
+ }
2884
4848
  export {
4849
+ BillingApiClient,
4850
+ BuildApiClient,
4851
+ SessionError,
2885
4852
  UserApiClient,
4853
+ addHexPrefix,
2886
4854
  assertValidImageReference,
2887
4855
  assertValidPrivateKey,
4856
+ calculateAppID,
4857
+ checkERC7702Delegation,
4858
+ createSiweMessage,
4859
+ deployApp,
2888
4860
  encodeStartAppData,
2889
4861
  encodeStopAppData,
2890
4862
  encodeTerminateAppData,
4863
+ encryptRSAOAEPAndAES256GCM,
2891
4864
  estimateBatchGas,
2892
4865
  estimateTransactionGas,
4866
+ executeBatch,
4867
+ executeDeployBatch,
4868
+ executeDeployBatched,
4869
+ executeDeploySequential,
4870
+ executeUpgradeBatch,
2893
4871
  extractAppNameFromImage,
2894
4872
  formatETH,
2895
4873
  generateNewPrivateKey,
4874
+ generateNonce,
2896
4875
  getActiveAppCount,
2897
4876
  getAllAppsByDeveloper,
4877
+ getAppProtectedHeaders,
2898
4878
  getAppsByCreator,
2899
4879
  getAppsByDeveloper,
2900
4880
  getAvailableEnvironments,
4881
+ getBillingEnvironmentConfig,
2901
4882
  getBuildType,
4883
+ getChainFromID,
4884
+ getComputeApiSession,
2902
4885
  getEnvironmentConfig,
4886
+ getKMSKeysForEnvironment,
2903
4887
  getMaxActiveAppsPerUser,
4888
+ isDelegated,
2904
4889
  isEnvironmentAvailable,
2905
4890
  isMainnet,
4891
+ isSessionValid,
4892
+ isSiweMessageExpired,
4893
+ isSiweMessageNotYetValid,
2906
4894
  isSubscriptionActive,
4895
+ loginToComputeApi,
4896
+ logoutFromComputeApi,
4897
+ noopLogger,
4898
+ parseSiweMessage,
4899
+ prepareDeployBatch,
4900
+ prepareUpgradeBatch,
2907
4901
  sanitizeString,
2908
4902
  sanitizeURL,
2909
4903
  sanitizeXURL,
4904
+ sendAndWaitForTransaction,
4905
+ stripHexPrefix,
4906
+ supportsEIP5792,
4907
+ supportsEIP7702,
4908
+ suspend,
4909
+ undelegate,
4910
+ upgradeApp,
4911
+ useComputeSession,
2910
4912
  validateAppID,
2911
4913
  validateAppName,
2912
4914
  validateCreateAppParams,