Skip to content

feat(clob): full CLOB order-type support (time-in-force, stp, MARKET)#10

Merged
ngurmen merged 13 commits into
mainfrom
mng/order-types
Jun 17, 2026
Merged

feat(clob): full CLOB order-type support (time-in-force, stp, MARKET)#10
ngurmen merged 13 commits into
mainfrom
mng/order-types

Conversation

@ngurmen

@ngurmen ngurmen commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add core/order_types.py as the single home for order enums (Side, OrderType, TimeInForce GTC/FOK/IOC/PO, SelfTradePrevention, OrderStatus), alias-aware parsers, and validate_order_combo; read and write paths share these maps so the integer↔label mapping cannot drift.
  • add_order gains optional time_in_force (default GTC) and stp (default CANCEL_TAKER), replacing the hardcoded type2=0/stp=0.
  • Extend the batch and cancel/add paths (add_limit_order_list, cancel_add_list) to honour per-order order_type/time_in_force/stp; add add_order_list as a clearer alias.
  • Document that replace_order inherits the original order's type/TIF/stp (the contract's cancelReplaceOrder carries only price and quantity).
  • Verified the order-type model against Dexalot/contracts (ITradePairs.sol / TradePairs.sol): type2 and stp integer values confirmed exact; STOP/STOPLIMIT are reserved-but-unplaceable Type1 members (reads label them, writes omit them); relaxed client-side validation to the one rule the contract relies on (LIMIT requires a price); accept contract-spelling stp aliases.
  • Docs: new README "Order Types & Time-in-Force" section; CLAUDE.md non-obvious decisions; fix pre-existing broken doc-site links flagged by make docs-build.
  • Bump version 0.5.16 → 0.6.0.

Quality Gates

  • make test (1117 passed)
  • make lint (ruff clean) / make mypy (strict, clean)
  • clob.py and order_types.py at 100% coverage
  • make docs-build ("No issues found")

Behavioural changes

  • New time_in_force and stp parameters are optional and default to today's behaviour (GTC / CANCEL_TAKER / LIMIT); no existing call site changes.
  • Client-side validation enforces only the contract-faithful invariant: a LIMIT order requires a price. MARKET orders ignore time_in_force and any supplied price on-chain, so the SDK no longer pre-rejects MARKET+GTC/PO or a MARKET order with a price (an earlier revision rejected the default MARKET order — fixed here). Per-pair allowed types, Post-Only, self-trade and FOK rules are enforced on-chain and surface as reverts (T-IVOT-01, T-POOA-01, T-T2PO-01, T-FOKF-01, T-STPR-01).
  • A MARKET BUY skips the pre-flight balance check (notional unknown without a price) and relies on the contract; MARKET SELL and all LIMIT paths keep the check.
  • Order reads label type1 2/3 as STOP/STOPLIMIT (faithful to the contract Type1 enum); the write API accepts only MARKET/LIMIT, so the SDK never originates a stop order. Genuinely-unrecognised enum integers render as UNKNOWN(<n>).

ngurmen added 13 commits June 17, 2026 15:19
Phase 1 of full CLOB order-type support.

- New core/order_types.py: Side, OrderType (MARKET/LIMIT only),
  TimeInForce (GTC/FOK/IOC/PO), SelfTradePrevention, OrderStatus IntEnums
  with on-chain integer values; name<->int maps; alias-aware parsers
  (parse_side/parse_order_type/parse_time_in_force/parse_stp); and
  validate_order_combo() encoding the type1 x type2 matrix.
- clob.py read paths (_transform_order_from_api, _format_order_data) now
  route through the shared maps via _enum_to_name -> enum_int_to_name,
  eliminating four duplicated inline enum dicts that could drift.
- Remove the speculative 2:"STOP"/3:"STOPLIMIT" labels: those values do
  not exist in the on-chain OrderType enum and the NewOrder struct has no
  trigger-price field. Unknown integers now surface as "UNKNOWN(<n>)"
  instead of a fabricated label.

