On-chain KMS uses a smart contract on Ethereum or Base as the key management layer. Only the contract owner can authorize compose file changes — enabling multisig, timelock, or DAO governance.
For a conceptual overview, see Cloud vs On-Chain KMS.
PHALA vs On-Chain KMS
| Feature | PHALA KMS | On-Chain KMS |
|---|
| Key management | Centralized Phala service | Smart contract on-chain |
| Compose updates | Direct API call | Two-phase: register on-chain, then API |
| Cost | Free | Gas fees |
| Chain | None | Ethereum or Base |
Deploy with CLI
The CLI handles the full on-chain flow automatically when you provide a private key:
phala deploy \
-c docker-compose.yml \
-e .env \
--kms base \
--private-key <key> \
--rpc-url <url>
This provisions the CVM, deploys the DstackApp contract, registers the compose hash and device on-chain, and starts the CVM.
Update with CLI
Single Owner
If you hold the contract owner’s private key:
phala deploy --cvm-id app_abc123 \
-c docker-compose.yml \
--private-key <key> \
--rpc-url <url>
The CLI detects the compose hash change, registers it on-chain, and completes the update.
Multisig Owner (Two-Phase)
When the contract owner is a multisig wallet (e.g., Gnosis Safe), the on-chain transaction requires multiple signatures. The CLI supports a prepare → approve → commit flow:
Phase 1 — Prepare:
phala deploy --cvm-id app_abc123 \
--prepare-only \
-c docker-compose.yml \
-e .env
Output:
Compose Hash: 0xff22c67f...
App ID: 09cef33ca9...
Chain: Base (ID: 8453)
Contract: https://basescan.org/address/0x09cef33c...
Commit Token: a5732e36-...
Commit URL: https://cloud.phala.com/my-team/cvms/.../confirm-update?token=...
API Commit URL: https://cloud-api.phala.com/api/v1/cvms/.../commit-update?token=... (POST)
On-chain Status:
Compose Hash: NOT registered
Device ID: registered
Multisig Approval:
Use the compose hash to propose addComposeHash(bytes32) on the DstackApp contract through your multisig wallet.
Phase 2 — Commit:
phala deploy --cvm-id app_abc123 \
--commit \
--token <commit-token>
Or commit via the API directly:
curl -X POST 'https://cloud-api.phala.com/api/v1/cvms/<uuid>/commit-update?token=<token>'
Or use the Confirm Update page in the dashboard (link in --prepare-only output).
The commit token is valid for 7 days. A new --prepare-only request invalidates any previous token for the same CVM.
Webhook Notifications
Configure webhooks to receive cvm.update.pending_approval events when a prepare-only update is created. The payload includes the compose hash, commit token, and commit URL for CI/CD or notification integration.
Deploy with SDK
For programmatic deployments, use the @phala/cloud SDK:
Step 1: Provision
import { createClient } from "@phala/cloud";
const client = createClient();
const provision = await client.provisionCvm({
name: "my-app",
compose_file: {
docker_compose_file: composeYaml,
allowed_envs: ["API_KEY"],
},
kms: "BASE",
});
Step 2: Deploy Contract
import { deployAppAuth } from "@phala/cloud";
const deployed = await deployAppAuth({
chain: provision.kms_info.chain,
rpcUrl: "https://...",
kmsContractAddress: provision.kms_info.kms_contract_address,
privateKey: "0x...",
deviceId: provision.device_id,
composeHash: provision.compose_hash,
});
Step 3: Commit
import { encryptEnvVars, parseEnvVars } from "@phala/cloud";
const { public_key } = await client.getAppEnvEncryptPubKey({
kms: provision.kms_info.slug,
app_id: deployed.appId,
});
const encrypted = await encryptEnvVars(parseEnvVars("API_KEY=secret"), public_key);
await client.commitCvmProvision({
app_id: deployed.appId,
compose_hash: provision.compose_hash,
encrypted_env: encrypted,
kms_id: provision.kms_info.slug,
contract_address: deployed.appAuthAddress,
deployer_address: deployed.deployer,
});
Update with SDK
Two-Phase Update
import { patchCvm, confirmCvmPatch, addComposeHash } from "@phala/cloud";
// Phase 1
const result = await patchCvm(client, {
id: "my-app",
docker_compose_file: newYaml,
});
if (result.requiresOnChainHash) {
// Register on-chain
const tx = await addComposeHash({
chain: result.kmsInfo.chain,
appId: result.appId as `0x${string}`,
composeHash: result.composeHash,
privateKey: "0x...",
});
// Phase 2
await confirmCvmPatch(client, {
id: "my-app",
composeHash: result.composeHash,
transactionHash: tx.transactionHash,
});
}
Prepare-Only (for Multisig)
const result = await patchCvm(client, {
id: "my-app",
docker_compose_file: newYaml,
prepareOnly: true,
});
if (result.requiresOnChainHash) {
console.log("Compose hash:", result.composeHash);
console.log("Commit token:", result.commitToken);
console.log("On-chain status:", result.onchainStatus);
// → Hand off to multisig for approval
}
Device Management
On-chain KMS uses a device allowlist in the smart contract. The CLI handles device registration automatically. For programmatic control:
import { addDevice, removeDevice, setAllowAnyDevice, checkDeviceAllowed } from "@phala/cloud";
// Add a device
await addDevice({ chain: base, appAddress: "0x...", deviceId: "0x...", privateKey: "0x..." });
// Check if allowed
const allowed = await checkDeviceAllowed({ chain: base, appAddress: "0x...", deviceId: "0x..." });
// Allow any device (bypass allowlist)
await setAllowAnyDevice({ chain: base, appAddress: "0x...", allow: true, privateKey: "0x..." });
checkOnChainPrerequisites
Batch-check device and compose hash registration in a single RPC call:
import { checkOnChainPrerequisites } from "@phala/cloud";
const prereqs = await checkOnChainPrerequisites({
chain: base,
appAddress: "0x...",
deviceId: "0x...",
composeHash: "0x...",
});
// prereqs.deviceAllowed, prereqs.composeHashAllowed