feat(voting): operator-voting package — limited DB balances-at-height + signatures, proposals, blacklist #510

Closed
opened 2026-08-09 08:25:56 +00:00 by PlasticDigits · 10 comments
PlasticDigits commented 2026-08-09 08:25:56 +00:00 (Migrated from gitlab.com)

Summary

Create a new standalone package operator-voting (sibling to indexer / under packages/ as appropriate) that provides the offchain voting control plane with least-privilege Postgres access:

  1. Read CL8Y CW20 balances at specific block heights from the indexer-maintained ledger (registered wallets only).
  2. Write/read wallet signatures (registration, proposal creation, votes).
  3. Host proposal + ballot APIs (no smart contracts), including env-based vote blacklist.

Frontend and indexer remain separate; this package must not receive the indexer’s broad DB credentials.

CL8Y: terra16wtml2q66g82fdkx66tap0qjkahqwp4lwq3ngtygacg5q0kzycgqvhpax3 (fee-discount / hpax3). LocalTerra TCL8Y via shared env.

Linked:

  • Indexer registration + transfer ledger (data plane)
  • Frontend WYSIWYG proposals + voting UX

Current codebase

Area Status
Indexer Axum API indexer/src/api/ — almost entirely read DEX analytics; only notable POST is route/solve (quote, not user auth). No signature verify, no vote routes
Indexer DB access Single DATABASE_URL with full migrate/write — unsuitable to expose directly to a voting write API
Crypto / ADR-36 No secp256k1/ADR-36 verify deps in indexer/Cargo.toml; frontend has Keplr tx sign (terraWalletSignTxRaw.ts) but no signArbitrary typing/helpers
Packages layout packages/localnet-trading-swarm (TS); Rust indexer is standalone crate cl8y-dex-indexer (not a workspace member with shared voting crate yet)
Governance in-repo On-chain factory governance + multisig runbooks — not tokenholder polls
Blacklists On-chain trading blacklist (factory/pair); not an offchain vote blacklist env
Coolify Indexer + Postgres already deployed (indexer.dex.cl8y.com); pattern for a second service/env exists via deploy docs

Why this is needed

Voting must accept user-submitted signatures and persist them. Putting that write path on the full indexer DB role expands blast radius (analytics tables, ingestion state). A dedicated operator-voting binary/package with a restricted DB role (SELECT on balance views + CRUD on signature/proposal/vote tables only) isolates abuse and matches the product constraint: limited database access only for balances-at-height reads and signature read/write.

Also centralizes ADR-36 verify, proposal rules (≥1000 CL8Y to create), snapshot freeze, tally, and VOTING_BLACKLIST enforcement in one audit surface.


Constraints / guardrails

  1. No smart contracts.
  2. Least privilege: operator-voting DB user must not INSERT/UPDATE indexer ledger tables, indexer_state, swap tables, etc. Prefer dedicated schema (e.g. voting) + GRANT SELECT on balance view/function from indexer migrations.
  3. Signature authenticity: verify Terra Classic ADR-36 (or documented equivalent Keplr signArbitrary) → recover/derive terra1… and require it matches claimed voter/proposer/registrar.
  4. Registration before electorate membership: reject votes/proposal weight for wallets not registered in the ledger at/before snapshot rules defined with the indexer issue.
  5. Proposal creation gate: proposer must have ≥ 1000 CL8Y at creation time per ledger (or live LCD + ledger tip — pick one, document; prefer ledger tip at current indexed height for consistency) and attach a valid signature over the proposal payload (canonical bytes).
  6. Snapshot: when a proposal is created (or explicitly snapshotted), freeze vote weights for registered wallets at height H using Postgres balance-at-height only (no archive LCD).
  7. Blacklist env: comma-separated bech32 addresses (e.g. VOTING_BLACKLIST_ADDRESSES or OPERATOR_VOTING_BLACKLIST) — blacklisted wallets cannot vote (and document whether they can still register/create proposals; default: cannot vote; creating proposals as blacklisted should also be denied unless product says otherwise — deny both vote and propose).
  8. Idempotency: one registration signature result per wallet; one vote per (proposal_id, wallet); proposal create is not replayable with same id.
  9. Replay protection: signed payloads must include domain separation (chain-id, app name, purpose, proposal id / register nonce, expiry or height bound as appropriate).
  10. Do not proxy arbitrary SQL; no admin “set balance” API.
  11. Rate-limit all POSTs; body size limits for proposal HTML.
  12. Sanitize/store WYSIWYG HTML safely (allowlist tags) — XSS is a product risk even if rendered later in frontend.
  13. Package name operator-voting as requested; shipable as its own Coolify service with its own DATABASE_URL (restricted).

