Summary
The API has no per-wallet database cost controls. A single wallet making expensive paginated queries, deep search requests, or large analytics calls can saturate the connection pool and degrade the experience for all users. This issue implements a query cost governor that assigns a cost unit to each query based on its estimated database impact, tracks per-wallet rolling spend in Redis, and throttles wallets that exceed the budget with a 429 response and a backoff window.
Scope
1. Query cost model
- Define a cost map in configuration: each route pattern maps to a cost unit (e.g.
GET /creators = 1, GET /creators/:id/holders = 3, GET /search = 5, GET /creators/:id/history = 2)
- Parameterised costs:
limit query param multiplies the base cost (e.g. limit=100 on a cost-3 route = cost 30)
- Cost map configurable via environment without code changes
2. Rolling budget tracking
- On each authenticated request, before query execution, read the wallet's rolling spend from Redis key
qcost:{walletAddress} (a sorted set of { cost, timestamp } entries)
- Evict entries older than the rolling window (default 60 seconds) from the sorted set
- If the sum of remaining entries plus the current request cost exceeds the budget (default 200 units/60s), return 429
query_budget_exceeded with Retry-After set to when the oldest entry will expire
- Otherwise, append the current cost entry and proceed
3. Cost response headers
- Add
X-Query-Cost: {cost} and X-Query-Budget-Remaining: {remaining} headers to every authenticated response
- On throttled responses include
X-Query-Budget-Reset: {unix_timestamp} indicating when the budget resets
4. Admin override
- Admin wallets (configurable list) bypass the governor entirely
- A
POST /internal/qcost/reset/:walletAddress endpoint (internal network only) allows operators to clear a wallet's budget immediately
5. Integration tests
- Send requests summing to exactly the budget — assert all succeed
- Send one request that pushes the total over the budget — assert 429 with correct
Retry-After
- Verify
X-Query-Cost and X-Query-Budget-Remaining headers on each response
- Wait for the rolling window to expire — assert requests succeed again
- Admin wallet sends requests exceeding the budget — assert no throttling
Acceptance Criteria
ETA: 24 hours
Coordinate on Telegram
Summary
The API has no per-wallet database cost controls. A single wallet making expensive paginated queries, deep search requests, or large analytics calls can saturate the connection pool and degrade the experience for all users. This issue implements a query cost governor that assigns a cost unit to each query based on its estimated database impact, tracks per-wallet rolling spend in Redis, and throttles wallets that exceed the budget with a 429 response and a backoff window.
Scope
1. Query cost model
GET /creators= 1,GET /creators/:id/holders= 3,GET /search= 5,GET /creators/:id/history= 2)limitquery param multiplies the base cost (e.g.limit=100on a cost-3 route = cost 30)2. Rolling budget tracking
qcost:{walletAddress}(a sorted set of{ cost, timestamp }entries)query_budget_exceededwithRetry-Afterset to when the oldest entry will expire3. Cost response headers
X-Query-Cost: {cost}andX-Query-Budget-Remaining: {remaining}headers to every authenticated responseX-Query-Budget-Reset: {unix_timestamp}indicating when the budget resets4. Admin override
POST /internal/qcost/reset/:walletAddressendpoint (internal network only) allows operators to clear a wallet's budget immediately5. Integration tests
Retry-AfterX-Query-CostandX-Query-Budget-Remainingheaders on each responseAcceptance Criteria
limitparam multiplierRetry-AfterandX-Query-Budget-ResetETA: 24 hours
Coordinate on Telegram