UNPKG

@phala/cloud

Version:
1,578 lines (1,559 loc) 81.7 kB
// src/client.ts import { ofetch } from "ofetch"; import debug from "debug"; // src/types/client.ts import { z } from "zod"; var ApiErrorSchema = z.object({ detail: z.union([ z.string(), z.array( z.object({ msg: z.string(), type: z.string().optional(), ctx: z.record(z.unknown()).optional() }) ), z.record(z.unknown()) ]), type: z.string().optional(), code: z.string().optional() }); var RequestError = class _RequestError extends Error { constructor(message, options) { super(message); this.name = "RequestError"; this.isRequestError = true; this.status = options?.status; this.statusText = options?.statusText; this.data = options?.data; this.request = options?.request; this.response = options?.response; this.detail = options?.detail || message; this.code = options?.code; this.type = options?.type; } /** * Create RequestError from FetchError */ static fromFetchError(error) { const parseResult = ApiErrorSchema.safeParse(error.data); if (parseResult.success) { return new _RequestError(error.message, { status: error.status ?? void 0, statusText: error.statusText ?? void 0, data: error.data, request: error.request ?? void 0, response: error.response ?? void 0, detail: parseResult.data.detail, code: parseResult.data.code ?? void 0, type: parseResult.data.type ?? void 0 }); } return new _RequestError(error.message, { status: error.status ?? void 0, statusText: error.statusText ?? void 0, data: error.data, request: error.request ?? void 0, response: error.response ?? void 0, detail: error.data?.detail || "Unknown API error", code: error.status?.toString() ?? void 0 }); } /** * Create RequestError from generic Error */ static fromError(error, request) { return new _RequestError(error.message, { request: request ?? void 0, detail: error.message }); } }; // src/client.ts var SUPPORTED_API_VERSIONS = ["2025-05-31"]; var logger = debug("phala::api-client"); function formatHeaders(headers) { return Object.entries(headers).map(([key, value]) => ` -H "${key}: ${value}"`).join("\n"); } function formatBody(body) { if (!body) return ""; const bodyStr = typeof body === "string" ? body : JSON.stringify(body, null, 2); return ` -d '${bodyStr.replace(/'/g, "\\'")}'`; } function formatResponse(status, statusText, headers, body) { const headerEntries = []; headers.forEach((value, key) => { headerEntries.push(`${key}: ${value}`); }); const headerStr = headerEntries.join("\n"); const bodyStr = typeof body === "string" ? body : JSON.stringify(body, null, 2); return [ `< HTTP/1.1 ${status} ${statusText}`, headerStr ? `< ${headerStr.replace(/\n/g, "\n< ")}` : "", "", bodyStr ].filter(Boolean).join("\n"); } var Client = class { constructor(config = {}) { const resolvedConfig = { ...config, apiKey: config.apiKey || process?.env?.PHALA_CLOUD_API_KEY, baseURL: config.baseURL || process?.env?.PHALA_CLOUD_API_PREFIX || "https://cloud-api.phala.network/api/v1" }; const version = resolvedConfig.version && SUPPORTED_API_VERSIONS.includes(resolvedConfig.version) ? resolvedConfig.version : SUPPORTED_API_VERSIONS[0]; this.config = resolvedConfig; if (!resolvedConfig.apiKey) { throw new Error( "API key is required. Provide it via config.apiKey or set PHALA_CLOUD_API_KEY environment variable." ); } const { apiKey, baseURL, timeout, headers, ...fetchOptions } = resolvedConfig; const requestHeaders = { "X-API-Key": apiKey, "X-Phala-Version": version, "Content-Type": "application/json", ...headers || {} }; this.fetchInstance = ofetch.create({ baseURL, timeout: timeout || 3e4, headers: requestHeaders, ...fetchOptions, // Log request in cURL format onRequest({ request, options }) { if (logger.enabled) { const method = options.method || "GET"; const url = typeof request === "string" ? request : request.url; const fullUrl = url.startsWith("http") ? url : `${baseURL}${url}`; const headerObj = {}; if (options.headers && typeof options.headers === "object") { Object.entries(options.headers).forEach(([key, value]) => { if (typeof value === "string") { headerObj[key] = value; } }); } const curlCommand = [ `> curl -X ${method} "${fullUrl}"`, formatHeaders(headerObj), options.body ? formatBody(options.body) : "" ].filter(Boolean).join("\n"); logger("\n=== REQUEST ===\n%s\n", curlCommand); } }, // Log response in cURL format onResponse({ request, response, options }) { if (logger.enabled) { const method = options.method || "GET"; const url = typeof request === "string" ? request : request.url; logger( "\n=== RESPONSE [%s %s] (%dms) ===\n%s\n", method, url, response.headers.get("x-response-time") || "?", formatResponse(response.status, response.statusText, response.headers, response._data) ); } }, // Generic handlers for response error (similar to request.ts) onResponseError: ({ request, response, options }) => { console.warn(`HTTP ${response.status}: ${response.url}`); if (logger.enabled) { const method = options.method || "GET"; const url = typeof request === "string" ? request : request.url; logger( "\n=== ERROR RESPONSE [%s %s] ===\n%s\n", method, url, formatResponse(response.status, response.statusText, response.headers, response._data) ); } } }); } /** * Get the underlying ofetch instance for advanced usage */ get raw() { return this.fetchInstance; } // ===== Direct methods (throw on error) ===== /** * Perform GET request (throws on error) */ async get(request, options) { return this.fetchInstance(request, { ...options, method: "GET" }); } /** * Perform POST request (throws on error) */ async post(request, body, options) { return this.fetchInstance(request, { ...options, method: "POST", body }); } /** * Perform PUT request (throws on error) */ async put(request, body, options) { return this.fetchInstance(request, { ...options, method: "PUT", body }); } /** * Perform PATCH request (throws on error) */ async patch(request, body, options) { return this.fetchInstance(request, { ...options, method: "PATCH", body }); } /** * Perform DELETE request (throws on error) */ async delete(request, options) { return this.fetchInstance(request, { ...options, method: "DELETE" }); } // ===== Safe methods (return SafeResult) ===== /** * Safe wrapper for any request method (zod-style result) */ async safeRequest(fn) { try { const data = await fn(); return { success: true, data }; } catch (error) { if (error && typeof error === "object" && "data" in error) { const requestError2 = RequestError.fromFetchError(error); return { success: false, error: requestError2 }; } if (error instanceof Error) { const requestError2 = RequestError.fromError(error); return { success: false, error: requestError2 }; } const requestError = new RequestError("Unknown error occurred", { detail: "Unknown error occurred" }); return { success: false, error: requestError }; } } /** * Safe GET request (returns SafeResult) */ async safeGet(request, options) { return this.safeRequest(() => this.get(request, options)); } /** * Safe POST request (returns SafeResult) */ async safePost(request, body, options) { return this.safeRequest(() => this.post(request, body, options)); } /** * Safe PUT request (returns SafeResult) */ async safePut(request, body, options) { return this.safeRequest(() => this.put(request, body, options)); } /** * Safe PATCH request (returns SafeResult) */ async safePatch(request, body, options) { return this.safeRequest(() => this.patch(request, body, options)); } /** * Safe DELETE request (returns SafeResult) */ async safeDelete(request, options) { return this.safeRequest(() => this.delete(request, options)); } }; function createClient(config = {}) { return new Client(config); } // src/types/kms_info.ts import { z as z2 } from "zod"; // src/types/supported_chains.ts import { anvil, base, mainnet } from "viem/chains"; var SUPPORTED_CHAINS = { [mainnet.id]: mainnet, [base.id]: base, [anvil.id]: anvil }; // src/types/kms_info.ts var KmsInfoBaseSchema = z2.object({ id: z2.string(), slug: z2.string().nullable(), url: z2.string(), version: z2.string(), chain_id: z2.number().nullable(), kms_contract_address: z2.string().nullable().transform((val) => val), gateway_app_id: z2.string().nullable().transform((val) => val) }).passthrough(); var KmsInfoSchema = KmsInfoBaseSchema.transform((data) => { if (data.chain_id != null) { const chain = SUPPORTED_CHAINS[data.chain_id]; if (chain) { return { ...data, chain }; } } return data; }); // src/types/cvm_info.ts import { z as z3 } from "zod"; var VmInfoSchema = z3.object({ id: z3.string(), name: z3.string(), status: z3.string(), uptime: z3.string(), app_url: z3.string().nullable(), app_id: z3.string(), instance_id: z3.string().nullable(), configuration: z3.any().optional(), // TODO: add VmConfiguration schema if needed exited_at: z3.string().nullable(), boot_progress: z3.string().nullable(), boot_error: z3.string().nullable(), shutdown_progress: z3.string().nullable(), image_version: z3.string().nullable() }); var ManagedUserSchema = z3.object({ id: z3.number(), username: z3.string() }); var CvmNodeSchema = z3.object({ id: z3.number(), name: z3.string(), region_identifier: z3.string().optional() }); var CvmNetworkUrlsSchema = z3.object({ app: z3.string(), instance: z3.string() }); var CvmInfoSchema = z3.object({ hosted: VmInfoSchema, name: z3.string(), managed_user: ManagedUserSchema.optional().nullable(), node: CvmNodeSchema.optional().nullable(), listed: z3.boolean().default(false), status: z3.string(), in_progress: z3.boolean().default(false), dapp_dashboard_url: z3.string().nullable(), syslog_endpoint: z3.string().nullable(), allow_upgrade: z3.boolean().default(false), project_id: z3.string().nullable(), // HashedId is represented as string in JS project_type: z3.string().nullable(), billing_period: z3.string().nullable(), kms_info: KmsInfoSchema.nullable(), vcpu: z3.number().nullable(), memory: z3.number().nullable(), disk_size: z3.number().nullable(), gateway_domain: z3.string().nullable(), public_urls: z3.array(CvmNetworkUrlsSchema) }).partial(); var CvmLegacyDetailSchema = z3.object({ id: z3.number(), name: z3.string(), status: z3.string(), in_progress: z3.boolean(), teepod_id: z3.number().nullable(), teepod: CvmNodeSchema, app_id: z3.string(), vm_uuid: z3.string().nullable(), instance_id: z3.string().nullable(), vcpu: z3.number().nullable(), memory: z3.number().nullable(), disk_size: z3.number().nullable(), base_image: z3.string(), encrypted_env_pubkey: z3.string().nullable(), listed: z3.boolean(), project_id: z3.string().nullable(), project_type: z3.string().nullable(), public_sysinfo: z3.boolean(), public_logs: z3.boolean(), dapp_dashboard_url: z3.string().nullable(), syslog_endpoint: z3.string().nullable(), kms_info: KmsInfoSchema.nullable(), contract_address: z3.string().nullable(), deployer_address: z3.string().nullable(), scheduled_delete_at: z3.string().nullable(), public_urls: z3.array(CvmNetworkUrlsSchema), gateway_domain: z3.string().nullable() }); // src/actions/get_current_user.ts import { z as z4 } from "zod"; // src/utils/index.ts import { encryptEnvVars } from "@phala/dstack-sdk/encrypt-env-vars"; // src/utils/get_error_message.ts function getErrorMessage(error) { if (typeof error.detail === "string") { return error.detail; } if (Array.isArray(error.detail)) { if (error.detail.length > 0) { return error.detail[0]?.msg || "Validation error"; } return "Validation error"; } if (typeof error.detail === "object" && error.detail !== null) { return JSON.stringify(error.detail); } return "Unknown error occurred"; } // src/utils/as-hex.ts import { isHex } from "viem"; function asHex(value) { if (typeof value === "string") { if (value.startsWith("0x") && isHex(value)) { return value; } else if (isHex(`0x${value}`)) { return `0x${value}`; } } throw new Error(`Invalid hex value: ${value}`); } // src/utils/validate-parameters.ts function validateActionParameters(parameters) { if (parameters?.schema !== void 0 && parameters?.schema !== false) { if (typeof parameters.schema !== "object" || parameters.schema === null || !("parse" in parameters.schema) || typeof parameters.schema.parse !== "function") { throw new Error("Invalid schema: must be a Zod schema object, false, or undefined"); } } } function safeValidateActionParameters(parameters) { if (parameters?.schema !== void 0 && parameters?.schema !== false) { if (typeof parameters.schema !== "object" || parameters.schema === null || !("parse" in parameters.schema) || typeof parameters.schema.parse !== "function") { return { success: false, error: { name: "ZodError", message: "Invalid schema: must be a Zod schema object, false, or undefined", issues: [ { code: "invalid_type", expected: "object", received: typeof parameters.schema, path: ["schema"], message: "Invalid schema: must be a Zod schema object, false, or undefined" } ] } }; } } return void 0; } // src/utils/network.ts var NetworkError = class extends Error { constructor(message, code, details) { super(message); this.code = code; this.details = details; this.name = "NetworkError"; } }; var WalletError = class extends Error { constructor(message, code, details) { super(message); this.code = code; this.details = details; this.name = "WalletError"; } }; var TransactionError = class extends Error { constructor(message, hash, details) { super(message); this.hash = hash; this.details = details; this.name = "TransactionError"; } }; function createNetworkClients(publicClient, walletClient, address, chainId) { return { publicClient, walletClient, address, chainId }; } async function checkNetworkStatus(clients, targetChainId) { try { const currentChainId = await clients.walletClient.getChainId(); return { isCorrectNetwork: currentChainId === targetChainId, currentChainId }; } catch (error) { throw new NetworkError( `Failed to check network status: ${error instanceof Error ? error.message : "Unknown error"}`, "NETWORK_CHECK_FAILED", error ); } } async function checkBalance(publicClient, address, minBalance) { try { const balance = await publicClient.getBalance({ address }); return { address, balance, sufficient: minBalance ? balance >= minBalance : true, required: minBalance }; } catch (error) { throw new NetworkError( `Failed to check balance: ${error instanceof Error ? error.message : "Unknown error"}`, "BALANCE_CHECK_FAILED", error ); } } async function waitForTransactionReceipt(publicClient, hash, options = {}) { const { timeout = 6e4, // 60 seconds default pollingInterval = 2e3, // 2 seconds default confirmations = 1 } = options; const startTime = Date.now(); return new Promise((resolve, reject) => { const poll = async () => { try { const receipt = await publicClient.getTransactionReceipt({ hash }); if (receipt) { if (confirmations > 1) { const currentBlock = await publicClient.getBlockNumber(); const confirmationCount = currentBlock - receipt.blockNumber + 1n; if (confirmationCount < BigInt(confirmations)) { const elapsed = Date.now() - startTime; if (elapsed >= timeout) { reject( new TransactionError(`Transaction confirmation timeout after ${timeout}ms`, hash) ); return; } setTimeout(poll, pollingInterval); return; } } resolve(receipt); } else { const elapsed = Date.now() - startTime; if (elapsed >= timeout) { reject(new TransactionError(`Transaction receipt timeout after ${timeout}ms`, hash)); return; } setTimeout(poll, pollingInterval); } } catch (error) { const elapsed = Date.now() - startTime; if (elapsed >= timeout) { reject( new TransactionError(`Transaction receipt timeout after ${timeout}ms`, hash, error) ); return; } setTimeout(poll, pollingInterval); } }; poll(); }); } async function executeTransaction(clients, operation, args, options = {}) { const { timeout = 6e4, confirmations = 1, onSubmitted, onConfirmed, onError } = options; try { const hash = await operation(clients, ...args); onSubmitted?.(hash); const receipt = await waitForTransactionReceipt(clients.publicClient, hash, { timeout, confirmations }); const success = receipt.status === "success"; if (success) { onConfirmed?.(receipt); } else { const error = new TransactionError("Transaction failed on-chain", hash, receipt); onError?.(error, hash); throw error; } return { hash, receipt, success }; } catch (error) { const txError = error instanceof TransactionError ? error : new TransactionError( `Transaction execution failed: ${error instanceof Error ? error.message : "Unknown error"}`, void 0, error ); onError?.(txError, txError.hash); throw txError; } } async function extractNetworkClients(publicClient, walletClient) { try { const address = walletClient.account?.address; if (!address) { throw new WalletError("WalletClient must have an account", "NO_ACCOUNT"); } const chainId = await walletClient.getChainId(); return createNetworkClients(publicClient, walletClient, address, chainId); } catch (error) { throw new WalletError( `Failed to extract network clients: ${error instanceof Error ? error.message : "Unknown error"}`, "EXTRACTION_FAILED", error ); } } async function validateNetworkPrerequisites(clients, requirements) { const { targetChainId, minBalance, requiredAddress } = requirements; const networkStatus = await checkNetworkStatus(clients, targetChainId); const balanceResult = await checkBalance(clients.publicClient, clients.address, minBalance); const addressValid = requiredAddress ? clients.address.toLowerCase() === requiredAddress.toLowerCase() : true; return { networkValid: networkStatus.isCorrectNetwork, balanceValid: balanceResult.sufficient, addressValid, details: { currentChainId: networkStatus.currentChainId, balance: balanceResult.balance, address: clients.address } }; } // src/utils/transaction.ts function createTransactionTracker() { let status = { state: "idle" }; let timeoutHandle; let abortController; const updateStatus = (newStatus) => { status = { ...status, ...newStatus }; }; const reset = () => { if (timeoutHandle) { clearTimeout(timeoutHandle); timeoutHandle = void 0; } if (abortController) { abortController.abort(); abortController = void 0; } status = { state: "idle" }; }; const abort = () => { if (abortController) { abortController.abort(); } if (timeoutHandle) { clearTimeout(timeoutHandle); timeoutHandle = void 0; } updateStatus({ state: "error", aborted: true, error: "Transaction aborted by user" }); }; const execute = async (operation, clients, args, options = {}) => { const { timeout = 6e4, confirmations = 1, onSubmitted, onConfirmed, onError, signal } = options; try { reset(); abortController = new AbortController(); if (signal) { if (signal.aborted) { throw new TransactionError("Operation was aborted before execution"); } signal.addEventListener("abort", () => { abort(); }); } updateStatus({ state: "submitting", startTime: Date.now(), error: void 0, hash: void 0, receipt: void 0, aborted: false }); if (abortController.signal.aborted) { throw new TransactionError("Transaction aborted"); } const hash = await operation(clients, ...args); if (abortController.signal.aborted) { throw new TransactionError("Transaction aborted after submission", hash); } updateStatus({ state: "pending", hash, submitTime: Date.now() }); onSubmitted?.(hash); if (timeout > 0) { timeoutHandle = setTimeout(() => { if (status.state === "pending" && !abortController?.signal.aborted) { updateStatus({ state: "timeout", error: `Transaction timeout after ${timeout}ms` }); } }, timeout); } const receipt = await Promise.race([ waitForTransactionReceipt(clients.publicClient, hash, { timeout, confirmations }), new Promise((_, reject) => { abortController?.signal.addEventListener("abort", () => { reject(new TransactionError("Transaction aborted while waiting for receipt", hash)); }); }) ]); if (timeoutHandle) { clearTimeout(timeoutHandle); timeoutHandle = void 0; } const success = receipt.status === "success"; updateStatus({ state: success ? "success" : "error", receipt, confirmTime: Date.now(), error: success ? void 0 : "Transaction failed on-chain" }); if (success) { onConfirmed?.(receipt); } else { const error = new TransactionError("Transaction failed on-chain", hash, receipt); onError?.(error, hash); throw error; } return { hash, receipt, success }; } catch (error) { const txError = error instanceof TransactionError ? error : new TransactionError( `Transaction execution failed: ${error instanceof Error ? error.message : "Unknown error"}`, status.hash, error ); updateStatus({ state: "error", error: txError.message }); onError?.(txError, status.hash); throw txError; } }; return { get status() { return { ...status }; }, get isIdle() { return status.state === "idle"; }, get isSubmitting() { return status.state === "submitting"; }, get isPending() { return status.state === "pending"; }, get isSuccess() { return status.state === "success"; }, get isError() { return status.state === "error"; }, get isTimeout() { return status.state === "timeout"; }, get isAborted() { return status.aborted === true; }, get isComplete() { return ["success", "error", "timeout"].includes(status.state); }, abort, reset, execute }; } async function executeBatchTransactions(operations, clients, batchOptions) { const { mode, failFast = false, onProgress } = batchOptions; const results = []; if (mode === "sequential") { for (let i = 0; i < operations.length; i++) { const op = operations[i]; if (!op) continue; const { operation, args, options } = op; try { const tracker = createTransactionTracker(); const result = await tracker.execute(operation, clients, args, options); results.push(result); onProgress?.(i + 1, operations.length, results); } catch (error) { const txError = error instanceof Error ? error : new Error(String(error)); results.push(txError); onProgress?.(i + 1, operations.length, results); if (failFast) { for (let j = i + 1; j < operations.length; j++) { results.push(new Error("Cancelled due to previous failure")); } break; } } } } else { const promises = operations.map(async ({ operation, args, options }) => { try { const tracker = createTransactionTracker(); return await tracker.execute(operation, clients, args, options); } catch (error) { return error instanceof Error ? error : new Error(String(error)); } }); const allResults = await Promise.allSettled(promises); results.push(...allResults.map((r) => r.status === "fulfilled" ? r.value : r.reason)); onProgress?.(operations.length, operations.length, results); } const successCount = results.filter((r) => !(r instanceof Error)).length; const errorCount = results.length - successCount; return { results, successCount, errorCount, allSuccessful: errorCount === 0 }; } async function executeTransactionWithRetry(operation, clients, args, options = {}, retryOptions = {}) { const { maxRetries = 3, initialDelay = 1e3, maxDelay = 1e4, backoffFactor = 2, retryCondition = () => true } = retryOptions; let lastError; let delay = initialDelay; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { const tracker = createTransactionTracker(); return await tracker.execute(operation, clients, args, options); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (attempt === maxRetries) { break; } if (!retryCondition(lastError)) { break; } await new Promise((resolve) => setTimeout(resolve, delay)); delay = Math.min(delay * backoffFactor, maxDelay); } } throw lastError; } async function estimateTransactionGas(clients, transaction, options = {}) { const { gasLimitMultiplier = 1.2, maxFeePerGasMultiplier = 1.1, priorityFeeMultiplier = 1.1 } = options; try { const estimatedGas = await clients.publicClient.estimateGas(transaction); const gasLimit = BigInt(Math.ceil(Number(estimatedGas) * gasLimitMultiplier)); let maxFeePerGas; let maxPriorityFeePerGas; try { const feeData = await clients.publicClient.estimateFeesPerGas(); if (feeData.maxFeePerGas) { maxFeePerGas = BigInt(Math.ceil(Number(feeData.maxFeePerGas) * maxFeePerGasMultiplier)); } if (feeData.maxPriorityFeePerGas) { maxPriorityFeePerGas = BigInt( Math.ceil(Number(feeData.maxPriorityFeePerGas) * priorityFeeMultiplier) ); } } catch (error) { } return { gasLimit, maxFeePerGas, maxPriorityFeePerGas }; } catch (error) { throw new TransactionError( `Gas estimation failed: ${error instanceof Error ? error.message : "Unknown error"}`, void 0, error ); } } // src/utils/client-factories.ts import { createPublicClient, createWalletClient, http, custom } from "viem"; import { privateKeyToAccount } from "viem/accounts"; function isBrowser() { return typeof window !== "undefined" && typeof window.ethereum !== "undefined"; } function getEthereumProvider() { if (!isBrowser()) return null; const ethereum = window.ethereum; return ethereum || null; } function createClientsFromPrivateKey(chain, privateKey, rpcUrl) { try { const account = privateKeyToAccount(privateKey); const publicClient = createPublicClient({ chain, transport: http(rpcUrl || chain.rpcUrls.default.http[0]) }); const walletClient = createWalletClient({ account, chain, transport: http(rpcUrl || chain.rpcUrls.default.http[0]) }); return createNetworkClients(publicClient, walletClient, account.address, chain.id); } catch (error) { throw new WalletError( `Failed to create clients from private key: ${error instanceof Error ? error.message : "Unknown error"}`, "CLIENT_CREATION_FAILED", error ); } } async function createClientsFromBrowser(chain, rpcUrl) { if (!isBrowser()) { throw new WalletError( "Browser wallet connection is only available in browser environment", "NOT_BROWSER_ENVIRONMENT" ); } const provider = getEthereumProvider(); if (!provider) { throw new WalletError( "No Ethereum provider found. Please install a wallet like MetaMask.", "NO_PROVIDER" ); } try { const accounts = await provider.request({ method: "eth_requestAccounts" }); if (!accounts || accounts.length === 0) { throw new WalletError("No accounts available", "NO_ACCOUNTS"); } const address = accounts[0]; const chainId = await provider.request({ method: "eth_chainId" }); const currentChainId = parseInt(chainId, 16); if (currentChainId !== chain.id) { await switchToNetwork(provider, chain.id); } const publicClient = createPublicClient({ chain, transport: http(rpcUrl || chain.rpcUrls.default.http[0]) }); const walletClient = createWalletClient({ account: address, chain, transport: custom(provider) }); return createNetworkClients(publicClient, walletClient, address, chain.id); } catch (error) { if (error instanceof WalletError || error instanceof NetworkError) { throw error; } throw new WalletError( `Failed to connect browser wallet: ${error instanceof Error ? error.message : "Unknown error"}`, "BROWSER_CONNECTION_FAILED", error ); } } async function switchToNetwork(provider, chainId) { try { await provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: `0x${chainId.toString(16)}` }] }); } catch (error) { const errorObj = error; if (errorObj.code === 4902) { throw new NetworkError( `Network ${chainId} not found in wallet. Please add it manually.`, "NETWORK_NOT_FOUND", error ); } throw new NetworkError( `Failed to switch network: ${errorObj.message || "Unknown error"}`, "NETWORK_SWITCH_FAILED", error ); } } async function addNetwork(provider, config) { try { await provider.request({ method: "wallet_addEthereumChain", params: [ { chainId: `0x${config.chainId.toString(16)}`, chainName: config.name, rpcUrls: [config.rpcUrl], blockExplorerUrls: config.blockExplorer ? [config.blockExplorer] : void 0, nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 } } ] }); } catch (error) { const errorObj = error; throw new NetworkError( `Failed to add network: ${errorObj.message || "Unknown error"}`, "NETWORK_ADD_FAILED", error ); } } async function autoCreateClients(chain, options = {}) { const { privateKey, rpcUrl, preferBrowser = false } = options; if (privateKey) { return createClientsFromPrivateKey(chain, privateKey, rpcUrl); } if (isBrowser() && (preferBrowser || !privateKey)) { return createClientsFromBrowser(chain, rpcUrl); } throw new WalletError( "No wallet connection method available. Provide a private key for server-side usage or use in browser environment.", "NO_CONNECTION_METHOD" ); } // src/actions/get_current_user.ts var CurrentUserSchema = z4.object({ username: z4.string(), email: z4.string(), credits: z4.number(), granted_credits: z4.number(), avatar: z4.string(), team_name: z4.string(), team_tier: z4.string() }).passthrough(); async function getCurrentUser(client, parameters) { validateActionParameters(parameters); const response = await client.get("/auth/me"); if (parameters?.schema === false) { return response; } const schema = parameters?.schema || CurrentUserSchema; return schema.parse(response); } async function safeGetCurrentUser(client, parameters) { const parameterValidationError = safeValidateActionParameters(parameters); if (parameterValidationError) { return parameterValidationError; } const httpResult = await client.safeGet("/auth/me"); if (!httpResult.success) { return httpResult; } if (parameters?.schema === false) { return { success: true, data: httpResult.data }; } const schema = parameters?.schema || CurrentUserSchema; return schema.safeParse(httpResult.data); } // src/actions/get_available_nodes.ts import { z as z5 } from "zod"; var AvailableOSImageSchema = z5.object({ name: z5.string(), is_dev: z5.boolean(), version: z5.tuple([z5.number(), z5.number(), z5.number()]), os_image_hash: z5.string().nullable().optional() }).passthrough(); var TeepodCapacitySchema = z5.object({ teepod_id: z5.number(), name: z5.string(), listed: z5.boolean(), resource_score: z5.number(), remaining_vcpu: z5.number(), remaining_memory: z5.number(), remaining_cvm_slots: z5.number(), images: z5.array(AvailableOSImageSchema), support_onchain_kms: z5.boolean().optional(), fmspc: z5.string().nullable().optional(), device_id: z5.string().nullable().optional(), region_identifier: z5.string().nullable().optional(), default_kms: z5.string().nullable().optional(), kms_list: z5.array(z5.string()).default([]) }).passthrough(); var ResourceThresholdSchema = z5.object({ max_instances: z5.number().nullable().optional(), max_vcpu: z5.number().nullable().optional(), max_memory: z5.number().nullable().optional(), max_disk: z5.number().nullable().optional() }).passthrough(); var AvailableNodesSchema = z5.object({ tier: z5.string(), // TeamTier is string enum capacity: ResourceThresholdSchema, nodes: z5.array(TeepodCapacitySchema), kms_list: z5.array(KmsInfoSchema) }).passthrough(); async function getAvailableNodes(client, parameters) { const response = await client.get("/teepods/available"); if (parameters?.schema === false) { return response; } const schema = parameters?.schema || AvailableNodesSchema; return schema.parse(response); } async function safeGetAvailableNodes(client, parameters) { const httpResult = await client.safeGet("/teepods/available"); if (!httpResult.success) { return httpResult; } if (parameters?.schema === false) { return { success: true, data: httpResult.data }; } const schema = parameters?.schema || AvailableNodesSchema; return schema.safeParse(httpResult.data); } // src/actions/provision_cvm.ts import { z as z6 } from "zod"; var ProvisionCvmSchema = z6.object({ app_id: z6.string().nullable().optional(), app_env_encrypt_pubkey: z6.string().nullable().optional(), compose_hash: z6.string(), fmspc: z6.string().nullable().optional(), device_id: z6.string().nullable().optional(), os_image_hash: z6.string().nullable().optional(), node_id: z6.number().nullable().optional(), // Transformed from teepod_id in response kms_id: z6.string().nullable().optional() }).passthrough(); var ProvisionCvmRequestSchema = z6.object({ node_id: z6.number().optional(), // recommended teepod_id: z6.number().optional(), // deprecated, for compatibility name: z6.string(), image: z6.string(), vcpu: z6.number(), memory: z6.number(), disk_size: z6.number(), compose_file: z6.object({ allowed_envs: z6.array(z6.string()).optional(), pre_launch_script: z6.string().optional(), docker_compose_file: z6.string().optional(), name: z6.string().optional(), kms_enabled: z6.boolean().optional(), public_logs: z6.boolean().optional(), public_sysinfo: z6.boolean().optional(), gateway_enabled: z6.boolean().optional(), // recommended tproxy_enabled: z6.boolean().optional() // deprecated, for compatibility }), listed: z6.boolean().optional(), instance_type: z6.string().nullable().optional(), kms_id: z6.string().optional(), env_keys: z6.array(z6.string()).optional() }).passthrough(); function autofillComposeFileName(appCompose) { if (appCompose.compose_file && !appCompose.compose_file.name) { return { ...appCompose, compose_file: { ...appCompose.compose_file, name: appCompose.name } }; } return appCompose; } function handleGatewayCompatibility(appCompose) { if (!appCompose.compose_file) { return appCompose; } const composeFile = { ...appCompose.compose_file }; if (typeof composeFile.gateway_enabled === "boolean" && typeof composeFile.tproxy_enabled === "boolean") { delete composeFile.tproxy_enabled; } else if (typeof composeFile.tproxy_enabled === "boolean" && typeof composeFile.gateway_enabled === "undefined") { composeFile.gateway_enabled = composeFile.tproxy_enabled; delete composeFile.tproxy_enabled; if (typeof window !== "undefined" ? window.console : globalThis.console) { console.warn( "[phala/cloud] tproxy_enabled is deprecated, please use gateway_enabled instead. See docs for migration." ); } } return { ...appCompose, compose_file: composeFile }; } function transformResponse(data, isDefaultSchema) { if (!isDefaultSchema || !data || typeof data !== "object") { return data; } if (data && typeof data === "object" && "teepod_id" in data) { const { teepod_id, ...rest } = data; return { ...rest, node_id: teepod_id }; } return data; } async function provisionCvm(client, appCompose, parameters) { validateActionParameters(parameters); const body = handleGatewayCompatibility(autofillComposeFileName(appCompose)); let requestBody = { ...body }; if (typeof body.node_id === "number") { requestBody = { ...body, teepod_id: body.node_id }; delete requestBody.node_id; } else if (typeof body.teepod_id === "number") { console.warn("[phala/cloud] teepod_id is deprecated, please use node_id instead."); } const response = await client.post("/cvms/provision", requestBody); const isDefaultSchema = parameters?.schema === void 0; const transformedData = transformResponse(response, isDefaultSchema); if (parameters?.schema === false) { return transformedData; } const usedSchema = parameters?.schema || ProvisionCvmSchema; return usedSchema.parse(transformedData); } async function safeProvisionCvm(client, appCompose, parameters) { const parameterValidationError = safeValidateActionParameters(parameters); if (parameterValidationError) { return parameterValidationError; } const schema = parameters?.schema; const body = handleGatewayCompatibility(autofillComposeFileName(appCompose)); let requestBody = { ...body }; if (typeof body.node_id === "number") { requestBody = { ...body, teepod_id: body.node_id }; delete requestBody.node_id; } else if (typeof body.teepod_id === "number") { console.warn("[phala/cloud] teepod_id is deprecated, please use node_id instead."); } const httpResult = await client.safePost("/cvms/provision", requestBody); if (!httpResult.success) { return httpResult; } if (schema === false) { return { success: true, data: httpResult.data }; } const isDefaultSchema = !schema; const usedSchema = schema || ProvisionCvmSchema; const transformResult = usedSchema.safeParse(transformResponse(httpResult.data, isDefaultSchema)); return transformResult; } // src/actions/commit_cvm_provision.ts import { z as z7 } from "zod"; var CommitCvmProvisionSchema = z7.object({ id: z7.number(), name: z7.string(), status: z7.string(), teepod_id: z7.number(), teepod: z7.object({ id: z7.number(), name: z7.string() }).nullable(), user_id: z7.number().nullable(), app_id: z7.string().nullable(), vm_uuid: z7.string().nullable(), instance_id: z7.string().nullable(), app_url: z7.string().nullable(), base_image: z7.string().nullable(), vcpu: z7.number(), memory: z7.number(), disk_size: z7.number(), manifest_version: z7.number().nullable(), version: z7.string().nullable(), runner: z7.string().nullable(), docker_compose_file: z7.string().nullable(), features: z7.array(z7.string()).nullable(), created_at: z7.string(), encrypted_env_pubkey: z7.string().nullable().optional(), app_auth_contract_address: z7.string().nullable().optional(), deployer_address: z7.string().nullable().optional() }).passthrough(); var CommitCvmProvisionRequestSchema = z7.object({ encrypted_env: z7.string().optional().nullable(), app_id: z7.string(), compose_hash: z7.string().optional(), kms_id: z7.string().optional(), contract_address: z7.string().optional(), deployer_address: z7.string().optional(), env_keys: z7.array(z7.string()).optional().nullable() }).passthrough(); async function commitCvmProvision(client, payload, parameters) { validateActionParameters(parameters); const response = await client.post("/cvms", payload); if (parameters?.schema === false) { return response; } const schema = parameters?.schema || CommitCvmProvisionSchema; return schema.parse(response); } async function safeCommitCvmProvision(client, payload, parameters) { const parameterValidationError = safeValidateActionParameters(parameters); if (parameterValidationError) { return parameterValidationError; } const httpResult = await client.safePost("/cvms", payload); if (!httpResult.success) { return httpResult; } if (parameters?.schema === false) { return { success: true, data: httpResult.data }; } const schema = parameters?.schema || CommitCvmProvisionSchema; const validationResult = schema.safeParse(httpResult.data); return validationResult; } // src/actions/deploy_app_auth.ts import { z as z8 } from "zod"; import { createPublicClient as createPublicClient2, createWalletClient as createWalletClient2, http as http2, parseEventLogs, parseEther } from "viem"; import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts"; var kmsAuthAbi = [ { inputs: [ { name: "deployer", type: "address" }, { name: "disableUpgrades", type: "bool" }, { name: "allowAnyDevice", type: "bool" }, { name: "deviceId", type: "bytes32" }, { name: "composeHash", type: "bytes32" } ], name: "deployAndRegisterApp", outputs: [{ name: "", type: "address" }], stateMutability: "nonpayable", type: "function" }, { inputs: [ { name: "appId", type: "address", indexed: true }, { name: "deployer", type: "address", indexed: true } ], name: "AppDeployedViaFactory", type: "event", anonymous: false }, { inputs: [{ name: "appId", type: "address", indexed: false }], name: "AppRegistered", type: "event", anonymous: false } ]; var DeployAppAuthRequestBaseSchema = z8.object({ // Chain configuration (conditionally required) chain: z8.unknown().optional(), rpcUrl: z8.string().optional(), // Contract configuration (required) kmsContractAddress: z8.string(), // Authentication mode: either privateKey OR walletClient (required, mutually exclusive) privateKey: z8.string().optional(), walletClient: z8.unknown().optional(), // Public client (optional, will create default if not provided) publicClient: z8.unknown().optional(), // App configuration (optional) allowAnyDevice: z8.boolean().optional().default(false), deviceId: z8.string().optional().default("0000000000000000000000000000000000000000000000000000000000000000"), composeHash: z8.string().optional().default("0000000000000000000000000000000000000000000000000000000000000000"), disableUpgrades: z8.boolean().optional().default(false), // Validation configuration (optional) skipPrerequisiteChecks: z8.boolean().optional().default(false), minBalance: z8.string().optional() // ETH amount as string, e.g., "0.01" }).passthrough(); var DeployAppAuthRequestSchema = DeployAppAuthRequestBaseSchema.refine( (data) => { const hasPrivateKey = !!data.privateKey; const hasWalletClient = !!data.walletClient; return hasPrivateKey !== hasWalletClient; }, { message: "Either 'privateKey' or 'walletClient' must be provided, but not both", path: ["privateKey", "walletClient"] } ).refine( (data) => { const hasPublicClient = !!data.publicClient; const hasWalletClient = !!data.walletClient; const hasChain = !!data.chain; if (hasPublicClient && hasWalletClient) { return true; } return hasChain; }, { message: "Chain is required when publicClient or walletClient is not provided", path: ["chain"] } ); var DeployAppAuthSchema = z8.object({ appId: z8.string(), appAuthAddress: z8.string(), deployer: z8.string(), transactionHash: z8.string(), blockNumber: z8.bigint().optional(), gasUsed: z8.bigint().optional() }).passthrough(); function parseDeploymentResult(receipt, deployer, kmsContractAddress) { try { const logs = parseEventLogs({ abi: kmsAuthAbi, eventName: "AppDeployedViaFactory", logs: receipt.logs, strict: false }); if (logs.length === 0) { if (receipt.status === "reverted") { throw new Error(`Transaction failed: ${receipt.transactionHash}`); } throw new Error( `Transaction ${receipt.transactionHash} has no AppDeployedViaFactory events. The deployment failed. Status: ${receipt.status}. Found ${receipt.logs.length} logs.` ); } const deploymentEvent = logs[0]; if (!deploymentEvent?.args) { throw new Error("Event has no data"); } const { appId, deployer: eventDeployer } = deploymentEvent.args; if (!appId) { throw new Error("Event missing appId"); } return { appId, appAuthAddress: appId, deployer, transactionHash: receipt.transactionHash, blockNumber: receipt.blockNumber, gasUsed: receipt.gasUsed }; } catch (error) { if (error instanceof Error) { throw error; } throw new Error(`Parse failed: ${error}`); } } async function deployAppAuth(request, parameters) { const validatedRequest = DeployAppAuthRequestSchema.parse(request); const { chain, rpcUrl, kmsContractAddress, privateKey, walletClient: providedWalletClient, publicClient: providedPublicClient, allowAnyDevice: rawAllowAnyDevice = false, deviceId = "0000000000000000000000000000000000000000000000000000000000000000", composeHash = "0000000000000000000000000000000000000000000000000000000000000000", disableUpgrades = false, skipPrerequisiteChecks = false, minBalance, timeout = 12e4, // 2 minutes default retryOptions, signal, onTransactionStateChange, onTransactionSubmitted, onTransactionConfirmed } = validatedRequest; const defaultDeviceId = "0000000000000000000000000000000000000000000000000000000000000000"; const hasSpecificDevice = deviceId !== defaultDeviceId && deviceId !== "0x" + defaultDeviceId; const allowAnyDevice = hasSpecificDevice ? false : rawAllowAnyDevice; let publicClient; let walletClient; let deployerAddress; let chainId; if (privateKey) { const account = privateKeyToAccount2(privateKey); if (providedPublicClient) { if (typeof providedPublicClient !== "object" || !providedPublicClient) { throw new Error("publicClient is invalid"); } publicClient = providedPublicClient; } else { if (!chain) { throw new Error("Chain required for publicClient"); } publicClient = createPublicClient2({ chain, transport: http2(rpcUrl) }); } if (!chain) { throw new Error("Chain required for walletClient"); } walletClient = createWalletClient2({ account, chain, transport: http2(rpcUrl) }); deployerAddress = account.address; chainId = chain.id; } else if (providedWalletClient) { if (typeof providedWalletClient !== "object" || !providedWalletClient) { throw new Error("walletClient is invalid"); } walletClient = providedWalletClient; if (providedPublicClient) { if (typeof providedPublicClient !== "object" || !providedPublicClient) { throw new Error("publicClient is invalid"); } publicClient = providedPublicClient; } else { if (!chain) { throw new Error("Chain required for publicClient"); } publicClient = createPublicClient2({ chain, transport: http2(rpcUrl) }); } if (!walletClient.account?.address) { throw new Error("WalletClient needs an account"); } deployerAddress = walletClient.account.address; if (chain) { chainId = chain.id; } else { chainId = await walletClient.getChainId();