fix(frontend): extra-debit Sell submits when TaxPreview.debit exceeds balance #1270

Merged
PlasticDigits merged 7 commits from issue/1267 into main 2026-09-21 03:59:28 +00:00

Summary

  • Swap and Trade Market no longer broadcast an extra-debit Sell when LCD TaxPreview.debit (or known sell_bps) exceeds wallet balance. Typed 100% / reverse offer is Insufficient Balance; Max still leaves debit room after human round-trip.
  • Live GetConfig.sell_bps is the sell detector. Catalog VITE_COMMUNITY_TAX_CODE_ID stays Create Token / Manage only. Unknown exempt stays fail-closed. Honest CW20 and sell_bps = 0 stay amount ≤ balance.
  • InsufficientForSellTax maps to retail copy. Pair/router wasm unchanged. Invariants S1267-1–S1267-8 in docs/frontend.md and skills/AGENTS_FRONTEND_EXTRA_DEBIT_SELL.md.

Fixes #1267

Test plan

  • make verify-issue-1267 (Vitest gate + Max + hook + humanize + Swap/Trade RTL + docs)
  • make verify-issue-593 (extra-debit Max / Create Token regressions)
  • LocalTerra execute of a tax-token 100% Sell is not required for this dApp gate (RTL + mocked preview). Optional: confirm CTA on / and /trade Market sell with a listed tax CW20.
  • Confirm catalog pin mismatch still extra-debits (GetConfig sell_bps > 0).
  • Confirm honest UST1 / native LUNC Max and submit are unchanged.
## Summary - Swap and Trade Market no longer broadcast an extra-debit Sell when LCD `TaxPreview.debit` (or known `sell_bps`) exceeds wallet balance. Typed 100% / reverse offer is Insufficient Balance; Max still leaves debit room after human round-trip. - Live `GetConfig.sell_bps` is the sell detector. Catalog `VITE_COMMUNITY_TAX_CODE_ID` stays Create Token / Manage only. Unknown exempt stays fail-closed. Honest CW20 and `sell_bps = 0` stay `amount ≤ balance`. - `InsufficientForSellTax` maps to retail copy. Pair/router wasm unchanged. Invariants **S1267-1–S1267-8** in `docs/frontend.md` and `skills/AGENTS_FRONTEND_EXTRA_DEBIT_SELL.md`. Fixes #1267 ## Test plan - [x] `make verify-issue-1267` (Vitest gate + Max + hook + humanize + Swap/Trade RTL + docs) - [x] `make verify-issue-593` (extra-debit Max / Create Token regressions) - [ ] LocalTerra execute of a tax-token 100% Sell is not required for this dApp gate (RTL + mocked preview). Optional: confirm CTA on `/` and `/trade` Market sell with a listed tax CW20. - [ ] Confirm catalog pin mismatch still extra-debits (GetConfig `sell_bps` > 0). - [ ] Confirm honest UST1 / native LUNC Max and submit are unchanged.
fix(frontend): gate extra-debit Sell when TaxPreview.debit exceeds balance
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
eefbde9d89
Swap and Trade Market block submit when LCD debit or known sell_bps would
exceed wallet balance. Catalog pin is not the sell detector. Max round-trip
and InsufficientForSellTax copy stay aligned with execute.
Author
Owner

drain skip: no occupying job for rebase/fix-pr/CI-wait; remaining continue

drain skip: no occupying job for rebase/fix-pr/CI-wait; remaining continue
Author
Owner

/agent review

/agent review
Author
Owner

cl8y-agent-control: queued bug_review job d4ae1ca3-f782-4443-bd72-c00a3ab03d6f (not executed; no Hetzner VM).

cl8y-agent-control: queued `bug_review` job `d4ae1ca3-f782-4443-bd72-c00a3ab03d6f` (not executed; no Hetzner VM).
Author
Owner

/agent review

/agent review
Author
Owner

cl8y-agent-control: queued bug_review job 94dabcad-408f-4a17-827f-b62ad3213b12 (not executed; no Hetzner VM).

