Skip to content

feat(lp-tab): per-token USD amounts + chart stats sidebar#107

Open
gisk0 wants to merge 10 commits intomainfrom
feat/lp-table-usd-and-chart-stats
Open

feat(lp-tab): per-token USD amounts + chart stats sidebar#107
gisk0 wants to merge 10 commits intomainfrom
feat/lp-table-usd-and-chart-stats

Conversation

@gisk0
Copy link
Copy Markdown
Collaborator

@gisk0 gisk0 commented Mar 26, 2026

What

Improves the LPs tab in pool detail pages.

LP Table

  • Replaces the opaque 'Net LP Tokens' column with two token-specific columns: GBPm and USDm (or whatever the pool's tokens are), showing each LP's proportional share of pool reserves
  • Each cell shows the token amount with a muted USD equivalent underneath (≈ $119.00)
  • Adds a Total Value column (sum of both token positions in USD) when an oracle price is available
  • Share % column kept

LP Concentration Chart

  • Removes empty wasted space to the right of the pie
  • Adds a stats sidebar alongside the chart with two panels:
    • Providers: total LP count, top holder %, top 3 combined share %
    • Pool Reserves: token0 reserve, token1 reserve, Total TVL (USD)

Implementation notes

  • Derives token amounts from pool.reserves0/1 × LP share (same oracle price logic as ReservesTab)
  • Gracefully degrades: falls back to if reserves not available, hides USD columns if no oracle price
  • Exports truncateAddress / resolvePieLabel for testability (fixes test that was testing inline closures)
  • All 489 tests passing

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 26, 2026

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

Project Deployment Actions Updated (UTC)
monitoring-dashboard Ready Ready Preview, Comment Mar 26, 2026 6:58pm

Request Review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Stale comment

This PR replaces LP token-unit display with per-token reserve-derived amounts, adds USD approximations, and adds an LP concentration stats sidebar. It also introduces label-aware pie labels and related tests.

Changed files reviewed:

  • ui-dashboard/src/app/pool/[poolId]/__tests__/page.test.tsx
  • ui-dashboard/src/app/pool/[poolId]/page.tsx
  • ui-dashboard/src/components/__tests__/lp-concentration-chart.test.ts
  • ui-dashboard/src/components/lp-concentration-chart.tsx

ui-dashboard/src/app/pool/[poolId]/page.tsx

🔴 Critical

  • L1096-L1097, L1140-L1149: USD conversion is enabled whenever feedVal !== null, but there is no guard that the pool is actually USD-convertible (i.e. exactly one side in USDM_SYMBOLS). For non-USDm pairs this treats one raw token reserve as USD and produces incorrect "Total Value".
  • L1140-L1147, L1161-L1174: When feedVal is missing, tok0Usd/tok1Usd fall back to raw token amounts and are still rendered as ≈ $... for non-USDm symbols. That displays fabricated USD values.

🟡 Important

  • L1081-L1204: New LP token-amount and USD logic has no direct assertions in page-level tests (conversion correctness, showUsd gating, missing-oracle behavior, non-USDm behavior).

ui-dashboard/src/components/lp-concentration-chart.tsx

🔴 Critical

  • L149-L151, L214-L218: Sidebar TVL is computed and rendered even when feedVal is null, by summing raw reserves and formatting as dollars. This is incorrect numeric output.

🟡 Important

  • L149-L151: USD derivation logic is duplicated and diverges from the safer poolTvlUSD semantics in tokens.ts (which explicitly returns 0 when no USDm side exists). This increases risk of inconsistent financial numbers across views.

ui-dashboard/src/app/pool/[poolId]/__tests__/page.test.tsx

🟡 Important

  • L158-L163: The updated assertion only checks that Share exists; it does not verify the newly added token columns, per-token amount rendering, USD visibility conditions, or incorrect-USD regression cases.

ui-dashboard/src/components/__tests__/lp-concentration-chart.test.ts

🟡 Important

  • L1-L47: Tests only cover truncateAddress and resolvePieLabel; there is no component-level coverage for stats sidebar calculations (topShare, top3Share, Total TVL) or for oracle-missing/non-USDm branches.

Final verdict: 🚫 Needs changes

Open in Web View Automation 

Sent by Cursor Automation: PR Review

? Number(pool.oraclePrice) / 1e24
: null;
const usdmIsToken0 = USDM_SYMBOLS.has(sym0);
const showUsd = feedVal !== null && hasReserves;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

showUsd should not be keyed only on feedVal !== null. In non-USDm pools this path still renders a USD total even though one side is not dollar-denominated. Please gate this with an explicit USD-convertibility predicate (for example, exactly one side in USDM_SYMBOLS) and keep this logic shared with existing TVL helpers to avoid drift.

minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
const showSubUsd = vUsd !== null && sym !== "USDm";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This renders ≈ $... whenever vUsd !== null, but vUsd is currently set to the raw token amount when feedVal is missing. That produces fabricated dollar values. vUsd should stay null unless an actual conversion happened, and sub-USD rows should render only in that case.

const hasPoolData = pool && (reserves0Raw > 0 || reserves1Raw > 0);
const usd0 = feedVal && !usdmIsToken0 ? reserves0Raw * feedVal : reserves0Raw;
const usd1 = feedVal && usdmIsToken0 ? reserves1Raw * feedVal : reserves1Raw;
const totalTvl = hasPoolData ? usd0 + usd1 : null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

totalTvl is computed whenever reserves exist, even if feedVal is null. In that case usd0/usd1 are raw token values, so the displayed $ TVL is incorrect. Please require a valid conversion path before showing USD TVL (same guard as LP table and/or poolTvlUSD semantics).


const html = renderToStaticMarkup(<PoolDetailPage />);
expect(html).toContain("Net LP Tokens");
expect(html).toContain("Share");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This test was updated for the new header, but the PR adds substantial new value-conversion behavior. Please add assertions for: token amount columns, USD total visibility only when conversion is valid, and no ≈ $ rendering when oracle price is unavailable.

});
});