Pure additive refactor — no write-path behavior change. Full unit
coverage for the new module; make test/lint/mypy green.
Phase 2 of full CLOB order-type support.

- add_order gains optional time_in_force ("GTC" default / FOK / IOC / PO,
  aliases accepted) and stp ("CANCEL_TAKER" default / CANCEL_MAKER /
  CANCEL_BOTH / CANCEL_NONE) params. The previously hardcoded type2=0 /
  stp=0 in the NewOrder struct are now resolved from these.
- New _resolve_order_modifiers helper parses both modifiers and runs
  validate_order_combo, rejecting invalid (type1, type2, price) combos
  client-side before any transaction is sent.
- MARKET hardening: a MARKET order must be IOC/FOK and carry no price; the
  pre-flight balance check is skipped for a MARKET BUY (quote notional is
  unknown without a price) and retained everywhere else.
- Defaults preserve today's behavior (GTC, CANCEL_TAKER, LIMIT) — no
  existing call site changes.

Tests cover each TIF/alias, each stp mode, MARKET IOC/FOK balance
handling, and pre-flight rejection of invalid combos. make test/lint/mypy
green.
Phase 3 of full CLOB order-type support.

- _build_order_tuple now resolves per-order order_type (default LIMIT),
  time_in_force (default GTC) and stp (default CANCEL_TAKER) via the shared
  _resolve_order_modifiers helper, validating each combo, and also returns
  the order's balance requirement (None for a MARKET BUY, whose notional is
  unknown). _process_orders_for_batch aggregates that instead of computing
  price*amount inline.
- _process_replacement gains the same per-replacement order_type/
  time_in_force/stp handling and MARKET-BUY balance skip.
- Both paths previously hardcoded type1=1/type2=0/stp=0.
- Add add_order_list as a clearer alias for add_limit_order_list, since the
  batch path is no longer limit-only; docstrings document the new keys.

Defaults preserve today's behavior. Tests cover mixed-type batches,
per-order TIF/stp, batch-wide rejection of invalid combos, and TIF/stp in
cancel_add_list. make test/lint/mypy green.
Phase 4 of full CLOB order-type support.

- replace_order docstring now states that cancelReplaceOrder carries only a
  new price and quantity, so the replacement inherits the original order's
  type1/time_in_force/stp; callers wanting to change those should use
  cancel_add_list. No new params (the contract cannot carry them).
- Add tests confirming the order-type revert codes (T-POOA-01, T-T2PO-01,
  T-STPR-01, T-FOKF-01) are present in errors.json, that
  _parse_revert_reason maps a raw revert to "<code>: <description>", and
  that sanitize_error_message preserves the code.

No production logic change beyond the docstring — the revert mapping
already existed via _parse_revert_reason. make test/lint/mypy green.
Phase 5 of full CLOB order-type support.

- README: new "Order Types & Time-in-Force" section (order_type /
  time_in_force / stp params, combo-validity table, MARKET/PO/stp examples,
  add_order_list alias, replace inheritance note); updated add_order
  signature in the affected-methods list; states STOP/STOPLIMIT are not
  supported.
- CLAUDE.md: five new Non-Obvious Decisions (order_types.py as the single
  enum home; STOP/STOPLIMIT absent on-chain; TIF/stp defaults + client-side
  combo validation; MARKET BUY balance skip; replace cannot change
  type/TIF/stp) and an order_types.py row in the Key File Reference.

No new config/env var, so env.example is unchanged. Full suite (1110),
lint, and mypy all green.
Close the six coverage gaps introduced by the order-type work, all
error/edge branches:

- invalid stp rejected in add_order (_resolve_order_modifiers stp path)
- MARKET BUY in a batch (price 0, balance skipped) and invalid per-order
  order_type rejected (_build_order_tuple)
