Skip to content

feat(go-api): analytics endpoint — events per contract #73

Description

@Depo-dev

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:

  1. Operators want to know which contracts are responsible for the most DB and Redis load, so they can plan capacity
  2. Developers want to understand the relative activity of different contracts on the network
  3. The dashboard (feat(infra): developer dashboard — event browser UI #76) uses this endpoint to populate a leaderboard view

Without this endpoint, operators have to run raw SQL queries to get this information.

What needs to be built

Endpoint

GET /v1/stats/contracts

Query parameters:

  • from_ledger (optional, integer) — lower bound, inclusive. Default: 0 (all time)
  • to_ledger (optional, integer) — upper bound, inclusive. Default: latest indexed ledger
  • network (optional, string) — testnet or mainnet. Default: testnet
  • limit (optional, integer 1-100) — number of contracts to return. Default: 50

Response:

{
  "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

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 NULL OR ledger_sequence >= )
  AND (::BIGINT IS NULL OR ledger_sequence <= )
GROUP BY contract_id
ORDER BY event_count DESC
LIMIT ;

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

Dependencies

Metadata

Metadata

Assignees

Labels

Stellar WaveIssues in the Stellar wave program

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions