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)

  1. Open the landing page and scroll: how a deposit, a proof and a slash fit together.
  2. Open /demo, a labelled simulation. Send a few requests, then reuse a request number to watch a cheater get slashed step by step.
  3. 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)

  1. 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.
  2. 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.
  3. 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.
  4. 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.

  1. Clone the repo and install:
git clone --recurse-submodules https://github.com/shane2512/QUOTA
cd QUOTA
pnpm install
  1. 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)"
  1. Send about 1 MON from the faucet to the funder address, then create a file named .env in 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
  1. Stake the agent (about 30 seconds):
pnpm --filter @quota/devtools enroll-agent
  1. Send a request. It prints OK with Wikipedia results, and a verified row appears on /service:
pnpm --filter @quota/demo-mcp agent "monad blockchain"
  1. Send another. The client refuses with quota exhausted for this epoch and sends nothing:
pnpm --filter @quota/demo-mcp agent "one more"
  1. 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.

  1. commitSlash publishes a hash of the secret, limit, receiver and a salt.
  2. After at least one block, revealSlash discloses 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.

PieceStateEvidence
Passkey custody (P256VERIFY)confirmedPrecompile verified on testnet; contract work in progress
Privy agent walletsconfirmedDeterministic secret derivation and sign-only on Monad confirmed
Dynamic walletsconfirmedServer wallet and embedded-wallet delegated access both live; slashes signed from an operator's wallet verified on-chain
Nansen screeningpartialWallet labels only; related-wallet checks need credits
BTX sealed mempoolnot usedUnavailable on testnet. Claims use commit then reveal
Cleanverse compliant treenot usedDropped: no access to the partner network
Qwen Scout agentpartialDeferred; agent loop stays provider-agnostic