Relevant files

New

  • operator-voting/ or packages/operator-voting/ — crate/package root (Cargo.toml or Node — prefer Rust to share sqlx/postgres ops with indexer, unless team standardizes TS; recommend Rust Axum sibling binary)
  • Migrations for voting schema: either owned by operator-voting or additive indexer migrations that create views + voting schema with grants
  • Dockerfile + Coolify env example
  • README: threat model, env vars, DB grants

Reuse / integrate

  • Indexer balance views from linked registration/ledger issue
  • indexer/src/api/mod.rs patterns (utoipa, CORS, governor rate limits) as reference — do not merge routes into indexer
  • Frontend will call this service’s base URL via new VITE_OPERATOR_VOTING_URL (frontend issue)

Reference only

  • frontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.ts, wallet.ts
  • docs/security-model.md, Coolify deploy runbooks

Responsibilities

Endpoint (illustrative) Behavior
POST /v1/register Verify register signature → persist sig → signal/insert registration for indexer ledger snapshot
GET /v1/registration/:addr Status + registered height
POST /v1/proposals Verify proposer sig; check ≥1000 CL8Y; sanitize WYSIWYG body; freeze snapshot at current indexed height; store proposal + sig
GET /v1/proposals, GET /v1/proposals/:id List/detail + tallies
POST /v1/proposals/:id/votes Verify vote sig; enforce blacklist; enforce registered + snapshot weight > 0; upsert forbidden (one vote); store sig + choice
GET /v1/proposals/:id/votes/:addr Own vote lookup
GET /v1/balances/:addr?height= Thin read through restricted view (optional; may be internal only)

Env

  • DATABASE_URL (restricted role)
  • CL8Y_TOKEN_ADDRESS
  • VOTING_BLACKLIST_ADDRESSES — comma-separated terra1… (trim whitespace; invalid entries fail startup)
  • CORS_ORIGINS, API_BIND, chain-id / signing domain constants
  • MIN_PROPOSAL_CL8Y default 1000 (human units) → 1000 * 10^18 raw

DB

  • Tables: signatures, proposals, proposal_snapshots (wallet → weight), votes
  • Read-only: voting_cl8y_balance_at(address, height) from indexer migrations

Crypto

  • ADR-36 verify compatible with Keplr signArbitrary on columbus-5 / LocalTerra chain-id
  • Store: address, pubkey, signature bytes, signed payload hash, purpose, created_at

Acceptance criteria

  • New operator-voting package builds and runs as its own service.
  • DB role used in docs/migrations cannot write indexer ingestion/ledger tables; can read balances-at-height; can R/W signature/proposal/vote tables.
  • Registration, proposal create, and vote endpoints verify wallet signatures and persist them.
  • Proposal create requires ≥ 1000 CL8Y (raw 18-dec) and a valid proposer signature over canonical proposal content.
  • Proposal creation freezes snapshot weights from Postgres ledger at height H (no archive LCD).
  • VOTING_BLACKLIST_ADDRESSES (or final agreed name) parsed from env; blacklisted addresses cannot vote (and cannot create proposals per guardrail above).
  • One vote per wallet per proposal; duplicate vote rejected.
  • OpenAPI/README + Coolify env example.
  • No smart contract changes; not merged into indexer API surface.

Test plan (all paths)

  1. Unit — sig verify: valid ADR-36 accepts; wrong address / mangled sig / wrong chain-id / wrong purpose reject.
  2. Unit — blacklist: parsing, trim, deny vote + deny propose.
  3. Unit — threshold: 999.9… CL8Y reject create; 1000 accept (boundary at 1000 * 10^18).
  4. Unit — HTML allowlist: script tags stripped/rejected.
  5. Integration — register: sig → row → indexer registration handoff → balance readable at height.
  6. Integration — proposal: registered whale ≥1000 creates; snapshot rows equal ledger at H.
  7. Integration — vote: weight equals snapshot (not live tip if they transferred after); tally sums.
  8. Integration — unregistered: cannot vote with weight; create may fail threshold.
  9. DB privilege test: restricted role SELECT on balance view OK; INSERT into swap_events / ledger tables fails.
  10. Idempotency / concurrency: double-submit vote unique constraint.

