Skip to content
Open
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
90 changes: 60 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,36 @@ Python client for [Extended API](https://api.docs.extended.exchange/).

Minimum Python version required to use this library is `3.10` (you can use [pyenv](https://github.com/pyenv/pyenv) to manage your Python versions easily).

## Installation
## Installation

```shell
pip install x10-python-trading-starknet
```

Our SDK makes use of a [Rust Library](https://github.com/x10xchange/stark-crypto-wrapper) to accelerate signing and hashing of stark components. Currently this library supports the following environments

| | 3.9 | 3.10 | 3.11 | 3.12 |
| --------------------- | :---: | :---: | :---: | :---: |
| linux (glibc) - x86 | ✅ | ✅ | ✅ | ✅ |
| linux (musl) - x86 | ✅ | ✅ | ✅ | ✅ |
| linux (glibc) - arm64 | ✅ | ✅ | ✅ | ✅ |
| linux (musl) - arm64 | ✅ | ✅ | ✅ | ✅ |
| OSX - arm64 | ✅ | ✅ | ✅ | ✅ |
| windows - x86 | ✅ | ✅ | ✅ | ✅ |
| windows - arm64 | ⚠️ | ⚠️ | ⚠️ | ⚠️ |


| | 3.9 | 3.10 | 3.11 | 3.12 |
| --------------------- | :-: | :--: | :--: | :--: |
| linux (glibc) - x86 | ✅ | ✅ | ✅ | ✅ |
| linux (musl) - x86 | ✅ | ✅ | ✅ | ✅ |
| linux (glibc) - arm64 | ✅ | ✅ | ✅ | ✅ |
| linux (musl) - arm64 | ✅ | ✅ | ✅ | ✅ |
| OSX - arm64 | ✅ | ✅ | ✅ | ✅ |
| windows - x86 | ✅ | ✅ | ✅ | ✅ |
| windows - arm64 | ⚠️ | ⚠️ | ⚠️ | ⚠️ |

## TLDR:

Register at [Extended Testnet](https://testnet.extended.exchange/) by connecting a supported Ethereum Wallet.
Register at [Extended Testnet](https://starknet.sepolia.extended.exchange/perp) by connecting a supported Ethereum Wallet.

Navigate to [Api Management](https://starknet.sepolia.extended.exchange/api-management)

Navigate to [Api Management](https://testnet.extended.exchange/api-management)
1. Generate an API key
2. Show API details (You will need these details to initialise a trading client)

Instantiate a Trading Account

```python
```python
from x10.perpetual.accounts import StarkPerpetualAccount
api_key:str = "<api>" #from api-management
public_key:str = "<public>" #from api-management
Expand All @@ -50,6 +49,7 @@ stark_account = StarkPerpetualAccount(
```

Instantiate a Trading Client

```python
from x10.perpetual.accounts import StarkPerpetualAccount
from x10.perpetual.configuration import TESTNET_CONFIG
Expand All @@ -75,22 +75,28 @@ There is also a skeleton implementation of a [blocking client](examples/simple_c

The SDK currently provides functionality across three main modules

### Order Management Module
### Order Management Module

The order module is accessed using the `orders` property of the trading client

```python
trading_client.orders
```

TODO

### Account Module
### Account Module

The account module is accessed using the account property of the trading client

```python
trading_client.account
```

it exposes functionality related to managing an active trading account

#### `get_balance`

Fetches the balance of the user's account.

```python
Expand All @@ -100,6 +106,7 @@ Fetches the balance of the user's account.
```

#### `get_positions`

Fetches the current positions of the user's account. It can filter the positions based on market names and position side.

```python
Expand All @@ -109,6 +116,7 @@ Fetches the current positions of the user's account. It can filter the positions
```

returns a list of

```python
class PositionModel(X10BaseModel):
id: int
Expand All @@ -131,6 +139,7 @@ class PositionModel(X10BaseModel):
```

#### `get_positions_history`

Fetches the historical positions of the user's account. It can filter the positions based on market names and position side.

```python
Expand All @@ -139,7 +148,8 @@ Fetches the historical positions of the user's account. It can filter the positi
logger.info("Positions: %s", positions.to_pretty_json())
```

returns a list of
returns a list of

```python
class PositionHistoryModel(X10BaseModel):
id: int
Expand All @@ -157,14 +167,17 @@ class PositionHistoryModel(X10BaseModel):
```

#### `get_open_orders`

Fetches the open orders of the user's account. It can filter the orders based on market names, order type, and order side.

```python
open_orders = await trading_client.account.get_open_orders()
await trading_client.orders.mass_cancel(order_ids=[order.id for order in open_orders.data])
```

returns a list of
```python

```python
class OpenOrderModel(X10BaseModel):
id: int
account_id: int
Expand All @@ -186,6 +199,7 @@ class OpenOrderModel(X10BaseModel):
```

#### `get_orders_history`

Fetches the historical orders of the user's account. It can filter the orders based on market names, order type, and order side

```python
Expand All @@ -202,22 +216,27 @@ Fetches the historical orders of the user's account. It can filter the orders ba
order_side=OrderSide.BUY
)
```

returns a list of `OpenOrderModel`

#### `get_trades`

Fetches the trades of the user's account. It can filter the trades based on market names, trade side, and trade type.

```python

```

#### `get_fees`

Fetches the trading fees for the specified markets.

```python
pass
```

#### `get_leverage`

Fetches the leverage for the specified markets.

```python
Expand All @@ -226,57 +245,69 @@ Fetches the leverage for the specified markets.
```

returns a list of

```python
class AccountLeverage(X10BaseModel):
market: str
leverage: Decimal
```

#### `update_leverage`

Updates the leverage for a specific market.

```python
await trading_client.account.update_leverage(market_name="BTC-USD", leverage=Decimal("20.0"))
```

### Markets Info Module
### Markets Info Module

The markets module is accessed using the `markets_info` property of the trading client

```python
trading_client.markets_info
```

TODO

## SDK Environment configurations (Since version 0.3.0)

The SDK is controlled by an `EndpointConfiguration` object passed to the various methods and clients, several helpful instances are defined in [configuration.py](x10/perpetual/configuration.py)

### `MAINNET_CONFIG` vs `MAINNET_CONFIG_LEGACY_SIGNING_DOMAIN`
If you previously onboarded to our mainnet environment on `app.x10.exchange`, you should use the `MAINNET_CONFIG_LEGACY_SIGNING_DOMAIN` configuration bundle, as this will allow you to regenerate the same l2 keys as were created by our mainnet environment that was running on a legacy domain.

If you previously onboarded to our mainnet environment on `app.x10.exchange`, you should use the `MAINNET_CONFIG_LEGACY_SIGNING_DOMAIN` configuration bundle, as this will allow you to regenerate the same l2 keys as were created by our mainnet environment that was running on a legacy domain.

All new accounts should use the `MAINNET_CONFIG` configuration bundle.

## OnBoarding via SDK (Since Version 0.3.0)

To onboard to the Extended Exchange, the `UserClient` defined in [user_client.py](x10/perpetual/user_client/user_client.py) provides a way to use an Ethereum account to onboard onto the Extended Exchange.
To onboard to the Extended Exchange, the `UserClient` defined in [user_client.py](x10/perpetual/user_client/user_client.py) provides a way to use an Ethereum account to onboard onto the Extended Exchange.

### TLDR - Check out: [onboarding_example.py](examples/onboarding_example.py)

### `onboard(referral_code: Optional[str] = None) -> OnBoardedAccount`

This method handles the onboarding process of a user. It generates an L2 key pair from the user's L1 Ethereum account, creates an onboarding payload, and sends it to the onboarding endpoint. Upon successful onboarding, it returns an `OnBoardedAccount` object containing the default account and the L2 key pair.

### `onboard_subaccount(account_index: int, description: str | None = None) -> OnBoardedAccount`

This method onboards a subaccount associated with the user's main account. It allows you to specify an `account_index` and an optional description. If a subaccount with the given index already exists, it retrieves and returns that subaccount. Otherwise, it creates a new subaccount and returns an `OnBoardedAccount` object with the subaccount details and the associated L2 key pair.

### `get_accounts() -> List[OnBoardedAccount]`

This method retrieves all the accounts associated with the user. It returns a list of `OnBoardedAccount` objects, each containing the account details and corresponding L2 key pair.

### `create_account_api_key(account: AccountModel, description: str | None) -> str`

This method generates an API key for a specified account. You can provide an optional description for the API key. It returns the newly created API key as a string.

### `perform_l1_withdrawal() -> str`

This method initiates a withdrawal from Layer 2 (L2) to Layer 1 (L1) using the user's Ethereum account. It calls the underlying contract function to perform the withdrawal and returns a string, typically a transaction hash or status.

### `available_l1_withdrawal_balance() -> Decimal`

This method retrieves the available balance for L1 withdrawals. It calls the underlying contract function to fetch the withdrawal balance and returns the balance as a `Decimal` value.

### Process of Obtaining a Stark Key Pair from an Ethereum Account
Expand All @@ -294,9 +325,10 @@ The first step in the process is to generate a signing structure that will be si
##### a. Define the Signing Structure

The message to be signed includes:
1. account index,
2. the Ethereum wallet address,
3. and whether the terms of service (TOS) are accepted.

1. account index,
2. the Ethereum wallet address,
3. and whether the terms of service (TOS) are accepted.

in the function `get_key_derivation_struct_to_sign`, the signing structure is constructed as follows:

Expand Down Expand Up @@ -352,20 +384,19 @@ Once the signing structure is prepared, it is signed using the Ethereum private

The signature obtained from the Ethereum account is then used to derive the Stark private key. This is done by truncating the r value from the Ethereum signature and using it as the basis for the Stark private key:

```python
```python
def get_private_key_from_eth_signature(eth_signature: str) -> int:
eth_sig_truncated = re.sub("^0x", "", eth_signature)
r = eth_sig_truncated[:64]
return stark_sign.grind_key(int(r, 16), stark_sign.EC_ORDER)
```

`stark_sign.grind_key` is a function imported from [`vendor/starkware/crypto/signature/signature.py`](vendor/starkware/crypto/signature/signature.py)
`stark_sign.grind_key` is a function imported from [`vendor/starkware/crypto/signature/signature.py`](vendor/starkware/crypto/signature/signature.py)

## Depositing via SDK (Since Version 0.3.0)

There is a new function `deposit` available on the [`AccountModule`](x10/perpetual/trading_client/account_module.py) which provides the ability to directly deposit USDC into your StarkEx account. For more details check out `call_stark_perpetual_deposit` in [contract.py](x10/perpetual/contract.py)


## Contribution

Make sure you have [poetry](https://python-poetry.org/) installed.
Expand All @@ -378,8 +409,7 @@ Make sure you have [poetry](https://python-poetry.org/) installed.
- Run it: `python -m examples.placed_order_example`

Custom commands:

- `make format` - format code with `black`
- `make lint` - run `safety`, `black`, `flake8` and `mypy` checks
- `make test` - run tests


1 change: 1 addition & 0 deletions x10/perpetual/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class AssetOperationModel(X10BaseModel):
asset: int
time: int
account_id: int
chain: str
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new chain field is defined as required (not Optional), but the test fixtures in tests/fixtures/assets.py don't provide this field when instantiating AssetOperationModel. This will cause test failures. Either make the field optional with Optional[str] = None, or update all test fixtures and test expectations to include the chain field.

Suggested change
chain: str
chain: Optional[str] = None

Copilot uses AI. Check for mistakes.

# When operation type is `TRANSFER`
counterparty_account_id: Optional[int] = None
Expand Down
8 changes: 0 additions & 8 deletions x10/perpetual/order_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,6 @@ def __create_order_object(
if exact_only:
raise NotImplementedError("`exact_only` option is not supported yet")

Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of NotImplementedError guards for TPSL POSITION type and MARKET price type is not accompanied by any tests, documentation, or examples demonstrating these features now work. Consider adding test coverage for these newly enabled features, or at minimum document in the PR description that backend support has been verified. Without tests, there's a risk these features may not actually be supported by the backend API, which could lead to runtime errors for users.

Suggested change
# Guard against unsupported TP/SL POSITION type until backend support is confirmed.
if tp_sl_type == OrderTpslType.POSITION:
raise NotImplementedError("`OrderTpslType.POSITION` is not supported yet")
# Guard against unsupported MARKET price type for TP/SL triggers until backend support is confirmed.
for tpsl_param in (take_profit, stop_loss):
if tpsl_param is not None and tpsl_param.price_type == OrderPriceType.MARKET:
raise NotImplementedError("`OrderPriceType.MARKET` for TP/SL is not supported yet")

Copilot uses AI. Check for mistakes.
if tp_sl_type == OrderTpslType.POSITION:
raise NotImplementedError("`POSITION` TPSL type is not supported yet")

if (take_profit and take_profit.price_type == OrderPriceType.MARKET) or (
stop_loss and stop_loss.price_type == OrderPriceType.MARKET
):
raise NotImplementedError("TPSL `MARKET` price type is not supported yet")

if nonce is None:
nonce = generate_nonce()

Expand Down
Loading