cl8y-agent-control: queued `bug_review` job `94dabcad-408f-4a17-827f-b62ad3213b12` (not executed; no Hetzner VM).
Author
Owner

RECOMMEND: FIX

Independent review of issue/1267 → main at eefbde9d (Fixes #1267). Frontend-only extra-debit Sell CTA. Pair/router/community-tax wasm unchanged. make verify-issue-1267 is green (73 Vitest + greps) and does not exercise the live LCD preview shape this PR treats as execute truth.

Must-fix

1. Live TaxPreview without send_msg fail-opens the new gate (High / S1267-1, AC1)

Severity: High for the ticket (typed 100% still broadcasts). Not on-chain tax evasion (T592-2 still holds).

Where: frontend-dapp/src/hooks/useCommunityTaxSellBps.ts:85-91 (queryTaxPreview omits sendMsg); frontend-dapp/src/utils/taxPreviewMaxSpend.ts:198-204 (parsed debitRaw wins over local sellBps); Swap frontend-dapp/src/pages/SwapPage.tsx:920-934; Trade frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx:399-413.

Why: classify_trade only returns TaxKind::Sell when to is a listed pair and send_msg is a Swap hook:

    if to_pair {
        if let Some(msg) = send_msg {
            if is_swap_send_hook(msg)
                && (!is_protocol_exempt(storage, self_addr, from)
                    || is_official_router(config, from))
            {
                return (TaxKind::Sell, config.sell_bps);
            }
        }
        return (TaxKind::Honest, 0);
    }

Multitest already records the no-hook shape: send_msg: None → kind = Honest, debit == credit (smartcontracts/contracts/community-tax-token/src/multitest.rs:2175-2184). E2E/swarm pass sendMsg: SWAP_HOOK_B64 (frontend-dapp/e2e/helpers/community-tax-e2e.ts:247). This dApp query does not.

Repro:

  1. Listed tax CW20, sell_bps = 500, wallet balance 1_050_000 raw, non-exempt.
  2. Swap / (or Trade Market Sell): type full displayed balance 1.05 (not Max).
  3. GetConfig succeeds → isTaxToken, preview enabled.
  4. LCD tax_preview without send_msg returns debit == "1050000" (Honest).
  5. extraDebitSubmitGate sees debitRaw != null and skips the correct extraDebitFromDeclared fallback (taxPreviewMaxSpend.ts:198-204). 1050000 > 1050000 is false → CTA enabled → wallet signs → InsufficientForSellTax + gas.

Worse UX race: while preview is in-flight, debitRaw is null so local bps does disable the CTA; when Honest preview lands, the button enables. That is the inverse of issue A10 (stale 0-tax / understated preview after tax detection).

Fix direction (do not skip tax / do not FoT):

  • Pass encoded Cw20HookMsg::Swap on pair-direct preview (same as e2e).
  • Never let Honest/debit == declared undercut known sellBps > 0. Use max(debit, extraDebitFromDeclared) or ignore debit unless kind === "sell".
  • Reuse swarm requiredWalletDebit (packages/localnet-trading-swarm/src/taxPreview.ts:41-54).

2. Router hops ignore hop_trader_debit (High / AC6, R607-7, S1267-5)

Severity: High on ops.length ≥ 2. Same gas-waste class.

Where: Preview to is VITE_ROUTER_ADDRESS (SwapPage.tsx:923, TradeMarketOrderPanel.tsx:402). Wallet→router is a 1:1 Send. On-chain Sell extra-debit for hops is hop_trader_debit on the router→pair preview (tax.rs:261-277), not debit on wallet→router.

Even after finding 1 is fixed, to=router + Swap hook still does not classify as pair-direct Sell (to_pair is false). TaxPreviewResponse.hop_trader_debit is already typed in communityTaxToken.ts:60 and never read.

Repro: Same as #1 with a ≥2-hop route. LCD debit == declared; leftover tax still extra-debits the trader (T592-13). 100% declared reverts.

Fix: Preview the hop (from=router, to=pair, Swap hook with trader) and requiredWalletDebit(..., 'router'), or keep local sellBps as a floor whenever hop_trader_debit is missing.

3. Tests encode the broken path as success (High for verification)

RTL mocks useCommunityTaxPreviewDebit as { debitRaw: null } (SwapPage.extraDebitSell.test.tsx:31-34, Trade sibling). Unit T1 that does pass debitRaw uses extraDebitFromDeclared(...) (correct Sell debit), not LCD Honest debit == declared. useCommunityTaxPreviewDebit is untested. verify-issue-1267.sh greps for extraDebitSubmitGate / pin inequality; it cannot catch missing send_msg.

Required tests before close:

  • Gate: { declared: B, balance: B, debitRaw: B, sellBps: 500 } → blockSubmit (Honest preview must not win).
  • Hook: queryTaxPreview called with sendMsg on pair-direct.
  • Router: hop_trader_debit (or local floor) included in wallet debit.
  • T10: reverse exactField=output offer = 100% balance still blocked (mocked reverseSimulateSwap.offer_amount is already '1050000' and unused).

Issue #1267 AC / attack table

ID Verdict
AC1 typed 100% no broadcast Fail on live LCD. Passes only on mocked debitRaw: null + local bps.
AC2 Max leaves debit room Max math OK (extraDebitMaxDeclaredRaw). LocalTerra execute not in this PR (issue allowed RTL).
AC3 Max human round-trip Met for Max helper. Typing displayed wallet 100% is AC1 (broken live).
AC4 pin ≠ detector Met if Create Token env is on (GetConfig.sell_bps, no code_id === pin).
AC5 unknown exempt fail-closed Met (effectiveExtraDebitSellBps / hook hanging IsExempt).
AC6 router 100% Helper keeps bps; live preview to=router undercuts it (finding 2).
AC7 honest / 0-bps Met when detection is honest.
AC8 humanize InsufficientForSellTax Toast mapped (humanizeTerraTxError.ts:45-47). Swap/Trade inline TxResultAlert still uses raw error.message (SwapPage.tsx:2237).
AC9 provide / limit / no wasm Met. Do not extra-debit Provide/PlaceLimitOrder* (T592-7).
AC10 verify-593 + new verify Grep+Vitest green; does not prove live TaxPreview.
T13 preview LCD error fail-closed previewUnresolved is computed (useCommunityTaxSellBps.ts:97) and never passed into the gate. Preview error falls back to local bps (usually still blocks 100%). GetConfig timeout is honest (useCommunityTaxSellBps.ts:36-39); classifyCommunityTaxQueryError exists for timeout→unresolved and is unused. Documented as S1267-6 vs issue Detect/T13.
A1–A2 skip tax / inbound FoT Not present. Wasm unchanged.
A3 pin-only detector Fixed for GetConfig success. Live Honest preview is a new A3-shaped hole.
A4 unknown exempt = skip Met.
A7 raw wasm in UI Toast OK; inline alert not.
A8 disable #678 for tax Not done.
A9 tokenlist as tax identity Not done. isCommunityTaxEnabled() still globally disables GetConfig if launcher/pin env is missing.
A11 hostile debit parseUintString does not throw. Hostile numeric understate is finding 1.
A12 retry spam CTA/mutation intended to block; live path still reaches chain.

Security audit (requested classes)

This diff is dApp execute gating. No indexer, Postgres, or rust server changes. No new privileges.

Class Result
Tax evasion / T592-2 Not introduced. Understated preview does not reduce on-chain tax.
Inbound FoT / H-01 / T592-1 Pair credit stays declared. No pair/router math.
#1228 SendFrom allowance Untouched. Do not treat this as a drain recipe.
Spoof Swap.trader (A5) Unchanged T592-13. Frontend cannot extra-debit another wallet.
Oracle / tokenomic / book manip No oracle or reserve math.
Access control / admin Wallet-scoped queries only. Catalog pin remains Create/Manage (assertCommunityTaxTemplate).
LCD MITM Over-report debit = self-DoS; under-report = this gate fail-open; chain fail-closed.
Hostile query strings / XSS Uint parse fail-closed; humanize copy is static.
Database leaks N/A.
Prompt injection None.
Smart contract attacks (reentrancy, classify, pause) Wasm not in this MR. Client still must match execute classify (send_msg + listed pair / hop).

Not BLOCK: no fund theft, no tax skip, no injection. On-chain still reverts. The defect is the same retail gas-burn #1267 exists to stop, now on the path the PR names as LCD truth.

Gap analysis

Features a user still hits:

  • Swap/Trade typed 100% / reverse offer of a tax CW20: signing modal + revert (the ticket).
  • Displayed Balance is still full wallet; Max is smaller. Expected, but CTA can still enable after Honest preview (finding 1).
  • Unresolved detection: disabled Swap with no Insufficient Balance copy (SwapPage.tsx:1289-1291).
  • Pool zap-in (OneSidedAddCard.tsx:157-168) and pay-with-any-token invoice still Send+Swap with amount ≤ balance only. Out of #1267 letter; same revert class. Limit place correctly ungated (T592-7).

UI/UX: AC1 allows “Insufficient Balance”; SELL_TAX_EXTRA_HINT already exists and is not used as the disabled CTA. Fine if the gate actually holds.

Testing / e2e: No Playwright tax-on 100% vs Max (issue optional). verify-issue-1267 is the wrong bar for S1267-1 until Honest-preview and send_msg are asserted. No useCommunityTaxPreviewDebit tests.

DRY / readability: declaredRawForMax is unused; Max uses offline extraDebitMaxDeclaredRaw (issue recommended LCD Max). classifyCommunityTaxQueryError is dead in production. Swap vs Trade gate wiring is duplicated (acceptable) but both omit previewUnresolved.

Gas: Extra LCD TaxPreview per amount (staleTime 5s). Cheap vs a revert. Not a reason to keep Honest debit as truth.

Packages: Swarm already has the correct debit helper; dApp reimplemented a weaker gate. Indexer/contracts untouched (good).

Best practice: Prefer execute-aligned preview (hook + hop), then floor with known sell_bps. Do not prefer a successful query that classified Honest.

What is fine

  • Catalog pin removed from sell detection (useCommunityTaxSellBps.ts:17-19, AC4).
  • Unknown manager exempt keeps bps (S1267-4).
  • Max round-trip helper (extraDebitMaxDeclaredRaw) and humanizeTerraTxError mapping.
  • Native / sell_bps = 0 stay amount ≤ balance.
  • Mutation + click also check blockSubmit (SwapPage.tsx:990-994, 2208).
  • No wasm / FoT / reopen of #1228 / #593.

Close bar

Do not close #1267 until:

  1. Honest LCD preview cannot enable a 100% tax sell when sell_bps > 0.
  2. Pair-direct preview includes Swap send_msg; router includes hop_trader_debit (or local floor).
  3. Vitest covers those shapes (not only debitRaw: null).
  4. Optional LocalTerra: type full balance → no wallet; Max executes (issue T1/T2).

SHA reviewed: eefbde9d89b7d3c58601fa90d64f61a9c7843454.

RECOMMEND: FIX Independent review of `issue/1267` → `main` at `eefbde9d` (Fixes #1267). Frontend-only extra-debit Sell CTA. Pair/router/community-tax wasm unchanged. `make verify-issue-1267` is green (73 Vitest + greps) **and does not exercise the live LCD preview shape this PR treats as execute truth**. ## Must-fix ### 1. Live `TaxPreview` without `send_msg` fail-opens the new gate (High / S1267-1, AC1) **Severity:** High for the ticket (typed 100% still broadcasts). Not on-chain tax evasion (T592-2 still holds). **Where:** `frontend-dapp/src/hooks/useCommunityTaxSellBps.ts:85-91` (`queryTaxPreview` omits `sendMsg`); `frontend-dapp/src/utils/taxPreviewMaxSpend.ts:198-204` (parsed `debitRaw` wins over local `sellBps`); Swap `frontend-dapp/src/pages/SwapPage.tsx:920-934`; Trade `frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx:399-413`. **Why:** `classify_trade` only returns `TaxKind::Sell` when `to` is a listed pair **and** `send_msg` is a Swap hook: ```173:182:smartcontracts/contracts/community-tax-token/src/tax.rs if to_pair { if let Some(msg) = send_msg { if is_swap_send_hook(msg) && (!is_protocol_exempt(storage, self_addr, from) || is_official_router(config, from)) { return (TaxKind::Sell, config.sell_bps); } } return (TaxKind::Honest, 0); } ``` Multitest already records the no-hook shape: `send_msg: None` → `kind = Honest`, `debit == credit` (`smartcontracts/contracts/community-tax-token/src/multitest.rs:2175-2184`). E2E/swarm pass `sendMsg: SWAP_HOOK_B64` (`frontend-dapp/e2e/helpers/community-tax-e2e.ts:247`). This dApp query does not. **Repro:** 1. Listed tax CW20, `sell_bps = 500`, wallet balance `1_050_000` raw, non-exempt. 2. Swap `/` (or Trade Market Sell): type full displayed balance `1.05` (not Max). 3. GetConfig succeeds → `isTaxToken`, preview enabled. 4. LCD `tax_preview` without `send_msg` returns `debit == "1050000"` (Honest). 5. `extraDebitSubmitGate` sees `debitRaw != null` and **skips** the correct `extraDebitFromDeclared` fallback (`taxPreviewMaxSpend.ts:198-204`). `1050000 > 1050000` is false → CTA enabled → wallet signs → `InsufficientForSellTax` + gas. Worse UX race: while preview is in-flight, `debitRaw` is null so local bps **does** disable the CTA; when Honest preview lands, the button **enables**. That is the inverse of issue A10 (stale 0-tax / understated preview after tax detection). **Fix direction (do not skip tax / do not FoT):** - Pass encoded `Cw20HookMsg::Swap` on pair-direct preview (same as e2e). - Never let Honest/`debit == declared` undercut known `sellBps > 0`. Use `max(debit, extraDebitFromDeclared)` or ignore `debit` unless `kind === "sell"`. - Reuse swarm `requiredWalletDebit` (`packages/localnet-trading-swarm/src/taxPreview.ts:41-54`). ### 2. Router hops ignore `hop_trader_debit` (High / AC6, R607-7, S1267-5) **Severity:** High on `ops.length ≥ 2`. Same gas-waste class. **Where:** Preview `to` is `VITE_ROUTER_ADDRESS` (`SwapPage.tsx:923`, `TradeMarketOrderPanel.tsx:402`). Wallet→router is a 1:1 Send. On-chain Sell extra-debit for hops is `hop_trader_debit` on the **router→pair** preview (`tax.rs:261-277`), not `debit` on wallet→router. Even after finding 1 is fixed, `to=router` + Swap hook still does not classify as pair-direct Sell (`to_pair` is false). `TaxPreviewResponse.hop_trader_debit` is already typed in `communityTaxToken.ts:60` and **never read**. **Repro:** Same as #1 with a ≥2-hop route. LCD `debit == declared`; leftover tax still extra-debits the trader (T592-13). 100% declared reverts. **Fix:** Preview the hop (`from=router`, `to=pair`, Swap hook with `trader`) and `requiredWalletDebit(..., 'router')`, or keep local `sellBps` as a floor whenever `hop_trader_debit` is missing. ### 3. Tests encode the broken path as success (High for verification) RTL mocks `useCommunityTaxPreviewDebit` as `{ debitRaw: null }` (`SwapPage.extraDebitSell.test.tsx:31-34`, Trade sibling). Unit T1 that *does* pass `debitRaw` uses `extraDebitFromDeclared(...)` (correct Sell debit), not LCD Honest `debit == declared`. `useCommunityTaxPreviewDebit` is untested. `verify-issue-1267.sh` greps for `extraDebitSubmitGate` / pin inequality; it cannot catch missing `send_msg`. **Required tests before close:** - Gate: `{ declared: B, balance: B, debitRaw: B, sellBps: 500 }` → `blockSubmit` (Honest preview must not win). - Hook: `queryTaxPreview` called with `sendMsg` on pair-direct. - Router: `hop_trader_debit` (or local floor) included in wallet debit. - T10: reverse `exactField=output` offer = 100% balance still blocked (mocked `reverseSimulateSwap.offer_amount` is already `'1050000'` and unused). ## Issue #1267 AC / attack table | ID | Verdict | | --- | --- | | AC1 typed 100% no broadcast | **Fail on live LCD.** Passes only on mocked `debitRaw: null` + local bps. | | AC2 Max leaves debit room | Max math OK (`extraDebitMaxDeclaredRaw`). LocalTerra execute not in this PR (issue allowed RTL). | | AC3 Max human round-trip | Met for Max helper. Typing **displayed** wallet 100% is AC1 (broken live). | | AC4 pin ≠ detector | Met if Create Token env is on (`GetConfig.sell_bps`, no `code_id === pin`). | | AC5 unknown exempt fail-closed | Met (`effectiveExtraDebitSellBps` / hook hanging `IsExempt`). | | AC6 router 100% | Helper keeps bps; live preview `to=router` **undercuts** it (finding 2). | | AC7 honest / 0-bps | Met when detection is honest. | | AC8 humanize `InsufficientForSellTax` | Toast mapped (`humanizeTerraTxError.ts:45-47`). Swap/Trade **inline** `TxResultAlert` still uses raw `error.message` (`SwapPage.tsx:2237`). | | AC9 provide / limit / no wasm | Met. Do not extra-debit Provide/`PlaceLimitOrder*` (T592-7). | | AC10 verify-593 + new verify | Grep+Vitest green; does not prove live TaxPreview. | | T13 preview LCD error fail-closed | `previewUnresolved` is computed (`useCommunityTaxSellBps.ts:97`) and **never passed** into the gate. Preview error falls back to local bps (usually still blocks 100%). GetConfig timeout is **honest** (`useCommunityTaxSellBps.ts:36-39`); `classifyCommunityTaxQueryError` exists for timeout→unresolved and is unused. Documented as S1267-6 vs issue Detect/T13. | | A1–A2 skip tax / inbound FoT | Not present. Wasm unchanged. | | A3 pin-only detector | Fixed for GetConfig success. Live Honest preview is a new A3-shaped hole. | | A4 unknown exempt = skip | Met. | | A7 raw wasm in UI | Toast OK; inline alert not. | | A8 disable #678 for tax | Not done. | | A9 tokenlist as tax identity | Not done. `isCommunityTaxEnabled()` still globally disables GetConfig if launcher/pin env is missing. | | A11 hostile debit | `parseUintString` does not throw. Hostile **numeric** understate is finding 1. | | A12 retry spam | CTA/mutation intended to block; live path still reaches chain. | ## Security audit (requested classes) This diff is dApp execute gating. No indexer, Postgres, or rust server changes. No new privileges. | Class | Result | | --- | --- | | Tax evasion / T592-2 | **Not introduced.** Understated preview does not reduce on-chain tax. | | Inbound FoT / H-01 / T592-1 | Pair credit stays declared. No pair/router math. | | #1228 `SendFrom` allowance | Untouched. Do not treat this as a drain recipe. | | Spoof `Swap.trader` (A5) | Unchanged T592-13. Frontend cannot extra-debit another wallet. | | Oracle / tokenomic / book manip | No oracle or reserve math. | | Access control / admin | Wallet-scoped queries only. Catalog pin remains Create/Manage (`assertCommunityTaxTemplate`). | | LCD MITM | Over-report debit = self-DoS; under-report = this gate fail-open; chain fail-closed. | | Hostile query strings / XSS | Uint parse fail-closed; humanize copy is static. | | Database leaks | N/A. | | Prompt injection | None. | | Smart contract attacks (reentrancy, classify, pause) | Wasm not in this MR. Client still must match execute classify (`send_msg` + listed pair / hop). | **Not BLOCK:** no fund theft, no tax skip, no injection. On-chain still reverts. The defect is the same retail gas-burn #1267 exists to stop, now on the path the PR names as LCD truth. ## Gap analysis **Features a user still hits:** - Swap/Trade typed 100% / reverse offer of a tax CW20: signing modal + revert (the ticket). - Displayed Balance is still full wallet; Max is smaller. Expected, but CTA can still enable after Honest preview (finding 1). - Unresolved detection: disabled **Swap** with no Insufficient Balance copy (`SwapPage.tsx:1289-1291`). - Pool zap-in (`OneSidedAddCard.tsx:157-168`) and pay-with-any-token invoice still `Send+Swap` with `amount ≤ balance` only. Out of #1267 letter; same revert class. Limit place correctly ungated (T592-7). **UI/UX:** AC1 allows “Insufficient Balance”; `SELL_TAX_EXTRA_HINT` already exists and is not used as the disabled CTA. Fine if the gate actually holds. **Testing / e2e:** No Playwright tax-on 100% vs Max (issue optional). `verify-issue-1267` is the wrong bar for S1267-1 until Honest-preview and `send_msg` are asserted. No `useCommunityTaxPreviewDebit` tests. **DRY / readability:** `declaredRawForMax` is unused; Max uses offline `extraDebitMaxDeclaredRaw` (issue recommended LCD Max). `classifyCommunityTaxQueryError` is dead in production. Swap vs Trade gate wiring is duplicated (acceptable) but both omit `previewUnresolved`. **Gas:** Extra LCD `TaxPreview` per amount (staleTime 5s). Cheap vs a revert. Not a reason to keep Honest debit as truth. **Packages:** Swarm already has the correct debit helper; dApp reimplemented a weaker gate. Indexer/contracts untouched (good). **Best practice:** Prefer execute-aligned preview (hook + hop), then **floor** with known `sell_bps`. Do not prefer a successful query that classified Honest. ## What is fine - Catalog pin removed from sell detection (`useCommunityTaxSellBps.ts:17-19`, AC4). - Unknown manager exempt keeps bps (S1267-4). - Max round-trip helper (`extraDebitMaxDeclaredRaw`) and `humanizeTerraTxError` mapping. - Native / `sell_bps = 0` stay `amount ≤ balance`. - Mutation + click also check `blockSubmit` (`SwapPage.tsx:990-994`, `2208`). - No wasm / FoT / reopen of #1228 / #593. ## Close bar Do not close #1267 until: 1. Honest LCD preview cannot enable a 100% tax sell when `sell_bps > 0`. 2. Pair-direct preview includes Swap `send_msg`; router includes `hop_trader_debit` (or local floor). 3. Vitest covers those shapes (not only `debitRaw: null`). 4. Optional LocalTerra: type full balance → no wallet; Max executes (issue T1/T2). SHA reviewed: `eefbde9d89b7d3c58601fa90d64f61a9c7843454`.
Live TaxPreview without send_msg returns debit === declared, which previously
skipped the local sell_bps floor and allowed a typed 100% tax sell.
Merge origin/main into issue/1267
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
11774a93eb
Keep extra-debit Sell submit gate and verify-issue-1267. Keep main verify-issue-1257 help target and theater-route slippage CTA.
Merge remote-tracking branch 'origin/main' into issue/1267
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
64443808f5
Merge origin/main into issue/1267
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
22b6c146e6
Include renovate.json from #1283 so the PR is not behind protected main.
Merge branch 'main' into issue/1267
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
b80b829c2f
Merge branch 'main' into issue/1267
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
c309c7ccd1
PlasticDigits deleted branch issue/1267 2026-09-21 03:59:28 +00:00
Sign in to join this conversation.
No reviewers
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!1270
No description provided.