From cae344a23b7da4eba36c010db8e84ff645b6102d Mon Sep 17 00:00:00 2001 From: alex101xela Date: Mon, 25 May 2026 14:10:32 +0400 Subject: [PATCH 1/5] Migrate signing code to a separate module --- examples/cases/advanced/load_testing.py | 2 +- .../create_limit_order_with_builder.py | 2 +- .../createorder/create_conditional_order.py | 2 +- .../cases/createorder/create_limit_order.py | 2 +- .../cases/createorder/create_market_order.py | 2 +- .../create_limit_order_with_partial_tpsl.py | 2 +- .../create_limit_order_with_position_tpsl.py | 2 +- .../cases/tpsl/create_partial_tpsl_order.py | 2 +- .../cases/tpsl/create_position_tpsl_order.py | 2 +- .../test_limit_order_object_settlement.py | 4 ++-- .../test_conditional_order_object.py | 2 +- .../order_object/test_limit_order_object.py | 10 +++++----- .../order_object/test_market_order_object.py | 4 ++-- .../order_object/test_order_object_attrs.py | 4 ++-- .../order_object/test_tpsl_order_object.py | 4 ++-- tests/perpetual/test_transfer_object.py | 2 +- x10/clients/rest/modules/account_module.py | 4 ++-- x10/clients/rest/modules/vault_module.py | 6 +++--- x10/clients/rest/rest_api_client.py | 2 +- .../simple_client/simple_trading_client.py | 2 +- x10/signing/__init__.py | 0 .../limit_order_object_settlement.py | 10 +++++----- x10/{perpetual => signing}/order_object.py | 2 +- .../order_object_settlement.py | 17 ++++++----------- x10/{perpetual => signing}/transfer_object.py | 15 ++++----------- x10/{perpetual => signing}/withdrawal_object.py | 13 +++---------- x10/utils/date.py | 13 ++++++++++++- 27 files changed, 62 insertions(+), 70 deletions(-) create mode 100644 x10/signing/__init__.py rename x10/{perpetual => signing}/limit_order_object_settlement.py (88%) rename x10/{perpetual => signing}/order_object.py (99%) rename x10/{perpetual => signing}/order_object_settlement.py (92%) rename x10/{perpetual => signing}/transfer_object.py (86%) rename x10/{perpetual => signing}/withdrawal_object.py (85%) diff --git a/examples/cases/advanced/load_testing.py b/examples/cases/advanced/load_testing.py index 29b724d..e7a8b2f 100644 --- a/examples/cases/advanced/load_testing.py +++ b/examples/cases/advanced/load_testing.py @@ -9,7 +9,7 @@ from x10.clients.stream import StreamClient from x10.models.market import MarketModel from x10.models.order import OrderSide -from x10.perpetual.order_object import create_order_object +from x10.signing.order_object import create_order_object LOGGER = logging.getLogger() MARKET_NAME = BTC_USD_MARKET diff --git a/examples/cases/buildercode/create_limit_order_with_builder.py b/examples/cases/buildercode/create_limit_order_with_builder.py index f0d4bb8..a01fe7b 100644 --- a/examples/cases/buildercode/create_limit_order_with_builder.py +++ b/examples/cases/buildercode/create_limit_order_with_builder.py @@ -9,7 +9,7 @@ init_env, ) from x10.models.order import OrderSide, TimeInForce -from x10.perpetual.order_object import create_order_object +from x10.signing.order_object import create_order_object LOGGER = logging.getLogger() MARKET_NAME = BTC_USD_MARKET diff --git a/examples/cases/createorder/create_conditional_order.py b/examples/cases/createorder/create_conditional_order.py index 19e124b..f61c641 100644 --- a/examples/cases/createorder/create_conditional_order.py +++ b/examples/cases/createorder/create_conditional_order.py @@ -15,7 +15,7 @@ OrderType, TimeInForce, ) -from x10.perpetual.order_object import OrderConditionalTriggerParam, create_order_object +from x10.signing.order_object import OrderConditionalTriggerParam, create_order_object LOGGER = logging.getLogger() MARKET_NAME = BTC_USD_MARKET diff --git a/examples/cases/createorder/create_limit_order.py b/examples/cases/createorder/create_limit_order.py index 62a9793..28feab8 100644 --- a/examples/cases/createorder/create_limit_order.py +++ b/examples/cases/createorder/create_limit_order.py @@ -8,7 +8,7 @@ get_adjust_price_by_pct, ) from x10.models.order import OrderSide, TimeInForce -from x10.perpetual.order_object import create_order_object +from x10.signing.order_object import create_order_object LOGGER = logging.getLogger() MARKET_NAME = BTC_USD_MARKET diff --git a/examples/cases/createorder/create_market_order.py b/examples/cases/createorder/create_market_order.py index 0f420ae..bc4fffc 100644 --- a/examples/cases/createorder/create_market_order.py +++ b/examples/cases/createorder/create_market_order.py @@ -3,7 +3,7 @@ from examples.utils import BTC_USD_MARKET, create_rest_client from x10.models.order import OrderSide, OrderType, TimeInForce -from x10.perpetual.order_object import create_order_object +from x10.signing.order_object import create_order_object from x10.utils.order import get_price_with_slippage LOGGER = logging.getLogger() diff --git a/examples/cases/tpsl/create_limit_order_with_partial_tpsl.py b/examples/cases/tpsl/create_limit_order_with_partial_tpsl.py index d20dd21..a212dee 100644 --- a/examples/cases/tpsl/create_limit_order_with_partial_tpsl.py +++ b/examples/cases/tpsl/create_limit_order_with_partial_tpsl.py @@ -15,7 +15,7 @@ OrderTriggerPriceType, TimeInForce, ) -from x10.perpetual.order_object import OrderTpslTriggerParam, create_order_object +from x10.signing.order_object import OrderTpslTriggerParam, create_order_object LOGGER = logging.getLogger() MARKET_NAME = BTC_USD_MARKET diff --git a/examples/cases/tpsl/create_limit_order_with_position_tpsl.py b/examples/cases/tpsl/create_limit_order_with_position_tpsl.py index e86de8d..616486a 100644 --- a/examples/cases/tpsl/create_limit_order_with_position_tpsl.py +++ b/examples/cases/tpsl/create_limit_order_with_position_tpsl.py @@ -14,7 +14,7 @@ OrderTriggerPriceType, TimeInForce, ) -from x10.perpetual.order_object import OrderTpslTriggerParam, create_order_object +from x10.signing.order_object import OrderTpslTriggerParam, create_order_object LOGGER = logging.getLogger() MARKET_NAME = BTC_USD_MARKET diff --git a/examples/cases/tpsl/create_partial_tpsl_order.py b/examples/cases/tpsl/create_partial_tpsl_order.py index 7576ea4..3856ac4 100644 --- a/examples/cases/tpsl/create_partial_tpsl_order.py +++ b/examples/cases/tpsl/create_partial_tpsl_order.py @@ -16,7 +16,7 @@ OrderType, TimeInForce, ) -from x10.perpetual.order_object import OrderTpslTriggerParam, create_order_object +from x10.signing.order_object import OrderTpslTriggerParam, create_order_object LOGGER = logging.getLogger() MARKET_NAME = BTC_USD_MARKET diff --git a/examples/cases/tpsl/create_position_tpsl_order.py b/examples/cases/tpsl/create_position_tpsl_order.py index ffc593f..ffbf535 100644 --- a/examples/cases/tpsl/create_position_tpsl_order.py +++ b/examples/cases/tpsl/create_position_tpsl_order.py @@ -16,7 +16,7 @@ OrderType, TimeInForce, ) -from x10.perpetual.order_object import OrderTpslTriggerParam, create_order_object +from x10.signing.order_object import OrderTpslTriggerParam, create_order_object LOGGER = logging.getLogger() MARKET_NAME = BTC_USD_MARKET diff --git a/tests/perpetual/limit_order_object/test_limit_order_object_settlement.py b/tests/perpetual/limit_order_object/test_limit_order_object_settlement.py index 2f4b8be..2b4dc93 100644 --- a/tests/perpetual/limit_order_object/test_limit_order_object_settlement.py +++ b/tests/perpetual/limit_order_object/test_limit_order_object_settlement.py @@ -16,13 +16,13 @@ async def test_create_buy_limit_order_settlement_data( mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) from x10.config import MAINNET_CONFIG - from x10.perpetual.limit_order_object_settlement import create_order_settlement_data + from x10.signing.limit_order_object_settlement import create_limit_order_settlement_data trading_account = create_trading_account() collateral_asset = get_asset_usd() vault_asset = get_asset_xvs() - settlement, quote_amount_human, base_amount_human = create_order_settlement_data( + settlement, quote_amount_human, base_amount_human = create_limit_order_settlement_data( quote_amount=Decimal("10"), base_amount=Decimal("7"), position_id=trading_account.vault, diff --git a/tests/perpetual/order_object/test_conditional_order_object.py b/tests/perpetual/order_object/test_conditional_order_object.py index dbc97ea..15f78a8 100644 --- a/tests/perpetual/order_object/test_conditional_order_object.py +++ b/tests/perpetual/order_object/test_conditional_order_object.py @@ -22,7 +22,7 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import ( + from x10.signing.order_object import ( OrderConditionalTriggerParam, create_order_object, ) diff --git a/tests/perpetual/order_object/test_limit_order_object.py b/tests/perpetual/order_object/test_limit_order_object.py index 987182c..ab4ee50 100644 --- a/tests/perpetual/order_object/test_limit_order_object.py +++ b/tests/perpetual/order_object/test_limit_order_object.py @@ -27,7 +27,7 @@ async def test_create_sell_order_with_default_expiration( freezer = freeze_time("2024-01-05 01:08:56.860694") frozen_time = freezer.start() - from x10.perpetual.order_object import create_order_object + from x10.signing.order_object import create_order_object frozen_time.move_to("2024-01-05 01:08:57") trading_account = create_trading_account() @@ -84,7 +84,7 @@ async def test_create_sell_order_with_default_expiration( async def test_create_sell_order(mocker: MockerFixture, create_trading_account, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import create_order_object + from x10.signing.order_object import create_order_object trading_account = create_trading_account() btc_usd_market = create_btc_usd_market() @@ -142,7 +142,7 @@ async def test_create_sell_order(mocker: MockerFixture, create_trading_account, async def test_create_buy_order(mocker: MockerFixture, create_trading_account, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import create_order_object + from x10.signing.order_object import create_order_object trading_account = create_trading_account() btc_usd_market = create_btc_usd_market() @@ -200,7 +200,7 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, c async def test_create_buy_order_with_order_tpsl(mocker: MockerFixture, create_trading_account, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import OrderTpslTriggerParam, create_order_object + from x10.signing.order_object import OrderTpslTriggerParam, create_order_object trading_account = create_trading_account() btc_usd_market = create_btc_usd_market() @@ -309,7 +309,7 @@ async def test_create_buy_order_with_position_tpsl( ): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import OrderTpslTriggerParam, create_order_object + from x10.signing.order_object import OrderTpslTriggerParam, create_order_object trading_account = create_trading_account() btc_usd_market = create_btc_usd_market() diff --git a/tests/perpetual/order_object/test_market_order_object.py b/tests/perpetual/order_object/test_market_order_object.py index 5767dd0..9c13d2f 100644 --- a/tests/perpetual/order_object/test_market_order_object.py +++ b/tests/perpetual/order_object/test_market_order_object.py @@ -20,7 +20,7 @@ async def test_create_sell_order(mocker: MockerFixture, create_trading_account, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import create_order_object + from x10.signing.order_object import create_order_object trading_account = create_trading_account() btc_usd_market = create_btc_usd_market() @@ -87,7 +87,7 @@ async def test_create_sell_order(mocker: MockerFixture, create_trading_account, async def test_create_buy_order(mocker: MockerFixture, create_trading_account, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import create_order_object + from x10.signing.order_object import create_order_object trading_account = create_trading_account() btc_usd_market = create_btc_usd_market() diff --git a/tests/perpetual/order_object/test_order_object_attrs.py b/tests/perpetual/order_object/test_order_object_attrs.py index d4b5a57..8fb8e5c 100644 --- a/tests/perpetual/order_object/test_order_object_attrs.py +++ b/tests/perpetual/order_object/test_order_object_attrs.py @@ -18,7 +18,7 @@ async def test_cancel_previous_order(mocker: MockerFixture, create_trading_account, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import create_order_object + from x10.signing.order_object import create_order_object trading_account = create_trading_account() btc_usd_market = create_btc_usd_market() @@ -48,7 +48,7 @@ async def test_cancel_previous_order(mocker: MockerFixture, create_trading_accou async def test_external_order_id(mocker: MockerFixture, create_trading_account, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import create_order_object + from x10.signing.order_object import create_order_object trading_account = create_trading_account() btc_usd_market = create_btc_usd_market() diff --git a/tests/perpetual/order_object/test_tpsl_order_object.py b/tests/perpetual/order_object/test_tpsl_order_object.py index 7505252..624236f 100644 --- a/tests/perpetual/order_object/test_tpsl_order_object.py +++ b/tests/perpetual/order_object/test_tpsl_order_object.py @@ -25,7 +25,7 @@ async def test_create_buy_partial_tpsl_order(mocker: MockerFixture, create_trading_account, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import OrderTpslTriggerParam, create_order_object + from x10.signing.order_object import OrderTpslTriggerParam, create_order_object trading_account = create_trading_account() btc_usd_market = create_btc_usd_market() @@ -119,7 +119,7 @@ async def test_create_buy_partial_tpsl_order(mocker: MockerFixture, create_tradi async def test_create_buy_position_tpsl_order(mocker: MockerFixture, create_trading_account, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.order_object import OrderTpslTriggerParam, create_order_object + from x10.signing.order_object import OrderTpslTriggerParam, create_order_object trading_account = create_trading_account() btc_usd_market = create_btc_usd_market() diff --git a/tests/perpetual/test_transfer_object.py b/tests/perpetual/test_transfer_object.py index efe3f1a..140cbd7 100644 --- a/tests/perpetual/test_transfer_object.py +++ b/tests/perpetual/test_transfer_object.py @@ -15,7 +15,7 @@ async def test_create_transfer(mocker: MockerFixture, create_trading_account, create_accounts, create_btc_usd_market): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.transfer_object import create_transfer_object + from x10.signing.transfer_object import create_transfer_object trading_account = create_trading_account() accounts = create_accounts() diff --git a/x10/clients/rest/modules/account_module.py b/x10/clients/rest/modules/account_module.py index 366185a..17ba311 100644 --- a/x10/clients/rest/modules/account_module.py +++ b/x10/clients/rest/modules/account_module.py @@ -18,8 +18,8 @@ from x10.models.position import PositionHistoryModel, PositionModel, PositionSide from x10.models.trade import AccountTradeModel, TradeType from x10.models.transfer import TransferResponseModel -from x10.perpetual.transfer_object import create_transfer_object -from x10.perpetual.withdrawal_object import create_withdrawal_object +from x10.signing.transfer_object import create_transfer_object +from x10.signing.withdrawal_object import create_withdrawal_object from x10.utils.http import ( WrappedApiResponseModel, send_get_request, diff --git a/x10/clients/rest/modules/vault_module.py b/x10/clients/rest/modules/vault_module.py index e9c1b89..9f02b90 100644 --- a/x10/clients/rest/modules/vault_module.py +++ b/x10/clients/rest/modules/vault_module.py @@ -10,7 +10,7 @@ from x10.core.stark_account import StarkPerpetualAccount from x10.errors import ApiError, ValidationError from x10.models.vault import DepositRequestModel, WithdrawRequestModel -from x10.perpetual.limit_order_object_settlement import create_order_settlement_data +from x10.signing.limit_order_object_settlement import create_limit_order_settlement_data from x10.utils.http import send_post_request # Protects from an error on shares pricing fluctuations. @@ -64,7 +64,7 @@ async def deposit_to_vault(self, *, collateral_amount: Decimal) -> None: vault_asset.precision, ) - settlement, collateral_amount_human, shares_amount_human = create_order_settlement_data( + settlement, collateral_amount_human, shares_amount_human = create_limit_order_settlement_data( quote_amount=collateral_amount, base_amount=vault_shares_expected, position_id=position_id, @@ -116,7 +116,7 @@ async def withdraw_from_vault(self, *, shares_amount: Decimal) -> None: vault_asset.precision, ) - settlement, collateral_amount_human, shares_amount_human = create_order_settlement_data( + settlement, collateral_amount_human, shares_amount_human = create_limit_order_settlement_data( quote_amount=collateral_amount_expected, base_amount=shares_amount, position_id=position_id, diff --git a/x10/clients/rest/rest_api_client.py b/x10/clients/rest/rest_api_client.py index 570c71c..453e781 100644 --- a/x10/clients/rest/rest_api_client.py +++ b/x10/clients/rest/rest_api_client.py @@ -18,7 +18,7 @@ SelfTradeProtectionLevel, TimeInForce, ) -from x10.perpetual.order_object import OrderTpslTriggerParam, create_order_object +from x10.signing.order_object import OrderTpslTriggerParam, create_order_object from x10.utils.date import utc_now from x10.utils.http import WrappedApiResponseModel from x10.utils.log import get_logger diff --git a/x10/perpetual/simple_client/simple_trading_client.py b/x10/perpetual/simple_client/simple_trading_client.py index 00ef108..19be7b1 100644 --- a/x10/perpetual/simple_client/simple_trading_client.py +++ b/x10/perpetual/simple_client/simple_trading_client.py @@ -21,7 +21,7 @@ OrderType, TimeInForce, ) -from x10.perpetual.order_object import create_order_object +from x10.signing.order_object import create_order_object def condition_to_awaitable(condition: asyncio.Condition) -> Awaitable: diff --git a/x10/signing/__init__.py b/x10/signing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/x10/perpetual/limit_order_object_settlement.py b/x10/signing/limit_order_object_settlement.py similarity index 88% rename from x10/perpetual/limit_order_object_settlement.py rename to x10/signing/limit_order_object_settlement.py index cd48ffb..e00a187 100644 --- a/x10/perpetual/limit_order_object_settlement.py +++ b/x10/signing/limit_order_object_settlement.py @@ -7,15 +7,15 @@ from x10.models.asset import Asset, AssetModel from x10.models.base import SettlementSignatureModel from x10.models.order import LimitOrderSettlementModel -from x10.perpetual.order_object_settlement import ( - calculate_order_settlement_expiration, +from x10.signing.order_object_settlement import ( + SETTLEMENT_EXPIRATION_BUFFER_DAYS, hash_limit_order, ) -from x10.utils.date import utc_now +from x10.utils.date import calc_settlement_expiration, utc_now from x10.utils.nonce import generate_nonce -def create_order_settlement_data( +def create_limit_order_settlement_data( *, quote_amount, base_amount, @@ -62,7 +62,7 @@ def create_order_settlement_data( base_asset_id=int(base_asset.settlement_external_id, 16), quote_asset_id=int(quote_asset.settlement_external_id, 16), fee_asset_id=int(quote_asset.settlement_external_id, 16), - expiration_timestamp=calculate_order_settlement_expiration(expire_time), + expiration_timestamp=calc_settlement_expiration(SETTLEMENT_EXPIRATION_BUFFER_DAYS, expire_time), nonce=nonce, receiver_position_id=position_id, sender_position_id=position_id, diff --git a/x10/perpetual/order_object.py b/x10/signing/order_object.py similarity index 99% rename from x10/perpetual/order_object.py rename to x10/signing/order_object.py index d78ddf0..db64c23 100644 --- a/x10/perpetual/order_object.py +++ b/x10/signing/order_object.py @@ -20,7 +20,7 @@ SelfTradeProtectionLevel, TimeInForce, ) -from x10.perpetual.order_object_settlement import ( +from x10.signing.order_object_settlement import ( SettlementDataCtx, create_order_settlement_data, ) diff --git a/x10/perpetual/order_object_settlement.py b/x10/signing/order_object_settlement.py similarity index 92% rename from x10/perpetual/order_object_settlement.py rename to x10/signing/order_object_settlement.py index ba2dc89..8d64646 100644 --- a/x10/perpetual/order_object_settlement.py +++ b/x10/signing/order_object_settlement.py @@ -1,6 +1,5 @@ -import math from dataclasses import dataclass -from datetime import datetime, timedelta +from datetime import datetime from decimal import Decimal from typing import Callable, Optional, Tuple @@ -21,6 +20,9 @@ StarkDebuggingOrderAmountsModel, StarkSettlementModel, ) +from x10.utils.date import calc_settlement_expiration + +SETTLEMENT_EXPIRATION_BUFFER_DAYS = 14 @dataclass(kw_only=True, frozen=True) @@ -44,13 +46,6 @@ class SettlementDataCtx: starknet_domain: StarknetDomain -def calculate_order_settlement_expiration(expiration_timestamp: datetime): - expire_time_with_buffer = expiration_timestamp + timedelta(days=14) - expire_time_as_seconds = math.ceil(expire_time_with_buffer.timestamp()) - - return expire_time_as_seconds - - def hash_limit_order( amount_base: StarkAmount, amount_quote: StarkAmount, @@ -73,7 +68,7 @@ def hash_limit_order( quote_amount=amount_quote.value, fee_amount=max_fee.value, fee_asset_id=int(collateral_asset.settlement_external_id, 16), - expiration=calculate_order_settlement_expiration(expiration_timestamp), + expiration=calc_settlement_expiration(SETTLEMENT_EXPIRATION_BUFFER_DAYS, expiration_timestamp), salt=nonce, user_public_key=public_key, domain_name=starknet_domain.name, @@ -104,7 +99,7 @@ def hash_order( quote_amount=amount_collateral.value, fee_amount=max_fee.value, fee_asset_id=int(collateral_asset.settlement_external_id, 16), - expiration=calculate_order_settlement_expiration(expiration_timestamp), + expiration=calc_settlement_expiration(SETTLEMENT_EXPIRATION_BUFFER_DAYS, expiration_timestamp), salt=nonce, user_public_key=public_key, domain_name=starknet_domain.name, diff --git a/x10/perpetual/transfer_object.py b/x10/signing/transfer_object.py similarity index 86% rename from x10/perpetual/transfer_object.py rename to x10/signing/transfer_object.py index 6523cab..d422b7c 100644 --- a/x10/perpetual/transfer_object.py +++ b/x10/signing/transfer_object.py @@ -1,5 +1,3 @@ -import math -from datetime import timedelta from decimal import Decimal from typing import List @@ -13,21 +11,16 @@ OnChainPerpetualTransferModel, StarkTransferSettlementModel, ) -from x10.utils.date import utc_now +from x10.utils.date import calc_settlement_expiration from x10.utils.nonce import generate_nonce +SETTLEMENT_EXPIRATION_BUFFER_DAYS = 21 + def find_account_by_id(accounts: List[AccountModel], account_id: int): return next((account for account in accounts if account.id == account_id), None) -def calc_expiration_timestamp(): - expire_time = utc_now() + timedelta(days=7) - expire_time_with_buffer = expire_time + timedelta(days=14) - expire_time_with_buffer_seconds = math.ceil(expire_time_with_buffer.timestamp()) - return expire_time_with_buffer_seconds - - # FIXME: Transfers are broken def create_transfer_object( from_vault: int, @@ -38,7 +31,7 @@ def create_transfer_object( stark_account: StarkPerpetualAccount, nonce: int | None = None, ) -> OnChainPerpetualTransferModel: - expiration_timestamp = calc_expiration_timestamp() + expiration_timestamp = calc_settlement_expiration(SETTLEMENT_EXPIRATION_BUFFER_DAYS) scaled_amount = amount.scaleb(config.endpoints.collateral_decimals) stark_amount = scaled_amount.to_integral_exact() starknet_domain: StarknetDomain = config.signing.starknet_domain diff --git a/x10/perpetual/withdrawal_object.py b/x10/signing/withdrawal_object.py similarity index 85% rename from x10/perpetual/withdrawal_object.py rename to x10/signing/withdrawal_object.py index 40ceb89..8747158 100644 --- a/x10/perpetual/withdrawal_object.py +++ b/x10/signing/withdrawal_object.py @@ -1,5 +1,3 @@ -import math -from datetime import timedelta from decimal import Decimal from fast_stark_crypto import get_withdrawal_msg_hash @@ -12,15 +10,10 @@ TimestampModel, WithdrawalRequestModel, ) -from x10.utils.date import utc_now +from x10.utils.date import calc_settlement_expiration from x10.utils.nonce import generate_nonce - -def calc_expiration_timestamp(): - expire_time = utc_now() - expire_time_with_buffer = expire_time + timedelta(days=15) - expire_time_with_buffer_seconds = math.ceil(expire_time_with_buffer.timestamp()) - return expire_time_with_buffer_seconds +SETTLEMENT_EXPIRATION_BUFFER_DAYS = 15 def create_withdrawal_object( @@ -34,7 +27,7 @@ def create_withdrawal_object( nonce: int | None = None, quote_id: str | None = None, ) -> WithdrawalRequestModel: - expiration_timestamp = calc_expiration_timestamp() + expiration_timestamp = calc_settlement_expiration(SETTLEMENT_EXPIRATION_BUFFER_DAYS) scaled_amount = amount.scaleb(config.endpoints.collateral_decimals) stark_amount = scaled_amount.to_integral_exact() starknet_domain: StarknetDomain = config.signing.starknet_domain diff --git a/x10/utils/date.py b/x10/utils/date.py index 74dfb4a..cd03903 100644 --- a/x10/utils/date.py +++ b/x10/utils/date.py @@ -1,5 +1,5 @@ import math -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone def utc_now(): @@ -11,3 +11,14 @@ def to_epoch_millis(value: datetime): # Use ceiling to match the hash_order logic which uses math.ceil return int(math.ceil(value.timestamp() * 1000)) + + +# FIXME: Add test +def calc_settlement_expiration(buffer_days: int, expiration_timestamp: datetime | None = None) -> int: + if expiration_timestamp is None: + expiration_timestamp = utc_now() + + expire_time_with_buffer = expiration_timestamp + timedelta(days=buffer_days) + expire_time_as_seconds = math.ceil(expire_time_with_buffer.timestamp()) + + return expire_time_as_seconds From 19373388f6e77fbe5ee9f020a16ed865e805a963 Mon Sep 17 00:00:00 2001 From: alex101xela Date: Mon, 25 May 2026 14:12:44 +0400 Subject: [PATCH 2/5] Migrate signing code to a separate module --- .../limit_order_object/test_limit_order_object_settlement.py | 4 +++- .../order_object/test_conditional_order_object.py | 0 .../order_object/test_limit_order_object.py | 0 .../order_object/test_market_order_object.py | 0 .../order_object/test_order_object_attrs.py | 0 .../order_object/test_tpsl_order_object.py | 0 tests/{perpetual => signing}/test_transfer_object.py | 0 7 files changed, 3 insertions(+), 1 deletion(-) rename tests/{perpetual => signing}/limit_order_object/test_limit_order_object_settlement.py (94%) rename tests/{perpetual => signing}/order_object/test_conditional_order_object.py (100%) rename tests/{perpetual => signing}/order_object/test_limit_order_object.py (100%) rename tests/{perpetual => signing}/order_object/test_market_order_object.py (100%) rename tests/{perpetual => signing}/order_object/test_order_object_attrs.py (100%) rename tests/{perpetual => signing}/order_object/test_tpsl_order_object.py (100%) rename tests/{perpetual => signing}/test_transfer_object.py (100%) diff --git a/tests/perpetual/limit_order_object/test_limit_order_object_settlement.py b/tests/signing/limit_order_object/test_limit_order_object_settlement.py similarity index 94% rename from tests/perpetual/limit_order_object/test_limit_order_object_settlement.py rename to tests/signing/limit_order_object/test_limit_order_object_settlement.py index 2b4dc93..a70e08f 100644 --- a/tests/perpetual/limit_order_object/test_limit_order_object_settlement.py +++ b/tests/signing/limit_order_object/test_limit_order_object_settlement.py @@ -16,7 +16,9 @@ async def test_create_buy_limit_order_settlement_data( mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) from x10.config import MAINNET_CONFIG - from x10.signing.limit_order_object_settlement import create_limit_order_settlement_data + from x10.signing.limit_order_object_settlement import ( + create_limit_order_settlement_data, + ) trading_account = create_trading_account() collateral_asset = get_asset_usd() diff --git a/tests/perpetual/order_object/test_conditional_order_object.py b/tests/signing/order_object/test_conditional_order_object.py similarity index 100% rename from tests/perpetual/order_object/test_conditional_order_object.py rename to tests/signing/order_object/test_conditional_order_object.py diff --git a/tests/perpetual/order_object/test_limit_order_object.py b/tests/signing/order_object/test_limit_order_object.py similarity index 100% rename from tests/perpetual/order_object/test_limit_order_object.py rename to tests/signing/order_object/test_limit_order_object.py diff --git a/tests/perpetual/order_object/test_market_order_object.py b/tests/signing/order_object/test_market_order_object.py similarity index 100% rename from tests/perpetual/order_object/test_market_order_object.py rename to tests/signing/order_object/test_market_order_object.py diff --git a/tests/perpetual/order_object/test_order_object_attrs.py b/tests/signing/order_object/test_order_object_attrs.py similarity index 100% rename from tests/perpetual/order_object/test_order_object_attrs.py rename to tests/signing/order_object/test_order_object_attrs.py diff --git a/tests/perpetual/order_object/test_tpsl_order_object.py b/tests/signing/order_object/test_tpsl_order_object.py similarity index 100% rename from tests/perpetual/order_object/test_tpsl_order_object.py rename to tests/signing/order_object/test_tpsl_order_object.py diff --git a/tests/perpetual/test_transfer_object.py b/tests/signing/test_transfer_object.py similarity index 100% rename from tests/perpetual/test_transfer_object.py rename to tests/signing/test_transfer_object.py From 23bbc63d205f9aae9995333548791f9016244b5e Mon Sep 17 00:00:00 2001 From: alex101xela Date: Mon, 25 May 2026 14:17:57 +0400 Subject: [PATCH 3/5] Migrate signing code to a separate module --- MIGRATION.md | 1 + 1 file changed, 1 insertion(+) diff --git a/MIGRATION.md b/MIGRATION.md index 13f1a5f..4b296b4 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -11,6 +11,7 @@ `x10.clients.stream.StreamClient` (same interface, renamed to match the `RestApiClient` naming convention). - `UserClient` replaced by `OnboardingClient`, which accepts an account address and a sign-message callback instead of a raw L1 private key. - `onboard_subaccount` error handling has changed. Previously, it silently recovered an existing sub-account (HTTP 409) by fetching it from `get_accounts()`. Now it raises `ValidationError` on conflict. Handle duplicates explicitly if you relied on the automatic recovery. +- Signing-related modules have been extracted from `x10.perpetual` into a dedicated `x10.signing` package. The old paths no longer exist — there are no backwards-compatible re-exports. - Fixes https://github.com/x10xchange/python_sdk/issues/99. --- From 70a733ec7c166a123aa1cf4ccb9e92b4aa574f9a Mon Sep 17 00:00:00 2001 From: alex101xela Date: Mon, 25 May 2026 14:22:49 +0400 Subject: [PATCH 4/5] Migrate signing code to a separate module --- tests/utils/test_date.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/utils/test_date.py b/tests/utils/test_date.py index 12dddd7..6db1025 100644 --- a/tests/utils/test_date.py +++ b/tests/utils/test_date.py @@ -1,10 +1,19 @@ from datetime import datetime +from freezegun import freeze_time from hamcrest import assert_that, equal_to, raises from x10.utils.date import to_epoch_millis +@freeze_time("2024-01-05 01:08:56.860694") +def test_utc_now(): + from x10.utils.date import utc_now + + expected_dt = datetime.fromisoformat("2024-01-05 01:08:56.860694+00:00") + assert_that(utc_now(), equal_to(expected_dt)) + + def test_convert_datetime_to_epoch_millis(): dt = datetime.fromisoformat("2024-01-08 11:35:20.447+00:00") @@ -17,3 +26,13 @@ def test_throw_on_non_utc_timezone(): assert_that(lambda: to_epoch_millis(dt1), raises(AssertionError, "`value` must be in UTC")) # type: ignore[misc] assert_that(lambda: to_epoch_millis(dt2), raises(AssertionError, "`value` must be in UTC")) # type: ignore[misc] + + +@freeze_time("2024-01-05 01:08:56.860694") +def test_calc_settlement_expiration(): + from x10.utils.date import calc_settlement_expiration + + custom_dt = datetime.fromisoformat("2024-01-08 11:35:20.447+00:00") + + assert_that(calc_settlement_expiration(10), equal_to(1705280937)) + assert_that(calc_settlement_expiration(10, custom_dt), equal_to(1705577721)) From 2895a9edc6e4c9e893ee8f0a1a0b3158a5488268 Mon Sep 17 00:00:00 2001 From: alex101xela Date: Mon, 25 May 2026 14:24:23 +0400 Subject: [PATCH 5/5] Migrate signing code to a separate module --- x10/utils/date.py | 1 - 1 file changed, 1 deletion(-) diff --git a/x10/utils/date.py b/x10/utils/date.py index cd03903..d445ed8 100644 --- a/x10/utils/date.py +++ b/x10/utils/date.py @@ -13,7 +13,6 @@ def to_epoch_millis(value: datetime): return int(math.ceil(value.timestamp() * 1000)) -# FIXME: Add test def calc_settlement_expiration(buffer_days: int, expiration_timestamp: datetime | None = None) -> int: if expiration_timestamp is None: expiration_timestamp = utc_now()