Search projects, hackathons, sponsors and tech

    ← All projects

    Saviour

    Security memory for AI agents: turn verified onchain threats into reusable intelligence.

    Demo

    About

    Detection already works — plenty of tools can prove an address drained a protocol. Nobody writes the finding down anywhere a machine can look it up, so every autonomous agent pays to rediscover the same fact. Saviours is the naming layer. One standardized Messari query pattern fans out across eight pinned deployments on The Graph Network to produce evidence rows. Deterministic rules turn rows into signals; an LLM writes the explanation and cites evidence ids but never owns the verdict and can never write on-chain. A validator sets the status, and the result is published as an ENSv2 subname — <address>.saviours.eth on Sepolia — with saviours.status and the evidence hash in text records, under Enhanced Access Control role ceilings. After that, resolving is free and does not involve us at all: cast, the @saviours/check npm package, or MCP read the Permissioned Resolver directly. Zero Graph queries, zero AI calls, zero dollars. Discovery is metered through a Bazantic x402 gateway and the calling agent pays it — a cent for an investigation, five cents for a deep evidence pull. The second agent's answer is free because the first agent paid for it. Product law we hold ourselves to: only WATCH and TAINTED are ever named. SAFE is never a name — no name means unknown, and unknown is not an endorsement. Seeded provenance is never counted as a live Graph detection.

    How it's made

    Stack :pnpm monorepo, TypeScript throughout. apps/web is Next.js (App Router). @saviours/core holds the Graph fan-out, signal derivation, ENS client, and Shield logic. contracts/ is Foundry (SavioursRegistry.sol). Two consumer surfaces beyond the UI: an MCP stdio server and a Bazantic x402 gateway. viem end-to-end for every chain call — no ethers. OpenAI is used for exactly one thing: writing the explanation paragraph. Every product code path reads live Graph Network data; anything synthetic is confined to /evals and labeled as fixtures. The state that matters — the verdict — lives on chain as ENS text records plus the registry, not in a database we could quietly edit. Pipeline. Resolve first: read saviours.status off ENS. A MEMORY HIT triggers Shield BLOCK/WARN at zero Graph calls, zero AI calls, zero dollars. A MISS (or an explicit forceFresh) fans out across the Messari Standardized Subgraph deployments, runs deriveSignals, has the LLM write an explanation that must cite evidence ids, then runs validateAssessment — and only if the result is WATCH or TAINTED, and writes are explicitly allowed, does it get named on ENSv2 and mirrored to SavioursRegistry. Every consumer after that reads ENS and pays nothing. That loop is the architecture; the UI is a walkthrough of it, not a separate product. The Graph — why standards are the project, not a dependency. packages/core/src/graph/protocols.ts pins eight Messari Standardized Subgraph deployment IDs across five schema-family templates (lending-cdp 3.1/2.0, dex-amm-ext 4.0, dex-amm 1.3.x, yield-aggregator 1.3). standard.ts fans one address across all eight with Promise.allSettled, so one dead indexer degrades coverage instead of killing the run, and records per-protocol status, row count, and latency. A shared transaction hash across protocols is what makes ATOMIC_MULTI_PROTOCOL a genuine set intersection instead of a narrative. Without the standardized schema this is eight bespoke integrations — not something one person ships solo in a week under Start Fresh rules. We deliberately kept adapterA (a community Uniswap V3 deployment) as a non-standard control: one bespoke query, one protocol — so the contrast between standardized and custom is visible in the source, not just claimed in a slide. EXCLUDED_PROTOCOLS names Balancer V2, PancakeSwap V3, and Convex with the actual reason each was dropped (indexing_error / no allocations), and the UI surfaces that list rather than hiding it — a security product that claims full coverage it doesn't have is worse than one that states its edges. Live gateway only, Subgraph Studio key, Ethereum mainnet, enforced by pnpm check:standard. MCP tools plus a SKILL.md at repo root let an agent check whether Graph covers a question before spending a query on it. ENS — the name is the API, not a label. Built on ENSv2 (Sepolia), as the track requires: UserRegistry at 0x3BA6b1c9F0018cac383C17C2ACA5A4dC0F370De5, PermissionedResolver at 0xF479306621F718F7d76875f67506ceD33717751c, under saviours.eth. Every verdict is <lowercase-address>.saviours.eth carrying saviours.status (WATCH or TAINTED), an evidence hash, and related saviours.* texts. Enhanced Access Control caps roles per record key — an investigator can write investigation records, but a setText call on saviours.dispute from that same role reverts on-chain (packages/core/src/ens/eacProbe.ts, exercised by pnpm check:ens-roles). Differential expiry is product policy expressed directly through ENSv2 primitives: WATCH names expire in 7 days, TAINTED names in 10 years. Shield is ENS-first (packages/core/src/shield/tier1.ts). Four consumers never touch our Next server at all: cast, @saviours/check@0.1.3 (default ens mode is viem-only), MCP, and consumers/plain-shield.html against a public Sepolia RPC. The property we actually care about: our server can go down and the verdict still resolves. Bazantic — pricing is the product law, not a bolt-on. Gateway at https://saviours.bazgateway.com (MCP over POST /mcp). We were not on Bazantic before this event. The tiers encode the loop directly: Free ($0) for shieldCheck, resolve, catalog, and dossier fetch; Standard (~$0.01) for investigate; Complex (~$0.05) for a full evidence pull plus case-ask. A MEMORY HIT is never charged on any surface, by construction. Our primary recipe, investigate-once-explain, is genuinely two-sided: The Graph supplies the live Messari rows, Saviours supplies the shield-first memory and the validator — neither side produces a usable answer alone on a cache miss. Also published: saviours-check-before-sign, dossier-deep-dive, fleet-triage, safe-swap-with-memory. Governance actions (dispute, revoke, EAC probe) are deliberately never exposed to agents through the gateway. Bazantic username: <FILL>. AI trust boundary — the part we will not compromise on. Evidence rows become signals through deterministic code, never the model: FLASHLOAN_ONE_SHOT, ATOMIC_MULTI_PROTOCOL, BOT_PROFILE, REGISTRY_COOCCURRENCE, and others. The LLM writes prose and must cite evidence ids; citations get re-attached from the live evidence set rather than trusted verbatim. validateAssessment is the only thing that sets status — a model-proposed TAINTED with no matching threat-class signal gets rejected outright. The AI never writes to ENS or the registry. Shortest way to say it: the model writes the paragraph; the code decides the stamp. Chain split — stated as a ceiling, not buried in a footnote. Evidence comes from Ethereum mainnet via The Graph. Memory lives on Sepolia ENSv2. Settlement is Base USDC via x402, roughly a cent on a miss. ENSv2 beta only exists on Sepolia right now — moving memory to mainnet this week would put us outside the ENS track entirely, so we don't. The split is printed in the product itself. Hacky / notable. Public production writes fail closed (assertWriteAllowed) so a public demo host cannot silently mutate hero ENS records mid-judging; local film runs use an explicit write grant. Our hero incident ATTACK-1 (0x935bfb495e33f74d2e9735df1da66ace442ede48) is restored and gated by pnpm pre-submit / restore:attack1 — a stray dispute against the hero during playground is how you lose the ENS demo beat, so we treat it as a guarded fixture. We denylist Vitalik's address to ESCALATE rather than BLOCK, so a well-known address can never produce a false positive on stage. When ENS text and the registry disagree, we do not patch the discrepancy in code — we fix the chain state. And we say plainly that the revoke story is incomplete rather than papering over it: an append-only registry that pretends to fully support revocation would be a worse kind of dishonest than an admitted gap. Deliberately not built. Eight pinned Graph deployments — not an unbounded fan-out, and not Substreams triggers. Not a decentralized dispute court — EAC is permissioned operators, stated as such. Not mainnet ENSv2 — the track is Sepolia beta and we stay there. Not a padded verified count — the headline is measured (~25 named · 2 Graph-verified; named is not the same claim as Graph-proven, and we do not let it read that way). Not charging for a MEMORY HIT, ever. Not treating SAFE as a name — an empty resolve means unknown, and unknown is never sold as an endorsement. Verify without trusting us. On public Sepolia: cast call 0xF479306621F718F7d76875f67506ceD33717751c "text(bytes32,string)(string)" $(cast namehash 0x935bfb495e33f74d2e9735df1da66ace442ede48.saviours.eth) saviours.status --rpc-url $SEPOLIA_RPC_URL → TAINTED, with no server of ours in the path. Then open https://www.saviours.xyz, run the Hook flow, and watch it BLOCK with a measured latency at $0.