chain-signer
Suíte de segurança de pré-assinatura para agentes de IA: sinaliza drenagens de carteira, phishing de permissão e ações arriscadas antes de assinar em cadeias EVM. Sem custódia.
Documentação
chain-signer
Um conjunto de segurança para agentes de IA — o cinto de segurança que pega a coisa perigosa ANTES que aconteça. Três guardiões, cada um acionável por conta própria (e como ferramentas MCP), emparelhando com qualquer carteira ou pilha de identidade:
preflight(tx)— decodificar uma transação não assinada e sinalizar drenagens antes de assinar (aprovação ilimitada/grande, aprovar-tudo, token e NFT transferFrom, atualização de proxy, permissão on-chain, Permit2 on-chain approve/permit/transferFrom, aprovações escondidas em multicall incl. lotes do roteador Uniswap e Multicall3 aggregate/aggregate3/aggregate3Value (o auxiliar de lote em cada cadeia EVM), aprovações envolvidas em ERC-4337/smart-account execute/executeBatch, Gnosis Safe multiSend/execTransaction e DSProxy execute, drenagens roteadas através do Uniswap Universal Router (comandos Permit2 permit/transferFrom incl. sub-planos), 1inch AggregationRouter v5 swap() com saída redirecionada ou slippage zero, 0x ExchangeProxy transformERC20() com slippage zero, delegação de conta EIP-7702, will-revert).inspect_typed_data(td)— pegar phishing de permissão em uma mensagem EIP-712 antes que o agente a assine (ERC-2612, Uniswap Permit2 incl. SignatureTransfer + variantes de testemunha, e permissões estilo DAI) e ordens Seaport que dão ativos de graça — consideração zero, lucros roteados para um terceiro, ou escondidos em uma árvore BulkOrder.check_action(action, policy)— aplicar limites de permitir/proibir + valor/destinatário antes que o agente aja.
Todos os três são à prova de falhas e são guardiões, não garantias. Também incluído: uma carteira multi-chain não custodial (burner, saldo, enviar, trocar) — o agente detém sua própria chave e assina localmente. Sem MetaMask, sem conta, sem custódia.
from chain_signer import assert_safe
assert_safe(tx) # raises if the tx is a drain/unlimited-approval/revert — review before signing
Instalação
pip install chain-signer
export ETHERSCAN_API_KEY=... # for live balance reads + broadcast (Etherscan v2)
O suporte a Bitcoin/Solana é opcional: pip install "chain-signer[all]".
Início rápido (10 segundos — offline, sem chave, sem fundos, sem rede)
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
Esse é o ponto: a drenagem é sinalizada antes que você a assine — sem chave, sem fundos, sem rede.
Carteira incluída (opcional — os guardiões emparelham com qualquer carteira)
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
Demos completos executáveis estão no repositório: examples/agent_safety_demo.py (os três guardiões param três
ataques reais) e examples/quickstart.py (carteira) — clone para executá-los, ou apenas importe como acima.
Pré-verificação de segurança (o ponto)
Antes de um agente assinar, entregue a tx não assinada a preflight() — ela decodifica o calldata e retorna
os riscos, ou use assert_safe() para parar completamente em um sinalizador ALTO. Offline, sem rede, nunca levanta.
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
O que ela sinaliza hoje: aprovação ilimitada/grande, increaseAllowance, setApprovalForAll,
ERC-20 transferFrom + ERC-721/1155 safeTransferFrom (drenagens de token e NFT), ERC-777 authorizeOperator/operatorSend
(drenagens de concessão de operador + puxada de operador), ERC-2612 on-chain e estilo DAI permit,
Permit2 on-chain approve/permit/transferFrom (único e lote — o roteador de aprovação dominante:
permissão uint160 ilimitada + puxada de drenagem) além de Permit2 SignatureTransfer permit(Witness)TransferFrom
(a puxada de permissão assinada de uma vez que protocolos de intenção/preenchimento usam), proxy upgradeTo/upgradeToAndCall, aprovações escondidas dentro de multicall (todas as variantes
de roteador, aninhadas) e Multicall3 aggregate/aggregate3/aggregate3Value (o auxiliar de lote
canônico implantado em um endereço em cada cadeia EVM), aprovações envolvidas em ERC-4337/smart-account execute/executeBatch, Gnosis Safe
multiSend/execTransaction, ou DSProxy execute(target,data)/execute(code,data) (decodificado e recursado),
drenagens roteadas através do Uniswap Universal Router
(execute(commands,inputs) — comandos Permit2 permit/transferFrom, lote e EXECUTE_SUB_PLAN),
delegação de conta EIP-7702 (o drenador de "atualização de carteira"), valor nativo grande,
calldata opaco, chamadas malformadas, e will-revert (com um hook de simulação).
Limites honestos (leia isto): esta é uma análise ESTÁTICA — ela decodifica calldata e corresponde a padrões de drenagem
conhecidos. NÃO é um simulador de transações: não pegará uma drenagem nova/obfuscada que não consegue decodificar
(essas recebem um sinalizador de baixa gravidade "desconhecido", não um bloqueio), e scanners baseados em simulação vão mais fundo aí.
A cobertura de segurança é apenas EVM hoje (sem análise de tx Solana/Bitcoin). E ainda não é comprovado em campo em
escala. Um guardião de primeira linha para padrões conhecidos — não uma garantia. Combine com simulação + revisão
humana para ações de alto valor.
Inspetor de mensagens assinadas (a metade off-chain)
Uma drenagem não precisa de uma transação. Um dApp pode pedir ao agente para assinar uma mensagem EIP-712 —
mais perigosamente um permit concedendo uma permissão ilimitada de token, que preflight (uma verificação de tx)
não consegue ver. inspect_typed_data() pega isso antes que o agente assine:
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', ...}]
Cobre as três principais formas de permissão: ERC-2612, Uniswap Permit2 (PermitSingle/PermitBatch, além de
SignatureTransfer e as variantes de testemunha que os protocolos de intenção usam), e estilo DAI (allowed: true),
além de ordens de marketplace Seaport que entregam ativos de graça — consideração zero, lucros
roteados para um terceiro enquanto seu ativo sai, ou a mesma doação enterrada em uma árvore merkle BulkOrder.
Offline, nunca levanta.
Assinante protegido (verificação + assinatura em uma chamada)
inspect_typed_data só protege quando o agente lembra de chamá-lo primeiro — sign_typed_data
sozinho assinará feliz uma mensagem de phishing de permissão. guarded_sign_typed_data() compõe os dois para que
a assinatura seja verificada por padrão: ele inspeciona e depois se recusa a assinar uma drenagem de ALTO risco.
from chain_signer import guarded_sign_typed_data, SignatureBlocked
sig = guarded_sign_typed_data(wallet, domain, types, message, "Permit") # raises SignatureBlocked on a drain
Em uma mensagem limpa, a assinatura é byte-idêntica a sign_typed_data; passe force=True para substituir.
Portão de política de ação (inspecione o que o agente FAZ)
A identidade diz quem é o agente; ela não impede uma ação ruim. check_action() aplica uma
política em uma chamada de ferramenta proposta antes de executá-la — à prova de falhas (nega em entrada ilegível):
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', ...}]}
Todos os três guardiões são expostos como ferramentas MCP (preflight, inspect_signature, check_action) — qualquer
runtime de agente (Claude, Cursor, …) pode chamá-los diretamente, somente leitura, sem chave.
O que é pego e o que não é — o mapa honesto de cobertura de ameaças: docs/THREAT-COVERAGE.md.
O que você obtém
preflight(tx)/assert_safe(tx)— decodificar uma tx não assinada e sinalizar padrões de drenagem antes de assinar.inspect_typed_data(td)— sinalizar phishing de permissão em uma mensagem EIP-712 antes que o agente a assine.guarded_sign_typed_data(w, domain, types, message, primary_type)— verificar e depois assinar; recusa uma drenagem.check_action(action, policy)— aplicar limites de permitir/proibir + valor/destinatário antes que o agente aja.burner()— uma carteira nova para uma tarefa única; descarte-a quando terminar.restore(key)— recarregar uma carteira depois a partir de sua chave privada exportada (mesma chave → mesmo endereço).send_ether(w, to, amount)— enviar em ETH (não wei); nonce, gás e transmissão tratados para você.get_balance(w)— saldo ao vivo da cadeia (indexador Etherscan v2, não um RPC público instável).swap(...)— trocas de token via 0x/Paraswap.- Carteiras opcionais Solana + Bitcoin via o extra
[all].
Garantia não custodial
A chave privada é gerada/carregada localmente, usada apenas para assinar, e nunca é registrada, retornada ou armazenada por esta biblioteca. Você detém a chave; nunca tocamos em seus fundos. Esse é o design inteiro.
Lidando com a chave (leia isto)
w.private_key é a chave da carteira. Trate-a como uma senha:
- NUNCA a registre, imprima em produção ou escreva em notas/memória/chat. Quem a tiver controla os fundos.
- Para uma carteira descartável com alguns dólares, isso é de baixo risco por design — mas a regra ainda vale.
- Para reutilizar uma carteira depois, armazene a chave em um gerenciador de segredos / variável de ambiente, então
restore(key). - Melhor:
export_encrypted(w, password)fornece um dicionário de keystore protegido por senha para armazenar em repouso;load_encrypted(keystore, password)traz a carteira de volta. Nunca armazene a chave bruta se puder armazenar o keystore.
Idiom de assinatura (nota para usuários de web3.py)
A carteira não expõe métodos sign_transaction / sign_message. A assinatura é feita por
funções auxiliares para as quais você passa a carteira — por exemplo, send_ether(w, to, amount) assina e transmite,
e sign_message(w, "text") retorna uma assinatura EIP-191 para fluxos de autenticação / login
(recuperável via eth_account Account.recover_message).
CLI no PATH
pip install pode avisar que o diretório de script chain-signer não está no seu PATH. A biblioteca funciona
independentemente; para usar a CLI diretamente, adicione esse diretório ao PATH ou execute python -m chain_signer ....
Superfície de ferramentas (para qualquer IA / MCP / CLI)
chain_signer.mcp_server expõe list_tools() e call_tool(name, arguments). CLI:
python -m chain_signer list
python -m chain_signer call create_wallet '{"chain":"evm"}'
Uso responsável
Ferramenta de propósito geral, não custodial. Você é responsável por usá-la dentro das leis e termos de serviço que se aplicam a você. Não é destinada ou comercializada para qualquer negociação restrita ou proibida em sua jurisdição.
Notas
- Saldos/transmissão usam o indexador Etherscan v2 (autoritativo), nunca um RPC público gratuito.
- Blocos de construção de baixo nível (
tx.send,call_contract, nonce/gás explícitos) permanecem disponíveis para uso avançado.
Pague uma API x402 em uma chamada
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.
Constrói + assina a autorização EIP-3009 que o x402 espera (o esquema "exato"). Seu agente paga uma API paga por conta própria — sem prompt de senha, sem cadastro, sem custódia.
Assinar dados tipados (EIP-712) — para pagamentos de agente / 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
Seu agente pode autorizar um pagamento assinando dados tipados localmente — sem prompt de senha, sem cadastro.
Execute como um servidor MCP
chain-signer também é um servidor Model Context Protocol (MCP), então agentes cientes de MCP podem usá-lo diretamente:
pip install chain-signer
chain-signer-mcp # speaks MCP over stdio (JSON-RPC 2.0)
Expõe 9 ferramentas. Os três guardiões de segurança (o ponto): preflight, inspect_signature,
check_action. Além da carteira não custodial: create_wallet, get_balance, send, call_contract, swap, bridge.
Conecte-o a qualquer cliente MCP (Claude Desktop, Cursor, etc.) adicionando-o à configuração
mcpServers do cliente:
{
"mcpServers": {
"chain-signer": {
"command": "chain-signer-mcp",
"env": { "ETHERSCAN_API_KEY": "your-key-for-live-balance-and-broadcast" }
}
}
}
Isso é tudo — o agente agora pode verificar cada tx, assinatura e ação através dos guardiões antes de
agir, e (opcionalmente) manter sua própria carteira para ler saldos, enviar e trocar como ferramentas nativas.
(ETHERSCAN_API_KEY é opcional; necessário apenas para leituras de saldo ao vivo e transmissão.)