mm-eth 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.
Files changed (50) hide show
  1. mm_eth/__init__.py +0 -0
  2. mm_eth/abi/zksync.json +2092 -0
  3. mm_eth/abi.py +130 -0
  4. mm_eth/account.py +70 -0
  5. mm_eth/anvil.py +56 -0
  6. mm_eth/cli/__init__.py +0 -0
  7. mm_eth/cli/calcs.py +88 -0
  8. mm_eth/cli/cli.py +233 -0
  9. mm_eth/cli/cli_helpers.py +195 -0
  10. mm_eth/cli/cli_utils.py +150 -0
  11. mm_eth/cli/cmd/__init__.py +0 -0
  12. mm_eth/cli/cmd/balance_cmd.py +59 -0
  13. mm_eth/cli/cmd/balances_cmd.py +121 -0
  14. mm_eth/cli/cmd/call_contract_cmd.py +44 -0
  15. mm_eth/cli/cmd/config_example_cmd.py +9 -0
  16. mm_eth/cli/cmd/deploy_cmd.py +41 -0
  17. mm_eth/cli/cmd/encode_input_data_cmd.py +10 -0
  18. mm_eth/cli/cmd/mnemonic_cmd.py +27 -0
  19. mm_eth/cli/cmd/node_cmd.py +47 -0
  20. mm_eth/cli/cmd/private_key_cmd.py +10 -0
  21. mm_eth/cli/cmd/rpc_cmd.py +81 -0
  22. mm_eth/cli/cmd/send_contract_cmd.py +247 -0
  23. mm_eth/cli/cmd/solc_cmd.py +25 -0
  24. mm_eth/cli/cmd/token_cmd.py +29 -0
  25. mm_eth/cli/cmd/transfer_erc20_cmd.py +275 -0
  26. mm_eth/cli/cmd/transfer_eth_cmd.py +252 -0
  27. mm_eth/cli/cmd/vault_cmd.py +16 -0
  28. mm_eth/cli/config_examples/balances.yml +15 -0
  29. mm_eth/cli/config_examples/call_contract.yml +5 -0
  30. mm_eth/cli/config_examples/transfer_erc20.yml +26 -0
  31. mm_eth/cli/config_examples/transfer_eth.yml +24 -0
  32. mm_eth/cli/validators.py +84 -0
  33. mm_eth/deploy.py +20 -0
  34. mm_eth/ens.py +16 -0
  35. mm_eth/erc20.py +240 -0
  36. mm_eth/ethernodes.py +34 -0
  37. mm_eth/py.typed +0 -0
  38. mm_eth/rpc.py +478 -0
  39. mm_eth/services/__init__.py +0 -0
  40. mm_eth/solc.py +34 -0
  41. mm_eth/tx.py +164 -0
  42. mm_eth/types.py +5 -0
  43. mm_eth/utils.py +245 -0
  44. mm_eth/vault.py +38 -0
  45. mm_eth/zksync.py +203 -0
  46. mm_eth-0.1.0.dist-info/METADATA +24 -0
  47. mm_eth-0.1.0.dist-info/RECORD +50 -0
  48. mm_eth-0.1.0.dist-info/WHEEL +5 -0
  49. mm_eth-0.1.0.dist-info/entry_points.txt +2 -0
  50. mm_eth-0.1.0.dist-info/top_level.txt +1 -0
