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/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/enums.py CHANGED
@@ -1,6 +1,12 @@
1
1
  from enum import StrEnum
2
2
 
3
3
 
4
+ class ListingState(StrEnum):
5
+ PRIVATE = "PRIVATE"
6
+ PUBLIC = "PUBLIC"
7
+ DELISTED = "DELISTED"
8
+
9
+
4
10
  class OrderType(StrEnum):
5
11
  LIMIT_ORDER = "LIMIT_ORDER"
6
12
  CANCEL_ORDER = "CANCEL_ORDER"
afp/exceptions.py CHANGED
@@ -1,12 +1,16 @@
1
- class BaseException(Exception):
1
+ class AFPException(Exception):
2
2
  pass
3
3
 
4
4
 
5
- class ClearingSystemError(BaseException):
5
+ class ConfigurationError(AFPException):
6
6
  pass
7
7
 
8
8
 
9
- class ExchangeError(BaseException):
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 config
8
+ from . import constants
8
9
  from .exceptions import (
9
10
  AuthenticationError,
10
11
  AuthorizationError,
@@ -15,7 +16,8 @@ from .exceptions import (
15
16
  from .schemas import (
16
17
  ExchangeParameters,
17
18
  ExchangeProduct,
18
- ExchangeProductSubmission,
19
+ ExchangeProductListingSubmission,
20
+ ExchangeProductUpdateSubmission,
19
21
  LoginSubmission,
20
22
  MarketDepthData,
21
23
  Order,
@@ -26,11 +28,16 @@ from .schemas import (
26
28
 
27
29
 
28
30
  class ExchangeClient:
31
+ _base_url: str
29
32
  _session: Session
30
33
 
31
- def __init__(self):
34
+ def __init__(self, base_url: str):
35
+ self._base_url = re.sub(r"/$", "", base_url)
32
36
  self._session = Session()
33
37
 
38
+ def __repr__(self) -> str:
39
+ return f"{self.__class__.__name__}(base_url={self._base_url})"
40
+
34
41
  # POST /nonce
35
42
  def generate_login_nonce(self) -> str:
36
43
  response = self._send_request("GET", "/nonce")
@@ -54,14 +61,22 @@ class ExchangeClient:
54
61
  return ExchangeProduct(**response.json())
55
62
 
56
63
  # POST /products
57
- def approve_product(self, product_submission: ExchangeProductSubmission) -> None:
64
+ def list_product(
65
+ self, listing_submission: ExchangeProductListingSubmission
66
+ ) -> None:
58
67
  self._send_request(
59
- "POST", "/products", data=product_submission.model_dump_json()
68
+ "POST", "/products", data=listing_submission.model_dump_json()
60
69
  )
61
70
 
62
- # DELETE /products
63
- def delist_product(self, product_id: str) -> None:
64
- self._send_request("DELETE", f"/products/{product_id}")
71
+ # PATCH /products
72
+ def update_product_listing(
73
+ self, product_id: str, update_submission: ExchangeProductUpdateSubmission
74
+ ) -> None:
75
+ self._send_request(
76
+ "PATCH",
77
+ f"/products/{product_id}",
78
+ data=update_submission.model_dump_json(),
79
+ )
65
80
 
66
81
  # POST /orders
67
82
  def submit_order(self, order_submission: OrderSubmission) -> Order:
@@ -123,19 +138,19 @@ class ExchangeClient:
123
138
  endpoint: str,
124
139
  *,
125
140
  stream: bool = False,
126
- api_version: int = config.DEFAULT_EXCHANGE_API_VERSION,
141
+ api_version: int = constants.DEFAULT_EXCHANGE_API_VERSION,
127
142
  **kwargs: Any,
128
143
  ) -> Response:
129
144
  kwargs["headers"] = {
130
145
  "Content-Type": "application/json",
131
146
  "Accept": "application/x-ndjson" if stream else "application/json",
132
- "User-Agent": config.USER_AGENT,
147
+ "User-Agent": constants.USER_AGENT,
133
148
  }
134
149
 
135
150
  try:
136
151
  response = self._session.request(
137
152
  method,
138
- f"{config.EXCHANGE_URL}/v{api_version}{endpoint}",
153
+ f"{self._base_url}/v{api_version}{endpoint}",
139
154
  stream=stream,
140
155
  **kwargs,
141
156
  )
@@ -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 config
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 * config.FEE_RATE_MULTIPLIER),
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(builder: LocalAccount, symbol: str) -> HexBytes:
58
+ def generate_product_id(builder_address: ChecksumAddress, symbol: str) -> HexBytes:
61
59
  types = ["address", "string"]
62
- values: list[Any] = [builder.address, symbol]
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
@@ -15,7 +15,7 @@ from pydantic import (
15
15
  )
16
16
 
17
17
  from . import validators
18
- from .enums import OrderSide, OrderState, OrderType, TradeState
18
+ from .enums import ListingState, OrderSide, OrderState, OrderType, TradeState
19
19
 
20
20
 
21
21
  # Use datetime internally but UNIX timestamp in client-server communication
@@ -53,18 +53,29 @@ class ExchangeParameters(Model):
53
53
  trading_fee_rate: Decimal
54
54
 
55
55
 
56
- # Trading API
56
+ # Admin API
57
57
 
58
58
 
59
- class ExchangeProductSubmission(Model):
59
+ class ExchangeProductListingSubmission(Model):
60
60
  id: Annotated[str, AfterValidator(validators.validate_hexstr32)]
61
61
 
62
62
 
63
+ class ExchangeProductUpdateSubmission(Model):
64
+ listing_state: ListingState
65
+
66
+
67
+ # Trading API
68
+
69
+
63
70
  class ExchangeProduct(Model):
64
71
  id: str
65
72
  symbol: str
66
73
  tick_size: int
67
74
  collateral_asset: str
75
+ listing_state: ListingState
76
+
77
+ def __str__(self) -> str:
78
+ return self.id
68
79
 
69
80
 
70
81
  class IntentData(Model):
@@ -152,6 +163,12 @@ class MarketDepthData(Model):
152
163
  # Clearing API
153
164
 
154
165
 
166
+ class Transaction(Model):
167
+ hash: str
168
+ data: dict[str, Any]
169
+ receipt: dict[str, Any]
170
+
171
+
155
172
  class Position(Model):
156
173
  id: str
157
174
  quantity: int
@@ -188,6 +205,9 @@ class ProductSpecification(Model):
188
205
  tradeout_interval: Annotated[int, Field(ge=0)]
189
206
  extended_metadata: str
190
207
 
208
+ def __str__(self) -> str:
209
+ return self.id
210
+
191
211
 
192
212
  # Liquidation API
193
213
 
afp/validators.py CHANGED
@@ -22,6 +22,7 @@ def validate_timedelta(value: timedelta) -> timedelta:
22
22
 
23
23
 
24
24
  def validate_hexstr(value: str, length: int | None = None) -> str:
25
+ value = str(value)
25
26
  if not value.startswith("0x"):
26
27
  raise ValueError(f"{value} should start with '0x'")
27
28
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: afp-sdk
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Summary: Autonomous Futures Protocol Python SDK
5
5
  Keywords: autonity,web3,trading,crypto,prediction,forecast,markets
6
6
  License-Expression: MIT
@@ -25,6 +25,10 @@ Description-Content-Type: text/markdown
25
25
 
26
26
  # Autonomous Futures Protocol Python SDK
27
27
 
28
+ Decentralized clearing and creation of Forecast Futures on any timeseries.
29
+
30
+ ![CI](https://github.com/autonity/afp-sdk/actions/workflows/ci.yml/badge.svg)
31
+
28
32
  ## Installation
29
33
 
30
34
  This library is published on PyPI as the [afp-sdk](https://pypi.org/project/afp-sdk/)
@@ -34,6 +38,11 @@ package. It can be installed in a virtualenv with:
34
38
  pip install afp-sdk
35
39
  ```
36
40
 
41
+ ## Documentation
42
+
43
+ See [afp.autonity.org](https://afp.autonity.org/) for the Autonomous Futures Protocol
44
+ documentation, including the Python SDK reference.
45
+
37
46
  ## Overview
38
47
 
39
48
  The `afp` package consists of the following:
@@ -43,6 +52,13 @@ The `afp` package consists of the following:
43
52
  - `afp.bindings` submodule: Low-level API that provides typed Python bindings
44
53
  for the Clearing System smart contracts.
45
54
 
55
+ ## Configuration
56
+
57
+ By default the SDK communicates with the AFP Clearing System contracts on
58
+ Autonity Mainnet, and the AutEx Exchange. Connection parameters can be
59
+ overridden via the `afp.AFP` constructor and environment variables; see the
60
+ documentation of the `afp.AFP` class for the available parameters.
61
+
46
62
  ## Usage
47
63
 
48
64
  ### Preparation
@@ -53,7 +69,8 @@ In order to trade in the AFP system, traders need to prepare the following:
53
69
  - The address of the product's collateral token.
54
70
  - An Autonity account for managing the margin account. It needs to hold a
55
71
  balance in ATN (for paying gas fee) and in the product's collateral token.
56
- - An Autonity account for signing intents. The two accounts can be the same.
72
+ - An Autonity account for signing intents and submitting trades to the
73
+ exchange. The two accounts can be the same.
57
74
  - The address of an Autonity RPC provider. They can be found on
58
75
  [Chainlist](https://chainlist.org/?search=autonity).
59
76
 
@@ -64,57 +81,60 @@ import os
64
81
 
65
82
  PRODUCT_ID = "0x38d502bb683f53ec7c3d7a14b4aa47ac717659e121426131c0189c15bf4b9460"
66
83
  COLLATERAL_ASSET = "0xD1A1e4035a164cF42228A8aAaBC2c0Ac9e49687B"
67
- MARGIN_ACCOUNT_PRIVATE_KEY = os.environ["MARGIN_ACCOUNT_PRIVATE_KEY"]
68
- INTENT_ACCOUNT_PRIVATE_KEY = os.environ["INTENT_ACCOUNT_PRIVATE_KEY"]
84
+ PRIVATE_KEY = os.environ["PRIVATE_KEY"]
69
85
  AUTONITY_RPC_URL = "https://bakerloo.autonity-apis.com"
70
86
  ```
71
87
 
72
- Account IDs (addresses) may be retrieved from the private keys with `eth_account`:
88
+ ### Configuration
73
89
 
74
- ```py
75
- from eth_account import Account
76
-
77
- MARGIN_ACCOUNT_ID = Account.from_key(MARGIN_ACCOUNT_PRIVATE_KEY).address
78
- INTENT_ACCOUNT_ID = Account.from_key(INTENT_ACCOUNT_PRIVATE_KEY).address
79
- ```
90
+ An application instance can be created with the `afp.AFP()` constructor. An instance
91
+ is associated with a trading venue and a margin account.
80
92
 
81
- ### Clearing API
93
+ The required constructor arguments are the RPC provider URL and the authenticator of
94
+ the blockchain account that manages the margin account.
82
95
 
83
- Functions of the Clearing API can be accessed via the `afp.Clearing`
84
- session object. It connects to the specified Autonity RPC provider and
85
- communicates with the Clearing System smart contracts.
96
+ An "authenticator" is a service that implements the `afp.Authenticator` protocol.
97
+ Available options are `afp.PrivateKeyAuthenticator` that reads the private key
98
+ from a constructor argument, and `afp.KeyfileAuthenticator` that reads the private
99
+ key from an encrypted keyfile.
86
100
 
87
101
  ```py
88
102
  import afp
89
103
 
90
- clearing = afp.Clearing(MARGIN_ACCOUNT_PRIVATE_KEY, AUTONITY_RPC_URL)
104
+ app = afp.AFP(
105
+ rpc_url=AUTONITY_RPC_URL,
106
+ authenticator=afp.PrivateKeyAuthenticator(PRIVATE_KEY),
107
+ )
91
108
  ```
92
109
 
93
- Collateral can be deposited into the margin account with
94
- `clearing.deposit_into_margin_account()`.
110
+ ### Margin Account API
95
111
 
96
- ```py
97
- from decimal import Decimal
112
+ Margin accounts can be managed via the `MarginAccount` session object. It
113
+ connects to the specified Autonity RPC provider and communicates with the
114
+ Clearing System smart contracts.
98
115
 
99
- clearing.deposit_into_margin_account(COLLATERAL_ASSET, Decimal("100.00"))
100
- print(clearing.capital(COLLATERAL_ASSET))
116
+ ```py
117
+ margin_account = app.MarginAccount()
101
118
  ```
102
119
 
103
- The intent account should be authorized to submit orders. This is only required
104
- if the intent account and the margin account are different.
120
+ Collateral can be deposited into the margin account with `margin_account.deposit()`.
105
121
 
106
122
  ```py
107
- clearing.authorize(COLLATERAL_ASSET, INTENT_ACCOUNT_ID)
123
+ from decimal import Decimal
124
+
125
+ margin_account.deposit(COLLATERAL_ASSET, Decimal("100.00"))
126
+ print(margin_account.capital(COLLATERAL_ASSET))
108
127
  ```
109
128
 
110
129
  ### Trading API
111
130
 
112
- The functions of the trading API can be accessed via the `afp.Trading` session
113
- object. It communicates with the AutEx exchange and authenticates on creation with
114
- the intent account's private key.
131
+ Functions of the trading API can be accessed via the `Trading` session object.
132
+ It communicates with the AutEx exchange and authenticates on creation with the
133
+ intent account's private key. The intent account authenticator is optional, it
134
+ defaults to the authenticator set in the `AFP` constructor.
115
135
 
116
136
  ```py
117
- trading = afp.Trading(INTENT_ACCOUNT_PRIVATE_KEY)
137
+ trading = app.Trading()
118
138
  ```
119
139
 
120
140
  To start trading a product, its parameters shall be retrieved from the server.
@@ -169,28 +189,12 @@ print(fills)
169
189
 
170
190
  See further code examples in the [examples](./examples/) directory.
171
191
 
172
- ## Configuration
173
-
174
- By default the SDK communicates with the AFP Clearing System contracts on
175
- Autonity Mainnet, and the AutEx Exchange. Connection parameters can be
176
- overridden with the following environment variables:
177
-
178
- ```sh
179
- AFP_EXCHANGE_URL=
180
- AFP_CHAIN_ID=
181
- AFP_CLEARING_DIAMOND_ADDRESS=
182
- AFP_MARGIN_ACCOUNT_REGISTRY_ADDRESS=
183
- AFP_ORACLE_PROVIDER_ADDRESS=
184
- AFP_PRODUCT_REGISTRY_ADDRESS=
185
- AFP_SYSTEM_VIEWER_ADDRESS=
186
- ```
187
-
188
192
  ## Development
189
193
 
190
194
  The package uses [`uv`](https://docs.astral.sh/uv/) as project manager.
191
195
 
192
- - Dependecies can be installed with the `uv sync` command.
193
- - Linters can be executed with the `uv run poe lint` command.
194
- - Tests can be executed with the `uv run poe test` command.
195
- - Distributions can be checked before release with the `uv run poe check-dist` command.
196
- - Markdown API documentation can be generated with the `uv run poe doc-gen` command.
196
+ - Install dependencies with the `uv sync` command.
197
+ - Execute linters with the `uv run poe lint` command.
198
+ - Run tests with the `uv run poe test` command.
199
+ - Check distributions before release with the `uv run poe check-dist` command.
200
+ - Generate markdown API documentation with the `uv run poe doc-gen` command.
@@ -0,0 +1,37 @@
1
+ afp/__init__.py,sha256=tvvXz4miC3QI8wy4vjAAf_LtL-19eEgBRA6dnrh3LA8,349
2
+ afp/afp.py,sha256=1xpocnWjALP6QlA-Hn7G2znttJ-ikcb2WA9sLueY3zc,9107
3
+ afp/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ afp/api/admin.py,sha256=6TiWCo0SB41CtXYTkTYYGQY7MrdB6OMTyU3-0J0V_7o,2170
5
+ afp/api/base.py,sha256=5X1joEwFX4gxYUDCSTvp_hSFCfLZCktnW-UtZFxDquk,4471
6
+ afp/api/margin_account.py,sha256=sC1DF7J3QTHR7cXcRjTG33oZswVy6qr9xnevQbrQ-ew,15256
7
+ afp/api/product.py,sha256=2N4bPBahuw19GcBbBL20oQ5RFRuUxa5LOZzmN7hs39U,10156
8
+ afp/api/trading.py,sha256=exQQ3LJA6XCbRAUBuYVjSJ5PHFi4COyqDORxC37LLfQ,10379
9
+ afp/auth.py,sha256=sV_9E6CgRWV1xYoppc4IdrnqNo5ZNDBIp6QF3fQbMWE,2055
10
+ afp/bindings/__init__.py,sha256=_n9xoogYi8AAlSx_PE-wjnwP1ujVyDUwoRM0BSm243U,1271
11
+ afp/bindings/auctioneer_facet.py,sha256=4p906zdU2lUsqpWlsiLE3dlxTPrlNpqk8DtjiQUWJ8M,23919
12
+ afp/bindings/bankruptcy_facet.py,sha256=-Kp4Ap5PLAmo_tFrp-1s_yAh0mtHS20z3BC5YIrVjrU,12548
13
+ afp/bindings/clearing_facet.py,sha256=cfyDo-s9_DesbK1Oe-8rfbTCS3HBX9X-L6HbzSydEFk,34898
14
+ afp/bindings/erc20.py,sha256=-jw7ohcEbUL7g3PpK3-a5_7ClUDCDHt1bezm-w7exzo,10822
15
+ afp/bindings/facade.py,sha256=8IehHRqmt9xVe4OJLVAfedprv1fc6mLj7COOjpI_AwA,2588
16
+ afp/bindings/final_settlement_facet.py,sha256=1U5F3WFvf4GUml3gb90YF9RZK_xCwBsX2zRQ8sWSkR0,9470
17
+ afp/bindings/margin_account.py,sha256=hqvHpfM62SAEXSt__NZzAQGu9z7DnIa9LbSgHHkhsKE,45872
18
+ afp/bindings/margin_account_registry.py,sha256=mx9sAU6HuC09d-LAf9bzP57HPLa3M6qXpN7Lj-uiXSc,18800
19
+ afp/bindings/mark_price_tracker_facet.py,sha256=vnVsAmpts_2Sv9Jmx3JlON8X8FJd9D2jOFhqSwXIA7A,2830
20
+ afp/bindings/oracle_provider.py,sha256=CMpVAXdUuTP_W9ZE297B-m9MjuW90yCOhNLMVl3kCf0,15863
21
+ afp/bindings/product_registry.py,sha256=-_h786jzMCsaTqqnoxpmVgBkGf45eCUMthp_PkqrET0,42137
22
+ afp/bindings/system_viewer.py,sha256=0FivdhpfXMrBesXcHkfO9uELyr7GiRmGe36xS5sURGE,41094
23
+ afp/bindings/trading_protocol.py,sha256=ZloF3REbjFq9v0UGVsM0_Lk0EhfWJKdeJ0PzVEnyZo0,39573
24
+ afp/config.py,sha256=_WKywiuty8poE1A0v46uBe1JGpfCzRlxCPamKennfpE,699
25
+ afp/constants.py,sha256=XIph4R0Tx-BPw_NZJgWUtb7NdS9sYzEciFRSStm3VMs,1840
26
+ afp/decorators.py,sha256=SEUQtbgPGc4iVPtBQV2eiCejcDAVImmXcI0uPXFhtJA,2774
27
+ afp/enums.py,sha256=U5b_3RteMiVJozp4WYAKzUfPXI1RdJLEK19jAg-LN8Q,588
28
+ afp/exceptions.py,sha256=frdS-EH84K0fOf92RgRgNkTe3VII2m36XNCS8eyXLLM,390
29
+ afp/exchange.py,sha256=MXQ-RhcI76l6bA9_IDTUq0RQGfwxBisrRhl6eC_yH4I,5983
30
+ afp/hashing.py,sha256=gBCWN93-ydRPlgnnorSvDQlylcnglrAypRDb-1K-20I,1949
31
+ afp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ afp/schemas.py,sha256=M6qr0GhCeT7KevzOso5dIhydYzaYx78Rs66IWdadJO8,5284
33
+ afp/validators.py,sha256=zQvPu3HDu6ADnEE72EJlS5hc1-xfru78Mzd74s1u_EM,1841
34
+ afp_sdk-0.5.1.dist-info/licenses/LICENSE,sha256=ZdaKItgc2ppfqta2OJV0oHpSJiK87PUxmUkUo-_0SB8,1065
35
+ afp_sdk-0.5.1.dist-info/WHEEL,sha256=lh7MMMfiuFQLQaR9J7pNBODdWf-aa5UOeuuDAol3xps,79
36
+ afp_sdk-0.5.1.dist-info/METADATA,sha256=RM6EyQq2W_ZlrkRtYcGf85pZg7S15poSNJq7CUEHz0w,6354
37
+ afp_sdk-0.5.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.8.17
2
+ Generator: uv 0.8.20
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,2 +0,0 @@
1
- # Automatically created by ruff.
2
- *
Binary file
@@ -1 +0,0 @@
1
- Signature: 8a477f597d28d172789f06886806bc55
afp/api/liquidation.py DELETED
@@ -1,167 +0,0 @@
1
- from decimal import Decimal
2
- from functools import cache
3
- from typing import Iterable
4
-
5
- from hexbytes import HexBytes
6
- from web3 import Web3
7
-
8
- from .. import validators
9
- from ..bindings import (
10
- BidData,
11
- ClearingDiamond,
12
- ProductRegistry,
13
- Side as OnChainOrderSide,
14
- )
15
- from ..bindings.facade import CLEARING_DIAMOND_ABI
16
- from ..bindings.product_registry import ABI as PRODUCT_REGISTRY_ABI
17
- from ..decorators import convert_web3_error
18
- from ..enums import OrderSide
19
- from ..schemas import AuctionData, Bid
20
- from .base import ClearingSystemAPI
21
-
22
-
23
- class Liquidation(ClearingSystemAPI):
24
- """API for participating in liquidation auctions.
25
-
26
- Parameters
27
- ----------
28
- private_key : str
29
- The private key of the blockchain account that participates in a liquidation
30
- auction.
31
- autonity_rpc_url : str
32
- The URL of a JSON-RPC provider for Autonity. (HTTPS only.)
33
- """
34
-
35
- @staticmethod
36
- def create_bid(product_id: str, price: Decimal, quantity: int, side: str) -> Bid:
37
- """Create a bid to be submitted to a liquidation auction.
38
-
39
- Parameters
40
- ----------
41
- product_id : str
42
- price: Decimal
43
- quantity: int
44
- side: str
45
-
46
- Returns
47
- -------
48
- afp.schemas.Bid
49
- """
50
- return Bid(
51
- product_id=product_id,
52
- price=price,
53
- quantity=quantity,
54
- side=getattr(OrderSide, side.upper()),
55
- )
56
-
57
- ### Transactions ###
58
-
59
- @convert_web3_error(CLEARING_DIAMOND_ABI)
60
- def request_liquidation(self, margin_account_id: str, collateral_asset: str) -> str:
61
- """Request a liquidation auction to be started.
62
-
63
- Parameters
64
- ----------
65
- margin_account_id : str
66
- The ID of the margin account to be liquidated.
67
- collateral_asset : str
68
- The address of the collateral token that the margin account is trading with.
69
-
70
- Returns
71
- -------
72
- str
73
- The hash of the transaction.
74
- """
75
- margin_account_id = validators.validate_address(margin_account_id)
76
- collateral_asset = validators.validate_address(collateral_asset)
77
-
78
- clearing_contract = ClearingDiamond(self._w3)
79
- tx_hash = clearing_contract.request_liquidation(
80
- margin_account_id, collateral_asset
81
- ).transact()
82
- self._w3.eth.wait_for_transaction_receipt(tx_hash)
83
- return Web3.to_hex(tx_hash)
84
-
85
- @convert_web3_error(CLEARING_DIAMOND_ABI, PRODUCT_REGISTRY_ABI)
86
- def submit_bids(
87
- self, margin_account_id: str, collateral_asset: str, bids: Iterable[Bid]
88
- ) -> str:
89
- """Submit bids to a liquidation auction.
90
-
91
- Parameters
92
- ----------
93
- margin_account_id : str
94
- The ID of the margin account that is being liquidated.
95
- collateral_asset : str
96
- The address of the collateral token that the margin account is trading with.
97
- bids: list of afp.schemas.Bid
98
-
99
- Returns
100
- -------
101
- str
102
- The hash of the transaction.
103
- """
104
- margin_account_id = validators.validate_address(margin_account_id)
105
- collateral_asset = validators.validate_address(collateral_asset)
106
-
107
- converted_bids = [
108
- BidData(
109
- product_id=HexBytes(bid.product_id),
110
- price=int(bid.price * 10 ** self._tick_size(bid.product_id)),
111
- quantity=bid.quantity,
112
- side=getattr(OnChainOrderSide, bid.side.name),
113
- )
114
- for bid in bids
115
- ]
116
-
117
- clearing_contract = ClearingDiamond(self._w3)
118
- tx_hash = clearing_contract.bid_auction(
119
- margin_account_id, collateral_asset, converted_bids
120
- ).transact()
121
- self._w3.eth.wait_for_transaction_receipt(tx_hash)
122
- return Web3.to_hex(tx_hash)
123
-
124
- ### Views ###
125
-
126
- @convert_web3_error(CLEARING_DIAMOND_ABI)
127
- def auction_data(
128
- self, margin_account_id: str, collateral_asset: str
129
- ) -> AuctionData:
130
- """Returns information on a liquidation auction.
131
-
132
- Parameters
133
- ----------
134
- margin_account_id : str
135
- The ID of the margin account to be liquidated.
136
- collateral_asset : str
137
- The address of the collateral token that the margin account is trading with.
138
-
139
- Returns
140
- -------
141
- str
142
- The hash of the transaction.
143
- """
144
- margin_account_id = validators.validate_address(margin_account_id)
145
- collateral_asset = validators.validate_address(collateral_asset)
146
-
147
- clearing_contract = ClearingDiamond(self._w3)
148
- data = clearing_contract.auction_data(margin_account_id, collateral_asset)
149
- divisor = 10 ** self._decimals(collateral_asset)
150
- return AuctionData(
151
- start_block=data.start_block,
152
- margin_account_equity_at_initiation=(
153
- Decimal(data.mae_at_initiation) / divisor
154
- ),
155
- maintenance_margin_used_at_initiation=(
156
- Decimal(data.mmu_at_initiation) / divisor
157
- ),
158
- margin_account_equity_now=(Decimal(data.mae_now) / divisor),
159
- maintenance_margin_used_now=(Decimal(data.mmu_now) / divisor),
160
- )
161
-
162
- ### Internal getters ###
163
-
164
- @cache
165
- def _tick_size(self, product_id: str) -> int:
166
- product_registry_contract = ProductRegistry(self._w3)
167
- return product_registry_contract.tick_size(HexBytes(product_id))