afp-sdk 0.1.2__tar.gz → 0.2.0__tar.gz
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_sdk-0.1.2 → afp_sdk-0.2.0}/CHANGELOG.md +15 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/PKG-INFO +1 -1
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/api/trading.py +13 -1
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/trading_protocol.py +121 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/validators.py +13 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/pyproject.toml +1 -1
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/tests/test_validators.py +24 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/uv.lock +2 -2
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/.env.template +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/.envrc +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/LICENSE +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/README.md +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/__init__.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/api/__init__.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/api/admin.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/api/base.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/api/builder.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/api/clearing.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/api/liquidation.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/__init__.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/auctioneer_facet.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/bankruptcy_facet.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/clearing_facet.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/erc20.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/facade.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/final_settlement_facet.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/margin_account.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/margin_account_registry.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/mark_price_tracker_facet.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/oracle_provider.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/bindings/product_registry.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/config.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/decorators.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/enums.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/exceptions.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/exchange.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/py.typed +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/schemas.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/afp/signing.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/devenv.lock +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/devenv.nix +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/devenv.yaml +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/examples/cancel_order.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/examples/create_product.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/examples/execute_trade.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/tests/__init__.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/tests/test_decorators.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/tests/test_hashing.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/tests/test_login.py +0 -0
- {afp_sdk-0.1.2 → afp_sdk-0.2.0}/tests/test_schemas.py +0 -0
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [v0.2.0] - 2025-09-02
|
|
2
|
+
|
|
3
|
+
### Changed
|
|
4
|
+
|
|
5
|
+
- Validate limit price to make sure it conforms to the product's tick size
|
|
6
|
+
([#12](https://github.com/autonity/afp-sdk/pull/12))
|
|
7
|
+
- Update Clearing System contract bindings for Autonity Bakerloo (Nile) Testnet
|
|
8
|
+
deployment as of 2025-09-02 ([#13](https://github.com/autonity/afp-sdk/pull/13))
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add optional `rounding` argument to `afp.Trading.create_intent`
|
|
13
|
+
([#12](https://github.com/autonity/afp-sdk/pull/12))
|
|
14
|
+
|
|
1
15
|
## [v0.1.2] - 2025-08-28
|
|
2
16
|
|
|
3
17
|
### Fixed
|
|
@@ -14,6 +28,7 @@
|
|
|
14
28
|
|
|
15
29
|
_First release._
|
|
16
30
|
|
|
31
|
+
[v0.2.0]: https://github.com/autonity/afp-sdk/releases/tag/v0.2.0
|
|
17
32
|
[v0.1.2]: https://github.com/autonity/afp-sdk/releases/tag/v0.1.2
|
|
18
33
|
[v0.1.1]: https://github.com/autonity/afp-sdk/releases/tag/v0.1.1
|
|
19
34
|
[v0.1.0]: https://github.com/autonity/afp-sdk/releases/tag/v0.1.0
|
|
@@ -54,6 +54,7 @@ class Trading(ExchangeAPI):
|
|
|
54
54
|
max_trading_fee_rate: Decimal,
|
|
55
55
|
good_until_time: datetime,
|
|
56
56
|
margin_account_id: str | None = None,
|
|
57
|
+
rounding: str | None = None,
|
|
57
58
|
) -> Intent:
|
|
58
59
|
"""Creates an intent with the given intent data, generates its hash and signs it
|
|
59
60
|
with the configured account's private key.
|
|
@@ -62,6 +63,13 @@ class Trading(ExchangeAPI):
|
|
|
62
63
|
is assumed to be the same as the margin account if the margin account ID is not
|
|
63
64
|
specified.
|
|
64
65
|
|
|
66
|
+
The limit price must have at most as many fractional digits as the product's
|
|
67
|
+
tick size, or if `rounding` is specified then the limit price is rounded to the
|
|
68
|
+
appropriate number of fractional digits. `rounding` may be one of
|
|
69
|
+
`ROUND_CEILING`, `ROUND_FLOOR`, `ROUND_UP`, `ROUND_DOWN`, `ROUND_HALF_UP`,
|
|
70
|
+
`ROUND_HALF_DOWN`, `ROUND_HALF_EVEN` and `ROUND_05UP`; see the rounding modes of
|
|
71
|
+
the `decimal` module of the Python Standard Library.
|
|
72
|
+
|
|
65
73
|
Parameters
|
|
66
74
|
----------
|
|
67
75
|
product : afp.schemas.ExchangeProduct
|
|
@@ -71,6 +79,8 @@ class Trading(ExchangeAPI):
|
|
|
71
79
|
max_trading_fee_rate : decimal.Decimal
|
|
72
80
|
good_until_time : datetime.datetime
|
|
73
81
|
margin_account_id : str, optional
|
|
82
|
+
rounding : str, optional
|
|
83
|
+
A rounding mode of the `decimal` module or `None` for no rounding.
|
|
74
84
|
|
|
75
85
|
Returns
|
|
76
86
|
-------
|
|
@@ -85,7 +95,9 @@ class Trading(ExchangeAPI):
|
|
|
85
95
|
intent_data = IntentData(
|
|
86
96
|
trading_protocol_id=self._trading_protocol_id,
|
|
87
97
|
product_id=product.id,
|
|
88
|
-
limit_price=
|
|
98
|
+
limit_price=validators.validate_limit_price(
|
|
99
|
+
Decimal(limit_price), product.tick_size, rounding
|
|
100
|
+
),
|
|
89
101
|
quantity=quantity,
|
|
90
102
|
max_trading_fee_rate=max_trading_fee_rate,
|
|
91
103
|
side=getattr(OrderSide, side.upper()),
|
|
@@ -59,6 +59,24 @@ class Trade:
|
|
|
59
59
|
intents: typing.List[Intent]
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
@dataclass
|
|
63
|
+
class Settlement:
|
|
64
|
+
"""Port of `struct Settlement` on the IMarginAccount contract."""
|
|
65
|
+
|
|
66
|
+
position_id: hexbytes.HexBytes
|
|
67
|
+
quantity: int
|
|
68
|
+
price: int
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class Order:
|
|
73
|
+
"""Port of `struct Order` on the ITradingProtocol contract."""
|
|
74
|
+
|
|
75
|
+
margin_account_id: eth_typing.ChecksumAddress
|
|
76
|
+
margin_account_contract: eth_typing.ChecksumAddress
|
|
77
|
+
settlement: Settlement
|
|
78
|
+
|
|
79
|
+
|
|
62
80
|
class TradingProtocol:
|
|
63
81
|
"""TradingProtocol contract binding.
|
|
64
82
|
|
|
@@ -101,6 +119,11 @@ class TradingProtocol:
|
|
|
101
119
|
"""Binding for `event RoleRevoked` on the TradingProtocol contract."""
|
|
102
120
|
return self._contract.events.RoleRevoked
|
|
103
121
|
|
|
122
|
+
@property
|
|
123
|
+
def SequenceExecuted(self) -> contract.ContractEvent:
|
|
124
|
+
"""Binding for `event SequenceExecuted` on the TradingProtocol contract."""
|
|
125
|
+
return self._contract.events.SequenceExecuted
|
|
126
|
+
|
|
104
127
|
@property
|
|
105
128
|
def Upgraded(self) -> contract.ContractEvent:
|
|
106
129
|
"""Binding for `event Upgraded` on the TradingProtocol contract."""
|
|
@@ -450,6 +473,36 @@ class TradingProtocol:
|
|
|
450
473
|
clearing_address,
|
|
451
474
|
)
|
|
452
475
|
|
|
476
|
+
def order_mae_check(
|
|
477
|
+
self,
|
|
478
|
+
orders: typing.List[Order],
|
|
479
|
+
) -> typing.List[bool]:
|
|
480
|
+
"""Binding for `orderMAECheck` on the TradingProtocol contract.
|
|
481
|
+
|
|
482
|
+
Parameters
|
|
483
|
+
----------
|
|
484
|
+
orders : typing.List[Order]
|
|
485
|
+
|
|
486
|
+
Returns
|
|
487
|
+
-------
|
|
488
|
+
typing.List[bool]
|
|
489
|
+
"""
|
|
490
|
+
return_value = self._contract.functions.orderMAECheck(
|
|
491
|
+
[
|
|
492
|
+
(
|
|
493
|
+
item.margin_account_id,
|
|
494
|
+
item.margin_account_contract,
|
|
495
|
+
(
|
|
496
|
+
item.settlement.position_id,
|
|
497
|
+
item.settlement.quantity,
|
|
498
|
+
item.settlement.price,
|
|
499
|
+
),
|
|
500
|
+
)
|
|
501
|
+
for item in orders
|
|
502
|
+
],
|
|
503
|
+
).call()
|
|
504
|
+
return [bool(return_value_elem) for return_value_elem in return_value]
|
|
505
|
+
|
|
453
506
|
def proxiable_uuid(
|
|
454
507
|
self,
|
|
455
508
|
) -> hexbytes.HexBytes:
|
|
@@ -718,6 +771,25 @@ ABI = typing.cast(
|
|
|
718
771
|
"name": "RoleRevoked",
|
|
719
772
|
"type": "event",
|
|
720
773
|
},
|
|
774
|
+
{
|
|
775
|
+
"anonymous": False,
|
|
776
|
+
"inputs": [
|
|
777
|
+
{
|
|
778
|
+
"indexed": False,
|
|
779
|
+
"internalType": "bool[]",
|
|
780
|
+
"name": "results",
|
|
781
|
+
"type": "bool[]",
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
"indexed": False,
|
|
785
|
+
"internalType": "bytes[]",
|
|
786
|
+
"name": "errors",
|
|
787
|
+
"type": "bytes[]",
|
|
788
|
+
},
|
|
789
|
+
],
|
|
790
|
+
"name": "SequenceExecuted",
|
|
791
|
+
"type": "event",
|
|
792
|
+
},
|
|
721
793
|
{
|
|
722
794
|
"anonymous": False,
|
|
723
795
|
"inputs": [
|
|
@@ -1100,6 +1172,55 @@ ABI = typing.cast(
|
|
|
1100
1172
|
"stateMutability": "nonpayable",
|
|
1101
1173
|
"type": "function",
|
|
1102
1174
|
},
|
|
1175
|
+
{
|
|
1176
|
+
"inputs": [
|
|
1177
|
+
{
|
|
1178
|
+
"components": [
|
|
1179
|
+
{
|
|
1180
|
+
"internalType": "address",
|
|
1181
|
+
"name": "marginAccountID",
|
|
1182
|
+
"type": "address",
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
"internalType": "address",
|
|
1186
|
+
"name": "marginAccountContract",
|
|
1187
|
+
"type": "address",
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
"components": [
|
|
1191
|
+
{
|
|
1192
|
+
"internalType": "bytes32",
|
|
1193
|
+
"name": "positionId",
|
|
1194
|
+
"type": "bytes32",
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
"internalType": "int256",
|
|
1198
|
+
"name": "quantity",
|
|
1199
|
+
"type": "int256",
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
"internalType": "uint256",
|
|
1203
|
+
"name": "price",
|
|
1204
|
+
"type": "uint256",
|
|
1205
|
+
},
|
|
1206
|
+
],
|
|
1207
|
+
"internalType": "struct IMarginAccount.Settlement",
|
|
1208
|
+
"name": "settlement",
|
|
1209
|
+
"type": "tuple",
|
|
1210
|
+
},
|
|
1211
|
+
],
|
|
1212
|
+
"internalType": "struct ITradingProtocol.Order[]",
|
|
1213
|
+
"name": "orders",
|
|
1214
|
+
"type": "tuple[]",
|
|
1215
|
+
}
|
|
1216
|
+
],
|
|
1217
|
+
"name": "orderMAECheck",
|
|
1218
|
+
"outputs": [
|
|
1219
|
+
{"internalType": "bool[]", "name": "results", "type": "bool[]"}
|
|
1220
|
+
],
|
|
1221
|
+
"stateMutability": "view",
|
|
1222
|
+
"type": "function",
|
|
1223
|
+
},
|
|
1103
1224
|
{
|
|
1104
1225
|
"inputs": [],
|
|
1105
1226
|
"name": "proxiableUUID",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from datetime import datetime, timedelta
|
|
2
|
+
from decimal import Decimal
|
|
2
3
|
|
|
3
4
|
from binascii import Error
|
|
4
5
|
from eth_typing.evm import ChecksumAddress
|
|
@@ -41,3 +42,15 @@ def validate_address(value: str) -> ChecksumAddress:
|
|
|
41
42
|
return Web3.to_checksum_address(value)
|
|
42
43
|
except ValueError:
|
|
43
44
|
raise ValueError(f"{value} is not a valid blockchain address")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def validate_limit_price(
|
|
48
|
+
value: Decimal, tick_size: int, rounding: str | None = None
|
|
49
|
+
) -> Decimal:
|
|
50
|
+
if rounding is None:
|
|
51
|
+
num_fractional_digits = abs(int(value.normalize().as_tuple().exponent))
|
|
52
|
+
if num_fractional_digits > tick_size:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"Limit price {value} can have at most {tick_size} fractional digits"
|
|
55
|
+
)
|
|
56
|
+
return value.quantize(Decimal("10") ** -tick_size, rounding=rounding)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import decimal
|
|
1
2
|
from datetime import datetime, UTC
|
|
3
|
+
from decimal import Decimal
|
|
2
4
|
|
|
3
5
|
import pytest
|
|
4
6
|
|
|
@@ -45,3 +47,25 @@ def test_validate_hexstr32__pass():
|
|
|
45
47
|
def test_validate_hexstr32__error(value):
|
|
46
48
|
with pytest.raises(ValueError):
|
|
47
49
|
validators.validate_hexstr32(value)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_validate_limit_price__pass():
|
|
53
|
+
assert str(validators.validate_limit_price(Decimal("1.25"), 2)) == "1.25"
|
|
54
|
+
assert str(validators.validate_limit_price(Decimal("1.25"), 4)) == "1.2500"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_validate_limit_price__rounding():
|
|
58
|
+
assert (
|
|
59
|
+
str(validators.validate_limit_price(Decimal("1.25"), 1, decimal.ROUND_DOWN))
|
|
60
|
+
== "1.2"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_validate_limit_price__error():
|
|
65
|
+
with pytest.raises(ValueError):
|
|
66
|
+
validators.validate_limit_price(Decimal("1.25"), 1)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_validate_limit_price__invalid_rounding_mode():
|
|
70
|
+
with pytest.raises(TypeError):
|
|
71
|
+
validators.validate_limit_price(Decimal("1.25"), 1, "foobar")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
version = 1
|
|
2
|
-
revision =
|
|
2
|
+
revision = 2
|
|
3
3
|
requires-python = ">=3.11"
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
@@ -13,7 +13,7 @@ wheels = [
|
|
|
13
13
|
|
|
14
14
|
[[package]]
|
|
15
15
|
name = "afp-sdk"
|
|
16
|
-
version = "0.
|
|
16
|
+
version = "0.2.0"
|
|
17
17
|
source = { editable = "." }
|
|
18
18
|
dependencies = [
|
|
19
19
|
{ name = "decorator" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|