Quotas & Limits
Two independent quota systems keep the agent from overspending: token quotas govern LLM usage, and crypto quotas govern how much ETH the MCP tools can send.
Token Quotas
- Implemented in
dapp/app/lib/quotas/token-quota.ts. - Controlled by
AI_CHAT_QUOTA_ENABLED,AI_CHAT_QUOTA_TOKENS, andAI_CHAT_QUOTA_WINDOW_SECinai.server.ts. - Only enforced when JWTs are mandatory and the state worker is active.
handleChatRequestperforms a pre-check before streaming and records usage (addUsage) when the SSE session ends. - Token estimates rely on simple heuristics (
estimateInputTokensFromModelMessages+estimateTokensFromText) so they work regardless of provider.
Crypto Quotas
- Implemented in
dapp/app/lib/quotas/crypto-quota.ts. - Configured via
AI_CRYPTO_QUOTA_ENABLED,AI_CRYPTO_QUOTA_DAILY_LIMIT,AI_CRYPTO_QUOTA_USER_LIMIT, andAI_CRYPTO_QUOTA_DURATION. precheckCryptoSpendis called before any send-crypto tool executes, covering both a global per-network window and a per-address window. Success/failure reasons bubble up into tool chips so the user knows whether the global pool or their individual allowance ran out.recordCryptoSpendincrements both windows via the state service after a transaction is mined.
Crypto quotas require the same Durable Object service as token quotas. If NEXT_PUBLIC_ENABLE_STATE_WORKER is false, the tooling falls back to “unlimited” windows, which is acceptable in local development but not production.
Durable State Service
The Cloudflare Durable Object at dapp/cloudflare/src/durable/state.ts handles nonce storage, rate limiting, and quota windows. The Next.js side communicates through dapp/app/lib/state/client.ts, which throws if the service is disabled or misconfigured.
Reset API
dapp/app/api/quota-reset/route.ts offers a guarded POST endpoint for admins. It supports three modes:
1. Full reset
POST with { "all": true, "scope": "token|crypto|both" }. Deletes every quota key for the selected scope(s).
2. Targeted tokens
POST with { "tokenIds": ["123", "456"] }. The handler maps token IDs to per-user keys and deletes them in batches.
3. Targeted crypto addresses
POST with { "scope": "crypto", "addresses": ["0xabc..."] }. Resets the per-address windows for the active network.
All requests must include the AI_QUOTA_RESET_SECRET via either the dedicated headers (x-quota-reset-secret), Authorization bearer, JSON body, or ?secret= query param. The route also refuses to run if the state service is disabled.
Summary Table
| Quota | Storage Key | Enforced By | Notes |
|---|---|---|---|
| Token | chat:quota:{tokenId} | handleChatRequest | Only active when JWTs are in play; guard rails for LLM consumption. |
| Crypto (global) | crypto:quota:{network}:all | send-crypto*.ts | Caps total daily spending for the active network. |
| Crypto (per user) | crypto:quota:{network}:addr:{user} | send-crypto*.ts | Prevents a single wallet from draining the pool. |