feat: /protocol vFDUSD tab — FDUSD reference price + Venus 1 vFDUSD rate #571

Closed
opened 2026-08-19 01:04:56 +00:00 by PlasticDigits · 21 comments
PlasticDigits commented 2026-08-19 01:04:56 +00:00 (Migrated from gitlab.com)

Summary

On /protocol, the vFDUSD oracle tab currently presents the indexer CEX FDUSD/USD snapshot as if it were a vFDUSD price (vFDUSD / USD heading + generic Reference price). Bundle both product fixes into one issue:

  1. Relabel that CEX snapshot FDUSD reference price.
  2. Add a distinct 1 vFDUSD Price section: how much FDUSD 1 vFDUSD redeems for via Venus (BSC Core Pool exchangeRateStored).

Do not split “rename the StatBox” vs “Venus exchange-rate poller” vs “docs/P550-9”. Related: #550 (unified oracle card + vFDUSD tab), #515 (ticker-scoped CEX oracle), #506 (UST1 window — different oracle), #489 (short retail copy).

Current codebase

/protocol already has a single CEX oracle card with USTC / LUNC / vFDUSD tabs (#550). The vFDUSD tab is a label lie: the indexer polls CEX FDUSD, then the UI titles it vFDUSD / USD. There is no Venus redeem rate anywhere on Protocol.

Layer Behavior today
Page ProtocolPage.tsx: Global stats → DEX hub prices → one ProtocolOracleCard → audit rows → hooks. ?ticker= allowlisted (ustc | lunc | vfdusd).
Oracle card ProtocolOracleCard.tsx: heading `${label} / USD` so vFDUSD → vFDUSD / USD. First StatBox label is always Reference price (formatProtocolOracleUsd(oracle.price_usd)). Sources table + recent history stay in the same card.
Ticker labels protocolOracleTicker.ts: tab text vfdusd: 'vFDUSD'. Unknown / fdusd / javascript: / ../ → ustc.
Indexer CEX feed indexer/src/indexer/oracle.rs OracleTicker::Vfdusd: MEXC FDUSDUSDT, CoinGecko first-digital-usd; KuCoin skipped (unlisted). Path /api/v1/oracle/price/vfdusd. X4: this feed is not used for DEX volume_usd. Invariant P550-9: labeled vFDUSD / USD, not $1, not the /ust1 window.
Token identity CW20 terra1mnl9azefrqpmu888ar2u6zrcwr80hxlt3avf4300r576cw5ar7esvxsvj3, registry name Venus FDUSD (bridged), 6 decimals (tokenRegistry.ts). This is a bridged Venus vToken, not naked FDUSD.
Venus (BSC) Not polled by this repo. Canonical Core Pool vFDUSD: 0xC4eF4229FEc74Ccfe17B2bdeF7715fAC740BA0ba. Underlying FDUSD: 0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409. exchangeRateStored() is a view (selector 0x182df0cd) returning mantissa 1e18; underlying_out = vToken_raw * exchangeRate / 1e18. exchangeRateCurrent() accrues interest (state-changing if sent as a tx) — do not send it from the indexer.
UST1 window ust1Window.ts effective_swap.oracle.rate is UST1 per vFDUSD (1e18 scale, ust1WindowMath.ts) for mint/redeem on /ust1. It is not a Protocol FDUSD redeem quote and includes window fee semantics. Ops already treat Venus/BSC integrity as the ust1-oracle pause trigger (ust1-wrap-production-ops.md).
CSP Production connect-src is env LCD/RPC/indexer + WC (viteCsp.ts). Browser must not call BSC RPC / BscScan / Venus HTTP.
Tests ProtocolPage.test.tsx asserts heading /vFDUSD \/ USD/i and getOraclePrice('vfdusd'). e2e/protocol-page.spec.ts P4 same heading. make verify-issue-550 greps those strings.
Docs docs/frontend.md P550-9; skills/AGENTS_FRONTEND_PROTOCOL_STATS.md; docs/runbooks/indexer-external-oracle.md.

Why this is needed

  1. vFDUSD ≠ FDUSD. The bridged asset is a Venus vToken whose redeem value vs FDUSD accrues. Showing CEX FDUSD/USD under a vFDUSD / USD heading teaches the wrong unit (the same class of mix-up #515 fixed for USTC vs LUNC).
  2. Operators and retail need both numbers. CEX FDUSD/USD is still useful as a reference. Venus exchangeRateStored answers the actual question: how much FDUSD does 1 vFDUSD redeem for. Neither number is the UST1 window rate.
  3. Hardcoding 1 vFDUSD = 1 FDUSD (or $1) is forbidden (P550-9 / X5). Without a Venus section, the UI either lies (current) or invites a fake peg.

Constraints / guardrails

  1. One card, vFDUSD tab only. Keep the existing USTC/LUNC/vFDUSD tablist. The Venus 1 vFDUSD Price block renders only when ticker === 'vfdusd'. USTC/LUNC keep today’s Reference price label and must not grow a Venus row.
  2. Copy (#489). Exact retail labels: FDUSD reference price (CEX StatBox on the vFDUSD tab) and 1 vFDUSD Price (new section heading). Value of the new section is N FDUSD (human FDUSD per 1 human vFDUSD), not $. Tab chip stays vFDUSD. Short “reference / Venus / not settlement” line is OK; no TWAP vs CEX vs window essay.
  3. Do not confuse oracles. CEX FDUSD/USD = indexer #515/#550 feed. Venus rate = BSC vToken redeem. UST1 window = /ust1 only. Pair TWAP = Charts. Do not substitute effective_swap.oracle.rate (UST1 units, window fees) for Venus FDUSD-out. Do not multiply CEX FDUSD/USD × Venus rate and headline it as the CEX “Reference price.”
  4. X1–X6 and X4 stay. Bare /api/v1/oracle/price remains a catalog. No fdusd path alias (still 400). Do not convert DEX volume_usd with FDUSD or Venus. Overview ustc_price_usd stays USTC.
  5. Advisory only (X5 / P550-11). Neither figure is settlement. Do not imply they price swaps, limits, or UST1 mint/redeem.
  6. Indexer owns BSC I/O. Poll Venus via indexer JSON-RPC eth_call to exchangeRateStored() on the pinned vFDUSD market. Soft-fail RPC errors (same pattern as CoinGecko 429). Cache + persist; do not live-scan BSC on every Protocol GET. Do not open BSC from the browser (CSP, RPC key leak, abuse). Pin the Core Pool address in code/docs; do not scrape Venus app HTML.
  7. Decimals. Convert with on-chain decimals() of vToken and underlying. Terra CW20 is 6dp; Venus vFDUSD is typically 8dp — display is 1 human vFDUSD, never raw-unit identity. Non-finite / zero / overflow → em-dash, never Infinity.
  8. Prefer exchangeRateStored (view). Do not send exchangeRateCurrent() as a transaction. If a call-static current rate is used, it must be read-only and documented.
  9. Allowlist / injection. Reuse parseProtocolOracleTicker. Never interpolate raw query strings into fetch URLs or eth_call payloads. Venus payload fields are numbers/strings — do not dangerouslySetInnerHTML.
  10. Outage. Venus failure must not blank the CEX FDUSD snapshot (and vice versa). Independent loading / retry / —. Market-data banner (#215) may include Venus outage on the vFDUSD tab only if it does not leak RPC URLs.
  11. Do not change factory/router audit rows, hub USD card (#556), USTC/LUNC history tables, /ust1 math, or swap settlement.

Relevant files

File Role
frontend-dapp/src/components/protocol/ProtocolOracleCard.tsx vFDUSD-only labels + 1 vFDUSD Price section
frontend-dapp/src/components/protocol/useProtocolOracleQueries.ts Wire Venus snapshot query (ticker-scoped; idle on ustc/lunc)
frontend-dapp/src/pages/ProtocolPage.tsx Pass-through / outage union if needed
frontend-dapp/src/pages/ProtocolPage.test.tsx RTL: labels, isolation from USTC/LUNC, mocks
frontend-dapp/e2e/protocol-page.spec.ts Playwright: vFDUSD tab copy + new section
frontend-dapp/src/services/indexer/client.ts Allowlisted client for Venus/vFDUSD extra fields
frontend-dapp/src/types/index.ts Response types
frontend-dapp/src/utils/protocolOracleTicker.ts Keep allowlist; optional label helpers
frontend-dapp/src/utils/formatProtocolStats.ts FDUSD-out formatter (em-dash non-finite)
indexer/src/indexer/oracle.rs Optional: keep CEX poll; do not relabel path vfdusd
indexer/src/api/oracle.rs Extend vfdusd snapshot or add a dedicated Venus route
new indexer Venus poller (suggested indexer/src/indexer/venus_vfdusd.rs) BSC eth_call + decimal conversion + cache
indexer/tests/api_oracle.rs API contract + unknown ticker 400
docs/frontend.md P550-9 + new Protocol invariants
docs/runbooks/indexer-external-oracle.md Venus vs CEX FDUSD
skills/AGENTS_FRONTEND_PROTOCOL_STATS.md Agent playbook
skills/AGENTS_INDEXER_EXTERNAL_ORACLE.md Do/don’t
scripts/qa/verify-issue-550.sh Stop requiring vFDUSD / USD heading; add this issue’s verify script
  1. UI (vFDUSD tab): Keep heading/tab vFDUSD (or vFDUSD / USD only if the card still clearly splits the two stats — prefer dropping / USD from the heading if it fights the new FDUSD-out section). First StatBox label FDUSD reference price bound to existing getOraclePrice('vfdusd'). Below sources (or beside the CEX row), a new data-testid="protocol-oracle-vfdusd-venus" section titled 1 vFDUSD Price showing format…(fdusd_per_vfdusd) + FDUSD, with source Venus and fetched time.
  2. API: Prefer additive JSON on GET /api/v1/oracle/price/vfdusd (e.g. venus: { fdusd_per_vfdusd, source, fetched_at, vtoken }) so the frontend does not grow a second ticker allowlist. USTC/LUNC responses omit venus or send null. Alternative: GET /api/v1/oracle/price/vfdusd/venus — still allowlisted, still 400 for unknown. Do not add path fdusd.
  3. Poller: Indexer loop: eth_call exchangeRateStored() (+ decimals / underlying once, cache). Config: BSC RPC URL(s) with failover; pin vToken 0xC4eF4229FEc74Ccfe17B2bdeF7715fAC740BA0ba. Soft-fail; persist last good sample like CEX oracle rows (distinct source, e.g. venus_bsc). LocalTerra/Postgres tests mock the RPC — do not require live BSC in CI.
  4. Docs/skills: Rewrite P550-9: CEX feed is FDUSD reference; Venus section is 1 vFDUSD → FDUSD. Keep “not $1, not UST1 window.” Add make verify-issue-<iid> (and a short skill if the poller is non-trivial).

Acceptance criteria

  • AC1. /protocol?ticker=vfdusd (or vFDUSD tab): CEX StatBox label is FDUSD reference price. Value is still indexer CEX FDUSD/USD (not hardcoded $1, not Venus).
  • AC2. Same tab: section 1 vFDUSD Price (protocol-oracle-vfdusd-venus) shows human FDUSD per 1 vFDUSD from Venus exchangeRateStored, with Venus source + time (or — + retry when missing).
  • AC3. USTC and LUNC tabs: label remains Reference price; 1 vFDUSD Price is absent; no Venus fetch required.
  • AC4. CEX outage does not hide a healthy Venus row; Venus outage does not hide a healthy CEX row.
  • AC5. ?ticker= allowlist unchanged (fdusd / XSS / ../ → ustc). No BSC hosts in production CSP connect-src.
  • AC6. X1–X6 / X4: catalog paths unchanged; no volume_usd conversion via FDUSD/Venus; no fdusd alias.
  • AC7. Docs/skills/P550-9 + make verify-issue-<iid> updated. #550 verify no longer asserts heading vFDUSD / USD if that string is removed.
  • AC8. #489: labels ≤ ~5 words; no architecture lecture on the card.

Test plan (all paths)

# Path Steps Expected
T1 Default USTC Open /protocol Reference price; no Venus section; getOraclePrice('ustc') only
T2 LUNC tab Click LUNC Heading LUNC; Reference price; no Venus section
T3 vFDUSD tab Click vFDUSD FDUSD reference price + 1 vFDUSD Price; CEX query vfdusd; Venus payload used
T4 Deep link /protocol?ticker=vfdusd Same as T3 without extra click
T5 Bad query ?ticker=fdusd, btc, javascript:…, ../vfdusd Falls back to USTC; no Venus UI
T6 CEX loading Delay getOraclePrice('vfdusd') CEX skeleton; Venus can still resolve
T7 Venus loading Delay Venus field/route Venus skeleton/—; CEX can still resolve
T8 CEX error 502 on CEX CEX retry; Venus intact
T9 Venus error RPC/indexer Venus miss Venus retry/—; CEX intact
T10 Venus zero/NaN/overflow Mock bad mantissa Em-dash; no crash
T11 Decimal fixture Known exchangeRateStored + decimals Human FDUSD-out matches hand calc (1 human vFDUSD)
T12 Keyboard tabs Arrow to vFDUSD Same as T3; a11y tablist unchanged
T13 History vFDUSD history table Still CEX USD history; not Venus samples unless explicitly designed (default: CEX-only history)
T14 Hub / stats Page order Global stats → hub → oracle → audit; hub card unchanged
T15 Indexer catalog GET /api/v1/oracle/price Still { tickers: [ustc,lunc,vfdusd] }; not a numeric body
T16 Unknown ticker API /price/fdusd 400
T17 LocalTerra/Postgres make setup-indexer-postgres; mock BSC Unit/integration pass without live BSC
T18 Playwright e2e/protocol-page.spec.ts + 5 workers vFDUSD tab labels; phone/tablet still usable
T19 #550 regression make verify-issue-550 Passes after heading-assert update
T20 Copy Inspect strings No “via the treasury wrap-mapper” / window-rate essay

Test plan (attack, hack, abuse)

# Vector Steps Expected
A1 Path injection ?ticker=../../oracle/price/vfdusd Allowlist → ustc; client never interpolates raw ticker into URL
A2 XSS ticker ?ticker=<img src=x onerror=alert(1)> ustc; no HTML in DOM from ticker
A3 XSS in Venus JSON source / vtoken with <script> Text nodes / allowlisted source label only
A4 Open redirect / javascript URL ticker javascript:alert(1) ustc; no navigation
A5 Browser BSC RPC Prod build network log on vFDUSD tab No eth_call / BscScan / publicnode BSC from the origin
A6 RPC key leak Indexer env BSC URL/keys stay server-side; not in Vite VITE_*
A7 eth_call amplification Burst Protocol reloads Indexer cache serves; poll interval bounded; no unbounded BSC fan-out
A8 Fake $1 peg Venus down + CEX ~1.00 UI must not invent 1.0 FDUSD/vFDUSD
A9 Cross-wire UST1 rate Compare /ust1 quote vs Protocol Venus Different units; Protocol must not paste window oracle.rate as FDUSD-out
A10 Cross-wire CEX into Venus Mock CEX 0.87, Venus 0.023 FDUSD reference price = $0.87; 1 vFDUSD Price = 0.023 FDUSD (not $0.87 and not 0.87 FDUSD)
A11 exchangeRateCurrent tx Indexer code review No signed/state-changing BSC txs; read-only eth_call
A12 SQL / ticker in DB ticker = vfdusd'; DROP Parameterized insert; enum/allowlist
A13 Volume USD abuse After deploy, inspect volume_usd ingest Still P522-Q + hub; not Venus/FDUSD
A14 CSP bypass Prod CSP No new https: blanket; no BSC host added “for convenience”
A15 Stale Venus as live Frozen poller Show fetched time; do not imply settlement; optional stale copy without leaking RPC

Verification criteria

make verify-issue-<iid>
# expected contents:
# - docs/skills: FDUSD reference price + 1 vFDUSD Price + Venus vToken pin
# - Protocol RTL + protocolOracleTicker + indexer oracle/Venus tests
# - playwright e2e-smoke protocol-page (5 workers) unless VERIFY_*_SKIP_E2E=1
make verify-issue-550   # regression
make verify-issue-515   # catalog / X1–X6

Ship is done when AC1–AC8 pass, T1–T20 and A1–A15 are recorded (or waived with reason), make verify-issue-<iid> is green on Cloud Agent (make setup-indexer-postgres; live BSC not required), and production CSP still does not call BSC from the browser.

## Summary On `/protocol`, the **vFDUSD** oracle tab currently presents the indexer CEX **FDUSD/USD** snapshot as if it were a vFDUSD price (`vFDUSD / USD` heading + generic **Reference price**). Bundle both product fixes into **one** issue: 1. Relabel that CEX snapshot **FDUSD reference price**. 2. Add a distinct **1 vFDUSD Price** section: how much **FDUSD** 1 vFDUSD redeems for **via Venus** (BSC Core Pool `exchangeRateStored`). Do **not** split “rename the StatBox” vs “Venus exchange-rate poller” vs “docs/P550-9”. Related: [#550](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/550) (unified oracle card + vFDUSD tab), [#515](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/515) (ticker-scoped CEX oracle), [#506](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/506) (UST1 window — different oracle), [#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489) (short retail copy). ## Current codebase `/protocol` already has a single CEX oracle card with USTC / LUNC / vFDUSD tabs ([#550](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/550)). The vFDUSD tab is a **label lie**: the indexer polls CEX **FDUSD**, then the UI titles it **vFDUSD / USD**. There is **no** Venus redeem rate anywhere on Protocol. | Layer | Behavior today | |-------|----------------| | **Page** | [`ProtocolPage.tsx`](frontend-dapp/src/pages/ProtocolPage.tsx): Global stats → DEX hub prices → **one** `ProtocolOracleCard` → audit rows → hooks. `?ticker=` allowlisted (`ustc` \| `lunc` \| `vfdusd`). | | **Oracle card** | [`ProtocolOracleCard.tsx`](frontend-dapp/src/components/protocol/ProtocolOracleCard.tsx): heading `` `${label} / USD` `` so vFDUSD → **vFDUSD / USD**. First StatBox label is always **Reference price** (`formatProtocolOracleUsd(oracle.price_usd)`). Sources table + recent history stay in the same card. | | **Ticker labels** | [`protocolOracleTicker.ts`](frontend-dapp/src/utils/protocolOracleTicker.ts): tab text `vfdusd: 'vFDUSD'`. Unknown / `fdusd` / `javascript:` / `../` → `ustc`. | | **Indexer CEX feed** | [`indexer/src/indexer/oracle.rs`](indexer/src/indexer/oracle.rs) `OracleTicker::Vfdusd`: MEXC `FDUSDUSDT`, CoinGecko `first-digital-usd`; KuCoin skipped (unlisted). Path `/api/v1/oracle/price/vfdusd`. **X4:** this feed is **not** used for DEX `volume_usd`. Invariant **P550-9:** labeled **vFDUSD / USD**, not `$1`, not the `/ust1` window. | | **Token identity** | CW20 `terra1mnl9azefrqpmu888ar2u6zrcwr80hxlt3avf4300r576cw5ar7esvxsvj3`, registry name **Venus FDUSD (bridged)**, **6** decimals ([`tokenRegistry.ts`](frontend-dapp/src/utils/tokenRegistry.ts)). This is a bridged Venus **vToken**, not naked FDUSD. | | **Venus (BSC)** | Not polled by this repo. Canonical Core Pool vFDUSD: [`0xC4eF4229FEc74Ccfe17B2bdeF7715fAC740BA0ba`](https://bscscan.com/address/0xC4eF4229FEc74Ccfe17B2bdeF7715fAC740BA0ba). Underlying FDUSD: `0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409`. `exchangeRateStored()` is a **view** (selector `0x182df0cd`) returning mantissa **1e18**; `underlying_out = vToken_raw * exchangeRate / 1e18`. `exchangeRateCurrent()` accrues interest (state-changing if sent as a tx) — do not send it from the indexer. | | **UST1 window** | [`ust1Window.ts`](frontend-dapp/src/services/terraclassic/ust1Window.ts) `effective_swap.oracle.rate` is **UST1 per vFDUSD** (1e18 scale, [`ust1WindowMath.ts`](frontend-dapp/src/utils/ust1WindowMath.ts)) for mint/redeem on `/ust1`. It is **not** a Protocol FDUSD redeem quote and includes window fee semantics. Ops already treat Venus/BSC integrity as the ust1-oracle pause trigger ([`ust1-wrap-production-ops.md`](docs/runbooks/ust1-wrap-production-ops.md)). | | **CSP** | Production `connect-src` is env LCD/RPC/indexer + WC ([`viteCsp.ts`](frontend-dapp/viteCsp.ts)). Browser must **not** call BSC RPC / BscScan / Venus HTTP. | | **Tests** | [`ProtocolPage.test.tsx`](frontend-dapp/src/pages/ProtocolPage.test.tsx) asserts heading `/vFDUSD \/ USD/i` and `getOraclePrice('vfdusd')`. [`e2e/protocol-page.spec.ts`](frontend-dapp/e2e/protocol-page.spec.ts) P4 same heading. `make verify-issue-550` greps those strings. | | **Docs** | [`docs/frontend.md`](docs/frontend.md) **P550-9**; [`skills/AGENTS_FRONTEND_PROTOCOL_STATS.md`](skills/AGENTS_FRONTEND_PROTOCOL_STATS.md); [`docs/runbooks/indexer-external-oracle.md`](docs/runbooks/indexer-external-oracle.md). | ## Why this is needed 1. **vFDUSD ≠ FDUSD.** The bridged asset is a Venus vToken whose redeem value vs FDUSD **accrues**. Showing CEX FDUSD/USD under a **vFDUSD / USD** heading teaches the wrong unit (the same class of mix-up #515 fixed for USTC vs LUNC). 2. **Operators and retail need both numbers.** CEX FDUSD/USD is still useful as a **reference**. Venus `exchangeRateStored` answers the actual question: **how much FDUSD does 1 vFDUSD redeem for**. Neither number is the UST1 window rate. 3. **Hardcoding 1 vFDUSD = 1 FDUSD (or $1) is forbidden** (**P550-9** / **X5**). Without a Venus section, the UI either lies (current) or invites a fake peg. ## Constraints / guardrails 1. **One card, vFDUSD tab only.** Keep the existing USTC/LUNC/vFDUSD tablist. The Venus **1 vFDUSD Price** block renders **only** when `ticker === 'vfdusd'`. USTC/LUNC keep today’s **Reference price** label and must **not** grow a Venus row. 2. **Copy (#489).** Exact retail labels: **FDUSD reference price** (CEX StatBox on the vFDUSD tab) and **1 vFDUSD Price** (new section heading). Value of the new section is **N FDUSD** (human FDUSD per 1 human vFDUSD), not `$`. Tab chip stays **vFDUSD**. Short “reference / Venus / not settlement” line is OK; no TWAP vs CEX vs window essay. 3. **Do not confuse oracles.** CEX FDUSD/USD = indexer `#515/#550` feed. Venus rate = BSC vToken redeem. UST1 window = `/ust1` only. Pair TWAP = Charts. Do **not** substitute `effective_swap.oracle.rate` (UST1 units, window fees) for Venus FDUSD-out. Do **not** multiply CEX FDUSD/USD × Venus rate and headline it as the CEX “Reference price.” 4. **X1–X6 and X4 stay.** Bare `/api/v1/oracle/price` remains a catalog. No `fdusd` path alias (still **400**). Do **not** convert DEX `volume_usd` with FDUSD or Venus. Overview `ustc_price_usd` stays USTC. 5. **Advisory only (X5 / P550-11).** Neither figure is settlement. Do not imply they price swaps, limits, or UST1 mint/redeem. 6. **Indexer owns BSC I/O.** Poll Venus via indexer JSON-RPC `eth_call` to `exchangeRateStored()` on the pinned vFDUSD market. Soft-fail RPC errors (same pattern as CoinGecko 429). Cache + persist; do not live-scan BSC on every Protocol GET. Do **not** open BSC from the browser (CSP, RPC key leak, abuse). Pin the Core Pool address in code/docs; do not scrape Venus app HTML. 7. **Decimals.** Convert with on-chain `decimals()` of vToken **and** underlying. Terra CW20 is 6dp; Venus vFDUSD is typically 8dp — display is **1 human vFDUSD**, never raw-unit identity. Non-finite / zero / overflow → em-dash, never `Infinity`. 8. **Prefer `exchangeRateStored` (view).** Do not send `exchangeRateCurrent()` as a transaction. If a call-static current rate is used, it must be read-only and documented. 9. **Allowlist / injection.** Reuse `parseProtocolOracleTicker`. Never interpolate raw query strings into fetch URLs or `eth_call` payloads. Venus payload fields are numbers/strings — do not `dangerouslySetInnerHTML`. 10. **Outage.** Venus failure must not blank the CEX FDUSD snapshot (and vice versa). Independent loading / retry / `—`. Market-data banner (#215) may include Venus outage on the vFDUSD tab only if it does not leak RPC URLs. 11. **Do not change** factory/router audit rows, hub USD card (#556), USTC/LUNC history tables, `/ust1` math, or swap settlement. ## Relevant files | File | Role | |------|------| | [`frontend-dapp/src/components/protocol/ProtocolOracleCard.tsx`](frontend-dapp/src/components/protocol/ProtocolOracleCard.tsx) | vFDUSD-only labels + **1 vFDUSD Price** section | | [`frontend-dapp/src/components/protocol/useProtocolOracleQueries.ts`](frontend-dapp/src/components/protocol/useProtocolOracleQueries.ts) | Wire Venus snapshot query (ticker-scoped; idle on ustc/lunc) | | [`frontend-dapp/src/pages/ProtocolPage.tsx`](frontend-dapp/src/pages/ProtocolPage.tsx) | Pass-through / outage union if needed | | [`frontend-dapp/src/pages/ProtocolPage.test.tsx`](frontend-dapp/src/pages/ProtocolPage.test.tsx) | RTL: labels, isolation from USTC/LUNC, mocks | | [`frontend-dapp/e2e/protocol-page.spec.ts`](frontend-dapp/e2e/protocol-page.spec.ts) | Playwright: vFDUSD tab copy + new section | | [`frontend-dapp/src/services/indexer/client.ts`](frontend-dapp/src/services/indexer/client.ts) | Allowlisted client for Venus/vFDUSD extra fields | | [`frontend-dapp/src/types/index.ts`](frontend-dapp/src/types/index.ts) | Response types | | [`frontend-dapp/src/utils/protocolOracleTicker.ts`](frontend-dapp/src/utils/protocolOracleTicker.ts) | Keep allowlist; optional label helpers | | [`frontend-dapp/src/utils/formatProtocolStats.ts`](frontend-dapp/src/utils/formatProtocolStats.ts) | FDUSD-out formatter (em-dash non-finite) | | [`indexer/src/indexer/oracle.rs`](indexer/src/indexer/oracle.rs) | Optional: keep CEX poll; do not relabel path `vfdusd` | | [`indexer/src/api/oracle.rs`](indexer/src/api/oracle.rs) | Extend vfdusd snapshot **or** add a dedicated Venus route | | new indexer Venus poller (suggested `indexer/src/indexer/venus_vfdusd.rs`) | BSC `eth_call` + decimal conversion + cache | | [`indexer/tests/api_oracle.rs`](indexer/tests/api_oracle.rs) | API contract + unknown ticker 400 | | [`docs/frontend.md`](docs/frontend.md) | **P550-9** + new Protocol invariants | | [`docs/runbooks/indexer-external-oracle.md`](docs/runbooks/indexer-external-oracle.md) | Venus vs CEX FDUSD | | [`skills/AGENTS_FRONTEND_PROTOCOL_STATS.md`](skills/AGENTS_FRONTEND_PROTOCOL_STATS.md) | Agent playbook | | [`skills/AGENTS_INDEXER_EXTERNAL_ORACLE.md`](skills/AGENTS_INDEXER_EXTERNAL_ORACLE.md) | Do/don’t | | [`scripts/qa/verify-issue-550.sh`](scripts/qa/verify-issue-550.sh) | Stop requiring `vFDUSD / USD` heading; add this issue’s verify script | ## Recommended direction 1. **UI (vFDUSD tab):** Keep heading/tab **vFDUSD** (or **vFDUSD / USD** only if the card still clearly splits the two stats — prefer dropping `/ USD` from the heading if it fights the new FDUSD-out section). First StatBox label **FDUSD reference price** bound to existing `getOraclePrice('vfdusd')`. Below sources (or beside the CEX row), a new `data-testid="protocol-oracle-vfdusd-venus"` section titled **1 vFDUSD Price** showing `format…(fdusd_per_vfdusd)` + ` FDUSD`, with source **Venus** and fetched time. 2. **API:** Prefer **additive JSON** on `GET /api/v1/oracle/price/vfdusd` (e.g. `venus: { fdusd_per_vfdusd, source, fetched_at, vtoken }`) so the frontend does not grow a second ticker allowlist. USTC/LUNC responses omit `venus` or send `null`. Alternative: `GET /api/v1/oracle/price/vfdusd/venus` — still allowlisted, still 400 for unknown. Do **not** add path `fdusd`. 3. **Poller:** Indexer loop: `eth_call` `exchangeRateStored()` (+ `decimals` / `underlying` once, cache). Config: BSC RPC URL(s) with failover; pin vToken `0xC4eF4229FEc74Ccfe17B2bdeF7715fAC740BA0ba`. Soft-fail; persist last good sample like CEX oracle rows (distinct `source`, e.g. `venus_bsc`). LocalTerra/Postgres tests mock the RPC — do not require live BSC in CI. 4. **Docs/skills:** Rewrite **P550-9**: CEX feed is **FDUSD reference**; Venus section is **1 vFDUSD → FDUSD**. Keep “not `$1`, not UST1 window.” Add `make verify-issue-<iid>` (and a short skill if the poller is non-trivial). ## Acceptance criteria - [ ] **AC1.** `/protocol?ticker=vfdusd` (or vFDUSD tab): CEX StatBox label is **FDUSD reference price**. Value is still indexer CEX FDUSD/USD (not hardcoded `$1`, not Venus). - [ ] **AC2.** Same tab: section **1 vFDUSD Price** (`protocol-oracle-vfdusd-venus`) shows human **FDUSD per 1 vFDUSD** from Venus `exchangeRateStored`, with Venus source + time (or `—` + retry when missing). - [ ] **AC3.** USTC and LUNC tabs: label remains **Reference price**; **1 vFDUSD Price** is absent; no Venus fetch required. - [ ] **AC4.** CEX outage does not hide a healthy Venus row; Venus outage does not hide a healthy CEX row. - [ ] **AC5.** `?ticker=` allowlist unchanged (`fdusd` / XSS / `../` → `ustc`). No BSC hosts in production CSP `connect-src`. - [ ] **AC6.** X1–X6 / X4: catalog paths unchanged; no `volume_usd` conversion via FDUSD/Venus; no `fdusd` alias. - [ ] **AC7.** Docs/skills/P550-9 + `make verify-issue-<iid>` updated. `#550` verify no longer asserts heading `vFDUSD / USD` if that string is removed. - [ ] **AC8.** `#489`: labels ≤ ~5 words; no architecture lecture on the card. ## Test plan (all paths) | # | Path | Steps | Expected | |---|------|-------|----------| | T1 | Default USTC | Open `/protocol` | **Reference price**; no Venus section; `getOraclePrice('ustc')` only | | T2 | LUNC tab | Click LUNC | Heading LUNC; **Reference price**; no Venus section | | T3 | vFDUSD tab | Click vFDUSD | **FDUSD reference price** + **1 vFDUSD Price**; CEX query `vfdusd`; Venus payload used | | T4 | Deep link | `/protocol?ticker=vfdusd` | Same as T3 without extra click | | T5 | Bad query | `?ticker=fdusd`, `btc`, `javascript:…`, `../vfdusd` | Falls back to USTC; no Venus UI | | T6 | CEX loading | Delay `getOraclePrice('vfdusd')` | CEX skeleton; Venus can still resolve | | T7 | Venus loading | Delay Venus field/route | Venus skeleton/`—`; CEX can still resolve | | T8 | CEX error | 502 on CEX | CEX retry; Venus intact | | T9 | Venus error | RPC/indexer Venus miss | Venus retry/`—`; CEX intact | | T10 | Venus zero/NaN/overflow | Mock bad mantissa | Em-dash; no crash | | T11 | Decimal fixture | Known `exchangeRateStored` + decimals | Human FDUSD-out matches hand calc (1 human vFDUSD) | | T12 | Keyboard tabs | Arrow to vFDUSD | Same as T3; a11y tablist unchanged | | T13 | History | vFDUSD history table | Still CEX USD history; not Venus samples unless explicitly designed (default: CEX-only history) | | T14 | Hub / stats | Page order | Global stats → hub → oracle → audit; hub card unchanged | | T15 | Indexer catalog | `GET /api/v1/oracle/price` | Still `{ tickers: [ustc,lunc,vfdusd] }`; not a numeric body | | T16 | Unknown ticker API | `/price/fdusd` | **400** | | T17 | LocalTerra/Postgres | `make setup-indexer-postgres`; mock BSC | Unit/integration pass without live BSC | | T18 | Playwright | `e2e/protocol-page.spec.ts` + 5 workers | vFDUSD tab labels; phone/tablet still usable | | T19 | `#550` regression | `make verify-issue-550` | Passes after heading-assert update | | T20 | Copy | Inspect strings | No “via the treasury wrap-mapper” / window-rate essay | ## Test plan (attack, hack, abuse) | # | Vector | Steps | Expected | |---|--------|-------|----------| | A1 | Path injection | `?ticker=../../oracle/price/vfdusd` | Allowlist → `ustc`; client never interpolates raw ticker into URL | | A2 | XSS ticker | `?ticker=<img src=x onerror=alert(1)>` | `ustc`; no HTML in DOM from ticker | | A3 | XSS in Venus JSON | `source` / `vtoken` with `<script>` | Text nodes / allowlisted source label only | | A4 | Open redirect / javascript URL | ticker `javascript:alert(1)` | `ustc`; no navigation | | A5 | Browser BSC RPC | Prod build network log on vFDUSD tab | No `eth_call` / BscScan / publicnode BSC from the origin | | A6 | RPC key leak | Indexer env | BSC URL/keys stay server-side; not in Vite `VITE_*` | | A7 | `eth_call` amplification | Burst Protocol reloads | Indexer cache serves; poll interval bounded; no unbounded BSC fan-out | | A8 | Fake `$1` peg | Venus down + CEX ~1.00 | UI must not invent 1.0 FDUSD/vFDUSD | | A9 | Cross-wire UST1 rate | Compare `/ust1` quote vs Protocol Venus | Different units; Protocol must not paste window `oracle.rate` as FDUSD-out | | A10 | Cross-wire CEX into Venus | Mock CEX `0.87`, Venus `0.023` | **FDUSD reference price** = $0.87; **1 vFDUSD Price** = 0.023 FDUSD (not $0.87 and not 0.87 FDUSD) | | A11 | `exchangeRateCurrent` tx | Indexer code review | No signed/state-changing BSC txs; read-only `eth_call` | | A12 | SQL / ticker in DB | `ticker` = `vfdusd'; DROP` | Parameterized insert; enum/allowlist | | A13 | Volume USD abuse | After deploy, inspect `volume_usd` ingest | Still P522-Q + hub; not Venus/FDUSD | | A14 | CSP bypass | Prod CSP | No new `https:` blanket; no BSC host added “for convenience” | | A15 | Stale Venus as live | Frozen poller | Show fetched time; do not imply settlement; optional stale copy without leaking RPC | ## Verification criteria ```bash make verify-issue-<iid> # expected contents: # - docs/skills: FDUSD reference price + 1 vFDUSD Price + Venus vToken pin # - Protocol RTL + protocolOracleTicker + indexer oracle/Venus tests # - playwright e2e-smoke protocol-page (5 workers) unless VERIFY_*_SKIP_E2E=1 make verify-issue-550 # regression make verify-issue-515 # catalog / X1–X6 ``` Ship is done when AC1–AC8 pass, T1–T20 and A1–A15 are recorded (or waived with reason), `make verify-issue-<iid>` is green on Cloud Agent (`make setup-indexer-postgres`; live BSC not required), and production CSP still does not call BSC from the browser.
PlasticDigits commented 2026-08-19 01:04:57 +00:00 (Migrated from gitlab.com)

marked as related to #550

marked as related to #550
PlasticDigits commented 2026-08-19 01:04:58 +00:00 (Migrated from gitlab.com)

marked as related to #515

marked as related to #515
PlasticDigits commented 2026-08-19 01:04:59 +00:00 (Migrated from gitlab.com)

marked as related to #506

marked as related to #506
PlasticDigits commented 2026-08-19 11:49:49 +00:00 (Migrated from gitlab.com)

mentioned in issue #574

mentioned in issue #574
PlasticDigits commented 2026-08-19 11:49:57 +00:00 (Migrated from gitlab.com)

mentioned in issue #575

mentioned in issue #575
PlasticDigits commented 2026-08-19 12:11:27 +00:00 (Migrated from gitlab.com)

mentioned in issue #580

mentioned in issue #580
PlasticDigits commented 2026-08-19 12:11:28 +00:00 (Migrated from gitlab.com)

marked as related to #580

marked as related to #580
PlasticDigits commented 2026-08-20 01:04:21 +00:00 (Migrated from gitlab.com)

mentioned in issue #572

mentioned in issue #572
PlasticDigits commented 2026-08-20 02:19:42 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1103

mentioned in merge request !1103
PlasticDigits commented 2026-08-20 02:21:34 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1104

mentioned in merge request !1104
PlasticDigits commented 2026-08-20 02:28:14 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1105

mentioned in merge request !1105
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-20 03:42:20 +00:00
PlasticDigits commented 2026-08-20 03:52:06 +00:00 (Migrated from gitlab.com)

mentioned in commit 9c802c1aac

mentioned in commit 9c802c1aac8278a64f02559d6434697d46d1a4a3
PlasticDigits commented 2026-08-20 03:54:28 +00:00 (Migrated from gitlab.com)

Merged via !1105.

make verify-issue-571 passed (docs, Venus lib + api_oracle, Protocol RTL, Playwright protocol-page 5 workers). Remaining production:

  • Set indexer BSC_RPC_URLS (never VITE_*); confirm Protocol vFDUSD tab shows CEX FDUSD reference and Venus 1 vFDUSD Price
  • Confirm no exchangeRateCurrent transaction and no /price/fdusd alias
Merged via !1105. `make verify-issue-571` passed (docs, Venus lib + `api_oracle`, Protocol RTL, Playwright protocol-page 5 workers). Remaining production: - Set indexer `BSC_RPC_URLS` (never `VITE_*`); confirm Protocol vFDUSD tab shows CEX FDUSD reference and Venus **1 vFDUSD Price** - Confirm no `exchangeRateCurrent` transaction and no `/price/fdusd` alias
PlasticDigits commented 2026-08-20 03:54:30 +00:00 (Migrated from gitlab.com)

mentioned in issue #570

mentioned in issue #570
PlasticDigits commented 2026-08-20 03:54:51 +00:00 (Migrated from gitlab.com)

mentioned in issue #583

mentioned in issue #583
PlasticDigits commented 2026-08-20 03:54:57 +00:00 (Migrated from gitlab.com)

marked as related to #583

marked as related to #583
PlasticDigits commented 2026-08-21 11:32:37 +00:00 (Migrated from gitlab.com)

mentioned in issue #588

mentioned in issue #588
PlasticDigits commented 2026-08-21 11:32:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #509

mentioned in issue #509
PlasticDigits commented 2026-08-21 11:32:52 +00:00 (Migrated from gitlab.com)

mentioned in issue #511

mentioned in issue #511
PlasticDigits commented 2026-08-24 00:30:20 +00:00 (Migrated from gitlab.com)

mentioned in issue #614

mentioned in issue #614
PlasticDigits commented 2026-08-26 01:11:06 +00:00 (Migrated from gitlab.com)

mentioned in issue #653

mentioned in issue #653
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/cl8y-dex-terraclassic#571
No description provided.