feat(frontend): show protocol LP positions as treasury assets with USD and include in CR #14

Closed
opened 2026-08-15 12:41:06 +00:00 by PlasticDigits · 10 comments
PlasticDigits commented 2026-08-15 12:41:06 +00:00 (Migrated from gitlab.com)

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):

Pair pattern Examples
UST1/USTR protocol/protocol LP
UST1/cUSTC unstablecoin / wrap LP
UST1/xxx any other UST1 pair (LUNC, USTC, ALPHA, vFDUSD, …)
USTR/xxx any USTR pair
cUSTC/xxx any cUSTC pair
cLUNC/xxx any cLUNC pair

xxx = 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

Component Path Behavior today
Holdings fetch frontend/src/hooks/useTreasury.ts Iterates tokenlist.json only. Skips TREASURY_HOLDING_SKIP_SYMBOLS (USTR, UST1, CLUNC, CUSTC) and the pinned UST1 / USTR / cLUNC / cUSTC addresses. No type: "lp". Does not call getTreasuryBalances() / AllBalances.
Skip list frontend/src/utils/constants.ts TREASURY_HOLDING_SKIP_SYMBOLS — raw protocol tokens are never CR holdings (#11 wrap / liability invariant).
Token registry frontend/public/assets/tokenlist.json Native USTC/LUNC + CW20 ALPHA, USTRIX, SpaceUSD, vFDUSD, USTR. Optional pool is spot-price config for the underlying CW20, not an LP share. Zero LP entries.
Asset grid frontend/src/components/treasury/TreasuryAssetsCard.tsx Shows tokenlist holdings with USD (balance * prices[symbol]). Hides $0 dust when priced (>= $1); unpriced non-zero still shown. No pair/LP row type.
Prices frontend/src/hooks/usePrices.ts, frontend/src/services/price.ts CEX LUNC/USTC; DEX simulate-swap of 1e6 CW20 units (Garuda simulate_swap / Terraswap simulation); 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).
CR math 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.
Ratios UI frontend/src/components/treasury/RatiosCard.tsx Incomplete banner lists includedSymbols / missingPriceSymbols.
Treasury page frontend/src/pages/TreasuryPage.tsx Disclaimer: cLUNC/cUSTC supplies are informational — not extra collateral.
On-chain balances contractService.getTreasuryBalances() Implemented but unused by the Treasury page. Contract AllBalances = native bank + governance CW20_WHITELIST only.
Whitelist treasury AddCw20 / Cw20Whitelist Docs (CONTRACTS.md, PROPOSAL.md): only whitelisted CW20s count toward CR. Frontend currently ignores the whitelist and trusts tokenlist.
DEX routers DEX_ROUTERS in constants.ts Garuda factory terra1ypwj6…nxkeh7 + router; Terraswap router terra1g3zc8…7872. No factory pairs pagination, no LP pool {} / total_share reader.
Icons TokenIcon /assets/tokens/{SYMBOL}.png — LP symbols like UST1-USTR will letter-fallback unless assets are added.

Mainnet pins (do not invent):

Name Address Decimals Role
Treasury terra16j5u6ey7a84g40sr3gd94nzg5w5fm45046k9s2347qhfpwm5fr6sem3lr2 — Holder of LP CW20s
UST1 terra1f0eqgy9w7e5e7up97vjudqwx38tesf8ylx75x2lv3nwm0clry0pqmgfy72 6 Liability (CR denominator). Raw balance still not a holding.
USTR terra1vy3kc0swag2rhn7jz6n72jp0l2ns0p6r6ez5grxq5uhj2rvs97fqfsetxv 18 Utility token. Raw balance still not a holding.
cLUNC terra1437qslye72t7qmmahn4t5chz50r8a62g45phwkquwpyu2l62u6ksqssgdg 6 Wrap receipt. Raw balance still not a holding.
cUSTC terra1nap4dxh9tv35v0ynd9m4k6zt6c0dq6weszc4j5m564kjls56hu7qcr56ch 6 Wrap receipt. Raw balance still not a holding.
vFDUSD terra1mnl9azefrqpmu888ar2u6zrcwr80hxlt3avf4300r576cw5ar7esvxsvj3 6 Bridged collateral (already a holding).
Finder https://finder.terraclassic.community/columbus-5 — Pair / LP contract links

Live CR is frontend-only today. A future on-chain collateralization contract (ARCHITECTURE Phase 2) is out of scope.


Why this is needed

  1. Treasury will / does hold DEX LP for protocol markets (UST1/USTR, UST1/cUSTC, and other UST1|USTR|cUSTC|cLUNC pairs). 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.
  2. Users judging backing only see spot tokens (USTC, LUNC, ALPHA, …). Protocol LP can be a material slice of reserves. Omitting it understates CR and hides where liquidity lives.
  3. Spot simulate_swap is 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.
  4. Without an allowlist + wrap-leg haircut, auto-counting every factory pair (or the wrap side of cUSTC/xxx) would either miss pairs or double-count native wrap backing already in the treasury bank.

Constraints / guardrails

Scope

  • Frontend + tokenlist + docs + tests. No new CosmWasm execute path. No change to wrap-mapper, ustc-swap, or InstantWithdraw.
  • Ops should AddCw20 each allowlisted LP on treasury so AllBalances / a future on-chain CR stay consistent. That is a governance checklist, not a contract feature in this issue.
  • Do not build a full DEX pair browser or paginate every Garuda/Terraswap pair (LCD quota + fake-pair flood).

