> For the complete documentation index, see [llms.txt](https://sodex.com/documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sodex.com/documentation/for-developers/sdks/python-sdk-guide.md).

# Python SDK Guide

The official Python SDK provides Spot, Perps, user-flow, signing, and WebSocket clients. It handles canonical request serialization, EIP-712 signing, signature prefixes, and in-process nonce coordination.

GitHub: [sodex-python-sdk-public](https://github.com/sodex-tech/sodex-python-sdk-public)

## Requirements

* Python 3.9 or later

## Installation

Install the reviewed `0.2.1` source revision directly from GitHub. This does not require or imply a PyPI release:

```bash
python -m pip install "git+https://github.com/sodex-tech/sodex-python-sdk-public.git@732ac02c0297e2b8ce65d23780cdab246c659d63"
```

## Configuration

`Client.from_env()` uses mainnet by default. Select testnet explicitly when testing signed writes:

```bash
export SODEX_NETWORK=testnet
export SODEX_PRIVATE_KEY=0x...
```

For a registered API key, the private key belongs to the API key while the address and name identify its master account registration:

```bash
export SODEX_ACCOUNT_ADDRESS=0x...
export SODEX_API_KEY_NAME=my-bot
```

## Public market data

Public reads do not require a private key:

```python
from sodex.client import Client

client = Client.from_env()
print(client.perps_tickers("BTC-USD")[0])
```

## Signed trading

The high-level client resolves the primary account and symbol identifiers, signs the request, and returns a typed receipt:

```python
from decimal import Decimal

from sodex.client import Client

client = Client.from_env()
receipt = client.perps_order(
    "BTC-USD",
    True,
    Decimal("0.001"),
    limit_price=Decimal("50000"),
)

print(receipt.order_id)
```

The REST response confirms acceptance, not execution. Save `order_id` and use an account WebSocket subscription to track order updates and fills.

## Account-level actions

API-key management and builder-fee approval must be signed by the master wallet. `approve_builder_fee()` applies the fee cap to both Spot and Perps:

```python
import os

from sodex.client import Client

master = Client.from_private_key(
    os.environ["SODEX_PRIVATE_KEY"],
    testnet=True,
)
master.approve_builder_fee(
    builder_id=int(os.environ["SODEX_BUILDER_ID"]),
    max_fee_rate=int(os.environ["SODEX_BUILDER_FEE_RATE"]),
)
```

Builder fee rates use tenths of a basis point: `10` is 1 bp of the order notional, charged to the user and sent to the builder. The approval range is `0` through `2000`; use `0` to clear the approval. Spot orders are capped at `2000` (2%) and Perps orders at `200` (0.2%). A user may have at most 10 builder approvals. See [Builder Codes in Trading](/documentation/for-developers/developers/trading/builder-codes-in-trading.md).

## Builder-attributed orders

After the master wallet approves the builder on the target engine, include the builder in the signed order:

```python
from decimal import Decimal
from sodex.client import BuilderParams, Client

client = Client.from_env()  # Set the network and registered API key explicitly.
receipt = client.perps_order(
    "BTC-USD",
    True,
    Decimal("0.001"),
    limit_price=Decimal("50000"),
    builder=BuilderParams(id=9, fee=20),
)
print(receipt.order_id)
```

Use your approved builder ID and an authorized rate; `20` means 2 bp. Spot supports batch attribution; Perps supports a batch default and per-order `RawOrder(builder=...)` overrides. Omitting the builder leaves it out of the signed payload. Approval on Spot and Perps is not an atomic update: inspect both engines after an ambiguous or partial result.

## Discover funding routes

This example performs public reads only and explicitly selects mainnet:

```python
from sodex.client import Client, Config

client = Client(Config(
    base_url="https://mainnet-gw.sodex.dev",
    chain_id=286623,
))
asset, chain = client.get_transfer_route("USDC", "BASE_ETH")
print(asset.coin, asset.decimals, asset.asset_id)
for name, method in (("custody", chain.custody), ("bridge", chain.bridge)):
    if method is None:
        continue
    print(name, "deposit:", method.allow_deposit, "withdraw:", method.allow_withdraw)
    if method.allow_withdraw:
        fee = method.withdraw_fee
        print("minimum:", method.min_withdraw_amount,
              "fee:", fee if fee not in (None, "") else "unknown")
```

The compatibility argument `get_transfer_configs(coin=...)` sends the REST filter `name`. Use `chain.withdrawal_method("custody")` or `"bridge"` to select and validate a withdrawal route. `custody_available` and `bridge_available` report **deposit** availability; they must not enable a withdrawal button. Use the selected method's minimum and fee fields instead of the legacy flattened chain minimums. Engine asset ID `0` is valid; nullable engine metadata must not be interpreted as a registered engine asset.

## Account stream readiness

This example observes a configured account and places no orders. Set `SODEX_NETWORK` and `SODEX_ACCOUNT_ADDRESS` before running it:

```python
import os
from sodex.client import Client as RestClient
from sodex.ws import Client as WebSocketClient

rest = RestClient.from_env()
ws = WebSocketClient.from_base_url(rest.base_url, engine="perps")
subscription = ws.subscribe_account(
    os.environ["SODEX_ACCOUNT_ADDRESS"],
    symbols=["BTC-USD"],
    on_order_update=lambda order: print(order.order_id, order.status),
    on_trade=lambda fill: print(fill.order_id, fill.trade_id, fill.price),
)
try:
    ws.connect()
    subscription.wait_ready(timeout=10)
    input("Subscription acknowledged; press Enter to close.\n")
finally:
    ws.close()
```

`connect()` starts background work and returns immediately. `wait_ready()` waits for subscription acknowledgements and raises on rejection or timeout; it does not establish that account state has been fully reconciled. Never call it from a reader callback. Use one WebSocket client per account owner. After reconnecting, readiness must be re-established and REST state/history must be reconciled; automatic resubscription does not replay missed fills.

## Funding completion and recovery

`wait_for_deposit()` waits for indexing, not destination credit. `wait_for_withdrawal()` returns recognized terminal records, including failures. Engine transfer calls return acceptance; `deposit_evm_to_engine()` waits for EVM execution, not engine settlement. Persist identifiers and inspect the relevant outcome before starting a dependent transfer.

## Data conventions

* Monetary values use `Decimal`.
* `uint64` identifiers, nonces, and timestamps use Python `int`.
* Spot uses the `spot` EIP-712 domain; Perps uses `futures`; cross-engine user actions use `universal`.
* Reconcile remote state before retrying an ambiguous signed write with a new nonce.

## More examples

See the [GitHub examples](https://github.com/sodex-tech/sodex-python-sdk-public/tree/main/examples) for account queries, orders, WebSocket streams, deposits, transfers, withdrawals, API-key management, and builder-fee approval.

## Related documentation

* [REST API](/documentation/for-developers/api-reference/trading-api/rest-v1.md)
* [WebSocket Streams](/documentation/for-developers/api-reference/trading-api/websocket-v1.md)
* [Rate Limits](/documentation/for-developers/developers/trading/api-rate-limits.md)
