Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ site/
.env*
!.env.example
!env.example

# Temporary planning docs (not for commit)
docs/*-plan.md
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ Unit tests in `tests/unit/` have no external dependencies. Integration tests in
- **Pairs missing display decimals are dropped at ingest**: `_store_clob_pairs` excludes any pair whose API record lacks `base_display_decimals` or `quote_display_decimals` and logs a WARNING. Downstream callers see "pair not found." Display decimals are contractual — defaulting them would mask the contract's `T-TMDQ-01` rejection downstream.
- **`min_trade_amount` / `max_trade_amount` are enforced client-side**: quote-token-denominated bounds checked by `_check_trade_amount_bounds` inside `_normalize_order_amounts` so all four CLOB write paths gain the check. A bound of `0` means "no bound" (some pairs legitimately omit). Stored as `Decimal` so notional comparison is exact.
- **`validate_positive_number` accepts int/float/Decimal/numeric-string**: renamed from `validate_positive_float` (alias kept for one release). Callers wanting precision-exact arithmetic should pass `Decimal('2933')` or `'2933.5'` directly; floats are still accepted and routed through `Decimal(str(value))` internally.
- **Order enums have one home — `core/order_types.py`**: `Side`, `OrderType` (`MARKET`/`LIMIT` only), `TimeInForce` (`GTC`/`FOK`/`IOC`/`PO`), `SelfTradePrevention`, `OrderStatus` IntEnums with on-chain integer values, plus alias-aware parsers and `validate_order_combo`. Read paths (`_format_order_data`, `_transform_order_from_api`) and write paths share these maps via `enum_int_to_name` / `_resolve_order_modifiers`, so the integer↔label mapping cannot drift. Canonical output labels are the on-chain names (`PO`, not `POST_ONLY`); aliases are accepted on input only.
- **STOP/STOPLIMIT: reserved on-chain, unplaceable, but read-labelled**: the contract `Type1` enum is `{MARKET, LIMIT, STOP, STOPLIMIT}` (verified against `ITradePairs.sol`), but STOP/STOPLIMIT are unused — `NewOrder` has no trigger-price field and no pair enables them. So the write-side `OrderType` enum is `{MARKET, LIMIT}` (the SDK never originates a stop order) while the read map `ORDER_TYPE_NAMES` includes all four contract names so reads stay faithful. Genuinely-unknown integers still map to `"UNKNOWN(<n>)"`.
- **`time_in_force` / `stp` default to today's behavior**: `add_order` (and per-order dicts in `add_limit_order_list` / `cancel_add_list`) accept optional `time_in_force` (default `GTC`) and `stp` (default `CANCEL_TAKER`), replacing the previously hardcoded `type2=0` / `stp=0`. `validate_order_combo` enforces only the one contract-faithful rule — **LIMIT requires a price** — verified against `TradePairs.sol`: the contract ignores `type2`/price for MARKET (no revert), and PO / per-pair-allowed-type / self-trade / FOK rules are enforced on-chain (`T-T2PO-01`, `T-POOA-01`, `T-IVOT-01`, `T-FOKF-01`, `T-STPR-01`). Do **not** re-add a MARKET-must-be-IOC/FOK check — it is stricter than the contract and rejected the default (GTC) MARKET order. `type2`/`stp` integer values are confirmed against the contract enums.
- **MARKET BUY skips the pre-flight balance check**: without a price the quote-token notional is unknown, so `add_order` / `_build_order_tuple` / `_process_replacement` skip the balance check for a MARKET BUY and let the contract enforce it. MARKET SELL (base amount known) and all LIMIT paths keep the check.
- **`replace_order` cannot change order type/TIF/stp**: `cancelReplaceOrder` carries only a new price and quantity, so the replacement inherits the original `type1`/`type2`/`stp`. To change those, cancel and re-place (e.g. `cancel_add_list`). The method intentionally exposes no `time_in_force`/`stp` params.

---

Expand Down Expand Up @@ -162,6 +167,7 @@ All paths relative to `src/dexalot_sdk/`.
| Entry point | `core/client.py` |
| Config | `core/config.py` |
| Base client | `core/base.py` |
| Order-type enums/validation | `core/order_types.py` |
| Caching | `utils/cache.py` |
| Result type | `utils/result.py` |
| Retry | `utils/retry.py` |
Expand Down
71 changes: 69 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,73 @@ async def place_order_safely(client, pair, side, amount, price):

See `examples/error_handling.py` for comprehensive error handling patterns.

## Order Types & Time-in-Force

CLOB orders are described by three on-chain fields, all exposed through optional
parameters that default to today's behavior:

- **`order_type`** (`type1`) — `"LIMIT"` (default) or `"MARKET"`.
- **`time_in_force`** (`type2`) — `"GTC"` (default), `"FOK"`, `"IOC"`, or `"PO"`
(Post-Only). Aliases such as `"POST_ONLY"`, `"FILL_OR_KILL"`,
`"IMMEDIATE_OR_CANCEL"` are accepted.
- **`stp`** (self-trade prevention) — `"CANCEL_TAKER"` (default),
`"CANCEL_MAKER"`, `"CANCEL_BOTH"`, or `"CANCEL_NONE"`. The contract spellings
(`"CANCELTAKER"`, `"CANCELMAKER"`, `"CANCELBOTH"`, `"NONE"`) are also accepted.

> Stop / stop-limit orders cannot be **placed**: although the contract `Type1`
> enum reserves `STOP`/`STOPLIMIT`, they are unused on-chain (the order struct
> has no trigger-price field and no pair enables them), so `order_type` accepts
> only `MARKET`/`LIMIT`. Order *reads* still label `type1` 2/3 as
> `STOP`/`STOPLIMIT` for fidelity with the contract enum.

### Validation

The SDK only pre-validates the one rule the contract itself relies on — **a
`LIMIT` order requires a price** — and otherwise defers to the contract, which
matches its real behavior:

- **`MARKET`** ignores `time_in_force` and any supplied price on-chain (no
revert), so the SDK does not constrain them. A MARKET order is typically
filled immediately.
- **`PO`** (Post-Only) is maker-only; a Post-Only order that would take reverts
`T-T2PO-01`, and a pair may require all orders be Post-Only (`T-POOA-01`).
- A pair may **disable** specific order types (e.g. MARKET) — those reverts
surface as `T-IVOT-01`. Other relevant reverts: `T-FOKF-01` (unfillable FOK),
`T-STPR-01` (self-trade prevented).

### Examples

```python
# Limit GTC (default) — unchanged from before
await client.add_order("AVAX/USDC", "BUY", 1.0, 25.0)

# Limit, Immediate-or-Cancel
await client.add_order("AVAX/USDC", "BUY", 1.0, 25.0, time_in_force="IOC")

# Post-Only (maker-only); reverts T-T2PO-01 if it would take
await client.add_order("AVAX/USDC", "SELL", 1.0, 25.0, time_in_force="PO")

# Market BUY (no price). The pre-flight balance check is skipped for a
# MARKET BUY since the notional is unknown without a price.
await client.add_order("AVAX/USDC", "BUY", 1.0, None, order_type="MARKET")

# Self-trade prevention: cancel the resting (maker) order on a self-match
await client.add_order("AVAX/USDC", "BUY", 1.0, 25.0, stp="CANCEL_MAKER")

# Batch with mixed types (add_order_list is an alias for add_limit_order_list)
await client.add_order_list([
{"pair": "AVAX/USDC", "side": "BUY", "amount": 1.0, "price": 24.0,
"time_in_force": "PO"},
{"pair": "AVAX/USDC", "side": "SELL", "amount": 1.0,
"order_type": "MARKET", "time_in_force": "IOC"},
])
```

**Replacing orders:** `replace_order` uses the contract's `cancelReplaceOrder`,
which carries only a new price and quantity — the replacement **inherits** the
original order's `order_type`, `time_in_force`, and `stp`. To change those,
cancel and place a new order (e.g. via `cancel_add_list`).

## Transaction Receipt Handling

All state-changing operations (placing orders, deposits, withdrawals, etc.) now support a `wait_for_receipt` parameter that controls whether the SDK waits for blockchain transaction confirmation before returning.
Expand Down Expand Up @@ -889,8 +956,8 @@ Use `wait_for_receipt=False` when:
All state-changing methods support `wait_for_receipt`:

**CLOB Operations:**
- `add_order(pair, side, amount, price, order_type="LIMIT", client_order_id=None, wait_for_receipt=True)`
- `add_limit_order_list(orders, wait_for_receipt=True)`
- `add_order(pair, side, amount, price, order_type="LIMIT", client_order_id=None, time_in_force="GTC", stp="CANCEL_TAKER", wait_for_receipt=True)`
- `add_limit_order_list(orders, wait_for_receipt=True)` (alias: `add_order_list`)
- `cancel_order(order_id, wait_for_receipt=True)`
- `cancel_order_by_client_id(client_order_id, wait_for_receipt=True)`
- `cancel_list_orders(order_ids, wait_for_receipt=True)`
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.16
0.6.0
4 changes: 2 additions & 2 deletions docs/python-sdk-user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Prefer passing a pre-built `eth_account.Account` object to the constructor inste

## Core concepts

### Result[T] — result-first operational API
### Result\[T\] — result-first operational API

Async operational methods return a `Result` object. Expected failures such as network errors, validation errors, and contract reverts are returned as `Result.fail(...)`. Some configuration or programmer errors can still raise immediately, so always check `.success` before accessing `.data` on Result-returning calls:

Expand Down Expand Up @@ -176,7 +176,7 @@ result = await client.add_order(
)
```

> **Precision note**: `amount` and `price` accept `int`, `float`, `Decimal`, and numeric `str`. The SDK converts to wei via `Decimal`-backed arithmetic — never float-multiplication — so exact values like `2933.0` round-trip cleanly. Precision exceeding the pair's `base_display_decimals` / `quote_display_decimals` is **rejected** rather than silently rounded; pass `Decimal('2.0')` or round explicitly. See the "Amount Precision and Display Decimals" section of [README.md](../README.md) for the full contract.
> **Precision note**: `amount` and `price` accept `int`, `float`, `Decimal`, and numeric `str`. The SDK converts to wei via `Decimal`-backed arithmetic — never float-multiplication — so exact values like `2933.0` round-trip cleanly. Precision exceeding the pair's `base_display_decimals` / `quote_display_decimals` is **rejected** rather than silently rounded; pass `Decimal('2.0')` or round explicitly. See the "Amount Precision and Display Decimals" section of the [README](https://github.com/Dexalot/dexalot-sdk-python/blob/main/README.md) for the full contract.

### Cancel an order

Expand Down
4 changes: 2 additions & 2 deletions docs/sdk-caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,5 +388,5 @@ The cache has a maximum size of 256 entries per level. If you're concerned about

## See Also

- [Python SDK README](../python/dexalot-sdk/README.md) - Python SDK main documentation
- [TypeScript SDK README](../typescript/dexalot-sdk/README.md) - TypeScript SDK main documentation
- [Python SDK README](https://github.com/Dexalot/dexalot-sdk-python/blob/main/README.md) - Python SDK main documentation
- [TypeScript SDK README](https://github.com/Dexalot/dexalot-sdk-typescript/blob/main/README.md) - TypeScript SDK main documentation
2 changes: 1 addition & 1 deletion docs/websocket.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Example trader event subscribe message :
{"type":"tradereventsubscribe", "signature":"0xXXXXXXXXXXXXXXXXXX:0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}
```
To generate signature you can use the sample code provided in RestApi Signed Endpoints.
* [Rest Api](/en/apiv2/RestApi.md)
* [Rest Api](rest-api.md)

Sample code unsubscribe:
```ts
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ license = "MIT"
license-files = [
"LICENSE.txt"
]
version = "0.5.16"
version = "0.6.0"
description = "Dexalot Python SDK - Core library for Dexalot interaction"
readme = "README.md"
requires-python = ">=3.12,<3.15"
Expand Down
2 changes: 1 addition & 1 deletion src/dexalot_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
secrets_vault_set,
)

__version__ = "0.5.16"
__version__ = "0.6.0"


def get_version() -> str:
Expand Down
Loading
Loading