diff --git a/MIGRATION.md b/MIGRATION.md index 2477861..00c84ef 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -8,3 +8,6 @@ classes were also renamed to follow the consistent `*Model` suffix convention. - `StarkPerpetualAccount` (previously in `x10.perpetual.accounts`) has moved to `x10.core.stark_account`. - The package now ships a `py.typed` marker (PEP 561), so mypy will now type-check against the SDK's inline annotations by default. +- `x10.perpetual.configuration` has been **deleted**. All config types and pre-built instances now live in `x10.config`. +- `TESTNET_CONFIG` and `MAINNET_CONFIG` are now instances of the new `Config` dataclass (groups URLs, singing settings, defaults). +- Market names constants have been removed from `x10.config`. Define market name strings inline in your own code. diff --git a/examples/cases/advanced/load_testing.py b/examples/cases/advanced/load_testing.py index b00d22f..47dd5b5 100644 --- a/examples/cases/advanced/load_testing.py +++ b/examples/cases/advanced/load_testing.py @@ -4,8 +4,7 @@ from asyncio import run from typing import Set -from examples.utils import create_trading_client -from x10.config import BTC_USD_MARKET +from examples.utils import BTC_USD_MARKET, create_trading_client from x10.models.market import MarketModel from x10.models.order import OrderSide from x10.perpetual.order_object import create_order_object diff --git a/examples/cases/advanced/market_maker.py b/examples/cases/advanced/market_maker.py index e839b33..955b070 100644 --- a/examples/cases/advanced/market_maker.py +++ b/examples/cases/advanced/market_maker.py @@ -5,8 +5,7 @@ from signal import SIGINT, SIGTERM from typing import Awaitable, Callable, List -from examples.utils import create_trading_client -from x10.config import BTC_USD_MARKET +from examples.utils import BTC_USD_MARKET, create_trading_client from x10.models.order import OrderSide from x10.perpetual.orderbook import OrderBook, OrderBookEntry diff --git a/examples/cases/advanced/onboarding_with_eth_account.py b/examples/cases/advanced/onboarding_with_eth_account.py index a7c9edf..89bf878 100644 --- a/examples/cases/advanced/onboarding_with_eth_account.py +++ b/examples/cases/advanced/onboarding_with_eth_account.py @@ -5,14 +5,14 @@ from eth_account.signers.local import LocalAccount from examples.utils import init_env +from x10.config import TESTNET_CONFIG from x10.core.stark_account import StarkPerpetualAccount -from x10.perpetual.configuration import TESTNET_CONFIG from x10.perpetual.trading_client.trading_client import PerpetualTradingClient from x10.perpetual.user_client.user_client import UserClient from x10.utils.string import is_hex_string LOGGER = logging.getLogger() -ENDPOINT_CONFIG = TESTNET_CONFIG +CONFIG = TESTNET_CONFIG async def run_example(): @@ -23,7 +23,7 @@ async def run_example(): assert is_hex_string(eth_account_private_key), "`eth_account_private_key` must be a hex string" eth_local_account: LocalAccount = Account.from_key(eth_account_private_key) - user_client = UserClient(endpoint_config=ENDPOINT_CONFIG, l1_private_key=eth_local_account.key.hex) + user_client = UserClient(config=CONFIG, l1_private_key=eth_local_account.key.hex) LOGGER.info("Onboarding with ETH account %s...", eth_local_account.address) @@ -36,7 +36,7 @@ async def run_example(): private_key=main_account.l2_key_pair.private_hex, vault=main_account.account.l2_vault, ) - trading_client = PerpetualTradingClient(ENDPOINT_CONFIG, starknet_account) + trading_client = PerpetualTradingClient(CONFIG, starknet_account) LOGGER.info("StarkNet public key: %s", starknet_account.public_key) diff --git a/examples/cases/buildercode/create_limit_order_with_builder.py b/examples/cases/buildercode/create_limit_order_with_builder.py index dfd7f36..8f1831f 100644 --- a/examples/cases/buildercode/create_limit_order_with_builder.py +++ b/examples/cases/buildercode/create_limit_order_with_builder.py @@ -2,12 +2,12 @@ from asyncio import run from examples.utils import ( + BTC_USD_MARKET, create_trading_client, find_order_and_cancel, get_adjust_price_by_pct, init_env, ) -from x10.config import BTC_USD_MARKET from x10.models.order import OrderSide, TimeInForce from x10.perpetual.order_object import create_order_object @@ -45,8 +45,8 @@ async def run_example(): time_in_force=TimeInForce.GTT, reduce_only=False, post_only=True, - builder_id=builder_id, builder_fee=builder_fee, + builder_id=builder_id, ) LOGGER.info("Placing order...") diff --git a/examples/cases/createorder/create_conditional_order.py b/examples/cases/createorder/create_conditional_order.py index e9461c9..6bbf4ff 100644 --- a/examples/cases/createorder/create_conditional_order.py +++ b/examples/cases/createorder/create_conditional_order.py @@ -2,11 +2,11 @@ from asyncio import run from examples.utils import ( + BTC_USD_MARKET, create_trading_client, find_order_and_cancel, get_adjust_price_by_pct, ) -from x10.config import BTC_USD_MARKET from x10.models.order import ( OrderPriceType, OrderSide, diff --git a/examples/cases/createorder/create_limit_order.py b/examples/cases/createorder/create_limit_order.py index 5d1b862..570c9ab 100644 --- a/examples/cases/createorder/create_limit_order.py +++ b/examples/cases/createorder/create_limit_order.py @@ -2,11 +2,11 @@ from asyncio import run from examples.utils import ( + BTC_USD_MARKET, create_trading_client, find_order_and_cancel, get_adjust_price_by_pct, ) -from x10.config import BTC_USD_MARKET from x10.models.order import OrderSide, TimeInForce from x10.perpetual.order_object import create_order_object diff --git a/examples/cases/createorder/create_market_order.py b/examples/cases/createorder/create_market_order.py index 5175ff1..3945328 100644 --- a/examples/cases/createorder/create_market_order.py +++ b/examples/cases/createorder/create_market_order.py @@ -1,8 +1,7 @@ import logging from asyncio import run -from examples.utils import create_trading_client -from x10.config import BTC_USD_MARKET, DEFAULT_MARKET_PRICE_SLIPPAGE +from examples.utils import BTC_USD_MARKET, create_trading_client from x10.models.order import OrderSide, OrderType, TimeInForce from x10.perpetual.order_object import create_order_object from x10.utils.order import get_price_with_slippage @@ -26,7 +25,7 @@ async def run_example(): side=order_side, price=best_market_price, min_price_change=market.trading_config.min_price_change, - slippage=DEFAULT_MARKET_PRICE_SLIPPAGE, + slippage=trading_client.config.defaults.market_price_slippage, ) LOGGER.info("Creating MARKET order object for market: %s", market.name) diff --git a/examples/cases/createorder/create_market_order_with_blocking_client.py b/examples/cases/createorder/create_market_order_with_blocking_client.py index 8a42521..d65f9d2 100644 --- a/examples/cases/createorder/create_market_order_with_blocking_client.py +++ b/examples/cases/createorder/create_market_order_with_blocking_client.py @@ -1,15 +1,14 @@ import asyncio import logging -from examples.utils import create_blocking_client -from x10.config import BTC_USD_MARKET, DEFAULT_MARKET_PRICE_SLIPPAGE +from examples.utils import BTC_USD_MARKET, create_blocking_client +from x10.config import TESTNET_CONFIG from x10.models.order import OrderSide, OrderType, TimeInForce -from x10.perpetual.configuration import TESTNET_CONFIG from x10.perpetual.orderbook import OrderBook from x10.utils.order import get_price_with_slippage LOGGER = logging.getLogger() -ENDPOINT_CONFIG = TESTNET_CONFIG +CONFIG = TESTNET_CONFIG MARKET_NAME = BTC_USD_MARKET @@ -21,7 +20,7 @@ async def best_ask_initialised(_best_ask): best_ask_condition.notify_all() orderbook = await OrderBook.create( - ENDPOINT_CONFIG, + CONFIG, market_name=market_name, start=True, best_ask_change_callback=best_ask_initialised, @@ -39,7 +38,7 @@ async def best_ask_initialised(_best_ask): async def run_example(): - blocking_client = create_blocking_client(ENDPOINT_CONFIG) + blocking_client = create_blocking_client(CONFIG) markets = await blocking_client.get_markets() market = markets[MARKET_NAME] @@ -56,7 +55,7 @@ async def run_example(): side=order_side, price=best_ask_entry.price, min_price_change=market.trading_config.min_price_change, - slippage=DEFAULT_MARKET_PRICE_SLIPPAGE, + slippage=blocking_client.config.defaults.market_price_slippage, ) LOGGER.info("Creating MARKET order for market %s: %s@%s", market.name, order_size, order_price) diff --git a/examples/cases/stream/subscribe_to_multiple_streams.py b/examples/cases/stream/subscribe_to_multiple_streams.py index 71fdb00..c224ce0 100644 --- a/examples/cases/stream/subscribe_to_multiple_streams.py +++ b/examples/cases/stream/subscribe_to_multiple_streams.py @@ -3,8 +3,7 @@ from asyncio import run from signal import SIGINT, SIGTERM -from examples.utils import create_stream_client, init_env -from x10.config import BTC_USD_MARKET +from examples.utils import BTC_USD_MARKET, create_stream_client, init_env LOGGER = logging.getLogger() MARKET_NAME = BTC_USD_MARKET 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 c79afe2..1fb7188 100644 --- a/examples/cases/tpsl/create_limit_order_with_partial_tpsl.py +++ b/examples/cases/tpsl/create_limit_order_with_partial_tpsl.py @@ -3,11 +3,11 @@ from decimal import Decimal from examples.utils import ( + BTC_USD_MARKET, create_trading_client, find_order_and_cancel, get_adjust_price_by_pct, ) -from x10.config import BTC_USD_MARKET, DEFAULT_MARKET_PRICE_SLIPPAGE from x10.models.order import ( OrderPriceType, OrderSide, @@ -34,7 +34,9 @@ async def run_example(): tp_trigger_price = adjust_price_by_pct(order_price, 0.5) tp_price = adjust_price_by_pct(tp_trigger_price, 0.5) sl_trigger_price = adjust_price_by_pct(order_price, -0.5) - sl_price = adjust_price_by_pct(sl_trigger_price, -DEFAULT_MARKET_PRICE_SLIPPAGE * Decimal("100")) + sl_price = adjust_price_by_pct( + sl_trigger_price, -trading_client.config.defaults.market_price_slippage * Decimal("100") + ) LOGGER.info("Creating LIMIT order object with TPSL for market: %s", market.name) 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 2d5029e..383be08 100644 --- a/examples/cases/tpsl/create_limit_order_with_position_tpsl.py +++ b/examples/cases/tpsl/create_limit_order_with_position_tpsl.py @@ -2,11 +2,11 @@ from asyncio import run from examples.utils import ( + BTC_USD_MARKET, create_trading_client, find_order_and_cancel, get_adjust_price_by_pct, ) -from x10.config import BTC_USD_MARKET from x10.models.order import ( OrderPriceType, OrderSide, diff --git a/examples/cases/tpsl/create_partial_tpsl_order.py b/examples/cases/tpsl/create_partial_tpsl_order.py index e94e1c5..cedfcdf 100644 --- a/examples/cases/tpsl/create_partial_tpsl_order.py +++ b/examples/cases/tpsl/create_partial_tpsl_order.py @@ -3,11 +3,11 @@ from decimal import Decimal from examples.utils import ( + BTC_USD_MARKET, create_trading_client, find_order_and_cancel, get_adjust_price_by_pct, ) -from x10.config import BTC_USD_MARKET from x10.models.order import ( OrderPriceType, OrderSide, diff --git a/examples/cases/tpsl/create_position_tpsl_order.py b/examples/cases/tpsl/create_position_tpsl_order.py index 5f5c979..0ce86f1 100644 --- a/examples/cases/tpsl/create_position_tpsl_order.py +++ b/examples/cases/tpsl/create_position_tpsl_order.py @@ -3,11 +3,11 @@ from decimal import Decimal from examples.utils import ( + BTC_USD_MARKET, create_trading_client, find_order_and_cancel, get_adjust_price_by_pct, ) -from x10.config import BTC_USD_MARKET from x10.models.order import ( OrderPriceType, OrderSide, diff --git a/examples/cases/withdraw/withdrawal_bridged.py b/examples/cases/withdraw/withdrawal_bridged.py index 02775d4..b5b8e6d 100644 --- a/examples/cases/withdraw/withdrawal_bridged.py +++ b/examples/cases/withdraw/withdrawal_bridged.py @@ -3,7 +3,7 @@ from decimal import Decimal from examples.utils import create_trading_client -from x10.perpetual.configuration import MAINNET_CONFIG +from x10.config import MAINNET_CONFIG LOGGER = logging.getLogger() FEE_THRESHOLD_USDC = 2 diff --git a/examples/cases/withdraw/withdrawal_starknet.py b/examples/cases/withdraw/withdrawal_starknet.py index 45144f6..10a6022 100644 --- a/examples/cases/withdraw/withdrawal_starknet.py +++ b/examples/cases/withdraw/withdrawal_starknet.py @@ -3,7 +3,7 @@ from decimal import Decimal from examples.utils import create_trading_client -from x10.perpetual.configuration import MAINNET_CONFIG +from x10.config import MAINNET_CONFIG from x10.utils.nonce import generate_nonce from x10.utils.string import is_hex_string diff --git a/examples/utils.py b/examples/utils.py index 5cf34d4..568fa80 100644 --- a/examples/utils.py +++ b/examples/utils.py @@ -10,14 +10,16 @@ import yaml from dotenv import load_dotenv +from x10.config import TESTNET_CONFIG, Config from x10.core.stark_account import StarkPerpetualAccount from x10.models.market import TradingConfigModel -from x10.perpetual.configuration import TESTNET_CONFIG, EndpointConfig from x10.perpetual.simple_client.simple_trading_client import BlockingTradingClient from x10.perpetual.stream_client import PerpetualStreamClient from x10.perpetual.trading_client import PerpetualTradingClient from x10.utils.string import is_hex_string +BTC_USD_MARKET = "BTC-USD" + @dataclass class EnvConfig: @@ -59,7 +61,7 @@ def init_env(require_private_api: bool = True): ) -def create_trading_client(endpoint_config: EndpointConfig = TESTNET_CONFIG): +def create_trading_client(config: Config = TESTNET_CONFIG): env_config = init_env() stark_account = StarkPerpetualAccount( @@ -69,10 +71,10 @@ def create_trading_client(endpoint_config: EndpointConfig = TESTNET_CONFIG): vault=env_config.vault_id, ) - return PerpetualTradingClient(endpoint_config, stark_account) + return PerpetualTradingClient(config, stark_account) -def create_blocking_client(endpoint_config: EndpointConfig = TESTNET_CONFIG): +def create_blocking_client(config: Config = TESTNET_CONFIG): env_config = init_env() stark_account = StarkPerpetualAccount( @@ -82,11 +84,11 @@ def create_blocking_client(endpoint_config: EndpointConfig = TESTNET_CONFIG): vault=env_config.vault_id, ) - return BlockingTradingClient(endpoint_config, stark_account) + return BlockingTradingClient(config, stark_account) -def create_stream_client(endpoint_config: EndpointConfig = TESTNET_CONFIG): - return PerpetualStreamClient(api_url=endpoint_config.stream_url) +def create_stream_client(config: Config = TESTNET_CONFIG): + return PerpetualStreamClient(api_url=config.endpoints.stream_url) def get_adjust_price_by_pct(config: TradingConfigModel): 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 7cc236c..2f4b8be 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 @@ -15,7 +15,7 @@ async def test_create_buy_limit_order_settlement_data( ): mocker.patch("x10.utils.nonce.generate_nonce", return_value=FROZEN_NONCE) - from x10.perpetual.configuration import MAINNET_CONFIG + from x10.config import MAINNET_CONFIG from x10.perpetual.limit_order_object_settlement import create_order_settlement_data trading_account = create_trading_account() @@ -29,7 +29,7 @@ async def test_create_buy_limit_order_settlement_data( quote_asset_model=collateral_asset, base_asset_model=vault_asset, starknet_account=trading_account, - starknet_domain=MAINNET_CONFIG.starknet_domain, + starknet_domain=MAINNET_CONFIG.signing.starknet_domain, is_buy=True, ) diff --git a/tests/perpetual/order_object/test_conditional_order_object.py b/tests/perpetual/order_object/test_conditional_order_object.py index 4373942..dbc97ea 100644 --- a/tests/perpetual/order_object/test_conditional_order_object.py +++ b/tests/perpetual/order_object/test_conditional_order_object.py @@ -5,6 +5,7 @@ from hamcrest import assert_that, equal_to from pytest_mock import MockerFixture +from x10.config import TESTNET_CONFIG from x10.models.order import ( OrderPriceType, OrderSide, @@ -12,7 +13,6 @@ OrderTriggerPriceType, OrderType, ) -from x10.perpetual.configuration import TESTNET_CONFIG FROZEN_NONCE = 1473459052 @@ -36,7 +36,7 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, c amount_of_synthetic=Decimal("0.00100000"), price=Decimal("43445.11680000"), side=OrderSide.BUY, - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, trigger=OrderConditionalTriggerParam( trigger_price=Decimal("43400"), trigger_price_type=OrderTriggerPriceType.INDEX, diff --git a/tests/perpetual/order_object/test_limit_order_object.py b/tests/perpetual/order_object/test_limit_order_object.py index 8ac267d..987182c 100644 --- a/tests/perpetual/order_object/test_limit_order_object.py +++ b/tests/perpetual/order_object/test_limit_order_object.py @@ -6,6 +6,7 @@ from hamcrest import assert_that, equal_to from pytest_mock import MockerFixture +from x10.config import TESTNET_CONFIG from x10.models.order import ( OrderPriceType, OrderSide, @@ -13,7 +14,6 @@ OrderTriggerPriceType, SelfTradeProtectionLevel, ) -from x10.perpetual.configuration import TESTNET_CONFIG from x10.utils.date import utc_now FROZEN_NONCE = 1473459052 @@ -38,7 +38,7 @@ async def test_create_sell_order_with_default_expiration( amount_of_synthetic=Decimal("0.00100000"), price=Decimal("43445.11680000"), side=OrderSide.SELL, - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, ) freezer.stop() assert_that( @@ -95,7 +95,7 @@ async def test_create_sell_order(mocker: MockerFixture, create_trading_account, price=Decimal("43445.11680000"), side=OrderSide.SELL, expire_time=utc_now() + timedelta(days=14), - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, nonce=FROZEN_NONCE, ) @@ -154,7 +154,7 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, c side=OrderSide.BUY, expire_time=utc_now() + timedelta(days=14), self_trade_protection_level=SelfTradeProtectionLevel.CLIENT, - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, ) assert_that( @@ -212,7 +212,7 @@ async def test_create_buy_order_with_order_tpsl(mocker: MockerFixture, create_tr side=OrderSide.BUY, expire_time=utc_now() + timedelta(days=14), self_trade_protection_level=SelfTradeProtectionLevel.CLIENT, - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, tp_sl_type=OrderTpslType.ORDER, take_profit=OrderTpslTriggerParam( trigger_price=Decimal("49000"), @@ -321,7 +321,7 @@ async def test_create_buy_order_with_position_tpsl( side=OrderSide.BUY, expire_time=utc_now() + timedelta(days=14), self_trade_protection_level=SelfTradeProtectionLevel.CLIENT, - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, tp_sl_type=OrderTpslType.POSITION, take_profit=OrderTpslTriggerParam( trigger_price=Decimal("49000"), diff --git a/tests/perpetual/order_object/test_market_order_object.py b/tests/perpetual/order_object/test_market_order_object.py index a914a48..5767dd0 100644 --- a/tests/perpetual/order_object/test_market_order_object.py +++ b/tests/perpetual/order_object/test_market_order_object.py @@ -6,8 +6,8 @@ from hamcrest import assert_that, equal_to from pytest_mock import MockerFixture +from x10.config import TESTNET_CONFIG from x10.models.order import OrderSide, OrderType, TimeInForce -from x10.perpetual.configuration import TESTNET_CONFIG from x10.utils.date import utc_now from x10.utils.order import get_price_with_slippage @@ -40,8 +40,8 @@ async def test_create_sell_order(mocker: MockerFixture, create_trading_account, side=order_side, expire_time=utc_now() + timedelta(days=14), time_in_force=TimeInForce.IOC, - starknet_domain=TESTNET_CONFIG.starknet_domain, nonce=FROZEN_NONCE, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, ) assert_that( @@ -107,7 +107,7 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, c side=order_side, expire_time=utc_now() + timedelta(days=14), time_in_force=TimeInForce.IOC, - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, nonce=FROZEN_NONCE, ) diff --git a/tests/perpetual/order_object/test_order_object_attrs.py b/tests/perpetual/order_object/test_order_object_attrs.py index fe5edb3..d4b5a57 100644 --- a/tests/perpetual/order_object/test_order_object_attrs.py +++ b/tests/perpetual/order_object/test_order_object_attrs.py @@ -6,8 +6,8 @@ from hamcrest import assert_that, equal_to, has_entries from pytest_mock import MockerFixture +from x10.config import TESTNET_CONFIG from x10.models.order import OrderSide -from x10.perpetual.configuration import TESTNET_CONFIG from x10.utils.date import utc_now FROZEN_NONCE = 1473459052 @@ -30,7 +30,7 @@ async def test_cancel_previous_order(mocker: MockerFixture, create_trading_accou side=OrderSide.BUY, expire_time=utc_now() + timedelta(days=14), previous_order_external_id="previous_custom_id", - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, ) assert_that( @@ -60,7 +60,7 @@ async def test_external_order_id(mocker: MockerFixture, create_trading_account, side=OrderSide.BUY, expire_time=utc_now() + timedelta(days=14), order_external_id="custom_id", - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, ) assert_that( diff --git a/tests/perpetual/order_object/test_tpsl_order_object.py b/tests/perpetual/order_object/test_tpsl_order_object.py index 77ad0d6..7505252 100644 --- a/tests/perpetual/order_object/test_tpsl_order_object.py +++ b/tests/perpetual/order_object/test_tpsl_order_object.py @@ -6,6 +6,7 @@ from hamcrest import assert_that, equal_to from pytest_mock import MockerFixture +from x10.config import TESTNET_CONFIG from x10.models.order import ( OrderPriceType, OrderSide, @@ -14,7 +15,6 @@ OrderType, SelfTradeProtectionLevel, ) -from x10.perpetual.configuration import TESTNET_CONFIG from x10.utils.date import utc_now FROZEN_NONCE = 1473459052 @@ -39,7 +39,7 @@ async def test_create_buy_partial_tpsl_order(mocker: MockerFixture, create_tradi reduce_only=True, expire_time=utc_now() + timedelta(days=14), self_trade_protection_level=SelfTradeProtectionLevel.CLIENT, - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, tp_sl_type=OrderTpslType.ORDER, take_profit=OrderTpslTriggerParam( trigger_price=Decimal("49000"), @@ -133,7 +133,7 @@ async def test_create_buy_position_tpsl_order(mocker: MockerFixture, create_trad reduce_only=True, expire_time=utc_now() + timedelta(days=14), self_trade_protection_level=SelfTradeProtectionLevel.CLIENT, - starknet_domain=TESTNET_CONFIG.starknet_domain, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, tp_sl_type=OrderTpslType.POSITION, take_profit=OrderTpslTriggerParam( trigger_price=Decimal("49000"), diff --git a/tests/perpetual/test_orderbook_price_impact.py b/tests/perpetual/test_orderbook_price_impact.py index 48a67a9..fa3d162 100644 --- a/tests/perpetual/test_orderbook_price_impact.py +++ b/tests/perpetual/test_orderbook_price_impact.py @@ -2,17 +2,16 @@ import decimal from unittest import TestCase +from x10.config import TESTNET_CONFIG from x10.models.orderbook import OrderbookUpdateModel -from x10.perpetual.configuration import TESTNET_CONFIG from x10.perpetual.orderbook import OrderBook class TestOrderBook(TestCase): def setUp(self): - self.endpoint_config = TESTNET_CONFIG self.market_name = "dummy-market" self.orderbook = OrderBook( - self.endpoint_config, + TESTNET_CONFIG, self.market_name, best_ask_change_callback=None, best_bid_change_callback=None, diff --git a/tests/perpetual/test_trading_client.py b/tests/perpetual/test_trading_client.py index 3b1eee7..5864af1 100644 --- a/tests/perpetual/test_trading_client.py +++ b/tests/perpetual/test_trading_client.py @@ -5,9 +5,9 @@ from aiohttp import web from hamcrest import assert_that, equal_to, has_length +from x10.config import TESTNET_CONFIG from x10.models.asset import AssetOperationModel from x10.models.market import MarketModel -from x10.perpetual.configuration import TESTNET_CONFIG from x10.utils.http import WrappedApiResponse @@ -33,8 +33,9 @@ async def test_get_markets(aiohttp_server, create_btc_usd_market): server = await aiohttp_server(app) url = f"http://{server.host}:{server.port}" - endpoint_config = dataclasses.replace(TESTNET_CONFIG, api_base_url=url) - trading_client = PerpetualTradingClient(endpoint_config=endpoint_config) + endpoints_config = dataclasses.replace(TESTNET_CONFIG.endpoints, api_base_url=url) + config = dataclasses.replace(TESTNET_CONFIG, endpoints=endpoints_config) + trading_client = PerpetualTradingClient(config=config) markets = await trading_client.markets_info.get_markets() assert_that(markets.status, equal_to("OK")) @@ -133,8 +134,9 @@ async def test_get_asset_operations(aiohttp_server, create_asset_operations, cre url = f"http://{server.host}:{server.port}" stark_account = create_trading_account() - endpoint_config = endpoint_config = dataclasses.replace(TESTNET_CONFIG, api_base_url=url) - trading_client = PerpetualTradingClient(endpoint_config=endpoint_config, stark_account=stark_account) + endpoints_config = dataclasses.replace(TESTNET_CONFIG.endpoints, api_base_url=url) + config = dataclasses.replace(TESTNET_CONFIG, endpoints=endpoints_config) + trading_client = PerpetualTradingClient(config=config, stark_account=stark_account) operations = await trading_client.account.asset_operations() assert_that(operations.status, equal_to("OK")) diff --git a/tests/perpetual/test_transfer_object.py b/tests/perpetual/test_transfer_object.py index cb185e8..efe3f1a 100644 --- a/tests/perpetual/test_transfer_object.py +++ b/tests/perpetual/test_transfer_object.py @@ -5,7 +5,7 @@ from hamcrest import assert_that, equal_to from pytest_mock import MockerFixture -from x10.perpetual.configuration import TESTNET_CONFIG +from x10.config import TESTNET_CONFIG FROZEN_NONCE = 1473459052 diff --git a/x10/config.py b/x10/config.py index 4a9fb17..be55959 100644 --- a/x10/config.py +++ b/x10/config.py @@ -1,21 +1,99 @@ -import importlib.metadata +from dataclasses import dataclass from decimal import Decimal -from x10.models.fee import TradingFeeModel -BTC_USD_MARKET = "BTC-USD" -SOL_USD_MARKET = "SOL-USD" -ADA_USD_MARKET = "ADA-USD" -ETH_USD_MARKET = "ETH-USD" +@dataclass(kw_only=True, frozen=True) +class StarknetDomain: + name: str + version: str + chain_id: str + revision: str -DEFAULT_MARKET_PRICE_SLIPPAGE = Decimal("0.0075") -DEFAULT_REQUEST_TIMEOUT_SECONDS = 500 -SDK_VERSION = importlib.metadata.version("x10-python-trading-starknet") -USER_AGENT = f"X10PythonTradingClient/{SDK_VERSION}" -DEFAULT_FEES = TradingFeeModel( - market="BTC-USD", - maker_fee_rate=(Decimal("2") / Decimal("10000")), - taker_fee_rate=(Decimal("5") / Decimal("10000")), - builder_fee_rate=Decimal("0"), +@dataclass(kw_only=True, frozen=True) +class DefaultsConfig: + market_price_slippage: Decimal + request_timeout_seconds: int + + +@dataclass(kw_only=True, frozen=True) +class SigningConfig: + signing_domain: str + starknet_domain: StarknetDomain + + +@dataclass(kw_only=True, frozen=True) +class EndpointsConfig: + """ + Attributes: + chain_rpc_url (str): Field is deprecated and will be removed. + asset_operations_contract (str): Field is deprecated and will be removed. + collateral_asset_contract (str): Field is deprecated and will be removed. + collateral_asset_on_chain_id (str): Field is deprecated and will be removed. + collateral_decimals (int): Field is deprecated and will be removed. + collateral_asset_id (str): Field is deprecated and will be removed. + """ + + chain_rpc_url: str + api_base_url: str + stream_url: str + onboarding_url: str + + asset_operations_contract: str + collateral_asset_contract: str + collateral_asset_on_chain_id: str + collateral_decimals: int + collateral_asset_id: str + + vault_asset_name: str + + +@dataclass(kw_only=True, frozen=True) +class Config: + defaults: DefaultsConfig + signing: SigningConfig + endpoints: EndpointsConfig + + +DEFAULTS = DefaultsConfig(market_price_slippage=Decimal("0.0075"), request_timeout_seconds=500) + +TESTNET_CONFIG = Config( + defaults=DEFAULTS, + signing=SigningConfig( + signing_domain="starknet.sepolia.extended.exchange", + starknet_domain=StarknetDomain(name="Perpetuals", version="v0", chain_id="SN_SEPOLIA", revision="1"), + ), + endpoints=EndpointsConfig( + chain_rpc_url="https://rpc.sepolia.org", + api_base_url="https://api.starknet.sepolia.extended.exchange/api/v1", + stream_url="wss://api.starknet.sepolia.extended.exchange/stream.extended.exchange/v1", + onboarding_url="https://api.starknet.sepolia.extended.exchange", + asset_operations_contract="", + collateral_asset_contract="0x05ba91db44b3e6a4485b5dbfcb17d791faa9cb6890a42731b66b3536b28b8ed5", + collateral_asset_on_chain_id="0x1", + collateral_decimals=6, + collateral_asset_id="0x1", + vault_asset_name="XVS", + ), +) + + +MAINNET_CONFIG = Config( + defaults=DEFAULTS, + signing=SigningConfig( + signing_domain="extended.exchange", + starknet_domain=StarknetDomain(name="Perpetuals", version="v0", chain_id="SN_MAIN", revision="1"), + ), + endpoints=EndpointsConfig( + chain_rpc_url="", + api_base_url="https://api.starknet.extended.exchange/api/v1", + stream_url="wss://api.starknet.extended.exchange/stream.extended.exchange/v1", + onboarding_url="https://api.starknet.extended.exchange", + asset_operations_contract="", + collateral_asset_contract="", + collateral_asset_on_chain_id="0x1", + collateral_decimals=6, + collateral_asset_id="0x1", + vault_asset_name="XVS", + ), ) diff --git a/x10/core/stark_account.py b/x10/core/stark_account.py index 3eb1eec..9fe1c26 100644 --- a/x10/core/stark_account.py +++ b/x10/core/stark_account.py @@ -1,21 +1,14 @@ -from typing import Dict, Tuple +from typing import Tuple from fast_stark_crypto import sign -from x10.models.fee import TradingFeeModel from x10.utils.string import is_hex_string class StarkPerpetualAccount: - """ - Attributes: - __trading_fee (dict): Field is deprecated and will be removed. - """ - __vault: int __private_key: int __public_key: int - __trading_fee: Dict[str, TradingFeeModel] def __init__(self, vault: int | str, private_key: str, public_key: str, api_key: str): assert is_hex_string(private_key) @@ -32,7 +25,6 @@ def __init__(self, vault: int | str, private_key: str, public_key: str, api_key: self.__private_key = int(private_key, base=16) self.__public_key = int(public_key, base=16) self.__api_key = api_key - self.__trading_fee = {} @property def vault(self): @@ -46,9 +38,5 @@ def public_key(self): def api_key(self): return self.__api_key - @property - def trading_fee(self): - return self.__trading_fee - def sign(self, msg_hash: int) -> Tuple[int, int]: return sign(private_key=self.__private_key, msg_hash=msg_hash) diff --git a/x10/perpetual/configuration.py b/x10/perpetual/configuration.py deleted file mode 100644 index 02dfeb0..0000000 --- a/x10/perpetual/configuration.py +++ /dev/null @@ -1,68 +0,0 @@ -from dataclasses import dataclass - - -@dataclass(kw_only=True) -class StarknetDomain: - name: str - version: str - chain_id: str - revision: str - - -@dataclass(kw_only=True) -class EndpointConfig: - """ - Attributes: - chain_rpc_url (str): Field is deprecated and will be removed. - asset_operations_contract (str): Field is deprecated and will be removed. - collateral_asset_contract (str): Field is deprecated and will be removed. - collateral_asset_on_chain_id (str): Field is deprecated and will be removed. - collateral_decimals (int): Field is deprecated and will be removed. - collateral_asset_id (str): Field is deprecated and will be removed. - """ - - chain_rpc_url: str - api_base_url: str - stream_url: str - onboarding_url: str - signing_domain: str - starknet_domain: StarknetDomain - - asset_operations_contract: str - collateral_asset_contract: str - collateral_asset_on_chain_id: str - collateral_decimals: int - collateral_asset_id: str - - vault_asset_name: str - - -TESTNET_CONFIG = EndpointConfig( - chain_rpc_url="https://rpc.sepolia.org", - api_base_url="https://api.starknet.sepolia.extended.exchange/api/v1", - stream_url="wss://api.starknet.sepolia.extended.exchange/stream.extended.exchange/v1", - onboarding_url="https://api.starknet.sepolia.extended.exchange", - signing_domain="starknet.sepolia.extended.exchange", - starknet_domain=StarknetDomain(name="Perpetuals", version="v0", chain_id="SN_SEPOLIA", revision="1"), - asset_operations_contract="", - collateral_asset_contract="0x05ba91db44b3e6a4485b5dbfcb17d791faa9cb6890a42731b66b3536b28b8ed5", - collateral_asset_on_chain_id="0x1", - collateral_decimals=6, - collateral_asset_id="0x1", - vault_asset_name="XVS", -) - -MAINNET_CONFIG = EndpointConfig( - chain_rpc_url="", - api_base_url="https://api.starknet.extended.exchange/api/v1", - stream_url="wss://api.starknet.extended.exchange/stream.extended.exchange/v1", - onboarding_url="https://api.starknet.extended.exchange", - signing_domain="extended.exchange", - starknet_domain=StarknetDomain(name="Perpetuals", version="v0", chain_id="SN_MAIN", revision="1"), - asset_operations_contract="", - collateral_asset_contract="", - collateral_asset_on_chain_id="0x1", - collateral_decimals=6, - collateral_asset_id="0x1", - vault_asset_name="XVS", -) diff --git a/x10/perpetual/limit_order_object_settlement.py b/x10/perpetual/limit_order_object_settlement.py index 1476b99..cd48ffb 100644 --- a/x10/perpetual/limit_order_object_settlement.py +++ b/x10/perpetual/limit_order_object_settlement.py @@ -1,12 +1,12 @@ import decimal from datetime import timedelta +from x10.config import StarknetDomain from x10.core.amount import HumanReadableAmount, StarkAmount from x10.core.stark_account import StarkPerpetualAccount from x10.models.asset import Asset, AssetModel from x10.models.base import SettlementSignatureModel from x10.models.order import LimitOrderSettlementModel -from x10.perpetual.configuration import StarknetDomain from x10.perpetual.order_object_settlement import ( calculate_order_settlement_expiration, hash_limit_order, diff --git a/x10/perpetual/order_object.py b/x10/perpetual/order_object.py index 70b3b38..5cd275c 100644 --- a/x10/perpetual/order_object.py +++ b/x10/perpetual/order_object.py @@ -3,9 +3,8 @@ from decimal import Decimal from typing import Callable, Optional, Tuple -from x10.config import DEFAULT_FEES +from x10.config import StarknetDomain from x10.core.stark_account import StarkPerpetualAccount -from x10.models.fee import TradingFeeModel from x10.models.market import MarketModel from x10.models.order import ( CreateOrderConditionalTriggerModel, @@ -20,7 +19,6 @@ SelfTradeProtectionLevel, TimeInForce, ) -from x10.perpetual.configuration import StarknetDomain from x10.perpetual.order_object_settlement import ( SettlementDataCtx, create_order_settlement_data, @@ -29,6 +27,8 @@ from x10.utils.nonce import generate_nonce from x10.utils.order import calc_entire_position_size +DEFAULT_TAKER_FEE = Decimal("0.0005") + @dataclass(kw_only=True) class OrderConditionalTriggerParam: @@ -62,6 +62,7 @@ def create_order_object( time_in_force: TimeInForce = TimeInForce.GTT, self_trade_protection_level: SelfTradeProtectionLevel = SelfTradeProtectionLevel.ACCOUNT, nonce: Optional[int] = None, + taker_fee: Optional[Decimal] = None, builder_fee: Optional[Decimal] = None, builder_id: Optional[int] = None, reduce_only: bool = False, @@ -77,8 +78,6 @@ def create_order_object( if expire_time is None: expire_time = utc_now() + timedelta(hours=1) - fees = account.trading_fee.get(market.name, DEFAULT_FEES) - return __create_order_object( market=market, order_type=order_type, @@ -86,7 +85,6 @@ def create_order_object( price=price, side=side, collateral_position_id=account.vault, - fees=fees, signer=account.sign, public_key=account.public_key, exact_only=False, @@ -98,6 +96,7 @@ def create_order_object( self_trade_protection_level=self_trade_protection_level, starknet_domain=starknet_domain, nonce=nonce, + taker_fee=taker_fee, builder_fee=builder_fee, builder_id=builder_id, reduce_only=reduce_only, @@ -156,7 +155,6 @@ def __create_order_object( price: Decimal, side: OrderSide, collateral_position_id: int, - fees: TradingFeeModel, signer: Callable[[int], Tuple[int, int]], public_key: int, starknet_domain: StarknetDomain, @@ -168,6 +166,7 @@ def __create_order_object( time_in_force: TimeInForce = TimeInForce.GTT, self_trade_protection_level: SelfTradeProtectionLevel = SelfTradeProtectionLevel.ACCOUNT, nonce: Optional[int] = None, + taker_fee: Optional[Decimal] = None, builder_fee: Optional[Decimal] = None, builder_id: Optional[int] = None, reduce_only: bool = False, @@ -222,11 +221,12 @@ def validate_tpsl_order(): if nonce is None: nonce = generate_nonce() - fee_rate = fees.taker_fee_rate + if taker_fee is None: + taker_fee = DEFAULT_TAKER_FEE settlement_data_ctx = SettlementDataCtx( market=market, - fees=fees, + taker_fee=taker_fee, builder_fee=builder_fee, nonce=nonce, collateral_position_id=collateral_position_id, @@ -267,7 +267,7 @@ def create_tpsl_trigger_model(trigger_param: OrderTpslTriggerParam | None): post_only=post_only, time_in_force=time_in_force, expiry_epoch_millis=to_epoch_millis(expire_time), - fee=fee_rate, + fee=taker_fee, self_trade_protection_level=self_trade_protection_level, nonce=Decimal(nonce), cancel_id=previous_order_external_id, diff --git a/x10/perpetual/order_object_settlement.py b/x10/perpetual/order_object_settlement.py index 106e79b..ea0a101 100644 --- a/x10/perpetual/order_object_settlement.py +++ b/x10/perpetual/order_object_settlement.py @@ -6,6 +6,7 @@ from fast_stark_crypto import get_limit_order_msg_hash, get_order_msg_hash +from x10.config import StarknetDomain from x10.core.amount import ( ROUNDING_BUY_CONTEXT, ROUNDING_FEE_CONTEXT, @@ -14,14 +15,12 @@ StarkAmount, ) from x10.models.base import SettlementSignatureModel -from x10.models.fee import TradingFeeModel from x10.models.market import MarketModel from x10.models.order import ( OrderSide, StarkDebuggingOrderAmountsModel, StarkSettlementModel, ) -from x10.perpetual.configuration import StarknetDomain @dataclass(kw_only=True) @@ -35,7 +34,7 @@ class OrderSettlementData: @dataclass(kw_only=True) class SettlementDataCtx: market: MarketModel - fees: TradingFeeModel + taker_fee: Decimal builder_fee: Optional[Decimal] nonce: int collateral_position_id: int @@ -127,7 +126,7 @@ def create_order_settlement_data( synthetic_amount_human = HumanReadableAmount(synthetic_amount, ctx.market.synthetic_asset) collateral_amount_human = HumanReadableAmount(synthetic_amount * price, ctx.market.collateral_asset) - total_fee = ctx.fees.taker_fee_rate + (ctx.builder_fee if ctx.builder_fee is not None else 0) + total_fee = ctx.taker_fee + (ctx.builder_fee if ctx.builder_fee is not None else 0) fee_amount_human = HumanReadableAmount( total_fee * collateral_amount_human.value, ctx.market.collateral_asset, diff --git a/x10/perpetual/orderbook.py b/x10/perpetual/orderbook.py index 8e58279..64123ca 100644 --- a/x10/perpetual/orderbook.py +++ b/x10/perpetual/orderbook.py @@ -6,8 +6,8 @@ from sortedcontainers import SortedDict +from x10.config import Config from x10.models.orderbook import OrderbookUpdateModel -from x10.perpetual.configuration import EndpointConfig from x10.perpetual.stream_client.stream_client import PerpetualStreamClient from x10.utils.http import StreamDataType @@ -30,27 +30,27 @@ class ImpactDetails: class OrderBook: @staticmethod async def create( - endpoint_config: EndpointConfig, + config: Config, market_name: str, best_ask_change_callback: Callable[[OrderBookEntry | None], Awaitable[None]] | None = None, best_bid_change_callback: Callable[[OrderBookEntry | None], Awaitable[None]] | None = None, start=False, depth: int | None = None, ) -> "OrderBook": - ob = OrderBook(endpoint_config, market_name, best_ask_change_callback, best_bid_change_callback, depth) + ob = OrderBook(config, market_name, best_ask_change_callback, best_bid_change_callback, depth) if start: await ob.start_orderbook() return ob def __init__( self, - endpoint_config: EndpointConfig, + config: Config, market_name: str, best_ask_change_callback: Callable[[OrderBookEntry | None], Awaitable[None]] | None = None, best_bid_change_callback: Callable[[OrderBookEntry | None], Awaitable[None]] | None = None, depth: int | None = None, ) -> None: - self.__stream_client = PerpetualStreamClient(api_url=endpoint_config.stream_url) + self.__stream_client = PerpetualStreamClient(api_url=config.endpoints.stream_url) self.__market_name = market_name self.__task: asyncio.Task | None = None self._bid_prices: "SortedDict[decimal.Decimal, OrderBookEntry]" = SortedDict() # type: ignore diff --git a/x10/perpetual/simple_client/simple_trading_client.py b/x10/perpetual/simple_client/simple_trading_client.py index 8b8b2d5..58e6c17 100644 --- a/x10/perpetual/simple_client/simple_trading_client.py +++ b/x10/perpetual/simple_client/simple_trading_client.py @@ -4,6 +4,7 @@ from decimal import Decimal from typing import Awaitable, Dict, Union, cast +from x10.config import Config from x10.core.stark_account import StarkPerpetualAccount from x10.models.account import AccountStreamDataModel from x10.models.market import MarketModel @@ -15,7 +16,6 @@ OrderType, TimeInForce, ) -from x10.perpetual.configuration import EndpointConfig from x10.perpetual.order_object import create_order_object from x10.perpetual.stream_client.perpetual_stream_connection import ( PerpetualStreamConnection, @@ -74,17 +74,17 @@ class CancelWaiter: class BlockingTradingClient: - def __init__(self, endpoint_config: EndpointConfig, account: StarkPerpetualAccount): + def __init__(self, config: Config, account: StarkPerpetualAccount): if not asyncio.get_event_loop().is_running(): raise RuntimeError( "BlockingTradingClient must be initialized from an async function, use BlockingTradingClient.create()" ) - self.__endpoint_config = endpoint_config + self.__config = config self.__account = account - self.__market_module = InfoMarketsModule(endpoint_config, api_key=account.api_key) - self.__orders_module = OrderManagementModule(endpoint_config, api_key=account.api_key) + self.__market_module = InfoMarketsModule(config, api_key=account.api_key) + self.__orders_module = OrderManagementModule(config, api_key=account.api_key) self.__markets: Union[None, Dict[str, MarketModel]] = None - self.__stream_client: PerpetualStreamClient = PerpetualStreamClient(api_url=endpoint_config.stream_url) + self.__stream_client: PerpetualStreamClient = PerpetualStreamClient(api_url=config.endpoints.stream_url) self.__account_stream: Union[ None, PerpetualStreamConnection[WrappedStreamResponse[AccountStreamDataModel]], @@ -94,8 +94,8 @@ def __init__(self, endpoint_config: EndpointConfig, account: StarkPerpetualAccou self.__stream_task = asyncio.create_task(self.___order_stream()) @staticmethod - async def create(endpoint_config: EndpointConfig, account: StarkPerpetualAccount) -> "BlockingTradingClient": - client = BlockingTradingClient(endpoint_config, account) + async def create(config: Config, account: StarkPerpetualAccount) -> "BlockingTradingClient": + client = BlockingTradingClient(config, account) await client.__stream_client.subscribe_to_account_updates(account.api_key) return client @@ -197,6 +197,7 @@ async def create_and_place_order( amount_of_synthetic: Decimal, price: Decimal, side: OrderSide, + taker_fee: Decimal, post_only: bool = False, previous_order_external_id: str | None = None, external_id: str | None = None, @@ -220,11 +221,12 @@ async def create_and_place_order( post_only=post_only, reduce_only=reduce_only, previous_order_external_id=previous_order_external_id, - starknet_domain=self.__endpoint_config.starknet_domain, + starknet_domain=self.__config.signing.starknet_domain, order_external_id=external_id, builder_fee=builder_fee, builder_id=builder_id, time_in_force=time_in_force, + taker_fee=taker_fee, ) if order.id in self.__order_waiters: @@ -252,3 +254,7 @@ async def close(self): self.__stream_task.cancel() if self.__account_stream: await self.__account_stream.close() + + @property + def config(self): + return self.__config diff --git a/x10/perpetual/stream_client/perpetual_stream_connection.py b/x10/perpetual/stream_client/perpetual_stream_connection.py index 706d10a..f379a4b 100644 --- a/x10/perpetual/stream_client/perpetual_stream_connection.py +++ b/x10/perpetual/stream_client/perpetual_stream_connection.py @@ -4,9 +4,8 @@ import websockets from websockets import WebSocketClientProtocol -from x10.config import USER_AGENT from x10.models.base import X10BaseModel -from x10.utils.http import RequestHeader +from x10.utils.http import USER_AGENT, RequestHeader from x10.utils.log import get_logger LOGGER = get_logger(__name__) diff --git a/x10/perpetual/trading_client/base_module.py b/x10/perpetual/trading_client/base_module.py index 650e35b..1e60bbb 100644 --- a/x10/perpetual/trading_client/base_module.py +++ b/x10/perpetual/trading_client/base_module.py @@ -1,37 +1,38 @@ from typing import Dict, Optional import aiohttp +from aiohttp import ClientTimeout +from x10.config import Config from x10.core.stark_account import StarkPerpetualAccount from x10.errors import X10Error -from x10.perpetual.configuration import EndpointConfig -from x10.utils.http import CLIENT_TIMEOUT, get_url +from x10.utils.http import get_url class BaseModule: - __endpoint_config: EndpointConfig + __config: Config __api_key: Optional[str] __stark_account: Optional[StarkPerpetualAccount] __session: Optional[aiohttp.ClientSession] def __init__( self, - endpoint_config: EndpointConfig, + config: Config, *, api_key: Optional[str] = None, stark_account: Optional[StarkPerpetualAccount] = None, ): super().__init__() - self.__endpoint_config = endpoint_config + self.__config = config self.__api_key = api_key self.__stark_account = stark_account self.__session = None def _get_url(self, path: str, *, query: Optional[Dict] = None, **path_params) -> str: - return get_url(f"{self.__endpoint_config.api_base_url}{path}", query=query, **path_params) + return get_url(f"{self._get_endpoint_config().api_base_url}{path}", query=query, **path_params) - def _get_endpoint_config(self) -> EndpointConfig: - return self.__endpoint_config + def _get_endpoint_config(self): + return self.__config.endpoints def _get_api_key(self): if not self.__api_key: @@ -47,7 +48,9 @@ def _get_stark_account(self): async def get_session(self) -> aiohttp.ClientSession: if self.__session is None: - created_session = aiohttp.ClientSession(timeout=CLIENT_TIMEOUT) + created_session = aiohttp.ClientSession( + timeout=ClientTimeout(total=self.__config.defaults.request_timeout_seconds) + ) self.__session = created_session return self.__session diff --git a/x10/perpetual/trading_client/testnet_module.py b/x10/perpetual/trading_client/testnet_module.py index f119f75..220ff67 100644 --- a/x10/perpetual/trading_client/testnet_module.py +++ b/x10/perpetual/trading_client/testnet_module.py @@ -2,9 +2,9 @@ import tenacity +from x10.config import Config from x10.models.asset import AssetOperationModel, AssetOperationStatus from x10.models.base import X10BaseModel -from x10.perpetual.configuration import EndpointConfig from x10.perpetual.trading_client.account_module import AccountModule from x10.perpetual.trading_client.base_module import BaseModule from x10.utils.http import WrappedApiResponse, send_post_request @@ -17,11 +17,11 @@ class ClaimResponseModel(X10BaseModel): class TestnetModule(BaseModule): def __init__( self, - endpoint_config: EndpointConfig, + config: Config, api_key: Optional[str] = None, account_module: Optional[AccountModule] = None, ): - super().__init__(endpoint_config, api_key=api_key) + super().__init__(config, api_key=api_key) self._account_module = account_module async def claim_testing_funds( diff --git a/x10/perpetual/trading_client/trading_client.py b/x10/perpetual/trading_client/trading_client.py index 60aeed1..a1804ab 100644 --- a/x10/perpetual/trading_client/trading_client.py +++ b/x10/perpetual/trading_client/trading_client.py @@ -2,6 +2,7 @@ from decimal import Decimal from typing import Dict, Optional +from x10.config import Config from x10.core.stark_account import StarkPerpetualAccount from x10.models.market import MarketModel from x10.models.order import ( @@ -11,7 +12,6 @@ SelfTradeProtectionLevel, TimeInForce, ) -from x10.perpetual.configuration import EndpointConfig from x10.perpetual.order_object import OrderTpslTriggerParam, create_order_object from x10.perpetual.trading_client.account_module import AccountModule from x10.perpetual.trading_client.info_markets_module import InfoMarketsModule @@ -33,7 +33,7 @@ class PerpetualTradingClient: __markets: Dict[str, MarketModel] | None - __config: EndpointConfig + __config: Config __stark_account: StarkPerpetualAccount | None __info_module: InfoModule @@ -49,6 +49,7 @@ async def place_order( amount_of_synthetic: Decimal, price: Decimal, side: OrderSide, + taker_fee: Decimal, post_only: bool = False, previous_order_id=None, expire_time: Optional[datetime] = None, @@ -87,8 +88,9 @@ async def place_order( expire_time=expire_time, time_in_force=time_in_force, self_trade_protection_level=self_trade_protection_level, - starknet_domain=self.__config.starknet_domain, + starknet_domain=self.__config.signing.starknet_domain, order_external_id=external_id, + taker_fee=taker_fee, builder_fee=builder_fee, builder_id=builder_id, reduce_only=reduce_only, @@ -109,25 +111,25 @@ async def __aenter__(self): async def __aexit__(self, exc_type, exc_value, traceback): await self.close() - def __init__(self, endpoint_config: EndpointConfig, stark_account: StarkPerpetualAccount | None = None): + def __init__(self, config: Config, stark_account: StarkPerpetualAccount | None = None): api_key = stark_account.api_key if stark_account else None - self.__config = endpoint_config + self.__config = config self.__markets = None self.__stark_account = stark_account - self.__info_module = InfoModule(endpoint_config) - self.__info_markets_module = InfoMarketsModule(endpoint_config, api_key=api_key) - self.__account_module = AccountModule(endpoint_config, api_key=api_key, stark_account=stark_account) - self.__order_management_module = OrderManagementModule(endpoint_config, api_key=api_key) + self.__info_module = InfoModule(config) + self.__info_markets_module = InfoMarketsModule(config, api_key=api_key) + self.__account_module = AccountModule(config, api_key=api_key, stark_account=stark_account) + self.__order_management_module = OrderManagementModule(config, api_key=api_key) self.__vault_module = VaultModule( - endpoint_config, + config, info_module=self.__info_module, account_module=self.__account_module, account=stark_account, api_key=api_key, ) - self.__testnet_module = TestnetModule(endpoint_config, api_key=api_key, account_module=self.__account_module) + self.__testnet_module = TestnetModule(config, api_key=api_key, account_module=self.__account_module) @property def config(self): diff --git a/x10/perpetual/trading_client/vault_module.py b/x10/perpetual/trading_client/vault_module.py index 6392e3f..7eaea03 100644 --- a/x10/perpetual/trading_client/vault_module.py +++ b/x10/perpetual/trading_client/vault_module.py @@ -3,11 +3,11 @@ from types import NoneType from typing import Optional +from x10.config import Config from x10.core.stark_account import StarkPerpetualAccount from x10.errors import X10Error from x10.models.base import X10BaseModel from x10.models.order import LimitOrderSettlementModel -from x10.perpetual.configuration import EndpointConfig from x10.perpetual.limit_order_object_settlement import create_order_settlement_data from x10.perpetual.trading_client.account_module import AccountModule from x10.perpetual.trading_client.base_module import BaseModule @@ -38,14 +38,14 @@ class WithdrawRequestModel(X10BaseModel): class VaultModule(BaseModule): def __init__( self, - endpoint_config: EndpointConfig, + config: Config, *, info_module: InfoModule, account_module: AccountModule, account: Optional[StarkPerpetualAccount] = None, api_key: Optional[str] = None, ): - super().__init__(endpoint_config, api_key=api_key) + super().__init__(config, api_key=api_key) self._info_module = info_module self._account_module = account_module diff --git a/x10/perpetual/transfer_object.py b/x10/perpetual/transfer_object.py index b0f8979..6523cab 100644 --- a/x10/perpetual/transfer_object.py +++ b/x10/perpetual/transfer_object.py @@ -5,6 +5,7 @@ from fast_stark_crypto import get_transfer_msg_hash +from x10.config import Config, StarknetDomain from x10.core.stark_account import StarkPerpetualAccount from x10.models.account import AccountModel from x10.models.base import SettlementSignatureModel @@ -12,7 +13,6 @@ OnChainPerpetualTransferModel, StarkTransferSettlementModel, ) -from x10.perpetual.configuration import EndpointConfig, StarknetDomain from x10.utils.date import utc_now from x10.utils.nonce import generate_nonce @@ -34,14 +34,14 @@ def create_transfer_object( to_vault: int, to_l2_key: int, amount: Decimal, - config: EndpointConfig, + config: Config, stark_account: StarkPerpetualAccount, nonce: int | None = None, ) -> OnChainPerpetualTransferModel: expiration_timestamp = calc_expiration_timestamp() - scaled_amount = amount.scaleb(config.collateral_decimals) + scaled_amount = amount.scaleb(config.endpoints.collateral_decimals) stark_amount = scaled_amount.to_integral_exact() - starknet_domain: StarknetDomain = config.starknet_domain + starknet_domain: StarknetDomain = config.signing.starknet_domain if nonce is None: nonce = generate_nonce() @@ -57,13 +57,13 @@ def create_transfer_object( domain_version=starknet_domain.version, domain_chain_id=starknet_domain.chain_id, domain_revision=starknet_domain.revision, - collateral_id=int(config.collateral_asset_on_chain_id, base=16), + collateral_id=int(config.endpoints.collateral_asset_on_chain_id, base=16), ) (transfer_signature_r, transfer_signature_s) = stark_account.sign(transfer_hash) settlement = StarkTransferSettlementModel( amount=int(stark_amount), - asset_id=int(config.collateral_asset_on_chain_id, base=16), + asset_id=int(config.endpoints.collateral_asset_on_chain_id, base=16), expiration_timestamp=expiration_timestamp, nonce=nonce, receiver_position_id=to_vault, @@ -78,5 +78,5 @@ def create_transfer_object( to_vault=to_vault, amount=amount, settlement=settlement, - transferred_asset=config.collateral_asset_on_chain_id, + transferred_asset=config.endpoints.collateral_asset_on_chain_id, ) diff --git a/x10/perpetual/user_client/user_client.py b/x10/perpetual/user_client/user_client.py index af6e91a..e68a4c6 100644 --- a/x10/perpetual/user_client/user_client.py +++ b/x10/perpetual/user_client/user_client.py @@ -3,13 +3,14 @@ from typing import Callable, Dict, List, Optional import aiohttp +from aiohttp import ClientTimeout from eth_account import Account from eth_account.messages import encode_defunct from eth_account.signers.local import LocalAccount +from x10.config import Config from x10.errors import X10Error from x10.models.account import AccountModel, ApiKeyRequestModel, ApiKeyResponseModel -from x10.perpetual.configuration import EndpointConfig from x10.perpetual.user_client.onboarding import ( OnboardedClientModel, StarkKeyPair, @@ -17,7 +18,7 @@ get_onboarding_payload, get_sub_account_creation_payload, ) -from x10.utils.http import CLIENT_TIMEOUT, get_url, send_get_request, send_post_request +from x10.utils.http import get_url, send_get_request, send_post_request L1_AUTH_SIGNATURE_HEADER = "L1_SIGNATURE" L1_MESSAGE_TIME_HEADER = "L1_MESSAGE_TIME" @@ -35,17 +36,17 @@ class OnBoardedAccount: class UserClient: - __endpoint_config: EndpointConfig + __config: Config __l1_private_key: Callable[[], str] __session: Optional[aiohttp.ClientSession] = None def __init__( self, - endpoint_config: EndpointConfig, + config: Config, l1_private_key: Callable[[], str], ): super().__init__() - self.__endpoint_config = endpoint_config + self.__config = config self.__l1_private_key = l1_private_key def _get_url(self, base_url: str, path: str, *, query: Optional[Dict] = None, **path_params) -> str: @@ -53,7 +54,9 @@ def _get_url(self, base_url: str, path: str, *, query: Optional[Dict] = None, ** async def get_session(self) -> aiohttp.ClientSession: if self.__session is None: - created_session = aiohttp.ClientSession(timeout=CLIENT_TIMEOUT) + created_session = aiohttp.ClientSession( + timeout=ClientTimeout(total=self.__config.defaults.request_timeout_seconds) + ) self.__session = created_session return self.__session @@ -66,16 +69,16 @@ async def close_session(self): async def onboard(self, referral_code: Optional[str] = None): signing_account: LocalAccount = Account.from_key(self.__l1_private_key()) key_pair = get_l2_keys_from_l1_account( - l1_account=signing_account, account_index=0, signing_domain=self.__endpoint_config.signing_domain + l1_account=signing_account, account_index=0, signing_domain=self.__config.signing.signing_domain ) payload = get_onboarding_payload( signing_account, - signing_domain=self.__endpoint_config.signing_domain, + signing_domain=self.__config.signing.signing_domain, key_pair=key_pair, referral_code=referral_code, - host=self.__endpoint_config.onboarding_url, + host=self._get_endpoint_config().onboarding_url, ) - url = self._get_url(self.__endpoint_config.onboarding_url, path="/auth/onboard") + url = self._get_url(self._get_endpoint_config().onboarding_url, path="/auth/onboard") onboarding_response = await send_post_request( await self.get_session(), url, OnboardedClientModel, json=payload.to_json() ) @@ -100,20 +103,20 @@ async def onboard_subaccount(self, account_index: int, description: str | None = key_pair = get_l2_keys_from_l1_account( l1_account=signing_account, account_index=account_index, - signing_domain=self.__endpoint_config.signing_domain, + signing_domain=self.__config.signing.signing_domain, ) payload = get_sub_account_creation_payload( account_index=account_index, l1_address=signing_account.address, key_pair=key_pair, description=description, - host=self.__endpoint_config.onboarding_url, + host=self._get_endpoint_config().onboarding_url, ) headers = { L1_AUTH_SIGNATURE_HEADER: l1_signature.signature.hex(), L1_MESSAGE_TIME_HEADER: auth_time_string, } - url = self._get_url(self.__endpoint_config.onboarding_url, path=request_path) + url = self._get_url(self._get_endpoint_config().onboarding_url, path=request_path) try: onboarding_response = await send_post_request( @@ -149,7 +152,7 @@ async def get_accounts(self) -> List[OnBoardedAccount]: L1_AUTH_SIGNATURE_HEADER: l1_signature.signature.hex(), L1_MESSAGE_TIME_HEADER: auth_time_string, } - url = self._get_url(self.__endpoint_config.onboarding_url, path=request_path) + url = self._get_url(self._get_endpoint_config().onboarding_url, path=request_path) response = await send_get_request(await self.get_session(), url, List[AccountModel], request_headers=headers) accounts = response.data or [] @@ -159,7 +162,7 @@ async def get_accounts(self) -> List[OnBoardedAccount]: l2_key_pair=get_l2_keys_from_l1_account( l1_account=signing_account, account_index=account.account_index, - signing_domain=self.__endpoint_config.signing_domain, + signing_domain=self.__config.signing.signing_domain, ), ) for account in accounts @@ -181,7 +184,7 @@ async def create_account_api_key(self, account: AccountModel, description: str | L1_MESSAGE_TIME_HEADER: auth_time_string, ACTIVE_ACCOUNT_HEADER: str(account.id), } - url = self._get_url(self.__endpoint_config.onboarding_url, path=request_path) + url = self._get_url(self._get_endpoint_config().onboarding_url, path=request_path) request = ApiKeyRequestModel(description=description) response = await send_post_request( await self.get_session(), @@ -194,3 +197,6 @@ async def create_account_api_key(self, account: AccountModel, description: str | if response_data is None: raise ValueError("No API key data returned from onboarding") return response_data.key + + def _get_endpoint_config(self): + return self.__config.endpoints diff --git a/x10/perpetual/withdrawal_object.py b/x10/perpetual/withdrawal_object.py index c0a86b6..40ceb89 100644 --- a/x10/perpetual/withdrawal_object.py +++ b/x10/perpetual/withdrawal_object.py @@ -4,6 +4,7 @@ from fast_stark_crypto import get_withdrawal_msg_hash +from x10.config import Config, StarknetDomain from x10.core.stark_account import StarkPerpetualAccount from x10.models.base import SettlementSignatureModel from x10.models.withdrawal import ( @@ -11,7 +12,6 @@ TimestampModel, WithdrawalRequestModel, ) -from x10.perpetual.configuration import EndpointConfig, StarknetDomain from x10.utils.date import utc_now from x10.utils.nonce import generate_nonce @@ -27,7 +27,7 @@ def create_withdrawal_object( amount: Decimal, recipient_stark_address: str, stark_account: StarkPerpetualAccount, - config: EndpointConfig, + config: Config, account_id: int, chain_id: str, description: str | None = None, @@ -35,9 +35,9 @@ def create_withdrawal_object( quote_id: str | None = None, ) -> WithdrawalRequestModel: expiration_timestamp = calc_expiration_timestamp() - scaled_amount = amount.scaleb(config.collateral_decimals) + scaled_amount = amount.scaleb(config.endpoints.collateral_decimals) stark_amount = scaled_amount.to_integral_exact() - starknet_domain: StarknetDomain = config.starknet_domain + starknet_domain: StarknetDomain = config.signing.starknet_domain if nonce is None: nonce = generate_nonce() @@ -53,7 +53,7 @@ def create_withdrawal_object( domain_version=starknet_domain.version, domain_chain_id=starknet_domain.chain_id, domain_revision=starknet_domain.revision, - collateral_id=int(config.collateral_asset_on_chain_id, base=16), + collateral_id=int(config.endpoints.collateral_asset_on_chain_id, base=16), ) (transfer_signature_r, transfer_signature_s) = stark_account.sign(withdrawal_hash) @@ -61,7 +61,7 @@ def create_withdrawal_object( settlement = StarkWithdrawalSettlementModel( recipient=int(recipient_stark_address, 16), position_id=stark_account.vault, - collateral_id=int(config.collateral_asset_on_chain_id, base=16), + collateral_id=int(config.endpoints.collateral_asset_on_chain_id, base=16), amount=int(stark_amount), expiration=TimestampModel(seconds=expiration_timestamp), salt=nonce, diff --git a/x10/utils/http.py b/x10/utils/http.py index 20ca774..e2aa049 100644 --- a/x10/utils/http.py +++ b/x10/utils/http.py @@ -4,18 +4,18 @@ from typing import Any, Dict, Generic, List, Optional, Sequence, Type, TypeVar, Union import aiohttp -from aiohttp import ClientResponse, ClientTimeout +from aiohttp import ClientResponse from pydantic import GetCoreSchemaHandler from pydantic_core import CoreSchema, core_schema from strenum import StrEnum -from x10.config import DEFAULT_REQUEST_TIMEOUT_SECONDS, USER_AGENT from x10.errors import X10Error from x10.models.base import X10BaseModel from x10.utils.log import get_logger +from x10.version import SDK_VERSION LOGGER = get_logger(__name__) -CLIENT_TIMEOUT = ClientTimeout(total=DEFAULT_REQUEST_TIMEOUT_SECONDS) +USER_AGENT = f"X10PythonTradingClient/{SDK_VERSION}" ApiResponseType = TypeVar("ApiResponseType", bound=Union[int, X10BaseModel, Sequence[X10BaseModel], None]) diff --git a/x10/version.py b/x10/version.py new file mode 100644 index 0000000..36b1fc8 --- /dev/null +++ b/x10/version.py @@ -0,0 +1,3 @@ +import importlib.metadata + +SDK_VERSION = importlib.metadata.version("x10-python-trading-starknet")