Trader: show Charts leaderboard at bottom of /trader and /trader/:address #657
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#657
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Show the existing Charts Trader leaderboard at the bottom of
/traderand/trader/:addresspage content (last section inapp-main-content, above the Layout legal footer). Reuse the Charts table — do not invent a second ranking or USD formula.This is one product change: empty lookup (
/trader) and profile (/trader/:address) both need the same board so Trader is a discovery surface, not only a paste-an-address form./portfoliostays wallet-home and is out of scope.Current codebase
Trader routes have no leaderboard
TraderPage.tsxis the only UI for:/traderparamAddrempty/trader/:addressgetTrader+TraderSummaryStats+TraderPositionsTable+ Trade History (getTraderTradeslimit 100). No board.Routes are wired in
App.tsxasTraderRouteShell(/traderand/trader/:address) withresetKeyson the address segment (#126). Nav label Trader is under More (navItems.ts).Profile queries:
GET /api/v1/traders/{addr}viagetTrader→parseIndexerTraderPayload(malformed JSON is a React Query error, not a crash).MarketDataServiceOutageBanner(#215).The page root is
<div className="space-y-4">. There is no leaderboard query andgetLeaderboardis not mocked inTraderPage.test.tsx. E2E smokee2e/trader-page.spec.tsonly asserts heading + positions.Leaderboard already exists on Charts
ChartsPage.tsxmounts a Leaderboardshell-panel-strongafter Recent Trades:useQuerykey['leaderboard', leaderboardSort],getLeaderboard(sort, 20),refetchInterval: 30_000.LEADERBOARD_TABS, private to Charts):total_volume_usd/best_trade_pnl/total_realized_pnl/worst_trade_pnl— labels Volume (USD) / Best Trade / Most Profit / Most Loss.formatIndexedVolumeUsdontotal_volume_usd(T553-1). NeverformatNum(total_volume).PnlValue./trader/${trader.address}viashortenAddress(..., 10, 6).RetryError(“Failed to load leaderboard”), “No traders yet”, tablearia-label="Trader leaderboard".data-testid="charts-leaderboard-volume".Client:
getLeaderboard→GET /api/v1/traders/leaderboard?sort=&limit=. Default sort in the helper istotal_volume_usd; Charts always passes an explicit sort.Indexer API (reuse only)
indexer/src/api/traders.rsGET /api/v1/traders/leaderboard:total_volume,total_volume_usd, rolling volumes, trade count, P&L fields,total_fees_paid.total_volume(raw). The dApp must requesttotal_volume_usdfor the Volume tab (T553-5).limitclamped1…200. Charts uses 20.(sort, limit)(#280). Do not add an uncached fan-out.getLeaderboarddoes not runparseIndexerTraderPayloadper row (profile-only). Do not regress Charts; if you add row parsing, keep Charts tests green.Shell placement (footer is Layout, not the page)
Layout.tsx:header→<main className="app-main-shell">(Outlet) →<footer className="app-footer-shell">(EnvironmentRibbon + legal notice) → mobile bottom nav. On small viewports.app-footer-shellalready pads above--app-mobile-nav-stack. The board must live in page content (last sibling inTraderPage), never insideapp-footer-shellor the mobile nav./portfoliosharesTraderSummaryStats/ positions / trades but is the connected wallet home (#212). Do not add the board there in this issue.Why this is needed
Trader in More is a dead-end unless the user already knows a bech32. The only retail leaderboard is buried at the bottom of
/chartsafter pair overview, candles, and tape. Users who open Trader to find wallets (or who land on a 404 / empty lookup) cannot browse ranks without leaving the page.Charts already implements the product: USD volume rank, three P&L tabs, 20 rows, links into
/trader/:addr. Duplicating that markup on Trader without extracting a shared component will fork T553-1–T553-6 (rawtotal_volumeprinting10,000,000T,$0vs—for unpriced activity).Constraints / guardrails
shell-panel-strong./traderand/trader/:address, including loading, 404, and profile outage. Profile query failure must not unmount the board. Board has its own loading / empty /RetryError.total_volume_usd, samelimit=20, samerefetchInterval: 30_000, same formatters (formatIndexedVolumeUsd,PnlValue). Prefer React Query key['leaderboard', sort]so Charts and Trader share cache.total_volumeas Volume. Volume tab sorts and displaystotal_volume_usd. Unpriced →—. Idle (total_trades === 0) →$0. Do not add rollingvolume_24h/7d/30dcolumns (raw API-only).shell-panel-strongaround the table + tabs. Noshell-panel*insideshell-panel*. Nocard-glassper row. Flat metric tiles stay onTraderSummaryStatsonly.python3 scripts/check_chrome_nesting.pymust stay green.VITE_*in retail strings. Outage copy stays the existing banner + boardRetryError.#280TTL, raise Charts’ 20, or “fix” Sybil/wash ranking (POS-02). Document the known limitation indocs/frontend.mdonly if you mention ranking./portfoliounchanged. Do not add a second USD formula. Do not showtotal_fees_paid(still—/ unused on Charts). Links stay/trader/{addr}withencodeURIComponent/ existingLink. Address text isshortenAddress(text node, notdangerouslySetInnerHTML).:addressrow when it appears in the top 20 (aria-current="page"or a row class). Do not invent “you are rank N” if the wallet is not in the page — that needs another API.#126/#215/#177stay: parse-or-fail profile, outage banner vs 404,resetKeyson address change. Board tab state may reset on address change (same page remount viaOutlet key={pathname}).Relevant files
frontend-dapp/src/pages/TraderPage.tsxfrontend-dapp/src/pages/ChartsPage.tsxfrontend-dapp/src/pages/TraderPage.test.tsxfrontend-dapp/src/pages/ChartsPage.test.tsxfrontend-dapp/src/services/indexer/client.tsgetLeaderboard— reusefrontend-dapp/src/utils/chartsOverviewStats.tsformatIndexedVolumeUsdfrontend-dapp/src/components/trader/PnlValue.tsxfrontend-dapp/src/components/common/Layout.tsxfrontend-dapp/e2e/trader-page.spec.tsdocs/frontend.md§ Charts trader leaderboard + § Trader profileskills/AGENTS_FRONTEND_TRADER_VOLUME_USD.mdindexer/src/api/traders.rsLikely new:
frontend-dapp/src/components/trader/TraderLeaderboard.tsx(+ unit tests). Charts becomes a thin wrapper.Recommended direction
TraderLeaderboard.charts-leaderboard-volumeor a shared testid that Charts tests are updated to), and visual order after Recent Trades.TraderPagerenders<TraderLeaderboard />(optionalhighlightAddress={traderAddr \|\| undefined}) as the last child of the page root — outside{trader && (…)}so empty / 404 / outage still show the board.getLeaderboardinTraderPage.test.tsx(same pattern as Charts).e2e/trader-page.spec.ts(and empty/traderif cheap): heading Leaderboard, table or empty copy, section abovefooter.app-footer-shell.AGENTS_FRONTEND_TRADER_LEADERBOARD.md.Acceptance criteria
/trader(no address): after the empty prompt, a Leaderboard section appears; Layout footer is still below it./trader/:address(found): board is below Trade History and above the footer./trader/:address404 and indexer outage: board still mounts; profile RetryError / outage banner unchanged; board errors are independent.getLeaderboard('total_volume_usd', 20); USD compact; no rawTfromtotal_volume; unpriced—; idle$0.PnlValuefields./trader/{address}; current-profile row may be highlighted if in the list.VITE_INDEXER_URL/127.0.0.1in copy.#553tests still pass./portfoliohas no new board.make verify-issue-653/ chrome nesting still passes. Light + dark, ~375px and ~1280px: table scrolls horizontally if needed; last rows are not hidden under footer or mobile nav.Test plan — functional paths
/trader,getLeaderboard→ rows[]/trader/{addr}, profile + trades + boardgetTrader404, board OKgetTrader502, board OKtotal_volume+ pricedtotal_volume_usd$compact; not10,000,000Ttotal_volume_usdnull,total_trades > 0—not$0total_trades === 0$0getLeaderboardcalled with matching sort; P&L cells/trader/{addr}:addressresetKeys; board still last; shared query cache OK/charts#553tests/portfolioTrader leaderboard/aria-label="Trader leaderboard"unless it already existed (it does not)/trader/trader/:addr--inkon--panel-bg(C653-8)/trader/{dev}Vitest:
TraderPage.test.tsx, extractedTraderLeaderboardtests, existingChartsPage.test.tsx#553 cases. Playwright: extende2e/trader-page.spec.ts; workers stay 5 for smoke.Test plan — attack, hack, and abuse
Leaderboard is unauthenticated read of indexed ranks. This issue must not widen write surface or weaken
#280/#126guards.address<script>/javascript:inaddressshortenAddress+ ReactLinkto={/trader/${addr}}. NodangerouslySetInnerHTML. Prefer skip/omit rows that failisValidTerraAddress/ parse. Click must not becomejavascript:.address=https://evilor//evilLinkstays path-absolute/trader/…. Nowindow.location = address."<img onerror=…>"PnlValue/formatIndexedVolumeUsdtext only.__proto__/constructorrowssort=total_volume;drop/ unknown keylimit=1e9or0/trader+/chartsgetTraderN+1.sort; Volume vs P&L cannot show the other tab’s numbers.#is list index of server order. Do not re-sort in the UI.total_volumeas USDTas dollars.total_volume_usdVITE_INDEXER_URL, host:port, or stack in banner/RetryError.Linkonly.relnot required for internal routes.#432CSV helpers./trader/{other}Verification criteria
Done when all of the following are true:
/traderand/trader/:address(found, 404, disconnect) show Leaderboard as the last main-content block; legal footer + mobile nav unchanged and not overlapped.getLeaderboardis called withtotal_volume_usd+20on first paint; tab changes request the matching sort only.make test-frontend(or scoped Vitest) covers Trader board paths + Charts #553.e2e/trader-page.spec.ts(or added spec) asserts the board on the connected profile path.make verify-issue-553andmake verify-issue-653still pass.docs/frontend.mdstates Trader hosts the same board; skill #553 mentions the shared component.git grep/ review: no secondformatNum(total_volume)on the new surface; no board onPortfolioPage.tsx; no indexer diff required.Out of scope
/portfolioleaderboard#280cache, default API sort, orlimitclampDependencies
Labels / owner / priority
frontendUXenhancementmissing-implementationdocstestinge2eOwner: frontend
Priority: P2
marked as related to #553
marked as related to #126
marked as related to #215
marked as related to #177
marked as related to #653
marked as related to #489
mentioned in issue #665
marked as related to #665
mentioned in issue #666
marked as related to #666
mentioned in commit
52fbb4286cmentioned in merge request !1166
mentioned in merge request !1167
mentioned in commit
bd6484d95dmentioned in commit
5d1647c208mentioned in commit
9a82f40580Merged to
mainvia !1167. Later !1176 (#666) mounts the sameTraderLeaderboardon/chartswithpairAddress(global board stays on/trader; Charts hides Best Trade).Leftover:
/traderstill shows the DEX-wide board (four tabs including Best Trade).make verify-issue-657. Pair ranks are #666.mentioned in issue #673