x10-python-trading-starknet 1.4.0__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/PKG-INFO +5 -5
  2. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/README.md +1 -1
  3. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/pyproject.toml +5 -5
  4. x10_python_trading_starknet-2.0.0/x10/clients/blocking/__init__.py +3 -0
  5. x10_python_trading_starknet-1.4.0/x10/perpetual/simple_client/simple_trading_client.py → x10_python_trading_starknet-2.0.0/x10/clients/blocking/blocking_trading_client.py +30 -29
  6. x10_python_trading_starknet-2.0.0/x10/clients/onboarding/__init__.py +1 -0
  7. x10_python_trading_starknet-2.0.0/x10/clients/onboarding/modules/account_module.py +32 -0
  8. x10_python_trading_starknet-2.0.0/x10/clients/onboarding/modules/auth_module.py +89 -0
  9. x10_python_trading_starknet-2.0.0/x10/clients/onboarding/modules/base_module.py +51 -0
  10. x10_python_trading_starknet-2.0.0/x10/clients/onboarding/onboarding_client.py +33 -0
  11. x10_python_trading_starknet-2.0.0/x10/clients/rest/__init__.py +1 -0
  12. {x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client → x10_python_trading_starknet-2.0.0/x10/clients/rest/modules}/account_module.py +71 -60
  13. {x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client → x10_python_trading_starknet-2.0.0/x10/clients/rest/modules}/base_module.py +8 -7
  14. x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client/info_markets_module.py → x10_python_trading_starknet-2.0.0/x10/clients/rest/modules/info_module.py +26 -7
  15. {x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client → x10_python_trading_starknet-2.0.0/x10/clients/rest/modules}/order_management_module.py +8 -15
  16. {x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client → x10_python_trading_starknet-2.0.0/x10/clients/rest/modules}/testnet_module.py +11 -13
  17. {x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client → x10_python_trading_starknet-2.0.0/x10/clients/rest/modules}/vault_module.py +22 -39
  18. x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client/trading_client.py → x10_python_trading_starknet-2.0.0/x10/clients/rest/rest_api_client.py +18 -21
  19. x10_python_trading_starknet-2.0.0/x10/clients/stream/__init__.py +2 -0
  20. {x10_python_trading_starknet-1.4.0/x10/perpetual/stream_client → x10_python_trading_starknet-2.0.0/x10/clients/stream}/stream_client.py +13 -15
  21. x10_python_trading_starknet-1.4.0/x10/perpetual/stream_client/perpetual_stream_connection.py → x10_python_trading_starknet-2.0.0/x10/clients/stream/stream_connection.py +8 -10
  22. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/core/amount.py +3 -3
  23. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/core/stark_account.py +2 -1
  24. x10_python_trading_starknet-2.0.0/x10/errors.py +22 -0
  25. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/asset.py +3 -2
  26. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/client.py +6 -0
  27. x10_python_trading_starknet-2.0.0/x10/models/http.py +60 -0
  28. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/order.py +8 -1
  29. x10_python_trading_starknet-2.0.0/x10/models/settings.py +5 -0
  30. x10_python_trading_starknet-2.0.0/x10/models/testnet.py +5 -0
  31. x10_python_trading_starknet-2.0.0/x10/models/vault.py +20 -0
  32. {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/signing}/limit_order_object_settlement.py +5 -5
  33. {x10_python_trading_starknet-1.4.0/x10/perpetual/user_client → x10_python_trading_starknet-2.0.0/x10/signing}/onboarding.py +55 -26
  34. {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/signing}/order_object.py +16 -15
  35. {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/signing}/order_object_settlement.py +8 -13
  36. {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/signing}/transfer_object.py +4 -11
  37. {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/signing}/withdrawal_object.py +3 -10
  38. x10_python_trading_starknet-2.0.0/x10/tools/__init__.py +0 -0
  39. {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/tools}/orderbook.py +22 -23
  40. x10_python_trading_starknet-2.0.0/x10/utils/__init__.py +0 -0
  41. x10_python_trading_starknet-2.0.0/x10/utils/date.py +23 -0
  42. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/utils/http.py +36 -79
  43. x10_python_trading_starknet-1.4.0/x10/errors.py +0 -2
  44. x10_python_trading_starknet-1.4.0/x10/perpetual/stream_client/__init__.py +0 -3
  45. x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client/__init__.py +0 -3
  46. x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client/info_module.py +0 -29
  47. x10_python_trading_starknet-1.4.0/x10/perpetual/user_client/user_client.py +0 -202
  48. x10_python_trading_starknet-1.4.0/x10/utils/date.py +0 -13
  49. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/LICENSE +0 -0
  50. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/__init__.py +0 -0
  51. {x10_python_trading_starknet-1.4.0/x10/core → x10_python_trading_starknet-2.0.0/x10/clients}/__init__.py +0 -0
  52. {x10_python_trading_starknet-1.4.0/x10/models → x10_python_trading_starknet-2.0.0/x10/clients/onboarding/modules}/__init__.py +0 -0
  53. {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/clients/rest/modules}/__init__.py +0 -0
  54. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/config.py +0 -0
  55. {x10_python_trading_starknet-1.4.0/x10/perpetual/simple_client → x10_python_trading_starknet-2.0.0/x10/core}/__init__.py +0 -0
  56. {x10_python_trading_starknet-1.4.0/x10/perpetual/user_client → x10_python_trading_starknet-2.0.0/x10/models}/__init__.py +0 -0
  57. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/account.py +4 -4
  58. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/balance.py +0 -0
  59. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/base.py +0 -0
  60. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/bridge.py +0 -0
  61. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/candle.py +0 -0
  62. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/fee.py +0 -0
  63. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/funding_rate.py +0 -0
  64. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/market.py +0 -0
  65. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/orderbook.py +0 -0
  66. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/position.py +0 -0
  67. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/trade.py +0 -0
  68. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/transfer.py +0 -0
  69. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/withdrawal.py +0 -0
  70. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/py.typed +0 -0
  71. {x10_python_trading_starknet-1.4.0/x10/utils → x10_python_trading_starknet-2.0.0/x10/signing}/__init__.py +0 -0
  72. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/utils/log.py +0 -0
  73. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/utils/nonce.py +0 -0
  74. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/utils/order.py +0 -0
  75. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/utils/string.py +0 -0
  76. {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: x10-python-trading-starknet
3
- Version: 1.4.0
3
+ Version: 2.0.0
4
4
  Summary: Python client for X10 API
5
5
  Home-page: https://github.com/x10xchange/python_sdk
6
6
  Author: X10
@@ -20,9 +20,9 @@ Requires-Dist: fast-stark-crypto (==0.5.0)
20
20
  Requires-Dist: pydantic (>=2.9.0)
21
21
  Requires-Dist: pyyaml (>=6.0.1)
22
22
  Requires-Dist: sortedcontainers (>=2.4.0)
23
- Requires-Dist: strenum (>=0.4.15,<0.5.0)
24
- Requires-Dist: tenacity (>=9.1.2,<10.0.0)
25
- Requires-Dist: websockets (>=12.0,<14.0)
23
+ Requires-Dist: strenum (>=0.4.15)
24
+ Requires-Dist: tenacity (>=9.1.2)
25
+ Requires-Dist: websockets (>=12.0)
26
26
  Project-URL: Documentation, https://api.docs.extended.exchange/
27
27
  Project-URL: Repository, https://github.com/x10xchange/python_sdk
28
28
  Description-Content-Type: text/markdown
@@ -286,7 +286,7 @@ All new accounts should use the `MAINNET_CONFIG` configuration bundle.
286
286
 
287
287
  ## OnBoarding via SDK (Since Version 0.3.0)
288
288
 
289
- To onboard to the Extended Exchange, the `UserClient` defined in [user_client.py](x10/perpetual/user_client/user_client.py) provides a way to use an Ethereum account to onboard onto the Extended Exchange.
289
+ To onboard to the Extended Exchange, the `UserClient` defined in [user_client.py](x10/perpetual/user_client/user_client.py) provides a way to use an Ethereum account to onboard onto the Extended Exchange.
290
290
 
291
291
  ### TLDR - Check out: [onboarding_example.py](examples/onboarding_example.py)
292
292
 
@@ -257,7 +257,7 @@ All new accounts should use the `MAINNET_CONFIG` configuration bundle.
257
257
 
258
258
  ## OnBoarding via SDK (Since Version 0.3.0)
259
259
 
260
- To onboard to the Extended Exchange, the `UserClient` defined in [user_client.py](x10/perpetual/user_client/user_client.py) provides a way to use an Ethereum account to onboard onto the Extended Exchange.
260
+ To onboard to the Extended Exchange, the `UserClient` defined in [user_client.py](x10/perpetual/user_client/user_client.py) provides a way to use an Ethereum account to onboard onto the Extended Exchange.
261
261
 
262
262
  ### TLDR - Check out: [onboarding_example.py](examples/onboarding_example.py)
263
263
 
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
5
5
 
6
6
  [tool.poetry]
7
7
  name = "x10-python-trading-starknet"
8
- version = "1.4.0"
8
+ version = "2.0.0"
9
9
  description = "Python client for X10 API"
10
10
  authors = ["X10 <tech@ex10.org>"]
11
11
  repository = "https://github.com/x10xchange/python_sdk"
@@ -29,9 +29,9 @@ pydantic = ">=2.9.0"
29
29
  python = "^3.10"
30
30
  pyyaml = ">=6.0.1"
31
31
  sortedcontainers = ">=2.4.0"
32
- strenum = "^0.4.15"
33
- tenacity = "^9.1.2"
34
- websockets = ">=12.0,<14.0"
32
+ strenum = ">=0.4.15"
33
+ tenacity = ">=9.1.2"
34
+ websockets = ">=12.0"
35
35
 
36
36
  [tool.poetry.group.dev.dependencies]
37
37
  black = "==23.12.0"
@@ -52,7 +52,7 @@ python-dotenv = "==1.0.1"
52
52
  safety = "==3.5.1"
53
53
  tox = "==4.11.4"
54
54
  types-pyyaml = "==6.0.12.12"
55
- typing-extensions = ">=4.9.0"
55
+ typing-extensions = "==4.15.0"
56
56
 
57
57
 
58
58
  [tool.mypy]
@@ -0,0 +1,3 @@
1
+ from x10.clients.blocking.blocking_trading_client import ( # noqa: F401
2
+ BlockingTradingClient,
3
+ )
@@ -1,12 +1,17 @@
1
1
  import asyncio
2
- import dataclasses
3
2
  import time
3
+ from dataclasses import dataclass
4
4
  from decimal import Decimal
5
- from typing import Awaitable, Dict, Union, cast
5
+ from typing import Awaitable, Dict, Optional, cast
6
6
 
7
+ from x10.clients.rest.modules.info_module import InfoModule
8
+ from x10.clients.rest.modules.order_management_module import OrderManagementModule
9
+ from x10.clients.stream import StreamClient, StreamConnection
7
10
  from x10.config import Config
8
11
  from x10.core.stark_account import StarkPerpetualAccount
12
+ from x10.errors import SdkError, ValidationError
9
13
  from x10.models.account import AccountStreamDataModel
14
+ from x10.models.http import WrappedStreamResponseModel
10
15
  from x10.models.market import MarketModel
11
16
  from x10.models.order import (
12
17
  NewOrderModel,
@@ -16,14 +21,7 @@ from x10.models.order import (
16
21
  OrderType,
17
22
  TimeInForce,
18
23
  )
19
- from x10.perpetual.order_object import create_order_object
20
- from x10.perpetual.stream_client.perpetual_stream_connection import (
21
- PerpetualStreamConnection,
22
- )
23
- from x10.perpetual.stream_client.stream_client import PerpetualStreamClient
24
- from x10.perpetual.trading_client.info_markets_module import InfoMarketsModule
25
- from x10.perpetual.trading_client.order_management_module import OrderManagementModule
26
- from x10.utils.http import WrappedStreamResponse
24
+ from x10.signing.order_object import create_order_object
27
25
 
28
26
 
29
27
  def condition_to_awaitable(condition: asyncio.Condition) -> Awaitable:
@@ -52,21 +50,21 @@ class TimedOpenOrderModel(OpenOrderModel):
52
50
  )
53
51
 
54
52
 
55
- @dataclasses.dataclass
53
+ @dataclass
56
54
  class TimedCancel:
57
55
  start_nanos: int
58
56
  end_nanos: int
59
57
  operation_ms: float
60
58
 
61
59
 
62
- @dataclasses.dataclass
60
+ @dataclass
63
61
  class OrderWaiter:
64
62
  condition: asyncio.Condition
65
63
  open_order: None | TimedOpenOrderModel
66
64
  start_nanos: int
67
65
 
68
66
 
69
- @dataclasses.dataclass
67
+ @dataclass
70
68
  class CancelWaiter:
71
69
  condition: asyncio.Condition
72
70
  start_nanos: int
@@ -74,24 +72,27 @@ class CancelWaiter:
74
72
 
75
73
 
76
74
  class BlockingTradingClient:
75
+ """
76
+ A client for placing orders and receiving updates in a blocking manner.
77
+ Waits for the confirmation from the WS stream after placing or canceling an order.
78
+ """
79
+
77
80
  def __init__(self, config: Config, account: StarkPerpetualAccount):
78
81
  if not asyncio.get_event_loop().is_running():
79
- raise RuntimeError(
82
+ raise SdkError(
80
83
  "BlockingTradingClient must be initialized from an async function, use BlockingTradingClient.create()"
81
84
  )
85
+
82
86
  self.__config = config
83
87
  self.__account = account
84
- self.__market_module = InfoMarketsModule(config, api_key=account.api_key)
88
+ self.__info_module = InfoModule(config, api_key=account.api_key)
85
89
  self.__orders_module = OrderManagementModule(config, api_key=account.api_key)
86
- self.__markets: Union[None, Dict[str, MarketModel]] = None
87
- self.__stream_client: PerpetualStreamClient = PerpetualStreamClient(api_url=config.endpoints.stream_url)
88
- self.__account_stream: Union[
89
- None,
90
- PerpetualStreamConnection[WrappedStreamResponse[AccountStreamDataModel]],
91
- ] = None
90
+ self.__markets: Optional[Dict[str, MarketModel]] = None
91
+ self.__stream_client: StreamClient = StreamClient(api_url=config.endpoints.stream_url)
92
+ self.__account_stream: Optional[StreamConnection[WrappedStreamResponseModel[AccountStreamDataModel]]] = None
92
93
  self.__order_waiters: Dict[str, OrderWaiter] = {}
93
94
  self.__cancel_waiters: Dict[str, CancelWaiter] = {}
94
- self.__stream_task = asyncio.create_task(self.___order_stream())
95
+ self.__stream_task = asyncio.create_task(self.__order_stream())
95
96
 
96
97
  @staticmethod
97
98
  async def create(config: Config, account: StarkPerpetualAccount) -> "BlockingTradingClient":
@@ -128,7 +129,7 @@ class BlockingTradingClient:
128
129
  else:
129
130
  await self.__handle_update(order)
130
131
 
131
- async def ___order_stream(self):
132
+ async def __order_stream(self):
132
133
  self.__account_stream = await self.__stream_client.subscribe_to_account_updates(self.__account.api_key)
133
134
  async for event in self.__account_stream:
134
135
  if not (event.data and event.data.orders):
@@ -136,7 +137,7 @@ class BlockingTradingClient:
136
137
  for order in event.data.orders:
137
138
  await self.__handle_order(order)
138
139
  print("Order stream closed, reconnecting...")
139
- await self.___order_stream()
140
+ await self.__order_stream()
140
141
 
141
142
  async def cancel_order(self, order_external_id: str) -> TimedCancel:
142
143
  awaitable: Awaitable
@@ -170,10 +171,10 @@ class BlockingTradingClient:
170
171
 
171
172
  async def get_markets(self) -> Dict[str, MarketModel]:
172
173
  if not self.__markets:
173
- markets = await self.__market_module.get_markets()
174
+ markets = await self.__info_module.get_markets()
174
175
  market_data = markets.data
175
176
  if not market_data:
176
- raise ValueError("Core market data is empty, check your connection or API key.")
177
+ raise ValidationError("Core market data is empty, check your connection or API key.")
177
178
  self.__markets = {m.name: m for m in market_data}
178
179
  return self.__markets
179
180
 
@@ -209,7 +210,7 @@ class BlockingTradingClient:
209
210
  ) -> TimedOpenOrderModel:
210
211
  market = (await self.get_markets()).get(market_name)
211
212
  if not market:
212
- raise ValueError(f"Market '{market_name}' not found.")
213
+ raise ValidationError(f"Market '{market_name}' not found.")
213
214
 
214
215
  order: NewOrderModel = create_order_object(
215
216
  account=self.__account,
@@ -230,7 +231,7 @@ class BlockingTradingClient:
230
231
  )
231
232
 
232
233
  if order.id in self.__order_waiters:
233
- raise ValueError(f"order with {order.id} hash already placed")
234
+ raise ValidationError(f"order with {order.id} hash already placed")
234
235
 
235
236
  self.__order_waiters[order.id] = OrderWaiter(asyncio.Condition(), None, start_nanos=time.time_ns())
236
237
  placed_order_task = asyncio.create_task(self.__orders_module.place_order(order))
@@ -246,7 +247,7 @@ class BlockingTradingClient:
246
247
  open_model = self.__order_waiters[order.id].open_order
247
248
  del self.__order_waiters[order.id]
248
249
  if not open_model:
249
- raise ValueError("No Fill or Placement received for order")
250
+ raise ValidationError("No Fill or Placement received for order")
250
251
  return open_model
251
252
 
252
253
  async def close(self):
@@ -0,0 +1 @@
1
+ from x10.clients.onboarding.onboarding_client import OnboardingClient # noqa: F401
@@ -0,0 +1,32 @@
1
+ from x10.clients.onboarding.modules.base_module import BaseModule
2
+ from x10.errors import ValidationError
3
+ from x10.models.account import ApiKeyRequestModel, ApiKeyResponseModel
4
+ from x10.signing.onboarding import sign_api_request
5
+ from x10.utils.http import RequestHeader, send_post_request
6
+
7
+
8
+ class AccountModule(BaseModule):
9
+ async def create_api_key(self, *, account_id: int, description: str) -> str:
10
+ request_path = "/api/v1/user/account/api-key"
11
+ signature = sign_api_request(request_path, self._sign_message)
12
+ headers: dict[str, str] = {
13
+ RequestHeader.AUTH_L1_SIGNATURE: signature.value,
14
+ RequestHeader.AUTH_L1_MESSAGE_TIME: signature.time,
15
+ RequestHeader.AUTH_ACTIVE_ACCOUNT: str(account_id),
16
+ }
17
+
18
+ payload = ApiKeyRequestModel(description=description)
19
+ url = self._get_url(request_path)
20
+ response = await send_post_request(
21
+ await self._get_session(),
22
+ url,
23
+ ApiKeyResponseModel,
24
+ json=payload.to_api_request_json(),
25
+ request_headers=headers,
26
+ )
27
+ response_data = response.data
28
+
29
+ if response_data is None:
30
+ raise ValidationError("No API key data returned from onboarding")
31
+
32
+ return response_data.key
@@ -0,0 +1,89 @@
1
+ from aiohttp.web_exceptions import HTTPConflict
2
+
3
+ from x10.clients.onboarding.modules.base_module import BaseModule
4
+ from x10.errors import SdkError, ValidationError
5
+ from x10.models.account import AccountModel
6
+ from x10.models.client import OnboardedClientModel
7
+ from x10.signing.onboarding import (
8
+ OnBoardedAccount,
9
+ get_l2_keys_from_l1_account,
10
+ get_onboarding_payload,
11
+ get_sub_account_creation_payload,
12
+ sign_api_request,
13
+ )
14
+ from x10.utils.http import RequestHeader, send_post_request
15
+
16
+
17
+ class SubAccountExists(SdkError):
18
+ pass
19
+
20
+
21
+ class AuthModule(BaseModule):
22
+ async def onboard_client(self, *, referral_code: str | None = None) -> OnBoardedAccount:
23
+ l2_key_pair = get_l2_keys_from_l1_account(
24
+ account_index=0,
25
+ account_address=self._get_account_address(),
26
+ signing_domain=self._get_config().signing.signing_domain,
27
+ sign_message=self._sign_message,
28
+ )
29
+ payload = get_onboarding_payload(
30
+ account_address=self._get_account_address(),
31
+ signing_domain=self._get_config().signing.signing_domain,
32
+ key_pair=l2_key_pair,
33
+ referral_code=referral_code,
34
+ host=self._get_config().endpoints.onboarding_url,
35
+ sign_message=self._sign_message,
36
+ )
37
+
38
+ url = self._get_url("/auth/onboard")
39
+ onboarding_response = await send_post_request(
40
+ await self._get_session(), url, OnboardedClientModel, json=payload.to_json()
41
+ )
42
+
43
+ onboarded_client = onboarding_response.data
44
+
45
+ if onboarded_client is None:
46
+ raise ValidationError("No account data returned from onboarding")
47
+
48
+ return OnBoardedAccount(account=onboarded_client.default_account, l2_key_pair=l2_key_pair)
49
+
50
+ async def onboard_subaccount(self, *, account_index: int, description: str):
51
+ request_path = "/auth/onboard/subaccount"
52
+ signature = sign_api_request(request_path, self._sign_message)
53
+ headers: dict[str, str] = {
54
+ RequestHeader.AUTH_L1_SIGNATURE: signature.value,
55
+ RequestHeader.AUTH_L1_MESSAGE_TIME: signature.time,
56
+ }
57
+
58
+ key_pair = get_l2_keys_from_l1_account(
59
+ account_index=account_index,
60
+ account_address=self._get_account_address(),
61
+ signing_domain=self._get_config().signing.signing_domain,
62
+ sign_message=self._sign_message,
63
+ )
64
+ payload = get_sub_account_creation_payload(
65
+ account_index=account_index,
66
+ l1_address=self._get_account_address(),
67
+ key_pair=key_pair,
68
+ description=description,
69
+ host=self._get_config().endpoints.onboarding_url,
70
+ )
71
+ url = self._get_url(request_path)
72
+
73
+ try:
74
+ onboarding_response = await send_post_request(
75
+ await self._get_session(),
76
+ url,
77
+ AccountModel,
78
+ json=payload.to_json(),
79
+ request_headers=headers,
80
+ response_code_to_exception={HTTPConflict.status_code: SubAccountExists},
81
+ )
82
+ onboarded_account = onboarding_response.data
83
+ except SubAccountExists:
84
+ raise ValidationError("Subaccount already exists")
85
+
86
+ if onboarded_account is None:
87
+ raise ValidationError("No account data returned from onboarding")
88
+
89
+ return OnBoardedAccount(account=onboarded_account, l2_key_pair=key_pair)
@@ -0,0 +1,51 @@
1
+ from typing import Dict, Optional
2
+
3
+ import aiohttp
4
+ from aiohttp import ClientTimeout
5
+ from eth_account.messages import SignableMessage
6
+ from eth_typing import ChecksumAddress
7
+
8
+ from x10.config import Config
9
+ from x10.signing.onboarding import SignMessageCallback
10
+ from x10.utils.http import get_url
11
+
12
+
13
+ class BaseModule:
14
+ __config: Config
15
+ __account_address: ChecksumAddress
16
+ __sign_message: SignMessageCallback
17
+ __session: Optional[aiohttp.ClientSession]
18
+
19
+ def __init__(self, config: Config, *, account_address: ChecksumAddress, sign_message: SignMessageCallback):
20
+ super().__init__()
21
+
22
+ self.__config = config
23
+ self.__account_address = account_address
24
+ self.__sign_message = sign_message
25
+ self.__session = None
26
+
27
+ def _get_url(self, path: str, *, query: Optional[Dict] = None, **path_params) -> str:
28
+ return get_url(f"{self.__config.endpoints.onboarding_url}{path}", query=query, **path_params)
29
+
30
+ def _get_config(self) -> Config:
31
+ return self.__config
32
+
33
+ def _get_account_address(self) -> ChecksumAddress:
34
+ return self.__account_address
35
+
36
+ def _sign_message(self, msg: SignableMessage) -> str:
37
+ return self.__sign_message(msg)
38
+
39
+ async def _get_session(self) -> aiohttp.ClientSession:
40
+ if self.__session is None:
41
+ created_session = aiohttp.ClientSession(
42
+ timeout=ClientTimeout(total=self.__config.defaults.request_timeout_seconds)
43
+ )
44
+ self.__session = created_session
45
+
46
+ return self.__session
47
+
48
+ async def close_session(self):
49
+ if self.__session:
50
+ await self.__session.close()
51
+ self.__session = None
@@ -0,0 +1,33 @@
1
+ from eth_typing import ChecksumAddress
2
+
3
+ from x10.clients.onboarding.modules.account_module import AccountModule
4
+ from x10.clients.onboarding.modules.auth_module import AuthModule
5
+ from x10.config import Config
6
+ from x10.signing.onboarding import SignMessageCallback
7
+
8
+
9
+ class OnboardingClient:
10
+ __account_module: AccountModule
11
+ __auth_module: AuthModule
12
+
13
+ async def close(self):
14
+ await self.__account_module.close_session()
15
+ await self.__auth_module.close_session()
16
+
17
+ async def __aenter__(self):
18
+ return self
19
+
20
+ async def __aexit__(self, exc_type, exc_value, traceback):
21
+ await self.close()
22
+
23
+ def __init__(self, config: Config, *, account_address: ChecksumAddress, sign_message: SignMessageCallback):
24
+ self.__account_module = AccountModule(config, account_address=account_address, sign_message=sign_message)
25
+ self.__auth_module = AuthModule(config, account_address=account_address, sign_message=sign_message)
26
+
27
+ @property
28
+ def account(self):
29
+ return self.__account_module
30
+
31
+ @property
32
+ def auth(self):
33
+ return self.__auth_module
@@ -0,0 +1 @@
1
+ from x10.clients.rest.rest_api_client import RestApiClient # noqa: F401