Batch cancel and batch claim expired limit orders (on-chain + frontend) #246
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#246
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
Add on-chain
CancelLimitOrders { order_ids: Vec<u64> }andClaimExpiredLimitOrders { order_ids: Vec<u64> }so makers can unlink many resting or parked-expiry orders in one transaction, with at most two CW20 refund transfers (token0 + token1), instead of N separate cancel/claim txs.Current codebase
ExecuteMsg::CancelLimitOrder { order_id }andExecuteMsg::ClaimExpiredLimitOrder { order_id }insmartcontracts/packages/dex-common/src/pair.rs. Handlers:execute_cancel_limit_order/execute_claim_expired_limit_orderinsmartcontracts/contracts/pair/src/contract.rs(~1169–1230, ~1103–1167).orderbook::unlink_order→ read/writePENDING_ESCROW_TOKEN0|1→ one CW20Transfersubmessage per order.CancelLimitOrders).CANCEL_LIMIT_ORDER_GAS_LIMIT = 450_000per tx (frontend-dapp/src/services/terraclassic/terraGas.ts). Ten cancels ≈ 4.5M gas + 10 signatures.PlaceLimitOrderBatchaggregates maker fees into one treasury transfer and refunds skipped rungs once (smartcontracts/contracts/pair/src/limit_placement.rs). Cap pattern:MAX_LIMIT_BATCH_RUNGS_HARD_CAP = 30(dex-common/src/limit_placement.rs).order_ids without an on-chain owner index.Why this is needed
Cancel-all and bulk cleanup are common for market makers and ladder traders. Today each order costs a full tx overhead (sig verify, wasm spin-up, pair loads, escrow item R/W, CW20 execute). Collapsing N cancels into one tx targets ~1.3–1.8M gas for 10 orders vs ~4.5M today, plus one wallet approval flow.
Constraints / guardrails
order_ids.len()≤MAX_LIMIT_BATCH_RUNGS_HARD_CAP(30), same ceiling as batch placement. Reuseclamp_max_batch_rungssemantics or add parallel constants index-common.info.sender; any failure → whole tx reverts (all-or-nothing, unlike batch placement’s partial skip for insert steps).assert_not_pausedgate as single cancel/claim (contract.rsdispatcher).EXPIRED_LIMIT_CLAIMS; cancel batch only ids in activeORDERSmap (not already parked).remainingper side (token0 asks / token1 bids); emit ≤ 2 CW20 transfers total. Do not change escrow accounting invariants (L1 / L5 —docs/contracts-security-audit.md).place_limit_order_batchcolumnar pattern) soindexer/src/indexer/parser.rscan index cancellations/claims without N separate txs.Relevant files
smartcontracts/packages/dex-common/src/pair.rssmartcontracts/contracts/pair/src/contract.rssmartcontracts/contracts/pair/src/orderbook.rssmartcontracts/contracts/pair/src/state.rssmartcontracts/contracts/pair/src/error.rssmartcontracts/tests/src/limit_order_tests.rsfrontend-dapp/src/services/terraclassic/pair.tsfrontend-dapp/src/components/trade/OrderBookPanel.tsxfrontend-dapp/src/hooks/useLimitOrderCancelMutation.tsfrontend-dapp/src/services/terraclassic/terraGas.tsdocs/limit-orders.mdRecommended solution direction
CancelLimitOrders { order_ids: Vec<u64> },ClaimExpiredLimitOrders { order_ids: Vec<u64> }toExecuteMsg.execute_cancel_limit_orders/execute_claim_expired_limit_orders:PAIR_INFO, token addrs once.action=cancel_limit_orders_batch,batch_count, then per-idlimit_order_cancelled(or claim equivalent).cancelLimitOrders(wallet, pair, orderIds[])andclaimExpiredLimitOrders(...)inpair.ts. ReplaceonCancelAllMyRestingloop with single batch call (keep confirm dialog). AddgasLimitForLimitOrderCancelBatch(n)≈CANCEL_LIMIT_ORDER_GAS_LIMITbase + marginal per id (measure on localterra).Acceptance criteria
order_idin vec → revert.PENDING_ESCROW_*after batch (existing prop tests pattern).Test plan — functional paths
/tradeorder book (extendfrontend-dapp/e2e/limit-orders-tx.spec.tsor order-book spec).gasLimitForLimitOrderCancelBatchmonotonic in N.Test plan — attack / abuse vectors
Verification criteria
make test-contractspasses with new integration tests.docs/limit-orders.mdupdated with batch cancel/claim messages and gas table.mentioned in commit
c93bb3c208Implementation summary (merged to
main@c93bb3c)Added on-chain batch maker withdrawal paths and wired the dApp cancel-all flow to use a single transaction.
Contract (
limit_batch_withdraw.rs)CancelLimitOrders { order_ids }— all-or-nothing owner checks, dedupe enforced, cap = pairmax_batch_rungs(≤30)ClaimExpiredLimitOrders { order_ids }— same rules for parked-expiry rowscancel_limit_order/claim_expired_limit_order+ batch summary attrs for indexerFrontend
cancelLimitOrders/claimExpiredLimitOrdersinpair.tsuseLimitOrderCancelMutation(number | number[])gasLimitForLimitOrderCancelBatch(n)= 400k + 80k×NIndexer
Docs / invariants
docs/contracts-security-audit.mddocs/limit-orders.md§ batch cancel/claim + gas tableskills/AGENTS_TERRACLASSIC_GAS.mdcrosslinkVerification checklist
make test-contracts(323 tests incl. 6 new batch cancel/claim tests)cargo test -p cl8y-dex-indexer --lib parse_limit_order_cancellations_batch_columnar/tradeorder book → one tx, all orders cancelledcancel_limit_orders_batch+ Nlimit_order_cancelledattrslimit_order_cancellationsfrom one txclaim_expired_limit_ordersrefunds both in one txterraGas.batchCancel.test.ts(monotonic gas formula)Follow-ups (optional): UI “Claim all parked” batch button on
LimitOrderMyPlacementsPanel(service fn exists; per-row claim unchanged).Request: @qa-agent-team please verify on LocalTerra + indexer ingestion per checklist above. Leaving issue open until QA sign-off.
mentioned in issue #253
mentioned in commit
0be09de77cmentioned in issue #259
Heads up — main npm run build (tsc -b) is red, and it traces back to this batch-cancel change (
c93bb3c).c93bb3cintroduced LimitOrderCancelInput = number | number[] in useLimitOrderCancelMutation (so "Cancel all mine" can pass an id array), but the cancel-mutation PROP types on the consuming components stayed UseMutationResult<..., number, ...>:So the components are wired to a number|number[] mutation through props typed number-only. tsc -b fails with 6 TS2322/TS2345 errors across OrderBookPanel, TradeOrderTicket, TradePage, LimitOrdersPage (e.g. "number[] is not assignable to number"). It wasn't caught because CI/local checks here run vitest + eslint, neither of which type-checks; only tsc -b / npm run build does.
I found this while verifying #268 (its "npm run typecheck clean" criterion failed for this reason, not for anything in the ladder code).
Fix (type-only): widen the cancelLimitOrderMutation / cancelMutation prop types to LimitOrderCancelInput across OrderBookPanel + TradeOrderTicket. After the change tsc -b exits clean and the OrderBookPanel/TradeOrderTicket/LimitOrdersPage tests stay green (30 passed). MR incoming.
Worth adding tsc -b (or npm run build) to CI so a type-only break like this doesn't slip through again. @PlasticDigits
mentioned in merge request !737
mentioned in issue #268
#246 verified — good to close. Both batch withdrawal paths proven live, plus the full test matrix.
Live (deployed wasm):
Contract / indexer / frontend tests:
Acceptance criteria all covered: N<=30 one tx <=2 CW20 (live + tests); duplicate / foreign / N>30 / paused -> whole tx reverts (tests); mixed bid+ask refunds correct token0/token1 (live); frontend cancel-all uses the batch API with the batch gas model.
One frontend note already filed above:
c93bb3cleft the cancel-mutation PROP types as number while the hook is number|number[], so tsc -b / npm run build was red. Fixed type-only in MR !737; with that the cancel-all wiring type-checks clean.Browser "Cancel all mine" on /trade is the UI wrapper over the same cancelLimitOrders batch path proven live above. Good to close. @PlasticDigits
mentioned in commit
90fca1be04mentioned in merge request !834
mentioned in issue #337
mentioned in commit
f875d5388amentioned in commit
0e3afcaef3mentioned in commit
65876e17c7mentioned in merge request !953
mentioned in issue #421
mentioned in issue #546
mentioned in issue #617