feat: /protocol UST1 window mint/redeem treasury fees #614

Closed
opened 2026-08-24 00:30:18 +00:00 by PlasticDigits · 24 comments
PlasticDigits commented 2026-08-24 00:30:18 +00:00 (Migrated from gitlab.com)

Summary

Add UST1 window mint/redeem treasury fees to the /protocol fee census — the same panel that already shows AMM swap and Book take. #586 explicitly left this out of scope (different contract). Production indexer has no UST1_WINDOW_ADDRESS pin, no FeeSource for the window, and no ingest.

Bundle deposit (vFDUSD→UST1 mint) and withdraw (UST1→vFDUSD redeem) into one issue. Do not split “pin env” vs “parser” vs “migration CHECK” vs “UI label” vs “mint vs redeem”.

Related: #586 (follow-up named in Constraints §14), #506 (/ust1 UI), #503 (ops / inventory), #550 (P550-11: CEX oracle ≠ UST1 window), #568 (stamp USD at ingest), #515 / #580 (never convert DEX fees with vFDUSD/FDUSD). Wrap/unwrap cUSTC/cLUNC ingest is a separate issue (mapper already in the enum; window is not).

Current codebase

Layer Behavior today
Window product /ust1 mints/redeems UST1 ↔ vFDUSD via CW20 Send to ust1-window — never the AMM router (ust1Window.ts, AGENTS_UST1_WINDOW_UI.md). Deposit hook { deposit: {} }; withdraw { withdraw: { min_vfdusd_out } }.
Fee On-chain effective_swap.fee_bps (approved target 100 = 1% on UST1 notional — ust1WindowMath.ts INV-SWAP-001/002). Goes to CMM treasury (cmm_treasury on window config). Columbus-5 window: terra1zxwpzpzpleatqn39r00grau4yt29sld8pw78s7ktvjafnj5nsaxq0h3rh2 (code 11566, REGISTRY.md).
Fee enum FeeSource is only swap_amm / book_take / limit_place / wrap / unwrap. SQL CHECK on protocol_fee_events and protocol_fee_stats_by_source matches. No ust1_* key.
Indexer env config.rs has WRAP_MAPPER_ADDRESS only. Grep of indexer/ has zero UST1_WINDOW. indexer/.env.example does not document a window pin.
UI ProtocolFeeStats.tsx SOURCE_LABEL / SOURCE_ORDER have no window row. Lead copy says “swaps, book takes, limit places, and wrap.” ProtocolFeeSourceKey is the five #586 keys. CEX oracle card explicitly not the UST1 window rate (P550-11).
Live (2026-08-24) Protocol fees Source is AMM + book (+ idle wrap/unwrap hidden). Window mint/redeem fees do not enter 24h/7d/30d totals.
#586 decision Constraints §14: “ust1-window mint/redeem fees (different contract; follow-up).”

Why this is needed

  1. Window fee is protocol / CMM income. Approved 1% on UST1 notional is the same class of treasury inflow as wrap-mapper fee_amount and pair commission_amount. /protocol is the USD census; omitting the window undercounts CMM.
  2. It is not a wrap. Different contract, different actions (deposit / withdraw via CW20 Send), different pin, different fee math. Folding it into wrap/unwrap would lie to retail and break the mapper pin.
  3. It is not an AMM swap. Secondary UST1 pools already contribute swap_amm. Counting window mint as a swap would mix oracle mint volume with pool commission.
  4. #586 left a documented hole. Operators who compare /ust1 fee (1%) to Protocol totals will think the indexer is wrong.

