afp-sdk 0.1.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 +10 -0
- afp/api/__init__.py +0 -0
- afp/api/admin.py +55 -0
- afp/api/base.py +76 -0
- afp/api/builder.py +201 -0
- afp/api/clearing.py +377 -0
- afp/api/liquidation.py +167 -0
- afp/api/trading.py +342 -0
- afp/bindings/__init__.py +48 -0
- afp/bindings/auctioneer_facet.py +758 -0
- afp/bindings/bankruptcy_facet.py +355 -0
- afp/bindings/clearing_facet.py +1019 -0
- afp/bindings/erc20.py +379 -0
- afp/bindings/facade.py +87 -0
- afp/bindings/final_settlement_facet.py +268 -0
- afp/bindings/margin_account.py +1355 -0
- afp/bindings/margin_account_registry.py +617 -0
- afp/bindings/mark_price_tracker_facet.py +111 -0
- afp/bindings/oracle_provider.py +539 -0
- afp/bindings/product_registry.py +1302 -0
- afp/bindings/trading_protocol.py +1181 -0
- afp/config.py +38 -0
- afp/decorators.py +74 -0
- afp/enums.py +27 -0
- afp/exceptions.py +26 -0
- afp/exchange.py +151 -0
- afp/py.typed +0 -0
- afp/schemas.py +207 -0
- afp/signing.py +69 -0
- afp/validators.py +43 -0
- afp_sdk-0.1.0.dist-info/METADATA +180 -0
- afp_sdk-0.1.0.dist-info/RECORD +34 -0
- afp_sdk-0.1.0.dist-info/WHEEL +4 -0
- afp_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
afp/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Autonity Futures Protocol Python SDK."""
|
|
2
|
+
|
|
3
|
+
from afp import bindings
|
|
4
|
+
from .api.admin import Admin
|
|
5
|
+
from .api.builder import Builder
|
|
6
|
+
from .api.clearing import Clearing
|
|
7
|
+
from .api.liquidation import Liquidation
|
|
8
|
+
from .api.trading import Trading
|
|
9
|
+
|
|
10
|
+
__all__ = ("bindings", "Admin", "Builder", "Clearing", "Liquidation", "Trading")
|
afp/api/__init__.py
ADDED
|
File without changes
|
afp/api/admin.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from .. import validators
|
|
2
|
+
from ..decorators import refresh_token_on_expiry
|
|
3
|
+
from ..schemas import ExchangeProductSubmission
|
|
4
|
+
from .base import ExchangeAPI
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Admin(ExchangeAPI):
|
|
8
|
+
"""API for AutEx administration, restricted to AutEx admins.
|
|
9
|
+
|
|
10
|
+
Authenticates with the exchange on creation.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
private_key : str
|
|
15
|
+
The private key of the exchange adminstrator account.
|
|
16
|
+
|
|
17
|
+
Raises
|
|
18
|
+
------
|
|
19
|
+
afp.exceptions.AuthenticationError
|
|
20
|
+
If the exchange rejects the login attempt.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@refresh_token_on_expiry
|
|
24
|
+
def approve_product(self, product_id: str) -> None:
|
|
25
|
+
"""Approves a product for trading on the exchange.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
product_id : str
|
|
30
|
+
|
|
31
|
+
Raises
|
|
32
|
+
------
|
|
33
|
+
afp.exceptions.AuthorizationError
|
|
34
|
+
If the configured account is not an exchange administrator.
|
|
35
|
+
"""
|
|
36
|
+
product = ExchangeProductSubmission(id=product_id)
|
|
37
|
+
self._exchange.approve_product(product)
|
|
38
|
+
|
|
39
|
+
@refresh_token_on_expiry
|
|
40
|
+
def delist_product(self, product_id: str) -> None:
|
|
41
|
+
"""Delists a product from the exchange.
|
|
42
|
+
|
|
43
|
+
New order submissions of this product will be rejected.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
product_id : str
|
|
48
|
+
|
|
49
|
+
Raises
|
|
50
|
+
------
|
|
51
|
+
afp.exceptions.AuthorizationError
|
|
52
|
+
If the configured account is not an exchange administrator.
|
|
53
|
+
"""
|
|
54
|
+
value = validators.validate_hexstr32(product_id)
|
|
55
|
+
self._exchange.delist_product(value)
|
afp/api/base.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from functools import cache
|
|
4
|
+
from typing import cast
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
|
|
7
|
+
from eth_account.account import Account
|
|
8
|
+
from eth_account.signers.local import LocalAccount
|
|
9
|
+
from eth_typing.evm import ChecksumAddress
|
|
10
|
+
from siwe import ISO8601Datetime, SiweMessage, siwe # type: ignore (untyped library)
|
|
11
|
+
from web3 import Web3, HTTPProvider
|
|
12
|
+
from web3.middleware import Middleware, SignAndSendRawMiddlewareBuilder
|
|
13
|
+
|
|
14
|
+
from .. import config, signing
|
|
15
|
+
from ..bindings.erc20 import ERC20
|
|
16
|
+
from ..exchange import ExchangeClient
|
|
17
|
+
from ..schemas import LoginSubmission
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
EXCHANGE_DOMAIN = urlparse(config.EXCHANGE_URL).netloc
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ClearingSystemAPI(ABC):
|
|
24
|
+
_account: LocalAccount
|
|
25
|
+
_w3: Web3
|
|
26
|
+
|
|
27
|
+
def __init__(self, private_key: str, autonity_rpc_url: str):
|
|
28
|
+
self._account = Account.from_key(private_key)
|
|
29
|
+
self._w3 = Web3(HTTPProvider(autonity_rpc_url))
|
|
30
|
+
|
|
31
|
+
# Configure the default sender account
|
|
32
|
+
self._w3.eth.default_account = self._account.address
|
|
33
|
+
signing_middleware = SignAndSendRawMiddlewareBuilder.build(self._account)
|
|
34
|
+
self._w3.middleware_onion.add(cast(Middleware, signing_middleware))
|
|
35
|
+
|
|
36
|
+
@cache
|
|
37
|
+
def _decimals(self, collateral_asset: ChecksumAddress) -> int:
|
|
38
|
+
token_contract = ERC20(self._w3, collateral_asset)
|
|
39
|
+
return token_contract.decimals()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ExchangeAPI(ABC):
|
|
43
|
+
_account: LocalAccount
|
|
44
|
+
_exchange: ExchangeClient
|
|
45
|
+
_trading_protocol_id: str
|
|
46
|
+
|
|
47
|
+
def __init__(self, private_key: str):
|
|
48
|
+
self._account = Account.from_key(private_key)
|
|
49
|
+
self._exchange = ExchangeClient()
|
|
50
|
+
self._login()
|
|
51
|
+
|
|
52
|
+
def _login(self):
|
|
53
|
+
nonce = self._exchange.generate_login_nonce()
|
|
54
|
+
message = self._generate_eip4361_message(self._account, nonce)
|
|
55
|
+
signature = signing.sign_message(self._account, message.encode("ascii"))
|
|
56
|
+
|
|
57
|
+
login_submission = LoginSubmission(
|
|
58
|
+
message=message, signature=Web3.to_hex(signature)
|
|
59
|
+
)
|
|
60
|
+
exchange_parameters = self._exchange.login(login_submission)
|
|
61
|
+
|
|
62
|
+
self._trading_protocol_id = exchange_parameters.trading_protocol_id
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def _generate_eip4361_message(account: LocalAccount, nonce: str) -> str:
|
|
66
|
+
message = SiweMessage(
|
|
67
|
+
domain=EXCHANGE_DOMAIN,
|
|
68
|
+
address=account.address,
|
|
69
|
+
uri=config.EXCHANGE_URL,
|
|
70
|
+
version=siwe.VersionEnum.one, # type: ignore
|
|
71
|
+
chain_id=config.CHAIN_ID,
|
|
72
|
+
issued_at=ISO8601Datetime.from_datetime(datetime.now()),
|
|
73
|
+
nonce=nonce,
|
|
74
|
+
statement=None,
|
|
75
|
+
)
|
|
76
|
+
return message.prepare_message()
|
afp/api/builder.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from eth_typing.evm import ChecksumAddress
|
|
6
|
+
from hexbytes import HexBytes
|
|
7
|
+
from web3 import Web3
|
|
8
|
+
|
|
9
|
+
from .. import config, signing, validators
|
|
10
|
+
from ..bindings import (
|
|
11
|
+
OracleSpecification,
|
|
12
|
+
Product,
|
|
13
|
+
ProductMetadata,
|
|
14
|
+
ProductRegistry,
|
|
15
|
+
)
|
|
16
|
+
from ..bindings.erc20 import ERC20
|
|
17
|
+
from ..bindings.product_registry import ABI as PRODUCT_REGISTRY_ABI
|
|
18
|
+
from ..decorators import convert_web3_error
|
|
19
|
+
from ..exceptions import NotFoundError
|
|
20
|
+
from ..schemas import ProductSpecification
|
|
21
|
+
from .base import ClearingSystemAPI
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Builder(ClearingSystemAPI):
|
|
25
|
+
"""API for building and submitting new products.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
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
|
+
"""
|
|
34
|
+
|
|
35
|
+
@convert_web3_error()
|
|
36
|
+
def create_product(
|
|
37
|
+
self,
|
|
38
|
+
*,
|
|
39
|
+
symbol: str,
|
|
40
|
+
description: str,
|
|
41
|
+
oracle_address: str,
|
|
42
|
+
fsv_decimals: int,
|
|
43
|
+
fsp_alpha: Decimal,
|
|
44
|
+
fsp_beta: int,
|
|
45
|
+
fsv_calldata: str,
|
|
46
|
+
start_time: datetime,
|
|
47
|
+
earliest_fsp_submission_time: datetime,
|
|
48
|
+
collateral_asset: str,
|
|
49
|
+
tick_size: int,
|
|
50
|
+
unit_value: Decimal,
|
|
51
|
+
initial_margin_requirement: Decimal,
|
|
52
|
+
maintenance_margin_requirement: Decimal,
|
|
53
|
+
offer_price_buffer: Decimal,
|
|
54
|
+
auction_bounty: Decimal,
|
|
55
|
+
tradeout_interval: int,
|
|
56
|
+
extended_metadata: str,
|
|
57
|
+
) -> ProductSpecification:
|
|
58
|
+
"""Creates a product specification with the given product data.
|
|
59
|
+
|
|
60
|
+
The builder account's address is derived from the private key; the price
|
|
61
|
+
quotation symbol is retrieved from the collateral asset.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
symbol : str
|
|
66
|
+
description : str
|
|
67
|
+
oracle_address: str
|
|
68
|
+
fsv_decimals: int
|
|
69
|
+
fsp_alpha: Decimal
|
|
70
|
+
fsp_beta: int
|
|
71
|
+
fsv_calldata: str
|
|
72
|
+
start_time : datetime
|
|
73
|
+
earliest_fsp_submission_time : datetime
|
|
74
|
+
collateral_asset : str
|
|
75
|
+
tick_size : int
|
|
76
|
+
unit_value : Decimal
|
|
77
|
+
initial_margin_requirement : Decimal
|
|
78
|
+
maintenance_margin_requirement : Decimal
|
|
79
|
+
offer_price_buffer : Decimal
|
|
80
|
+
auction_bounty : Decimal
|
|
81
|
+
tradeout_interval : int
|
|
82
|
+
extended_metadata : str
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
afp.schemas.ProductSpecification
|
|
87
|
+
"""
|
|
88
|
+
product_id = Web3.to_hex(signing.generate_product_id(self._account, symbol))
|
|
89
|
+
|
|
90
|
+
erc20_contract = ERC20(self._w3, Web3.to_checksum_address(collateral_asset))
|
|
91
|
+
price_quotation = erc20_contract.symbol()
|
|
92
|
+
|
|
93
|
+
if not price_quotation:
|
|
94
|
+
raise NotFoundError(f"No ERC20 token found at address {collateral_asset}")
|
|
95
|
+
|
|
96
|
+
if len(self._w3.eth.get_code(Web3.to_checksum_address(oracle_address))) == 0:
|
|
97
|
+
raise NotFoundError(f"No contract found at oracle address {oracle_address}")
|
|
98
|
+
|
|
99
|
+
return ProductSpecification(
|
|
100
|
+
id=product_id,
|
|
101
|
+
builder_id=self._account.address,
|
|
102
|
+
symbol=symbol,
|
|
103
|
+
description=description,
|
|
104
|
+
oracle_address=oracle_address,
|
|
105
|
+
fsv_decimals=fsv_decimals,
|
|
106
|
+
fsp_alpha=fsp_alpha,
|
|
107
|
+
fsp_beta=fsp_beta,
|
|
108
|
+
fsv_calldata=fsv_calldata,
|
|
109
|
+
price_quotation=price_quotation,
|
|
110
|
+
collateral_asset=collateral_asset,
|
|
111
|
+
start_time=start_time,
|
|
112
|
+
earliest_fsp_submission_time=earliest_fsp_submission_time,
|
|
113
|
+
tick_size=tick_size,
|
|
114
|
+
unit_value=unit_value,
|
|
115
|
+
initial_margin_requirement=initial_margin_requirement,
|
|
116
|
+
maintenance_margin_requirement=maintenance_margin_requirement,
|
|
117
|
+
offer_price_buffer=offer_price_buffer,
|
|
118
|
+
auction_bounty=auction_bounty,
|
|
119
|
+
tradeout_interval=tradeout_interval,
|
|
120
|
+
extended_metadata=extended_metadata,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@convert_web3_error(PRODUCT_REGISTRY_ABI)
|
|
124
|
+
def register_product(self, product: ProductSpecification) -> str:
|
|
125
|
+
"""Submits a product specification to the clearing system.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
product : afp.schemas.ProductSpecification
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
str
|
|
134
|
+
The hash of the transaction.
|
|
135
|
+
"""
|
|
136
|
+
erc20_contract = ERC20(
|
|
137
|
+
self._w3, cast(ChecksumAddress, product.collateral_asset)
|
|
138
|
+
)
|
|
139
|
+
decimals = erc20_contract.decimals()
|
|
140
|
+
|
|
141
|
+
product_registry_contract = ProductRegistry(self._w3)
|
|
142
|
+
tx_hash = product_registry_contract.register(
|
|
143
|
+
self._convert_product_specification(product, decimals)
|
|
144
|
+
).transact()
|
|
145
|
+
self._w3.eth.wait_for_transaction_receipt(tx_hash)
|
|
146
|
+
return Web3.to_hex(tx_hash)
|
|
147
|
+
|
|
148
|
+
@convert_web3_error(PRODUCT_REGISTRY_ABI)
|
|
149
|
+
def product_state(self, product_id: str) -> str:
|
|
150
|
+
"""Returns the current state of a product.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
product_id : str
|
|
155
|
+
The ID of the product.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
str
|
|
160
|
+
"""
|
|
161
|
+
product_id = validators.validate_hexstr32(product_id)
|
|
162
|
+
product_registry_contract = ProductRegistry(self._w3)
|
|
163
|
+
state = product_registry_contract.state(HexBytes(product_id))
|
|
164
|
+
return state.name
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def _convert_product_specification(
|
|
168
|
+
product: ProductSpecification, decimals: int
|
|
169
|
+
) -> Product:
|
|
170
|
+
return Product(
|
|
171
|
+
metadata=ProductMetadata(
|
|
172
|
+
builder=cast(ChecksumAddress, product.builder_id),
|
|
173
|
+
symbol=product.symbol,
|
|
174
|
+
description=product.description,
|
|
175
|
+
),
|
|
176
|
+
oracle_spec=OracleSpecification(
|
|
177
|
+
oracle_address=cast(ChecksumAddress, product.oracle_address),
|
|
178
|
+
fsv_decimals=product.fsv_decimals,
|
|
179
|
+
fsp_alpha=int(product.fsp_alpha * config.FULL_PRECISION_MULTIPLIER),
|
|
180
|
+
fsp_beta=product.fsp_beta,
|
|
181
|
+
fsv_calldata=HexBytes(product.fsv_calldata),
|
|
182
|
+
),
|
|
183
|
+
price_quotation=product.price_quotation,
|
|
184
|
+
collateral_asset=cast(ChecksumAddress, product.collateral_asset),
|
|
185
|
+
start_time=int(product.start_time.timestamp()),
|
|
186
|
+
earliest_fsp_submission_time=int(
|
|
187
|
+
product.earliest_fsp_submission_time.timestamp()
|
|
188
|
+
),
|
|
189
|
+
tick_size=product.tick_size,
|
|
190
|
+
unit_value=int(product.unit_value * 10**decimals),
|
|
191
|
+
initial_margin_requirement=int(
|
|
192
|
+
product.initial_margin_requirement * config.RATE_MULTIPLIER
|
|
193
|
+
),
|
|
194
|
+
maintenance_margin_requirement=int(
|
|
195
|
+
product.maintenance_margin_requirement * config.RATE_MULTIPLIER
|
|
196
|
+
),
|
|
197
|
+
offer_price_buffer=int(product.offer_price_buffer * config.RATE_MULTIPLIER),
|
|
198
|
+
auction_bounty=int(product.auction_bounty * config.RATE_MULTIPLIER),
|
|
199
|
+
tradeout_interval=product.tradeout_interval,
|
|
200
|
+
extended_metadata=product.extended_metadata,
|
|
201
|
+
)
|