Agent-safe signing · Devnet · ~10 minutes

Give an AI agent a Solana wallet it cannot drain

Most agents run with a private key in an environment variable. This walks through the alternative: a session key bounded by a policy the root key signed, connected to Claude or Cursor over MCP, or dropped into Solana Agent Kit. The agent proposes; the policy decides; the root never comes online.

Before you start. Coldstar is beta software and this quickstart is devnet-only. The policy engine and signing core are in scope for a planned independent audit. Read the posture note before you put anything of value behind it.

1Install

npm install coldstar-agent-signer @solana/web3.js tweetnacl

The package is MIT-licensed and published from github.com/ExpertVagabond/coldstar-agent-signer. It exports ColdstarWallet, the policy engine, and two commands: coldstar-sign-policy for the cold side and coldstar-signer-mcp for the agent side.

2Write the policy

A short JSON file. Rules are evaluated in this order, first match wins: blocklist, program allowlist, escalate threshold, per-transaction limit, recipient allowlist, daily cap, then auto-sign.

{
  "version": 1,
  "limits": { "perTxSol": 0.05, "dailySol": 0.2 },
  "allowPrograms": [
    "11111111111111111111111111111111",              // System program (SOL transfers)
    "ComputeBudget111111111111111111111111111111"   // most SDKs prepend a priority-fee instruction
  ],
  "allowRecipients": ["<address the agent may pay>"],
  "allowTokens": ["SOL"],
  "blockRecipients": ["<address that must never receive funds>"],
  "escalateAboveSol": 0.05
}

Keep the program allowlist short. A transfer through an allowlisted program such as Jupiter cannot be statically decoded to an amount, so allowlisting a program means trusting what it does internally. Simulation-based accounting is available opt-in (COLDSTAR_SIMULATE=1) and tightens this; it does not remove it.

3Have the root sign it (cold side)

This is the step that makes the model real. The root key signs the policy for one session public key, with an expiry. Run it on the offline machine; the output is plain JSON that crosses the gap by file or QR. The root is not needed again until the policy changes or the grant expires.

# on the air-gapped machine; root.json never leaves it
coldstar-sign-policy --root /media/cold/root.json --policy coldstar.policy.json \
  --session <session public key> --expires 7d > envelope.json

On the online host, the signer verifies the signature, that the envelope names the session key it holds, and the expiry, and refuses to start otherwise. Pin the root's public key too; without it, any key could have "signed" the policy.

Trying it on one laptop? Generate a throwaway root with solana-keygen new -o root.json, or run the Solana Agent Kit starter, whose npm run sign-policy does this with a clearly labelled demo root.

4Connect the agent

Claude Desktop · Claude Code · Cursor (MCP)

Add the server to your MCP client's config. The model gets five tools and never a key: status, verdict, sign, sign-and-send, and transfer SOL. Every outcome comes back as data (signed, escalated, rejected, send_failed) so the model reads the reason and stops instead of retrying.

{
  "mcpServers": {
    "coldstar": {
      "command": "npx",
      "args": ["-y", "-p", "coldstar-agent-signer", "coldstar-signer-mcp"],
      "env": {
        "RPC_URL": "https://api.devnet.solana.com",
        "COLDSTAR_POLICY": "/abs/path/envelope.json",
        "COLDSTAR_ROOT_PUBKEY": "<root public key>",
        "COLDSTAR_REQUIRE_ENVELOPE": "1",
        "COLDSTAR_SESSION_KEYFILE": "/abs/path/session.json",
        "COLDSTAR_LEDGER": "/abs/path/ledger.json"
      }
    }
  }
}
Solana Agent Kit

ColdstarWallet implements the kit's BaseWallet, so it replaces KeypairWallet and every plugin keeps working. Use the kit's signOnly mode and broadcast yourself; the kit's default send path signs each transaction twice, which a metering wallet correctly counts twice.

import { SolanaAgentKit } from "solana-agent-kit";
import TokenPlugin from "@solana-agent-kit/plugin-token";
import { ColdstarWallet, FileSpendLedger } from "coldstar-agent-signer";

const wallet = ColdstarWallet.fromEnvelope({
  envelope: JSON.parse(fs.readFileSync("envelope.json", "utf8")),
  expectedRoot: process.env.COLDSTAR_ROOT_PUBKEY,
  session,                                        // the Keypair the root authorised; disposable
  rpcUrl: RPC_URL,
  ledger: new FileSpendLedger(".coldstar-ledger.json"),  // daily cap survives restarts
  onEscalate: async (tx, reason) => null,        // hand tx to the cold device; return it signed, or null
});
const agent = new SolanaAgentKit(wallet, RPC_URL, { signOnly: true }).use(TokenPlugin);

5Watch the three outcomes

AUTO_SIGN

Under the per-transaction limit and daily cap, to an allowlisted recipient through an allowlisted program. The session key signs. Latency is a local policy check.

ESCALATE

Over the threshold, or to an address the policy has not seen. The unsigned transaction goes to a human on the offline device as a QR code. Nothing online can produce that signature.

REJECT

Blocklisted recipient. The prompt-injected agent asking to send everything to an attacker gets a policy error. No signature is ever produced.

Ask the agent to send a small amount to the allowed address and it will. Ask it to send everything to the blocked address and watch the tool call fail closed. That second request is the whole reason this exists.

Frequently asked questions

Does the agent ever see a private key?

No. The agent holds a session key bounded by a policy the root signed; the root stays offline and only ever signs the policy. Through MCP the model does not even see the session key, only signed transactions or a refusal.

What happens when the agent tries to pay an address that is not allowed?

An address missing from the allowlist escalates to a human on the cold device. An address on the blocklist is rejected with no signature.

Is this ready for mainnet?

Not yet. Beta, pre-audit. Devnet only, short program allowlist, read the posture note.

How does this compare to Turnkey, Privy, or Coinbase's agent wallets?

Those keep the key in a vendor's enclave and evaluate policy there. Coldstar keeps the root on your own offline drive and evaluates policy on your machine. Each has real trade-offs; we wrote them up honestly in the agent-wallet comparison.

Read the code first

Security software you cannot read is a promise. This one is 80 tests and about a thousand lines you can.

GitHubnpmWhy this design