Constraints / guardrails

  1. New pin, same pin rules as wrap. UST1_WINDOW_ADDRESS: trim, reject whitespace / wrong HRP / LIKE %, exact terra1 equality on reserved _contract_address only (#285). Empty / garbage → omit window sources (not fake idle $0). Do not reuse WRAP_MAPPER_ADDRESS.
  2. Fail closed on amount + token. Lock wasm attr names from a captured columbus-5 or LocalTerra window tx (and/or ust1-window crate). Require an explicit treasury fee amount. Never infer ust1_notional × fee_bps. Missing identity → drop.
  3. Retail source labels (not wasm strings). Prefer UST1 mint / UST1 redeem (or UST1 window as one row if captured fees cannot be split). Do not show deposit / withdraw / effective_swap. Hide idle $0 (PFee-3).
  4. USD catalog. Fee token identity from attrs (likely UST1 notional). Price with hub UST1 (H1–H10 / PFee-7). Never vFDUSD/FDUSD CEX. Never $1 UST1 peg. Never 2.5× USTR. Stamp fee_usd at ingest (#568).
  5. Not these amounts: oracle rate spread, Venus redeem, Classic burn tax, gas, min_vfdusd_out slippage haircut, paused/stale reject, rolling 24h unused capacity.
  6. GET stays O(1). Additive overview scalars; breakdown via existing /protocol/fees rollup. Migration must widen CHECK constraints (ust1_mint / ust1_redeem or the chosen keys). FeeSource::parse stays allowlist — unknown / javascript: rejected.
  7. window= query param stays 24h | 7d | 30d (400 else). Do not overload that word with “ust1-window”.
  8. Cardinality. Fixed enum + top-8 tokens + other. No per-user window tape on /protocol.
  9. Do not bind-mount indexer/ into root Docker for cargo.
  10. Out of scope. Changing fee_bps. Soft-launch /mint faucet. Wrap-mapper ingest (separate issue). Pair-creation uluna. Marketing the CEX vFDUSD tab as the window rate.

Relevant files

Path Role
indexer/src/indexer/protocol_fees.rs New source(s), pin helper, parse_*_window_fees
indexer/src/indexer/parser.rs Call ingest beside wrap; flattened-event scan if LCD merges CW20 send + window hook
indexer/src/config.rs + indexer/.env.example UST1_WINDOW_ADDRESS
indexer/migrations/ Widen protocol_fee_events_source_chk + protocol_fee_stats_by_source_source_chk; optional ust1_window_configured on global_stats_24h (mirror wrap_mapper_configured)
indexer/src/db/queries/protocol_fees.rs Rollup omit when window unconfigured
indexer/src/api/protocol_fees.rs Expose ust1_window_configured (additive JSON)
indexer/src/indexer/volume_aggregator.rs Pass configured flag into refresh
frontend-dapp/src/types/index.ts ProtocolFeeSourceKey + response flag
frontend-dapp/src/components/protocol/ProtocolFeeStats.tsx Labels, order, omit-if-unconfigured, lead copy
frontend-dapp/src/pages/ProtocolPage.test.tsx Source table
frontend-dapp/src/services/terraclassic/ust1Window.ts Execute shape for fixtures
skills/AGENTS_FRONTEND_PROTOCOL_STATS.md New PFee / extend PFee-3/PFee-6
skills/AGENTS_UST1_WINDOW_UI.md Crosslink (do not overload /mint)
docs/indexer-invariants.md, docs/frontend.md § Protocol, docs/runbooks/overview-global-stats-brin.md Docs
deployments/mainnet-ust1-wrap/coolify.env.example Indexer Coolify pin (not only Vite)
Upstream ust1-window effective_swap / wasm attrs
  1. Capture window wasm first (LocalTerra ust1-window smoke or columbus-5 deposit + withdraw). Lock action, fee amount key, token key, and whether mint vs redeem are distinct. Do not invent attrs from fee_bps alone.
  2. Pin + parse beside wrap: parse_ust1_window_address / parse_ust1_window_fees. Flattened CW20 send + hook must still attribute the fee to the window _contract_address, not the UST1/vFDUSD token contract.
  3. Sources. If attrs split cleanly: ust1_mint + ust1_redeem with labels UST1 mint / UST1 redeem. If not: one ust1_window row. Put them in SOURCE_ORDER after unwrap, before AMM swap (or after wrap family — keep wrap/window grouped).
  4. Migration widens CHECKs; add ust1_window_configured (or a generic sources_configured object — prefer the wrap mirror for one flag). Additive GET JSON: old indexers without the flag → hide window rows, do not invent $0 (PFee-10).
  5. Coolify. Document UST1_WINDOW_ADDRESS on the indexer service (Vite VITE_UST1_WINDOW_ADDRESS does not feed the indexer).
  6. Verify script make verify-issue-<iid>: docs, CHECK + parse allowlist, omit-if-unconfigured, fixture ingest, UI labels, no GET scan, no vFDUSD conversion.

Acceptance criteria

  • UST1_WINDOW_ADDRESS parsed like the wrap pin; invalid/empty omits window sources.
  • Deposit and withdraw fixtures (or one captured + one synthetic matching locked attrs) insert protocol_fee_events with the new source(s), positive amount_raw, and hub-stamped fee_usd when UST1 is priced.
  • No row when fee amount / token attrs are missing (no × fee_bps).
  • Spoof window-shaped wasm from a non-pinned contract is ignored.
  • Overview 24h/7d/30d totals include priced window fees; Δ% rules unchanged (then ≤ 0 → null).
  • /protocol Source shows retail UST1 mint / UST1 redeem (or the chosen single label) when events exist; idle $0 hidden; unconfigured omitted.
  • CEX oracle card / hub card copy still does not claim to be the window rate.
  • GET /overview and /protocol/fees stay O(1); window= allowlist unchanged.
  • make verify-issue-586 still passes. New make verify-issue-<iid> is the gate.

Test plan (happy + edge)

Path Expect
vFDUSD send { deposit } ust1_mint (or bundled source); fee token from attrs; USD via hub UST1 if that is the fee asset
UST1 send { withdraw } ust1_redeem; slippage min_vfdusd_out is not the fee
Unconfigured window env Sources omitted; AMM/book/wrap unchanged
Configured + zero events Hidden idle $0; totals "0"
UST1 hub down Event stored; fee_usd NULL; activity+all-unpriced → —
Replay ON CONFLICT no double-count
Flattened wasm (token send + window hook + cw20 mint) One fee row scoped to window contract
Additive JSON (old indexer) Missing flag → no window rows, no invented $0
?ticker=vfdusd on /protocol Fee panel ignores ticker (PFee-10)
--fresh Δ% empty until 2×W

Test plan (attack / abuse)

Vector Guard
Attacker contract emits action=deposit + huge fee_amount Pin on _contract_address
Forged contract_address (no underscore) Ignored (#285)
Infer fee from amount × 100 when attr missing No row
Price window fee with CEX vFDUSD / $1 UST1 / 2.5× USTR Forbidden — tests use hub catalog only
Count unused rolling 24h limit or pause as a fee Forbidden
Count Venus exchangeRateCurrent as treasury Forbidden (#571 is display-only)
XSS / javascript: in symbol Text only (PFee-4)
source=javascript: in rollup FeeSource::parse drops
window=30d); DROP TABLE 400 allowlist
Widen CHECK but forget rollup enum → insert panic Migration + FeeSource::ALL + UI key updated together
Double-count window fee as swap_amm (user later sells UST1 on AMM) Window fee is window-only; later swap is swap_amm only
Bind-mount indexer + root cargo Do not; host tests
GET scans protocol_fee_events for the new sources Forbidden

Verification criteria

make setup-indexer-postgres
make verify-issue-586
make verify-issue-<iid>
# optional chain:
make setup-cloud-localterra
# deposit + withdraw against LocalTerra window (or documented columbus-5 hashes)

Close only when Coolify indexer env documents the pin, a captured-or-LocalTerra tx increments the new source event_count, USD uses hub/catalog (not vFDUSD/$1), unconfigured omit works, and #586 verify still passes. UI-only labels without ingest are not done.

## Summary Add **UST1 window** mint/redeem treasury fees to the `/protocol` fee census — the same panel that already shows **AMM swap** and **Book take**. #586 explicitly left this **out of scope** (different contract). Production indexer has **no** `UST1_WINDOW_ADDRESS` pin, **no** `FeeSource` for the window, and **no** ingest. Bundle **deposit (vFDUSD→UST1 mint)** and **withdraw (UST1→vFDUSD redeem)** into **one** issue. Do **not** split “pin env” vs “parser” vs “migration CHECK” vs “UI label” vs “mint vs redeem”. Related: [#586](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/586) (follow-up named in Constraints §14), [#506](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/506) (`/ust1` UI), [#503](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/503) (ops / inventory), [#550](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/550) (**P550-11**: CEX oracle ≠ UST1 window), [#568](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/568) (stamp USD at ingest), [#515](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/515) / [#580](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/580) (never convert DEX fees with vFDUSD/FDUSD). Wrap/unwrap cUSTC/cLUNC ingest is a **separate** issue (mapper already in the enum; window is not). ## Current codebase | Layer | Behavior today | |-------|----------------| | **Window product** | `/ust1` mints/redeems UST1 ↔ vFDUSD via CW20 `Send` to **ust1-window** — never the AMM router ([`ust1Window.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/ust1Window.ts), [`AGENTS_UST1_WINDOW_UI.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_UST1_WINDOW_UI.md)). Deposit hook `{ deposit: {} }`; withdraw `{ withdraw: { min_vfdusd_out } }`. | | **Fee** | On-chain `effective_swap.fee_bps` (approved target **100** = 1% on **UST1 notional** — [`ust1WindowMath.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/ust1WindowMath.ts) INV-SWAP-001/002). Goes to CMM treasury (`cmm_treasury` on window config). Columbus-5 window: `terra1zxwpzpzpleatqn39r00grau4yt29sld8pw78s7ktvjafnj5nsaxq0h3rh2` (code **11566**, [`REGISTRY.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/deployments/mainnet-ust1-wrap/REGISTRY.md)). | | **Fee enum** | [`FeeSource`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/indexer/protocol_fees.rs) is only `swap_amm` / `book_take` / `limit_place` / `wrap` / `unwrap`. SQL CHECK on `protocol_fee_events` and `protocol_fee_stats_by_source` matches. No `ust1_*` key. | | **Indexer env** | [`config.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/config.rs) has `WRAP_MAPPER_ADDRESS` only. Grep of `indexer/` has **zero** `UST1_WINDOW`. [`indexer/.env.example`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/.env.example) does not document a window pin. | | **UI** | [`ProtocolFeeStats.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/protocol/ProtocolFeeStats.tsx) `SOURCE_LABEL` / `SOURCE_ORDER` have no window row. Lead copy says “swaps, book takes, limit places, and wrap.” [`ProtocolFeeSourceKey`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/types/index.ts) is the five #586 keys. CEX oracle card explicitly **not** the UST1 window rate (**P550-11**). | | **Live (2026-08-24)** | Protocol fees Source is AMM + book (+ idle wrap/unwrap hidden). Window mint/redeem fees do not enter 24h/7d/30d totals. | | **#586 decision** | Constraints §14: “ust1-window mint/redeem fees (different contract; follow-up).” | ## Why this is needed 1. **Window fee is protocol / CMM income.** Approved 1% on UST1 notional is the same class of treasury inflow as wrap-mapper `fee_amount` and pair `commission_amount`. `/protocol` is the USD census; omitting the window undercounts CMM. 2. **It is not a wrap.** Different contract, different actions (`deposit` / `withdraw` via CW20 Send), different pin, different fee math. Folding it into `wrap`/`unwrap` would lie to retail and break the mapper pin. 3. **It is not an AMM swap.** Secondary UST1 pools already contribute `swap_amm`. Counting window mint as a swap would mix oracle mint volume with pool commission. 4. **#586 left a documented hole.** Operators who compare `/ust1` fee (1%) to Protocol totals will think the indexer is wrong. ## Constraints / guardrails 1. **New pin, same pin rules as wrap.** `UST1_WINDOW_ADDRESS`: trim, reject whitespace / wrong HRP / `LIKE %`, exact `terra1` equality on reserved `_contract_address` only (#285). Empty / garbage → **omit** window sources (not fake idle `$0`). Do not reuse `WRAP_MAPPER_ADDRESS`. 2. **Fail closed on amount + token.** Lock wasm attr names from a **captured** columbus-5 or LocalTerra window tx (and/or ust1-window crate). Require an explicit treasury fee amount. **Never** infer `ust1_notional × fee_bps`. Missing identity → drop. 3. **Retail source labels (not wasm strings).** Prefer **UST1 mint** / **UST1 redeem** (or **UST1 window** as one row if captured fees cannot be split). Do not show `deposit` / `withdraw` / `effective_swap`. Hide idle `$0` (**PFee-3**). 4. **USD catalog.** Fee token identity from attrs (likely UST1 notional). Price with **hub UST1** (**H1–H10** / **PFee-7**). **Never** vFDUSD/FDUSD CEX. **Never** `$1` UST1 peg. **Never** `2.5×` USTR. Stamp `fee_usd` at ingest (#568). 5. **Not these amounts:** oracle rate spread, Venus redeem, Classic burn tax, gas, `min_vfdusd_out` slippage haircut, paused/stale reject, rolling 24h unused capacity. 6. **GET stays O(1).** Additive overview scalars; breakdown via existing `/protocol/fees` rollup. Migration must **widen** CHECK constraints (`ust1_mint` / `ust1_redeem` or the chosen keys). `FeeSource::parse` stays allowlist — unknown / `javascript:` rejected. 7. **`window=` query param** stays `24h` \| `7d` \| `30d` (400 else). Do not overload that word with “ust1-window”. 8. **Cardinality.** Fixed enum + top-8 tokens + `other`. No per-user window tape on `/protocol`. 9. **Do not bind-mount `indexer/` into root Docker for cargo.** 10. **Out of scope.** Changing `fee_bps`. Soft-launch `/mint` faucet. Wrap-mapper ingest (separate issue). Pair-creation `uluna`. Marketing the CEX vFDUSD tab as the window rate. ## Relevant files | Path | Role | |------|------| | [`indexer/src/indexer/protocol_fees.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/indexer/protocol_fees.rs) | New source(s), pin helper, `parse_*_window_fees` | | [`indexer/src/indexer/parser.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/indexer/parser.rs) | Call ingest beside wrap; flattened-event scan if LCD merges CW20 `send` + window hook | | [`indexer/src/config.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/config.rs) + [`indexer/.env.example`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/.env.example) | `UST1_WINDOW_ADDRESS` | | [`indexer/migrations/`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/tree/main/indexer/migrations) | Widen `protocol_fee_events_source_chk` + `protocol_fee_stats_by_source_source_chk`; optional `ust1_window_configured` on `global_stats_24h` (mirror `wrap_mapper_configured`) | | [`indexer/src/db/queries/protocol_fees.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/db/queries/protocol_fees.rs) | Rollup omit when window unconfigured | | [`indexer/src/api/protocol_fees.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/api/protocol_fees.rs) | Expose `ust1_window_configured` (additive JSON) | | [`indexer/src/indexer/volume_aggregator.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/indexer/volume_aggregator.rs) | Pass configured flag into refresh | | [`frontend-dapp/src/types/index.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/types/index.ts) | `ProtocolFeeSourceKey` + response flag | | [`frontend-dapp/src/components/protocol/ProtocolFeeStats.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/protocol/ProtocolFeeStats.tsx) | Labels, order, omit-if-unconfigured, lead copy | | [`frontend-dapp/src/pages/ProtocolPage.test.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/pages/ProtocolPage.test.tsx) | Source table | | [`frontend-dapp/src/services/terraclassic/ust1Window.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/ust1Window.ts) | Execute shape for fixtures | | [`skills/AGENTS_FRONTEND_PROTOCOL_STATS.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_PROTOCOL_STATS.md) | New PFee / extend PFee-3/PFee-6 | | [`skills/AGENTS_UST1_WINDOW_UI.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_UST1_WINDOW_UI.md) | Crosslink (do not overload `/mint`) | | [`docs/indexer-invariants.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/indexer-invariants.md), [`docs/frontend.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/frontend.md) § Protocol, [`docs/runbooks/overview-global-stats-brin.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/runbooks/overview-global-stats-brin.md) | Docs | | [`deployments/mainnet-ust1-wrap/coolify.env.example`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/deployments/mainnet-ust1-wrap/coolify.env.example) | Indexer Coolify pin (not only Vite) | | Upstream | [ust1-window](https://gitlab.com/PlasticDigits/ust1-window) `effective_swap` / wasm attrs | ## Recommended direction 1. **Capture window wasm first** (LocalTerra ust1-window smoke or columbus-5 deposit + withdraw). Lock `action`, fee amount key, token key, and whether mint vs redeem are distinct. Do not invent attrs from `fee_bps` alone. 2. **Pin + parse** beside wrap: `parse_ust1_window_address` / `parse_ust1_window_fees`. Flattened CW20 `send` + hook must still attribute the fee to the **window** `_contract_address`, not the UST1/vFDUSD token contract. 3. **Sources.** If attrs split cleanly: `ust1_mint` + `ust1_redeem` with labels **UST1 mint** / **UST1 redeem**. If not: one `ust1_window` row. Put them in `SOURCE_ORDER` after unwrap, before AMM swap (or after wrap family — keep wrap/window grouped). 4. **Migration** widens CHECKs; add `ust1_window_configured` (or a generic `sources_configured` object — prefer the wrap mirror for one flag). Additive GET JSON: old indexers without the flag → hide window rows, do not invent `$0` (**PFee-10**). 5. **Coolify.** Document `UST1_WINDOW_ADDRESS` on the **indexer** service (Vite `VITE_UST1_WINDOW_ADDRESS` does not feed the indexer). 6. **Verify script** `make verify-issue-<iid>`: docs, CHECK + parse allowlist, omit-if-unconfigured, fixture ingest, UI labels, no GET scan, no vFDUSD conversion. ## Acceptance criteria - [ ] `UST1_WINDOW_ADDRESS` parsed like the wrap pin; invalid/empty omits window sources. - [ ] Deposit and withdraw fixtures (or one captured + one synthetic matching locked attrs) insert `protocol_fee_events` with the new source(s), positive `amount_raw`, and hub-stamped `fee_usd` when UST1 is priced. - [ ] No row when fee amount / token attrs are missing (no `× fee_bps`). - [ ] Spoof window-shaped wasm from a non-pinned contract is ignored. - [ ] Overview 24h/7d/30d totals **include** priced window fees; Δ% rules unchanged (`then ≤ 0` → `null`). - [ ] `/protocol` Source shows retail **UST1 mint** / **UST1 redeem** (or the chosen single label) when events exist; idle `$0` hidden; unconfigured omitted. - [ ] CEX oracle card / hub card copy still does **not** claim to be the window rate. - [ ] GET `/overview` and `/protocol/fees` stay O(1); `window=` allowlist unchanged. - [ ] `make verify-issue-586` still passes. New `make verify-issue-<iid>` is the gate. ## Test plan (happy + edge) | Path | Expect | |------|--------| | vFDUSD `send` `{ deposit }` | `ust1_mint` (or bundled source); fee token from attrs; USD via hub UST1 if that is the fee asset | | UST1 `send` `{ withdraw }` | `ust1_redeem`; slippage `min_vfdusd_out` is **not** the fee | | Unconfigured window env | Sources omitted; AMM/book/wrap unchanged | | Configured + zero events | Hidden idle `$0`; totals `"0"` | | UST1 hub down | Event stored; `fee_usd` NULL; activity+all-unpriced → `—` | | Replay | `ON CONFLICT` no double-count | | Flattened wasm (token `send` + window hook + cw20 mint) | One fee row scoped to window contract | | Additive JSON (old indexer) | Missing flag → no window rows, no invented `$0` | | `?ticker=vfdusd` on `/protocol` | Fee panel ignores ticker (**PFee-10**) | | `--fresh` | Δ% empty until 2×W | ## Test plan (attack / abuse) | Vector | Guard | |--------|--------| | Attacker contract emits `action=deposit` + huge `fee_amount` | Pin on `_contract_address` | | Forged `contract_address` (no underscore) | Ignored (#285) | | Infer fee from `amount × 100` when attr missing | No row | | Price window fee with CEX vFDUSD / `$1` UST1 / `2.5×` USTR | Forbidden — tests use hub catalog only | | Count unused rolling 24h limit or pause as a fee | Forbidden | | Count Venus `exchangeRateCurrent` as treasury | Forbidden (#571 is display-only) | | XSS / `javascript:` in symbol | Text only (**PFee-4**) | | `source=javascript:` in rollup | `FeeSource::parse` drops | | `window=30d); DROP TABLE` | 400 allowlist | | Widen CHECK but forget rollup enum → insert panic | Migration + `FeeSource::ALL` + UI key updated together | | Double-count window fee as `swap_amm` (user later sells UST1 on AMM) | Window fee is window-only; later swap is `swap_amm` only | | Bind-mount indexer + root cargo | Do not; host tests | | GET scans `protocol_fee_events` for the new sources | Forbidden | ## Verification criteria ```bash make setup-indexer-postgres make verify-issue-586 make verify-issue-<iid> # optional chain: make setup-cloud-localterra # deposit + withdraw against LocalTerra window (or documented columbus-5 hashes) ``` Close only when Coolify indexer env documents the pin, a captured-or-LocalTerra tx increments the new source `event_count`, USD uses hub/catalog (not vFDUSD/`$1`), unconfigured omit works, and #586 verify still passes. UI-only labels without ingest are **not** done.
PlasticDigits commented 2026-08-24 00:30:20 +00:00 (Migrated from gitlab.com)

marked as related to #586

marked as related to #586
PlasticDigits commented 2026-08-24 00:30:21 +00:00 (Migrated from gitlab.com)

marked as related to #506

marked as related to #506
PlasticDigits commented 2026-08-24 00:30:21 +00:00 (Migrated from gitlab.com)

marked as related to #503

marked as related to #503
PlasticDigits commented 2026-08-24 00:30:22 +00:00 (Migrated from gitlab.com)

marked as related to #613

marked as related to #613
PlasticDigits commented 2026-08-24 00:30:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #613

mentioned in issue #613
PlasticDigits commented 2026-08-24 00:48:28 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1129

mentioned in merge request !1129
PlasticDigits commented 2026-08-24 00:52:11 +00:00 (Migrated from gitlab.com)

Follow-up for live ingest: ust1-window must emit fee_amount + fee_asset (UST1) on deposit/withdraw. Opened ust1-window#33.

DEX pin/parse/UI is in !1129 and stays fail-closed on columbus-5 11566 (bps attrs only). Do not infer ust1_notional × fee_bps here.

Follow-up for live ingest: ust1-window must emit `fee_amount` + `fee_asset` (UST1) on deposit/withdraw. Opened [ust1-window#33](https://gitlab.com/PlasticDigits/ust1-window/-/issues/33). DEX pin/parse/UI is in [!1129](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/411) and stays fail-closed on columbus-5 **11566** (bps attrs only). Do not infer `ust1_notional × fee_bps` here.
PlasticDigits commented 2026-08-24 02:44:27 +00:00 (Migrated from gitlab.com)

mentioned in merge request ust1-window!732

mentioned in merge request ust1-window!732
PlasticDigits commented 2026-08-24 02:45:10 +00:00 (Migrated from gitlab.com)

mentioned in merge request ust1-window!733

mentioned in merge request ust1-window!733
PlasticDigits commented 2026-08-24 02:48:11 +00:00 (Migrated from gitlab.com)

mentioned in commit c37265dc48

mentioned in commit c37265dc48c4f97d2eff445d735eab2bc3f40c3c
PlasticDigits commented 2026-08-24 03:00:01 +00:00 (Migrated from gitlab.com)

mentioned in commit 992b980a3f

mentioned in commit 992b980a3f17b0ee78279e98c0110cf46a7e50c3
PlasticDigits commented 2026-08-24 03:01:15 +00:00 (Migrated from gitlab.com)

mentioned in issue #616

mentioned in issue #616
PlasticDigits commented 2026-08-24 03:01:18 +00:00 (Migrated from gitlab.com)

marked as related to #616

marked as related to #616
PlasticDigits commented 2026-08-24 03:01:45 +00:00 (Migrated from gitlab.com)

Merge sanity (!1129 landed on main; does not close this issue; CI not waited).

Conflicts with !1128 wrap ingest resolved: keep #613 notify_deposit / fee segment scan and ust1_mint / ust1_redeem pin/parse. Follow-up 992b980a restored PFee-1–PFee-12 in docs/frontend.md so verify-issue-586 / 614 greps both pass.

Automated: make verify-issue-614 6/6 (docs/CHECK/pin, lib parse fail-closed on crate bps-only attrs, integration omit-if-unconfigured, Protocol UST1 mint / UST1 redeem labels).

Problems that keep this issue open

  • Columbus-5 window 11566 still emits fee_*_bps / vfdusd_to_treasury without fee_amount. Live mint/redeem stay dropped (fail closed). Upstream: ust1-window#33.
  • Coolify indexer pin UST1_WINDOW_ADDRESS is not set by this merge. Vite alone is not enough.
  • Do not infer ust1_notional × fee_bps here.

Ops checklist: #616.

**Merge sanity (!1129 landed on `main`; does not close this issue; CI not waited).** Conflicts with !1128 wrap ingest resolved: keep #613 `notify_deposit` / `fee` segment scan **and** `ust1_mint` / `ust1_redeem` pin/parse. Follow-up `992b980a` restored `PFee-1–PFee-12` in `docs/frontend.md` so `verify-issue-586` / `614` greps both pass. Automated: `make verify-issue-614` **6/6** (docs/CHECK/pin, lib parse fail-closed on crate bps-only attrs, integration omit-if-unconfigured, Protocol **UST1 mint** / **UST1 redeem** labels). **Problems that keep this issue open** - Columbus-5 window **11566** still emits `fee_*_bps` / `vfdusd_to_treasury` without `fee_amount`. Live mint/redeem stay dropped (fail closed). Upstream: [ust1-window#33](https://gitlab.com/PlasticDigits/ust1-window/-/issues/33). - Coolify indexer pin `UST1_WINDOW_ADDRESS` is not set by this merge. Vite alone is not enough. - Do not infer `ust1_notional × fee_bps` here. Ops checklist: [#616](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/616).
PlasticDigits commented 2026-08-24 03:25:11 +00:00 (Migrated from gitlab.com)

Upstream window now emits fee_amount (columbus-5)

ust1-window#33 store+migrate is on the same address. Pin stays terra1zxwpzpzpleatqn39r00grau4yt29sld8pw78s7ktvjafnj5nsaxq0h3rh2 (do not instantiate a second window).

New code id 11618 (was 11566)
Store F4D79240E8009740C63F3342A2AA655CE7015579FC376B765AC3821EF823C60A
Migrate 009BD391E27AE8A6A7FCBA94443E2A257875A19603C51FAB1CC6C6B91E8E747D
Admin terra1xsecn… (cl8y2_admin) unchanged
Config fee_bps=100 preserved

Live mint/redeem can now name fee_amount + fee_asset (UST1). Indexer still fail-closes if Coolify UST1_WINDOW_ADDRESS is unset. Vite pin is not enough.

Still needed to close this issue: Coolify indexer pin + one captured deposit and one withdraw incrementing protocol_fee_events ust1_mint / ust1_redeem. See #616. Do not infer ust1_notional × fee_bps.

## Upstream window now emits `fee_amount` (columbus-5) [ust1-window#33](https://gitlab.com/PlasticDigits/ust1-window/-/issues/33) store+migrate is on the **same** address. Pin stays `terra1zxwpzpzpleatqn39r00grau4yt29sld8pw78s7ktvjafnj5nsaxq0h3rh2` (do not instantiate a second window). | | | |--|--| | New code id | **11618** (was 11566) | | Store | `F4D79240E8009740C63F3342A2AA655CE7015579FC376B765AC3821EF823C60A` | | Migrate | `009BD391E27AE8A6A7FCBA94443E2A257875A19603C51FAB1CC6C6B91E8E747D` | | Admin | `terra1xsecn…` (`cl8y2_admin`) unchanged | | Config | `fee_bps=100` preserved | Live mint/redeem can now name `fee_amount` + `fee_asset` (UST1). Indexer still fail-closes if Coolify `UST1_WINDOW_ADDRESS` is unset. Vite pin is not enough. **Still needed to close this issue:** Coolify indexer pin + one captured deposit and one withdraw incrementing `protocol_fee_events` `ust1_mint` / `ust1_redeem`. See [#616](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/616). Do not infer `ust1_notional × fee_bps`.
PlasticDigits commented 2026-08-24 04:57:43 +00:00 (Migrated from gitlab.com)

Live indexer still missing UST1_WINDOW_ADDRESS

After the 2026-08-24 Coolify redeploy:

  • dApp bakes VITE_UST1_WINDOW_ADDRESS=terra1zxwp…h3rh2
  • GET /api/v1/protocol/fees → ust1_window_configured: false (mint/redeem rows omitted, not idle $0)
  • Columbus-5 window is 11618 on the same address (fee_bps=100, paused=false) — wasm fee_amount / fee_asset are live on-chain (ust1-window#33 store+migrate)

Set indexer Coolify:

UST1_WINDOW_ADDRESS=terra1zxwpzpzpleatqn39r00grau4yt29sld8pw78s7ktvjafnj5nsaxq0h3rh2

Then one deposit + one withdraw to close the live-tx AC. Do not infer fees from fee_*_bps. Issue stays open.

**Live indexer still missing `UST1_WINDOW_ADDRESS`** After the 2026-08-24 Coolify redeploy: - dApp bakes `VITE_UST1_WINDOW_ADDRESS=terra1zxwp…h3rh2` - `GET /api/v1/protocol/fees` → `ust1_window_configured: false` (mint/redeem rows omitted, not idle `$0`) - Columbus-5 window is **11618** on the same address (`fee_bps=100`, paused=false) — wasm `fee_amount` / `fee_asset` are live on-chain ([ust1-window#33](https://gitlab.com/PlasticDigits/ust1-window/-/issues/33) store+migrate) Set indexer Coolify: ``` UST1_WINDOW_ADDRESS=terra1zxwpzpzpleatqn39r00grau4yt29sld8pw78s7ktvjafnj5nsaxq0h3rh2 ``` Then one deposit + one withdraw to close the live-tx AC. Do not infer fees from `fee_*_bps`. Issue stays open.
PlasticDigits commented 2026-08-25 01:55:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #631

mentioned in issue #631
PlasticDigits commented 2026-08-25 06:07:04 +00:00 (Migrated from gitlab.com)

mentioned in commit e56a1fd323

mentioned in commit e56a1fd323bbd4075cb2fc7ee6314c626fc7a707
PlasticDigits commented 2026-08-25 06:07:21 +00:00 (Migrated from gitlab.com)

Verification (worktree verify-issue-614)

Ran make verify-issue-614 on a git worktree. First pass failed only because the worktree had no frontend-dapp/node_modules (vitest: not found). Fixed the verify scripts to bootstrap a sibling symlink or npm ci (same pattern as #516 / #634). Re-ran after that: 7/7 PASS, including the new live leftover probe.

Pushed to main as 89515ec1 (docs + I614 playbook + verify bootstrap). Local primary main still had another agent's uncommitted files after 5×90s, so this was git push origin HEAD:main from the worktree (did not touch that dirty checkout).

What landed on main (this verify)

  • New third-party skill skills/AGENTS_INDEXER_UST1_WINDOW_FEES.md (I614-1–I614-8).
  • Registry / runbook / PFee-13 now record columbus-5 window 11618 (same address as 11566) emitting fee_amount + fee_asset. 11566 bps-only attrs stay fail-closed. Never ust1_out × fee_total_bps.
  • Crosslinks in AGENTS.md, docs/indexer-invariants.md, docs/frontend.md, docs/testing.md, wrap skill, UST1 UI skill, #616 leftover copy (do not reopen #614).
  • make verify-issue-614 now greps I614 + 11618, bootstraps worktree frontend deps, and probes live ust1_window_configured + mint/redeem event_count.

Live indexer (2026-08-25, indexer.dex.cl8y.com)

Check Result
ust1_window_configured true
ust1_mint event_count=2, amount_usd≈1.58
ust1_redeem event_count=3, amount_usd≈3.70
Hub UST1 GET /hub-prices ≈ 1.015 (not $1, not CEX vFDUSD)
Overview fees total_fees_24h_usd includes the window rows

Coolify indexer pin is set. Vite-only bake is still not enough on rebuild — keep UST1_WINDOW_ADDRESS=terra1zxwpzpzpleatqn39r00grau4yt29sld8pw78s7ktvjafnj5nsaxq0h3rh2.

Checklist (issue body + comments)

  • UST1_WINDOW_ADDRESS parsed like wrap; invalid/empty omits window sources
  • Deposit / withdraw fixtures insert ust1_mint / ust1_redeem with positive amount_raw; hub-stamped fee_usd when UST1 is priced
  • Missing fee_amount / token → no row (no × fee_bps); 11566 crate attrs fail closed
  • Spoof / forged contract_address ignored
  • Overview 24h/7d/30d totals include priced window fees; Δ% then ≤ 0 → null
  • /protocol Source labels UST1 mint / UST1 redeem; idle $0 hidden; unconfigured omitted
  • CEX oracle / hub cards do not claim to be the window rate (P550-11)
  • GET /overview and /protocol/fees stay O(1); window= allowlist unchanged
  • make verify-issue-586 still passes
  • Coolify indexer env documents the pin (coolify.env.example + REGISTRY)
  • Captured live deposit + withdraw increment event_count (comment leftover)
  • USD from hub catalog, not vFDUSD / $1 UST1
  • Upstream 11618 fee_amount + fee_asset (ust1-window#33) — do not infer from bps

All verification criteria from the issue body and comments pass. Closing.

make setup-indexer-postgres
make verify-issue-614
# optional live leftover:
VERIFY614_REQUIRE_LIVE=1 make verify-issue-614
## Verification (worktree `verify-issue-614`) Ran `make verify-issue-614` on a git worktree. First pass failed only because the worktree had no `frontend-dapp/node_modules` (`vitest: not found`). Fixed the verify scripts to bootstrap a sibling symlink or `npm ci` (same pattern as #516 / #634). Re-ran after that: **7/7 PASS**, including the new live leftover probe. Pushed to `main` as `89515ec1` (docs + I614 playbook + verify bootstrap). Local primary `main` still had another agent's uncommitted files after 5×90s, so this was `git push origin HEAD:main` from the worktree (did not touch that dirty checkout). ### What landed on main (this verify) - New third-party skill [`skills/AGENTS_INDEXER_UST1_WINDOW_FEES.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_INDEXER_UST1_WINDOW_FEES.md) (**I614-1–I614-8**). - Registry / runbook / **PFee-13** now record columbus-5 window **11618** (same address as 11566) emitting `fee_amount` + `fee_asset`. 11566 bps-only attrs stay fail-closed. Never `ust1_out × fee_total_bps`. - Crosslinks in `AGENTS.md`, `docs/indexer-invariants.md`, `docs/frontend.md`, `docs/testing.md`, wrap skill, UST1 UI skill, #616 leftover copy (do not reopen #614). - `make verify-issue-614` now greps I614 + 11618, bootstraps worktree frontend deps, and probes live `ust1_window_configured` + mint/redeem `event_count`. ### Live indexer (2026-08-25, `indexer.dex.cl8y.com`) | Check | Result | |-------|--------| | `ust1_window_configured` | `true` | | `ust1_mint` | `event_count=2`, `amount_usd≈1.58` | | `ust1_redeem` | `event_count=3`, `amount_usd≈3.70` | | Hub UST1 | `GET /hub-prices` ≈ **1.015** (not `$1`, not CEX vFDUSD) | | Overview fees | `total_fees_24h_usd` includes the window rows | Coolify indexer pin is set. Vite-only bake is still not enough on rebuild — keep `UST1_WINDOW_ADDRESS=terra1zxwpzpzpleatqn39r00grau4yt29sld8pw78s7ktvjafnj5nsaxq0h3rh2`. ### Checklist (issue body + comments) - [x] `UST1_WINDOW_ADDRESS` parsed like wrap; invalid/empty omits window sources - [x] Deposit / withdraw fixtures insert `ust1_mint` / `ust1_redeem` with positive `amount_raw`; hub-stamped `fee_usd` when UST1 is priced - [x] Missing `fee_amount` / token → no row (no `× fee_bps`); 11566 crate attrs fail closed - [x] Spoof / forged `contract_address` ignored - [x] Overview 24h/7d/30d totals include priced window fees; Δ% `then ≤ 0` → `null` - [x] `/protocol` Source labels **UST1 mint** / **UST1 redeem**; idle `$0` hidden; unconfigured omitted - [x] CEX oracle / hub cards do **not** claim to be the window rate (**P550-11**) - [x] GET `/overview` and `/protocol/fees` stay O(1); `window=` allowlist unchanged - [x] `make verify-issue-586` still passes - [x] Coolify indexer env documents the pin (`coolify.env.example` + REGISTRY) - [x] Captured live deposit + withdraw increment `event_count` (comment leftover) - [x] USD from hub catalog, not vFDUSD / `$1` UST1 - [x] Upstream 11618 `fee_amount` + `fee_asset` (ust1-window#33) — do not infer from bps All verification criteria from the issue body and comments pass. Closing. ```bash make setup-indexer-postgres make verify-issue-614 # optional live leftover: VERIFY614_REQUIRE_LIVE=1 make verify-issue-614 ```
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-25 06:07:22 +00:00
PlasticDigits commented 2026-08-25 06:11:42 +00:00 (Migrated from gitlab.com)

mentioned in commit c89be8e43e

mentioned in commit c89be8e43eb38b1cac35746040edade4127e6969
PlasticDigits commented 2026-08-27 01:00:19 +00:00 (Migrated from gitlab.com)

mentioned in issue #682

mentioned in issue #682
PlasticDigits commented 2026-08-27 01:00:29 +00:00 (Migrated from gitlab.com)

mentioned in issue #683

mentioned in issue #683
PlasticDigits commented 2026-08-27 06:03:02 +00:00 (Migrated from gitlab.com)

mentioned in issue #687

mentioned in issue #687
PlasticDigits commented 2026-08-27 09:00:10 +00:00 (Migrated from gitlab.com)

mentioned in issue #689

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