What is / is not a holding

  • Raw UST1 / USTR / cLUNC / cUSTC remain in TREASURY_HOLDING_SKIP_SYMBOLS. Do not put those CW20s on the holdings loop (#11).
  • LP shares whose pair involves those tokens are holdings. Identify them by pair/LP contract address + type: "lp", never by symbol substring alone (USTR must not skip UST1-USTR-LP).
  • Only pairs where at least one pinned asset is UST1, USTR, cUSTC, or cLUNC. Random ALPHA/LUNC LP is out of scope unless it is also one of those (it isn’t).

Allowlist (required for CR)

  • A pair enters the asset grid and CR only if it is explicitly listed (tokenlist type: "lp" or an equivalent pinned lplist).
  • Factory 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.
  • Never auto-include an unlisted factory pair in CR (attacker deploys UST1/SCAM, donates dust UST1, pumps SCAM, sends LP to treasury).
  • Addresses must be pinned terra1… strings — never query-string / user input (addresses.ts).

Valuation (NAV, not spot)

share        = treasury_lp_balance / pool.total_share
displayUsd   = share × (reserveA × pxA + reserveB × pxB)
crUsd        = share × (crEligible(A) × reserveA × pxA + crEligible(B) × reserveB × pxB)
  • Read reserves + total_share from the pair (pool {} / dex-equivalent). Do not simulate_swap the LP token.
  • total_share == 0 or lp_balance == 0 → no row / $0 (do not divide by zero).
  • Missing or invalid USD on a CR-eligible leg → omit that LP from assetsUsd, add it to missingPriceSymbols, set incomplete. Never treat missing USD as $0 or $1.
  • Decimal hygiene: use rawToWholeNumber / isValidPositivePrice (decimals.ts). LP decimals from token_info (typically 6). USTR leg is 18. Do not Number(bigint) raw amounts.
  • Reuse LCD rate limits (LCD_CONFIG.minRequestInterval). No poll faster than existing treasury refetch. Dedupe pair queries.

CR eligibility of each LP leg (crEligible)

Leg Display USD CR numerator Why
UST1 (pinned) yes yes Circulating supply already includes pool UST1 (liability). Counting the treasury’s LP claim on that UST1 nets (asset vs liability). Excluding it would understate CR.
USTR (pinned) yes yes Raw USTR is still not a holding; USTR inside an allowlisted LP is collateral (this issue).
cLUNC / cUSTC (pinned wrap addresses only) yes no Wraps are receipts on native uluna / uusd already in the treasury bank. Counting the wrap leg + native = double-count.
Other tokenlist / native (LUNC, USTC, ALPHA, vFDUSD, …) yes yes Reserves sit in the pool, not in the treasury bank. LP share is the claim.
Unknown / unpriced / unpinned “cUSTC” lookalike hide or incomplete no Fake wrap / fake UST1 must not enter CR.

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)

  • CR denominator = UST1 token_info.total_supply only. 1 UST1 = $1 liability.
  • ∞ only on successful zero UST1 supply. Query failure → N/A, never ∞.
  • vFDUSD as an LP quote uses the same session oracle as #10 — never invent $1/vFDUSD. Never DEX-simulate vFDUSD (oracleTokens.ts).
  • Do not scan LCD txs or paginate all_accounts.

Relevant files

File Role
frontend/public/assets/tokenlist.json Add type: "lp" entries (pair + LP token + dex + underlying assets)
frontend/src/hooks/useTreasury.ts Fetch LP balances; attach display/CR USD; do not skip LP rows via symbol skip-list
frontend/src/utils/treasuryRatios.ts (+ .test.ts) Accept precomputed LP crUsd (do not invent a fake DEX “LP price”)
frontend/src/utils/constants.ts Keep skip-list for raw protocol tokens; optional LP helper constants
frontend/src/services/price.ts Pair pool / reserve query helpers (or a new lpNav service — do not reuse simulate-swap for LP mint)
frontend/src/hooks/usePrices.ts Price underlying legs only; do not run CW20 simulate on type: "lp"
frontend/src/components/treasury/TreasuryAssetsCard.tsx LP rows: pair label, full USD, wrap-haircut hint, finder link
frontend/src/components/treasury/RatiosCard.tsx Included symbols must list LP ids; wrap-haircut visible in incomplete/included copy
frontend/src/types/treasury.ts LP fields (pair, displayUsd, crUsd, legs, haircut)
frontend/src/services/contract.ts getTokenBalance / getTokenInfoStrict for LP CW20; optional whitelist cross-check
frontend/src/utils/decimals.ts Reuse for reserve / share math
frontend/src/components/common/TokenIcon.tsx Letter fallback OK; optional UST1-USTR.png etc.
skills/frontend-ust1-ratios/SKILL.md Amend: LP NAV + wrap-leg haircut; raw wraps still excluded
docs/ARCHITECTURE.md, docs/DEPLOYMENT.md, docs/CONTRACTS.md Document LP-as-collateral + whitelist ops

  1. Schema — extend tokenlist (keep one file unless it gets messy):
{
  "symbol": "UST1-USTR",
  "name": "UST1/USTR LP",
  "type": "lp",
  "address": "<lp cw20 or pair if the pair is the LP token>",
  "decimals": 6,
  "gradient": "from-emerald-500 to-amber-500",
  "iconColor": "text-emerald-400",
  "pool": {
    "address": "<pair contract>",
    "dex": "garuda",
    "name": "GARUDA UST1/USTR",
    "assets": [
      { "symbol": "UST1", "address": "terra1f0eq…fy72" },
      { "symbol": "USTR", "address": "terra1vy3k…setxv" }
    ]
  }
}

