avantis-trader-sdk 0.2.1__py3-none-any.whl → 0.3.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.
@@ -1,4 +1,5 @@
1
1
  from .client import TraderClient
2
2
  from .feed.feed_client import FeedClient
3
+ from .signers.base import BaseSigner
3
4
 
4
5
  __version__ = "0.2.1"
@@ -13,18 +13,29 @@ from .rpc.trade import TradeRPC
13
13
  from .utils import decoder
14
14
  from .feed.feed_client import FeedClient
15
15
 
16
+ from .signers.base import BaseSigner
17
+ from .signers.local_signer import LocalSigner
18
+ from .signers.kms_signer import KMSSigner
19
+
16
20
 
17
21
  class TraderClient:
18
22
  """
19
23
  This class provides methods to interact with the Avantis smart contracts.
20
24
  """
21
25
 
22
- def __init__(self, provider_url, l1_provider_url="https://eth.llamarpc.com"):
26
+ def __init__(
27
+ self,
28
+ provider_url,
29
+ l1_provider_url="https://eth.llamarpc.com",
30
+ signer: BaseSigner = None,
31
+ ):
23
32
  """
24
33
  Constructor for the TraderClient class.
25
34
 
26
35
  Args:
27
36
  provider_url: The URL of the Ethereum node provider.
37
+ l1_provider_url (optional): The URL of the L1 Ethereum node provider.
38
+ signer (optional): The signer to use for signing transactions.
28
39
  """
