feat: add per user realized performance tracking#357
Merged
Conversation
filo87
reviewed
Apr 21, 2026
…-performance-to-indexer
2dbd348 to
f3f1a47
Compare
filo87
approved these changes
Apr 21, 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.
Closes #310
TL;DR
Indexes per-account realized P&L and related accounting on
tokenInstance:Transferby extendingtoken_instance_position, appending immutableinvestor_position_checkpointrows, and centralizing the math in a dedicated helper.Motivation (Why)
What changed
Schema (
ponder.schema.ts)TokenInstancePosition: AddstokenPriceAtLastChange,cumulativeEarnings,costBasis,cumulativeRealizedPnl(with sensible defaults where applicable).InvestorPositionCheckpoint: Newinvestor_position_checkpointtable: 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 andlogIndex. Relations toTokenInstance,Account,Pool.Handlers (
tokenInstanceHandlers.ts)tokenInstance:Transfer, readstokenPricefrom the token instance and refactors sender/receiver updates intohandleUserPositionChange(increase vs decrease).from === toand both user paths): checkpoint skipped (warning logged).tokenPriceis null or non-positive: checkpoint skipped; existing positions still get balance updated viasetBalanceso balances stay consistent (no accounting row).serviceWarn): e.g. first-seen sender on decrease, first-seen recipient balance mismatch—handler returns early without writing a checkpoint in those cases.computeInvestorPositionCheckpoint,InvestorPositionCheckpointService.createCheckpoint, thenapplyCheckpointAccountingon 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 (withbigintMaxguard).Services
InvestorPositionCheckpointService: thincreateCheckpoint→insertwith logging.TokenInstancePositionService:setBalance, setters for new fields,applyCheckpointAccountingchaining for checkpoint output.Logging (
logger.ts)serviceWarn: stderr[WARN]prefix for non-fatal issues (skipped checkpoints, invariants).Exports (
services/index.ts)InvestorPositionCheckpointService.How to test / verification
pnpm update-registry(mainnet per project rules),pnpm codegenbecauseponder.schema.tschanged, thenpnpm typecheckandpnpm lint.tokenInstance:Transfer; confirminvestor_position_checkpointrows for non–self-transfer user legs whentokenPriceis set; confirm balances still update when price is missing; inspect logs forserviceWarnpaths (self-transfer, bad price, invariants).Risk & rollout
tokenPriceat transfer time and positiontokenPriceAtLastChange; if price is stale or decimals differ from expectations, P&L semantics may diverge from an off-chain model.Registry / codegen note
generated/or lockfiles in the recorded diff; Ponder schema did change, sopnpm codegenis 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).