design: research “draw a chart” inventory path bot (secret polyline, zig-zag / offload, risk limits) #717

Open
opened 2026-09-01 08:14:34 +00:00 by PlasticDigits · 5 comments
PlasticDigits commented 2026-09-01 08:14:34 +00:00 (Migrated from gitlab.com)

Summary

Investigate a “draw a chart” inventory bot: an operator draws a secret price path over a chosen time window (mouse / pointer on a chart), and a bot trades the operator’s own inventory toward that path — including retracement zig-zags, offload / cash-raise legs, and a later second rally — under hard risk limits (max volume per period, stop-loss, inventory floors).

This issue is a product + architecture + threat-model spike. Do not ship a new CosmWasm message, vault, retail Draw tab, indexer schema, or hosted signer in this work item. The deliverable is a written recommendation: go / no-go / integrator-only, with architecture (off-chain keeper vs compile-to-existing ladder vs new vault), gas, secrecy model, and market-integrity constraints.

Related, not duplicates:

  • #546 — range grid (buy↔sell flip inside [P_low, P_high]). This ticket is a time-indexed price path, not a static band.
  • #617 / #618 — V3 Grid vault + keeper (not shipped).
  • #206 — one-shot one-sided ladder place (opening shot only).
  • #505 — OrderStatus for bot custody (L21).
  • Localnet swarm (packages/localnet-trading-swarm, scripts/bots/) is LocalTerra load only, not a production MM.

