fix: My Open Limits Cancel fails or is unreachable (order #1 · Sell UST1) #530

Closed
opened 2026-08-16 07:14:02 +00:00 by PlasticDigits · 12 comments
PlasticDigits commented 2026-08-16 07:14:02 +00:00 (Migrated from gitlab.com)

Summary

Retail report on dex.cl8y.com: My open limits still shows a resting row and the user cannot cancel it.

Why won't My Open Limits order be canceled? ●order #1 · Sell UST1 · 82.044004487226 · placed 2026-08-15T14:21:43

That string is the active row in LimitOrderMyPlacementsPanel (green ●, not parked ◆ / dust ▫). Bundle investigation + fix for every surface that can leave a maker unable to cancel or unable to understand why Cancel is dead: /trade compact panel, /limits page panel, book row × / Cancel all mine, Advanced Cancel by order ID, and Portfolio (view-only). Do not split “indexer stale row” vs “disabled Cancel” vs “error toast missed” vs “sticky CTA covers the button” into separate issues.

Related: #135 (cancel vs indexer), #141 / #142 (parked lifecycle), #162 (book row cancel), #419 (open orders first), #505 (OrderStatus / L21), #524 (UST1 display invert — Sell UST1), #527 (Chrome sticky Place limit can sit over ticket body), #529 (UST1/USTR place bounds — cancel is order_id only; do not treat #529 as the cancel root unless evidence says the row is on that pair and cancel itself reverts).

Evidence

  • Surface copy matches LimitOrderMyPlacementsPanel active line: order #{id} · {Buy|Sell} {base} · {price} · placed {timestamp}.
  • Pair/side: Sell UST1 at ~82.04 (human). That magnitude is consistent with a UST1-as-base display (e.g. UST1/USTR-class quotes). Confirm pair address + factory asset_0/asset_1 + invert state before changing contracts.
  • Placed 2026-08-15T14:21:43 (indexer block_timestamp, truncated to 19 chars).
  • No tx hash in the report. Treat as mainnet / columbus-5 until a LocalTerra repro is found.

Current codebase

Cancel is implemented and covered for the happy path. The report is a lifecycle / UX hole: the panel can keep showing an active ● row when the order is no longer cancelable, or the Cancel control can be disabled / covered / unexplained.

Layer Behavior today
On-chain ExecuteMsg::CancelLimitOrder { order_id } / CancelLimitOrders — owner-only, only while the row is in ORDERS. Blocked while the pair is paused (L6 / #120). After park, cancel is the wrong message — use ClaimExpiredLimitOrder. Query OrderStatus (#505): Active / ParkedRefund / Unknown. Unknown ≠ filled (also cancel, claim, never-placed).
Indexer Place → limit_order_placements.lifecycle_status = active. Cancel event → row omitted from default placements (#135). Park → parked_expired + remaining_escrow; claim → refunded. Fills are inserted into limit_order_fills only — process_limit_order_fill does not flip placement lifecycle or remaining escrow. A fully filled order can remain active in GET .../limit-placements and still render as ● with Cancel.
dApp cancel One useLimitOrderCancelMutation(pairAddr, wallet) shared by ticket panel, book ×, Cancel all mine, Advanced form. Preflight: skip if orderIdHasIndexedCancellation. Broadcast cancel_limit_order. On success: toast + invalidate limitPlacements / limitCancellations / book / wallet history. CosmWasm “LimitOrder not found” is humanized (#135).
Active-row Cancel disable Disabled when disconnected, pair paused, blacklist, mutation pending (any id), or indexed cancellation exists. When already-cancelled, the label still says Cancel — no “Already cancelled / no longer on the book” reason.
/trade compact panel variant="compact" sits below .trade-limit-submit-sticky (TradeOrderTicket). Active list is max-h-24 overflow-y-auto; panel max-h-48. Cancel is text-[9px]. Combined with #527, the user can read the ●order #1 line and still not reach a working Cancel.
/limits Full panel + Advanced Cancel by order ID. Same mutation + same disable rules.
Portfolio PortfolioOpenLimitsSection lists pair / id / side / price / status / placed. No cancel. Copy says manage on Limits or Trade.
UST1 invert (#524) Display Sell UST1 may be factory ask (pay UST1) or inverted bid. Cancel uses pair + order_id only — invert must not change the execute payload.

Playbooks: skills/AGENTS_FRONTEND_LIMIT_PARKED_EXPIRED.md, skills/AGENTS_ORDER_STATUS_QUERY.md, docs/limit-orders.md § Cancel / dApp retail form.

Why this is needed

  1. Maker funds look stuck. An ● row with a dead or failing Cancel is a custody incident from the user’s point of view, even when escrow already filled, parked, or cancelled.
  2. Indexer active is not “still on ORDERS”. Fills never update placement lifecycle. Cancel-indexed rows can still render as active if the placements query and the cancellation guard disagree. LCD OrderStatus is unused by the panel.
  3. Failure reasons are easy to miss. Disable without a label change; window.confirm; toast + TxResultAlert below a clipped compact list; sticky Place limit covering the CTA (#527).
  4. Support cannot close the loop without a documented triage (LCD OrderStatus + indexer placement + cancellation + fills) and a UI that matches that truth.

Constraints / guardrails

  1. Do not weaken L6. Pause still blocks cancel/claim on-chain and in the UI. Do not add a “force cancel” that burns gas on a guaranteed Paused revert.
  2. Owner-only. Never cancel another wallet’s order_id. Book × stays order.owner === connected address.
  3. No double refund. Cancel only while ORDERS has the row. After park, Claim only. Do not invent a third execute path.
  4. OrderStatus::Unknown is not proof of fill (L21 / #505). Classify fill vs cancel vs never-placed from indexer fills/cancellations + local tx, not from Unknown alone.
  5. Do not change on-chain price bounds here. #529 is place-time MIN/MAX_LIMIT_PRICE. Cancel does not send a price.
  6. Do not change indexer candle / price_usd math (#522 / #524). Invert stays display-only; cancel payload stays factory order_id.
  7. Copy (#489). No token0 / token1 / raw bid/ask in retail. Blocking errors: one short sentence + optional Docs. Disabled Cancel must state why (paused, restricted, already gone, filled, claim instead).
  8. No always-on essay banners on /trade or /limits. Progressive disclosure / inline row status only.
  9. Preserve testids unless renamed in the same MR: trade-cancel-placement-{id}, limits-page-cancel-placement-{id}, trade-book-cancel-{bid\|ask}-{id}, trade-ticket-placements-anchor.
  10. Do not skip LocalTerra with a false “no chain in agent VM”. Probe make has-localterra; provision make setup-cloud-localterra when the fix needs place/fill/cancel/park.

Relevant files

File Role
frontend-dapp/src/components/trade/LimitOrderMyPlacementsPanel.tsx Active ● row + Cancel disable/label
frontend-dapp/src/hooks/useLimitOrderCancelMutation.ts Shared cancel mutation + invalidations
frontend-dapp/src/utils/limitOrderCancelUserMessage.ts Indexed-cancel guard + “not found” copy
frontend-dapp/src/utils/limitPlacementLifecycle.ts active / parked_expired / refunded partition
frontend-dapp/src/components/trade/TradeOrderTicket.tsx Compact panel below sticky CTA
frontend-dapp/src/pages/LimitOrdersPage.tsx Page panel + Advanced cancel-by-id
frontend-dapp/src/components/trade/OrderBookPanel.tsx Book × / Cancel all mine
frontend-dapp/src/components/portfolio/PortfolioOpenLimitsSection.tsx View-only open limits
frontend-dapp/src/services/terraclassic/pair.ts cancelLimitOrder / batch
indexer/src/db/queries/limit_order_lifecycle.rs Placement lifecycle SQL
indexer/src/indexer/parser.rs process_limit_order_fill (no lifecycle write)
smartcontracts/contracts/pair/src/orderbook.rs query_order_status
docs/limit-orders.md Cancel / park / indexer invariants

Investigate first (reproduce the report class, then fix). Do not ship a cosmetic “Cancel” relabel without proving which class the UST1 row is.

  1. Triage matrix (required in the MR or issue comment): for a stuck ● row, record LCD OrderStatus, indexer placement lifecycle_status, indexer cancellation row, indexer fills for that order_id, pair is_paused, connected wallet vs owner.
  2. Reconcile “open” with chain. Prefer LCD OrderStatus (or equivalent) before offering Cancel:
    • Active → Cancel enabled (unless pause/blacklist/gas).
    • ParkedRefund → show Claim, not Cancel.
    • Unknown → do not leave a fake ● Cancel. Classify via fills vs cancellations; show Filled / Already cancelled / No longer on the book and hide Cancel.
  3. Indexer (if fills are the gap): when a fill consumes remaining escrow (or the order leaves ORDERS without cancel/park), stop listing the row as default-open active. Do not treat Unknown as fill without a fill event.
  4. Cancel CTA integrity: disabled Cancel must say why. Compact /trade Cancel must be reachable (not under sticky Place limit; not clipped without scroll affordance). Errors must be visible next to the row, not only in a toast below max-h-24.
  5. Keep #135 humanize for the race where the user still submits and the map is empty.

Acceptance criteria

  • AC1 — Reproduce or classify the report. Document which triage class matches order #1 · Sell UST1 · 82.04… (or an equivalent LocalTerra fixture). If mainnet pair/tx cannot be recovered, ship a fixture that matches the same UI string and failure mode.
  • AC2 — Cancel works when the order is Active on-chain. /trade compact, /limits panel, book ×, Advanced form: confirm → cancel_limit_order → row leaves Open; escrow returns; indexer catch-up matches.
  • AC3 — No fake Cancel on gone orders. Fully filled, already cancelled, or Unknown without ORDERS: no enabled Cancel. Row status is Filled / Cancelled / gone — not a stuck ● with a dead button.
  • AC4 — Parked ≠ Cancel. ParkedRefund / parked_expired stays Claim refund / Claim dust. Pause/blacklist copy unchanged (L6).
  • AC5 — Disabled reason. Paused → Unavailable (pair paused). Blacklist → Trading restricted. Already gone → explicit gone copy (not a mute Cancel).
  • AC6 — /trade reachability. At desktop 1280×720 and phone, Cancel for a single open row is clickable (elementFromPoint hits the button, not trade-limit-submit).
  • AC7 — Invert / UST1. Display Sell UST1 still cancels the factory order_id on the selected pair. No extra execute fields.
  • AC8 — Docs + verify target. docs/limit-orders.md + parked-expired playbook state the open-row reconciliation. make verify-issue-<iid> covers unit + the new paths.

Test plan (all paths)

Investigation / LCD + indexer

# Path Assert
I1 Place ask Sell {UST1 or fixture base}, still resting LCD OrderStatus=Active; panel ●; Cancel enabled
I2 Cancel success Placement omitted; cancellation row exists; panel empty or not that id
I3 Full fill (taker/hybrid) Placement not shown as cancelable ●; fill row exists; Cancel hidden
I4 Partial fill, remainder on book Still Active; Cancel refunds remaining only
I5 TTL expiry then park ParkedRefund / Claim — not Cancel
I6 Dust flush park (reason=DustFilled) Claim dust — not Cancel
I7 Pair paused Cancel disabled + pause copy; chain revert if forced
I8 Wrong wallet No Cancel on others’ book rows; own panel empty
I9 Indexer lag after successful cancel UI does not re-enable Cancel from a stale active row once cancellation or LCD Unknown is known
I10 UST1 invert on / off Same order_id cancel payload

Unit / RTL (make test-frontend)

  1. LimitOrderMyPlacementsPanel: Active + Cancel; parked → Claim; filled/unknown fixture → no Cancel + status copy; paused/blacklist labels; already-indexed cancellation label.
  2. useLimitOrderCancelMutation: indexed-cancel throws; single vs batch; invalidations.
  3. Lifecycle helpers: fill/gone classification if new; existing park/dust tests still pass.
  4. Trade ticket: compact panel Cancel not a child of trade-limit-submit-sticky.

Indexer (make test-indexer-integration / scoped)

  1. Full fill removes or reclassifies default-open placement (if indexer change ships).
  2. Cancel still omits placement (#135).
  3. Park/claim lifecycle unchanged (#142).
  4. Columnar / flattened wasm fill+swap streams still ingest (#141 parser).

Playwright

# Path Assert
P1 limit-orders-tx.spec.ts Existing place + panel cancel still green
P2 /trade compact Place → trade-cancel-placement-{id} visible and clickable; confirm; Cancel submitted
P3 Full-fill fixture (or chain) Row not cancelable; no cancel_limit_order broadcast
P4 Parked Claim testids only
P5 Pause / blacklist Cancel disabled + copy
P6 Desktop Chrome elementFromPoint on Cancel ≠ Place limit sticky
P7 /limits Advanced Cancel by id still works; helper still points at row actions first

Manual QA (mainnet report class)

  1. Find the UST1 pair + order_id=1 (or current equivalent). Query LCD OrderStatus and indexer placements/fills/cancels.
  2. If Active, cancel from /trade and /limits; confirm refund.
  3. If not Active, confirm the UI no longer offers Cancel and shows the correct next action.

Test plan — attack, hack, and abuse vectors

Vector What to prove
A1 Cancel someone else’s order Book × and panel Cancel never appear for owner !== connected wallet. On-chain still Unauthorized.
A2 Cancel-after-fill grief UI does not broadcast cancel_limit_order against Unknown/filled ids (gas grief). If a race still hits chain, humanize — no raw map key.
A3 Double cancel / double refund Second Cancel disabled after indexed cancel or LCD gone. Parked path cannot cancel. No second CW20 refund.
A4 Pause bypass Disabled + no broadcast while is_paused.
A5 Blacklist bypass Same as place/swap gates (SEC-E01).
A6 Confirm spoof / clickjack #527 overlay: elementFromPoint on Cancel is Cancel, not Place limit. window.confirm still required for panel cancel.
A7 order_id confusion Ids are per pair. Cancelling #1 on pair A must not target pair B. Pair switch clears pending mutation context.
A8 Invert spoof Toggling UST1 invert must not change cancel_limit_order.order_id or pair address.
A9 XSS Indexer price / timestamps render as text (82.044…, ISO slice). No innerHTML.
A10 Sequence / fee grief Existing broadcast pending disable + fee humanize. Do not add a parallel cancel client.

Verification criteria

Issue is done when all of the following are true:

  1. Triage written. The report class (or a matching fixture) is identified with LCD + indexer evidence.
  2. No stuck ● + dead Cancel. Active-on-chain orders cancel; gone orders do not offer Cancel.
  3. Automated coverage for I1–I7 (unit and/or e2e) and A1–A3, A6–A8.
  4. Invariants preserved: L6 pause, owner-only, no double refund, L21 Unknown ≠ fill, #524 convert-on-submit untouched, #529 out of scope unless cancel itself reverts.
  5. make verify-issue-<iid> green. Do not skip chain work with a false “no LocalTerra” without make has-localterra / make setup-cloud-localterra.
  6. Docs (docs/limit-orders.md + playbook) describe open-row reconciliation.

Mode

  • limit order
  • hybrid (fills that remove the order from ORDERS without a cancel event)

Severity

  • major UX / incorrect accounting display (maker believes escrow is still cancellable)
## Summary Retail report on **dex.cl8y.com**: **My open limits** still shows a resting row and the user cannot cancel it. > Why won't My Open Limits order be canceled? ●order #1 · Sell UST1 · 82.044004487226 · placed 2026-08-15T14:21:43 That string is the **active** row in `LimitOrderMyPlacementsPanel` (green `●`, not parked `◆` / dust `▫`). Bundle **investigation + fix** for every surface that can leave a maker unable to cancel or unable to understand why Cancel is dead: `/trade` compact panel, `/limits` page panel, book row `×` / **Cancel all mine**, Advanced **Cancel by order ID**, and Portfolio (view-only). Do **not** split “indexer stale row” vs “disabled Cancel” vs “error toast missed” vs “sticky CTA covers the button” into separate issues. Related: [#135](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/135) (cancel vs indexer), [#141](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/141) / [#142](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/142) (parked lifecycle), [#162](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/162) (book row cancel), [#419](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/419) (open orders first), [#505](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/505) (`OrderStatus` / **L21**), [#524](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/524) (UST1 display invert — **Sell UST1**), [#527](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/527) (Chrome sticky Place limit can sit over ticket body), [#529](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/529) (UST1/USTR **place** bounds — cancel is `order_id` only; do not treat #529 as the cancel root unless evidence says the row is on that pair **and** cancel itself reverts). ## Evidence - Surface copy matches `LimitOrderMyPlacementsPanel` active line: `order #{id} · {Buy|Sell} {base} · {price} · placed {timestamp}`. - Pair/side: **Sell UST1** at **~82.04** (human). That magnitude is consistent with a UST1-as-base display (e.g. UST1/USTR-class quotes). Confirm pair address + factory `asset_0`/`asset_1` + invert state before changing contracts. - Placed **2026-08-15T14:21:43** (indexer `block_timestamp`, truncated to 19 chars). - No tx hash in the report. Treat as **mainnet / columbus-5** until a LocalTerra repro is found. ## Current codebase Cancel is implemented and covered for the **happy path**. The report is a **lifecycle / UX hole**: the panel can keep showing an **active** `●` row when the order is no longer cancelable, or the Cancel control can be disabled / covered / unexplained. | Layer | Behavior today | |-------|----------------| | **On-chain** | `ExecuteMsg::CancelLimitOrder { order_id }` / `CancelLimitOrders` — **owner-only**, only while the row is in `ORDERS`. Blocked while the pair is **paused** (**L6** / [#120](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/120)). After park, cancel is the wrong message — use `ClaimExpiredLimitOrder`. Query `OrderStatus` (**#505**): `Active` / `ParkedRefund` / `Unknown`. `Unknown` ≠ filled (also cancel, claim, never-placed). | | **Indexer** | Place → `limit_order_placements.lifecycle_status = active`. Cancel event → row omitted from default placements (**#135**). Park → `parked_expired` + `remaining_escrow`; claim → `refunded`. **Fills are inserted into `limit_order_fills` only** — `process_limit_order_fill` does **not** flip placement lifecycle or remaining escrow. A **fully filled** order can remain `active` in `GET .../limit-placements` and still render as `●` with **Cancel**. | | **dApp cancel** | One `useLimitOrderCancelMutation(pairAddr, wallet)` shared by ticket panel, book `×`, Cancel all mine, Advanced form. Preflight: skip if `orderIdHasIndexedCancellation`. Broadcast `cancel_limit_order`. On success: toast + invalidate `limitPlacements` / `limitCancellations` / book / wallet history. CosmWasm “LimitOrder not found” is humanized (#135). | | **Active-row Cancel disable** | Disabled when disconnected, pair paused, blacklist, mutation pending (**any** id), or indexed cancellation exists. **When already-cancelled, the label still says `Cancel`** — no “Already cancelled / no longer on the book” reason. | | **`/trade` compact panel** | `variant="compact"` sits **below** `.trade-limit-submit-sticky` (`TradeOrderTicket`). Active list is `max-h-24 overflow-y-auto`; panel `max-h-48`. Cancel is `text-[9px]`. Combined with [#527](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/527), the user can **read** the `●order #1` line and still not reach a working Cancel. | | **`/limits`** | Full panel + Advanced **Cancel by order ID**. Same mutation + same disable rules. | | **Portfolio** | `PortfolioOpenLimitsSection` lists pair / id / side / price / status / placed. **No cancel.** Copy says manage on Limits or Trade. | | **UST1 invert (#524)** | Display **Sell UST1** may be factory **ask** (pay UST1) or inverted **bid**. Cancel uses **pair + `order_id` only** — invert must not change the execute payload. | Playbooks: [`skills/AGENTS_FRONTEND_LIMIT_PARKED_EXPIRED.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_LIMIT_PARKED_EXPIRED.md), [`skills/AGENTS_ORDER_STATUS_QUERY.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_ORDER_STATUS_QUERY.md), [`docs/limit-orders.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/limit-orders.md) § Cancel / dApp retail form. ## Why this is needed 1. **Maker funds look stuck.** An `●` row with a dead or failing Cancel is a custody incident from the user’s point of view, even when escrow already filled, parked, or cancelled. 2. **Indexer `active` is not “still on `ORDERS`”.** Fills never update placement lifecycle. Cancel-indexed rows can still render as active if the placements query and the cancellation guard disagree. LCD `OrderStatus` is unused by the panel. 3. **Failure reasons are easy to miss.** Disable without a label change; `window.confirm`; toast + `TxResultAlert` below a clipped compact list; sticky Place limit covering the CTA ([#527](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/527)). 4. **Support cannot close the loop** without a documented triage (LCD `OrderStatus` + indexer placement + cancellation + fills) and a UI that matches that truth. ## Constraints / guardrails 1. **Do not weaken L6.** Pause still blocks cancel/claim on-chain and in the UI. Do not add a “force cancel” that burns gas on a guaranteed `Paused` revert. 2. **Owner-only.** Never cancel another wallet’s `order_id`. Book `×` stays `order.owner ===` connected address. 3. **No double refund.** Cancel only while `ORDERS` has the row. After park, **Claim** only. Do not invent a third execute path. 4. **`OrderStatus::Unknown` is not proof of fill** (**L21** / #505). Classify fill vs cancel vs never-placed from indexer fills/cancellations + local tx, not from `Unknown` alone. 5. **Do not change on-chain price bounds** here. [#529](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/529) is place-time `MIN/MAX_LIMIT_PRICE`. Cancel does not send a price. 6. **Do not change indexer candle / `price_usd` math** (#522 / #524). Invert stays display-only; cancel payload stays factory `order_id`. 7. **Copy (#489).** No `token0` / `token1` / raw `bid`/`ask` in retail. Blocking errors: one short sentence + optional Docs. Disabled Cancel must state **why** (paused, restricted, already gone, filled, claim instead). 8. **No always-on essay banners** on `/trade` or `/limits`. Progressive disclosure / inline row status only. 9. **Preserve testids** unless renamed in the same MR: `trade-cancel-placement-{id}`, `limits-page-cancel-placement-{id}`, `trade-book-cancel-{bid\|ask}-{id}`, `trade-ticket-placements-anchor`. 10. **Do not skip LocalTerra** with a false “no chain in agent VM”. Probe `make has-localterra`; provision `make setup-cloud-localterra` when the fix needs place/fill/cancel/park. ## Relevant files | File | Role | |------|------| | [`frontend-dapp/src/components/trade/LimitOrderMyPlacementsPanel.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/trade/LimitOrderMyPlacementsPanel.tsx) | Active `●` row + Cancel disable/label | | [`frontend-dapp/src/hooks/useLimitOrderCancelMutation.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/hooks/useLimitOrderCancelMutation.ts) | Shared cancel mutation + invalidations | | [`frontend-dapp/src/utils/limitOrderCancelUserMessage.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/limitOrderCancelUserMessage.ts) | Indexed-cancel guard + “not found” copy | | [`frontend-dapp/src/utils/limitPlacementLifecycle.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/limitPlacementLifecycle.ts) | `active` / `parked_expired` / `refunded` partition | | [`frontend-dapp/src/components/trade/TradeOrderTicket.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/trade/TradeOrderTicket.tsx) | Compact panel below sticky CTA | | [`frontend-dapp/src/pages/LimitOrdersPage.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/pages/LimitOrdersPage.tsx) | Page panel + Advanced cancel-by-id | | [`frontend-dapp/src/components/trade/OrderBookPanel.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/trade/OrderBookPanel.tsx) | Book `×` / Cancel all mine | | [`frontend-dapp/src/components/portfolio/PortfolioOpenLimitsSection.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/portfolio/PortfolioOpenLimitsSection.tsx) | View-only open limits | | [`frontend-dapp/src/services/terraclassic/pair.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/terraclassic/pair.ts) | `cancelLimitOrder` / batch | | [`indexer/src/db/queries/limit_order_lifecycle.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/db/queries/limit_order_lifecycle.rs) | Placement lifecycle SQL | | [`indexer/src/indexer/parser.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/indexer/parser.rs) | `process_limit_order_fill` (no lifecycle write) | | [`smartcontracts/contracts/pair/src/orderbook.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/smartcontracts/contracts/pair/src/orderbook.rs) | `query_order_status` | | [`docs/limit-orders.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/limit-orders.md) | Cancel / park / indexer invariants | ## Recommended direction **Investigate first (reproduce the report class, then fix).** Do not ship a cosmetic “Cancel” relabel without proving which class the UST1 row is. 1. **Triage matrix (required in the MR or issue comment):** for a stuck `●` row, record LCD `OrderStatus`, indexer placement `lifecycle_status`, indexer cancellation row, indexer fills for that `order_id`, pair `is_paused`, connected wallet vs `owner`. 2. **Reconcile “open” with chain.** Prefer LCD `OrderStatus` (or equivalent) before offering Cancel: - `Active` → Cancel enabled (unless pause/blacklist/gas). - `ParkedRefund` → show Claim, not Cancel. - `Unknown` → do **not** leave a fake `●` Cancel. Classify via fills vs cancellations; show **Filled** / **Already cancelled** / **No longer on the book** and hide Cancel. 3. **Indexer (if fills are the gap):** when a fill consumes remaining escrow (or the order leaves `ORDERS` without cancel/park), stop listing the row as default-open `active`. Do not treat `Unknown` as fill without a fill event. 4. **Cancel CTA integrity:** disabled Cancel must say why. Compact `/trade` Cancel must be reachable (not under sticky Place limit; not clipped without scroll affordance). Errors must be visible next to the row, not only in a toast below `max-h-24`. 5. **Keep #135 humanize** for the race where the user still submits and the map is empty. ## Acceptance criteria - [ ] **AC1 — Reproduce or classify the report.** Document which triage class matches `order #1 · Sell UST1 · 82.04…` (or an equivalent LocalTerra fixture). If mainnet pair/tx cannot be recovered, ship a fixture that matches the same UI string and failure mode. - [ ] **AC2 — Cancel works when the order is `Active` on-chain.** `/trade` compact, `/limits` panel, book `×`, Advanced form: confirm → `cancel_limit_order` → row leaves **Open**; escrow returns; indexer catch-up matches. - [ ] **AC3 — No fake Cancel on gone orders.** Fully filled, already cancelled, or `Unknown` without `ORDERS`: no enabled **Cancel**. Row status is Filled / Cancelled / gone — not a stuck `●` with a dead button. - [ ] **AC4 — Parked ≠ Cancel.** `ParkedRefund` / `parked_expired` stays **Claim refund** / **Claim dust**. Pause/blacklist copy unchanged (**L6**). - [ ] **AC5 — Disabled reason.** Paused → `Unavailable (pair paused)`. Blacklist → `Trading restricted`. Already gone → explicit gone copy (not a mute `Cancel`). - [ ] **AC6 — `/trade` reachability.** At desktop 1280×720 and phone, Cancel for a single open row is clickable (`elementFromPoint` hits the button, not `trade-limit-submit`). - [ ] **AC7 — Invert / UST1.** Display **Sell UST1** still cancels the factory `order_id` on the selected pair. No extra execute fields. - [ ] **AC8 — Docs + verify target.** `docs/limit-orders.md` + parked-expired playbook state the open-row reconciliation. `make verify-issue-<iid>` covers unit + the new paths. ## Test plan (all paths) ### Investigation / LCD + indexer | # | Path | Assert | |---|------|--------| | I1 | Place ask **Sell {UST1 or fixture base}**, still resting | LCD `OrderStatus=Active`; panel `●`; Cancel enabled | | I2 | Cancel success | Placement omitted; cancellation row exists; panel empty or not that id | | I3 | Full fill (taker/hybrid) | Placement **not** shown as cancelable `●`; fill row exists; Cancel hidden | | I4 | Partial fill, remainder on book | Still `Active`; Cancel refunds **remaining** only | | I5 | TTL expiry then park | `ParkedRefund` / Claim — not Cancel | | I6 | Dust flush park (`reason=DustFilled`) | Claim dust — not Cancel | | I7 | Pair paused | Cancel disabled + pause copy; chain revert if forced | | I8 | Wrong wallet | No Cancel on others’ book rows; own panel empty | | I9 | Indexer lag after successful cancel | UI does not re-enable Cancel from a stale `active` row once cancellation or LCD `Unknown` is known | | I10 | UST1 invert on / off | Same `order_id` cancel payload | ### Unit / RTL (`make test-frontend`) 1. `LimitOrderMyPlacementsPanel`: Active + Cancel; parked → Claim; filled/unknown fixture → no Cancel + status copy; paused/blacklist labels; already-indexed cancellation label. 2. `useLimitOrderCancelMutation`: indexed-cancel throws; single vs batch; invalidations. 3. Lifecycle helpers: fill/gone classification if new; existing park/dust tests still pass. 4. Trade ticket: compact panel Cancel not a child of `trade-limit-submit-sticky`. ### Indexer (`make test-indexer-integration` / scoped) 1. Full fill removes or reclassifies default-open placement (if indexer change ships). 2. Cancel still omits placement (#135). 3. Park/claim lifecycle unchanged (#142). 4. Columnar / flattened wasm fill+swap streams still ingest (#141 parser). ### Playwright | # | Path | Assert | |---|------|--------| | P1 | `limit-orders-tx.spec.ts` | Existing place + panel cancel still green | | P2 | `/trade` compact | Place → `trade-cancel-placement-{id}` visible and clickable; confirm; Cancel submitted | | P3 | Full-fill fixture (or chain) | Row not cancelable; no `cancel_limit_order` broadcast | | P4 | Parked | Claim testids only | | P5 | Pause / blacklist | Cancel disabled + copy | | P6 | Desktop Chrome | `elementFromPoint` on Cancel ≠ Place limit sticky | | P7 | `/limits` Advanced | Cancel by id still works; helper still points at row actions first | ### Manual QA (mainnet report class) 1. Find the UST1 pair + `order_id=1` (or current equivalent). Query LCD `OrderStatus` and indexer placements/fills/cancels. 2. If `Active`, cancel from `/trade` and `/limits`; confirm refund. 3. If not `Active`, confirm the UI no longer offers Cancel and shows the correct next action. ## Test plan — attack, hack, and abuse vectors | Vector | What to prove | |--------|----------------| | **A1 Cancel someone else’s order** | Book `×` and panel Cancel never appear for `owner !==` connected wallet. On-chain still `Unauthorized`. | | **A2 Cancel-after-fill grief** | UI does not broadcast `cancel_limit_order` against `Unknown`/filled ids (gas grief). If a race still hits chain, humanize — no raw map key. | | **A3 Double cancel / double refund** | Second Cancel disabled after indexed cancel or LCD gone. Parked path cannot cancel. No second CW20 refund. | | **A4 Pause bypass** | Disabled + no broadcast while `is_paused`. | | **A5 Blacklist bypass** | Same as place/swap gates (SEC-E01). | | **A6 Confirm spoof / clickjack** | `#527` overlay: `elementFromPoint` on Cancel is Cancel, not Place limit. `window.confirm` still required for panel cancel. | | **A7 order_id confusion** | Ids are **per pair**. Cancelling `#1` on pair A must not target pair B. Pair switch clears pending mutation context. | | **A8 Invert spoof** | Toggling UST1 invert must not change `cancel_limit_order.order_id` or pair address. | | **A9 XSS** | Indexer price / timestamps render as text (`82.044…`, ISO slice). No `innerHTML`. | | **A10 Sequence / fee grief** | Existing broadcast pending disable + fee humanize. Do not add a parallel cancel client. | ## Verification criteria Issue is done when **all** of the following are true: 1. **Triage written.** The report class (or a matching fixture) is identified with LCD + indexer evidence. 2. **No stuck `●` + dead Cancel.** Active-on-chain orders cancel; gone orders do not offer Cancel. 3. **Automated coverage** for I1–I7 (unit and/or e2e) and A1–A3, A6–A8. 4. **Invariants preserved:** L6 pause, owner-only, no double refund, L21 `Unknown` ≠ fill, #524 convert-on-submit untouched, #529 out of scope unless cancel itself reverts. 5. **`make verify-issue-<iid>`** green. Do not skip chain work with a false “no LocalTerra” without `make has-localterra` / `make setup-cloud-localterra`. 6. **Docs** (`docs/limit-orders.md` + playbook) describe open-row reconciliation. ## Mode - [x] limit order - [x] hybrid (fills that remove the order from `ORDERS` without a cancel event) ## Severity - [x] major UX / incorrect accounting display (maker believes escrow is still cancellable)
PlasticDigits commented 2026-08-16 07:17:27 +00:00 (Migrated from gitlab.com)

mentioned in commit b35548b994

mentioned in commit b35548b9944794657728089deaa6895b301a47f8
PlasticDigits commented 2026-08-16 07:31:14 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1059

mentioned in merge request !1059
PlasticDigits commented 2026-08-16 08:44:23 +00:00 (Migrated from gitlab.com)

Merge sanity check for !1059 (before landing on main):

  • AC1–AC8 look implemented for My open limits (LCD OrderStatus + indexer fills/cancels; report-class fixture → Filled; Claim for ParkedRefund; cancel payload is order_id only).
  • Book-row × is not UI-reconciled the same way — a stale filled book row can still show a clickable × until broadcast fails.
  • One in-flight cancel disables every open-limit Cancel with “Cancelling…”.
  • No Playwright elementFromPoint for AC6 (compact Cancel vs Place limit). Verify script is unit/docs.
  • Must land after #527 footer dock (or rebase onto it): keep My open limits in the scroll body above trade-ticket-submit-footer, not inside the money CTA. Do not resurrect trade-limit-submit-sticky.
  • Not blocking merge after that rebase. MR pipeline failed immediately (duration 0) — not treated as a code verdict.
Merge sanity check for !1059 (before landing on main): - AC1–AC8 look implemented for My open limits (LCD `OrderStatus` + indexer fills/cancels; report-class fixture → Filled; Claim for `ParkedRefund`; cancel payload is `order_id` only). - Book-row `×` is not UI-reconciled the same way — a stale filled book row can still show a clickable `×` until broadcast fails. - One in-flight cancel disables every open-limit Cancel with “Cancelling…”. - No Playwright `elementFromPoint` for AC6 (compact Cancel vs Place limit). Verify script is unit/docs. - Must land after #527 footer dock (or rebase onto it): keep My open limits in the scroll body **above** `trade-ticket-submit-footer`, not inside the money CTA. Do not resurrect `trade-limit-submit-sticky`. - Not blocking merge after that rebase. MR pipeline failed immediately (duration 0) — not treated as a code verdict.
PlasticDigits commented 2026-08-16 08:44:25 +00:00 (Migrated from gitlab.com)

mentioned in issue #527

mentioned in issue #527
PlasticDigits commented 2026-08-16 08:49:51 +00:00 (Migrated from gitlab.com)

mentioned in commit 9fd8b4eadf

mentioned in commit 9fd8b4eadfbdbefcedfb3bc198ddaa814bf0e07d
PlasticDigits commented 2026-08-16 08:49:56 +00:00 (Migrated from gitlab.com)

mentioned in commit 367b772e7b

mentioned in commit 367b772e7b8caedfc53fa2488af3b636da39e408
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-16 08:49:56 +00:00
PlasticDigits commented 2026-08-16 13:17:24 +00:00 (Migrated from gitlab.com)

mentioned in commit 40e614ae7c

mentioned in commit 40e614ae7ceef00cd62a4ef7c4cd841e69cd682e
PlasticDigits commented 2026-08-17 10:26:09 +00:00 (Migrated from gitlab.com)

mentioned in issue #546

mentioned in issue #546
PlasticDigits commented 2026-08-24 03:15:17 +00:00 (Migrated from gitlab.com)

mentioned in issue #617

mentioned in issue #617
PlasticDigits commented 2026-08-24 03:15:38 +00:00 (Migrated from gitlab.com)

mentioned in issue #618

mentioned in issue #618
PlasticDigits commented 2026-08-24 03:15:46 +00:00 (Migrated from gitlab.com)

mentioned in issue #619

mentioned in issue #619
PlasticDigits commented 2026-09-01 08:14:37 +00:00 (Migrated from gitlab.com)

mentioned in issue #717

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