feat(treasury): A–E asset quality tiers with 5-year CR credits (spot + LP) #19
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#19
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
Introduce a documented A–E asset-quality classification for every treasury holding (spot tokens and allowlisted LP legs), plus a % CR credit so lower-quality assets do not enter the collateralization-ratio numerator at full mark-to-market.
This is not the existing ECONOMICS operation tiers (RED / YELLOW / GREEN / BLUE at 95% / 110% / 190%). Those describe what the system does at a given CR. This issue describes how much of each asset’s USD is allowed to count when computing that CR.
In scope (one bundled issue — taxonomy + CR math + UI disclosure + quarterly research process):
Depends on #11 CR math, #10 vFDUSD USD, and #14 LP reserve NAV. This issue owns quality credits — not a new on-chain CR contract, not CoinGecko-in-the-browser, not the unimplemented USTC min(7d/24h/1h) TWAP in
plans/PROPOSAL.md(complementary price conservatism; out of scope).Current codebase
frontend/src/utils/treasuryRatios.ts(+.test.ts)CR% = (Σ priced USD / whole UST1) × 100. Spot path:whole × price. LP path: precomputedcrUsd(NAV, wrap legs already dropped). Every priced dollar counts at 100%. Missing USD → omit +incomplete.frontend/src/hooks/useTreasury.tsnative/cw20(skip raw UST1 / USTR / cLUNC / cUSTC) + allowlistedtype: "lp"viacomputeLpNav. No quality factor.frontend/src/utils/lpNav.ts,lpEligibility.tsdisplayUsd= full NAV;crUsdexcludes pinned wrap legs only (double-count guard). USTR / other / UST1 legs enter CR at full USD.frontend/src/components/treasury/TreasuryAssetsCard.tsxfrontend/src/components/treasury/RatiosCard.tsxCR_TIERS95 / 110 / 190). Incomplete banner lists included / missing-price symbols. No quality-haircut copy.frontend/public/assets/tokenlist.jsonUST1-USTR,UST1-cUSTC,UST1-SpaceUSD. Notier/crCreditBps.frontend/src/hooks/usePrices.ts,frontend/src/services/price.tsdocs/ECONOMICS.mdplans/PROPOSAL.mdCW20_WHITELIST/AllBalancescontracts/scripts/is deploy / migrate only. No CoinGecko / CryptoRank / CMC / holder pack.Mainnet pins (do not invent):
terra1mnl9…svj3uusdulunaterra1x6e6…ysuxzterra1r3ea…trcp5terra1cvd5…v79zlterra1vy3k…setxvcrUsdtoday (#14)terra1f0eq…fy72crUsdterra16j5u…m3lr2Live CR is frontend-only. A future on-chain collateralization contract (ARCHITECTURE Phase 2) is out of scope, but the committed credit table must be shaped so governance can later ingest the same numbers.
Why this is needed
CR is currently quality-blind. $1 of vFDUSD (A-grade, fiat-backed, Venus-normalized) and $1 of a thin TerraClassic CW20 both move the ratio the same amount. ECONOMICS’ 5-year lockout thesis only works if the collateral that remains is still realizable after a long stress window. Treating USTRIX / SpaceUSD / ALPHA as equivalent to vFDUSD overstates solvency.
Operation tiers assume quality collateral. RED (
< 95%) is justified by “BTC, ETH, quality stables recover with M2.” If a large slice of the numerator is a microcap that can go to zero and stay there, the displayed GREEN/BLUE band is a false safety signal.Diversification without grading is a trap. The target book (ECONOMICS: USTC → other stables → blue-chip → RWA) will add many names. Without a written ladder and a haircut, every new tokenlist pin silently inflates CR at 100% MTM.
LP NAV made this urgent. #14 correctly counts reserve claims. An allowlisted
UST1-SpaceUSDLP can now put SpaceUSD into CR at full NAV. Wrap legs are haircut for double-count, not for quality. Those are different risks.Short-term vol is the wrong haircut. A 24h drawdown is already in the mark. The missing control is 5-year impairment (death, hyperinflation, broken tokenomics, vanished liquidity, concentrated unlocks, no product/market fit). Quarterly human review + research scripts — not a live volatility scaler.
Classification without process will rot. A static blog table gets stale. Scripts must produce an evidence pack (CoinGecko, CryptoRank, CMC, on-chain holders, CEX/DEX depth, primary sources). Humans assign the grade. The frontend must never auto-promote an asset because an API printed a volume spike.
Constraints / guardrails
Scope
Naming — do not collide with operation tiers
docs/ECONOMICS.md,CR_TIERSDo not call quality grades “tier 1–5” in UI or docs. Letters avoid “is this RED or tier-2?” Letters also match the user’s A-grade vs B-grade examples (vFDUSD = A; USTC / LUNC = B).
What quality is (and is not)
Quality credit answers: over a 5-year hold, what fraction of today’s USD mark is still a conservative claim on realizable collateral?
It is not:
incomplete, never invent a credit)AddCw20(unlisted still never enters CR)crCreditBpsRecommended ladder (starting SSoT — first review may retune percents, not the letters)
99% is an allowed documented override for an asset that is almost A (one residual 5y factor: bridge, issuer, listing count) — not a sixth letter. Put it in the file as
crCreditBps: 9900with a written rationale. Do not invent other ad-hoc percents in application code; the file is the only place credits live.First quarterly pack must confirm or replace the D-tier defaults. Shipping D/25% for those three CW20s is fail-conservative, not a claim that the review already happened.
Scoring dimensions (research pack must cover each)
Use these for the manual grade. Scripts collect evidence; they do not output a grade.
Short-term price path is context, not a scoring axis.
How credit composes with existing CR rules
Hard rules:
incompleteif the balance is material (display ≥ $1 or non-zero and unpriced rules already show it). Never treat unrated as A/100%.whole × price; LPdisplayUsd). CR uses credited USD. UI must show both when they differ.ustcPerUst1remains physical USTC / UST1 (not quality-adjusted). Quality affectscollateralizationandassetsToLiabilitiesonly.0…10000). No floats in the committed file. Apply with integer math then scale — do not0.8 *in a way that drifts.vFDUSDmust not inherit A.Quarterly process (manual; scripts are input)
research-pack/JSON + markdown (gitignored raw dumps OK; committed summary + sources required).tier+crCreditBps+ rationale +asOf+nextReview.nextReviewin the past) → UI warning +incompleteor documented grace (max 30 days). Do not silently keep showing a 14-month-old A as if it were current.Scripts may be scheduled (cron / CI manual job). They must fail closed (partial pack + error list), never write the live credit file.
Other invariants (keep #10 / #11 / #14)
token_info.total_supply. 1 UST1 = $1 liability.∞only on successful zero UST1 supply. Query failure →N/A, never∞.$1fallback.Number(bigint)raw amounts.terra1…/ known denoms only (isTerraContractAddress).Relevant files
frontend/public/assets/assetTiers.json(ordocs/asset-tiers/current.json+ frontend copy)crCreditBps,asOf,nextReview, rationale idfrontend/src/utils/assetQuality.ts(+.test.ts)docs/ASSET_QUALITY.mddocs/asset-tiers/playbook.mdscripts/asset-classification/skills/frontend-asset-quality/SKILL.mdfrontend/src/utils/treasuryRatios.ts(+ tests)frontend/src/utils/lpNav.ts/lpEligibility.tsfrontend/src/hooks/useTreasury.tscrUsdfrontend/src/types/treasury.ts/tokenlist.tsqualityTier,crCreditBps,displayUsdvscrUsdon spot as well as LPfrontend/src/components/treasury/TreasuryAssetsCard.tsxfrontend/src/components/treasury/RatiosCard.tsxfrontend/public/assets/tokenlist.jsontiermirror is OK; canonical is the address-keyed filedocs/ECONOMICS.mdASSET_QUALITY.md; do not overload “Collateralization Ratio Tiers”docs/ARCHITECTURE.md,docs/CONTRACTS.md,docs/DEPLOYMENT.md,docs/README.mdskills/frontend-ust1-ratios/SKILL.md,skills/frontend-treasury-lp-nav/SKILL.md,skills/README.mdfrontend/src/hooks/usePrices.tsRecommended direction
1. Taxonomy + committed file
Match address (CW20 / LP mint) or denom (
uusd,uluna). Never match symbol alone.2. Pure functions
creditBpsFor(ref, table) → number | null—null= unrated.applyCreditUsd(usd, bps) → number—floor/round-half-evendocumented; unit-tested.lpNavgrows an optionalqualityBpsper leg (default: unrated → fail closed for that CR leg). Display path ignores quality.3.
useTreasuryAfter today’s spot price /
computeLpNav:displayUsd = whole × price;crUsd = applyCredit(displayUsd, bps)(ornullif unpriced / unrated).Do not stuff quality into
usePrices()[symbol].4. UI
A…Eon each shown asset (including LP; LP may show per-leg grades).crUsd < displayUsd: secondary line e.g.CR counts $X (B / 80%).< 10000entered the sum, footnote that CR is quality-adjusted and linkdocs/ASSET_QUALITY.md(or an in-app “what is this?”).5. Research scripts (
scripts/asset-classification/)One CLI, pluggable sources, write
scripts/asset-classification/out/(gitignored) + a review markdown stub.Minimum adapters (skip a source cleanly if no key / timeout):
CMC_API_KEYsettoken_info, treasury balance, optional top holders (rate-limit; noall_accountsstorm)pool {}depth for tokenlist / LP pinssources.mdchecklist: official site, Venus/FDUSD docs, press — filled by humansCLI must run offline against recorded fixtures for tests (no live network in
npm test).6. Docs + skill
docs/ASSET_QUALITY.mdis the human SSoT. ECONOMICS gets a short pointer, not a second conflicting table.CR_TIERS.7. Ops
AddCw20still required forAllBalancesconsistency (#14 ops). Quality file does not replace the whitelist.Acceptance criteria
docs/ASSET_QUALITY.md, clearly separate from RED/YELLOW/GREEN/BLUE.spotUsd × creditand per-leg LP credits. Display MTM unchanged.incomplete+ listed. Never 100% by default.incomplete. Quality does not invent USD.ustcPerUst1unchanged (physical units).collateralization/assetsToLiabilitiesquality-adjusted.nextReviewbehavior.skills/README.mdupdated.Test plan (all paths)
Registry / resolve
uusd→ B / 8000;uluna→ B / 8000.vFDUSDwith a different address → unrated, not A.crCreditBpsoutside0…10000or non-integer → reject file.Spot math
ustcPerUst1still uses whole USTC.incompleteif that is the only way we know it exists and we choose the “material uncredited” rule — document: E is rated 0%, so it is included as $0 credit and not a missing-price. Unrated ≠ E. Test both.bps = 9900override → 99% of MTM (rounding documented).LP math (compose with #14)
UST1/cUSTC10% of 1000 UST1 + 1000 cUSTC: display $101; crUsd = $100 (wrap out, UST1 $1, no quality on wrap).UST1/USTR10% of 1000 UST1 + 2000 USTR @ $0.50: display $200; crUsd = $100 if USTR is E/0% (only UST1 leg).UST1/SpaceUSD10% of 1000 UST1 + SpaceUSD NAV $1000: display includes both; CR = $100 + $1000 × 25% = $350 at D/25%.crUsd = null(keep #14 fail-closed).UI
nextReview: warning shown.Scripts / process
assetTiers.json.all_accounts/ unbounded holder pagination not used.Regression
∞/N/A/ incomplete-price rules unchanged.cd frontend && npm testgreen.Test plan (attack, hack, abuse)
vFDUSD/USTCinherits A/Bprices.USTC *= 0.8breaks display, DEX quotes, and other pagesusePricesunchanged0.8 * 1e20liesrawToWholeNumberthen creditnextReviewwarning + playbook emergency path; test stale UIVITE_secret for research APIs--source https://attackerhits internal hostsall_accountspagination DoS< $1tier: Avs file BVerification criteria
Σ (MTM_i × bps_i / 10000)for CR-eligible legs/assets, plus #14 wrap exclusions, divided by whole UST1, matches on-screen CR within rounding.UST1-USTRCR counts the UST1 leg only (USTR E/0%) unless a merged review documents a non-zero USTR credit.frontend/src; no quality multiply insideusePrices/price.ts; no symbol-only tier lookup;CR_TIERSuntouched except comments pointing at the new doc.∞.docs/ASSET_QUALITY.md+ playbook + skill exist; ECONOMICS links them and does not merge A–E into RED/YELLOW/GREEN/BLUE.cd frontend && npm test— #11 / #14 fixtures pass at 10000 bps; new quality fixtures pass.assetTiers.json.marked as related to #10
marked as related to #11
marked as related to #14
mentioned in issue #22