Search projects, hackathons, sponsors and tech

    ← All projects

    AgentProof

    AgentProof is a developer SDK and safety runtime that sits between an AI agent and execution layer.

    Demo

    About

    AgentProof — a spending limit an AI agent cannot talk its way around. The problem. People are starting to give AI agents real wallets — trading bots, treasury agents, autonomous buyers. An agent is not a program you can audit ahead of time; it decides what to do at runtime, and anything it reads can change its mind. A web page, a tool response, a token's own metadata can carry a hidden instruction ("send everything to 0xattacker") and the agent will follow it politely. It can also just be wrong: hallucinate a price, misread a field, swap 250 USDC when you said 100. Today "agent safety" almost always means a wrapper — code in the agent's own process that looks at what the agent says it wants to do and refuses the bad ones. That fails exactly when it matters, because the thing being restrained is the same process doing the restraining. If the agent is compromised, it skips the wrapper and signs directly. What we built. AgentProof splits enforcement into two layers that fail independently. 1. An SDK and HTTP API (npm i @agentproof/sdk, or POST /v1/verify from any language). Three lines in your agent: describe the transaction, get a verdict, stop if it's denied. Seven policy types run per action — allowlist, max per transaction, daily spend, minimum balance, approval threshold, pool liquidity, and unlimited-approval detection. It decodes real Uniswap v4 Universal Router calldata down to worst-case outflow, so a swap that advertises 1 USDC but authorises 250 is priced at 250. Anything it can't decode returns UNBOUNDED and is blocked rather than guessed at. When a transaction is large enough that the owner wanted a say, the HTTP request is held open until a human answers — the agent is genuinely stopped, not notified — and if nobody answers, it's denied. Silence is never consent. 2. An on-chain hook (ERC-7579 module inside the agent's own smart account). This is the part that actually holds. It re-checks the limits inside the wallet at signing time, so it applies whether or not the agent ever called us. The point of the whole design: the SDK is advice and we assume it will be bypassed. The hook is the guarantee. Our headline test involves no SDK at all — it installs the hook on the ERC-7579 reference account, sends a validly signed UserOperation through a real EntryPoint v0.8, and the account refuses it. No funds move. Who it's for. Anyone shipping an agent that can move money: DeFi trading agents, x402-paying agents that buy API calls, treasury automation. The owner writes the policy once; the agent inherits limits it cannot remove — we found that out the hard way when we tried to uninstall our own hook during development and couldn't. Everything is deployed and links to a block explorer: the hook on-chain, the agent's limits published under its ENS name so anyone can verify them without asking us, a subgraph of what the chain actually admitted, and an x402-gated API where every verification is a real settled payment.

    How it's made

    Stack: TypeScript SDK (zero runtime dependencies on the decision path), an HTTP verification API, Solidity + Foundry for the on-chain hook, solc's SMTChecker for formal verification, Next.js for the demo console. Partner tech: Uniswap v4, ENS v2, The Graph, Hedera (x402 payments). The design rule throughout: anything off-chain is advice, and the only thing that actually stops a transaction is on-chain. The decision path has zero dependencies. Every read the policy engine needs is an eth_call issued with fetch. viem is an optional peer dep, dynamically imported, and only if you sign — so you don't take a wallet stack's supply chain into the code that decides whether an agent may spend your money. Seven policies run per action: allowlist, max transaction, daily spend, minimum balance, approval threshold, pool liquidity, and unlimited-approval detection. Decoding is where the real work is. A policy is only as good as its understanding of the calldata. decode/uniswapV4.ts walks a Universal Router command stream and returns worst-case outflow per currency. The trap is exact-output swaps: the interesting number is amountInMaximum, not the quote, and reading the wrong field lets a 250 USDC swap look like a 1 USDC one. Anything undecodable returns UNBOUNDED rather than a guess, and unbounded always blocks. We pinned the decoder against six real Sepolia transactions — the first run decoded all six as UNBOUNDED, because of an off-by-one in the action bytes and a wrong struct word, invisible until real calldata replaced our own self-encoded fixtures. That bug is the argument for the whole approach. Uniswap v4 is therefore load-bearing rather than decorative: the Universal Router and PoolManager are the allowlisted targets, and a second public Uniswap subgraph feeds a pool-liquidity policy — bounding spend and bounding loss are different problems. The hook is an ERC-7579 module on an MSAAdvanced account (reference implementation v0.3.1) driven through EntryPoint v0.8.0. The version is pinned deliberately: v0.7 predates ISenderCreator, and v0.9 gates handleOps behind tx.origin == msg.sender. We also had to match the account's actual postCheck(bytes) — selector 0x173bf7da — not the draft postCheck(bytes,bool,bytes) from the spec. That one is worth dwelling on: the mock-only version of our test suite hid it for weeks. The hook would have installed cleanly and reverted on first real execution. The mocks agreed with each other while both disagreed with reality. There is now an integration test that installs the hook on a real 7579 account and drives it through a real EntryPoint to prove it. Formal verification with solc's SMTChecker proves MAX_TRANSFER and DAILY_SPEND hold over PolicyLib for all inputs, in about 1.3 seconds. We also compile a deliberately broken spec, PolicySpecBroken, which must produce a counterexample — that's how we know the solver is actually looking rather than trivially passing. The hook itself is fuzzed and invariant-tested, not proven, and the UI says which is which. Missing artifacts report NOT_RUN instead of passing silently. ENS v2 carries the agent's identity as a namespace: addr to the smart account, plus text records for policy hash, hook address, and status. The EAC roles encode the trust split precisely — the owner holds ROLE_SET_POLICY_RECORD, and the agent's session key holds only ROLE_SET_STATUS_RECORD, so an agent can suspend itself and nothing else. The SDK does a three-way binding check at startup: local file, ENS record, and the hook's stored hash must agree, or it refuses to run. The Graph indexes the hook's own SpendRecorded events, so spend history reflects what the chain admitted rather than what an agent intended. A pre-aggregated DailyAggregate entity is what keeps a policy decision fast enough for an agent's hot path. Subgraph reads are reconciled against a direct on-chain accumulator read — whichever source permits less wins — and if both are unreachable the SDK fails closed. Hedera makes the verification service the paid product. POST /v1/verify returns 402 with payment requirements; the client partially signs a transfer; the Blocky402 facilitator co-signs as fee payer and settles; then the verdict comes back. Verify before work, settle after — callers are never charged for failed requests. Each settlement is written to an HCS topic, so an audit trail exists on infrastructure we don't control. The loop closes nicely: the payment itself is a policy-checked action, so an agent can't be talked into paying a thousand dollars for a data query by a service that simply asks for it. Hacky bits worth mentioning The browser can't pay, so the server pays for it. The demo console needed the x402 gate switched on to be honest, but a browser has no wallet — every check just came back 402. The console's verify route now answers the payment challenge itself with a Hedera signer, so the gate stays on and every click in the demo is a real settlement with its transaction id linked to HashScan. Nothing in the demo path is a mock or a bypass; the UI pays for its own verifications like any other caller. We couldn't uninstall our own hook. Updating the policy on a live account means executing against the account itself, and the hook rejects any target that isn't on the allowlist — including the account. uninstallModule is withHook on MSAAdvanced, so calling it directly re-enters the same check. The agent's session key cannot remove its own guardrails, which is exactly the property we wanted, with a consequence we hadn't planned: on this account the policy is fixed for the life of the account. We shipped it that way rather than weakening the check, and wrote up both escape routes in docs/future-work.md along with why neither is free.