Test plan — attack, hack & abuse vectors

  1. Signature malleability / replay: reuse register sig as vote sig — purpose/domain separation must fail.
  2. Cross-proposal replay: vote sig for proposal A submitted to B — reject.
  3. Blacklist bypass: checksum/case variants of bech32 — normalize before compare.
  4. Proposer below threshold via stale cache: always read ledger at create height; test race with transfer out before create.
  5. Vote after selling CL8Y: weight must remain snapshot weight (not tip) — document; test transfer-after-snapshot still counts old weight (standard snapshot voting).
  6. XSS in proposal body: stored HTML cannot execute privileged actions; CSP notes for frontend.
  7. Postgres credential leak: confirm service boots with restricted URL only in prod compose example.
  8. DoS: huge HTML body, high QPS vote spam — rate limits + body cap.
  9. Enumeration: optional — do not leak full voter list if product says private; if public tallies, document.
  10. Confused deputy: frontend passes arbitrary voter field ≠ recovered address — reject.

Verification criteria

  • CI job builds/tests operator-voting.
  • Integration tests prove restricted DB grants (positive + negative).
  • LocalTerra path: register → create proposal → vote → tally matches snapshot weights.
  • Env blacklist verified with a denylisted test address.
  • README documents grant SQL and Coolify env.
  • Security-minded review of verify + replay + XSS paths before mainnet Coolify expose.

Labels

backend indexer enhancement architecture security deploy

Priority

P1