Pin both LP token and pair if they differ (some DEXes). Native legs use denom (uluna, uusd) not a CW20 address.

  1. 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).

  2. useTreasury

    • Continue tokenlist loop for native / cw20 (unchanged skip-list).
    • Separate loop for type === "lp": getTokenBalance(lp, treasury) + pair reserves + token_info decimals.
    • Push a TreasuryAsset (or LP subtype) with displayUsd / crUsd.
    • Pass crUsd into computeTreasuryRatios as an extra USD addend (or usdOverride on that row) — do not put NAV into usePrices()[symbol] as if it were a spot.
  3. Prices for legs — reuse usePrices / priceService for 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.

  4. UI

    • Grid row label UST1/USTR (or pool name).
    • Primary USD = displayUsd. If haircut: secondary line e.g. CR counts $X (cUSTC leg omitted).
    • Finder on the pair or LP contract.
    • Sort with other assets by display USD; $1 dust rule uses display USD.
    • Ratios includedSymbols includes the LP symbol when crUsd > 0.
  5. Ops / docs

    • List each mainnet LP pin in DEPLOYMENT.
    • Checklist: governance AddCw20 for each LP CW20.
    • Update frontend-ust1-ratios skill: LP NAV in numerator; wrap legs excluded; raw protocol tokens still skipped.
  6. 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

  • Allowlisted UST1/USTR, UST1/cUSTC, and other pinned UST1/xxx, USTR/xxx, cUSTC/xxx, cLUNC/xxx LPs with non-zero treasury balance appear on Treasury Assets with a pair label and full NAV USD.
  • Zero LP balance → no row (same dust rules as other assets when priced).
  • CR numerator increases by crUsd only (wrap legs excluded; UST1/USTR/other eligible legs included).
  • Raw UST1 / USTR / cLUNC / cUSTC still absent from the asset grid and still absent from CR (supply cards unchanged).
  • Unpriced CR-eligible leg → that LP omitted from CR + incomplete + symbol listed; card may still show balance without a fake $0 CR contribution.
  • total_share == 0 / failed pool query → no crash; LP not in CR; incomplete if balance > 0.
  • usePrices does not simulate-swap LP mints; vFDUSD legs still oracle-only.
  • Tokenlist-only CW20s (ALPHA, vFDUSD, …) and native USTC/LUNC still work; #11 ∞ / N/A rules unchanged.
  • Docs + frontend-ust1-ratios skill updated; DEPLOYMENT lists LP pins + AddCw20 ops.

Test plan (all paths)

Discovery / registry

  • type: "lp" with valid pins + treasury balance > 0 → one grid row.
  • Same pair missing from tokenlist → not in grid/CR even if factory pair finds it (helper may warn).
  • Symbol UST1-USTR is not skipped by TREASURY_HOLDING_SKIP_SYMBOLS.
  • Raw USTR / UST1 / cUSTC / cLUNC still skipped.
  • Unknown dex string → fail closed (no CR), no throw spam.
  • Testnet empty addresses: no query storm.

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.
  • LP balance 0 → no row.
  • USTR 18 dp vs UST1 6 dp: whole-token conversion matches rawToWholeNumber.
  • Huge reserves: no Number(bigint) precision lie; unusable whole → incomplete.

Prices / incomplete

  • LUNC/USTC CEX fail → LUNC/USTC legs unpriced → LP incomplete, not $0 CR.
  • vFDUSD leg: oracle miss → incomplete; never $1/vFDUSD.
  • UST1 leg CR value is $1 per whole UST1 even if a DEX UST1 print exists.
  • One of two LPs priced, one not → CR includes only the priced LP; banner lists the other.