- cancel_add_list MARKET BUY, plus invalid order_type / time_in_force
  rejection (_process_replacement)

clob.py coverage 99% -> 100%. make test/lint/mypy green.
Pre-existing link issues surfaced by `make docs-build` (unrelated to the
order-type work):

- python-sdk-user-guide.md: escape `Result\[T\]` heading so Markdown stops
  parsing `[T]` as an undefined reference link; repoint the README link to
  the GitHub URL (../README.md is outside docs_dir).
- sdk-caching.md: repoint the stale monorepo README paths to the Python and
  TypeScript GitHub READMEs.
- websocket.md: link to the in-site rest-api.md instead of the non-existent
  /en/apiv2/RestApi.md.

`make docs-build` now reports "No issues found".
Synced via scripts/version_manager.py across VERSION, pyproject.toml,
src/dexalot_sdk/__init__.py, and uv.lock. Minor bump for the new CLOB
order-type surface (time_in_force / stp / MARKET hardening).
Verified against Dexalot/contracts ITradePairs.sol: the contract Type1
enum is {MARKET, LIMIT, STOP, STOPLIMIT} — STOP/STOPLIMIT are real enum
members (reserved/unused, no trigger-price field, never enabled per pair),
not fabricated labels. An earlier change dropped them from the read map on
the mistaken premise that they don't exist on-chain.

Read paths now label type1 2/3 as STOP/STOPLIMIT again (faithful to the
contract enum), while the write-side OrderType enum still omits them so the
SDK never originates a stop order. Tests/docs updated in a later commit.
Verified against Dexalot/contracts TradePairs.sol: the contract does NOT
reject MARKET+GTC/PO or a MARKET order carrying a price — it ignores type2
and price for MARKET (no revert). The previous client-side rules ("MARKET
must be IOC/FOK", "MARKET must not specify a price") were stricter than the
chain and, because time_in_force defaults to GTC, rejected the natural
default MARKET order.

validate_order_combo now enforces only the contract-faithful invariant —
LIMIT requires a price — and defers everything else (per-pair allowed
types, Post-Only, self-trade, FOK fill) to on-chain reverts. Tests/docs
updated in a later commit.
The contract STP enum spells modes without underscores (CANCELTAKER,
CANCELMAKER, CANCELBOTH) and uses bare NONE. Accept those spellings as
input aliases alongside the readable CANCEL_* canonical names, so values
copied from the contract parse directly. Also note in the enum docstring
that the integer values are now confirmed against ITradePairs.sol.
Follow-up to the three contract-reconciliation fixes.

Tests:
- read map: assert type1 2/3 label as STOP/STOPLIMIT; write enum still
  omits them; genuinely-unknown ints -> UNKNOWN(<n>).
- validate_order_combo: MARKET is permissive (GTC/PO/price all accepted);
  only LIMIT-requires-price enforced (single + batch).
- add_order: default MARKET (GTC) succeeds; unknown time_in_force/stp still
  rejected pre-send.
- parse_stp: accept contract spellings CANCELTAKER/CANCELMAKER/CANCELBOTH.

Docs (README + CLAUDE.md):
- STOP/STOPLIMIT are reserved-but-unplaceable contract enum members (not
  "do not exist"); reads label them, writes omit them.
- Replace the IOC/FOK-only MARKET combo table with the actual rule (LIMIT
  requires price; MARKET ignores type2/price; PO/allowed-type/FOK/STP
  enforced on-chain). Note stp contract-spelling aliases.

clob.py and order_types.py at 100% coverage; full suite (1117), lint,
mypy, and docs-build all green.
@ngurmen ngurmen requested review from hsyndeniz and ilkerulutas June 17, 2026 13:21
@ngurmen ngurmen merged commit c60546e into main Jun 17, 2026
3 checks passed
@ngurmen ngurmen deleted the mng/order-types branch June 17, 2026 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants