chain-signer
Suite de seguridad de pre-firma para agentes de IA: detecta drenajes de billeteras, phishing de permisos y acciones riesgosas antes de firmar en cadenas EVM. Sin custodia.
Documentación
chain-signer
Un conjunto de seguridad para agentes de IA: el cinturón de seguridad que detecta lo peligroso ANTES de que ocurra. Tres guardias, cada uno invocable por separado (y como herramientas MCP), que se combinan con cualquier billetera o stack de identidad:
preflight(tx)— decodifica una transacción sin firmar y marca los drenajes antes de firmar (aprobación ilimitada/grande, approve-all, token y NFT transferFrom, actualización de proxy, permit on-chain, Permit2 on-chain approve/permit/transferFrom, aprobaciones ocultas en multicall incl. lotes del router de Uniswap y Multicall3 aggregate/aggregate3/aggregate3Value (el helper de lotes desplegado en cada cadena EVM), aprobaciones envueltas en ERC-4337/smart-account execute/executeBatch, Gnosis Safe multiSend/execTransaction y DSProxy execute, drenajes enrutados a través del Uniswap Universal Router (comandos Permit2 permit/transferFrom incl. sub-planes), 1inch AggregationRouter v5 swap() con salida redirigida o slippage cero, 0x ExchangeProxy transformERC20() con slippage cero, delegación de cuenta EIP-7702, will-revert).inspect_typed_data(td)— detecta permit-phishing en un mensaje EIP-712 antes de que el agente lo firme (ERC-2612, Uniswap Permit2 incl. SignatureTransfer + variantes witness, permisos estilo DAI) y órdenes de Seaport que regalan activos — consideración cero, ingresos enrutados a un tercero, u ocultos en un árbol BulkOrder.check_action(action, policy)— aplica límites de permitir/prohibir + valor/destinatario antes de que el agente actúe.
Los tres fallan de forma segura y son guardias, no garantías. También incluido: una billetera multi-cadena no custodial (burner, balance, send, swap) — el agente tiene su propia clave y firma localmente. Sin MetaMask, sin cuenta, sin custodia.
from chain_signer import assert_safe
assert_safe(tx) # raises if the tx is a drain/unlimited-approval/revert — review before signing
Instalación
pip install chain-signer
export ETHERSCAN_API_KEY=... # for live balance reads + broadcast (Etherscan v2)
El soporte de Bitcoin/Solana es opcional: pip install "chain-signer[all]".
Inicio rápido (10 segundos — sin conexión, sin clave, sin fondos, sin red)
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
Esa es la cuña: el drenaje se marca antes de que lo firmes — sin clave, sin fondos, sin red.
Billetera incluida (opcional — los guardias se combinan con cualquier billetera)
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
Hay demos completas ejecutables en el repositorio: examples/agent_safety_demo.py (los tres guardias detienen tres ataques reales) y examples/quickstart.py (billetera) — clónalo para ejecutarlas, o simplemente importa como arriba.
Verificación previa de seguridad (la cuña)
Antes de que un agente firme, entrega la tx sin firmar a preflight() — decodifica el calldata y devuelve los riesgos, o usa assert_safe() para detener en seco ante una bandera HIGH. Sin conexión, sin red, nunca lanza excepciones.
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
Lo que marca hoy: aprobación ilimitada/grande, increaseAllowance, setApprovalForAll,
ERC-20 transferFrom + ERC-721/1155 safeTransferFrom (drenajes de token y NFT), ERC-777 authorizeOperator/operatorSend
(concesión de operador + drenajes de extracción de operador), permit on-chain estilo ERC-2612 y DAI,
Permit2 on-chain approve/permit/transferFrom (individual y por lotes — el router de aprobación dominante:
allowance uint160 ilimitada + extracción de drenaje) además de Permit2 SignatureTransfer permit(Witness)TransferFrom
(el pull de intención/firma de un solo uso que usan los protocolos de relleno), proxy upgradeTo/upgradeToAndCall, aprobaciones ocultas dentro de multicall (todas las variantes de router, anidadas) y Multicall3 aggregate/aggregate3/aggregate3Value (el helper canónico de lotes
desplegado en una dirección en cada cadena EVM), aprobaciones envueltas en ERC-4337/smart-account execute/executeBatch, Gnosis Safe
multiSend/execTransaction, o DSProxy execute(target,data)/execute(code,data) (decodificado y recursado),
drenajes enrutados a través del Uniswap Universal Router
(execute(commands,inputs) — comandos Permit2 permit/transferFrom, por lotes y EXECUTE_SUB_PLAN),
delegación de cuenta EIP-7702 (el drenador de "actualización de billetera"), valor nativo grande,
calldata opaco, llamadas malformadas y will-revert (con un hook de simulación).
Límites honestos (léelos): esto es análisis ESTÁTICO — decodifica calldata y compara con patrones de drenaje conocidos. NO es un simulador de transacciones: no detectará un drenaje novedoso/ofuscado que no pueda decodificar
(esos reciben una bandera de "desconocido" de baja severidad, no un bloqueo), y los escáneres basados en simulación profundizan más ahí.
La cobertura de seguridad es solo EVM hoy (sin análisis de tx de Solana/Bitcoin). Y aún no está probado en campo a
escala. Una línea de defensa de primera línea para patrones conocidos — no una garantía. Combínalo con simulación + revisión
humana para acciones de alto valor.
Inspector de mensajes firmados (la mitad fuera de cadena)
Un drenaje no necesita una transacción. Una dApp puede pedirle al agente que firme un mensaje EIP-712 —
lo más peligroso es un permit que otorga una allowance de token ilimitada, que preflight (una verificación de tx)
no puede ver. inspect_typed_data() lo detecta antes de que el agente firme:
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', ...}]
Cubre las tres formas principales de permit: ERC-2612, Uniswap Permit2 (PermitSingle/PermitBatch, además de
SignatureTransfer y las variantes witness que usan los protocolos de intención), y estilo DAI (allowed: true),
además de órdenes de mercado Seaport que entregan activos a cambio de nada — consideración cero, ingresos
enrutados a un tercero mientras tu activo sale, o el mismo regalo enterrado en un árbol merkle BulkOrder.
Sin conexión, nunca lanza excepciones.
Firmante protegido (pantalla + firma en una sola llamada)
inspect_typed_data solo protege cuando el agente recuerda llamarlo primero — sign_typed_data
solo firmará felizmente un mensaje de permit-phishing. guarded_sign_typed_data() compone los dos para que
la firma sea examinada por defecto: inspecciona y luego se niega a firmar un drenaje de riesgo HIGH.
from chain_signer import guarded_sign_typed_data, SignatureBlocked
sig = guarded_sign_typed_data(wallet, domain, types, message, "Permit") # raises SignatureBlocked on a drain
En un mensaje limpio, la firma es byte-idéntica a sign_typed_data; pasa force=True para anular.
Puerta de política de acción (inspecciona lo que el agente HACE)
La identidad te dice quién es el agente; no detiene una acción mala. check_action() aplica una
política a una llamada de herramienta propuesta antes de que se ejecute — a prueba de fallos (deniega en entrada ilegible):
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', ...}]}
Los tres guardias están expuestos como herramientas MCP (preflight, inspect_signature, check_action) — cualquier
runtime de agente (Claude, Cursor, …) puede llamarlos directamente, solo lectura, sin clave.
Qué se detecta y qué no — el mapa honesto de cobertura de amenazas: docs/THREAT-COVERAGE.md.
Lo que obtienes
preflight(tx)/assert_safe(tx)— decodifica una tx sin firmar y marca patrones de drenaje antes de firmar.inspect_typed_data(td)— marca permit-phishing en un mensaje EIP-712 antes de que el agente lo firme.guarded_sign_typed_data(w, domain, types, message, primary_type)— examina y luego firma; se niega a un drenaje.check_action(action, policy)— aplica límites de permitir/prohibir + valor/destinatario antes de que el agente actúe.burner()— una billetera nueva para una tarea puntual; descártala cuando termines.restore(key)— recarga una billetera más tarde desde su clave privada exportada (misma clave → misma dirección).send_ether(w, to, amount)— envía en ETH (no wei); nonce, gas y transmisión se manejan por ti.get_balance(w)— saldo en vivo desde la cadena (índice Etherscan v2, no un RPC público inestable).swap(...)— swaps de tokens vía 0x/Paraswap.- Billeteras opcionales de Solana + Bitcoin vía el extra
[all].
Garantía no custodial
La clave privada se genera/carga localmente, se usa solo para firmar, y nunca se registra, devuelve o almacena por esta biblioteca. Tú tienes la clave; nosotros nunca tocamos tus fondos. Ese es todo el diseño.
Manejo de la clave (lee esto)
w.private_key son las llaves de la billetera. Trátala como una contraseña:
- NUNCA la registres, la imprimas en producción, o la escribas en notas/memoria/chat. Cualquiera que la tenga controla los fondos.
- Para una billetera desechable con unos pocos dólares, esto es de bajo riesgo por diseño — pero la regla sigue en pie.
- Para reutilizar una billetera más tarde, guarda la clave en un gestor de secretos / variable de entorno, luego
restore(key). - Mejor:
export_encrypted(w, password)da un dict de keystore protegido por contraseña para almacenar en reposo;load_encrypted(keystore, password)trae la billetera de vuelta. Nunca almacenes la clave cruda si puedes almacenar el keystore.
Idioma de firma (nota para usuarios de web3.py)
La billetera no expone métodos sign_transaction / sign_message. La firma se hace con
helpers de función a los que le pasas la billetera — p. ej. send_ether(w, to, amount) firma y transmite,
y sign_message(w, "text") devuelve una firma EIP-191 para flujos de autenticación / inicio de sesión
(recuperable vía eth_account Account.recover_message).
CLI en PATH
pip install puede advertir que el directorio de scripts de chain-signer no está en tu PATH. La biblioteca funciona
de todos modos; para usar la CLI directamente, agrega ese directorio a PATH o ejecuta python -m chain_signer ....
Superficie de herramientas (para cualquier IA / MCP / CLI)
chain_signer.mcp_server expone list_tools() y call_tool(name, arguments). CLI:
python -m chain_signer list
python -m chain_signer call create_wallet '{"chain":"evm"}'
Uso responsable
Herramienta de propósito general, no custodial. Eres responsable de usarla dentro de las leyes y términos de servicio que te aplican. No está destinada ni comercializada para ningún trading restringido o prohibido en tu jurisdicción.
Notas
- Los saldos/transmisiones usan el índice Etherscan v2 (autoritativo), nunca un RPC público gratuito.
- Los bloques de construcción de bajo nivel (
tx.send,call_contract, nonce/gas explícitos) siguen disponibles para uso avanzado.
Paga una API x402 en una sola llamada
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.
Construye + firma la autorización EIP-3009 que x402 espera (el esquema "exacto"). Tu agente paga una API de pago por sí mismo — sin prompt de contraseña, sin registro, sin custodia.
Firma datos tipados (EIP-712) — para pagos 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
Tu agente puede autorizar un pago firmando datos tipados localmente — sin prompt de contraseña, sin registro.
Ejecutar como servidor MCP
chain-signer también es un servidor Model Context Protocol (MCP), por lo que los agentes compatibles con MCP pueden usarlo directamente:
pip install chain-signer
chain-signer-mcp # speaks MCP over stdio (JSON-RPC 2.0)
Expone 9 herramientas. Los tres guardias de seguridad (la cuña): preflight, inspect_signature,
check_action. Además de la billetera no custodial: create_wallet, get_balance, send, call_contract, swap, bridge.
Conéctalo a cualquier cliente MCP (Claude Desktop, Cursor, etc.) agregándolo a la configuración
mcpServers del cliente:
{
"mcpServers": {
"chain-signer": {
"command": "chain-signer-mcp",
"env": { "ETHERSCAN_API_KEY": "your-key-for-live-balance-and-broadcast" }
}
}
}
Eso es todo — el agente ahora puede examinar cada tx, firma y acción a través de los guardias antes de
actuar, y (opcionalmente) tener su propia billetera para leer saldos, enviar y hacer swaps como herramientas nativas.
(ETHERSCAN_API_KEY es opcional; solo se necesita para lecturas de saldo en vivo y transmisión.)