Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,15 @@
"open": "^10.0.0",
"ora": "^6.3.1",
"prompts": "^2.4.2",
"punycode": "^2.3.1",
"semver": "^7.6.3",
"viem": "^2.7.0",
"zod": "^3.24.1"
},
"optionalDependencies": {
"@safe-global/api-kit": "^4.0.0",
"@safe-global/protocol-kit": "^6.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/bun": "latest",
Expand Down
112 changes: 7 additions & 105 deletions cli/src/commands/allow-devices/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
import chalk from "chalk";
import inquirer from "inquirer";
import {
type Chain,
type PublicClient,
type WalletClient,
createPublicClient,
createWalletClient,
http,
} from "viem";
import { privateKeyToAccount, nonceManager } from "viem/accounts";
import {
safeGetCvmInfo,
safeGetAppDeviceAllowlist,
safeGetAvailableNodes,
safeAddDevice,
safeRemoveDevice,
Expand All @@ -27,6 +16,12 @@ import type { CommandContext } from "@/src/core/types";
import { getClient } from "@/src/lib/client";
import { printTable } from "@/src/lib/table";
import { logger } from "@/src/utils/logger";
import {
resolveAppContract,
resolvePrivateKey,
createSharedClients,
txExplorerUrl,
} from "@/src/utils/onchain";
import {
allowDevicesGroup,
allowDevicesListMeta,
Expand Down Expand Up @@ -62,15 +57,7 @@ export function isValidDeviceId(deviceId: string): boolean {
return DEVICE_ID_REGEX.test(deviceId);
}

export function txExplorerUrl(
chain: (typeof SUPPORTED_CHAINS)[keyof typeof SUPPORTED_CHAINS],
txHash: string | undefined,
): string | null {
if (!txHash) return null;
const baseUrl = chain.blockExplorers?.default?.url;
if (!baseUrl) return null;
return `${baseUrl}/tx/${txHash}`;
}
export { txExplorerUrl } from "@/src/utils/onchain";

/**
* Determine the allow-any flag value for the `allow-any` command.
Expand Down Expand Up @@ -153,33 +140,6 @@ function isExitPromptError(error: unknown): boolean {
);
}

function resolvePrivateKey(input: { privateKey?: string }): `0x${string}` {
const key = input.privateKey || process.env.PRIVATE_KEY;
if (!key) {
throw new Error(
"Private key required. Use --private-key or set PRIVATE_KEY env var.",
);
}
return (key.startsWith("0x") ? key : `0x${key}`) as `0x${string}`;
}

function createSharedClients(
chain: Chain,
privateKey: `0x${string}`,
rpcUrl?: string,
) {
const account = privateKeyToAccount(privateKey, { nonceManager });
const publicClient = createPublicClient({
chain,
transport: http(rpcUrl),
}) as unknown as PublicClient;
const walletClient = createWalletClient({
account,
chain,
transport: http(rpcUrl),
}) as unknown as WalletClient;
return { publicClient, walletClient };
}

async function resolveDeviceIdOrNodeName(
deviceInput: string,
Expand Down Expand Up @@ -225,64 +185,6 @@ async function resolveDeviceIdOrNodeName(
return normalizeDeviceId(resolvedDeviceId);
}

async function resolveAppContract(
cvmIdentifier: string,
context: CommandContext,
) {
const client = await getClient();

const infoResult = await safeGetCvmInfo(client, { id: cvmIdentifier });
if (!infoResult.success) {
context.fail(infoResult.error.message);
return null;
}

const cvm = infoResult.data;
if (!cvm) {
context.fail("CVM not found");
return null;
}

const appId = cvm.app_id;
if (!appId) {
context.fail("CVM has no app_id assigned yet.");
return null;
}

const allowlistResult = await safeGetAppDeviceAllowlist(client, { appId });
if (!allowlistResult.success) {
context.fail(allowlistResult.error.message);
return null;
}

const allowlist = allowlistResult.data;
if (!allowlist.is_onchain_kms) {
context.fail(
"This app does not use on-chain KMS. Device management requires an on-chain KMS.",
);
return null;
}

if (!allowlist.chain_id || !allowlist.app_contract_address) {
context.fail(
"Missing chain_id or app_contract_address in allowlist response.",
);
return null;
}

const chain = SUPPORTED_CHAINS[allowlist.chain_id];
if (!chain) {
context.fail(`Unsupported chain_id: ${allowlist.chain_id}`);
return null;
}

return {
chain,
chainId: allowlist.chain_id,
appContractAddress: allowlist.app_contract_address as `0x${string}`,
allowlist,
};
}

// ── list ────────────────────────────────────────────────────────────

Expand Down
66 changes: 66 additions & 0 deletions cli/src/commands/compose-hash/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { z } from "zod";
import type { CommandMeta } from "@/src/core/types";
import { jsonOption } from "@/src/core/common-flags";

const cvmArgument = {
name: "cvm",
description: "CVM identifier (UUID, app_id, instance_id, or name)",
required: true,
target: "cvm",
};

export const composeHashMeta: CommandMeta = {
name: "compose-hash",
description:
"Compute the compose hash for a CVM update",
stability: "unstable",
arguments: [cvmArgument],
options: [
{
name: "compose",
shorthand: "c",
description:
"Path to new Docker Compose file (default: docker-compose.yml)",
type: "string",
target: "compose",
group: "basic",
},
{
name: "pre-launch-script",
description: "Path to pre-launch script",
type: "string",
target: "preLaunchScript",
group: "advanced",
},
{
name: "env",
shorthand: "e",
description:
"Environment variable (KEY=VALUE) or env file path (repeatable)",
type: "string[]",
target: "env",
group: "basic",
},
jsonOption,
],
examples: [
{
name: "Get compose hash for a new compose file",
value: "phala compose-hash app_abc123 -c new-docker-compose.yml",
},
{
name: "Get compose hash with environment variables",
value: "phala compose-hash app_abc123 -c docker-compose.yml -e .env",
},
],
};

export const composeHashSchema = z.object({
cvm: z.string(),
compose: z.string().optional(),
preLaunchScript: z.string().optional(),
env: z.array(z.string()).optional(),
json: z.boolean().default(false),
});

export type ComposeHashInput = z.infer<typeof composeHashSchema>;
Loading