29
40
  self.web3 = Web3(
30
41
  Web3.HTTPProvider(provider_url, request_kwargs={"timeout": 60})
@@ -57,6 +68,8 @@ class TraderClient:
57
68
  self.snapshot = SnapshotRPC(self)
58
69
  self.trade = TradeRPC(self, FeedClient)
59
70
 
71
+ self.signer = signer
72
+
60
73
  def load_contract(self, name):
61
74
  """
62
75
  Loads the contract ABI and address from the local filesystem.
@@ -105,46 +118,87 @@ class TraderClient:
105
118
 
106
119
  return raw_data
107
120
 
108
- async def write_contract(
109
- self, private_key, contract_name, function_name, *args, **kwargs
110
- ):
121
+ async def write_contract(self, contract_name, function_name, *args, **kwargs):
111
122
  """
112
123
  Calls a write function of a contract.
113
124
 
114
125
  Args:
115
- private_key: The private key of the wallet.
116
126
  contract_name: The name of the contract.
117
127
  function_name: The name of the function.
118
128
  args: The arguments to the function.
119
129
 
120
130
  Returns:
121
- The transaction hash or the transaction object if private key is None.
131
+ The transaction hash or the transaction object if signer is None.
122
132
  """
123
133
  contract = self.contracts.get(contract_name)
124
134
  if not contract:
125
135
  raise ValueError(f"Contract {contract_name} not found")
126
136
 
127
- transaction = contract.functions[function_name](*args).build_transaction(kwargs)
137
+ if self.has_signer() and "from" not in kwargs:
138
+ kwargs["from"] = self.get_signer().get_ethereum_address()
139
+
140
+ transaction = await contract.functions[function_name](*args).build_transaction(
141
+ kwargs
142
+ )
128
143
 
129
- if private_key is None:
144
+ if not self.has_signer():
130
145
  return transaction
131
146
 
132
- signed_txn = await self.sign_transaction(private_key, transaction)
147
+ signed_txn = await self.sign_transaction(transaction)
133
148
  tx_hash = await self.send_and_get_transaction_hash(signed_txn)
134
149
  return tx_hash
135
150
 
136
- async def sign_transaction(self, private_key, transaction):
151
+ def set_signer(self, signer: BaseSigner):
152
+ """
153
+ Sets the signer.
154
+ """
155
+ self.signer = signer
156
+
157
+ def get_signer(self):
158
+ """
159
+ Gets the signer.
160
+ """
161
+ return self.signer
162
+
163
+ def remove_signer(self):
164
+ """
165
+ Removes the signer.
166
+ """
167
+ self.signer = None
168
+
169
+ def has_signer(self):
170
+ """
171
+ Checks if the signer is set.
172
+ """
173
+ return self.signer is not None
174
+
175
+ def set_local_signer(self, private_key):
176
+ """
177
+ Sets the local signer.
178
+ """
179
+ self.signer = LocalSigner(private_key, self.async_web3)
180
+
181
+ def set_aws_kms_signer(self, kms_key_id, region_name="us-east-1"):
182
+ """
183
+ Sets the AWS KMS signer.
184
+ """
185
+ self.signer = KMSSigner(self.async_web3, kms_key_id, region_name)
186
+
187
+ async def sign_transaction(self, transaction):
137
188
  """
138
189
  Signs a transaction.
139
190
 
140
191
  Args:
141
- private_key: The private key of the wallet.
142
192
  transaction: The transaction object.
143
193
 
144
194
  Returns:
145
195
  The signed transaction object.
146
196
  """
147
- return self.async_web3.eth.account.sign_transaction(transaction, private_key)
197
+ if not self.has_signer():
198
+ raise ValueError(
199
+ "No signer is set. Please set a signer using `set_signer`."
200
+ )
201
+ return await self.signer.sign_transaction(transaction)
148
202
 
149
203
  async def send_and_get_transaction_hash(self, signed_txn):
150
204
  """
@@ -170,31 +224,34 @@ class TraderClient:
170
224
  """
171
225
  return await self.async_web3.eth.wait_for_transaction_receipt(tx_hash)
172
226
 
173
- async def sign_and_get_receipt(self, private_key, transaction):
227
+ async def sign_and_get_receipt(self, transaction):
174
228
  """
175
229
  Signs a transaction and waits for it to be mined.
176
230
 
177
231
  Args:
178
- private_key: The private key of the wallet.
179
232
  transaction: The transaction object.
180
233
 
181
234
  Returns:
182
235
  The transaction receipt.
183
236
  """
184
- signed_txn = await self.sign_transaction(private_key, transaction)
237
+ gas_estimate = await self.get_gas_estimate(transaction)
238
+ transaction["gas"] = gas_estimate
239
+ signed_txn = await self.sign_transaction(transaction)
185
240
  tx_hash = await self.send_and_get_transaction_hash(signed_txn)
186
241
  return await self.wait_for_transaction_receipt(tx_hash)
187
242
 
188
- async def get_transaction_count(self, address):
243
+ async def get_transaction_count(self, address=None):
189
244
  """
190
245
  Gets the transaction count.
191
246
 
192
247
  Args:
193
- address: The address.
248
+ address (optional): The address.
194
249
 
195
250
  Returns:
196
251
  The transaction count.
197
252
  """
253
+ if address is None:
254
+ address = self.get_signer().get_ethereum_address()
198
255
  return await self.async_web3.eth.get_transaction_count(address)
199
256
 
200
257
  async def get_gas_price(self):
@@ -215,31 +272,70 @@ class TraderClient:
215
272
  """
216
273
  return await self.async_web3.eth.chain_id
217
274
 
218
- async def get_balance(self, address):
275
+ async def get_balance(self, address=None):
219
276
  """
220
277
  Gets the balance.
221
278
 
222
279
  Args:
223
- address: The address.
280
+ address (optional): The address.
224
281
 
225
282
  Returns:
226
283
  The balance.
227
284
  """
285
+ if address is None:
286
+ address = self.get_signer().get_ethereum_address()
228
287
  return await self.async_web3.eth.get_balance(address)
229
288
 
230
- async def get_usdc_balance(self, address):
289
+ async def get_usdc_balance(self, address=None):
231
290
  """
232
291
  Gets the USDC balance.
233
292
 
234
293
  Args:
235
- address: The address.
294
+ address (optional): The address.
236
295
 
237
296
  Returns:
238
297
  The USDC balance.
239
298
  """
299
+ if address is None:
300
+ address = self.get_signer().get_ethereum_address()
240
301
  balance = await self.read_contract("USDC", "balanceOf", address, decode=False)
241
302
  return balance / 10**6
242
303
 
304
+ async def get_usdc_allowance_for_trading(self, address=None):
305
+ """
306
+ Gets the USDC allowance for the Trading Storage contract.
307
+
308
+ Args:
309
+ address (optional): The address.
310
+
311
+ Returns:
312
+ The USDC allowance.
313
+ """
314
+ if address is None:
315
+ address = self.get_signer().get_ethereum_address()
316
+
317
+ trading_storage_address = self.contracts["TradingStorage"].address
318
+
319
+ allowance = await self.read_contract(
320
+ "USDC", "allowance", address, trading_storage_address, decode=False
321
+ )
322
+ return allowance / 10**6
323
+
324
+ async def approve_usdc_for_trading(self, amount=100000):
325
+ """
326
+ Approves the USDC amount for the Trading Storage contract.
327
+
328
+ Args:
329
+ amount (optional): The amount to approve. Defaults to $100,000.
330
+
331
+ Returns:
332
+ The transaction hash.
333
+ """
334
+ trading_storage_address = self.contracts["TradingStorage"].address
335
+ return await self.write_contract(
336
+ "USDC", "approve", trading_storage_address, int(amount * 10**6)
337
+ )
338
+
243
339
  async def get_gas_estimate(self, transaction):
244
340
  """
245
341
  Gets the gas estimate.
File without changes
@@ -0,0 +1,98 @@
1
+ from typing import Tuple
2
+
3
+ from Crypto.Hash import keccak
4
+ from eth_account.account import Account
5
+ from eth_utils import to_checksum_address
6
+ from pyasn1.codec.der.decoder import decode as der_decode
7
+ from pyasn1.type import namedtype, univ
8
+
9
+
10
+ class SPKIAlgorithmIdentifierRecord(univ.Sequence):
11
+ componentType = namedtype.NamedTypes(
12
+ namedtype.NamedType("algorithm", univ.ObjectIdentifier()),
13
+ namedtype.OptionalNamedType("parameters", univ.Any()),
14
+ )
15
+
16
+
17
+ class SPKIRecord(univ.Sequence):
18
+ componentType = namedtype.NamedTypes(
19
+ namedtype.NamedType("algorithm", SPKIAlgorithmIdentifierRecord()),
20
+ namedtype.NamedType("subjectPublicKey", univ.BitString()),
21
+ )
22
+
23
+
24
+ class ECDSASignatureRecord(univ.Sequence):
25
+ componentType = namedtype.NamedTypes(
26
+ namedtype.NamedType("r", univ.Integer()),
27
+ namedtype.NamedType("s", univ.Integer()),
28
+ )
29
+
30
+
31
+ def public_key_int_to_eth_address(pubkey: int) -> str:
32
+ """
33
+ Given an integer public key, calculate the ethereum address.
34
+ """
35
+ hex_string = hex(pubkey).replace("0x", "")
36
+ padded_hex_string = hex_string.replace("0x", "").zfill(130)[2:]
37
+
38
+ k = keccak.new(digest_bits=256)
39
+ k.update(bytes.fromhex(padded_hex_string))
40
+ return to_checksum_address(bytes.fromhex(k.hexdigest())[-20:].hex())
41
+
42
+
43
+ def der_encoded_public_key_to_eth_address(pubkey: bytes) -> str:
44
+ """
45
+ Given a KMS Public Key, calculate the ethereum address.
46
+ """
47
+ received_record, _ = der_decode(pubkey, asn1Spec=SPKIRecord())
48
+ return public_key_int_to_eth_address(
49
+ int(received_record["subjectPublicKey"].asBinary(), 2)
50
+ )
51
+
52
+
53
+ def get_sig_r_s(signature: bytes) -> Tuple[int, int]:
54
+ """
55
+ Given a KMS signature, calculate r and s.
56
+ """
57
+ received_record, _ = der_decode(signature, asn1Spec=ECDSASignatureRecord())
58
+ r = int(received_record["r"].prettyPrint())
59
+ s = int(received_record["s"].prettyPrint())
60
+
61
+ max_value_on_curve = (
62
+ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
63
+ )
64
+
65
+ if 2 * s >= max_value_on_curve:
66
+ # s is on wrong side of curve, flip it
67
+ s = max_value_on_curve - s
68
+ return r, s
69
+
70
+
71
+ def get_sig_v(msg_hash: bytes, r: int, s: int, expected_address: str) -> int:
72
+ """
73
+ Given a message hash, r, s and an ethereum address, recover the
74
+ recovery parameter v.
75
+ """
76
+ acc = Account()
77
+ recovered = acc._recover_hash(msg_hash, vrs=(27, r, s))
78
+ recovered2 = acc._recover_hash(msg_hash, vrs=(28, r, s))
79
+ expected_checksum_address = to_checksum_address(expected_address)
80
+
81
+ if recovered == expected_checksum_address:
82
+ return 0
83
+ elif recovered2 == expected_checksum_address:
84
+ return 1
85
+
86
+ raise ValueError("Invalid Signature, cannot compute v, addresses do not match!")
87
+
88
+
89
+ def get_sig_r_s_v(
90
+ msg_hash: bytes, signature: bytes, address: str
91
+ ) -> Tuple[int, int, int]:
92
+ """
93
+ Given a message hash, a KMS signature and an ethereum address calculate r,
94
+ s, and v.
95
+ """
96
+ r, s = get_sig_r_s(signature)
97
+ v = get_sig_v(msg_hash, r, s, address)
98
+ return r, s, v
@@ -182,5 +182,33 @@
182
182
  "XAU/USD": {
183
183
  "id": "0x765d2ba906dbc32ca17cc11f5310a89e9ee1f6420508c63861f2f8ba4ee34bb2",
184
184
  "group": "metal"
185
+ },
186
+ "ZRO/USD": {
187
+ "id": "0x3bd860bea28bf982fa06bcf358118064bb114086cc03993bd76197eaab0b8018",
188
+ "group": "crypto"
189
+ },
190
+ "INJ/USD": {
191
+ "id": "0x7a5bc1d2b56ad029048cd63964b3ad2776eadf812edc1a43a31406cb54bff592",
192
+ "group": "crypto"
193
+ },
194
+ "ZK/USD": {
195
+ "id": "0xcc03dc09298fb447e0bf9afdb760d5b24340fd2167fd33d8967dd8f9a141a2e8",
196
+ "group": "crypto"
197
+ },
198
+ "AAVE/USD": {
199
+ "id": "0x2b9ab1e972a281585084148ba1389800799bd4be63b957507db1349314e47445",
200
+ "group": "crypto"
201
+ },
202
+ "SUI/USD": {
203
+ "id": "0x23d7315113f5b1d3ba7a83604c44b94d79f4fd69af77f804fc7f920a6dc65744",
204
+ "group": "crypto"
205
+ },
206
+ "TAO/USD": {
207
+ "id": "0x410f41de235f2db824e562ea7ab2d3d3d4ff048316c61d629c0b93f58584e1af",
208
+ "group": "crypto"
209
+ },
210
+ "EIGEN/USD": {
211
+ "id": "0xc65db025687356496e8653d0d6608eec64ce2d96e2e28c530e574f0e4f712380",
212
+ "group": "crypto"
185
213
  }
186
214
  }
@@ -46,6 +46,9 @@ class PairsCache:
46
46
  decoded_data.append(PairInfo(**decoded))
47
47
 
48
48
  for index, pair_info in enumerate(decoded_data):
49
+ if not pair_info.from_:
50
+ pair_info.from_ = f"DELISTED_{index}"
51
+ pair_info.to = f"DELISTED_{index}"
49
52
  self._pair_info_cache[index] = pair_info
50
53
 
51
54
  group_indexes = set([pair.group_index for pair in decoded_data])
@@ -7,6 +7,7 @@ from ..types import (
7
7
  PendingLimitOrderExtendedResponse,
8
8
  MarginUpdateType,
9
9
  )
10
+ from typing import Optional
10
11
  import math
11
12
 
12
13
 
@@ -31,6 +32,7 @@ class TradeRPC:
31
32
  trade_input: TradeInput,
32
33
  trade_input_order_type: TradeInputOrderType,
33
34
  slippage_percentage: int,
35
+ execution_fee: Optional[float] = None,
34
36
  ):
35
37
  """
36
38
  Builds a transaction to open a trade.
@@ -45,7 +47,16 @@ class TradeRPC:
45
47
  """
46
48
  Trading = self.client.contracts.get("Trading")
47
49
 
48
- execution_fee = await self.get_trade_execution_fee()
50
+ if (
51
+ trade_input.trader == "0x1234567890123456789012345678901234567890"
52
+ and self.client.get_signer() is not None
53
+ ):
54
+ trade_input.trader = await self.client.get_signer().get_ethereum_address()
55
+
56
+ if execution_fee is not None:
57
+ execution_fee_wei = int(execution_fee * 10**18)
58
+ else:
59
+ execution_fee_wei = await self.get_trade_execution_fee()
49
60
 
50
61
  if (
51
62
  trade_input_order_type == TradeInputOrderType.MARKET
@@ -73,7 +84,7 @@ class TradeRPC:
73
84
  ).build_transaction(
74
85
  {
75
86
  "from": trade_input.trader,
76
- "value": execution_fee,
87
+ "value": execution_fee_wei,
77
88
  "chainId": self.client.chain_id,
78
89
  "nonce": await self.client.get_transaction_count(trade_input.trader),
79
90
  }
@@ -89,6 +100,7 @@ class TradeRPC:
89
100
  The trade execution fee
90
101
  """
91
102
  execution_fee = round(0.00035, 18) # default value
103
+ execution_fee_wei = int(execution_fee * 10**18)
92
104
 
93
105
  try:
94
106
  feeScalar = 0.001
@@ -107,9 +119,9 @@ class TradeRPC:
107
119
  return feeEstimate
108
120
  except Exception as e:
109
121
  print("Error getting correct trade execution fee. Using fallback: ", e)
110
- return execution_fee
122
+ return execution_fee_wei
111
123
 
112
- async def get_trades(self, trader: str):
124
+ async def get_trades(self, trader: Optional[str] = None):
113
125
  """
114
126
  Gets the trades.
115
127
 
@@ -119,6 +131,9 @@ class TradeRPC:
119
131
  Returns:
120
132
  The trades.
121
133
  """
134
+ if trader is None:
135
+ trader = self.client.get_signer().get_ethereum_address()
136
+
122
137
  result = (
123
138
  await self.client.contracts.get("Multicall")
124
139
  .functions.getPositions(trader)
@@ -198,7 +213,11 @@ class TradeRPC:
198
213
  return trades, pendingOpenLimitOrders
199
214
 
200
215
  async def build_trade_close_tx(
201
- self, trader: str, pair_index: int, trade_index: int, collateral_to_close: float
216
+ self,
217
+ pair_index: int,
218
+ trade_index: int,
219
+ collateral_to_close: float,
220
+ trader: Optional[str] = None,
202
221
  ):
203
222
  """
204
223
  Builds a transaction to close a trade.
@@ -207,12 +226,16 @@ class TradeRPC:
207
226
  pair_index: The pair index.
208
227
  trade_index: The trade index.
209
228
  collateral_to_close: The collateral to close.
229
+ trader (optional): The trader's wallet address.
210
230
 
211
231
  Returns:
212
232
  A transaction object.
213
233
  """
214
234
  Trading = self.client.contracts.get("Trading")
215
235
 
236
+ if trader is None:
237
+ trader = self.client.get_signer().get_ethereum_address()
238
+
216
239
  execution_fee = await self.get_trade_execution_fee()
217
240
 
218
241
  transaction = await Trading.functions.closeTradeMarket(
@@ -229,7 +252,7 @@ class TradeRPC:
229
252
  return transaction
230
253
 
231
254
  async def build_order_cancel_tx(
232
- self, trader: str, pair_index: int, trade_index: int
255
+ self, pair_index: int, trade_index: int, trader: Optional[str] = None
233
256
  ):
234
257
  """
235
258
  Builds a transaction to cancel an order.
@@ -237,13 +260,16 @@ class TradeRPC:
237
260
  Args:
238
261
  pair_index: The pair index.
239
262
  trade_index: The trade/order index.
240
- position_size: The position size.
263
+ trader (optional): The trader's wallet address.
241
264
 
242
265
  Returns:
243
266
  A transaction object.
244
267
  """
245
268
  Trading = self.client.contracts.get("Trading")
246
269
 
270
+ if trader is None:
271
+ trader = self.client.get_signer().get_ethereum_address()
272
+
247
273
  transaction = await Trading.functions.cancelOpenLimitOrder(
248
274
  pair_index, trade_index
249
275
  ).build_transaction(
@@ -258,11 +284,11 @@ class TradeRPC:
258
284
 
259
285
  async def build_trade_margin_update_tx(
260
286
  self,
261
- trader: str,
262
287
  pair_index: int,
263
288
  trade_index: int,
264
289
  margin_update_type: MarginUpdateType,
265
290
  collateral_change: float,
291
+ trader: Optional[str] = None,
266
292
  ):
267
293
  """
268
294
  Builds a transaction to update the margin of a trade.
@@ -272,12 +298,15 @@ class TradeRPC:
272
298
  trade_index: The trade index.
273
299
  margin_update_type: The margin update type.
274
300
  collateral_change: The collateral change.
275
-
301
+ trader (optional): The trader's wallet address.
276
302
  Returns:
277
303
  A transaction object.
278
304
  """
279
305
  Trading = self.client.contracts.get("Trading")
280
306
 
307
+ if trader is None:
308
+ trader = self.client.get_signer().get_ethereum_address()
309
+
281
310
  collateral_change = int(collateral_change * 10**6)
282
311
  fee_in_wei = 1 * 10**18
283
312
 
@@ -1,4 +1,5 @@
1
1
  from ..types import TradeInput, LossProtectionInfo
2
+ from typing import Optional
2
3
 
3
4
 
4
5
  class TradingParametersRPC:
@@ -75,7 +76,7 @@ class TradingParametersRPC:
75
76
  return await self.get_loss_protection_percentage_by_tier(tier, trade.pairIndex)
76
77
 
77
78
  async def get_loss_protection_for_trade_input(
78
- self, trade: TradeInput, opening_fee_usdc: float | None = None
79
+ self, trade: TradeInput, opening_fee_usdc: Optional[float] = None
79
80
  ):
80
81
  """
81
82
  Retrieves the loss protection for a trade.
@@ -104,17 +105,21 @@ class TradingParametersRPC:
104
105
  percentage=loss_protection_percentage, amount=loss_protection_usdc
105
106
  )
106
107
 
107
- async def get_trade_referral_rebate_percentage(self, trader: str):
108
+ async def get_trade_referral_rebate_percentage(self, trader: Optional[str] = None):
108
109
  """
109
110
  Retrieves the trade referral rebate percentage for a trader.
110
111
 
111
112
  Args:
112
- trader: The trader's wallet address.
113
+ trader (optional): The trader's wallet address.
113
114
 
114
115
  Returns:
115
116
  The trade referral rebate percentage.
116
117
  """
117
118
  Referral = self.client.contracts.get("Referral")
119
+
120
+ if trader is None:
121
+ trader = self.client.get_signer().get_ethereum_address()
122
+
118
123
  trader_referral_info = await Referral.functions.getTraderReferralInfo(
119
124
  trader
120
125
  ).call()
File without changes
@@ -0,0 +1,15 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class BaseSigner(ABC):
5
+ """
6
+ Base class for signers.
7
+ """
8
+
9
+ @abstractmethod
10
+ async def sign_transaction(self, transaction):
11
+ pass
12
+
13
+ @abstractmethod
14
+ def get_ethereum_address(self):
15
+ pass
@@ -0,0 +1,146 @@
1
+ from collections.abc import Mapping
2
+ from typing import Any, NamedTuple, Tuple
3
+
4
+ import boto3
5
+ from .base import BaseSigner
6
+ from eth_account._utils.signing import sign_transaction_dict
7
+ from eth_utils.curried import keccak
8
+ from hexbytes import HexBytes
9
+ from toolz import dissoc
10
+ from web3 import Web3
11
+
12
+ from ..crypto.spki import der_encoded_public_key_to_eth_address, get_sig_r_s_v
13
+
14
+
15
+ class Signature:
16
+ """Kinda compatible Signature class"""
17
+
18
+ def __init__(self, r: int, s: int, v: int) -> None:
19
+ self.r = r
20
+ self.s = s
21
+ self.v = v
22
+
23
+ @property
24
+ def vrs(self) -> Tuple[int, int, int]:
25
+ return self.v, self.r, self.s
26
+
27
+
28
+ def __getitem__(self: Any, index: Any) -> Any:
29
+ try:
30
+ return tuple.__getitem__(self, index)
31
+ except TypeError:
32
+ return getattr(self, index)
33
+
34
+
35
+ class SignedTransaction(NamedTuple):
36
+ """Kinda compatible SignedTransaction class"""
37
+
38
+ rawTransaction: HexBytes
39
+ hash: HexBytes
40
+ r: int
41
+ s: int
42
+ v: int
43
+
44
+ def __getitem__(self, index: Any) -> Any:
45
+ return __getitem__(self, index)
46
+
47
+
48
+ class KMSSigner(BaseSigner):
49
+ def __init__(self, web3, kms_key_id, region_name="us-east-1"):
50
+ self._web3 = web3
51
+ self._key_id = kms_key_id
52
+ self._kms_client = boto3.client("kms", region_name)
53
+ self.address = self.get_public_key()
54
+
55
+ async def sign_transaction(self, transaction):
56
+ """
57
+ Signs a transaction using AWS KMS.
58
+
59
+ Args:
60
+ transaction: The transaction object to be signed.
61
+
62
+ Returns:
63
+ The signed transaction object.
64
+ """
65
+ return await self._sign_transaction(transaction)
66
+
67
+ def get_public_key(self):
68
+ """
69
+ Retrieves the public key associated with the KMS key.
70
+ """
71
+ eth_address = self.get_eth_address()
72
+ return Web3.to_checksum_address(eth_address)
73
+
74
+ def get_ethereum_address(self):
75
+ """
76
+ Derives the Ethereum wallet address from the public key.
77
+
78
+ Returns:
79
+ str: The Ethereum wallet address in checksum format.
80
+ """
81
+ return self.address
82
+
83
+ async def _sign_transaction(self, transaction_dict: dict) -> SignedTransaction:
84
+ """
85
+ Somewhat fixed up version of Account.sign_transaction, to use the custom PrivateKey
86
+ impl -- BasicKmsAccount
87
+ https://github.com/ethereum/eth-account/blob/master/eth_account/account.py#L619
88
+ """
89
+
90
+ if not isinstance(transaction_dict, Mapping):
91
+ raise TypeError(
92
+ "transaction_dict must be dict-like, got %r" % transaction_dict
93
+ )
94
+
95
+ # allow from field, *only* if it matches the private key
96
+ if "from" in transaction_dict:
97
+ if transaction_dict["from"] == self.address:
98
+ sanitized_transaction = dissoc(transaction_dict, "from")
99
+ else:
100
+ raise TypeError(
101
+ "from field must match key's %s, but it was %s"
102
+ % (
103
+ self.address,
104
+ transaction_dict["from"],
105
+ )
106
+ )
107
+ else:
108
+ sanitized_transaction = transaction_dict
109
+
110
+ if "nonce" not in sanitized_transaction:
111
+ sanitized_transaction["nonce"] = await self._web3.eth.get_transaction_count(
112
+ self.address
113
+ )
114
+
115
+ # sign transaction
116
+ (
117
+ v,
118
+ r,
119
+ s,
120
+ encoded_transaction,
121
+ ) = sign_transaction_dict(self, sanitized_transaction)
122
+ transaction_hash = keccak(encoded_transaction)
123
+
124
+ return SignedTransaction(
125
+ rawTransaction=HexBytes(encoded_transaction),
126
+ hash=HexBytes(transaction_hash),
127
+ r=r,
128
+ s=s,
129
+ v=v,
130
+ )
131
+
132
+ def get_eth_address(self) -> str:
133
+ """Calculate ethereum address for given AWS KMS key."""
134
+ pubkey = self._kms_client.get_public_key(KeyId=self._key_id)["PublicKey"]
135
+ return der_encoded_public_key_to_eth_address(pubkey)
136
+
137
+ def sign_msg_hash(self, msg_hash: HexBytes) -> Signature:
138
+ signature = self._kms_client.sign(
139
+ KeyId=self._key_id,
140
+ Message=bytes(msg_hash),
141
+ MessageType="DIGEST",
142
+ SigningAlgorithm="ECDSA_SHA_256",
143
+ )
144
+ act_signature = signature["Signature"]
145
+ r, s, v = get_sig_r_s_v(msg_hash, act_signature, self.address)
146
+ return Signature(r, s, v)
@@ -0,0 +1,16 @@
1
+ from web3 import AsyncWeb3
2
+ from .base import BaseSigner
3
+
4
+
5
+ class LocalSigner(BaseSigner):
6
+ def __init__(self, private_key, async_web3: AsyncWeb3):
7
+ self.private_key = private_key
8
+ self.async_web3 = async_web3
9
+
10
+ async def sign_transaction(self, transaction):
11
+ return self.async_web3.eth.account.sign_transaction(
12
+ transaction, self.private_key
13
+ )
14
+
15
+ def get_ethereum_address(self):
16
+ return self.async_web3.eth.account.from_key(self.private_key).address
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: avantis-trader-sdk
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: SDK for interacting with Avantis trading contracts.
5
5
  Home-page: https://avantisfi.com/
6
6
  Author: Avantis Labs
@@ -12,9 +12,14 @@ Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: OS Independent
13
13
  Requires-Python: >=3.6
14
14
  Description-Content-Type: text/markdown
15
- Requires-Dist: web3>=6.15.1
16
- Requires-Dist: pydantic>=2.8.2
17
- Requires-Dist: websockets>=12.0
15
+ Requires-Dist: web3<7,>=6.15.1
16
+ Requires-Dist: pydantic<3,>=2.8.2
17
+ Requires-Dist: websockets<14,>=12.0
18
+ Requires-Dist: boto3<2,>=1.35.44
19
+ Requires-Dist: eth-account<0.12.0,>=0.10.0
20
+ Requires-Dist: toolz<1,>=0.12.1
21
+ Requires-Dist: eth-utils<5,>=2.1.0
22
+ Requires-Dist: pyasn1<1,>=0.6.1
18
23
 
19
24
  # Welcome to Avantis Trader SDK’s documentation!
20
25
 
@@ -64,6 +69,12 @@ To get started with the Avantis Trader SDK, follow these steps to install the pa
64
69
  1. Ensure you have Python 3.6 or later installed on your system.
65
70
  2. Install the SDK using pip:
66
71
 
72
+ ```bash
73
+ pip install avantis-trader-sdk==0.2.1
74
+ ```
75
+
76
+ or
77
+
67
78
  ```bash
68
79
  pip install git+https://github.com/Avantis-Labs/avantis_trader_sdk.git
69
80
  ```
@@ -1,5 +1,5 @@
1
- avantis_trader_sdk/__init__.py,sha256=gtrDyFR2yMc5EwzreEtpoIQkXWiGfHHN7O55LDdXxxY,101
2
- avantis_trader_sdk/client.py,sha256=qu4NZ0_6oK1aCo9NuKWXrj88ey5KF3yIPLklsYokgJc,8196
1
+ avantis_trader_sdk/__init__.py,sha256=XfcfMCSlyYvs5_uVHDGSOxLxOpaHLclcjsirqmcXLS4,139
2
+ avantis_trader_sdk/client.py,sha256=QfljOKja32K89bl0_PTvcT0QrF2WX2yS3lIxNjU5mAA,11068
3
3
  avantis_trader_sdk/config.py,sha256=ieqm9dAcbdmarBp7UfIHQQYSDx5KlVZ6OmZvxjacPeE,585
4
4
  avantis_trader_sdk/types.py,sha256=6B4t21ohWiyB9yZEfTC7ckznzgFFIW93z_J2m6_azPU,11754
5
5
  avantis_trader_sdk/utils.py,sha256=JE3hiDA8a9KHW08u0lVsuXi6Npl8GcuUdvrSkwohvDc,2909
@@ -191,22 +191,28 @@ avantis_trader_sdk/abis/updateOP.s.sol/UpdateScript.json,sha256=Tkh73aDzZ6tsTATC
191
191
  avantis_trader_sdk/abis/upgradesBase.s.sol/UpgradeScript.json,sha256=7Qyoo-Ud4PLZzSWRooVfXGiKcXivx6C4mJYqSel2UXI,360625
192
192
  avantis_trader_sdk/abis/veTranche.t.sol/veTranche.json,sha256=qrG02DkLGA4Ybrca4ZROQXDKDRrfn-r3QBI-NxZD5Ts,1432757
193
193
  avantis_trader_sdk/abis/wire.s.sol/Wire.json,sha256=b8M2fVPiLMMZhapuD3FilICRjfRWPjaKRnbsXuG9apo,269138
194
+ avantis_trader_sdk/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
195
+ avantis_trader_sdk/crypto/spki.py,sha256=CNy7A8TTwBHiNSzIj7uqiHKAeLcn1Q9MbszW_2mdXgI,3080
194
196
  avantis_trader_sdk/feed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
195
- avantis_trader_sdk/feed/feedIds.json,sha256=UvjZX2XItWz-Ygz3QuhakZBhsA3dd1NY5_BAkYpFWg4,5813
197
+ avantis_trader_sdk/feed/feedIds.json,sha256=T77nww3eRiQt8rqZBDpdxA49USGyfI0dQBPnzo-H1dE,6697
196
198
  avantis_trader_sdk/feed/feed_client.py,sha256=fnATL8bCNzteZZolMgrycXHNK25_TI4Lg_Y4oVN9ep8,7423
197
199
  avantis_trader_sdk/rpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
198
200
  avantis_trader_sdk/rpc/asset_parameters.py,sha256=OkGyfSmiCUl7fgJaUJFQekpQ8o2_JbbSwh1gWAXdZKY,19717
199
201
  avantis_trader_sdk/rpc/blended.py,sha256=UHgrPEvkJwQJRxTrVG03Ir8IjJRGenQFov1bJvbuGi4,2512
200
202
  avantis_trader_sdk/rpc/category_parameters.py,sha256=yw-6Ib1kS25JTmZQzH7Xyn1dehUqGYBoZM_bO7G4lEE,8181
201
203
  avantis_trader_sdk/rpc/fee_parameters.py,sha256=fawGjRVGSIugEh2rs93Qyu4e68ejDzUrn0ZlbkwmEf8,9161
202
- avantis_trader_sdk/rpc/pairs_cache.py,sha256=VLSw4IupUxOJFZWxrkYI84aB5ieMbMlFbY4xZ-aKj-k,3910
204
+ avantis_trader_sdk/rpc/pairs_cache.py,sha256=CxaZKNv38AW90oINcJdG7QVGfOVYEiTWSV_vtdQ570M,4066
203
205
  avantis_trader_sdk/rpc/rpc_helpers.py,sha256=d3dzwEaAUVo700qK7S-bBSVX3UtrOKbPEGPqgxHS5sk,292
204
206
  avantis_trader_sdk/rpc/snapshot.py,sha256=2EMtNqfeB37dr4EsuSMBm0CAYbwWMv9evML8MZocNFw,5494
205
- avantis_trader_sdk/rpc/trade.py,sha256=QVjFlex5XmX1qAdMOqSAcIFQjNYznkOOs2qFLKYgtMc,10096
206
- avantis_trader_sdk/rpc/trading_parameters.py,sha256=uVHMTXjzP7NqhuGjqmikuDhfwrsxwCDdZrtQ4qAoXy4,4540
207
+ avantis_trader_sdk/rpc/trade.py,sha256=Zvtj2WbDv_uuR0l8SKQ99mL1BDv9vVAP_xOUy9VRIUU,11250
208
+ avantis_trader_sdk/rpc/trading_parameters.py,sha256=ZvEJ-kjrogENIhPsETRIp-FdVaIdd2mT2fyM84SuFJk,4702
209
+ avantis_trader_sdk/signers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
210
+ avantis_trader_sdk/signers/base.py,sha256=QaOu0CxFq60oR4LegCp1XwONMQx8ZShXyiLZvfcbCPM,260
211
+ avantis_trader_sdk/signers/kms_signer.py,sha256=lxK3f9KQsdCDAvOE1SHleKjI8zD_3PTvywDjDVQGDKg,4448
212
+ avantis_trader_sdk/signers/local_signer.py,sha256=kUx5vExiBfvFGmoMCFR6b7_4cXx2mvYOJNqZQDIEcG8,505
207
213
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
208
214
  tests/test_client.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
209
- avantis_trader_sdk-0.2.1.dist-info/METADATA,sha256=DqsceaZd56C2e6UC42U9N7WZ67pR0KZiP03pOZ7wzhA,4659
210
- avantis_trader_sdk-0.2.1.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
211
- avantis_trader_sdk-0.2.1.dist-info/top_level.txt,sha256=XffaQJ68SGT1KUz2HHXSGSEsmNy8-AGjgtO127xhzQA,25
212
- avantis_trader_sdk-0.2.1.dist-info/RECORD,,
215
+ avantis_trader_sdk-0.3.1.dist-info/METADATA,sha256=zyqqV0h2A_q_IBGSb_DMs0F0u8hDl9Sr4R98UfYU6tg,4923
216
+ avantis_trader_sdk-0.3.1.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
217
+ avantis_trader_sdk-0.3.1.dist-info/top_level.txt,sha256=XffaQJ68SGT1KUz2HHXSGSEsmNy8-AGjgtO127xhzQA,25
218
+ avantis_trader_sdk-0.3.1.dist-info/RECORD,,