Put your agent on a leash.
Onchain spend allowances for AI agents and automations on Monad testnet — fund a personal vault, attach hard caps, revoke instantly, and audit every action through a live ledger.
Programmable onchain allowances so agents can spend for you without spending you.
Agents need wallets to act, but unlimited approvals and hot keys are a personal disaster.
- You want an agent to pay, trade, or hit APIs.
- You do not want infinite
approve, shared private keys, or “trust me” offchain limits. - When something goes wrong, you need a hard stop and a verifiable trail — not a spreadsheet and a hope.
This is a problem I hit the moment agent + money touch the same wallet.
LEASH is a personal policy vault for agent spend:
- You create a vault and deposit USDC (owner-controlled custody).
- You open a session for a spender (agent wallet): total cap, optional period cap, expiry, optional allowlist, optional ERC-8004
agentId. - The agent can only
spendinside that policy; over-cap / bad recipient / expired → reverts onchain. - You can
revokein one transaction. - Everyone can read the story on the Envio activity ledger (not a mocked feed).
Hard limits live in Solidity. The UI is a command center, not the source of truth.
| Layer | Name | What it does |
|---|---|---|
| ① | SURFACE | Next.js command center — wallet UX, fund, sessions, ledger, spend simulator |
| ② | ACCESS | Wallet writes · thin Express/viem API · Envio GraphQL reads |
| ③ | CUSTODY | LeashFactory + LeashVault — one vault per owner, deposit/withdraw |
| ④ | LEASH | Sessions: caps, period, expiry, allowlist, canSpend, spend, revoke |
| ⑤ | PROOF | Envio HyperIndex → live Activity feed · explorers for tx proof |
Agent spend loop: preflight (canSpend) → onchain spend → events indexed → ledger updates.
| Feature | Description |
|---|---|
| Personal vault | One vault per owner via factory |
| Deposit / withdraw | Owner moves USDC in and out |
| Agent sessions | Cap, period cap, expiry, agentId, allowlist |
| Onchain enforcement | spend only by spender; caps are hard |
| Preflight | canSpend view + API + Policy lab UI |
| Spend simulator | Demo agent spend without a real bot |
| Instant revoke | Owner cuts the leash in one tx |
| Live ledger | Envio-indexed Vault / Session / Activity |
| Command-center UI | Dark operational design (not a generic template) |
| Layer | Stack |
|---|---|
| Chain | Monad testnet (10143) |
| Contracts | Solidity 0.8 · Foundry · OpenZeppelin |
| Indexer | Envio HyperIndex (hosted GraphQL) |
| API | Node · Express · viem |
| Frontend | Next.js 15 · TypeScript · Tailwind CSS |
| Wallet | Wagmi · viem · RainbowKit |
| Data / state | TanStack Query · Axios · Zustand |
| Motion | Framer Motion (landing atmosphere) |
There is no separate “ToolRegistry.” The onchain surface is a factory + personal vault.
| Contract | Role |
|---|---|
| LeashFactory | createVault / createVaultFor · vaultOf(owner) · one vault per owner |
| LeashVault | deposit · withdraw · createSession · spend · revoke · canSpend · allowlist |
| Network | Contract | Address |
|---|---|---|
| Monad testnet | LeashFactory | 0x5BA28C60b1A49C809c9A455cca1436bff86a49e0 |
| Monad testnet | USDC (x402 guide) | 0x534b2f3A21130d7a60830c2Df862319e593943A3 |
Vaults are created per user by the factory (clone / deploy pattern via createVault). Look up yours with vaultOf(owner) or the app overview after connecting.
- Factory (MonadVision): https://testnet.monadvision.com/address/0x5BA28C60b1A49C809c9A455cca1436bff86a49e0
- Factory (MonadScan): https://testnet.monadscan.com/address/0x5BA28C60b1A49C809c9A455cca1436bff86a49e0
https://indexer.dev.hyperindex.xyz/25cb989/v1/graphql
Example:
{
_meta { isReady progressBlock eventsProcessed }
Vault(limit: 5) { address owner depositCount sessionCount }
Activity(order_by: { timestamp: desc }, limit: 10) { kind txHash amount }
}LEASH/
├── architecture.svg # System architecture diagram
├── README.md
├── backend/ # Contracts + thin API
│ ├── src/ # LeashFactory, LeashVault, mocks
│ ├── test/ # Foundry tests (10/10)
│ ├── script/ # Deploy scripts
│ ├── abi/ # Exported ABIs
│ ├── deployments/ # chainId → addresses (e.g. 10143.json)
│ └── api/ # Express + viem read/preflight API
├── indexer/ # Envio HyperIndex project
│ ├── config.yaml
│ ├── schema.graphql
│ └── src/handlers/ # Vault + factory event handlers
└── frontend/ # Next.js command center
├── DESIGN.md # UI design system
└── src/
├── app/ # Routes: /, /app, fund, sessions, ledger, policy-lab
├── components/ # UI + LEASH domain + motion
├── hooks/ # vault / sessions / ledger
└── lib/ # wagmi, envio, api, abis
- Node.js 22+
- Foundry (
forge,cast) - Monad testnet MON (gas) + testnet USDC for deposits
- WalletConnect project id (optional for WalletConnect; injected wallets work)
cd backend
forge test -vvDeploy (see backend/script/deploy.sh + backend/.env.example).
Already deployed factory address is in backend/deployments/10143.json.
cd backend/api
cp ../.env.example .env # set LEASH_FACTORY_ADDRESS, RPC_URL, etc.
npm install
npm run dev # default http://localhost:8787Hosted indexer is already running (URL above). For local Envio:
cd indexer
pnpm install
pnpm codegen
# see indexer/README.md for dev / cloud deploycd frontend
cp .env.example .env.local
# set NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID if needed
# GraphQL + factory defaults match testnet deploy
npm install
npm run dev # http://localhost:3000| Env (frontend) | Purpose |
|---|---|
NEXT_PUBLIC_LEASH_FACTORY |
Factory address |
NEXT_PUBLIC_USDC |
Testnet USDC |
NEXT_PUBLIC_ENVIO_GRAPHQL_URL |
Envio GraphQL endpoint |
NEXT_PUBLIC_API_URL |
Backend API (policy lab) |
NEXT_PUBLIC_RPC_URL |
Monad RPC |
NEXT_PUBLIC_CHAIN_ID |
10143 |
- Owner controls deposit, withdraw, create session, revoke.
- Spender can only
spendwithin an active session policy. - Backend API is read / preflight only — it does not hold private keys.
- Never commit
.env/.env.local(see root.gitignore).
Hackathon project (BuildAnything Spark · Monad). See individual package folders for dependency licenses.