describe("resolvePieLabel", () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Coverage currently validates label helpers only. The new sidebar logic (topShare, top3Share, reserve/TVL display) needs component-level tests, especially around oracle-missing and non-USD-convertible pools where this PR currently risks incorrect dollar output.

gisk0 added 8 commits March 26, 2026 18:56
- Add optional getLabel prop to LpConcentrationChart
- Use resolved label for pie slice labels and hover tooltip
- Show original address as secondary line in hover when label differs
- Wire getLabel from useAddressLabels() in LpsTab
- Add useAddressLabels mock to __tests__/page.test.tsx
When an address has no label, hovering showed the truncated address
twice (once as label, once as raw address). Now:
- labelled address → show name only
- unlabelled address → show truncated address only
…+ add unit tests

- Extract truncateAddress + resolvePieLabel as exported pure helpers
- Narrow getLabel prop to (address: string) => string (chart never passes null;
  call site wraps with adapter lambda)
- Add 6 unit tests covering named label, unlabelled, and no-resolver cases
…lues

- LPs table: swap 'Net LP Tokens' for per-token amount columns (sym0, sym1)
  with inline USD equivalent in muted subtext
- Add 'Total Value' column (summed USD) when oracle price is available
- Chart header: add stats sidebar next to pie — total LPs, top holder %,
  top 3 share %, pool reserves per token, total TVL
- Extract truncateAddress / resolvePieLabel as named exports for testability
- Fix existing test to match updated column names (Share instead of Net LP Tokens)
Plotly merges pie slices with duplicate labels and sums their values,
which would silently misrepresent LP concentration when two addresses
share the same custom label (e.g. both labelled 'Team Wallet').

Fix: use raw addresses as the  key (guaranteed unique) and put
the resolved human names in  (shown in legend) and
(shown in hover). Also:
- Remove duplicated truncateAddress, import from shared @/lib/format
- Narrow getLabel prop type to (string) => string; call site wraps adapter
- Update unit tests: rename _ params to _addr to pass ESLint no-unused-vars,
  add duplicate-label collision test documenting the labels/text split
… oracle

- showUsd now requires hasUsdmSide (XOR: exactly one token is USDm)
  so non-USDm pairs don't display fabricated dollar values
- tok0Usd / tok1Usd now explicitly null when conversion is not valid,
  preventing raw token amounts being rendered as USD
- Chart sidebar totalTvl is null when feedVal is null or no USDm side,
  so TVL only appears when conversion is meaningful
- Import USDM_SYMBOLS in lp-concentration-chart for consistent USD semantics
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Stale comment

This PR adds per-token LP amount estimation, USD conversion, and a new LP concentration stats sidebar in the pool detail view.
The feature direction is good, but there are correctness gaps around invalid oracle-price handling and missing regression coverage for the new calculation/rendering paths.

ui-dashboard/src/app/pool/[poolId]/page.tsx

  • 🟡 IMPORTANT (L1092-L1100, L1148-L1165): feedVal is considered valid as long as it is non-null. If pool.oraclePrice is malformed or overflows (Number(...) => NaN/Infinity), showUsd can stay enabled and USD cells can render ≈ $NaN / $NaN. Guard feedVal with Number.isFinite(...) (and ideally > 0) before enabling USD rendering/conversion.
  • 🟡 IMPORTANT (L1081-L1220): the new per-position token/USD allocation logic is not covered by tests. Current LP tab test only verifies the header text change and ordering. Add cases for token0-USDm, token1-USDm, no-USDm pair, and invalid oracle price fallback.

ui-dashboard/src/components/lp-concentration-chart.tsx

  • 🟡 IMPORTANT (L153-L165): totalTvl and fmtUsd assume feedVal is a finite number. With non-finite input, sidebar can display $NaN. Apply the same finite-value guard before TVL math and formatting.
  • 🟢 SUGGESTION (L29-L38): resolvePieLabel uses a function-type cast (getLabel as (address: string) => string). This can be simplified by using a single callback type ((address: string | null) => string) end-to-end and removing the cast.

ui-dashboard/src/app/pool/[poolId]/__tests__/page.test.tsx

  • 🟡 IMPORTANT (L163-L171): test assertions do not verify the newly introduced LP token columns, conditional Total Value column, or USD conversion rendering paths.

ui-dashboard/src/components/__tests__/lp-concentration-chart.test.ts

  • 🟡 IMPORTANT (L8-L42): tests still validate only resolvePieLabel; there is no coverage for the new stats sidebar (Total LPs, Top holder, Top 3 share, reserves/TVL block).

Final verdict: 🚫 Needs changes

Open in Web View Automation 

Sent by Cursor Automation: PR Review

- replace Plotly legend with explicit custom legend so labels always win
- keep raw addresses as slice keys to avoid accidental slice merging
- left-align chart + legend block and simplify right-side summary card
- use USDM_SYMBOLS consistently for LP sub-USD display
- strengthen LP table and chart tests for USD gating + sidebar rendering
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Stale comment

This PR replaces LP provider rows from raw LP token units to per-token amounts, adds conditional USD valuation, and expands the LP concentration chart with a custom legend plus a stats sidebar.

No findings after reviewing all changed files against the critical/important/suggestion criteria.

Residual risk/testing gap: I could not execute the updated Vitest files in this environment because workspace dependencies are not installed (vitest: not found), so this assessment is static-analysis only.

Final verdict: ✅ LGTM

Open in Web View Automation 

Sent by Cursor Automation: PR Review

- reorder tabs to LPs, Swaps, Reserves, Liquidity, Oracle, OLS
- make LPs the default tab when no ?tab= is present
- only show OLS when the pool has an active OLS registration
- remove stale rebalances tab wiring and update tests for the new default
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR adds per-token LP amounts and a richer LP concentration sidebar, and it also changes tab routing/default behavior on the pool detail page. The LP math/chart work is solid overall, but there is a functional regression in tab availability that should block merge.

ui-dashboard/src/app/pool/[poolId]/page.tsx

  • 🔴 CRITICAL (80-92, 101-107, 328-369): the rebalances tab was removed from TABS, removed from searchable-tab handling, and removed from tab panel rendering. This makes rebalance history unreachable in the UI and breaks existing ?tab=rebalances URLs by forcing fallback to another tab.
  • 🟡 IMPORTANT (252-259, 1366-1384): OLS_POOL is queried in the parent component to decide tab visibility and queried again inside OlsTab. That duplicates polling/network load and can cause avoidable content flicker for direct ?tab=ols navigation.

ui-dashboard/src/app/pool/[poolId]/page.test.tsx

  • 🟡 IMPORTANT (307-401): rebalance search test coverage was removed from the tab-search suite, so the regression above is no longer guarded by tests.

Final verdict: 🚫 Needs changes

Open in Web View Automation 

Sent by Cursor Automation: PR Review

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.

1 participant