fix(frontend): extra-debit Sell submits when TaxPreview.debit exceeds balance #1267
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#1267
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
Retail listed-pair Sell of a community-tax CW20 still broadcasts when the wallet holds the declared offer amount but not
TaxPreview.debit(declared + sell tax). The token wasm fail-closes withInsufficient balance for extra-debit sell tax(InsufficientForSellTax). The user pays gas; balances do not move.This is T592-2 working as designed on-chain. The defect is the dApp execute path: Swap/Trade treat
amount ≤ CW20 balanceas fundable, while extra-debit Sell requiresdebit ≤ balance. Max already hasapplyExtraDebitSellCap(#593 /taxPreviewMaxSpend.ts). Typed amounts, reverse-quote offer sizing, percentage fill, and the Swap CTA do not use that cap. Wallet/toast copy still shows the raw wasm string (nohumanizeTerraTxErrormap).Not #1228 (closed:
SendFromallowance vsTaxPreview.debit; crate + ALPHA instance rotate already landed). Not #9 (genericOverflow: Cannot Sub). Not #678 (UST1 acquire theater). Not #593 (Create Token + Max helper — Max exists; submit gate does not). Do not reopen those.Parent invariants: #592 T592-1/T592-2/T592-13, #607 R607-7 (Max must leave extra-debit room on pair-direct and router hops), #609 manager-directory skip.
Repro (Given / When / Then)
Given a connected wallet holding
Bof a factory-listed community-tax CW20 (sell_bps > 0, pair registered, wallet not manager-exempt), and Swap/(or Trade market Sell) with that token as You Pay.When the user sets You Pay to the full displayed balance
B(type/paste, not Max) and confirms Swap.Then the dApp must not broadcast. CTA is Insufficient Balance (or equivalent one-liner that the sell needs extra tokens for sell tax). No signing modal.
TaxPreview { from: wallet, to: listed pair, amount: B }hasdebit > B.Given the same wallet and token.
When the user taps Max.
Then You Pay is
maxDeclaredForExtraDebitSell(B, sell_bps)(LCDTaxPreview.debit≤B), Swap is enabled, and execute credits the pair the declared amount (T592-1) while owner debit matches preview.Given the same setup except
sell_bps = 0or the wallet is manager-directory / manager-role skip (#609 / #633).When they sell
B.Then extra-debit does not apply;
amount ≤ balanceremains the fundable test (Honest / skip). Do not cap 100% on exempt wallets after skip is known. Unknown skip stays fail-closed (keepsell_bps, never unlock 100% early — existingeffectiveExtraDebitSellBps).Expected vs actual
TaxPreview.debit > balanceeven ifdeclared ≤ balanceinsufficientBalanceisrawInputAmount > balanceQuery.dataonly (SwapPage.tsx)computeMaxSpendableHumanAmount+extraDebitSellBps)useCommunityTaxSellBpssetsisTaxonly when LCDcode_id === COMMUNITY_TAX_CODE_ID(single env pin). Listed instances on a newer listed id (e.g. 11666 vs a 11630 pin) skipGetConfig/sell_bps; Max and gate behave as 0-taxSends declared 1:1 to router; extra-debit tax still comes from leftover. Declared must leave tax room (R607-7)amount ≤ balancegate; 100% Send leaves 0 leftover →InsufficientForSellTaxContractError/ message-index dump (#489 / #371 class)failed to execute message; … Insufficient balance for extra-debit sell tax: execute wasm contract failedcanSubmitdoes not compare debit vs escrow/wallet balanceWhy this is needed
balance.TaxPreview/GetConfigon the pay contract is the source of truth for this wallet and this pair.InsufficientForSellTaxand keep the CTA from reaching the wallet.Constraints / guardrails
amount.amount.Sendas 1:1. Extra-debit stays.TransferFromand limitPlaceLimitOrder*Sendstay 1:1. Do not extra-debit those here.extraDebitSellBpsForExecutealready ignoresusesRouteron purpose — keep that.TaxPreview.debit/credit/taxis the execute source of truth. LocalmaxDeclaredForExtraDebitSellis an offline helper; it must not disagree with preview on the success path. Do not invent a second bps formula.code_id === VITE_COMMUNITY_TAX_CODE_IDto arm extra-debit. That equality is for Create Token catalog / manage chrome. Sell sizing should use LCD preview/config (or a listed tax code set that includes every currently listed tax wasm). Unknown / in-flight: fail closed (do not treat as 0-tax).nullskip keeps bps.Sell tax extraalready exists (SELL_TAX_EXTRA_HINT). No lecture banner.InsufficientForSellTaxfires whenTaxPreview.debit ≤ balance(that would be a new contract bug; do not assume it).Relevant files
frontend-dapp/src/pages/SwapPage.tsxinsufficientBalancevsrawInputAmount/balanceQuery; Max already passesextraDebitSellBpsForExecutefrontend-dapp/src/components/trade/TradeMarketOrderPanel.tsxcanSubmit/ Max; same extra-debit importfrontend-dapp/src/hooks/useCommunityTaxSellBps.tsisTax = code_id === COMMUNITY_TAX_CODE_ID— misses rotated listed wasmfrontend-dapp/src/utils/taxPreviewMaxSpend.tsmaxDeclaredForExtraDebitSell/effectiveExtraDebitSellBps/ route hintfrontend-dapp/src/utils/maxSpendableAmount.tsfrontend-dapp/src/utils/humanizeTerraTxError.ts(or tx humanize sibling)Insufficient balance for extra-debit sell taxfrontend-dapp/src/services/terraclassic/communityTaxToken.tsTaxPreview/GetConfig/IsExemptsmartcontracts/contracts/community-tax-token/src/error.rsInsufficientForSellTaxstring — do not change unless preview/execute divergefrontend-dapp/src/utils/taxPreviewMaxSpend.test.tsdocs/frontend.md/skills/AGENTS_COMMUNITY_TAX_CW20.mdscripts/qa/verify-issue-593.shRecommended direction
sellDebitExceedsBalance({ declaredRaw, balanceRaw, debitRaw })anddeclaredRawForMax({ balanceRaw, debitPreview }). Prefer LCDTaxPreviewfor the live(from=wallet, to=pair or router, amount=declared)once the route is known. Fallback tomaxDeclaredForExtraDebitSell(balance, sell_bps)only when preview is unavailable andsell_bpsis known; if both unknown, disable Swap (fail closed), do not assume 0-tax.insufficientBalanceor extra-debit shortfall → same disabled Insufficient Balance (optionally reuseSELL_TAX_EXTRA_HINTas the existing one-liner, not a second banner). Do not broadcast.GetConfig/TaxPreviewwhen the pay asset is a CW20 (factory-listed). Stop using catalog pin equality as the onlyisTaxswitch for sell sizing. Create Token / Manage may keep the pin.toRawAmount(human, decimals)must be≤ maxDeclared. If format round-trip would exceed, step declared down 1 raw unit until preview debit fits (table test).message index.Acceptance criteria
sell_bps > 0, non-exempt wallet, declared = full CW20 balance. When they click Swap/Trade. Then no tx is built or signed; CTA disabled.TaxPreview.debit ≤ balanceand execute succeeds in LocalTerra tax-on (or crate+RTL with mocked preview). Pair credit = declared.code_id≠VITE_COMMUNITY_TAX_CODE_IDbut instance still extra-debits (listed tax wasm). Max + CTA still apply extra-debit. No 0-tax 100% sell.ops.length ≥ 2listed-tax pay: 100% declared still blocked; Max leaves tax room (R607-7).sell_bps = 0: behavior unchanged vs today (amount ≤ balanceonly).InsufficientForSellTax(if a race still hits it) humanizes to one short sentence; no GitLab/Forgejo ids, no CLI.make verify-issue-593and extra-debit unit tests stay green. Newmake verify-issue-<iid>.Test plan (functional paths)
sell_bps = 500, balance1_050_000, type declared1_050_000simulateexecute / no broadcast1_000_000; preview debit1_050_000code_id11666 (or any listed tax id) while env pin is another listed idsell_bps = 0exactField=outputimplying offer = 100% balanceamount > balancecargo test -p cl8y-community-tax-tokenonly if touching wasm (default: do not). Frontend: Vitest helpers + RTL Swap/Trade CTA. Optional Playwright tax-on LocalTerra Max vs 100% (make verify-issue-622stay green).Test plan — attack, hack, and abuse
amount - tax(inbound FoT)Swap.traderto extra-debit another walletTransferFromprovideTaxPreviewnon-numeric debitBigIntthrow in UIDo not publish a columbus-5 copy-paste that drains a victim wallet via
SendFrom(that is #1228’s closed surface).Verification criteria
Manual LocalTerra tax-on pair (#623 / #622): Max sell succeeds; typing full balance does not open the wallet. Listed columbus-5 tax instance: same CTA without requiring the catalog pin to match that instance’s
code_id.Out of scope
VITE_COMMUNITY_TAX_CODE_IDas a substitute for LCD extra-debit detection (pin may still need a docs leftover; it is not the gate).First-pass model recommendation
Recommendation: grok-high
Rationale: Extra-debit Sell is founder-required wasm/tax (T592-2 / T592-13), not a local three-file UI tweak. A safe first pass must keep pair inbound 1:1, align Swap + Trade submit with LCD
TaxPreview(not a second bps), stop using catalog pin equality as the only tax detector, preserve manager skip fail-closed, and avoid tax-evasion “fixes.” Wrong direction bricks sells or skips tax. Verification is table-driven debit math plus RTL CTA, not one snapshot. Composer’s low/medium local-edit bar does not hold (contracts/wasm + cross-surface execute gate)./agent implement
cl8y-agent-control: queued
implementjob96528e6e-8429-4767-8943-6054dfcfcd6a(not executed; no Hetzner VM).Merge complete: PR #1270 landed on main (
ce5f9804).Met: AC1 typed 100% extra-debit Sell is blocked.
extraDebitSubmitGatenow usesmax(LCD debit, local extra-debit from sell_bps)whensell_bps > 0, so Honest TaxPreview (debit === declared) cannot enable a full-balance tax sell.make verify-issue-1267passed (76 Vitest), including that Honest shape. Woodpecker on the PR was green.Leftover (not this close bar): pair-direct TaxPreview still omits Swap
send_msg; router still ignoreshop_trader_debitand relies on the local floor. InlineTxResultAlertcan still show rawerror.message(toast humanize is mapped). Tracking in a follow-up issue.Follow-up leftover: #1285 (send_msg / hop_trader_debit / inline humanize).