Docs
QUOTA is a primitive: contracts, two SDKs and a middleware. The snippets below show the target interface; packages are under construction and names may change.
Judge quickstart
No account or password is needed: any email address works for login (a one-time code is emailed). Use Chrome or Edge. Everything runs on Monad testnet and the contracts are unaudited.
Quick look (2 minutes, no funds)
- Open the landing page and scroll: how a deposit, a proof and a slash fit together.
- Open /demo, a labelled simulation. Send a few requests, then reuse a request number to watch a cheater get slashed step by step.
- Open /service: live chain data and a live request feed from the demo server. Rows show only short throwaway numbers, never who sent a request.
Stake an agent in the browser (about 5 minutes, needs test MON)
- Open /operator and log in with any email and the code you receive. A wallet that only you own is created (Privy). Copy its address.
- Send about 0.6 MON from the Monad faucet to that address: 0.1 MON of stake per request of limit, plus about 0.45 MON for gas. Wait about 30 seconds and reload.
- Click Create passkey and register and approve with Windows Hello, Touch ID or your phone (two prompts: create, then sign). Passkeys only work on quota-metro.vercel.app.
- Click Enroll agent with a limit of 1 and approve with your passkey. The agent turns
active, and the chain has verified your passkey signature.
Send requests from an agent and trigger a real slash (about 10 minutes)
You need Node 22, pnpm 11 and about 1 test MON. This uses throwaway keys and a software passkey (test tooling), and runs against the live demo server.
- Clone the repo and install:
git clone --recurse-submodules https://github.com/shane2512/QUOTA
cd QUOTA
pnpm install- Make two throwaway keys by running this twice: one funder, one agent. Never reuse a real key.
pnpm --filter @quota/demo-mcp exec node --input-type=module -e "import {generatePrivateKey,privateKeyToAccount} from 'viem/accounts'; const k=generatePrivateKey(); console.log(k, privateKeyToAccount(k).address)"- Send about 1 MON from the faucet to the funder address, then create a file named
.envin the repo root:
MONAD_RPC_URL=https://testnet-rpc.monad.xyz
QUOTA_REGISTRY_ADDRESS=0xCBdfda8ebF4302793C06a402E9753C4F43799990
WEBAUTHN_RP_ID=quota-metro.vercel.app
DEPLOYER_PRIVATE_KEY=<the funder key>
AGENT_PRIVATE_KEY=<the agent key>
AGENT_LIMIT=1
QUOTA_SERVER_URL=https://quota-demo-mcp.onrender.com/mcp- Stake the agent (about 30 seconds):
pnpm --filter @quota/devtools enroll-agent- Send a request. It prints
OKwith Wikipedia results, and a verified row appears on /service:
pnpm --filter @quota/demo-mcp agent "monad blockchain"- Send another. The client refuses with
quota exhausted for this epochand sends nothing:
pnpm --filter @quota/demo-mcp agent "one more"- Cheat by reusing a request number. Both calls print
REJECTED (violation), and about 30 seconds later /service shows a slashed row with commit and reveal transactions. This permanently burns the throwaway agent.
pnpm --filter @quota/demo-mcp agent --cheat "one" "two"The first request after a quiet period can take about a minute: the demo server sleeps on a free plan.
Optional: delegate slashing to your own wallet (Dynamic)
Open /slasher, sign in with an email code (a Dynamic embedded wallet is created) and click Approve delegation. The server status then reads “Slashing from your wallet”, meaning later slashes on our server are signed from your own wallet. Revoke delegation undoes it.
Check it on-chain
Two slashes whose commit and reveal were both sent from an operator's Dynamic embedded wallet, 0x4872…FF8C: commit, reveal. Open either and read the sender.
Service quickstart
Protect an Express, Hono or MCP server in a few lines. You choose how many requests an hour and which trust lists you accept.
pnpm add @quota/server @quota/slasher
import { quota } from "@quota/server";
import { Slasher, DynamicServiceWallet } from "@quota/slasher";
const slasher = new Slasher({ wallet: new DynamicServiceWallet() });
app.use(quota({
serverId: "lantern-search",
perEpoch: 100, // requests per hour per staked agent
trees: ["open"], // or ["open", "screened"]
onViolation: slasher.submit,
}));What you get
- Proof verification off-chain in milliseconds.
- A nullifier store holding one line per epoch and nullifier.
- Automatic secret recovery on a repeat, queued to the slasher.
Agent quickstart
Stake once from the operator console, then sign each request through the SDK.
import { QuotaClient, PrivyAgentWallet } from "@quota/client";
const quota = new QuotaClient({ wallet: new PrivyAgentWallet() });
const headers = await quota.signRequest("lantern-search", payloadHash);
await fetch(url, { headers });The client tracks request numbers per epoch and refuses to exceed your limit, so an honest agent can never trigger a slash by accident.
How a slash works
Each request carries one point on a line derived from the agent's secret. A repeated request number yields a second point, which fixes the line and reveals the secret. The slasher then claims the stake in two steps.
commitSlashpublishes a hash of the secret, limit, receiver and a salt.- After at least one block,
revealSlashdiscloses them. Copying the reveal with a different receiver fails the hash check.
Threat model
A stolen agent key
It cannot touch the deposit. Unstaking, limit changes and withdrawal address changes need the operator's passkey, verified on-chain with a nonce.
Copy and steal
A searcher watching the public mempool sees only a hash during the commit. This is a documented fallback, not a sealed mempool.
Splitting stake
Allowance is linear in stake, so many small deposits gain nothing over one large one.
What it does not cover
It is not proof of personhood, and the quota is per server. Services should size their minimum stake above the value of the abuse they expect.
Integration status
Each integration is listed with what has actually been observed, not what is planned.
| Piece | State | Evidence |
|---|---|---|
| Passkey custody (P256VERIFY) | confirmed | Precompile verified on testnet; contract work in progress |
| Privy agent wallets | confirmed | Deterministic secret derivation and sign-only on Monad confirmed |
| Dynamic wallets | confirmed | Server wallet and embedded-wallet delegated access both live; slashes signed from an operator's wallet verified on-chain |
| Nansen screening | partial | Wallet labels only; related-wallet checks need credits |
| BTX sealed mempool | not used | Unavailable on testnet. Claims use commit then reveal |
| Cleanverse compliant tree | not used | Dropped: no access to the partner network |
| Qwen Scout agent | partial | Deferred; agent loop stays provider-agnostic |