feat(treasury): A–E asset quality tiers with 5-year CR credits (spot + LP) #19

Open
opened 2026-08-24 06:12:57 +00:00 by PlasticDigits · 4 comments
PlasticDigits commented 2026-08-24 06:12:57 +00:00 (Migrated from gitlab.com)

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

Piece Outcome
Classification A–E grades with written 5-year-horizon criteria
CR credit Per-tier % (starting: 100 / 80 / 50 / 25 / 0; 99% allowed as a documented intra-tier override)
Spot assets Apply credit after USD price, before summing CR
LP positions Apply credit per CR-eligible leg after reserve NAV (#14); wrap-leg double-count haircut stays orthogonal
Cadence Manual quarterly re-rate; scripts fetch a research pack; humans commit the file
Disclosure Treasury UI shows MTM vs CR-credited USD + tier badge + “quality haircut” footnote

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

Component Path Behavior today
CR math frontend/src/utils/treasuryRatios.ts (+ .test.ts) CR% = (Σ priced USD / whole UST1) × 100. Spot path: whole × price. LP path: precomputed crUsd (NAV, wrap legs already dropped). Every priced dollar counts at 100%. Missing USD → omit + incomplete.
Holdings frontend/src/hooks/useTreasury.ts Tokenlist native / cw20 (skip raw UST1 / USTR / cLUNC / cUSTC) + allowlisted type: "lp" via computeLpNav. No quality factor.
LP NAV frontend/src/utils/lpNav.ts, lpEligibility.ts displayUsd = full NAV; crUsd excludes pinned wrap legs only (double-count guard). USTR / other / UST1 legs enter CR at full USD.
Asset grid frontend/src/components/treasury/TreasuryAssetsCard.tsx Shows MTM USD. LP rows can footnote wrap haircut. No quality tier, no CR-credit vs MTM split for spot tokens.
Ratios UI frontend/src/components/treasury/RatiosCard.tsx ECONOMICS operation colors (CR_TIERS 95 / 110 / 190). Incomplete banner lists included / missing-price symbols. No quality-haircut copy.
Token registry frontend/public/assets/tokenlist.json USTC, LUNC, ALPHA, USTRIX, SpaceUSD, vFDUSD + LP UST1-USTR, UST1-cUSTC, UST1-SpaceUSD. No tier / crCreditBps.
Prices frontend/src/hooks/usePrices.ts, frontend/src/services/price.ts CEX LUNC/USTC; DEX simulate-swap for most CW20; vFDUSD session oracle. Spot price ≠ 5-year quality.
Economics SSoT docs/ECONOMICS.md Operation tiers + 5-year patience thesis. Collateral plan: diversify away from USTC toward BTC / ETH / quality stables. RED-tier rationale assumes “high-quality collateral (BTC, ETH, quality stablecoins)” — not every tokenlist CW20. No asset-quality ladder.
Proposal (unimplemented) plans/PROPOSAL.md USTC CR value = lesser of 7d / 24h / 1h weighted average. Complementary price conservatism; does not grade ALPHA vs vFDUSD.
On-chain treasury CW20_WHITELIST / AllBalances Binary include/exclude. No haircut. Live CR remains frontend-only.
Research scripts — None. contracts/scripts/ is deploy / migrate only. No CoinGecko / CryptoRank / CMC / holder pack.

Mainnet pins (do not invent):

Name Address / denom Role today Suggested starting quality (first commit; review may change)
vFDUSD terra1mnl9…svj3 Bridged Venus FDUSD; oracle USD (#10) A / 100%
USTC uusd Foundation collateral; CEX priced B / 80%
LUNC uluna Gas / native; CEX priced B / 80%
ALPHA terra1x6e6…ysuxz Tokenlist CW20; DEX priced D / 25% until first pack
USTRIX terra1r3ea…trcp5 Tokenlist CW20; DEX priced D / 25% until first pack
SpaceUSD terra1cvd5…v79zl Tokenlist CW20; DEX priced D / 25% until first pack
USTR (raw) terra1vy3k…setxv Not a holding (#11) N/A (still skipped)
USTR in LP same pin, as a leg Full crUsd today (#14) E / 0% (reflexive protocol token; see guardrails)
UST1 (raw) terra1f0eq…fy72 Liability, not an asset N/A
UST1 in LP same pin, as a leg CR at $1 (liability unit) 100% — not quality-rated
cLUNC / cUSTC (raw) wrap pins Supply cards only N/A
cLUNC / cUSTC LP legs wrap pins Display only; out of crUsd Stay 0% CR (double-count, not a quality grade)
Treasury terra16j5u…m3lr2 Holder —

Live 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

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

  2. 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.

  3. 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.

  4. LP NAV made this urgent. #14 correctly counts reserve claims. An allowlisted UST1-SpaceUSD LP can now put SpaceUSD into CR at full NAV. Wrap legs are haircut for double-count, not for quality. Those are different risks.

  5. 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.

  6. 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

  • Frontend CR math + tokenlist/tier file + Treasury UI + docs + skill + research scripts + tests.
  • No new CosmWasm execute path. No change to wrap-mapper, ustc-swap, InstantWithdraw, or ust1-window mint rules.
  • Do not implement the PROPOSAL USTC 7d/24h/1h min-TWAP here (separate price-oracle issue).
  • Do not fetch CoinGecko / CMC / CryptoRank from the browser on treasury page load (quota, key leak, XSS-via-API, ToS, CR flapping).
  • Official vendor APIs + documented public pages only. API keys via env / local config — never committed. No credentialed scraping of sites that forbid it.

Naming — do not collide with operation tiers

Name What it is Source
CR operation tier RED / YELLOW / GREEN / BLUE docs/ECONOMICS.md, CR_TIERS
Asset quality tier A / B / C / D / E this issue

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

  • Daily / weekly volatility, ATR, or “crypto beta”
  • A substitute for missing price (unpriced → still omit + incomplete, never invent a credit)
  • A substitute for the wrap double-count haircut
  • A substitute for tokenlist allowlisting / AddCw20 (unlisted still never enters CR)
  • Automatic; APIs do not write crCreditBps
Quality CR credit 5-year thesis Starting examples
A 100% Existential loss is issuer / bridge / contract risk, not “this market disappears.” Deep listings, durable tokenomics or fiat backing, size >> treasury position. vFDUSD; future BTC / ETH / USDC-class
B 80% Established, real CEX+DEX markets, survived prior crises, but structural residual risk (single-chain, depeg history, inflation path, concentrated community). Base case is survival; not FDUSD-equivalent. USTC, LUNC
C 50% Viable mid: some listings and volume, weaker or unproven tokenomic sustainability, thinner depth vs a protocol-sized exit. none pinned yet
D 25% Speculative / microcap / uncertain PMF / high holder concentration. High death or permanent-illiquidity probability over 5 years. ALPHA, USTRIX, SpaceUSD until first pack
E 0% Distressed, insufficient evidence, reflexive protocol token, or unrated. Shown in the grid at MTM; not in CR. USTR as LP leg; any new airdrop until rated

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: 9900 with 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.

Dimension 5-year question Typical sources
Liquidity Can a protocol-sized position exit without becoming the market? CEX order books, DEX TVL/depth, #14 pool reserves
Volume Is turnover organic and persistent, or a 24h wash spike? CoinGecko / CMC / CryptoRank volume; DEX swap history
Listing Will there still be a mark and an exit venue? CEX list (Binance already used for LUNC/USTC), CryptoRank listings
Market cap Is treasury a material % of float? CG / CMC MC vs treasury USD
Longevity Has it been stress-tested (depeg, bear, exploit)? Genesis / relaunch date, incident timeline
Tokenomic design Are emissions, sinks, and upgrade keys survivable? Whitepaper, contracts, CryptoRank allocation
Holder allocation Team / VC / top-10 concentration, unlock cliffs On-chain holders, CryptoRank, explorers
Inflation vs deflation Does net issuance grind the asset toward zero over 5y? Supply schedule, burn vs mint, LUNC tax/burn
PMF / sustainability Why does demand persist (gas, collateral, yield, meme)? Product sites, Venus/FDUSD docs, TerraClassic usage

Short-term price path is context, not a scoring axis.

How credit composes with existing CR rules

spotCrUsd = whole × spotUsd × qualityCredit          // qualityCredit in [0, 1]
lpLegCrUsd = claim_i × usd_i × qualityCredit(leg)    // only if leg is already CR-eligible
lpCrUsd   = Σ lpLegCrUsd
CR%       = (Σ spotCrUsd + Σ lpCrUsd) / wholeUST1 × 100

Hard rules:

  1. Wrap legs stay 0% CR (pinned cLUNC / cUSTC only). Quality is not applied to a leg that is already excluded.
  2. UST1 LP legs stay $1 × 100%. That is the liability unit (#11 / #14), not an asset grade.
  3. USTR in an allowlisted LP defaults to E / 0%. Raw USTR is already excluded. Counting protocol equity as UST1 collateral is the LUNA-style reflexive loop ECONOMICS rejected. First review may assign a non-zero credit; default is fail-closed.
  4. Unrated = E / 0% + incomplete if the balance is material (display ≥ $1 or non-zero and unpriced rules already show it). Never treat unrated as A/100%.
  5. Unpriced still omits (no $0, no $1). Quality cannot “fill in” a missing oracle/CEX/DEX price.
  6. Display USD stays full MTM (spot whole × price; LP displayUsd). CR uses credited USD. UI must show both when they differ.
  7. ustcPerUst1 remains physical USTC / UST1 (not quality-adjusted). Quality affects collateralization and assetsToLiabilities only.
  8. Credits are basis points integers (0…10000). No floats in the committed file. Apply with integer math then scale — do not 0.8 * in a way that drifts.
  9. One quality row per pinned address or native denom, not per symbol string. A fake CW20 named vFDUSD must not inherit A.
  10. Tokenlist pin + quality file must agree. Quality for an address not on the tokenlist / LP allowlist is ignored (cannot sneak CR via the tier file alone).

Quarterly process (manual; scripts are input)

  1. Run the research scripts → research-pack/ JSON + markdown (gitignored raw dumps OK; committed summary + sources required).
  2. Humans fill a review checklist (every dimension above, plus incidents / press / official sites).
  3. Propose tier + crCreditBps + rationale + asOf + nextReview.
  4. PR review (second human). Merge updates the file the frontend reads.
  5. Emergency out-of-cycle re-rate is allowed (depeg, exploit, delist, hyperinflation, bridge halt, governance capture). Same PR bar; do not wait for the calendar.
  6. Stale file (nextReview in the past) → UI warning + incomplete or 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)

  • CR denominator = UST1 token_info.total_supply. 1 UST1 = $1 liability.
  • ∞ only on successful zero UST1 supply. Query failure → N/A, never ∞.
  • vFDUSD USD from session oracle — never DEX-simulate, never $1 fallback.
  • No factory crawl; no LP mint simulate-swap; no Number(bigint) raw amounts.
  • Addresses: pinned terra1… / known denoms only (isTerraContractAddress).

Relevant files

File Role
New frontend/public/assets/assetTiers.json (or docs/asset-tiers/current.json + frontend copy) Committed SSoT: address/denom → tier, crCreditBps, asOf, nextReview, rationale id
New frontend/src/utils/assetQuality.ts (+ .test.ts) Pure: resolve credit by address/denom; apply bps; fail-closed unrated
New docs/ASSET_QUALITY.md Taxonomy, dimensions, process, current table, distinct from operation tiers
New docs/asset-tiers/playbook.md Quarterly checklist + emergency re-rate
New scripts/asset-classification/ Research-pack fetchers (CoinGecko, CryptoRank, CMC, on-chain holders, CEX/DEX depth, source list)
New skills/frontend-asset-quality/SKILL.md Agent skill; cross-link #11 / #14
frontend/src/utils/treasuryRatios.ts (+ tests) Accept quality-adjusted USD (or apply inside if inputs carry credit). Do not bake percents into this file.
frontend/src/utils/lpNav.ts / lpEligibility.ts Per-leg quality on CR-eligible legs; wrap still 0; UST1 leg still $1
frontend/src/hooks/useTreasury.ts Load tier file; apply after prices / NAV; pass credited crUsd
frontend/src/types/treasury.ts / tokenlist.ts qualityTier, crCreditBps, displayUsd vs crUsd on spot as well as LP
frontend/src/components/treasury/TreasuryAssetsCard.tsx Tier badge; MTM vs CR-credited when they differ
frontend/src/components/treasury/RatiosCard.tsx Quality-haircut footnote (like wrap-haircut copy); do not reuse RED/YELLOW colors for A–E
frontend/public/assets/tokenlist.json Optional tier mirror is OK; canonical is the address-keyed file
docs/ECONOMICS.md New subsection under Treasury / Collateral; link to ASSET_QUALITY.md; do not overload “Collateralization Ratio Tiers”
docs/ARCHITECTURE.md, docs/CONTRACTS.md, docs/DEPLOYMENT.md, docs/README.md Point at the quality file + process
skills/frontend-ust1-ratios/SKILL.md, skills/frontend-treasury-lp-nav/SKILL.md, skills/README.md Amend: quality credit after NAV / spot price
frontend/src/hooks/usePrices.ts No quality fetch; prices stay prices

1. Taxonomy + committed file

{
  "version": 1,
  "asOf": "2026-08-24",
  "nextReview": "2026-11-24",
  "horizon": "5y-impairment-not-vol",
  "assets": [
    {
      "id": "vfdusd",
      "symbol": "vFDUSD",
      "address": "terra1mnl9azefrqpmu888ar2u6zrcwr80hxlt3avf4300r576cw5ar7esvxsvj3",
      "tier": "A",
      "crCreditBps": 10000,
      "rationale": "Venus FDUSD claim; oracle-priced; institutional backing; 5y risk is issuer/bridge/Venus not market death"
    },
    {
      "id": "ustc",
      "symbol": "USTC",
      "denom": "uusd",
      "tier": "B",
      "crCreditBps": 8000,
      "rationale": "Foundation collateral; CEX markets; depeg history + single-chain + residual inflation"
    }
  ]
}

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-even documented; unit-tested.
  • lpNav grows an optional qualityBps per leg (default: unrated → fail closed for that CR leg). Display path ignores quality.

3. useTreasury

After today’s spot price / computeLpNav:

  • Spot: displayUsd = whole × price; crUsd = applyCredit(displayUsd, bps) (or null if unpriced / unrated).
  • LP: compute NAV as today; multiply each CR-eligible leg’s USD by that asset’s bps; UST1 leg stays 100%; wrap stays excluded.

Do not stuff quality into usePrices()[symbol].

4. UI

  • Badge A…E on each shown asset (including LP; LP may show per-leg grades).
  • If crUsd < displayUsd: secondary line e.g. CR counts $X (B / 80%).
  • RatiosCard: if any credit < 10000 entered the sum, footnote that CR is quality-adjusted and link docs/ASSET_QUALITY.md (or an in-app “what is this?”).
  • A–E colors must not be the same as RED/YELLOW/GREEN/BLUE.

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

Source Use
CoinGecko MC, volume, age, CEX list (public API)
CoinMarketCap Same, if CMC_API_KEY set
CryptoRank Listings, allocation, fundraising
On-chain LCD/RPC: token_info, treasury balance, optional top holders (rate-limit; no all_accounts storm)
CEX Reuse LUNC/USTC Binance path; do not invent new keys in repo
DEX Existing pair pool {} depth for tokenlist / LP pins
Manual sources.md checklist: official site, Venus/FDUSD docs, press — filled by humans

CLI must run offline against recorded fixtures for tests (no live network in npm test).

6. Docs + skill

  • docs/ASSET_QUALITY.md is the human SSoT. ECONOMICS gets a short pointer, not a second conflicting table.
  • Skill: when touching CR / tokenlist / LP NAV, apply quality after price/NAV; unrated fail-closed; do not confuse with CR_TIERS.

7. Ops

  • Adding a tokenlist pin requires a quality row in the same PR (or the asset shows MTM and marks CR incomplete).
  • Governance AddCw20 still required for AllBalances consistency (#14 ops). Quality file does not replace the whitelist.

Acceptance criteria

  • Written A–E taxonomy + scoring dimensions + 5-year (not vol) rationale in docs/ASSET_QUALITY.md, clearly separate from RED/YELLOW/GREEN/BLUE.
  • Committed machine-readable table: vFDUSD A/100%, USTC B/80%, LUNC B/80%; ALPHA / USTRIX / SpaceUSD D/25% (or first-pack grades); USTR LP leg E/0% unless review documents otherwise.
  • CR numerator uses spotUsd × credit and per-leg LP credits. Display MTM unchanged.
  • Wrap LP legs still excluded from CR. UST1 LP legs still $1 × 100%. Raw UST1 / USTR / wraps still not holdings.
  • Unrated material balance → omitted from CR + incomplete + listed. Never 100% by default.
  • Unpriced still omitted + incomplete. Quality does not invent USD.
  • ustcPerUst1 unchanged (physical units). collateralization / assetsToLiabilities quality-adjusted.
  • Treasury UI: tier badge; MTM vs CR-credited when they differ; RatiosCard quality footnote; A–E colors ≠ operation-tier colors.
  • Research scripts produce a pack from configured sources; do not write the live credit file; keys env-only; fixtures cover offline tests.
  • Playbook: quarterly cadence, dual-human PR, emergency re-rate, stale-nextReview behavior.
  • Skills + ECONOMICS / ARCHITECTURE / CONTRACTS / DEPLOYMENT / README / skills/README.md updated.
  • Existing #11 / #14 fixtures still pass when credits are 10000; new fixtures prove haircuts.

Test plan (all paths)

Registry / resolve

  • Address match: vFDUSD pin → A / 10000 bps.
  • Denom match: uusd → B / 8000; uluna → B / 8000.
  • Symbol-only lookalike CW20 named vFDUSD with a different address → unrated, not A.
  • Quality row for an address not on tokenlist → ignored (no CR).
  • Tokenlist pin without a quality row → MTM shown; CR omit + incomplete.
  • Duplicate ids / conflicting bps for same address → fail closed at load (no silent last-write-wins).
  • crCreditBps outside 0…10000 or non-integer → reject file.
  • LP mint address can have its own row, but CR uses leg credits, not a single LP grade (unless documented as an extra structural cap — default is per-leg only).

Spot math

  • 1e6 UST1, $2e6 vFDUSD at A/100% → CR 200% (existing fixture still holds).
  • 1e6 UST1, $2e6 USTC MTM at B/80% → CR 160%, A/L 1.60x; ustcPerUst1 still uses whole USTC.
  • 1e6 UST1, $1e6 vFDUSD (A) + $1e6 ALPHA MTM (D/25%) → assetsUsd = 1e6 + 250k → CR 125%.
  • E/0% with MTM $1e6 → CR contribution 0; incomplete if 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.
  • Unpriced B-tier USTC → omit, incomplete, not 80% of $0.
  • bps = 9900 override → 99% of MTM (rounding documented).

LP math (compose with #14)

  • UST1/cUSTC 10% of 1000 UST1 + 1000 cUSTC: display $101; crUsd = $100 (wrap out, UST1 $1, no quality on wrap).
  • UST1/USTR 10% of 1000 UST1 + 2000 USTR @ $0.50: display $200; crUsd = $100 if USTR is E/0% (only UST1 leg).
  • UST1/SpaceUSD 10% of 1000 UST1 + SpaceUSD NAV $1000: display includes both; CR = $100 + $1000 × 25% = $350 at D/25%.
  • Unpriced CR-eligible quality-rated leg → whole LP crUsd = null (keep #14 fail-closed).
  • Wrap + quality must not double-apply (wrap already 0).

UI

  • Badge visible for A–E; LP shows leg grades or a composite label that does not imply a fake single grade.
  • B-tier row: MTM $100, CR counts $80.
  • E-tier row still visible at MTM ≥ $1; CR footnote explains $0 credit.
  • Ratios incomplete banner still lists missing prices; quality footnote is separate.
  • Stale nextReview: warning shown.
  • Operation-tier colors on the ratio unchanged (95/110/190).

Scripts / process

  • CLI with fixtures (no network) writes a pack containing one section per dimension.
  • Missing API key → skip source + error list, exit non-zero or documented soft-fail; no crash, no empty “success”.
  • Live mode does not overwrite assetTiers.json.
  • Rate-limit / 429 → retry-once or skip; no tight loop.
  • all_accounts / unbounded holder pagination not used.
  • Playbook checklist items map 1:1 to pack sections.

Regression

  • #11: ∞ / N/A / incomplete-price rules unchanged.
  • #14: wrap haircut fixtures unchanged when quality on remaining legs is 10000.
  • #10: vFDUSD still oracle-only.
  • cd frontend && npm test green.

Test plan (attack, hack, abuse)

Vector Risk Test / guard
Wash-volume pump → auto A Attacker rents 24h volume; script or UI promotes junk to 100% CR Scripts never write credits; frontend never reads CG/CMC live for CR
Symbol spoof CW20 ticker vFDUSD / USTC inherits A/B Resolve by address / denom only
Tier-file injection Extra row for unlisted SCAM address counts in CR Quality ignored unless tokenlist / LP allowlist also pins it
Unrated = 100% New airdrop to treasury silently inflates CR Unrated → 0 CR + incomplete
Unrated = missing-price $0 dump Confuse E/unrated with unpriced and flash RED E is explicit $0 credit; unpriced is omit; both labeled
Reflexive USTR loop USTR LP legs at 100% recreate LUNA-as-collateral Default USTR leg E/0%; raw USTR still skipped
Wrap + quality double haircut / double count cUSTC leg counted after “quality B” or dropped twice incorrectly Wrap excluded first; quality never revives it
UST1 quality downgrade Someone rates UST1 LP leg E and understates CR UST1 leg forced $1 × 100%
Credit in the price map prices.USTC *= 0.8 breaks display, DEX quotes, and other pages Quality only on CR path; usePrices unchanged
bps overflow / 120% File says 12000; CR > MTM Reject at parse
Negative / NaN bps Invert CR Reject at parse
Float drift 0.8 * 1e20 lies Integer bps; rawToWholeNumber then credit
Stale A after death Token rugged; file still 100% for 2 years nextReview warning + playbook emergency path; test stale UI
API key in repo / frontend bundle Stolen CMC/CG key Grep CI; keys only in env; no VITE_ secret for research APIs
SSRF / open URL in script --source https://attacker hits internal hosts Allowlist hostnames for adapters
ToS / scrape flood IP ban, legal, poisoned HTML → bad pack Official APIs; fixture tests; no HTML parse as CR input
LCD holder storm all_accounts pagination DoS Cap / skip; pack can say “holders: manual”
Oracle bypass via quality Set vFDUSD E but still invent $1 Quality does not create USD; #10 still required
Donate dust + D-tier Still some CR from junk D/25% + existing $1 display dust; optional: do not CR-count display < $1
Two files disagree tokenlist tier: A vs file B One canonical file; tokenlist mirror optional and must match in a unit test
Operation-tier confusion UI paints asset “RED” meaning quality E Separate copy + colors
Governance surprise Credit change moves BLUE → YELLOW without notice PR + docs; UI shows adjusted vs MTM so the haircut is visible
MITM pack Poisoned CG JSON if someone later wires it live Not wired live; if CI fetches, pin checksum / recorded snapshot

Verification criteria

  1. Mainnet Treasury: every shown holding has a quality badge. vFDUSD CR USD equals MTM. USTC / LUNC CR USD equals 80% of MTM (or the committed bps). ALPHA / USTRIX / SpaceUSD CR USD equals 25% of MTM (or first-pack bps).
  2. Independent calculator: Σ (MTM_i × bps_i / 10000) for CR-eligible legs/assets, plus #14 wrap exclusions, divided by whole UST1, matches on-screen CR within rounding.
  3. UST1-USTR CR counts the UST1 leg only (USTR E/0%) unless a merged review documents a non-zero USTR credit.
  4. Grep clean: no CoinGecko/CMC/CryptoRank calls under frontend/src; no quality multiply inside usePrices / price.ts; no symbol-only tier lookup; CR_TIERS untouched except comments pointing at the new doc.
  5. Airplane-mode / API-pack down: treasury page still loads from the committed file; does not drop to 100% MTM CR and does not become ∞.
  6. docs/ASSET_QUALITY.md + playbook + skill exist; ECONOMICS links them and does not merge A–E into RED/YELLOW/GREEN/BLUE.
  7. cd frontend && npm test — #11 / #14 fixtures pass at 10000 bps; new quality fixtures pass.
  8. Script dry-run with fixtures produces a pack; live run (manual) does not modify assetTiers.json.
## 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): | Piece | Outcome | |-------|---------| | Classification | A–E grades with written 5-year-horizon criteria | | CR credit | Per-tier % (starting: 100 / 80 / 50 / 25 / 0; 99% allowed as a documented intra-tier override) | | Spot assets | Apply credit after USD price, before summing CR | | LP positions | Apply credit **per CR-eligible leg** after reserve NAV (#14); wrap-leg double-count haircut stays orthogonal | | Cadence | **Manual** quarterly re-rate; scripts fetch a research pack; humans commit the file | | Disclosure | Treasury UI shows MTM vs CR-credited USD + tier badge + “quality haircut” footnote | Depends on [#11](https://gitlab.com/PlasticDigits2/ustr-cmm/-/issues/11) CR math, [#10](https://gitlab.com/PlasticDigits2/ustr-cmm/-/issues/10) vFDUSD USD, and [#14](https://gitlab.com/PlasticDigits2/ustr-cmm/-/issues/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 | Component | Path | Behavior today | |-----------|------|----------------| | CR math | `frontend/src/utils/treasuryRatios.ts` (+ `.test.ts`) | `CR% = (Σ priced USD / whole UST1) × 100`. Spot path: `whole × price`. LP path: precomputed `crUsd` (NAV, wrap legs already dropped). **Every priced dollar counts at 100%.** Missing USD → omit + `incomplete`. | | Holdings | `frontend/src/hooks/useTreasury.ts` | Tokenlist `native` / `cw20` (skip raw UST1 / USTR / cLUNC / cUSTC) + allowlisted `type: "lp"` via `computeLpNav`. No quality factor. | | LP NAV | `frontend/src/utils/lpNav.ts`, `lpEligibility.ts` | `displayUsd` = full NAV; `crUsd` excludes **pinned wrap** legs only (double-count guard). USTR / other / UST1 legs enter CR at full USD. | | Asset grid | `frontend/src/components/treasury/TreasuryAssetsCard.tsx` | Shows MTM USD. LP rows can footnote wrap haircut. No quality tier, no CR-credit vs MTM split for spot tokens. | | Ratios UI | `frontend/src/components/treasury/RatiosCard.tsx` | ECONOMICS **operation** colors (`CR_TIERS` 95 / 110 / 190). Incomplete banner lists included / missing-price symbols. No quality-haircut copy. | | Token registry | `frontend/public/assets/tokenlist.json` | USTC, LUNC, ALPHA, USTRIX, SpaceUSD, vFDUSD + LP `UST1-USTR`, `UST1-cUSTC`, `UST1-SpaceUSD`. No `tier` / `crCreditBps`. | | Prices | `frontend/src/hooks/usePrices.ts`, `frontend/src/services/price.ts` | CEX LUNC/USTC; DEX simulate-swap for most CW20; vFDUSD session oracle. **Spot price ≠ 5-year quality.** | | Economics SSoT | `docs/ECONOMICS.md` | Operation tiers + 5-year patience thesis. Collateral plan: diversify **away** from USTC toward BTC / ETH / quality stables. RED-tier rationale assumes “high-quality collateral (BTC, ETH, quality stablecoins)” — **not** every tokenlist CW20. No asset-quality ladder. | | Proposal (unimplemented) | `plans/PROPOSAL.md` | USTC CR value = **lesser of 7d / 24h / 1h** weighted average. Complementary **price** conservatism; does not grade ALPHA vs vFDUSD. | | On-chain | treasury `CW20_WHITELIST` / `AllBalances` | Binary include/exclude. No haircut. Live CR remains **frontend-only**. | | Research scripts | — | **None.** `contracts/scripts/` is deploy / migrate only. No CoinGecko / CryptoRank / CMC / holder pack. | **Mainnet pins (do not invent):** | Name | Address / denom | Role today | Suggested **starting** quality (first commit; review may change) | |------|-----------------|------------|------------------------------------------------------------------| | vFDUSD | `terra1mnl9…svj3` | Bridged Venus FDUSD; oracle USD (#10) | **A / 100%** | | USTC | `uusd` | Foundation collateral; CEX priced | **B / 80%** | | LUNC | `uluna` | Gas / native; CEX priced | **B / 80%** | | ALPHA | `terra1x6e6…ysuxz` | Tokenlist CW20; DEX priced | **D / 25%** until first pack | | USTRIX | `terra1r3ea…trcp5` | Tokenlist CW20; DEX priced | **D / 25%** until first pack | | SpaceUSD | `terra1cvd5…v79zl` | Tokenlist CW20; DEX priced | **D / 25%** until first pack | | USTR (raw) | `terra1vy3k…setxv` | **Not a holding** (#11) | N/A (still skipped) | | USTR **in LP** | same pin, as a leg | Full `crUsd` today (#14) | **E / 0%** (reflexive protocol token; see guardrails) | | UST1 (raw) | `terra1f0eq…fy72` | Liability, not an asset | N/A | | UST1 **in LP** | same pin, as a leg | CR at **$1** (liability unit) | **100%** — not quality-rated | | cLUNC / cUSTC (raw) | wrap pins | Supply cards only | N/A | | cLUNC / cUSTC **LP legs** | wrap pins | Display only; **out** of `crUsd` | Stay **0% CR** (double-count, not a quality grade) | | Treasury | `terra16j5u…m3lr2` | Holder | — | Live 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 1. **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. 2. **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**. 3. **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. 4. **LP NAV made this urgent.** #14 correctly counts reserve claims. An allowlisted `UST1-SpaceUSD` LP can now put SpaceUSD into CR at full NAV. Wrap legs are haircut for *double-count*, not for *quality*. Those are different risks. 5. **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. 6. **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 - **Frontend CR math + tokenlist/tier file + Treasury UI + docs + skill + research scripts + tests.** - No new CosmWasm execute path. No change to wrap-mapper, ustc-swap, InstantWithdraw, or ust1-window mint rules. - Do **not** implement the PROPOSAL USTC 7d/24h/1h min-TWAP here (separate price-oracle issue). - Do **not** fetch CoinGecko / CMC / CryptoRank from the **browser** on treasury page load (quota, key leak, XSS-via-API, ToS, CR flapping). - Official vendor APIs + documented public pages only. API keys via env / local config — **never** committed. No credentialed scraping of sites that forbid it. ### Naming — do not collide with operation tiers | Name | What it is | Source | |------|------------|--------| | **CR operation tier** | RED / YELLOW / GREEN / BLUE | `docs/ECONOMICS.md`, `CR_TIERS` | | **Asset quality tier** | A / B / C / D / E | **this issue** | Do 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**: - Daily / weekly volatility, ATR, or “crypto beta” - A substitute for missing price (unpriced → still omit + `incomplete`, never invent a credit) - A substitute for the wrap double-count haircut - A substitute for tokenlist allowlisting / `AddCw20` (unlisted still never enters CR) - Automatic; APIs do not write `crCreditBps` ### Recommended ladder (starting SSoT — first review may retune percents, not the letters) | Quality | CR credit | 5-year thesis | Starting examples | |---------|-----------|---------------|-------------------| | **A** | **100%** | Existential loss is issuer / bridge / contract risk, not “this market disappears.” Deep listings, durable tokenomics or fiat backing, size >> treasury position. | vFDUSD; future BTC / ETH / USDC-class | | **B** | **80%** | Established, real CEX+DEX markets, survived prior crises, but structural residual risk (single-chain, depeg history, inflation path, concentrated community). Base case is survival; not FDUSD-equivalent. | **USTC, LUNC** | | **C** | **50%** | Viable mid: some listings and volume, weaker or unproven tokenomic sustainability, thinner depth vs a protocol-sized exit. | none pinned yet | | **D** | **25%** | Speculative / microcap / uncertain PMF / high holder concentration. High death or permanent-illiquidity probability over 5 years. | ALPHA, USTRIX, SpaceUSD until first pack | | **E** | **0%** | Distressed, insufficient evidence, reflexive protocol token, or **unrated**. Shown in the grid at MTM; **not** in CR. | USTR as LP leg; any new airdrop until rated | **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: 9900` with 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. | Dimension | 5-year question | Typical sources | |-----------|-----------------|-----------------| | Liquidity | Can a protocol-sized position exit without becoming the market? | CEX order books, DEX TVL/depth, #14 pool reserves | | Volume | Is turnover organic and persistent, or a 24h wash spike? | CoinGecko / CMC / CryptoRank volume; DEX swap history | | Listing | Will there still be a mark and an exit venue? | CEX list (Binance already used for LUNC/USTC), CryptoRank listings | | Market cap | Is treasury a material % of float? | CG / CMC MC vs treasury USD | | Longevity | Has it been stress-tested (depeg, bear, exploit)? | Genesis / relaunch date, incident timeline | | Tokenomic design | Are emissions, sinks, and upgrade keys survivable? | Whitepaper, contracts, CryptoRank allocation | | Holder allocation | Team / VC / top-10 concentration, unlock cliffs | On-chain holders, CryptoRank, explorers | | Inflation vs deflation | Does net issuance grind the asset toward zero over 5y? | Supply schedule, burn vs mint, LUNC tax/burn | | PMF / sustainability | Why does demand persist (gas, collateral, yield, meme)? | Product sites, Venus/FDUSD docs, TerraClassic usage | Short-term price path is **context**, not a scoring axis. ### How credit composes with existing CR rules ``` spotCrUsd = whole × spotUsd × qualityCredit // qualityCredit in [0, 1] lpLegCrUsd = claim_i × usd_i × qualityCredit(leg) // only if leg is already CR-eligible lpCrUsd = Σ lpLegCrUsd CR% = (Σ spotCrUsd + Σ lpCrUsd) / wholeUST1 × 100 ``` Hard rules: 1. **Wrap legs stay 0% CR** (pinned cLUNC / cUSTC only). Quality is not applied to a leg that is already excluded. 2. **UST1 LP legs stay $1 × 100%.** That is the liability unit (#11 / #14), not an asset grade. 3. **USTR in an allowlisted LP defaults to E / 0%.** Raw USTR is already excluded. Counting protocol equity as UST1 collateral is the LUNA-style reflexive loop ECONOMICS rejected. First review may assign a non-zero credit; default is fail-closed. 4. **Unrated = E / 0% + `incomplete`** if the balance is material (display ≥ $1 or non-zero and unpriced rules already show it). Never treat unrated as A/100%. 5. **Unpriced still omits** (no $0, no $1). Quality cannot “fill in” a missing oracle/CEX/DEX price. 6. **Display USD stays full MTM** (spot `whole × price`; LP `displayUsd`). CR uses credited USD. UI must show both when they differ. 7. **`ustcPerUst1`** remains **physical USTC / UST1** (not quality-adjusted). Quality affects `collateralization` and `assetsToLiabilities` only. 8. Credits are **basis points integers** (`0…10000`). No floats in the committed file. Apply with integer math then scale — do not `0.8 *` in a way that drifts. 9. One quality row **per pinned address or native denom**, not per symbol string. A fake CW20 named `vFDUSD` must not inherit A. 10. Tokenlist pin + quality file must agree. Quality for an address **not** on the tokenlist / LP allowlist is ignored (cannot sneak CR via the tier file alone). ### Quarterly process (manual; scripts are input) 1. Run the research scripts → `research-pack/` JSON + markdown (gitignored raw dumps OK; **committed** summary + sources required). 2. Humans fill a review checklist (every dimension above, plus incidents / press / official sites). 3. Propose `tier` + `crCreditBps` + rationale + `asOf` + `nextReview`. 4. PR review (second human). Merge updates the file the frontend reads. 5. **Emergency out-of-cycle** re-rate is allowed (depeg, exploit, delist, hyperinflation, bridge halt, governance capture). Same PR bar; do not wait for the calendar. 6. Stale file (`nextReview` in the past) → UI warning + `incomplete` **or** 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) - CR denominator = UST1 `token_info.total_supply`. 1 UST1 = $1 liability. - `∞` only on successful zero UST1 supply. Query failure → `N/A`, never `∞`. - vFDUSD USD from session oracle — never DEX-simulate, never `$1` fallback. - No factory crawl; no LP mint simulate-swap; no `Number(bigint)` raw amounts. - Addresses: pinned `terra1…` / known denoms only (`isTerraContractAddress`). --- ## Relevant files | File | Role | |------|------| | **New** `frontend/public/assets/assetTiers.json` (or `docs/asset-tiers/current.json` + frontend copy) | Committed SSoT: address/denom → tier, `crCreditBps`, `asOf`, `nextReview`, rationale id | | **New** `frontend/src/utils/assetQuality.ts` (+ `.test.ts`) | Pure: resolve credit by address/denom; apply bps; fail-closed unrated | | **New** `docs/ASSET_QUALITY.md` | Taxonomy, dimensions, process, current table, distinct from operation tiers | | **New** `docs/asset-tiers/playbook.md` | Quarterly checklist + emergency re-rate | | **New** `scripts/asset-classification/` | Research-pack fetchers (CoinGecko, CryptoRank, CMC, on-chain holders, CEX/DEX depth, source list) | | **New** `skills/frontend-asset-quality/SKILL.md` | Agent skill; cross-link #11 / #14 | | `frontend/src/utils/treasuryRatios.ts` (+ tests) | Accept quality-adjusted USD (or apply inside if inputs carry credit). Do not bake percents into this file. | | `frontend/src/utils/lpNav.ts` / `lpEligibility.ts` | Per-leg quality on CR-eligible legs; wrap still 0; UST1 leg still $1 | | `frontend/src/hooks/useTreasury.ts` | Load tier file; apply after prices / NAV; pass credited `crUsd` | | `frontend/src/types/treasury.ts` / `tokenlist.ts` | `qualityTier`, `crCreditBps`, `displayUsd` vs `crUsd` on **spot** as well as LP | | `frontend/src/components/treasury/TreasuryAssetsCard.tsx` | Tier badge; MTM vs CR-credited when they differ | | `frontend/src/components/treasury/RatiosCard.tsx` | Quality-haircut footnote (like wrap-haircut copy); do not reuse RED/YELLOW colors for A–E | | `frontend/public/assets/tokenlist.json` | Optional `tier` mirror is OK; **canonical** is the address-keyed file | | `docs/ECONOMICS.md` | New subsection under Treasury / Collateral; link to `ASSET_QUALITY.md`; do not overload “Collateralization Ratio Tiers” | | `docs/ARCHITECTURE.md`, `docs/CONTRACTS.md`, `docs/DEPLOYMENT.md`, `docs/README.md` | Point at the quality file + process | | `skills/frontend-ust1-ratios/SKILL.md`, `skills/frontend-treasury-lp-nav/SKILL.md`, `skills/README.md` | Amend: quality credit after NAV / spot price | | `frontend/src/hooks/usePrices.ts` | **No** quality fetch; prices stay prices | --- ## Recommended direction ### 1. Taxonomy + committed file ```json { "version": 1, "asOf": "2026-08-24", "nextReview": "2026-11-24", "horizon": "5y-impairment-not-vol", "assets": [ { "id": "vfdusd", "symbol": "vFDUSD", "address": "terra1mnl9azefrqpmu888ar2u6zrcwr80hxlt3avf4300r576cw5ar7esvxsvj3", "tier": "A", "crCreditBps": 10000, "rationale": "Venus FDUSD claim; oracle-priced; institutional backing; 5y risk is issuer/bridge/Venus not market death" }, { "id": "ustc", "symbol": "USTC", "denom": "uusd", "tier": "B", "crCreditBps": 8000, "rationale": "Foundation collateral; CEX markets; depeg history + single-chain + residual inflation" } ] } ``` 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-even` documented; unit-tested. - `lpNav` grows an optional `qualityBps` per leg (default: unrated → fail closed for that **CR** leg). Display path ignores quality. ### 3. `useTreasury` After today’s spot price / `computeLpNav`: - Spot: `displayUsd = whole × price`; `crUsd = applyCredit(displayUsd, bps)` (or `null` if unpriced / unrated). - LP: compute NAV as today; multiply each **CR-eligible** leg’s USD by that asset’s bps; UST1 leg stays 100%; wrap stays excluded. Do **not** stuff quality into `usePrices()[symbol]`. ### 4. UI - Badge `A`…`E` on each shown asset (including LP; LP may show per-leg grades). - If `crUsd < displayUsd`: secondary line e.g. `CR counts $X (B / 80%)`. - RatiosCard: if any credit `< 10000` entered the sum, footnote that CR is **quality-adjusted** and link `docs/ASSET_QUALITY.md` (or an in-app “what is this?”). - A–E colors must **not** be the same as RED/YELLOW/GREEN/BLUE. ### 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): | Source | Use | |--------|-----| | CoinGecko | MC, volume, age, CEX list (public API) | | CoinMarketCap | Same, if `CMC_API_KEY` set | | CryptoRank | Listings, allocation, fundraising | | On-chain | LCD/RPC: `token_info`, treasury balance, optional top holders (rate-limit; no `all_accounts` storm) | | CEX | Reuse LUNC/USTC Binance path; do not invent new keys in repo | | DEX | Existing pair `pool {}` depth for tokenlist / LP pins | | Manual | `sources.md` checklist: official site, Venus/FDUSD docs, press — filled by humans | CLI must run offline against **recorded fixtures** for tests (no live network in `npm test`). ### 6. Docs + skill - `docs/ASSET_QUALITY.md` is the human SSoT. ECONOMICS gets a short pointer, not a second conflicting table. - Skill: when touching CR / tokenlist / LP NAV, apply quality after price/NAV; unrated fail-closed; do not confuse with `CR_TIERS`. ### 7. Ops - Adding a tokenlist pin **requires** a quality row in the same PR (or the asset shows MTM and marks CR incomplete). - Governance `AddCw20` still required for `AllBalances` consistency (#14 ops). Quality file does not replace the whitelist. --- ## Acceptance criteria - [ ] Written A–E taxonomy + scoring dimensions + 5-year (not vol) rationale in `docs/ASSET_QUALITY.md`, clearly separate from RED/YELLOW/GREEN/BLUE. - [ ] Committed machine-readable table: vFDUSD **A/100%**, USTC **B/80%**, LUNC **B/80%**; ALPHA / USTRIX / SpaceUSD **D/25%** (or first-pack grades); USTR LP leg **E/0%** unless review documents otherwise. - [ ] CR numerator uses `spotUsd × credit` and per-leg LP credits. Display MTM unchanged. - [ ] Wrap LP legs still excluded from CR. UST1 LP legs still $1 × 100%. Raw UST1 / USTR / wraps still not holdings. - [ ] Unrated material balance → omitted from CR + `incomplete` + listed. Never 100% by default. - [ ] Unpriced still omitted + `incomplete`. Quality does not invent USD. - [ ] `ustcPerUst1` unchanged (physical units). `collateralization` / `assetsToLiabilities` quality-adjusted. - [ ] Treasury UI: tier badge; MTM vs CR-credited when they differ; RatiosCard quality footnote; A–E colors ≠ operation-tier colors. - [ ] Research scripts produce a pack from configured sources; **do not** write the live credit file; keys env-only; fixtures cover offline tests. - [ ] Playbook: quarterly cadence, dual-human PR, emergency re-rate, stale-`nextReview` behavior. - [ ] Skills + ECONOMICS / ARCHITECTURE / CONTRACTS / DEPLOYMENT / README / `skills/README.md` updated. - [ ] Existing #11 / #14 fixtures still pass when credits are 10000; new fixtures prove haircuts. --- ## Test plan (all paths) **Registry / resolve** - [ ] Address match: vFDUSD pin → A / 10000 bps. - [ ] Denom match: `uusd` → B / 8000; `uluna` → B / 8000. - [ ] Symbol-only lookalike CW20 named `vFDUSD` with a different address → unrated, not A. - [ ] Quality row for an address **not** on tokenlist → ignored (no CR). - [ ] Tokenlist pin without a quality row → MTM shown; CR omit + incomplete. - [ ] Duplicate ids / conflicting bps for same address → fail closed at load (no silent last-write-wins). - [ ] `crCreditBps` outside `0…10000` or non-integer → reject file. - [ ] LP mint address can have its own row, but CR uses **leg** credits, not a single LP grade (unless documented as an extra structural cap — default is per-leg only). **Spot math** - [ ] 1e6 UST1, $2e6 vFDUSD at A/100% → CR 200% (existing fixture still holds). - [ ] 1e6 UST1, $2e6 USTC MTM at B/80% → CR **160%**, A/L **1.60x**; `ustcPerUst1` still uses whole USTC. - [ ] 1e6 UST1, $1e6 vFDUSD (A) + $1e6 ALPHA MTM (D/25%) → assetsUsd = 1e6 + 250k → CR **125%**. - [ ] E/0% with MTM $1e6 → CR contribution **0**; `incomplete` if 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. - [ ] Unpriced B-tier USTC → omit, incomplete, **not** 80% of $0. - [ ] `bps = 9900` override → 99% of MTM (rounding documented). **LP math (compose with #14)** - [ ] `UST1/cUSTC` 10% of 1000 UST1 + 1000 cUSTC: display $101; **crUsd = $100** (wrap out, UST1 $1, no quality on wrap). - [ ] `UST1/USTR` 10% of 1000 UST1 + 2000 USTR @ $0.50: display $200; **crUsd = $100** if USTR is E/0% (only UST1 leg). - [ ] `UST1/SpaceUSD` 10% of 1000 UST1 + SpaceUSD NAV $1000: display includes both; CR = $100 + $1000 × 25% = **$350** at D/25%. - [ ] Unpriced CR-eligible quality-rated leg → whole LP `crUsd = null` (keep #14 fail-closed). - [ ] Wrap + quality must not double-apply (wrap already 0). **UI** - [ ] Badge visible for A–E; LP shows leg grades or a composite label that does not imply a fake single grade. - [ ] B-tier row: MTM $100, CR counts $80. - [ ] E-tier row still visible at MTM ≥ $1; CR footnote explains $0 credit. - [ ] Ratios incomplete banner still lists missing **prices**; quality footnote is separate. - [ ] Stale `nextReview`: warning shown. - [ ] Operation-tier colors on the **ratio** unchanged (95/110/190). **Scripts / process** - [ ] CLI with fixtures (no network) writes a pack containing one section per dimension. - [ ] Missing API key → skip source + error list, exit non-zero or documented soft-fail; no crash, no empty “success”. - [ ] Live mode does not overwrite `assetTiers.json`. - [ ] Rate-limit / 429 → retry-once or skip; no tight loop. - [ ] `all_accounts` / unbounded holder pagination **not** used. - [ ] Playbook checklist items map 1:1 to pack sections. **Regression** - [ ] #11: `∞` / `N/A` / incomplete-price rules unchanged. - [ ] #14: wrap haircut fixtures unchanged when quality on remaining legs is 10000. - [ ] #10: vFDUSD still oracle-only. - [ ] `cd frontend && npm test` green. --- ## Test plan (attack, hack, abuse) | Vector | Risk | Test / guard | |--------|------|----------------| | **Wash-volume pump → auto A** | Attacker rents 24h volume; script or UI promotes junk to 100% CR | Scripts never write credits; frontend never reads CG/CMC live for CR | | **Symbol spoof** | CW20 ticker `vFDUSD` / `USTC` inherits A/B | Resolve by address / denom only | | **Tier-file injection** | Extra row for unlisted SCAM address counts in CR | Quality ignored unless tokenlist / LP allowlist also pins it | | **Unrated = 100%** | New airdrop to treasury silently inflates CR | Unrated → 0 CR + incomplete | | **Unrated = missing-price $0 dump** | Confuse E/unrated with unpriced and flash RED | E is explicit $0 credit; unpriced is omit; both labeled | | **Reflexive USTR loop** | USTR LP legs at 100% recreate LUNA-as-collateral | Default USTR leg E/0%; raw USTR still skipped | | **Wrap + quality double haircut / double count** | cUSTC leg counted after “quality B” or dropped twice incorrectly | Wrap excluded first; quality never revives it | | **UST1 quality downgrade** | Someone rates UST1 LP leg E and understates CR | UST1 leg forced $1 × 100% | | **Credit in the price map** | `prices.USTC *= 0.8` breaks display, DEX quotes, and other pages | Quality only on CR path; `usePrices` unchanged | | **bps overflow / 120%** | File says 12000; CR > MTM | Reject at parse | | **Negative / NaN bps** | Invert CR | Reject at parse | | **Float drift** | `0.8 * 1e20` lies | Integer bps; `rawToWholeNumber` then credit | | **Stale A after death** | Token rugged; file still 100% for 2 years | `nextReview` warning + playbook emergency path; test stale UI | | **API key in repo / frontend bundle** | Stolen CMC/CG key | Grep CI; keys only in env; no `VITE_` secret for research APIs | | **SSRF / open URL in script** | `--source https://attacker` hits internal hosts | Allowlist hostnames for adapters | | **ToS / scrape flood** | IP ban, legal, poisoned HTML → bad pack | Official APIs; fixture tests; no HTML parse as CR input | | **LCD holder storm** | `all_accounts` pagination DoS | Cap / skip; pack can say “holders: manual” | | **Oracle bypass via quality** | Set vFDUSD E but still invent $1 | Quality does not create USD; #10 still required | | **Donate dust + D-tier** | Still some CR from junk | D/25% + existing $1 display dust; optional: do not CR-count display `< $1` | | **Two files disagree** | tokenlist `tier: A` vs file B | One canonical file; tokenlist mirror optional and must match in a unit test | | **Operation-tier confusion** | UI paints asset “RED” meaning quality E | Separate copy + colors | | **Governance surprise** | Credit change moves BLUE → YELLOW without notice | PR + docs; UI shows adjusted vs MTM so the haircut is visible | | **MITM pack** | Poisoned CG JSON if someone later wires it live | Not wired live; if CI fetches, pin checksum / recorded snapshot | --- ## Verification criteria 1. Mainnet Treasury: every shown holding has a quality badge. vFDUSD CR USD equals MTM. USTC / LUNC CR USD equals **80%** of MTM (or the committed bps). ALPHA / USTRIX / SpaceUSD CR USD equals **25%** of MTM (or first-pack bps). 2. Independent calculator: `Σ (MTM_i × bps_i / 10000)` for CR-eligible legs/assets, plus #14 wrap exclusions, divided by whole UST1, matches on-screen CR within rounding. 3. `UST1-USTR` CR counts the UST1 leg only (USTR E/0%) unless a merged review documents a non-zero USTR credit. 4. Grep clean: no CoinGecko/CMC/CryptoRank calls under `frontend/src`; no quality multiply inside `usePrices` / `price.ts`; no symbol-only tier lookup; `CR_TIERS` untouched except comments pointing at the new doc. 5. Airplane-mode / API-pack down: treasury page still loads from the **committed** file; does not drop to 100% MTM CR and does not become `∞`. 6. `docs/ASSET_QUALITY.md` + playbook + skill exist; ECONOMICS links them and does not merge A–E into RED/YELLOW/GREEN/BLUE. 7. `cd frontend && npm test` — #11 / #14 fixtures pass at 10000 bps; new quality fixtures pass. 8. Script dry-run with fixtures produces a pack; live run (manual) does not modify `assetTiers.json`.
PlasticDigits commented 2026-08-24 06:12:59 +00:00 (Migrated from gitlab.com)

marked as related to #10

marked as related to #10
PlasticDigits commented 2026-08-24 06:13:00 +00:00 (Migrated from gitlab.com)

marked as related to #11

marked as related to #11
PlasticDigits commented 2026-08-24 06:15:14 +00:00 (Migrated from gitlab.com)

marked as related to #14

marked as related to #14
PlasticDigits commented 2026-08-27 10:46:29 +00:00 (Migrated from gitlab.com)

mentioned in issue #22

mentioned in issue #22
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#19
No description provided.