## Summary Create a new standalone package **`operator-voting`** (sibling to `indexer` / under `packages/` as appropriate) that provides the **offchain voting control plane** with **least-privilege Postgres access**: 1. **Read** CL8Y CW20 balances at specific block heights from the indexer-maintained ledger (registered wallets only). 2. **Write/read wallet signatures** (registration, proposal creation, votes). 3. Host **proposal + ballot APIs** (no smart contracts), including env-based **vote blacklist**. Frontend and indexer remain separate; this package must not receive the indexer’s broad DB credentials. **CL8Y:** `terra16wtml2q66g82fdkx66tap0qjkahqwp4lwq3ngtygacg5q0kzycgqvhpax3` (fee-discount / hpax3). LocalTerra TCL8Y via shared env. Linked: - Indexer registration + transfer ledger (data plane) - Frontend WYSIWYG proposals + voting UX --- ## Current codebase | Area | Status | |------|--------| | Indexer Axum API | `indexer/src/api/` — almost entirely **read** DEX analytics; only notable POST is `route/solve` (quote, not user auth). No signature verify, no vote routes | | Indexer DB access | Single `DATABASE_URL` with full migrate/write — unsuitable to expose directly to a voting write API | | Crypto / ADR-36 | **No** secp256k1/ADR-36 verify deps in `indexer/Cargo.toml`; frontend has Keplr **tx** sign (`terraWalletSignTxRaw.ts`) but **no** `signArbitrary` typing/helpers | | Packages layout | `packages/localnet-trading-swarm` (TS); Rust indexer is standalone crate `cl8y-dex-indexer` (not a workspace member with shared voting crate yet) | | Governance in-repo | On-chain factory governance + multisig runbooks — **not** tokenholder polls | | Blacklists | On-chain trading blacklist (factory/pair); **not** an offchain vote blacklist env | | Coolify | Indexer + Postgres already deployed (`indexer.dex.cl8y.com`); pattern for a second service/env exists via deploy docs | --- ## Why this is needed Voting must accept **user-submitted signatures** and persist them. Putting that write path on the full indexer DB role expands blast radius (analytics tables, ingestion state). A dedicated **`operator-voting`** binary/package with a **restricted DB role** (SELECT on balance views + CRUD on signature/proposal/vote tables only) isolates abuse and matches the product constraint: *limited database access only for balances-at-height reads and signature read/write*. Also centralizes ADR-36 verify, proposal rules (≥1000 CL8Y to create), snapshot freeze, tally, and `VOTING_BLACKLIST` enforcement in one audit surface. --- ## Constraints / guardrails 1. **No smart contracts.** 2. **Least privilege:** `operator-voting` DB user must **not** INSERT/UPDATE indexer ledger tables, `indexer_state`, swap tables, etc. Prefer dedicated schema (e.g. `voting`) + `GRANT SELECT` on balance view/function from indexer migrations. 3. **Signature authenticity:** verify Terra Classic ADR-36 (or documented equivalent Keplr `signArbitrary`) → recover/derive `terra1…` and require it matches claimed voter/proposer/registrar. 4. **Registration before electorate membership:** reject votes/proposal weight for wallets not registered in the ledger at/before snapshot rules defined with the indexer issue. 5. **Proposal creation gate:** proposer must have **≥ 1000 CL8Y** at creation time per ledger (or live LCD + ledger tip — pick one, document; prefer ledger tip at current indexed height for consistency) **and** attach a valid signature over the proposal payload (canonical bytes). 6. **Snapshot:** when a proposal is created (or explicitly snapshotted), freeze vote weights for registered wallets at height `H` using **Postgres balance-at-height only** (no archive LCD). 7. **Blacklist env:** comma-separated bech32 addresses (e.g. `VOTING_BLACKLIST_ADDRESSES` or `OPERATOR_VOTING_BLACKLIST`) — blacklisted wallets **cannot vote** (and document whether they can still register/create proposals; default: **cannot vote**; creating proposals as blacklisted should also be denied unless product says otherwise — **deny both vote and propose**). 8. **Idempotency:** one registration signature result per wallet; one vote per `(proposal_id, wallet)`; proposal create is not replayable with same id. 9. **Replay protection:** signed payloads must include domain separation (chain-id, app name, purpose, proposal id / register nonce, expiry or height bound as appropriate). 10. **Do not** proxy arbitrary SQL; no admin “set balance” API. 11. Rate-limit all POSTs; body size limits for proposal HTML. 12. Sanitize/store WYSIWYG HTML safely (allowlist tags) — XSS is a product risk even if rendered later in frontend. 13. Package name **`operator-voting`** as requested; shipable as its own Coolify service with its own `DATABASE_URL` (restricted). --- ## Relevant files **New** - `operator-voting/` or `packages/operator-voting/` — crate/package root (`Cargo.toml` or Node — **prefer Rust** to share sqlx/postgres ops with indexer, unless team standardizes TS; recommend **Rust Axum** sibling binary) - Migrations for voting schema: either owned by `operator-voting` or additive indexer migrations that create views + `voting` schema with grants - Dockerfile + Coolify env example - README: threat model, env vars, DB grants **Reuse / integrate** - Indexer balance views from linked registration/ledger issue - `indexer/src/api/mod.rs` patterns (utoipa, CORS, governor rate limits) as reference — do not merge routes into indexer - Frontend will call this service’s base URL via new `VITE_OPERATOR_VOTING_URL` (frontend issue) **Reference only** - `frontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.ts`, `wallet.ts` - `docs/security-model.md`, Coolify deploy runbooks --- ## Recommended direction ### Responsibilities | Endpoint (illustrative) | Behavior | |-------------------------|----------| | `POST /v1/register` | Verify register signature → persist sig → signal/insert registration for indexer ledger snapshot | | `GET /v1/registration/:addr` | Status + registered height | | `POST /v1/proposals` | Verify proposer sig; check ≥1000 CL8Y; sanitize WYSIWYG body; freeze snapshot at current indexed height; store proposal + sig | | `GET /v1/proposals`, `GET /v1/proposals/:id` | List/detail + tallies | | `POST /v1/proposals/:id/votes` | Verify vote sig; enforce blacklist; enforce registered + snapshot weight > 0; upsert forbidden (one vote); store sig + choice | | `GET /v1/proposals/:id/votes/:addr` | Own vote lookup | | `GET /v1/balances/:addr?height=` | Thin read through restricted view (optional; may be internal only) | ### Env - `DATABASE_URL` (restricted role) - `CL8Y_TOKEN_ADDRESS` - `VOTING_BLACKLIST_ADDRESSES` — comma-separated `terra1…` (trim whitespace; invalid entries fail startup) - `CORS_ORIGINS`, `API_BIND`, chain-id / signing domain constants - `MIN_PROPOSAL_CL8Y` default `1000` (human units) → `1000 * 10^18` raw ### DB - Tables: `signatures`, `proposals`, `proposal_snapshots` (wallet → weight), `votes` - Read-only: `voting_cl8y_balance_at(address, height)` from indexer migrations ### Crypto - ADR-36 verify compatible with Keplr `signArbitrary` on `columbus-5` / LocalTerra chain-id - Store: address, pubkey, signature bytes, signed payload hash, purpose, created_at --- ## Acceptance criteria - [ ] New `operator-voting` package builds and runs as its own service. - [ ] DB role used in docs/migrations cannot write indexer ingestion/ledger tables; can read balances-at-height; can R/W signature/proposal/vote tables. - [ ] Registration, proposal create, and vote endpoints verify wallet signatures and persist them. - [ ] Proposal create requires ≥ 1000 CL8Y (raw 18-dec) and a valid proposer signature over canonical proposal content. - [ ] Proposal creation freezes snapshot weights from Postgres ledger at height H (no archive LCD). - [ ] `VOTING_BLACKLIST_ADDRESSES` (or final agreed name) parsed from env; blacklisted addresses cannot vote (and cannot create proposals per guardrail above). - [ ] One vote per wallet per proposal; duplicate vote rejected. - [ ] OpenAPI/README + Coolify env example. - [ ] No smart contract changes; not merged into indexer API surface. --- ## Test plan (all paths) 1. **Unit — sig verify:** valid ADR-36 accepts; wrong address / mangled sig / wrong chain-id / wrong purpose reject. 2. **Unit — blacklist:** parsing, trim, deny vote + deny propose. 3. **Unit — threshold:** 999.9… CL8Y reject create; 1000 accept (boundary at `1000 * 10^18`). 4. **Unit — HTML allowlist:** script tags stripped/rejected. 5. **Integration — register:** sig → row → indexer registration handoff → balance readable at height. 6. **Integration — proposal:** registered whale ≥1000 creates; snapshot rows equal ledger at H. 7. **Integration — vote:** weight equals snapshot (not live tip if they transferred after); tally sums. 8. **Integration — unregistered:** cannot vote with weight; create may fail threshold. 9. **DB privilege test:** restricted role SELECT on balance view OK; INSERT into `swap_events` / ledger tables fails. 10. **Idempotency / concurrency:** double-submit vote unique constraint. --- ## Test plan — attack, hack & abuse vectors 1. **Signature malleability / replay:** reuse register sig as vote sig — purpose/domain separation must fail. 2. **Cross-proposal replay:** vote sig for proposal A submitted to B — reject. 3. **Blacklist bypass:** checksum/case variants of bech32 — normalize before compare. 4. **Proposer below threshold via stale cache:** always read ledger at create height; test race with transfer out before create. 5. **Vote after selling CL8Y:** weight must remain snapshot weight (not tip) — document; test transfer-after-snapshot still counts old weight (standard snapshot voting). 6. **XSS in proposal body:** stored HTML cannot execute privileged actions; CSP notes for frontend. 7. **Postgres credential leak:** confirm service boots with restricted URL only in prod compose example. 8. **DoS:** huge HTML body, high QPS vote spam — rate limits + body cap. 9. **Enumeration:** optional — do not leak full voter list if product says private; if public tallies, document. 10. **Confused deputy:** frontend passes arbitrary `voter` field ≠ recovered address — reject. --- ## Verification criteria - [ ] CI job builds/tests `operator-voting`. - [ ] Integration tests prove restricted DB grants (positive + negative). - [ ] LocalTerra path: register → create proposal → vote → tally matches snapshot weights. - [ ] Env blacklist verified with a denylisted test address. - [ ] README documents grant SQL and Coolify env. - [ ] Security-minded review of verify + replay + XSS paths before mainnet Coolify expose. ## Labels `backend` `indexer` `enhancement` `architecture` `security` `deploy` ## Priority P1
PlasticDigits commented 2026-08-09 08:26:05 +00:00 (Migrated from gitlab.com)