mm_eth/rpc.py ADDED
@@ -0,0 +1,478 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from typing import Any, Literal, cast
6
+
7
+ import websocket
8
+ from mm_std import Err, Ok, Result, hr, random_choice
9
+ from pydantic import BaseModel
10
+ from web3.types import BlockIdentifier
11
+
12
+ from mm_eth.types import Nodes, Proxies
13
+ from mm_eth.utils import hex_str_to_int, random_node, random_proxy
14
+
15
+
16
+ @dataclass
17
+ class TxReceipt:
18
+ tx_hash: str
19
+ tx_index: int
20
+ block_number: int
21
+ from_address: str
22
+ to_address: str | None
23
+ contract_address: str | None
24
+ status: int | None
25
+
26
+
27
+ @dataclass
28
+ class Log:
29
+ address: str
30
+ block_hash: str
31
+ block_number: int
32
+ data: str
33
+ log_index: int
34
+ removed: bool
35
+ topics: list[str]
36
+ transaction_hash: str
37
+ transaction_index: int
38
+
39
+ @classmethod
40
+ def from_json_rpc_dict(cls, data: dict[str, Any]) -> Result[Log]:
41
+ try:
42
+ return Ok(
43
+ Log(
44
+ address=data["address"],
45
+ block_hash=data["blockHash"],
46
+ block_number=int(data["blockNumber"], 16),
47
+ data=data["data"],
48
+ log_index=int(data["logIndex"], 16),
49
+ removed=data["removed"],
50
+ topics=data["topics"],
51
+ transaction_hash=data["transactionHash"],
52
+ transaction_index=int(data["transactionIndex"], 16),
53
+ ),
54
+ )
55
+ except Exception as err:
56
+ return Err(f"exception: {err}")
57
+
58
+
59
+ class TxData(BaseModel):
60
+ block_number: int | None # for pending tx it can be none
61
+ from_: str
62
+ to: str | None
63
+ gas: int
64
+ gas_price: int
65
+ value: int
66
+ hash: str
67
+ input: str
68
+ nonce: int
69
+ v: int
70
+ r: str
71
+ s: str
72
+
73
+
74
+ def rpc_call(
75
+ *,
76
+ nodes: Nodes,
77
+ method: str,
78
+ params: list[object],
79
+ id_: int = 1,
80
+ timeout: int = 10,
81
+ proxies: Proxies = None,
82
+ attempts: int = 1,
83
+ ) -> Result[Any]:
84
+ data = {"jsonrpc": "2.0", "method": method, "params": params, "id": id_}
85
+ res: Result[Any] = Err("not started yet")
86
+ for _ in range(attempts):
87
+ node = random_node(nodes)
88
+ if node.startswith("http"):
89
+ res = _http_call(node, data, timeout, random_proxy(proxies))
90
+ else:
91
+ res = _ws_call(node, data, timeout)
92
+ if isinstance(res, Ok):
93
+ return res
94
+ return res
95
+
96
+
97
+ def _http_call(node: str, data: dict[str, object], timeout: int, proxy: str | None) -> Result[Any]:
98
+ res = hr(node, method="POST", proxy=proxy, timeout=timeout, params=data, json_params=True)
99
+ try:
100
+ if res.is_error():
101
+ return res.to_err_result()
102
+
103
+ err = res.json.get("error", {}).get("message", "")
104
+ if err:
105
+ return res.to_err_result(f"service_error: {err}")
106
+ if "result" in res.json:
107
+ return res.to_ok_result(res.json["result"])
108
+
109
+ return res.to_err_result("unknown_response")
110
+ except Exception as err:
111
+ return res.to_err_result(f"exception: {err}")
112
+
113
+
114
+ def _ws_call(node: str, data: dict[str, object], timeout: int) -> Result[Any]:
115
+ try:
116
+ ws = websocket.create_connection(node, timeout=timeout)
117
+ ws.send(json.dumps(data))
118
+ response = json.loads(ws.recv())
119
+ ws.close()
120
+ err = response.get("error", {}).get("message", "")
121
+ if err:
122
+ return Err(f"service_error: {err}")
123
+ if "result" in response:
124
+ return Ok(response["result"])
125
+ return Err(f"unknown_response: {response}")
126
+ except TimeoutError:
127
+ return Err("timeout")
128
+ except Exception as err:
129
+ return Err(f"exception: {err}")
130
+
131
+
132
+ def eth_block_number(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
133
+ return rpc_call(
134
+ nodes=rpc_urls,
135
+ method="eth_blockNumber",
136
+ params=[],
137
+ timeout=timeout,
138
+ proxies=proxies,
139
+ attempts=attempts,
140
+ ).and_then(hex_str_to_int)
141
+
142
+
143
+ def eth_chain_id(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
144
+ return rpc_call(
145
+ nodes=rpc_urls,
146
+ method="eth_chainId",
147
+ params=[],
148
+ timeout=timeout,
149
+ proxies=proxies,
150
+ attempts=attempts,
151
+ ).and_then(hex_str_to_int)
152
+
153
+
154
+ def net_peer_count(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
155
+ return rpc_call(
156
+ nodes=rpc_urls,
157
+ method="net_peerCount",
158
+ params=[],
159
+ timeout=timeout,
160
+ proxies=proxies,
161
+ attempts=attempts,
162
+ ).and_then(hex_str_to_int)
163
+
164
+
165
+ def web3_client_version(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
166
+ return rpc_call(
167
+ nodes=rpc_urls,
168
+ method="web3_clientVersion",
169
+ params=[],
170
+ timeout=timeout,
171
+ proxies=proxies,
172
+ attempts=attempts,
173
+ )
174
+
175
+
176
+ def net_version(nodes: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
177
+ return rpc_call(nodes=nodes, method="net_version", params=[], timeout=timeout, proxies=proxies, attempts=attempts)
178
+
179
+
180
+ def eth_get_code(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
181
+ return rpc_call(
182
+ nodes=rpc_urls,
183
+ method="eth_getCode",
184
+ params=[address, "latest"],
185
+ timeout=timeout,
186
+ proxies=proxies,
187
+ attempts=attempts,
188
+ )
189
+
190
+
191
+ def eth_send_raw_transaction(
192
+ rpc_urls: Nodes,
193
+ raw_tx: str,
194
+ timeout: int = 10,
195
+ proxies: Proxies = None,
196
+ attempts: int = 1,
197
+ ) -> Result[str]:
198
+ return rpc_call(
199
+ nodes=rpc_urls,
200
+ method="eth_sendRawTransaction",
201
+ params=[raw_tx],
202
+ timeout=timeout,
203
+ proxies=proxies,
204
+ attempts=attempts,
205
+ )
206
+
207
+
208
+ def eth_get_balance(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
209
+ return rpc_call(
210
+ nodes=rpc_urls,
211
+ method="eth_getBalance",
212
+ params=[address, "latest"],
213
+ timeout=timeout,
214
+ proxies=proxies,
215
+ attempts=attempts,
216
+ ).and_then(hex_str_to_int)
217
+
218
+
219
+ def eth_get_transaction_count(
220
+ rpc_urls: Nodes,
221
+ address: str,
222
+ timeout: int = 10,
223
+ proxies: Proxies = None,
224
+ attempts: int = 1,
225
+ ) -> Result[int]:
226
+ return rpc_call(
227
+ nodes=rpc_urls,
228
+ method="eth_getTransactionCount",
229
+ params=[address, "latest"],
230
+ timeout=timeout,
231
+ proxies=proxies,
232
+ attempts=attempts,
233
+ ).and_then(hex_str_to_int)
234
+
235
+
236
+ def eth_get_block_by_number(
237
+ rpc_urls: Nodes,
238
+ block_number: BlockIdentifier,
239
+ full_transaction: bool = False,
240
+ timeout: int = 10,
241
+ proxies: Proxies = None,
242
+ attempts: int = 1,
243
+ ) -> Result[dict[str, Any]]:
244
+ return rpc_call(
245
+ nodes=rpc_urls,
246
+ method="eth_getBlockByNumber",
247
+ params=[hex(block_number) if isinstance(block_number, int) else block_number, full_transaction],
248
+ timeout=timeout,
249
+ proxies=proxies,
250
+ attempts=attempts,
251
+ )
252
+
253
+
254
+ def eth_get_logs(
255
+ rpc_urls: Nodes,
256
+ *,
257
+ address: str | None = None,
258
+ topics: list[str] | None = None,
259
+ from_block: BlockIdentifier | None = None,
260
+ to_block: BlockIdentifier | None = None,
261
+ timeout: int = 10,
262
+ proxies: Proxies = None,
263
+ attempts: int = 1,
264
+ ) -> Result[list[Log]]:
265
+ params: dict[str, object] = {}
266
+ if address:
267
+ params["address"] = address
268
+ if isinstance(from_block, int):
269
+ params["fromBlock"] = hex(from_block)
270
+ else:
271
+ params["fromBlock"] = "earliest"
272
+ if isinstance(to_block, int):
273
+ params["toBlock"] = hex(to_block)
274
+ if topics:
275
+ params["topics"] = topics
276
+
277
+ res = rpc_call(nodes=rpc_urls, method="eth_getLogs", params=[params], proxies=proxies, attempts=attempts, timeout=timeout)
278
+ if isinstance(res, Err):
279
+ return res
280
+
281
+ result: list[Log] = []
282
+ for log_data in res.ok:
283
+ log_res = Log.from_json_rpc_dict(log_data)
284
+ if isinstance(log_res, Err):
285
+ return Err(log_res.err, data=res.data)
286
+ result.append(log_res.ok)
287
+ return Ok(result, data=res.data)
288
+
289
+
290
+ def eth_get_transaction_receipt(
291
+ rpc_urls: Nodes,
292
+ tx_hash: str,
293
+ timeout: int = 10,
294
+ proxies: Proxies = None,
295
+ attempts: int = 1,
296
+ ) -> Result[TxReceipt]:
297
+ res = rpc_call(
298
+ nodes=rpc_urls,
299
+ method="eth_getTransactionReceipt",
300
+ params=[tx_hash],
301
+ timeout=timeout,
302
+ proxies=proxies,
303
+ attempts=attempts,
304
+ )
305
+ if isinstance(res, Err):
306
+ return res
307
+
308
+ if res.ok is None:
309
+ return Err("no_receipt", data=res.data)
310
+
311
+ try:
312
+ status = None
313
+ receipt = cast(dict[str, Any], res.ok)
314
+ if "status" in receipt:
315
+ status = int(receipt["status"], 16)
316
+ return Ok(
317
+ TxReceipt(
318
+ tx_hash=tx_hash,
319
+ tx_index=int(receipt["transactionIndex"], 16),
320
+ block_number=int(receipt["blockNumber"], 16),
321
+ from_address=receipt["from"],
322
+ to_address=receipt.get("to"),
323
+ contract_address=receipt.get("contractAddress"),
324
+ status=status,
325
+ ),
326
+ data=res.data,
327
+ )
328
+ except Exception as err:
329
+ return Err(f"exception: {err}", data=res.data)
330
+
331
+
332
+ def eth_get_transaction_by_hash(
333
+ rpc_urls: Nodes,
334
+ tx_hash: str,
335
+ timeout: int = 10,
336
+ proxies: Proxies = None,
337
+ attempts: int = 1,
338
+ ) -> Result[TxData]:
339
+ res = rpc_call(
340
+ nodes=rpc_urls,
341
+ method="eth_getTransactionByHash",
342
+ params=[tx_hash],
343
+ timeout=timeout,
344
+ proxies=proxies,
345
+ attempts=attempts,
346
+ )
347
+ if isinstance(res, Err):
348
+ return res
349
+ if res.ok is None:
350
+ return Err("not_found", data=res.data)
351
+
352
+ try:
353
+ tx = res.ok
354
+ return Ok(
355
+ TxData(
356
+ block_number=int(tx["blockNumber"], 16) if tx["blockNumber"] is not None else None,
357
+ from_=tx["from"],
358
+ to=tx.get("to"),
359
+ gas=int(tx["gas"], 16),
360
+ gas_price=int(tx["gasPrice"], 16),
361
+ value=int(tx["value"], 16),
362
+ nonce=int(tx["nonce"], 16),
363
+ input=tx["input"],
364
+ hash=tx_hash,
365
+ v=int(tx["v"], 16),
366
+ r=tx.get("r"),
367
+ s=tx.get("s"),
368
+ ),
369
+ data=res.data,
370
+ )
371
+
372
+ except Exception as err:
373
+ return Err(f"exception: {err}", data=res.data)
374
+
375
+
376
+ def eth_call(
377
+ rpc_urls: Nodes,
378
+ to: str,
379
+ data: str,
380
+ timeout: int = 10,
381
+ proxies: Proxies = None,
382
+ attempts: int = 1,
383
+ ) -> Result[str]:
384
+ return rpc_call(
385
+ nodes=rpc_urls,
386
+ method="eth_call",
387
+ params=[{"to": to, "data": data}, "latest"],
388
+ timeout=timeout,
389
+ proxies=proxies,
390
+ attempts=attempts,
391
+ )
392
+
393
+
394
+ def eth_estimate_gas(
395
+ rpc_urls: Nodes,
396
+ from_: str,
397
+ to: str | None = None,
398
+ value: int | None = 0,
399
+ data: str | None = None,
400
+ type_: Literal["0x0", "0x2"] | None = None,
401
+ timeout: int = 10,
402
+ proxies: Proxies = None,
403
+ attempts: int = 1,
404
+ ) -> Result[int]:
405
+ params: dict[str, Any] = {"from": from_}
406
+ if to:
407
+ params["to"] = to
408
+ if data:
409
+ params["data"] = data
410
+ if value:
411
+ params["value"] = hex(value)
412
+ if type_:
413
+ params["type"] = type_
414
+ return rpc_call(
415
+ nodes=rpc_urls,
416
+ method="eth_estimateGas",
417
+ params=[params],
418
+ timeout=timeout,
419
+ proxies=proxies,
420
+ attempts=attempts,
421
+ ).and_then(hex_str_to_int)
422
+
423
+
424
+ def eth_gas_price(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
425
+ return rpc_call(
426
+ nodes=rpc_urls,
427
+ method="eth_gasPrice",
428
+ params=[],
429
+ timeout=timeout,
430
+ proxies=proxies,
431
+ attempts=attempts,
432
+ ).and_then(hex_str_to_int)
433
+
434
+
435
+ def eth_syncing(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[bool | dict[str, int]]:
436
+ res = rpc_call(nodes=rpc_urls, method="eth_syncing", params=[], timeout=timeout, proxies=proxies, attempts=attempts)
437
+ if isinstance(res, Err):
438
+ return res
439
+
440
+ if isinstance(res.ok, dict):
441
+ result = {}
442
+ for k, v in res.ok.items():
443
+ if v:
444
+ result[k] = int(v, 16)
445
+ else:
446
+ result[k] = v
447
+ if result.get("currentBlock", None) and result.get("highestBlock", None):
448
+ result["remaining"] = result["highestBlock"] - result["currentBlock"]
449
+ return Ok(result, res.data)
450
+
451
+ return res
452
+
453
+
454
+ def get_tx_status(rpc_urls: Nodes, tx_hash: str, timeout: int = 5, proxies: Proxies = None, attempts: int = 5) -> Result[int]:
455
+ res: Result[int] = Err("not started yet")
456
+ for _ in range(attempts):
457
+ node = cast(str, random_choice(rpc_urls))
458
+ cast(str | None, random_choice(proxies))
459
+ receipt_res = eth_get_transaction_receipt(node, tx_hash, timeout, proxies=proxies, attempts=1)
460
+ if isinstance(receipt_res, Err) and receipt_res.err == "no_receipt":
461
+ return receipt_res
462
+ if isinstance(receipt_res, Ok) and receipt_res.ok.status is None:
463
+ return Err("no_status", data=res.data)
464
+
465
+ if isinstance(receipt_res, Ok):
466
+ return Ok(cast(int, receipt_res.ok.status), data=receipt_res.data)
467
+ res = receipt_res
468
+
469
+ return res
470
+
471
+
472
+ def get_base_fee_per_gas(rpc_urls: Nodes, timeout: int = 5, proxies: Proxies = None, attempts: int = 5) -> Result[int]:
473
+ res = eth_get_block_by_number(rpc_urls, "latest", False, timeout=timeout, proxies=proxies, attempts=attempts)
474
+ if isinstance(res, Err):
475
+ return res
476
+ if "baseFeePerGas" in res.ok:
477
+ return Ok(int(res.ok["baseFeePerGas"], 16), data=res.data)
478
+ return Err("no_base_fee_per_gas", data=res.data)
File without changes
mm_eth/solc.py ADDED
@@ -0,0 +1,34 @@
1
+ import os.path
2
+ import random
3
+ import shutil
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+ from mm_std import Err, Ok, Result, run_command
8
+
9
+
10
+ @dataclass
11
+ class SolcResult:
12
+ bin: str
13
+ abi: str
14
+
15
+
16
+ def solc(contract_name: str, contract_path: str, tmp_dir: str) -> Result[SolcResult]:
17
+ if tmp_dir.startswith("~"):
18
+ tmp_dir = os.path.expanduser(tmp_dir)
19
+ if contract_path.startswith("~"):
20
+ contract_path = os.path.expanduser(contract_path)
21
+ work_dir = f"{tmp_dir}/solc_{contract_name}_{random.randint(0, 100_000_000)}"
22
+ abi_path = f"{work_dir}/{contract_name}.abi"
23
+ bin_path = f"{work_dir}/{contract_name}.bin"
24
+ try:
25
+ Path(work_dir).mkdir(parents=True)
26
+ cmd = f"solc -o '{work_dir}' --abi --bin --optimize {contract_path}"
27
+ run_command(cmd)
28
+ abi = Path(abi_path).read_text()
29
+ bin_ = Path(bin_path).read_text()
30
+ return Ok(SolcResult(bin=bin_, abi=abi))
31
+ except Exception as e:
32
+ return Err(f"exception: {e}")
33
+ finally:
34
+ shutil.rmtree(work_dir, ignore_errors=True)
mm_eth/tx.py ADDED
@@ -0,0 +1,164 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import rlp
6
+ from eth_utils import keccak
7
+ from pydantic import BaseModel
8
+ from rlp.sedes import Binary, big_endian_int, binary
9
+ from web3 import Web3
10
+ from web3.auto import w3
11
+
12
+ from mm_eth.utils import hex_to_bytes
13
+
14
+
15
+ class SignedTx(BaseModel):
16
+ tx_hash: str
17
+ raw_tx: str
18
+
19
+
20
+ class RPLTransaction(rlp.Serializable): # type: ignore[misc]
21
+ fields = [ # noqa: RUF012
22
+ ("nonce", big_endian_int),
23
+ ("gas_price", big_endian_int),
24
+ ("gas", big_endian_int),
25
+ ("to", Binary.fixed_length(20, allow_empty=True)),
26
+ ("value", big_endian_int),
27
+ ("data", binary),
28
+ ("v", big_endian_int),
29
+ ("r", big_endian_int),
30
+ ("s", big_endian_int),
31
+ ]
32
+
33
+ @staticmethod
34
+ def new_tx(
35
+ *,
36
+ nonce: int,
37
+ gas_price: int,
38
+ gas: int,
39
+ v: int,
40
+ r: str,
41
+ s: str,
42
+ data: str | None = None,
43
+ value: int | None = None,
44
+ to: str | None = None,
45
+ ) -> RPLTransaction:
46
+ if to:
47
+ to = hex_to_bytes(to) # type:ignore
48
+ if data:
49
+ data = hex_to_bytes(data) # type:ignore
50
+ if not value:
51
+ value = 0
52
+ r = int(r, 16) # type:ignore
53
+ s = int(s, 16) # type:ignore
54
+ return RPLTransaction(nonce, gas_price, gas, to, value, data, v, r, s)
55
+
56
+
57
+ class DecodedRawTx(BaseModel):
58
+ tx_hash: str
59
+ from_: str
60
+ to: str | None
61
+ nonce: int
62
+ gas: int
63
+ gas_price: int
64
+ value: int
65
+ data: str
66
+ chain_id: int
67
+ r: str
68
+ s: str
69
+ v: int
70
+
71
+
72
+ def encode_raw_tx_with_signature(
73
+ *,
74
+ nonce: int,
75
+ gas_price: int,
76
+ gas: int,
77
+ v: int,
78
+ r: str,
79
+ s: str,
80
+ data: str | None = None,
81
+ value: int | None = None,
82
+ to: str | None = None,
83
+ ) -> str:
84
+ tx = RPLTransaction.new_tx(nonce=nonce, gas_price=gas_price, gas=gas, v=v, r=r, s=s, data=data, value=value, to=to)
85
+ return Web3.to_hex(rlp.encode(tx))
86
+
87
+
88
+ def sign_legacy_tx(
89
+ *,
90
+ nonce: int,
91
+ gas_price: int,
92
+ gas: int,
93
+ private_key: str,
94
+ chain_id: int,
95
+ data: str | None = None,
96
+ value: int | None = None,
97
+ to: str | None = None,
98
+ ) -> SignedTx:
99
+ tx: dict[str, Any] = {"gas": gas, "gasPrice": gas_price, "nonce": nonce, "chainId": chain_id}
100
+ if to:
101
+ tx["to"] = Web3.to_checksum_address(to)
102
+ if value:
103
+ tx["value"] = value
104
+ if data:
105
+ tx["data"] = data
106
+
107
+ signed = w3.eth.account.sign_transaction(tx, private_key)
108
+ return SignedTx(tx_hash=signed.hash.hex(), raw_tx=signed.rawTransaction.hex())
109
+
110
+
111
+ def sign_tx(
112
+ *,
113
+ nonce: int,
114
+ max_fee_per_gas: int,
115
+ max_priority_fee_per_gas: int,
116
+ gas: int,
117
+ private_key: str,
118
+ chain_id: int,
119
+ data: str | None = None,
120
+ value: int | None = None,
121
+ to: str | None = None,
122
+ ) -> SignedTx:
123
+ tx: dict[str, Any] = {
124
+ "type": "0x2",
125
+ "gas": gas,
126
+ "maxFeePerGas": max_fee_per_gas,
127
+ "maxPriorityFeePerGas": max_priority_fee_per_gas,
128
+ "nonce": nonce,
129
+ "chainId": chain_id,
130
+ }
131
+ if value:
132
+ tx["value"] = value
133
+ if data:
134
+ tx["data"] = data
135
+ if to:
136
+ tx["to"] = Web3.to_checksum_address(to)
137
+
138
+ signed = w3.eth.account.sign_transaction(tx, private_key)
139
+ return SignedTx(tx_hash=signed.hash.hex(), raw_tx=signed.rawTransaction.hex())
140
+
141
+
142
+ def decode_raw_tx(raw_tx: str) -> DecodedRawTx:
143
+ tx: Any = rlp.decode(hex_to_bytes(raw_tx), RPLTransaction)
144
+ tx_hash = Web3.to_hex(keccak(hex_to_bytes(raw_tx)))
145
+ from_ = w3.eth.account.recover_transaction(raw_tx)
146
+ to = Web3.to_checksum_address(tx.to) if tx.to else None
147
+ data = Web3.to_hex(tx.data)
148
+ r = hex(tx.r)
149
+ s = hex(tx.s)
150
+ chain_id = (tx.v - 35) // 2 if tx.v % 2 else (tx.v - 36) // 2
151
+ return DecodedRawTx(
152
+ tx_hash=tx_hash,
153
+ from_=from_,
154
+ to=to,
155
+ data=data,
156
+ chain_id=chain_id,
157
+ r=r,
158
+ s=s,
159
+ v=tx.v,
160
+ gas=tx.gas,
161
+ gas_price=tx.gas_price,
162
+ value=tx.value,
163
+ nonce=tx.nonce,
164
+ )
mm_eth/types.py ADDED
@@ -0,0 +1,5 @@
1
+ from collections.abc import Sequence
2
+ from typing import TypeAlias
3
+
4
+ Proxies: TypeAlias = str | Sequence[str] | None
5
+ Nodes: TypeAlias = str | Sequence[str]