afp-sdk 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
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.
- afp/__init__.py +12 -7
- afp/afp.py +210 -0
- afp/api/admin.py +1 -14
- afp/api/base.py +82 -29
- afp/api/{clearing.py → margin_account.py} +172 -104
- afp/api/{builder.py → product.py} +103 -37
- afp/api/trading.py +12 -29
- afp/auth.py +66 -0
- afp/bindings/facade.py +25 -13
- afp/config.py +22 -36
- afp/constants.py +52 -0
- afp/decorators.py +3 -1
- afp/exceptions.py +7 -3
- afp/exchange.py +11 -5
- afp/{signing.py → hashing.py} +4 -12
- afp/schemas.py +9 -0
- afp/validators.py +1 -0
- {afp_sdk-0.4.0.dist-info → afp_sdk-0.5.0.dist-info}/METADATA +54 -50
- afp_sdk-0.5.0.dist-info/RECORD +37 -0
- {afp_sdk-0.4.0.dist-info → afp_sdk-0.5.0.dist-info}/WHEEL +1 -1
- afp/.ruff_cache/.gitignore +0 -2
- afp/.ruff_cache/0.12.7/14089225400260942471 +0 -0
- afp/.ruff_cache/CACHEDIR.TAG +0 -1
- afp/api/liquidation.py +0 -167
- afp_sdk-0.4.0.dist-info/RECORD +0 -38
- {afp_sdk-0.4.0.dist-info → afp_sdk-0.5.0.dist-info}/licenses/LICENSE +0 -0
afp/api/trading.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Generator
|
|
|
5
5
|
|
|
6
6
|
from web3 import Web3
|
|
7
7
|
|
|
8
|
-
from .. import
|
|
8
|
+
from .. import hashing, validators
|
|
9
9
|
from ..decorators import refresh_token_on_expiry
|
|
10
10
|
from ..enums import OrderType
|
|
11
11
|
from ..schemas import (
|
|
@@ -25,20 +25,7 @@ from .base import ExchangeAPI
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class Trading(ExchangeAPI):
|
|
28
|
-
"""API for trading in the AutEx exchange.
|
|
29
|
-
|
|
30
|
-
Authenticates with the exchange on creation.
|
|
31
|
-
|
|
32
|
-
Parameters
|
|
33
|
-
----------
|
|
34
|
-
private_key : str
|
|
35
|
-
The private key of the account that submits intents to the exchange.
|
|
36
|
-
|
|
37
|
-
Raises
|
|
38
|
-
------
|
|
39
|
-
afp.exceptions.AuthenticationError
|
|
40
|
-
If the exchange rejects the login attempt.
|
|
41
|
-
"""
|
|
28
|
+
"""API for trading in the AutEx exchange."""
|
|
42
29
|
|
|
43
30
|
@staticmethod
|
|
44
31
|
def _generate_nonce() -> int:
|
|
@@ -89,7 +76,7 @@ class Trading(ExchangeAPI):
|
|
|
89
76
|
margin_account_id = (
|
|
90
77
|
validators.validate_address(margin_account_id)
|
|
91
78
|
if margin_account_id is not None
|
|
92
|
-
else self.
|
|
79
|
+
else self._authenticator.address
|
|
93
80
|
)
|
|
94
81
|
|
|
95
82
|
intent_data = IntentData(
|
|
@@ -104,17 +91,17 @@ class Trading(ExchangeAPI):
|
|
|
104
91
|
good_until_time=good_until_time,
|
|
105
92
|
nonce=self._generate_nonce(),
|
|
106
93
|
)
|
|
107
|
-
intent_hash =
|
|
94
|
+
intent_hash = hashing.generate_intent_hash(
|
|
108
95
|
intent_data=intent_data,
|
|
109
96
|
margin_account_id=margin_account_id,
|
|
110
|
-
intent_account_id=self.
|
|
97
|
+
intent_account_id=self._authenticator.address,
|
|
111
98
|
tick_size=product.tick_size,
|
|
112
99
|
)
|
|
113
|
-
signature =
|
|
100
|
+
signature = self._authenticator.sign_message(intent_hash)
|
|
114
101
|
return Intent(
|
|
115
102
|
hash=Web3.to_hex(intent_hash),
|
|
116
103
|
margin_account_id=margin_account_id,
|
|
117
|
-
intent_account_id=self.
|
|
104
|
+
intent_account_id=self._authenticator.address,
|
|
118
105
|
signature=Web3.to_hex(signature),
|
|
119
106
|
data=intent_data,
|
|
120
107
|
)
|
|
@@ -160,12 +147,12 @@ class Trading(ExchangeAPI):
|
|
|
160
147
|
If the exchange rejects the cancellation.
|
|
161
148
|
"""
|
|
162
149
|
nonce = self._generate_nonce()
|
|
163
|
-
cancellation_hash =
|
|
164
|
-
signature =
|
|
150
|
+
cancellation_hash = hashing.generate_order_cancellation_hash(nonce, intent_hash)
|
|
151
|
+
signature = self._authenticator.sign_message(cancellation_hash)
|
|
165
152
|
cancellation_data = OrderCancellationData(
|
|
166
153
|
intent_hash=intent_hash,
|
|
167
154
|
nonce=nonce,
|
|
168
|
-
intent_account_id=self.
|
|
155
|
+
intent_account_id=self._authenticator.address,
|
|
169
156
|
signature=Web3.to_hex(signature),
|
|
170
157
|
)
|
|
171
158
|
submission = OrderSubmission(
|
|
@@ -174,7 +161,6 @@ class Trading(ExchangeAPI):
|
|
|
174
161
|
)
|
|
175
162
|
return self._exchange.submit_order(submission)
|
|
176
163
|
|
|
177
|
-
@refresh_token_on_expiry
|
|
178
164
|
def products(self) -> list[ExchangeProduct]:
|
|
179
165
|
"""Retrieves the products approved for trading on the exchange.
|
|
180
166
|
|
|
@@ -184,7 +170,6 @@ class Trading(ExchangeAPI):
|
|
|
184
170
|
"""
|
|
185
171
|
return self._exchange.get_approved_products()
|
|
186
172
|
|
|
187
|
-
@refresh_token_on_expiry
|
|
188
173
|
def product(self, product_id: str) -> ExchangeProduct:
|
|
189
174
|
"""Retrieves a product for trading by its ID.
|
|
190
175
|
|
|
@@ -266,7 +251,7 @@ class Trading(ExchangeAPI):
|
|
|
266
251
|
list of afp.schemas.OrderFill
|
|
267
252
|
"""
|
|
268
253
|
filter = OrderFillFilter(
|
|
269
|
-
intent_account_id=self.
|
|
254
|
+
intent_account_id=self._authenticator.address,
|
|
270
255
|
product_id=product_id,
|
|
271
256
|
margin_account_id=margin_account_id,
|
|
272
257
|
intent_hash=intent_hash,
|
|
@@ -302,7 +287,7 @@ class Trading(ExchangeAPI):
|
|
|
302
287
|
afp.schemas.OrderFill
|
|
303
288
|
"""
|
|
304
289
|
filter = OrderFillFilter(
|
|
305
|
-
intent_account_id=self.
|
|
290
|
+
intent_account_id=self._authenticator.address,
|
|
306
291
|
product_id=product_id,
|
|
307
292
|
margin_account_id=margin_account_id,
|
|
308
293
|
intent_hash=intent_hash,
|
|
@@ -312,7 +297,6 @@ class Trading(ExchangeAPI):
|
|
|
312
297
|
)
|
|
313
298
|
yield from self._exchange.iter_order_fills(filter)
|
|
314
299
|
|
|
315
|
-
@refresh_token_on_expiry
|
|
316
300
|
def market_depth(self, product_id: str) -> MarketDepthData:
|
|
317
301
|
"""Retrieves the depth of market for the given product.
|
|
318
302
|
|
|
@@ -332,7 +316,6 @@ class Trading(ExchangeAPI):
|
|
|
332
316
|
value = validators.validate_hexstr32(product_id)
|
|
333
317
|
return self._exchange.get_market_depth_data(value)
|
|
334
318
|
|
|
335
|
-
@refresh_token_on_expiry
|
|
336
319
|
def iter_market_depth(
|
|
337
320
|
self, product_id: str
|
|
338
321
|
) -> Generator[MarketDepthData, None, None]:
|
afp/auth.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Protocol, cast
|
|
4
|
+
|
|
5
|
+
from eth_account.account import Account
|
|
6
|
+
from eth_account.datastructures import SignedTransaction
|
|
7
|
+
from eth_account.messages import encode_defunct
|
|
8
|
+
from eth_account.signers.local import LocalAccount
|
|
9
|
+
from eth_account.types import TransactionDictType
|
|
10
|
+
from eth_typing.evm import ChecksumAddress
|
|
11
|
+
from hexbytes import HexBytes
|
|
12
|
+
from web3.types import TxParams
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Authenticator(Protocol):
|
|
16
|
+
address: ChecksumAddress
|
|
17
|
+
|
|
18
|
+
def sign_message(self, message: bytes) -> HexBytes: ...
|
|
19
|
+
|
|
20
|
+
def sign_transaction(self, params: TxParams) -> SignedTransaction: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PrivateKeyAuthenticator(Authenticator):
|
|
24
|
+
"""Authenticates with a private key specified in a constructor argument.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
private_key: str
|
|
29
|
+
The private key of a blockchain account.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
_account: LocalAccount
|
|
33
|
+
|
|
34
|
+
def __init__(self, private_key: str) -> None:
|
|
35
|
+
self._account = Account.from_key(private_key)
|
|
36
|
+
self.address = self._account.address
|
|
37
|
+
|
|
38
|
+
def sign_message(self, message: bytes) -> HexBytes:
|
|
39
|
+
eip191_message = encode_defunct(message)
|
|
40
|
+
signed_message = self._account.sign_message(eip191_message)
|
|
41
|
+
return signed_message.signature
|
|
42
|
+
|
|
43
|
+
def sign_transaction(self, params: TxParams) -> SignedTransaction:
|
|
44
|
+
return self._account.sign_transaction(cast(TransactionDictType, params))
|
|
45
|
+
|
|
46
|
+
def __repr__(self):
|
|
47
|
+
return f"{self.__class__.__name__}(address='{self.address}')"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class KeyfileAuthenticator(PrivateKeyAuthenticator):
|
|
51
|
+
"""Authenticates with a private key read from an encrypted keyfile.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
keyfile : str
|
|
56
|
+
The path to the keyfile.
|
|
57
|
+
password : str
|
|
58
|
+
The password for decrypting the keyfile.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, key_file: str, password: str) -> None:
|
|
62
|
+
with open(os.path.expanduser(key_file), encoding="utf8") as f:
|
|
63
|
+
key_data = json.load(f)
|
|
64
|
+
|
|
65
|
+
private_key = Account.decrypt(key_data, password=password)
|
|
66
|
+
super().__init__(private_key.to_0x_hex())
|
afp/bindings/facade.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from itertools import chain
|
|
2
2
|
|
|
3
|
+
from eth_typing.evm import ChecksumAddress
|
|
4
|
+
|
|
3
5
|
from web3 import Web3
|
|
4
6
|
|
|
5
|
-
from .. import config
|
|
6
7
|
from . import (
|
|
7
8
|
auctioneer_facet,
|
|
8
9
|
bankruptcy_facet,
|
|
@@ -14,6 +15,7 @@ from . import (
|
|
|
14
15
|
product_registry,
|
|
15
16
|
system_viewer,
|
|
16
17
|
)
|
|
18
|
+
from ..constants import defaults
|
|
17
19
|
|
|
18
20
|
# In order to include a facet in the ClearingDiamond facade:
|
|
19
21
|
# 1. Add its ABI to CLEARING_DIAMOND_ABI
|
|
@@ -46,10 +48,10 @@ class ClearingDiamond(
|
|
|
46
48
|
w3 : web3.Web3
|
|
47
49
|
"""
|
|
48
50
|
|
|
49
|
-
def __init__(
|
|
50
|
-
self
|
|
51
|
-
|
|
52
|
-
)
|
|
51
|
+
def __init__(
|
|
52
|
+
self, w3: Web3, address: ChecksumAddress = defaults.CLEARING_DIAMOND_ADDRESS
|
|
53
|
+
):
|
|
54
|
+
self._contract = w3.eth.contract(address=address, abi=CLEARING_DIAMOND_ABI)
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
class MarginAccountRegistry(margin_account_registry.MarginAccountRegistry):
|
|
@@ -60,8 +62,12 @@ class MarginAccountRegistry(margin_account_registry.MarginAccountRegistry):
|
|
|
60
62
|
w3 : web3.Web3
|
|
61
63
|
"""
|
|
62
64
|
|
|
63
|
-
def __init__(
|
|
64
|
-
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
w3: Web3,
|
|
68
|
+
address: ChecksumAddress = defaults.MARGIN_ACCOUNT_REGISTRY_ADDRESS,
|
|
69
|
+
):
|
|
70
|
+
super().__init__(w3, address)
|
|
65
71
|
|
|
66
72
|
|
|
67
73
|
class OracleProvider(oracle_provider.OracleProvider):
|
|
@@ -72,8 +78,10 @@ class OracleProvider(oracle_provider.OracleProvider):
|
|
|
72
78
|
w3 : web3.Web3
|
|
73
79
|
"""
|
|
74
80
|
|
|
75
|
-
def __init__(
|
|
76
|
-
|
|
81
|
+
def __init__(
|
|
82
|
+
self, w3: Web3, address: ChecksumAddress = defaults.ORACLE_PROVIDER_ADDRESS
|
|
83
|
+
):
|
|
84
|
+
super().__init__(w3, address)
|
|
77
85
|
|
|
78
86
|
|
|
79
87
|
class ProductRegistry(product_registry.ProductRegistry):
|
|
@@ -84,8 +92,10 @@ class ProductRegistry(product_registry.ProductRegistry):
|
|
|
84
92
|
w3 : web3.Web3
|
|
85
93
|
"""
|
|
86
94
|
|
|
87
|
-
def __init__(
|
|
88
|
-
|
|
95
|
+
def __init__(
|
|
96
|
+
self, w3: Web3, address: ChecksumAddress = defaults.PRODUCT_REGISTRY_ADDRESS
|
|
97
|
+
):
|
|
98
|
+
super().__init__(w3, address)
|
|
89
99
|
|
|
90
100
|
|
|
91
101
|
class SystemViewer(system_viewer.SystemViewer):
|
|
@@ -96,5 +106,7 @@ class SystemViewer(system_viewer.SystemViewer):
|
|
|
96
106
|
w3 : web3.Web3
|
|
97
107
|
"""
|
|
98
108
|
|
|
99
|
-
def __init__(
|
|
100
|
-
|
|
109
|
+
def __init__(
|
|
110
|
+
self, w3: Web3, address: ChecksumAddress = defaults.SYSTEM_VIEWER_ADDRESS
|
|
111
|
+
):
|
|
112
|
+
super().__init__(w3, address)
|
afp/config.py
CHANGED
|
@@ -1,42 +1,28 @@
|
|
|
1
|
-
import
|
|
1
|
+
from dataclasses import dataclass
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from eth_typing.evm import ChecksumAddress
|
|
4
4
|
|
|
5
|
+
from .auth import Authenticator
|
|
5
6
|
|
|
6
|
-
# Constants from clearing/contracts/lib/constants.sol
|
|
7
|
-
RATE_MULTIPLIER = 10**4
|
|
8
|
-
FEE_RATE_MULTIPLIER = 10**6
|
|
9
|
-
FULL_PRECISION_MULTIPLIER = 10**18
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"AFP_EXCHANGE_URL", "https://afp-exchange-stable.up.railway.app"
|
|
15
|
-
)
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class Config:
|
|
10
|
+
authenticator: Authenticator | None
|
|
16
11
|
|
|
17
|
-
|
|
12
|
+
# Venue parameters
|
|
13
|
+
exchange_url: str
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
34
|
-
)
|
|
35
|
-
PRODUCT_REGISTRY_ADDRESS = Web3.to_checksum_address(
|
|
36
|
-
os.getenv(
|
|
37
|
-
"AFP_PRODUCT_REGISTRY_ADDRESS", "0x86B3829471929B115367DA0958f56A6AB844b08e"
|
|
38
|
-
)
|
|
39
|
-
)
|
|
40
|
-
SYSTEM_VIEWER_ADDRESS = Web3.to_checksum_address(
|
|
41
|
-
os.getenv("AFP_SYSTEM_VIEWER_ADDRESS", "0xfF2DFcC44a95cce96E03EfC33C65c8Be671Bae5B")
|
|
42
|
-
)
|
|
15
|
+
# Blockchain parameters
|
|
16
|
+
rpc_url: str | None
|
|
17
|
+
chain_id: int
|
|
18
|
+
gas_limit: int | None
|
|
19
|
+
max_fee_per_gas: int | None
|
|
20
|
+
max_priority_fee_per_gas: int | None
|
|
21
|
+
timeout_seconds: int
|
|
22
|
+
|
|
23
|
+
# Clearing System parameters
|
|
24
|
+
clearing_diamond_address: ChecksumAddress
|
|
25
|
+
margin_account_registry_address: ChecksumAddress
|
|
26
|
+
oracle_provider_address: ChecksumAddress
|
|
27
|
+
product_registry_address: ChecksumAddress
|
|
28
|
+
system_viewer_address: ChecksumAddress
|
afp/constants.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _int_or_none(value: str | None) -> int | None:
|
|
6
|
+
return int(value) if value is not None else None
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
USER_AGENT = "afp-sdk"
|
|
10
|
+
DEFAULT_EXCHANGE_API_VERSION = 1
|
|
11
|
+
|
|
12
|
+
# Constants from clearing/contracts/lib/constants.sol
|
|
13
|
+
RATE_MULTIPLIER = 10**4
|
|
14
|
+
FEE_RATE_MULTIPLIER = 10**6
|
|
15
|
+
FULL_PRECISION_MULTIPLIER = 10**18
|
|
16
|
+
|
|
17
|
+
defaults = SimpleNamespace(
|
|
18
|
+
# Authentication parameters
|
|
19
|
+
KEYFILE=os.getenv("AFP_KEYFILE", None),
|
|
20
|
+
KEYFILE_PASSWORD=os.getenv("AFP_KEYFILE_PASSWORD", ""),
|
|
21
|
+
PRIVATE_KEY=os.getenv("AFP_PRIVATE_KEY", None),
|
|
22
|
+
# Venue parameters
|
|
23
|
+
EXCHANGE_URL=os.getenv(
|
|
24
|
+
"AFP_EXCHANGE_URL", "https://afp-exchange-stable.up.railway.app"
|
|
25
|
+
),
|
|
26
|
+
# Blockchain parameters
|
|
27
|
+
RPC_URL=os.getenv("AFP_RPC_URL", None),
|
|
28
|
+
CHAIN_ID=int(os.getenv("AFP_CHAIN_ID", 65000000)),
|
|
29
|
+
GAS_LIMIT=_int_or_none(os.getenv("AFP_GAS_LIMIT", None)),
|
|
30
|
+
MAX_FEE_PER_GAS=_int_or_none(os.getenv("AFP_MAX_FEE_PER_GAS", None)),
|
|
31
|
+
MAX_PRIORITY_FEE_PER_GAS=_int_or_none(
|
|
32
|
+
os.getenv("AFP_MAX_PRIORITY_FEE_PER_GAS", None)
|
|
33
|
+
),
|
|
34
|
+
TIMEOUT_SECONDS=int(os.getenv("AFP_TIMEOUT_SECONDS", 10)),
|
|
35
|
+
# Clearing System parameters
|
|
36
|
+
CLEARING_DIAMOND_ADDRESS=os.getenv(
|
|
37
|
+
"AFP_CLEARING_DIAMOND_ADDRESS", "0x5B5411F1548254d25360d71FE40cFc1cC983B2A2"
|
|
38
|
+
),
|
|
39
|
+
MARGIN_ACCOUNT_REGISTRY_ADDRESS=os.getenv(
|
|
40
|
+
"AFP_MARGIN_ACCOUNT_REGISTRY_ADDRESS",
|
|
41
|
+
"0x99f4FA9Cdce7AD227eB84907936a8FeF2095D846",
|
|
42
|
+
),
|
|
43
|
+
ORACLE_PROVIDER_ADDRESS=os.getenv(
|
|
44
|
+
"AFP_ORACLE_PROVIDER_ADDRESS", "0xF2A2A27da33D30B4BF38D7e186E7B0b1e964e55c"
|
|
45
|
+
),
|
|
46
|
+
PRODUCT_REGISTRY_ADDRESS=os.getenv(
|
|
47
|
+
"AFP_PRODUCT_REGISTRY_ADDRESS", "0x86B3829471929B115367DA0958f56A6AB844b08e"
|
|
48
|
+
),
|
|
49
|
+
SYSTEM_VIEWER_ADDRESS=os.getenv(
|
|
50
|
+
"AFP_SYSTEM_VIEWER_ADDRESS", "0xfF2DFcC44a95cce96E03EfC33C65c8Be671Bae5B"
|
|
51
|
+
),
|
|
52
|
+
)
|
afp/decorators.py
CHANGED
|
@@ -45,8 +45,10 @@ def convert_web3_error(*contract_abis: ABI) -> Callable[..., Any]:
|
|
|
45
45
|
)
|
|
46
46
|
if reason == "no data":
|
|
47
47
|
reason = "Unspecified reason"
|
|
48
|
+
if reason is None:
|
|
49
|
+
reason = "Unknown error"
|
|
48
50
|
raise ClearingSystemError(
|
|
49
|
-
"Contract call reverted" + f": {reason}" if reason else ""
|
|
51
|
+
"Contract call reverted" + (f": {reason}" if reason else "")
|
|
50
52
|
) from contract_error
|
|
51
53
|
except Web3RPCError as rpc_error:
|
|
52
54
|
reason = None
|
afp/exceptions.py
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
class
|
|
1
|
+
class AFPException(Exception):
|
|
2
2
|
pass
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class
|
|
5
|
+
class ConfigurationError(AFPException):
|
|
6
6
|
pass
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class
|
|
9
|
+
class ClearingSystemError(AFPException):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ExchangeError(AFPException):
|
|
10
14
|
pass
|
|
11
15
|
|
|
12
16
|
|
afp/exchange.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import re
|
|
2
3
|
from typing import Any, Generator
|
|
3
4
|
|
|
4
5
|
import requests
|
|
5
6
|
from requests import Response, Session
|
|
6
7
|
|
|
7
|
-
from . import
|
|
8
|
+
from . import constants
|
|
8
9
|
from .exceptions import (
|
|
9
10
|
AuthenticationError,
|
|
10
11
|
AuthorizationError,
|
|
@@ -26,11 +27,16 @@ from .schemas import (
|
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class ExchangeClient:
|
|
30
|
+
_base_url: str
|
|
29
31
|
_session: Session
|
|
30
32
|
|
|
31
|
-
def __init__(self):
|
|
33
|
+
def __init__(self, base_url: str):
|
|
34
|
+
self._base_url = re.sub(r"/$", "", base_url)
|
|
32
35
|
self._session = Session()
|
|
33
36
|
|
|
37
|
+
def __repr__(self) -> str:
|
|
38
|
+
return f"{self.__class__.__name__}(base_url={self._base_url})"
|
|
39
|
+
|
|
34
40
|
# POST /nonce
|
|
35
41
|
def generate_login_nonce(self) -> str:
|
|
36
42
|
response = self._send_request("GET", "/nonce")
|
|
@@ -123,19 +129,19 @@ class ExchangeClient:
|
|
|
123
129
|
endpoint: str,
|
|
124
130
|
*,
|
|
125
131
|
stream: bool = False,
|
|
126
|
-
api_version: int =
|
|
132
|
+
api_version: int = constants.DEFAULT_EXCHANGE_API_VERSION,
|
|
127
133
|
**kwargs: Any,
|
|
128
134
|
) -> Response:
|
|
129
135
|
kwargs["headers"] = {
|
|
130
136
|
"Content-Type": "application/json",
|
|
131
137
|
"Accept": "application/x-ndjson" if stream else "application/json",
|
|
132
|
-
"User-Agent":
|
|
138
|
+
"User-Agent": constants.USER_AGENT,
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
try:
|
|
136
142
|
response = self._session.request(
|
|
137
143
|
method,
|
|
138
|
-
f"{
|
|
144
|
+
f"{self._base_url}/v{api_version}{endpoint}",
|
|
139
145
|
stream=stream,
|
|
140
146
|
**kwargs,
|
|
141
147
|
)
|
afp/{signing.py → hashing.py}
RENAMED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
from typing import Any, cast
|
|
2
2
|
|
|
3
3
|
from eth_abi.packed import encode_packed
|
|
4
|
-
from eth_account import messages
|
|
5
4
|
from eth_typing.evm import ChecksumAddress
|
|
6
|
-
from eth_account.signers.local import LocalAccount
|
|
7
5
|
from hexbytes import HexBytes
|
|
8
6
|
from web3 import Web3
|
|
9
7
|
|
|
10
|
-
from . import
|
|
8
|
+
from . import constants
|
|
11
9
|
from .bindings import clearing_facet
|
|
12
10
|
from .schemas import IntentData, OrderSide
|
|
13
11
|
|
|
@@ -44,7 +42,7 @@ def generate_intent_hash(
|
|
|
44
42
|
HexBytes(intent_data.product_id),
|
|
45
43
|
int(intent_data.limit_price * 10**tick_size),
|
|
46
44
|
intent_data.quantity,
|
|
47
|
-
int(intent_data.max_trading_fee_rate *
|
|
45
|
+
int(intent_data.max_trading_fee_rate * constants.FEE_RATE_MULTIPLIER),
|
|
48
46
|
int(intent_data.good_until_time.timestamp()),
|
|
49
47
|
ORDER_SIDE_MAPPING[intent_data.side],
|
|
50
48
|
]
|
|
@@ -57,13 +55,7 @@ def generate_order_cancellation_hash(nonce: int, intent_hash: str) -> HexBytes:
|
|
|
57
55
|
return Web3.keccak(encode_packed(types, values))
|
|
58
56
|
|
|
59
57
|
|
|
60
|
-
def generate_product_id(
|
|
58
|
+
def generate_product_id(builder_address: ChecksumAddress, symbol: str) -> HexBytes:
|
|
61
59
|
types = ["address", "string"]
|
|
62
|
-
values: list[Any] = [
|
|
60
|
+
values: list[Any] = [builder_address, symbol]
|
|
63
61
|
return Web3.keccak(encode_packed(types, values))
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def sign_message(account: LocalAccount, message: bytes) -> HexBytes:
|
|
67
|
-
eip191_message = messages.encode_defunct(message)
|
|
68
|
-
signed_message = account.sign_message(eip191_message)
|
|
69
|
-
return signed_message.signature
|
afp/schemas.py
CHANGED
|
@@ -66,6 +66,9 @@ class ExchangeProduct(Model):
|
|
|
66
66
|
tick_size: int
|
|
67
67
|
collateral_asset: str
|
|
68
68
|
|
|
69
|
+
def __str__(self) -> str:
|
|
70
|
+
return self.id
|
|
71
|
+
|
|
69
72
|
|
|
70
73
|
class IntentData(Model):
|
|
71
74
|
trading_protocol_id: str
|
|
@@ -152,6 +155,12 @@ class MarketDepthData(Model):
|
|
|
152
155
|
# Clearing API
|
|
153
156
|
|
|
154
157
|
|
|
158
|
+
class Transaction(Model):
|
|
159
|
+
hash: str
|
|
160
|
+
data: dict[str, Any]
|
|
161
|
+
receipt: dict[str, Any]
|
|
162
|
+
|
|
163
|
+
|
|
155
164
|
class Position(Model):
|
|
156
165
|
id: str
|
|
157
166
|
quantity: int
|
afp/validators.py
CHANGED