fix(frontend): extra-debit Sell submits when TaxPreview.debit exceeds balance #1270
No reviewers
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!1270
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "issue/1267"
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
TaxPreview.debit(or knownsell_bps) exceeds wallet balance. Typed 100% / reverse offer is Insufficient Balance; Max still leaves debit room after human round-trip.GetConfig.sell_bpsis the sell detector. CatalogVITE_COMMUNITY_TAX_CODE_IDstays Create Token / Manage only. Unknown exempt stays fail-closed. Honest CW20 andsell_bps = 0stayamount ≤ balance.InsufficientForSellTaxmaps to retail copy. Pair/router wasm unchanged. Invariants S1267-1–S1267-8 indocs/frontend.mdandskills/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)/and/tradeMarket sell with a listed tax CW20.sell_bps> 0).drain skip: no occupying job for rebase/fix-pr/CI-wait; remaining continue
/agent review
cl8y-agent-control: queued
bug_reviewjobd4ae1ca3-f782-4443-bd72-c00a3ab03d6f(not executed; no Hetzner VM)./agent review
cl8y-agent-control: queued
bug_reviewjob94dabcad-408f-4a17-827f-b62ad3213b12(not executed; no Hetzner VM).RECOMMEND: FIX
Independent review of
issue/1267→mainateefbde9d(Fixes #1267). Frontend-only extra-debit Sell CTA. Pair/router/community-tax wasm unchanged.make verify-issue-1267is green (73 Vitest + greps) and does not exercise the live LCD preview shape this PR treats as execute truth.Must-fix
1. Live
TaxPreviewwithoutsend_msgfail-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(queryTaxPreviewomitssendMsg);frontend-dapp/src/utils/taxPreviewMaxSpend.ts:198-204(parseddebitRawwins over localsellBps); Swapfrontend-dapp/src/pages/SwapPage.tsx:920-934; Tradefrontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx:399-413.Why:
classify_tradeonly returnsTaxKind::Sellwhentois a listed pair andsend_msgis a Swap hook: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 passsendMsg: SWAP_HOOK_B64(frontend-dapp/e2e/helpers/community-tax-e2e.ts:247). This dApp query does not.Repro:
sell_bps = 500, wallet balance1_050_000raw, non-exempt./(or Trade Market Sell): type full displayed balance1.05(not Max).isTaxToken, preview enabled.tax_previewwithoutsend_msgreturnsdebit == "1050000"(Honest).extraDebitSubmitGateseesdebitRaw != nulland skips the correctextraDebitFromDeclaredfallback (taxPreviewMaxSpend.ts:198-204).1050000 > 1050000is false → CTA enabled → wallet signs →InsufficientForSellTax+ gas.Worse UX race: while preview is in-flight,
debitRawis 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):
Cw20HookMsg::Swapon pair-direct preview (same as e2e).debit == declaredundercut knownsellBps > 0. Usemax(debit, extraDebitFromDeclared)or ignoredebitunlesskind === "sell".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
toisVITE_ROUTER_ADDRESS(SwapPage.tsx:923,TradeMarketOrderPanel.tsx:402). Wallet→router is a 1:1 Send. On-chain Sell extra-debit for hops ishop_trader_debiton the router→pair preview (tax.rs:261-277), notdebiton wallet→router.Even after finding 1 is fixed,
to=router+ Swap hook still does not classify as pair-direct Sell (to_pairis false).TaxPreviewResponse.hop_trader_debitis already typed incommunityTaxToken.ts:60and 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 withtrader) andrequiredWalletDebit(..., 'router'), or keep localsellBpsas a floor wheneverhop_trader_debitis missing.3. Tests encode the broken path as success (High for verification)
RTL mocks
useCommunityTaxPreviewDebitas{ debitRaw: null }(SwapPage.extraDebitSell.test.tsx:31-34, Trade sibling). Unit T1 that does passdebitRawusesextraDebitFromDeclared(...)(correct Sell debit), not LCD Honestdebit == declared.useCommunityTaxPreviewDebitis untested.verify-issue-1267.shgreps forextraDebitSubmitGate/ pin inequality; it cannot catch missingsend_msg.Required tests before close:
{ declared: B, balance: B, debitRaw: B, sellBps: 500 }→blockSubmit(Honest preview must not win).queryTaxPreviewcalled withsendMsgon pair-direct.hop_trader_debit(or local floor) included in wallet debit.exactField=outputoffer = 100% balance still blocked (mockedreverseSimulateSwap.offer_amountis already'1050000'and unused).Issue #1267 AC / attack table
debitRaw: null+ local bps.extraDebitMaxDeclaredRaw). LocalTerra execute not in this PR (issue allowed RTL).GetConfig.sell_bps, nocode_id === pin).effectiveExtraDebitSellBps/ hook hangingIsExempt).to=routerundercuts it (finding 2).InsufficientForSellTaxhumanizeTerraTxError.ts:45-47). Swap/Trade inlineTxResultAlertstill uses rawerror.message(SwapPage.tsx:2237).PlaceLimitOrder*(T592-7).previewUnresolvedis 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);classifyCommunityTaxQueryErrorexists for timeout→unresolved and is unused. Documented as S1267-6 vs issue Detect/T13.isCommunityTaxEnabled()still globally disables GetConfig if launcher/pin env is missing.parseUintStringdoes not throw. Hostile numeric understate is finding 1.Security audit (requested classes)
This diff is dApp execute gating. No indexer, Postgres, or rust server changes. No new privileges.
SendFromallowanceSwap.trader(A5)assertCommunityTaxTemplate).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:
SwapPage.tsx:1289-1291).OneSidedAddCard.tsx:157-168) and pay-with-any-token invoice stillSend+Swapwithamount ≤ balanceonly. Out of #1267 letter; same revert class. Limit place correctly ungated (T592-7).UI/UX: AC1 allows “Insufficient Balance”;
SELL_TAX_EXTRA_HINTalready 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-1267is the wrong bar for S1267-1 until Honest-preview andsend_msgare asserted. NouseCommunityTaxPreviewDebittests.DRY / readability:
declaredRawForMaxis unused; Max uses offlineextraDebitMaxDeclaredRaw(issue recommended LCD Max).classifyCommunityTaxQueryErroris dead in production. Swap vs Trade gate wiring is duplicated (acceptable) but both omitpreviewUnresolved.Gas: Extra LCD
TaxPreviewper 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
useCommunityTaxSellBps.ts:17-19, AC4).extraDebitMaxDeclaredRaw) andhumanizeTerraTxErrormapping.sell_bps = 0stayamount ≤ balance.blockSubmit(SwapPage.tsx:990-994,2208).Close bar
Do not close #1267 until:
sell_bps > 0.send_msg; router includeshop_trader_debit(or local floor).debitRaw: null).SHA reviewed:
eefbde9d89b7d3c58601fa90d64f61a9c7843454.