feat(voting): indexer registration + CL8Y CW20 balance ledger for registered wallets (no archive LCD) #509
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#509
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
Add an offchain CL8Y (hpax3) voter registration + balance ledger in the indexer/Postgres stack so snapshot vote power can be computed without an archive LCD.
CL8Y token (same as fee-discount
cl8y_token):terra16wtml2q66g82fdkx66tap0qjkahqwp4lwq3ngtygacg5q0kzycgqvhpax3(18 decimals). LocalTerra uses TCL8Y viaVITE_CL8Y_TOKEN_ADDRESS.Product flow (this issue owns the data plane):
operator-voting— linked issue).Balance+ current block height (no historical/archive query).transfer/send/ mint-burn (if any) events to/from registered wallets, maintaining a running balance and height-addressable history for those wallets.Depends on / pairs with:
operator-votingpackage (signatures + restricted balance reads)Current codebase
frontend-dapp/src/utils/constants.ts(VITE_CL8Y_TOKEN_ADDRESS/ hpax3 fallback),scripts/lib/mainnet-soft-launch-defaults.sh(MAINNET_CL8Y_TOKEN_ADDRESS), fee-discountcl8y_tokengetTokenBalanceinfrontend-dapp/src/services/terraclassic/queries.ts; fee-discount contract also queriesCw20QueryMsg::Balanceon register /GetDiscountindexer/src/lcd/mod.rsquery_contract; usestoken_info/ fee-discount registration — no CW20Balancehelper, nox-cosmos-block-heightarchive queriesget_latest_block_height/get_block_at_heightfor indexing/reorg onlyindexer/src/indexer/parser.rs— DEX swaps, limits, hooks, fee-discount registry; does not index CW20 Transfer/Send for arbitrary tokensindexer/migrations/—assets,traders,trader_positions,pair_reserves, etc. No holder/balance/transfer tables.trader_positionsare explicitly not on-chain balances (docs/indexer-invariants.md)operator-votingWhy this is needed
Community / operator snapshot voting needs height-frozen CL8Y vote power without operating an archive node. Enumerating all CL8Y holders on mainnet LCD is slow/unreliable. Opt-in registration + transfer tracking for the registered set gives a closed electorate with balances reconstructible at proposal heights from Postgres alone after registration.
Same CL8Y that gates fee-discount tiers should gate voting weight for consistency.
Constraints / guardrails
operator-voting) only.x-cosmos-block-height.CL8Y_TOKEN_ADDRESS/ reuse fee-discount config /VITE_CL8Y_TOKEN_ADDRESSparity on LocalTerra TCL8Y).NUMERICwithout float drift._contract_address(indexer invariant #285) — only trust CL8Y contract emissions.transfer,send, and any mint/burn actions the deployed code ID emits; ignore unrelated wasm.indexer_state.last_indexed_heightpatterns; balances must be correct after reorg unwind/replay for registered wallets.traders.tier_idortrader_positionsas vote weight.operator-votingmust only read balances (restricted role/views) — see linked package issue.Relevant files
Indexer / DB
indexer/migrations/(new migration(s))indexer/src/indexer/parser.rs,block_indexer.rs,poller.rs,mod.rsindexer/src/lcd/mod.rs,lcd/types.rsindexer/src/db/queries/,db/mod.rsindexer/src/config.rs,startup.rs,main.rsindexer/src/indexer/trader_tracker.rs(pattern for fee-discount events — do not overload)docs/indexer-invariants.mdCL8Y / balance references
frontend-dapp/src/utils/constants.ts,tokenRegistry.tsfrontend-dapp/src/services/terraclassic/queries.tsscripts/lib/mainnet-soft-launch-defaults.shdocs/reference/fee-discount-tiers.mdsmartcontracts/contracts/fee-discount/(balance query semantics reference only — no contract changes)Deploy
docker/indexer/Dockerfile, Coolify indexer env examples underdeployments/Recommended direction
Schema (illustrative)
voting_registrations—wallet_address,registered_at_height,registered_at_time,initial_balance,status, optional link to signature id owned byoperator-votingcl8y_cw20_transfers— height, tx hash, from, to, amount, action, only rows involving ≥1 registered wallet + CL8Y contractcl8y_balancesor height-addressable checkpoints — running balance per registered wallet after each affecting transfer; enough to answerbalance(wallet, height)forheight >= registered_at_height(0 or null before registration)Registration ingest
operator-votingverifies ADR-36 (or chosen) signature and inserts/requests registration.{"balance":{"address":...}}on CL8Y CW20initial_balance+ heightregistered_at_heightforward, transfer parser updates that wallet’s ledger.Transfer indexing
parser.rs(or dedicated module): when_contract_address == CL8Y_TOKENand action ∈ {transfer, send, …}, iffromortois registered, append transfer + update balances.Balance-at-height API surface for
operator-votingvoting_cl8y_balance_at(wallet, height)granted only to theoperator_votingDB role — no broad table write grants.LocalTerra
Acceptance criteria
(wallet, height).balance(wallet, H)forH >= registered_at_heightmatches LCD live balance after replaying transfers up to H (property/integration tested on LocalTerra).Test plan (all paths)
_contract_address; registered vs unregistered from/to; forgedcontract_addresswithout underscore ignored (#285).Test plan — attack, hack & abuse vectors
contract_address(no underscore) claiming CL8Y transfer — must not credit.Verification criteria
cargo testfor indexer units covering parser + balance helpers.balance_at(LocalTerra or fixtures).x-cosmos-block-heightin the registration/ledger path.operator-votingissue can read balances via restricted DB role only.Labels
indexerbackendenhancementarchitecturesecurityPriority
P1
Bundle links
Offchain CL8Y snapshot voting (no contracts, no archive LCD):
operator-votingpackage — restricted DB, signatures, proposals, votes, blacklist envSuggested order: #509 → #510 → #511 (UI can stub #510 with MSW in parallel).
mentioned in issue #510
mentioned in issue #511
marked as related to #510
marked as related to #511
mentioned in issue #588
marked as related to #588
Scope add: BSC BEP-20 CL8Y ledger (core)
A majority of CL8Y holders are on BSC, not Terra Classic. The registration + balance ledger in this issue must also track BEP-20 CL8Y for registered EVM wallets — same “live balance at registration + transfers forward, no archive node” model as CW20.
Canonical BEP-20:
0x8F452a1fdd388A45e1080992eFF051b4dd9048d2Do not treat Terra CW20-only as a complete data plane. Reuse
BSC_RPC_URLStransport; do not overload the Venus vFDUSD poller (#571).Tracking issue (BSC integration): https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/588
Voting work left this repo on 2026-08-25.
Canonical project: https://gitlab.com/PlasticDigits/voting
Also opened there: Legal clickwrap (#5), wallet reuse (#6). Do not implement voting in this DEX repo.
mentioned in issue #637