mentioned in issue #509

mentioned in issue #509
PlasticDigits commented 2026-08-09 08:26:06 +00:00 (Migrated from gitlab.com)
Issue Role
#509 Indexer/Postgres registration + CL8Y ledger (balance-at-height source)
#510 (this) operator-voting — signatures, proposals, votes, VOTING_BLACKLIST_ADDRESSES
#511 Frontend UX

Depends on #509 balance views/grants. Blocks #511 for full-stack E2E.

## Bundle links | Issue | Role | |-------|------| | **#509** | Indexer/Postgres registration + CL8Y ledger (balance-at-height source) | | **#510** (this) | `operator-voting` — signatures, proposals, votes, `VOTING_BLACKLIST_ADDRESSES` | | **#511** | Frontend UX | Depends on #509 balance views/grants. Blocks #511 for full-stack E2E.
PlasticDigits commented 2026-08-09 08:26:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #511

mentioned in issue #511
PlasticDigits commented 2026-08-09 08:26:15 +00:00 (Migrated from gitlab.com)

marked as related to #509

marked as related to #509
PlasticDigits commented 2026-08-09 08:26:18 +00:00 (Migrated from gitlab.com)

marked as related to #511

marked as related to #511
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:38 +00:00 (Migrated from gitlab.com)

marked as related to #588

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

