@phala/cloud
Version:
TypeScript SDK for Phala Cloud API
1,445 lines (1,425 loc) • 98.2 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
AddComposeHashSchema: () => AddComposeHashSchema,
ApiErrorSchema: () => ApiErrorSchema,
AvailableNodesSchema: () => AvailableNodesSchema,
CommitCvmComposeFileUpdateRequestSchema: () => CommitCvmComposeFileUpdateRequestSchema,
CommitCvmComposeFileUpdateSchema: () => CommitCvmComposeFileUpdateSchema,
CommitCvmProvisionRequestSchema: () => CommitCvmProvisionRequestSchema,
CommitCvmProvisionSchema: () => CommitCvmProvisionSchema,
CurrentUserSchema: () => CurrentUserSchema,
CvmInfoSchema: () => CvmInfoSchema,
CvmLegacyDetailSchema: () => CvmLegacyDetailSchema,
CvmNetworkUrlsSchema: () => CvmNetworkUrlsSchema,
CvmNodeSchema: () => CvmNodeSchema,
DeployAppAuthRequestSchema: () => DeployAppAuthRequestSchema,
DeployAppAuthSchema: () => DeployAppAuthSchema,
GetAppEnvEncryptPubKeySchema: () => GetAppEnvEncryptPubKeySchema,
GetCvmListSchema: () => GetCvmListSchema,
GetKmsListSchema: () => GetKmsListSchema,
InstanceTypeSchema: () => InstanceTypeSchema,
KmsInfoSchema: () => KmsInfoSchema,
ListWorkspacesSchema: () => ListWorkspacesSchema,
ManagedUserSchema: () => ManagedUserSchema,
NetworkError: () => NetworkError,
PaginatedInstanceTypesSchema: () => PaginatedInstanceTypesSchema,
PaginationMetadataSchema: () => PaginationMetadataSchema,
ProvisionCvmComposeFileUpdateRequestSchema: () => ProvisionCvmComposeFileUpdateRequestSchema,
ProvisionCvmComposeFileUpdateResultSchema: () => ProvisionCvmComposeFileUpdateResultSchema,
ProvisionCvmRequestSchema: () => ProvisionCvmRequestSchema,
ProvisionCvmSchema: () => ProvisionCvmSchema,
RequestError: () => RequestError,
SUPPORTED_CHAINS: () => SUPPORTED_CHAINS,
TransactionError: () => TransactionError,
VmInfoSchema: () => VmInfoSchema,
WalletError: () => WalletError,
WorkspaceResponseSchema: () => WorkspaceResponseSchema,
addComposeHash: () => addComposeHash,
addNetwork: () => addNetwork,
asHex: () => asHex,
autoCreateClients: () => autoCreateClients,
checkBalance: () => checkBalance,
checkNetworkStatus: () => checkNetworkStatus,
commitCvmComposeFileUpdate: () => commitCvmComposeFileUpdate,
commitCvmProvision: () => commitCvmProvision,
createClient: () => createClient,
createClientsFromBrowser: () => createClientsFromBrowser,
createClientsFromPrivateKey: () => createClientsFromPrivateKey,
createNetworkClients: () => createNetworkClients,
createTransactionTracker: () => createTransactionTracker,
deployAppAuth: () => deployAppAuth,
encryptEnvVars: () => import_encrypt_env_vars2.encryptEnvVars,
estimateTransactionGas: () => estimateTransactionGas,
executeBatchTransactions: () => executeBatchTransactions,
executeTransaction: () => executeTransaction,
executeTransactionWithRetry: () => executeTransactionWithRetry,
extractNetworkClients: () => extractNetworkClients,
getAppEnvEncryptPubKey: () => getAppEnvEncryptPubKey,
getAvailableNodes: () => getAvailableNodes,
getComposeHash: () => import_get_compose_hash2.getComposeHash,
getCurrentUser: () => getCurrentUser,
getCvmComposeFile: () => getCvmComposeFile,
getCvmInfo: () => getCvmInfo,
getCvmList: () => getCvmList,
getErrorMessage: () => getErrorMessage,
getKmsInfo: () => getKmsInfo,
getKmsList: () => getKmsList,
getWorkspace: () => getWorkspace,
listInstanceTypes: () => listInstanceTypes,
listWorkspaces: () => listWorkspaces,
parseEnv: () => parseEnv,
parseEnvVars: () => parseEnvVars,
provisionCvm: () => provisionCvm,
provisionCvmComposeFileUpdate: () => provisionCvmComposeFileUpdate,
safeAddComposeHash: () => safeAddComposeHash,
safeCommitCvmComposeFileUpdate: () => safeCommitCvmComposeFileUpdate,
safeCommitCvmProvision: () => safeCommitCvmProvision,
safeDeployAppAuth: () => safeDeployAppAuth,
safeGetAppEnvEncryptPubKey: () => safeGetAppEnvEncryptPubKey,
safeGetAvailableNodes: () => safeGetAvailableNodes,
safeGetCurrentUser: () => safeGetCurrentUser,
safeGetCvmComposeFile: () => safeGetCvmComposeFile,
safeGetCvmInfo: () => safeGetCvmInfo,
safeGetCvmList: () => safeGetCvmList,
safeGetKmsInfo: () => safeGetKmsInfo,
safeGetKmsList: () => safeGetKmsList,
safeGetWorkspace: () => safeGetWorkspace,
safeListInstanceTypes: () => safeListInstanceTypes,
safeListWorkspaces: () => safeListWorkspaces,
safeProvisionCvm: () => safeProvisionCvm,
safeProvisionCvmComposeFileUpdate: () => safeProvisionCvmComposeFileUpdate,
safeValidateActionParameters: () => safeValidateActionParameters,
switchToNetwork: () => switchToNetwork,
validateActionParameters: () => validateActionParameters,
validateNetworkPrerequisites: () => validateNetworkPrerequisites,
verifyEnvEncryptPublicKey: () => import_verify_env_encrypt_public_key.verifyEnvEncryptPublicKey,
waitForTransactionReceipt: () => waitForTransactionReceipt
});
module.exports = __toCommonJS(index_exports);
// src/client.ts
var import_ofetch = require("ofetch");
var import_debug = __toESM(require("debug"));
// src/types/client.ts
var import_zod = require("zod");
var ApiErrorSchema = import_zod.z.object({
detail: import_zod.z.union([
import_zod.z.string(),
import_zod.z.array(
import_zod.z.object({
msg: import_zod.z.string(),
type: import_zod.z.string().optional(),
ctx: import_zod.z.record(import_zod.z.unknown()).optional()
})
),
import_zod.z.record(import_zod.z.unknown())
]),
type: import_zod.z.string().optional(),
code: import_zod.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 = (0, import_debug.default)("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.useCookieAuth && !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, useCookieAuth, onResponseError, ...fetchOptions } = resolvedConfig;
const requestHeaders = {
"X-Phala-Version": version,
"Content-Type": "application/json"
};
if (headers && typeof headers === "object") {
Object.entries(headers).forEach(([key, value]) => {
if (typeof value === "string") {
requestHeaders[key] = value;
}
});
}
if (!useCookieAuth && apiKey) {
requestHeaders["X-API-Key"] = apiKey;
}
this.fetchInstance = import_ofetch.ofetch.create({
baseURL,
timeout: timeout || 3e4,
headers: requestHeaders,
...useCookieAuth ? { credentials: "include" } : {},
...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)
);
}
if (onResponseError) {
onResponseError({ request, response, options });
}
}
});
}
/**
* 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
var import_zod2 = require("zod");
// src/types/supported_chains.ts
var import_chains = require("viem/chains");
var SUPPORTED_CHAINS = {
[import_chains.mainnet.id]: import_chains.mainnet,
[import_chains.base.id]: import_chains.base,
[import_chains.anvil.id]: import_chains.anvil
};
// src/types/kms_info.ts
var KmsInfoBaseSchema = import_zod2.z.object({
id: import_zod2.z.string(),
slug: import_zod2.z.string().nullable(),
url: import_zod2.z.string(),
version: import_zod2.z.string(),
chain_id: import_zod2.z.number().nullable(),
kms_contract_address: import_zod2.z.string().nullable().transform((val) => val),
gateway_app_id: import_zod2.z.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
var import_zod3 = require("zod");
var VmInfoSchema = import_zod3.z.object({
id: import_zod3.z.string(),
name: import_zod3.z.string(),
status: import_zod3.z.string(),
uptime: import_zod3.z.string(),
app_url: import_zod3.z.string().nullable(),
app_id: import_zod3.z.string(),
instance_id: import_zod3.z.string().nullable(),
configuration: import_zod3.z.any().optional(),
// TODO: add VmConfiguration schema if needed
exited_at: import_zod3.z.string().nullable(),
boot_progress: import_zod3.z.string().nullable(),
boot_error: import_zod3.z.string().nullable(),
shutdown_progress: import_zod3.z.string().nullable(),
image_version: import_zod3.z.string().nullable()
});
var ManagedUserSchema = import_zod3.z.object({
id: import_zod3.z.number(),
username: import_zod3.z.string()
});
var CvmNodeSchema = import_zod3.z.object({
id: import_zod3.z.number(),
name: import_zod3.z.string(),
region_identifier: import_zod3.z.string().optional()
});
var CvmNetworkUrlsSchema = import_zod3.z.object({
app: import_zod3.z.string(),
instance: import_zod3.z.string()
});
var CvmInfoSchema = import_zod3.z.object({
hosted: VmInfoSchema,
name: import_zod3.z.string(),
managed_user: ManagedUserSchema.optional().nullable(),
node: CvmNodeSchema.optional().nullable(),
listed: import_zod3.z.boolean().default(false),
status: import_zod3.z.string(),
in_progress: import_zod3.z.boolean().default(false),
dapp_dashboard_url: import_zod3.z.string().nullable(),
syslog_endpoint: import_zod3.z.string().nullable(),
allow_upgrade: import_zod3.z.boolean().default(false),
project_id: import_zod3.z.string().nullable(),
// HashedId is represented as string in JS
project_type: import_zod3.z.string().nullable(),
billing_period: import_zod3.z.string().nullable(),
kms_info: KmsInfoSchema.nullable(),
vcpu: import_zod3.z.number().nullable(),
memory: import_zod3.z.number().nullable(),
disk_size: import_zod3.z.number().nullable(),
gateway_domain: import_zod3.z.string().nullable(),
public_urls: import_zod3.z.array(CvmNetworkUrlsSchema)
}).partial();
var CvmLegacyDetailSchema = import_zod3.z.object({
id: import_zod3.z.number(),
name: import_zod3.z.string(),
status: import_zod3.z.string(),
in_progress: import_zod3.z.boolean(),
teepod_id: import_zod3.z.number().nullable(),
teepod: CvmNodeSchema,
app_id: import_zod3.z.string(),
vm_uuid: import_zod3.z.string().nullable(),
instance_id: import_zod3.z.string().nullable(),
vcpu: import_zod3.z.number().nullable(),
memory: import_zod3.z.number().nullable(),
disk_size: import_zod3.z.number().nullable(),
base_image: import_zod3.z.string(),
encrypted_env_pubkey: import_zod3.z.string().nullable(),
listed: import_zod3.z.boolean(),
project_id: import_zod3.z.string().nullable(),
project_type: import_zod3.z.string().nullable(),
public_sysinfo: import_zod3.z.boolean(),
public_logs: import_zod3.z.boolean(),
dapp_dashboard_url: import_zod3.z.string().nullable(),
syslog_endpoint: import_zod3.z.string().nullable(),
kms_info: KmsInfoSchema.nullable(),
contract_address: import_zod3.z.string().nullable(),
deployer_address: import_zod3.z.string().nullable(),
scheduled_delete_at: import_zod3.z.string().nullable(),
public_urls: import_zod3.z.array(CvmNetworkUrlsSchema),
gateway_domain: import_zod3.z.string().nullable()
});
// src/actions/get_current_user.ts
var import_zod4 = require("zod");
// src/utils/index.ts
var import_encrypt_env_vars = require("@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
var import_viem = require("viem");
function asHex(value) {
if (typeof value === "string") {
if (value.startsWith("0x") && (0, import_viem.isHex)(value)) {
return value;
} else if ((0, import_viem.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
var import_viem2 = require("viem");
var import_accounts = require("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 = (0, import_accounts.privateKeyToAccount)(privateKey);
const publicClient = (0, import_viem2.createPublicClient)({
chain,
transport: (0, import_viem2.http)(rpcUrl || chain.rpcUrls.default.http[0])
});
const walletClient = (0, import_viem2.createWalletClient)({
account,
chain,
transport: (0, import_viem2.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 = (0, import_viem2.createPublicClient)({
chain,
transport: (0, import_viem2.http)(rpcUrl || chain.rpcUrls.default.http[0])
});
const walletClient = (0, import_viem2.createWalletClient)({
account: address,
chain,
transport: (0, import_viem2.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 = import_zod4.z.object({
username: import_zod4.z.string(),
email: import_zod4.z.string(),
credits: import_zod4.z.number(),
granted_credits: import_zod4.z.number(),
avatar: import_zod4.z.string(),
team_name: import_zod4.z.string(),
team_tier: import_zod4.z.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
var import_zod5 = require("zod");
var AvailableOSImageSchema = import_zod5.z.object({
name: import_zod5.z.string(),
is_dev: import_zod5.z.boolean(),
version: import_zod5.z.union([
import_zod5.z.tuple([import_zod5.z.number(), import_zod5.z.number(), import_zod5.z.number()]),
import_zod5.z.tuple([import_zod5.z.number(), import_zod5.z.number(), import_zod5.z.number(), import_zod5.z.number()])
]),
os_image_hash: import_zod5.z.string().nullable().optional()
}).passthrough();
var TeepodCapacitySchema = import_zod5.z.object({
teepod_id: import_zod5.z.number(),
name: import_zod5.z.string(),
listed: import_zod5.z.boolean(),
resource_score: import_zod5.z.number(),
remaining_vcpu: import_zod5.z.number(),
remaining_memory: import_zod5.z.number(),
remaining_cvm_slots: import_zod5.z.number(),
images: import_zod5.z.array(AvailableOSImageSchema),
support_onchain_kms: import_zod5.z.boolean().optional(),
fmspc: import_zod5.z.string().nullable().optional(),
device_id: import_zod5.z.string().nullable().optional(),
region_identifier: import_zod5.z.string().nullable().optional(),
default_kms: import_zod5.z.string().nullable().optional(),
kms_list: import_zod5.z.array(import_zod5.z.string()).default([])
}).passthrough();
var ResourceThresholdSchema = import_zod5.z.object({
max_instances: import_zod5.z.number().nullable().optional(),
max_vcpu: import_zod5.z.number().nullable().optional(),
max_memory: import_zod5.z.number().nullable().optional(),
max_disk: import_zod5.z.number().nullable().optional()
}).passthrough();
var AvailableNodesSchema = import_zod5.z.object({
tier: import_zod5.z.string(),
// TeamTier is string enum
capacity: ResourceThresholdSchema,
nodes: import_zod5.z.array(TeepodCapacitySchema),
kms_list: import_zod5.z.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
var import_zod6 = require("zod");
var ProvisionCvmSchema = import_zod6.z.object({
app_id: import_zod6.z.string().nullable().optional(),
app_env_encrypt_pubkey: import_zod6.z.string().nullable().optional(),
compose_hash: import_zod6.z.string(),
fmspc: import_zod6.z.string().nullable().optional(),
device_id: import_zod6.z.string().nullable().optional(),
os_image_hash: import_zod6.z.string().nullable().optional(),
node_id: import_zod6.z.number().nullable().optional(),
// Transformed from teepod_id in response
kms_id: import_zod6.z.string().nullable().optional()
}).passthrough();
var ProvisionCvmRequestSchema = import_zod6.z.object({
node_id: import_zod6.z.number().optional(),
// recommended
teepod_id: import_zod6.z.number().optional(),
// deprecated, for compatibility
name: import_zod6.z.string(),
image: import_zod6.z.string(),
vcpu: import_zod6.z.number(),
memory: import_zod6.z.number(),
disk_size: import_zod6.z.number(),
compose_file: import_zod6.z.object({
allowed_envs: import_zod6.z.array(import_zod6.z.string()).optional(),
pre_launch_script: import_zod6.z.string().optional(),
docker_compose_file: import_zod6.z.string().optional(),
name: import_zod6.z.string().optional(),
kms_enabled: import_zod6.z.boolean().optional(),
public_logs: import_zod6.z.boolean().optional(),
public_sysinfo: import_zod6.z.boolean().optional(),
gateway_enabled: import_zod6.z.boolean().optional(),
// recommended
tproxy_enabled: import_zod6.z.boolean().optional()
// deprecated, for compatibility
}),
listed: import_zod6.z.boolean().optional(),
instance_type: import_zod6.z.string().nullable().optional(),
kms_id: import_zod6.z.string().optional(),
env_keys: import_zod6.z.array(import_zod6.z.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
var import_zod7 = require("zod");
var CommitCvmProvisionSchema = import_zod7.z.object({
id: import_zod7.z.number(),
name: import_zod7.z.string(),
status: import_zod7.z.string(),
teepod_id: import_zod7.z.number(),
teepod: import_zod7.z.object({
id: import_zo