@layr-labs/ecloud-sdk 0.4.0-dev.2 → 0.4.0-dev.4

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/common/auth/session.ts","../src/client/modules/billing/index.ts","../src/client/common/utils/billingapi.ts","../src/client/common/utils/auth.ts","../src/client/common/auth/billingSession.ts","../src/client/common/config/environment.ts","../src/client/common/utils/logger.ts","../src/client/common/utils/userapi.ts","../src/client/common/utils/helpers.ts","../src/client/common/constants.ts","../src/client/common/utils/retry.ts","../src/client/common/utils/billing.ts","../src/client/common/telemetry/noop.ts","../src/client/common/telemetry/posthog.ts","../src/client/common/telemetry/index.ts","../src/client/common/telemetry/metricsContext.ts","../src/client/common/telemetry/wrapper.ts","../src/client/common/contract/eip7702.ts","../src/client/common/types/index.ts","../src/client/common/abis/ERC7702Delegator.json","../src/client/common/contract/caller.ts","../src/client/common/abis/USDCCredits.json","../src/client/common/abis/ERC20.json"],"sourcesContent":["/**\n * Compute API Session Management\n *\n * This module provides utilities for managing authentication sessions with the compute API\n * using SIWE (Sign-In with Ethereum).\n */\n\nimport { Address, Hex } from \"viem\";\n\nexport interface ComputeApiConfig {\n /** Base URL of the compute API (e.g., \"https://api.eigencloud.xyz\") */\n baseUrl: string;\n}\n\nexport interface SessionInfo {\n /** Whether the session is authenticated */\n authenticated: boolean;\n /** Authenticated wallet address (if authenticated) */\n address?: Address;\n /** Chain ID used for authentication (if authenticated) */\n chainId?: number;\n}\n\nexport interface LoginResult {\n /** Whether login was successful */\n success: boolean;\n /** Authenticated wallet address */\n address: Address;\n}\n\nexport interface LoginRequest {\n /** SIWE message string */\n message: string;\n /** Hex-encoded signature (with or without 0x prefix) */\n signature: Hex | string;\n}\n\n/**\n * Error thrown when session operations fail\n */\nexport class SessionError extends Error {\n constructor(\n message: string,\n public readonly code:\n | \"NETWORK_ERROR\"\n | \"INVALID_SIGNATURE\"\n | \"INVALID_MESSAGE\"\n | \"SESSION_EXPIRED\"\n | \"UNAUTHORIZED\"\n | \"UNKNOWN\",\n public readonly statusCode?: number,\n ) {\n super(message);\n this.name = \"SessionError\";\n }\n}\n\n/**\n * Strip 0x prefix from hex string if present\n */\nfunction stripHexPrefix(hex: string): string {\n return hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n}\n\n/**\n * Parse error response body\n */\nasync function parseErrorResponse(response: Response): Promise<string> {\n try {\n const data = (await response.json()) as { error?: string };\n return data.error || response.statusText;\n } catch {\n return response.statusText;\n }\n}\n\n/**\n * Login to the compute API using SIWE\n *\n * This establishes a session with the compute API by verifying the SIWE message\n * and signature. On success, a session cookie is set in the browser.\n *\n * @param config - Compute API configuration\n * @param request - Login request containing SIWE message and signature\n * @returns Login result with the authenticated address\n *\n * @example\n * ```typescript\n * import { createSiweMessage, loginToComputeApi } from \"@layr-labs/ecloud-sdk/browser\";\n *\n * const { message } = createSiweMessage({\n * address: userAddress,\n * chainId: 11155111,\n * domain: window.location.host,\n * uri: window.location.origin,\n * });\n *\n * const signature = await signMessageAsync({ message });\n * const result = await loginToComputeApi(\n * { baseUrl: \"https://api.eigencloud.xyz\" },\n * { message, signature }\n * );\n * ```\n */\nexport async function loginToComputeApi(\n config: ComputeApiConfig,\n request: LoginRequest,\n): Promise<LoginResult> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/siwe/login`, {\n method: \"POST\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n message: request.message,\n signature: stripHexPrefix(request.signature),\n }),\n });\n } catch (error) {\n throw new SessionError(\n `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,\n \"NETWORK_ERROR\",\n );\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n const status = response.status;\n\n if (status === 400) {\n if (errorMessage.toLowerCase().includes(\"siwe\")) {\n throw new SessionError(`Invalid SIWE message: ${errorMessage}`, \"INVALID_MESSAGE\", status);\n }\n throw new SessionError(`Bad request: ${errorMessage}`, \"INVALID_MESSAGE\", status);\n }\n\n if (status === 401) {\n throw new SessionError(`Invalid signature: ${errorMessage}`, \"INVALID_SIGNATURE\", status);\n }\n\n throw new SessionError(`Login failed: ${errorMessage}`, \"UNKNOWN\", status);\n }\n\n const data = (await response.json()) as { success: boolean; address: string };\n\n return {\n success: data.success,\n address: data.address as Address,\n };\n}\n\n/**\n * Get the current session status from the compute API\n *\n * @param config - Compute API configuration\n * @returns Session information including authentication status and address\n *\n * @example\n * ```typescript\n * const session = await getComputeApiSession({ baseUrl: \"https://api.eigencloud.xyz\" });\n * if (session.authenticated) {\n * console.log(`Logged in as ${session.address}`);\n * }\n * ```\n */\nexport async function getComputeApiSession(config: ComputeApiConfig): Promise<SessionInfo> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/session`, {\n method: \"GET\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n } catch {\n // Network error - return unauthenticated session\n return {\n authenticated: false,\n };\n }\n\n // If we get a 401, return unauthenticated session\n if (response.status === 401) {\n return {\n authenticated: false,\n };\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n throw new SessionError(`Failed to get session: ${errorMessage}`, \"UNKNOWN\", response.status);\n }\n\n const data = (await response.json()) as {\n authenticated: boolean;\n address?: string;\n chain_id?: number;\n };\n\n return {\n authenticated: data.authenticated,\n address: data.address as Address | undefined,\n chainId: data.chain_id,\n };\n}\n\n/**\n * Logout from the compute API\n *\n * This destroys the current session and clears the session cookie.\n *\n * @param config - Compute API configuration\n *\n * @example\n * ```typescript\n * await logoutFromComputeApi({ baseUrl: \"https://api.eigencloud.xyz\" });\n * ```\n */\nexport async function logoutFromComputeApi(config: ComputeApiConfig): Promise<void> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/logout`, {\n method: \"POST\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n } catch (error) {\n throw new SessionError(\n `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,\n \"NETWORK_ERROR\",\n );\n }\n\n // Ignore 401 errors during logout (already logged out)\n if (response.status === 401) {\n return;\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n throw new SessionError(`Logout failed: ${errorMessage}`, \"UNKNOWN\", response.status);\n }\n}\n\n/**\n * Check if a session is still valid (not expired)\n *\n * This is a convenience function that checks the session status\n * and returns a boolean.\n *\n * @param config - Compute API configuration\n * @returns True if session is authenticated, false otherwise\n */\nexport async function isSessionValid(config: ComputeApiConfig): Promise<boolean> {\n const session = await getComputeApiSession(config);\n return session.authenticated;\n}\n","/**\n * Main Billing namespace entry point\n *\n * Accepts viem's WalletClient which abstracts over both local accounts\n * (privateKeyToAccount) and external signers (MetaMask, etc.).\n */\n\nimport type { WalletClient, PublicClient } from \"viem\";\nimport { type Address, type Hex, encodeFunctionData } from \"viem\";\n\nimport { BillingApiClient } from \"../../common/utils/billingapi\";\nimport { getBillingEnvironmentConfig, getBuildType, getEnvironmentConfig } from \"../../common/config/environment\";\nimport { getLogger, isSubscriptionActive } from \"../../common/utils\";\nimport { withSDKTelemetry } from \"../../common/telemetry/wrapper\";\nimport { executeBatch, type Execution } from \"../../common/contract/eip7702\";\n\nimport USDCCreditsABI from \"../../common/abis/USDCCredits.json\";\nimport ERC20ABI from \"../../common/abis/ERC20.json\";\n\nimport type {\n ProductID,\n SubscriptionOpts,\n SubscribeResponse,\n CancelResponse,\n ProductSubscriptionResponse,\n} from \"../../common/types\";\n\nexport interface TopUpOpts {\n /** Amount in raw USDC units (6 decimals, e.g. 50_000_000n = 50 USDC) */\n amount: bigint;\n /** Target account for purchaseCreditsFor (defaults to wallet address) */\n account?: Address;\n}\n\nexport interface TopUpResult {\n txHash: Hex;\n walletAddress: Address;\n}\n\nexport interface TopUpInfo {\n usdcAddress: Address;\n minimumPurchase: bigint;\n usdcBalance: bigint;\n currentAllowance: bigint;\n}\n\nexport interface BillingModule {\n address: Address;\n subscribe: (opts?: SubscriptionOpts) => Promise<SubscribeResponse>;\n getStatus: (opts?: SubscriptionOpts) => Promise<ProductSubscriptionResponse>;\n cancel: (opts?: SubscriptionOpts) => Promise<CancelResponse>;\n /** Read on-chain state needed for top-up */\n getTopUpInfo: () => Promise<TopUpInfo>;\n /** Purchase credits with USDC on-chain */\n topUp: (opts: TopUpOpts) => Promise<TopUpResult>;\n}\n\nexport interface BillingModuleConfig {\n verbose?: boolean;\n walletClient: WalletClient;\n skipTelemetry?: boolean; // Skip telemetry when called from CLI\n publicClient: PublicClient;\n environment: string;\n}\n\nexport function createBillingModule(config: BillingModuleConfig): BillingModule {\n const { verbose = false, skipTelemetry = false, walletClient, publicClient, environment } = config;\n\n // Get address from wallet client's account\n if (!walletClient.account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n const address = walletClient.account.address as Address;\n\n const logger = getLogger(verbose);\n\n // Get billing environment configuration\n const billingEnvConfig = getBillingEnvironmentConfig(getBuildType());\n\n // Create billing API client\n const billingApi = new BillingApiClient(billingEnvConfig, walletClient);\n\n // Resolve on-chain config\n const environmentConfig = getEnvironmentConfig(environment);\n const usdcCreditsAddress = environmentConfig.usdcCreditsAddress;\n if (!usdcCreditsAddress) {\n throw new Error(`USDCCredits contract address not configured for environment \"${environment}\"`);\n }\n\n const module: BillingModule = {\n address,\n\n async getTopUpInfo(): Promise<TopUpInfo> {\n const usdcAddress = await publicClient.readContract({\n address: usdcCreditsAddress,\n abi: USDCCreditsABI,\n functionName: \"usdc\",\n }) as Address;\n\n const [minimumPurchase, usdcBalance, currentAllowance] = await Promise.all([\n publicClient.readContract({\n address: usdcCreditsAddress,\n abi: USDCCreditsABI,\n functionName: \"minimumPurchase\",\n }) as Promise<bigint>,\n publicClient.readContract({\n address: usdcAddress,\n abi: ERC20ABI,\n functionName: \"balanceOf\",\n args: [address],\n }) as Promise<bigint>,\n publicClient.readContract({\n address: usdcAddress,\n abi: ERC20ABI,\n functionName: \"allowance\",\n args: [address, usdcCreditsAddress],\n }) as Promise<bigint>,\n ]);\n\n return { usdcAddress, minimumPurchase, usdcBalance, currentAllowance };\n },\n\n async topUp(opts: TopUpOpts): Promise<TopUpResult> {\n return withSDKTelemetry(\n {\n functionName: \"topUp\",\n skipTelemetry,\n properties: { amount: opts.amount.toString() },\n },\n async () => {\n const targetAccount = opts.account ?? address;\n\n // Read on-chain state\n const { usdcAddress, currentAllowance } = await module.getTopUpInfo();\n\n // Build executions array\n const executions: Execution[] = [];\n\n // Conditionally include approve if allowance insufficient\n if (currentAllowance < opts.amount) {\n executions.push({\n target: usdcAddress,\n value: 0n,\n callData: encodeFunctionData({\n abi: ERC20ABI,\n functionName: \"approve\",\n args: [usdcCreditsAddress, opts.amount],\n }),\n });\n }\n\n // Always include purchaseCreditsFor\n executions.push({\n target: usdcCreditsAddress,\n value: 0n,\n callData: encodeFunctionData({\n abi: USDCCreditsABI,\n functionName: \"purchaseCreditsFor\",\n args: [opts.amount, targetAccount],\n }),\n });\n\n const txHash = await executeBatch(\n {\n walletClient,\n publicClient,\n environmentConfig,\n executions,\n pendingMessage: \"Submitting credit purchase...\",\n },\n logger,\n );\n\n return { txHash, walletAddress: address };\n },\n );\n },\n\n async subscribe(opts) {\n return withSDKTelemetry(\n {\n functionName: \"subscribe\",\n skipTelemetry: skipTelemetry, // Skip if called from CLI\n properties: { productId: opts?.productId || \"compute\" },\n },\n async () => {\n const productId: ProductID = opts?.productId || \"compute\";\n\n // Check existing subscription status first\n logger.debug(`Checking existing subscription for ${productId}...`);\n const currentStatus = await billingApi.getSubscription(productId);\n\n // If already active or trialing, don't create new checkout\n if (isSubscriptionActive(currentStatus.subscriptionStatus)) {\n logger.debug(`Subscription already active: ${currentStatus.subscriptionStatus}`);\n return {\n type: \"already_active\" as const,\n status: currentStatus.subscriptionStatus,\n };\n }\n\n // If subscription has payment issues, return portal URL instead\n if (\n currentStatus.subscriptionStatus === \"past_due\" ||\n currentStatus.subscriptionStatus === \"unpaid\"\n ) {\n logger.debug(`Subscription has payment issue: ${currentStatus.subscriptionStatus}`);\n return {\n type: \"payment_issue\" as const,\n status: currentStatus.subscriptionStatus,\n portalUrl: currentStatus.portalUrl,\n };\n }\n\n // Create new checkout session\n logger.debug(`Creating subscription for ${productId}...`);\n const result = await billingApi.createSubscription(productId, {\n successUrl: opts?.successUrl,\n cancelUrl: opts?.cancelUrl,\n });\n\n logger.debug(`Checkout URL: ${result.checkoutUrl}`);\n return {\n type: \"checkout_created\" as const,\n checkoutUrl: result.checkoutUrl,\n };\n },\n );\n },\n\n async getStatus(opts) {\n return withSDKTelemetry(\n {\n functionName: \"getStatus\",\n skipTelemetry: skipTelemetry, // Skip if called from CLI\n properties: { productId: opts?.productId || \"compute\" },\n },\n async () => {\n const productId: ProductID = opts?.productId || \"compute\";\n logger.debug(`Fetching subscription status for ${productId}...`);\n\n const result = await billingApi.getSubscription(productId);\n\n logger.debug(`Subscription status: ${result.subscriptionStatus}`);\n return result;\n },\n );\n },\n\n async cancel(opts) {\n return withSDKTelemetry(\n {\n functionName: \"cancel\",\n skipTelemetry: skipTelemetry, // Skip if called from CLI\n properties: { productId: opts?.productId || \"compute\" },\n },\n async () => {\n const productId: ProductID = opts?.productId || \"compute\";\n\n // Check existing subscription status first\n logger.debug(`Checking subscription status for ${productId}...`);\n const currentStatus = await billingApi.getSubscription(productId);\n\n // If no active subscription, don't attempt to cancel\n if (!isSubscriptionActive(currentStatus.subscriptionStatus)) {\n logger.debug(`No active subscription to cancel: ${currentStatus.subscriptionStatus}`);\n return {\n type: \"no_active_subscription\" as const,\n status: currentStatus.subscriptionStatus,\n };\n }\n\n // Cancel the subscription\n logger.debug(`Canceling subscription for ${productId}...`);\n await billingApi.cancelSubscription(productId);\n\n logger.debug(`Subscription canceled successfully`);\n return {\n type: \"canceled\" as const,\n };\n },\n );\n },\n };\n\n return module;\n}\n","/**\n * BillingAPI Client to manage product subscriptions\n * Standalone client - does not depend on chain infrastructure\n *\n * Accepts viem's WalletClient which abstracts over both local accounts\n * (privateKeyToAccount) and external signers (MetaMask, etc.).\n *\n * Supports two authentication modes:\n * 1. EIP-712 signature auth (default) - signs each request with typed data\n * 2. Session auth (optional) - uses SIWE session cookies\n */\n\nimport axios, { AxiosResponse } from \"axios\";\nimport { Address, type WalletClient } from \"viem\";\nimport {\n ProductID,\n CreateSubscriptionOptions,\n CreateSubscriptionResponse,\n GetSubscriptionOptions,\n ProductSubscriptionResponse,\n} from \"../types\";\nimport { calculateBillingAuthSignature } from \"./auth\";\nimport { BillingEnvironmentConfig } from \"../types\";\nimport {\n loginToBillingApi,\n logoutFromBillingApi,\n getBillingApiSession,\n type BillingSessionInfo,\n type BillingLoginResult,\n type BillingLoginRequest,\n} from \"../auth/billingSession\";\n\nexport interface BillingApiClientOptions {\n /**\n * Use session-based authentication instead of per-request signatures.\n * When true, the client will rely on session cookies set by SIWE login.\n * When false (default), uses EIP-712 typed data signatures for each request.\n */\n useSession?: boolean;\n}\n\n/**\n * BillingAPI Client for managing product subscriptions.\n */\nexport class BillingApiClient {\n private readonly useSession: boolean;\n\n constructor(\n private readonly config: BillingEnvironmentConfig,\n private readonly walletClient: WalletClient | null,\n private readonly options: BillingApiClientOptions = {},\n ) {\n this.useSession = options.useSession ?? false;\n\n // Validate that walletClient is provided when not using session auth\n if (!this.useSession && !walletClient) {\n throw new Error(\"WalletClient is required when not using session authentication\");\n }\n }\n\n /**\n * Get the address of the connected wallet\n * Returns undefined if using session auth without a wallet client\n */\n get address(): Address | undefined {\n const account = this.walletClient?.account;\n if (!account) {\n if (!this.useSession) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n return undefined;\n }\n return account.address;\n }\n\n /**\n * Get the base URL of the billing API\n */\n get baseUrl(): string {\n return this.config.billingApiServerURL;\n }\n\n // ==========================================================================\n // SIWE Session Methods\n // ==========================================================================\n\n /**\n * Login to the billing API using SIWE\n *\n * This establishes a session with the billing API by verifying the SIWE message\n * and signature. On success, a session cookie is set in the browser.\n *\n * @param request - Login request containing SIWE message and signature\n * @returns Login result with the authenticated address\n *\n * @example\n * ```typescript\n * const { message } = createSiweMessage({\n * address: userAddress,\n * chainId: 11155111,\n * domain: window.location.host,\n * uri: window.location.origin,\n * });\n *\n * const signature = await signMessageAsync({ message });\n * const result = await billingClient.siweLogin({ message, signature });\n * ```\n */\n async siweLogin(request: BillingLoginRequest): Promise<BillingLoginResult> {\n return loginToBillingApi({ baseUrl: this.baseUrl }, request);\n }\n\n /**\n * Logout from the billing API\n *\n * This destroys the current session and clears the session cookie.\n */\n async siweLogout(): Promise<void> {\n return logoutFromBillingApi({ baseUrl: this.baseUrl });\n }\n\n /**\n * Get the current session status from the billing API\n *\n * @returns Session information including authentication status and address\n */\n async getSession(): Promise<BillingSessionInfo> {\n return getBillingApiSession({ baseUrl: this.baseUrl });\n }\n\n /**\n * Check if there is a valid session\n *\n * @returns True if session is authenticated, false otherwise\n */\n async isSessionValid(): Promise<boolean> {\n const session = await this.getSession();\n return session.authenticated;\n }\n\n // ==========================================================================\n // Subscription Methods\n // ==========================================================================\n\n async createSubscription(\n productId: ProductID = \"compute\",\n options?: CreateSubscriptionOptions,\n ): Promise<CreateSubscriptionResponse> {\n const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;\n const body = options\n ? {\n success_url: options.successUrl,\n cancel_url: options.cancelUrl,\n return_url: options.returnUrl,\n }\n : undefined;\n const resp = await this.makeAuthenticatedRequest(endpoint, \"POST\", productId, body);\n return resp.json();\n }\n\n async getSubscription(\n productId: ProductID = \"compute\",\n options?: GetSubscriptionOptions,\n ): Promise<ProductSubscriptionResponse> {\n let endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;\n if (options?.returnUrl) {\n const params = new URLSearchParams({ return_url: options.returnUrl });\n endpoint = `${endpoint}?${params.toString()}`;\n }\n const resp = await this.makeAuthenticatedRequest(endpoint, \"GET\", productId);\n return resp.json();\n }\n\n async cancelSubscription(productId: ProductID = \"compute\"): Promise<void> {\n const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;\n await this.makeAuthenticatedRequest(endpoint, \"DELETE\", productId);\n }\n\n // ==========================================================================\n // Internal Methods\n // ==========================================================================\n\n /**\n * Make an authenticated request to the billing API\n *\n * Uses session auth if useSession is true, otherwise uses EIP-712 signature auth.\n */\n private async makeAuthenticatedRequest(\n url: string,\n method: \"GET\" | \"POST\" | \"DELETE\",\n productId: ProductID,\n body?: Record<string, unknown>,\n ): Promise<{ json: () => Promise<any>; text: () => Promise<string> }> {\n if (this.useSession) {\n return this.makeSessionAuthenticatedRequest(url, method, body);\n }\n return this.makeSignatureAuthenticatedRequest(url, method, productId, body);\n }\n\n /**\n * Make a request using session-based authentication (cookies)\n */\n private async makeSessionAuthenticatedRequest(\n url: string,\n method: \"GET\" | \"POST\" | \"DELETE\",\n body?: Record<string, unknown>,\n ): Promise<{ json: () => Promise<any>; text: () => Promise<string> }> {\n const headers: Record<string, string> = {};\n\n // Add content-type header if body is present\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n try {\n // Use fetch with credentials: 'include' for cookie-based auth\n const response = await fetch(url, {\n method,\n credentials: \"include\", // Include cookies for session management\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n const status = response.status;\n const statusText = status >= 200 && status < 300 ? \"OK\" : \"Error\";\n\n if (status < 200 || status >= 300) {\n let errorBody: string;\n try {\n errorBody = await response.text();\n } catch {\n errorBody = statusText;\n }\n throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${errorBody}`);\n }\n\n // Return Response-like object for compatibility\n const responseData = await response.json();\n return {\n json: async () => responseData,\n text: async () => JSON.stringify(responseData),\n };\n } catch (error: any) {\n // Handle network errors\n if (error.name === \"TypeError\" || error.message?.includes(\"fetch\")) {\n throw new Error(\n `Failed to connect to BillingAPI at ${url}: ${error.message}\\n` +\n `Please check:\\n` +\n `1. Your internet connection\\n` +\n `2. The API server is accessible: ${this.config.billingApiServerURL}\\n` +\n `3. Firewall/proxy settings`,\n );\n }\n // Re-throw other errors as-is\n throw error;\n }\n }\n\n /**\n * Make a request using EIP-712 signature authentication\n */\n private async makeSignatureAuthenticatedRequest(\n url: string,\n method: \"GET\" | \"POST\" | \"DELETE\",\n productId: ProductID,\n body?: Record<string, unknown>,\n ): Promise<{ json: () => Promise<any>; text: () => Promise<string> }> {\n if (!this.walletClient) {\n throw new Error(\"WalletClient is required for signature authentication\");\n }\n\n // Calculate expiry (5 minutes from now)\n const expiry = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);\n\n // Use EIP-712 typed data signature for billing auth\n const { signature } = await calculateBillingAuthSignature({\n walletClient: this.walletClient,\n product: productId,\n expiry,\n });\n\n // Prepare headers\n const headers: Record<string, string> = {\n Authorization: `Bearer ${signature}`,\n \"X-Account\": this.address!,\n \"X-Expiry\": expiry.toString(),\n };\n\n // Add content-type header if body is present\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n try {\n // Use axios to make the request\n const response: AxiosResponse = await axios({\n method,\n url,\n headers,\n data: body,\n timeout: 30_000,\n maxRedirects: 0,\n validateStatus: () => true, // Don't throw on any status\n });\n\n const status = response.status;\n const statusText = status >= 200 && status < 300 ? \"OK\" : \"Error\";\n\n if (status < 200 || status >= 300) {\n const body =\n typeof response.data === \"string\" ? response.data : JSON.stringify(response.data);\n throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${body}`);\n }\n\n // Return Response-like object for compatibility\n return {\n json: async () => response.data,\n text: async () =>\n typeof response.data === \"string\" ? response.data : JSON.stringify(response.data),\n };\n } catch (error: any) {\n // Handle network errors\n if (\n error.message?.includes(\"fetch failed\") ||\n error.message?.includes(\"ECONNREFUSED\") ||\n error.message?.includes(\"ENOTFOUND\") ||\n error.cause\n ) {\n const cause = error.cause?.message || error.cause || error.message;\n throw new Error(\n `Failed to connect to BillingAPI at ${url}: ${cause}\\n` +\n `Please check:\\n` +\n `1. Your internet connection\\n` +\n `2. The API server is accessible: ${this.config.billingApiServerURL}\\n` +\n `3. Firewall/proxy settings`,\n );\n }\n // Re-throw other errors as-is\n throw error;\n }\n }\n}\n","/**\n * Shared authentication utilities for API clients\n */\n\nimport { Hex, parseAbi, type Address, type PublicClient, type WalletClient } from \"viem\";\n\n// Minimal AppController ABI for permission calculation\nconst APP_CONTROLLER_ABI = parseAbi([\n \"function calculateApiPermissionDigestHash(bytes4 permission, uint256 expiry) view returns (bytes32)\",\n]);\n\nexport interface PermissionSignatureOptions {\n permission: Hex;\n expiry: bigint;\n appControllerAddress: Address;\n publicClient: PublicClient;\n walletClient: WalletClient;\n}\n\nexport interface PermissionSignatureResult {\n signature: string;\n digest: Hex;\n}\n\n/**\n * Calculate permission digest via AppController contract and sign it with EIP-191\n *\n * Works with any WalletClient - whether backed by a local private key or external signer.\n */\nexport async function calculatePermissionSignature(\n options: PermissionSignatureOptions,\n): Promise<PermissionSignatureResult> {\n const { permission, expiry, appControllerAddress, publicClient, walletClient } = options;\n\n // Calculate permission digest hash using AppController contract\n const digest = (await publicClient.readContract({\n address: appControllerAddress,\n abi: APP_CONTROLLER_ABI,\n functionName: \"calculateApiPermissionDigestHash\",\n args: [permission, expiry],\n })) as Hex;\n\n // Get account from wallet client\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n // Sign the digest using EIP-191 (signMessage handles prefixing automatically)\n const signature = await walletClient.signMessage({\n account,\n message: { raw: digest },\n });\n\n return { signature, digest };\n}\n\nexport interface BillingAuthSignatureOptions {\n walletClient: WalletClient;\n product: string;\n expiry: bigint;\n}\n\nexport interface BillingAuthSignatureResult {\n signature: Hex;\n expiry: bigint;\n}\n\nconst generateBillingSigData = (product: string, expiry: bigint) => {\n return {\n domain: {\n name: \"EigenCloud Billing API\",\n version: \"1\",\n },\n types: {\n BillingAuth: [\n { name: \"product\", type: \"string\" },\n { name: \"expiry\", type: \"uint256\" },\n ],\n },\n primaryType: \"BillingAuth\" as const,\n message: {\n product,\n expiry,\n },\n };\n};\n\n/**\n * Sign billing authentication message using EIP-712 typed data\n */\nexport async function calculateBillingAuthSignature(\n options: BillingAuthSignatureOptions,\n): Promise<BillingAuthSignatureResult> {\n const { walletClient, product, expiry } = options;\n\n // Get account from wallet client\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n // Sign using EIP-712 typed data\n const signature = await walletClient.signTypedData({\n account,\n ...generateBillingSigData(product, expiry),\n });\n\n return { signature, expiry };\n}\n\nexport interface BuildAuthSignatureOptions {\n walletClient: WalletClient;\n expiry: bigint;\n}\n\nexport interface BuildAuthSignatureResult {\n signature: Hex;\n expiry: bigint;\n}\n\n/**\n * Sign build authentication message using EIP-712 typed data\n */\nexport async function calculateBuildAuthSignature(\n options: BuildAuthSignatureOptions,\n): Promise<BuildAuthSignatureResult> {\n const { walletClient, expiry } = options;\n\n // Get account from wallet client\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n const signature = await walletClient.signTypedData({\n account,\n domain: {\n name: \"EigenCloud Build API\",\n version: \"1\",\n },\n types: {\n BuildAuth: [{ name: \"expiry\", type: \"uint256\" }],\n },\n primaryType: \"BuildAuth\",\n message: {\n expiry,\n },\n });\n\n return { signature, expiry };\n}\n","/**\n * Billing API Session Management\n *\n * This module provides utilities for managing authentication sessions with the billing API\n * using SIWE (Sign-In with Ethereum).\n *\n * The billing API now supports the same SIWE-based session authentication as the compute API,\n * allowing users to sign once and authenticate to both APIs simultaneously.\n */\n\nimport { Address, Hex } from \"viem\";\n\nexport interface BillingApiConfig {\n /** Base URL of the billing API (e.g., \"https://billing.eigencloud.xyz\") */\n baseUrl: string;\n}\n\nexport interface BillingSessionInfo {\n /** Whether the session is authenticated */\n authenticated: boolean;\n /** Authenticated wallet address (if authenticated) */\n address?: Address;\n /** Chain ID used for authentication (if authenticated) */\n chainId?: number;\n /** Unix timestamp when authentication occurred (if authenticated) */\n authenticatedAt?: number;\n}\n\nexport interface BillingLoginResult {\n /** Whether login was successful */\n success: boolean;\n /** Authenticated wallet address */\n address: Address;\n}\n\nexport interface BillingLoginRequest {\n /** SIWE message string */\n message: string;\n /** Hex-encoded signature (with or without 0x prefix) */\n signature: Hex | string;\n}\n\n/**\n * Error thrown when billing session operations fail\n */\nexport class BillingSessionError extends Error {\n constructor(\n message: string,\n public readonly code:\n | \"NETWORK_ERROR\"\n | \"INVALID_SIGNATURE\"\n | \"INVALID_MESSAGE\"\n | \"SESSION_EXPIRED\"\n | \"UNAUTHORIZED\"\n | \"UNKNOWN\",\n public readonly statusCode?: number,\n ) {\n super(message);\n this.name = \"BillingSessionError\";\n }\n}\n\n/**\n * Strip 0x prefix from hex string if present\n */\nfunction stripHexPrefix(hex: string): string {\n return hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n}\n\n/**\n * Parse error response body\n */\nasync function parseErrorResponse(response: Response): Promise<string> {\n try {\n const data = (await response.json()) as { error?: string };\n return data.error || response.statusText;\n } catch {\n return response.statusText;\n }\n}\n\n/**\n * Login to the billing API using SIWE\n *\n * This establishes a session with the billing API by verifying the SIWE message\n * and signature. On success, a session cookie is set in the browser.\n *\n * The billing API accepts the same SIWE message format as the compute API,\n * so users only need to sign once and can send the same message/signature\n * to both APIs.\n *\n * @param config - Billing API configuration\n * @param request - Login request containing SIWE message and signature\n * @returns Login result with the authenticated address\n *\n * @example\n * ```typescript\n * import { createSiweMessage, loginToBillingApi } from \"@layr-labs/ecloud-sdk/browser\";\n *\n * const { message } = createSiweMessage({\n * address: userAddress,\n * chainId: 11155111,\n * domain: window.location.host,\n * uri: window.location.origin,\n * });\n *\n * const signature = await signMessageAsync({ message });\n *\n * // Can send to both APIs with the same message/signature\n * const [computeResult, billingResult] = await Promise.all([\n * loginToComputeApi({ baseUrl: computeApiUrl }, { message, signature }),\n * loginToBillingApi({ baseUrl: billingApiUrl }, { message, signature }),\n * ]);\n * ```\n */\nexport async function loginToBillingApi(\n config: BillingApiConfig,\n request: BillingLoginRequest,\n): Promise<BillingLoginResult> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/siwe/login`, {\n method: \"POST\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n message: request.message,\n signature: stripHexPrefix(request.signature),\n }),\n });\n } catch (error) {\n throw new BillingSessionError(\n `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,\n \"NETWORK_ERROR\",\n );\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n const status = response.status;\n\n if (status === 400) {\n if (errorMessage.toLowerCase().includes(\"siwe\")) {\n throw new BillingSessionError(`Invalid SIWE message: ${errorMessage}`, \"INVALID_MESSAGE\", status);\n }\n throw new BillingSessionError(`Bad request: ${errorMessage}`, \"INVALID_MESSAGE\", status);\n }\n\n if (status === 401) {\n throw new BillingSessionError(`Invalid signature: ${errorMessage}`, \"INVALID_SIGNATURE\", status);\n }\n\n throw new BillingSessionError(`Login failed: ${errorMessage}`, \"UNKNOWN\", status);\n }\n\n const data = (await response.json()) as { success: boolean; address: string };\n\n return {\n success: data.success,\n address: data.address as Address,\n };\n}\n\n/**\n * Get the current session status from the billing API\n *\n * @param config - Billing API configuration\n * @returns Session information including authentication status and address\n *\n * @example\n * ```typescript\n * const session = await getBillingApiSession({ baseUrl: \"https://billing.eigencloud.xyz\" });\n * if (session.authenticated) {\n * console.log(`Logged in as ${session.address}`);\n * }\n * ```\n */\nexport async function getBillingApiSession(config: BillingApiConfig): Promise<BillingSessionInfo> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/session`, {\n method: \"GET\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n } catch {\n // Network error - return unauthenticated session\n return {\n authenticated: false,\n };\n }\n\n // If we get a 401, return unauthenticated session\n if (response.status === 401) {\n return {\n authenticated: false,\n };\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n throw new BillingSessionError(`Failed to get session: ${errorMessage}`, \"UNKNOWN\", response.status);\n }\n\n const data = (await response.json()) as {\n authenticated: boolean;\n address?: string;\n chainId?: number;\n authenticatedAt?: number;\n };\n\n return {\n authenticated: data.authenticated,\n address: data.address as Address | undefined,\n chainId: data.chainId,\n authenticatedAt: data.authenticatedAt,\n };\n}\n\n/**\n * Logout from the billing API\n *\n * This destroys the current session and clears the session cookie.\n *\n * @param config - Billing API configuration\n *\n * @example\n * ```typescript\n * await logoutFromBillingApi({ baseUrl: \"https://billing.eigencloud.xyz\" });\n * ```\n */\nexport async function logoutFromBillingApi(config: BillingApiConfig): Promise<void> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/logout`, {\n method: \"POST\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n } catch (error) {\n throw new BillingSessionError(\n `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,\n \"NETWORK_ERROR\",\n );\n }\n\n // Ignore 401 errors during logout (already logged out)\n if (response.status === 401) {\n return;\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n throw new BillingSessionError(`Logout failed: ${errorMessage}`, \"UNKNOWN\", response.status);\n }\n}\n\n/**\n * Check if a billing session is still valid (not expired)\n *\n * This is a convenience function that checks the session status\n * and returns a boolean.\n *\n * @param config - Billing API configuration\n * @returns True if session is authenticated, false otherwise\n */\nexport async function isBillingSessionValid(config: BillingApiConfig): Promise<boolean> {\n const session = await getBillingApiSession(config);\n return session.authenticated;\n}\n\n/**\n * Login to both compute and billing APIs simultaneously\n *\n * This is a convenience function that sends the same SIWE message and signature\n * to both APIs in parallel, establishing sessions with both services at once.\n *\n * @param computeConfig - Compute API configuration\n * @param billingConfig - Billing API configuration\n * @param request - Login request containing SIWE message and signature\n * @returns Object containing login results for both APIs\n *\n * @example\n * ```typescript\n * import { createSiweMessage, loginToBothApis } from \"@layr-labs/ecloud-sdk/browser\";\n *\n * const { message } = createSiweMessage({\n * address: userAddress,\n * chainId: 11155111,\n * domain: window.location.host,\n * uri: window.location.origin,\n * });\n *\n * const signature = await signMessageAsync({ message });\n * const { compute, billing } = await loginToBothApis(\n * { baseUrl: computeApiUrl },\n * { baseUrl: billingApiUrl },\n * { message, signature }\n * );\n * ```\n */\nexport async function loginToBothApis(\n computeConfig: { baseUrl: string },\n billingConfig: BillingApiConfig,\n request: BillingLoginRequest,\n): Promise<{\n compute: BillingLoginResult;\n billing: BillingLoginResult;\n}> {\n // Import the compute login function dynamically to avoid circular dependencies\n const { loginToComputeApi } = await import(\"./session\");\n\n const [compute, billing] = await Promise.all([\n loginToComputeApi(computeConfig, request),\n loginToBillingApi(billingConfig, request),\n ]);\n\n return { compute, billing };\n}\n\n/**\n * Logout from both compute and billing APIs simultaneously\n *\n * @param computeConfig - Compute API configuration\n * @param billingConfig - Billing API configuration\n *\n * @example\n * ```typescript\n * await logoutFromBothApis(\n * { baseUrl: computeApiUrl },\n * { baseUrl: billingApiUrl }\n * );\n * ```\n */\nexport async function logoutFromBothApis(\n computeConfig: { baseUrl: string },\n billingConfig: BillingApiConfig,\n): Promise<void> {\n // Import the compute logout function dynamically to avoid circular dependencies\n const { logoutFromComputeApi } = await import(\"./session\");\n\n await Promise.all([\n logoutFromComputeApi(computeConfig),\n logoutFromBillingApi(billingConfig),\n ]);\n}\n","/**\n * Environment configuration for different networks\n */\n\nimport { Address } from \"viem\";\nimport { BillingEnvironmentConfig, EnvironmentConfig } from \"../types\";\n\n// Chain IDs\nexport const SEPOLIA_CHAIN_ID = 11155111;\nexport const MAINNET_CHAIN_ID = 1;\n\n// Common addresses across all chains\nexport const CommonAddresses: Record<string, string> = {\n ERC7702Delegator: \"0x63c0c19a282a1b52b07dd5a65b58948a07dae32b\",\n};\n\n// Addresses specific to each chain\nexport const ChainAddresses: Record<number, Record<string, string>> = {\n [MAINNET_CHAIN_ID]: {\n PermissionController: \"0x25E5F8B1E7aDf44518d35D5B2271f114e081f0E5\",\n },\n [SEPOLIA_CHAIN_ID]: {\n PermissionController: \"0x44632dfBdCb6D3E21EF613B0ca8A6A0c618F5a37\",\n },\n};\n\n// Billing environment configurations (separate from chain environments)\nconst BILLING_ENVIRONMENTS: Record<\"dev\" | \"prod\", BillingEnvironmentConfig> = {\n dev: {\n billingApiServerURL: \"https://billingapi-dev.eigencloud.xyz\",\n },\n prod: {\n billingApiServerURL: \"https://billingapi.eigencloud.xyz\",\n },\n};\n\n// Chain environment configurations\nconst ENVIRONMENTS: Record<string, Omit<EnvironmentConfig, \"chainID\">> = {\n \"sepolia-dev\": {\n name: \"sepolia\",\n build: \"dev\",\n appControllerAddress: \"0xa86DC1C47cb2518327fB4f9A1627F51966c83B92\",\n permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,\n erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,\n kmsServerURL: \"http://10.128.0.57:8080\",\n userApiServerURL: \"https://userapi-compute-sepolia-dev.eigencloud.xyz\",\n defaultRPCURL: \"https://ethereum-sepolia-rpc.publicnode.com\",\n usdcCreditsAddress: \"0xbdA3897c3A428763B59015C64AB766c288C97376\" as Address,\n },\n sepolia: {\n name: \"sepolia\",\n build: \"prod\",\n appControllerAddress: \"0x0dd810a6ffba6a9820a10d97b659f07d8d23d4E2\",\n permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,\n erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,\n kmsServerURL: \"http://10.128.15.203:8080\",\n userApiServerURL: \"https://userapi-compute-sepolia-prod.eigencloud.xyz\",\n defaultRPCURL: \"https://ethereum-sepolia-rpc.publicnode.com\",\n billingRPCURL: \"https://ethereum-rpc.publicnode.com\",\n },\n \"mainnet-alpha\": {\n name: \"mainnet-alpha\",\n build: \"prod\",\n appControllerAddress: \"0xc38d35Fc995e75342A21CBd6D770305b142Fbe67\",\n permissionControllerAddress: ChainAddresses[MAINNET_CHAIN_ID].PermissionController,\n erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,\n kmsServerURL: \"http://10.128.0.2:8080\",\n userApiServerURL: \"https://userapi-compute.eigencloud.xyz\",\n defaultRPCURL: \"https://ethereum-rpc.publicnode.com\",\n },\n};\n\nconst CHAIN_ID_TO_ENVIRONMENT: Record<string, string> = {\n [SEPOLIA_CHAIN_ID.toString()]: \"sepolia\",\n [MAINNET_CHAIN_ID.toString()]: \"mainnet-alpha\",\n};\n\n/**\n * Get environment configuration\n */\nexport function getEnvironmentConfig(environment: string, chainID?: bigint): EnvironmentConfig {\n const env = ENVIRONMENTS[environment];\n if (!env) {\n throw new Error(`Unknown environment: ${environment}`);\n }\n\n // Check if environment is available in current build\n if (!isEnvironmentAvailable(environment)) {\n throw new Error(\n `Environment ${environment} is not available in this build type. ` +\n `Available environments: ${getAvailableEnvironments().join(\", \")}`,\n );\n }\n\n // If chainID provided, validate it matches\n if (chainID) {\n const expectedEnv = CHAIN_ID_TO_ENVIRONMENT[chainID.toString()];\n if (expectedEnv && expectedEnv !== environment) {\n throw new Error(`Environment ${environment} does not match chain ID ${chainID}`);\n }\n }\n\n // Determine chain ID from environment if not provided\n // Both \"sepolia\" and \"sepolia-dev\" use Sepolia chain ID\n const resolvedChainID =\n chainID ||\n (environment === \"sepolia\" || environment === \"sepolia-dev\"\n ? SEPOLIA_CHAIN_ID\n : MAINNET_CHAIN_ID);\n\n return {\n ...env,\n chainID: BigInt(resolvedChainID),\n };\n}\n\n/**\n * Get billing environment configuration\n * @param build - The build type (\"dev\" or \"prod\")\n */\nexport function getBillingEnvironmentConfig(build: \"dev\" | \"prod\"): {\n billingApiServerURL: string;\n} {\n const config = BILLING_ENVIRONMENTS[build];\n if (!config) {\n throw new Error(`Unknown billing environment: ${build}`);\n }\n return config;\n}\n\n/**\n * Detect environment from chain ID\n */\nexport function detectEnvironmentFromChainID(chainID: bigint): string | undefined {\n return CHAIN_ID_TO_ENVIRONMENT[chainID.toString()];\n}\n\n/**\n * Get build type from environment variable or build-time constant (defaults to 'prod')\n * BUILD_TYPE_BUILD_TIME is replaced at build time by tsup's define option\n */\n// @ts-ignore - BUILD_TYPE_BUILD_TIME is injected at build time by tsup\ndeclare const BUILD_TYPE_BUILD_TIME: string | undefined;\n\nexport function getBuildType(): \"dev\" | \"prod\" {\n // First check build-time constant (set by tsup define)\n // @ts-ignore - BUILD_TYPE_BUILD_TIME is injected at build time\n const buildTimeType =\n typeof BUILD_TYPE_BUILD_TIME !== \"undefined\" ? BUILD_TYPE_BUILD_TIME?.toLowerCase() : undefined;\n\n // Fall back to runtime environment variable\n const runtimeType = process.env.BUILD_TYPE?.toLowerCase();\n\n const buildType = buildTimeType || runtimeType;\n\n if (buildType === \"dev\") {\n return \"dev\";\n }\n return \"prod\";\n}\n\n/**\n * Get available environments based on build type\n * - dev: only \"sepolia-dev\"\n * - prod: \"sepolia\" and \"mainnet-alpha\"\n */\nexport function getAvailableEnvironments(): string[] {\n const buildType = getBuildType();\n\n if (buildType === \"dev\") {\n return [\"sepolia-dev\"];\n }\n\n // prod build\n return [\"sepolia\", \"mainnet-alpha\"];\n}\n\n/**\n * Check if an environment is available in the current build\n */\nexport function isEnvironmentAvailable(environment: string): boolean {\n return getAvailableEnvironments().includes(environment);\n}\n\n/**\n * Check if environment is mainnet (chain ID 1)\n */\nexport function isMainnet(environmentConfig: EnvironmentConfig): boolean {\n return environmentConfig.chainID === BigInt(MAINNET_CHAIN_ID);\n}\n","/**\n * Default logger\n */\n\nimport { Logger } from \"../types\";\n\nexport const defaultLogger: Logger = {\n info: (...args) => console.info(...args),\n warn: (...args) => console.warn(...args),\n error: (...args) => console.error(...args),\n debug: (...args) => console.debug(...args),\n};\n\nexport const getLogger: (verbose?: boolean) => Logger = (verbose?: boolean) => ({\n info: (...args) => console.info(...args),\n warn: (...args) => console.warn(...args),\n error: (...args) => console.error(...args),\n debug: (...args) => verbose && console.debug(...args),\n});\n","import axios, { AxiosResponse } from \"axios\";\nimport { Address, Hex, type PublicClient, type WalletClient } from \"viem\";\nimport { calculatePermissionSignature } from \"./auth\";\nimport { EnvironmentConfig } from \"../types\";\nimport { stripHexPrefix } from \"./helpers\";\nimport { requestWithRetry } from \"./retry\";\nimport {\n loginToComputeApi,\n logoutFromComputeApi,\n getComputeApiSession,\n type LoginRequest,\n type LoginResult,\n type SessionInfo,\n} from \"../auth/session\";\n\nexport interface AppProfileInfo {\n name: string;\n website?: string;\n description?: string;\n xURL?: string;\n imageURL?: string;\n}\n\nexport interface AppMetrics {\n cpu_utilization_percent?: number;\n memory_utilization_percent?: number;\n memory_used_bytes?: number;\n memory_total_bytes?: number;\n}\n\nexport interface DerivedAddress {\n address: string;\n derivationPath: string;\n}\n\nexport interface AppInfo {\n address: Address;\n status: string;\n ip: string;\n machineType: string;\n profile?: AppProfileInfo;\n metrics?: AppMetrics;\n evmAddresses: DerivedAddress[];\n solanaAddresses: DerivedAddress[];\n}\n\nexport interface AppInfoResponse {\n apps: Array<{\n addresses: {\n data: {\n evmAddresses: DerivedAddress[];\n solanaAddresses: DerivedAddress[];\n };\n signature: string;\n };\n app_status: string;\n ip: string;\n machine_type: string;\n profile?: AppProfileInfo;\n metrics?: AppMetrics;\n }>;\n}\n\n// ==================== App Releases (/apps/:id) ====================\n\ntype JsonObject = Record<string, unknown>;\n\nfunction isJsonObject(value: unknown): value is JsonObject {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readString(obj: JsonObject, key: string): string | undefined {\n const v = obj[key];\n return typeof v === \"string\" ? v : undefined;\n}\n\nfunction readNumber(obj: JsonObject, key: string): number | undefined {\n const v = obj[key];\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n}\n\nexport type AppContractStatus = \"STARTED\" | \"STOPPED\" | \"TERMINATED\" | \"SUSPENDED\" | string;\n\nexport interface AppReleaseBuild {\n buildId?: string;\n billingAddress?: string;\n repoUrl?: string;\n gitRef?: string;\n status?: string;\n buildType?: string;\n imageName?: string;\n imageDigest?: string;\n imageUrl?: string;\n provenanceJson?: unknown;\n provenanceSignature?: string;\n createdAt?: string;\n updatedAt?: string;\n errorMessage?: string;\n dependencies?: Record<string, AppReleaseBuild>;\n}\n\nexport interface AppRelease {\n appId?: string;\n rmsReleaseId?: string;\n imageDigest?: string;\n registryUrl?: string;\n publicEnv?: string;\n encryptedEnv?: string;\n upgradeByTime?: number;\n createdAt?: string;\n createdAtBlock?: string;\n build?: AppReleaseBuild;\n}\n\nexport interface AppResponse {\n id: string;\n creator?: string;\n contractStatus?: AppContractStatus;\n releases: AppRelease[];\n}\n\nconst MAX_ADDRESS_COUNT = 5;\n\n// Permission constants\nexport const CanViewAppLogsPermission = \"0x2fd3f2fe\" as Hex;\nexport const CanViewSensitiveAppInfoPermission = \"0x0e67b22f\" as Hex;\nexport const CanUpdateAppProfilePermission = \"0x036fef61\" as Hex;\n\n/**\n * SDK_VERSION_BUILD_TIME is replaced at build time by tsup's define option\n */\n// @ts-ignore - SDK_VERSION_BUILD_TIME is injected at build time by tsup\ndeclare const SDK_VERSION_BUILD_TIME: string | undefined;\n\n/**\n * Get the default client ID using the build-time version\n */\nfunction getDefaultClientId(): string {\n // @ts-ignore - SDK_VERSION_BUILD_TIME is injected at build time\n const version = typeof SDK_VERSION_BUILD_TIME !== \"undefined\" ? SDK_VERSION_BUILD_TIME : \"0.0.0\";\n return `ecloud-sdk/v${version}`;\n}\n\n/**\n * Options for UserApiClient\n */\nexport interface UserApiClientOptions {\n /** Custom client ID for request tracking */\n clientId?: string;\n /**\n * Use SIWE session authentication instead of per-request signatures.\n * When true, requests rely on session cookies set by loginToComputeApi().\n * When false (default), each request is signed individually.\n */\n useSession?: boolean;\n}\n\n/**\n * UserAPI Client for interacting with the EigenCloud UserAPI service.\n */\nexport class UserApiClient {\n private readonly clientId: string;\n private readonly useSession: boolean;\n\n constructor(\n private readonly config: EnvironmentConfig,\n private readonly walletClient: WalletClient,\n private readonly publicClient: PublicClient,\n options?: UserApiClientOptions,\n ) {\n this.clientId = options?.clientId || getDefaultClientId();\n this.useSession = options?.useSession ?? false;\n }\n\n /**\n * Get the address of the connected wallet\n */\n get address(): Address {\n const account = this.walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n return account.address;\n }\n\n async getInfos(appIDs: Address[], addressCount = 1): Promise<AppInfo[]> {\n const count = Math.min(addressCount, MAX_ADDRESS_COUNT);\n\n const endpoint = `${this.config.userApiServerURL}/info`;\n const url = `${endpoint}?${new URLSearchParams({ apps: appIDs.join(\",\") })}`;\n\n const res = await this.makeAuthenticatedRequest(url, CanViewSensitiveAppInfoPermission);\n const result: AppInfoResponse = await res.json();\n\n // optional: verify signatures with KMS key\n // const { signingKey } = getKMSKeysForEnvironment(this.config.name);\n\n // Truncate without mutating the original object\n // API returns apps in the same order as the request, so use appIDs[i] as the address\n return result.apps.map((app, i) => {\n // TODO: Implement signature verification\n // const valid = await verifyKMSSignature(appInfo.addresses, signingKey);\n // if (!valid) {\n // throw new Error(`Invalid signature for app ${appIDs[i]}`);\n // }\n\n // Slice derived addresses to requested count\n const evmAddresses = app.addresses?.data?.evmAddresses?.slice(0, count) || [];\n const solanaAddresses = app.addresses?.data?.solanaAddresses?.slice(0, count) || [];\n\n return {\n address: appIDs[i] as Address,\n status: app.app_status,\n ip: app.ip,\n machineType: app.machine_type,\n profile: app.profile,\n metrics: app.metrics,\n evmAddresses,\n solanaAddresses,\n };\n });\n }\n\n /**\n * Get app details from UserAPI (includes releases and build/provenance info when available).\n *\n * Endpoint: GET /apps/:appAddress\n */\n async getApp(appAddress: Address): Promise<AppResponse> {\n const endpoint = `${this.config.userApiServerURL}/apps/${appAddress}`;\n const res = await this.makeAuthenticatedRequest(endpoint);\n const raw = (await res.json()) as unknown;\n\n if (!isJsonObject(raw)) {\n throw new Error(\"Unexpected /apps/:id response: expected object\");\n }\n\n const id = readString(raw, \"id\");\n if (!id) {\n throw new Error(\"Unexpected /apps/:id response: missing 'id'\");\n }\n\n const releasesRaw = raw.releases;\n const releases = Array.isArray(releasesRaw)\n ? releasesRaw.map((r) => transformAppRelease(r)).filter((r): r is AppRelease => !!r)\n : [];\n\n return {\n id,\n creator: readString(raw, \"creator\"),\n contractStatus: (readString(raw, \"contract_status\") ?? readString(raw, \"contractStatus\")) as\n | AppContractStatus\n | undefined,\n releases,\n };\n }\n\n /**\n * Get available SKUs (instance types) from UserAPI\n */\n async getSKUs(): Promise<{\n skus: Array<{\n sku: string;\n description: string;\n vcpus?: number;\n memory_mb?: number;\n monthly_price_usd?: number;\n hourly_price_usd?: number;\n platform?: string;\n }>;\n }> {\n const endpoint = `${this.config.userApiServerURL}/skus`;\n const response = await this.makeAuthenticatedRequest(endpoint);\n\n const result = await response.json();\n\n // Transform response to match expected format\n return {\n skus: result.skus || result.SKUs || [],\n };\n }\n\n /**\n * Get logs for an app\n */\n async getLogs(appID: Address): Promise<string> {\n const endpoint = `${this.config.userApiServerURL}/logs/${appID}`;\n const response = await this.makeAuthenticatedRequest(endpoint, CanViewAppLogsPermission);\n return await response.text();\n }\n\n /**\n * Get statuses for apps\n */\n async getStatuses(appIDs: Address[]): Promise<Array<{ address: Address; status: string }>> {\n const endpoint = `${this.config.userApiServerURL}/status`;\n const url = `${endpoint}?${new URLSearchParams({ apps: appIDs.join(\",\") })}`;\n const response = await this.makeAuthenticatedRequest(url);\n const result = await response.json();\n\n // Transform response to match expected format\n // The API returns an array of app statuses\n const apps = result.apps || result.Apps || [];\n return apps.map((app: any, i: number) => ({\n address: (app.address || appIDs[i]) as Address,\n status: app.app_status || app.App_Status || \"\",\n }));\n }\n\n /**\n * Upload app profile information with optional image\n *\n * @param appAddress - The app's contract address\n * @param name - Display name for the app\n * @param options - Optional fields including website, description, xURL, and image\n * @param options.image - Image file as Blob or File (browser: from input element, Node.js: new Blob([buffer]))\n * @param options.imageName - Filename for the image (required if image is provided)\n */\n async uploadAppProfile(\n appAddress: Address,\n name: string,\n options?: {\n website?: string;\n description?: string;\n xURL?: string;\n image?: Blob | File;\n imageName?: string;\n },\n ): Promise<{\n name: string;\n website?: string;\n description?: string;\n xURL?: string;\n imageURL?: string;\n }> {\n const endpoint = `${this.config.userApiServerURL}/apps/${appAddress}/profile`;\n\n // Build multipart form data using Web FormData API (works in browser and Node.js 18+)\n const formData = new FormData();\n\n // Add required name field\n formData.append(\"name\", name);\n\n // Add optional text fields\n if (options?.website) {\n formData.append(\"website\", options.website);\n }\n if (options?.description) {\n formData.append(\"description\", options.description);\n }\n if (options?.xURL) {\n formData.append(\"xURL\", options.xURL);\n }\n\n // Add optional image file (Blob or File)\n if (options?.image) {\n // If it's a File, use its name; otherwise require imageName\n const fileName =\n options.image instanceof File ? options.image.name : options.imageName || \"image\";\n formData.append(\"image\", options.image, fileName);\n }\n\n // Make authenticated POST request\n // Note: Don't set Content-Type header manually - axios will set it with the correct boundary\n const headers: Record<string, string> = {\n \"x-client-id\": this.clientId,\n };\n\n // Add auth headers if not using session auth\n if (!this.useSession) {\n const expiry = BigInt(Math.floor(Date.now() / 1000) + 5 * 60); // 5 minutes\n const authHeaders = await this.generateAuthHeaders(CanUpdateAppProfilePermission, expiry);\n Object.assign(headers, authHeaders);\n }\n\n try {\n const response: AxiosResponse = await axios.post(endpoint, formData, {\n headers,\n maxRedirects: 0,\n validateStatus: () => true, // Don't throw on any status\n maxContentLength: Infinity, // Allow large file uploads\n maxBodyLength: Infinity, // Allow large file uploads\n withCredentials: true, // Include cookies for session auth\n });\n\n const status = response.status;\n\n if (status !== 200 && status !== 201) {\n const body =\n typeof response.data === \"string\" ? response.data : JSON.stringify(response.data);\n\n // Detect Cloudflare challenge page\n if (status === 403 && body.includes(\"Cloudflare\") && body.includes(\"challenge-platform\")) {\n throw new Error(\n `Cloudflare protection is blocking the request. This is likely due to bot detection.\\n` +\n `Status: ${status}`,\n );\n }\n\n throw new Error(\n `UserAPI request failed: ${status} ${status >= 200 && status < 300 ? \"OK\" : \"Error\"} - ${body.substring(0, 500)}${body.length > 500 ? \"...\" : \"\"}`,\n );\n }\n\n return response.data;\n } catch (error: any) {\n if (\n error.message?.includes(\"fetch failed\") ||\n error.message?.includes(\"ECONNREFUSED\") ||\n error.message?.includes(\"ENOTFOUND\") ||\n error.cause\n ) {\n const cause = error.cause?.message || error.cause || error.message;\n throw new Error(\n `Failed to connect to UserAPI at ${endpoint}: ${cause}\\n` +\n `Please check:\\n` +\n `1. Your internet connection\\n` +\n `2. The API server is accessible: ${this.config.userApiServerURL}\\n` +\n `3. Firewall/proxy settings`,\n );\n }\n throw error;\n }\n }\n\n private async makeAuthenticatedRequest(\n url: string,\n permission?: Hex,\n ): Promise<{ json: () => Promise<any>; text: () => Promise<string> }> {\n const headers: Record<string, string> = {\n \"x-client-id\": this.clientId,\n };\n // Add auth headers if permission is specified and not using session auth\n if (permission && !this.useSession) {\n const expiry = BigInt(Math.floor(Date.now() / 1000) + 5 * 60); // 5 minutes\n const authHeaders = await this.generateAuthHeaders(permission, expiry);\n Object.assign(headers, authHeaders);\n }\n\n try {\n const response: AxiosResponse = await requestWithRetry({\n method: \"GET\",\n url,\n headers,\n maxRedirects: 0,\n withCredentials: true, // Include cookies for session auth\n });\n\n const status = response.status;\n const statusText = status >= 200 && status < 300 ? \"OK\" : \"Error\";\n\n if (status < 200 || status >= 300) {\n const body =\n typeof response.data === \"string\" ? response.data : JSON.stringify(response.data);\n throw new Error(`UserAPI request failed: ${status} ${statusText} - ${body}`);\n }\n\n // Return Response-like object for compatibility\n return {\n json: async () => response.data,\n text: async () =>\n typeof response.data === \"string\" ? response.data : JSON.stringify(response.data),\n };\n } catch (error: any) {\n // Handle network errors (fetch failed, connection refused, etc.)\n if (\n error.message?.includes(\"fetch failed\") ||\n error.message?.includes(\"ECONNREFUSED\") ||\n error.message?.includes(\"ENOTFOUND\") ||\n error.cause\n ) {\n const cause = error.cause?.message || error.cause || error.message;\n throw new Error(\n `Failed to connect to UserAPI at ${url}: ${cause}\\n` +\n `Please check:\\n` +\n `1. Your internet connection\\n` +\n `2. The API server is accessible: ${this.config.userApiServerURL}\\n` +\n `3. Firewall/proxy settings`,\n );\n }\n // Re-throw other errors as-is\n throw error;\n }\n }\n\n /**\n * Generate authentication headers for UserAPI requests\n */\n private async generateAuthHeaders(\n permission: Hex,\n expiry: bigint,\n ): Promise<Record<string, string>> {\n // Calculate permission signature using shared auth utility\n const { signature } = await calculatePermissionSignature({\n permission,\n expiry,\n appControllerAddress: this.config.appControllerAddress,\n publicClient: this.publicClient,\n walletClient: this.walletClient,\n });\n\n // Return auth headers\n return {\n Authorization: `Bearer ${stripHexPrefix(signature)}`,\n \"X-eigenx-expiry\": expiry.toString(),\n };\n }\n\n // ==========================================================================\n // SIWE Session Management\n // ==========================================================================\n\n /**\n * Login to the compute API using SIWE (Sign-In with Ethereum)\n *\n * This establishes a session with the compute API by verifying the SIWE message\n * and signature. On success, a session cookie is set in the browser.\n *\n * @param request - Login request containing SIWE message and signature\n * @returns Login result with the authenticated address\n *\n * @example\n * ```typescript\n * import { createSiweMessage } from \"@layr-labs/ecloud-sdk/browser\";\n *\n * const { message } = createSiweMessage({\n * address: userAddress,\n * chainId: 11155111,\n * domain: window.location.host,\n * uri: window.location.origin,\n * });\n *\n * const signature = await signMessageAsync({ message });\n * const result = await client.siweLogin({ message, signature });\n * ```\n */\n async siweLogin(request: LoginRequest): Promise<LoginResult> {\n return loginToComputeApi({ baseUrl: this.config.userApiServerURL }, request);\n }\n\n /**\n * Logout from the compute API\n *\n * This destroys the current session and clears the session cookie.\n *\n * @example\n * ```typescript\n * await client.siweLogout();\n * ```\n */\n async siweLogout(): Promise<void> {\n return logoutFromComputeApi({ baseUrl: this.config.userApiServerURL });\n }\n\n /**\n * Get the current SIWE session status from the compute API\n *\n * @returns Session information including authentication status and address\n *\n * @example\n * ```typescript\n * const session = await client.getSiweSession();\n * if (session.authenticated) {\n * console.log(`Logged in as ${session.address}`);\n * }\n * ```\n */\n async getSiweSession(): Promise<SessionInfo> {\n return getComputeApiSession({ baseUrl: this.config.userApiServerURL });\n }\n}\n\nfunction transformAppReleaseBuild(raw: unknown): AppReleaseBuild | undefined {\n if (!isJsonObject(raw)) return undefined;\n\n const depsRaw = raw.dependencies;\n const deps: Record<string, AppReleaseBuild> | undefined = isJsonObject(depsRaw)\n ? Object.fromEntries(\n Object.entries(depsRaw).flatMap(([digest, depRaw]) => {\n const parsed = transformAppReleaseBuild(depRaw);\n return parsed ? ([[digest, parsed]] as const) : [];\n }),\n )\n : undefined;\n\n return {\n buildId: readString(raw, \"build_id\") ?? readString(raw, \"buildId\"),\n billingAddress: readString(raw, \"billing_address\") ?? readString(raw, \"billingAddress\"),\n repoUrl: readString(raw, \"repo_url\") ?? readString(raw, \"repoUrl\"),\n gitRef: readString(raw, \"git_ref\") ?? readString(raw, \"gitRef\"),\n status: readString(raw, \"status\"),\n buildType: readString(raw, \"build_type\") ?? readString(raw, \"buildType\"),\n imageName: readString(raw, \"image_name\") ?? readString(raw, \"imageName\"),\n imageDigest: readString(raw, \"image_digest\") ?? readString(raw, \"imageDigest\"),\n imageUrl: readString(raw, \"image_url\") ?? readString(raw, \"imageUrl\"),\n provenanceJson: raw.provenance_json ?? raw.provenanceJson,\n provenanceSignature:\n readString(raw, \"provenance_signature\") ?? readString(raw, \"provenanceSignature\"),\n createdAt: readString(raw, \"created_at\") ?? readString(raw, \"createdAt\"),\n updatedAt: readString(raw, \"updated_at\") ?? readString(raw, \"updatedAt\"),\n errorMessage: readString(raw, \"error_message\") ?? readString(raw, \"errorMessage\"),\n dependencies: deps,\n };\n}\n\nfunction transformAppRelease(raw: unknown): AppRelease | undefined {\n if (!isJsonObject(raw)) return undefined;\n\n return {\n appId: readString(raw, \"appId\") ?? readString(raw, \"app_id\"),\n rmsReleaseId: readString(raw, \"rmsReleaseId\") ?? readString(raw, \"rms_release_id\"),\n imageDigest: readString(raw, \"imageDigest\") ?? readString(raw, \"image_digest\"),\n registryUrl: readString(raw, \"registryUrl\") ?? readString(raw, \"registry_url\"),\n publicEnv: readString(raw, \"publicEnv\") ?? readString(raw, \"public_env\"),\n encryptedEnv: readString(raw, \"encryptedEnv\") ?? readString(raw, \"encrypted_env\"),\n upgradeByTime: readNumber(raw, \"upgradeByTime\") ?? readNumber(raw, \"upgrade_by_time\"),\n createdAt: readString(raw, \"createdAt\") ?? readString(raw, \"created_at\"),\n createdAtBlock: readString(raw, \"createdAtBlock\") ?? readString(raw, \"created_at_block\"),\n build: raw.build ? transformAppReleaseBuild(raw.build) : undefined,\n };\n}\n","/**\n * General utility helpers\n */\n\nimport { extractChain, createPublicClient, createWalletClient, http, fallback } from \"viem\";\nimport type { Chain, Hex, PublicClient, WalletClient } from \"viem\";\nimport { sepolia } from \"viem/chains\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { SUPPORTED_CHAINS } from \"../constants\";\n\n/**\n * Get a viem Chain object from a chain ID.\n * Supports mainnet (1) and sepolia (11155111), defaults to the fallback chain for unknown chains.\n */\nexport function getChainFromID(chainID: bigint, fallback: Chain = sepolia): Chain {\n const id = Number(chainID) as (typeof SUPPORTED_CHAINS)[number][\"id\"];\n return extractChain({ chains: SUPPORTED_CHAINS, id }) || fallback;\n}\n\n/**\n * Create viem clients from a private key\n *\n * This is a convenience helper for CLI and server applications that have direct\n * access to a private key. For browser applications using external wallets (MetaMask, etc.),\n * create the WalletClient directly using viem's createWalletClient with a custom transport.\n *\n * @example\n * // CLI usage with private key\n * const { walletClient, publicClient } = createClients({\n * privateKey: '0x...',\n * rpcUrl: 'https://sepolia.infura.io/v3/...',\n * chainId: 11155111n\n * });\n *\n * @example\n * // Browser usage with external wallet (create clients directly)\n * const walletClient = createWalletClient({\n * chain: sepolia,\n * transport: custom(window.ethereum!)\n * });\n * const publicClient = createPublicClient({\n * chain: sepolia,\n * transport: custom(window.ethereum!)\n * });\n */\nexport function createClients(options: {\n privateKey: string | Hex;\n rpcUrl: string | string[];\n chainId: bigint;\n}): {\n walletClient: WalletClient;\n publicClient: PublicClient;\n} {\n const { privateKey, rpcUrl, chainId } = options;\n\n const privateKeyHex = addHexPrefix(privateKey);\n const account = privateKeyToAccount(privateKeyHex);\n const chain = getChainFromID(chainId);\n\n const transport =\n typeof rpcUrl === \"string\" ? http(rpcUrl) : fallback(rpcUrl.map((url) => http(url)));\n\n const publicClient = createPublicClient({\n chain,\n transport,\n });\n\n const walletClient = createWalletClient({\n account,\n chain,\n transport,\n });\n\n return { walletClient, publicClient };\n}\n\n/**\n * Ensure hex string has 0x prefix\n */\nexport function addHexPrefix(value: string): Hex {\n return (value.startsWith(\"0x\") ? value : `0x${value}`) as Hex;\n}\n\n/**\n * Remove 0x prefix from hex string if present\n */\nexport function stripHexPrefix(value: string): string {\n return value.startsWith(\"0x\") ? value.slice(2) : value;\n}\n","/**\n * Constants used throughout the SDK\n */\n\nimport { sepolia, mainnet } from \"viem/chains\";\n\nexport const SUPPORTED_CHAINS = [mainnet, sepolia] as const;\n\nexport const DOCKER_PLATFORM = \"linux/amd64\";\nexport const REGISTRY_PROPAGATION_WAIT_SECONDS = 3;\nexport const LAYERED_DOCKERFILE_NAME = \"Dockerfile.eigencompute\";\nexport const ENV_SOURCE_SCRIPT_NAME = \"compute-source-env.sh\";\nexport const KMS_CLIENT_BINARY_NAME = \"kms-client\";\nexport const KMS_ENCRYPTION_KEY_NAME = \"kms-encryption-public-key.pem\";\nexport const KMS_SIGNING_KEY_NAME = \"kms-signing-public-key.pem\";\nexport const TLS_KEYGEN_BINARY_NAME = \"tls-keygen\";\nexport const CADDYFILE_NAME = \"Caddyfile\";\nexport const TEMP_IMAGE_PREFIX = \"ecloud-temp-\";\nexport const LAYERED_BUILD_DIR_PREFIX = \"ecloud-layered-build\";\nexport const SHA256_PREFIX = \"sha256:\";\nexport const JWT_FILE_PATH = \"/run/container_launcher/attestation_verifier_claims_token\";\n","import axios, { AxiosRequestConfig, AxiosResponse } from \"axios\";\n\nconst MAX_RETRIES = 5;\nconst INITIAL_BACKOFF_MS = 1000;\nconst MAX_BACKOFF_MS = 30000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction getRetryDelay(res: AxiosResponse, attempt: number): number {\n const backoff = Math.min(INITIAL_BACKOFF_MS * Math.pow(2, attempt), MAX_BACKOFF_MS);\n const retryAfter = res.headers[\"retry-after\"];\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10);\n if (!isNaN(seconds) && seconds > 0) {\n return Math.min(seconds * 1000, MAX_BACKOFF_MS);\n }\n }\n return backoff;\n}\n\nexport async function requestWithRetry(config: AxiosRequestConfig): Promise<AxiosResponse> {\n let lastResponse: AxiosResponse | undefined;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n const res = await axios({ ...config, validateStatus: () => true });\n lastResponse = res;\n\n if (res.status !== 429) {\n return res;\n }\n\n if (attempt < MAX_RETRIES) {\n const delay = getRetryDelay(res, attempt);\n await sleep(delay);\n }\n }\n\n return lastResponse!;\n}\n","/**\n * Billing utility functions\n */\n\nimport type { SubscriptionStatus } from \"../types\";\n\n/**\n * Check if subscription status allows deploying apps\n */\nexport function isSubscriptionActive(status: SubscriptionStatus): boolean {\n return status === \"active\" || status === \"trialing\";\n}\n","/**\n * No-op telemetry client implementation\n */\n\nimport { TelemetryClient, Metric } from \"./types\";\n\n/**\n * NoopClient implements the TelemetryClient interface with no-op methods\n */\nexport class NoopClient implements TelemetryClient {\n /**\n * AddMetric implements the TelemetryClient interface\n */\n async addMetric(_metric: Metric): Promise<void> {\n // No-op\n }\n\n /**\n * Close implements the TelemetryClient interface\n */\n async close(): Promise<void> {\n // No-op\n }\n}\n\n/**\n * Check if a client is a NoopClient\n */\nexport function isNoopClient(client: TelemetryClient): boolean {\n return client instanceof NoopClient;\n}\n","/**\n * PostHog telemetry client implementation\n *\n * Uses the official posthog-node library\n */\n\nimport { PostHog } from \"posthog-node\";\nimport { TelemetryClient, Metric, AppEnvironment } from \"./types\";\n\n/**\n * PostHogClient implements the TelemetryClient interface using posthog-node\n */\nexport class PostHogClient implements TelemetryClient {\n private readonly client: PostHog;\n private readonly namespace: string;\n private readonly appEnvironment: AppEnvironment;\n\n constructor(environment: AppEnvironment, namespace: string, apiKey: string, endpoint?: string) {\n this.namespace = namespace;\n this.appEnvironment = environment;\n\n // Initialize PostHog client\n // posthog-node expects the full URL for the host option\n const host = endpoint || \"https://us.i.posthog.com\";\n\n this.client = new PostHog(apiKey, {\n host: host,\n flushAt: 1, // Flush immediately for CLI/SDK usage\n flushInterval: 0, // Disable interval flushing\n });\n\n // Identify the user with their UUID\n this.client.identify({\n distinctId: environment.userUUID,\n properties: {\n os: environment.os,\n arch: environment.arch,\n ...(environment.cliVersion ? { cliVersion: environment.cliVersion } : {}),\n },\n });\n }\n\n /**\n * AddMetric implements the TelemetryClient interface\n */\n async addMetric(metric: Metric): Promise<void> {\n // Never throw errors from telemetry operations\n try {\n // Create properties map starting with base properties\n const props: Record<string, any> = {\n name: metric.name,\n value: metric.value,\n };\n\n // Add metric dimensions\n for (const [k, v] of Object.entries(metric.dimensions)) {\n props[k] = v;\n }\n\n // Capture event using the namespace as the event name\n // With flushAt: 1, events are automatically flushed after each capture\n this.client.capture({\n distinctId: this.appEnvironment.userUUID,\n event: this.namespace,\n properties: props,\n });\n } catch {\n // Silently ignore telemetry errors\n }\n }\n\n /**\n * Close implements the TelemetryClient interface\n */\n async close(): Promise<void> {\n try {\n // Shutdown PostHog client and flush any pending events\n // shutdown() is synchronous but internally handles async cleanup\n this.client.shutdown();\n } catch {\n // Silently ignore errors during shutdown\n }\n }\n}\n\n/**\n * Embedded PostHog API key (can be exposed in TypeScript)\n * This can be set at build time or overridden via environment variable\n */\n// @ts-ignore - POSTHOG_API_KEY_BUILD_TIME is injected at build time by tsup\ndeclare const POSTHOG_API_KEY_BUILD_TIME: string | undefined;\n\n/**\n * Get PostHog API key from environment variable or build-time constant\n */\nexport function getPostHogAPIKey(): string | undefined {\n // Priority order:\n // 1. Environment variable\n // 2. Build-time constant (set at build time)\n // Check environment variable first\n if (process.env.ECLOUD_POSTHOG_KEY) {\n return process.env.ECLOUD_POSTHOG_KEY;\n }\n\n // Return build-time constant if available\n // @ts-ignore - POSTHOG_API_KEY_BUILD_TIME is injected at build time\n return typeof POSTHOG_API_KEY_BUILD_TIME !== \"undefined\" ? POSTHOG_API_KEY_BUILD_TIME : undefined;\n}\n\n/**\n * Get PostHog endpoint from environment variable or default\n */\nexport function getPostHogEndpoint(): string {\n return process.env.ECLOUD_POSTHOG_ENDPOINT || \"https://us.i.posthog.com\";\n}\n","/**\n * Telemetry module for ECloud SDK and CLI\n *\n * Provides telemetry functionality matching the Go implementation.\n * Supports both \"ecloud-cli\" and \"ecloud-sdk\" namespaces.\n */\n\nimport { TelemetryClient, Metric, AppEnvironment } from \"./types\";\nimport { NoopClient, isNoopClient } from \"./noop\";\nimport { PostHogClient, getPostHogAPIKey, getPostHogEndpoint } from \"./posthog\";\nimport * as os from \"os\";\n\nexport * from \"./types\";\nexport * from \"./metricsContext\";\nexport * from \"./noop\";\nexport * from \"./posthog\";\nexport * from \"./wrapper\";\n\n/**\n * Options for creating a telemetry client\n */\nexport interface TelemetryClientOptions {\n /**\n * Whether telemetry is enabled (only enabled if explicitly set to true, defaults to disabled)\n */\n telemetryEnabled?: boolean;\n /**\n * PostHog API key (if not provided, will check environment variables)\n */\n apiKey?: string;\n /**\n * PostHog endpoint (if not provided, will use default)\n */\n endpoint?: string;\n}\n\n/**\n * Create a telemetry client\n *\n * @param environment - Application environment information (must include userUUID)\n * @param namespace - Namespace for telemetry events (\"ecloud-cli\" or \"ecloud-sdk\")\n * @param options - Optional telemetry client options\n * @returns TelemetryClient instance (NoopClient if telemetry is disabled or no API key)\n */\nexport function createTelemetryClient(\n environment: AppEnvironment,\n namespace: \"ecloud-cli\" | \"ecloud-sdk\",\n options?: TelemetryClientOptions,\n): TelemetryClient {\n // Check if telemetry is enabled\n // Only enabled if explicitly set to true\n // If undefined or false, telemetry is disabled\n const telemetryEnabled = options?.telemetryEnabled === true;\n\n // If telemetry is disabled, return noop client\n if (!telemetryEnabled) {\n return new NoopClient();\n }\n\n // Get API key from options, environment variable, or return noop\n const resolvedApiKey = options?.apiKey || getPostHogAPIKey();\n if (!resolvedApiKey) {\n // No API key available, return noop client\n return new NoopClient();\n }\n\n // Get endpoint from options or environment variable\n const endpoint = options?.endpoint || getPostHogEndpoint();\n\n try {\n return new PostHogClient(environment, namespace, resolvedApiKey, endpoint);\n } catch {\n // If initialization fails, return noop client\n return new NoopClient();\n }\n}\n\n/**\n * Create an AppEnvironment from current system information\n *\n * @param userUUID - User UUID for identification (required - no I/O in SDK)\n * @param cliVersion - Optional CLI version (for CLI usage)\n * @param osOverride - Optional OS override (defaults to current platform)\n * @param archOverride - Optional architecture override (defaults to current architecture)\n * @returns AppEnvironment with user UUID, OS, and architecture\n */\nexport function createAppEnvironment(\n userUUID: string,\n cliVersion?: string,\n osOverride?: string,\n archOverride?: string,\n): AppEnvironment {\n return {\n userUUID,\n cliVersion,\n os: osOverride || os.platform(),\n arch: archOverride || os.arch(),\n };\n}\n\n/**\n * Emit metrics from a metrics context\n *\n * @param client - Telemetry client to use\n * @param context - Metrics context containing metrics to emit\n * @returns Promise that resolves when all metrics are emitted\n */\nexport async function emitMetrics(\n client: TelemetryClient,\n context: {\n metrics: Metric[];\n properties: Record<string, string>;\n },\n): Promise<void> {\n if (isNoopClient(client)) {\n return;\n }\n\n // Emit each metric with properties merged into dimensions\n for (const metric of context.metrics) {\n const dimensions = {\n ...metric.dimensions,\n ...context.properties,\n };\n\n const metricWithProperties: Metric = {\n ...metric,\n dimensions,\n };\n\n try {\n await client.addMetric(metricWithProperties);\n } catch {\n // Silently ignore telemetry errors\n }\n }\n}\n","/**\n * MetricsContext management\n */\n\nimport { MetricsContext } from \"./types\";\n\n/**\n * Create a new metrics context\n */\nexport function createMetricsContext(): MetricsContext {\n return {\n startTime: new Date(),\n metrics: [],\n properties: {},\n };\n}\n\n/**\n * Add a metric to the context without dimensions\n */\nexport function addMetric(context: MetricsContext, name: string, value: number): void {\n addMetricWithDimensions(context, name, value, {});\n}\n\n/**\n * Add a metric to the context with dimensions\n */\nexport function addMetricWithDimensions(\n context: MetricsContext,\n name: string,\n value: number,\n dimensions: Record<string, string>,\n): void {\n context.metrics.push({\n name,\n value,\n dimensions,\n });\n}\n","/**\n * Telemetry wrapper utilities for SDK functions\n *\n * Provides helpers to wrap SDK function execution with telemetry tracking\n */\n\nimport {\n createTelemetryClient,\n createAppEnvironment,\n createMetricsContext,\n addMetric,\n addMetricWithDimensions,\n emitMetrics,\n} from \"./index\";\nimport { randomUUID } from \"crypto\";\n\n/**\n * Generate a random UUID for telemetry identification\n * Used when userUUID is not provided (SDK usage outside CLI)\n */\nfunction generateRandomUUID(): string {\n return randomUUID();\n}\n\n/**\n * Options for telemetry wrapper\n */\nexport interface TelemetryWrapperOptions {\n /**\n * Function name for telemetry (e.g., \"deploy\", \"upgrade\", \"createApp\")\n */\n functionName: string;\n /**\n * Skip telemetry if true (used when called from CLI)\n */\n skipTelemetry?: boolean;\n /**\n * Additional properties to include in telemetry\n */\n properties?: Record<string, string>;\n /**\n * User UUID for identification (required if skipTelemetry is false)\n * If not provided and telemetry is enabled, will generate a random UUID for this session\n */\n userUUID?: string;\n /**\n * Whether telemetry is enabled (defaults to true if not provided)\n */\n telemetryEnabled?: boolean;\n /**\n * PostHog API key (optional, will check environment variables if not provided)\n */\n apiKey?: string;\n /**\n * PostHog endpoint (optional, will use default if not provided)\n */\n endpoint?: string;\n}\n\n/**\n * Wrap a function execution with telemetry\n *\n * @param options - Telemetry wrapper options\n * @param action - The function to execute\n * @returns The result of the action\n */\nexport async function withSDKTelemetry<T>(\n options: TelemetryWrapperOptions,\n action: () => Promise<T>,\n): Promise<T> {\n // Skip telemetry if requested (e.g., when called from CLI)\n if (options.skipTelemetry) {\n return action();\n }\n\n // Generate a random UUID if not provided (for SDK usage outside CLI)\n // This ensures each SDK session has a unique identifier\n const userUUID = options.userUUID || generateRandomUUID();\n\n const environment = createAppEnvironment(userUUID);\n const client = createTelemetryClient(environment, \"ecloud-sdk\", {\n telemetryEnabled: options.telemetryEnabled,\n apiKey: options.apiKey,\n endpoint: options.endpoint,\n });\n const metrics = createMetricsContext();\n\n // Set source to identify SDK usage\n metrics.properties[\"source\"] = \"ecloud-sdk\";\n\n // Set function name in properties\n metrics.properties[\"function\"] = options.functionName;\n\n // Add any additional properties\n if (options.properties) {\n Object.assign(metrics.properties, options.properties);\n }\n\n // Add initial count metric\n addMetric(metrics, \"Count\", 1);\n\n let actionError: Error | undefined;\n let result: T;\n\n try {\n result = await action();\n return result;\n } catch (err) {\n actionError = err instanceof Error ? err : new Error(String(err));\n throw err;\n } finally {\n // Add result metric\n const resultValue = actionError ? \"Failure\" : \"Success\";\n const dimensions: Record<string, string> = {};\n if (actionError) {\n dimensions[\"error\"] = actionError.message;\n }\n addMetricWithDimensions(metrics, resultValue, 1, dimensions);\n\n // Add duration metric\n const duration = Date.now() - metrics.startTime.getTime();\n addMetric(metrics, \"DurationMilliseconds\", duration);\n\n // Emit all metrics\n try {\n await emitMetrics(client, metrics);\n await client.close();\n } catch {\n // Silently ignore telemetry errors\n }\n }\n}\n","/**\n * EIP-7702 transaction handling\n *\n * This module handles EIP-7702 delegation and batch execution.\n */\n\nimport { Address, Hex, encodeFunctionData, encodeAbiParameters, decodeErrorResult } from \"viem\";\n\nimport type {\n WalletClient,\n PublicClient,\n SendTransactionParameters,\n SignAuthorizationReturnType,\n} from \"viem\";\nimport { EnvironmentConfig, Logger, noopLogger } from \"../types\";\n\nimport ERC7702DelegatorABI from \"../abis/ERC7702Delegator.json\";\n\nimport { GasEstimate, formatETH } from \"./caller\";\n\n// Mode 0x01 is executeBatchMode (32 bytes, padded, big endian)\nconst EXECUTE_BATCH_MODE =\n \"0x0100000000000000000000000000000000000000000000000000000000000000\" as Hex;\n\nconst GAS_LIMIT_BUFFER_PERCENTAGE = 20n; // 20%\nconst GAS_PRICE_BUFFER_PERCENTAGE = 100n; // 100%\n\nexport type Execution = {\n target: Address;\n value: bigint;\n callData: Hex;\n};\n\n/**\n * Encode executions array and pack into execute function call data\n */\nfunction encodeExecuteBatchData(executions: Execution[]): Hex {\n const encodedExecutions = encodeAbiParameters(\n [\n {\n type: \"tuple[]\",\n components: [\n { name: \"target\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"callData\", type: \"bytes\" },\n ],\n },\n ],\n [executions],\n );\n\n return encodeFunctionData({\n abi: ERC7702DelegatorABI,\n functionName: \"execute\",\n args: [EXECUTE_BATCH_MODE, encodedExecutions],\n });\n}\n\n/**\n * Options for estimating batch gas\n */\nexport interface EstimateBatchGasOptions {\n publicClient: PublicClient;\n account: Address;\n executions: Execution[];\n authorizationList?: SignAuthorizationReturnType[];\n}\n\n/**\n * Estimate gas cost for a batch transaction\n *\n * Use this to get cost estimate before prompting user for confirmation.\n */\nexport async function estimateBatchGas(options: EstimateBatchGasOptions): Promise<GasEstimate> {\n const { publicClient, account, executions, authorizationList } = options;\n\n const executeBatchData = encodeExecuteBatchData(executions);\n\n // EIP-7702 transactions send to self (the EOA with delegated code)\n const [gasTipCap, block, estimatedGas] = await Promise.all([\n publicClient.estimateMaxPriorityFeePerGas(),\n publicClient.getBlock(),\n publicClient.estimateGas({\n account,\n to: account,\n data: executeBatchData,\n authorizationList,\n }),\n ]);\n\n const baseFee = block.baseFeePerGas ?? 0n;\n\n // Calculate gas price with 100% buffer: (baseFee + gasTipCap) * 2\n const maxFeePerGas = ((baseFee + gasTipCap) * (100n + GAS_PRICE_BUFFER_PERCENTAGE)) / 100n;\n\n // Add 20% buffer to gas limit\n const gasLimit = (estimatedGas * (100n + GAS_LIMIT_BUFFER_PERCENTAGE)) / 100n;\n\n const maxCostWei = gasLimit * maxFeePerGas;\n\n return {\n gasLimit,\n maxFeePerGas,\n maxPriorityFeePerGas: gasTipCap,\n maxCostWei,\n maxCostEth: formatETH(maxCostWei),\n };\n}\n\nexport interface ExecuteBatchOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n executions: Execution[];\n pendingMessage: string;\n /** Optional gas params from estimation */\n gas?: GasEstimate;\n /** Optional pre-created authorization list (skips internal creation if provided) */\n authorizationList?: SignAuthorizationReturnType[];\n}\n\n/**\n * Check if account is delegated to ERC-7702 delegator\n */\nexport async function checkERC7702Delegation(\n publicClient: PublicClient,\n account: Address,\n delegatorAddress: Address,\n): Promise<boolean> {\n const code = await publicClient.getCode({ address: account });\n if (!code) {\n return false;\n }\n\n // Check if code matches EIP-7702 delegation pattern: 0xef0100 || delegator_address\n const expectedCode = `0xef0100${delegatorAddress.slice(2)}`;\n return code.toLowerCase() === expectedCode.toLowerCase();\n}\n\n/**\n * Options for creating an authorization list\n */\nexport interface CreateAuthorizationListOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n}\n\n/**\n * Create an authorization list for EIP-7702 delegation if needed\n *\n * Returns undefined if the account is already delegated.\n * Use this to create the auth list before gas estimation to get accurate estimates.\n */\nexport async function createAuthorizationList(\n options: CreateAuthorizationListOptions,\n): Promise<SignAuthorizationReturnType[] | undefined> {\n const { walletClient, publicClient, environmentConfig } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"Wallet client must have an account\");\n }\n\n // Check if already delegated\n const isDelegated = await checkERC7702Delegation(\n publicClient,\n account.address,\n environmentConfig.erc7702DelegatorAddress as Address,\n );\n\n if (isDelegated) {\n return undefined;\n }\n\n // Create authorization\n const transactionNonce = await publicClient.getTransactionCount({\n address: account.address,\n blockTag: \"pending\",\n });\n\n const chainId = await publicClient.getChainId();\n const authorizationNonce = transactionNonce + 1;\n\n const signedAuthorization = await walletClient.signAuthorization({\n account,\n contractAddress: environmentConfig.erc7702DelegatorAddress as Address,\n chainId: chainId,\n nonce: Number(authorizationNonce),\n });\n\n return [signedAuthorization];\n}\n\n/**\n * Execute batch of operations via EIP-7702 delegator\n */\nexport async function executeBatch(options: ExecuteBatchOptions, logger: Logger = noopLogger): Promise<Hex> {\n const {\n walletClient,\n publicClient,\n environmentConfig,\n executions,\n pendingMessage,\n gas,\n authorizationList: providedAuthList,\n } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"Wallet client must have an account\");\n }\n\n const chain = walletClient.chain;\n if (!chain) {\n throw new Error(\"Wallet client must have a chain\");\n }\n\n const executeBatchData = encodeExecuteBatchData(executions);\n\n // Use provided authorization list or create one if needed\n let authorizationList: Array<SignAuthorizationReturnType> = providedAuthList || [];\n if (authorizationList.length === 0) {\n // Check if account is delegated\n const isDelegated = await checkERC7702Delegation(\n publicClient,\n account.address,\n environmentConfig.erc7702DelegatorAddress as Address,\n );\n\n // Create authorization if needed\n if (!isDelegated) {\n const transactionNonce = await publicClient.getTransactionCount({\n address: account.address,\n blockTag: \"pending\",\n });\n\n const chainId = await publicClient.getChainId();\n const authorizationNonce = transactionNonce + 1;\n\n logger.debug(\"Using wallet client signing for EIP-7702 authorization\");\n\n const signedAuthorization = await walletClient.signAuthorization({\n account,\n contractAddress: environmentConfig.erc7702DelegatorAddress as Address,\n chainId: chainId,\n nonce: Number(authorizationNonce),\n });\n\n authorizationList = [signedAuthorization];\n }\n }\n\n // 5. Show pending message\n if (pendingMessage) {\n logger.info(pendingMessage);\n }\n\n const txRequest: SendTransactionParameters = {\n account: walletClient.account!,\n chain,\n to: account.address,\n data: executeBatchData,\n value: 0n,\n };\n\n if (authorizationList.length > 0) {\n txRequest.authorizationList = authorizationList;\n }\n\n // Add gas params if provided\n if (gas?.gasLimit) {\n txRequest.gas = gas.gasLimit;\n }\n if (gas?.maxFeePerGas) {\n txRequest.maxFeePerGas = gas.maxFeePerGas;\n }\n if (gas?.maxPriorityFeePerGas) {\n txRequest.maxPriorityFeePerGas = gas.maxPriorityFeePerGas;\n }\n\n const hash = await walletClient.sendTransaction(txRequest);\n logger.info(`Transaction sent: ${hash}`);\n\n const receipt = await publicClient.waitForTransactionReceipt({ hash });\n\n if (receipt.status === \"reverted\") {\n let revertReason = \"Unknown reason\";\n try {\n await publicClient.call({\n to: account.address,\n data: executeBatchData,\n account: account.address,\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (callError: any) {\n if (callError.data) {\n try {\n const decoded = decodeErrorResult({\n abi: ERC7702DelegatorABI,\n data: callError.data,\n });\n revertReason = `${decoded.errorName}: ${JSON.stringify(decoded.args)}`;\n } catch {\n revertReason = callError.message || \"Unknown reason\";\n }\n } else {\n revertReason = callError.message || \"Unknown reason\";\n }\n }\n throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);\n }\n\n return hash;\n}\n","/**\n * Core types for ECloud SDK\n */\n\nimport { Address, Hex, SignAuthorizationReturnType } from \"viem\";\nimport { GasEstimate } from \"../contract/caller\";\n\nexport type AppId = Address;\n\nexport type logVisibility = \"public\" | \"private\" | \"off\";\n\nexport interface DeployAppOpts {\n /** App name - required */\n name: string;\n /** Path to Dockerfile (if building from Dockerfile) - either this or imageRef is required */\n dockerfile?: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Image reference (registry/path:tag) - either this or dockerfile is required */\n imageRef?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n /** Billing mode: developer (default) or app (isolated billing) */\n billTo?: \"developer\" | \"app\";\n /** Optional gas params from estimation */\n gas?: GasEstimate;\n}\n\nexport interface UpgradeAppOpts {\n /** Path to Dockerfile (if building from Dockerfile) - either this or imageRef is required */\n dockerfile?: string;\n /** Image reference (registry/path:tag) - either this or dockerfile is required */\n imageRef?: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n gas?: GasEstimate;\n}\n\n/** Options for prepareDeploy */\nexport interface PrepareDeployOpts {\n /** App name - required */\n name: string;\n /** Path to Dockerfile (if building from Dockerfile) */\n dockerfile?: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Image reference (registry/path:tag) */\n imageRef?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n /** Resource usage monitoring setting - optional */\n resourceUsageMonitoring?: \"enable\" | \"disable\";\n /** Billing mode: developer (default) or app (isolated billing) */\n billTo?: \"developer\" | \"app\";\n /** Skip quota check */\n skipQuotaCheck?: boolean;\n /** Optional salt for deterministic app address prediction (32 bytes) */\n salt?: Uint8Array | Buffer;\n}\n\n/** Options for prepareUpgrade */\nexport interface PrepareUpgradeOpts {\n /** Path to Dockerfile (if building from Dockerfile) */\n dockerfile?: string;\n /** Image reference (registry/path:tag) */\n imageRef?: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n /** Resource usage monitoring setting - optional */\n resourceUsageMonitoring?: \"enable\" | \"disable\";\n}\n\n/** Options for prepareDeployFromVerifiableBuild */\nexport interface PrepareDeployFromVerifiableBuildOpts {\n /** App name - required */\n name: string;\n /** Image reference (registry/path:tag) - required */\n imageRef: string;\n /** Image digest (sha256:...) - required */\n imageDigest: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n /** Resource usage monitoring setting - optional */\n resourceUsageMonitoring?: \"enable\" | \"disable\";\n /** Billing mode: developer (default) or app (isolated billing) */\n billTo?: \"developer\" | \"app\";\n}\n\n/** Options for prepareUpgradeFromVerifiableBuild */\nexport interface PrepareUpgradeFromVerifiableBuildOpts {\n /** Image reference (registry/path:tag) - required */\n imageRef: string;\n /** Image digest (sha256:...) - required */\n imageDigest: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n /** Resource usage monitoring setting - optional */\n resourceUsageMonitoring?: \"enable\" | \"disable\";\n}\n\n/** Gas options for execute functions */\nexport interface GasOpts {\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n}\n\n/** Result from executeDeploy */\nexport interface ExecuteDeployResult {\n appId: AppId;\n txHash: Hex;\n appName: string;\n imageRef: string;\n}\n\n/** Result from executeUpgrade */\nexport interface ExecuteUpgradeResult {\n appId: AppId;\n txHash: Hex;\n imageRef: string;\n}\n\n/** Data-only batch for deploy (clients provided by module) */\nexport interface PreparedDeployData {\n /** The app ID that will be deployed */\n appId: AppId;\n /** The salt used for deployment */\n salt: Uint8Array;\n /** Batch executions to be sent */\n executions: Array<{ target: Address; value: bigint; callData: Hex }>;\n /** Pre-created authorization list for gas estimation accuracy (optional) */\n authorizationList?: SignAuthorizationReturnType[];\n}\n\n/** Data-only batch for upgrade (clients provided by module) */\nexport interface PreparedUpgradeData {\n /** The app ID being upgraded */\n appId: AppId;\n /** Batch executions to be sent */\n executions: Array<{ target: Address; value: bigint; callData: Hex }>;\n /** Pre-created authorization list for gas estimation accuracy (optional) */\n authorizationList?: SignAuthorizationReturnType[];\n}\n\n/** Prepared deployment ready for execution */\nexport interface PreparedDeploy {\n /** The prepared data (executions, appId, etc.) */\n data: PreparedDeployData;\n /** App name */\n appName: string;\n /** Final image reference */\n imageRef: string;\n}\n\n/** Prepared upgrade ready for execution */\nexport interface PreparedUpgrade {\n /** The prepared data (executions, appId, etc.) */\n data: PreparedUpgradeData;\n /** App ID being upgraded */\n appId: AppId;\n /** Final image reference */\n imageRef: string;\n}\n\nexport interface LifecycleOpts {\n gas?: GasEstimate;\n}\n\nexport interface AppRecord {\n id: AppId;\n owner: Address;\n image: string;\n status: \"starting\" | \"running\" | \"stopped\" | \"terminated\";\n createdAt: number; // epoch ms\n lastUpdatedAt: number; // epoch ms\n}\n\nexport interface DeployOptions {\n /** Private key for signing transactions (hex string with or without 0x prefix) - optional, will prompt if not provided */\n privateKey?: string;\n /** RPC URL for blockchain connection - optional, uses environment default if not provided */\n rpcUrl?: string;\n /** Environment name (e.g., 'sepolia', 'mainnet-alpha') - optional, defaults to 'sepolia' */\n environment?: string;\n /** Path to Dockerfile (if building from Dockerfile) */\n dockerfilePath?: string;\n /** Image reference (registry/path:tag) - optional, will prompt if not provided */\n imageRef?: string;\n /** Path to .env file - optional, will use .env if exists or prompt */\n envFilePath?: string;\n /** App name - optional, will prompt if not provided */\n appName?: string;\n /** Instance type - optional, will prompt if not provided */\n instanceType?: string;\n /** Log visibility setting - optional, will prompt if not provided */\n logVisibility?: logVisibility;\n}\n\nexport interface DeployResult {\n /** App ID (contract address) */\n appId: AppId;\n /** App name */\n appName: string;\n /** Final image reference */\n imageRef: string;\n /** IP address (if available) */\n ipAddress?: string;\n /** Transaction hash */\n txHash: Hex;\n}\n\nexport interface BillingEnvironmentConfig {\n billingApiServerURL: string;\n}\n\nexport interface EnvironmentConfig {\n name: string;\n build: \"dev\" | \"prod\";\n chainID: bigint;\n appControllerAddress: Address;\n permissionControllerAddress: string;\n erc7702DelegatorAddress: string;\n kmsServerURL: string;\n userApiServerURL: string;\n defaultRPCURL: string;\n billingRPCURL?: string;\n usdcCreditsAddress?: Address;\n}\n\nexport interface Release {\n rmsRelease: {\n artifacts: Array<{\n digest: Uint8Array; // 32 bytes\n registry: string;\n }>;\n upgradeByTime: number; // Unix timestamp\n };\n publicEnv: Uint8Array; // JSON bytes\n encryptedEnv: Uint8Array; // Encrypted string bytes\n}\n\nexport interface ParsedEnvironment {\n public: Record<string, string>;\n private: Record<string, string>;\n}\n\nexport interface ImageDigestResult {\n digest: Uint8Array; // 32 bytes\n registry: string;\n platform: string;\n}\n\nexport interface DockerImageConfig {\n cmd: string[];\n entrypoint: string[];\n user: string;\n labels: Record<string, string>;\n}\n\nexport interface Logger {\n debug(message: string, ...args: any[]): void;\n info(message: string, ...args: any[]): void;\n warn(message: string, ...args: any[]): void;\n error(message: string, ...args: any[]): void;\n}\n\n/**\n * No-op logger for browser usage when logging is not needed\n */\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n\n/**\n * Profile information for an app\n */\nexport interface AppProfile {\n /** App name (required) */\n name: string;\n /** Website URL (optional) */\n website?: string;\n /** Description (optional) */\n description?: string;\n /** X (Twitter) URL (optional) */\n xURL?: string;\n /** Path to image file (optional) */\n image?: Blob | File;\n /** Image name (optional) */\n imageName?: string;\n}\n\n/**\n * Profile response from API\n */\nexport interface AppProfileResponse {\n name: string;\n website?: string;\n description?: string;\n xURL?: string;\n imageURL?: string;\n}\n\n// Billing types\nexport type ProductID = \"compute\";\nexport type ChainID = \"ethereum-mainnet\" | \"ethereum-sepolia\";\n\nexport type SubscriptionStatus =\n | \"incomplete\"\n | \"incomplete_expired\"\n | \"trialing\"\n | \"active\"\n | \"past_due\"\n | \"canceled\"\n | \"unpaid\"\n | \"paused\"\n | \"inactive\";\n\nexport interface SubscriptionLineItem {\n description: string;\n price: number;\n quantity: number;\n currency: string;\n subtotal: number;\n}\n\nexport interface CreateSubscriptionOptions {\n /** URL to redirect to after successful checkout */\n successUrl?: string;\n /** URL to redirect to if checkout is canceled */\n cancelUrl?: string;\n /** URL to return if return link is clicked from stripe portal */\n returnUrl?: string;\n}\n\nexport interface GetSubscriptionOptions {\n /** URL to return to from the billing portal */\n returnUrl?: string;\n}\n\nexport interface CreateSubscriptionResponse {\n checkoutUrl: string;\n}\n\nexport interface CheckoutCreatedResponse {\n type: \"checkout_created\";\n checkoutUrl: string;\n}\n\nexport interface AlreadyActiveResponse {\n type: \"already_active\";\n status: SubscriptionStatus;\n}\n\nexport interface PaymentIssueResponse {\n type: \"payment_issue\";\n status: SubscriptionStatus;\n portalUrl?: string;\n}\n\nexport type SubscribeResponse =\n | CheckoutCreatedResponse\n | AlreadyActiveResponse\n | PaymentIssueResponse;\n\nexport interface CancelSuccessResponse {\n type: \"canceled\";\n}\n\nexport interface NoActiveSubscriptionResponse {\n type: \"no_active_subscription\";\n status: SubscriptionStatus;\n}\n\nexport type CancelResponse = CancelSuccessResponse | NoActiveSubscriptionResponse;\n\nexport interface ProductSubscriptionResponse {\n productId: ProductID;\n subscriptionStatus: SubscriptionStatus;\n currentPeriodStart?: string;\n currentPeriodEnd?: string;\n lineItems?: SubscriptionLineItem[];\n upcomingInvoiceSubtotal?: number;\n upcomingInvoiceTotal?: number;\n creditsApplied?: number;\n remainingCredits?: number;\n nextCreditExpiry?: number;\n cancelAtPeriodEnd?: boolean;\n canceledAt?: string;\n portalUrl?: string;\n}\n\nexport interface SubscriptionOpts {\n productId?: ProductID;\n /** URL to redirect to after successful checkout */\n successUrl?: string;\n /** URL to redirect to if checkout is canceled */\n cancelUrl?: string;\n}\n\n// Billing environment configuration\nexport interface BillingEnvironmentConfig {\n billingApiServerURL: string;\n}\n\n/**\n * Progress callback for sequential deployment\n * Called after each step completes\n */\nexport type DeployProgressCallback = (step: DeployStep, txHash?: Hex) => void;\n\n/**\n * Steps in sequential deployment flow\n */\nexport type DeployStep = \"createApp\" | \"acceptAdmin\" | \"setPublicLogs\" | \"complete\";\n\n/**\n * Result from sequential deployment\n */\nexport interface SequentialDeployResult {\n appId: AppId;\n txHashes: {\n createApp: Hex;\n acceptAdmin: Hex;\n setPublicLogs?: Hex;\n };\n}\n","[\n {\n \"type\": \"constructor\",\n \"inputs\": [\n {\n \"name\": \"_delegationManager\",\n \"type\": \"address\",\n \"internalType\": \"contractIDelegationManager\"\n },\n {\n \"name\": \"_entryPoint\",\n \"type\": \"address\",\n \"internalType\": \"contractIEntryPoint\"\n }\n ],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"receive\",\n \"stateMutability\": \"payable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"DOMAIN_VERSION\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"NAME\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"PACKED_USER_OP_TYPEHASH\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"VERSION\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"addDeposit\",\n \"inputs\": [],\n \"outputs\": [],\n \"stateMutability\": \"payable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"delegationManager\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"contractIDelegationManager\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"disableDelegation\",\n \"inputs\": [\n {\n \"name\": \"_delegation\",\n \"type\": \"tuple\",\n \"internalType\": \"structDelegation\",\n \"components\": [\n {\n \"name\": \"delegate\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"delegator\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"authority\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"caveats\",\n \"type\": \"tuple[]\",\n \"internalType\": \"structCaveat[]\",\n \"components\": [\n {\n \"name\": \"enforcer\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"terms\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"args\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n },\n {\n \"name\": \"salt\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"eip712Domain\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"fields\",\n \"type\": \"bytes1\",\n \"internalType\": \"bytes1\"\n },\n {\n \"name\": \"name\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n },\n {\n \"name\": \"version\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n },\n {\n \"name\": \"chainId\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"verifyingContract\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"salt\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"extensions\",\n \"type\": \"uint256[]\",\n \"internalType\": \"uint256[]\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"enableDelegation\",\n \"inputs\": [\n {\n \"name\": \"_delegation\",\n \"type\": \"tuple\",\n \"internalType\": \"structDelegation\",\n \"components\": [\n {\n \"name\": \"delegate\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"delegator\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"authority\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"caveats\",\n \"type\": \"tuple[]\",\n \"internalType\": \"structCaveat[]\",\n \"components\": [\n {\n \"name\": \"enforcer\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"terms\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"args\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n },\n {\n \"name\": \"salt\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"entryPoint\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"contractIEntryPoint\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"execute\",\n \"inputs\": [\n {\n \"name\": \"_execution\",\n \"type\": \"tuple\",\n \"internalType\": \"structExecution\",\n \"components\": [\n {\n \"name\": \"target\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"value\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"callData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"payable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"execute\",\n \"inputs\": [\n {\n \"name\": \"_mode\",\n \"type\": \"bytes32\",\n \"internalType\": \"ModeCode\"\n },\n {\n \"name\": \"_executionCalldata\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"payable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"executeFromExecutor\",\n \"inputs\": [\n {\n \"name\": \"_mode\",\n \"type\": \"bytes32\",\n \"internalType\": \"ModeCode\"\n },\n {\n \"name\": \"_executionCalldata\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"returnData_\",\n \"type\": \"bytes[]\",\n \"internalType\": \"bytes[]\"\n }\n ],\n \"stateMutability\": \"payable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getDeposit\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getDomainHash\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getNonce\",\n \"inputs\": [\n {\n \"name\": \"_key\",\n \"type\": \"uint192\",\n \"internalType\": \"uint192\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getNonce\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getPackedUserOperationHash\",\n \"inputs\": [\n {\n \"name\": \"_userOp\",\n \"type\": \"tuple\",\n \"internalType\": \"structPackedUserOperation\",\n \"components\": [\n {\n \"name\": \"sender\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"nonce\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"initCode\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"callData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"accountGasLimits\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"preVerificationGas\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"gasFees\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"paymasterAndData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getPackedUserOperationTypedDataHash\",\n \"inputs\": [\n {\n \"name\": \"_userOp\",\n \"type\": \"tuple\",\n \"internalType\": \"structPackedUserOperation\",\n \"components\": [\n {\n \"name\": \"sender\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"nonce\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"initCode\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"callData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"accountGasLimits\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"preVerificationGas\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"gasFees\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"paymasterAndData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"isDelegationDisabled\",\n \"inputs\": [\n {\n \"name\": \"_delegationHash\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bool\",\n \"internalType\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"isValidSignature\",\n \"inputs\": [\n {\n \"name\": \"_hash\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"_signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"magicValue_\",\n \"type\": \"bytes4\",\n \"internalType\": \"bytes4\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"onERC1155BatchReceived\",\n \"inputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"uint256[]\",\n \"internalType\": \"uint256[]\"\n },\n {\n \"name\": \"\",\n \"type\": \"uint256[]\",\n \"internalType\": \"uint256[]\"\n },\n {\n \"name\": \"\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes4\",\n \"internalType\": \"bytes4\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"onERC1155Received\",\n \"inputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes4\",\n \"internalType\": \"bytes4\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"onERC721Received\",\n \"inputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes4\",\n \"internalType\": \"bytes4\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"redeemDelegations\",\n \"inputs\": [\n {\n \"name\": \"_permissionContexts\",\n \"type\": \"bytes[]\",\n \"internalType\": \"bytes[]\"\n },\n {\n \"name\": \"_modes\",\n \"type\": \"bytes32[]\",\n \"internalType\": \"ModeCode[]\"\n },\n {\n \"name\": \"_executionCallDatas\",\n \"type\": \"bytes[]\",\n \"internalType\": \"bytes[]\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"supportsExecutionMode\",\n \"inputs\": [\n {\n \"name\": \"_mode\",\n \"type\": \"bytes32\",\n \"internalType\": \"ModeCode\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bool\",\n \"internalType\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"supportsInterface\",\n \"inputs\": [\n {\n \"name\": \"_interfaceId\",\n \"type\": \"bytes4\",\n \"internalType\": \"bytes4\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bool\",\n \"internalType\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"validateUserOp\",\n \"inputs\": [\n {\n \"name\": \"_userOp\",\n \"type\": \"tuple\",\n \"internalType\": \"structPackedUserOperation\",\n \"components\": [\n {\n \"name\": \"sender\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"nonce\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"initCode\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"callData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"accountGasLimits\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"preVerificationGas\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"gasFees\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"paymasterAndData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n },\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"_missingAccountFunds\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"validationData_\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"withdrawDeposit\",\n \"inputs\": [\n {\n \"name\": \"_withdrawAddress\",\n \"type\": \"address\",\n \"internalType\": \"addresspayable\"\n },\n {\n \"name\": \"_withdrawAmount\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"event\",\n \"name\": \"EIP712DomainChanged\",\n \"inputs\": [],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"SentPrefund\",\n \"inputs\": [\n {\n \"name\": \"sender\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"amount\",\n \"type\": \"uint256\",\n \"indexed\": false,\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"success\",\n \"type\": \"bool\",\n \"indexed\": false,\n \"internalType\": \"bool\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"SetDelegationManager\",\n \"inputs\": [\n {\n \"name\": \"newDelegationManager\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"contractIDelegationManager\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"SetEntryPoint\",\n \"inputs\": [\n {\n \"name\": \"entryPoint\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"contractIEntryPoint\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"TryExecuteUnsuccessful\",\n \"inputs\": [\n {\n \"name\": \"batchExecutionindex\",\n \"type\": \"uint256\",\n \"indexed\": false,\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"result\",\n \"type\": \"bytes\",\n \"indexed\": false,\n \"internalType\": \"bytes\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"error\",\n \"name\": \"ECDSAInvalidSignature\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"ECDSAInvalidSignatureLength\",\n \"inputs\": [\n {\n \"name\": \"length\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ]\n },\n {\n \"type\": \"error\",\n \"name\": \"ECDSAInvalidSignatureS\",\n \"inputs\": [\n {\n \"name\": \"s\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ]\n },\n {\n \"type\": \"error\",\n \"name\": \"ExecutionFailed\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"InvalidEIP712NameLength\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"InvalidEIP712VersionLength\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"InvalidShortString\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"NotDelegationManager\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"NotEntryPoint\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"NotEntryPointOrSelf\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"NotSelf\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"StringTooLong\",\n \"inputs\": [\n {\n \"name\": \"str\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n }\n ]\n },\n {\n \"type\": \"error\",\n \"name\": \"UnauthorizedCallContext\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"UnsupportedCallType\",\n \"inputs\": [\n {\n \"name\": \"callType\",\n \"type\": \"bytes1\",\n \"internalType\": \"CallType\"\n }\n ]\n },\n {\n \"type\": \"error\",\n \"name\": \"UnsupportedExecType\",\n \"inputs\": [\n {\n \"name\": \"execType\",\n \"type\": \"bytes1\",\n \"internalType\": \"ExecType\"\n }\n ]\n }\n]\n","/**\n * Contract interactions\n *\n * This module handles on-chain contract interactions using viem.\n *\n * Accepts viem's WalletClient and PublicClient directly, which abstract over both\n * local accounts (privateKeyToAccount) and external signers (MetaMask, etc.).\n *\n * @example\n * // CLI usage with private key\n * const { walletClient, publicClient } = createClients({ privateKey, rpcUrl, chainId });\n * await deployApp({ walletClient, publicClient, environmentConfig, ... }, logger);\n *\n * @example\n * // Browser usage with external wallet\n * const walletClient = createWalletClient({ chain, transport: custom(window.ethereum!) });\n * const publicClient = createPublicClient({ chain, transport: custom(window.ethereum!) });\n * await deployApp({ walletClient, publicClient, environmentConfig, ... }, logger);\n */\n\nimport { executeBatch, checkERC7702Delegation } from \"./eip7702\";\nimport { Address, Hex, encodeFunctionData, decodeErrorResult, bytesToHex } from \"viem\";\nimport type { WalletClient, PublicClient } from \"viem\";\n\nimport {\n EnvironmentConfig,\n Logger,\n PreparedDeployData,\n PreparedUpgradeData,\n noopLogger,\n DeployProgressCallback,\n SequentialDeployResult,\n} from \"../types\";\nimport { Release } from \"../types\";\nimport { getChainFromID } from \"../utils/helpers\";\n\nimport AppControllerABI from \"../abis/AppController.json\";\nimport PermissionControllerABI from \"../abis/PermissionController.json\";\n\n/**\n * Gas estimation result\n */\nexport interface GasEstimate {\n /** Estimated gas limit for the transaction */\n gasLimit: bigint;\n /** Max fee per gas (EIP-1559) */\n maxFeePerGas: bigint;\n /** Max priority fee per gas (EIP-1559) */\n maxPriorityFeePerGas: bigint;\n /** Maximum cost in wei (gasLimit * maxFeePerGas) */\n maxCostWei: bigint;\n /** Maximum cost formatted as ETH string */\n maxCostEth: string;\n}\n\n/**\n * Options for estimating transaction gas\n */\nexport interface EstimateGasOptions {\n publicClient: PublicClient;\n from: Address;\n to: Address;\n data: Hex;\n value?: bigint;\n}\n\n/**\n * Format Wei to ETH string\n */\nexport function formatETH(wei: bigint): string {\n const eth = Number(wei) / 1e18;\n const costStr = eth.toFixed(6);\n // Remove trailing zeros and decimal point if needed\n const trimmed = costStr.replace(/\\.?0+$/, \"\");\n // If result is \"0\", show \"<0.000001\" for small amounts\n if (trimmed === \"0\" && wei > 0n) {\n return \"<0.000001\";\n }\n return trimmed;\n}\n\n/**\n * Estimate gas cost for a transaction\n *\n * Use this to get cost estimate before prompting user for confirmation.\n */\nexport async function estimateTransactionGas(options: EstimateGasOptions): Promise<GasEstimate> {\n const { publicClient, from, to, data, value = 0n } = options;\n\n // Get current gas prices\n const fees = await publicClient.estimateFeesPerGas();\n\n // Estimate gas for the transaction\n const gasLimit = await publicClient.estimateGas({\n account: from,\n to,\n data,\n value,\n });\n\n const maxFeePerGas = fees.maxFeePerGas;\n const maxPriorityFeePerGas = fees.maxPriorityFeePerGas;\n const maxCostWei = gasLimit * maxFeePerGas;\n const maxCostEth = formatETH(maxCostWei);\n\n return {\n gasLimit,\n maxFeePerGas,\n maxPriorityFeePerGas,\n maxCostWei,\n maxCostEth,\n };\n}\n\n/**\n * Deploy app options\n */\nexport interface DeployAppOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n salt: Uint8Array;\n release: Release;\n publicLogs: boolean;\n imageRef: string;\n gas?: GasEstimate;\n}\n\n/**\n * Options for calculateAppID\n */\nexport interface CalculateAppIDOptions {\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n ownerAddress: Address;\n salt: Uint8Array;\n}\n\n/**\n * Prepared deploy batch ready for gas estimation and execution\n */\nexport interface PreparedDeployBatch {\n /** The app ID that will be deployed */\n appId: Address;\n /** The salt used for deployment */\n salt: Uint8Array;\n /** Batch executions to be sent */\n executions: Array<{ target: Address; value: bigint; callData: Hex }>;\n /** Wallet client for sending transaction */\n walletClient: WalletClient;\n /** Public client for reading chain state */\n publicClient: PublicClient;\n /** Environment configuration */\n environmentConfig: EnvironmentConfig;\n}\n\n/**\n * Prepared upgrade batch ready for gas estimation and execution\n */\nexport interface PreparedUpgradeBatch {\n /** The app ID being upgraded */\n appId: Address;\n /** Batch executions to be sent */\n executions: Array<{ target: Address; value: bigint; callData: Hex }>;\n /** Wallet client for sending transaction */\n walletClient: WalletClient;\n /** Public client for reading chain state */\n publicClient: PublicClient;\n /** Environment configuration */\n environmentConfig: EnvironmentConfig;\n}\n\n/**\n * Calculate app ID from owner address and salt\n */\nexport async function calculateAppID(options: CalculateAppIDOptions): Promise<Address> {\n const { publicClient, environmentConfig, ownerAddress, salt } = options;\n\n // Ensure salt is properly formatted as hex string (32 bytes = 64 hex chars)\n // bytesToHex returns 0x-prefixed string, slice(2) removes the prefix for padding\n const saltHexString = bytesToHex(salt).slice(2);\n // Pad to 64 characters if needed\n const paddedSaltHex = saltHexString.padStart(64, \"0\");\n const saltHex = `0x${paddedSaltHex}` as Hex;\n\n const appID = await publicClient.readContract({\n address: environmentConfig.appControllerAddress as Address,\n abi: AppControllerABI,\n functionName: \"calculateAppId\",\n args: [ownerAddress, saltHex],\n });\n\n return appID as Address;\n}\n\n/**\n * Options for preparing a deploy batch\n */\nexport interface PrepareDeployBatchOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n salt: Uint8Array;\n release: Release;\n publicLogs: boolean;\n imageRef: string;\n billTo?: \"developer\" | \"app\";\n}\n\n/**\n * Prepare deploy batch - creates executions without sending transaction\n *\n * Use this to get the prepared batch for gas estimation before executing.\n */\nexport async function prepareDeployBatch(\n options: PrepareDeployBatchOptions,\n logger: Logger = noopLogger,\n): Promise<PreparedDeployBatch> {\n const { walletClient, publicClient, environmentConfig, salt, release, publicLogs } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n // 1. Calculate app ID\n logger.info(\"Calculating app ID...\");\n const appId = await calculateAppID({\n publicClient,\n environmentConfig,\n ownerAddress: account.address,\n salt,\n });\n\n // Verify the app ID calculation matches what createApp will deploy\n logger.debug(`App ID calculated: ${appId}`);\n logger.debug(`This address will be used for acceptAdmin call`);\n\n // 2. Pack create app call\n const saltHexString = bytesToHex(salt).slice(2);\n const paddedSaltHex = saltHexString.padStart(64, \"0\");\n const saltHex = `0x${paddedSaltHex}` as Hex;\n\n // Convert Release Uint8Array values to hex strings for viem\n const releaseForViem = {\n rmsRelease: {\n artifacts: release.rmsRelease.artifacts.map((artifact) => ({\n digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, \"0\")}` as Hex,\n registry: artifact.registry,\n })),\n upgradeByTime: release.rmsRelease.upgradeByTime,\n },\n publicEnv: bytesToHex(release.publicEnv) as Hex,\n encryptedEnv: bytesToHex(release.encryptedEnv) as Hex,\n };\n\n const functionName = options.billTo === \"app\" ? \"createAppWithIsolatedBilling\" : \"createApp\";\n const createData = encodeFunctionData({\n abi: AppControllerABI,\n functionName,\n args: [saltHex, releaseForViem],\n });\n\n // 3. Pack accept admin call\n const acceptAdminData = encodeFunctionData({\n abi: PermissionControllerABI,\n functionName: \"acceptAdmin\",\n args: [appId],\n });\n\n // 4. Assemble executions\n // CRITICAL: Order matters! createApp must complete first\n const executions: Array<{\n target: Address;\n value: bigint;\n callData: Hex;\n }> = [\n {\n target: environmentConfig.appControllerAddress,\n value: 0n,\n callData: createData,\n },\n {\n target: environmentConfig.permissionControllerAddress as Address,\n value: 0n,\n callData: acceptAdminData,\n },\n ];\n\n // 5. Add public logs permission if requested\n if (publicLogs) {\n const anyoneCanViewLogsData = encodeFunctionData({\n abi: PermissionControllerABI,\n functionName: \"setAppointee\",\n args: [\n appId,\n \"0x493219d9949348178af1f58740655951a8cd110c\" as Address, // AnyoneCanCallAddress\n \"0x57ee1fb74c1087e26446abc4fb87fd8f07c43d8d\" as Address, // ApiPermissionsTarget\n \"0x2fd3f2fe\" as Hex, // CanViewAppLogsPermission\n ],\n });\n executions.push({\n target: environmentConfig.permissionControllerAddress as Address,\n value: 0n,\n callData: anyoneCanViewLogsData,\n });\n }\n\n return {\n appId,\n salt,\n executions,\n walletClient,\n publicClient,\n environmentConfig,\n };\n}\n\n/**\n * Execute a prepared deploy batch\n */\nexport async function executeDeployBatch(\n data: PreparedDeployData,\n context: {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n },\n gas?: GasEstimate,\n logger: Logger = noopLogger,\n): Promise<{ appId: Address; txHash: Hex }> {\n const pendingMessage = \"Deploying new app...\";\n\n const txHash = await executeBatch(\n {\n walletClient: context.walletClient,\n publicClient: context.publicClient,\n environmentConfig: context.environmentConfig,\n executions: data.executions,\n pendingMessage,\n gas,\n authorizationList: data.authorizationList,\n },\n logger,\n );\n\n return { appId: data.appId, txHash };\n}\n\n/**\n * Deploy app on-chain (convenience wrapper that prepares and executes)\n */\nexport async function deployApp(\n options: DeployAppOptions,\n logger: Logger = noopLogger,\n): Promise<{ appId: Address; txHash: Hex }> {\n const prepared = await prepareDeployBatch(options, logger);\n\n // Extract data and context from prepared batch\n const data: PreparedDeployData = {\n appId: prepared.appId,\n salt: prepared.salt,\n executions: prepared.executions,\n };\n const context = {\n walletClient: prepared.walletClient,\n publicClient: prepared.publicClient,\n environmentConfig: prepared.environmentConfig,\n };\n\n return executeDeployBatch(data, context, options.gas, logger);\n}\n\n/**\n * Check if wallet account supports EIP-7702 signing\n *\n * Local accounts (from privateKeyToAccount) support signAuthorization.\n * JSON-RPC accounts (browser wallets like MetaMask) do not.\n */\nexport function supportsEIP7702(walletClient: WalletClient): boolean {\n const account = walletClient.account;\n if (!account) return false;\n\n // Local accounts have type \"local\", JSON-RPC accounts have type \"json-rpc\"\n // Only local accounts support signAuthorization\n return account.type === \"local\";\n}\n\n/**\n * Options for sequential deployment (non-EIP-7702)\n */\nexport interface ExecuteDeploySequentialOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n /** Prepared deployment data from prepareDeployBatch */\n data: PreparedDeployData;\n /** Whether to set public logs permission */\n publicLogs: boolean;\n /** Optional callback for progress updates */\n onProgress?: DeployProgressCallback;\n}\n\n/**\n * Execute deployment as sequential transactions (non-EIP-7702 fallback)\n *\n * Use this for browser wallets (JSON-RPC accounts) that don't support signAuthorization.\n * This requires 2-3 wallet signatures instead of 1, but works with all wallet types.\n *\n * Steps:\n * 1. createApp - Creates the app on-chain\n * 2. acceptAdmin - Accepts admin role for the app\n * 3. setAppointee (optional) - Sets public logs permission\n */\nexport async function executeDeploySequential(\n options: ExecuteDeploySequentialOptions,\n logger: Logger = noopLogger,\n): Promise<SequentialDeployResult> {\n const { walletClient, publicClient, environmentConfig, data, publicLogs, onProgress } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n const chain = getChainFromID(environmentConfig.chainID);\n const txHashes: { createApp: Hex; acceptAdmin: Hex; setPublicLogs?: Hex } = {\n createApp: \"0x\" as Hex,\n acceptAdmin: \"0x\" as Hex,\n };\n\n // Step 1: Create App\n logger.info(\"Step 1/3: Creating app...\");\n onProgress?.(\"createApp\");\n\n const createAppExecution = data.executions[0];\n const createAppHash = await walletClient.sendTransaction({\n account,\n to: createAppExecution.target,\n data: createAppExecution.callData,\n value: createAppExecution.value,\n chain,\n });\n\n logger.info(`createApp transaction sent: ${createAppHash}`);\n const createAppReceipt = await publicClient.waitForTransactionReceipt({ hash: createAppHash });\n\n if (createAppReceipt.status === \"reverted\") {\n throw new Error(`createApp transaction reverted: ${createAppHash}`);\n }\n\n txHashes.createApp = createAppHash;\n logger.info(`createApp confirmed in block ${createAppReceipt.blockNumber}`);\n\n // Step 2: Accept Admin\n logger.info(\"Step 2/3: Accepting admin role...\");\n onProgress?.(\"acceptAdmin\", createAppHash);\n\n const acceptAdminExecution = data.executions[1];\n const acceptAdminHash = await walletClient.sendTransaction({\n account,\n to: acceptAdminExecution.target,\n data: acceptAdminExecution.callData,\n value: acceptAdminExecution.value,\n chain,\n });\n\n logger.info(`acceptAdmin transaction sent: ${acceptAdminHash}`);\n const acceptAdminReceipt = await publicClient.waitForTransactionReceipt({\n hash: acceptAdminHash,\n });\n\n if (acceptAdminReceipt.status === \"reverted\") {\n throw new Error(`acceptAdmin transaction reverted: ${acceptAdminHash}`);\n }\n\n txHashes.acceptAdmin = acceptAdminHash;\n logger.info(`acceptAdmin confirmed in block ${acceptAdminReceipt.blockNumber}`);\n\n // Step 3: Set Public Logs (if requested and present in executions)\n if (publicLogs && data.executions.length > 2) {\n logger.info(\"Step 3/3: Setting public logs permission...\");\n onProgress?.(\"setPublicLogs\", acceptAdminHash);\n\n const setAppointeeExecution = data.executions[2];\n const setAppointeeHash = await walletClient.sendTransaction({\n account,\n to: setAppointeeExecution.target,\n data: setAppointeeExecution.callData,\n value: setAppointeeExecution.value,\n chain,\n });\n\n logger.info(`setAppointee transaction sent: ${setAppointeeHash}`);\n const setAppointeeReceipt = await publicClient.waitForTransactionReceipt({\n hash: setAppointeeHash,\n });\n\n if (setAppointeeReceipt.status === \"reverted\") {\n throw new Error(`setAppointee transaction reverted: ${setAppointeeHash}`);\n }\n\n txHashes.setPublicLogs = setAppointeeHash;\n logger.info(`setAppointee confirmed in block ${setAppointeeReceipt.blockNumber}`);\n }\n\n onProgress?.(\"complete\", txHashes.setPublicLogs || txHashes.acceptAdmin);\n\n logger.info(`Deployment complete! App ID: ${data.appId}`);\n\n return {\n appId: data.appId,\n txHashes,\n };\n}\n\n/**\n * Result from EIP-5792 batched deployment\n */\nexport interface BatchedDeployResult {\n appId: Address;\n /** Batch ID from sendCalls (can be used with getCallsStatus) */\n batchId: string;\n /** Transaction receipts from the batch */\n receipts: Array<{ transactionHash: Hex }>;\n}\n\n/**\n * Options for EIP-5792 batched deployment\n */\nexport interface ExecuteDeployBatchedOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n /** Prepared deployment data from prepareDeployBatch */\n data: PreparedDeployData;\n /** Whether to set public logs permission */\n publicLogs: boolean;\n /** Optional callback for progress updates */\n onProgress?: DeployProgressCallback;\n}\n\n/**\n * Check if wallet supports EIP-5792 (sendCalls/wallet_sendCalls)\n *\n * This checks the wallet's capabilities to see if it supports atomic batch calls.\n * MetaMask and other modern wallets are adding support for this standard.\n */\nexport async function supportsEIP5792(walletClient: WalletClient): Promise<boolean> {\n try {\n // Check if getCapabilities method exists\n if (typeof walletClient.getCapabilities !== \"function\") {\n return false;\n }\n\n const account = walletClient.account;\n if (!account) return false;\n\n // Try to get capabilities - if this works, the wallet supports EIP-5792\n const capabilities = await walletClient.getCapabilities({\n account: account.address,\n });\n\n // Check if we got any capabilities back\n return (\n capabilities !== null && capabilities !== undefined && Object.keys(capabilities).length > 0\n );\n } catch {\n // If getCapabilities fails, the wallet doesn't support EIP-5792\n return false;\n }\n}\n\n/**\n * Execute deployment using EIP-5792 sendCalls (batched wallet calls)\n *\n * This batches all deployment transactions (createApp, acceptAdmin, setPublicLogs)\n * into a single wallet interaction. Better UX than sequential transactions.\n *\n * Use this for browser wallets that support EIP-5792 but not EIP-7702.\n *\n * @returns BatchedDeployResult with appId and batch receipts\n */\nexport async function executeDeployBatched(\n options: ExecuteDeployBatchedOptions,\n logger: Logger = noopLogger,\n): Promise<BatchedDeployResult> {\n const { walletClient, environmentConfig, data, publicLogs, onProgress } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n const chain = getChainFromID(environmentConfig.chainID);\n\n // Build calls array for sendCalls\n const calls: Array<{ to: Address; data: Hex; value: bigint }> = data.executions.map(\n (execution) => ({\n to: execution.target,\n data: execution.callData,\n value: execution.value,\n }),\n );\n\n // If public logs is false but executions include the permission call, filter it out\n // (This shouldn't happen if prepareDeployBatch was called correctly, but be safe)\n const filteredCalls = publicLogs ? calls : calls.slice(0, 2);\n\n logger.info(`Deploying with EIP-5792 sendCalls (${filteredCalls.length} calls)...`);\n onProgress?.(\"createApp\");\n\n try {\n // Send all calls in a single batch\n const { id: batchId } = await walletClient.sendCalls({\n account,\n chain,\n calls: filteredCalls,\n forceAtomic: true,\n });\n\n logger.info(`Batch submitted with ID: ${batchId}`);\n onProgress?.(\"acceptAdmin\");\n\n // Poll for batch completion using getCallsStatus\n let status: any;\n let attempts = 0;\n const maxAttempts = 120; // 10 minutes max (5s intervals)\n\n while (attempts < maxAttempts) {\n try {\n status = await walletClient.getCallsStatus({ id: batchId });\n\n if (status.status === \"success\" || status.status === \"confirmed\") {\n logger.info(`Batch confirmed with ${status.receipts?.length || 0} receipts`);\n break;\n }\n\n if (status.status === \"failed\" || status.status === \"reverted\") {\n throw new Error(`Batch transaction failed: ${status.status}`);\n }\n } catch (statusError: any) {\n // Some wallets may not support getCallsStatus, wait and check chain\n if (statusError.message?.includes(\"not supported\")) {\n logger.warn(\"getCallsStatus not supported, waiting for chain confirmation...\");\n // Fall back to waiting a fixed time\n await new Promise((resolve) => setTimeout(resolve, 15000));\n break;\n }\n throw statusError;\n }\n\n // Wait 5 seconds before next poll\n await new Promise((resolve) => setTimeout(resolve, 5000));\n attempts++;\n }\n\n if (attempts >= maxAttempts) {\n throw new Error(\"Timeout waiting for batch confirmation\");\n }\n\n if (publicLogs) {\n onProgress?.(\"setPublicLogs\");\n }\n onProgress?.(\"complete\");\n\n // Extract transaction hashes from receipts\n const receipts = (status?.receipts || []).map((r: any) => ({\n transactionHash: r.transactionHash || r.hash,\n }));\n\n logger.info(`Deployment complete! App ID: ${data.appId}`);\n\n return {\n appId: data.appId,\n batchId,\n receipts,\n };\n } catch (error: any) {\n // Check if the error indicates sendCalls is not supported\n if (\n error.message?.includes(\"not supported\") ||\n error.message?.includes(\"wallet_sendCalls\") ||\n error.code === -32601 // Method not found\n ) {\n throw new Error(\"EIP5792_NOT_SUPPORTED\");\n }\n throw error;\n }\n}\n\n/**\n * Upgrade app options\n */\nexport interface UpgradeAppOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n appID: Address;\n release: Release;\n publicLogs: boolean;\n needsPermissionChange: boolean;\n imageRef: string;\n gas?: GasEstimate;\n}\n\n/**\n * Options for preparing an upgrade batch\n */\nexport interface PrepareUpgradeBatchOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n appID: Address;\n release: Release;\n publicLogs: boolean;\n needsPermissionChange: boolean;\n imageRef: string;\n}\n\n/**\n * Prepare upgrade batch - creates executions without sending transaction\n *\n * Use this to get the prepared batch for gas estimation before executing.\n */\nexport async function prepareUpgradeBatch(\n options: PrepareUpgradeBatchOptions,\n): Promise<PreparedUpgradeBatch> {\n const {\n walletClient,\n publicClient,\n environmentConfig,\n appID,\n release,\n publicLogs,\n needsPermissionChange,\n } = options;\n\n // 1. Pack upgrade app call\n // Convert Release Uint8Array values to hex strings for viem\n const releaseForViem = {\n rmsRelease: {\n artifacts: release.rmsRelease.artifacts.map((artifact) => ({\n digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, \"0\")}` as Hex,\n registry: artifact.registry,\n })),\n upgradeByTime: release.rmsRelease.upgradeByTime,\n },\n publicEnv: bytesToHex(release.publicEnv) as Hex,\n encryptedEnv: bytesToHex(release.encryptedEnv) as Hex,\n };\n\n const upgradeData = encodeFunctionData({\n abi: AppControllerABI,\n functionName: \"upgradeApp\",\n args: [appID, releaseForViem],\n });\n\n // 2. Start with upgrade execution\n const executions: Array<{\n target: Address;\n value: bigint;\n callData: Hex;\n }> = [\n {\n target: environmentConfig.appControllerAddress,\n value: 0n,\n callData: upgradeData,\n },\n ];\n\n // 3. Add permission transaction if needed\n if (needsPermissionChange) {\n if (publicLogs) {\n // Add public permission (private→public)\n const addLogsData = encodeFunctionData({\n abi: PermissionControllerABI,\n functionName: \"setAppointee\",\n args: [\n appID,\n \"0x493219d9949348178af1f58740655951a8cd110c\" as Address, // AnyoneCanCallAddress\n \"0x57ee1fb74c1087e26446abc4fb87fd8f07c43d8d\" as Address, // ApiPermissionsTarget\n \"0x2fd3f2fe\" as Hex, // CanViewAppLogsPermission\n ],\n });\n executions.push({\n target: environmentConfig.permissionControllerAddress as Address,\n value: 0n,\n callData: addLogsData,\n });\n } else {\n // Remove public permission (public→private)\n const removeLogsData = encodeFunctionData({\n abi: PermissionControllerABI,\n functionName: \"removeAppointee\",\n args: [\n appID,\n \"0x493219d9949348178af1f58740655951a8cd110c\" as Address, // AnyoneCanCallAddress\n \"0x57ee1fb74c1087e26446abc4fb87fd8f07c43d8d\" as Address, // ApiPermissionsTarget\n \"0x2fd3f2fe\" as Hex, // CanViewAppLogsPermission\n ],\n });\n executions.push({\n target: environmentConfig.permissionControllerAddress as Address,\n value: 0n,\n callData: removeLogsData,\n });\n }\n }\n\n return {\n appId: appID,\n executions,\n walletClient,\n publicClient,\n environmentConfig,\n };\n}\n\n/**\n * Execute a prepared upgrade batch\n */\nexport async function executeUpgradeBatch(\n data: PreparedUpgradeData,\n context: {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n },\n gas?: GasEstimate,\n logger: Logger = noopLogger,\n): Promise<Hex> {\n const pendingMessage = `Upgrading app ${data.appId}...`;\n\n const txHash = await executeBatch(\n {\n walletClient: context.walletClient,\n publicClient: context.publicClient,\n environmentConfig: context.environmentConfig,\n executions: data.executions,\n pendingMessage,\n gas,\n authorizationList: data.authorizationList,\n },\n logger,\n );\n\n return txHash;\n}\n\n/**\n * Upgrade app on-chain (convenience wrapper that prepares and executes)\n */\nexport async function upgradeApp(\n options: UpgradeAppOptions,\n logger: Logger = noopLogger,\n): Promise<Hex> {\n const prepared = await prepareUpgradeBatch(options);\n\n // Extract data and context from prepared batch\n const data: PreparedUpgradeData = {\n appId: prepared.appId,\n executions: prepared.executions,\n };\n const context = {\n walletClient: prepared.walletClient,\n publicClient: prepared.publicClient,\n environmentConfig: prepared.environmentConfig,\n };\n\n return executeUpgradeBatch(data, context, options.gas, logger);\n}\n\n/**\n * Send and wait for transaction with confirmation support\n */\nexport interface SendTransactionOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n to: Address;\n data: Hex;\n value?: bigint;\n pendingMessage: string;\n txDescription: string;\n gas?: GasEstimate;\n}\n\nexport async function sendAndWaitForTransaction(\n options: SendTransactionOptions,\n logger: Logger = noopLogger,\n): Promise<Hex> {\n const {\n walletClient,\n publicClient,\n environmentConfig,\n to,\n data,\n value = 0n,\n pendingMessage,\n txDescription,\n gas,\n } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n const chain = getChainFromID(environmentConfig.chainID);\n\n // Show pending message if provided\n if (pendingMessage) {\n logger.info(`\\n${pendingMessage}`);\n }\n\n // Send transaction with optional gas params\n const hash = await walletClient.sendTransaction({\n account,\n to,\n data,\n value,\n ...(gas?.maxFeePerGas && { maxFeePerGas: gas.maxFeePerGas }),\n ...(gas?.maxPriorityFeePerGas && {\n maxPriorityFeePerGas: gas.maxPriorityFeePerGas,\n }),\n chain,\n });\n\n logger.info(`Transaction sent: ${hash}`);\n\n // Wait for receipt\n const receipt = await publicClient.waitForTransactionReceipt({ hash });\n\n if (receipt.status === \"reverted\") {\n let revertReason = \"Unknown reason\";\n try {\n await publicClient.call({\n to,\n data,\n account: account.address,\n });\n } catch (callError: any) {\n if (callError.data) {\n try {\n const decoded = decodeErrorResult({\n abi: AppControllerABI,\n data: callError.data,\n });\n const formattedError = formatAppControllerError(decoded);\n revertReason = formattedError.message;\n } catch {\n revertReason = callError.message || \"Unknown reason\";\n }\n } else {\n revertReason = callError.message || \"Unknown reason\";\n }\n }\n logger.error(`${txDescription} transaction (hash: ${hash}) reverted: ${revertReason}`);\n throw new Error(`${txDescription} transaction (hash: ${hash}) reverted: ${revertReason}`);\n }\n\n return hash;\n}\n\n/**\n * Format AppController errors to user-friendly messages\n */\nfunction formatAppControllerError(decoded: {\n errorName: string;\n args?: readonly unknown[];\n}): Error {\n const errorName = decoded.errorName;\n\n switch (errorName) {\n case \"MaxActiveAppsExceeded\":\n return new Error(\n \"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\",\n );\n case \"GlobalMaxActiveAppsExceeded\":\n return new Error(\n \"the platform has reached the maximum number of active apps. please try again later\",\n );\n case \"InvalidPermissions\":\n return new Error(\"you don't have permission to perform this operation\");\n case \"AppAlreadyExists\":\n return new Error(\"an app with this owner and salt already exists\");\n case \"AppDoesNotExist\":\n return new Error(\"the specified app does not exist\");\n case \"InvalidAppStatus\":\n return new Error(\"the app is in an invalid state for this operation\");\n case \"MoreThanOneArtifact\":\n return new Error(\"only one artifact is allowed per release\");\n case \"InvalidSignature\":\n return new Error(\"invalid signature provided\");\n case \"SignatureExpired\":\n return new Error(\"the provided signature has expired\");\n case \"InvalidReleaseMetadataURI\":\n return new Error(\"invalid release metadata URI provided\");\n case \"InvalidShortString\":\n return new Error(\"invalid short string format\");\n default:\n return new Error(`contract error: ${errorName}`);\n }\n}\n\n/**\n * Get active app count for a user\n */\nexport async function getActiveAppCount(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n user: Address,\n): Promise<number> {\n const count = await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getActiveAppCount\",\n args: [user],\n });\n\n return Number(count);\n}\n\n/**\n * Get max active apps per user (quota limit)\n */\nexport async function getMaxActiveAppsPerUser(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n user: Address,\n): Promise<number> {\n const quota = await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getMaxActiveAppsPerUser\",\n args: [user],\n });\n\n return Number(quota);\n}\n\n/**\n * Get apps by creator (paginated)\n */\nexport interface AppConfig {\n release: any; // Release struct from contract\n status: number; // AppStatus enum\n}\n\nexport async function getAppsByCreator(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n creator: Address,\n offset: bigint,\n limit: bigint,\n): Promise<{ apps: Address[]; appConfigs: AppConfig[] }> {\n const result = (await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getAppsByCreator\",\n args: [creator, offset, limit],\n })) as [Address[], AppConfig[]];\n\n // Result is a tuple: [Address[], AppConfig[]]\n return {\n apps: result[0],\n appConfigs: result[1],\n };\n}\n\n/**\n * Get apps by developer\n */\nexport async function getAppsByDeveloper(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n developer: Address,\n offset: bigint,\n limit: bigint,\n): Promise<{ apps: Address[]; appConfigs: AppConfig[] }> {\n const result = (await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getAppsByDeveloper\",\n args: [developer, offset, limit],\n })) as [Address[], AppConfig[]];\n\n // Result is a tuple: [Address[], AppConfig[]]\n return {\n apps: result[0],\n appConfigs: result[1],\n };\n}\n\n/**\n * Get billing type for an app (0 = DEFAULT, 1 = ISOLATED)\n */\nexport async function getBillingType(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n app: Address,\n): Promise<number> {\n const result = await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getBillingType\",\n args: [app],\n });\n return Number(result);\n}\n\n/**\n * Get apps by billing account (paginated)\n */\nexport async function getAppsByBillingAccount(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n account: Address,\n offset: bigint,\n limit: bigint,\n): Promise<{ apps: Address[]; appConfigs: AppConfig[] }> {\n const result = (await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getAppsByBillingAccount\",\n args: [account, offset, limit],\n })) as [Address[], AppConfig[]];\n return { apps: result[0], appConfigs: result[1] };\n}\n\n/**\n * Fetch all apps by a developer by auto-pagination\n */\nexport async function getAllAppsByDeveloper(\n publicClient: PublicClient,\n env: EnvironmentConfig,\n developer: Address,\n pageSize: bigint = 100n,\n): Promise<{ apps: Address[]; appConfigs: AppConfig[] }> {\n let offset = 0n;\n const allApps: Address[] = [];\n const allConfigs: AppConfig[] = [];\n\n while (true) {\n const { apps, appConfigs } = await getAppsByDeveloper(\n publicClient,\n env,\n developer,\n offset,\n pageSize,\n );\n\n if (apps.length === 0) break;\n\n allApps.push(...apps);\n allConfigs.push(...appConfigs);\n\n if (apps.length < Number(pageSize)) break;\n\n offset += pageSize;\n }\n\n return {\n apps: allApps,\n appConfigs: allConfigs,\n };\n}\n\n/**\n * Get latest release block numbers for multiple apps\n */\nexport async function getAppLatestReleaseBlockNumbers(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n appIDs: Address[],\n): Promise<Map<Address, number>> {\n // Fetch block numbers in parallel\n const results = await Promise.all(\n appIDs.map((appID) =>\n publicClient\n .readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getAppLatestReleaseBlockNumber\",\n args: [appID],\n })\n .catch(() => null),\n ),\n );\n\n const blockNumbers = new Map<Address, number>();\n for (let i = 0; i < appIDs.length; i++) {\n const result = results[i];\n if (result !== null && result !== undefined) {\n blockNumbers.set(appIDs[i], Number(result));\n }\n }\n\n return blockNumbers;\n}\n\n/**\n * Get block timestamps for multiple block numbers\n */\nexport async function getBlockTimestamps(\n publicClient: PublicClient,\n blockNumbers: number[],\n): Promise<Map<number, number>> {\n // Deduplicate block numbers\n const uniqueBlockNumbers = [...new Set(blockNumbers)].filter((n) => n > 0);\n\n const timestamps = new Map<number, number>();\n\n // Fetch blocks in parallel\n const blocks = await Promise.all(\n uniqueBlockNumbers.map((blockNumber) =>\n publicClient.getBlock({ blockNumber: BigInt(blockNumber) }).catch(() => null),\n ),\n );\n\n for (let i = 0; i < uniqueBlockNumbers.length; i++) {\n const block = blocks[i];\n if (block) {\n timestamps.set(uniqueBlockNumbers[i], Number(block.timestamp));\n }\n }\n\n return timestamps;\n}\n\n/**\n * Suspend options\n */\nexport interface SuspendOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n account: Address;\n apps: Address[];\n}\n\n/**\n * Suspend apps for an account\n */\nexport async function suspend(\n options: SuspendOptions,\n logger: Logger = noopLogger,\n): Promise<Hex | false> {\n const { walletClient, publicClient, environmentConfig, account, apps } = options;\n\n const suspendData = encodeFunctionData({\n abi: AppControllerABI,\n functionName: \"suspend\",\n args: [account, apps],\n });\n\n const pendingMessage = `Suspending ${apps.length} app(s)...`;\n\n return sendAndWaitForTransaction(\n {\n walletClient,\n publicClient,\n environmentConfig,\n to: environmentConfig.appControllerAddress as Address,\n data: suspendData,\n pendingMessage,\n txDescription: \"Suspend\",\n },\n logger,\n );\n}\n\n/**\n * Options for checking delegation status\n */\nexport interface IsDelegatedOptions {\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n address: Address;\n}\n\n/**\n * Check if account is delegated to the ERC-7702 delegator\n */\nexport async function isDelegated(options: IsDelegatedOptions): Promise<boolean> {\n const { publicClient, environmentConfig, address } = options;\n\n return checkERC7702Delegation(\n publicClient,\n address,\n environmentConfig.erc7702DelegatorAddress as Address,\n );\n}\n\n/**\n * Undelegate options\n */\nexport interface UndelegateOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n}\n\n/**\n * Undelegate account (removes EIP-7702 delegation)\n */\nexport async function undelegate(\n options: UndelegateOptions,\n logger: Logger = noopLogger,\n): Promise<Hex> {\n const { walletClient, publicClient, environmentConfig } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n const chain = getChainFromID(environmentConfig.chainID);\n\n // Create authorization to undelegate (empty address = undelegate)\n const transactionNonce = await publicClient.getTransactionCount({\n address: account.address,\n blockTag: \"pending\",\n });\n\n const chainId = await publicClient.getChainId();\n const authorizationNonce = BigInt(transactionNonce) + 1n;\n\n logger.debug(\"Signing undelegate authorization\");\n\n const signedAuthorization = await walletClient.signAuthorization({\n contractAddress: \"0x0000000000000000000000000000000000000000\" as Address,\n chainId: chainId,\n nonce: Number(authorizationNonce),\n account: account,\n });\n\n const authorizationList = [signedAuthorization];\n\n // Send transaction with authorization list\n const hash = await walletClient.sendTransaction({\n account,\n to: account.address, // Send to self\n data: \"0x\" as Hex, // Empty data\n value: 0n,\n authorizationList,\n chain,\n });\n\n logger.info(`Transaction sent: ${hash}`);\n\n const receipt = await publicClient.waitForTransactionReceipt({ hash });\n\n if (receipt.status === \"reverted\") {\n logger.error(`Undelegate transaction (hash: ${hash}) reverted`);\n throw new Error(`Undelegate transaction (hash: ${hash}) reverted`);\n }\n\n return hash;\n}\n","[\n {\n \"type\": \"function\",\n \"name\": \"purchaseCreditsFor\",\n \"stateMutability\": \"nonpayable\",\n \"inputs\": [\n { \"name\": \"amount\", \"type\": \"uint256\" },\n { \"name\": \"account\", \"type\": \"address\" }\n ],\n \"outputs\": []\n },\n {\n \"type\": \"function\",\n \"name\": \"purchaseCredits\",\n \"stateMutability\": \"nonpayable\",\n \"inputs\": [\n { \"name\": \"amount\", \"type\": \"uint256\" }\n ],\n \"outputs\": []\n },\n {\n \"type\": \"function\",\n \"name\": \"usdc\",\n \"stateMutability\": \"view\",\n \"inputs\": [],\n \"outputs\": [\n { \"name\": \"\", \"type\": \"address\" }\n ]\n },\n {\n \"type\": \"function\",\n \"name\": \"minimumPurchase\",\n \"stateMutability\": \"view\",\n \"inputs\": [],\n \"outputs\": [\n { \"name\": \"\", \"type\": \"uint256\" }\n ]\n },\n {\n \"type\": \"event\",\n \"name\": \"CreditsPurchased\",\n \"inputs\": [\n { \"name\": \"purchaser\", \"type\": \"address\", \"indexed\": true },\n { \"name\": \"account\", \"type\": \"address\", \"indexed\": true },\n { \"name\": \"amount\", \"type\": \"uint256\", \"indexed\": false }\n ]\n }\n]\n","[\n {\n \"type\": \"function\",\n \"name\": \"approve\",\n \"stateMutability\": \"nonpayable\",\n \"inputs\": [\n { \"name\": \"spender\", \"type\": \"address\" },\n { \"name\": \"amount\", \"type\": \"uint256\" }\n ],\n \"outputs\": [\n { \"name\": \"\", \"type\": \"bool\" }\n ]\n },\n {\n \"type\": \"function\",\n \"name\": \"balanceOf\",\n \"stateMutability\": \"view\",\n \"inputs\": [\n { \"name\": \"account\", \"type\": \"address\" }\n ],\n \"outputs\": [\n { \"name\": \"\", \"type\": \"uint256\" }\n ]\n },\n {\n \"type\": \"function\",\n \"name\": \"allowance\",\n \"stateMutability\": \"view\",\n \"inputs\": [\n { \"name\": \"owner\", \"type\": \"address\" },\n { \"name\": \"spender\", \"type\": \"address\" }\n ],\n \"outputs\": [\n { \"name\": \"\", \"type\": \"uint256\" }\n ]\n }\n]\n"],"mappings":"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","names":["encodeFunctionData","body","axios","sepolia","axios","encodeFunctionData","decodeErrorResult","encodeFunctionData","decodeErrorResult","encodeFunctionData"]}
1
+ {"version":3,"sources":["../src/client/common/auth/session.ts","../src/client/modules/billing/index.ts","../src/client/common/utils/billingapi.ts","../src/client/common/utils/auth.ts","../src/client/common/auth/billingSession.ts","../src/client/common/config/environment.ts","../src/client/common/utils/logger.ts","../src/client/common/utils/userapi.ts","../src/client/common/utils/helpers.ts","../src/client/common/constants.ts","../src/client/common/utils/retry.ts","../src/client/common/utils/billing.ts","../src/client/common/telemetry/noop.ts","../src/client/common/telemetry/posthog.ts","../src/client/common/telemetry/index.ts","../src/client/common/telemetry/metricsContext.ts","../src/client/common/telemetry/wrapper.ts","../src/client/common/contract/eip7702.ts","../src/client/common/types/index.ts","../src/client/common/abis/ERC7702Delegator.json","../src/client/common/contract/caller.ts","../src/client/common/abis/USDCCredits.json","../src/client/common/abis/ERC20.json"],"sourcesContent":["/**\n * Compute API Session Management\n *\n * This module provides utilities for managing authentication sessions with the compute API\n * using SIWE (Sign-In with Ethereum).\n */\n\nimport { Address, Hex } from \"viem\";\n\nexport interface ComputeApiConfig {\n /** Base URL of the compute API (e.g., \"https://api.eigencloud.xyz\") */\n baseUrl: string;\n}\n\nexport interface SessionInfo {\n /** Whether the session is authenticated */\n authenticated: boolean;\n /** Authenticated wallet address (if authenticated) */\n address?: Address;\n /** Chain ID used for authentication (if authenticated) */\n chainId?: number;\n}\n\nexport interface LoginResult {\n /** Whether login was successful */\n success: boolean;\n /** Authenticated wallet address */\n address: Address;\n}\n\nexport interface LoginRequest {\n /** SIWE message string */\n message: string;\n /** Hex-encoded signature (with or without 0x prefix) */\n signature: Hex | string;\n}\n\n/**\n * Error thrown when session operations fail\n */\nexport class SessionError extends Error {\n constructor(\n message: string,\n public readonly code:\n | \"NETWORK_ERROR\"\n | \"INVALID_SIGNATURE\"\n | \"INVALID_MESSAGE\"\n | \"SESSION_EXPIRED\"\n | \"UNAUTHORIZED\"\n | \"UNKNOWN\",\n public readonly statusCode?: number,\n ) {\n super(message);\n this.name = \"SessionError\";\n }\n}\n\n/**\n * Strip 0x prefix from hex string if present\n */\nfunction stripHexPrefix(hex: string): string {\n return hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n}\n\n/**\n * Parse error response body\n */\nasync function parseErrorResponse(response: Response): Promise<string> {\n try {\n const data = (await response.json()) as { error?: string };\n return data.error || response.statusText;\n } catch {\n return response.statusText;\n }\n}\n\n/**\n * Login to the compute API using SIWE\n *\n * This establishes a session with the compute API by verifying the SIWE message\n * and signature. On success, a session cookie is set in the browser.\n *\n * @param config - Compute API configuration\n * @param request - Login request containing SIWE message and signature\n * @returns Login result with the authenticated address\n *\n * @example\n * ```typescript\n * import { createSiweMessage, loginToComputeApi } from \"@layr-labs/ecloud-sdk/browser\";\n *\n * const { message } = createSiweMessage({\n * address: userAddress,\n * chainId: 11155111,\n * domain: window.location.host,\n * uri: window.location.origin,\n * });\n *\n * const signature = await signMessageAsync({ message });\n * const result = await loginToComputeApi(\n * { baseUrl: \"https://api.eigencloud.xyz\" },\n * { message, signature }\n * );\n * ```\n */\nexport async function loginToComputeApi(\n config: ComputeApiConfig,\n request: LoginRequest,\n): Promise<LoginResult> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/siwe/login`, {\n method: \"POST\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n message: request.message,\n signature: stripHexPrefix(request.signature),\n }),\n });\n } catch (error) {\n throw new SessionError(\n `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,\n \"NETWORK_ERROR\",\n );\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n const status = response.status;\n\n if (status === 400) {\n if (errorMessage.toLowerCase().includes(\"siwe\")) {\n throw new SessionError(`Invalid SIWE message: ${errorMessage}`, \"INVALID_MESSAGE\", status);\n }\n throw new SessionError(`Bad request: ${errorMessage}`, \"INVALID_MESSAGE\", status);\n }\n\n if (status === 401) {\n throw new SessionError(`Invalid signature: ${errorMessage}`, \"INVALID_SIGNATURE\", status);\n }\n\n throw new SessionError(`Login failed: ${errorMessage}`, \"UNKNOWN\", status);\n }\n\n const data = (await response.json()) as { success: boolean; address: string };\n\n return {\n success: data.success,\n address: data.address as Address,\n };\n}\n\n/**\n * Get the current session status from the compute API\n *\n * @param config - Compute API configuration\n * @returns Session information including authentication status and address\n *\n * @example\n * ```typescript\n * const session = await getComputeApiSession({ baseUrl: \"https://api.eigencloud.xyz\" });\n * if (session.authenticated) {\n * console.log(`Logged in as ${session.address}`);\n * }\n * ```\n */\nexport async function getComputeApiSession(config: ComputeApiConfig): Promise<SessionInfo> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/session`, {\n method: \"GET\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n } catch {\n // Network error - return unauthenticated session\n return {\n authenticated: false,\n };\n }\n\n // If we get a 401, return unauthenticated session\n if (response.status === 401) {\n return {\n authenticated: false,\n };\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n throw new SessionError(`Failed to get session: ${errorMessage}`, \"UNKNOWN\", response.status);\n }\n\n const data = (await response.json()) as {\n authenticated: boolean;\n address?: string;\n chain_id?: number;\n };\n\n return {\n authenticated: data.authenticated,\n address: data.address as Address | undefined,\n chainId: data.chain_id,\n };\n}\n\n/**\n * Logout from the compute API\n *\n * This destroys the current session and clears the session cookie.\n *\n * @param config - Compute API configuration\n *\n * @example\n * ```typescript\n * await logoutFromComputeApi({ baseUrl: \"https://api.eigencloud.xyz\" });\n * ```\n */\nexport async function logoutFromComputeApi(config: ComputeApiConfig): Promise<void> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/logout`, {\n method: \"POST\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n } catch (error) {\n throw new SessionError(\n `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,\n \"NETWORK_ERROR\",\n );\n }\n\n // Ignore 401 errors during logout (already logged out)\n if (response.status === 401) {\n return;\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n throw new SessionError(`Logout failed: ${errorMessage}`, \"UNKNOWN\", response.status);\n }\n}\n\n/**\n * Check if a session is still valid (not expired)\n *\n * This is a convenience function that checks the session status\n * and returns a boolean.\n *\n * @param config - Compute API configuration\n * @returns True if session is authenticated, false otherwise\n */\nexport async function isSessionValid(config: ComputeApiConfig): Promise<boolean> {\n const session = await getComputeApiSession(config);\n return session.authenticated;\n}\n","/**\n * Main Billing namespace entry point\n *\n * Accepts viem's WalletClient which abstracts over both local accounts\n * (privateKeyToAccount) and external signers (MetaMask, etc.).\n */\n\nimport type { WalletClient, PublicClient } from \"viem\";\nimport { type Address, type Hex, encodeFunctionData } from \"viem\";\n\nimport { BillingApiClient } from \"../../common/utils/billingapi\";\nimport { getBillingEnvironmentConfig, getBuildType, getEnvironmentConfig } from \"../../common/config/environment\";\nimport { getLogger, isSubscriptionActive } from \"../../common/utils\";\nimport { withSDKTelemetry } from \"../../common/telemetry/wrapper\";\nimport { executeBatch, type Execution } from \"../../common/contract/eip7702\";\n\nimport USDCCreditsABI from \"../../common/abis/USDCCredits.json\";\nimport ERC20ABI from \"../../common/abis/ERC20.json\";\n\nimport type {\n ProductID,\n SubscriptionOpts,\n SubscribeResponse,\n CancelResponse,\n ProductSubscriptionResponse,\n} from \"../../common/types\";\n\nexport interface TopUpOpts {\n /** Amount in raw USDC units (6 decimals, e.g. 50_000_000n = 50 USDC) */\n amount: bigint;\n /** Target account for purchaseCreditsFor (defaults to wallet address) */\n account?: Address;\n}\n\nexport interface TopUpResult {\n txHash: Hex;\n walletAddress: Address;\n}\n\nexport interface TopUpInfo {\n usdcAddress: Address;\n minimumPurchase: bigint;\n usdcBalance: bigint;\n currentAllowance: bigint;\n}\n\nexport interface BillingModule {\n address: Address;\n subscribe: (opts?: SubscriptionOpts) => Promise<SubscribeResponse>;\n getStatus: (opts?: SubscriptionOpts) => Promise<ProductSubscriptionResponse>;\n cancel: (opts?: SubscriptionOpts) => Promise<CancelResponse>;\n /** Read on-chain state needed for top-up */\n getTopUpInfo: () => Promise<TopUpInfo>;\n /** Purchase credits with USDC on-chain */\n topUp: (opts: TopUpOpts) => Promise<TopUpResult>;\n}\n\nexport interface BillingModuleConfig {\n verbose?: boolean;\n walletClient: WalletClient;\n skipTelemetry?: boolean; // Skip telemetry when called from CLI\n publicClient: PublicClient;\n environment: string;\n}\n\nexport function createBillingModule(config: BillingModuleConfig): BillingModule {\n const { verbose = false, skipTelemetry = false, walletClient, publicClient, environment } = config;\n\n // Get address from wallet client's account\n if (!walletClient.account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n const address = walletClient.account.address as Address;\n\n const logger = getLogger(verbose);\n\n // Get billing environment configuration\n const billingEnvConfig = getBillingEnvironmentConfig(getBuildType());\n\n // Create billing API client\n const billingApi = new BillingApiClient(billingEnvConfig, walletClient);\n\n // Resolve on-chain config\n const environmentConfig = getEnvironmentConfig(environment);\n const usdcCreditsAddress = environmentConfig.usdcCreditsAddress;\n if (!usdcCreditsAddress) {\n throw new Error(`USDCCredits contract address not configured for environment \"${environment}\"`);\n }\n\n const module: BillingModule = {\n address,\n\n async getTopUpInfo(): Promise<TopUpInfo> {\n const usdcAddress = await publicClient.readContract({\n address: usdcCreditsAddress,\n abi: USDCCreditsABI,\n functionName: \"usdc\",\n }) as Address;\n\n const [minimumPurchase, usdcBalance, currentAllowance] = await Promise.all([\n publicClient.readContract({\n address: usdcCreditsAddress,\n abi: USDCCreditsABI,\n functionName: \"minimumPurchase\",\n }) as Promise<bigint>,\n publicClient.readContract({\n address: usdcAddress,\n abi: ERC20ABI,\n functionName: \"balanceOf\",\n args: [address],\n }) as Promise<bigint>,\n publicClient.readContract({\n address: usdcAddress,\n abi: ERC20ABI,\n functionName: \"allowance\",\n args: [address, usdcCreditsAddress],\n }) as Promise<bigint>,\n ]);\n\n return { usdcAddress, minimumPurchase, usdcBalance, currentAllowance };\n },\n\n async topUp(opts: TopUpOpts): Promise<TopUpResult> {\n return withSDKTelemetry(\n {\n functionName: \"topUp\",\n skipTelemetry,\n properties: { amount: opts.amount.toString() },\n },\n async () => {\n const targetAccount = opts.account ?? address;\n\n // Read on-chain state\n const { usdcAddress, currentAllowance } = await module.getTopUpInfo();\n\n // Build executions array\n const executions: Execution[] = [];\n\n // Conditionally include approve if allowance insufficient\n if (currentAllowance < opts.amount) {\n executions.push({\n target: usdcAddress,\n value: 0n,\n callData: encodeFunctionData({\n abi: ERC20ABI,\n functionName: \"approve\",\n args: [usdcCreditsAddress, opts.amount],\n }),\n });\n }\n\n // Always include purchaseCreditsFor\n executions.push({\n target: usdcCreditsAddress,\n value: 0n,\n callData: encodeFunctionData({\n abi: USDCCreditsABI,\n functionName: \"purchaseCreditsFor\",\n args: [opts.amount, targetAccount],\n }),\n });\n\n const txHash = await executeBatch(\n {\n walletClient,\n publicClient,\n environmentConfig,\n executions,\n pendingMessage: \"Submitting credit purchase...\",\n },\n logger,\n );\n\n return { txHash, walletAddress: address };\n },\n );\n },\n\n async subscribe(opts) {\n return withSDKTelemetry(\n {\n functionName: \"subscribe\",\n skipTelemetry: skipTelemetry, // Skip if called from CLI\n properties: { productId: opts?.productId || \"compute\" },\n },\n async () => {\n const productId: ProductID = opts?.productId || \"compute\";\n\n // Check existing subscription status first\n logger.debug(`Checking existing subscription for ${productId}...`);\n const currentStatus = await billingApi.getSubscription(productId);\n\n // If already active or trialing, don't create new checkout\n if (isSubscriptionActive(currentStatus.subscriptionStatus)) {\n logger.debug(`Subscription already active: ${currentStatus.subscriptionStatus}`);\n return {\n type: \"already_active\" as const,\n status: currentStatus.subscriptionStatus,\n };\n }\n\n // If subscription has payment issues, return portal URL instead\n if (\n currentStatus.subscriptionStatus === \"past_due\" ||\n currentStatus.subscriptionStatus === \"unpaid\"\n ) {\n logger.debug(`Subscription has payment issue: ${currentStatus.subscriptionStatus}`);\n return {\n type: \"payment_issue\" as const,\n status: currentStatus.subscriptionStatus,\n portalUrl: currentStatus.portalUrl,\n };\n }\n\n // Create new checkout session\n logger.debug(`Creating subscription for ${productId}...`);\n const result = await billingApi.createSubscription(productId, {\n successUrl: opts?.successUrl,\n cancelUrl: opts?.cancelUrl,\n });\n\n logger.debug(`Checkout URL: ${result.checkoutUrl}`);\n return {\n type: \"checkout_created\" as const,\n checkoutUrl: result.checkoutUrl,\n };\n },\n );\n },\n\n async getStatus(opts) {\n return withSDKTelemetry(\n {\n functionName: \"getStatus\",\n skipTelemetry: skipTelemetry, // Skip if called from CLI\n properties: { productId: opts?.productId || \"compute\" },\n },\n async () => {\n const productId: ProductID = opts?.productId || \"compute\";\n logger.debug(`Fetching subscription status for ${productId}...`);\n\n const result = await billingApi.getSubscription(productId);\n\n logger.debug(`Subscription status: ${result.subscriptionStatus}`);\n return result;\n },\n );\n },\n\n async cancel(opts) {\n return withSDKTelemetry(\n {\n functionName: \"cancel\",\n skipTelemetry: skipTelemetry, // Skip if called from CLI\n properties: { productId: opts?.productId || \"compute\" },\n },\n async () => {\n const productId: ProductID = opts?.productId || \"compute\";\n\n // Check existing subscription status first\n logger.debug(`Checking subscription status for ${productId}...`);\n const currentStatus = await billingApi.getSubscription(productId);\n\n // If no active subscription, don't attempt to cancel\n if (!isSubscriptionActive(currentStatus.subscriptionStatus)) {\n logger.debug(`No active subscription to cancel: ${currentStatus.subscriptionStatus}`);\n return {\n type: \"no_active_subscription\" as const,\n status: currentStatus.subscriptionStatus,\n };\n }\n\n // Cancel the subscription\n logger.debug(`Canceling subscription for ${productId}...`);\n await billingApi.cancelSubscription(productId);\n\n logger.debug(`Subscription canceled successfully`);\n return {\n type: \"canceled\" as const,\n };\n },\n );\n },\n };\n\n return module;\n}\n","/**\n * BillingAPI Client to manage product subscriptions\n * Standalone client - does not depend on chain infrastructure\n *\n * Accepts viem's WalletClient which abstracts over both local accounts\n * (privateKeyToAccount) and external signers (MetaMask, etc.).\n *\n * Supports two authentication modes:\n * 1. EIP-712 signature auth (default) - signs each request with typed data\n * 2. Session auth (optional) - uses SIWE session cookies\n */\n\nimport axios, { AxiosResponse } from \"axios\";\nimport { Address, type WalletClient } from \"viem\";\nimport {\n ProductID,\n CreateSubscriptionOptions,\n CreateSubscriptionResponse,\n GetSubscriptionOptions,\n ProductSubscriptionResponse,\n} from \"../types\";\nimport { calculateBillingAuthSignature } from \"./auth\";\nimport { BillingEnvironmentConfig } from \"../types\";\nimport {\n loginToBillingApi,\n logoutFromBillingApi,\n getBillingApiSession,\n type BillingSessionInfo,\n type BillingLoginResult,\n type BillingLoginRequest,\n} from \"../auth/billingSession\";\n\nexport interface BillingApiClientOptions {\n /**\n * Use session-based authentication instead of per-request signatures.\n * When true, the client will rely on session cookies set by SIWE login.\n * When false (default), uses EIP-712 typed data signatures for each request.\n */\n useSession?: boolean;\n}\n\n/**\n * BillingAPI Client for managing product subscriptions.\n */\nexport class BillingApiClient {\n private readonly useSession: boolean;\n\n constructor(\n private readonly config: BillingEnvironmentConfig,\n private readonly walletClient: WalletClient | null,\n private readonly options: BillingApiClientOptions = {},\n ) {\n this.useSession = options.useSession ?? false;\n\n // Validate that walletClient is provided when not using session auth\n if (!this.useSession && !walletClient) {\n throw new Error(\"WalletClient is required when not using session authentication\");\n }\n }\n\n /**\n * Get the address of the connected wallet\n * Returns undefined if using session auth without a wallet client\n */\n get address(): Address | undefined {\n const account = this.walletClient?.account;\n if (!account) {\n if (!this.useSession) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n return undefined;\n }\n return account.address;\n }\n\n /**\n * Get the base URL of the billing API\n */\n get baseUrl(): string {\n return this.config.billingApiServerURL;\n }\n\n // ==========================================================================\n // SIWE Session Methods\n // ==========================================================================\n\n /**\n * Login to the billing API using SIWE\n *\n * This establishes a session with the billing API by verifying the SIWE message\n * and signature. On success, a session cookie is set in the browser.\n *\n * @param request - Login request containing SIWE message and signature\n * @returns Login result with the authenticated address\n *\n * @example\n * ```typescript\n * const { message } = createSiweMessage({\n * address: userAddress,\n * chainId: 11155111,\n * domain: window.location.host,\n * uri: window.location.origin,\n * });\n *\n * const signature = await signMessageAsync({ message });\n * const result = await billingClient.siweLogin({ message, signature });\n * ```\n */\n async siweLogin(request: BillingLoginRequest): Promise<BillingLoginResult> {\n return loginToBillingApi({ baseUrl: this.baseUrl }, request);\n }\n\n /**\n * Logout from the billing API\n *\n * This destroys the current session and clears the session cookie.\n */\n async siweLogout(): Promise<void> {\n return logoutFromBillingApi({ baseUrl: this.baseUrl });\n }\n\n /**\n * Get the current session status from the billing API\n *\n * @returns Session information including authentication status and address\n */\n async getSession(): Promise<BillingSessionInfo> {\n return getBillingApiSession({ baseUrl: this.baseUrl });\n }\n\n /**\n * Check if there is a valid session\n *\n * @returns True if session is authenticated, false otherwise\n */\n async isSessionValid(): Promise<boolean> {\n const session = await this.getSession();\n return session.authenticated;\n }\n\n // ==========================================================================\n // Subscription Methods\n // ==========================================================================\n\n async createSubscription(\n productId: ProductID = \"compute\",\n options?: CreateSubscriptionOptions,\n ): Promise<CreateSubscriptionResponse> {\n const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;\n const body = options\n ? {\n success_url: options.successUrl,\n cancel_url: options.cancelUrl,\n return_url: options.returnUrl,\n }\n : undefined;\n const resp = await this.makeAuthenticatedRequest(endpoint, \"POST\", productId, body);\n return resp.json();\n }\n\n async getSubscription(\n productId: ProductID = \"compute\",\n options?: GetSubscriptionOptions,\n ): Promise<ProductSubscriptionResponse> {\n let endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;\n if (options?.returnUrl) {\n const params = new URLSearchParams({ return_url: options.returnUrl });\n endpoint = `${endpoint}?${params.toString()}`;\n }\n const resp = await this.makeAuthenticatedRequest(endpoint, \"GET\", productId);\n return resp.json();\n }\n\n async cancelSubscription(productId: ProductID = \"compute\"): Promise<void> {\n const endpoint = `${this.config.billingApiServerURL}/products/${productId}/subscription`;\n await this.makeAuthenticatedRequest(endpoint, \"DELETE\", productId);\n }\n\n // ==========================================================================\n // Internal Methods\n // ==========================================================================\n\n /**\n * Make an authenticated request to the billing API\n *\n * Uses session auth if useSession is true, otherwise uses EIP-712 signature auth.\n */\n private async makeAuthenticatedRequest(\n url: string,\n method: \"GET\" | \"POST\" | \"DELETE\",\n productId: ProductID,\n body?: Record<string, unknown>,\n ): Promise<{ json: () => Promise<any>; text: () => Promise<string> }> {\n if (this.useSession) {\n return this.makeSessionAuthenticatedRequest(url, method, body);\n }\n return this.makeSignatureAuthenticatedRequest(url, method, productId, body);\n }\n\n /**\n * Make a request using session-based authentication (cookies)\n */\n private async makeSessionAuthenticatedRequest(\n url: string,\n method: \"GET\" | \"POST\" | \"DELETE\",\n body?: Record<string, unknown>,\n ): Promise<{ json: () => Promise<any>; text: () => Promise<string> }> {\n const headers: Record<string, string> = {};\n\n // Add content-type header if body is present\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n try {\n // Use fetch with credentials: 'include' for cookie-based auth\n const response = await fetch(url, {\n method,\n credentials: \"include\", // Include cookies for session management\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n const status = response.status;\n const statusText = status >= 200 && status < 300 ? \"OK\" : \"Error\";\n\n if (status < 200 || status >= 300) {\n let errorBody: string;\n try {\n errorBody = await response.text();\n } catch {\n errorBody = statusText;\n }\n throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${errorBody}`);\n }\n\n // Return Response-like object for compatibility\n const responseData = await response.json();\n return {\n json: async () => responseData,\n text: async () => JSON.stringify(responseData),\n };\n } catch (error: any) {\n // Handle network errors\n if (error.name === \"TypeError\" || error.message?.includes(\"fetch\")) {\n throw new Error(\n `Failed to connect to BillingAPI at ${url}: ${error.message}\\n` +\n `Please check:\\n` +\n `1. Your internet connection\\n` +\n `2. The API server is accessible: ${this.config.billingApiServerURL}\\n` +\n `3. Firewall/proxy settings`,\n );\n }\n // Re-throw other errors as-is\n throw error;\n }\n }\n\n /**\n * Make a request using EIP-712 signature authentication\n */\n private async makeSignatureAuthenticatedRequest(\n url: string,\n method: \"GET\" | \"POST\" | \"DELETE\",\n productId: ProductID,\n body?: Record<string, unknown>,\n ): Promise<{ json: () => Promise<any>; text: () => Promise<string> }> {\n if (!this.walletClient) {\n throw new Error(\"WalletClient is required for signature authentication\");\n }\n\n // Calculate expiry (5 minutes from now)\n const expiry = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);\n\n // Use EIP-712 typed data signature for billing auth\n const { signature } = await calculateBillingAuthSignature({\n walletClient: this.walletClient,\n product: productId,\n expiry,\n });\n\n // Prepare headers\n const headers: Record<string, string> = {\n Authorization: `Bearer ${signature}`,\n \"X-Account\": this.address!,\n \"X-Expiry\": expiry.toString(),\n };\n\n // Add content-type header if body is present\n if (body) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n try {\n // Use axios to make the request\n const response: AxiosResponse = await axios({\n method,\n url,\n headers,\n data: body,\n timeout: 30_000,\n maxRedirects: 0,\n validateStatus: () => true, // Don't throw on any status\n });\n\n const status = response.status;\n const statusText = status >= 200 && status < 300 ? \"OK\" : \"Error\";\n\n if (status < 200 || status >= 300) {\n const body =\n typeof response.data === \"string\" ? response.data : JSON.stringify(response.data);\n throw new Error(`BillingAPI request failed: ${status} ${statusText} - ${body}`);\n }\n\n // Return Response-like object for compatibility\n return {\n json: async () => response.data,\n text: async () =>\n typeof response.data === \"string\" ? response.data : JSON.stringify(response.data),\n };\n } catch (error: any) {\n // Handle network errors\n if (\n error.message?.includes(\"fetch failed\") ||\n error.message?.includes(\"ECONNREFUSED\") ||\n error.message?.includes(\"ENOTFOUND\") ||\n error.cause\n ) {\n const cause = error.cause?.message || error.cause || error.message;\n throw new Error(\n `Failed to connect to BillingAPI at ${url}: ${cause}\\n` +\n `Please check:\\n` +\n `1. Your internet connection\\n` +\n `2. The API server is accessible: ${this.config.billingApiServerURL}\\n` +\n `3. Firewall/proxy settings`,\n );\n }\n // Re-throw other errors as-is\n throw error;\n }\n }\n}\n","/**\n * Shared authentication utilities for API clients\n */\n\nimport { Hex, parseAbi, type Address, type PublicClient, type WalletClient } from \"viem\";\n\n// Minimal AppController ABI for permission calculation\nconst APP_CONTROLLER_ABI = parseAbi([\n \"function calculateApiPermissionDigestHash(bytes4 permission, uint256 expiry) view returns (bytes32)\",\n]);\n\nexport interface PermissionSignatureOptions {\n permission: Hex;\n expiry: bigint;\n appControllerAddress: Address;\n publicClient: PublicClient;\n walletClient: WalletClient;\n}\n\nexport interface PermissionSignatureResult {\n signature: string;\n digest: Hex;\n}\n\n/**\n * Calculate permission digest via AppController contract and sign it with EIP-191\n *\n * Works with any WalletClient - whether backed by a local private key or external signer.\n */\nexport async function calculatePermissionSignature(\n options: PermissionSignatureOptions,\n): Promise<PermissionSignatureResult> {\n const { permission, expiry, appControllerAddress, publicClient, walletClient } = options;\n\n // Calculate permission digest hash using AppController contract\n const digest = (await publicClient.readContract({\n address: appControllerAddress,\n abi: APP_CONTROLLER_ABI,\n functionName: \"calculateApiPermissionDigestHash\",\n args: [permission, expiry],\n })) as Hex;\n\n // Get account from wallet client\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n // Sign the digest using EIP-191 (signMessage handles prefixing automatically)\n const signature = await walletClient.signMessage({\n account,\n message: { raw: digest },\n });\n\n return { signature, digest };\n}\n\nexport interface BillingAuthSignatureOptions {\n walletClient: WalletClient;\n product: string;\n expiry: bigint;\n}\n\nexport interface BillingAuthSignatureResult {\n signature: Hex;\n expiry: bigint;\n}\n\nconst generateBillingSigData = (product: string, expiry: bigint) => {\n return {\n domain: {\n name: \"EigenCloud Billing API\",\n version: \"1\",\n },\n types: {\n BillingAuth: [\n { name: \"product\", type: \"string\" },\n { name: \"expiry\", type: \"uint256\" },\n ],\n },\n primaryType: \"BillingAuth\" as const,\n message: {\n product,\n expiry,\n },\n };\n};\n\n/**\n * Sign billing authentication message using EIP-712 typed data\n */\nexport async function calculateBillingAuthSignature(\n options: BillingAuthSignatureOptions,\n): Promise<BillingAuthSignatureResult> {\n const { walletClient, product, expiry } = options;\n\n // Get account from wallet client\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n // Sign using EIP-712 typed data\n const signature = await walletClient.signTypedData({\n account,\n ...generateBillingSigData(product, expiry),\n });\n\n return { signature, expiry };\n}\n\nexport interface BuildAuthSignatureOptions {\n walletClient: WalletClient;\n expiry: bigint;\n}\n\nexport interface BuildAuthSignatureResult {\n signature: Hex;\n expiry: bigint;\n}\n\n/**\n * Sign build authentication message using EIP-712 typed data\n */\nexport async function calculateBuildAuthSignature(\n options: BuildAuthSignatureOptions,\n): Promise<BuildAuthSignatureResult> {\n const { walletClient, expiry } = options;\n\n // Get account from wallet client\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n const signature = await walletClient.signTypedData({\n account,\n domain: {\n name: \"EigenCloud Build API\",\n version: \"1\",\n },\n types: {\n BuildAuth: [{ name: \"expiry\", type: \"uint256\" }],\n },\n primaryType: \"BuildAuth\",\n message: {\n expiry,\n },\n });\n\n return { signature, expiry };\n}\n","/**\n * Billing API Session Management\n *\n * This module provides utilities for managing authentication sessions with the billing API\n * using SIWE (Sign-In with Ethereum).\n *\n * The billing API now supports the same SIWE-based session authentication as the compute API,\n * allowing users to sign once and authenticate to both APIs simultaneously.\n */\n\nimport { Address, Hex } from \"viem\";\n\nexport interface BillingApiConfig {\n /** Base URL of the billing API (e.g., \"https://billing.eigencloud.xyz\") */\n baseUrl: string;\n}\n\nexport interface BillingSessionInfo {\n /** Whether the session is authenticated */\n authenticated: boolean;\n /** Authenticated wallet address (if authenticated) */\n address?: Address;\n /** Chain ID used for authentication (if authenticated) */\n chainId?: number;\n /** Unix timestamp when authentication occurred (if authenticated) */\n authenticatedAt?: number;\n}\n\nexport interface BillingLoginResult {\n /** Whether login was successful */\n success: boolean;\n /** Authenticated wallet address */\n address: Address;\n}\n\nexport interface BillingLoginRequest {\n /** SIWE message string */\n message: string;\n /** Hex-encoded signature (with or without 0x prefix) */\n signature: Hex | string;\n}\n\n/**\n * Error thrown when billing session operations fail\n */\nexport class BillingSessionError extends Error {\n constructor(\n message: string,\n public readonly code:\n | \"NETWORK_ERROR\"\n | \"INVALID_SIGNATURE\"\n | \"INVALID_MESSAGE\"\n | \"SESSION_EXPIRED\"\n | \"UNAUTHORIZED\"\n | \"UNKNOWN\",\n public readonly statusCode?: number,\n ) {\n super(message);\n this.name = \"BillingSessionError\";\n }\n}\n\n/**\n * Strip 0x prefix from hex string if present\n */\nfunction stripHexPrefix(hex: string): string {\n return hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n}\n\n/**\n * Parse error response body\n */\nasync function parseErrorResponse(response: Response): Promise<string> {\n try {\n const data = (await response.json()) as { error?: string };\n return data.error || response.statusText;\n } catch {\n return response.statusText;\n }\n}\n\n/**\n * Login to the billing API using SIWE\n *\n * This establishes a session with the billing API by verifying the SIWE message\n * and signature. On success, a session cookie is set in the browser.\n *\n * The billing API accepts the same SIWE message format as the compute API,\n * so users only need to sign once and can send the same message/signature\n * to both APIs.\n *\n * @param config - Billing API configuration\n * @param request - Login request containing SIWE message and signature\n * @returns Login result with the authenticated address\n *\n * @example\n * ```typescript\n * import { createSiweMessage, loginToBillingApi } from \"@layr-labs/ecloud-sdk/browser\";\n *\n * const { message } = createSiweMessage({\n * address: userAddress,\n * chainId: 11155111,\n * domain: window.location.host,\n * uri: window.location.origin,\n * });\n *\n * const signature = await signMessageAsync({ message });\n *\n * // Can send to both APIs with the same message/signature\n * const [computeResult, billingResult] = await Promise.all([\n * loginToComputeApi({ baseUrl: computeApiUrl }, { message, signature }),\n * loginToBillingApi({ baseUrl: billingApiUrl }, { message, signature }),\n * ]);\n * ```\n */\nexport async function loginToBillingApi(\n config: BillingApiConfig,\n request: BillingLoginRequest,\n): Promise<BillingLoginResult> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/siwe/login`, {\n method: \"POST\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n message: request.message,\n signature: stripHexPrefix(request.signature),\n }),\n });\n } catch (error) {\n throw new BillingSessionError(\n `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,\n \"NETWORK_ERROR\",\n );\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n const status = response.status;\n\n if (status === 400) {\n if (errorMessage.toLowerCase().includes(\"siwe\")) {\n throw new BillingSessionError(`Invalid SIWE message: ${errorMessage}`, \"INVALID_MESSAGE\", status);\n }\n throw new BillingSessionError(`Bad request: ${errorMessage}`, \"INVALID_MESSAGE\", status);\n }\n\n if (status === 401) {\n throw new BillingSessionError(`Invalid signature: ${errorMessage}`, \"INVALID_SIGNATURE\", status);\n }\n\n throw new BillingSessionError(`Login failed: ${errorMessage}`, \"UNKNOWN\", status);\n }\n\n const data = (await response.json()) as { success: boolean; address: string };\n\n return {\n success: data.success,\n address: data.address as Address,\n };\n}\n\n/**\n * Get the current session status from the billing API\n *\n * @param config - Billing API configuration\n * @returns Session information including authentication status and address\n *\n * @example\n * ```typescript\n * const session = await getBillingApiSession({ baseUrl: \"https://billing.eigencloud.xyz\" });\n * if (session.authenticated) {\n * console.log(`Logged in as ${session.address}`);\n * }\n * ```\n */\nexport async function getBillingApiSession(config: BillingApiConfig): Promise<BillingSessionInfo> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/session`, {\n method: \"GET\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n } catch {\n // Network error - return unauthenticated session\n return {\n authenticated: false,\n };\n }\n\n // If we get a 401, return unauthenticated session\n if (response.status === 401) {\n return {\n authenticated: false,\n };\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n throw new BillingSessionError(`Failed to get session: ${errorMessage}`, \"UNKNOWN\", response.status);\n }\n\n const data = (await response.json()) as {\n authenticated: boolean;\n address?: string;\n chainId?: number;\n authenticatedAt?: number;\n };\n\n return {\n authenticated: data.authenticated,\n address: data.address as Address | undefined,\n chainId: data.chainId,\n authenticatedAt: data.authenticatedAt,\n };\n}\n\n/**\n * Logout from the billing API\n *\n * This destroys the current session and clears the session cookie.\n *\n * @param config - Billing API configuration\n *\n * @example\n * ```typescript\n * await logoutFromBillingApi({ baseUrl: \"https://billing.eigencloud.xyz\" });\n * ```\n */\nexport async function logoutFromBillingApi(config: BillingApiConfig): Promise<void> {\n let response: Response;\n\n try {\n response = await fetch(`${config.baseUrl}/auth/logout`, {\n method: \"POST\",\n credentials: \"include\", // Include cookies for session management\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n } catch (error) {\n throw new BillingSessionError(\n `Network error connecting to ${config.baseUrl}: ${error instanceof Error ? error.message : String(error)}`,\n \"NETWORK_ERROR\",\n );\n }\n\n // Ignore 401 errors during logout (already logged out)\n if (response.status === 401) {\n return;\n }\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n throw new BillingSessionError(`Logout failed: ${errorMessage}`, \"UNKNOWN\", response.status);\n }\n}\n\n/**\n * Check if a billing session is still valid (not expired)\n *\n * This is a convenience function that checks the session status\n * and returns a boolean.\n *\n * @param config - Billing API configuration\n * @returns True if session is authenticated, false otherwise\n */\nexport async function isBillingSessionValid(config: BillingApiConfig): Promise<boolean> {\n const session = await getBillingApiSession(config);\n return session.authenticated;\n}\n\n/**\n * Login to both compute and billing APIs simultaneously\n *\n * This is a convenience function that sends the same SIWE message and signature\n * to both APIs in parallel, establishing sessions with both services at once.\n *\n * @param computeConfig - Compute API configuration\n * @param billingConfig - Billing API configuration\n * @param request - Login request containing SIWE message and signature\n * @returns Object containing login results for both APIs\n *\n * @example\n * ```typescript\n * import { createSiweMessage, loginToBothApis } from \"@layr-labs/ecloud-sdk/browser\";\n *\n * const { message } = createSiweMessage({\n * address: userAddress,\n * chainId: 11155111,\n * domain: window.location.host,\n * uri: window.location.origin,\n * });\n *\n * const signature = await signMessageAsync({ message });\n * const { compute, billing } = await loginToBothApis(\n * { baseUrl: computeApiUrl },\n * { baseUrl: billingApiUrl },\n * { message, signature }\n * );\n * ```\n */\nexport async function loginToBothApis(\n computeConfig: { baseUrl: string },\n billingConfig: BillingApiConfig,\n request: BillingLoginRequest,\n): Promise<{\n compute: BillingLoginResult;\n billing: BillingLoginResult;\n}> {\n // Import the compute login function dynamically to avoid circular dependencies\n const { loginToComputeApi } = await import(\"./session\");\n\n const [compute, billing] = await Promise.all([\n loginToComputeApi(computeConfig, request),\n loginToBillingApi(billingConfig, request),\n ]);\n\n return { compute, billing };\n}\n\n/**\n * Logout from both compute and billing APIs simultaneously\n *\n * @param computeConfig - Compute API configuration\n * @param billingConfig - Billing API configuration\n *\n * @example\n * ```typescript\n * await logoutFromBothApis(\n * { baseUrl: computeApiUrl },\n * { baseUrl: billingApiUrl }\n * );\n * ```\n */\nexport async function logoutFromBothApis(\n computeConfig: { baseUrl: string },\n billingConfig: BillingApiConfig,\n): Promise<void> {\n // Import the compute logout function dynamically to avoid circular dependencies\n const { logoutFromComputeApi } = await import(\"./session\");\n\n await Promise.all([\n logoutFromComputeApi(computeConfig),\n logoutFromBillingApi(billingConfig),\n ]);\n}\n","/**\n * Environment configuration for different networks\n */\n\nimport { Address } from \"viem\";\nimport { BillingEnvironmentConfig, EnvironmentConfig } from \"../types\";\n\n// Chain IDs\nexport const SEPOLIA_CHAIN_ID = 11155111;\nexport const MAINNET_CHAIN_ID = 1;\n\n// Common addresses across all chains\nexport const CommonAddresses: Record<string, string> = {\n ERC7702Delegator: \"0x63c0c19a282a1b52b07dd5a65b58948a07dae32b\",\n};\n\n// Addresses specific to each chain\nexport const ChainAddresses: Record<number, Record<string, string>> = {\n [MAINNET_CHAIN_ID]: {\n PermissionController: \"0x25E5F8B1E7aDf44518d35D5B2271f114e081f0E5\",\n },\n [SEPOLIA_CHAIN_ID]: {\n PermissionController: \"0x44632dfBdCb6D3E21EF613B0ca8A6A0c618F5a37\",\n },\n};\n\n// Billing environment configurations (separate from chain environments)\nconst BILLING_ENVIRONMENTS: Record<\"dev\" | \"prod\", BillingEnvironmentConfig> = {\n dev: {\n billingApiServerURL: \"https://billingapi-dev.eigencloud.xyz\",\n },\n prod: {\n billingApiServerURL: \"https://billingapi.eigencloud.xyz\",\n },\n};\n\n// Chain environment configurations\nconst ENVIRONMENTS: Record<string, Omit<EnvironmentConfig, \"chainID\">> = {\n \"sepolia-dev\": {\n name: \"sepolia\",\n build: \"dev\",\n appControllerAddress: \"0xa86DC1C47cb2518327fB4f9A1627F51966c83B92\",\n permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,\n erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,\n kmsServerURL: \"http://10.128.0.57:8080\",\n userApiServerURL: \"https://userapi-compute-sepolia-dev.eigencloud.xyz\",\n defaultRPCURL: \"https://ethereum-sepolia-rpc.publicnode.com\",\n usdcCreditsAddress: \"0xbdA3897c3A428763B59015C64AB766c288C97376\" as Address,\n },\n sepolia: {\n name: \"sepolia\",\n build: \"prod\",\n appControllerAddress: \"0x0dd810a6ffba6a9820a10d97b659f07d8d23d4E2\",\n permissionControllerAddress: ChainAddresses[SEPOLIA_CHAIN_ID].PermissionController,\n erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,\n kmsServerURL: \"http://10.128.15.203:8080\",\n userApiServerURL: \"https://userapi-compute-sepolia-prod.eigencloud.xyz\",\n defaultRPCURL: \"https://ethereum-sepolia-rpc.publicnode.com\",\n billingRPCURL: \"https://ethereum-rpc.publicnode.com\",\n },\n \"mainnet-alpha\": {\n name: \"mainnet-alpha\",\n build: \"prod\",\n appControllerAddress: \"0xc38d35Fc995e75342A21CBd6D770305b142Fbe67\",\n permissionControllerAddress: ChainAddresses[MAINNET_CHAIN_ID].PermissionController,\n erc7702DelegatorAddress: CommonAddresses.ERC7702Delegator,\n kmsServerURL: \"http://10.128.0.2:8080\",\n userApiServerURL: \"https://userapi-compute.eigencloud.xyz\",\n defaultRPCURL: \"https://ethereum-rpc.publicnode.com\",\n },\n};\n\nconst CHAIN_ID_TO_ENVIRONMENT: Record<string, string> = {\n [SEPOLIA_CHAIN_ID.toString()]: \"sepolia\",\n [MAINNET_CHAIN_ID.toString()]: \"mainnet-alpha\",\n};\n\n/**\n * Get environment configuration\n */\nexport function getEnvironmentConfig(environment: string, chainID?: bigint): EnvironmentConfig {\n const env = ENVIRONMENTS[environment];\n if (!env) {\n throw new Error(`Unknown environment: ${environment}`);\n }\n\n // Check if environment is available in current build\n if (!isEnvironmentAvailable(environment)) {\n throw new Error(\n `Environment ${environment} is not available in this build type. ` +\n `Available environments: ${getAvailableEnvironments().join(\", \")}`,\n );\n }\n\n // If chainID provided, validate it matches\n if (chainID) {\n const expectedEnv = CHAIN_ID_TO_ENVIRONMENT[chainID.toString()];\n if (expectedEnv && expectedEnv !== environment) {\n throw new Error(`Environment ${environment} does not match chain ID ${chainID}`);\n }\n }\n\n // Determine chain ID from environment if not provided\n // Both \"sepolia\" and \"sepolia-dev\" use Sepolia chain ID\n const resolvedChainID =\n chainID ||\n (environment === \"sepolia\" || environment === \"sepolia-dev\"\n ? SEPOLIA_CHAIN_ID\n : MAINNET_CHAIN_ID);\n\n return {\n ...env,\n chainID: BigInt(resolvedChainID),\n };\n}\n\n/**\n * Get billing environment configuration\n * @param build - The build type (\"dev\" or \"prod\")\n */\nexport function getBillingEnvironmentConfig(build: \"dev\" | \"prod\"): {\n billingApiServerURL: string;\n} {\n const config = BILLING_ENVIRONMENTS[build];\n if (!config) {\n throw new Error(`Unknown billing environment: ${build}`);\n }\n return config;\n}\n\n/**\n * Detect environment from chain ID\n */\nexport function detectEnvironmentFromChainID(chainID: bigint): string | undefined {\n return CHAIN_ID_TO_ENVIRONMENT[chainID.toString()];\n}\n\n/**\n * Get build type from environment variable or build-time constant (defaults to 'prod')\n * BUILD_TYPE_BUILD_TIME is replaced at build time by tsup's define option\n */\n// @ts-ignore - BUILD_TYPE_BUILD_TIME is injected at build time by tsup\ndeclare const BUILD_TYPE_BUILD_TIME: string | undefined;\n\nexport function getBuildType(): \"dev\" | \"prod\" {\n // First check build-time constant (set by tsup define)\n // @ts-ignore - BUILD_TYPE_BUILD_TIME is injected at build time\n const buildTimeType =\n typeof BUILD_TYPE_BUILD_TIME !== \"undefined\" ? BUILD_TYPE_BUILD_TIME?.toLowerCase() : undefined;\n\n // Fall back to runtime environment variable\n const runtimeType = process.env.BUILD_TYPE?.toLowerCase();\n\n const buildType = buildTimeType || runtimeType;\n\n if (buildType === \"dev\") {\n return \"dev\";\n }\n return \"prod\";\n}\n\n/**\n * Get available environments based on build type\n * - dev: only \"sepolia-dev\"\n * - prod: \"sepolia\" and \"mainnet-alpha\"\n */\nexport function getAvailableEnvironments(): string[] {\n const buildType = getBuildType();\n\n if (buildType === \"dev\") {\n return [\"sepolia-dev\"];\n }\n\n // prod build\n return [\"sepolia\", \"mainnet-alpha\"];\n}\n\n/**\n * Check if an environment is available in the current build\n */\nexport function isEnvironmentAvailable(environment: string): boolean {\n return getAvailableEnvironments().includes(environment);\n}\n\n/**\n * Check if environment is mainnet (chain ID 1)\n */\nexport function isMainnet(environmentConfig: EnvironmentConfig): boolean {\n return environmentConfig.chainID === BigInt(MAINNET_CHAIN_ID);\n}\n","/**\n * Default logger\n */\n\nimport { Logger } from \"../types\";\n\nexport const defaultLogger: Logger = {\n info: (...args) => console.info(...args),\n warn: (...args) => console.warn(...args),\n error: (...args) => console.error(...args),\n debug: (...args) => console.debug(...args),\n};\n\nexport const getLogger: (verbose?: boolean) => Logger = (verbose?: boolean) => ({\n info: (...args) => console.info(...args),\n warn: (...args) => console.warn(...args),\n error: (...args) => console.error(...args),\n debug: (...args) => verbose && console.debug(...args),\n});\n","import axios, { AxiosResponse } from \"axios\";\nimport { Address, Hex, type PublicClient, type WalletClient } from \"viem\";\nimport { calculatePermissionSignature } from \"./auth\";\nimport { EnvironmentConfig } from \"../types\";\nimport { stripHexPrefix } from \"./helpers\";\nimport { requestWithRetry } from \"./retry\";\nimport {\n loginToComputeApi,\n logoutFromComputeApi,\n getComputeApiSession,\n type LoginRequest,\n type LoginResult,\n type SessionInfo,\n} from \"../auth/session\";\n\nexport interface AppProfileInfo {\n name: string;\n website?: string;\n description?: string;\n xURL?: string;\n imageURL?: string;\n}\n\nexport interface AppMetrics {\n cpu_utilization_percent?: number;\n memory_utilization_percent?: number;\n memory_used_bytes?: number;\n memory_total_bytes?: number;\n}\n\nexport interface DerivedAddress {\n address: string;\n derivationPath: string;\n}\n\nexport interface AppInfo {\n address: Address;\n status: string;\n ip: string;\n machineType: string;\n profile?: AppProfileInfo;\n metrics?: AppMetrics;\n evmAddresses: DerivedAddress[];\n solanaAddresses: DerivedAddress[];\n}\n\nexport interface AppInfoResponse {\n apps: Array<{\n addresses: {\n data: {\n evmAddresses: DerivedAddress[];\n solanaAddresses: DerivedAddress[];\n };\n signature: string;\n };\n app_status: string;\n ip: string;\n machine_type: string;\n profile?: AppProfileInfo;\n metrics?: AppMetrics;\n }>;\n}\n\n// ==================== App Releases (/apps/:id) ====================\n\ntype JsonObject = Record<string, unknown>;\n\nfunction isJsonObject(value: unknown): value is JsonObject {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readString(obj: JsonObject, key: string): string | undefined {\n const v = obj[key];\n return typeof v === \"string\" ? v : undefined;\n}\n\nfunction readNumber(obj: JsonObject, key: string): number | undefined {\n const v = obj[key];\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n}\n\nexport type AppContractStatus = \"STARTED\" | \"STOPPED\" | \"TERMINATED\" | \"SUSPENDED\" | string;\n\nexport interface AppReleaseBuild {\n buildId?: string;\n billingAddress?: string;\n repoUrl?: string;\n gitRef?: string;\n status?: string;\n buildType?: string;\n imageName?: string;\n imageDigest?: string;\n imageUrl?: string;\n provenanceJson?: unknown;\n provenanceSignature?: string;\n createdAt?: string;\n updatedAt?: string;\n errorMessage?: string;\n dependencies?: Record<string, AppReleaseBuild>;\n}\n\nexport interface AppRelease {\n appId?: string;\n rmsReleaseId?: string;\n imageDigest?: string;\n registryUrl?: string;\n publicEnv?: string;\n encryptedEnv?: string;\n upgradeByTime?: number;\n createdAt?: string;\n createdAtBlock?: string;\n build?: AppReleaseBuild;\n}\n\nexport interface AppResponse {\n id: string;\n creator?: string;\n contractStatus?: AppContractStatus;\n releases: AppRelease[];\n}\n\nconst MAX_ADDRESS_COUNT = 5;\n\n// Permission constants\nexport const CanViewAppLogsPermission = \"0x2fd3f2fe\" as Hex;\nexport const CanViewSensitiveAppInfoPermission = \"0x0e67b22f\" as Hex;\nexport const CanUpdateAppProfilePermission = \"0x036fef61\" as Hex;\n\n/**\n * SDK_VERSION_BUILD_TIME is replaced at build time by tsup's define option\n */\n// @ts-ignore - SDK_VERSION_BUILD_TIME is injected at build time by tsup\ndeclare const SDK_VERSION_BUILD_TIME: string | undefined;\n\n/**\n * Get the default client ID using the build-time version\n */\nfunction getDefaultClientId(): string {\n // @ts-ignore - SDK_VERSION_BUILD_TIME is injected at build time\n const version = typeof SDK_VERSION_BUILD_TIME !== \"undefined\" ? SDK_VERSION_BUILD_TIME : \"0.0.0\";\n return `ecloud-sdk/v${version}`;\n}\n\n/**\n * Options for UserApiClient\n */\nexport interface UserApiClientOptions {\n /** Custom client ID for request tracking */\n clientId?: string;\n /**\n * Use SIWE session authentication instead of per-request signatures.\n * When true, requests rely on session cookies set by loginToComputeApi().\n * When false (default), each request is signed individually.\n */\n useSession?: boolean;\n}\n\n/**\n * UserAPI Client for interacting with the EigenCloud UserAPI service.\n */\nexport class UserApiClient {\n private readonly clientId: string;\n private readonly useSession: boolean;\n\n constructor(\n private readonly config: EnvironmentConfig,\n private readonly walletClient: WalletClient,\n private readonly publicClient: PublicClient,\n options?: UserApiClientOptions,\n ) {\n this.clientId = options?.clientId || getDefaultClientId();\n this.useSession = options?.useSession ?? false;\n }\n\n /**\n * Get the address of the connected wallet\n */\n get address(): Address {\n const account = this.walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n return account.address;\n }\n\n async getInfos(appIDs: Address[], addressCount = 1): Promise<AppInfo[]> {\n const count = Math.min(addressCount, MAX_ADDRESS_COUNT);\n\n const endpoint = `${this.config.userApiServerURL}/info`;\n const url = `${endpoint}?${new URLSearchParams({ apps: appIDs.join(\",\") })}`;\n\n const res = await this.makeAuthenticatedRequest(url, CanViewSensitiveAppInfoPermission);\n const result: AppInfoResponse = await res.json();\n\n // optional: verify signatures with KMS key\n // const { signingKey } = getKMSKeysForEnvironment(this.config.name);\n\n // Truncate without mutating the original object\n // API returns apps in the same order as the request, so use appIDs[i] as the address\n return result.apps.map((app, i) => {\n // TODO: Implement signature verification\n // const valid = await verifyKMSSignature(appInfo.addresses, signingKey);\n // if (!valid) {\n // throw new Error(`Invalid signature for app ${appIDs[i]}`);\n // }\n\n // Slice derived addresses to requested count\n const evmAddresses = app.addresses?.data?.evmAddresses?.slice(0, count) || [];\n const solanaAddresses = app.addresses?.data?.solanaAddresses?.slice(0, count) || [];\n\n return {\n address: appIDs[i] as Address,\n status: app.app_status,\n ip: app.ip,\n machineType: app.machine_type,\n profile: app.profile,\n metrics: app.metrics,\n evmAddresses,\n solanaAddresses,\n };\n });\n }\n\n /**\n * Get app details from UserAPI (includes releases and build/provenance info when available).\n *\n * Endpoint: GET /apps/:appAddress\n */\n async getApp(appAddress: Address): Promise<AppResponse> {\n const endpoint = `${this.config.userApiServerURL}/apps/${appAddress}`;\n const res = await this.makeAuthenticatedRequest(endpoint);\n const raw = (await res.json()) as unknown;\n\n if (!isJsonObject(raw)) {\n throw new Error(\"Unexpected /apps/:id response: expected object\");\n }\n\n const id = readString(raw, \"id\");\n if (!id) {\n throw new Error(\"Unexpected /apps/:id response: missing 'id'\");\n }\n\n const releasesRaw = raw.releases;\n const releases = Array.isArray(releasesRaw)\n ? releasesRaw.map((r) => transformAppRelease(r)).filter((r): r is AppRelease => !!r)\n : [];\n\n return {\n id,\n creator: readString(raw, \"creator\"),\n contractStatus: (readString(raw, \"contract_status\") ?? readString(raw, \"contractStatus\")) as\n | AppContractStatus\n | undefined,\n releases,\n };\n }\n\n /**\n * Get available SKUs (instance types) from UserAPI\n */\n async getSKUs(): Promise<{\n skus: Array<{\n sku: string;\n description: string;\n vcpus?: number;\n memory_mb?: number;\n monthly_price_usd?: number;\n hourly_price_usd?: number;\n platform?: string;\n }>;\n }> {\n const endpoint = `${this.config.userApiServerURL}/skus`;\n const response = await this.makeAuthenticatedRequest(endpoint);\n\n const result = await response.json();\n\n // Transform response to match expected format\n return {\n skus: result.skus || result.SKUs || [],\n };\n }\n\n /**\n * Get logs for an app\n */\n async getLogs(appID: Address): Promise<string> {\n const endpoint = `${this.config.userApiServerURL}/logs/${appID}`;\n const response = await this.makeAuthenticatedRequest(endpoint, CanViewAppLogsPermission);\n return await response.text();\n }\n\n /**\n * Get statuses for apps\n */\n async getStatuses(appIDs: Address[]): Promise<Array<{ address: Address; status: string }>> {\n const endpoint = `${this.config.userApiServerURL}/status`;\n const url = `${endpoint}?${new URLSearchParams({ apps: appIDs.join(\",\") })}`;\n const response = await this.makeAuthenticatedRequest(url);\n const result = await response.json();\n\n // Transform response to match expected format\n // The API returns an array of app statuses\n const apps = result.apps || result.Apps || [];\n return apps.map((app: any, i: number) => ({\n address: (app.address || appIDs[i]) as Address,\n status: app.app_status || app.App_Status || \"\",\n }));\n }\n\n /**\n * Upload app profile information with optional image\n *\n * @param appAddress - The app's contract address\n * @param name - Display name for the app\n * @param options - Optional fields including website, description, xURL, and image\n * @param options.image - Image file as Blob or File (browser: from input element, Node.js: new Blob([buffer]))\n * @param options.imageName - Filename for the image (required if image is provided)\n */\n async uploadAppProfile(\n appAddress: Address,\n name: string,\n options?: {\n website?: string;\n description?: string;\n xURL?: string;\n image?: Blob | File;\n imageName?: string;\n },\n ): Promise<{\n name: string;\n website?: string;\n description?: string;\n xURL?: string;\n imageURL?: string;\n }> {\n const endpoint = `${this.config.userApiServerURL}/apps/${appAddress}/profile`;\n\n // Build multipart form data using Web FormData API (works in browser and Node.js 18+)\n const formData = new FormData();\n\n // Add required name field\n formData.append(\"name\", name);\n\n // Add optional text fields\n if (options?.website) {\n formData.append(\"website\", options.website);\n }\n if (options?.description) {\n formData.append(\"description\", options.description);\n }\n if (options?.xURL) {\n formData.append(\"xURL\", options.xURL);\n }\n\n // Add optional image file (Blob or File)\n if (options?.image) {\n // If it's a File, use its name; otherwise require imageName\n const fileName =\n options.image instanceof File ? options.image.name : options.imageName || \"image\";\n formData.append(\"image\", options.image, fileName);\n }\n\n // Make authenticated POST request\n // Note: Don't set Content-Type header manually - axios will set it with the correct boundary\n const headers: Record<string, string> = {\n \"x-client-id\": this.clientId,\n };\n\n // Add auth headers if not using session auth\n if (!this.useSession) {\n const expiry = BigInt(Math.floor(Date.now() / 1000) + 5 * 60); // 5 minutes\n const authHeaders = await this.generateAuthHeaders(CanUpdateAppProfilePermission, expiry);\n Object.assign(headers, authHeaders);\n }\n\n try {\n const response: AxiosResponse = await axios.post(endpoint, formData, {\n headers,\n maxRedirects: 0,\n validateStatus: () => true, // Don't throw on any status\n maxContentLength: Infinity, // Allow large file uploads\n maxBodyLength: Infinity, // Allow large file uploads\n withCredentials: true, // Include cookies for session auth\n });\n\n const status = response.status;\n\n if (status !== 200 && status !== 201) {\n const body =\n typeof response.data === \"string\" ? response.data : JSON.stringify(response.data);\n\n // Detect Cloudflare challenge page\n if (status === 403 && body.includes(\"Cloudflare\") && body.includes(\"challenge-platform\")) {\n throw new Error(\n `Cloudflare protection is blocking the request. This is likely due to bot detection.\\n` +\n `Status: ${status}`,\n );\n }\n\n throw new Error(\n `UserAPI request failed: ${status} ${status >= 200 && status < 300 ? \"OK\" : \"Error\"} - ${body.substring(0, 500)}${body.length > 500 ? \"...\" : \"\"}`,\n );\n }\n\n return response.data;\n } catch (error: any) {\n if (\n error.message?.includes(\"fetch failed\") ||\n error.message?.includes(\"ECONNREFUSED\") ||\n error.message?.includes(\"ENOTFOUND\") ||\n error.cause\n ) {\n const cause = error.cause?.message || error.cause || error.message;\n throw new Error(\n `Failed to connect to UserAPI at ${endpoint}: ${cause}\\n` +\n `Please check:\\n` +\n `1. Your internet connection\\n` +\n `2. The API server is accessible: ${this.config.userApiServerURL}\\n` +\n `3. Firewall/proxy settings`,\n );\n }\n throw error;\n }\n }\n\n private async makeAuthenticatedRequest(\n url: string,\n permission?: Hex,\n ): Promise<{ json: () => Promise<any>; text: () => Promise<string> }> {\n const headers: Record<string, string> = {\n \"x-client-id\": this.clientId,\n };\n // Add auth headers if permission is specified and not using session auth\n if (permission && !this.useSession) {\n const expiry = BigInt(Math.floor(Date.now() / 1000) + 5 * 60); // 5 minutes\n const authHeaders = await this.generateAuthHeaders(permission, expiry);\n Object.assign(headers, authHeaders);\n }\n\n try {\n const response: AxiosResponse = await requestWithRetry({\n method: \"GET\",\n url,\n headers,\n maxRedirects: 0,\n withCredentials: true, // Include cookies for session auth\n });\n\n const status = response.status;\n const statusText = status >= 200 && status < 300 ? \"OK\" : \"Error\";\n\n if (status < 200 || status >= 300) {\n const body =\n typeof response.data === \"string\" ? response.data : JSON.stringify(response.data);\n throw new Error(`UserAPI request failed: ${status} ${statusText} - ${body}`);\n }\n\n // Return Response-like object for compatibility\n return {\n json: async () => response.data,\n text: async () =>\n typeof response.data === \"string\" ? response.data : JSON.stringify(response.data),\n };\n } catch (error: any) {\n // Handle network errors (fetch failed, connection refused, etc.)\n if (\n error.message?.includes(\"fetch failed\") ||\n error.message?.includes(\"ECONNREFUSED\") ||\n error.message?.includes(\"ENOTFOUND\") ||\n error.cause\n ) {\n const cause = error.cause?.message || error.cause || error.message;\n throw new Error(\n `Failed to connect to UserAPI at ${url}: ${cause}\\n` +\n `Please check:\\n` +\n `1. Your internet connection\\n` +\n `2. The API server is accessible: ${this.config.userApiServerURL}\\n` +\n `3. Firewall/proxy settings`,\n );\n }\n // Re-throw other errors as-is\n throw error;\n }\n }\n\n /**\n * Generate authentication headers for UserAPI requests\n */\n private async generateAuthHeaders(\n permission: Hex,\n expiry: bigint,\n ): Promise<Record<string, string>> {\n // Calculate permission signature using shared auth utility\n const { signature } = await calculatePermissionSignature({\n permission,\n expiry,\n appControllerAddress: this.config.appControllerAddress,\n publicClient: this.publicClient,\n walletClient: this.walletClient,\n });\n\n // Return auth headers\n return {\n Authorization: `Bearer ${stripHexPrefix(signature)}`,\n \"X-eigenx-expiry\": expiry.toString(),\n };\n }\n\n // ==========================================================================\n // SIWE Session Management\n // ==========================================================================\n\n /**\n * Login to the compute API using SIWE (Sign-In with Ethereum)\n *\n * This establishes a session with the compute API by verifying the SIWE message\n * and signature. On success, a session cookie is set in the browser.\n *\n * @param request - Login request containing SIWE message and signature\n * @returns Login result with the authenticated address\n *\n * @example\n * ```typescript\n * import { createSiweMessage } from \"@layr-labs/ecloud-sdk/browser\";\n *\n * const { message } = createSiweMessage({\n * address: userAddress,\n * chainId: 11155111,\n * domain: window.location.host,\n * uri: window.location.origin,\n * });\n *\n * const signature = await signMessageAsync({ message });\n * const result = await client.siweLogin({ message, signature });\n * ```\n */\n async siweLogin(request: LoginRequest): Promise<LoginResult> {\n return loginToComputeApi({ baseUrl: this.config.userApiServerURL }, request);\n }\n\n /**\n * Logout from the compute API\n *\n * This destroys the current session and clears the session cookie.\n *\n * @example\n * ```typescript\n * await client.siweLogout();\n * ```\n */\n async siweLogout(): Promise<void> {\n return logoutFromComputeApi({ baseUrl: this.config.userApiServerURL });\n }\n\n /**\n * Get the current SIWE session status from the compute API\n *\n * @returns Session information including authentication status and address\n *\n * @example\n * ```typescript\n * const session = await client.getSiweSession();\n * if (session.authenticated) {\n * console.log(`Logged in as ${session.address}`);\n * }\n * ```\n */\n async getSiweSession(): Promise<SessionInfo> {\n return getComputeApiSession({ baseUrl: this.config.userApiServerURL });\n }\n}\n\nfunction transformAppReleaseBuild(raw: unknown): AppReleaseBuild | undefined {\n if (!isJsonObject(raw)) return undefined;\n\n const depsRaw = raw.dependencies;\n const deps: Record<string, AppReleaseBuild> | undefined = isJsonObject(depsRaw)\n ? Object.fromEntries(\n Object.entries(depsRaw).flatMap(([digest, depRaw]) => {\n const parsed = transformAppReleaseBuild(depRaw);\n return parsed ? ([[digest, parsed]] as const) : [];\n }),\n )\n : undefined;\n\n return {\n buildId: readString(raw, \"build_id\") ?? readString(raw, \"buildId\"),\n billingAddress: readString(raw, \"billing_address\") ?? readString(raw, \"billingAddress\"),\n repoUrl: readString(raw, \"repo_url\") ?? readString(raw, \"repoUrl\"),\n gitRef: readString(raw, \"git_ref\") ?? readString(raw, \"gitRef\"),\n status: readString(raw, \"status\"),\n buildType: readString(raw, \"build_type\") ?? readString(raw, \"buildType\"),\n imageName: readString(raw, \"image_name\") ?? readString(raw, \"imageName\"),\n imageDigest: readString(raw, \"image_digest\") ?? readString(raw, \"imageDigest\"),\n imageUrl: readString(raw, \"image_url\") ?? readString(raw, \"imageUrl\"),\n provenanceJson: raw.provenance_json ?? raw.provenanceJson,\n provenanceSignature:\n readString(raw, \"provenance_signature\") ?? readString(raw, \"provenanceSignature\"),\n createdAt: readString(raw, \"created_at\") ?? readString(raw, \"createdAt\"),\n updatedAt: readString(raw, \"updated_at\") ?? readString(raw, \"updatedAt\"),\n errorMessage: readString(raw, \"error_message\") ?? readString(raw, \"errorMessage\"),\n dependencies: deps,\n };\n}\n\nfunction transformAppRelease(raw: unknown): AppRelease | undefined {\n if (!isJsonObject(raw)) return undefined;\n\n return {\n appId: readString(raw, \"appId\") ?? readString(raw, \"app_id\"),\n rmsReleaseId: readString(raw, \"rmsReleaseId\") ?? readString(raw, \"rms_release_id\"),\n imageDigest: readString(raw, \"imageDigest\") ?? readString(raw, \"image_digest\"),\n registryUrl: readString(raw, \"registryUrl\") ?? readString(raw, \"registry_url\"),\n publicEnv: readString(raw, \"publicEnv\") ?? readString(raw, \"public_env\"),\n encryptedEnv: readString(raw, \"encryptedEnv\") ?? readString(raw, \"encrypted_env\"),\n upgradeByTime: readNumber(raw, \"upgradeByTime\") ?? readNumber(raw, \"upgrade_by_time\"),\n createdAt: readString(raw, \"createdAt\") ?? readString(raw, \"created_at\"),\n createdAtBlock: readString(raw, \"createdAtBlock\") ?? readString(raw, \"created_at_block\"),\n build: raw.build ? transformAppReleaseBuild(raw.build) : undefined,\n };\n}\n","/**\n * General utility helpers\n */\n\nimport { extractChain, createPublicClient, createWalletClient, http, fallback } from \"viem\";\nimport type { Chain, Hex, PublicClient, WalletClient } from \"viem\";\nimport { sepolia } from \"viem/chains\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { SUPPORTED_CHAINS } from \"../constants\";\n\n/**\n * Get a viem Chain object from a chain ID.\n * Supports mainnet (1) and sepolia (11155111), defaults to the fallback chain for unknown chains.\n */\nexport function getChainFromID(chainID: bigint, fallback: Chain = sepolia): Chain {\n const id = Number(chainID) as (typeof SUPPORTED_CHAINS)[number][\"id\"];\n return extractChain({ chains: SUPPORTED_CHAINS, id }) || fallback;\n}\n\n/**\n * Create viem clients from a private key\n *\n * This is a convenience helper for CLI and server applications that have direct\n * access to a private key. For browser applications using external wallets (MetaMask, etc.),\n * create the WalletClient directly using viem's createWalletClient with a custom transport.\n *\n * @example\n * // CLI usage with private key\n * const { walletClient, publicClient } = createClients({\n * privateKey: '0x...',\n * rpcUrl: 'https://sepolia.infura.io/v3/...',\n * chainId: 11155111n\n * });\n *\n * @example\n * // Browser usage with external wallet (create clients directly)\n * const walletClient = createWalletClient({\n * chain: sepolia,\n * transport: custom(window.ethereum!)\n * });\n * const publicClient = createPublicClient({\n * chain: sepolia,\n * transport: custom(window.ethereum!)\n * });\n */\nexport function createClients(options: {\n privateKey: string | Hex;\n rpcUrl: string | string[];\n chainId: bigint;\n}): {\n walletClient: WalletClient;\n publicClient: PublicClient;\n} {\n const { privateKey, rpcUrl, chainId } = options;\n\n const privateKeyHex = addHexPrefix(privateKey);\n const account = privateKeyToAccount(privateKeyHex);\n const chain = getChainFromID(chainId);\n\n const transport =\n typeof rpcUrl === \"string\" ? http(rpcUrl) : fallback(rpcUrl.map((url) => http(url)));\n\n const publicClient = createPublicClient({\n chain,\n transport,\n });\n\n const walletClient = createWalletClient({\n account,\n chain,\n transport,\n });\n\n return { walletClient, publicClient };\n}\n\n/**\n * Ensure hex string has 0x prefix\n */\nexport function addHexPrefix(value: string): Hex {\n return (value.startsWith(\"0x\") ? value : `0x${value}`) as Hex;\n}\n\n/**\n * Remove 0x prefix from hex string if present\n */\nexport function stripHexPrefix(value: string): string {\n return value.startsWith(\"0x\") ? value.slice(2) : value;\n}\n","/**\n * Constants used throughout the SDK\n */\n\nimport { sepolia, mainnet } from \"viem/chains\";\n\nexport const SUPPORTED_CHAINS = [mainnet, sepolia] as const;\n\nexport const DOCKER_PLATFORM = \"linux/amd64\";\nexport const REGISTRY_PROPAGATION_WAIT_SECONDS = 3;\nexport const LAYERED_DOCKERFILE_NAME = \"Dockerfile.eigencompute\";\nexport const ENV_SOURCE_SCRIPT_NAME = \"compute-source-env.sh\";\nexport const KMS_CLIENT_BINARY_NAME = \"kms-client\";\nexport const KMS_ENCRYPTION_KEY_NAME = \"kms-encryption-public-key.pem\";\nexport const KMS_SIGNING_KEY_NAME = \"kms-signing-public-key.pem\";\nexport const TLS_KEYGEN_BINARY_NAME = \"tls-keygen\";\nexport const CADDYFILE_NAME = \"Caddyfile\";\nexport const TEMP_IMAGE_PREFIX = \"ecloud-temp-\";\nexport const LAYERED_BUILD_DIR_PREFIX = \"ecloud-layered-build\";\nexport const SHA256_PREFIX = \"sha256:\";\nexport const JWT_FILE_PATH = \"/run/container_launcher/attestation_verifier_claims_token\";\n","import axios, { AxiosRequestConfig, AxiosResponse } from \"axios\";\n\nconst MAX_RETRIES = 5;\nconst INITIAL_BACKOFF_MS = 1000;\nconst MAX_BACKOFF_MS = 30000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction getRetryDelay(res: AxiosResponse, attempt: number): number {\n const backoff = Math.min(INITIAL_BACKOFF_MS * Math.pow(2, attempt), MAX_BACKOFF_MS);\n const retryAfter = res.headers[\"retry-after\"];\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10);\n if (!isNaN(seconds) && seconds > 0) {\n return Math.min(seconds * 1000, MAX_BACKOFF_MS);\n }\n }\n return backoff;\n}\n\nexport async function requestWithRetry(config: AxiosRequestConfig): Promise<AxiosResponse> {\n let lastResponse: AxiosResponse | undefined;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n const res = await axios({ ...config, validateStatus: () => true });\n lastResponse = res;\n\n if (res.status !== 429) {\n return res;\n }\n\n if (attempt < MAX_RETRIES) {\n const delay = getRetryDelay(res, attempt);\n await sleep(delay);\n }\n }\n\n return lastResponse!;\n}\n","/**\n * Billing utility functions\n */\n\nimport type { SubscriptionStatus } from \"../types\";\n\n/**\n * Check if subscription status allows deploying apps\n */\nexport function isSubscriptionActive(status: SubscriptionStatus): boolean {\n return status === \"active\" || status === \"trialing\";\n}\n","/**\n * No-op telemetry client implementation\n */\n\nimport { TelemetryClient, Metric } from \"./types\";\n\n/**\n * NoopClient implements the TelemetryClient interface with no-op methods\n */\nexport class NoopClient implements TelemetryClient {\n /**\n * AddMetric implements the TelemetryClient interface\n */\n async addMetric(_metric: Metric): Promise<void> {\n // No-op\n }\n\n /**\n * Close implements the TelemetryClient interface\n */\n async close(): Promise<void> {\n // No-op\n }\n}\n\n/**\n * Check if a client is a NoopClient\n */\nexport function isNoopClient(client: TelemetryClient): boolean {\n return client instanceof NoopClient;\n}\n","/**\n * PostHog telemetry client implementation\n *\n * Uses the official posthog-node library\n */\n\nimport { PostHog } from \"posthog-node\";\nimport { TelemetryClient, Metric, AppEnvironment } from \"./types\";\n\n/**\n * PostHogClient implements the TelemetryClient interface using posthog-node\n */\nexport class PostHogClient implements TelemetryClient {\n private readonly client: PostHog;\n private readonly namespace: string;\n private readonly appEnvironment: AppEnvironment;\n\n constructor(environment: AppEnvironment, namespace: string, apiKey: string, endpoint?: string) {\n this.namespace = namespace;\n this.appEnvironment = environment;\n\n // Initialize PostHog client\n // posthog-node expects the full URL for the host option\n const host = endpoint || \"https://us.i.posthog.com\";\n\n this.client = new PostHog(apiKey, {\n host: host,\n flushAt: 1, // Flush immediately for CLI/SDK usage\n flushInterval: 0, // Disable interval flushing\n });\n\n // Identify the user with their UUID\n this.client.identify({\n distinctId: environment.userUUID,\n properties: {\n os: environment.os,\n arch: environment.arch,\n ...(environment.cliVersion ? { cliVersion: environment.cliVersion } : {}),\n },\n });\n }\n\n /**\n * AddMetric implements the TelemetryClient interface\n */\n async addMetric(metric: Metric): Promise<void> {\n // Never throw errors from telemetry operations\n try {\n // Create properties map starting with base properties\n const props: Record<string, any> = {\n name: metric.name,\n value: metric.value,\n };\n\n // Add metric dimensions\n for (const [k, v] of Object.entries(metric.dimensions)) {\n props[k] = v;\n }\n\n // Capture event using the namespace as the event name\n // With flushAt: 1, events are automatically flushed after each capture\n this.client.capture({\n distinctId: this.appEnvironment.userUUID,\n event: this.namespace,\n properties: props,\n });\n } catch {\n // Silently ignore telemetry errors\n }\n }\n\n /**\n * Close implements the TelemetryClient interface\n */\n async close(): Promise<void> {\n try {\n // Shutdown PostHog client and flush any pending events\n // shutdown() is synchronous but internally handles async cleanup\n this.client.shutdown();\n } catch {\n // Silently ignore errors during shutdown\n }\n }\n}\n\n/**\n * Embedded PostHog API key (can be exposed in TypeScript)\n * This can be set at build time or overridden via environment variable\n */\n// @ts-ignore - POSTHOG_API_KEY_BUILD_TIME is injected at build time by tsup\ndeclare const POSTHOG_API_KEY_BUILD_TIME: string | undefined;\n\n/**\n * Get PostHog API key from environment variable or build-time constant\n */\nexport function getPostHogAPIKey(): string | undefined {\n // Priority order:\n // 1. Environment variable\n // 2. Build-time constant (set at build time)\n // Check environment variable first\n if (process.env.ECLOUD_POSTHOG_KEY) {\n return process.env.ECLOUD_POSTHOG_KEY;\n }\n\n // Return build-time constant if available\n // @ts-ignore - POSTHOG_API_KEY_BUILD_TIME is injected at build time\n return typeof POSTHOG_API_KEY_BUILD_TIME !== \"undefined\" ? POSTHOG_API_KEY_BUILD_TIME : undefined;\n}\n\n/**\n * Get PostHog endpoint from environment variable or default\n */\nexport function getPostHogEndpoint(): string {\n return process.env.ECLOUD_POSTHOG_ENDPOINT || \"https://us.i.posthog.com\";\n}\n","/**\n * Telemetry module for ECloud SDK and CLI\n *\n * Provides telemetry functionality matching the Go implementation.\n * Supports both \"ecloud-cli\" and \"ecloud-sdk\" namespaces.\n */\n\nimport { TelemetryClient, Metric, AppEnvironment } from \"./types\";\nimport { NoopClient, isNoopClient } from \"./noop\";\nimport { PostHogClient, getPostHogAPIKey, getPostHogEndpoint } from \"./posthog\";\nimport * as os from \"os\";\n\nexport * from \"./types\";\nexport * from \"./metricsContext\";\nexport * from \"./noop\";\nexport * from \"./posthog\";\nexport * from \"./wrapper\";\n\n/**\n * Options for creating a telemetry client\n */\nexport interface TelemetryClientOptions {\n /**\n * Whether telemetry is enabled (only enabled if explicitly set to true, defaults to disabled)\n */\n telemetryEnabled?: boolean;\n /**\n * PostHog API key (if not provided, will check environment variables)\n */\n apiKey?: string;\n /**\n * PostHog endpoint (if not provided, will use default)\n */\n endpoint?: string;\n}\n\n/**\n * Create a telemetry client\n *\n * @param environment - Application environment information (must include userUUID)\n * @param namespace - Namespace for telemetry events (\"ecloud-cli\" or \"ecloud-sdk\")\n * @param options - Optional telemetry client options\n * @returns TelemetryClient instance (NoopClient if telemetry is disabled or no API key)\n */\nexport function createTelemetryClient(\n environment: AppEnvironment,\n namespace: \"ecloud-cli\" | \"ecloud-sdk\",\n options?: TelemetryClientOptions,\n): TelemetryClient {\n // Check if telemetry is enabled\n // Only enabled if explicitly set to true\n // If undefined or false, telemetry is disabled\n const telemetryEnabled = options?.telemetryEnabled === true;\n\n // If telemetry is disabled, return noop client\n if (!telemetryEnabled) {\n return new NoopClient();\n }\n\n // Get API key from options, environment variable, or return noop\n const resolvedApiKey = options?.apiKey || getPostHogAPIKey();\n if (!resolvedApiKey) {\n // No API key available, return noop client\n return new NoopClient();\n }\n\n // Get endpoint from options or environment variable\n const endpoint = options?.endpoint || getPostHogEndpoint();\n\n try {\n return new PostHogClient(environment, namespace, resolvedApiKey, endpoint);\n } catch {\n // If initialization fails, return noop client\n return new NoopClient();\n }\n}\n\n/**\n * Create an AppEnvironment from current system information\n *\n * @param userUUID - User UUID for identification (required - no I/O in SDK)\n * @param cliVersion - Optional CLI version (for CLI usage)\n * @param osOverride - Optional OS override (defaults to current platform)\n * @param archOverride - Optional architecture override (defaults to current architecture)\n * @returns AppEnvironment with user UUID, OS, and architecture\n */\nexport function createAppEnvironment(\n userUUID: string,\n cliVersion?: string,\n osOverride?: string,\n archOverride?: string,\n): AppEnvironment {\n return {\n userUUID,\n cliVersion,\n os: osOverride || os.platform(),\n arch: archOverride || os.arch(),\n };\n}\n\n/**\n * Emit metrics from a metrics context\n *\n * @param client - Telemetry client to use\n * @param context - Metrics context containing metrics to emit\n * @returns Promise that resolves when all metrics are emitted\n */\nexport async function emitMetrics(\n client: TelemetryClient,\n context: {\n metrics: Metric[];\n properties: Record<string, string>;\n },\n): Promise<void> {\n if (isNoopClient(client)) {\n return;\n }\n\n // Emit each metric with properties merged into dimensions\n for (const metric of context.metrics) {\n const dimensions = {\n ...metric.dimensions,\n ...context.properties,\n };\n\n const metricWithProperties: Metric = {\n ...metric,\n dimensions,\n };\n\n try {\n await client.addMetric(metricWithProperties);\n } catch {\n // Silently ignore telemetry errors\n }\n }\n}\n","/**\n * MetricsContext management\n */\n\nimport { MetricsContext } from \"./types\";\n\n/**\n * Create a new metrics context\n */\nexport function createMetricsContext(): MetricsContext {\n return {\n startTime: new Date(),\n metrics: [],\n properties: {},\n };\n}\n\n/**\n * Add a metric to the context without dimensions\n */\nexport function addMetric(context: MetricsContext, name: string, value: number): void {\n addMetricWithDimensions(context, name, value, {});\n}\n\n/**\n * Add a metric to the context with dimensions\n */\nexport function addMetricWithDimensions(\n context: MetricsContext,\n name: string,\n value: number,\n dimensions: Record<string, string>,\n): void {\n context.metrics.push({\n name,\n value,\n dimensions,\n });\n}\n","/**\n * Telemetry wrapper utilities for SDK functions\n *\n * Provides helpers to wrap SDK function execution with telemetry tracking\n */\n\nimport {\n createTelemetryClient,\n createAppEnvironment,\n createMetricsContext,\n addMetric,\n addMetricWithDimensions,\n emitMetrics,\n} from \"./index\";\nimport { randomUUID } from \"crypto\";\n\n/**\n * Generate a random UUID for telemetry identification\n * Used when userUUID is not provided (SDK usage outside CLI)\n */\nfunction generateRandomUUID(): string {\n return randomUUID();\n}\n\n/**\n * Options for telemetry wrapper\n */\nexport interface TelemetryWrapperOptions {\n /**\n * Function name for telemetry (e.g., \"deploy\", \"upgrade\", \"createApp\")\n */\n functionName: string;\n /**\n * Skip telemetry if true (used when called from CLI)\n */\n skipTelemetry?: boolean;\n /**\n * Additional properties to include in telemetry\n */\n properties?: Record<string, string>;\n /**\n * User UUID for identification (required if skipTelemetry is false)\n * If not provided and telemetry is enabled, will generate a random UUID for this session\n */\n userUUID?: string;\n /**\n * Whether telemetry is enabled (defaults to true if not provided)\n */\n telemetryEnabled?: boolean;\n /**\n * PostHog API key (optional, will check environment variables if not provided)\n */\n apiKey?: string;\n /**\n * PostHog endpoint (optional, will use default if not provided)\n */\n endpoint?: string;\n}\n\n/**\n * Wrap a function execution with telemetry\n *\n * @param options - Telemetry wrapper options\n * @param action - The function to execute\n * @returns The result of the action\n */\nexport async function withSDKTelemetry<T>(\n options: TelemetryWrapperOptions,\n action: () => Promise<T>,\n): Promise<T> {\n // Skip telemetry if requested (e.g., when called from CLI)\n if (options.skipTelemetry) {\n return action();\n }\n\n // Generate a random UUID if not provided (for SDK usage outside CLI)\n // This ensures each SDK session has a unique identifier\n const userUUID = options.userUUID || generateRandomUUID();\n\n const environment = createAppEnvironment(userUUID);\n const client = createTelemetryClient(environment, \"ecloud-sdk\", {\n telemetryEnabled: options.telemetryEnabled,\n apiKey: options.apiKey,\n endpoint: options.endpoint,\n });\n const metrics = createMetricsContext();\n\n // Set source to identify SDK usage\n metrics.properties[\"source\"] = \"ecloud-sdk\";\n\n // Set function name in properties\n metrics.properties[\"function\"] = options.functionName;\n\n // Add any additional properties\n if (options.properties) {\n Object.assign(metrics.properties, options.properties);\n }\n\n // Add initial count metric\n addMetric(metrics, \"Count\", 1);\n\n let actionError: Error | undefined;\n let result: T;\n\n try {\n result = await action();\n return result;\n } catch (err) {\n actionError = err instanceof Error ? err : new Error(String(err));\n throw err;\n } finally {\n // Add result metric\n const resultValue = actionError ? \"Failure\" : \"Success\";\n const dimensions: Record<string, string> = {};\n if (actionError) {\n dimensions[\"error\"] = actionError.message;\n }\n addMetricWithDimensions(metrics, resultValue, 1, dimensions);\n\n // Add duration metric\n const duration = Date.now() - metrics.startTime.getTime();\n addMetric(metrics, \"DurationMilliseconds\", duration);\n\n // Emit all metrics\n try {\n await emitMetrics(client, metrics);\n await client.close();\n } catch {\n // Silently ignore telemetry errors\n }\n }\n}\n","/**\n * EIP-7702 transaction handling\n *\n * This module handles EIP-7702 delegation and batch execution.\n */\n\nimport { Address, Hex, encodeFunctionData, encodeAbiParameters, decodeErrorResult } from \"viem\";\n\nimport type {\n WalletClient,\n PublicClient,\n SendTransactionParameters,\n SignAuthorizationReturnType,\n} from \"viem\";\nimport { EnvironmentConfig, Logger, noopLogger } from \"../types\";\n\nimport ERC7702DelegatorABI from \"../abis/ERC7702Delegator.json\";\n\nimport { GasEstimate, formatETH } from \"./caller\";\n\n// Mode 0x01 is executeBatchMode (32 bytes, padded, big endian)\nconst EXECUTE_BATCH_MODE =\n \"0x0100000000000000000000000000000000000000000000000000000000000000\" as Hex;\n\nconst GAS_LIMIT_BUFFER_PERCENTAGE = 20n; // 20%\nconst GAS_PRICE_BUFFER_PERCENTAGE = 100n; // 100%\n\nexport type Execution = {\n target: Address;\n value: bigint;\n callData: Hex;\n};\n\n/**\n * Encode executions array and pack into execute function call data\n */\nfunction encodeExecuteBatchData(executions: Execution[]): Hex {\n const encodedExecutions = encodeAbiParameters(\n [\n {\n type: \"tuple[]\",\n components: [\n { name: \"target\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"callData\", type: \"bytes\" },\n ],\n },\n ],\n [executions],\n );\n\n return encodeFunctionData({\n abi: ERC7702DelegatorABI,\n functionName: \"execute\",\n args: [EXECUTE_BATCH_MODE, encodedExecutions],\n });\n}\n\n/**\n * Options for estimating batch gas\n */\nexport interface EstimateBatchGasOptions {\n publicClient: PublicClient;\n account: Address;\n executions: Execution[];\n authorizationList?: SignAuthorizationReturnType[];\n}\n\n/**\n * Estimate gas cost for a batch transaction\n *\n * Use this to get cost estimate before prompting user for confirmation.\n */\nexport async function estimateBatchGas(options: EstimateBatchGasOptions): Promise<GasEstimate> {\n const { publicClient, account, executions, authorizationList } = options;\n\n const executeBatchData = encodeExecuteBatchData(executions);\n\n // EIP-7702 transactions send to self (the EOA with delegated code)\n const [gasTipCap, block, estimatedGas] = await Promise.all([\n publicClient.estimateMaxPriorityFeePerGas(),\n publicClient.getBlock(),\n publicClient.estimateGas({\n account,\n to: account,\n data: executeBatchData,\n authorizationList,\n }),\n ]);\n\n const baseFee = block.baseFeePerGas ?? 0n;\n\n // Calculate gas price with 100% buffer: (baseFee + gasTipCap) * 2\n const maxFeePerGas = ((baseFee + gasTipCap) * (100n + GAS_PRICE_BUFFER_PERCENTAGE)) / 100n;\n\n // Add 20% buffer to gas limit\n const gasLimit = (estimatedGas * (100n + GAS_LIMIT_BUFFER_PERCENTAGE)) / 100n;\n\n const maxCostWei = gasLimit * maxFeePerGas;\n\n return {\n gasLimit,\n maxFeePerGas,\n maxPriorityFeePerGas: gasTipCap,\n maxCostWei,\n maxCostEth: formatETH(maxCostWei),\n };\n}\n\nexport interface ExecuteBatchOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n executions: Execution[];\n pendingMessage: string;\n /** Optional gas params from estimation */\n gas?: GasEstimate;\n /** Optional pre-created authorization list (skips internal creation if provided) */\n authorizationList?: SignAuthorizationReturnType[];\n}\n\n/**\n * Check if account is delegated to ERC-7702 delegator\n */\nexport async function checkERC7702Delegation(\n publicClient: PublicClient,\n account: Address,\n delegatorAddress: Address,\n): Promise<boolean> {\n const code = await publicClient.getCode({ address: account });\n if (!code) {\n return false;\n }\n\n // Check if code matches EIP-7702 delegation pattern: 0xef0100 || delegator_address\n const expectedCode = `0xef0100${delegatorAddress.slice(2)}`;\n return code.toLowerCase() === expectedCode.toLowerCase();\n}\n\n/**\n * Options for creating an authorization list\n */\nexport interface CreateAuthorizationListOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n}\n\n/**\n * Create an authorization list for EIP-7702 delegation if needed\n *\n * Returns undefined if the account is already delegated.\n * Use this to create the auth list before gas estimation to get accurate estimates.\n */\nexport async function createAuthorizationList(\n options: CreateAuthorizationListOptions,\n): Promise<SignAuthorizationReturnType[] | undefined> {\n const { walletClient, publicClient, environmentConfig } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"Wallet client must have an account\");\n }\n\n // Check if already delegated\n const isDelegated = await checkERC7702Delegation(\n publicClient,\n account.address,\n environmentConfig.erc7702DelegatorAddress as Address,\n );\n\n if (isDelegated) {\n return undefined;\n }\n\n // Create authorization\n const transactionNonce = await publicClient.getTransactionCount({\n address: account.address,\n blockTag: \"pending\",\n });\n\n const chainId = await publicClient.getChainId();\n const authorizationNonce = transactionNonce + 1;\n\n const signedAuthorization = await walletClient.signAuthorization({\n account,\n contractAddress: environmentConfig.erc7702DelegatorAddress as Address,\n chainId: chainId,\n nonce: Number(authorizationNonce),\n });\n\n return [signedAuthorization];\n}\n\nconst TRANSIENT_PATTERNS = [\n \"indexing is in progress\",\n \"is not in the chain\",\n] as const;\n\nfunction isTransientReceiptError(error: unknown): boolean {\n const msg = error instanceof Error ? error.message : String(error);\n return TRANSIENT_PATTERNS.some((p) => msg.includes(p));\n}\n\nasync function waitForReceipt(\n publicClient: PublicClient,\n hash: Hex,\n logger: Logger,\n { maxRetries = 5, baseDelayMs = 2_000 } = {},\n) {\n for (let attempt = 0; ; attempt++) {\n try {\n return await publicClient.waitForTransactionReceipt({ hash });\n } catch (error) {\n if (!isTransientReceiptError(error) || attempt >= maxRetries) throw error;\n\n const delay = baseDelayMs * 2 ** attempt;\n logger.debug(\n `Receipt not available yet (attempt ${attempt + 1}/${maxRetries}), retrying in ${delay}ms...`,\n );\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n}\n\n/**\n * Execute batch of operations via EIP-7702 delegator\n */\nexport async function executeBatch(options: ExecuteBatchOptions, logger: Logger = noopLogger): Promise<Hex> {\n const {\n walletClient,\n publicClient,\n environmentConfig,\n executions,\n pendingMessage,\n gas,\n authorizationList: providedAuthList,\n } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"Wallet client must have an account\");\n }\n\n const chain = walletClient.chain;\n if (!chain) {\n throw new Error(\"Wallet client must have a chain\");\n }\n\n const executeBatchData = encodeExecuteBatchData(executions);\n\n // Use provided authorization list or create one if needed\n let authorizationList: Array<SignAuthorizationReturnType> = providedAuthList || [];\n if (authorizationList.length === 0) {\n // Check if account is delegated\n const isDelegated = await checkERC7702Delegation(\n publicClient,\n account.address,\n environmentConfig.erc7702DelegatorAddress as Address,\n );\n\n // Create authorization if needed\n if (!isDelegated) {\n const transactionNonce = await publicClient.getTransactionCount({\n address: account.address,\n blockTag: \"pending\",\n });\n\n const chainId = await publicClient.getChainId();\n const authorizationNonce = transactionNonce + 1;\n\n logger.debug(\"Using wallet client signing for EIP-7702 authorization\");\n\n const signedAuthorization = await walletClient.signAuthorization({\n account,\n contractAddress: environmentConfig.erc7702DelegatorAddress as Address,\n chainId: chainId,\n nonce: Number(authorizationNonce),\n });\n\n authorizationList = [signedAuthorization];\n }\n }\n\n // 5. Show pending message\n if (pendingMessage) {\n logger.info(pendingMessage);\n }\n\n const txRequest: SendTransactionParameters = {\n account: walletClient.account!,\n chain,\n to: account.address,\n data: executeBatchData,\n value: 0n,\n };\n\n if (authorizationList.length > 0) {\n txRequest.authorizationList = authorizationList;\n }\n\n // Add gas params if provided\n if (gas?.gasLimit) {\n txRequest.gas = gas.gasLimit;\n }\n if (gas?.maxFeePerGas) {\n txRequest.maxFeePerGas = gas.maxFeePerGas;\n }\n if (gas?.maxPriorityFeePerGas) {\n txRequest.maxPriorityFeePerGas = gas.maxPriorityFeePerGas;\n }\n\n const hash = await walletClient.sendTransaction(txRequest);\n logger.info(`Transaction sent: ${hash}`);\n\n const receipt = await waitForReceipt(publicClient, hash, logger);\n\n if (receipt.status === \"reverted\") {\n let revertReason = \"Unknown reason\";\n try {\n await publicClient.call({\n to: account.address,\n data: executeBatchData,\n account: account.address,\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (callError: any) {\n if (callError.data) {\n try {\n const decoded = decodeErrorResult({\n abi: ERC7702DelegatorABI,\n data: callError.data,\n });\n revertReason = `${decoded.errorName}: ${JSON.stringify(decoded.args)}`;\n } catch {\n revertReason = callError.message || \"Unknown reason\";\n }\n } else {\n revertReason = callError.message || \"Unknown reason\";\n }\n }\n throw new Error(`Transaction reverted: ${hash}. Reason: ${revertReason}`);\n }\n\n return hash;\n}\n","/**\n * Core types for ECloud SDK\n */\n\nimport { Address, Hex, SignAuthorizationReturnType } from \"viem\";\nimport { GasEstimate } from \"../contract/caller\";\n\nexport type AppId = Address;\n\nexport type logVisibility = \"public\" | \"private\" | \"off\";\n\nexport interface DeployAppOpts {\n /** App name - required */\n name: string;\n /** Path to Dockerfile (if building from Dockerfile) - either this or imageRef is required */\n dockerfile?: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Image reference (registry/path:tag) - either this or dockerfile is required */\n imageRef?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n /** Billing mode: developer (default) or app (isolated billing) */\n billTo?: \"developer\" | \"app\";\n /** Optional gas params from estimation */\n gas?: GasEstimate;\n}\n\nexport interface UpgradeAppOpts {\n /** Path to Dockerfile (if building from Dockerfile) - either this or imageRef is required */\n dockerfile?: string;\n /** Image reference (registry/path:tag) - either this or dockerfile is required */\n imageRef?: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n gas?: GasEstimate;\n}\n\n/** Options for prepareDeploy */\nexport interface PrepareDeployOpts {\n /** App name - required */\n name: string;\n /** Path to Dockerfile (if building from Dockerfile) */\n dockerfile?: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Image reference (registry/path:tag) */\n imageRef?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n /** Resource usage monitoring setting - optional */\n resourceUsageMonitoring?: \"enable\" | \"disable\";\n /** Billing mode: developer (default) or app (isolated billing) */\n billTo?: \"developer\" | \"app\";\n /** Skip quota check */\n skipQuotaCheck?: boolean;\n /** Optional salt for deterministic app address prediction (32 bytes) */\n salt?: Uint8Array | Buffer;\n}\n\n/** Options for prepareUpgrade */\nexport interface PrepareUpgradeOpts {\n /** Path to Dockerfile (if building from Dockerfile) */\n dockerfile?: string;\n /** Image reference (registry/path:tag) */\n imageRef?: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n /** Resource usage monitoring setting - optional */\n resourceUsageMonitoring?: \"enable\" | \"disable\";\n}\n\n/** Options for prepareDeployFromVerifiableBuild */\nexport interface PrepareDeployFromVerifiableBuildOpts {\n /** App name - required */\n name: string;\n /** Image reference (registry/path:tag) - required */\n imageRef: string;\n /** Image digest (sha256:...) - required */\n imageDigest: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n /** Resource usage monitoring setting - optional */\n resourceUsageMonitoring?: \"enable\" | \"disable\";\n /** Billing mode: developer (default) or app (isolated billing) */\n billTo?: \"developer\" | \"app\";\n}\n\n/** Options for prepareUpgradeFromVerifiableBuild */\nexport interface PrepareUpgradeFromVerifiableBuildOpts {\n /** Image reference (registry/path:tag) - required */\n imageRef: string;\n /** Image digest (sha256:...) - required */\n imageDigest: string;\n /** Path to .env file - optional */\n envFile?: string;\n /** Instance type SKU - required */\n instanceType: string;\n /** Log visibility setting - required */\n logVisibility: logVisibility;\n /** Resource usage monitoring setting - optional */\n resourceUsageMonitoring?: \"enable\" | \"disable\";\n}\n\n/** Gas options for execute functions */\nexport interface GasOpts {\n maxFeePerGas?: bigint;\n maxPriorityFeePerGas?: bigint;\n}\n\n/** Result from executeDeploy */\nexport interface ExecuteDeployResult {\n appId: AppId;\n txHash: Hex;\n appName: string;\n imageRef: string;\n}\n\n/** Result from executeUpgrade */\nexport interface ExecuteUpgradeResult {\n appId: AppId;\n txHash: Hex;\n imageRef: string;\n}\n\n/** Data-only batch for deploy (clients provided by module) */\nexport interface PreparedDeployData {\n /** The app ID that will be deployed */\n appId: AppId;\n /** The salt used for deployment */\n salt: Uint8Array;\n /** Batch executions to be sent */\n executions: Array<{ target: Address; value: bigint; callData: Hex }>;\n /** Pre-created authorization list for gas estimation accuracy (optional) */\n authorizationList?: SignAuthorizationReturnType[];\n}\n\n/** Data-only batch for upgrade (clients provided by module) */\nexport interface PreparedUpgradeData {\n /** The app ID being upgraded */\n appId: AppId;\n /** Batch executions to be sent */\n executions: Array<{ target: Address; value: bigint; callData: Hex }>;\n /** Pre-created authorization list for gas estimation accuracy (optional) */\n authorizationList?: SignAuthorizationReturnType[];\n}\n\n/** Prepared deployment ready for execution */\nexport interface PreparedDeploy {\n /** The prepared data (executions, appId, etc.) */\n data: PreparedDeployData;\n /** App name */\n appName: string;\n /** Final image reference */\n imageRef: string;\n}\n\n/** Prepared upgrade ready for execution */\nexport interface PreparedUpgrade {\n /** The prepared data (executions, appId, etc.) */\n data: PreparedUpgradeData;\n /** App ID being upgraded */\n appId: AppId;\n /** Final image reference */\n imageRef: string;\n}\n\nexport interface LifecycleOpts {\n gas?: GasEstimate;\n}\n\nexport interface AppRecord {\n id: AppId;\n owner: Address;\n image: string;\n status: \"starting\" | \"running\" | \"stopped\" | \"terminated\";\n createdAt: number; // epoch ms\n lastUpdatedAt: number; // epoch ms\n}\n\nexport interface DeployOptions {\n /** Private key for signing transactions (hex string with or without 0x prefix) - optional, will prompt if not provided */\n privateKey?: string;\n /** RPC URL for blockchain connection - optional, uses environment default if not provided */\n rpcUrl?: string;\n /** Environment name (e.g., 'sepolia', 'mainnet-alpha') - optional, defaults to 'sepolia' */\n environment?: string;\n /** Path to Dockerfile (if building from Dockerfile) */\n dockerfilePath?: string;\n /** Image reference (registry/path:tag) - optional, will prompt if not provided */\n imageRef?: string;\n /** Path to .env file - optional, will use .env if exists or prompt */\n envFilePath?: string;\n /** App name - optional, will prompt if not provided */\n appName?: string;\n /** Instance type - optional, will prompt if not provided */\n instanceType?: string;\n /** Log visibility setting - optional, will prompt if not provided */\n logVisibility?: logVisibility;\n}\n\nexport interface DeployResult {\n /** App ID (contract address) */\n appId: AppId;\n /** App name */\n appName: string;\n /** Final image reference */\n imageRef: string;\n /** IP address (if available) */\n ipAddress?: string;\n /** Transaction hash */\n txHash: Hex;\n}\n\nexport interface BillingEnvironmentConfig {\n billingApiServerURL: string;\n}\n\nexport interface EnvironmentConfig {\n name: string;\n build: \"dev\" | \"prod\";\n chainID: bigint;\n appControllerAddress: Address;\n permissionControllerAddress: string;\n erc7702DelegatorAddress: string;\n kmsServerURL: string;\n userApiServerURL: string;\n defaultRPCURL: string;\n billingRPCURL?: string;\n usdcCreditsAddress?: Address;\n}\n\nexport interface Release {\n rmsRelease: {\n artifacts: Array<{\n digest: Uint8Array; // 32 bytes\n registry: string;\n }>;\n upgradeByTime: number; // Unix timestamp\n };\n publicEnv: Uint8Array; // JSON bytes\n encryptedEnv: Uint8Array; // Encrypted string bytes\n}\n\nexport interface ParsedEnvironment {\n public: Record<string, string>;\n private: Record<string, string>;\n}\n\nexport interface ImageDigestResult {\n digest: Uint8Array; // 32 bytes\n registry: string;\n platform: string;\n}\n\nexport interface DockerImageConfig {\n cmd: string[];\n entrypoint: string[];\n user: string;\n labels: Record<string, string>;\n}\n\nexport interface Logger {\n debug(message: string, ...args: any[]): void;\n info(message: string, ...args: any[]): void;\n warn(message: string, ...args: any[]): void;\n error(message: string, ...args: any[]): void;\n}\n\n/**\n * No-op logger for browser usage when logging is not needed\n */\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n\n/**\n * Profile information for an app\n */\nexport interface AppProfile {\n /** App name (required) */\n name: string;\n /** Website URL (optional) */\n website?: string;\n /** Description (optional) */\n description?: string;\n /** X (Twitter) URL (optional) */\n xURL?: string;\n /** Path to image file (optional) */\n image?: Blob | File;\n /** Image name (optional) */\n imageName?: string;\n}\n\n/**\n * Profile response from API\n */\nexport interface AppProfileResponse {\n name: string;\n website?: string;\n description?: string;\n xURL?: string;\n imageURL?: string;\n}\n\n// Billing types\nexport type ProductID = \"compute\";\nexport type ChainID = \"ethereum-mainnet\" | \"ethereum-sepolia\";\n\nexport type SubscriptionStatus =\n | \"incomplete\"\n | \"incomplete_expired\"\n | \"trialing\"\n | \"active\"\n | \"past_due\"\n | \"canceled\"\n | \"unpaid\"\n | \"paused\"\n | \"inactive\";\n\nexport interface SubscriptionLineItem {\n description: string;\n price: number;\n quantity: number;\n currency: string;\n subtotal: number;\n}\n\nexport interface CreateSubscriptionOptions {\n /** URL to redirect to after successful checkout */\n successUrl?: string;\n /** URL to redirect to if checkout is canceled */\n cancelUrl?: string;\n /** URL to return if return link is clicked from stripe portal */\n returnUrl?: string;\n}\n\nexport interface GetSubscriptionOptions {\n /** URL to return to from the billing portal */\n returnUrl?: string;\n}\n\nexport interface CreateSubscriptionResponse {\n checkoutUrl: string;\n}\n\nexport interface CheckoutCreatedResponse {\n type: \"checkout_created\";\n checkoutUrl: string;\n}\n\nexport interface AlreadyActiveResponse {\n type: \"already_active\";\n status: SubscriptionStatus;\n}\n\nexport interface PaymentIssueResponse {\n type: \"payment_issue\";\n status: SubscriptionStatus;\n portalUrl?: string;\n}\n\nexport type SubscribeResponse =\n | CheckoutCreatedResponse\n | AlreadyActiveResponse\n | PaymentIssueResponse;\n\nexport interface CancelSuccessResponse {\n type: \"canceled\";\n}\n\nexport interface NoActiveSubscriptionResponse {\n type: \"no_active_subscription\";\n status: SubscriptionStatus;\n}\n\nexport type CancelResponse = CancelSuccessResponse | NoActiveSubscriptionResponse;\n\nexport interface ProductSubscriptionResponse {\n productId: ProductID;\n subscriptionStatus: SubscriptionStatus;\n currentPeriodStart?: string;\n currentPeriodEnd?: string;\n lineItems?: SubscriptionLineItem[];\n upcomingInvoiceSubtotal?: number;\n upcomingInvoiceTotal?: number;\n creditsApplied?: number;\n remainingCredits?: number;\n nextCreditExpiry?: number;\n cancelAtPeriodEnd?: boolean;\n canceledAt?: string;\n portalUrl?: string;\n}\n\nexport interface SubscriptionOpts {\n productId?: ProductID;\n /** URL to redirect to after successful checkout */\n successUrl?: string;\n /** URL to redirect to if checkout is canceled */\n cancelUrl?: string;\n}\n\n// Billing environment configuration\nexport interface BillingEnvironmentConfig {\n billingApiServerURL: string;\n}\n\n/**\n * Progress callback for sequential deployment\n * Called after each step completes\n */\nexport type DeployProgressCallback = (step: DeployStep, txHash?: Hex) => void;\n\n/**\n * Steps in sequential deployment flow\n */\nexport type DeployStep = \"createApp\" | \"acceptAdmin\" | \"setPublicLogs\" | \"complete\";\n\n/**\n * Result from sequential deployment\n */\nexport interface SequentialDeployResult {\n appId: AppId;\n txHashes: {\n createApp: Hex;\n acceptAdmin: Hex;\n setPublicLogs?: Hex;\n };\n}\n","[\n {\n \"type\": \"constructor\",\n \"inputs\": [\n {\n \"name\": \"_delegationManager\",\n \"type\": \"address\",\n \"internalType\": \"contractIDelegationManager\"\n },\n {\n \"name\": \"_entryPoint\",\n \"type\": \"address\",\n \"internalType\": \"contractIEntryPoint\"\n }\n ],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"receive\",\n \"stateMutability\": \"payable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"DOMAIN_VERSION\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"NAME\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"PACKED_USER_OP_TYPEHASH\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"VERSION\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"addDeposit\",\n \"inputs\": [],\n \"outputs\": [],\n \"stateMutability\": \"payable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"delegationManager\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"contractIDelegationManager\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"disableDelegation\",\n \"inputs\": [\n {\n \"name\": \"_delegation\",\n \"type\": \"tuple\",\n \"internalType\": \"structDelegation\",\n \"components\": [\n {\n \"name\": \"delegate\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"delegator\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"authority\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"caveats\",\n \"type\": \"tuple[]\",\n \"internalType\": \"structCaveat[]\",\n \"components\": [\n {\n \"name\": \"enforcer\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"terms\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"args\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n },\n {\n \"name\": \"salt\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"eip712Domain\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"fields\",\n \"type\": \"bytes1\",\n \"internalType\": \"bytes1\"\n },\n {\n \"name\": \"name\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n },\n {\n \"name\": \"version\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n },\n {\n \"name\": \"chainId\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"verifyingContract\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"salt\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"extensions\",\n \"type\": \"uint256[]\",\n \"internalType\": \"uint256[]\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"enableDelegation\",\n \"inputs\": [\n {\n \"name\": \"_delegation\",\n \"type\": \"tuple\",\n \"internalType\": \"structDelegation\",\n \"components\": [\n {\n \"name\": \"delegate\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"delegator\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"authority\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"caveats\",\n \"type\": \"tuple[]\",\n \"internalType\": \"structCaveat[]\",\n \"components\": [\n {\n \"name\": \"enforcer\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"terms\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"args\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n },\n {\n \"name\": \"salt\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"entryPoint\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"contractIEntryPoint\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"execute\",\n \"inputs\": [\n {\n \"name\": \"_execution\",\n \"type\": \"tuple\",\n \"internalType\": \"structExecution\",\n \"components\": [\n {\n \"name\": \"target\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"value\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"callData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"payable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"execute\",\n \"inputs\": [\n {\n \"name\": \"_mode\",\n \"type\": \"bytes32\",\n \"internalType\": \"ModeCode\"\n },\n {\n \"name\": \"_executionCalldata\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"payable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"executeFromExecutor\",\n \"inputs\": [\n {\n \"name\": \"_mode\",\n \"type\": \"bytes32\",\n \"internalType\": \"ModeCode\"\n },\n {\n \"name\": \"_executionCalldata\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"returnData_\",\n \"type\": \"bytes[]\",\n \"internalType\": \"bytes[]\"\n }\n ],\n \"stateMutability\": \"payable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getDeposit\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getDomainHash\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getNonce\",\n \"inputs\": [\n {\n \"name\": \"_key\",\n \"type\": \"uint192\",\n \"internalType\": \"uint192\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getNonce\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getPackedUserOperationHash\",\n \"inputs\": [\n {\n \"name\": \"_userOp\",\n \"type\": \"tuple\",\n \"internalType\": \"structPackedUserOperation\",\n \"components\": [\n {\n \"name\": \"sender\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"nonce\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"initCode\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"callData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"accountGasLimits\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"preVerificationGas\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"gasFees\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"paymasterAndData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getPackedUserOperationTypedDataHash\",\n \"inputs\": [\n {\n \"name\": \"_userOp\",\n \"type\": \"tuple\",\n \"internalType\": \"structPackedUserOperation\",\n \"components\": [\n {\n \"name\": \"sender\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"nonce\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"initCode\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"callData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"accountGasLimits\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"preVerificationGas\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"gasFees\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"paymasterAndData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"isDelegationDisabled\",\n \"inputs\": [\n {\n \"name\": \"_delegationHash\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bool\",\n \"internalType\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"isValidSignature\",\n \"inputs\": [\n {\n \"name\": \"_hash\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"_signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"magicValue_\",\n \"type\": \"bytes4\",\n \"internalType\": \"bytes4\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"onERC1155BatchReceived\",\n \"inputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"uint256[]\",\n \"internalType\": \"uint256[]\"\n },\n {\n \"name\": \"\",\n \"type\": \"uint256[]\",\n \"internalType\": \"uint256[]\"\n },\n {\n \"name\": \"\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes4\",\n \"internalType\": \"bytes4\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"onERC1155Received\",\n \"inputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes4\",\n \"internalType\": \"bytes4\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"onERC721Received\",\n \"inputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes4\",\n \"internalType\": \"bytes4\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"redeemDelegations\",\n \"inputs\": [\n {\n \"name\": \"_permissionContexts\",\n \"type\": \"bytes[]\",\n \"internalType\": \"bytes[]\"\n },\n {\n \"name\": \"_modes\",\n \"type\": \"bytes32[]\",\n \"internalType\": \"ModeCode[]\"\n },\n {\n \"name\": \"_executionCallDatas\",\n \"type\": \"bytes[]\",\n \"internalType\": \"bytes[]\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"supportsExecutionMode\",\n \"inputs\": [\n {\n \"name\": \"_mode\",\n \"type\": \"bytes32\",\n \"internalType\": \"ModeCode\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bool\",\n \"internalType\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"supportsInterface\",\n \"inputs\": [\n {\n \"name\": \"_interfaceId\",\n \"type\": \"bytes4\",\n \"internalType\": \"bytes4\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bool\",\n \"internalType\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"validateUserOp\",\n \"inputs\": [\n {\n \"name\": \"_userOp\",\n \"type\": \"tuple\",\n \"internalType\": \"structPackedUserOperation\",\n \"components\": [\n {\n \"name\": \"sender\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"nonce\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"initCode\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"callData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"accountGasLimits\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"preVerificationGas\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"gasFees\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"paymasterAndData\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ]\n },\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"_missingAccountFunds\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"validationData_\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"withdrawDeposit\",\n \"inputs\": [\n {\n \"name\": \"_withdrawAddress\",\n \"type\": \"address\",\n \"internalType\": \"addresspayable\"\n },\n {\n \"name\": \"_withdrawAmount\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"event\",\n \"name\": \"EIP712DomainChanged\",\n \"inputs\": [],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"SentPrefund\",\n \"inputs\": [\n {\n \"name\": \"sender\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"amount\",\n \"type\": \"uint256\",\n \"indexed\": false,\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"success\",\n \"type\": \"bool\",\n \"indexed\": false,\n \"internalType\": \"bool\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"SetDelegationManager\",\n \"inputs\": [\n {\n \"name\": \"newDelegationManager\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"contractIDelegationManager\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"SetEntryPoint\",\n \"inputs\": [\n {\n \"name\": \"entryPoint\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"contractIEntryPoint\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"TryExecuteUnsuccessful\",\n \"inputs\": [\n {\n \"name\": \"batchExecutionindex\",\n \"type\": \"uint256\",\n \"indexed\": false,\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"result\",\n \"type\": \"bytes\",\n \"indexed\": false,\n \"internalType\": \"bytes\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"error\",\n \"name\": \"ECDSAInvalidSignature\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"ECDSAInvalidSignatureLength\",\n \"inputs\": [\n {\n \"name\": \"length\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ]\n },\n {\n \"type\": \"error\",\n \"name\": \"ECDSAInvalidSignatureS\",\n \"inputs\": [\n {\n \"name\": \"s\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ]\n },\n {\n \"type\": \"error\",\n \"name\": \"ExecutionFailed\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"InvalidEIP712NameLength\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"InvalidEIP712VersionLength\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"InvalidShortString\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"NotDelegationManager\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"NotEntryPoint\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"NotEntryPointOrSelf\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"NotSelf\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"StringTooLong\",\n \"inputs\": [\n {\n \"name\": \"str\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n }\n ]\n },\n {\n \"type\": \"error\",\n \"name\": \"UnauthorizedCallContext\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"UnsupportedCallType\",\n \"inputs\": [\n {\n \"name\": \"callType\",\n \"type\": \"bytes1\",\n \"internalType\": \"CallType\"\n }\n ]\n },\n {\n \"type\": \"error\",\n \"name\": \"UnsupportedExecType\",\n \"inputs\": [\n {\n \"name\": \"execType\",\n \"type\": \"bytes1\",\n \"internalType\": \"ExecType\"\n }\n ]\n }\n]\n","/**\n * Contract interactions\n *\n * This module handles on-chain contract interactions using viem.\n *\n * Accepts viem's WalletClient and PublicClient directly, which abstract over both\n * local accounts (privateKeyToAccount) and external signers (MetaMask, etc.).\n *\n * @example\n * // CLI usage with private key\n * const { walletClient, publicClient } = createClients({ privateKey, rpcUrl, chainId });\n * await deployApp({ walletClient, publicClient, environmentConfig, ... }, logger);\n *\n * @example\n * // Browser usage with external wallet\n * const walletClient = createWalletClient({ chain, transport: custom(window.ethereum!) });\n * const publicClient = createPublicClient({ chain, transport: custom(window.ethereum!) });\n * await deployApp({ walletClient, publicClient, environmentConfig, ... }, logger);\n */\n\nimport { executeBatch, checkERC7702Delegation } from \"./eip7702\";\nimport { Address, Hex, encodeFunctionData, decodeErrorResult, bytesToHex } from \"viem\";\nimport type { WalletClient, PublicClient } from \"viem\";\n\nimport {\n EnvironmentConfig,\n Logger,\n PreparedDeployData,\n PreparedUpgradeData,\n noopLogger,\n DeployProgressCallback,\n SequentialDeployResult,\n} from \"../types\";\nimport { Release } from \"../types\";\nimport { getChainFromID } from \"../utils/helpers\";\n\nimport AppControllerABI from \"../abis/AppController.json\";\nimport PermissionControllerABI from \"../abis/PermissionController.json\";\n\n/**\n * Gas estimation result\n */\nexport interface GasEstimate {\n /** Estimated gas limit for the transaction */\n gasLimit: bigint;\n /** Max fee per gas (EIP-1559) */\n maxFeePerGas: bigint;\n /** Max priority fee per gas (EIP-1559) */\n maxPriorityFeePerGas: bigint;\n /** Maximum cost in wei (gasLimit * maxFeePerGas) */\n maxCostWei: bigint;\n /** Maximum cost formatted as ETH string */\n maxCostEth: string;\n}\n\n/**\n * Options for estimating transaction gas\n */\nexport interface EstimateGasOptions {\n publicClient: PublicClient;\n from: Address;\n to: Address;\n data: Hex;\n value?: bigint;\n}\n\n/**\n * Format Wei to ETH string\n */\nexport function formatETH(wei: bigint): string {\n const eth = Number(wei) / 1e18;\n const costStr = eth.toFixed(6);\n // Remove trailing zeros and decimal point if needed\n const trimmed = costStr.replace(/\\.?0+$/, \"\");\n // If result is \"0\", show \"<0.000001\" for small amounts\n if (trimmed === \"0\" && wei > 0n) {\n return \"<0.000001\";\n }\n return trimmed;\n}\n\n/**\n * Estimate gas cost for a transaction\n *\n * Use this to get cost estimate before prompting user for confirmation.\n */\nexport async function estimateTransactionGas(options: EstimateGasOptions): Promise<GasEstimate> {\n const { publicClient, from, to, data, value = 0n } = options;\n\n // Get current gas prices\n const fees = await publicClient.estimateFeesPerGas();\n\n // Estimate gas for the transaction\n const gasLimit = await publicClient.estimateGas({\n account: from,\n to,\n data,\n value,\n });\n\n const maxFeePerGas = fees.maxFeePerGas;\n const maxPriorityFeePerGas = fees.maxPriorityFeePerGas;\n const maxCostWei = gasLimit * maxFeePerGas;\n const maxCostEth = formatETH(maxCostWei);\n\n return {\n gasLimit,\n maxFeePerGas,\n maxPriorityFeePerGas,\n maxCostWei,\n maxCostEth,\n };\n}\n\n/**\n * Deploy app options\n */\nexport interface DeployAppOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n salt: Uint8Array;\n release: Release;\n publicLogs: boolean;\n imageRef: string;\n gas?: GasEstimate;\n}\n\n/**\n * Options for calculateAppID\n */\nexport interface CalculateAppIDOptions {\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n ownerAddress: Address;\n salt: Uint8Array;\n}\n\n/**\n * Prepared deploy batch ready for gas estimation and execution\n */\nexport interface PreparedDeployBatch {\n /** The app ID that will be deployed */\n appId: Address;\n /** The salt used for deployment */\n salt: Uint8Array;\n /** Batch executions to be sent */\n executions: Array<{ target: Address; value: bigint; callData: Hex }>;\n /** Wallet client for sending transaction */\n walletClient: WalletClient;\n /** Public client for reading chain state */\n publicClient: PublicClient;\n /** Environment configuration */\n environmentConfig: EnvironmentConfig;\n}\n\n/**\n * Prepared upgrade batch ready for gas estimation and execution\n */\nexport interface PreparedUpgradeBatch {\n /** The app ID being upgraded */\n appId: Address;\n /** Batch executions to be sent */\n executions: Array<{ target: Address; value: bigint; callData: Hex }>;\n /** Wallet client for sending transaction */\n walletClient: WalletClient;\n /** Public client for reading chain state */\n publicClient: PublicClient;\n /** Environment configuration */\n environmentConfig: EnvironmentConfig;\n}\n\n/**\n * Calculate app ID from owner address and salt\n */\nexport async function calculateAppID(options: CalculateAppIDOptions): Promise<Address> {\n const { publicClient, environmentConfig, ownerAddress, salt } = options;\n\n // Ensure salt is properly formatted as hex string (32 bytes = 64 hex chars)\n // bytesToHex returns 0x-prefixed string, slice(2) removes the prefix for padding\n const saltHexString = bytesToHex(salt).slice(2);\n // Pad to 64 characters if needed\n const paddedSaltHex = saltHexString.padStart(64, \"0\");\n const saltHex = `0x${paddedSaltHex}` as Hex;\n\n const appID = await publicClient.readContract({\n address: environmentConfig.appControllerAddress as Address,\n abi: AppControllerABI,\n functionName: \"calculateAppId\",\n args: [ownerAddress, saltHex],\n });\n\n return appID as Address;\n}\n\n/**\n * Options for preparing a deploy batch\n */\nexport interface PrepareDeployBatchOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n salt: Uint8Array;\n release: Release;\n publicLogs: boolean;\n imageRef: string;\n billTo?: \"developer\" | \"app\";\n}\n\n/**\n * Prepare deploy batch - creates executions without sending transaction\n *\n * Use this to get the prepared batch for gas estimation before executing.\n */\nexport async function prepareDeployBatch(\n options: PrepareDeployBatchOptions,\n logger: Logger = noopLogger,\n): Promise<PreparedDeployBatch> {\n const { walletClient, publicClient, environmentConfig, salt, release, publicLogs } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n // 1. Calculate app ID\n logger.info(\"Calculating app ID...\");\n const appId = await calculateAppID({\n publicClient,\n environmentConfig,\n ownerAddress: account.address,\n salt,\n });\n\n // Verify the app ID calculation matches what createApp will deploy\n logger.debug(`App ID calculated: ${appId}`);\n logger.debug(`This address will be used for acceptAdmin call`);\n\n // 2. Pack create app call\n const saltHexString = bytesToHex(salt).slice(2);\n const paddedSaltHex = saltHexString.padStart(64, \"0\");\n const saltHex = `0x${paddedSaltHex}` as Hex;\n\n // Convert Release Uint8Array values to hex strings for viem\n const releaseForViem = {\n rmsRelease: {\n artifacts: release.rmsRelease.artifacts.map((artifact) => ({\n digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, \"0\")}` as Hex,\n registry: artifact.registry,\n })),\n upgradeByTime: release.rmsRelease.upgradeByTime,\n },\n publicEnv: bytesToHex(release.publicEnv) as Hex,\n encryptedEnv: bytesToHex(release.encryptedEnv) as Hex,\n };\n\n const functionName = options.billTo === \"app\" ? \"createAppWithIsolatedBilling\" : \"createApp\";\n const createData = encodeFunctionData({\n abi: AppControllerABI,\n functionName,\n args: [saltHex, releaseForViem],\n });\n\n // 3. Pack accept admin call\n const acceptAdminData = encodeFunctionData({\n abi: PermissionControllerABI,\n functionName: \"acceptAdmin\",\n args: [appId],\n });\n\n // 4. Assemble executions\n // CRITICAL: Order matters! createApp must complete first\n const executions: Array<{\n target: Address;\n value: bigint;\n callData: Hex;\n }> = [\n {\n target: environmentConfig.appControllerAddress,\n value: 0n,\n callData: createData,\n },\n {\n target: environmentConfig.permissionControllerAddress as Address,\n value: 0n,\n callData: acceptAdminData,\n },\n ];\n\n // 5. Add public logs permission if requested\n if (publicLogs) {\n const anyoneCanViewLogsData = encodeFunctionData({\n abi: PermissionControllerABI,\n functionName: \"setAppointee\",\n args: [\n appId,\n \"0x493219d9949348178af1f58740655951a8cd110c\" as Address, // AnyoneCanCallAddress\n \"0x57ee1fb74c1087e26446abc4fb87fd8f07c43d8d\" as Address, // ApiPermissionsTarget\n \"0x2fd3f2fe\" as Hex, // CanViewAppLogsPermission\n ],\n });\n executions.push({\n target: environmentConfig.permissionControllerAddress as Address,\n value: 0n,\n callData: anyoneCanViewLogsData,\n });\n }\n\n return {\n appId,\n salt,\n executions,\n walletClient,\n publicClient,\n environmentConfig,\n };\n}\n\n/**\n * Execute a prepared deploy batch\n */\nexport async function executeDeployBatch(\n data: PreparedDeployData,\n context: {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n },\n gas?: GasEstimate,\n logger: Logger = noopLogger,\n): Promise<{ appId: Address; txHash: Hex }> {\n const pendingMessage = \"Deploying new app...\";\n\n const txHash = await executeBatch(\n {\n walletClient: context.walletClient,\n publicClient: context.publicClient,\n environmentConfig: context.environmentConfig,\n executions: data.executions,\n pendingMessage,\n gas,\n authorizationList: data.authorizationList,\n },\n logger,\n );\n\n return { appId: data.appId, txHash };\n}\n\n/**\n * Deploy app on-chain (convenience wrapper that prepares and executes)\n */\nexport async function deployApp(\n options: DeployAppOptions,\n logger: Logger = noopLogger,\n): Promise<{ appId: Address; txHash: Hex }> {\n const prepared = await prepareDeployBatch(options, logger);\n\n // Extract data and context from prepared batch\n const data: PreparedDeployData = {\n appId: prepared.appId,\n salt: prepared.salt,\n executions: prepared.executions,\n };\n const context = {\n walletClient: prepared.walletClient,\n publicClient: prepared.publicClient,\n environmentConfig: prepared.environmentConfig,\n };\n\n return executeDeployBatch(data, context, options.gas, logger);\n}\n\n/**\n * Check if wallet account supports EIP-7702 signing\n *\n * Local accounts (from privateKeyToAccount) support signAuthorization.\n * JSON-RPC accounts (browser wallets like MetaMask) do not.\n */\nexport function supportsEIP7702(walletClient: WalletClient): boolean {\n const account = walletClient.account;\n if (!account) return false;\n\n // Local accounts have type \"local\", JSON-RPC accounts have type \"json-rpc\"\n // Only local accounts support signAuthorization\n return account.type === \"local\";\n}\n\n/**\n * Options for sequential deployment (non-EIP-7702)\n */\nexport interface ExecuteDeploySequentialOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n /** Prepared deployment data from prepareDeployBatch */\n data: PreparedDeployData;\n /** Whether to set public logs permission */\n publicLogs: boolean;\n /** Optional callback for progress updates */\n onProgress?: DeployProgressCallback;\n}\n\n/**\n * Execute deployment as sequential transactions (non-EIP-7702 fallback)\n *\n * Use this for browser wallets (JSON-RPC accounts) that don't support signAuthorization.\n * This requires 2-3 wallet signatures instead of 1, but works with all wallet types.\n *\n * Steps:\n * 1. createApp - Creates the app on-chain\n * 2. acceptAdmin - Accepts admin role for the app\n * 3. setAppointee (optional) - Sets public logs permission\n */\nexport async function executeDeploySequential(\n options: ExecuteDeploySequentialOptions,\n logger: Logger = noopLogger,\n): Promise<SequentialDeployResult> {\n const { walletClient, publicClient, environmentConfig, data, publicLogs, onProgress } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n const chain = getChainFromID(environmentConfig.chainID);\n const txHashes: { createApp: Hex; acceptAdmin: Hex; setPublicLogs?: Hex } = {\n createApp: \"0x\" as Hex,\n acceptAdmin: \"0x\" as Hex,\n };\n\n // Step 1: Create App\n logger.info(\"Step 1/3: Creating app...\");\n onProgress?.(\"createApp\");\n\n const createAppExecution = data.executions[0];\n const createAppHash = await walletClient.sendTransaction({\n account,\n to: createAppExecution.target,\n data: createAppExecution.callData,\n value: createAppExecution.value,\n chain,\n });\n\n logger.info(`createApp transaction sent: ${createAppHash}`);\n const createAppReceipt = await publicClient.waitForTransactionReceipt({ hash: createAppHash });\n\n if (createAppReceipt.status === \"reverted\") {\n throw new Error(`createApp transaction reverted: ${createAppHash}`);\n }\n\n txHashes.createApp = createAppHash;\n logger.info(`createApp confirmed in block ${createAppReceipt.blockNumber}`);\n\n // Step 2: Accept Admin\n logger.info(\"Step 2/3: Accepting admin role...\");\n onProgress?.(\"acceptAdmin\", createAppHash);\n\n const acceptAdminExecution = data.executions[1];\n const acceptAdminHash = await walletClient.sendTransaction({\n account,\n to: acceptAdminExecution.target,\n data: acceptAdminExecution.callData,\n value: acceptAdminExecution.value,\n chain,\n });\n\n logger.info(`acceptAdmin transaction sent: ${acceptAdminHash}`);\n const acceptAdminReceipt = await publicClient.waitForTransactionReceipt({\n hash: acceptAdminHash,\n });\n\n if (acceptAdminReceipt.status === \"reverted\") {\n throw new Error(`acceptAdmin transaction reverted: ${acceptAdminHash}`);\n }\n\n txHashes.acceptAdmin = acceptAdminHash;\n logger.info(`acceptAdmin confirmed in block ${acceptAdminReceipt.blockNumber}`);\n\n // Step 3: Set Public Logs (if requested and present in executions)\n if (publicLogs && data.executions.length > 2) {\n logger.info(\"Step 3/3: Setting public logs permission...\");\n onProgress?.(\"setPublicLogs\", acceptAdminHash);\n\n const setAppointeeExecution = data.executions[2];\n const setAppointeeHash = await walletClient.sendTransaction({\n account,\n to: setAppointeeExecution.target,\n data: setAppointeeExecution.callData,\n value: setAppointeeExecution.value,\n chain,\n });\n\n logger.info(`setAppointee transaction sent: ${setAppointeeHash}`);\n const setAppointeeReceipt = await publicClient.waitForTransactionReceipt({\n hash: setAppointeeHash,\n });\n\n if (setAppointeeReceipt.status === \"reverted\") {\n throw new Error(`setAppointee transaction reverted: ${setAppointeeHash}`);\n }\n\n txHashes.setPublicLogs = setAppointeeHash;\n logger.info(`setAppointee confirmed in block ${setAppointeeReceipt.blockNumber}`);\n }\n\n onProgress?.(\"complete\", txHashes.setPublicLogs || txHashes.acceptAdmin);\n\n logger.info(`Deployment complete! App ID: ${data.appId}`);\n\n return {\n appId: data.appId,\n txHashes,\n };\n}\n\n/**\n * Result from EIP-5792 batched deployment\n */\nexport interface BatchedDeployResult {\n appId: Address;\n /** Batch ID from sendCalls (can be used with getCallsStatus) */\n batchId: string;\n /** Transaction receipts from the batch */\n receipts: Array<{ transactionHash: Hex }>;\n}\n\n/**\n * Options for EIP-5792 batched deployment\n */\nexport interface ExecuteDeployBatchedOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n /** Prepared deployment data from prepareDeployBatch */\n data: PreparedDeployData;\n /** Whether to set public logs permission */\n publicLogs: boolean;\n /** Optional callback for progress updates */\n onProgress?: DeployProgressCallback;\n}\n\n/**\n * Check if wallet supports EIP-5792 (sendCalls/wallet_sendCalls)\n *\n * This checks the wallet's capabilities to see if it supports atomic batch calls.\n * MetaMask and other modern wallets are adding support for this standard.\n */\nexport async function supportsEIP5792(walletClient: WalletClient): Promise<boolean> {\n try {\n // Check if getCapabilities method exists\n if (typeof walletClient.getCapabilities !== \"function\") {\n return false;\n }\n\n const account = walletClient.account;\n if (!account) return false;\n\n // Try to get capabilities - if this works, the wallet supports EIP-5792\n const capabilities = await walletClient.getCapabilities({\n account: account.address,\n });\n\n // Check if we got any capabilities back\n return (\n capabilities !== null && capabilities !== undefined && Object.keys(capabilities).length > 0\n );\n } catch {\n // If getCapabilities fails, the wallet doesn't support EIP-5792\n return false;\n }\n}\n\n/**\n * Execute deployment using EIP-5792 sendCalls (batched wallet calls)\n *\n * This batches all deployment transactions (createApp, acceptAdmin, setPublicLogs)\n * into a single wallet interaction. Better UX than sequential transactions.\n *\n * Use this for browser wallets that support EIP-5792 but not EIP-7702.\n *\n * @returns BatchedDeployResult with appId and batch receipts\n */\nexport async function executeDeployBatched(\n options: ExecuteDeployBatchedOptions,\n logger: Logger = noopLogger,\n): Promise<BatchedDeployResult> {\n const { walletClient, environmentConfig, data, publicLogs, onProgress } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n const chain = getChainFromID(environmentConfig.chainID);\n\n // Build calls array for sendCalls\n const calls: Array<{ to: Address; data: Hex; value: bigint }> = data.executions.map(\n (execution) => ({\n to: execution.target,\n data: execution.callData,\n value: execution.value,\n }),\n );\n\n // If public logs is false but executions include the permission call, filter it out\n // (This shouldn't happen if prepareDeployBatch was called correctly, but be safe)\n const filteredCalls = publicLogs ? calls : calls.slice(0, 2);\n\n logger.info(`Deploying with EIP-5792 sendCalls (${filteredCalls.length} calls)...`);\n onProgress?.(\"createApp\");\n\n try {\n // Send all calls in a single batch\n const { id: batchId } = await walletClient.sendCalls({\n account,\n chain,\n calls: filteredCalls,\n forceAtomic: true,\n });\n\n logger.info(`Batch submitted with ID: ${batchId}`);\n onProgress?.(\"acceptAdmin\");\n\n // Poll for batch completion using getCallsStatus\n let status: any;\n let attempts = 0;\n const maxAttempts = 120; // 10 minutes max (5s intervals)\n\n while (attempts < maxAttempts) {\n try {\n status = await walletClient.getCallsStatus({ id: batchId });\n\n if (status.status === \"success\" || status.status === \"confirmed\") {\n logger.info(`Batch confirmed with ${status.receipts?.length || 0} receipts`);\n break;\n }\n\n if (status.status === \"failed\" || status.status === \"reverted\") {\n throw new Error(`Batch transaction failed: ${status.status}`);\n }\n } catch (statusError: any) {\n // Some wallets may not support getCallsStatus, wait and check chain\n if (statusError.message?.includes(\"not supported\")) {\n logger.warn(\"getCallsStatus not supported, waiting for chain confirmation...\");\n // Fall back to waiting a fixed time\n await new Promise((resolve) => setTimeout(resolve, 15000));\n break;\n }\n throw statusError;\n }\n\n // Wait 5 seconds before next poll\n await new Promise((resolve) => setTimeout(resolve, 5000));\n attempts++;\n }\n\n if (attempts >= maxAttempts) {\n throw new Error(\"Timeout waiting for batch confirmation\");\n }\n\n if (publicLogs) {\n onProgress?.(\"setPublicLogs\");\n }\n onProgress?.(\"complete\");\n\n // Extract transaction hashes from receipts\n const receipts = (status?.receipts || []).map((r: any) => ({\n transactionHash: r.transactionHash || r.hash,\n }));\n\n logger.info(`Deployment complete! App ID: ${data.appId}`);\n\n return {\n appId: data.appId,\n batchId,\n receipts,\n };\n } catch (error: any) {\n // Check if the error indicates sendCalls is not supported\n if (\n error.message?.includes(\"not supported\") ||\n error.message?.includes(\"wallet_sendCalls\") ||\n error.code === -32601 // Method not found\n ) {\n throw new Error(\"EIP5792_NOT_SUPPORTED\");\n }\n throw error;\n }\n}\n\n/**\n * Upgrade app options\n */\nexport interface UpgradeAppOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n appID: Address;\n release: Release;\n publicLogs: boolean;\n needsPermissionChange: boolean;\n imageRef: string;\n gas?: GasEstimate;\n}\n\n/**\n * Options for preparing an upgrade batch\n */\nexport interface PrepareUpgradeBatchOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n appID: Address;\n release: Release;\n publicLogs: boolean;\n needsPermissionChange: boolean;\n imageRef: string;\n}\n\n/**\n * Prepare upgrade batch - creates executions without sending transaction\n *\n * Use this to get the prepared batch for gas estimation before executing.\n */\nexport async function prepareUpgradeBatch(\n options: PrepareUpgradeBatchOptions,\n): Promise<PreparedUpgradeBatch> {\n const {\n walletClient,\n publicClient,\n environmentConfig,\n appID,\n release,\n publicLogs,\n needsPermissionChange,\n } = options;\n\n // 1. Pack upgrade app call\n // Convert Release Uint8Array values to hex strings for viem\n const releaseForViem = {\n rmsRelease: {\n artifacts: release.rmsRelease.artifacts.map((artifact) => ({\n digest: `0x${bytesToHex(artifact.digest).slice(2).padStart(64, \"0\")}` as Hex,\n registry: artifact.registry,\n })),\n upgradeByTime: release.rmsRelease.upgradeByTime,\n },\n publicEnv: bytesToHex(release.publicEnv) as Hex,\n encryptedEnv: bytesToHex(release.encryptedEnv) as Hex,\n };\n\n const upgradeData = encodeFunctionData({\n abi: AppControllerABI,\n functionName: \"upgradeApp\",\n args: [appID, releaseForViem],\n });\n\n // 2. Start with upgrade execution\n const executions: Array<{\n target: Address;\n value: bigint;\n callData: Hex;\n }> = [\n {\n target: environmentConfig.appControllerAddress,\n value: 0n,\n callData: upgradeData,\n },\n ];\n\n // 3. Add permission transaction if needed\n if (needsPermissionChange) {\n if (publicLogs) {\n // Add public permission (private→public)\n const addLogsData = encodeFunctionData({\n abi: PermissionControllerABI,\n functionName: \"setAppointee\",\n args: [\n appID,\n \"0x493219d9949348178af1f58740655951a8cd110c\" as Address, // AnyoneCanCallAddress\n \"0x57ee1fb74c1087e26446abc4fb87fd8f07c43d8d\" as Address, // ApiPermissionsTarget\n \"0x2fd3f2fe\" as Hex, // CanViewAppLogsPermission\n ],\n });\n executions.push({\n target: environmentConfig.permissionControllerAddress as Address,\n value: 0n,\n callData: addLogsData,\n });\n } else {\n // Remove public permission (public→private)\n const removeLogsData = encodeFunctionData({\n abi: PermissionControllerABI,\n functionName: \"removeAppointee\",\n args: [\n appID,\n \"0x493219d9949348178af1f58740655951a8cd110c\" as Address, // AnyoneCanCallAddress\n \"0x57ee1fb74c1087e26446abc4fb87fd8f07c43d8d\" as Address, // ApiPermissionsTarget\n \"0x2fd3f2fe\" as Hex, // CanViewAppLogsPermission\n ],\n });\n executions.push({\n target: environmentConfig.permissionControllerAddress as Address,\n value: 0n,\n callData: removeLogsData,\n });\n }\n }\n\n return {\n appId: appID,\n executions,\n walletClient,\n publicClient,\n environmentConfig,\n };\n}\n\n/**\n * Execute a prepared upgrade batch\n */\nexport async function executeUpgradeBatch(\n data: PreparedUpgradeData,\n context: {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n },\n gas?: GasEstimate,\n logger: Logger = noopLogger,\n): Promise<Hex> {\n const pendingMessage = `Upgrading app ${data.appId}...`;\n\n const txHash = await executeBatch(\n {\n walletClient: context.walletClient,\n publicClient: context.publicClient,\n environmentConfig: context.environmentConfig,\n executions: data.executions,\n pendingMessage,\n gas,\n authorizationList: data.authorizationList,\n },\n logger,\n );\n\n return txHash;\n}\n\n/**\n * Upgrade app on-chain (convenience wrapper that prepares and executes)\n */\nexport async function upgradeApp(\n options: UpgradeAppOptions,\n logger: Logger = noopLogger,\n): Promise<Hex> {\n const prepared = await prepareUpgradeBatch(options);\n\n // Extract data and context from prepared batch\n const data: PreparedUpgradeData = {\n appId: prepared.appId,\n executions: prepared.executions,\n };\n const context = {\n walletClient: prepared.walletClient,\n publicClient: prepared.publicClient,\n environmentConfig: prepared.environmentConfig,\n };\n\n return executeUpgradeBatch(data, context, options.gas, logger);\n}\n\n/**\n * Send and wait for transaction with confirmation support\n */\nexport interface SendTransactionOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n to: Address;\n data: Hex;\n value?: bigint;\n pendingMessage: string;\n txDescription: string;\n gas?: GasEstimate;\n}\n\nexport async function sendAndWaitForTransaction(\n options: SendTransactionOptions,\n logger: Logger = noopLogger,\n): Promise<Hex> {\n const {\n walletClient,\n publicClient,\n environmentConfig,\n to,\n data,\n value = 0n,\n pendingMessage,\n txDescription,\n gas,\n } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n const chain = getChainFromID(environmentConfig.chainID);\n\n // Show pending message if provided\n if (pendingMessage) {\n logger.info(`\\n${pendingMessage}`);\n }\n\n // Send transaction with optional gas params\n const hash = await walletClient.sendTransaction({\n account,\n to,\n data,\n value,\n ...(gas?.maxFeePerGas && { maxFeePerGas: gas.maxFeePerGas }),\n ...(gas?.maxPriorityFeePerGas && {\n maxPriorityFeePerGas: gas.maxPriorityFeePerGas,\n }),\n chain,\n });\n\n logger.info(`Transaction sent: ${hash}`);\n\n // Wait for receipt\n const receipt = await publicClient.waitForTransactionReceipt({ hash });\n\n if (receipt.status === \"reverted\") {\n let revertReason = \"Unknown reason\";\n try {\n await publicClient.call({\n to,\n data,\n account: account.address,\n });\n } catch (callError: any) {\n if (callError.data) {\n try {\n const decoded = decodeErrorResult({\n abi: AppControllerABI,\n data: callError.data,\n });\n const formattedError = formatAppControllerError(decoded);\n revertReason = formattedError.message;\n } catch {\n revertReason = callError.message || \"Unknown reason\";\n }\n } else {\n revertReason = callError.message || \"Unknown reason\";\n }\n }\n logger.error(`${txDescription} transaction (hash: ${hash}) reverted: ${revertReason}`);\n throw new Error(`${txDescription} transaction (hash: ${hash}) reverted: ${revertReason}`);\n }\n\n return hash;\n}\n\n/**\n * Format AppController errors to user-friendly messages\n */\nfunction formatAppControllerError(decoded: {\n errorName: string;\n args?: readonly unknown[];\n}): Error {\n const errorName = decoded.errorName;\n\n switch (errorName) {\n case \"MaxActiveAppsExceeded\":\n return new Error(\n \"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\",\n );\n case \"GlobalMaxActiveAppsExceeded\":\n return new Error(\n \"the platform has reached the maximum number of active apps. please try again later\",\n );\n case \"InvalidPermissions\":\n return new Error(\"you don't have permission to perform this operation\");\n case \"AppAlreadyExists\":\n return new Error(\"an app with this owner and salt already exists\");\n case \"AppDoesNotExist\":\n return new Error(\"the specified app does not exist\");\n case \"InvalidAppStatus\":\n return new Error(\"the app is in an invalid state for this operation\");\n case \"MoreThanOneArtifact\":\n return new Error(\"only one artifact is allowed per release\");\n case \"InvalidSignature\":\n return new Error(\"invalid signature provided\");\n case \"SignatureExpired\":\n return new Error(\"the provided signature has expired\");\n case \"InvalidReleaseMetadataURI\":\n return new Error(\"invalid release metadata URI provided\");\n case \"InvalidShortString\":\n return new Error(\"invalid short string format\");\n default:\n return new Error(`contract error: ${errorName}`);\n }\n}\n\n/**\n * Get active app count for a user\n */\nexport async function getActiveAppCount(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n user: Address,\n): Promise<number> {\n const count = await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getActiveAppCount\",\n args: [user],\n });\n\n return Number(count);\n}\n\n/**\n * Get max active apps per user (quota limit)\n */\nexport async function getMaxActiveAppsPerUser(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n user: Address,\n): Promise<number> {\n const quota = await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getMaxActiveAppsPerUser\",\n args: [user],\n });\n\n return Number(quota);\n}\n\n/**\n * Get apps by creator (paginated)\n */\nexport interface AppConfig {\n release: any; // Release struct from contract\n status: number; // AppStatus enum\n}\n\nexport async function getAppsByCreator(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n creator: Address,\n offset: bigint,\n limit: bigint,\n): Promise<{ apps: Address[]; appConfigs: AppConfig[] }> {\n const result = (await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getAppsByCreator\",\n args: [creator, offset, limit],\n })) as [Address[], AppConfig[]];\n\n // Result is a tuple: [Address[], AppConfig[]]\n return {\n apps: result[0],\n appConfigs: result[1],\n };\n}\n\n/**\n * Get apps by developer\n */\nexport async function getAppsByDeveloper(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n developer: Address,\n offset: bigint,\n limit: bigint,\n): Promise<{ apps: Address[]; appConfigs: AppConfig[] }> {\n const result = (await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getAppsByDeveloper\",\n args: [developer, offset, limit],\n })) as [Address[], AppConfig[]];\n\n // Result is a tuple: [Address[], AppConfig[]]\n return {\n apps: result[0],\n appConfigs: result[1],\n };\n}\n\n/**\n * Get billing type for an app (0 = DEFAULT, 1 = ISOLATED)\n */\nexport async function getBillingType(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n app: Address,\n): Promise<number> {\n const result = await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getBillingType\",\n args: [app],\n });\n return Number(result);\n}\n\n/**\n * Get apps by billing account (paginated)\n */\nexport async function getAppsByBillingAccount(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n account: Address,\n offset: bigint,\n limit: bigint,\n): Promise<{ apps: Address[]; appConfigs: AppConfig[] }> {\n const result = (await publicClient.readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getAppsByBillingAccount\",\n args: [account, offset, limit],\n })) as [Address[], AppConfig[]];\n return { apps: result[0], appConfigs: result[1] };\n}\n\n/**\n * Fetch all apps by a developer by auto-pagination\n */\nexport async function getAllAppsByDeveloper(\n publicClient: PublicClient,\n env: EnvironmentConfig,\n developer: Address,\n pageSize: bigint = 100n,\n): Promise<{ apps: Address[]; appConfigs: AppConfig[] }> {\n let offset = 0n;\n const allApps: Address[] = [];\n const allConfigs: AppConfig[] = [];\n\n while (true) {\n const { apps, appConfigs } = await getAppsByDeveloper(\n publicClient,\n env,\n developer,\n offset,\n pageSize,\n );\n\n if (apps.length === 0) break;\n\n allApps.push(...apps);\n allConfigs.push(...appConfigs);\n\n if (apps.length < Number(pageSize)) break;\n\n offset += pageSize;\n }\n\n return {\n apps: allApps,\n appConfigs: allConfigs,\n };\n}\n\n/**\n * Get latest release block numbers for multiple apps\n */\nexport async function getAppLatestReleaseBlockNumbers(\n publicClient: PublicClient,\n environmentConfig: EnvironmentConfig,\n appIDs: Address[],\n): Promise<Map<Address, number>> {\n // Fetch block numbers in parallel\n const results = await Promise.all(\n appIDs.map((appID) =>\n publicClient\n .readContract({\n address: environmentConfig.appControllerAddress,\n abi: AppControllerABI,\n functionName: \"getAppLatestReleaseBlockNumber\",\n args: [appID],\n })\n .catch(() => null),\n ),\n );\n\n const blockNumbers = new Map<Address, number>();\n for (let i = 0; i < appIDs.length; i++) {\n const result = results[i];\n if (result !== null && result !== undefined) {\n blockNumbers.set(appIDs[i], Number(result));\n }\n }\n\n return blockNumbers;\n}\n\n/**\n * Get block timestamps for multiple block numbers\n */\nexport async function getBlockTimestamps(\n publicClient: PublicClient,\n blockNumbers: number[],\n): Promise<Map<number, number>> {\n // Deduplicate block numbers\n const uniqueBlockNumbers = [...new Set(blockNumbers)].filter((n) => n > 0);\n\n const timestamps = new Map<number, number>();\n\n // Fetch blocks in parallel\n const blocks = await Promise.all(\n uniqueBlockNumbers.map((blockNumber) =>\n publicClient.getBlock({ blockNumber: BigInt(blockNumber) }).catch(() => null),\n ),\n );\n\n for (let i = 0; i < uniqueBlockNumbers.length; i++) {\n const block = blocks[i];\n if (block) {\n timestamps.set(uniqueBlockNumbers[i], Number(block.timestamp));\n }\n }\n\n return timestamps;\n}\n\n/**\n * Suspend options\n */\nexport interface SuspendOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n account: Address;\n apps: Address[];\n}\n\n/**\n * Suspend apps for an account\n */\nexport async function suspend(\n options: SuspendOptions,\n logger: Logger = noopLogger,\n): Promise<Hex | false> {\n const { walletClient, publicClient, environmentConfig, account, apps } = options;\n\n const suspendData = encodeFunctionData({\n abi: AppControllerABI,\n functionName: \"suspend\",\n args: [account, apps],\n });\n\n const pendingMessage = `Suspending ${apps.length} app(s)...`;\n\n return sendAndWaitForTransaction(\n {\n walletClient,\n publicClient,\n environmentConfig,\n to: environmentConfig.appControllerAddress as Address,\n data: suspendData,\n pendingMessage,\n txDescription: \"Suspend\",\n },\n logger,\n );\n}\n\n/**\n * Options for checking delegation status\n */\nexport interface IsDelegatedOptions {\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n address: Address;\n}\n\n/**\n * Check if account is delegated to the ERC-7702 delegator\n */\nexport async function isDelegated(options: IsDelegatedOptions): Promise<boolean> {\n const { publicClient, environmentConfig, address } = options;\n\n return checkERC7702Delegation(\n publicClient,\n address,\n environmentConfig.erc7702DelegatorAddress as Address,\n );\n}\n\n/**\n * Undelegate options\n */\nexport interface UndelegateOptions {\n walletClient: WalletClient;\n publicClient: PublicClient;\n environmentConfig: EnvironmentConfig;\n}\n\n/**\n * Undelegate account (removes EIP-7702 delegation)\n */\nexport async function undelegate(\n options: UndelegateOptions,\n logger: Logger = noopLogger,\n): Promise<Hex> {\n const { walletClient, publicClient, environmentConfig } = options;\n\n const account = walletClient.account;\n if (!account) {\n throw new Error(\"WalletClient must have an account attached\");\n }\n\n const chain = getChainFromID(environmentConfig.chainID);\n\n // Create authorization to undelegate (empty address = undelegate)\n const transactionNonce = await publicClient.getTransactionCount({\n address: account.address,\n blockTag: \"pending\",\n });\n\n const chainId = await publicClient.getChainId();\n const authorizationNonce = BigInt(transactionNonce) + 1n;\n\n logger.debug(\"Signing undelegate authorization\");\n\n const signedAuthorization = await walletClient.signAuthorization({\n contractAddress: \"0x0000000000000000000000000000000000000000\" as Address,\n chainId: chainId,\n nonce: Number(authorizationNonce),\n account: account,\n });\n\n const authorizationList = [signedAuthorization];\n\n // Send transaction with authorization list\n const hash = await walletClient.sendTransaction({\n account,\n to: account.address, // Send to self\n data: \"0x\" as Hex, // Empty data\n value: 0n,\n authorizationList,\n chain,\n });\n\n logger.info(`Transaction sent: ${hash}`);\n\n const receipt = await publicClient.waitForTransactionReceipt({ hash });\n\n if (receipt.status === \"reverted\") {\n logger.error(`Undelegate transaction (hash: ${hash}) reverted`);\n throw new Error(`Undelegate transaction (hash: ${hash}) reverted`);\n }\n\n return hash;\n}\n","[\n {\n \"type\": \"function\",\n \"name\": \"purchaseCreditsFor\",\n \"stateMutability\": \"nonpayable\",\n \"inputs\": [\n { \"name\": \"amount\", \"type\": \"uint256\" },\n { \"name\": \"account\", \"type\": \"address\" }\n ],\n \"outputs\": []\n },\n {\n \"type\": \"function\",\n \"name\": \"purchaseCredits\",\n \"stateMutability\": \"nonpayable\",\n \"inputs\": [\n { \"name\": \"amount\", \"type\": \"uint256\" }\n ],\n \"outputs\": []\n },\n {\n \"type\": \"function\",\n \"name\": \"usdc\",\n \"stateMutability\": \"view\",\n \"inputs\": [],\n \"outputs\": [\n { \"name\": \"\", \"type\": \"address\" }\n ]\n },\n {\n \"type\": \"function\",\n \"name\": \"minimumPurchase\",\n \"stateMutability\": \"view\",\n \"inputs\": [],\n \"outputs\": [\n { \"name\": \"\", \"type\": \"uint256\" }\n ]\n },\n {\n \"type\": \"event\",\n \"name\": \"CreditsPurchased\",\n \"inputs\": [\n { \"name\": \"purchaser\", \"type\": \"address\", \"indexed\": true },\n { \"name\": \"account\", \"type\": \"address\", \"indexed\": true },\n { \"name\": \"amount\", \"type\": \"uint256\", \"indexed\": false }\n ]\n }\n]\n","[\n {\n \"type\": \"function\",\n \"name\": \"approve\",\n \"stateMutability\": \"nonpayable\",\n \"inputs\": [\n { \"name\": \"spender\", \"type\": \"address\" },\n { \"name\": \"amount\", \"type\": \"uint256\" }\n ],\n \"outputs\": [\n { \"name\": \"\", \"type\": \"bool\" }\n ]\n },\n {\n \"type\": \"function\",\n \"name\": \"balanceOf\",\n \"stateMutability\": \"view\",\n \"inputs\": [\n { \"name\": \"account\", \"type\": \"address\" }\n ],\n \"outputs\": [\n { \"name\": \"\", \"type\": \"uint256\" }\n ]\n },\n {\n \"type\": \"function\",\n \"name\": \"allowance\",\n \"stateMutability\": \"view\",\n \"inputs\": [\n { \"name\": \"owner\", \"type\": \"address\" },\n { \"name\": \"spender\", \"type\": \"address\" }\n ],\n \"outputs\": [\n { \"name\": \"\", \"type\": \"uint256\" }\n ]\n }\n]\n"],"mappings":"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","names":["encodeFunctionData","body","axios","sepolia","axios","encodeFunctionData","decodeErrorResult","encodeFunctionData","decodeErrorResult","encodeFunctionData"]}