You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 3 of the PRD adds analytics endpoints. The first and most valuable analytics query is: which contracts are generating the most events, and how much activity has each contract seen in a given ledger range?
This serves three distinct use cases:
Operators want to know which contracts are responsible for the most DB and Redis load, so they can plan capacity
Developers want to understand the relative activity of different contracts on the network
Ordered by event_count DESC (highest volume contract first).
SQL query
SELECT
contract_id,
COUNT(*) AS event_count,
MAX(ledger_sequence) AS last_seen_ledger,
MAX(ledger_timestamp) AS last_seen_at
FROM soroban_events
WHERE
network =AND (::BIGINT IS NULLOR ledger_sequence >= )
AND (::BIGINT IS NULLOR ledger_sequence <= )
GROUP BY contract_id
ORDER BY event_count DESCLIMIT ;
This query requires the compound index from #61 (contract_id, ledger_sequence DESC) to avoid a seq scan. Without that index, this query will be extremely slow on large datasets. This issue depends on #61 being merged first.
Caching
The response should be cached in Redis for 60 seconds using the cache key stats:contracts:{network}:{from}:{to}:{limit}. This is important because:
The query involves a GROUP BY on potentially millions of rows
Dashboard polling intervals could hammer the DB if not cached
60-second staleness is completely acceptable for analytics data
Set Cache-Control: public, max-age=60 on the response.
If Redis is unavailable, fall through to the DB query (cache is best-effort, not required for correctness).
Performance requirement
The query must complete in under 200ms on a 10 million row table with the indexes from #61 in place. Verify this before merging with an EXPLAIN ANALYZE run on a seeded dataset.
Authentication
This endpoint requires a valid API key (same as all other /v1 endpoints). It is not a public endpoint — analytics data could reveal commercially sensitive information about network activity patterns.
Acceptance criteria
GET /v1/stats/contracts returns a correctly sorted list with accurate counts
Ledger range filter works correctly (events outside the range are excluded)
Result is cached in Redis for 60 seconds
Cache miss gracefully falls back to DB if Redis is unavailable
Context
Phase 3 of the PRD adds analytics endpoints. The first and most valuable analytics query is: which contracts are generating the most events, and how much activity has each contract seen in a given ledger range?
This serves three distinct use cases:
Without this endpoint, operators have to run raw SQL queries to get this information.
What needs to be built
Endpoint
GET /v1/stats/contractsQuery parameters:
from_ledger(optional, integer) — lower bound, inclusive. Default: 0 (all time)to_ledger(optional, integer) — upper bound, inclusive. Default: latest indexed ledgernetwork(optional, string) —testnetormainnet. Default:testnetlimit(optional, integer 1-100) — number of contracts to return. Default: 50Response:
{ "contracts": [ { "contract_id": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "event_count": 48219, "last_seen_ledger": 55001, "last_seen_at": "2026-06-22T10:00:00Z" } ], "from_ledger": 50000, "to_ledger": 55001, "network": "testnet", "generated_at": "2026-06-22T10:05:00Z" }Ordered by
event_count DESC(highest volume contract first).SQL query
This query requires the compound index from #61 (
contract_id, ledger_sequence DESC) to avoid a seq scan. Without that index, this query will be extremely slow on large datasets. This issue depends on #61 being merged first.Caching
The response should be cached in Redis for 60 seconds using the cache key
stats:contracts:{network}:{from}:{to}:{limit}. This is important because:Set
Cache-Control: public, max-age=60on the response.If Redis is unavailable, fall through to the DB query (cache is best-effort, not required for correctness).
Performance requirement
The query must complete in under 200ms on a 10 million row table with the indexes from #61 in place. Verify this before merging with an
EXPLAIN ANALYZErun on a seeded dataset.Authentication
This endpoint requires a valid API key (same as all other
/v1endpoints). It is not a public endpoint — analytics data could reveal commercially sensitive information about network activity patterns.Acceptance criteria
GET /v1/stats/contractsreturns a correctly sorted list with accurate countsCache-Control: public, max-age=60header present on responseDependencies