feat(frontend): show protocol LP positions as treasury assets with USD and include in CR #14
Labels
No labels
bug
docs
documentation
duplicate
enhancement
frontend
good first issue
help wanted
invalid
missing-implementation
priority
medium
product
QA
question
testing
UX
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/ustr-cmm#14
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 protocol liquidity-pool (LP) positions held by the treasury as first-class treasury assets, with USD mark-to-market, and include them in the collateralization ratio (CR) numerator.
In scope (one bundled issue — display + valuation + CR):
UST1/USTRUST1/cUSTCUST1/xxxUSTR/xxxcUSTC/xxxcLUNC/xxxxxx= any other native or CW20 the pair actually holds. DEXes already used by this frontend: Garuda, Terraswap, Terraport.Depends on #11 CR math and #10 vFDUSD USD. This issue owns LP discovery (allowlisted), reserve NAV, asset-grid rows, and CR inclusion — not a new on-chain CR contract.
Current codebase
frontend/src/hooks/useTreasury.tstokenlist.jsononly. SkipsTREASURY_HOLDING_SKIP_SYMBOLS(USTR,UST1,CLUNC,CUSTC) and the pinned UST1 / USTR / cLUNC / cUSTC addresses. Notype: "lp". Does not callgetTreasuryBalances()/AllBalances.frontend/src/utils/constants.tsTREASURY_HOLDING_SKIP_SYMBOLS— raw protocol tokens are never CR holdings (#11 wrap / liability invariant).frontend/public/assets/tokenlist.jsonpoolis spot-price config for the underlying CW20, not an LP share. Zero LP entries.frontend/src/components/treasury/TreasuryAssetsCard.tsxbalance * prices[symbol]). Hides$0dust when priced (>= $1); unpriced non-zero still shown. No pair/LP row type.frontend/src/hooks/usePrices.ts,frontend/src/services/price.tssimulate_swap/ Terraswapsimulation); vFDUSD session-once oracle. Cannot price an LP share — simulate-swap of an LP mint is the wrong model (spot of one token, not NAV of both reserves).frontend/src/utils/treasuryRatios.ts(+ tests)CR% = (Σ whole_balance × usd_price / whole UST1) × 100. Unpriced balances omitted +incomplete. Invariant: do not count cLUNC/cUSTC or UST1 as assets.frontend/src/components/treasury/RatiosCard.tsxincludedSymbols/missingPriceSymbols.frontend/src/pages/TreasuryPage.tsxcontractService.getTreasuryBalances()AllBalances= native bank + governanceCW20_WHITELISTonly.AddCw20/Cw20WhitelistCONTRACTS.md,PROPOSAL.md): only whitelisted CW20s count toward CR. Frontend currently ignores the whitelist and trusts tokenlist.DEX_ROUTERSinconstants.tsterra1ypwj6…nxkeh7+ router; Terraswap routerterra1g3zc8…7872. No factory pairs pagination, no LPpool {}/total_sharereader.TokenIcon/assets/tokens/{SYMBOL}.png— LP symbols likeUST1-USTRwill letter-fallback unless assets are added.Mainnet pins (do not invent):
terra16j5u6ey7a84g40sr3gd94nzg5w5fm45046k9s2347qhfpwm5fr6sem3lr2terra1f0eqgy9w7e5e7up97vjudqwx38tesf8ylx75x2lv3nwm0clry0pqmgfy72terra1vy3kc0swag2rhn7jz6n72jp0l2ns0p6r6ez5grxq5uhj2rvs97fqfsetxvterra1437qslye72t7qmmahn4t5chz50r8a62g45phwkquwpyu2l62u6ksqssgdgterra1nap4dxh9tv35v0ynd9m4k6zt6c0dq6weszc4j5m564kjls56hu7qcr56chterra1mnl9azefrqpmu888ar2u6zrcwr80hxlt3avf4300r576cw5ar7esvxsvj3https://finder.terraclassic.community/columbus-5Live CR is frontend-only today. A future on-chain collateralization contract (ARCHITECTURE Phase 2) is out of scope.
Why this is needed
UST1/USTR,UST1/cUSTC, and otherUST1|USTR|cUSTC|cLUNCpairs). Those shares are real, withdrawable claims on both reserves. The asset grid currently cannot see them — tokenlist has no LP type, and skip-list logic would also drop any entry named UST1/USTR/cLUNC/cUSTC.simulate_swapis the wrong price for an LP mint. CR must use reserve NAV (share × Σ reserve_i × usd_i). Using a simulated LP→LUNC quote would mis-value the position and is easy to manipulate.cUSTC/xxx) would either miss pairs or double-count native wrap backing already in the treasury bank.Constraints / guardrails
Scope
AddCw20each allowlisted LP on treasury soAllBalances/ a future on-chain CR stay consistent. That is a governance checklist, not a contract feature in this issue.What is / is not a holding
TREASURY_HOLDING_SKIP_SYMBOLS. Do not put those CW20s on the holdings loop (#11).type: "lp", never by symbol substring alone (USTRmust not skipUST1-USTR-LP).ALPHA/LUNCLP is out of scope unless it is also one of those (it isn’t).Allowlist (required for CR)
type: "lp"or an equivalent pinnedlplist).pair { asset1, asset2 }lookups for known protocol token × known quotes (LUNC/uluna, USTC/uusd, UST1, USTR, cUSTC, cLUNC, vFDUSD, and other tokenlist CW20s) on Garuda / Terraswap / Terraport may be used to discover candidates and fail a test if a live pair exists but is missing from the list.UST1/SCAM, donates dust UST1, pumps SCAM, sends LP to treasury).terra1…strings — never query-string / user input (addresses.ts).Valuation (NAV, not spot)
total_sharefrom the pair (pool {}/ dex-equivalent). Do notsimulate_swapthe LP token.total_share == 0orlp_balance == 0→ no row /$0(do not divide by zero).assetsUsd, add it tomissingPriceSymbols, setincomplete. Never treat missing USD as$0or$1.rawToWholeNumber/isValidPositivePrice(decimals.ts). LP decimals fromtoken_info(typically 6). USTR leg is 18. Do notNumber(bigint)raw amounts.LCD_CONFIG.minRequestInterval). No poll faster than existing treasury refetch. Dedupe pair queries.CR eligibility of each LP leg (
crEligible)uluna/uusdalready in the treasury bank. Counting the wrap leg + native = double-count.Display the full NAV on the card. If
crUsd < displayUsd(wrap haircut), the incomplete/included copy or a footnote must say the wrap leg is shown but not added to CR.Other invariants (keep #11)
token_info.total_supplyonly. 1 UST1 = $1 liability.∞only on successful zero UST1 supply. Query failure →N/A, never∞.$1/vFDUSD. Never DEX-simulate vFDUSD (oracleTokens.ts).all_accounts.Relevant files
frontend/public/assets/tokenlist.jsontype: "lp"entries (pair + LP token + dex + underlying assets)frontend/src/hooks/useTreasury.tsfrontend/src/utils/treasuryRatios.ts(+.test.ts)crUsd(do not invent a fake DEX “LP price”)frontend/src/utils/constants.tsfrontend/src/services/price.tspool/ reserve query helpers (or a newlpNavservice — do not reuse simulate-swap for LP mint)frontend/src/hooks/usePrices.tstype: "lp"frontend/src/components/treasury/TreasuryAssetsCard.tsxfrontend/src/components/treasury/RatiosCard.tsxfrontend/src/types/treasury.tspair,displayUsd,crUsd,legs,haircut)frontend/src/services/contract.tsgetTokenBalance/getTokenInfoStrictfor LP CW20; optional whitelist cross-checkfrontend/src/utils/decimals.tsfrontend/src/components/common/TokenIcon.tsxUST1-USTR.pngetc.skills/frontend-ust1-ratios/SKILL.mddocs/ARCHITECTURE.md,docs/DEPLOYMENT.md,docs/CONTRACTS.mdRecommended direction
Pin both LP token and pair if they differ (some DEXes). Native legs use
denom(uluna,uusd) not a CW20 address.lpNav.ts(pure) — inputs:lpBalance,totalShare, reserves, per-leg USD, pinned wrap/UST1/USTR addresses. Outputs:displayUsd,crUsd,includedLegs,haircutLegs,incomplete. Unit-test this hard (see fixtures below).useTreasurynative/cw20(unchanged skip-list).type === "lp":getTokenBalance(lp, treasury)+ pair reserves +token_infodecimals.TreasuryAsset(or LP subtype) withdisplayUsd/crUsd.crUsdintocomputeTreasuryRatiosas an extra USD addend (orusdOverrideon that row) — do not put NAV intousePrices()[symbol]as if it were a spot.Prices for legs — reuse
usePrices/priceServicefor tokenlist underlyings. UST1 liability unit for the UST1 leg is $1 (ECONOMICS), not a DEX UST1 print, unless you also show DEX UST1 as informational and still CR-value the UST1 leg at $1. Pick $1/UST1 for CR (consistent with the denominator). Display may show DEX UST1 if you want; document the split.UI
UST1/USTR(or poolname).displayUsd. If haircut: secondary line e.g.CR counts $X (cUSTC leg omitted).$1dust rule uses display USD.includedSymbolsincludes the LP symbol whencrUsd > 0.Ops / docs
AddCw20for each LP CW20.frontend-ust1-ratiosskill: LP NAV in numerator; wrap legs excluded; raw protocol tokens still skipped.Discovery (optional helper, not CR input) — given pinned protocol tokens × tokenlist quotes, query factory
pair. If a pair exists and is missing from tokenlist, log / test-fail. Humans add the pin. Do not silently CR-count it.Acceptance criteria
UST1/USTR,UST1/cUSTC, and other pinnedUST1/xxx,USTR/xxx,cUSTC/xxx,cLUNC/xxxLPs with non-zero treasury balance appear on Treasury Assets with a pair label and full NAV USD.crUsdonly (wrap legs excluded; UST1/USTR/other eligible legs included).incomplete+ symbol listed; card may still show balance without a fake$0CR contribution.total_share == 0/ failed pool query → no crash; LP not in CR; incomplete if balance> 0.usePricesdoes not simulate-swap LP mints; vFDUSD legs still oracle-only.∞/N/Arules unchanged.frontend-ust1-ratiosskill updated; DEPLOYMENT lists LP pins +AddCw20ops.Test plan (all paths)
Discovery / registry
type: "lp"with valid pins + treasury balance> 0→ one grid row.pairfinds it (helper may warn).UST1-USTRis not skipped byTREASURY_HOLDING_SKIP_SYMBOLS.USTR/UST1/cUSTC/cLUNCstill skipped.NAV math (unit fixtures)
total_share = 1e6, treasury LP =1e5(10%), reserves 1000 UST1 + 2000 USTR, px UST1$1, USTR$0.50→displayUsd = crUsd = 0.1 × (1000×1 + 2000×0.50) = 200.UST1/cUSTC: same 10% share, 1000 UST1 + 1000 cUSTC, cUSTC$0.01→displayUsd = 0.1 × (1000 + 10) = 101,crUsd = 100(cUSTC leg dropped).cLUNC/LUNC: 10% of 1e6 cLUNC + 1e6 LUNC, LUNC$0.0001→ display includes both; CR only LUNC side.USTR/LUNC: both legs in CR (USTR eligible inside LP).total_share = 0→ no NaN/Infinity USD; not in CR.0→ no row.rawToWholeNumber.Number(bigint)precision lie; unusable whole → incomplete.Prices / incomplete
$0CR.$1/vFDUSD.$1per whole UST1 even if a DEX UST1 print exists.UI
noopener).∞then a finite % because an LP query lagged (keep #11 supply rules).Integration
crUsd = 100_000LP → CR210%, A/L2.10x.200%.npm testcoverslpNav+ extendedcomputeTreasuryRatios.Test plan (attack, hack, abuse)
UST1/SCAMLP sent to treasurypairsdump; factory used only as optional pin checkerUST1-USTRskipped likeUST1includes('UST1')cUSTCLP leg + treasuryuusdboth in CRcrUsdexcludes pinned cLUNC/cUSTC legs; fixture abovecUSTCcounted as wrap (wrong haircut) or as collateralgetTokenPriceUsd(lpAddress)fortype: "lp"< $1, still don’t treat as priced CR if reserves fail sanity). Do not silently cap CR without labelingtotal_sharevs balance → >100% of poollp_balance > total_share, fail closed (incomplete, not in CR)Number(bigint)rawToWholeNumberonlyisTerraContractAddressAddCw20’dCw20Whitelist(do not silently drop a pin solely because whitelist lags — label it)rel="noopener noreferrer"return_amount/ reserve stringsVerification criteria
> 0is a visible asset row; USD equalsshare × Σ (whole_reserve × usd)within rounding.(spotAssetsUsd + Σ crUsd) / whole_UST1 × 100with wrap legs out ofcrUsd. Independent calculator on livepool {}+ balances must match.getTokenPriceUsd; skip-list still exactUSTR|UST1|CLUNC|CUSTC; no wrap address in the CR asset loop except as an LP display leg.N/A— not∞, not a sudden drop of LP USD to$0presented as full CR.skills/frontend-ust1-ratios/SKILL.md+docs/ARCHITECTURE.md/DEPLOYMENT.mddescribe LP NAV, wrap haircut, andAddCw20ops.cd frontend && npm test— existing #11 fixtures still pass; new LP fixtures pass.mentioned in merge request !33
Implemented in !33 (
c38ae70).Acceptance criteria
type: "lp"rows with non-zero treasury balance appear with pair label + full NAV USD (schema + fetch + UI; no live Garuda protocol pairs to pin yet)crUsdonly (wrap legs excluded; UST1/USTR/other included)total_share == 0/ failed pool → no crash; not in CR; incomplete if balance > 0usePricesdoes not simulate-swap LP mints; vFDUSD still oracle-only∞/N/Aunchangedfrontend-ust1-ratios+ newfrontend-treasury-lp-navskill; DEPLOYMENTAddCw20opsNot in this MR (follow-ups)
pairlookups for UST1/USTR/cUSTC/cLUNC × tokenlist quotes returned none. Ops addtype: "lp"+AddCw20when pairs exist.Cw20Whitelistmismatch warning in the UI.cd frontend && npm test→ 49 passed (twice).tsc+ eslint clean.Merge review of !33 (
c38ae70) vs #14 ACLocal sanity:
cd frontend && npm test→ 49/49;type-checkandlintclean. GitLab reports mergeable, no conflicts. Merging now (no automerge, not waiting on CI).Meets AC
crUsdonlyUST1-USTRLP symbols not caught by skip listtotal_share == 0, over-share, unpriced CR-eligible legs →crUsd: null+ incompleteusePricessimulate-swap; vFDUSD oracle path unchangedfrontend-treasury-lp-nav, DEPLOYMENT ops checklist)Problems / follow-ups (non-blocking)
tokenlist.json— intentional per DEPLOYMENT (no Garuda protocol pairs at implementation). Ops must addtype:"lp"rows + governanceAddCw20before mainnet LP rows render. Mainnet E2E of AC1 cannot be verified until then.treasuryLp.tshas no mocked integration tests — pure math is covered; LCD-mocked fetch tests for leg matching /liquidity_tokenmismatch would reduce regression risk.resolveLpLegUsdmaps wrap → native by symbol, not pinned address — a typo inpool.assets[].symbolcould mis-price display USD. Low risk (ops-controlled pins).Cw20Whitelistmismatch warning in the UI (optional in the issue).Recommendation: merge with notes; smoke-test Treasury page after the first LP pin lands.
mentioned in commit
4482924766mentioned in issue #16
marked as related to #16
mentioned in merge request !34
mentioned in merge request !35
mentioned in issue #19
marked as related to #19