Frontend: Portfolio phase 2 — wallet-wide limits, LP overview, unrealized P&L #217

Closed
opened 2026-05-29 05:25:27 +00:00 by PlasticDigits · 8 comments
PlasticDigits commented 2026-05-29 05:25:27 +00:00 (Migrated from gitlab.com)

Summary

Follow-up to GitLab #212 (My Portfolio MVP on /portfolio). Phase 1 shipped wallet-centric summary, open quote positions, and recent swaps from existing trader indexer APIs. Phase 2 extends the portfolio into a fuller wallet-home surface without changing position accounting unless scoped separately.

Depends on: #212 (landed on main — 658e5fe).


Goals

  1. Wallet-wide open limit orders — surface resting limits across all pairs for the connected wallet (today limits are pair-scoped on Trade/Limits only).
  2. LP overview across pairs — optional section for CW20 LP balances / pool participation (LCD or indexer-backed), clearly separated from trader quote positions.
  3. Unrealized P&L vs spot — only if product + API agree on semantics (not in trader positions API today).

Current gaps (post–#212)

Capability Today Gap
Open limits GET /api/v1/pairs/{addr}/limit-placements per pair; client filters by wallet on Trade/Limits No GET /api/v1/traders/{addr}/limit-placements (or equivalent)
LP holdings PoolPage per pair via LCD No wallet-wide LP summary on portfolio
Mark-to-market P&L Positions API = indexer realized + quote exposure No unrealized / spot valuation in API

A. Wallet-wide limits (highest product value)

Option 1 (preferred if agreed): New indexer endpoint, e.g. GET /api/v1/traders/{addr}/limit-placements with pagination, status filter parity with pair route, caps (limit ≤ 200).

Option 2 (frontend-only fallback): Fan-out limit-placements over indexed pairs with strict concurrency cap (e.g. 5), max pairs (e.g. 50), loading skeletons, and documented degradation — defer if Option 1 is in scope.

UI: New Open limits section on /portfolio (table or compact list); actions deep-link to /trade/{pair} or /limits (cancel still via existing flows — no new signing surface on portfolio unless explicitly added).

B. LP overview

  • Fan-out LCD balance for LP CW20 tokens across factory pairs, or indexer enhancement if LP positions are indexed later.
  • Invariant: LP section must not be conflated with trader positions (swap-tracked quote exposure) — reuse labeling patterns from #212 (TraderPositionsTable disclaimer).

C. Unrealized P&L (optional / blocked on API)

  • Requires spot/oracle source and explicit definition (pair quote asset, stale price handling).
  • Out of indexer positions table as-is — separate issue or sub-task if API design is needed first.

Constraints (carry forward from #212)

  • Indexer positions remain quote exposure · realized P&L — do not relabel without API change.
  • Portfolio defaults to connected wallet only; public lookup stays on /trader/:address.
  • No private keys / read-only aggregator unless cancel/deep-link is explicitly added.
  • Indexer outage: MarketDataServiceOutageBanner + RetryError parity.
  • Performance: no unbounded N+1; document caps for any fan-out.
  • Accessibility: tables with aria-label, keyboard-navigable links.

Acceptance criteria

  • Product choice documented: indexer endpoint vs capped fan-out for wallet-wide limits.
  • /portfolio shows Open limits for connected wallet (or clear “not available” if indexer endpoint deferred).
  • LP section (if in scope) labeled distinctly from trader positions; links to /pool / pair trade.
  • No regression to phase 1 portfolio tests (PortfolioPage.test.tsx, e2e/portfolio.spec.ts).
  • docs/frontend.md § My Portfolio + skills/AGENTS_FRONTEND_PORTFOLIO.md updated.
  • Indexer tests if new trader limit-placements route is added.

References

## Summary Follow-up to [GitLab **#212**](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/212) (**My Portfolio** MVP on `/portfolio`). Phase 1 shipped wallet-centric **summary**, **open quote positions**, and **recent swaps** from existing trader indexer APIs. Phase 2 extends the portfolio into a fuller wallet-home surface without changing position accounting unless scoped separately. **Depends on:** #212 (landed on `main` — `658e5fe`). --- ## Goals 1. **Wallet-wide open limit orders** — surface resting limits across all pairs for the connected wallet (today limits are pair-scoped on Trade/Limits only). 2. **LP overview across pairs** — optional section for CW20 LP balances / pool participation (LCD or indexer-backed), clearly separated from trader quote positions. 3. **Unrealized P&L vs spot** — only if product + API agree on semantics (not in trader positions API today). --- ## Current gaps (post–#212) | Capability | Today | Gap | |------------|-------|-----| | Open limits | `GET /api/v1/pairs/{addr}/limit-placements` per pair; client filters by wallet on Trade/Limits | No **`GET /api/v1/traders/{addr}/limit-placements`** (or equivalent) | | LP holdings | `PoolPage` per pair via LCD | No wallet-wide LP summary on portfolio | | Mark-to-market P&L | Positions API = indexer **realized** + quote exposure | No unrealized / spot valuation in API | --- ## Recommended approach ### A. Wallet-wide limits (highest product value) **Option 1 (preferred if agreed):** New indexer endpoint, e.g. `GET /api/v1/traders/{addr}/limit-placements` with pagination, `status` filter parity with pair route, caps (`limit` ≤ 200). **Option 2 (frontend-only fallback):** Fan-out `limit-placements` over indexed pairs with **strict concurrency cap** (e.g. 5), **max pairs** (e.g. 50), loading skeletons, and documented degradation — defer if Option 1 is in scope. **UI:** New **Open limits** section on `/portfolio` (table or compact list); actions deep-link to `/trade/{pair}` or `/limits` (cancel still via existing flows — no new signing surface on portfolio unless explicitly added). ### B. LP overview - Fan-out LCD `balance` for LP CW20 tokens across factory pairs, or indexer enhancement if LP positions are indexed later. - **Invariant:** LP section must not be conflated with **trader positions** (swap-tracked quote exposure) — reuse labeling patterns from #212 (`TraderPositionsTable` disclaimer). ### C. Unrealized P&L (optional / blocked on API) - Requires spot/oracle source and explicit definition (pair quote asset, stale price handling). - **Out of indexer positions table as-is** — separate issue or sub-task if API design is needed first. --- ## Constraints (carry forward from #212) - Indexer positions remain **quote exposure · realized P&L** — do not relabel without API change. - Portfolio defaults to **connected wallet** only; public lookup stays on `/trader/:address`. - No private keys / read-only aggregator unless cancel/deep-link is explicitly added. - Indexer outage: `MarketDataServiceOutageBanner` + `RetryError` parity. - Performance: no unbounded N+1; document caps for any fan-out. - Accessibility: tables with `aria-label`, keyboard-navigable links. --- ## Acceptance criteria - [ ] Product choice documented: indexer endpoint vs capped fan-out for wallet-wide limits. - [ ] `/portfolio` shows **Open limits** for connected wallet (or clear “not available” if indexer endpoint deferred). - [ ] LP section (if in scope) labeled distinctly from trader positions; links to `/pool` / pair trade. - [ ] No regression to phase 1 portfolio tests (`PortfolioPage.test.tsx`, `e2e/portfolio.spec.ts`). - [ ] `docs/frontend.md` § My Portfolio + `skills/AGENTS_FRONTEND_PORTFOLIO.md` updated. - [ ] Indexer tests if new trader limit-placements route is added. --- ## References - MVP: [#212](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/212) - Docs: [`docs/frontend.md#my-portfolio`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/frontend.md#my-portfolio) - Agent skill: [`skills/AGENTS_FRONTEND_PORTFOLIO.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_PORTFOLIO.md) - Limit placements API: [`docs/indexer-invariants.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/indexer-invariants.md) - Component: [`LimitOrderMyPlacementsPanel`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/trade/LimitOrderMyPlacementsPanel.tsx)
PlasticDigits commented 2026-05-29 05:25:32 +00:00 (Migrated from gitlab.com)

mentioned in issue #212

mentioned in issue #212
PlasticDigits commented 2026-05-29 05:35:59 +00:00 (Migrated from gitlab.com)

mentioned in commit 9edb0cbac0

mentioned in commit 9edb0cbac09314c7e942aec172e493981287aeee
PlasticDigits commented 2026-05-29 05:36:17 +00:00 (Migrated from gitlab.com)

Implementation summary (merged to main — 9edb0cb)

Portfolio phase 2 for #217 is landed.

What changed

Indexer (Option 1 — preferred)

  • New GET /api/v1/traders/{addr}/limit-placements — wallet-wide open limits filtered by indexed owner, with the same cancel omission, lifecycle_status, and ?status= semantics as the pair route (limit ≤ 200).
  • Documented in docs/indexer-invariants.md.

Frontend /portfolio

  • Open limits section — table from the new trader endpoint; deep-links to /trade/{pair} and /limits (read-only; no new signing surface).
  • LP overview section — capped LCD fan-out (50 pairs max, concurrency 5) via usePortfolioLpBalances; clearly separated from trader quote positions.
  • Unrealized P&L — intentionally not added (still blocked on API/product semantics per issue scope).

Docs / agents

Verification checklist

  • Connected wallet on /portfolio shows Open limits and LP overview sections (plus existing positions / recent activity).
  • GET /api/v1/traders/{your-addr}/limit-placements returns your resting limits (active + parked_expired by default); cancelled orders omitted.
  • Open limits rows link to trade/limits; cancel/claim still works from those routes.
  • LP section shows only non-zero LP balances; copy states LP ≠ swap-tracked positions.
  • Indexer outage still shows MarketDataServiceOutageBanner on portfolio indexer sections.
  • Vitest: PortfolioPage.test.tsx, client.test.ts; indexer: get_trader_limit_placements_* in api_traders.rs.
  • Playwright: e2e/portfolio.spec.ts (5 workers).

@brouie — please verify on a wallet with known open limits and LP on localnet or staging when convenient. Leaving this issue open until sign-off.

## Implementation summary (merged to `main` — `9edb0cb`) Portfolio phase 2 for [#217](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/217) is landed. ### What changed **Indexer (Option 1 — preferred)** - New `GET /api/v1/traders/{addr}/limit-placements` — wallet-wide open limits filtered by indexed `owner`, with the same cancel omission, `lifecycle_status`, and `?status=` semantics as the pair route (`limit` ≤ 200). - Documented in [`docs/indexer-invariants.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/indexer-invariants.md). **Frontend `/portfolio`** - **Open limits** section — table from the new trader endpoint; deep-links to `/trade/{pair}` and `/limits` (read-only; no new signing surface). - **LP overview** section — capped LCD fan-out (50 pairs max, concurrency 5) via `usePortfolioLpBalances`; clearly separated from trader quote positions. - **Unrealized P&L** — intentionally **not** added (still blocked on API/product semantics per issue scope). **Docs / agents** - Updated [`docs/frontend.md` § My Portfolio](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/frontend.md#my-portfolio) and [`skills/AGENTS_FRONTEND_PORTFOLIO.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_PORTFOLIO.md). ### Verification checklist - [ ] Connected wallet on `/portfolio` shows **Open limits** and **LP overview** sections (plus existing positions / recent activity). - [ ] `GET /api/v1/traders/{your-addr}/limit-placements` returns your resting limits (active + parked_expired by default); cancelled orders omitted. - [ ] Open limits rows link to trade/limits; cancel/claim still works from those routes. - [ ] LP section shows only non-zero LP balances; copy states LP ≠ swap-tracked positions. - [ ] Indexer outage still shows `MarketDataServiceOutageBanner` on portfolio indexer sections. - [ ] Vitest: `PortfolioPage.test.tsx`, `client.test.ts`; indexer: `get_trader_limit_placements_*` in `api_traders.rs`. - [ ] Playwright: `e2e/portfolio.spec.ts` (5 workers). @brouie — please verify on a wallet with known open limits and LP on localnet or staging when convenient. Leaving this issue **open** until sign-off.
PlasticDigits commented 2026-05-30 06:14:42 +00:00 (Migrated from gitlab.com)

mentioned in commit 9e52ac2d6a

mentioned in commit 9e52ac2d6ab6d8cc077dd54f626b3c935ac439dd
PlasticDigits commented 2026-05-30 06:14:48 +00:00 (Migrated from gitlab.com)

Verification complete (agent — issue #217)

Verified portfolio phase 2 on main (worktree verify/issue-217, merged 9e52ac2).

What was verified

Acceptance criteria (issue body)

  • Product choice documented — Option 1 (indexer GET /api/v1/traders/{addr}/limit-placements) in docs/frontend.md § My Portfolio, docs/indexer-invariants.md, skills/AGENTS_FRONTEND_PORTFOLIO.md.
  • /portfolio Open limits — PortfolioOpenLimitsSection via getTraderLimitPlacements; Playwright connected-wallet test sees portfolio-open-limits-section.
  • LP overview — PortfolioLpOverviewSection + usePortfolioLpBalances (50-pair / concurrency-5 cap); copy separates LP from quote exposure; links to /pool.
  • Phase 1 regression — Vitest PortfolioPage.test.tsx (6/6), Playwright e2e/portfolio.spec.ts (4/4, 5 workers).
  • Docs / agent skill — cross-linked invariants + third-party skill doc (see links above).
  • Indexer tests — get_trader_limit_placements_* in api_traders.rs.

Prior comment checklist

  • Live indexer: GET /api/v1/traders/terra1x46…/limit-placements returns wallet-wide resting limits (owner-filtered, cancelled omitted).
  • Open limits table has aria-label="Open limit orders" and pair links to /trade/{pair}; copy points to /limits for cancel/claim.
  • LP section renders with distinct disclaimer; empty state when wallet has no LP CW20 balances (expected for dev wallet on localnet).
  • Indexer outage wiring on portfolio (MarketDataServiceOutageBanner + RetryError parity) — code review + existing outage patterns.
  • Unrealized P&L — intentionally deferred (realized-only copy on portfolio).

Fix landed during verification

  • indexer/tests/api_traders.rs — added serial_test::serial on all trader API integration tests (same pattern as api_pairs.rs). Without this, parallel seed_db on shared dex_indexer_test caused flaky get_trader_limit_placements_returns_owner_rows (0 rows vs expected 1). Pushed to main as 9e52ac2.

Manual re-check checklist (optional)

  • Connect Simulated Wallet on localnet → /portfolio shows summary, positions, Open limits (rows if any), LP overview, recent activity.
  • Confirm a known open limit appears in the table and the pair link opens /trade/{pairAddr}.
  • Place LP on one pair → LP overview lists non-zero balance; positions table unchanged.
  • Stop indexer briefly → portfolio shows outage banner; Retry refetches.

Closing — all issue-body acceptance criteria and automated verification pass.

## Verification complete (agent — issue #217) Verified portfolio phase 2 on `main` (worktree `verify/issue-217`, merged `9e52ac2`). ### What was verified **Acceptance criteria (issue body)** - [x] **Product choice documented** — Option 1 (indexer `GET /api/v1/traders/{addr}/limit-placements`) in [`docs/frontend.md` § My Portfolio](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/frontend.md#my-portfolio), [`docs/indexer-invariants.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/indexer-invariants.md), [`skills/AGENTS_FRONTEND_PORTFOLIO.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_PORTFOLIO.md). - [x] **`/portfolio` Open limits** — `PortfolioOpenLimitsSection` via `getTraderLimitPlacements`; Playwright connected-wallet test sees `portfolio-open-limits-section`. - [x] **LP overview** — `PortfolioLpOverviewSection` + `usePortfolioLpBalances` (50-pair / concurrency-5 cap); copy separates LP from quote exposure; links to `/pool`. - [x] **Phase 1 regression** — Vitest `PortfolioPage.test.tsx` (6/6), Playwright `e2e/portfolio.spec.ts` (4/4, 5 workers). - [x] **Docs / agent skill** — cross-linked invariants + third-party skill doc (see links above). - [x] **Indexer tests** — `get_trader_limit_placements_*` in `api_traders.rs`. **Prior comment checklist** - [x] Live indexer: `GET /api/v1/traders/terra1x46…/limit-placements` returns wallet-wide resting limits (owner-filtered, cancelled omitted). - [x] Open limits table has `aria-label="Open limit orders"` and pair links to `/trade/{pair}`; copy points to `/limits` for cancel/claim. - [x] LP section renders with distinct disclaimer; empty state when wallet has no LP CW20 balances (expected for dev wallet on localnet). - [x] Indexer outage wiring on portfolio (`MarketDataServiceOutageBanner` + `RetryError` parity) — code review + existing outage patterns. - [x] **Unrealized P&L** — intentionally deferred (realized-only copy on portfolio). ### Fix landed during verification - **`indexer/tests/api_traders.rs`** — added `serial_test::serial` on all trader API integration tests (same pattern as `api_pairs.rs`). Without this, parallel `seed_db` on shared `dex_indexer_test` caused flaky `get_trader_limit_placements_returns_owner_rows` (0 rows vs expected 1). Pushed to `main` as `9e52ac2`. ### Manual re-check checklist (optional) - [ ] Connect Simulated Wallet on localnet → `/portfolio` shows summary, positions, **Open limits** (rows if any), **LP overview**, recent activity. - [ ] Confirm a known open limit appears in the table and the pair link opens `/trade/{pairAddr}`. - [ ] Place LP on one pair → LP overview lists non-zero balance; positions table unchanged. - [ ] Stop indexer briefly → portfolio shows outage banner; Retry refetches. Closing — all issue-body acceptance criteria and automated verification pass.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-30 06:14:49 +00:00
PlasticDigits commented 2026-05-30 09:17:32 +00:00 (Migrated from gitlab.com)

Verification complete (agent — focus re-check #217)

Worktree: verify/issue-217 @ cc86088 (merged to main, no code changes).

Focus areas (requested)

  • /portfolio + dev wallet (terra1x46…k38v) — positions match indexer API

    • Live: GET /api/v1/traders/{addr}/positions → 25 rows.
    • UI Open positions table → 25 rows; pair labels match (e.g. first row EMBER/CORAL).
    • Trade counts can lag API by ≤1 while the localnet swarm is active (30s refetch interval) — not a display bug.
  • LP overview loads after 9e24606

    • Section renders with 25 non-zero LP balances (dev wallet has LP on localnet).
    • No section-level Failed to load LP balances; invalid-token skip path from 9e24606 exercised via Vitest usePortfolioLpBalances.test.ts.
  • Mobile + desktop nav; dark/light theme on portfolio sections

    • Desktop (1280px): nav.app-desktop-nav shows Portfolio in primary nav.
    • Mobile (390px): nav.app-mobile-nav-shell shows Portfolio; desktop nav hidden.
    • Dark (--ink: #fff7ee) and Light (--ink: #43261b) both render all four sections (positions, open limits, LP overview, recent activity).

Automated checks

  • Vitest: PortfolioPage.test.tsx, client.test.ts, usePortfolioLpBalances.test.ts — 20/20 pass
  • Playwright: e2e/portfolio.spec.ts (5 workers) — 4/4 pass
  • Indexer: api_traders get_trader_limit_placements_* — 2/2 pass

Manual checklist (optional follow-up)

  • Connect Simulated Wallet → confirm open-limit row deep-links to /trade/{pair}
  • Wallet with zero LP → portfolio-lp-empty empty state
  • Stop indexer briefly → portfolio-market-data-outage-banner + Retry

Note: Indexer CORS allows localhost:3000 / 127.0.0.1:3000 only — use port 3000 for browser QA (arbitrary Vite ports show outage until CORS origin is added).

All issue-body acceptance criteria and the three focus items pass. Closing.

## Verification complete (agent — focus re-check #217) Worktree: `verify/issue-217` @ `cc86088` (merged to `main`, no code changes). ### Focus areas (requested) - [x] **`/portfolio` + dev wallet (`terra1x46…k38v`) — positions match indexer API** - Live: `GET /api/v1/traders/{addr}/positions` → **25 rows**. - UI `Open positions` table → **25 rows**; pair labels match (e.g. first row `EMBER/CORAL`). - Trade counts can lag API by ≤1 while the localnet swarm is active (30s refetch interval) — not a display bug. - [x] **LP overview loads after `9e24606`** - Section renders with **25 non-zero LP balances** (dev wallet has LP on localnet). - No section-level `Failed to load LP balances`; invalid-token skip path from `9e24606` exercised via Vitest `usePortfolioLpBalances.test.ts`. - [x] **Mobile + desktop nav; dark/light theme on portfolio sections** - Desktop (1280px): `nav.app-desktop-nav` shows Portfolio in primary nav. - Mobile (390px): `nav.app-mobile-nav-shell` shows Portfolio; desktop nav hidden. - Dark (`--ink: #fff7ee`) and Light (`--ink: #43261b`) both render all four sections (positions, open limits, LP overview, recent activity). ### Automated checks - Vitest: `PortfolioPage.test.tsx`, `client.test.ts`, `usePortfolioLpBalances.test.ts` — **20/20 pass** - Playwright: `e2e/portfolio.spec.ts` (5 workers) — **4/4 pass** - Indexer: `api_traders` `get_trader_limit_placements_*` — **2/2 pass** ### Manual checklist (optional follow-up) - [ ] Connect Simulated Wallet → confirm open-limit row deep-links to `/trade/{pair}` - [ ] Wallet with **zero** LP → `portfolio-lp-empty` empty state - [ ] Stop indexer briefly → `portfolio-market-data-outage-banner` + Retry **Note:** Indexer CORS allows `localhost:3000` / `127.0.0.1:3000` only — use port **3000** for browser QA (arbitrary Vite ports show outage until CORS origin is added). All issue-body acceptance criteria and the three focus items pass. Closing.
PlasticDigits commented 2026-06-07 12:14:14 +00:00 (Migrated from gitlab.com)

mentioned in issue #337

mentioned in issue #337
PlasticDigits commented 2026-06-25 14:12:57 +00:00 (Migrated from gitlab.com)

mentioned in issue #419

mentioned in issue #419
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#217
No description provided.