UI

  • Haircut LP shows full USD + CR-omitted wrap note.
  • Finder opens pinned pair/LP on columbus-5 (noopener).
  • Sort by display USD with spot tokens; USTC rank exception unchanged.
  • Loading skeleton; no flicker of CR ∞ then a finite % because an LP query lagged (keep #11 supply rules).

Integration

  • Fixture: 1e6 UST1 outstanding, $2e6 spot assets, plus crUsd = 100_000 LP → CR 210%, A/L 2.10x.
  • Removing the LP pin returns CR to 200%.
  • Treasury refetch updates LP balance + reserves; LCD spacing holds.
  • npm test covers lpNav + extended computeTreasuryRatios.

Test plan (attack, hack, abuse)

Vector Risk Test / guard
Fake UST1/SCAM LP sent to treasury Worthless LP + pumped SCAM inflates CR Unlisted pair never enters CR; only tokenlist pins
Factory pagination / “all pairs” crawl LCD ban + attacker-created pair flood No pairs dump; factory used only as optional pin checker
Symbol skip collision UST1-USTR skipped like UST1 Skip is exact symbol set + pinned token addresses, not includes('UST1')
Wrap double-count cUSTC LP leg + treasury uusd both in CR crUsd excludes pinned cLUNC/cUSTC legs; fixture above
Fake wrap address Attacker CW20 named cUSTC counted as wrap (wrong haircut) or as collateral Haircut only pinned wrap addresses; unknown CW20 in an unlisted pair is not in CR
UST1 as raw asset Liability counted twice Raw UST1 still not in holdings loop
Simulate-swap LP mint Thin-pool spot ≠ NAV; sandwich / donation games Grep: no getTokenPriceUsd(lpAddress) for type: "lp"
Donation attack on reserves Inflate NAV before screenshot Allowlist + incomplete/depth note acceptable; optional: flag pool USD below a documented dust floor (e.g. display hide < $1, still don’t treat as priced CR if reserves fail sanity). Do not silently cap CR without labeling
Reserve / share desync Stale total_share vs balance → >100% of pool If lp_balance > total_share, fail closed (incomplete, not in CR)
Unpriced = $0 CEX/LCD outage dumps CR to fake RED Incomplete; partial sum labeled
Unpriced = $1 Invented UST1/vFDUSD/USTR vFDUSD never $1; USTR never $1; UST1 leg $1 is the documented liability unit only
Decimal attack 18 vs 6 mix → 10^12 CR error Unit tests USTR-18 + UST1-6
Number(bigint) Supplies / reserves > 2^53 lie rawToWholeNumber only
Query-string pair address Phishing / wrong contract Pins only; isTerraContractAddress
vFDUSD DEX path Oracle bypass via LP quote simulate vFDUSD USD from #10 oracle; do not simulate vFDUSD
Whitelist bypass story UI counts LP not AddCw20’d Docs/ops checklist; optional warning if tokenlist LP ∉ Cw20Whitelist (do not silently drop a pin solely because whitelist lags — label it)
Finder spoof Wrong explorer URL Pin finder + address; rel="noopener noreferrer"
Double-count same LP twice tokenlist duplicate + AllBalances merge One row per LP address
Re-entrancy / LCD MITM garbage Huge return_amount / reserve strings Sanity: non-finite / negative / over-share → incomplete, not in CR

Verification criteria

  1. Mainnet treasury page: every pinned protocol LP with balance > 0 is a visible asset row; USD equals share × Σ (whole_reserve × usd) within rounding.
  2. CR on screen equals (spotAssetsUsd + Σ crUsd) / whole_UST1 × 100 with wrap legs out of crUsd. Independent calculator on live pool {} + balances must match.
  3. Grep clean: no LP mint passed to getTokenPriceUsd; skip-list still exact USTR|UST1|CLUNC|CUSTC; no wrap address in the CR asset loop except as an LP display leg.
  4. Airplane-mode / LCD kill after first success: last-good or incomplete/N/A — not ∞, not a sudden drop of LP USD to $0 presented as full CR.
  5. skills/frontend-ust1-ratios/SKILL.md + docs/ARCHITECTURE.md / DEPLOYMENT.md describe LP NAV, wrap haircut, and AddCw20 ops.
  6. cd frontend && npm test — existing #11 fixtures still pass; new LP fixtures pass.
## 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): | Pair pattern | Examples | |--------------|----------| | `UST1/USTR` | protocol/protocol LP | | `UST1/cUSTC` | unstablecoin / wrap LP | | `UST1/xxx` | any other UST1 pair (LUNC, USTC, ALPHA, vFDUSD, …) | | `USTR/xxx` | any USTR pair | | `cUSTC/xxx` | any cUSTC pair | | `cLUNC/xxx` | any cLUNC pair | `xxx` = any other native or CW20 the pair actually holds. DEXes already used by this frontend: **Garuda**, **Terraswap**, **Terraport**. Depends on [#11](https://gitlab.com/PlasticDigits2/ustr-cmm/-/issues/11) CR math and [#10](https://gitlab.com/PlasticDigits2/ustr-cmm/-/issues/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 | Component | Path | Behavior today | |-----------|------|----------------| | Holdings fetch | `frontend/src/hooks/useTreasury.ts` | Iterates **`tokenlist.json` only**. Skips `TREASURY_HOLDING_SKIP_SYMBOLS` (`USTR`, `UST1`, `CLUNC`, `CUSTC`) and the pinned UST1 / USTR / cLUNC / cUSTC addresses. No `type: "lp"`. Does **not** call `getTreasuryBalances()` / `AllBalances`. | | Skip list | `frontend/src/utils/constants.ts` | `TREASURY_HOLDING_SKIP_SYMBOLS` — raw protocol tokens are **never** CR holdings (#11 wrap / liability invariant). | | Token registry | `frontend/public/assets/tokenlist.json` | Native USTC/LUNC + CW20 ALPHA, USTRIX, SpaceUSD, vFDUSD, USTR. Optional `pool` is **spot-price** config for the *underlying* CW20, not an LP share. **Zero LP entries.** | | Asset grid | `frontend/src/components/treasury/TreasuryAssetsCard.tsx` | Shows tokenlist holdings with USD (`balance * prices[symbol]`). Hides `$0` dust when priced (`>= $1`); unpriced non-zero still shown. No pair/LP row type. | | Prices | `frontend/src/hooks/usePrices.ts`, `frontend/src/services/price.ts` | CEX LUNC/USTC; DEX **simulate-swap** of 1e6 CW20 units (Garuda `simulate_swap` / Terraswap `simulation`); 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). | | CR math | `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**. | | Ratios UI | `frontend/src/components/treasury/RatiosCard.tsx` | Incomplete banner lists `includedSymbols` / `missingPriceSymbols`. | | Treasury page | `frontend/src/pages/TreasuryPage.tsx` | Disclaimer: cLUNC/cUSTC supplies are informational — **not extra collateral**. | | On-chain balances | `contractService.getTreasuryBalances()` | Implemented but **unused by the Treasury page**. Contract `AllBalances` = native bank + **governance `CW20_WHITELIST` only**. | | Whitelist | treasury `AddCw20` / `Cw20Whitelist` | Docs (`CONTRACTS.md`, `PROPOSAL.md`): only whitelisted CW20s count toward CR. Frontend currently **ignores** the whitelist and trusts tokenlist. | | DEX routers | `DEX_ROUTERS` in `constants.ts` | Garuda factory `terra1ypwj6…nxkeh7` + router; Terraswap router `terra1g3zc8…7872`. No factory **pairs pagination**, no LP `pool {}` / `total_share` reader. | | Icons | `TokenIcon` | `/assets/tokens/{SYMBOL}.png` — LP symbols like `UST1-USTR` will letter-fallback unless assets are added. | **Mainnet pins (do not invent):** | Name | Address | Decimals | Role | |------|---------|----------|------| | Treasury | `terra16j5u6ey7a84g40sr3gd94nzg5w5fm45046k9s2347qhfpwm5fr6sem3lr2` | — | Holder of LP CW20s | | UST1 | `terra1f0eqgy9w7e5e7up97vjudqwx38tesf8ylx75x2lv3nwm0clry0pqmgfy72` | 6 | Liability (CR denominator). Raw balance still **not** a holding. | | USTR | `terra1vy3kc0swag2rhn7jz6n72jp0l2ns0p6r6ez5grxq5uhj2rvs97fqfsetxv` | 18 | Utility token. Raw balance still **not** a holding. | | cLUNC | `terra1437qslye72t7qmmahn4t5chz50r8a62g45phwkquwpyu2l62u6ksqssgdg` | 6 | Wrap receipt. Raw balance still **not** a holding. | | cUSTC | `terra1nap4dxh9tv35v0ynd9m4k6zt6c0dq6weszc4j5m564kjls56hu7qcr56ch` | 6 | Wrap receipt. Raw balance still **not** a holding. | | vFDUSD | `terra1mnl9azefrqpmu888ar2u6zrcwr80hxlt3avf4300r576cw5ar7esvxsvj3` | 6 | Bridged collateral (already a holding). | | Finder | `https://finder.terraclassic.community/columbus-5` | — | Pair / LP contract links | Live CR is **frontend-only** today. A future on-chain collateralization contract (ARCHITECTURE Phase 2) is **out of scope**. --- ## Why this is needed 1. Treasury **will / does** hold DEX LP for protocol markets (`UST1/USTR`, `UST1/cUSTC`, and other `UST1|USTR|cUSTC|cLUNC` pairs). 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. 2. Users judging backing only see spot tokens (USTC, LUNC, ALPHA, …). Protocol LP can be a **material** slice of reserves. Omitting it **understates** CR and hides where liquidity lives. 3. Spot `simulate_swap` is 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. 4. Without an **allowlist + wrap-leg haircut**, auto-counting every factory pair (or the wrap side of `cUSTC/xxx`) would either miss pairs or **double-count** native wrap backing already in the treasury bank. --- ## Constraints / guardrails ### Scope - **Frontend + tokenlist + docs + tests.** No new CosmWasm execute path. No change to wrap-mapper, ustc-swap, or InstantWithdraw. - Ops **should** `AddCw20` each allowlisted LP on treasury so `AllBalances` / a future on-chain CR stay consistent. That is a **governance checklist**, not a contract feature in this issue. - Do **not** build a full DEX pair browser or paginate every Garuda/Terraswap pair (LCD quota + fake-pair flood). ### What is / is not a holding - **Raw** UST1 / USTR / cLUNC / cUSTC remain in `TREASURY_HOLDING_SKIP_SYMBOLS`. Do not put those CW20s on the holdings loop (#11). - **LP shares** whose *pair* involves those tokens **are** holdings. Identify them by **pair/LP contract address + `type: "lp"`**, never by symbol substring alone (`USTR` must not skip `UST1-USTR-LP`). - Only pairs where **at least one** pinned asset is UST1, USTR, cUSTC, or cLUNC. Random `ALPHA/LUNC` LP is **out of scope** unless it is also one of those (it isn’t). ### Allowlist (required for CR) - A pair enters the **asset grid and CR only if it is explicitly listed** (tokenlist `type: "lp"` or an equivalent pinned `lplist`). - Factory `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. - **Never** auto-include an unlisted factory pair in CR (attacker deploys `UST1/SCAM`, donates dust UST1, pumps SCAM, sends LP to treasury). - Addresses must be pinned `terra1…` strings — never query-string / user input (`addresses.ts`). ### Valuation (NAV, not spot) ``` share = treasury_lp_balance / pool.total_share displayUsd = share × (reserveA × pxA + reserveB × pxB) crUsd = share × (crEligible(A) × reserveA × pxA + crEligible(B) × reserveB × pxB) ``` - Read reserves + `total_share` from the pair (`pool {}` / dex-equivalent). Do **not** `simulate_swap` the LP token. - `total_share == 0` or `lp_balance == 0` → no row / `$0` (do not divide by zero). - Missing or invalid USD on a **CR-eligible** leg → omit that LP from `assetsUsd`, add it to `missingPriceSymbols`, set `incomplete`. **Never** treat missing USD as `$0` or `$1`. - Decimal hygiene: use `rawToWholeNumber` / `isValidPositivePrice` (`decimals.ts`). LP decimals from `token_info` (typically 6). USTR leg is 18. Do not `Number(bigint)` raw amounts. - Reuse LCD rate limits (`LCD_CONFIG.minRequestInterval`). No poll faster than existing treasury refetch. Dedupe pair queries. ### CR eligibility of each LP leg (`crEligible`) | Leg | Display USD | CR numerator | Why | |-----|-------------|--------------|-----| | UST1 (pinned) | yes | **yes** | Circulating supply already includes pool UST1 (liability). Counting the treasury’s LP claim on that UST1 **nets** (asset vs liability). Excluding it would understate CR. | | USTR (pinned) | yes | **yes** | Raw USTR is still not a holding; **USTR inside an allowlisted LP is collateral** (this issue). | | cLUNC / cUSTC (pinned wrap addresses **only**) | yes | **no** | Wraps are receipts on native `uluna` / `uusd` **already in the treasury bank**. Counting the wrap leg + native = **double-count**. | | Other tokenlist / native (LUNC, USTC, ALPHA, vFDUSD, …) | yes | **yes** | Reserves sit in the **pool**, not in the treasury bank. LP share is the claim. | | Unknown / unpriced / unpinned “cUSTC” lookalike | hide or incomplete | **no** | Fake wrap / fake UST1 must not enter CR. | 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) - CR denominator = UST1 `token_info.total_supply` only. 1 UST1 = $1 liability. - `∞` only on successful zero UST1 supply. Query failure → `N/A`, never `∞`. - vFDUSD as an LP **quote** uses the same session oracle as #10 — never invent `$1/vFDUSD`. Never DEX-simulate vFDUSD (`oracleTokens.ts`). - Do not scan LCD txs or paginate `all_accounts`. --- ## Relevant files | File | Role | |------|------| | `frontend/public/assets/tokenlist.json` | Add `type: "lp"` entries (pair + LP token + dex + underlying assets) | | `frontend/src/hooks/useTreasury.ts` | Fetch LP balances; attach display/CR USD; do not skip LP rows via symbol skip-list | | `frontend/src/utils/treasuryRatios.ts` (+ `.test.ts`) | Accept precomputed LP `crUsd` (do not invent a fake DEX “LP price”) | | `frontend/src/utils/constants.ts` | Keep skip-list for **raw** protocol tokens; optional LP helper constants | | `frontend/src/services/price.ts` | Pair `pool` / reserve query helpers (or a new `lpNav` service — do not reuse simulate-swap for LP mint) | | `frontend/src/hooks/usePrices.ts` | Price **underlying** legs only; do not run CW20 simulate on `type: "lp"` | | `frontend/src/components/treasury/TreasuryAssetsCard.tsx` | LP rows: pair label, full USD, wrap-haircut hint, finder link | | `frontend/src/components/treasury/RatiosCard.tsx` | Included symbols must list LP ids; wrap-haircut visible in incomplete/included copy | | `frontend/src/types/treasury.ts` | LP fields (`pair`, `displayUsd`, `crUsd`, `legs`, `haircut`) | | `frontend/src/services/contract.ts` | `getTokenBalance` / `getTokenInfoStrict` for LP CW20; optional whitelist cross-check | | `frontend/src/utils/decimals.ts` | Reuse for reserve / share math | | `frontend/src/components/common/TokenIcon.tsx` | Letter fallback OK; optional `UST1-USTR.png` etc. | | `skills/frontend-ust1-ratios/SKILL.md` | Amend: LP NAV + wrap-leg haircut; raw wraps still excluded | | `docs/ARCHITECTURE.md`, `docs/DEPLOYMENT.md`, `docs/CONTRACTS.md` | Document LP-as-collateral + whitelist ops | --- ## Recommended direction 1. **Schema** — extend tokenlist (keep one file unless it gets messy): ```json { "symbol": "UST1-USTR", "name": "UST1/USTR LP", "type": "lp", "address": "<lp cw20 or pair if the pair is the LP token>", "decimals": 6, "gradient": "from-emerald-500 to-amber-500", "iconColor": "text-emerald-400", "pool": { "address": "<pair contract>", "dex": "garuda", "name": "GARUDA UST1/USTR", "assets": [ { "symbol": "UST1", "address": "terra1f0eq…fy72" }, { "symbol": "USTR", "address": "terra1vy3k…setxv" } ] } } ``` Pin **both** LP token and pair if they differ (some DEXes). Native legs use `denom` (`uluna`, `uusd`) not a CW20 address. 2. **`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). 3. **`useTreasury`** - Continue tokenlist loop for `native` / `cw20` (unchanged skip-list). - Separate loop for `type === "lp"`: `getTokenBalance(lp, treasury)` + pair reserves + `token_info` decimals. - Push a `TreasuryAsset` (or LP subtype) with `displayUsd` / `crUsd`. - Pass `crUsd` into `computeTreasuryRatios` as an extra USD addend (or `usdOverride` on that row) — **do not** put NAV into `usePrices()[symbol]` as if it were a spot. 4. **Prices for legs** — reuse `usePrices` / `priceService` for 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. 5. **UI** - Grid row label `UST1/USTR` (or pool `name`). - Primary USD = `displayUsd`. If haircut: secondary line e.g. `CR counts $X (cUSTC leg omitted)`. - Finder on the pair or LP contract. - Sort with other assets by display USD; `$1` dust rule uses display USD. - Ratios `includedSymbols` includes the LP symbol when `crUsd > 0`. 6. **Ops / docs** - List each mainnet LP pin in DEPLOYMENT. - Checklist: governance `AddCw20` for each LP CW20. - Update `frontend-ust1-ratios` skill: LP NAV in numerator; wrap legs excluded; raw protocol tokens still skipped. 7. **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 - [ ] Allowlisted `UST1/USTR`, `UST1/cUSTC`, and other pinned `UST1/xxx`, `USTR/xxx`, `cUSTC/xxx`, `cLUNC/xxx` LPs with **non-zero** treasury balance appear on Treasury Assets with a pair label and **full NAV USD**. - [ ] Zero LP balance → no row (same dust rules as other assets when priced). - [ ] CR numerator increases by **`crUsd` only** (wrap legs excluded; UST1/USTR/other eligible legs included). - [ ] Raw UST1 / USTR / cLUNC / cUSTC still **absent** from the asset grid and still **absent** from CR (supply cards unchanged). - [ ] Unpriced CR-eligible leg → that LP omitted from CR + `incomplete` + symbol listed; card may still show balance without a fake `$0` CR contribution. - [ ] `total_share == 0` / failed pool query → no crash; LP not in CR; incomplete if balance `> 0`. - [ ] `usePrices` does not simulate-swap LP mints; vFDUSD legs still oracle-only. - [ ] Tokenlist-only CW20s (ALPHA, vFDUSD, …) and native USTC/LUNC still work; #11 `∞` / `N/A` rules unchanged. - [ ] Docs + `frontend-ust1-ratios` skill updated; DEPLOYMENT lists LP pins + `AddCw20` ops. --- ## Test plan (all paths) **Discovery / registry** - [ ] `type: "lp"` with valid pins + treasury balance `> 0` → one grid row. - [ ] Same pair missing from tokenlist → **not** in grid/CR even if factory `pair` finds it (helper may warn). - [ ] Symbol `UST1-USTR` is **not** skipped by `TREASURY_HOLDING_SKIP_SYMBOLS`. - [ ] Raw `USTR` / `UST1` / `cUSTC` / `cLUNC` still skipped. - [ ] Unknown dex string → fail closed (no CR), no throw spam. - [ ] Testnet empty addresses: no query storm. **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. - [ ] LP balance `0` → no row. - [ ] USTR 18 dp vs UST1 6 dp: whole-token conversion matches `rawToWholeNumber`. - [ ] Huge reserves: no `Number(bigint)` precision lie; unusable whole → incomplete. **Prices / incomplete** - [ ] LUNC/USTC CEX fail → LUNC/USTC legs unpriced → LP incomplete, not `$0` CR. - [ ] vFDUSD leg: oracle miss → incomplete; never `$1/vFDUSD`. - [ ] UST1 leg CR value is `$1` per whole UST1 even if a DEX UST1 print exists. - [ ] One of two LPs priced, one not → CR includes only the priced LP; banner lists the other. **UI** - [ ] Haircut LP shows full USD + CR-omitted wrap note. - [ ] Finder opens pinned pair/LP on columbus-5 (`noopener`). - [ ] Sort by display USD with spot tokens; USTC rank exception unchanged. - [ ] Loading skeleton; no flicker of CR `∞` then a finite % because an LP query lagged (keep #11 supply rules). **Integration** - [ ] Fixture: 1e6 UST1 outstanding, $2e6 spot assets, plus `crUsd = 100_000` LP → CR `210%`, A/L `2.10x`. - [ ] Removing the LP pin returns CR to `200%`. - [ ] Treasury refetch updates LP balance + reserves; LCD spacing holds. - [ ] `npm test` covers `lpNav` + extended `computeTreasuryRatios`. --- ## Test plan (attack, hack, abuse) | Vector | Risk | Test / guard | |--------|------|----------------| | **Fake `UST1/SCAM` LP sent to treasury** | Worthless LP + pumped SCAM inflates CR | Unlisted pair never enters CR; only tokenlist pins | | **Factory pagination / “all pairs” crawl** | LCD ban + attacker-created pair flood | No `pairs` dump; factory used only as optional pin checker | | **Symbol skip collision** | `UST1-USTR` skipped like `UST1` | Skip is exact symbol set + pinned **token** addresses, not `includes('UST1')` | | **Wrap double-count** | `cUSTC` LP leg + treasury `uusd` both in CR | `crUsd` excludes pinned cLUNC/cUSTC legs; fixture above | | **Fake wrap address** | Attacker CW20 named `cUSTC` counted as wrap (wrong haircut) **or** as collateral | Haircut **only** pinned wrap addresses; unknown CW20 in an unlisted pair is not in CR | | **UST1 as raw asset** | Liability counted twice | Raw UST1 still not in holdings loop | | **Simulate-swap LP mint** | Thin-pool spot ≠ NAV; sandwich / donation games | Grep: no `getTokenPriceUsd(lpAddress)` for `type: "lp"` | | **Donation attack on reserves** | Inflate NAV before screenshot | Allowlist + incomplete/depth note acceptable; optional: flag pool USD below a documented dust floor (e.g. display hide `< $1`, still don’t treat as priced CR if reserves fail sanity). Do **not** silently cap CR without labeling | | **Reserve / share desync** | Stale `total_share` vs balance → >100% of pool | If `lp_balance > total_share`, fail closed (incomplete, not in CR) | | **Unpriced = $0** | CEX/LCD outage dumps CR to fake RED | Incomplete; partial sum labeled | | **Unpriced = $1** | Invented UST1/vFDUSD/USTR | vFDUSD never $1; USTR never $1; UST1 **leg** $1 is the documented liability unit only | | **Decimal attack** | 18 vs 6 mix → 10^12 CR error | Unit tests USTR-18 + UST1-6 | | **`Number(bigint)`** | Supplies / reserves > 2^53 lie | `rawToWholeNumber` only | | **Query-string pair address** | Phishing / wrong contract | Pins only; `isTerraContractAddress` | | **vFDUSD DEX path** | Oracle bypass via LP quote simulate | vFDUSD USD from #10 oracle; do not simulate vFDUSD | | **Whitelist bypass story** | UI counts LP not `AddCw20`’d | Docs/ops checklist; optional warning if tokenlist LP ∉ `Cw20Whitelist` (do not silently drop a pin solely because whitelist lags — label it) | | **Finder spoof** | Wrong explorer URL | Pin finder + address; `rel="noopener noreferrer"` | | **Double-count same LP twice** | tokenlist duplicate + AllBalances merge | One row per LP address | | **Re-entrancy / LCD MITM garbage** | Huge `return_amount` / reserve strings | Sanity: non-finite / negative / over-share → incomplete, not in CR | --- ## Verification criteria 1. Mainnet treasury page: every **pinned** protocol LP with balance `> 0` is a visible asset row; USD equals `share × Σ (whole_reserve × usd)` within rounding. 2. CR on screen equals `(spotAssetsUsd + Σ crUsd) / whole_UST1 × 100` with wrap legs **out** of `crUsd`. Independent calculator on live `pool {}` + balances must match. 3. Grep clean: no LP mint passed to `getTokenPriceUsd`; skip-list still exact `USTR|UST1|CLUNC|CUSTC`; no wrap address in the CR asset loop except as an LP **display** leg. 4. Airplane-mode / LCD kill after first success: last-good or incomplete/`N/A` — **not** `∞`, **not** a sudden drop of LP USD to `$0` presented as full CR. 5. `skills/frontend-ust1-ratios/SKILL.md` + `docs/ARCHITECTURE.md` / `DEPLOYMENT.md` describe LP NAV, wrap haircut, and `AddCw20` ops. 6. `cd frontend && npm test` — existing #11 fixtures still pass; new LP fixtures pass.
PlasticDigits commented 2026-08-15 12:49:57 +00:00 (Migrated from gitlab.com)

mentioned in merge request !33

mentioned in merge request !33
PlasticDigits commented 2026-08-15 12:50:26 +00:00 (Migrated from gitlab.com)

Implemented in !33 (c38ae70).

Acceptance criteria

  • Allowlisted 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)
  • Zero LP balance → no row
  • CR uses crUsd only (wrap legs excluded; UST1/USTR/other included)
  • Raw UST1 / USTR / cLUNC / cUSTC still absent from the asset grid / CR
  • Unpriced CR-eligible leg → omit from CR + incomplete
  • total_share == 0 / failed pool → no crash; not in CR; incomplete if balance > 0
  • usePrices does not simulate-swap LP mints; vFDUSD still oracle-only
  • Spot CW20s + #11 ∞ / N/A unchanged
  • Docs + frontend-ust1-ratios + new frontend-treasury-lp-nav skill; DEPLOYMENT AddCw20 ops

