@layr-labs/ecloud-sdk 0.1.2 → 0.2.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -30,10 +30,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AuthRequiredError: () => AuthRequiredError,
34
+ BUILD_STATUS: () => BUILD_STATUS,
35
+ BadRequestError: () => BadRequestError,
36
+ BuildError: () => BuildError,
37
+ BuildFailedError: () => BuildFailedError,
38
+ ConflictError: () => ConflictError,
39
+ ForbiddenError: () => ForbiddenError,
33
40
  NoopClient: () => NoopClient,
41
+ NotFoundError: () => NotFoundError,
34
42
  PRIMARY_LANGUAGES: () => PRIMARY_LANGUAGES,
35
43
  PostHogClient: () => PostHogClient,
44
+ TimeoutError: () => TimeoutError,
36
45
  UserApiClient: () => UserApiClient,
46
+ addHexPrefix: () => addHexPrefix,
37
47
  addMetric: () => addMetric,
38
48
  addMetricWithDimensions: () => addMetricWithDimensions,
39
49
  assertValidFilePath: () => assertValidFilePath,
@@ -43,6 +53,7 @@ __export(index_exports, {
43
53
  createApp: () => createApp,
44
54
  createAppEnvironment: () => createAppEnvironment,
45
55
  createBillingModule: () => createBillingModule,
56
+ createBuildModule: () => createBuildModule,
46
57
  createComputeModule: () => createComputeModule,
47
58
  createECloudClient: () => createECloudClient,
48
59
  createMetricsContext: () => createMetricsContext,
@@ -86,12 +97,15 @@ __export(index_exports, {
86
97
  listStoredKeys: () => listStoredKeys,
87
98
  logs: () => logs,
88
99
  prepareDeploy: () => prepareDeploy,
100
+ prepareDeployFromVerifiableBuild: () => prepareDeployFromVerifiableBuild,
89
101
  prepareUpgrade: () => prepareUpgrade,
102
+ prepareUpgradeFromVerifiableBuild: () => prepareUpgradeFromVerifiableBuild,
90
103
  requirePrivateKey: () => requirePrivateKey,
91
104
  sanitizeString: () => sanitizeString,
92
105
  sanitizeURL: () => sanitizeURL,
93
106
  sanitizeXURL: () => sanitizeXURL,
94
107
  storePrivateKey: () => storePrivateKey,
108
+ stripHexPrefix: () => stripHexPrefix,
95
109
  validateAppID: () => validateAppID,
96
110
  validateAppName: () => validateAppName,
97
111
  validateCreateAppParams: () => validateCreateAppParams,
@@ -207,7 +221,7 @@ function getBillingEnvironmentConfig(build) {
207
221
  return config;
208
222
  }
209
223
  function getBuildType() {
210
- const buildTimeType = true ? "prod"?.toLowerCase() : void 0;
224
+ const buildTimeType = true ? "dev"?.toLowerCase() : void 0;
211
225
  const runtimeType = process.env.BUILD_TYPE?.toLowerCase();
212
226
  const buildType = buildTimeType || runtimeType;
213
227
  if (buildType === "dev") {
@@ -425,9 +439,9 @@ async function pushDockerImage(docker, imageRef, logger) {
425
439
  if (!output.includes("digest:") && !output.includes("pushed") && !output.includes("Pushed")) {
426
440
  logger?.debug?.("No clear success indicator in push output, verifying...");
427
441
  }
428
- logger?.info?.("Image push completed successfully");
429
442
  try {
430
443
  await verifyImageExists(imageRef, logger);
444
+ logger?.info?.("Image push completed successfully");
431
445
  resolve2();
432
446
  } catch (error) {
433
447
  reject(error);
@@ -1266,6 +1280,67 @@ Please verify the image exists: docker manifest inspect ${finalImageRef}`
1266
1280
  };
1267
1281
  }
1268
1282
 
1283
+ // src/client/common/release/prebuilt.ts
1284
+ async function createReleaseFromImageDigest(options, logger) {
1285
+ const { imageRef, imageDigest, envFilePath, instanceType, environmentConfig, appId } = options;
1286
+ if (!/^sha256:[0-9a-f]{64}$/i.test(imageDigest)) {
1287
+ throw new Error(`imageDigest must be in format sha256:<64 hex>, got: ${imageDigest}`);
1288
+ }
1289
+ let publicEnv = {};
1290
+ let privateEnv = {};
1291
+ if (envFilePath) {
1292
+ logger.info("Parsing environment file...");
1293
+ const parsed = parseAndValidateEnvFile(envFilePath);
1294
+ publicEnv = parsed.public;
1295
+ privateEnv = parsed.private;
1296
+ } else {
1297
+ logger.info("Continuing without environment file");
1298
+ }
1299
+ publicEnv["EIGEN_MACHINE_TYPE_PUBLIC"] = instanceType;
1300
+ logger.info(`Instance type: ${instanceType}`);
1301
+ logger.info("Encrypting environment variables...");
1302
+ const { encryptionKey } = getKMSKeysForEnvironment(
1303
+ environmentConfig.name,
1304
+ environmentConfig.build
1305
+ );
1306
+ const protectedHeaders = getAppProtectedHeaders(appId);
1307
+ const privateEnvBytes = Buffer.from(JSON.stringify(privateEnv));
1308
+ const encryptedEnvStr = await encryptRSAOAEPAndAES256GCM(
1309
+ encryptionKey,
1310
+ privateEnvBytes,
1311
+ protectedHeaders
1312
+ );
1313
+ const digestHex = imageDigest.split(":")[1];
1314
+ const digestBytes = new Uint8Array(Buffer.from(digestHex, "hex"));
1315
+ if (digestBytes.length !== 32) {
1316
+ throw new Error(`Digest must be exactly 32 bytes, got ${digestBytes.length}`);
1317
+ }
1318
+ const registry = extractRegistryNameNoDocker(imageRef);
1319
+ return {
1320
+ rmsRelease: {
1321
+ artifacts: [{ digest: digestBytes, registry }],
1322
+ upgradeByTime: Math.floor(Date.now() / 1e3) + 3600
1323
+ },
1324
+ publicEnv: new Uint8Array(Buffer.from(JSON.stringify(publicEnv))),
1325
+ encryptedEnv: new Uint8Array(Buffer.from(encryptedEnvStr))
1326
+ };
1327
+ }
1328
+ function extractRegistryNameNoDocker(imageRef) {
1329
+ let name = imageRef;
1330
+ const tagIndex = name.lastIndexOf(":");
1331
+ if (tagIndex !== -1 && !name.substring(tagIndex + 1).includes("/")) {
1332
+ name = name.substring(0, tagIndex);
1333
+ }
1334
+ const digestIndex = name.indexOf("@");
1335
+ if (digestIndex !== -1) {
1336
+ name = name.substring(0, digestIndex);
1337
+ }
1338
+ if ([...name].filter((c) => c === "/").length === 1) {
1339
+ name = `docker.io/${name}`;
1340
+ }
1341
+ return name;
1342
+ }
1343
+
1269
1344
  // src/client/common/contract/caller.ts
1270
1345
  var import_accounts2 = require("viem/accounts");
1271
1346
 
@@ -2302,27 +2377,55 @@ var ERC7702Delegator_default = [
2302
2377
  ];
2303
2378
 
2304
2379
  // src/client/common/contract/eip7702.ts
2380
+ var EXECUTE_BATCH_MODE = "0x0100000000000000000000000000000000000000000000000000000000000000";
2381
+ var GAS_LIMIT_BUFFER_PERCENTAGE = 20n;
2382
+ var GAS_PRICE_BUFFER_PERCENTAGE = 100n;
2383
+ function encodeExecuteBatchData(executions) {
2384
+ const encodedExecutions = (0, import_viem.encodeAbiParameters)(
2385
+ [
2386
+ {
2387
+ type: "tuple[]",
2388
+ components: [
2389
+ { name: "target", type: "address" },
2390
+ { name: "value", type: "uint256" },
2391
+ { name: "callData", type: "bytes" }
2392
+ ]
2393
+ }
2394
+ ],
2395
+ [executions]
2396
+ );
2397
+ return (0, import_viem.encodeFunctionData)({
2398
+ abi: ERC7702Delegator_default,
2399
+ functionName: "execute",
2400
+ args: [EXECUTE_BATCH_MODE, encodedExecutions]
2401
+ });
2402
+ }
2305
2403
  async function estimateBatchGas(options) {
2306
- const { publicClient, executions } = options;
2307
- const fees = await publicClient.estimateFeesPerGas();
2308
- const baseGas = 100000n;
2309
- const perExecutionGas = 50000n;
2310
- const estimatedGas = baseGas + BigInt(executions.length) * perExecutionGas;
2311
- const gasLimit = estimatedGas * 120n / 100n;
2312
- const maxFeePerGas = fees.maxFeePerGas;
2313
- const maxPriorityFeePerGas = fees.maxPriorityFeePerGas;
2404
+ const { publicClient, account, executions } = options;
2405
+ const executeBatchData = encodeExecuteBatchData(executions);
2406
+ const [gasTipCap, block, estimatedGas] = await Promise.all([
2407
+ publicClient.estimateMaxPriorityFeePerGas(),
2408
+ publicClient.getBlock(),
2409
+ publicClient.estimateGas({
2410
+ account,
2411
+ to: account,
2412
+ data: executeBatchData
2413
+ })
2414
+ ]);
2415
+ const baseFee = block.baseFeePerGas ?? 0n;
2416
+ const maxFeePerGas = (baseFee + gasTipCap) * (100n + GAS_PRICE_BUFFER_PERCENTAGE) / 100n;
2417
+ const gasLimit = estimatedGas * (100n + GAS_LIMIT_BUFFER_PERCENTAGE) / 100n;
2314
2418
  const maxCostWei = gasLimit * maxFeePerGas;
2315
- const maxCostEth = formatETH(maxCostWei);
2316
2419
  return {
2317
2420
  gasLimit,
2318
2421
  maxFeePerGas,
2319
- maxPriorityFeePerGas,
2422
+ maxPriorityFeePerGas: gasTipCap,
2320
2423
  maxCostWei,
2321
- maxCostEth
2424
+ maxCostEth: formatETH(maxCostWei)
2322
2425
  };
2323
2426
  }
2324
2427
  async function checkERC7702Delegation(publicClient, account, delegatorAddress) {
2325
- const code = await publicClient.getBytecode({ address: account });
2428
+ const code = await publicClient.getCode({ address: account });
2326
2429
  if (!code) {
2327
2430
  return false;
2328
2431
  }
@@ -2339,36 +2442,7 @@ async function executeBatch(options, logger) {
2339
2442
  if (!chain) {
2340
2443
  throw new Error("Wallet client must have a chain");
2341
2444
  }
2342
- const encodedExecutions = (0, import_viem.encodeAbiParameters)(
2343
- [
2344
- {
2345
- type: "tuple[]",
2346
- components: [
2347
- { name: "target", type: "address" },
2348
- { name: "value", type: "uint256" },
2349
- { name: "callData", type: "bytes" }
2350
- ]
2351
- }
2352
- ],
2353
- [executions]
2354
- );
2355
- const executeBatchMode = "0x0100000000000000000000000000000000000000000000000000000000000000";
2356
- let executeBatchData;
2357
- try {
2358
- executeBatchData = (0, import_viem.encodeFunctionData)({
2359
- abi: ERC7702Delegator_default,
2360
- functionName: "execute",
2361
- args: [executeBatchMode, encodedExecutions]
2362
- });
2363
- } catch {
2364
- const functionSignature = "execute(bytes32,bytes)";
2365
- const selector = (0, import_viem.keccak256)((0, import_viem.toBytes)(functionSignature)).slice(0, 10);
2366
- const encodedParams = (0, import_viem.encodeAbiParameters)(
2367
- [{ type: "bytes32" }, { type: "bytes" }],
2368
- [executeBatchMode, encodedExecutions]
2369
- );
2370
- executeBatchData = (0, import_viem.concat)([selector, encodedParams]);
2371
- }
2445
+ const executeBatchData = encodeExecuteBatchData(executions);
2372
2446
  const isDelegated2 = await checkERC7702Delegation(
2373
2447
  publicClient,
2374
2448
  account.address,
@@ -2522,12 +2596,23 @@ function stripHexPrefix(value) {
2522
2596
  }
2523
2597
 
2524
2598
  // src/client/common/utils/userapi.ts
2599
+ function isJsonObject(value) {
2600
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2601
+ }
2602
+ function readString(obj, key) {
2603
+ const v = obj[key];
2604
+ return typeof v === "string" ? v : void 0;
2605
+ }
2606
+ function readNumber(obj, key) {
2607
+ const v = obj[key];
2608
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
2609
+ }
2525
2610
  var MAX_ADDRESS_COUNT = 5;
2526
2611
  var CanViewAppLogsPermission = "0x2fd3f2fe";
2527
2612
  var CanViewSensitiveAppInfoPermission = "0x0e67b22f";
2528
2613
  var CanUpdateAppProfilePermission = "0x036fef61";
2529
2614
  function getDefaultClientId() {
2530
- const version = true ? "0.1.2" : "0.0.0";
2615
+ const version = true ? "0.2.0-dev.1" : "0.0.0";
2531
2616
  return `ecloud-sdk/v${version}`;
2532
2617
  }
2533
2618
  var UserApiClient = class {
@@ -2561,6 +2646,31 @@ var UserApiClient = class {
2561
2646
  };
2562
2647
  });
2563
2648
  }
2649
+ /**
2650
+ * Get app details from UserAPI (includes releases and build/provenance info when available).
2651
+ *
2652
+ * Endpoint: GET /apps/:appAddress
2653
+ */
2654
+ async getApp(appAddress) {
2655
+ const endpoint = `${this.config.userApiServerURL}/apps/${appAddress}`;
2656
+ const res = await this.makeAuthenticatedRequest(endpoint);
2657
+ const raw = await res.json();
2658
+ if (!isJsonObject(raw)) {
2659
+ throw new Error("Unexpected /apps/:id response: expected object");
2660
+ }
2661
+ const id = readString(raw, "id");
2662
+ if (!id) {
2663
+ throw new Error("Unexpected /apps/:id response: missing 'id'");
2664
+ }
2665
+ const releasesRaw = raw.releases;
2666
+ const releases = Array.isArray(releasesRaw) ? releasesRaw.map((r) => transformAppRelease(r)).filter((r) => !!r) : [];
2667
+ return {
2668
+ id,
2669
+ creator: readString(raw, "creator"),
2670
+ contractStatus: readString(raw, "contract_status") ?? readString(raw, "contractStatus"),
2671
+ releases
2672
+ };
2673
+ }
2564
2674
  /**
2565
2675
  * Get available SKUs (instance types) from UserAPI
2566
2676
  */
@@ -2733,6 +2843,48 @@ Please check:
2733
2843
  };
2734
2844
  }
2735
2845
  };
2846
+ function transformAppReleaseBuild(raw) {
2847
+ if (!isJsonObject(raw)) return void 0;
2848
+ const depsRaw = raw.dependencies;
2849
+ const deps = isJsonObject(depsRaw) ? Object.fromEntries(
2850
+ Object.entries(depsRaw).flatMap(([digest, depRaw]) => {
2851
+ const parsed = transformAppReleaseBuild(depRaw);
2852
+ return parsed ? [[digest, parsed]] : [];
2853
+ })
2854
+ ) : void 0;
2855
+ return {
2856
+ buildId: readString(raw, "build_id") ?? readString(raw, "buildId"),
2857
+ billingAddress: readString(raw, "billing_address") ?? readString(raw, "billingAddress"),
2858
+ repoUrl: readString(raw, "repo_url") ?? readString(raw, "repoUrl"),
2859
+ gitRef: readString(raw, "git_ref") ?? readString(raw, "gitRef"),
2860
+ status: readString(raw, "status"),
2861
+ buildType: readString(raw, "build_type") ?? readString(raw, "buildType"),
2862
+ imageName: readString(raw, "image_name") ?? readString(raw, "imageName"),
2863
+ imageDigest: readString(raw, "image_digest") ?? readString(raw, "imageDigest"),
2864
+ imageUrl: readString(raw, "image_url") ?? readString(raw, "imageUrl"),
2865
+ provenanceJson: raw.provenance_json ?? raw.provenanceJson,
2866
+ provenanceSignature: readString(raw, "provenance_signature") ?? readString(raw, "provenanceSignature"),
2867
+ createdAt: readString(raw, "created_at") ?? readString(raw, "createdAt"),
2868
+ updatedAt: readString(raw, "updated_at") ?? readString(raw, "updatedAt"),
2869
+ errorMessage: readString(raw, "error_message") ?? readString(raw, "errorMessage"),
2870
+ dependencies: deps
2871
+ };
2872
+ }
2873
+ function transformAppRelease(raw) {
2874
+ if (!isJsonObject(raw)) return void 0;
2875
+ return {
2876
+ appId: readString(raw, "appId") ?? readString(raw, "app_id"),
2877
+ rmsReleaseId: readString(raw, "rmsReleaseId") ?? readString(raw, "rms_release_id"),
2878
+ imageDigest: readString(raw, "imageDigest") ?? readString(raw, "image_digest"),
2879
+ registryUrl: readString(raw, "registryUrl") ?? readString(raw, "registry_url"),
2880
+ publicEnv: readString(raw, "publicEnv") ?? readString(raw, "public_env"),
2881
+ encryptedEnv: readString(raw, "encryptedEnv") ?? readString(raw, "encrypted_env"),
2882
+ upgradeByTime: readNumber(raw, "upgradeByTime") ?? readNumber(raw, "upgrade_by_time"),
2883
+ createdAt: readString(raw, "createdAt") ?? readString(raw, "created_at"),
2884
+ createdAtBlock: readString(raw, "createdAtBlock") ?? readString(raw, "created_at_block"),
2885
+ build: raw.build ? transformAppReleaseBuild(raw.build) : void 0
2886
+ };
2887
+ }
2736
2888
 
2737
2889
  // src/client/common/utils/billing.ts
2738
2890
  function isSubscriptionActive(status) {
@@ -5554,6 +5706,97 @@ async function withSDKTelemetry(options, action) {
5554
5706
  }
5555
5707
 
5556
5708
  // src/client/modules/compute/app/deploy.ts
5709
+ async function prepareDeployFromVerifiableBuild(options, logger = defaultLogger) {
5710
+ return withSDKTelemetry(
5711
+ {
5712
+ functionName: "prepareDeployFromVerifiableBuild",
5713
+ skipTelemetry: options.skipTelemetry,
5714
+ properties: {
5715
+ environment: options.environment || "sepolia"
5716
+ }
5717
+ },
5718
+ async () => {
5719
+ if (!options.privateKey) throw new Error("privateKey is required for deployment");
5720
+ if (!options.imageRef) throw new Error("imageRef is required for deployment");
5721
+ if (!options.imageDigest) throw new Error("imageDigest is required for deployment");
5722
+ assertValidImageReference(options.imageRef);
5723
+ validateAppName(options.appName);
5724
+ validateLogVisibility(options.logVisibility);
5725
+ if (!/^sha256:[0-9a-f]{64}$/i.test(options.imageDigest)) {
5726
+ throw new Error(
5727
+ `imageDigest must be in format sha256:<64 hex>, got: ${options.imageDigest}`
5728
+ );
5729
+ }
5730
+ const { publicLogs } = validateLogVisibility(options.logVisibility);
5731
+ validateResourceUsageMonitoring(options.resourceUsageMonitoring);
5732
+ logger.debug("Performing preflight checks...");
5733
+ const preflightCtx = await doPreflightChecks(
5734
+ {
5735
+ privateKey: options.privateKey,
5736
+ rpcUrl: options.rpcUrl,
5737
+ environment: options.environment
5738
+ },
5739
+ logger
5740
+ );
5741
+ logger.debug("Checking quota availability...");
5742
+ await checkQuotaAvailable(preflightCtx);
5743
+ const salt = generateRandomSalt();
5744
+ logger.debug(`Generated salt: ${Buffer.from(salt).toString("hex")}`);
5745
+ logger.debug("Calculating app ID...");
5746
+ const appIDToBeDeployed = await calculateAppID(
5747
+ preflightCtx.privateKey,
5748
+ options.rpcUrl || preflightCtx.rpcUrl,
5749
+ preflightCtx.environmentConfig,
5750
+ salt
5751
+ );
5752
+ logger.info(``);
5753
+ logger.info(`App ID: ${appIDToBeDeployed}`);
5754
+ logger.info(``);
5755
+ const release = await createReleaseFromImageDigest(
5756
+ {
5757
+ imageRef: options.imageRef,
5758
+ imageDigest: options.imageDigest,
5759
+ envFilePath: options.envFilePath,
5760
+ instanceType: options.instanceType,
5761
+ environmentConfig: preflightCtx.environmentConfig,
5762
+ appId: appIDToBeDeployed
5763
+ },
5764
+ logger
5765
+ );
5766
+ logger.debug("Preparing deploy batch...");
5767
+ const batch = await prepareDeployBatch(
5768
+ {
5769
+ privateKey: preflightCtx.privateKey,
5770
+ rpcUrl: options.rpcUrl || preflightCtx.rpcUrl,
5771
+ environmentConfig: preflightCtx.environmentConfig,
5772
+ salt,
5773
+ release,
5774
+ publicLogs
5775
+ },
5776
+ logger
5777
+ );
5778
+ logger.debug("Estimating gas...");
5779
+ const gasEstimate = await estimateBatchGas({
5780
+ publicClient: batch.publicClient,
5781
+ account: batch.walletClient.account.address,
5782
+ executions: batch.executions
5783
+ });
5784
+ const data = {
5785
+ appId: batch.appId,
5786
+ salt: batch.salt,
5787
+ executions: batch.executions
5788
+ };
5789
+ return {
5790
+ prepared: {
5791
+ data,
5792
+ appName: options.appName,
5793
+ imageRef: options.imageRef
5794
+ },
5795
+ gasEstimate
5796
+ };
5797
+ }
5798
+ );
5799
+ }
5557
5800
  function validateDeployOptions(options) {
5558
5801
  if (!options.privateKey) {
5559
5802
  throw new Error("privateKey is required for deployment");
@@ -5774,7 +6017,7 @@ async function prepareDeploy(options, logger = defaultLogger) {
5774
6017
  logger.debug("Estimating gas...");
5775
6018
  const gasEstimate = await estimateBatchGas({
5776
6019
  publicClient: batch.publicClient,
5777
- environmentConfig: batch.environmentConfig,
6020
+ account: batch.walletClient.account.address,
5778
6021
  executions: batch.executions
5779
6022
  });
5780
6023
  const data = {
@@ -5864,6 +6107,81 @@ async function checkAppLogPermission(preflightCtx, appAddress, logger) {
5864
6107
  }
5865
6108
 
5866
6109
  // src/client/modules/compute/app/upgrade.ts
6110
+ async function prepareUpgradeFromVerifiableBuild(options, logger = defaultLogger) {
6111
+ return withSDKTelemetry(
6112
+ {
6113
+ functionName: "prepareUpgradeFromVerifiableBuild",
6114
+ skipTelemetry: options.skipTelemetry,
6115
+ properties: {
6116
+ environment: options.environment || "sepolia"
6117
+ }
6118
+ },
6119
+ async () => {
6120
+ logger.debug("Performing preflight checks...");
6121
+ const preflightCtx = await doPreflightChecks(
6122
+ {
6123
+ privateKey: options.privateKey,
6124
+ rpcUrl: options.rpcUrl,
6125
+ environment: options.environment
6126
+ },
6127
+ logger
6128
+ );
6129
+ const appID = validateUpgradeOptions(options);
6130
+ assertValidImageReference(options.imageRef);
6131
+ if (!/^sha256:[0-9a-f]{64}$/i.test(options.imageDigest)) {
6132
+ throw new Error(
6133
+ `imageDigest must be in format sha256:<64 hex>, got: ${options.imageDigest}`
6134
+ );
6135
+ }
6136
+ const { publicLogs } = validateLogVisibility(options.logVisibility);
6137
+ validateResourceUsageMonitoring(options.resourceUsageMonitoring);
6138
+ const envFilePath = options.envFilePath || "";
6139
+ logger.info("Preparing release (verifiable build, no local layering)...");
6140
+ const release = await createReleaseFromImageDigest(
6141
+ {
6142
+ imageRef: options.imageRef,
6143
+ imageDigest: options.imageDigest,
6144
+ envFilePath,
6145
+ instanceType: options.instanceType,
6146
+ environmentConfig: preflightCtx.environmentConfig,
6147
+ appId: appID
6148
+ },
6149
+ logger
6150
+ );
6151
+ logger.debug("Checking current log permission state...");
6152
+ const currentlyPublic = await checkAppLogPermission(preflightCtx, appID, logger);
6153
+ const needsPermissionChange = currentlyPublic !== publicLogs;
6154
+ logger.debug("Preparing upgrade batch...");
6155
+ const batch = await prepareUpgradeBatch({
6156
+ privateKey: preflightCtx.privateKey,
6157
+ rpcUrl: options.rpcUrl || preflightCtx.rpcUrl,
6158
+ environmentConfig: preflightCtx.environmentConfig,
6159
+ appId: appID,
6160
+ release,
6161
+ publicLogs,
6162
+ needsPermissionChange
6163
+ });
6164
+ logger.debug("Estimating gas...");
6165
+ const gasEstimate = await estimateBatchGas({
6166
+ publicClient: batch.publicClient,
6167
+ account: batch.walletClient.account.address,
6168
+ executions: batch.executions
6169
+ });
6170
+ const data = {
6171
+ appId: batch.appId,
6172
+ executions: batch.executions
6173
+ };
6174
+ return {
6175
+ prepared: {
6176
+ data,
6177
+ appId: appID,
6178
+ imageRef: options.imageRef
6179
+ },
6180
+ gasEstimate
6181
+ };
6182
+ }
6183
+ );
6184
+ }
5867
6185
  function validateUpgradeOptions(options) {
5868
6186
  if (!options.privateKey) {
5869
6187
  throw new Error("privateKey is required for upgrade");
@@ -6026,7 +6344,7 @@ async function prepareUpgrade(options, logger = defaultLogger) {
6026
6344
  logger.debug("Estimating gas...");
6027
6345
  const gasEstimate = await estimateBatchGas({
6028
6346
  publicClient: batch.publicClient,
6029
- environmentConfig: batch.environmentConfig,
6347
+ account: batch.walletClient.account.address,
6030
6348
  executions: batch.executions
6031
6349
  });
6032
6350
  const data = {
@@ -6846,6 +7164,24 @@ function createAppModule(ctx) {
6846
7164
  logger
6847
7165
  );
6848
7166
  },
7167
+ async prepareDeployFromVerifiableBuild(opts) {
7168
+ return prepareDeployFromVerifiableBuild(
7169
+ {
7170
+ privateKey,
7171
+ rpcUrl: ctx.rpcUrl,
7172
+ environment: ctx.environment,
7173
+ appName: opts.name,
7174
+ instanceType: opts.instanceType,
7175
+ envFilePath: opts.envFile,
7176
+ imageRef: opts.imageRef,
7177
+ imageDigest: opts.imageDigest,
7178
+ logVisibility: opts.logVisibility,
7179
+ resourceUsageMonitoring: opts.resourceUsageMonitoring,
7180
+ skipTelemetry
7181
+ },
7182
+ logger
7183
+ );
7184
+ },
6849
7185
  async executeDeploy(prepared, gas) {
6850
7186
  const account = (0, import_accounts5.privateKeyToAccount)(privateKey);
6851
7187
  const chain = getChainFromID(environment.chainID);
@@ -6906,6 +7242,24 @@ function createAppModule(ctx) {
6906
7242
  logger
6907
7243
  );
6908
7244
  },
7245
+ async prepareUpgradeFromVerifiableBuild(appId, opts) {
7246
+ return prepareUpgradeFromVerifiableBuild(
7247
+ {
7248
+ appId,
7249
+ privateKey,
7250
+ rpcUrl: ctx.rpcUrl,
7251
+ environment: ctx.environment,
7252
+ instanceType: opts.instanceType,
7253
+ envFilePath: opts.envFile,
7254
+ imageRef: opts.imageRef,
7255
+ imageDigest: opts.imageDigest,
7256
+ logVisibility: opts.logVisibility,
7257
+ resourceUsageMonitoring: opts.resourceUsageMonitoring,
7258
+ skipTelemetry
7259
+ },
7260
+ logger
7261
+ );
7262
+ },
6909
7263
  async executeUpgrade(prepared, gas) {
6910
7264
  const account = (0, import_accounts5.privateKeyToAccount)(privateKey);
6911
7265
  const chain = getChainFromID(environment.chainID);
@@ -7527,6 +7881,360 @@ function createBillingModule(config) {
7527
7881
  };
7528
7882
  }
7529
7883
 
7884
+ // src/client/common/utils/buildapi.ts
7885
+ var import_axios3 = __toESM(require("axios"), 1);
7886
+ var import_accounts9 = require("viem/accounts");
7887
+ var BuildApiClient = class {
7888
+ constructor(options) {
7889
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
7890
+ this.clientId = options.clientId;
7891
+ if (options.privateKey) {
7892
+ this.account = (0, import_accounts9.privateKeyToAccount)(options.privateKey);
7893
+ }
7894
+ }
7895
+ async submitBuild(payload) {
7896
+ return this.authenticatedJsonRequest("/builds", "POST", payload);
7897
+ }
7898
+ async getBuild(buildId) {
7899
+ return this.publicJsonRequest(`/builds/${encodeURIComponent(buildId)}`);
7900
+ }
7901
+ async getBuildByDigest(digest) {
7902
+ return this.publicJsonRequest(`/builds/image/${encodeURIComponent(digest)}`);
7903
+ }
7904
+ async verify(identifier) {
7905
+ return this.publicJsonRequest(`/builds/verify/${encodeURIComponent(identifier)}`);
7906
+ }
7907
+ async getLogs(buildId) {
7908
+ return this.authenticatedTextRequest(`/builds/${encodeURIComponent(buildId)}/logs`);
7909
+ }
7910
+ async listBuilds(params) {
7911
+ const res = await (0, import_axios3.default)({
7912
+ url: `${this.baseUrl}/builds`,
7913
+ method: "GET",
7914
+ params,
7915
+ headers: this.clientId ? { "x-client-id": this.clientId } : void 0,
7916
+ timeout: 6e4,
7917
+ validateStatus: () => true
7918
+ });
7919
+ if (res.status < 200 || res.status >= 300) throw buildApiHttpError(res);
7920
+ return res.data;
7921
+ }
7922
+ async publicJsonRequest(path8) {
7923
+ const res = await (0, import_axios3.default)({
7924
+ url: `${this.baseUrl}${path8}`,
7925
+ method: "GET",
7926
+ headers: this.clientId ? { "x-client-id": this.clientId } : void 0,
7927
+ timeout: 6e4,
7928
+ validateStatus: () => true
7929
+ });
7930
+ if (res.status < 200 || res.status >= 300) throw buildApiHttpError(res);
7931
+ return res.data;
7932
+ }
7933
+ async authenticatedJsonRequest(path8, method, body) {
7934
+ if (!this.account) throw new Error("Private key required for authenticated requests");
7935
+ const headers = {
7936
+ "Content-Type": "application/json"
7937
+ };
7938
+ if (this.clientId) headers["x-client-id"] = this.clientId;
7939
+ const expiry = BigInt(Math.floor(Date.now() / 1e3) + 60);
7940
+ const { signature } = await calculateBillingAuthSignature({
7941
+ account: this.account,
7942
+ product: "compute",
7943
+ expiry
7944
+ });
7945
+ headers.Authorization = `Bearer ${signature}`;
7946
+ headers["X-eigenx-expiry"] = expiry.toString();
7947
+ headers["X-Account"] = this.account.address;
7948
+ const res = await (0, import_axios3.default)({
7949
+ url: `${this.baseUrl}${path8}`,
7950
+ method,
7951
+ headers,
7952
+ data: body,
7953
+ timeout: 6e4,
7954
+ validateStatus: () => true
7955
+ });
7956
+ if (res.status < 200 || res.status >= 300) throw buildApiHttpError(res);
7957
+ return res.data;
7958
+ }
7959
+ async authenticatedTextRequest(path8) {
7960
+ if (!this.account) throw new Error("Private key required for authenticated requests");
7961
+ const headers = {};
7962
+ if (this.clientId) headers["x-client-id"] = this.clientId;
7963
+ const expiry = BigInt(Math.floor(Date.now() / 1e3) + 60);
7964
+ const { signature } = await calculateBillingAuthSignature({
7965
+ account: this.account,
7966
+ product: "compute",
7967
+ expiry
7968
+ });
7969
+ headers.Authorization = `Bearer ${signature}`;
7970
+ headers["X-eigenx-expiry"] = expiry.toString();
7971
+ headers["X-Account"] = this.account.address;
7972
+ const res = await (0, import_axios3.default)({
7973
+ url: `${this.baseUrl}${path8}`,
7974
+ method: "GET",
7975
+ headers,
7976
+ timeout: 6e4,
7977
+ responseType: "text",
7978
+ validateStatus: () => true
7979
+ });
7980
+ if (res.status < 200 || res.status >= 300) throw buildApiHttpError(res);
7981
+ return typeof res.data === "string" ? res.data : JSON.stringify(res.data);
7982
+ }
7983
+ };
7984
+ function buildApiHttpError(res) {
7985
+ const status = res.status;
7986
+ const body = typeof res.data === "string" ? res.data : res.data ? JSON.stringify(res.data) : "";
7987
+ const url = res.config?.url ? ` ${res.config.url}` : "";
7988
+ return new Error(`BuildAPI request failed: ${status}${url} - ${body || "Unknown error"}`);
7989
+ }
7990
+
7991
+ // src/client/modules/build/types.ts
7992
+ var BUILD_STATUS = {
7993
+ BUILDING: "building",
7994
+ SUCCESS: "success",
7995
+ FAILED: "failed"
7996
+ };
7997
+
7998
+ // src/client/modules/build/errors.ts
7999
+ var BuildError = class extends Error {
8000
+ constructor(message) {
8001
+ super(message);
8002
+ this.name = "BuildError";
8003
+ }
8004
+ };
8005
+ var AuthRequiredError = class extends BuildError {
8006
+ constructor(message = "Authentication required") {
8007
+ super(message);
8008
+ this.name = "AuthRequiredError";
8009
+ }
8010
+ };
8011
+ var BuildFailedError = class extends BuildError {
8012
+ constructor(message, buildId) {
8013
+ super(message);
8014
+ this.buildId = buildId;
8015
+ this.name = "BuildFailedError";
8016
+ }
8017
+ };
8018
+ var ConflictError = class extends BuildError {
8019
+ constructor(message = "Build already in progress") {
8020
+ super(message);
8021
+ this.name = "ConflictError";
8022
+ }
8023
+ };
8024
+ var NotFoundError = class extends BuildError {
8025
+ constructor(message = "Build not found") {
8026
+ super(message);
8027
+ this.name = "NotFoundError";
8028
+ }
8029
+ };
8030
+ var ForbiddenError = class extends BuildError {
8031
+ constructor(message = "Permission denied") {
8032
+ super(message);
8033
+ this.name = "ForbiddenError";
8034
+ }
8035
+ };
8036
+ var TimeoutError = class extends BuildError {
8037
+ constructor(message = "Operation timed out") {
8038
+ super(message);
8039
+ this.name = "TimeoutError";
8040
+ }
8041
+ };
8042
+ var BadRequestError = class extends BuildError {
8043
+ constructor(message = "Bad request") {
8044
+ super(message);
8045
+ this.name = "BadRequestError";
8046
+ }
8047
+ };
8048
+
8049
+ // src/client/modules/build/index.ts
8050
+ var DEFAULT_POLL_INTERVAL = 2e3;
8051
+ var DEFAULT_TIMEOUT = 30 * 60 * 1e3;
8052
+ function createBuildModule(config) {
8053
+ const { verbose = false, skipTelemetry = false } = config;
8054
+ const logger = getLogger(verbose);
8055
+ const environment = config.environment || "sepolia";
8056
+ const environmentConfig = getEnvironmentConfig(environment);
8057
+ const api = new BuildApiClient({
8058
+ baseUrl: environmentConfig.userApiServerURL,
8059
+ privateKey: config.privateKey ? addHexPrefix(config.privateKey) : void 0,
8060
+ clientId: config.clientId
8061
+ });
8062
+ return {
8063
+ async submit(request) {
8064
+ return withSDKTelemetry(
8065
+ {
8066
+ functionName: "build.submit",
8067
+ skipTelemetry,
8068
+ properties: { environment, repoUrl: request.repoUrl }
8069
+ },
8070
+ async () => {
8071
+ if (!config.privateKey) throw new AuthRequiredError("Private key required for submit()");
8072
+ const data = await api.submitBuild({
8073
+ repo_url: request.repoUrl,
8074
+ git_ref: request.gitRef,
8075
+ dockerfile_path: request.dockerfilePath ?? "Dockerfile",
8076
+ caddyfile_path: request.caddyfilePath,
8077
+ build_context_path: request.buildContextPath ?? ".",
8078
+ dependencies: request.dependencies ?? []
8079
+ });
8080
+ logger.debug(`Submitted build: ${data.build_id}`);
8081
+ return { buildId: data.build_id };
8082
+ }
8083
+ );
8084
+ },
8085
+ async list(options) {
8086
+ const { billingAddress, limit, offset } = options;
8087
+ return withSDKTelemetry(
8088
+ {
8089
+ functionName: "build.list",
8090
+ skipTelemetry,
8091
+ properties: {
8092
+ environment,
8093
+ billingAddress,
8094
+ ...limit !== void 0 ? { limit: String(limit) } : {},
8095
+ ...offset !== void 0 ? { offset: String(offset) } : {}
8096
+ }
8097
+ },
8098
+ async () => {
8099
+ const data = await api.listBuilds({
8100
+ billing_address: billingAddress,
8101
+ limit,
8102
+ offset
8103
+ });
8104
+ return Array.isArray(data) ? data.map(transformBuild) : [];
8105
+ }
8106
+ );
8107
+ },
8108
+ async get(buildId) {
8109
+ return withSDKTelemetry(
8110
+ { functionName: "build.get", skipTelemetry, properties: { environment, buildId } },
8111
+ async () => transformBuild(await api.getBuild(buildId))
8112
+ );
8113
+ },
8114
+ async getByDigest(digest) {
8115
+ return withSDKTelemetry(
8116
+ { functionName: "build.getByDigest", skipTelemetry, properties: { environment, digest } },
8117
+ async () => transformBuild(await api.getBuildByDigest(digest))
8118
+ );
8119
+ },
8120
+ async verify(identifier) {
8121
+ return withSDKTelemetry(
8122
+ { functionName: "build.verify", skipTelemetry, properties: { environment, identifier } },
8123
+ async () => transformVerifyResult(await api.verify(identifier))
8124
+ );
8125
+ },
8126
+ async getLogs(buildId) {
8127
+ return withSDKTelemetry(
8128
+ { functionName: "build.getLogs", skipTelemetry, properties: { environment, buildId } },
8129
+ async () => {
8130
+ if (!config.privateKey) throw new AuthRequiredError("Private key required for getLogs()");
8131
+ return api.getLogs(buildId);
8132
+ }
8133
+ );
8134
+ },
8135
+ async submitAndWait(request, options = {}) {
8136
+ const { buildId } = await this.submit(request);
8137
+ return this.waitForBuild(buildId, options);
8138
+ },
8139
+ async waitForBuild(buildId, options = {}) {
8140
+ const {
8141
+ onLog,
8142
+ onProgress,
8143
+ pollIntervalMs = DEFAULT_POLL_INTERVAL,
8144
+ timeoutMs = DEFAULT_TIMEOUT
8145
+ } = options;
8146
+ const startTime = Date.now();
8147
+ let lastLogLength = 0;
8148
+ while (true) {
8149
+ if (Date.now() - startTime > timeoutMs) {
8150
+ throw new TimeoutError(`Build timed out after ${timeoutMs}ms`);
8151
+ }
8152
+ const build = await this.get(buildId);
8153
+ let logs2 = "";
8154
+ try {
8155
+ logs2 = await this.getLogs(buildId);
8156
+ if (onLog && logs2.length > lastLogLength) {
8157
+ onLog(logs2.slice(lastLogLength));
8158
+ lastLogLength = logs2.length;
8159
+ }
8160
+ } catch {
8161
+ }
8162
+ onProgress?.({ build, logs: logs2 });
8163
+ if (build.status === BUILD_STATUS.SUCCESS) return build;
8164
+ if (build.status === BUILD_STATUS.FAILED) {
8165
+ throw new BuildFailedError(build.errorMessage ?? "Build failed", buildId);
8166
+ }
8167
+ await sleep2(pollIntervalMs);
8168
+ }
8169
+ },
8170
+ async *streamLogs(buildId, pollIntervalMs = DEFAULT_POLL_INTERVAL) {
8171
+ let lastLength = 0;
8172
+ while (true) {
8173
+ const build = await this.get(buildId);
8174
+ let logs2 = "";
8175
+ try {
8176
+ logs2 = await this.getLogs(buildId);
8177
+ } catch {
8178
+ }
8179
+ if (logs2.length > lastLength) {
8180
+ yield {
8181
+ content: logs2.slice(lastLength),
8182
+ totalLength: logs2.length,
8183
+ isComplete: build.status !== BUILD_STATUS.BUILDING,
8184
+ finalStatus: build.status !== BUILD_STATUS.BUILDING ? build.status : void 0
8185
+ };
8186
+ lastLength = logs2.length;
8187
+ }
8188
+ if (build.status !== BUILD_STATUS.BUILDING) break;
8189
+ await sleep2(pollIntervalMs);
8190
+ }
8191
+ }
8192
+ };
8193
+ }
8194
+ function sleep2(ms) {
8195
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
8196
+ }
8197
+ function transformBuild(raw) {
8198
+ return {
8199
+ buildId: raw.build_id,
8200
+ billingAddress: raw.billing_address,
8201
+ repoUrl: raw.repo_url,
8202
+ gitRef: raw.git_ref,
8203
+ status: raw.status,
8204
+ buildType: raw.build_type,
8205
+ imageName: raw.image_name,
8206
+ imageUrl: raw.image_url,
8207
+ imageDigest: raw.image_digest,
8208
+ provenanceJson: raw.provenance_json ?? void 0,
8209
+ provenanceSignature: raw.provenance_signature ?? void 0,
8210
+ errorMessage: raw.error_message ?? void 0,
8211
+ createdAt: raw.created_at,
8212
+ updatedAt: raw.updated_at,
8213
+ dependencies: raw.dependencies ? Object.fromEntries(Object.entries(raw.dependencies).map(([k, v]) => [k, transformBuild(v)])) : void 0
8214
+ };
8215
+ }
8216
+ function transformVerifyResult(raw) {
8217
+ if (raw.status === "verified") {
8218
+ return {
8219
+ status: "verified",
8220
+ buildId: raw.build_id,
8221
+ imageUrl: raw.image_url,
8222
+ imageDigest: raw.image_digest,
8223
+ repoUrl: raw.repo_url,
8224
+ gitRef: raw.git_ref,
8225
+ provenanceJson: raw.provenance_json,
8226
+ provenanceSignature: raw.provenance_signature,
8227
+ payloadType: raw.payload_type,
8228
+ payload: raw.payload
8229
+ };
8230
+ }
8231
+ return {
8232
+ status: "failed",
8233
+ error: raw.error,
8234
+ buildId: raw.build_id
8235
+ };
8236
+ }
8237
+
7530
8238
  // src/client/common/utils/instance.ts
7531
8239
  async function getCurrentInstanceType(preflightCtx, appID, logger, clientId) {
7532
8240
  try {
@@ -7581,10 +8289,20 @@ function createECloudClient(cfg) {
7581
8289
  }
7582
8290
  // Annotate the CommonJS export names for ESM import in node:
7583
8291
  0 && (module.exports = {
8292
+ AuthRequiredError,
8293
+ BUILD_STATUS,
8294
+ BadRequestError,
8295
+ BuildError,
8296
+ BuildFailedError,
8297
+ ConflictError,
8298
+ ForbiddenError,
7584
8299
  NoopClient,
8300
+ NotFoundError,
7585
8301
  PRIMARY_LANGUAGES,
7586
8302
  PostHogClient,
8303
+ TimeoutError,
7587
8304
  UserApiClient,
8305
+ addHexPrefix,
7588
8306
  addMetric,
7589
8307
  addMetricWithDimensions,
7590
8308
  assertValidFilePath,
@@ -7594,6 +8312,7 @@ function createECloudClient(cfg) {
7594
8312
  createApp,
7595
8313
  createAppEnvironment,
7596
8314
  createBillingModule,
8315
+ createBuildModule,
7597
8316
  createComputeModule,
7598
8317
  createECloudClient,
7599
8318
  createMetricsContext,
@@ -7637,12 +8356,15 @@ function createECloudClient(cfg) {
7637
8356
  listStoredKeys,
7638
8357
  logs,
7639
8358
  prepareDeploy,
8359
+ prepareDeployFromVerifiableBuild,
7640
8360
  prepareUpgrade,
8361
+ prepareUpgradeFromVerifiableBuild,
7641
8362
  requirePrivateKey,
7642
8363
  sanitizeString,
7643
8364
  sanitizeURL,
7644
8365
  sanitizeXURL,
7645
8366
  storePrivateKey,
8367
+ stripHexPrefix,
7646
8368
  validateAppID,
7647
8369
  validateAppName,
7648
8370
  validateCreateAppParams,