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.
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/PKG-INFO +5 -5
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/README.md +1 -1
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/pyproject.toml +5 -5
- x10_python_trading_starknet-2.0.0/x10/clients/blocking/__init__.py +3 -0
- 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
- x10_python_trading_starknet-2.0.0/x10/clients/onboarding/__init__.py +1 -0
- x10_python_trading_starknet-2.0.0/x10/clients/onboarding/modules/account_module.py +32 -0
- x10_python_trading_starknet-2.0.0/x10/clients/onboarding/modules/auth_module.py +89 -0
- x10_python_trading_starknet-2.0.0/x10/clients/onboarding/modules/base_module.py +51 -0
- x10_python_trading_starknet-2.0.0/x10/clients/onboarding/onboarding_client.py +33 -0
- x10_python_trading_starknet-2.0.0/x10/clients/rest/__init__.py +1 -0
- {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
- {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
- 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
- {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
- {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
- {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
- 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
- x10_python_trading_starknet-2.0.0/x10/clients/stream/__init__.py +2 -0
- {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
- 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
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/core/amount.py +3 -3
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/core/stark_account.py +2 -1
- x10_python_trading_starknet-2.0.0/x10/errors.py +22 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/asset.py +3 -2
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/client.py +6 -0
- x10_python_trading_starknet-2.0.0/x10/models/http.py +60 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/order.py +8 -1
- x10_python_trading_starknet-2.0.0/x10/models/settings.py +5 -0
- x10_python_trading_starknet-2.0.0/x10/models/testnet.py +5 -0
- x10_python_trading_starknet-2.0.0/x10/models/vault.py +20 -0
- {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
- {x10_python_trading_starknet-1.4.0/x10/perpetual/user_client → x10_python_trading_starknet-2.0.0/x10/signing}/onboarding.py +55 -26
- {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/signing}/order_object.py +16 -15
- {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/signing}/order_object_settlement.py +8 -13
- {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/signing}/transfer_object.py +4 -11
- {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/signing}/withdrawal_object.py +3 -10
- x10_python_trading_starknet-2.0.0/x10/tools/__init__.py +0 -0
- {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/tools}/orderbook.py +22 -23
- x10_python_trading_starknet-2.0.0/x10/utils/__init__.py +0 -0
- x10_python_trading_starknet-2.0.0/x10/utils/date.py +23 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/utils/http.py +36 -79
- x10_python_trading_starknet-1.4.0/x10/errors.py +0 -2
- x10_python_trading_starknet-1.4.0/x10/perpetual/stream_client/__init__.py +0 -3
- x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client/__init__.py +0 -3
- x10_python_trading_starknet-1.4.0/x10/perpetual/trading_client/info_module.py +0 -29
- x10_python_trading_starknet-1.4.0/x10/perpetual/user_client/user_client.py +0 -202
- x10_python_trading_starknet-1.4.0/x10/utils/date.py +0 -13
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/LICENSE +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/__init__.py +0 -0
- {x10_python_trading_starknet-1.4.0/x10/core → x10_python_trading_starknet-2.0.0/x10/clients}/__init__.py +0 -0
- {x10_python_trading_starknet-1.4.0/x10/models → x10_python_trading_starknet-2.0.0/x10/clients/onboarding/modules}/__init__.py +0 -0
- {x10_python_trading_starknet-1.4.0/x10/perpetual → x10_python_trading_starknet-2.0.0/x10/clients/rest/modules}/__init__.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/config.py +0 -0
- {x10_python_trading_starknet-1.4.0/x10/perpetual/simple_client → x10_python_trading_starknet-2.0.0/x10/core}/__init__.py +0 -0
- {x10_python_trading_starknet-1.4.0/x10/perpetual/user_client → x10_python_trading_starknet-2.0.0/x10/models}/__init__.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/account.py +4 -4
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/balance.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/base.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/bridge.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/candle.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/fee.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/funding_rate.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/market.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/orderbook.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/position.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/trade.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/transfer.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/models/withdrawal.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/py.typed +0 -0
- {x10_python_trading_starknet-1.4.0/x10/utils → x10_python_trading_starknet-2.0.0/x10/signing}/__init__.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/utils/log.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/utils/nonce.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/utils/order.py +0 -0
- {x10_python_trading_starknet-1.4.0 → x10_python_trading_starknet-2.0.0}/x10/utils/string.py +0 -0
- {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:
|
|
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
|
|
24
|
-
Requires-Dist: tenacity (>=9.1.2
|
|
25
|
-
Requires-Dist: websockets (>=12.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 = "
|
|
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 = "
|
|
33
|
-
tenacity = "
|
|
34
|
-
websockets = ">=12.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 = "
|
|
55
|
+
typing-extensions = "==4.15.0"
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
[tool.mypy]
|
|
@@ -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,
|
|
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.
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
|
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.
|
|
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:
|
|
87
|
-
self.__stream_client:
|
|
88
|
-
self.__account_stream:
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
174
|
+
markets = await self.__info_module.get_markets()
|
|
174
175
|
market_data = markets.data
|
|
175
176
|
if not market_data:
|
|
176
|
-
raise
|
|
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
|
|
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
|
|
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
|
|
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
|