Set up Keyhole
Keyhole gives any MCP-capable AI agent its own Solana wallet. It runs as a small server on the same machine as the agent, keeps keys in an encrypted vault, and enforces spending limits that only you can change.
Install
Install the package globally. It adds two commands: keyhole for you and keyhole-mcp for your agent.
npm install -g keyhole-moneyCreate the vault
The vault is one encrypted file that holds every wallet key and its limits. Pick a passphrase of at least 12 characters. If you lose it, the keys inside can't be recovered, so export anything valuable first (see Export a wallet).
keyhole initThe vault is written to ~/.keyhole/vault.json with permissions set so only your user can read it.
Connect your agent
For Claude Code, one command does it:
claude mcp add keyhole \
-e KEYHOLE_PASSPHRASE='your passphrase' \
-- keyhole-mcpConfig for Claude Desktop, Cursor, Codex and other clients is on the MCP page. The server starts on devnet. Add KEYHOLE_RPC pointing at a mainnet RPC when you want real funds.
First wallet
Ask your agent something like:
you Open a wallet called scout and give me the address.
agent calling wallet_create { label: "scout" }
agent Done. Send SOL to 7Kq3bXw9RfLm2VtP8cYh4NnAe6JdGsUo1ZiQ5HrkTWpESend a small amount of SOL to that address from any wallet. The agent can now check its balance and pay within its limits. You can also open wallets yourself with keyhole create scout.
Owner CLI
Everything that changes what an agent is allowed to do sits here, not in MCP. Each command asks for your passphrase, or reads it from KEYHOLE_PASSPHRASE.
| Command | What it does |
|---|---|
keyhole init | Create the vault |
keyhole create [label] | Open a wallet yourself |
keyhole list | Every wallet with its SOL balance |
keyhole limits <wallet> | Caps and spend in the last 24 hours |
keyhole policy <wallet> … | Change limits, tokens, allowlist, freeze |
keyhole settings … | Vault-wide switches |
keyhole export <wallet> | Print the secret key for import elsewhere |
Changes are read by the running server on the next tool call. No restart needed.
Limits
Every new wallet starts with these defaults:
| Rule | Default | Change with |
|---|---|---|
| Largest single SOL send | 0.1 SOL | --max-tx 0.5 |
| SOL used in any 24 hours, Keyhole fee, network fees and token-account rent included | 0.5 SOL | --daily 2 |
| Only pay approved addresses | off | --allow <addr,addr> --allowlist-only on |
| Sign plain-text log-in messages | off | --sign on |
| Freeze all sends and signing | off | --freeze / --unfreeze |
| Tokens | none allowed | see Tokens |
keyhole policy scout --max-tx 0.25 --daily 1
keyhole policy scout --allow 9xF2pLqTzR4mWv8NcY3hKs6BdJa1GeUo7XiQ5HrnvQ7c --allowlist-only on
keyhole policy scout --freezeThe 24-hour window rolls, so a send made at 09:00 stops counting at 09:00 the next day. Each send reserves its amount before it is broadcast, so an agent firing many payments at once still stops at the cap. A send that times out keeps counting, because it may still land. Vault-wide, you can stop agents opening wallets and cap how many exist:
keyhole settings --agent-create off --max-wallets 5Tokens
No token can be sent until you give its mint a limit. This stops an agent moving something you didn't expect it to hold. Amounts are in whole tokens.
# allow USDC: up to 50 per send, 200 per day
keyhole policy scout --token EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v --token-max 50 --token-daily 200
# take it away again
keyhole policy scout --token-remove EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1vClassic SPL and Token-2022 mints both work. If the recipient has no token account yet, the wallet creates one and pays its rent (about 0.002 SOL), which counts against the SOL daily limit.
Fees
On mainnet, every send pays a 1% Keyhole fee on top of the amount, in the same transaction. The recipient always gets exactly what the agent sent.
- SOL sends: 1% in SOL. Sending 0.05 SOL costs 0.0505 SOL plus the network fee.
- Token sends: 1% in the same token. If the fee wallet has no account for that token yet, the first send pays its rent (about 0.002 SOL).
- Where it goes: the Keyhole fee wallet. What it collects is used to buy the KEY token for the treasury.
- Limits: the fee counts against your daily limits, so a 0.5 SOL daily cap covers payments and fees together.
- Devnet and local testing: no Keyhole fee.
Export a wallet
Prints the address and the base58 secret key, which Phantom, Solflare and the Solana CLI can import. Only run this in a terminal nobody else can see.
keyhole export scoutMCP tools
The agent sees eight tools: wallet_create, wallet_list, wallet_balance, wallet_limits, wallet_send_sol, wallet_send_token, wallet_sign_message and wallet_history. Inputs, outputs and errors for each are on the MCP page.
Environment
| Variable | Default | Purpose |
|---|---|---|
KEYHOLE_PASSPHRASE | none | Unlocks the vault. Required by the server. |
KEYHOLE_RPC | devnet | Solana RPC endpoint for reads and sends. |
KEYHOLE_HOME | ~/.keyhole | Vault folder. Use one per agent to keep them apart. |
Security model
What it protects against
- An agent spending more than you meant. Every limit is checked, and the amount reserved, before anything is signed. Parallel calls and timed-out sends can't push it past the cap.
- Prompt injection draining a wallet. A tricked agent can only spend within its limits, and only to allowlisted addresses if that's switched on. It has no tool that changes limits.
- Hidden SOL leaks. Network fees and token-account rent count against the SOL limit.
- Hand-edited limits. The vault and spend ledger are sealed with a key derived from your passphrase. Raising a limit in the file, unfreezing, or deleting the ledger makes Keyhole refuse to send.
- Signing a transaction disguised as a message. Message signing is off by default, accepts plain text only, and logs every signature.
- Someone copying the vault file. Keys are encrypted with AES-256-GCM, bound to their wallet, under a key derived from your passphrase with scrypt (N = 217).
What it doesn't
- An agent that can run any shell command as you. Claude Code, Cursor and Codex can run commands. If one can read your MCP config, it can read the passphrase and use the keys directly. Turn on your client's sandbox and keep
~/.keyholeand your MCP config outside it, or only give the wallet to agents that use it through MCP alone. - A compromised computer. Anyone with your passphrase and the vault file can use the keys. Keep balances small.
- Known advisories in Solana's JavaScript libraries. Keyhole pins exact versions of
@solana/web3.js1.99.0 and@solana/spl-token0.4.15. Both carry upstream advisories about parsing RPC responses. Use an RPC you trust.
Privacy tips
- Every wallet is a separate keypair with no link to the others on chain. Funding several of them from the same wallet links them, so fund each from a different source if that matters to you.
- Public RPCs can see your IP and which addresses you look up. Use your own RPC node for the most privacy.
- Memos are public on chain. Don't put anything personal in them.
- Keyhole fees go to one public fee address, so anyone can tell a wallet uses Keyhole. That doesn't reveal who owns it, or tie your wallets to each other any more than to other Keyhole users.
Troubleshooting
| Message | Fix |
|---|---|
no vault at … | Run keyhole init, or check KEYHOLE_HOME. |
wrong passphrase | The passphrase in your MCP config doesn't match the one used at init. |
over the per-transaction limit | Working as intended. Raise it with --max-tx if you want. |
has no limit set | Allow that token's mint with --token. |
| Send fails with insufficient funds | The wallet needs a little SOL for fees, even when sending tokens. |
changed outside keyhole | The vault or spend ledger was edited by hand. Restore vault.json.bak from the same folder, or create a new vault and move funds. |
message signing is off | Turn it on for that wallet with keyhole policy <wallet> --sign on. |
| Tools don't appear | Restart your client after adding the server, and check that keyhole-mcp is on your PATH. |