Confidential third-party bot review (#650) stays out of this write-up. Do not copy findings from that issue into this one.

Current codebase

There is no draw-to-trade surface. Charts are read-only candles. Limits are one-shot. Bots in-repo are LocalTerra swarm, not path followers.

Layer Behavior today
Charts (/trade, /charts) TradingView lightweight-charts candlesticks + volume + optional SMA/RSI. USD Y-axis, newest-N candles, interval chips, pair invert. No drawing tools, polyline overlay, or “target path” series. Canvas is aria-hidden; AT uses region + aria-live. Playbook: skills/AGENTS_FRONTEND_PRICE_CHART.md.
Limit place Cw20HookMsg::PlaceLimitOrderBatch / PlaceLimitOrderLadder. Ladder expands equal only. One side per tx. Hard cap 100 rungs. Crossing allowed on-chain; dApp is post-only UX (#297 / #385).
Fill / recycle Hybrid execute_swap walks the book under max_maker_fills (100) and MAX_SCAN_STEPS (500) — L5. Maker payouts go to the order owner wallet. No opposite-side re-place, no timed schedule, no path object.
Trade ticket /trade Market (always-on hybrid quote) + Limit + /limits Ladder. No inventory planner, no stop-loss bot, no max-volume/period governor.
Indexer Candles, tape, limit-book / insert-hints, limit-fills. Placement lifecycle_status=active is not proof the row is still in ORDERS (#530). Bots must use LCD OrderStatus.
Swarm Poisson random swaps / limits / LP on localterra only (network === "localterra"). Profiles are not path-following.
Keys Simulated Wallet is LocalTerra-only. Official dApp does not custody production keys.
Not present Path compiler, secret chart store, zig-zag / offload phase machine, volume/period governor, stop-loss keeper, draw overlay, “paint toward target” taker loop.

Product confusion to avoid: this is not v2 AMM LP (provide_liquidity), not /ust1 mint, and not the V3 range grid. Support already tells users book escrow ≠ pool LP (#531). Do not call a drawn path “LP” in retail copy.

On-chain truth vs secret chart: every fill, swap, and resting limit is public (wasm events, indexer tape, LCD). A “secret” drawing does not hide the resulting trades. Copy-traders and adversaries can reconstruct an approximate path from the tape.

Why this is needed

Community product ask (paraphrased, no chat transcript, identities omitted): token owners and average traders want a visual way to say “move my inventory along this curve for the next N hours/days,” including planned dips (retracements) and sell-to-cash windows so a later rally has quote inventory — with risk caps. A range grid (#546) does not express time or phase (rally vs dip vs offload).

  1. Today’s ladder is an opening shot. A maker who wants “up, dip, up again” must babysit /limits and the Market ticket by hand. That is the actual inventory loop.
  2. Retail cannot run that loop. There is no visual compiler from a mouse path to resting limits + timed takes + cash reserve. Power users would script it anyway; we should decide whether the protocol hosts that UX or documents an integrator-only path.
  3. Wrong product is harmful. If the only value is “make the public chart look like I drew it,” that is tape painting / wash-adjacent and this spike must recommend NO-GO for an official dApp feature. If the value is honest inventory management (rebalance, staged exit, MM with a published or private schedule, risk-capped), the spike must say how to keep the UX on that side of the line.
  4. Grid vs path. Shipping this as a thin skin on #546 would be the wrong abstraction (static band ≠ timed polyline). The write-up must compare and not merge the two without an explicit decision.

Constraints / guardrails

  1. Spike only. No new ExecuteMsg, no vault, no /draw route, no indexer table for paths, no production keeper keys in this issue. Follow-up implementation issues only after the write-up is accepted (including a market-integrity section signed off in the issue comments).
  2. Market integrity (hard). Official product must not be designed as a secret pump, wash-trade, spoof (place+cancel to paint), or self-match engine. Disallow:
    • the bot trading against itself on the same pair (wash);
    • cancel-spam whose purpose is a fake print without intending to fill;
    • protocol-hosted “hidden path that moves price for spectators.”
      Secret chart = operator strategy confidentiality (not uploaded to the indexer / not shown to other wallets), not concealment of illegal activity. Trades remain public.
  3. Legal / listings. Token-owner inventory along a path can still look like wash or manipulation to CG/CMC/regulators. The spike must list residual risk and whether this stays self-hosted / integrator-only vs official dApp. Do not imply CL8Y endorses painting candles.
  4. Do not put path-follow on the taker hot path. No extra inserts inside execute_swap. Same default as #546: forbidden unless proven within L5 gas (it will not be).
  5. Keep L5 / L6 / L14 / L17 / L20 / L21 / L22 / I13 / G1–G14. Pause still blocks place/cancel/claim. Hints advisory. Human vs raw prices. OrderStatus is the custody oracle. Greedy/hybrid unchanged.
  6. Keys. Hosted dApp must not sign for the user. Simulated Wallet stays LocalTerra. Bot = user-held keys, documented script, authz only if columbus-5 actually supports the needed grant (verify; do not assume), or a future vault (#617-style) that the user deposits into.
  7. Chart secrecy. Default: path lives on the operator device (encrypted at rest). Do not persist drawings in indexer Postgres, Coolify logs, or public Git. If a later optional backup exists, it is end-to-end encrypted and out of scope for this spike’s implementation.
  8. Risk limits are mandatory in any GO design: max notional / max base+quote volume per period, stop-loss (and optional take-profit), min remaining inventory, max taker slippage / max_spread, max open rungs, halt on pause / blacklist / code_id_frozen. A path that would exceed caps must not arm.
  9. Zig-zag / offload are first-class phases, not “draw sloppily.” Retracement and cash-raise must be labeled segments with inventory rules (e.g. offload = sell into quote up to cap; retrace = buy back with reserved quote; rally = spend quote). Freehand noise must be resampled / simplified so it cannot become 10k dust orders.
  10. Do not break PriceChart invariants (#148 / #151 / #211 / #336 / #705). Prefer a separate overlay canvas or a dedicated tool page over overloading PriceChartLightweightCanvas.
  11. Cognitive load (#489). If a later UI exists, it is a dedicated Advanced / operator flow — not a lecture on /pool or a default /trade mode. Do not add always-on essays.
  12. No farm/APR chrome. Do not imply this is an incentive program.
  13. Do not change pool math, wrap fees, treasury, or factory listing in this spike.
  14. LocalTerra for any gas or keeper prototype. Do not report SKIP (no LocalTerra) without provisioning (make setup-cloud-localterra).
  15. Community-tax pairs: extra-debit / buy split / launch guards (T592, H608, R607) apply to the bot wallet like any other trader. Path compiler must not assume 1:1 CW20 in/out.

Relevant files

File Role
frontend-dapp/src/components/charts/PriceChart.tsx Chart shell, interval, a11y
frontend-dapp/src/components/charts/PriceChartLightweightCanvas.tsx lightweight-charts mount; do not casually add drawing here
frontend-dapp/src/pages/TradePage.tsx /trade workspace
frontend-dapp/src/components/trade/LimitOrderLadderPanel.tsx Closest visual “many prices at once” UX
frontend-dapp/src/utils/limitOrderLadder.ts Equal-rung preview; amounts
smartcontracts/packages/dex-common/src/limit_placement.rs Ladder expand, rung cap
smartcontracts/contracts/pair/src/limit_placement.rs Batch execute
smartcontracts/contracts/pair/src/orderbook.rs Insert/match; OrderStatus
docs/limit-orders.md Place/fill/gas
docs/integrators.md Book HTTP, hints, L21
indexer/src/indexer/parser.rs Placement/fill attrs
packages/localnet-trading-swarm/ LocalTerra bot guardrails (chain/env)
skills/AGENTS_LIMIT_ORDER_BATCH_LADDER.md Ladder invariants
skills/AGENTS_ORDER_STATUS_QUERY.md Bot custody oracle
skills/AGENTS_FRONTEND_PRICE_CHART.md Chart do-not-break list
docs/security-model.md Tier 0 MM registration; EOA-only self-register

Evaluate four architectures against the same user story: pair + time horizon + polyline of (t, price) + optional phase tags (rally / retrace / offload / hold) + inventory + risk envelope. Recommend one, or none (document as integrator-only / NO-GO).

A — Path compiler → existing batch/ladder (default candidate)

  1. Resample the drawing to ≤ max_batch_rungs monotonic-in-time waypoints (Douglas–Peucker or bucket-by-interval).
  2. Map each waypoint to resting limits on the appropriate side given current mid vs target (bid below, ask above). Mixed sides = two batches (do not silently invent mixed-side wasm).
  3. Offload / second rally: reserve a quote budget (do not place 100% of inventory on the first up-leg). Retrace legs use reserved quote to bid; rally-2 spends it.
  4. Taker chase (hybrid Market) only when the path is behind and maker resting cannot catch up — hard volume/period + slippage caps. Default off in v0.
  5. Signing: user bot or documented script. dApp may export a path JSON for the bot; it must not broadcast.

B — Off-chain keeper with schedule

Same as A, plus a local process that re-arms on fills (OrderStatus + indexer limit-fills), respects time (t on the polyline), and halts on stop-loss / pause / blacklist. Chain guard like the swarm: refuse non-localterra until a later explicit mainnet flag. No keys in indexer/.

C — New vault (only if A/B fail the non-custodial “set and forget” bar)

Reuse lessons from #546 option E / #617 (vault owns orders, permissionless crank cannot withdraw). Do not start here. Path-following inside a vault is a new strategy object, not a grid spread.

D — NO-GO / integrator-only

If honest inventory value cannot be separated from tape-painting, or gas/inventory/tax make it unusable, publish a short integrator note (“compile your polyline to PlaceLimitOrderBatch + your own keeper”) and do not add official Draw UX.

Default assumption: A + optional B on LocalTerra; C blocked on #546/#617 decisions; official retail Draw UX not in v0.

Zig-zag / offload (must be in the model, not a later surprise):

Phase Inventory intent Typical actions
Rally Spend quote (or bid inventory) toward higher target Asks above rising path; optional capped taker buy only if under-inventory
Retrace Planned dip; do not dump the whole bag Bids along the dip; no aggressive market sells that break the path
Offload Raise quote for rally-2 Asks / capped sells; stop when quote reserve hits target
Hold Time passes; no trade No new takes; optionally cancel-through if mid leaves a band

Risk envelope (minimum fields for any GO spec):

  • max_volume_base / max_volume_quote per rolling window (and per calendar day)
  • stop_loss (mark vs path or vs entry VWAP — spike must pick one and test both)
  • max_taker_bps / max_spread
  • max_open_rungs ≤ pair max_batch_rungs
  • min_inventory_base / min_inventory_quote floors
  • Halt: pair pause, wallet/token/pair blacklist, code_id_frozen, indexer/LCD outage

Acceptance criteria

  • Written spike (comment or docs/ draft only if product wants it public) covering: current gaps, grid-vs-path, A/B/C/D recommendation, gas ballpark (LocalTerra batch place vs N takes), secrecy model, tax/pause/blacklist.
  • Explicit GO / NO-GO / integrator-only for an official dApp Draw UX.
  • Market-integrity section: wash, spoof, self-match, leaked-path front-run, tape reconstruction; what the product refuses to do.
  • Path data model: polyline + phase tags + resampling cap; secret-at-rest; no indexer persistence in v0.
  • Risk envelope specified (volume/period, stop-loss, floors, halt conditions).
  • Zig-zag + offload + second rally described as phases with inventory rules (not “draw a W and hope”).
  • Comparison table vs #546 range grid (when to use which).
  • No wasm / dApp / indexer schema merged under this IID.
  • Follow-up implementation issues filed only if GO (split UI compiler vs keeper vs vault; do not sneak Draw into #617).

Test plan (all paths) — for the spike and for any later prototype

These tests are required in the write-up as a plan; implement them only on a follow-up IID (except paper/unit sketches of the compiler).

Path compiler (pure)

  1. Empty / single-point / two-point drawings.
  2. Dense scribble → resampled ≤ rung cap; no 10k orders.
  3. Non-monotonic time (user draws backwards) → reject or auto-sort with warning.
  4. Price ≤ 0, below USD scale 0, or outside L20 human band → reject.
  5. Mixed-decimal pairs (UST1/USTR) — human draw → raw prices (L20 / #529).
  6. Phase tags: rally then retrace then offload then rally-2; reserved quote actually funds rally-2.
  7. Path that would exceed max_volume_* does not arm.
  8. Invert display (#524 / #543): drawing is in display space; compiler converts to factory token1/token0.

Maker / ladder mapping

  1. Target above mid → asks; below → bids; through-market rungs blocked by client post-only (same as ladder) unless a later spec explicitly allows crossing (on-chain still allows).
  2. Two-sided initial map = two txs; one failing side does not leave a half-armed path without a documented rollback.
  3. Rung count vs max_batch_rungs / hard cap 100.
  4. Insert hints via indexer (#267); resolved:false → no fabricated id (L14).

Time / keeper (LocalTerra only if prototyped)

  1. Waypoints fire in time order; early waypoints do not dump inventory needed for later offload.
  2. Fill → OrderStatus Unknown is not treated as “definitely filled” (#505 / #530).
  3. Indexer lag: keeper does not double-place.
  4. Pair pause (L6): halt place/cancel/claim; resume policy documented.
  5. Blacklist wallet/token/pair: halt.
  6. code_id_frozen / F6: halt (#585).
  7. Community-tax sell extra-debit / buy split: inventory accounting uses net, not gross.
  8. Stop-loss: mark move vs threshold cancels remaining and stops taker chase.
  9. Volume/period cap: further takes/places refused; existing rests optionally cancelled (pick one; test both if both exist).
  10. Chain/env guard: refuse columbus-5 unless an explicit later flag exists (mirror swarm).

UI (only if a later Draw overlay exists)

  1. Dedicated overlay does not remount PriceChart on every pointer move; interval switch still one createChart (#148).
  2. Secret: path not in network tab / indexer / share URL / Open Graph.
  3. Disconnect: draw + preview without wallet; arm requires connect (parity with ladder #494).
  4. A11y: canvas stays aria-hidden; path summary in text (waypoint count, horizon, caps).
  5. Cognitive load: not default /trade mode.

Docs / product

  1. Copy never says “LP” or “guaranteed chart.”
  2. Integrator note if D: polyline → batch msgs, no official bot.

Test plan — attack, hack, and abuse vectors

Treat these as must-fail-closed. A GO design that cannot mitigate a row is NO-GO for official UX (integrator-only at most).

ID Vector Expected
X1 Wash / self-match — bot places bid and ask that fill each other, or two keys it controls Detect same-owner / labeled bot wallets; refuse self-trade. Protocol pair may still match two EOAs; product must not orchestrate wash.
X2 Spoof / flicker — place path rungs and cancel before they can fill to paint the book Rate-limit cancels; min time-in-force; cancel-storm halt.
X3 Tape painting — aggressive taker buys/sells whose only goal is candle shape Default maker-only; taker chase off; volume/period caps; no “match the candle at any cost” mode.
X4 Secret path leak — path uploaded to indexer, analytics, Sentry, share link, screenshot OCR in support v0: local-only; no telemetry of coordinates; docs warn that trades still leak the path.
X5 Front-run leaked JSON — operator exports path; adversary races waypoints Treat export as a secret; optional commit-reveal out of v0 scope; do not put path on-chain.
X6 MEV / sandwich on taker chase Public mempool is the given (#299); tight max_spread / min_return; prefer maker.
X7 Grief takers by inserting path-follow work in execute_swap Forbidden (same as #546).
X8 Key theft via “dApp signs for you” / mnemonic in frontend Forbidden. Simulated Wallet LocalTerra-only.
X9 Authz over-grant — MsgExecuteContract on pair allows Cancel + drain If authz is proposed, grant must be least privilege (document actual columbus-5 modules). Prefer vault (#617) over god-grant.
X10 Dust / gas grief — scribble → 100 rungs × many pairs Resample + global arm cap + LUNC preflight (ladder gates).
X11 Crossing the book to force a print Client post-only unless explicit; never silently disable #297.
X12 Blacklist / pause bypass via bot retry Halt; do not retry through BlacklistGuardUnavailable fail-open (SEC-I03).
X13 Tax-token drain — ignore extra-debit, over-spend allowance Net accounting; allowance = gross sell debit.
X14 Stop-loss hunt — adversary pushes mid to trigger stop then reverts Document; optional TWAP stop; not a contract change in this spike.
X15 Inventory insolvency — rally-2 with no quote after a full offload miss Phase budgets; floors; dry-run before arm.
X16 Indexer poison — fake candles so the draw overlay mis-scales Trust boundary already drops bad OHLC (#226); drawing uses sanitized series only.
X17 LCD / progress botnet from a chatty keeper Reuse LCD-heavy 10 RPS / global 60 RPS (#363 / #694); backoff; do not hammer route/solve.
X18 Frozen code_id / F6 — bot keeps taking on a frozen hop Halt; respect route/solve exclude (#585).
X19 Tier 0 / MM registration abuse — path bot self-registers as contract MM EOA-only Register; Tier 0 is governance RegisterWallet only (docs/security-model.md).
X20 Social engineering — “draw a chart” marketed as guaranteed price** Copy + risk modal; no performance claims (#138 NFA posture).

No exploit PoCs in the public issue beyond the table. If a later implementation needs a confidential security child, file it confidential and do not paste exploits here.

Verification criteria

The spike is done when:

  1. A reviewer can answer GO / NO-GO / integrator-only from the write-up without reading chat logs.
  2. Grid (#546) vs path (this issue) is unambiguous.
  3. Wash / spoof / tape-paint are explicit refusals, not “later.”
  4. Secrecy is defined as local strategy, with the caveat that tape reconstructs the path.
  5. Zig-zag, offload, second rally, and risk limits appear in the data model, not as slogans.
  6. Repo has no accidental Draw UI, wasm, or indexer migration merged under this IID.
  7. LocalTerra gas notes (if any) used make has-localterra / make setup-cloud-localterra — no false “no chain in VM.”
  8. #650 confidential material is not quoted.

Owner type

product / architecture (spike). Later: frontend (compiler overlay), backend (optional keeper package), contracts only if C.

Priority

P2 (priority::medium) — useful operator UX, not a launch blocker. Blocked on integrity sign-off before any implementation IID.

## Summary Investigate a **“draw a chart” inventory bot**: an operator draws a **secret price path** over a **chosen time window** (mouse / pointer on a chart), and a bot **trades the operator’s own inventory** toward that path — including **retracement zig-zags**, **offload / cash-raise legs**, and a later **second rally** — under **hard risk limits** (max volume per period, stop-loss, inventory floors). This issue is a **product + architecture + threat-model spike**. Do **not** ship a new CosmWasm message, vault, retail Draw tab, indexer schema, or hosted signer in this work item. The deliverable is a written recommendation: **go / no-go / integrator-only**, with **architecture** (off-chain keeper vs compile-to-existing ladder vs new vault), **gas**, **secrecy model**, and **market-integrity constraints**. **Related, not duplicates:** - [#546](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/546) — **range grid** (buy↔sell flip inside `[P_low, P_high]`). This ticket is a **time-indexed price path**, not a static band. - [#617](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/617) / [#618](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/618) — V3 Grid vault + keeper (not shipped). - [#206](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/206) — one-shot one-sided ladder place (opening shot only). - [#505](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/505) — `OrderStatus` for bot custody (**L21**). - Localnet swarm (`packages/localnet-trading-swarm`, `scripts/bots/`) is **LocalTerra load only**, not a production MM. Confidential third-party bot review ([#650](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/650)) stays **out of this write-up**. Do not copy findings from that issue into this one. ## Current codebase There is **no** draw-to-trade surface. Charts are **read-only** candles. Limits are **one-shot**. Bots in-repo are **LocalTerra swarm**, not path followers. | Layer | Behavior today | |-------|----------------| | **Charts (`/trade`, `/charts`)** | TradingView **lightweight-charts** candlesticks + volume + optional SMA/RSI. USD Y-axis, newest-N candles, interval chips, pair invert. **No** drawing tools, polyline overlay, or “target path” series. Canvas is `aria-hidden`; AT uses region + `aria-live`. Playbook: [`skills/AGENTS_FRONTEND_PRICE_CHART.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_PRICE_CHART.md). | | **Limit place** | `Cw20HookMsg::PlaceLimitOrderBatch` / `PlaceLimitOrderLadder`. Ladder expands **`equal` only**. **One side per tx.** Hard cap **100** rungs. Crossing allowed **on-chain**; dApp is **post-only** UX ([#297](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/297) / [#385](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/385)). | | **Fill / recycle** | Hybrid `execute_swap` walks the book under `max_maker_fills` (100) and `MAX_SCAN_STEPS` (500) — **L5**. Maker payouts go to the **order owner wallet**. **No** opposite-side re-place, **no** timed schedule, **no** path object. | | **Trade ticket** | `/trade` Market (always-on hybrid quote) + Limit + `/limits` Ladder. No inventory planner, no stop-loss bot, no max-volume/period governor. | | **Indexer** | Candles, tape, `limit-book` / `insert-hints`, `limit-fills`. Placement `lifecycle_status=active` is **not** proof the row is still in `ORDERS` ([#530](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/530)). Bots must use LCD `OrderStatus`. | | **Swarm** | Poisson random swaps / limits / LP on **localterra** only (`network === "localterra"`). Profiles are **not** path-following. | | **Keys** | Simulated Wallet is **LocalTerra-only**. Official dApp does **not** custody production keys. | | **Not present** | Path compiler, secret chart store, zig-zag / offload phase machine, volume/period governor, stop-loss keeper, draw overlay, “paint toward target” taker loop. | **Product confusion to avoid:** this is **not** v2 AMM LP (`provide_liquidity`), **not** `/ust1` mint, and **not** the V3 range grid. Support already tells users book escrow ≠ pool LP ([#531](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/531)). Do not call a drawn path “LP” in retail copy. **On-chain truth vs secret chart:** every fill, swap, and resting limit is **public** (wasm events, indexer tape, LCD). A “secret” drawing does **not** hide the resulting trades. Copy-traders and adversaries can reconstruct an approximate path from the tape. ## Why this is needed Community product ask (paraphrased, **no chat transcript**, identities omitted): token owners and average traders want a **visual** way to say “move my inventory along this curve for the next N hours/days,” including **planned dips** (retracements) and **sell-to-cash** windows so a later rally has quote inventory — with **risk caps**. A range grid (#546) does not express **time** or **phase** (rally vs dip vs offload). 1. **Today’s ladder is an opening shot.** A maker who wants “up, dip, up again” must babysit `/limits` and the Market ticket by hand. That is the actual inventory loop. 2. **Retail cannot run that loop.** There is no visual compiler from a mouse path to resting limits + timed takes + cash reserve. Power users would script it anyway; we should decide whether the protocol **hosts** that UX or documents an **integrator-only** path. 3. **Wrong product is harmful.** If the only value is “make the public chart look like I drew it,” that is **tape painting / wash-adjacent** and this spike must recommend **NO-GO** for an official dApp feature. If the value is **honest inventory management** (rebalance, staged exit, MM with a published or private schedule, risk-capped), the spike must say how to keep the UX on that side of the line. 4. **Grid vs path.** Shipping this as a thin skin on #546 would be the wrong abstraction (static band ≠ timed polyline). The write-up must compare and **not** merge the two without an explicit decision. ## Constraints / guardrails 1. **Spike only.** No new `ExecuteMsg`, no vault, no `/draw` route, no indexer table for paths, no production keeper keys in this issue. Follow-up implementation issues **only after** the write-up is accepted (including a **market-integrity** section signed off in the issue comments). 2. **Market integrity (hard).** Official product **must not** be designed as a secret pump, wash-trade, spoof (place+cancel to paint), or self-match engine. Disallow: - the bot trading **against itself** on the same pair (wash); - **cancel-spam** whose purpose is a fake print without intending to fill; - **protocol-hosted** “hidden path that moves price for spectators.” Secret chart = **operator strategy confidentiality** (not uploaded to the indexer / not shown to other wallets), **not** concealment of illegal activity. Trades remain public. 3. **Legal / listings.** Token-owner inventory along a path can still look like **wash or manipulation** to CG/CMC/regulators. The spike must list residual risk and whether this stays **self-hosted / integrator-only** vs official dApp. Do not imply CL8Y endorses painting candles. 4. **Do not put path-follow on the taker hot path.** No extra inserts inside `execute_swap`. Same default as #546: **forbidden** unless proven within **L5** gas (it will not be). 5. **Keep L5 / L6 / L14 / L17 / L20 / L21 / L22 / I13 / G1–G14.** Pause still blocks place/cancel/claim. Hints advisory. Human vs raw prices. `OrderStatus` is the custody oracle. Greedy/hybrid unchanged. 6. **Keys.** Hosted dApp **must not** sign for the user. Simulated Wallet stays LocalTerra. Bot = user-held keys, documented script, `authz` **only if** columbus-5 actually supports the needed grant (verify; do not assume), or a future vault (#617-style) that the user deposits into. 7. **Chart secrecy.** Default: path lives **on the operator device** (encrypted at rest). Do **not** persist drawings in indexer Postgres, Coolify logs, or public Git. If a later optional backup exists, it is end-to-end encrypted and out of scope for this spike’s implementation. 8. **Risk limits are mandatory in any GO design:** max notional / max base+quote volume **per period**, stop-loss (and optional take-profit), min remaining inventory, max taker slippage / `max_spread`, max open rungs, halt on pause / blacklist / `code_id_frozen`. A path that would exceed caps **must not** arm. 9. **Zig-zag / offload are first-class phases, not “draw sloppily.”** Retracement and cash-raise must be **labeled segments** with inventory rules (e.g. offload = sell into quote up to cap; retrace = buy back with reserved quote; rally = spend quote). Freehand noise must be **resampled / simplified** so it cannot become 10k dust orders. 10. **Do not break PriceChart invariants** ([#148](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/148) / [#151](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/151) / [#211](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/211) / [#336](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/336) / [#705](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/705)). Prefer a **separate overlay canvas** or a dedicated tool page over overloading `PriceChartLightweightCanvas`. 11. **Cognitive load ([#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489)).** If a later UI exists, it is a **dedicated** Advanced / operator flow — not a lecture on `/pool` or a default `/trade` mode. Do not add always-on essays. 12. **No farm/APR chrome.** Do not imply this is an incentive program. 13. **Do not change pool math, wrap fees, treasury, or factory listing** in this spike. 14. **LocalTerra** for any gas or keeper prototype. Do not report `SKIP (no LocalTerra)` without provisioning (`make setup-cloud-localterra`). 15. **Community-tax pairs:** extra-debit / buy split / launch guards (**T592**, **H608**, **R607**) apply to the **bot wallet** like any other trader. Path compiler must not assume 1:1 CW20 in/out. ## Relevant files | File | Role | |------|------| | [`frontend-dapp/src/components/charts/PriceChart.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/charts/PriceChart.tsx) | Chart shell, interval, a11y | | [`frontend-dapp/src/components/charts/PriceChartLightweightCanvas.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/charts/PriceChartLightweightCanvas.tsx) | lightweight-charts mount; **do not casually add drawing here** | | [`frontend-dapp/src/pages/TradePage.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/pages/TradePage.tsx) | `/trade` workspace | | [`frontend-dapp/src/components/trade/LimitOrderLadderPanel.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/trade/LimitOrderLadderPanel.tsx) | Closest visual “many prices at once” UX | | [`frontend-dapp/src/utils/limitOrderLadder.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/limitOrderLadder.ts) | Equal-rung preview; amounts | | [`smartcontracts/packages/dex-common/src/limit_placement.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/smartcontracts/packages/dex-common/src/limit_placement.rs) | Ladder expand, rung cap | | [`smartcontracts/contracts/pair/src/limit_placement.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/smartcontracts/contracts/pair/src/limit_placement.rs) | Batch execute | | [`smartcontracts/contracts/pair/src/orderbook.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/smartcontracts/contracts/pair/src/orderbook.rs) | Insert/match; `OrderStatus` | | [`docs/limit-orders.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/limit-orders.md) | Place/fill/gas | | [`docs/integrators.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/integrators.md) | Book HTTP, hints, **L21** | | [`indexer/src/indexer/parser.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/indexer/parser.rs) | Placement/fill attrs | | [`packages/localnet-trading-swarm/`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/tree/main/packages/localnet-trading-swarm) | LocalTerra bot guardrails (chain/env) | | [`skills/AGENTS_LIMIT_ORDER_BATCH_LADDER.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_LIMIT_ORDER_BATCH_LADDER.md) | Ladder invariants | | [`skills/AGENTS_ORDER_STATUS_QUERY.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_ORDER_STATUS_QUERY.md) | Bot custody oracle | | [`skills/AGENTS_FRONTEND_PRICE_CHART.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_PRICE_CHART.md) | Chart do-not-break list | | [`docs/security-model.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/security-model.md) | Tier 0 MM registration; EOA-only self-register | ## Recommended direction Evaluate **four** architectures against the same user story: pair + time horizon + polyline of `(t, price)` + optional **phase tags** (rally / retrace / offload / hold) + inventory + **risk envelope**. Recommend **one**, or **none** (document as integrator-only / NO-GO). ### A — Path compiler → existing batch/ladder (default candidate) 1. Resample the drawing to ≤ `max_batch_rungs` **monotonic-in-time** waypoints (Douglas–Peucker or bucket-by-interval). 2. Map each waypoint to **resting limits** on the appropriate side given **current mid vs target** (bid below, ask above). Mixed sides = **two** batches (do not silently invent mixed-side wasm). 3. **Offload / second rally:** reserve a **quote budget** (do not place 100% of inventory on the first up-leg). Retrace legs use reserved quote to bid; rally-2 spends it. 4. **Taker chase** (hybrid Market) only when the path is **behind** and maker resting cannot catch up — **hard** volume/period + slippage caps. Default **off** in v0. 5. Signing: **user bot** or documented script. dApp may **export** a path JSON for the bot; it must **not** broadcast. ### B — Off-chain keeper with schedule Same as A, plus a local process that **re-arms** on fills (`OrderStatus` + indexer `limit-fills`), respects time (`t` on the polyline), and **halts** on stop-loss / pause / blacklist. Chain guard like the swarm: refuse non-`localterra` until a later explicit mainnet flag. **No** keys in `indexer/`. ### C — New vault (only if A/B fail the non-custodial “set and forget” bar) Reuse **lessons** from #546 option E / #617 (vault owns orders, permissionless crank cannot withdraw). **Do not** start here. Path-following inside a vault is a **new** strategy object, not a grid spread. ### D — NO-GO / integrator-only If honest inventory value cannot be separated from tape-painting, or gas/inventory/tax make it unusable, publish a short integrator note (“compile your polyline to `PlaceLimitOrderBatch` + your own keeper”) and **do not** add official Draw UX. **Default assumption:** **A + optional B on LocalTerra**; **C blocked** on #546/#617 decisions; official retail Draw UX **not** in v0. **Zig-zag / offload (must be in the model, not a later surprise):** | Phase | Inventory intent | Typical actions | |-------|------------------|-----------------| | Rally | Spend quote (or bid inventory) toward higher target | Asks above rising path; optional capped taker buy only if under-inventory | | Retrace | Planned dip; **do not** dump the whole bag | Bids along the dip; **no** aggressive market sells that break the path | | Offload | Raise quote for rally-2 | Asks / capped sells; stop when quote reserve hits target | | Hold | Time passes; no trade | No new takes; optionally cancel-through if mid leaves a band | **Risk envelope (minimum fields for any GO spec):** - `max_volume_base` / `max_volume_quote` per rolling window (and per calendar day) - `stop_loss` (mark vs path or vs entry VWAP — spike must pick one and test both) - `max_taker_bps` / `max_spread` - `max_open_rungs` ≤ pair `max_batch_rungs` - `min_inventory_base` / `min_inventory_quote` floors - Halt: pair pause, wallet/token/pair blacklist, `code_id_frozen`, indexer/LCD outage ## Acceptance criteria - [ ] Written spike (comment or `docs/` draft **only if** product wants it public) covering: current gaps, grid-vs-path, A/B/C/D recommendation, gas ballpark (LocalTerra batch place vs N takes), secrecy model, tax/pause/blacklist. - [ ] Explicit **GO / NO-GO / integrator-only** for an official dApp Draw UX. - [ ] Market-integrity section: wash, spoof, self-match, leaked-path front-run, tape reconstruction; what the product **refuses** to do. - [ ] Path data model: polyline + phase tags + resampling cap; secret-at-rest; **no** indexer persistence in v0. - [ ] Risk envelope specified (volume/period, stop-loss, floors, halt conditions). - [ ] Zig-zag + offload + second rally described as phases with inventory rules (not “draw a W and hope”). - [ ] Comparison table vs #546 range grid (when to use which). - [ ] No wasm / dApp / indexer schema merged under this IID. - [ ] Follow-up implementation issues filed **only** if GO (split UI compiler vs keeper vs vault; do not sneak Draw into #617). ## Test plan (all paths) — for the spike and for any later prototype These tests are **required in the write-up** as a plan; implement them only on a follow-up IID (except paper/unit sketches of the compiler). ### Path compiler (pure) 1. Empty / single-point / two-point drawings. 2. Dense scribble → resampled ≤ rung cap; no 10k orders. 3. Non-monotonic time (user draws backwards) → reject or auto-sort with warning. 4. Price ≤ 0, below USD scale 0, or outside **L20** human band → reject. 5. Mixed-decimal pairs (UST1/USTR) — human draw → raw prices (**L20** / [#529](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/529)). 6. Phase tags: rally then retrace then offload then rally-2; reserved quote actually funds rally-2. 7. Path that would exceed `max_volume_*` **does not arm**. 8. Invert display (#524 / #543): drawing is in **display** space; compiler converts to factory token1/token0. ### Maker / ladder mapping 9. Target above mid → asks; below → bids; through-market rungs blocked by **client** post-only (same as ladder) unless a later spec explicitly allows crossing (on-chain still allows). 10. Two-sided initial map = **two** txs; one failing side does not leave a half-armed path without a documented rollback. 11. Rung count vs `max_batch_rungs` / hard cap 100. 12. Insert hints via indexer (#267); `resolved:false` → no fabricated id (**L14**). ### Time / keeper (LocalTerra only if prototyped) 13. Waypoints fire in time order; early waypoints do not dump inventory needed for later offload. 14. Fill → `OrderStatus` `Unknown` is **not** treated as “definitely filled” ([#505](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/505) / [#530](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/530)). 15. Indexer lag: keeper does not double-place. 16. Pair pause (**L6**): halt place/cancel/claim; resume policy documented. 17. Blacklist wallet/token/pair: halt. 18. `code_id_frozen` / F6: halt (#585). 19. Community-tax sell extra-debit / buy split: inventory accounting uses **net**, not gross. 20. Stop-loss: mark move vs threshold cancels remaining and **stops** taker chase. 21. Volume/period cap: further takes/places refused; existing rests optionally cancelled (pick one; test both if both exist). 22. Chain/env guard: refuse columbus-5 unless an explicit later flag exists (mirror swarm). ### UI (only if a later Draw overlay exists) 23. Dedicated overlay does not remount `PriceChart` on every pointer move; interval switch still one `createChart` ([#148](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/148)). 24. Secret: path not in network tab / indexer / share URL / Open Graph. 25. Disconnect: draw + preview without wallet; arm requires connect (parity with ladder #494). 26. A11y: canvas stays `aria-hidden`; path summary in text (waypoint count, horizon, caps). 27. Cognitive load: not default `/trade` mode. ### Docs / product 28. Copy never says “LP” or “guaranteed chart.” 29. Integrator note if D: polyline → batch msgs, no official bot. ## Test plan — attack, hack, and abuse vectors Treat these as **must-fail-closed**. A GO design that cannot mitigate a row is **NO-GO** for official UX (integrator-only at most). | ID | Vector | Expected | |----|--------|----------| | X1 | **Wash / self-match** — bot places bid and ask that fill each other, or two keys it controls | Detect same-owner / labeled bot wallets; **refuse** self-trade. Protocol pair may still match two EOAs; product must not **orchestrate** wash. | | X2 | **Spoof / flicker** — place path rungs and cancel before they can fill to paint the book | Rate-limit cancels; min time-in-force; cancel-storm halt. | | X3 | **Tape painting** — aggressive taker buys/sells whose only goal is candle shape | Default **maker-only**; taker chase off; volume/period caps; no “match the candle at any cost” mode. | | X4 | **Secret path leak** — path uploaded to indexer, analytics, Sentry, share link, screenshot OCR in support | v0: local-only; no telemetry of coordinates; docs warn that **trades still leak the path**. | | X5 | **Front-run leaked JSON** — operator exports path; adversary races waypoints | Treat export as a **secret**; optional commit-reveal out of v0 scope; do not put path on-chain. | | X6 | **MEV / sandwich** on taker chase | Public mempool is the given ([#299](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/299)); tight `max_spread` / `min_return`; prefer maker. | | X7 | **Grief takers** by inserting path-follow work in `execute_swap` | **Forbidden** (same as #546). | | X8 | **Key theft** via “dApp signs for you” / mnemonic in frontend | **Forbidden.** Simulated Wallet LocalTerra-only. | | X9 | **Authz over-grant** — `MsgExecuteContract` on pair allows `Cancel` + drain | If authz is proposed, grant must be **least privilege** (document actual columbus-5 modules). Prefer vault (#617) over god-grant. | | X10 | **Dust / gas grief** — scribble → 100 rungs × many pairs | Resample + global arm cap + LUNC preflight (ladder gates). | | X11 | **Crossing the book** to force a print | Client post-only unless explicit; never silently disable #297. | | X12 | **Blacklist / pause bypass** via bot retry | Halt; do not retry through `BlacklistGuardUnavailable` fail-open (SEC-I03). | | X13 | **Tax-token drain** — ignore extra-debit, over-spend allowance | Net accounting; allowance = gross sell debit. | | X14 | **Stop-loss hunt** — adversary pushes mid to trigger stop then reverts | Document; optional TWAP stop; not a contract change in this spike. | | X15 | **Inventory insolvency** — rally-2 with no quote after a full offload miss | Phase budgets; floors; dry-run before arm. | | X16 | **Indexer poison** — fake candles so the draw overlay mis-scales | Trust boundary already drops bad OHLC ([#226](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/226)); drawing uses sanitized series only. | | X17 | **LCD / progress botnet** from a chatty keeper | Reuse LCD-heavy 10 RPS / global 60 RPS ([#363](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/363) / [#694](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/694)); backoff; do not hammer `route/solve`. | | X18 | **Frozen code_id / F6** — bot keeps taking on a frozen hop | Halt; respect `route/solve` exclude (#585). | | X19 | **Tier 0 / MM registration abuse** — path bot self-registers as contract MM | EOA-only `Register`; Tier 0 is governance `RegisterWallet` only ([`docs/security-model.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/security-model.md)). | | X20 | **Social engineering** — “draw a chart” marketed as guaranteed price** | Copy + risk modal; no performance claims ([#138](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/138) NFA posture). | **No exploit PoCs** in the public issue beyond the table. If a later implementation needs a confidential security child, file it **confidential** and do not paste exploits here. ## Verification criteria The spike is **done** when: 1. A reviewer can answer **GO / NO-GO / integrator-only** from the write-up without reading chat logs. 2. Grid (#546) vs path (this issue) is unambiguous. 3. Wash / spoof / tape-paint are **explicit refusals**, not “later.” 4. Secrecy is defined as **local strategy**, with the caveat that **tape reconstructs the path**. 5. Zig-zag, offload, second rally, and risk limits appear in the data model, not as slogans. 6. Repo has **no** accidental Draw UI, wasm, or indexer migration merged under this IID. 7. LocalTerra gas notes (if any) used `make has-localterra` / `make setup-cloud-localterra` — no false “no chain in VM.” 8. #650 confidential material is **not** quoted. ## Owner type product / architecture (spike). Later: frontend (compiler overlay), backend (optional keeper package), contracts **only** if C. ## Priority P2 (`priority::medium`) — useful operator UX, not a launch blocker. Blocked on integrity sign-off before any implementation IID.
PlasticDigits commented 2026-09-01 08:14:36 +00:00 (Migrated from gitlab.com)

marked as related to #546

marked as related to #546
PlasticDigits commented 2026-09-01 08:14:37 +00:00 (Migrated from gitlab.com)

marked as related to #617

marked as related to #617
PlasticDigits commented 2026-09-01 08:14:38 +00:00 (Migrated from gitlab.com)

marked as related to #618

marked as related to #618
PlasticDigits commented 2026-09-01 08:14:38 +00:00 (Migrated from gitlab.com)

marked as related to #206

marked as related to #206
PlasticDigits commented 2026-09-01 08:14:39 +00:00 (Migrated from gitlab.com)

marked as related to #505

marked as related to #505
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#717
No description provided.