Skip to content

feat: init portfolio history#1067

Open
w84april wants to merge 59 commits intomainfrom
feat/portfolio-history-api
Open

feat: init portfolio history#1067
w84april wants to merge 59 commits intomainfrom
feat/portfolio-history-api

Conversation

@w84april
Copy link
Copy Markdown
Collaborator

@w84april w84april commented Feb 16, 2026

Description

This branch expands the portfolio backend from a historical balance chart API into a broader holdings platform with:

  • a cached holdings history API
  • a FIFO-based portfolio PnL API
  • a vault-level drilldown API for accounting inspection
  • better handling for staking, migrations, router flows, rewards, and selected CoW settlement buys
  • more reliable price fetching and caching
  • frontend integration for Portfolio PnL, Economic Gain, and stable vs volatile breakdowns

It also adds operational support for lazy cache invalidation when new vaults are indexed, plus cleanup chores for stale cache rows.

What Was Added

Public API surfaces

  • GET /api/holdings/history
    • returns chart-ready daily USD portfolio history
    • uses cache-first aggregation and recalculates only missing days
    • supports version, refresh, fetchType, and paginationMode
  • GET /api/holdings/pnl
    • returns compact portfolio PnL summary plus vault-family rows
    • supports strict, zero_basis, and windfall handling for unknown-basis transfer-ins
    • exposes both totalPnlUsd and totalEconomicGainUsd
    • includes stable vs volatile category breakdowns
  • GET /api/holdings/pnl/drilldown
    • returns the same valuation basis as /pnl, but expanded with lots, realized consumption, unknown-basis receipts/withdrawals, and journal-style inspection data
    • intended for detailed accounting/debug UI
  • POST /api/admin/invalidate-cache
    • records vault invalidations so users holding newly indexed vaults refresh lazily on next request
  • POST /api/holdings/chores
    • cleanup endpoint for stale cache maintenance

Accounting Model

The PnL engine now models Yearn positions as vault-family FIFO lots rather than simple balance snapshots.

It:

  • groups underlying vaults and staking wrappers into a single family
  • tracks lot location (vault vs staked) instead of treating staking as a new investment
  • uses acquisition-time PPS and acquisition-time token prices for known-basis USD accounting
  • separates Portfolio PnL from Economic Gain
  • supports three unknown-basis policies:
    • strict: exclude ambiguous lots from PnL
    • zero_basis: treat ambiguous receipts as zero-cost basis
    • windfall: split receipt-time value from later market PnL
  • keeps conservative partial-basis behavior when full reconstruction is not possible

Processing Flow

History flow

  1. Load cached daily totals.
  2. Detect stale cache via vault invalidation timestamps.
  3. Fetch Envio events when missing days require recomputation.
  4. Build a share-balance timeline.
  5. Resolve vault metadata and PPS from Kong.
  6. Fetch underlying asset prices from DefiLlama.
  7. Compute daily USD totals.
  8. Persist results back to cache.

PnL flow

  1. Fetch address-scoped events.
  2. Enrich with tx-scoped context for same-hash router, staking, and migration flows.
  3. Normalize into vault-family ledgers.
  4. Build FIFO lots and realized entries.
  5. Value remaining lots with current PPS and token price.
  6. Apply the chosen unknown-basis policy.
  7. Materialize summary rows and drilldown payloads.

Data Sources

The holdings and PnL pipeline now combines:

  • Envio GraphQL for deposits, withdrawals, and transfers
  • Kong for vault metadata and historical/current PPS
  • DefiLlama for historical/current USD prices of the vault asset token
  • Ethereum RPC + CoW settlement logs for selected known-basis synthetic acquisition enrichment

Pricing / Performance Improvements

This branch also hardens price fetching significantly:

  • tuned DefiLlama batching for reliability
  • pro/free GET fallback behavior
  • request splitting on infra-sized failures
  • exact positive-price caching
  • exact negative-cache for unsupported price points with TTL
  • support for fetchType=parallel and paginationMode=all on both history and PnL surfaces
  • support for the same fetch controls on Vercel handlers, not only the Bun server

Frontend Integration

The Portfolio page now consumes the new APIs and surfaces:

  • chart history from /api/holdings/history
  • Portfolio PnL and Economic Gain from /api/holdings/pnl
  • tooltip copy explaining the metric formulas
  • stable vs volatile breakdowns under PnL and Economic Gain
  • updated response typing and schema validation