Not in this MR (follow-ups)

  • Live pair pins: Garuda pair lookups for UST1/USTR/cUSTC/cLUNC × tokenlist quotes returned none. Ops add type: "lp" + AddCw20 when pairs exist.
  • No in-app factory crawl (forbidden as CR input).
  • No Cw20Whitelist mismatch warning in the UI.
  • No on-chain CR contract (still frontend-only).
  • No Playwright suite in this frontend.

cd frontend && npm test → 49 passed (twice). tsc + eslint clean.

Implemented in !33 (`c38ae70`). ## Acceptance criteria - [x] Allowlisted `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**) - [x] Zero LP balance → no row - [x] CR uses `crUsd` only (wrap legs excluded; UST1/USTR/other included) - [x] Raw UST1 / USTR / cLUNC / cUSTC still absent from the asset grid / CR - [x] Unpriced CR-eligible leg → omit from CR + incomplete - [x] `total_share == 0` / failed pool → no crash; not in CR; incomplete if balance > 0 - [x] `usePrices` does not simulate-swap LP mints; vFDUSD still oracle-only - [x] Spot CW20s + #11 `∞` / `N/A` unchanged - [x] Docs + `frontend-ust1-ratios` + new `frontend-treasury-lp-nav` skill; DEPLOYMENT `AddCw20` ops ## Not in this MR (follow-ups) - Live pair pins: Garuda `pair` lookups for UST1/USTR/cUSTC/cLUNC × tokenlist quotes returned none. Ops add `type: "lp"` + `AddCw20` when pairs exist. - No in-app factory crawl (forbidden as CR input). - No `Cw20Whitelist` mismatch warning in the UI. - No on-chain CR contract (still frontend-only). - No Playwright suite in this frontend. `cd frontend && npm test` → 49 passed (twice). `tsc` + eslint clean.
PlasticDigits commented 2026-08-15 12:53:45 +00:00 (Migrated from gitlab.com)

