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/api/clearing.py ADDED
@@ -0,0 +1,377 @@
1
+ from decimal import Decimal
2
+ from functools import cache
3
+
4
+ from eth_typing.evm import ChecksumAddress
5
+ from hexbytes import HexBytes
6
+ from web3 import Web3
7
+ from web3.exceptions import ContractCustomError
8
+
9
+ from .. import validators
10
+ from ..bindings import (
11
+ ClearingDiamond,
12
+ MarginAccount,
13
+ MarginAccountRegistry,
14
+ ProductRegistry,
15
+ )
16
+ from ..bindings.erc20 import ERC20
17
+ from ..bindings.facade import CLEARING_DIAMOND_ABI
18
+ from ..bindings.margin_account import ABI as MARGIN_CONTRACT_ABI
19
+ from ..bindings.margin_account_registry import ABI as MARGIN_ACCOUNT_REGISTRY_ABI
20
+ from ..bindings.product_registry import ABI as PRODUCT_REGISTRY_ABI
21
+ from ..decorators import convert_web3_error
22
+ from ..exceptions import NotFoundError
23
+ from ..schemas import Position
24
+ from .base import ClearingSystemAPI
25
+ from .builder import Builder
26
+
27
+
28
+ class Clearing(ClearingSystemAPI):
29
+ """API for managing margin accounts.
30
+
31
+ Parameters
32
+ ----------
33
+ private_key : str
34
+ The private key of the blockchain account that manages the margin account.
35
+ autonity_rpc_url : str
36
+ The URL of a JSON-RPC provider for Autonity. (HTTPS only.)
37
+ """
38
+
39
+ ### Transactions ###
40
+
41
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
42
+ def authorize(self, collateral_asset: str, intent_account_id: str) -> str:
43
+ """Authorizes a blockchain account to submit intents to the clearing system
44
+ using the margin account associated with the collateral asset.
45
+
46
+ Parameters
47
+ ----------
48
+ collateral_asset : str
49
+ The address of the collateral token.
50
+ intent_account_id : str
51
+ The address of the intent account.
52
+
53
+ Returns
54
+ -------
55
+ str
56
+ The hash of the transaction.
57
+ """
58
+ collateral_asset = validators.validate_address(collateral_asset)
59
+ intent_account_id = validators.validate_address(intent_account_id)
60
+
61
+ tx_hash = (
62
+ self._margin_contract(collateral_asset)
63
+ .authorize(intent_account_id)
64
+ .transact()
65
+ )
66
+ self._w3.eth.wait_for_transaction_receipt(tx_hash)
67
+ return Web3.to_hex(tx_hash)
68
+
69
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
70
+ def deposit_into_margin_account(
71
+ self, collateral_asset: str, amount: Decimal
72
+ ) -> tuple[str, str]:
73
+ """Deposits the specified amount of collateral tokens into the margin account
74
+ associated with the collateral asset.
75
+
76
+ First approves the token transfer with the collateral token, then executes the
77
+ transfer.
78
+
79
+ Parameters
80
+ ----------
81
+ collateral_asset : str
82
+ The address of the collateral token.
83
+ amount : Decimal
84
+ The amount of collateral tokens to deposit.
85
+
86
+ Returns
87
+ -------
88
+ str
89
+ The hash of the approval transaction.
90
+ str
91
+ The hash of the deposit transaction.
92
+ """
93
+ collateral_asset = validators.validate_address(collateral_asset)
94
+ token_amount = int(amount * 10 ** self._decimals(collateral_asset))
95
+ token_contract = ERC20(self._w3, collateral_asset)
96
+
97
+ tx1_hash = token_contract.approve(
98
+ self._margin_contract(collateral_asset)._contract.address, # type: ignore
99
+ token_amount,
100
+ ).transact()
101
+ self._w3.eth.wait_for_transaction_receipt(tx1_hash)
102
+
103
+ tx2_hash = (
104
+ self._margin_contract(collateral_asset).deposit(token_amount).transact()
105
+ )
106
+ self._w3.eth.wait_for_transaction_receipt(tx2_hash)
107
+
108
+ return (Web3.to_hex(tx1_hash), Web3.to_hex(tx2_hash))
109
+
110
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
111
+ def withdraw_from_margin_account(
112
+ self, collateral_asset: str, amount: Decimal
113
+ ) -> str:
114
+ """Withdraws the specified amount of collateral tokens from the margin account
115
+ associated with the collateral asset.
116
+
117
+ Parameters
118
+ ----------
119
+ collateral_asset : str
120
+ The address of the collateral token.
121
+ amount : Decimal
122
+ The amount of collateral tokens to withdraw.
123
+
124
+ Returns
125
+ -------
126
+ str
127
+ The hash of the transaction.
128
+ """
129
+ collateral_asset = validators.validate_address(collateral_asset)
130
+ token_amount = int(amount * 10 ** self._decimals(collateral_asset))
131
+ tx_hash = (
132
+ self._margin_contract(collateral_asset).withdraw(token_amount).transact()
133
+ )
134
+ self._w3.eth.wait_for_transaction_receipt(tx_hash)
135
+ return Web3.to_hex(tx_hash)
136
+
137
+ @convert_web3_error(CLEARING_DIAMOND_ABI)
138
+ def initiate_final_settlement(self, product_id: str, accounts: list[str]) -> str:
139
+ """Initiate final settlement (closeout) process for the specified accounts.
140
+
141
+ The product must be in Final Settlement state. The accounts must hold non-zero
142
+ positions in the product that offset each other (i.e. the sum of their position
143
+ sizes is 0.)
144
+
145
+ Parameters
146
+ ----------
147
+ product_id : str
148
+ The ID of the product.
149
+ accounts : list of str
150
+ List of margin account IDs to initiate settlement for.
151
+
152
+ Returns
153
+ -------
154
+ str
155
+ The hash of the transaction.
156
+ """
157
+ product_id = validators.validate_hexstr32(product_id)
158
+ addresses = [validators.validate_address(account) for account in accounts]
159
+
160
+ clearing_contract = ClearingDiamond(self._w3)
161
+ tx_hash = clearing_contract.initiate_final_settlement(
162
+ HexBytes(product_id), addresses
163
+ ).transact()
164
+ self._w3.eth.wait_for_transaction_receipt(tx_hash)
165
+ return Web3.to_hex(tx_hash)
166
+
167
+ ### Views ###
168
+
169
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
170
+ def capital(self, collateral_asset: str) -> Decimal:
171
+ """Returns the amount of collateral tokens in the margin account associated
172
+ with the collateral asset.
173
+
174
+ Parameters
175
+ ----------
176
+ collateral_asset : str
177
+ The address of the collateral token.
178
+
179
+ Returns
180
+ -------
181
+ Decimal
182
+ """
183
+ collateral_asset = validators.validate_address(collateral_asset)
184
+ amount = self._margin_contract(collateral_asset).capital(self._account.address)
185
+ return Decimal(amount) / 10 ** self._decimals(collateral_asset)
186
+
187
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
188
+ def position(self, collateral_asset: str, position_id: str) -> Position:
189
+ """Returns the parameters of a position in the margin account associated with
190
+ the collateral asset.
191
+
192
+ Parameters
193
+ ----------
194
+ collateral_asset : str
195
+ The address of the collateral token.
196
+ position_id: str
197
+ The ID of the position.
198
+
199
+ Returns
200
+ -------
201
+ afp.schemas.Position
202
+ """
203
+ validators.validate_hexstr32(position_id)
204
+ data = self._margin_contract(collateral_asset).position_data(
205
+ self._account.address, HexBytes(position_id)
206
+ )
207
+ decimals = self._decimals(collateral_asset)
208
+ return Position(
209
+ id=Web3.to_hex(data.position_id),
210
+ quantity=data.quantity,
211
+ cost_basis=Decimal(data.cost_basis) / 10**decimals,
212
+ maintenance_margin=Decimal(data.maintenance_margin) / 10**decimals,
213
+ pnl=Decimal(data.pnl) / 10**decimals,
214
+ )
215
+
216
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
217
+ def positions(self, collateral_asset: str) -> list[Position]:
218
+ """Returns all positions in the margin account associated with the collateral
219
+ asset.
220
+
221
+ Parameters
222
+ ----------
223
+ collateral_asset : str
224
+ The address of the collateral token.
225
+
226
+ Returns
227
+ -------
228
+ list of afp.schemas.Position
229
+ """
230
+ collateral_asset = validators.validate_address(collateral_asset)
231
+ position_ids = self._margin_contract(collateral_asset).positions(
232
+ self._account.address
233
+ )
234
+ return [self.position(collateral_asset, Web3.to_hex(id)) for id in position_ids]
235
+
236
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
237
+ def margin_account_equity(self, collateral_asset: str) -> Decimal:
238
+ """Returns the margin account equity in the margin account associated with the
239
+ collateral asset.
240
+
241
+ Parameters
242
+ ----------
243
+ collateral_asset : str
244
+ The address of the collateral token.
245
+
246
+ Returns
247
+ -------
248
+ Decimal
249
+ """
250
+ collateral_asset = validators.validate_address(collateral_asset)
251
+ amount = self._margin_contract(collateral_asset).mae(self._account.address)
252
+ return Decimal(amount) / 10 ** self._decimals(collateral_asset)
253
+
254
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
255
+ def maintenance_margin_available(self, collateral_asset: str) -> Decimal:
256
+ """Returns the maintenance margin available in the margin account associated
257
+ with the collateral asset.
258
+
259
+ Parameters
260
+ ----------
261
+ collateral_asset : str
262
+ The address of the collateral token.
263
+
264
+ Returns
265
+ -------
266
+ Decimal
267
+ """
268
+ collateral_asset = validators.validate_address(collateral_asset)
269
+ amount = self._margin_contract(collateral_asset).mma(self._account.address)
270
+ return Decimal(amount) / 10 ** self._decimals(collateral_asset)
271
+
272
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
273
+ def maintenance_margin_used(self, collateral_asset: str) -> Decimal:
274
+ """Returns the maintenance margin used in the margin account associated with
275
+ the collateral asset.
276
+
277
+ Parameters
278
+ ----------
279
+ collateral_asset : str
280
+ The address of the collateral token.
281
+
282
+ Returns
283
+ -------
284
+ Decimal
285
+ """
286
+ collateral_asset = validators.validate_address(collateral_asset)
287
+ amount = self._margin_contract(collateral_asset).mmu(self._account.address)
288
+ return Decimal(amount) / 10 ** self._decimals(collateral_asset)
289
+
290
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
291
+ def profit_and_loss(self, collateral_asset: str) -> Decimal:
292
+ """Returns the profit and loss in the margin account associated with the
293
+ collateral asset.
294
+
295
+ Parameters
296
+ ----------
297
+ collateral_asset : str
298
+ The address of the collateral token.
299
+
300
+ Returns
301
+ -------
302
+ Decimal
303
+ """
304
+ collateral_asset = validators.validate_address(collateral_asset)
305
+ amount = self._margin_contract(collateral_asset).pnl(self._account.address)
306
+ return Decimal(amount) / 10 ** self._decimals(collateral_asset)
307
+
308
+ @convert_web3_error(MARGIN_CONTRACT_ABI)
309
+ def withdrawable_amount(self, collateral_asset: str) -> Decimal:
310
+ """Returns the amount of collateral tokens withdrawable from the margin account
311
+ associated with the collateral asset.
312
+
313
+ Parameters
314
+ ----------
315
+ collateral_asset : str
316
+ The address of the collateral token.
317
+
318
+ Returns
319
+ -------
320
+ Decimal
321
+ """
322
+ collateral_asset = validators.validate_address(collateral_asset)
323
+ amount = self._margin_contract(collateral_asset).withdrawable(
324
+ self._account.address
325
+ )
326
+ return Decimal(amount) / 10 ** self._decimals(collateral_asset)
327
+
328
+ @convert_web3_error(PRODUCT_REGISTRY_ABI)
329
+ def collateral_asset(self, product_id: str) -> str:
330
+ """Returns the collateral asset of a product.
331
+
332
+ Parameters
333
+ ----------
334
+ product_id : str
335
+ The ID of the product.
336
+
337
+ Returns
338
+ -------
339
+ str
340
+ """
341
+ product_registry_contract = ProductRegistry(self._w3)
342
+ collateral_asset = product_registry_contract.collateral_asset(
343
+ HexBytes(product_id)
344
+ )
345
+ if Web3.to_int(hexstr=collateral_asset) == 0:
346
+ raise NotFoundError("Product not found in the product registry")
347
+ return collateral_asset
348
+
349
+ def product_state(self, product_id: str) -> str:
350
+ """Returns the current state of a product.
351
+
352
+ Parameters
353
+ ----------
354
+ product_id : str
355
+ The ID of the product.
356
+
357
+ Returns
358
+ -------
359
+ str
360
+ """
361
+ return Builder.product_state(self, product_id)
362
+
363
+ ### Internal getters ###
364
+
365
+ @cache
366
+ @convert_web3_error(MARGIN_ACCOUNT_REGISTRY_ABI)
367
+ def _margin_contract(self, collateral_asset: ChecksumAddress) -> MarginAccount:
368
+ margin_account_registry_contract = MarginAccountRegistry(self._w3)
369
+ try:
370
+ margin_contract_address = (
371
+ margin_account_registry_contract.get_margin_account(
372
+ Web3.to_checksum_address(collateral_asset)
373
+ )
374
+ )
375
+ except ContractCustomError:
376
+ raise NotFoundError("No margin account found for collateral asset")
377
+ return MarginAccount(self._w3, margin_contract_address)
afp/api/liquidation.py ADDED
@@ -0,0 +1,167 @@
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))