Operational Changes

This branch adds lazy cache invalidation for newly indexed vaults:

  1. After deploying an indexer update with new vaults, call POST /api/admin/invalidate-cache.
  2. Vault invalidation timestamps are recorded in the database.
  3. On the next user request, the system checks whether any held vaults were invalidated after cache creation.
  4. If stale, the affected user cache is cleared and recalculated lazily.

It also adds a chores endpoint for stale cache cleanup.

Documentation

This branch ships with dedicated docs for both the high-level holdings system and the accounting model:

  • api/lib/holdings/README.md
  • api/lib/holdings/PNL.md

Testing

The work includes substantial coverage across:

  • history aggregation
  • GraphQL event fetching
  • DefiLlama batching and caching
  • Kong metadata/PPS integration
  • CoW settlement enrichment
  • PnL mode behavior
  • vault metadata resolution
  • drilldown and summary materialization

Screenshots

image

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
yearnfi Ready Ready Preview, Comment Apr 10, 2026 11:09pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
yearnfi-nextjs Ignored Ignored Preview Apr 10, 2026 11:09pm

Request Review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 16, 2026

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
See the Details below.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 794104e.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

License Issues

package.json

PackageVersionLicenseIssue Type
@neondatabase/serverless^1.0.2NullUnknown License
Allowed Licenses: MIT, Apache-2.0, BSD-3-Clause, BSD-2-Clause, ISC, CC0-1.0, CC-BY-3.0, CC-BY-4.0, Unlicense

OpenSSF Scorecard

PackageVersionScoreDetails
npm/@neondatabase/serverless ^1.0.2 UnknownUnknown
npm/@types/pg ^8.16.0 UnknownUnknown

Scanned Files

  • package.json

@socket-security
Copy link
Copy Markdown

socket-security Bot commented Feb 16, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​pg@​8.20.01001007391100
Added@​neondatabase/​serverless@​1.0.29810010086100

View full report

@w84april w84april changed the base branch from main to dev February 16, 2026 16:55
@w84april w84april changed the base branch from dev to main February 16, 2026 16:56
Comment thread api/lib/holdings/README.md Outdated
Comment on lines +188 to +192
### DELETE `/api/v1/holdings/cache`
Clear cached data (useful for forcing recalculation).

```bash
curl -X DELETE "http://localhost:3001/api/v1/holdings/cache?address=0x..."
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this permissioned access? We shouldn't have DELETE api endpoints exposed publicly

Also, can we merge in feat/portfolio-history-adjustments - it just replaces let/for to const/maps, something I've expanded the CLAUDE.md doc to include in future

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Sure, this was added for simpler testing purposes only. Thanks for pointing it out

@w84april w84april marked this pull request as draft February 17, 2026 20:19
@murderteeth
Copy link
Copy Markdown
Collaborator

murderteeth commented Mar 30, 2026

image if we have an error and don't return anything, this should probably collapse or be hidden and not take up space.

for the empty state (not the same as error state) instead of collapse or hide, lets show a placeholder\mockup. something that communicates "this is how awesome your portfolio would look if you had one" followed with a clear call to action, Explore Vaults, or whatever. use muted colors and\or a badge that clearly says "example", so it's clear we're not showing real holdings.

the portfolio page is popular, users spend a lot of time on it. we dont want to undersell it.

* feat: init pnl

* feat: consider staking

* feat: omit migrate

* chore: support migrate

* feat: 3 calc types

* feat: add totalEconomicGainUsd

* chore: docs

* chore: refactor

* feat: actually consider price at deposit time

* chore: optimize price fetch

* fix: cache

* chore: make fetching more aggressive

* feat: test params

* feat: improve totalCurrentValueUsd

* feat: share events & kong

* feat: fe + history fix

* fix: defillama batching

* feat: tooltips

* feat: handle cow

* feat: tune defillama

* chore: tune a bit more

* feat: add missing prices cache

* feat: docs

* fix: timeout

* chore: copy

* feat: stable vs volatile

* Feat/portfolio pnl excessive (#1148)

* feat: init excessive

* chore: naming

* feat: actually add drilldown

* fix: v2/v3 filter

* fix: version

* fix: env

* feat: cover crvusd rewards

* feat: vault to vault migrate via enso

* feat: init katana rewards

* chore: upd receiver address

* feat: fix bold

* feat: add fetchType + paginationMode

* feat: support fetchtype + pagination on vercel
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.

4 participants