Merge review of !33 (c38ae70) vs #14 AC

Local sanity: cd frontend && npm test → 49/49; type-check and lint clean. GitLab reports mergeable, no conflicts. Merging now (no automerge, not waiting on CI).

Meets AC

  • Reserve NAV + wrap-leg haircut; CR uses crUsd only
  • Raw UST1/USTR/cLUNC/cUSTC still excluded; UST1-USTR LP symbols not caught by skip list
  • Fail-closed: unknown dex, total_share == 0, over-share, unpriced CR-eligible legs → crUsd: null + incomplete
  • LP mints excluded from usePrices simulate-swap; vFDUSD oracle path unchanged
  • #11 ∞/N/A rules preserved; 210% CR fixture passes
  • Docs/skills updated (frontend-treasury-lp-nav, DEPLOYMENT ops checklist)

Problems / follow-ups (non-blocking)

  1. No live LP pins in tokenlist.json — intentional per DEPLOYMENT (no Garuda protocol pairs at implementation). Ops must add type:"lp" rows + governance AddCw20 before mainnet LP rows render. Mainnet E2E of AC1 cannot be verified until then.
  2. treasuryLp.ts has no mocked integration tests — pure math is covered; LCD-mocked fetch tests for leg matching / liquidity_token mismatch would reduce regression risk.
  3. Optional factory discovery helper (warn when a live pair is missing from tokenlist) is not implemented — acceptable per issue scope.
  4. resolveLpLegUsd maps wrap → native by symbol, not pinned address — a typo in pool.assets[].symbol could mis-price display USD. Low risk (ops-controlled pins).
  5. No Cw20Whitelist mismatch warning in the UI (optional in the issue).

