fix: /protocol Wrap/Unwrap ingest — cUSTC/cLUNC fee_amount actually lands #613
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#613
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Make
/protocolWrap / Unwrap rows real for cUSTC and cLUNC. The #586 fee census already has those sources and Coolify already pinsWRAP_MAPPER_ADDRESS, but productionGET /api/v1/protocol/feesreportsevent_count: 0for wrap and unwrap on 24h / 7d / 30d (sampled 2026-08-24). The UI hides idle$0sources (PFee-3), so operators only see AMM swap and Book take.Bundle wrap + unwrap, both denoms (uusd↔cUSTC, uluna↔cLUNC), and every retail execute path (direct
/wrap, treasurywrap_deposit, CW20sendunwrap, wrap+router combo, routerunwrap_output). Do not split “attr parser” vs “LocalTerra fixture” vs “UI row” vs “cUSTC vs cLUNC”.Related: #586 (protocol fees; wrap ingest in-scope, ust1-window out of scope), #516 (split
fee_wrap_bps/fee_unwrap_bps), #590 (M590-2 Coolify pin, M590-4 unwrapfee_amountonly), #512 (burn tax ≠ protocol fee), #568 (stamp USD at ingest), #550 / #556 (USD catalog / hub). UST1 window mint/redeem is a separate follow-up.Current codebase
FeeSourcealready hasWrap/Unwrap(wrap/unwrap). Retail labels Wrap / Unwrap.FeeSource::ALLincludes both.parse_wrap_feesaccepts wasm only when_contract_addressequals pinnedWRAP_MAPPER_ADDRESS,actionis exactlywrap|unwrap,fee_amountis a positive raw integer, and token identity isfee_asset/fee_denom/native_denom/denom. Fail-closed: missing amount or token → drop.tax_amount/instant_withdrawignored. Uses last-value attrs on the whole event — not the per-actionsegment scan used for limit lifecycle (#141 / #285).parser.rsprocess_wrap_fee→ingest_protocol_feewhenconfig.wrap_mapper_addressisSome. Replay-safeUNIQUE (tx_hash, source, ordinal).WRAP_MAPPER_ADDRESSviaconfig.rs+parse_wrap_mapper_address(terra1bech32 only). Empty / garbage → wrap/unwrap omitted from breakdown (not fake$0). Columbus-5 mapper:terra1xuuuhpmyd5t29ry7mydg7ra2q2phrwhx7j28nx7x9sjw6zznkumsz0nmd2(REGISTRY.md).protocol_fees.rsqueries +GET /api/v1/protocol/fees. CHECK constraint:source IN ('swap_amm','book_take','limit_place','wrap','unwrap'). GET is O(1) / 60s cache — must notSUM protocol_fee_events.ProtocolFeeStats.tsx: labels Wrap / Unwrap; hide wrap-family unlesswrap_mapper_configured; hide idle$0. Tokens table is the denom split (not extra source rows).wrap_depositwith native funds (wrapViaTreasury), not a userexecuteon the mapper. Unwrap is CW20send→ mapper{ unwrap }. Swap/LP combos addwrap_depositthen router; output unwrap is routerunwrap_output→ mapper InstantWithdraw (NATIVE_TOKEN_WRAPPING.md). LCD often flattens several wasm emissions into onewasmstream.https://indexer.dex.cl8y.com/api/v1/protocol/fees?window=24h→wrap_mapper_configured: true, wrap/unwrapevent_count: 0on 24h and 7d and 30d. AMM + book take are non-zero. Token mix (UST1 / USTR / cUSTC / cLUNC) is swap/book commission, not wrap fees. Fee ingest itself only exists since #586 (~2026-08-21).fee_amount/ ignoretax_amount/ zero amount inprotocol_fees.rs. Integration:indexer/tests/indexer_protocol_fees.rs. UI:ProtocolPage.test.tsx(hides idle unwrap). Verify:make verify-issue-586. No fixture from a captured columbus-5 / LocalTerra wrap tx.Why this is needed
/protocolclaims wrap is a fee source (#586 / PFee-3) but production never shows it. Operators cannot tell whether wrap treasury is idle or the indexer is dropping events.fee_wrap_bps/fee_unwrap_bps, product-visible on/wrapand in swap quotes). Leaving them at$0undercounts the same treasury the AMM/book rows report.action=wrap\|unwrap+fee_amounton the mapper contract. Retail wrap starts at treasurywrap_deposit; combos flatten wasm; InstantWithdraw usestax_amount. Last-value parse on a flattened event whose lastactionisswap/unwrap_output/instant_withdrawsilently drops the mapper fee.$0” alone. Either nobody wrapped since ingest started, or every wrap failed the fail-closed attr gate. This issue must prove which with a captured tx, then make the parser match reality without inventingamount × bps.Constraints / guardrails
tax_amount/hook_fee_amount/ spread / gas are not protocol fees.gross × fee_wrap_bps. Missing token identity → drop.WRAP_MAPPER_ADDRESS. Do not count treasurywrap_depositas a fee unless the mapper emission is present and pinned. Do notLIKE %/ case-fold. Spoofaction=wrapon another contract must stay ignored.wasmstream, parse eachactionsegment and scope by reserved_contract_addressonly (#285). Do not trust user-setcontract_address.wrap/unwrap. Retail labels stay Wrap / Unwrap. Do not add “cUSTC wrap” / “cLUNC wrap” source rows — token mix stays in the Tokens table.uusd→ USTC oracle; LUNC/cLUNC/uluna→ LUNC. Never vFDUSD. Never$1UST1. Stampfee_usdat ingest (#568).SUM protocol_fee_eventson/overviewor/protocol/fees. No new unbounded breakdown.$0). Idle + configured + zero events may stay hidden in the Source table (PFee-3); totals stay"0".indexer/into root Docker for cargo. Hostcargo test/make verify-issue-*.fee_*_bps. Pair-creationuluna. Headliningtraders.total_fees_paid.Relevant files
indexer/src/indexer/protocol_fees.rsparse_wrap_fees, pin, fail-closed attrsindexer/src/indexer/parser.rsprocess_wrap_fee; flattened-event patterns (wasm_kv_map_after_action)indexer/src/config.rsWRAP_MAPPER_ADDRESSindexer/src/db/queries/protocol_fees.rsindexer/migrations/20260821120000_protocol_fees.sqlindexer/tests/indexer_protocol_fees.rsfrontend-dapp/src/components/protocol/ProtocolFeeStats.tsxfrontend-dapp/src/services/terraclassic/wrapMapper.tsfrontend-dapp/src/services/terraclassic/router.tsskills/AGENTS_FRONTEND_PROTOCOL_STATS.mdskills/AGENTS_WRAP_MAPPER_SPLIT_FEES.mddocs/indexer-invariants.mdscripts/qa/verify-issue-586.shverify-issue-<iid>Recommended direction
wrap_deposit+ unwrap + wrap+2hop +unwrap_outputtx aftermake setup-cloud-localterra. Lock the exactactionstrings, fee amount key, and token keys in a fixture comment. Do not guess from #586 comments if LCD disagrees.parse_wrap_feesto that fixture. Prefer per-actionsegments + reserved_contract_address(reuse parser helpers). Keep exact mapper pin. Map both denoms into the samewrap/unwrapsources.unwrap_outputInstantWithdraw must persist unwrapfee_amountand nottax_amount.protocol_fee_eventsrow → rollupevent_count ≥ 1→GET /protocol/feesSource Wrap / Unwrap with non-zero USD when hub/oracle prices the token.WRAP_MAPPER_ADDRESS” as the fix if it is alreadytrueon production.Acceptance criteria
protocol_fee_events.source = wrapwith positiveamount_rawand stampedfee_usdwhen the token is catalogued.source = unwrap. InstantWithdrawtax_amountis not stored.swap_ammonce each.action=wrap+fee_amountis ignored.fee_amount/ token identity → no row (noamount × bps).WRAP_MAPPER_ADDRESSstill omits wrap/unwrap fromby_source(not$0).SUMofprotocol_fee_events./protocolSource shows Wrap / Unwrap whenevent_count > 0; idle$0still hidden. Tokens table can show uusd / uluna / cUSTC / cLUNC as wrap-fee tokens when those events exist.make verify-issue-586still passes. Newmake verify-issue-<iid>covers this issue’s fixtures + docs crosslinks.Test plan (happy + edge)
wrap_deposituusd → cUSTCwraprow; token identity native or CW20 per captured attrs; USD via USTC catalogwrap_deposituluna → cLUNCwraprow; LUNC catalogsendunwrap cUSTC / cLUNCunwraprow; nottax_amountswap_amm; no drop because last action isswapunwrap_outputInstantWithdrawfee_amountonlyfee_amountfee_usdNULL; window with only unpriced wrap → overviewnull(—), not$0event_count=0hidden in UI; totals"0"ON CONFLICT DO NOTHING; no double-count--fresh/ young indexerTest plan (attack / abuse)
action=wrap+ hugefee_amount_contract_addressonlycontract_address(no underscore)javascript:/ XSS indenom/ symbolfee_amountoverflow / non-numeric / negativeNUMERIC(38,0)+amount_raw > 0CHECKamount × bpswhen attr missingfee_amountandtax_amountwrap_depositwith no mapper fee emissionTERRA1…, prefix+address)parse_wrap_mapper_address/wrap_mapper_matchesrejectwindow=24h' OR 1=1on GETbook_commission_amountOVERVIEW_GLOBAL_STATS_LIVE=1Verification criteria
Close only when: (1) parser matches a captured tx (not only the synthetic #586 attrs), (2) LocalTerra or columbus-5 proof that wrap/unwrap
event_countincrements, (3) spoof/tax/bps-inference attacks stay fail-closed, (4) GET remains O(1). “wrap_mapper_configured: true” alone is not done.marked as related to #586
marked as related to #516
marked as related to #590
marked as related to #614
Sibling follow-up for UST1 window mint/redeem (not wrap-mapper): #614
mentioned in merge request !1128
mentioned in commit
4c120e831cmentioned in commit
aa8991cf02mentioned in commit
574793b4efmentioned in commit
e1490bbf59mentioned in issue #616
marked as related to #616
Merge sanity (!1128 landed on
main; CI not waited).The MR source branch had been reset to
main(0 commits). Restored the real commit fromissue/613-wrap-unwrap-ingest(4de6d839) onto the MR branch, then merged.Automated on the integrated tip:
make verify-issue-6136/6 after a follow-up docs grep fix (992b980a— keepPFee-1–PFee-12so nestedverify-issue-586still matches). Lib wrap parse (notify_deposit +fee, flattened combo, no burn-tax) + integration ingest/rollup/GET + Protocol idle-hide all passed.Acceptance still open on production
GET /api/v1/protocol/feescan still show wrap/unwrapevent_count: 0.fee_usd. Fixtures cover both denoms; live tx confirmation is ops.tax_amountis not stored (unit + integration).Post-merge Coolify + live wrap proof: #616.
mentioned in issue #614
mentioned in issue #610
mentioned in issue #631
mentioned in issue #682
mentioned in issue #683
mentioned in issue #687
mentioned in issue #689