feat: /pool table v2 LP USD column (pair AMM TVL from #569 catalog) #655
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#655
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
Add a v2 LP USD column to the
/poolpairs table (Pair / Vol / Fee / Created / Source) that shows the human USD value of each factory v2 AMM pool. Bundle indexer list JSON + sort + dApp column + docs/verify in one issue. Do not split “API field” vs “table cell” vs “sort caret.”This is pair TVL (both reserve legs, P522-Q / hub catalog), not the connected wallet’s LP-token mark-to-market, not book escrow, not V3 Grid / #619 vaults, not CoinGecko
liquidity_in_usd(that field is mislabeled 24h volume).Related: #547 (
/pooltable), #569 (protocol TVL catalog), #556 (hub USD), #534 (catalog rank), #562 (hide gems), #576 (Vol = trailing 24h), #489 (no lectures), #653 (no nested chrome), #243 (pair_volume_24hrollup pattern).Current codebase
/poolis a sortable indexer table. Protocol already knows how to price a factory pair in USD. The pair list API does not expose that number, so the table cannot show it without per-row LCD (forbidden) or inventing TVL from volume./pooltablePoolPairsTable.tsx: columns Pair, Vol, Fee, Created, Source, Actions (Charts / Manage). Vol usesformatQuoteVolume24h(volume_quote_24h, asset_1.decimals). Created cells are always—(list JSON has no timestamp). Source is Factory / Indexer (+ Frozen). Expand ManagecolSpan={6}mountsPoolAdvancedManage(LCDgetPoolonly on expand — P547-9 / A8).PoolPage.tsx+poolListQuery.ts: default catalog fetcheslimit=500sort=volume_24h&order=desc, clientsortIndexerPairsByCatalog, paginate 20. Search uses indexerrelevance. Column clicks use indexersort/order(symbol,volume_24h,fee,created) with no catalog overlay (P547-3–P547-4). Production omits gems (P562-3).GET /api/v1/pairs(indexer/src/api/pairs.rsPairResponse):pair_address,asset_0/asset_1,lp_token,fee_bps,is_active,code_id_frozen, optionalvolume_quote_24h. Noliquidity_usd/ TVL.IndexerPairinfrontend-dapp/src/types/index.tsmatches. Sort allowlist:id,fee,created,symbol,volume_24h,relevance. Invalidsort→ 400.limitclamp 1–100 (PAIR_LIST_LIMIT_MAX);offset> 10_000 → 400. SearchqisILIKE-escaped (SEC-I04).pair_volume_24h(~5 min).sort=volume_24hisCOALESCE(pv.volume_quote, 0)— raw quote units, not USD. Unpriced idle pairs zero on refresh (#577 D3)./pool)protocol_tvl.rsprotocol_pair_tvl: P522-Q + hub. Both legs priced →h0×usd0 + h1×usd1. Exactly one catalogued →2×that leg (CPAMM). Neither / stale / same-asset / overflow /reserve_*=0→ omit (not$0). Book escrow not included. Refresh writes globaltotal_liquidity_usdonly — no per-pair rollup for the list API.uusd→ USTC oracle; LUNC/cLUNC/uluna→ LUNC; UST1/USTR →hub_prices. Never$1UST1, never2.5×USTR, never vFDUSD/FDUSD (P569-4, P550-10). Identity is contract/denom (symbol-spoof natives skipped).pair_reservesupserted bybook_snapshot.rs(~10s). RawNUMERIC. Stale TTL same class as hub / book mirror.formatProtocolUsd: missing / non-finite →—, neverInfinity. Protocol Total liquidity already uses this ontotal_liquidity_usd.poolListQuery.tscomments claim listlimitmax 1000; indexerPAIR_LIST_LIMIT_MAXis 100, so cataloglimit=500is clamped to 100. Out of scope unless the new sort cannot page correctly.PoolPage.test.tsx,poolListQuery.test.ts,e2e/pool-table-547.spec.ts,make verify-issue-547. Indexer:api_pairs.rs,indexer_pair_volume_pagination.rs,indexer_protocol_liquidity.rs.docs/frontend.md§ Liquidity pools list (P547-1–P547-10),skills/AGENTS_FRONTEND_POOL_TABLE.md,skills/AGENTS_FRONTEND_PROTOCOL_STATS.md(P569),docs/indexer-invariants.md(pair list volume + protocol TVL).Why this is needed
/poolto add liquidity need stock (TVL) next to flow (Vol).protocol_pair_tvlis the protocol census primitive./protocolonly shows the sum. Re-deriving TVL in the browser from LCDgetPoolwould re-open A8 (N+1 on first paint) and drift from Protocol’s catalog.volume_quote_24h, CGliquidity_in_usd, LP supply × last price, or$1UST1. Those are the same mistakes #550 / #569 already forbade.sort=volume_24his a JOIN onpair_volume_24h, not a liveSUM. Per-pair USD sort must be the same class: materialized, indexed, NULLS LAST — notpair_reserves ⋈ oracle_priceson GET.Constraints / guardrails
protocol_pair_tvl/pair_usable_for_protocol_tvl(or extract a shared helper). Same quotes load asrefresh_protocol_liquidity. Never vFDUSD. Never$1UST1. Never2.5×USTR. Identity = contract/denom./api/v1/pairsstays a rollup JOIN. Do not walkpair_reserves+ oracles + hub on the request path (includingsort=liquidity_usd). Stamp per-pair USD on the aggregator / hub / protocol-TVL refresh (same cadence as #569). Cache-miss list = SQL only.PairResponse/IndexerPair, e.g.liquidity_usd(human USD decimal string, same class astotal_liquidity_usd). Omit ornullwhen unpriced. Old indexers → dApp shows—. Do not break existing clients. Do not renamevolume_quote_24h.$0. Stale reserves, zero reserves, same-asset, overflow, hub/oracle down, neither leg catalogued → omit field /null. UI em-dash.$0is only a real empty-but-priced pool (if that can exist afterreserves_usable; if not, still—). Sorting: NULLS LAST, thenp.id. Do notCOALESCE(..., 0)for display or for “unpriced ranks as poorest.”pair_reserves. No resting book escrow, parked dust, wallet LP balances, wrap-mapper inventory, or V3 Grid vault AUM. Header v2 LP USD +titlethat this is factory AMM pool USD (trailing snapshot), not 24h volume.getPool/getPairFeeConfig/ hub-price fetch per table row. Manage expand stays the only LCD pair query.liquidity_usd(orlp_usd— pick one, document it) toPairListSort,IndexerPairSort,POOL_COLUMN_SORTS. Default order desc. Catalog default (no column click) still UST1-hub rank — do not overlay catalog on this column. Search staysrelevance.PAIR_LIST_LIMIT_MAXor change catalog fetch unless the new sort is broken without it. If you touch the clamp, document why and keepoffset10_000 / negative-limit → 1.formatProtocolUsd(or a shared wrapper). NeverInfinity/NaN/ scientific-notation raw in the cell. XSS/javascript:in a spoofed numeric string →—(text node only).card-glassaround each<tr>. Keepoverflow-x-auto; bumpmin-wif the extra column wraps badly. Light + dark; phone 375px horizontal scroll OK.aria-sortpattern as Vol.data-testidfor header + cell (e.g.pool-sort-lp-usd,pool-row-lp-usd). Manage expandcolSpanmust include the new column.liquidity_in_usdor Protocol headlines here.title: “Factory v2 pool USD (reference).”Relevant files
indexer/src/api/pairs.rsPairResponse,parse_pair_list_sort, list mappingindexer/src/db/queries/pairs.rsPairListRow,PairListSort, JOIN +ORDER BYindexer/src/indexer/protocol_tvl.rsprotocol_pair_tvl— reuseindexer/src/indexer/hub_usd.rspair_tvl/ReservePairindexer/src/indexer/pair_price_usd.rsindexer/src/db/queries/hub_prices.rslist_reserve_pairs, hub quotesindexer/migrations/pair_liquidity_usd(or equivalent) rollup + index, mirrorpair_volume_24hindexer/tests/api_pairs.rsindexer/tests/indexer_protocol_liquidity.rsindexer/tests/indexer_pair_volume_pagination.rsEXPLAIN)frontend-dapp/src/types/index.tsIndexerPair,IndexerPairSortfrontend-dapp/src/services/indexer/client.tsgetPairstypes onlyfrontend-dapp/src/utils/poolListQuery.tsPOOL_COLUMN_SORTS, default order descfrontend-dapp/src/components/pool/PoolPairsTable.tsxcolSpanfrontend-dapp/src/pages/PoolPage.tsxfrontend-dapp/src/utils/formatProtocolStats.tsformatProtocolUsdfrontend-dapp/src/pages/PoolPage.test.tsxsort=liquidity_usdfrontend-dapp/src/utils/__tests__/poolListQuery.test.tsfrontend-dapp/e2e/pool-table-547.spec.tsdocs/frontend.mddocs/indexer-invariants.mdskills/AGENTS_FRONTEND_POOL_TABLE.mdscripts/qa/verify-issue-NNN.sh+Makefilemake verify-issue-NNNRecommended direction
pair_liquidity_usd(pair_idPK,liquidity_usd NUMERIC(38,18)nullable,updated_at). Refresh insiderefresh_protocol_liquidity(or the same hub tick): for each usable reserve pair,protocol_pair_tvl→ upsert; unusable / omitted →NULL(or delete row). Index(liquidity_usd DESC NULLS LAST).pair_volume_24h. Map toliquidity_usd: Option<String>(skip_serializing_if). Sort keyliquidity_usd(default desc). Invalid sort string still 400 (allowlist — no raw SQL from query).SortHeaderafter Vol (right-aligned, mono). Cell =formatProtocolUsd(ip.liquidity_usd)or—. WirePOOL_COLUMN_SORTS+defaultOrderForPoolSortdesc. Update ManagecolSpan.make verify-issue-NNN(frontend Vitest + indexer lib/integration + grep invariants). Do not require a new CosmWasm migrate.Do not compute USD in the browser from reserves +
GET /hub-prices. Do not LCD-simulate every row.Acceptance criteria
/pooltable shows a v2 LP USD column between Vol and Fee (or immediately after Vol if layout requires; not buried after Source).$1.2k/$12.34via existingformatProtocolUsd/formatNum) or —. NeverInfinity,NaN, raw 18-dec integer, or quote-denom volume.GET /api/v1/pairsitems include optionalliquidity_usdstring when priced; omitted/nullwhen not. Additive: old fields unchanged.sort=liquidity_usd&order=desc; toggle asc/desc;aria-sorton that<th>only. Catalog default (no click) unchanged.protocol_pair_tvlfor the same reserves + quotes (not$1/leg, not2.5×USTR).getPool/getPairFeeConfig/ per-row LCD. Network tab / Vitest spies stay A8-clean.sort=liquidity_usd;DROP/sort=__proto__/sort=tvl→ 400.limit=-1still clamps to 1.EXPLAIN(or equivalent test) forsort=liquidity_usddoes not scanswap_eventsor live-sum reserves on GET.make verify-issue-NNNland with the implementation.Test plan (all paths)
Indexer
liquidity_usd=h0×usd0 + h1×usd1string2×priced leg (CPAMM)null$1h_ust1 + h_custc×ustcwith ust1=1 unless hub is 12.5×USTCsnapshot_at/reserve_*=0/ same-assetGET /pairs?sort=liquidity_usd&order=descidorder=ascsort=liquidity_usdunknown-typo / injectionvolume_24h/relevancepages includeliquidity_usdwhen setprotocol_pair_tvlstamp (not a second formula)fits_numeric_38_18faillimit/offsetcaps unchangedq=+sort=liquidity_usdqempty / column mode)Frontend
sort=liquidity_usduntil clickgetPairs({ sort: 'liquidity_usd', order: 'desc', limit: 20 })order: 'asc';aria-sortflipsnull/""/"Infinity"/"NaN"—"1234.5"$+ compact numberliquidity_usdhugecolSpanmatches column count; Advanced still LCD-on-expand onlycard-glass—, page does not crashE2E / LocalTerra
make has-localterra+/pool$or—(not blank)Test plan (attack, hack, abuse)
symbol=USTR/USTRonugemliquidity_usd(A7)2×USTC only; not1e24USDliquidity_usd=<img onerror=…>/javascript:—; no HTMLsort=liquidity_usd;DELETE/sort=id FROM pairs--ORDER BYenum only/poolpaint does not query pair contractssort=liquidity_usdEXPLAIN has noswap_events/ no live reserve×oracle joinoffset=10001%/_inqwhile sorting USD$0—volume_quote_24hwhen field missing—onlyliquidity_in_usdreused as cell1e+19Number.isFinite→—if non-finite; indexer should emit normalized decimal like volumejavascript:pair address in ChartsLink(P547-5)card-glassper row (#489 / #653)$from a deleted rollup row across refresh (NULL/delete the stamp)Verification criteria
Ship is done when all of the following hold:
make verify-issue-NNN(new) is green: indexer tests for stamp + list + sort + 400 + EXPLAIN; Vitest PoolPage /poolListQuery/ format; static grep thatPoolPairsTabledoes not callgetPool.make verify-issue-547still green (table, catalog default, Vol/Fee/Pair sorts, Charts href).make verify-issue-569still green (global TVL formula unchanged)./poolshows v2 LP USD; sort desc; Manage expand; no extra LCD storm on first paint.docs/frontend.mdP547 extension,docs/indexer-invariants.mdpair-list liquidity row,skills/AGENTS_FRONTEND_POOL_TABLE.mddo/don’t + verify target. AGENTS.md verify one-liner if that is the house style.—, no console throw.Out of scope: V3 Grid TVL (#619), per-wallet LP USD on
/portfolio, Created timestamp cells (#547 leftover), raising pair-listlimitto 500/1000, CGliquidity_in_usdrename, Protocol page changes.marked as related to #547
marked as related to #569
marked as related to #556
marked as related to #534
marked as related to #562
mentioned in issue #661
mentioned in issue #662
marked as related to #662
mentioned in issue #664
marked as related to #664
mentioned in commit
cbb45740f0mentioned in merge request !1173
mentioned in commit
638e9ca59cmentioned in commit
d52bdf039amentioned in commit
b64331ffecMerged to
mainvia !1173. List JOIN now fillsliquidity_usdviapair_to_response. Module ispair_liquidity_usd(not a secondpair_liquidity.rs). Default sort desc includesliquidity_usd.Leftover: Coolify indexer migrate
20260826150000_pair_liquidity_usd.sql+ redeploy so/poolUSD and Created/#662 stay populated. Unstamped rows still omit USD (by design).make verify-issue-655.mentioned in issue #673
mentioned in issue #685
marked as related to #685
mentioned in issue #690
mentioned in issue #692
marked as related to #692
mentioned in issue #701