Recommendation: merge with notes; smoke-test Treasury page after the first LP pin lands.

## Merge review of !33 (`c38ae70`) vs #14 AC Local sanity: `cd frontend && npm test` → 49/49; `type-check` and `lint` clean. GitLab reports mergeable, no conflicts. Merging now (no automerge, not waiting on CI). ### Meets AC - Reserve NAV + wrap-leg haircut; CR uses `crUsd` only - Raw UST1/USTR/cLUNC/cUSTC still excluded; `UST1-USTR` LP symbols not caught by skip list - Fail-closed: unknown dex, `total_share == 0`, over-share, unpriced CR-eligible legs → `crUsd: null` + incomplete - LP mints excluded from `usePrices` simulate-swap; vFDUSD oracle path unchanged - #11 ∞/N/A rules preserved; 210% CR fixture passes - Docs/skills updated (`frontend-treasury-lp-nav`, DEPLOYMENT ops checklist) ### Problems / follow-ups (non-blocking) 1. **No live LP pins in `tokenlist.json`** — intentional per DEPLOYMENT (no Garuda protocol pairs at implementation). Ops must add `type:"lp"` rows + governance `AddCw20` before mainnet LP rows render. Mainnet E2E of AC1 cannot be verified until then. 2. **`treasuryLp.ts` has no mocked integration tests** — pure math is covered; LCD-mocked fetch tests for leg matching / `liquidity_token` mismatch would reduce regression risk. 3. **Optional factory discovery helper** (warn when a live pair is missing from tokenlist) is not implemented — acceptable per issue scope. 4. **`resolveLpLegUsd` maps wrap → native by symbol, not pinned address** — a typo in `pool.assets[].symbol` could mis-price display USD. Low risk (ops-controlled pins). 5. No `Cw20Whitelist` mismatch warning in the UI (optional in the issue). **Recommendation:** merge with notes; smoke-test Treasury page after the first LP pin lands.
PlasticDigits commented 2026-08-15 12:53:50 +00:00 (Migrated from gitlab.com)

mentioned in commit 4482924766

mentioned in commit 44829247668e6f46cf94b0dd182c5bb264c4e7ea
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-15 12:53:51 +00:00
PlasticDigits commented 2026-08-17 01:06:21 +00:00 (Migrated from gitlab.com)

mentioned in issue #16

mentioned in issue #16
PlasticDigits commented 2026-08-17 01:06:36 +00:00 (Migrated from gitlab.com)

marked as related to #16

marked as related to #16
PlasticDigits commented 2026-08-24 00:18:42 +00:00 (Migrated from gitlab.com)

mentioned in merge request !34

mentioned in merge request !34
PlasticDigits commented 2026-08-24 00:19:07 +00:00 (Migrated from gitlab.com)

mentioned in merge request !35

mentioned in merge request !35
PlasticDigits commented 2026-08-24 06:12:59 +00:00 (Migrated from gitlab.com)

mentioned in issue #19

mentioned in issue #19
PlasticDigits commented 2026-08-24 06:15:14 +00:00 (Migrated from gitlab.com)

marked as related to #19

marked as related to #19
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/ustr-cmm#14
No description provided.