{
  "markdown": "<!-- mcp-name: io.github.rudimentall1/agentic-wallet-guardian-v3 -->\n# Agentic Wallet Guardian\n[![agentic-wallet-guardian-v3 MCP server](https://glama.ai/mcp/servers/rudimentall1/agentic-wallet-guardian-v3/badges/card.svg)](https://glama.ai/mcp/servers/rudimentall1/agentic-wallet-guardian-v3)\n\n📄 [Read the white paper](docs/whitepaper.pdf)\n\n**A self-hosted decision engine that sits between an AI agent and blockchain\nexecution.** Agents submit a proposed action, Guardian returns an\nexplainable ALLOW / WARN / BLOCK before anything gets signed or broadcast.\n\n```\nPOST /decision   ->   ALLOW / WARN / BLOCK  (with a reasoned explanation)\n```\n\nIt runs on your own infrastructure, using your own policy rules and your\nown reputation data - see [Why self-hosted](#why-self-hosted) for why that\nmatters and how this differs from calling a hosted security API directly.\n\n**See it decide, live:** run the API locally (`GUARDIAN_ENABLE_CORS_FOR_BROWSER_DEMO=true\nuvicorn api.main:app --reload`), then open [`examples/browser-demo.html`](examples/browser-demo.html)\nin a browser - no build step, no server for the page itself. Every\nscenario button sends a real `POST /decision` to your running instance\nand renders the actual response (risk score, every signal that fed into\nit, every policy rule that fired) - nothing in the page is scripted or\nfaked. `GUARDIAN_ENABLE_CORS_FOR_BROWSER_DEMO` is off by default (see\n`guardian/config.py`) since it's specifically for this local-demo case,\nnot something to leave on for a real deployment.\n\n---\n\n## Why self-hosted\n\nThere are good hosted alternatives for agent-transaction security (GoPlus's\nAgentGuard, Blockaid, Chainalysis/TRM for compliance). If you just want a\nrisk score and don't care who sees the query, calling one of those directly\nis less work than running this. Guardian exists for the cases where that\ntradeoff doesn't work for you:\n\n- **Nothing about which wallets, contracts, or amounts your agents touch\n  leaves your infrastructure.** Threat-intel and contract allow/deny checks\n  are local JSON files you populate yourself (see\n  `data/threat_lists/README.md`), not a lookup call to a third party. A\n  hosted API inherently sees every address and amount you ask it about.\n- **Your policy rules live in your code, not a vendor's dashboard.**\n  Spending caps, reputation gates, and which action types require\n  confirmation are plain Python in `guardian/policy/`, reviewable and\n  changeable without waiting on anyone else's product roadmap.\n- **No per-call fees or rate limits imposed by someone else** - only the\n  ones you configure for your own users (`GUARDIAN_RATE_LIMIT_PER_MINUTE`).\n- **No vendor lock-in.** Every external data source (RPC endpoint,\n  Blockscout instance, DexScreener) is swappable behind a small provider\n  interface - see [Architecture](#architecture).\n\nThe honest tradeoff going the other way: you also take on running it,\nkeeping your local threat lists current, and you don't get a hosted\nvendor's chain coverage or dedicated threat-research team for free. This is\nthe right choice for teams that specifically need data sovereignty or deep\npolicy customization - not a strict upgrade over every hosted option.\n\n---\n\n## Architecture\n\n```\n                AI Agent\n                    |\n                    v\n             Action Intent\n   { agent_id, wallet, chain, action_type,\n     target, amount, metadata }\n                    |\n                    v\n        ┌───────────────────────────────┐\n        │   Guardian Decision Engine    │\n        ├───────────────────────────────┤\n        │  1. Hard Rules                │  <- chain support, sanity checks\n        │  2. Wallet Intelligence       │  <- mock | real RPC (web3.py)\n        │  3. Token Intelligence        │  <- mock | real DexScreener | real GoPlus\n        │  4. Contract Intelligence     │  <- local lists, then mock | real Blockscout | real GoPlus\n        │  5. Simulation                │  <- mock | real eth_call dry-run (see below)\n        │  6. Threat Intelligence       │  <- local JSON allow/deny lists\n        │  7. Anomaly Detection         │  <- vs. this agent's own history (see below)\n        │  8. Policy Engine             │  <- spending caps, reputation gates\n        │  9. Risk Fusion               │  <- signals -> single 0-100 score\n        │ 10. Reputation Adjustment     │\n        │ 11. Explanation               │  <- evidence -> human-readable reasons\n        └───────────────────────────────┘\n                    |\n                    v\n          ALLOW / WARN / BLOCK\n                    |\n                    v\n          Blockchain Execution\n```\n\nEvery data source in steps 2-4 is a small provider interface with a mock\nimplementation (zero config, zero network calls) and a real one, selected\nper-source by environment variable - see `.env.example`. Switching from\ndemo mode to a real deployment is a config change, not a code change.\n\n### Repository layout\n\n```\nguardian/\n    config.py          GuardianConfig - the one place that reads os.environ\n    core/               ActionIntent, Signal, Decision, EvaluationContext\n                            (zero external dependencies - no pydantic/FastAPI)\n    decision/           DecisionEngine (orchestrator), RiskFusionEngine, hard rules\n    reasoning/          explanation + confidence builders\n    intelligence/\n        wallet/           analyzer.py + providers.py (mock | RpcWalletDataProvider)\n        token/            analyzer.py + providers.py (mock | DexScreenerTokenDataProvider | GoPlusTokenDataProvider)\n        contract/         analyzer.py + providers.py (mock | BlockscoutContractDataProvider | GoPlusContractDataProvider)\n        simulation/       pre-execution dry-run (mock | real eth_call) + tx_builder.py (real calldata for transfer/approve)\n        goplus_client.py  shared GoPlus Token Security API client (used by both contract + token)\n        threat/           blocklist.py (local AddressList) + intelligence.py\n    policy/             PolicyEngine + policy templates (spending caps, reputation gates)\n    reputation/         AgentReputation (score derived from decision history)\n    memory/             storage.py (protocol) + InMemoryStorage + sqlite_storage.py\napi/\n    main.py             FastAPI app: /decision, /health, /capabilities, /agents/{id}/history, /demo/{scenario}\n    security.py         API-key auth dependency + rate-limit middleware\n    schemas.py          pydantic request/response models (API boundary only)\nmcp_server.py           MCP stdio server - same DecisionEngine, no HTTP required\ndata/threat_lists/      local, operator-maintained allow/deny lists (empty by default - see its README)\nscripts/\n    refresh_ofac_list.py   fetch OFAC's public SDN list into the local threat list\ntests/                  101 tests covering the engine, policy, reputation, and every provider\n```\n\n`guardian/*` is intentionally dependency-free (standard library only,\nexcept where a real provider needs `httpx` or `web3`), so the decision\ncore can be unit-tested, embedded in another service, or ported to a\ndifferent web framework without dragging FastAPI along. Only `api/`\ntouches pydantic/FastAPI.\n\n---\n\n## Honesty about the current state\n\nThis is real, runnable, tested decision infrastructure with real (not\nmock) data sources available for every signal source - but \"available\"\nisn't the same as \"flip a switch and trust it blindly.\" Specifics:\n\n- **Wallet (RPC provider):** `is_contract` and `tx_count` (nonce-based) are\n  reliable with any JSON-RPC endpoint. Wallet *age* requires an\n  archive-capable node and is off by default\n  (`GUARDIAN_RPC_ESTIMATE_AGE=false`) - most free public RPC endpoints\n  don't serve historical state, so this fails closed to \"unknown\" rather\n  than guessing.\n- **Contract (Blockscout provider):** real verification-status lookups\n  against a public Blockscout instance. Their exact response schema and\n  rate limits can change - this is written to degrade to \"unknown\" on any\n  unexpected response, never to fabricate an answer, but hasn't been load-\n  tested against production traffic.\n- **Token (DexScreener provider):** real liquidity data, but matching a\n  bare ticker symbol to an on-chain pair is inherently ambiguous (many\n  unrelated tokens share a symbol, and scammers deliberately mint\n  look-alikes). The provider picks the highest-liquidity pair on the\n  requested chain and reports its own match confidence rather than\n  presenting a guess as certain - for anything where that ambiguity\n  matters, match by contract address instead of symbol.\n- **Contract + Token (GoPlus provider):** real contract-security\n  (owner-can-drain, mintable, self-destruct, hidden owner) and\n  trading-security (honeypot, buy/sell tax, blacklist, pausable transfers,\n  holder concentration) from GoPlus's Token Security API - meaningfully\n  more signal types than Blockscout/DexScreener give individually, since\n  GoPlus's own static analysis covers both in one call. Two real limits:\n  it only has data for contracts it's actually analyzed (mostly token\n  contracts, not generic dApp/router contracts), and `GoPlusTokenDataProvider`\n  needs a contract *address* - a bare symbol like \"PEPE\" can't be resolved\n  and is honestly reported as unverifiable rather than guessed at.\n- **Sanctioned-address list is real, populated data**: 103 addresses (100\n  EVM + 3 Solana) from OFAC's SDN list, via\n  [0xB10C/ofac-sanctioned-digital-currency-addresses](https://github.com/0xB10C/ofac-sanctioned-digital-currency-addresses) -\n  verified end-to-end (a known-sanctioned address correctly triggers\n  `BLOCK` through the full pipeline) and verified to correctly reflect\n  delistings, not just additions (Tornado Cash's addresses, removed from\n  the SDN list in March 2025, are correctly absent). Re-run\n  `scripts/refresh_ofac_list.py` periodically - sanctions change in both\n  directions.\n- **`malicious_contracts.json` / `verified_contracts.json` still ship\n  empty on purpose** (see `data/threat_lists/README.md`) - there's no\n  single authoritative source for \"malicious contract\" the way OFAC's\n  list is authoritative for sanctions, so populating these is a judgment\n  call for whoever operates this instance, not something to seed by\n  default with unverified entries.\n- **Simulation is real, but conditional.** `RpcSimulationProvider`\n  (`GUARDIAN_SIMULATION_PROVIDER=rpc`) genuinely dry-runs a transaction via\n  `eth_call`/`eth_estimateGas` against current chain state - a revert comes\n  back with its actual reason, not a guess, and ERC-20 `approve()` amounts\n  are decoded from real calldata instead of inferred. This activates when\n  the caller supplies raw calldata via `intent.metadata[\"data\"]`, OR - new -\n  when `GUARDIAN_TX_BUILDER=rpc` is also set and the intent is a plain\n  `transfer` or `approve` (see next bullet). A `swap` intent with no\n  transaction built yet still has nothing to dry-run - Guardian reports\n  that honestly (`simulation_not_attempted`) rather than guessing.\n- **Transaction building closes part of that gap, deliberately not all\n  of it.** `RpcTransactionBuilder` (`GUARDIAN_TX_BUILDER=rpc`) turns a\n  semantic `transfer`/`approve` intent into real calldata - it fetches the\n  token's actual `decimals()` via RPC rather than assuming 18 (a wrong\n  assumption there would scale the amount by orders of magnitude), and\n  deliberately has no hardcoded token-address registry: a bare symbol like\n  \"USDC\" is refused rather than guessed at, since a wrong address here\n  wouldn't just be a bad risk signal, it'd be an artifact that could end up\n  in a real transaction. `swap` is built against Uniswap V2 Router02 only\n  (one immutable, well-known contract - function selectors computed\n  locally via `Web3.keccak`, not copied from memory) - real\n  `getAmountsOut()` on-chain quote, caller-supplied `max_slippage_bps`\n  required (never a default, same reasoning as decimals above). `bridge`\n  is a genuinely open-ended L2/bridge routing problem in general - dozens\n  of protocols, wildly different trust models - but this module handles\n  one well-scoped slice of it: L1 -> L2 deposits through a destination\n  chain's own official OP Stack bridge (currently: Base and Optimism -\n  `depositETHTo`/`depositERC20To` on `L1StandardBridge`, both addresses\n  independently cross-checked - Base against Etherscan's label plus\n  basehub.org, Optimism against the official\n  ethereum-optimism/superchain-registry plus a second independent\n  dev-tool config - before being hardcoded).\n  L2 -> L1 withdrawals are NOT built - that's a genuinely different, much\n  slower proof/challenge-window flow, not a variant of the deposit call.\n  Bridging to anywhere else, or via any non-canonical bridge, returns\n  `None` rather than guessing.\n- **Intent verification can now actually enforce, not just flag.**\n  `decision/intent_verification.py` compares an agent's declared\n  `approve` amount against what the simulated calldata really encodes -\n  but that comparison needs the token's `decimals()` to convert between\n  human units and atomic ones. `GUARDIAN_DECIMALS_PROVIDER=rpc`\n  (`RpcTokenDecimalsProvider`, see\n  `guardian/intelligence/token/decimals.py`) fetches that for real via\n  `eth_call`, cached forever per (chain, token) since a deployed\n  contract's `decimals()` can't change. Left at its `null` default, a\n  mismatch this module could otherwise catch degrades to an honest\n  \"cannot verify\" WARN instead - same \"no silent guessing\" rule as\n  everywhere else in this module, not a gap that got missed.\n  `RpcTransactionBuilder` shares this same cache when both are\n  configured with a real provider, instead of doing its own independent,\n  uncached lookup for the same token.\n- **Storage:** `InMemoryStorage` (default, zero setup), `SQLiteStorage`\n  (`GUARDIAN_STORAGE_BACKEND=sqlite` - persists across restarts, no\n  external infra), or `PostgresStorage`\n  (`GUARDIAN_STORAGE_BACKEND=postgres` + `GUARDIAN_POSTGRES_DSN` - the\n  fit for multiple replicas behind a load balancer, where SQLite's\n  single-writer model becomes the bottleneck; `pip install -r\n  requirements-postgres.txt`). Tested against a real local Postgres\n  instance, not mocked - see `tests/test_postgres_storage.py`. Redis is\n  still open if you specifically want it; the two-method\n  `MemoryBackend` interface is small enough to implement against\n  anything.\n- **API auth/rate-limiting** are intentionally minimal - built for one\n  self-hosted instance behind your own network boundary, not a\n  multi-tenant gateway. Put a real API gateway in front if you need that.\n- **Not security-audited.** The policy engine and risk fusion logic have\n  not been reviewed by anyone outside this repo. Treat `BLOCK` as a strong\n  signal, not a guarantee, until that's happened.\n\nEverything downstream of a `Signal` - fusion, policy, reputation,\nexplanation, the API - does **not** need to change as any of the above\ngets hardened further. That boundary is the actual design contract here.\n\n---\n\n## Quickstart\n\nZero-config demo mode (mock providers, in-memory storage, no auth):\n\n```bash\npip install -r requirements.txt\nuvicorn api.main:app --reload\n```\n\nOr with Docker:\n\n```bash\ndocker compose up --build\n```\n\nTry the canned scenarios:\n\n```bash\ncurl http://localhost:8000/demo/safe\ncurl http://localhost:8000/demo/unknown\ncurl http://localhost:8000/demo/malicious\n```\n\nOr submit your own intent:\n\n```bash\ncurl -X POST http://localhost:8000/decision \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n        \"agent_id\": \"trading-agent-001\",\n        \"wallet\": \"0x742d35Cc6634C0532925a3b844Bc454e4438f44e\",\n        \"chain\": \"ethereum\",\n        \"action_type\": \"swap\",\n        \"from_token\": \"ETH\",\n        \"to_token\": \"USDC\",\n        \"amount\": 5\n      }'\n```\n\n### Going from demo to a real self-hosted deployment\n\nCopy `.env.example` to `.env` and adjust:\n\n```bash\ncp .env.example .env\n```\n\nAt minimum for a real deployment: set `GUARDIAN_API_KEY` (auth is off by\ndefault), `GUARDIAN_STORAGE_BACKEND=sqlite` (persistence), and whichever\n`GUARDIAN_*_PROVIDER` variables you want pointed at real data instead of\nmock - see the comments in `.env.example` for every option, and\n`RpcWalletDataProvider`/`BlockscoutContractDataProvider`/\n`DexScreenerTokenDataProvider`/`GoPlusContractDataProvider`/\n`GoPlusTokenDataProvider`'s docstrings for what each one actually\ngives you.\n\n**If more than one agent shares this deployment**, also set\n`GUARDIAN_AGENT_API_KEYS` (format: `agent_id:key,agent_id2:key2`). A\nsingle `GUARDIAN_API_KEY` only proves *a* caller holds a valid key - it\ndoes not prove *which* agent_id a given request actually came from, since\n`agent_id` is just a field in the request body. Anyone holding the shared\nkey can submit any agent_id and inherit that agent_id's accumulated\nreputation and capability grants. `GUARDIAN_AGENT_API_KEYS` binds each\nagent_id to its own key; `GUARDIAN_API_KEY`, if also set, keeps working\nas a master key that can act as any agent, for admin/testing use. For a\ngenuinely single-agent deployment, `GUARDIAN_API_KEY` alone is fine.\n\n### MCP (no HTTP required)\n\nFor agent frameworks that speak MCP (LangChain, CrewAI, Claude Desktop,\netc.), `mcp_server.py` exposes the same decision engine as two tools\n(`evaluate_action`, `get_agent_history`) over stdio - install\n`requirements-mcp.txt` alongside `requirements.txt` (they resolve into one\nenvironment; see the comment at the top of `requirements-mcp.txt`) and\npoint your MCP client at `python mcp_server.py`.\n\n---\n\n## Running the tests\n\n```bash\npip install -r requirements.txt -r requirements-chain.txt\npytest -q\n```\n\n`requirements-chain.txt` (`web3`) is only needed for the RPC-provider\ntests; the rest of the suite runs with just `requirements.txt`. The\n`guardian/*` core has no external dependencies beyond that, so it's also\nrunnable with:\n\n```bash\nPYTHONPATH=. python3 -m unittest discover -s tests -v\n```\n\nCI (`.github/workflows/ci.yml`) runs the full suite on every push/PR\nagainst Python 3.11 and 3.12.\n\n---\n\n## Signed, verifiable decisions (OAA)\n\nEvery decision this service returns — from a single policy check up\nthrough the full pipeline — is signed as an [OAA (Open Agent\nAttestation)](https://github.com/rudimentall1/open-agent-attestation)\ntoken: an Ed25519-signed JWT wrapping the decision, the action, and\nthe reason.\n\nAnyone holding the public key can verify a decision offline, without\ncalling back to whatever instance of Guardian issued it — useful for\nan auditor, a downstream service, or just a record you want to trust\nlater without trusting the server that produced it.\n\n```bash\npython examples/example_oaa_attestation.py\npython examples/example_full_pipeline.py   # capability -> intent -> engine -> OAA\n```\n\nThe reference OAA implementation is ~150 lines\n(`oaa.py`/`attestation.py` upstream) and is shared, unmodified,\nacross this project and [agent-guardrail](https://github.com/rudimentall1/agent-guardrail) —\nsame signing format, same verification path, no per-project fork.\n\n---\n\n## Using Guardian in front of MetaMask Agent Wallet\n\nMetaMask Agent Wallet's Guard Mode / Beast Mode apply the same static\nspend limits and allowlists to every agent. Guardian is a second,\nindependent check in front of it: does *this* specific action look\nright for *this* agent, right now — before the `mm` CLI is ever\ninvoked.\n\n[`skills/guardian-check/`](skills/guardian-check/) is a standard\n[Agent Skill](https://agentskills.io) — the same open format MetaMask\nitself uses for `mm` (`npx skills add MetaMask/agent-skills`). Install\nit alongside MetaMask's own skill in any Agent-Skills-compatible\nruntime (Claude Code, Cursor, Codex, OpenClaw), and the agent will\ncall a running Guardian instance for an ALLOW/WARN/BLOCK decision\nbefore running any `mm` command that moves funds — `mm send`, `mm\nswap`, `mm bridge`, `mm perps`, `mm predict trade`, `mm earn`, `mm\naave`, `mm pay`.\n\nGuardian never holds keys and never executes anything — `mm` remains\nthe only thing that signs or broadcasts. This is a decision gate the\nagent is instructed to consult first, not a modification to\nMetaMask's own pipeline (there's no public hook for that today).\n\n```bash\nuvicorn api.main:app --reload   # run Guardian locally\nexport GUARDIAN_API_URL=\"http://localhost:8000\"\npython skills/guardian-check/scripts/check.py \\\n  --agent-id my-agent --wallet 0x... --chain ethereum \\\n  --action-type transfer --target 0x... --amount 50\n```\n\n---\n\n## From advisory to enforced: GuardianValidator\n\nEverything above is advisory - Guardian tells you ALLOW/WARN/BLOCK, but\nthe caller still has to *choose* to respect that. [`onchain/`](onchain/)\nis a real ERC-7579 validator module for ERC-4337 smart accounts that\ncloses that gap: once installed, the account's UserOperations only ever\nreach the chain if a trusted Guardian signer attested, for that *exact*\noperation, that the decision was ALLOW - not something an agent can skip\nasking or ignore the answer to. See [`onchain/README.md`](onchain/README.md)\nfor why it needs a second, EVM-native signature format alongside OAA\n(Ed25519 has no EVM precompile and costs ~2,000,000 gas to verify in pure\nSolidity; this module's entire `validateUserOp` costs 27k-59k gas), what's\ndeliberately out of scope (WARN never passes on-chain; no professional\naudit yet), and how to build, test, and deploy it - including a test that\nverifies a signature produced by real, running Python\n(`guardian/onchain_attestation.py`) is accepted by the real Solidity\ncontract, not two implementations that only agree with themselves.\n\n---\n\n## Roadmap\n\n1. ~~Replace the mock wallet/token/contract analyzers with real data\n   sources.~~ Done - see [Honesty about the current state](#honesty-about-the-current-state)\n   for what \"real\" does and doesn't cover yet per source.\n2. ~~Wire up real pre-execution simulation.~~ Done for `transfer`/\n   `approve` end to end (`GUARDIAN_SIMULATION_PROVIDER=rpc` +\n   `GUARDIAN_TX_BUILDER=rpc` - see\n   [Honesty about the current state](#honesty-about-the-current-state)).\n   ~~`swap` needs real DEX routing.~~ Done against Uniswap V2 Router02 -\n   real on-chain `getAmountsOut()` quote, explicit caller-supplied\n   `max_slippage_bps` (never defaulted), no calldata built without a real\n   quote. Fixed a real bug found while building this: simulation was\n   dry-running against `intent.target` (the recipient/spender encoded\n   *inside* ERC-20 calldata) instead of the actual contract being called\n   (`from_token`) - meaning transfer/approve simulation silently\n   \"succeeded\" against any EOA recipient regardless of whether the real\n   call would have reverted. `BuiltTransaction` now carries an explicit\n   `to`; see `tests/test_tx_builder.py` for the regression tests that\n   would have caught it. ~~`bridge` still open.~~ Done for L1->L2\n   deposits to Base and Optimism via the official OP Stack\n   `L1StandardBridge` (`depositETHTo`/`depositERC20To`) - other\n   destinations, other bridge protocols, and L2->L1 withdrawals all\n   remain open; see\n   [Honesty about the current state](#honesty-about-the-current-state).\n3. ~~Populate threat-intel / sanctions feeds; stop shipping empty\n   sets.~~ Done for sanctions (`sanctioned_addresses.json` - 103 real OFAC\n   SDN addresses, refreshable via `scripts/refresh_ofac_list.py`).\n   `malicious_contracts.json` / `verified_contracts.json` remain empty by\n   design - no single authoritative source exists to seed them the way\n   OFAC's list does for sanctions.\n4. ~~Swap `InMemoryStorage` for a persistent backend.~~ `SQLiteStorage` is\n   available; ~~a Postgres/Redis backend is still open for multi-replica\n   deployments.~~ `PostgresStorage` done - tested against a real local\n   Postgres instance (`tests/test_postgres_storage.py`), same\n   two-method `MemoryBackend` interface as the other backends. Redis\n   remains open if specifically wanted.\n5. ~~Add an MCP server wrapper.~~ Done (`mcp_server.py`). A packaged\n   Python/TypeScript SDK on top of the REST API is still open.\n6. Publish an OpenAPI spec and a hosted demo endpoint.\n7. Get the policy engine and risk fusion reviewed/audited before anyone\n   relies on a `BLOCK` from this service in production - it's a security\n   tool, so it needs the same scrutiny it applies to others.\n8. ~~Add per-agent capability limits (delegation scoping).~~ Done -\n   `guardian/policy/capabilities.py`, and wired into\n   `DecisionEngine.evaluate()` via an optional `capability_registry`\n   constructor argument (previously it was a standalone module you had\n   to call yourself outside the normal pipeline - see\n   `examples/example_capability_limits.py`, which now runs through\n   `DecisionEngine` directly). Opt-in: pass no registry (the default)\n   and nothing changes; an operator can grant a specific agent a scoped\n   capability (allowed action types, allowed chains, per-action and\n   daily spending caps, an expiry) with zero private-key material\n   involved. Agents with no grant are unaffected. This module still\n   never touches private keys or session-key issuance itself - that\n   remains out of scope, a categorically higher-stakes problem. ~~Real\n   enforcement of a decision (vs. an agent choosing to respect it)\n   remains deliberately out of scope.~~ Partially done -\n   [`onchain/`](onchain/)'s `GuardianValidator` is an ERC-7579 module\n   that makes a decision genuinely unbypassable for any ERC-4337 smart\n   account that installs it, without Guardian ever holding a key. It\n   does not manage session keys, custody, or account creation - it\n   only gates execution behind an attestation - so this is a real,\n   load-bearing piece of \"account abstraction,\" not the whole of it.\n9. ~~Verify declared intent against decoded simulation results.~~\n   Done - `guardian/decision/intent_verification.py` catches\n   the case where an agent declares one amount but the actual calldata\n   it was handed encodes a meaningfully different (but still finite)\n   one, and `DecisionEngine.evaluate()` now actually calls it (it\n   didn't before - the module and its example script existed, but\n   nothing in the real decision pipeline invoked it). ~~It's still not\n   a working guardrail on its own, though: comparing atomic units needs\n   the token's `decimals()`, and no decimals provider exists yet.~~\n   `GUARDIAN_DECIMALS_PROVIDER=rpc` (`RpcTokenDecimalsProvider`, see\n   `guardian/intelligence/token/decimals.py`) closes that: a real\n   `eth_call` to the token's own `decimals()`, cached forever per\n   (chain, token) since that value can never change once a contract is\n   deployed. Left at its `null` default, every `approve` with a\n   successful, finite-amount simulation still gets an honest \"cannot\n   verify without decimals\" WARN instead of either a false BLOCK or a\n   silent skip - configuring the real provider is what turns that into\n   an actual BLOCK on a genuine mismatch. See\n   `examples/example_intent_verification.py` for the check blocking a\n   real mismatch, and `tests/test_decision_engine.py`'s\n   `TestIntentVerificationWithRealDecimalsProvider` for the end-to-end\n   version wired through a real (mocked-RPC) decimals lookup rather\n   than a hand-supplied `token_decimals` argument.\n10. ~~Flag actions that deviate from an agent's own historical\n    pattern.~~ Done - `guardian/intelligence/anomaly/analyzer.py`.\n    Distinct from reputation (a single trust score) and policy (static,\n    operator-set limits): this compares the current intent against\n    *this specific agent's* own recorded history - new action type,\n    new chain, or an amount that's a statistical outlier versus what\n    this agent has done before, even if it's within policy limits and\n    the agent's reputation is fine. Honestly reports \"insufficient\n    history\" rather than guessing a baseline from fewer than 5 prior\n    data points - see `tests/test_anomaly_detection.py`.\n11. ~~Sit in front of a real agent wallet, not just accept intents\n    from a generic API caller.~~ Done for MetaMask Agent Wallet -\n    `skills/guardian-check/` is a standard Agent Skill an agent\n    installs alongside MetaMask's own `mm` skill; the agent calls it\n    before running any fund-moving `mm` command and only proceeds on\n    ALLOW. Tested end-to-end against a live `uvicorn` instance\n    (ALLOW/WARN/BLOCK/config-error all exercised for real, not just\n    asserted) - see the \"Using Guardian in front of MetaMask Agent\n    Wallet\" section above. No public hook exists (yet) to run inside\n    MetaMask's own pipeline; this works at the agent-orchestration\n    layer instead.\n\n---\n\n## Related projects\n\nSame author, same principle applied elsewhere:\n\n- [agent-guardrail](https://github.com/rudimentall1/agent-guardrail) -\n  a generic policy firewall for AI agent tool calls (not\n  blockchain-specific). Published on PyPI, MIT, 46 tests.\n- [x402-attest](https://github.com/rudimentall1/x402-attest) -\n  cryptographically signed (Ed25519), independently verifiable\n  attestations for agent-to-agent payment policy decisions. Early\n  proof of concept.\n- [open-agent-attestation](https://github.com/rudimentall1/open-agent-attestation) -\n  vendor-neutral open spec (JWT+EdDSA) for signing agent policy\n  decisions, verifiable by anyone. x402-attest above uses a custom\n  format; this is the generalized version. Draft v0.1.\n\n---\n\n## License\n\nMIT - see `LICENSE`.\n",
  "bytes": 29259,
  "sha": "6a07060cf043c298005c039589186c62b729f83044ece6a9125d438ba7e28c38",
  "repo_slug": "rudimentall1/agentic-wallet-guardian-v3",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_rudimentall1_agentic_wallet_gu_914c324a/readme"
}