
Leash_dash
Keep your AI agents on a leash: bind an agent, set on-chain spend limits, cut it off with one write.
Demo
About
Everyone is racing to build AI agents that can spend money. The part almost nobody has built is the place you sit and actually govern them once they can. That is what LEASH is. It is a control centre for the AI agents that spend your money, where every agent you run is on a leash you can see and pull. The problem Once you hand an agent a wallet so it can pay for things over x402, the only real control you have left is the private key itself. There is no per-call limit, no list of who it is allowed to pay, no hierarchy if you are running a fleet, and no single off switch. The part that actually worries me is revocation, because if a key leaks today, cutting that agent off means going service by service and rotating keys by hand, which is exactly the situation where slow manual work is the last thing you want. What LEASH does LEASH gives an organisation one console to run its whole fleet of paying agents. You bind an agent you already run, give it a spending account that you and LEASH co-own, set its limits, fund it, and if you ever need to, you pull the leash and cut it off everywhere. The thing that makes this work is ENS. Each agent becomes a child name like `data.acme.leash.eth`, and the spend policy for that agent (its per-call cap, the payees it is allowed to hit, its Hedera account, and optionally a rolling daily or weekly budget) lives right there in the name's resolver record. The bit I care about most happens at payment time: a facilitator I run reads that live ENS record before it settles anything, and it only co-signs a payment that is actually within policy. So the moment you clear the record, the very next payment the agent tries just fails. One write on Sepolia and it is done everywhere, with no key rotation and no chasing down services. How it works It is five steps end to end. You bind an existing agent whose identity already lives on another chain, say an Arc or Base wallet or an ERC-8004 registration, and LEASH resolves that identity on-chain and then generates a fresh Hedera spending keypair for the agent. The public half goes into a two-of-two Hedera account that you and LEASH co-own, and the private half is shown to you once, right there at bind, and never stored by LEASH. If you would rather bring your own, pasting your agent's own Hedera public key is still supported as an advanced option. Either way LEASH never holds the agent's private key. Then it writes the policy into the agent's ENS resolver record. From there, every time the agent pays, the facilitator reads that live record, checks the cap and the allowlist and the window, and only adds its co-signature if everything passes, settling gas-free on Hedera so the agent spends zero on gas. Every decision, allow or deny, gets logged to a Hedera Consensus Service topic so it is checkable on-chain. And revoking is the whole point: clearing the record is a single Sepolia transaction, and because the facilitator re-reads with no cache right before it settles, the next payment fails closed rather than slipping through. Being honest about the guarantee I would rather be straight about how strong this is. The enforcement is facilitator-trusted, not trustless. The facilitator I run is the thing reading your policy and deciding whether to co-sign, and the chain's job is to hold the policy and the revocation, not to physically block a payment. The model I keep coming back to is a corporate card, where the employee is genuinely independent and holds their own card, but the company sets the limit and can freeze it. So control is real and independence is real, but trustlessness is not,for now. Same spirit on the rolling daily and weekly caps, which are a soft budget rather than an exact one, and on identity, which is on-chain-resolved rather than a proof that the person binding actually controls the address. Two ways to see it work The fastest is the sandbox at `/demo`, which needs no login, no wallet and no ETH. It governs a real agent of mine called SOLV-001, an autonomous agent running on a Circle wallet on Arc (its on-chain-resolved identity is `0x927c1d756d12879aebea0772f3ee220f21f4841a`), and it walks you through five moments: meet the agent, watch it pay three dollars inside its cap and settle gas-free, watch it try fifty and get refused, revoke it and watch the same payment fail closed, then watch a leaked key try to over-fund it and get denied by the funding rail. The second way is the real product at `/app`, where you log in with Privy, spin up your org, and bind an agent you already run by pasting its Hedera public key and its identity. You get the same controls on your own agent, and LEASH still never touches its private key. What is live right now Everything below is on live rails. If you only click a few things, click these. - The app: https://leash.ink (sandbox at /demo, proof page at /proof) - The code, MIT and public: https://github.com/dmustapha/leash - Revoking an agent in one write, on Sepolia: `0xf03e14d1c0ed8528d90f9d0e5aa4bc55cb2fe29baedea97596d902c427702293` - A gas-free settle where the agent pays zero HBAR, on Hedera: `0.0.10487802@1789205977.654877070` Every headline number here recomputes from committed evidence with `npm run verify:claims`, and the full ledger is in `submission/proof.md`. There are 102 unit tests and 8 integration tests behind it, plus the live on-chain runs. Where it goes next Right now the actual spend governance runs on Hedera, because the co-signing facilitator I run is the x402 Hedera one, even though binding identity already works across chains and the funding rail is general enough to move. The near-term work is standing that co-signer and policy read up on other EVM chains too. Further out I want to move enforcement from something I am trusted to do toward something the chain checks itself, most likely a contract wallet that mirrors the cap on-chain.
How it's made
The frontend is a Next.js app on Vercel, and the two rails behind it, the x402 Hedera facilitator and a small x402-gated resource server, run on Render. There is a Neon Postgres in the stack too, but I want to be clear that it is only an index for the UI and never the thing that decides whether a payment is allowed. It is all TypeScript, with 102 unit tests and 8 integration tests, plus a set of live runs that actually hit Sepolia and Hedera instead of mocking them. For the ENS side, and I used ENSv2 on Sepolia with a three-level hierarchy: a root, an org under it, and each agent as a child name beneath the org. The spend policy sits in the agent name's resolver record under the key `leash.policy`, and the facilitator reads it with a single `PermissionedResolver.text` call right before it settles. Nothing is hard-coded, the caps and the allowlist and the revocation are all real Sepolia transactions, and revoking is just `clearPolicy`, which is one write. The reason ENS was the right primitive rather than a database is that the record is already a public source of truth anyone can read, so revocation is not a flag I flip in my own system, it is an on-chain fact. For Hedera I self-hosted an `@x402/core` plus `@x402/hedera` facilitator, the same shape as Blocky402, and it settles with the native fee-payer scheme, which is the partner feature that actually matters here: the agent pays zero HBAR and is only debited the USDC it is spending, using its own six-decimal HTS test token. The gate itself is a pure `authorize` function with no database in it. There are two reads of the ENS record, an advisory one before verify that is allowed a short cache, and the authoritative one right before settle that never caches. That second read is the part I sweated over, because it is what closes the window where a revoke lands in between verify and settle. Without it you could revoke and still have an in-flight payment go through, which would quietly turn the whole promise into a lie. Every decision, with its reason, is written to a Hedera Consensus Service topic, all the amounts are compared as BigInt on raw units so nothing rounds, and if the RPC or the mirror is unreachable the gate fails closed rather than defaulting to zero. The two-of-two account is how I keep the agent genuinely independent while still being able to govern it. When you bind an agent, LEASH generates a fresh Hedera spending keypair for it, puts the public half into a KeyList account alongside LEASH's own co-signer key with a threshold of two, and hands the private half back to the user once, at bind. LEASH never stores that key, so it is not in the facilitator, the database or the env anywhere. If someone would rather supply their own agent public key instead of having one generated, that path is still there as an advanced option, and the invariant holds either way: LEASH never retains the agent's private key. LEASH only adds its signature at settle, after the gate passes, at a single spot in the code. I checked both failure directions on-chain: the agent signing alone gets a clean `MISSING_COSIGN` and nothing settles, and LEASH together with its own operator key still cannot move the agent's funds. I also assert at startup that the co-signer key is not the same as the gas fee-payer key, so the line about the authority key not being the gas key is literally true in the running process and not just a claim in a README. Privy is the second, independent rail. The treasury is a P-256-owner Privy wallet driven through `@privy-io/server-auth`, and its policy caps and allowlists the actual USDC transfer calldata, so an over-cap top-up comes back as `FUNDING_DENIED` before it ever broadcasts. There is deliberately no on-chain artifact for that denial, the absence is the proof. The hacky detail I did not expect is that a KeyList account has no key-derived EVM alias, so funding has to target its long-zero EVM address, and that took me a while to work out. I kept Privy strictly funding-only on purpose, because the moment it co-signs a payment the honest story falls apart. Identity uses ERC-8004. On register, LEASH resolves the owner live against the canonical Sepolia registry and writes the `agent.erc8004` and `agent.address` records, and a mismatch just errors instead of registering. I label this on-chain-resolved rather than verified, and that wording is deliberate, because `ownerOf` tells you who the registry says owns the agent but it does not prove the person binding actually controls that address. A proper signed proof-of-control handshake is something I want, it just is not in yet. The last thing I would rather disclose than bury is that the rolling daily and weekly caps are a soft budget. They are summed from the HCS topic through the mirror node, which lags, so under enough concurrency you could overshoot by roughly the number of in-flight payments times the per-call cap. The hard bounds are the per-call cap on live ENS and the funding cap on Privy. On top of that the console runs `requireOwner` on every mutating route, the mutation routes are rate limited, and the replay set is stored in Neon so a replayed payment is still rejected even if the facilitator restarts. If you want to check the addresses yourself, the policy resolver the facilitator reads is `0xdC460cd7151D679CF5D1e34595e1ac890A5E4978` on Sepolia, the sandbox registry is `0xB45830aeaf0A00367A450635a110cffA6878A679`, the org-parent registry is `0xb36e0ede0Ed38653c5aB8222B409a6b2Ba78b409`, the canonical ERC-8004 registry I resolve against is `0x8004A818BFB912233c491871b3d84c89A494BD9e`, and on Hedera the 2-of-2 spending account is `0.0.10508343` and the audit topic is `0.0.10496492`.