Custody tiers: the vault and TEE wallets
The Candle CLI holds your keys in two custody tiers. Tier 1, the vault, is self-custody: keys that live encrypted on your machine and that nothing but you can sign with. Tier 2, TEE wallets, is agent access: wallets an agent can trade from through one API key, while your vault keeps the key and a sweep home. You move a wallet from Tier 1 to Tier 2 by promoting it, and funds come back to Tier 1 whenever you choose.
These custody tiers are separate from your account plan (Free, Believer, Pro, Max), which is covered in Agent access & API keys. Every command on this page is documented flag by flag in the CLI reference.
| Tier 1: the vault | Tier 2: TEE wallets | |
|---|---|---|
| What it is for | Holding funds. Your bank | An agent trading on your behalf |
| Where the key lives | vault.enc on your machine, encrypted | Your vault, and a copy in Privy’s TEE |
| Who can sign | Only you, after unlocking a factor | The one API key the wallet is bound to, within its scopes and caps; and you, from the vault |
| Does Candle see it | Never: no API, relay or server sees a vault key | Yes: it is a linked wallet on your account |
| How funds leave | vault transfer, signed on your machine, to any address | candle transfer (agent), vault transfer (you), or a sweep home |
Which command do I need?
Section titled “Which command do I need?”| I want to | Run |
|---|---|
| Give a key TEE wallets, or move them to another key | candle tee rebind <wallets...> --to-key <key> (or --label-prefix <p> for many) |
| Trade a key’s wallets from another machine (a server that runs the bot) | candle tee signer new --key <key> on that machine, then promote with --to-key <key>; see Key signers |
| Let a key use a linked wallet you imported | The agent console’s Agents tab, signed in (keys wallets set from a key can only narrow) |
| Turn a vault key into a TEE wallet | candle vault promote --in-place <label> --sweep-to <cold key>, or --from <cold key> for a fresh one |
| Send funds out of a TEE wallet as the agent | candle transfer --to vault (or to a trusted wallet) with a Read:Write:Transfer key |
| Send funds anywhere yourself | candle vault transfer <address> --from <label> |
| Mark wallets as yours so agents can send to them | candle wallets trust <selectors...> |
| Stop an agent | candle tee disable <address>, then candle keys revoke <prefix> |
| Bring everything home | candle tee disable <address>, then candle tee sweep <address> or candle vault demote <address> (--emergency: see In an emergency) |
Tier 1: the vault
Section titled “Tier 1: the vault”The vault is one file, vault.enc, in your config directory (~/.config/candle by default; set CANDLE_CONFIG_DIR to move it, or pass -k <path> for one command). It holds a 24-word recovery root every derived key comes from, one encrypted blob per key, and one wrapped copy of the data key per factor. No environment variable and no --yes opens it: every unlock is a factor you present on a terminal.
candle vault init # one passphrase factor and a recovery root, no keys yetcandle vault new-key --chain solana --label treasury # derive a keycandle vault list --balances # every key, with SOL balances over your RPCcandle vault backup --to /Volumes/BACKUP/vault.enc # copy, then verify the copy in fullFactors
Section titled “Factors”A factor is something that opens the vault. You can hold several, and any one of them opens it.
| Factor | Status | Notes |
|---|---|---|
passphrase | Available | vault init generates eight words (about 103 bits) and shows them once; --own-passphrase takes your own, 16 characters or more |
security-key | Available | A FIDO2 key with hmac-secret and user verification (a PIN or the key’s built-in check), through the bundled candle-fido2 helper. Two keys make a recoverable pair. A security key can authorize adding another one; --device picks which key |
touch-id | Not yet released | This Mac’s Secure Enclave behind Touch ID. Needs the Apple-signed candle-enclave.app helper |
passkey | Not yet released | A synced iCloud Keychain passkey. Needs the same signed helper, on macOS 15 or later |
A security key is another way in, not a second step. Any one factor opens the vault on its own, a security key included: adding one does not make the passphrase plus the key a requirement, and anyone holding the passphrase still opens the vault without the key. There is no unlock that needs two factors together; that would be a separate proposal. Guard each factor as if it were the only one.
Touch ID and passkey are built and wait on Apple’s approval of Candle’s developer enrolment; until they ship, vault factor add touch-id and vault factor add passkey refuse with VAULT_FACTOR_UNSUPPORTED_ON_PLATFORM.
candle vault factor listcandle vault factor add security-key --label yubikey-acandle vault factor add security-key --label yubikey-b --device <id> # the second key; the first can open the vaultSetting a security key’s PIN. A key with no PIN and no built-in biometric is refused at enrolment (VAULT_UV_UNSUPPORTED), and nothing is written. Set the PIN with a tool that prompts for it, and never pass a PIN as a command-line flag: an argument lands in shell history and is visible to other users in ps.
- YubiKey:
ykman fido access change-pin. With no PIN set it creates one; leave out--pinand--new-pinso it prompts. - Other keys, any platform: Chrome’s
chrome://settings/securityKeys, then Create a PIN. - Linux: libfido2’s
fido2-token -S <device>, which prompts.fido2-token -Llists the devices.
The CLI itself never takes a PIN on its command line. It asks on a hidden prompt when a key needs one.
Keep the recovery phrase offline and separate from any factor. vault phrase show prints it once per ceremony; vault restore --phrase rebuilds a vault from it with a new passphrase. Removing a factor is not revocation: a copy of the vault made while the factor existed still opens with it, so after a compromised factor the answer is a new vault and moving the funds.
Moving funds out of the vault
Section titled “Moving funds out of the vault”candle vault transfer <address> --amount 5 --asset SOL --from treasury --rpc-url https://<your-rpc>vault transfer builds and signs on your machine, to any address, for SOL or any SPL or Token-2022 mint. It shows the decoded transfer and the fee, makes you type the destination’s last six characters, then asks for your factor again. There is no --yes.
Tier 2: TEE wallets
Section titled “Tier 2: TEE wallets”A TEE wallet is a Solana or Hood wallet an agent can use (Hood: see Hood TEE wallets). Its key is held in a trusted execution environment (Privy’s TEE) and bound to exactly one of your API keys, so an agent holding that API key can trade from it without your vault being unlocked. The server always admits a TEE wallet on trades and the relay. Swaps and self-signed launch are admitted only where the deployment enables them (launch also needs the wallet to allow it), LP only where the deployment enables it and the key has lp:write, and candle transfer only for a key that carries transfer:bound. Everything else refuses.
Three things are true of every TEE wallet:
- Your vault still holds its key. You can always move its funds yourself with
vault transfer, or sweep it home, with no help from Candle’s servers. - It has a pinned vault: one cold vault key that is its sweep destination, fixed when it was promoted. Sweeps, demotes and
candle transfer --to vaultall go there. - Trade it from the machine that holds its signer. Every TEE transaction is approved by the signer that owns the wallet. Without a key signer, that is a per-wallet relay signer in the promoting machine’s keychain, and it does not move, even when the wallet is rebound to another API key. A wallet on its key’s signer trades from the machine that holds that signer (see Key signers).
A copy of a promoted key stays in Privy’s TEE permanently. Demoting stops the agent and brings the funds home, but it does not delete that copy, which is why an in-place or batch promote asks you to acknowledge it and why a swept fresh TEE wallet is retired rather than reused.
Promoting a wallet
Section titled “Promoting a wallet”Promoting is how a wallet moves from Tier 1 to Tier 2. There are two ways, and they differ in which address the agent gets.
--in-place | --from | |
|---|---|---|
| Which address | An existing vault key, at its own address | A fresh key, derived for this purpose |
| Its funds and history | Kept: the agent trades the address as it is | None: you fund it explicitly |
| Where the key is derived | The ordinary vault path wallet apps scan | The vault’s TEE branch (m/44'/501'/n'/1'), which wallet apps do not scan |
| Pinned vault | --sweep-to <cold key> | The --from <cold key> itself |
| In a restored vault | Refused (every restored key is marked exposureUnknown, for good) | Refused (a restored vault never allocates new keys) |
In place, for an address that already holds what the agent should use:
candle vault promote --in-place trading-1 --sweep-to treasury --rpc-url https://<your-rpc>Before anything is written the CLI shows the wallet’s holdings, checks over your RPC whether it is a token mint, freeze, program-upgrade or stake authority (nine requests; public endpoints refuse the token scans; multisig membership is not checked), prints the Candle account, API key and API that will control it, read live, and asks for the address’s last six characters and the word confirm.
Fresh, for a clean agent wallet you fund on purpose:
candle vault promote --from treasury --label scalpercandle vault fund <tee-address> --amount 1 --asset SOL --rpc-url https://<your-rpc>--label names the new TEE wallet; without it the wallet is tee-<n>. vault fund to a TEE wallet always signs from that wallet’s pinned vault key (here treasury), so it takes no --from: --from applies to an external wallet only, and passing it for a TEE wallet is refused.
Many at once. vault promote-batch promotes 2 to 256 vault keys in place under one unlock and one confirm. The file is one <label> <destination> per line, or a CSV with label and sweep_to columns. Every row is checked against the whole set first, the batch checks your account has linked-wallet room for all of them, each key is committed on its own, and re-running the same file resumes an interrupted batch.
candle vault promote-batch --pairs-from ./promote-plan.csv --rpc-url https://<your-rpc>Choosing the API key. A promote binds the wallet to the key the CLI is using. --to-key <prefix|label>, on promote and promote-batch, binds it to another key on your account instead. The named key’s secret never touches the machine. What happens next depends on whether the named key has a key signer:
- No key signer: the import runs under the calling key, then the wallet is rebound to the one you named. The per-wallet signer stays on this machine, so trade the wallet from here.
- A key signer: before the unlock the CLI reads that signer and checks it against the fingerprint this machine pinned. The first time, it asks you to type the full fingerprint the trading machine printed. The import then puts the wallet on that signer and binds it to the named key in one call, and this machine stores no signer for it. The wallet trades from the machine holding the key signer.
Promoted wallets are linked wallets, so they count against your plan’s linked-wallet cap (see Tiers and caps).
Moving funds out of a TEE wallet
Section titled “Moving funds out of a TEE wallet”There are three ways, for three situations.
| Command | Who uses it | Destinations | Signed by |
|---|---|---|---|
candle transfer | An agent, through the wallet’s bound key | The wallet’s pinned vault, or a wallet on your account that you linked while signed in or marked trusted | Privy’s TEE, after this machine approves the relay |
candle vault transfer --from <wallet> | You | Any address | Your vault, on your machine |
candle tee sweep / candle vault demote | You | The pinned vault, everything | Your vault, on your machine |
candle transfer needs the bound key to be Read:Write:Transfer (see below). Widen the bound key with candle keys access <prefix> --access read-write-transfer, or mint a Read:Write:Transfer key and candle tee rebind the wallet to it. To the pinned vault it can send any token and max is allowed; no spend cap or volume window applies, but a single SOL transfer is limited to the server’s backstop ceiling (50 SOL by default), so a larger balance takes more than one transfer. To a linked wallet it sends base assets (SOL, USDC, CNDL, USDG), and the bound key must carry a per-asset spend cap for that asset and a USD --tx-limit; the amount is valued and counted against that window, shared with the key’s trades. A key without both is refused for linked destinations and can still send to the vault. The destination and its kind are shown before you confirm.
candle transfer --to vault --asset SOL --amount max --wallet trading-1candle transfer --to treasury-2 --asset USDC --amount 250 --wallet trading-1candle vault transfer from a promoted wallet works exactly as from any vault key, with three extra checks: it refuses while a sweep is pending, it warns before you confirm when the wallet is still enabled (an agent may be trading it at the same moment), and it records the finalized transfer in Candle’s history when the key has activity:write.
candle vault transfer <address> --amount 5 --asset SOL --from trading-1 --rpc-url https://<your-rpc>Sweeping home. tee sweep moves everything a TEE wallet holds to its pinned vault, tokens first and SOL last, and re-reads the balances after finality before it reports the wallet empty. vault demote stops the agent first and then sweeps. Open Meteora DAMM v2 LP positions are closed first, through a server-built close the CLI verifies before signing.
candle vault demote <tee-address> --rpc-url https://<your-rpc>Trusted wallets
Section titled “Trusted wallets”A wallet you linked while signed in is trusted, so an agent’s candle transfer can send to it; a wallet an API key imported is not until you mark it. wallet trust runs as the account owner over the device token, shows a preview, and asks you to type confirm. What trust allows is in Trusted wallets.
candle wallet trust 'treasury-*' 'dest-*' # a label, address, id, or prefix ending in *; quote it for the shellcandle wallet untrust dest-3In an emergency
Section titled “In an emergency”If an agent misbehaves, its API key leaks, or Candle’s API is unreachable, you can stop it and recover the funds with only your vault.
-
Stop the agent.
candle tee disable <tee-address>records the stop locally first, then asks the server. It exits 0 only on a verified stop and 3 while the stop is pending. With no API key or no answer from the server it exits 1: the stop is recorded locally but not enforced, so go straight to the sweep. -
Cut off the key.
candle keys revoke <prefix>, or revoke it in the agent console. -
Sweep home without depending on the API. With
--emergency, a sweep does not need Candle’s API: if the API is unreachable or the key is revoked, it sweeps with only the local key and the pinned vault. If the API does answer, it still reads the wallet’s state and refuses a wallet that is still enabled, so run step 1 first.vault demote --emergencytries the stop request before it sweeps.Terminal window candle tee sweep <tee-address> --rpc-url https://<your-rpc> --emergencycandle vault demote <tee-address> --rpc-url https://<your-rpc> --emergencyAn emergency sweep moves an open LP position’s NFT to the vault instead of closing it; unwind it later from a fresh TEE wallet.
-
Start again with a new wallet. A swept address is retired, because its key is still in the TEE.
tee rebind (below) is not incident response: after a leak, the path is stop, sweep, new wallet.
Hood TEE wallets
Section titled “Hood TEE wallets”An EVM vault key (vault new-key --chain evm) can be promoted into a TEE wallet on Hood (chain id 4663), with the same two modes and the same safeguards as on Solana. Its pinned vault is a cold EVM vault key. Funding, sweeping and vault transfer read over --rpc-url, else CANDLE_EVM_RPC_URL, else the built-in Hood RPC, and the host is printed before the first read.
candle vault promote --in-place hood-1 --sweep-to hood-cold # an existing EVM vault key, at its own addresscandle vault promote --from hood-cold # a fresh key on m/44'/60'/n'/1'/0'candle vault fund <0x tee-address> --amount 0.02 --asset ETH # or --asset USDG, from the pinned EVM vault keyThe fresh branch m/44'/60'/n'/1'/0' is hardened at every level, which no wallet app scans. The first EVM TEE wallet moves the vault file to version 4, which a CLI older than this one refuses to open; vault restore --phrase --evm-tee-count <k> with k of 1 or more does the same.
The sealed EVM record. An EVM RPC cannot list what tokens a wallet holds, so the vault keeps a hint file beside itself: vault.evm-record.sealed (for -k <path>, the path minus .enc, plus .evm-record.sealed). Every promote writes its wallet’s start height to it, and a trade leg that lands on this machine adds the token it touched, without unlocking the vault. Each line is encrypted to a key only the unlocked vault can read, so the file shows no addresses. A lost record loses the hint, never the funds.
vault backupwrites the record beside the copy (<copy minus .enc>.evm-record.sealed), copying only the lines that decrypt and reporting how many it dropped. It refuses if that path already exists.vault verify-backupadds a ninth check: every line of the record beside the copy decrypts under the copy’s key. A copy with no record beside it passes and says so.- To bring a backup’s record back, move the backup file and its record together to the vault path you will open, or open the backup itself with
-k. A phrase restore never brings the record. vault restorerefuses while a vault exists at the path, and names its record path too: move both aside. A record left behind by an earlier vault is renamed<record path>.orphaned-<UTC timestamp>(never deleted) by the write that creates the new vault’s record key.
Sweeping home. tee sweep <0x address> and vault demote <0x address> send every token the sweep finds, then ETH last as the balance minus gas, to the pinned vault. The tokens come from USDG and WETH, the sealed record, the server’s traded-token list (not under --emergency), every --token <0x...> you pass, and the ERC-20 Transfer logs to the wallet from its recorded start height or --from-block <n>. The output names every source that ran. A token received before the start height needs --token.
candle tee sweep <0x tee-address>candle tee sweep <0x tee-address> --emergency --from-block 1200000 --token 0x...- A wallet with tokens and no ETH for gas is refused with
EVM_SWEEP_NEEDS_GAS, and the output prints thevault fundcommand. - An ordinary sweep, and
vault transfer --froma Hood TEE wallet, first read whether a sequenced trade holds the wallet’s nonce. They refuse while it does (WALLET_BUSY, with the operation id) and when that cannot be read (WALLET_LOCK_UNKNOWN). - A wallet with no linked-wallet id (never enabled, or a restored entry left stranded) has no server row. An ordinary sweep refuses with
WALLET_LOCK_UNKNOWN.tee sweep --emergencymoves the funds and exits 3; it does not recordsweptAtor retire the entry.vault demoteof that wallet takes the same emergency path, so it exits 3 as well. --emergencymakes no Candle API call. It compares the chain’slatestandpendingnonce: when a transaction is in flight it signs at that nonce and says it may replace it, which is not guaranteed if that transaction paid a higher fee.- After a phrase restore there is no record on the new machine: pass
--from-block(the promote height, or any earlier Hood block) or--token. Without either, the sweep says the traded tokens were not scanned and does not exit 0. - Exit 0 means the wallet was observed empty (ETH at most the gas a final transfer could not move) and every source the sweep needed ran.
Rebinding a wallet to another key
Section titled “Rebinding a wallet to another key”candle tee rebind moves TEE wallets to another API key on the same account. The funds do not move. It is owner only (device token), never opens the vault, and shows a preview before you type confirm. candle tee rebinds [wallet] lists every rebind with time, from, to and the device that did it.
What happens to the signer depends on the key you move the wallets to:
- It has no key signer: only the binding moves. Each wallet keeps its signer and trades from the machine that holds it: the machine that promoted it, or, for a wallet already on a key signer, the machine holding that key signer. The preview names which.
- It has a key signer: the wallet’s owner moves to that signer together with the binding, in one step. The machine you run the rebind on signs each owner change with the signer that owns the wallet now, so run it on that machine. If it does not hold that signer, the rebind refuses before
confirmand names the machine that does.
candle tee rebind trading-1 trading-2 --to-key agent-bcandle tee rebind --label-prefix tr- --to-key agent-ccandle tee rebinds trading-1Key signers: trading from another machine
Section titled “Key signers: trading from another machine”Without a key signer, a TEE wallet trades only from the machine that promoted it, because that machine holds its signer. A key signer lets a bot on another machine (an always-on server) trade a key’s wallets while the vault stays on your own machine. The key signer is generated on the machine that will trade and never leaves it.
-
On the trading machine, with that key’s API key configured and no device token:
Terminal window candle tee signer new --key tr-2It prints the signer’s fingerprint (
CNDL-XXXX-XXXX-XXXX), an approval link and a code, and waits. -
The account owner approves, from the link or from a machine with the device token, by typing the full fingerprint the trading machine printed:
Terminal window candle keys signer approve ABCD-EFGH --key tr-2 -
On the vault machine, promote onto the key. The first time, type the same full fingerprint; the CLI pins it and refuses a different signer later until you confirm the new one.
Terminal window candle vault promote-batch --pairs-from ./plan.csv --to-key tr-2The wallets trade from the trading machine at once.
Existing wallets keep trading from the machine that holds their current owner until candle keys signer move runs there. Approving a key signer changes no wallet’s owner. Wallets promoted before it keep their per-wallet signer, and wallets on an earlier signer of the key keep that one:
candle keys signer move tr-2Run it on the machine holding the old signer. It signs each owner change there and moves every wallet it can onto the key’s signer. It can be run again: a wallet whose owner already moved completes without a new signature. It deletes an old signer from this machine only after the server shows no wallet left on it, and it never deletes the key’s active signer. For a revoked key, move its wallets to a live key with --to-key <key> and the device token. When the live key has a signer, the first time you type its full fingerprint, as for vault promote --to-key. Neither --to-key nor tee rebind signs an owner change to a signer other than the one you confirmed: if the key’s signer changes before the commit, nothing is signed and the command asks again next run.
A key with no signer is not a break. tee rebind and vault promote --to-key onto it work exactly as before.
Keep these in mind:
- Do not log the trading machine in. A machine with a device token and a key signer can approve its own signer request, which removes the owner’s approval.
tee signer newandcandle doctorwarn when both are present. - One trading machine per key. Running
tee signer newagain for the same key on a machine whose earlier signer still owns wallets is refused and names the count;--forceadds the new signer and keeps the old one untilkeys signer movehas moved its wallets. Trading from two machines means two keys. --out <path>is opt-in and weaker. It also writes the signer as a plaintext PEM (mode 0600) for an SDK process that cannot read the secret store. The secret store still holds it.- If the trading machine is lost, its wallets cannot move to a new signer. Recover with
tee disable, thentee sweep, then a fresh promote. Avault promotewallet is derived from the vault, so the sweep recovers its funds; atee newwallet is swept only from thetee-wallets.encon the machine that made it. - On a server, the signer lives in the CLI’s secret store. Without a keychain that is
credentials.enc, opened withCANDLE_KEYRING_PASSPHRASE; under systemd give it throughLoadCredential=or a file only the service can read, not an exported shell variable.candle doctorreports a signer this machine cannot open. Run a bot on a headless machine covers systemd, launchd and SSH, and SDK: trade with a key signer PEM covers an SDK bot using--out.
API keys: the three access levels
Section titled “API keys: the three access levels”Every agent uses an API key, and a key’s access level (Read, Read:Write or Read:Write:Transfer) decides what it can do. Only Read:Write:Transfer can move funds out of the TEE wallet it is bound to, and a device login cannot mint one yet. Change an existing key’s level in place with candle keys access (below). The levels, their scopes, custom scope sets, --tx-limit and the active key limit are in Access levels.
candle keys create --access read-write-transfer --label rebalancerEach key is an agent profile with its own wallet set: candle keys wallets <prefix> shows it, keys wallets set replaces it, and keys wallets scope <prefix> --scope selected|all limits it to assigned wallets. From the CLI these run under the API key, which needs launch:write; under a key, set can only narrow the set and --scope all is refused with LOOSEN_REQUIRES_SESSION. Widen it in the agent console.
Changing a key’s level in place. candle keys access <prefix|label> --access <level> moves a key between the three levels and keeps its prefix, secret, wallets, caps and --tx-limit. It shows a preview first. Widening needs the device token and the key’s prefix typed back at a terminal; nothing else supplies it. Narrowing asks for y, and --yes skips the prompt. candle keys access self --access <level> lets a key narrow its own access, never widen it. Widening to Read:Write:Transfer lets the key move its TEE wallets’ funds to their vaults at once; sending to a linked wallet still needs a per-asset spend cap and a --tx-limit on the key. candle keys access <prefix> --history lists every change with the device or key that made it.
Your RPC endpoint
Section titled “Your RPC endpoint”Vault and TEE commands read Solana over an RPC endpoint you choose, never over Candle’s servers, so those reads do not tell Candle your vault addresses. (Candle does know each promoted wallet’s pinned vault, because the promote records it.)
vault list, vault promote-batch, tee status and tee sweep take --rpc-url or fall back to CANDLE_SOLANA_RPC_URL, which you can set once in your shell profile:
export CANDLE_SOLANA_RPC_URL="https://mainnet.helius-rpc.com/?api-key=<your-key>"vault transfer, vault fund, vault demote and vault promote --in-place ignore that variable and need --rpc-url on every run.
vault list --balances sends every matched address to that one endpoint together, which links them for whoever runs it.
Next steps
Section titled “Next steps”- CLI reference: every command and flag.
- Agent access & API keys: plans, scopes and limits on the server side.
- Agent wallets & spend limits: linked wallets, caps and profiles.
- Verify a Candle release: check a download before you trust it with keys.