Search projects, hackathons, sponsors and tech

    ← All projects

    Re:Leash

    Permission for an AI agent that shrinks on its own unless a verified human keeps showing up.

    About

    Re:Leash is a permission layer for AI agents that run money on-chain. The problem: to run a position for you, an agent needs a key. A key never expires, never gets smaller, and does not care whether you are still around. Every agent-drained-the-wallet incident is the same story: a stale, unbounded permission plus one bad prompt or one bad night. Leash replaces the key with a mandate. You create a vault (your money stays in it), name your agent under leash.eth, and the agent gets exactly two calls: ship (open a position) and dock (close it). Its authority is a cap in USDC that halves every 24 hours from the last time a verified human showed up, and reaches zero after 72 hours. It cannot renew itself; only a World ID proof from the vault's human moves the clock. It can always close a position and can never withdraw. The cap is not advisory. It is enforced inside the swap: a SwapVM opcode reads the vault's live mandate on every quote and swap and trims the USDC leg to what is allowed right now. A 10,000 USDC ask against 7,500 of authority fills 7,500. At zero it is refused. When the agent is refused it winds down and closes; when the human comes back it resumes. What is on Sepolia, all live and driven from the browser with any wallet: VaultFactory (your own vault + <you>.leash.eth + the agent's mandate in one transaction), the ENSv2 role bit that is the permission, World ID v4 verification with one-human-per-vault binding and single-use nonces, a 1inch Aqua router extended with the MandateGate opcode (26.6k gas per swap against the real ENSv2 registry), a self-running agent, a random market taker, and an attack script that tries six ways to get the money (withdraw, self-grant, self-verify, raise the cap, ship without the cap token, a 100,000 USDC trade) and is refused six times on chain. The agent field also shows the agent's ERC-8004 identity from the Sepolia Identity Registry: 8004 says who an agent is, Leash says what it may do right now. Unregistered agents are bounded the same way. Demo runs at 1 block = 4.8 hours so the whole arc (verify, trade, trim, zero, refused, close, return) happens in about three minutes on real Sepolia.

    How it's made

    Contracts (Foundry, solc 0.8.30 via-IR): - Vault.sol is the Aqua maker. It holds the tokens, exposes ship/dock to the agent, withdraw/setCap to the owner, verify to the backend, and mandate() -> (alive, cap, token) for the gate. limitAt halves per 24h, linear inside a day, zero at 72h; a `speed` immutable makes the demo clock 1440x. - VaultFactory.sol deploys a vault for msg.sender and, in the same transaction, registers <label>.leash.eth to the caller in the ENSv2 UserRegistry with the MANDATE and tier admin bits, and grants the agent MANDATE. ENSv2 token admin bits cannot be delegated, so the factory and backend hold root roles on the registry: the backend can set tiers but can never touch MANDATE; only the owner can revoke. - MandateGate.sol is SwapVM opcode 0x2f. It runs immediately before XYCSwap in the strategy program, reads Vault.mandate(), and trims the USDC leg; when USDC is the computed leg it solves the inverse of x*y=k so the trim lands exactly on the cap. Reverts MandateRevoked / MandateEmpty / MandateTokenMissing. No storage writes inside the opcode. MandateAquaRouter is 1inch's Aqua router with _runOpcode overridden: 22,509 bytes, under the 24,576 limit. - 71 unit tests (fuzzed decay monotonicity, both token orderings because MakerTraits requires tokenA < tokenB, revoke/expiry) and 27 fork tests against the real ENSv2 and Aqua deployments on Sepolia. World ID (IDKit v4): the backend signs an rp_context, the browser opens World App, the completion result is verified server-side at /api/v4/verify/{rp_id}; the credential type maps to a tier cap (Orb 15,000 / Passport 7,500 / Selfie 2,000 USDC). Replay protection is the single-use rp_context nonce, not the nullifier, because the same human must be able to come back; the nullifier binds one human to one vault so a second person cannot renew someone else's agent. Legacy 3.0 proofs are accepted for phones without World ID 4. Backend: Node 22, viem, SQLite (node:sqlite). Every read that composes one screen is pinned to one block number, after a bug where two nodes behind a load balancer showed a state that never existed. The backend builds calldata; the user's own wallet signs, discovered via EIP-6963 so several installed extensions cannot swallow each other's requests. Playwright drives the real page with an injected wallet stub and a fake IDKit through 19 steps on an Anvil fork of Sepolia, including the attack script. ERC-8004: balanceOf on the Identity Registry, then the Registered(owner) event, since the standard defines no address -> agentId lookup. The demo agent is registered with a base64 data-URI registration file so nothing depends on our hosting. Hacky bits worth admitting: forge script cannot deploy the router (constructor decoding), so deploy.sh uses forge create; forge script's console output is the pre-broadcast simulation, so the agent labels fills "simulated" and the dashboard shows the mined fill from the router's Swapped event; the landing's logo is 2,219 instanced spheres in Three.js that tighten every six seconds, which is the product in one motion.