Agentic Wallet Guardian
Motor de decisão auto-hospedado que fica entre agentes de IA e a execução em blockchain, retornando ALLOW/WARN/BLOCK antes que qualquer transação seja assinada.
Documentação
Agentic Wallet Guardian
Um mecanismo de decisão auto-hospedado que fica entre um agente de IA e a execução em blockchain. Agentes enviam uma ação proposta, o Guardian retorna uma decisão explicável ALLOW / WARN / BLOCK antes que qualquer coisa seja assinada ou transmitida.
POST /decision -> ALLOW / WARN / BLOCK (with a reasoned explanation)
Ele roda na sua própria infraestrutura, usando suas próprias regras de política e seus próprios dados de reputação — veja Por que auto-hospedado para entender por que isso importa e como isso difere de chamar uma API de segurança hospedada diretamente.
Por que auto-hospedado
Existem boas alternativas hospedadas para segurança de transações de agentes (AgentGuard da GoPlus, Blockaid, Chainalysis/TRM para conformidade). Se você só quer um score de risco e não se importa com quem vê a consulta, chamar uma dessas diretamente é menos trabalho do que rodar isto. O Guardian existe para os casos em que essa troca não funciona para você:
- Nada sobre quais carteiras, contratos ou valores seus agentes tocam
sai da sua infraestrutura. Verificações de inteligência de ameaças e de
permissão/negação de contratos são arquivos JSON locais que você mesmo preenche
(veja
data/threat_lists/README.md), não uma chamada de consulta a terceiros. Uma API hospedada inerentemente vê cada endereço e valor que você pergunta a ela. - Suas regras de política vivem no seu código, não no painel de um fornecedor.
Limites de gastos, portões de reputação e quais tipos de ação exigem
confirmação são Python puro em
guardian/policy/, revisáveis e alteráveis sem esperar o roadmap de produto de ninguém. - Sem taxas por chamada ou limites de taxa impostos por terceiros — apenas
os que você configura para seus próprios usuários (
GUARDIAN_RATE_LIMIT_PER_MINUTE). - Sem dependência de fornecedor. Cada fonte de dados externa (endpoint RPC, instância Blockscout, DexScreener) é substituível atrás de uma pequena interface de provedor — veja Arquitetura.
A troca honesta no outro sentido: você também assume a responsabilidade de executá-lo, manter suas listas locais de ameaças atualizadas, e você não recebe a cobertura de cadeias de um fornecedor hospedado ou uma equipe dedicada de pesquisa de ameaças de graça. Esta é a escolha certa para equipes que precisam especificamente de soberania de dados ou personalização profunda de políticas — não uma atualização estrita sobre toda opção hospedada.
Arquitetura
AI Agent
|
v
Action Intent
{ agent_id, wallet, chain, action_type,
target, amount, metadata }
|
v
┌───────────────────────────────┐
│ Guardian Decision Engine │
├───────────────────────────────┤
│ 1. Hard Rules │ <- chain support, sanity checks
│ 2. Wallet Intelligence │ <- mock | real RPC (web3.py)
│ 3. Token Intelligence │ <- mock | real DexScreener | real GoPlus
│ 4. Contract Intelligence │ <- local lists, then mock | real Blockscout | real GoPlus
│ 5. Simulation │ <- mock | real eth_call dry-run (see below)
│ 6. Threat Intelligence │ <- local JSON allow/deny lists
│ 7. Anomaly Detection │ <- vs. this agent's own history (see below)
│ 8. Policy Engine │ <- spending caps, reputation gates
│ 9. Risk Fusion │ <- signals -> single 0-100 score
│ 10. Reputation Adjustment │
│ 11. Explanation │ <- evidence -> human-readable reasons
└───────────────────────────────┘
|
v
ALLOW / WARN / BLOCK
|
v
Blockchain Execution
Cada fonte de dados nas etapas 2–4 é uma pequena interface de provedor com uma
implementação mock (zero configuração, zero chamadas de rede) e uma real, selecionada
por fonte via variável de ambiente — veja .env.example. Mudar do
modo demo para uma implantação real é uma mudança de configuração, não de código.
Estrutura do repositório
guardian/
config.py GuardianConfig - the one place that reads os.environ
core/ ActionIntent, Signal, Decision, EvaluationContext
(zero external dependencies - no pydantic/FastAPI)
decision/ DecisionEngine (orchestrator), RiskFusionEngine, hard rules
reasoning/ explanation + confidence builders
intelligence/
wallet/ analyzer.py + providers.py (mock | RpcWalletDataProvider)
token/ analyzer.py + providers.py (mock | DexScreenerTokenDataProvider | GoPlusTokenDataProvider)
contract/ analyzer.py + providers.py (mock | BlockscoutContractDataProvider | GoPlusContractDataProvider)
simulation/ pre-execution dry-run (mock | real eth_call) + tx_builder.py (real calldata for transfer/approve)
goplus_client.py shared GoPlus Token Security API client (used by both contract + token)
threat/ blocklist.py (local AddressList) + intelligence.py
policy/ PolicyEngine + policy templates (spending caps, reputation gates)
reputation/ AgentReputation (score derived from decision history)
memory/ storage.py (protocol) + InMemoryStorage + sqlite_storage.py
api/
main.py FastAPI app: /decision, /health, /capabilities, /agents/{id}/history, /demo/{scenario}
security.py API-key auth dependency + rate-limit middleware
schemas.py pydantic request/response models (API boundary only)
mcp_server.py MCP stdio server - same DecisionEngine, no HTTP required
data/threat_lists/ local, operator-maintained allow/deny lists (empty by default - see its README)
scripts/
refresh_ofac_list.py fetch OFAC's public SDN list into the local threat list
tests/ 101 tests covering the engine, policy, reputation, and every provider
guardian/* é intencionalmente livre de dependências (apenas biblioteca padrão,
exceto onde um provedor real precisa de httpx ou web3), para que o núcleo
de decisão possa ser testado unitariamente, embutido em outro serviço, ou portado para
um framework web diferente sem arrastar FastAPI junto. Apenas api/
toca em pydantic/FastAPI.
Transparência sobre o estado atual
Isto é infraestrutura de decisão real, executável, testada, com fontes de dados reais (não mock) disponíveis para cada sinal — mas "disponível" não é o mesmo que "ligar um interruptor e confiar cegamente." Detalhes:
- Carteira (provedor RPC):
is_contractetx_count(baseado em nonce) são confiáveis com qualquer endpoint JSON-RPC. A idade da carteira exige um nó com capacidade de arquivo e está desativada por padrão (GUARDIAN_RPC_ESTIMATE_AGE=false) — a maioria dos endpoints RPC públicos gratuitos não serve estado histórico, então isso falha de forma segura para "desconhecido" em vez de adivinhar. - Contrato (provedor Blockscout): consultas reais de status de verificação contra uma instância pública do Blockscout. O esquema exato de resposta e os limites de taxa podem mudar — isto é escrito para degradar para "desconhecido" em qualquer resposta inesperada, nunca para fabricar uma resposta, mas não foi testado sob carga contra tráfego de produção.
- Token (provedor DexScreener): dados reais de liquidez, mas combinar um símbolo de ticker simples a um par on-chain é inerentemente ambíguo (muitos tokens não relacionados compartilham um símbolo, e golpistas deliberadamente criam imitações). O provedor escolhe o par de maior liquidez na cadeia solicitada e relata sua própria confiança de correspondência em vez de apresentar um palpite como certo — para qualquer coisa onde essa ambiguidade importa, combine por endereço de contrato em vez de símbolo.
- Contrato + Token (provedor GoPlus): segurança real de contrato
(dono pode drenar, mintável, autodestrutível, dono oculto) e
segurança de negociação (honeypot, imposto de compra/venda, lista negra,
transferências pausáveis, concentração de detentores) da API Token Security
da GoPlus — significativamente mais tipos de sinal do que Blockscout/DexScreener
dão individualmente, já que a análise estática da própria GoPlus cobre ambos
em uma chamada. Dois limites reais: só tem dados para contratos que realmente
analisou (principalmente contratos de token, não contratos genéricos de dApp/router),
e
GoPlusTokenDataProviderprecisa de um endereço de contrato — um símbolo simples como "PEPE" não pode ser resolvido e é honestamente relatado como não verificável em vez de adivinhado. - Lista de endereços sancionados é real, com dados populados: 103 endereços (100
EVM + 3 Solana) da lista SDN do OFAC, via
0xB10C/ofac-sanctioned-digital-currency-addresses —
verificado de ponta a ponta (um endereço conhecidamente sancionado corretamente
aciona
BLOCKatravés do pipeline completo) e verificado para refletir corretamente remoções, não apenas adições (os endereços do Tornado Cash, removidos da lista SDN em março de 2025, estão corretamente ausentes). Reexecutescripts/refresh_ofac_list.pyperiodicamente — sanções mudam em ambas as direções. malicious_contracts.json/verified_contracts.jsonainda são entregues vazios de propósito (vejadata/threat_lists/README.md) — não há uma única fonte autoritativa para "contrato malicioso" da mesma forma que a lista do OFAC é autoritativa para sanções, então popular estes é uma decisão de julgamento para quem opera esta instância, não algo para semear por padrão com entradas não verificadas.- Simulação é real, mas condicional.
RpcSimulationProvider(GUARDIAN_SIMULATION_PROVIDER=rpc) genuinamente executa um dry-run de uma transação viaeth_call/eth_estimateGascontra o estado atual da cadeia — um revert retorna com seu motivo real, não um palpite, e valores de ERC-20approve()são decodificados de calldata real em vez de inferidos. Isso ativa quando o chamador fornece calldata bruto viaintent.metadata["data"], OU — novo — quandoGUARDIAN_TX_BUILDER=rpctambém está definido e a intenção é umtransferouapprovesimples (veja o próximo item). Uma intençãoswapsem transação construída ainda não tem nada para dry-run — o Guardian relata isso honestamente (simulation_not_attempted) em vez de adivinhar. - Construção de transação fecha parte dessa lacuna, deliberadamente não toda.
RpcTransactionBuilder(GUARDIAN_TX_BUILDER=rpc) transforma uma intenção semânticatransfer/approveem calldata real — busca odecimals()real do token via RPC em vez de assumir 18 (uma suposição errada ali escalaria o valor por ordens de magnitude), e deliberadamente não tem registro de endereço de token codificado: um símbolo simples como "USDC" é recusado em vez de adivinhado, já que um endereço errado aqui não seria apenas um sinal de risco ruim, seria um artefato que poderia acabar em uma transação real.swapé construído apenas contra Uniswap V2 Router02 (um contrato imutável e bem conhecido — seletores de função calculados localmente viaWeb3.keccak, não copiados da memória) — cotação on-chain real degetAmountsOut(),max_slippage_bpsfornecido pelo chamador obrigatório (nunca um padrão, mesmo raciocínio dos decimais acima).bridgeé um problema genuinamente aberto de roteamento L2/bridge em geral — dezenas de protocolos, modelos de confiança muito diferentes — mas este módulo lida com uma fatia bem delimitada: depósitos L1 -> L2 através da ponte oficial OP Stack da própria cadeia de destino (atualmente: Base e Optimism —depositETHTo/depositERC20ToemL1StandardBridge, ambos os endereços verificados de forma independente — Base contra o rótulo do Etherscan mais basehub.org, Optimism contra o registro oficial ethereum-optimism/superchain-registry mais uma segunda configuração independente de ferramenta de dev — antes de serem codificados). Retiradas L2 -> L1 NÃO são construídas — esse é um fluxo genuinamente diferente, muito mais lento, de prova/janela de desafio, não uma variante da chamada de depósito. Ponte para qualquer outro lugar, ou via qualquer ponte não canônica, retornaNoneem vez de adivinhar. - Armazenamento:
InMemoryStorage(padrão, zero configuração),SQLiteStorage(GUARDIAN_STORAGE_BACKEND=sqlite— persiste entre reinicializações, sem infraestrutura externa), ouPostgresStorage(GUARDIAN_STORAGE_BACKEND=postgres+GUARDIAN_POSTGRES_DSN— a opção certa para múltiplas réplicas atrás de um balanceador de carga, onde o modelo de escrita única do SQLite se torna o gargalo;pip install -r requirements-postgres.txt). Testado contra uma instância Postgres local real, não mockado — vejatests/test_postgres_storage.py. Redis está ainda aberto se você quiser especificamente; a interface de dois métodosMemoryBackendé pequena o suficiente para implementar contra qualquer coisa. - Autenticação/limite de taxa da API são intencionalmente mínimos — construídos para uma instância auto-hospedada atrás do seu próprio limite de rede, não um gateway multi-tenant. Coloque um gateway de API real na frente se precisar disso.
- Não auditado por segurança. O mecanismo de política e a lógica de fusão de risco
não foram revisados por ninguém fora deste repositório. Trate
BLOCKcomo um sinal forte, não uma garantia, até que isso aconteça.
Tudo a jusante de um Signal — fusão, política, reputação,
explicação, a API — não precisa mudar conforme qualquer um dos itens acima
é endurecido ainda mais. Esse limite é o contrato de design real aqui.
Início rápido
Modo demo de zero configuração (provedores mock, armazenamento em memória, sem autenticação):
pip install -r requirements.txt
uvicorn api.main:app --reload
Ou com Docker:
docker compose up --build
Experimente os cenários prontos:
curl http://localhost:8000/demo/safe
curl http://localhost:8000/demo/unknown
curl http://localhost:8000/demo/malicious
Ou envie sua própria intenção:
curl -X POST http://localhost:8000/decision \
-H "Content-Type: application/json" \
-d '{
"agent_id": "trading-agent-001",
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"chain": "ethereum",
"action_type": "swap",
"from_token": "ETH",
"to_token": "USDC",
"amount": 5
}'
Indo do demo para uma implantação real auto-hospedada
Copie .env.example para .env e ajuste:
cp .env.example .env
No mínimo para uma implantação real: defina GUARDIAN_API_KEY (autenticação está
desativada por padrão), GUARDIAN_STORAGE_BACKEND=sqlite (persistência), e quaisquer
variáveis GUARDIAN_*_PROVIDER que você quiser apontar para dados reais em vez de
mock — veja os comentários em .env.example para cada opção, e
os docstrings de RpcWalletDataProvider/BlockscoutContractDataProvider/
DexScreenerTokenDataProvider/GoPlusContractDataProvider/
GoPlusTokenDataProvider para o que cada um realmente
te dá.
MCP (sem HTTP necessário)
Para frameworks de agentes que falam MCP (LangChain, CrewAI, Claude Desktop,
etc.), mcp_server.py expõe o mesmo mecanismo de decisão como duas ferramentas
(evaluate_action, get_agent_history) via stdio — instale
requirements-mcp.txt em seu próprio ambiente virtual (veja o comentário
no topo desse arquivo para saber por que ele não pode compartilhar um ambiente com
requirements.txt) e aponte seu cliente MCP para
python mcp_server.py.
Executando os testes
pip install -r requirements.txt -r requirements-chain.txt
pytest -q
requirements-chain.txt (web3) só é necessário para os testes do provedor
RPC; o resto da suíte roda apenas com requirements.txt. O
núcleo guardian/* não tem dependências externas além disso, então também é
executável com:
PYTHONPATH=. python3 -m unittest discover -s tests -v
CI (.github/workflows/ci.yml) roda a suíte completa em cada push/PR
contra Python 3.11 e 3.12.
Decisões assinadas e verificáveis (OAA)
Cada decisão que este serviço retorna — de uma única verificação de política até o pipeline completo — é assinada como um token OAA (Open Agent Attestation): um JWT assinado com Ed25519 que envolve a decisão, a ação e o motivo.
Qualquer pessoa com a chave pública pode verificar uma decisão offline, sem chamar de volta a instância do Guardian que a emitiu — útil para um auditor, um serviço downstream, ou apenas um registro que você quer confiar depois sem confiar no servidor que o produziu.
python examples/example_oaa_attestation.py
python examples/example_full_pipeline.py # capability -> intent -> engine -> OAA
A implementação de referência do OAA tem cerca de 150 linhas
(oaa.py/attestation.py upstream) e é compartilhada, sem modificações,
entre este projeto e agent-guardrail —
mesmo formato de assinatura, mesmo caminho de verificação, sem fork por projeto.
Usando o Guardian na frente da MetaMask Agent Wallet
O Guard Mode / Beast Mode da MetaMask Agent Wallet aplicam os mesmos
limites de gasto estáticos e allowlists para cada agente. O Guardian é uma
segunda verificação independente na frente deles: esta ação específica parece
correta para este agente, agora mesmo — antes de o CLI mm ser
invocado.
skills/guardian-check/ é uma
Agent Skill padrão — o mesmo formato aberto que a própria MetaMask
usa para mm (npx skills add MetaMask/agent-skills). Instale-a
junto com a skill da própria MetaMask em qualquer runtime compatível com
Agent-Skills (Claude Code, Cursor, Codex, OpenClaw), e o agente
chamará uma instância do Guardian em execução para uma decisão ALLOW/WARN/BLOCK
antes de executar qualquer comando mm que mova fundos — mm send, mm swap, mm bridge, mm perps, mm predict trade, mm earn, mm aave, mm pay.
O Guardian nunca detém chaves e nunca executa nada — mm continua sendo
a única coisa que assina ou transmite. Este é um portão de decisão que o
agente é instruído a consultar primeiro, não uma modificação no
pipeline da própria MetaMask (não há hook público para isso hoje).
uvicorn api.main:app --reload # run Guardian locally
export GUARDIAN_API_URL="http://localhost:8000"
python skills/guardian-check/scripts/check.py \
--agent-id my-agent --wallet 0x... --chain ethereum \
--action-type transfer --target 0x... --amount 50
Roadmap
Substituir os analisadores mock de wallet/token/contrato por fontes de dados reais.Concluído — veja Honestidade sobre o estado atual para saber o que "real" cobre e ainda não cobre por fonte.Integrar simulação real de pré-execução.Concluído paratransfer/approvede ponta a ponta (GUARDIAN_SIMULATION_PROVIDER=rpc+GUARDIAN_TX_BUILDER=rpc— veja Honestidade sobre o estado atual).Concluído contra o Uniswap V2 Router02 — cotaçãoswapprecisa de roteamento DEX real.getAmountsOut()real on-chain,max_slippage_bpsexplícito fornecido pelo chamador (nunca padronizado), nenhum calldata construído sem uma cotação real. Corrigido um bug real encontrado durante a construção: a simulação estava fazendo dry-run contraintent.target(o destinatário/gastador codificado dentro do calldata ERC-20) em vez do contrato real sendo chamado (from_token) — o que significa que a simulação de transfer/approve "tinha sucesso" silenciosamente contra qualquer destinatário EOA, independentemente de a chamada real ter revertido.BuiltTransactionagora carrega umtoexplícito; vejatests/test_tx_builder.pypara os testes de regressão que teriam detectado isso.Concluído para depósitos L1->L2 para Base e Optimism via obridgeainda em aberto.L1StandardBridgeoficial do OP Stack (depositETHTo/depositERC20To) — outros destinos, outros protocolos de ponte e retiradas L2->L1 todos permanecem em aberto; veja Honestidade sobre o estado atual.Popular feeds de threat-intel / sanções; parar de enviar conjuntos vazios.Concluído para sanções (sanctioned_addresses.json— 103 endereços reais da lista OFAC SDN, atualizáveis viascripts/refresh_ofac_list.py).malicious_contracts.json/verified_contracts.jsonpermanecem vazios por design — não existe uma única fonte autoritativa para populá-los da mesma forma que a lista da OFAC faz para sanções.TrocarInMemoryStoragepor um backend persistente.SQLiteStorageestá disponível;um backend Postgres/Redis ainda está em aberto para implantações multi-réplica.PostgresStorageconcluído — testado contra uma instância Postgres local real (tests/test_postgres_storage.py), mesma interfaceMemoryBackendde dois métodos que os outros backends. Redis permanece em aberto se especificamente desejado.Adicionar um wrapper de servidor MCP.Concluído (mcp_server.py). Um SDK empacotado em Python/TypeScript sobre a API REST ainda está em aberto.- Publicar uma especificação OpenAPI e um endpoint de demonstração hospedado.
- Fazer o motor de políticas e a fusão de risco serem revisados/auditados antes que alguém
confie em um
BLOCKdeste serviço em produção — é uma ferramenta de segurança, então precisa do mesmo escrutínio que aplica aos outros. Adicionar limites de capacidade por agente (escopo de delegação).Concluído —guardian/policy/capabilities.py. Opt-in: um operador pode conceder a um agente específico uma capacidade com escopo (tipos de ação permitidos, cadeias permitidas, limites de gasto por ação e diários, uma expiração) com zero material de chave privada envolvido. Agentes sem concessão não são afetados — vejaexamples/example_capability_limits.py. Gerenciamento real de chaves (session keys, abstração de conta) permanece deliberadamente fora do escopo — um problema categoricamente de maior risco.Verificar a intenção declarada contra resultados de simulação decodificados.Concluído paraapprove—guardian/decision/intent_verification.pydetecta o caso em que um agente declara um valor, mas o calldata real que recebeu codifica um valor significativamente diferente (mas ainda finito). Isso é distinto do sinal existente de "approval ilimitado", que só detecta valores próximos de uint256-max — vejaexamples/example_intent_verification.py.Sinalizar ações que se desviam do padrão histórico do próprio agente.Concluído —guardian/intelligence/anomaly/analyzer.py. Distinto de reputação (uma pontuação de confiança única) e política (estática, limites definidos pelo operador): isso compara a intenção atual contra o histórico registrado deste agente específico — novo tipo de ação, nova cadeia ou um valor que é um outlier estatístico em comparação com o que este agente fez antes, mesmo que esteja dentro dos limites da política e a reputação do agente esteja boa. Relata honestamente "histórico insuficiente" em vez de adivinhar uma linha de base com menos de 5 pontos de dados anteriores — vejatests/test_anomaly_detection.py.Ficar na frente de uma carteira de agente real, não apenas aceitar intents de um chamador de API genérico.Concluído para MetaMask Agent Wallet —skills/guardian-check/é uma Agent Skill padrão que um agente instala junto com a skillmmda própria MetaMask; o agente a chama antes de executar qualquer comandommque mova fundos e só prossegue em ALLOW. Testado de ponta a ponta contra uma instânciauvicornao vivo (ALLOW/WARN/BLOCK/config-error todos exercitados de verdade, não apenas afirmados) — veja a seção "Usando o Guardian na frente da MetaMask Agent Wallet" acima. Não existe hook público (ainda) para executar dentro do pipeline da própria MetaMask; isso funciona na camada de orquestração do agente.
Projetos relacionados
Mesmo autor, mesmo princípio aplicado em outros lugares:
- agent-guardrail — um firewall de políticas genérico para chamadas de ferramentas de agentes de IA (não específico de blockchain). Publicado no PyPI, MIT, 46 testes.
- x402-attest — atestações criptograficamente assinadas (Ed25519), verificáveis de forma independente, para decisões de política de pagamento entre agentes. Prova de conceito inicial.
- open-agent-attestation — especificação aberta neutra de fornecedor (JWT+EdDSA) para assinar decisões de política de agentes, verificável por qualquer pessoa. O x402-attest acima usa um formato personalizado; esta é a versão generalizada. Rascunho v0.1.
Licença
MIT — veja LICENSE.