feat(api): add account summary endpoint with materialized aggregates#89
Merged
Merged
Conversation
- Add AccountSummary Prisma model: PK (address, contractId), stores totalSent, totalReceived, net, txCount, lastActivityAt as rolling aggregates; indexes on address and lastActivityAt for O(1) reads - Add upsertAccountSummaries() in db.ts: accumulates per-(address, contractId) deltas in-memory then emits one raw UPSERT per unique pair using PostgreSQL NUMERIC arithmetic on string-stored amounts - Update indexer pollOnce to call upsertAccountSummaries alongside upsertTransfers so aggregates stay consistent under live ingestion - Add src/api/accounts.ts router: GET /accounts/:address/summary returns one row per asset with raw amounts, display-formatted amounts, txCount, and lastActivityAt; supports optional contractId filter - Mount accounts router in api.ts at /accounts - Add scripts/backfill-account-summaries.ts: truncates and rebuilds AccountSummary from all historical TokenTransfer rows in configurable batches; run with npm run backfill:summaries - Add backfill:summaries script to package.json - Add supertest unit tests for the summary endpoint Closes Miracle656#59
Upstream merged CAP-46 NFT indexing (NftTransfer, NftMetadata models, ingester/nft.ts, NFT pipeline in indexer). Resolved by keeping both: - NFT models + helpers from upstream - AccountSummary model + upsertAccountSummaries from this branch - indexer pollOnce now runs fungible path (with account summary updates) and NFT path together
|
@Ipramking Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Miracle656
approved these changes
May 30, 2026
Owner
Miracle656
left a comment
There was a problem hiding this comment.
Clean materialized aggregate.
- Schema: composite
@@unique([address, contractId])is exactly right; amounts stored as decimalStringavoids JS BigInt precision loss on serialization. upsertAccountSummaries: usesprisma.$executeRawtemplate-literal form, so every${…}is a bound parameter — no SQL injection surface. The NUMERIC cast ("totalSent"::NUMERIC + ${sentStr}::NUMERIC) ⇒ TEXT roundtrip is the standard way to do additive math on text-encoded large integers in Postgres.netis recomputed at UPDATE time from the row's current totals plus the delta — correctness preserved even if the delta is negative on net.- In-memory delta accumulation before flushing keeps it to O(unique-addresses) round-trips per batch instead of O(transfers). Right trade-off for the indexer's batched ingestion.
- Backfill script + supertest coverage round it out.
One forward-looking note (not blocking): the per-pair sequential $executeRaw will become the slow path on heavy days; a single multi-row INSERT … ON CONFLICT with unnest(ARRAY[…]) would batch it. Worth filing as a follow-up if backfill turns out to be slow on large historical data.
Merging. Closes #59.
This was referenced May 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AccountSummaryPrisma model with composite unique key(address, contractId)— storestotalSent,totalReceived,net,txCount,lastActivityAt; indexed onaddressandlastActivityAtfor O(1) readsupsertAccountSummaries()indb.ts: accumulates per-(address, contractId)deltas in memory, then emits one raw PostgreSQLUPSERTper unique pair usingNUMERICarithmetic on string-stored amounts — stays consistent under concurrent ingestionpollOncecallsupsertAccountSummariesright afterupsertTransfersso the two tables never divergesrc/api/accounts.tsrouter mounted at/accounts:GET /accounts/:address/summary— returns one row per asset with raw amounts, human-readable display amounts,txCount, andlastActivityAt; optionalcontractIdquery filterscripts/backfill-account-summaries.ts— truncates and rebuildsAccountSummaryfrom all historicalTokenTransferrows in configurable batches; run vianpm run backfill:summariesTest plan
npm testpassesnpm run backfill:summariespopulatesAccountSummaryfrom existing transfersGET /accounts/<address>/summaryreturns one row per asset touchedGET /accounts/<address>/summary?contractId=<C...>filters to one tokennetequalstotalReceived - totalSentfor all rows/transfers/*and/summary/:addressendpoints unaffectedCloses #59