Scope add: EVM signatures + BSC snapshot weight (core)

A majority of CL8Y holders are on BSC, not Terra Classic. operator-voting must verify EIP-191 personal_sign from EVM wallets (in addition to Terra ADR-36), persist signatures for 0x addresses, and freeze vote power from the BSC BEP-20 ledger as well as the Terra CW20 ledger.

Proposal create should record both a Terra Classic height and a BSC block number. VOTING_BLACKLIST_ADDRESSES must accept normalized EVM addresses.

Terra-only auth/weights would exclude most tokenholders — not an acceptable MVP.

Tracking issue (BSC integration): https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/588

Related Role
#509 Terra CW20 and BSC BEP-20 ledgers
#510 (this) ADR-36 and EIP-191; dual snapshot; EVM blacklist
#511 EVM wallet register / vote UX
#588 BSC integration contract / guardrails
## Scope add: EVM signatures + BSC snapshot weight (core) A **majority of CL8Y holders are on BSC**, not Terra Classic. `operator-voting` must verify **EIP-191 `personal_sign`** from EVM wallets (in addition to Terra ADR-36), persist signatures for `0x` addresses, and freeze vote power from the **BSC BEP-20 ledger** as well as the Terra CW20 ledger. Proposal create should record **both** a Terra Classic height and a **BSC block number**. `VOTING_BLACKLIST_ADDRESSES` must accept normalized EVM addresses. Terra-only auth/weights would exclude most tokenholders — not an acceptable MVP. **Tracking issue (BSC integration):** https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/588 | Related | Role | |---------|------| | **#509** | Terra CW20 **and** BSC BEP-20 ledgers | | **#510** (this) | ADR-36 **and** EIP-191; dual snapshot; EVM blacklist | | **#511** | EVM wallet register / vote UX | | **#588** | BSC integration contract / guardrails |
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-25 06:07:23 +00:00
PlasticDigits commented 2026-08-25 06:08:50 +00:00 (Migrated from gitlab.com)

Voting work left this repo on 2026-08-25.

Canonical project: https://gitlab.com/PlasticDigits/voting

Former IID here New
#509 https://gitlab.com/PlasticDigits/voting/-/issues/1
#510 https://gitlab.com/PlasticDigits/voting/-/issues/2
#511 https://gitlab.com/PlasticDigits/voting/-/issues/3
#588 https://gitlab.com/PlasticDigits/voting/-/issues/4

Also opened there: Legal clickwrap (#5), wallet reuse (#6). Do not implement voting in this DEX repo.

Voting work left this repo on 2026-08-25. Canonical project: https://gitlab.com/PlasticDigits/voting | Former IID here | New | |-----------------|-----| | #509 | https://gitlab.com/PlasticDigits/voting/-/issues/1 | | #510 | https://gitlab.com/PlasticDigits/voting/-/issues/2 | | #511 | https://gitlab.com/PlasticDigits/voting/-/issues/3 | | #588 | https://gitlab.com/PlasticDigits/voting/-/issues/4 | Also opened there: Legal clickwrap (#5), wallet reuse (#6). Do not implement voting in this DEX repo.
PlasticDigits commented 2026-08-25 06:08:58 +00:00 (Migrated from gitlab.com)

mentioned in issue #637

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