{
  "markdown": "# chain-signer\n\n<!-- mcp-name: io.github.Kevthetech143/chain-signer -->\n\n![PyPI](https://img.shields.io/pypi/v/chain-signer) ![Python](https://img.shields.io/pypi/pyversions/chain-signer) ![License](https://img.shields.io/pypi/l/chain-signer) ![Release](https://github.com/Kevthetech143/chain-signer/actions/workflows/release.yml/badge.svg)\n\n\nA security suite for AI agents — the seatbelt that catches the dangerous thing BEFORE it happens.\nThree guards, each callable on its own (and as MCP tools), pairing with any wallet or identity stack:\n\n- `preflight(tx)` — decode an unsigned transaction and flag drains before signing (unlimited/large\n  approval, approve-all, token & NFT transferFrom, proxy upgrade, on-chain permit, on-chain Permit2\n  approve/permit/transferFrom, approvals hidden in multicall incl. Uniswap router batches and\n  Multicall3 aggregate/aggregate3/aggregate3Value (the batch helper on every EVM chain), approvals\n  wrapped in ERC-4337/smart-account execute/executeBatch, Gnosis Safe multiSend/execTransaction and DSProxy\n  execute, drains routed through the Uniswap Universal Router (Permit2 permit/transferFrom commands\n  incl. sub-plans), 1inch AggregationRouter v5 swap() with redirected output or zero slippage,\n  0x ExchangeProxy transformERC20() with zero slippage, EIP-7702 account delegation, will-revert).\n- `inspect_typed_data(td)` — catch permit-phishing in an EIP-712 message before the agent signs it\n  (ERC-2612, Uniswap Permit2 incl. SignatureTransfer + witness variants, DAI-style permits) and Seaport\n  orders that give assets away — zero consideration, proceeds routed to a third party, or hidden in a\n  BulkOrder tree.\n- `check_action(action, policy)` — enforce allow/forbid + value/recipient limits before the agent acts.\n\nAll three fail safe and are guards, not guarantees. Also bundled: a non-custodial multi-chain wallet\n(burner, balance, send, swap) — the agent holds its own key and signs locally. No MetaMask, no\naccount, no custody.\n\n```python\nfrom chain_signer import assert_safe\nassert_safe(tx)   # raises if the tx is a drain/unlimited-approval/revert — review before signing\n```\n\n## Install\n```\npip install chain-signer\nexport ETHERSCAN_API_KEY=...   # for live balance reads + broadcast (Etherscan v2)\n```\nBitcoin/Solana support is optional: `pip install \"chain-signer[all]\"`.\n\n## Quickstart (10 seconds — offline, no key, no funds, no network)\n```\npip install chain-signer\n```\n```python\nfrom chain_signer import preflight\nspender = \"0x\" + \"22\" * 20\ntx = {\"to\": \"0x\" + \"33\" * 20, \"data\": \"0x095ea7b3\" + spender[2:].rjust(64, \"0\") + \"f\" * 64, \"value\": 0}\nprint(preflight(tx))   # ok=False — flags unlimited_approval before you'd ever sign\n```\nThat's the wedge: the drain gets flagged before you'd ever sign it — no key, no funds, no network.\n\n### Bundled wallet (optional — the guards pair with any wallet)\n```python\nfrom chain_signer import burner, send_ether\nfrom chain_signer.balance import get_balance\n\nw = burner()                          # fresh throwaway wallet; the agent owns w.private_key\nprint(w.address, get_balance(w))      # live on-chain balance\nsend_ether(w, \"0x...recipient\", 0.001)  # auto nonce+gas, signed locally, broadcast\n```\nFull runnable demos are in the repo: `examples/agent_safety_demo.py` (all three guards stop three\nreal attacks) and `examples/quickstart.py` (wallet) — clone to run them, or just import as above.\n\n## Safety preflight (the wedge)\nBefore an agent signs, hand the unsigned tx to `preflight()` — it decodes the calldata and returns\nthe risks, or use `assert_safe()` to hard-stop on a HIGH flag. Offline, no network, never raises.\n```python\nfrom chain_signer import preflight, assert_safe\n\n# an unlimited-allowance approve() to a spender — the classic drain setup\ntx = {\"to\": token, \"data\": \"0x095ea7b3\" + spender_padded + \"f\"*64, \"value\": 0}\n\nreport = preflight(tx)\n# {'decoded': {...}, 'ok': False,\n#  'risk_flags': [{'code': 'unlimited_approval', 'severity': 'HIGH',\n#                  'detail': 'approve() grants an effectively-unlimited allowance ...'}]}\n\nassert_safe(tx)          # raises ValueError on a HIGH flag; pass force=True to override\nassert_safe(tx, sim=my_simulator)   # optional: also flag will-revert via your simulation hook\n```\nWhat it flags today: unlimited/large approval, `increaseAllowance`, `setApprovalForAll`,\nERC-20 `transferFrom` + ERC-721/1155 `safeTransferFrom` (token & NFT drains), ERC-777 `authorizeOperator`/`operatorSend`\n(operator-grant + operator-pull drains), on-chain ERC-2612 and DAI-style `permit`,\non-chain Permit2 `approve`/`permit`/`transferFrom` (single **and** batch — the dominant approval router:\nunlimited uint160 allowance + drain pull) plus Permit2 SignatureTransfer `permit(Witness)TransferFrom`\n(the one-shot signed-permit pull intent/filler protocols use), proxy `upgradeTo`/`upgradeToAndCall`, approvals hidden inside `multicall` (all router\nvariants, nested) **and Multicall3 `aggregate`/`aggregate3`/`aggregate3Value`** (the canonical batch\nhelper deployed at one address on every EVM chain), approvals wrapped in ERC-4337/smart-account `execute`/`executeBatch`, Gnosis Safe\n`multiSend`/`execTransaction`, or DSProxy `execute(target,data)`/`execute(code,data)` (decoded and recursed),\ndrains routed through the Uniswap **Universal Router**\n(`execute(commands,inputs)` — Permit2 `permit`/`transferFrom` commands, batch and `EXECUTE_SUB_PLAN`),\nEIP-7702 account delegation (the \"wallet upgrade\" drainer), large native value,\nopaque calldata, malformed calls, and will-revert (with a sim hook).\nHonest limits (read these): this is STATIC analysis — it decodes calldata and matches known drain\npatterns. It is NOT a transaction simulator: it won't catch a novel/obfuscated drain it can't decode\n(those get a low-severity \"unknown\" flag, not a block), and simulation-based scanners go deeper there.\nSafety coverage is EVM-only today (no Solana/Bitcoin tx analysis). And it is not yet field-proven at\nscale. A first-line guard for known patterns — not a guarantee. Pair it with simulation + human\nreview for high-value actions.\n\n## Signed-message inspector (the off-chain half)\nA drain doesn't need a transaction. A dApp can ask the agent to **sign** an EIP-712 message —\nmost dangerously a `permit` granting an unlimited token allowance, which `preflight` (a tx check)\ncan't see. `inspect_typed_data()` catches it before the agent signs:\n```python\nfrom chain_signer import inspect_typed_data\nreport = inspect_typed_data(typed_data)   # the EIP-712 object you're about to sign\n# ok=False, risk_flags=[{'code': 'unlimited_permit_signature', 'severity': 'HIGH', ...}]\n```\nCovers all three major permit shapes: **ERC-2612**, **Uniswap Permit2** (PermitSingle/PermitBatch, plus\nSignatureTransfer and the witness variants intent protocols use), and **DAI-style** (`allowed: true`),\nplus **Seaport** marketplace orders that hand assets over for nothing — zero consideration, proceeds\nrouted to a third party while your asset leaves, or the same giveaway buried in a BulkOrder merkle tree.\nOffline, never raises.\n\n### Guarded signer (screen + sign in one call)\n`inspect_typed_data` only protects when the agent remembers to call it first — `sign_typed_data`\nalone will happily sign a permit-phishing message. `guarded_sign_typed_data()` composes the two so\nsigning is screened by **default**: it inspects, then refuses to sign a HIGH-risk drain.\n```python\nfrom chain_signer import guarded_sign_typed_data, SignatureBlocked\nsig = guarded_sign_typed_data(wallet, domain, types, message, \"Permit\")  # raises SignatureBlocked on a drain\n```\nOn a clean message the signature is byte-identical to `sign_typed_data`; pass `force=True` to override.\n\n## Action-policy gate (inspect what the agent DOES)\nIdentity tells you *who* the agent is; it doesn't stop a bad *action*. `check_action()` enforces a\npolicy on a proposed tool call before it runs — fail-safe (denies on unreadable input):\n```python\nfrom chain_signer import check_action\npolicy = {\"forbid_tools\": [\"bridge\"], \"max_value_wei\": 10**18, \"allow_recipients\": [trusted_addr]}\nr = check_action({\"tool\": \"send\", \"args\": {\"to\": addr, \"value_wei\": 5*10**18}}, policy)\n# {'allowed': False, 'violations': [{'code': 'value_over_limit', ...}]}\n```\n\nAll three guards are exposed as MCP tools (`preflight`, `inspect_signature`, `check_action`) — any\nagent runtime (Claude, Cursor, …) can call them directly, read-only, no key.\n\nWhat's caught and what isn't — the honest threat-coverage map: [`docs/THREAT-COVERAGE.md`](docs/THREAT-COVERAGE.md).\n\n## What you get\n- `preflight(tx)` / `assert_safe(tx)` — decode an unsigned tx and flag drain patterns before signing.\n- `inspect_typed_data(td)` — flag permit-phishing in an EIP-712 message before the agent signs it.\n- `guarded_sign_typed_data(w, domain, types, message, primary_type)` — screen then sign; refuses a drain.\n- `check_action(action, policy)` — enforce allow/forbid + value/recipient limits before the agent acts.\n- `burner()` — a fresh wallet for a one-off task; discard it when done.\n- `restore(key)` — reload a wallet later from its exported private key (same key → same address).\n- `send_ether(w, to, amount)` — send in ETH (not wei); nonce, gas, and broadcast handled for you.\n- `get_balance(w)` — live balance from the chain (Etherscan v2 indexer, not a flaky public RPC).\n- `swap(...)` — token swaps via 0x/Paraswap.\n- Optional Solana + Bitcoin wallets via the `[all]` extra.\n\n## Non-custodial guarantee\nThe private key is generated/loaded locally, used only to sign, and never logged, returned, or\nstored by this library. You hold the key; we never touch your funds. That is the whole design.\n\n## Handling the key (read this)\n`w.private_key` is the keys to the wallet. Treat it like a password:\n- NEVER log it, print it in production, or write it into notes/memory/chat. Anyone who has it controls the funds.\n- For a burner holding a few dollars this is low-stakes by design — but the rule still holds.\n- To reuse a wallet later, store the key in a secret manager / env var, then `restore(key)`.\n- 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.\n\n## Signing idiom (note for web3.py users)\nThe wallet does not expose `sign_transaction` / `sign_message` methods. Signing is done by\nfunction helpers you pass the wallet to — e.g. `send_ether(w, to, amount)` signs and broadcasts,\nand `sign_message(w, \"text\")` returns an EIP-191 signature for auth / sign-in flows\n(recoverable via eth_account `Account.recover_message`).\n\n## CLI on PATH\n`pip install` may warn that the `chain-signer` script dir isn't on your PATH. The library works\nregardless; to use the CLI directly, add that dir to PATH or run `python -m chain_signer ...`.\n\n## Tool surface (for any AI / MCP / CLI)\n`chain_signer.mcp_server` exposes `list_tools()` and `call_tool(name, arguments)`. CLI:\n```\npython -m chain_signer list\npython -m chain_signer call create_wallet '{\"chain\":\"evm\"}'\n```\n\n## Responsible use\nGeneral-purpose, non-custodial tooling. You are responsible for using it within the laws and\nterms of service that apply to you. Not intended or marketed for any restricted or prohibited\ntrading in your jurisdiction.\n\n## Notes\n- Balances/broadcast use the Etherscan v2 indexer (authoritative), never a free public RPC.\n- Low-level building blocks (`tx.send`, `call_contract`, explicit nonce/gas) remain available for advanced use.\n\n## Pay an x402 API in one call\n```python\nfrom chain_signer import burner, sign_x402_payment\nw = burner()\npayload = sign_x402_payment(w, token=USDC, to=PAY_TO, value=1000, valid_before=EXPIRES, chain_id=8453)\n# -> {\"signature\", \"authorization\"} ready for the x402 payment header. Signed locally, no prompt.\n```\nBuilds + signs the EIP-3009 authorization x402 expects (the \"exact\" scheme). Your agent pays a\npaid API by itself — no password prompt, no signup, no custody.\n\n## Sign typed data (EIP-712) — for agent payments / x402\n```python\nfrom chain_signer import burner, sign_typed_data\nw = burner()\nsig = sign_typed_data(w, domain, types, message)  # EIP-712; for x402 / EIP-3009 authorizations\n```\nYour agent can authorize a payment by signing typed data locally — no password prompt, no signup.\n\n## Run as an MCP server\nchain-signer is also a Model Context Protocol (MCP) server, so MCP-aware agents can use it directly:\n```\npip install chain-signer\nchain-signer-mcp          # speaks MCP over stdio (JSON-RPC 2.0)\n```\nExposes 9 tools. The three security guards (the wedge): `preflight`, `inspect_signature`,\n`check_action`. Plus the non-custodial wallet: create_wallet, get_balance, send, call_contract, swap, bridge.\n\nWire it into any MCP client (Claude Desktop, Cursor, etc.) by adding it to the client's\n`mcpServers` config:\n```json\n{\n  \"mcpServers\": {\n    \"chain-signer\": {\n      \"command\": \"chain-signer-mcp\",\n      \"env\": { \"ETHERSCAN_API_KEY\": \"your-key-for-live-balance-and-broadcast\" }\n    }\n  }\n}\n```\nThat's all — the agent can now screen every tx, signature, and action through the guards before it\nacts, and (optionally) hold its own wallet to read balances, send, and swap as native tools.\n(`ETHERSCAN_API_KEY` is optional; needed only for live balance reads and broadcasting.)\n",
  "bytes": 13318,
  "sha": "1817ae4d5a4b749caa1d429957fa8f1393795770dbfe18b95bf39f45aa1f2058",
  "repo_slug": "kevthetech143/chain-signer",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_kevthetech143_chain_signer_3d4d7956/readme"
}