feat(indexer): tax-aware route/solve ranking for community tax CW20 #615

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

Community tax classify T592-13 / #607 improved option 2 (official-router hops tax the original trader). Retail always-on hybrid #596. Swap + Trade market share GET /route/solve #501. Catalog buy_bps / sell_bps #594. Solver contract: ADR 0002, docs/route-solver.md.

Playbooks: skills/AGENTS_COMMUNITY_TAX_ROUTER.md, skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md, skills/AGENTS_HYBRID_QUOTING.md.


Current codebase

GET /api/v1/route/solve ranks candidate paths by router simulate_swap_operations / pair hybrid_simulation estimated_amount_out. Optional trader is CL8Y fee-discount only (#238 / #245 / #283).

It does not read the #594 catalog or TaxPreview. Community buy/sell never enter the score.

That is still quote=execute for pair/router hop amounts (inbound stays 1:1, T592-1 / H-01). It is not best net for the trader after #607.

Frontend Max shrinks amount_in for sell extra-debit (extraDebitSellBpsForExecute). Wallet sim / You Receive is still pre-buy-tax pair out.


Why this is needed

After #607 option 2, advertised bps apply on pair-direct and official-router hops. The solver can still pick a route that is worse net, or that fails at execute:

  1. Tax token as output — sim amount_out is pre-buy-split. You Receive overstates. Ranking among those paths is monotone (same buy_bps), but display ≠ execute net.
  2. Tax token as intermediate hop (e.g. UST1 → TAX → USTR) — pair→router is 1:1; router Send+Swap extra-debits the trader in TAX leftover. The user never received that leftover (tokens sat on the router). Execute hits InsufficientForSellTax unless they already hold TAX. Solver can still pick this path if pool+book out is highest.
  3. Live 11611 vs option-2 wasm — columbus-5 11611 pin is Honest hops until CMM migrate. New crate / post-migrate instances tax router hops. Solver must not assume one policy for every code_id.

Do not “fix” this by adding pair/router FoT math (H-01). Do not turn hybrid off (#596).


Constraints / guardrails

  1. Pair/router swap math and LCD sim inputs stay unchanged. Tax is a score / display / path-eligibility layer on top of existing hybrid sims.
  2. Inbound to pair / router stays 1:1 (T592-1). Execute still Sends the declared offer; extra-debit is leftover, not a smaller hop.
  3. min_return / max_spread stay on pair/router amounts (H-01). UI receive for a tax-token output is post-buy-split.
  4. Manager-directory skip (E609-1): if trader is catalog-exempt, apply 0 bps. Unknown exempt → fail closed (keep bps).
  5. Do not honor a spoofable client buy_bps query param as the only source — prefer indexer catalog snapshot + optional LCD TaxPreview check.
  6. Hybrid GET cache keys must include the tax identity used for ranking (bps and/or “router hops tax” flag). Do not serve a no-tax cached winner to a tax-aware client (or the reverse).
  7. Official dApp stays on GET best-execution. No retail pool_only to dodge tax (#596).

Ranking contract (implement this)

Let raw_out = today’s hop/router sim output (unchanged).

Eligibility (fail closed):

  • Drop a candidate path if any hop sells a catalogued community-tax token (Send+Swap of that token) and that token is not token_in.
    • Rationale: extra-debit needs leftover of that token; the hop credit sits on the router, not the trader. Picking it is a revert trap after option-2 wasm.
  • Exception: token_in is that tax token (first hop sell). Max already reserved leftover. All remaining paths pay the same sell extra-debit in token_in — do not re-rank on that leftover.

Score:

  • If token_out is a catalogued tax token and buy_bps > 0 and trader is not directory-exempt: net_out = raw_out - floor(raw_out * buy_bps / 10000).
  • Else net_out = raw_out.
  • Winner = max net_out (same tie-break as today: first path with that out).

Response (additive, do not break integrators):

  • Keep estimated_amount_out = raw_out (execute / min_return / wallet sim still use this).
  • Add estimated_amount_out_net (and optionally tax_kind / buy_tax / sell_tax / tax_notes) so Swap/Trade You Receive can show post-buy-split when token_out is the tax token.
  • optimality_scope / hybrid_notes must say ranking is net of catalog buy/sell policy for this snapshot, inside the existing top-5 / grid bounds.

11611 pin:

  • Catalog row for live 11611 (pre-migrate hash) must not treat router hops as taxed for eligibility (intermediate TAX hop still Honest on-chain).
  • Detect via stored code_id + known option-2 data_hash or an explicit indexer flag after migrate. Document the switch in the #607 playbook. Do not silently mark unmigrated 11611 as option 2.

Relevant files


Acceptance criteria

  • Path rank uses net_out as specified; hop LCD/DB sims unchanged.
  • Candidate that sells a catalogued tax token as a middle hop is skipped when that token is not token_in (option-2 wasm only).
  • token_in tax token: same extra-debit on every remaining path; winner still max raw_out / net_out of the ask.
  • token_out tax token: estimated_amount_out_net matches TaxPreview buy split; Swap/Trade You Receive uses net; submit still Sends declared and uses pre-tax min_return.
  • Manager-exempt trader → 0 bps (catalog or LCD). Unknown exempt fail-closed.
  • Cache key isolates tax-aware vs unaware winners.
  • Unmigrated 11611 policy documented and implemented (router hops Honest until flag/hash flip).
  • make verify-issue-607 still green (do not regress classify). New make verify-issue-<this> for solver + frontend helpers.
  • Skills + docs/route-solver.md + ADR 0002 amendment + cross-links.

Test plan

  1. TAX→UST1 direct vs TAX→USTR→UST1: both eligible; extra-debit same; pick higher raw_out.
  2. UST1→TAX: net_out < raw_out by buy bps; You Receive shows net; execute still works.
  3. UST1→TAX→USTR vs UST1→USTR (option-2 wasm): first path skipped; second can win.
  4. Same as (3) on unmigrated 11611: first path still eligible (Honest hops).
  5. Manager-exempt trader: net_out == raw_out.
  6. Hybrid book split unchanged vs today’s sim for a single eligible path (quote=execute hop amounts).
  7. Cache: tax token vs ordinary CW20 do not share a winner row.
  8. Do not create pair insolvency: inbound 1:1 regardless of score.

Verification

  • Design close = written ranking contract in this issue + T592-13 held.
  • Implementation = indexer + dApp receive net + verify script. Not a passing PoC alone.
  • Do not teach the pair/router to size extra-debit.
## Parent / related Community tax classify **T592-13** / [#607](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/607) improved option 2 (official-router hops tax the original trader). Retail always-on hybrid [#596](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/596). Swap + Trade market share `GET /route/solve` [#501](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/501). Catalog `buy_bps` / `sell_bps` [#594](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/594). Solver contract: [ADR 0002](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/adr/0002-global-best-execution-route-solver.md), [`docs/route-solver.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/route-solver.md). Playbooks: [`skills/AGENTS_COMMUNITY_TAX_ROUTER.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_COMMUNITY_TAX_ROUTER.md), [`skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md), [`skills/AGENTS_HYBRID_QUOTING.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_HYBRID_QUOTING.md). --- ## Current codebase `GET /api/v1/route/solve` ranks candidate paths by router `simulate_swap_operations` / pair `hybrid_simulation` **`estimated_amount_out`**. Optional `trader` is **CL8Y fee-discount** only (#238 / #245 / #283). It does **not** read the #594 catalog or `TaxPreview`. Community buy/sell never enter the score. That is still quote=execute for **pair/router hop amounts** (inbound stays 1:1, **T592-1** / **H-01**). It is **not** best **net** for the trader after #607. Frontend Max shrinks `amount_in` for sell extra-debit (`extraDebitSellBpsForExecute`). Wallet sim / You Receive is still pre-buy-tax pair out. --- ## Why this is needed After #607 option 2, advertised bps apply on pair-direct **and** official-router hops. The solver can still pick a route that is worse net, or that **fails at execute**: 1. **Tax token as output** — sim `amount_out` is pre-buy-split. You Receive overstates. Ranking among those paths is monotone (same `buy_bps`), but display ≠ execute net. 2. **Tax token as intermediate hop** (e.g. UST1 → TAX → USTR) — pair→router is 1:1; router `Send+Swap` extra-debits the trader in **TAX leftover**. The user never received that leftover (tokens sat on the router). Execute hits `InsufficientForSellTax` unless they already hold TAX. Solver can still pick this path if pool+book out is highest. 3. **Live 11611 vs option-2 wasm** — columbus-5 **11611** pin is Honest hops until CMM migrate. New crate / post-migrate instances tax router hops. Solver must not assume one policy for every `code_id`. Do not “fix” this by adding pair/router FoT math (**H-01**). Do not turn hybrid off (#596). --- ## Constraints / guardrails 1. Pair/router **swap math and LCD sim inputs stay unchanged**. Tax is a **score / display / path-eligibility** layer on top of existing hybrid sims. 2. Inbound to pair / router stays 1:1 (**T592-1**). Execute still Sends the **declared** offer; extra-debit is leftover, not a smaller hop. 3. `min_return` / `max_spread` stay on **pair/router** amounts (H-01). UI receive for a tax-token **output** is post-buy-split. 4. Manager-directory skip (**E609-1**): if `trader` is catalog-exempt, apply 0 bps. Unknown exempt → fail closed (keep bps). 5. Do not honor a spoofable client `buy_bps` query param as the only source — prefer indexer catalog snapshot + optional LCD `TaxPreview` check. 6. Hybrid GET cache keys must include the tax identity used for ranking (bps and/or “router hops tax” flag). Do not serve a no-tax cached winner to a tax-aware client (or the reverse). 7. Official dApp stays on GET best-execution. No retail `pool_only` to dodge tax (#596). --- ## Ranking contract (implement this) Let `raw_out` = today’s hop/router sim output (unchanged). **Eligibility (fail closed):** - Drop a candidate path if any hop **sells** a catalogued community-tax token (`Send+Swap` of that token) **and** that token is **not** `token_in`. - Rationale: extra-debit needs leftover of that token; the hop credit sits on the router, not the trader. Picking it is a revert trap after option-2 wasm. - Exception: `token_in` **is** that tax token (first hop sell). Max already reserved leftover. All remaining paths pay the same sell extra-debit in `token_in` — do not re-rank on that leftover. **Score:** - If `token_out` is a catalogued tax token and `buy_bps > 0` and trader is not directory-exempt: `net_out = raw_out - floor(raw_out * buy_bps / 10000)`. - Else `net_out = raw_out`. - Winner = max `net_out` (same tie-break as today: first path with that out). **Response (additive, do not break integrators):** - Keep `estimated_amount_out` = **`raw_out`** (execute / `min_return` / wallet sim still use this). - Add `estimated_amount_out_net` (and optionally `tax_kind` / `buy_tax` / `sell_tax` / `tax_notes`) so Swap/Trade You Receive can show post-buy-split when `token_out` is the tax token. - `optimality_scope` / `hybrid_notes` must say ranking is net of catalog buy/sell policy for this snapshot, inside the existing top-5 / grid bounds. **11611 pin:** - Catalog row for live **11611** (pre-migrate hash) must **not** treat router hops as taxed for eligibility (intermediate TAX hop still Honest on-chain). - Detect via stored `code_id` + known option-2 `data_hash` **or** an explicit indexer flag after migrate. Document the switch in the #607 playbook. Do not silently mark unmigrated 11611 as option 2. --- ## Relevant files - `indexer/src/api/route_solver.rs` / `best_execution.rs` (winner compare) - `indexer/src/api/community_tokens.rs` + `db/queries/community_tokens.rs` (bps snapshot) - Hybrid GET cache key (`discount_tier` pattern — add tax identity) - `frontend-dapp/src/utils/cw20RouteSolveQuote.ts` (You Receive = net when output is tax token) - `frontend-dapp/src/utils/taxPreviewMaxSpend.ts` (Max already extra-debits; do not double-count) - `docs/route-solver.md`, ADR 0002 amendment, [`skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md`](skills/AGENTS_INDEXER_HYBRID_BEST_EXECUTION.md), [`skills/AGENTS_HYBRID_QUOTING.md`](skills/AGENTS_HYBRID_QUOTING.md), [`skills/AGENTS_COMMUNITY_TAX_ROUTER.md`](skills/AGENTS_COMMUNITY_TAX_ROUTER.md) --- ## Acceptance criteria - [ ] Path rank uses `net_out` as specified; hop LCD/DB sims unchanged. - [ ] Candidate that sells a catalogued tax token as a **middle** hop is skipped when that token is not `token_in` (option-2 wasm only). - [ ] `token_in` tax token: same extra-debit on every remaining path; winner still max `raw_out` / `net_out` of the ask. - [ ] `token_out` tax token: `estimated_amount_out_net` matches `TaxPreview` buy split; Swap/Trade You Receive uses net; submit still Sends declared and uses pre-tax `min_return`. - [ ] Manager-exempt `trader` → 0 bps (catalog or LCD). Unknown exempt fail-closed. - [ ] Cache key isolates tax-aware vs unaware winners. - [ ] Unmigrated 11611 policy documented and implemented (router hops Honest until flag/hash flip). - [ ] `make verify-issue-607` still green (do not regress classify). New `make verify-issue-<this>` for solver + frontend helpers. - [ ] Skills + `docs/route-solver.md` + ADR 0002 amendment + cross-links. --- ## Test plan 1. TAX→UST1 direct vs TAX→USTR→UST1: both eligible; extra-debit same; pick higher `raw_out`. 2. UST1→TAX: `net_out` < `raw_out` by buy bps; You Receive shows net; execute still works. 3. UST1→TAX→USTR vs UST1→USTR (option-2 wasm): first path **skipped**; second can win. 4. Same as (3) on unmigrated 11611: first path still eligible (Honest hops). 5. Manager-exempt trader: `net_out == raw_out`. 6. Hybrid book split unchanged vs today’s sim for a single eligible path (quote=execute hop amounts). 7. Cache: tax token vs ordinary CW20 do not share a winner row. 8. Do not create pair insolvency: inbound 1:1 regardless of score. --- ## Verification - Design close = written ranking contract in this issue + T592-13 held. - Implementation = indexer + dApp receive net + verify script. Not a passing PoC alone. - Do **not** teach the pair/router to size extra-debit.
PlasticDigits commented 2026-08-24 00:35:17 +00:00 (Migrated from gitlab.com)

marked as related to #607

marked as related to #607
PlasticDigits commented 2026-08-24 00:35:17 +00:00 (Migrated from gitlab.com)

marked as related to #596

marked as related to #596
PlasticDigits commented 2026-08-24 00:35:18 +00:00 (Migrated from gitlab.com)

marked as related to #501

marked as related to #501
PlasticDigits commented 2026-08-24 00:35:19 +00:00 (Migrated from gitlab.com)

marked as related to #594

marked as related to #594
PlasticDigits commented 2026-08-24 00:53:50 +00:00 (Migrated from gitlab.com)

mentioned in commit 17ee753b69

mentioned in commit 17ee753b692eb3407d142b2254ecea796ea92f5a
PlasticDigits commented 2026-08-24 00:54:07 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1131

mentioned in merge request !1131
PlasticDigits commented 2026-08-24 00:54:14 +00:00 (Migrated from gitlab.com)

Implemented on issue/615-tax-aware-route-ranking — MR !1131. make verify-issue-615 passed (includes verify-issue-607 + retest).

Done vs issue acceptance

  • Path rank uses net_out; hop LCD/DB sims unchanged (R615-1).
  • Middle-hop TAX sell skipped when not token_in — option-2 only (COMMUNITY_TAX_OPTION2_*).
  • token_in tax: leftover not re-ranked; winner max ask raw/net.
  • token_out tax: estimated_amount_out_net + You Receive net; min_return pre-tax.
  • Manager-exempt → 0 bps; unknown/LCD fail-closed.
  • Cache key includes tax identity.
  • Unmigrated 11611 Honest until env flip (R615-5 + #607 playbook).
  • verify-issue-607 + verify-issue-615; skills / route-solver / ADR 0002 / invariants.

Not in this change: option-2 wasm/classify (keep on feat/607-router-tax-option-2); live columbus-5 execute of the skip; catalog data_hash storage; pair/router FoT math; LCD TaxPreview on every hop.

Playbook: skills/AGENTS_INDEXER_TAX_AWARE_ROUTING.md (R615-1–R615-8).

Implemented on `issue/615-tax-aware-route-ranking` — MR !1131. `make verify-issue-615` passed (includes `verify-issue-607` + retest). **Done vs issue acceptance** - [x] Path rank uses `net_out`; hop LCD/DB sims unchanged (R615-1). - [x] Middle-hop TAX sell skipped when not `token_in` — option-2 only (`COMMUNITY_TAX_OPTION2_*`). - [x] `token_in` tax: leftover not re-ranked; winner max ask raw/net. - [x] `token_out` tax: `estimated_amount_out_net` + You Receive net; `min_return` pre-tax. - [x] Manager-exempt → 0 bps; unknown/LCD fail-closed. - [x] Cache key includes tax identity. - [x] Unmigrated 11611 Honest until env flip (R615-5 + #607 playbook). - [x] `verify-issue-607` + `verify-issue-615`; skills / route-solver / ADR 0002 / invariants. **Not in this change:** option-2 wasm/classify (keep on `feat/607-router-tax-option-2`); live columbus-5 execute of the skip; catalog `data_hash` storage; pair/router FoT math; LCD TaxPreview on every hop. Playbook: `skills/AGENTS_INDEXER_TAX_AWARE_ROUTING.md` (R615-1–R615-8).
PlasticDigits commented 2026-08-24 02:45:18 +00:00 (Migrated from gitlab.com)

mentioned in commit 2d9b573b6f

mentioned in commit 2d9b573b6fa8a2f34af1e778cb9e235f3e3bf9cc
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-24 02:45:19 +00:00
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:19 +00:00 (Migrated from gitlab.com)

marked as related to #616

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

Merge sanity (!1131 landed on main; CI not waited).

Conflicts with !1127 were docs/Makefile/skills: kept option-2 implement close (not disclose-only Honest hops) plus R615 ranking + 11611 pin.

Automated: make verify-issue-615 7/7 (R615 docs/ADR, indexer net rank + cache identity, You Receive net helpers, nested verify-issue-607).

Acceptance leftover

  • Unmigrated columbus-5 11611 stays Honest for eligibility until COMMUNITY_TAX_OPTION2_CODE_IDS / COMMUNITY_TAX_OPTION2_DATA_HASHES after CMM migrate. Ranking will still allow a middle TAX hop until that flip.
  • Live execute of UST1→TAX→USTR skip was not run (unit-tested only).
  • Catalog still does not store data_hash (env pin only).

Post-merge env flip + live ranking QA: #616.

**Merge sanity (!1131 landed on `main`; CI not waited).** Conflicts with !1127 were docs/Makefile/skills: kept option-2 implement close (not disclose-only Honest hops) plus R615 ranking + 11611 pin. Automated: `make verify-issue-615` **7/7** (R615 docs/ADR, indexer net rank + cache identity, You Receive net helpers, nested `verify-issue-607`). **Acceptance leftover** - Unmigrated columbus-5 **11611** stays Honest for eligibility until `COMMUNITY_TAX_OPTION2_CODE_IDS` / `COMMUNITY_TAX_OPTION2_DATA_HASHES` after CMM migrate. Ranking will still allow a middle TAX hop until that flip. - Live execute of UST1→TAX→USTR skip was not run (unit-tested only). - Catalog still does not store `data_hash` (env pin only). Post-merge env flip + live ranking QA: [#616](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/616).
PlasticDigits commented 2026-08-24 05:54:00 +00:00 (Migrated from gitlab.com)

mentioned in issue #620

mentioned in issue #620
PlasticDigits commented 2026-08-24 05:54:02 +00:00 (Migrated from gitlab.com)

marked as related to #620

marked as related to #620
PlasticDigits commented 2026-08-24 05:54:14 +00:00 (Migrated from gitlab.com)

mentioned in issue #622

mentioned in issue #622
PlasticDigits commented 2026-08-24 05:54:18 +00:00 (Migrated from gitlab.com)

marked as related to #622

marked as related to #622
PlasticDigits commented 2026-08-27 11:52:20 +00:00 (Migrated from gitlab.com)

mentioned in issue #690

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