From chaingpt
Gives Claude Code its own EVM wallet with admin-defined policies enforced at the tool layer, preventing prompt injection-based theft. Features encrypted keystore and policy gate.
How this skill is triggered — by the user, by Claude, or both
Slash command
/chaingpt:agent-walletThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
The agent has its own EOA wallet on every EVM chain it supports. The admin (you, in your shell) sets policies that the agent cannot violate or revoke — even if a malicious prompt convinces the LLM to try.
The agent has its own EOA wallet on every EVM chain it supports. The admin (you, in your shell) sets policies that the agent cannot violate or revoke — even if a malicious prompt convinces the LLM to try.
The attacker's goal: prompt-inject the agent to drain its wallet to an attacker address.
The plugin's defense: the policy check is in code, not in the LLM's prompt. Every chaingpt_agent_wallet_sign_and_send call:
checkPolicy(intent) — pure deterministic code that doesn't see the LLM's context.The attacker can convince the LLM to call sign_and_send(to=attacker, value=ALL) — but the tool layer refuses because attacker isn't in allowedToAddresses or value exceeds maxTxValueWei or killSwitch=true. The trust boundary is the tool code, not the LLM.
There is no MCP tool that writes the policy file. The admin edits it directly with a text editor. There is no MCP tool that reads or sets the passphrase. The passphrase lives only in the shell env var or the OS keychain — never in the keystore file, never in the LLM's context.
The keystore passphrase resolves in this priority order:
CHAINGPT_AGENT_WALLET_PASSPHRASE env var — explicit override. Best for CI / headless / power users who want zero process-list and zero keychain exposure.security) or Linux (libsecret via secret-tool), if no env var is set, chaingpt_agent_wallet_init generates a strong 256-bit passphrase and stores it in the keychain. You never type or remember it; the MCP server reads it back on each load.# Just init — a strong passphrase is generated + stored in your OS keychain.
claude
> initialize the agent wallet
The init output tells you it used the keychain and how to export the passphrase for backup.
# Set a strong passphrase BEFORE starting the MCP server (>= 16 chars)
export CHAINGPT_AGENT_WALLET_PASSPHRASE="your-strong-passphrase-here-min-16-chars"
claude
> initialize the agent wallet
Back up the passphrase either way. Keychain entry:
service=chaingpt-mcp-agent-wallet account=keystore-passphrase. Export on macOS withsecurity find-generic-password -s chaingpt-mcp-agent-wallet -a keystore-passphrase -w. Lose it (and any backup) → the keystore is unrecoverable. There is no recovery path.
Security tradeoff of the keychain option: the secret stays out of plaintext-on-disk and out of the LLM context, but the keychain is unlocked while you're logged in — a local attacker on an unlocked session could read it. That's a much higher bar than a plaintext file and appropriate for a low-value bounded hot wallet. For zero local exposure, use Option B.
This creates two files:
| File | Contents | Who edits it |
|---|---|---|
~/.chaingpt-mcp/agent-wallet/keystore.json | AES-256-GCM encrypted private key | Generated once by the init tool. Never edit by hand. Back it up. |
~/.chaingpt-mcp/agent-wallet/policy.json | Plain JSON rules | You, the admin, with a text editor. The agent has NO tool that writes this file. |
Both default to ~/.chaingpt-mcp/agent-wallet/ but can be overridden via CHAINGPT_KEYSTORE_FILE and CHAINGPT_AGENT_POLICY_FILE.
| Tool | Mutates state? | Notes |
|---|---|---|
chaingpt_agent_wallet_init | Creates keystore | One-shot. Refuses if file exists. |
chaingpt_agent_wallet_address | No | Returns the agent's EOA address. Use this to receive funds. |
chaingpt_agent_wallet_status | No | Address + policy digest + kill-switch state. Run this before any signing. |
chaingpt_agent_wallet_balances | No | Native-coin balances across requested chains. |
chaingpt_agent_wallet_policy | No (read-only) | Shows the current policy JSON. Cannot modify it. |
chaingpt_agent_wallet_sign_and_send | Signs + broadcasts a tx | The only tool that can move funds. Gated by policy. |
chaingpt_agent_wallet_serve_ui | Starts a local HTTP server | Dashboard on http://127.0.0.1:8787. Read-only view. |
Default policy.json (lazily created on first read) is the Balanced DeFi policy: killSwitch: false, major DEX/lending routers allow-listed, 0.1 native per-tx cap, 0.3 native + 20 txs per rolling 24h (maxDailySpendWei / maxDailyTxCount), memo required. A corrupt or partially-missing policy file always falls back to fail-closed (killSwitch: true) — tampering can never open the gates. Apply the "Locked down" template (or set killSwitch: true) for a refuse-everything posture.
Example production policy (allow DEX rebalancing on Base, capped at 0.1 ETH/tx, audit memo required):
{
"version": 1,
"killSwitch": false,
"allowedChains": [8453],
"allowedToAddresses": [
"0x6352a56caadc4f1e25cd6c75970fa768a3304e64",
"0x111111125421ca6dc452d289314280a0f8842a65"
],
"blockedToAddresses": [
"0x0000000000000000000000000000000000000000"
],
"maxTxValueWei": "100000000000000000",
"maxTxGas": "500000",
"blockedSelectors": [],
"requireMemo": true,
"notes": "Base only, OpenOcean + 1inch routers only, 0.1 ETH cap, memo required for audit",
"updatedAt": "2026-05-18T20:00:00Z"
}
| Field | Type | Behavior when unset | Behavior when set |
|---|---|---|---|
killSwitch | bool | refuses everything (fail-closed) | true refuses everything; false proceeds to other checks |
allowedChains | int[] | any chain allowed | refuses if chainId not in list |
allowedToAddresses | string[] | any address allowed | refuses if to not in list (case-insensitive) |
blockedToAddresses | string[] | nothing blocked | refuses if to matches (case-insensitive) |
maxTxValueWei | string | no cap | refuses if native value > max |
maxTxGas | string | no cap | refuses if gasLimit > max; an explicit gasLimit becomes REQUIRED (auto-estimation would bypass the cap) |
maxDailySpendWei | string | no velocity cap | refuses if 24h ledger spend + this tx value would exceed the cap (fail-closed if the ledger is unreadable) |
maxDailyTxCount | int | no velocity cap | refuses once the rolling-24h signed-tx count reaches the cap |
blockedSelectors | string[] | nothing blocked | refuses if first 4 bytes of data match (e.g. 0xa9059cbb blocks ERC-20 transfer) |
requireMemo | bool | no memo required | refuses if the memo arg is missing |
Precedence: kill switch > blockedToAddresses > allowedToAddresses > value caps > gas cap > daily velocity caps > blockedSelectors > memo. Any single failure refuses the tx.
The same bounded-autonomy model on Solana. Separate Ed25519 keystore (solana-keystore.json, same cipher + same admin passphrase), gated by the solana policy sub-object that — like everything else here — no MCP tool can write.
chaingpt_agent_wallet_solana_init # one-time keystore
chaingpt_agent_wallet_solana_address # fund this (the balance is the outermost cap)
<any builder: jupiter swap / marginfi / kamino / transfer> → unsigned VersionedTransaction (base64)
chaingpt_agent_wallet_solana_sign_and_send txBase64=<…> memo=<…>
Policy block (admin-only, dashboard or text editor):
"solana": {
"enabled": true,
"allowedPrograms": ["11111111111111111111111111111111", "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
"maxTxLamports": "100000000",
"maxDailySpendLamports": "300000000",
"maxDailyTxCount": 20,
"requireMemo": true
}
Hard facts to relay accurately:
solana.enabled: true refuses every Solana signing op. unrestricted does not bypass it.maxTxLamports + the 24h window. Simulation unavailable or failing ⇒ refusal, never a blind broadcast.The endgame: the user's ERC-7579 smart account grants the agent's EOA a SCOPED on-chain session (Smart Sessions module). The caps live in audited contracts and are validated by the EntryPoint. Status: BETA — the module addresses are verified deployed on Base Sepolia and the encoders are unit-tested, but the end-to-end live proof is not yet published. Treat the on-chain column below as the DESIGNED guarantee, not yet an independently-demonstrated one; the local gate is your tested fence today.
| Threat | Local policy gate (tested) | On-chain session caps (designed, beta) |
|---|---|---|
| Prompt injection | ✅ blocks | ✅ designed to block |
| Policy file tampered/rewritten | ❌ falls | ✅ designed to block |
| Full host compromise (keystore stolen) | ❌ falls | ✅ designed to block, bounded by remaining allowance + expiry (live proof pending) |
chaingpt_aa_session_build_grant chain=base account=<user SCW> tokenCaps=[{token: USDC, cap: "100000000"}] validUntil=<unix>
→ OWNER signs the userOpHash externally → chaingpt_aa_submit_userop
chaingpt_aa_session_status # chain-authoritative: enabled? remaining?
chaingpt_agent_wallet_4337_sign_and_send … # the agent acts; local gates AND chain caps both apply
chaingpt_aa_session_build_revoke … # incident response: chain-level kill
Hard facts: erc4337.enabled policy opt-in is fail-closed and OFF by default everywhere (this surface acts on a third-party account). Unbounded grants are refused at build time. A bundler rejection of an over-cap op is the product working — never retry around it. v1 supports Biconomy Nexus 1.x accounts.
1. chaingpt_agent_wallet_status # see address + policy digest + kill switch state
2. chaingpt_agent_wallet_balances # confirm funded on the target chain
3. chaingpt_agent_wallet_policy # read the active rules in full
4. chaingpt_agent_wallet_sign_and_send chain=… to=… valueWei=… data=… memo="…"
If the call gets refused with a policy reason: do not try to work around it from the agent side. Surface the reason to the admin and let them edit the policy file (or override) themselves.
> Use chaingpt_agent_wallet_serve_ui
Returns a http://127.0.0.1:8787 URL and a one-time admin token printed in the tool output (also saved to ~/.chaingpt-mcp/agent-wallet/.admin-token, 0600). The token rotates on every restart.
Open the URL in your browser. Paste the admin token at the login screen. The dashboard then shows:
.bak backupRecall the threat model: the attacker controls the LLM via prompt injection. The defenses, in layers:
savePolicy function — it's only imported by the localhost HTTP server.Origin on form submits..bak. A botched save can't corrupt the policy file mid-write, and the previous version is recoverable.127.0.0.1 only. Never on 0.0.0.0 — the dashboard is not reachable from other machines on the network.The single failure mode that would bypass all of this: malware running on the admin's machine that can read the admin token file AND make HTTP requests to localhost. At that point the attacker has shell access and can read the keystore directly; the policy file is no longer the weakest link.
| Method | Path | Behavior |
|---|---|---|
GET / | login form (if unauthed) or redirect to /dashboard | — |
POST /login | check admin token, set session cookie, redirect to /dashboard | requires Origin |
GET /dashboard | full admin UI (auth required) | — |
GET /api/policy | current policy JSON | requires session |
POST /api/policy | save new policy after validation | requires session + Origin |
POST /api/killswitch | toggle the kill switch (set=on/off) | requires session + Origin |
GET /api/status | JSON with address + balances + policy digest | requires session |
GET /logout | clear session cookie, redirect to login | — |
CHAINGPT_AGENT_WALLET_PASSPHRASE → the keystore is unrecoverable. There is no recovery path. Back up the passphrase out-of-band (1Password, hardware safe, etc.).chaingpt_agent_wallet_addresschaingpt_agent_wallet_balances chains=[base,arbitrum]The agent is now ready to operate within its policy bounds.
All agent-wallet tools cost 0 ChainGPT credits. The wallet is custody-on-the-user's-machine (not custody-via-ChainGPT). The credit funnel comes from upstream tools the user/agent calls (chaingpt_research_token, chaingpt_risk_token, chaingpt_intel_token) before deciding to deploy.
~/.chaingpt-mcp/agent-wallet/{keystore.json,policy.json} (override via env).npx claudepluginhub chaingpt-org/chaingpt-claude-skill --plugin chaingptProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.