Skip to content

feat: add per user realized performance tracking#357

Merged
filo87 merged 5 commits intomainfrom
310-feat-add-per-user-performance-to-indexer
Apr 21, 2026
Merged

feat: add per user realized performance tracking#357
filo87 merged 5 commits intomainfrom
310-feat-add-per-user-performance-to-indexer

Conversation

@fabioembrio
Copy link
Copy Markdown
Collaborator

@fabioembrio fabioembrio commented Apr 21, 2026

Closes #310

TL;DR

Indexes per-account realized P&L and related accounting on tokenInstance:Transfer by extending token_instance_position, appending immutable investor_position_checkpoint rows, and centralizing the math in a dedicated helper.

Motivation (Why)

  • Problem: The indexer tracked token instance positions (balances) but did not expose investor-level performance: cost basis, unrealized-style period earnings (price change on the prior balance), and realized P&L when positions decrease.
  • Impact without this: Consumers cannot answer “how did this wallet perform on this share token?” from the API/indexer alone; product and analytics depend on off-chain recomputation or approximations.

What changed

Schema (ponder.schema.ts)

  • TokenInstancePosition: Adds tokenPriceAtLastChange, cumulativeEarnings, costBasis, cumulativeRealizedPnl (with sensible defaults where applicable).
  • InvestorPositionCheckpoint: New investor_position_checkpoint table: one row per handled transfer side, keyed by (tokenId, centrifugeId, accountAddress, createdAtBlock, logIndex) with pool, balances before/after, price, earnings and P&L fields, trigger and logIndex. Relations to TokenInstance, Account, Pool.
  • Minor: JSDoc on dynamic token yield snapshot columns.

Handlers (tokenInstanceHandlers.ts)

  • On tokenInstance:Transfer, reads tokenPrice from the token instance and refactors sender/receiver updates into handleUserPositionChange (increase vs decrease).
  • Self-transfers (from === to and both user paths): checkpoint skipped (warning logged).
  • When tokenPrice is null or non-positive: checkpoint skipped; existing positions still get balance updated via setBalance so balances stay consistent (no accounting row).
  • Invariant warnings (new serviceWarn): e.g. first-seen sender on decrease, first-seen recipient balance mismatch—handler returns early without writing a checkpoint in those cases.
  • Otherwise: computeInvestorPositionCheckpoint, InvestorPositionCheckpointService.createCheckpoint, then applyCheckpointAccounting on the position.

Pure logic (investorPositionCheckpoint.ts)

  • computePeriodEarnings: balanceBefore * (currentPrice - previousPrice) when prior price exists and balance > 0.
  • computeRemovedCostBasis: pro-rata removal on decreases: (amount * costBasisBefore) / balanceBefore.
  • computeInvestorPositionCheckpoint: combines the above; increases extend cost basis and keep realized P&L; decreases compute realized P&L and update cumulative realized P&L and cost basis (with bigintMax guard).

Services

  • InvestorPositionCheckpointService: thin createCheckpointinsert with logging.
  • TokenInstancePositionService: setBalance, setters for new fields, applyCheckpointAccounting chaining for checkpoint output.

Logging (logger.ts)

  • serviceWarn: stderr [WARN] prefix for non-fatal issues (skipped checkpoints, invariants).

Exports (services/index.ts)

  • Re-exports InvestorPositionCheckpointService.

How to test / verification

  • Automated: No new or changed tests in this diff.
  • Local / CI (expected for this repo): After merging or when validating the branch, from repo root run pnpm update-registry (mainnet per project rules), pnpm codegen because ponder.schema.ts changed, then pnpm typecheck and pnpm lint.
  • Indexer: Run Ponder against a chain that emits tokenInstance:Transfer; confirm investor_position_checkpoint rows for non–self-transfer user legs when tokenPrice is set; confirm balances still update when price is missing; inspect logs for serviceWarn paths (self-transfer, bad price, invariants).
  • Visuals: N/A (backend/indexer only).

Risk & rollout

  • Schema migration / backfill: New columns default to 0 / null as defined; existing positions gain full accounting only as new transfers are processed unless you reindex from scratch (or run a dedicated backfill—not in this diff).
  • Correctness assumptions: Accounting uses token instance tokenPrice at transfer time and position tokenPriceAtLastChange; if price is stale or decimals differ from expectations, P&L semantics may diverge from an off-chain model.
  • Early returns on warnings: Some edge cases skip checkpoint writes; balances may still be advanced in the “unknown price” path—reviewers should confirm that matches product intent for partial data.
  • Rollback: Revert the PR and redeploy the previous indexer image; DB may already contain new table/column data—plan whether to drop or keep for partial deployments.

Registry / codegen note

  • This branch does not modify generated/ or lockfiles in the recorded diff; Ponder schema did change, so pnpm codegen is required after pull/merge before a clean build. If CI does not run codegen implicitly, ensure it does for this PR.

Atomic PR note: The change is a single cohesive feature (schema + handler + math + services). No split recommended unless you want schema/migration in one PR and handler wiring in a follow-up (usually unnecessary here).

@fabioembrio fabioembrio requested a review from filo87 April 21, 2026 09:33
@fabioembrio fabioembrio self-assigned this Apr 21, 2026
@fabioembrio fabioembrio linked an issue Apr 21, 2026 that may be closed by this pull request
Comment thread src/handlers/tokenInstanceHandlers.ts Outdated
Comment thread src/helpers/investorPositionCheckpoint.ts Outdated
@fabioembrio fabioembrio force-pushed the 310-feat-add-per-user-performance-to-indexer branch from 2dbd348 to f3f1a47 Compare April 21, 2026 14:42
@filo87 filo87 merged commit 807c18a into main Apr 21, 2026
7 checks passed
@filo87 filo87 deleted the 310-feat-add-per-user-performance-to-indexer branch April 21, 2026 14:44
@api-gitops api-gitops Bot mentioned this pull request Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add per user performance to indexer

2 participants