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

No one assigned

    Labels

    Stellar WaveIssues in the Stellar wave program

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions