chain-signer
Non-custodial agent wallet with a transaction preflight that decodes an unsigned EVM tx and flags drain patterns (unlimited/large approval, approve-all, token & NFT transferFrom, proxy upgrade, on-chain permit, approvals hidden in multicall) before signing.
README
chain-signer
<!-- mcp-name: io.github.Kevthetech143/chain-signer -->
A security suite for AI agents — the seatbelt that catches the dangerous thing BEFORE it happens. Three guards, each callable on its own (and as MCP tools), pairing with any wallet or identity stack:
preflight(tx)— decode an unsigned transaction and flag drains before signing (unlimited/large approval, approve-all, token & NFT transferFrom, proxy upgrade, on-chain permit, approvals hidden in multicall incl. Uniswap router batches, EIP-7702 account delegation, will-revert).inspect_typed_data(td)— catch permit-phishing in an EIP-712 message before the agent signs it (ERC-2612, Uniswap Permit2, DAI-style permits).check_action(action, policy)— enforce allow/forbid + value/recipient limits before the agent acts.
All three fail safe and are guards, not guarantees. Also bundled: a non-custodial multi-chain wallet (burner, balance, send, swap) — the agent holds its own key and signs locally. No MetaMask, no account, no custody.
from chain_signer import assert_safe
assert_safe(tx) # raises if the tx is a drain/unlimited-approval/revert — review before signing
Install
pip install chain-signer
export ETHERSCAN_API_KEY=... # for live balance reads + broadcast (Etherscan v2)
Bitcoin/Solana support is optional: pip install "chain-signer[all]".
Quickstart (5 lines)
from chain_signer import burner, send_ether
from chain_signer.balance import get_balance
w = burner() # fresh throwaway wallet; the agent owns w.private_key
print(w.address, get_balance(w)) # live on-chain balance
send_ether(w, "0x...recipient", 0.001) # auto nonce+gas, signed locally, broadcast
That's it — your agent just held a wallet and moved funds, no human in the loop.
Try it in 10 seconds (offline — no key, no funds, no network)
pip install chain-signer
from chain_signer import preflight
spender = "0x" + "22" * 20
tx = {"to": "0x" + "33" * 20, "data": "0x095ea7b3" + spender[2:].rjust(64, "0") + "f" * 64, "value": 0}
print(preflight(tx)) # ok=False — flags unlimited_approval before you'd ever sign
Full runnable demos are in the repo: examples/agent_safety_demo.py (all three guards stop three
real attacks) and examples/quickstart.py (wallet) — clone to run them, or just import as above.
Safety preflight (the wedge)
Before an agent signs, hand the unsigned tx to preflight() — it decodes the calldata and returns
the risks, or use assert_safe() to hard-stop on a HIGH flag. Offline, no network, never raises.
from chain_signer import preflight, assert_safe
# an unlimited-allowance approve() to a spender — the classic drain setup
tx = {"to": token, "data": "0x095ea7b3" + spender_padded + "f"*64, "value": 0}
report = preflight(tx)
# {'decoded': {...}, 'ok': False,
# 'risk_flags': [{'code': 'unlimited_approval', 'severity': 'HIGH',
# 'detail': 'approve() grants an effectively-unlimited allowance ...'}]}
assert_safe(tx) # raises ValueError on a HIGH flag; pass force=True to override
assert_safe(tx, sim=my_simulator) # optional: also flag will-revert via your simulation hook
What it flags today: unlimited/large approval, increaseAllowance, setApprovalForAll,
ERC-20 transferFrom + ERC-721/1155 safeTransferFrom (token & NFT drains), on-chain permit,
proxy upgradeTo/upgradeToAndCall, approvals hidden inside multicall (all router variants,
nested), EIP-7702 account delegation (the "wallet upgrade" drainer), large native value, opaque
calldata, malformed calls, and will-revert (with a sim hook).
Honest limits (read these): this is STATIC analysis — it decodes calldata and matches known drain
patterns. It is NOT a transaction simulator: it won't catch a novel/obfuscated drain it can't decode
(those get a low-severity "unknown" flag, not a block), and simulation-based scanners go deeper there.
Safety coverage is EVM-only today (no Solana/Bitcoin tx analysis). And it is not yet field-proven at
scale. A first-line guard for known patterns — not a guarantee. Pair it with simulation + human
review for high-value actions.
Signed-message inspector (the off-chain half)
A drain doesn't need a transaction. A dApp can ask the agent to sign an EIP-712 message —
most dangerously a permit granting an unlimited token allowance, which preflight (a tx check)
can't see. inspect_typed_data() catches it before the agent signs:
from chain_signer import inspect_typed_data
report = inspect_typed_data(typed_data) # the EIP-712 object you're about to sign
# ok=False, risk_flags=[{'code': 'unlimited_permit_signature', 'severity': 'HIGH', ...}]
Covers all three major permit shapes: ERC-2612, Uniswap Permit2 (PermitSingle/PermitBatch),
and DAI-style (allowed: true). Offline, never raises.
Action-policy gate (inspect what the agent DOES)
Identity tells you who the agent is; it doesn't stop a bad action. check_action() enforces a
policy on a proposed tool call before it runs — fail-safe (denies on unreadable input):
from chain_signer import check_action
policy = {"forbid_tools": ["bridge"], "max_value_wei": 10**18, "allow_recipients": [trusted_addr]}
r = check_action({"tool": "send", "args": {"to": addr, "value_wei": 5*10**18}}, policy)
# {'allowed': False, 'violations': [{'code': 'value_over_limit', ...}]}
All three guards are exposed as MCP tools (preflight, inspect_signature, check_action) — any
agent runtime (Claude, Cursor, …) can call them directly, read-only, no key.
What's caught and what isn't — the honest threat-coverage map: docs/THREAT-COVERAGE.md.
What you get
preflight(tx)/assert_safe(tx)— decode an unsigned tx and flag drain patterns before signing.inspect_typed_data(td)— flag permit-phishing in an EIP-712 message before the agent signs it.check_action(action, policy)— enforce allow/forbid + value/recipient limits before the agent acts.burner()— a fresh wallet for a one-off task; discard it when done.restore(key)— reload a wallet later from its exported private key (same key → same address).send_ether(w, to, amount)— send in ETH (not wei); nonce, gas, and broadcast handled for you.get_balance(w)— live balance from the chain (Etherscan v2 indexer, not a flaky public RPC).swap(...)— token swaps via 0x/Paraswap.- Optional Solana + Bitcoin wallets via the
[all]extra.
Non-custodial guarantee
The private key is generated/loaded locally, used only to sign, and never logged, returned, or stored by this library. You hold the key; we never touch your funds. That is the whole design.
Handling the key (read this)
w.private_key is the keys to the wallet. Treat it like a password:
- NEVER log it, print it in production, or write it into notes/memory/chat. Anyone who has it controls the funds.
- For a burner holding a few dollars this is low-stakes by design — but the rule still holds.
- To reuse a wallet later, store the key in a secret manager / env var, then
restore(key). - Better:
export_encrypted(w, password)gives a password-protected keystore dict to store at rest;load_encrypted(keystore, password)brings the wallet back. Never store the raw key if you can store the keystore.
Signing idiom (note for web3.py users)
The wallet does not expose sign_transaction / sign_message methods. Signing is done by
function helpers you pass the wallet to — e.g. send_ether(w, to, amount) signs and broadcasts,
and sign_message(w, "text") returns an EIP-191 signature for auth / sign-in flows
(recoverable via eth_account Account.recover_message).
CLI on PATH
pip install may warn that the chain-signer script dir isn't on your PATH. The library works
regardless; to use the CLI directly, add that dir to PATH or run python -m chain_signer ....
Tool surface (for any AI / MCP / CLI)
chain_signer.mcp_server exposes list_tools() and call_tool(name, arguments). CLI:
python -m chain_signer list
python -m chain_signer call create_wallet '{"chain":"evm"}'
Responsible use
General-purpose, non-custodial tooling. You are responsible for using it within the laws and terms of service that apply to you. Not intended or marketed for any restricted or prohibited trading in your jurisdiction.
Notes
- Balances/broadcast use the Etherscan v2 indexer (authoritative), never a free public RPC.
- Low-level building blocks (
tx.send,call_contract, explicit nonce/gas) remain available for advanced use.
Pay an x402 API in one call
from chain_signer import burner, sign_x402_payment
w = burner()
payload = sign_x402_payment(w, token=USDC, to=PAY_TO, value=1000, valid_before=EXPIRES, chain_id=8453)
# -> {"signature", "authorization"} ready for the x402 payment header. Signed locally, no prompt.
Builds + signs the EIP-3009 authorization x402 expects (the "exact" scheme). Your agent pays a paid API by itself — no password prompt, no signup, no custody.
Sign typed data (EIP-712) — for agent payments / x402
from chain_signer import burner, sign_typed_data
w = burner()
sig = sign_typed_data(w, domain, types, message) # EIP-712; for x402 / EIP-3009 authorizations
Your agent can authorize a payment by signing typed data locally — no password prompt, no signup.
Run as an MCP server
chain-signer is also a Model Context Protocol (MCP) server, so MCP-aware agents can use it directly:
pip install chain-signer
chain-signer-mcp # speaks MCP over stdio (JSON-RPC 2.0)
Exposes 6 tools: create_wallet, get_balance (balance), send, call_contract, swap, bridge.
Wire it into any MCP client (Claude Desktop, Cursor, etc.) by adding it to the client's
mcpServers config:
{
"mcpServers": {
"chain-signer": {
"command": "chain-signer-mcp",
"env": { "ETHERSCAN_API_KEY": "your-key-for-live-balance-and-broadcast" }
}
}
}
That's all — the agent can now make a wallet, read balances, send, and swap as native tools.
(ETHERSCAN_API_KEY is optional; needed only for live balance reads and broadcasting.)
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.