afp-sdk 0.4.0__py3-none-any.whl → 0.5.1__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 +32 -18
- afp/api/base.py +82 -29
- afp/api/{clearing.py → margin_account.py} +172 -104
- afp/api/{builder.py → product.py} +136 -42
- 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/enums.py +6 -0
- afp/exceptions.py +7 -3
- afp/exchange.py +26 -11
- afp/{signing.py → hashing.py} +4 -12
- afp/schemas.py +23 -3
- afp/validators.py +1 -0
- {afp_sdk-0.4.0.dist-info → afp_sdk-0.5.1.dist-info}/METADATA +54 -50
- afp_sdk-0.5.1.dist-info/RECORD +37 -0
- {afp_sdk-0.4.0.dist-info → afp_sdk-0.5.1.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.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,44 +1,40 @@
|
|
|
1
|
+
import warnings
|
|
1
2
|
from datetime import datetime
|
|
2
3
|
from decimal import Decimal
|
|
3
|
-
from typing import cast
|
|
4
|
+
from typing import Any, cast
|
|
4
5
|
|
|
5
6
|
from eth_typing.evm import ChecksumAddress
|
|
6
7
|
from hexbytes import HexBytes
|
|
7
8
|
from web3 import Web3
|
|
8
9
|
|
|
9
|
-
from .. import
|
|
10
|
+
from .. import constants, hashing, validators
|
|
10
11
|
from ..bindings import (
|
|
12
|
+
ClearingDiamond,
|
|
11
13
|
OracleSpecification,
|
|
12
|
-
Product,
|
|
14
|
+
Product as OnChainProduct,
|
|
13
15
|
ProductMetadata,
|
|
14
16
|
ProductRegistry,
|
|
15
17
|
)
|
|
16
18
|
from ..bindings.erc20 import ERC20
|
|
19
|
+
from ..bindings.facade import CLEARING_DIAMOND_ABI
|
|
17
20
|
from ..bindings.product_registry import ABI as PRODUCT_REGISTRY_ABI
|
|
18
21
|
from ..decorators import convert_web3_error
|
|
19
22
|
from ..exceptions import NotFoundError
|
|
20
|
-
from ..schemas import ProductSpecification
|
|
23
|
+
from ..schemas import ProductSpecification, Transaction
|
|
21
24
|
from .base import ClearingSystemAPI
|
|
22
25
|
|
|
23
26
|
|
|
24
|
-
class
|
|
25
|
-
"""API for
|
|
27
|
+
class Product(ClearingSystemAPI):
|
|
28
|
+
"""API for managing products."""
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
----------
|
|
29
|
-
private_key : str
|
|
30
|
-
The private key of the blockchain account that submits the product.
|
|
31
|
-
autonity_rpc_url : str
|
|
32
|
-
The URL of a JSON-RPC provider for Autonity. (HTTPS only.)
|
|
33
|
-
"""
|
|
30
|
+
### Factories ###
|
|
34
31
|
|
|
35
32
|
@convert_web3_error()
|
|
36
|
-
def
|
|
33
|
+
def create(
|
|
37
34
|
self,
|
|
38
35
|
*,
|
|
39
36
|
symbol: str,
|
|
40
37
|
description: str,
|
|
41
|
-
oracle_address: str,
|
|
42
38
|
fsv_decimals: int,
|
|
43
39
|
fsp_alpha: Decimal,
|
|
44
40
|
fsp_beta: Decimal,
|
|
@@ -53,6 +49,7 @@ class Builder(ClearingSystemAPI):
|
|
|
53
49
|
auction_bounty: Decimal,
|
|
54
50
|
tradeout_interval: int,
|
|
55
51
|
extended_metadata: str,
|
|
52
|
+
oracle_address: str | None = None,
|
|
56
53
|
) -> ProductSpecification:
|
|
57
54
|
"""Creates a product specification with the given product data.
|
|
58
55
|
|
|
@@ -63,7 +60,6 @@ class Builder(ClearingSystemAPI):
|
|
|
63
60
|
----------
|
|
64
61
|
symbol : str
|
|
65
62
|
description : str
|
|
66
|
-
oracle_address: str
|
|
67
63
|
fsv_decimals: int
|
|
68
64
|
fsp_alpha: Decimal
|
|
69
65
|
fsp_beta: int
|
|
@@ -78,25 +74,30 @@ class Builder(ClearingSystemAPI):
|
|
|
78
74
|
auction_bounty : Decimal
|
|
79
75
|
tradeout_interval : int
|
|
80
76
|
extended_metadata : str
|
|
77
|
+
oracle_address: str, optional
|
|
81
78
|
|
|
82
79
|
Returns
|
|
83
80
|
-------
|
|
84
81
|
afp.schemas.ProductSpecification
|
|
85
82
|
"""
|
|
86
|
-
|
|
83
|
+
if oracle_address is None:
|
|
84
|
+
oracle_address = self._config.oracle_provider_address
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
price_quotation = erc20_contract.symbol()
|
|
90
|
-
|
|
91
|
-
if not price_quotation:
|
|
86
|
+
if len(self._w3.eth.get_code(Web3.to_checksum_address(collateral_asset))) == 0:
|
|
92
87
|
raise NotFoundError(f"No ERC20 token found at address {collateral_asset}")
|
|
93
|
-
|
|
94
88
|
if len(self._w3.eth.get_code(Web3.to_checksum_address(oracle_address))) == 0:
|
|
95
89
|
raise NotFoundError(f"No contract found at oracle address {oracle_address}")
|
|
96
90
|
|
|
91
|
+
erc20_contract = ERC20(self._w3, Web3.to_checksum_address(collateral_asset))
|
|
92
|
+
price_quotation = erc20_contract.symbol()
|
|
93
|
+
|
|
94
|
+
product_id = Web3.to_hex(
|
|
95
|
+
hashing.generate_product_id(self._authenticator.address, symbol)
|
|
96
|
+
)
|
|
97
|
+
|
|
97
98
|
return ProductSpecification(
|
|
98
99
|
id=product_id,
|
|
99
|
-
builder_id=self.
|
|
100
|
+
builder_id=self._authenticator.address,
|
|
100
101
|
symbol=symbol,
|
|
101
102
|
description=description,
|
|
102
103
|
oracle_address=oracle_address,
|
|
@@ -117,33 +118,89 @@ class Builder(ClearingSystemAPI):
|
|
|
117
118
|
extended_metadata=extended_metadata,
|
|
118
119
|
)
|
|
119
120
|
|
|
121
|
+
def create_product(self, **kwargs: Any) -> ProductSpecification:
|
|
122
|
+
"""Deprecated alias of `Product.create`."""
|
|
123
|
+
warnings.warn(
|
|
124
|
+
"Product.create_product() is deprecated. Use Product.create() instead.",
|
|
125
|
+
DeprecationWarning,
|
|
126
|
+
stacklevel=2,
|
|
127
|
+
)
|
|
128
|
+
return self.create(**kwargs)
|
|
129
|
+
|
|
130
|
+
### Transactions ###
|
|
131
|
+
|
|
120
132
|
@convert_web3_error(PRODUCT_REGISTRY_ABI)
|
|
121
|
-
def
|
|
133
|
+
def register(self, product_specification: ProductSpecification) -> Transaction:
|
|
122
134
|
"""Submits a product specification to the clearing system.
|
|
123
135
|
|
|
124
136
|
Parameters
|
|
125
137
|
----------
|
|
126
|
-
|
|
138
|
+
product_specification : afp.schemas.ProductSpecification
|
|
127
139
|
|
|
128
140
|
Returns
|
|
129
141
|
-------
|
|
130
|
-
|
|
131
|
-
|
|
142
|
+
afp.schemas.Transaction
|
|
143
|
+
Transaction parameters.
|
|
132
144
|
"""
|
|
133
145
|
erc20_contract = ERC20(
|
|
134
|
-
self._w3, cast(ChecksumAddress,
|
|
146
|
+
self._w3, cast(ChecksumAddress, product_specification.collateral_asset)
|
|
135
147
|
)
|
|
136
148
|
decimals = erc20_contract.decimals()
|
|
137
149
|
|
|
138
|
-
product_registry_contract = ProductRegistry(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
150
|
+
product_registry_contract = ProductRegistry(
|
|
151
|
+
self._w3, self._config.product_registry_address
|
|
152
|
+
)
|
|
153
|
+
return self._transact(
|
|
154
|
+
product_registry_contract.register(
|
|
155
|
+
self._convert_product_specification(product_specification, decimals)
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def register_product(self, product: ProductSpecification) -> Transaction:
|
|
160
|
+
"""Deprecated alias of `Product.register`."""
|
|
161
|
+
warnings.warn(
|
|
162
|
+
"Product.register_product() is deprecated. Use Product.register() instead.",
|
|
163
|
+
DeprecationWarning,
|
|
164
|
+
stacklevel=2,
|
|
165
|
+
)
|
|
166
|
+
return self.register(product)
|
|
167
|
+
|
|
168
|
+
@convert_web3_error(CLEARING_DIAMOND_ABI)
|
|
169
|
+
def initiate_final_settlement(
|
|
170
|
+
self, product_id: str, accounts: list[str]
|
|
171
|
+
) -> Transaction:
|
|
172
|
+
"""Initiate final settlement (closeout) process for the specified accounts.
|
|
173
|
+
|
|
174
|
+
The product must be in Final Settlement state. The accounts must hold non-zero
|
|
175
|
+
positions in the product that offset each other (i.e. the sum of their position
|
|
176
|
+
sizes is 0.)
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
product_id : str
|
|
181
|
+
The ID of the product.
|
|
182
|
+
accounts : list of str
|
|
183
|
+
List of margin account IDs to initiate settlement for.
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
afp.schemas.Transaction
|
|
188
|
+
Transaction parameters.
|
|
189
|
+
"""
|
|
190
|
+
product_id = validators.validate_hexstr32(product_id)
|
|
191
|
+
addresses = [validators.validate_address(account) for account in accounts]
|
|
192
|
+
|
|
193
|
+
clearing_contract = ClearingDiamond(
|
|
194
|
+
self._w3, self._config.clearing_diamond_address
|
|
195
|
+
)
|
|
196
|
+
return self._transact(
|
|
197
|
+
clearing_contract.initiate_final_settlement(HexBytes(product_id), addresses)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
### Views ###
|
|
144
201
|
|
|
145
202
|
@convert_web3_error(PRODUCT_REGISTRY_ABI)
|
|
146
|
-
def
|
|
203
|
+
def state(self, product_id: str) -> str:
|
|
147
204
|
"""Returns the current state of a product.
|
|
148
205
|
|
|
149
206
|
Parameters
|
|
@@ -156,15 +213,52 @@ class Builder(ClearingSystemAPI):
|
|
|
156
213
|
str
|
|
157
214
|
"""
|
|
158
215
|
product_id = validators.validate_hexstr32(product_id)
|
|
159
|
-
product_registry_contract = ProductRegistry(
|
|
216
|
+
product_registry_contract = ProductRegistry(
|
|
217
|
+
self._w3, self._config.product_registry_address
|
|
218
|
+
)
|
|
160
219
|
state = product_registry_contract.state(HexBytes(product_id))
|
|
161
220
|
return state.name
|
|
162
221
|
|
|
222
|
+
def product_state(self, product_id: str) -> str:
|
|
223
|
+
"""Deprecated alias of `Product.state`."""
|
|
224
|
+
warnings.warn(
|
|
225
|
+
"Product.product_state() is deprecated. Use Product.state() instead.",
|
|
226
|
+
DeprecationWarning,
|
|
227
|
+
stacklevel=2,
|
|
228
|
+
)
|
|
229
|
+
return self.state(product_id)
|
|
230
|
+
|
|
231
|
+
@convert_web3_error(PRODUCT_REGISTRY_ABI)
|
|
232
|
+
def collateral_asset(self, product_id: str) -> str:
|
|
233
|
+
"""Returns the collateral asset of a product.
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
product_id : str
|
|
238
|
+
The ID of the product.
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
str
|
|
243
|
+
"""
|
|
244
|
+
product_id = validators.validate_hexstr32(product_id)
|
|
245
|
+
product_registry_contract = ProductRegistry(
|
|
246
|
+
self._w3, self._config.product_registry_address
|
|
247
|
+
)
|
|
248
|
+
collateral_asset = product_registry_contract.collateral_asset(
|
|
249
|
+
HexBytes(product_id)
|
|
250
|
+
)
|
|
251
|
+
if Web3.to_int(hexstr=collateral_asset) == 0:
|
|
252
|
+
raise NotFoundError("Product not found in the product registry")
|
|
253
|
+
return collateral_asset
|
|
254
|
+
|
|
255
|
+
### Internal helpers ###
|
|
256
|
+
|
|
163
257
|
@staticmethod
|
|
164
258
|
def _convert_product_specification(
|
|
165
259
|
product: ProductSpecification, decimals: int
|
|
166
|
-
) ->
|
|
167
|
-
return
|
|
260
|
+
) -> OnChainProduct:
|
|
261
|
+
return OnChainProduct(
|
|
168
262
|
metadata=ProductMetadata(
|
|
169
263
|
builder=cast(ChecksumAddress, product.builder_id),
|
|
170
264
|
symbol=product.symbol,
|
|
@@ -173,7 +267,7 @@ class Builder(ClearingSystemAPI):
|
|
|
173
267
|
oracle_spec=OracleSpecification(
|
|
174
268
|
oracle_address=cast(ChecksumAddress, product.oracle_address),
|
|
175
269
|
fsv_decimals=product.fsv_decimals,
|
|
176
|
-
fsp_alpha=int(product.fsp_alpha *
|
|
270
|
+
fsp_alpha=int(product.fsp_alpha * constants.FULL_PRECISION_MULTIPLIER),
|
|
177
271
|
fsp_beta=int(product.fsp_beta * 10**product.fsv_decimals),
|
|
178
272
|
fsv_calldata=HexBytes(product.fsv_calldata),
|
|
179
273
|
),
|
|
@@ -186,12 +280,12 @@ class Builder(ClearingSystemAPI):
|
|
|
186
280
|
tick_size=product.tick_size,
|
|
187
281
|
unit_value=int(product.unit_value * 10**decimals),
|
|
188
282
|
initial_margin_requirement=int(
|
|
189
|
-
product.initial_margin_requirement *
|
|
283
|
+
product.initial_margin_requirement * constants.RATE_MULTIPLIER
|
|
190
284
|
),
|
|
191
285
|
maintenance_margin_requirement=int(
|
|
192
|
-
product.maintenance_margin_requirement *
|
|
286
|
+
product.maintenance_margin_requirement * constants.RATE_MULTIPLIER
|
|
193
287
|
),
|
|
194
|
-
auction_bounty=int(product.auction_bounty *
|
|
288
|
+
auction_bounty=int(product.auction_bounty * constants.RATE_MULTIPLIER),
|
|
195
289
|
tradeout_interval=product.tradeout_interval,
|
|
196
290
|
extended_metadata=product.extended_metadata,
|
|
197
291
|
)
|
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
|
+
key_file : 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
|
+
)
|