mm-eth 0.5.16__py3-none-any.whl → 0.6.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 (46) hide show
  1. mm_eth/abi.py +2 -4
  2. mm_eth/account.py +51 -18
  3. mm_eth/anvil.py +8 -8
  4. mm_eth/cli/calcs.py +1 -16
  5. mm_eth/cli/cli.py +55 -159
  6. mm_eth/cli/cli_utils.py +14 -27
  7. mm_eth/cli/cmd/balance_cmd.py +15 -16
  8. mm_eth/cli/cmd/balances_cmd.py +35 -36
  9. mm_eth/cli/cmd/deploy_cmd.py +9 -11
  10. mm_eth/cli/cmd/node_cmd.py +20 -15
  11. mm_eth/cli/cmd/solc_cmd.py +7 -6
  12. mm_eth/cli/cmd/transfer_cmd.py +210 -128
  13. mm_eth/cli/cmd/wallet/private_key_cmd.py +5 -4
  14. mm_eth/cli/rpc_helpers.py +32 -115
  15. mm_eth/cli/validators.py +13 -16
  16. mm_eth/converters.py +56 -0
  17. mm_eth/erc20.py +6 -224
  18. mm_eth/retry.py +143 -0
  19. mm_eth/rpc.py +226 -428
  20. mm_eth/solc.py +30 -17
  21. mm_eth/tx.py +8 -9
  22. mm_eth/utils.py +0 -224
  23. {mm_eth-0.5.16.dist-info → mm_eth-0.6.0.dist-info}/METADATA +3 -3
  24. mm_eth-0.6.0.dist-info/RECORD +32 -0
  25. mm_eth/async_rpc.py +0 -94
  26. mm_eth/cli/cmd/call_contract_cmd.py +0 -44
  27. mm_eth/cli/cmd/encode_input_data_cmd.py +0 -10
  28. mm_eth/cli/cmd/example_cmd.py +0 -9
  29. mm_eth/cli/cmd/rpc_cmd.py +0 -78
  30. mm_eth/cli/cmd/token_cmd.py +0 -29
  31. mm_eth/cli/cmd/tx_cmd.py +0 -16
  32. mm_eth/cli/cmd/vault_cmd.py +0 -19
  33. mm_eth/cli/examples/balances.toml +0 -18
  34. mm_eth/cli/examples/call_contract.toml +0 -9
  35. mm_eth/cli/examples/transfer.toml +0 -46
  36. mm_eth/cli/print_helpers.py +0 -37
  37. mm_eth/constants.py +0 -1
  38. mm_eth/ens.py +0 -106
  39. mm_eth/ethernodes.py +0 -34
  40. mm_eth/json_encoder.py +0 -15
  41. mm_eth/py.typed +0 -0
  42. mm_eth/rpc_async.py +0 -170
  43. mm_eth/vault.py +0 -38
  44. mm_eth-0.5.16.dist-info/RECORD +0 -49
  45. {mm_eth-0.5.16.dist-info → mm_eth-0.6.0.dist-info}/WHEEL +0 -0
  46. {mm_eth-0.5.16.dist-info → mm_eth-0.6.0.dist-info}/entry_points.txt +0 -0
mm_eth/abi.py CHANGED
@@ -11,8 +11,6 @@ from pydantic import BaseModel
11
11
  from web3 import Web3
12
12
  from web3.auto import w3
13
13
 
14
- from mm_eth.utils import hex_to_bytes
15
-
16
14
 
17
15
  @dataclass
18
16
  class NameTypeValue:
@@ -99,7 +97,7 @@ def encode_function_input_by_signature(func_signature: str, args: list[Any]) ->
99
97
  func_abi: ABIFunction = {
100
98
  "name": func_name,
101
99
  "type": "function",
102
- "inputs": [{"type": t} for t in arg_types], # type: ignore[typeddict-item]
100
+ "inputs": [{"type": t} for t in arg_types],
103
101
  }
104
102
  return encode_function_input_by_abi(func_abi, func_name, args)
105
103
 
@@ -110,7 +108,7 @@ def encode_function_signature(func_name_with_types: str) -> HexStr:
110
108
 
111
109
 
112
110
  def decode_data(types: list[str], data: str) -> tuple[Any, ...]:
113
- return eth_abi.decode(types, hex_to_bytes(data))
111
+ return eth_abi.decode(types, eth_utils.to_bytes(hexstr=HexStr(data)))
114
112
 
115
113
 
116
114
  def encode_data(types: list[str], args: list[Any]) -> str:
mm_eth/account.py CHANGED
@@ -1,41 +1,64 @@
1
1
  from dataclasses import dataclass
2
2
 
3
- import eth_utils
4
3
  from eth_account import Account
5
4
  from eth_account.hdaccount import Mnemonic
6
5
  from eth_account.signers.local import LocalAccount
7
6
  from eth_account.types import Language
8
7
  from eth_keys import KeyAPI
9
- from eth_typing import ChecksumAddress
10
8
  from eth_utils import decode_hex
9
+ from mm_std import Result
11
10
 
12
11
  Account.enable_unaudited_hdwallet_features()
13
12
 
14
13
  key_api = KeyAPI()
15
14
 
15
+ # Default derivation path template for Ethereum HD wallets
16
16
  DEFAULT_DERIVATION_PATH = "m/44'/60'/0'/0/{i}"
17
17
 
18
18
 
19
19
  @dataclass
20
20
  class DerivedAccount:
21
+ """Represents an account derived from a mnemonic phrase."""
22
+
21
23
  index: int
22
24
  path: str
23
25
  address: str
24
26
  private_key: str
25
27
 
26
28
 
27
- def to_checksum_address(address: str) -> ChecksumAddress:
28
- return eth_utils.to_checksum_address(address)
29
+ def generate_mnemonic(num_words: int = 24) -> str:
30
+ """
31
+ Generates a BIP39 mnemonic phrase in English.
29
32
 
33
+ Args:
34
+ num_words (int): Number of words in the mnemonic (12, 15, 18, 21, or 24).
30
35
 
31
- def generate_mnemonic(num_words: int = 24) -> str:
36
+ Returns:
37
+ str: Generated mnemonic phrase.
38
+ """
32
39
  mnemonic = Mnemonic(Language.ENGLISH)
33
40
  return mnemonic.generate(num_words=num_words)
34
41
 
35
42
 
36
43
  def derive_accounts(mnemonic: str, passphrase: str, derivation_path: str, limit: int) -> list[DerivedAccount]:
44
+ """
45
+ Derives multiple Ethereum accounts from a given mnemonic phrase.
46
+
47
+ Args:
48
+ mnemonic (str): BIP39 mnemonic phrase.
49
+ passphrase (str): Optional BIP39 passphrase.
50
+ derivation_path (str): Path template with '{i}' as index placeholder.
51
+ limit (int): Number of accounts to derive.
52
+
53
+ Raises:
54
+ ValueError: If derivation_path does not contain '{i}'.
55
+
56
+ Returns:
57
+ list[DerivedAccount]: List of derived Ethereum accounts.
58
+ """
37
59
  if "{i}" not in derivation_path:
38
60
  raise ValueError("derivation_path must contain {i}, for example: " + DEFAULT_DERIVATION_PATH)
61
+
39
62
  result: list[DerivedAccount] = []
40
63
  for i in range(limit):
41
64
  path = derivation_path.replace("{i}", str(i))
@@ -45,27 +68,37 @@ def derive_accounts(mnemonic: str, passphrase: str, derivation_path: str, limit:
45
68
  return result
46
69
 
47
70
 
48
- def address_from_private(private_key: str) -> str:
49
- """returns address in lower case"""
50
- acc: LocalAccount = Account.from_key(private_key)
51
- return acc.address.lower()
71
+ def private_to_address(private_key: str, lower: bool = False) -> Result[str]:
72
+ """
73
+ Converts a private key to its corresponding Ethereum address.
52
74
 
75
+ Args:
76
+ private_key (str): Hex-encoded private key.
77
+ lower (bool): Whether to return address in lowercase.
53
78
 
54
- def private_to_address(private_key: str) -> str | None:
55
- """returns address in lower case"""
79
+ Returns:
80
+ Result[str]: Ok(address) or Err(exception) on failure.
81
+ """
56
82
  try:
57
- return key_api.PrivateKey(decode_hex(private_key)).public_key.to_address().lower()
58
- except Exception:
59
- return None
83
+ acc: LocalAccount = Account.from_key(private_key)
84
+ address = acc.address.lower() if lower else acc.address
85
+ return Result.ok(address)
86
+ except Exception as e:
87
+ return Result.err(e)
60
88
 
61
89
 
62
90
  def is_private_key(private_key: str) -> bool:
91
+ """
92
+ Checks if a given hex string is a valid Ethereum private key.
93
+
94
+ Args:
95
+ private_key (str): Hex-encoded private key.
96
+
97
+ Returns:
98
+ bool: True if valid, False otherwise.
99
+ """
63
100
  try:
64
101
  key_api.PrivateKey(decode_hex(private_key)).public_key.to_address()
65
102
  return True # noqa: TRY300
66
103
  except Exception:
67
104
  return False
68
-
69
-
70
- def is_address(address: str) -> bool:
71
- return eth_utils.is_address(address)
mm_eth/anvil.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import time
4
4
  from subprocess import Popen # nosec
5
5
 
6
- from mm_std import Err, Ok, Result
6
+ from mm_std import Result
7
7
  from mm_std.net import get_free_local_port
8
8
 
9
9
  from mm_eth import account, rpc
@@ -25,16 +25,16 @@ class Anvil:
25
25
  if self.process:
26
26
  self.process.kill()
27
27
 
28
- def check(self) -> bool:
29
- res = rpc.eth_chain_id(self.rpc_url)
30
- return isinstance(res, Ok) and res.ok == self.chain_id
28
+ async def check(self) -> bool:
29
+ res = await rpc.eth_chain_id(self.rpc_url)
30
+ return res.is_ok() and res.unwrap() == self.chain_id
31
31
 
32
32
  @property
33
33
  def rpc_url(self) -> str:
34
34
  return f"http://localhost:{self.port}"
35
35
 
36
36
  @classmethod
37
- def launch(
37
+ async def launch(
38
38
  cls,
39
39
  chain_id: int = 31337,
40
40
  port: int | None = None,
@@ -49,8 +49,8 @@ class Anvil:
49
49
  port = get_free_local_port()
50
50
  anvil = Anvil(chain_id=chain_id, port=port, mnemonic=mnemonic)
51
51
  anvil.start_process()
52
- if anvil.check():
53
- return Ok(anvil)
52
+ if await anvil.check():
53
+ return Result.ok(anvil)
54
54
  port = get_free_local_port()
55
55
 
56
- return Err("can't lauch anvil")
56
+ return Result.err("can't launch anvil")
mm_eth/cli/calcs.py CHANGED
@@ -1,9 +1,7 @@
1
- import random
2
-
3
1
  import mm_crypto_utils
4
2
  from mm_crypto_utils import VarInt
5
3
 
6
- from mm_eth.constants import SUFFIX_DECIMALS
4
+ from mm_eth.cli.validators import SUFFIX_DECIMALS
7
5
 
8
6
 
9
7
  def calc_eth_expression(expression: str, var: VarInt | None = None) -> int:
@@ -12,16 +10,3 @@ def calc_eth_expression(expression: str, var: VarInt | None = None) -> int:
12
10
 
13
11
  def calc_token_expression(expression: str, token_decimals: int, var: VarInt | None = None) -> int:
14
12
  return mm_crypto_utils.calc_int_expression(expression, var=var, suffix_decimals={"t": token_decimals})
15
-
16
-
17
- def calc_function_args(value: str) -> str:
18
- while True:
19
- if "random(" not in value:
20
- return value
21
- start_index = value.index("random(")
22
- stop_index = value.index(")", start_index)
23
- random_range = [int(v.strip()) for v in value[start_index + 7 : stop_index].split(",")]
24
- if len(random_range) != 2:
25
- raise ValueError("wrong random(from,to) template")
26
- rand_value = str(random.randint(random_range[0], random_range[1]))
27
- value = value[0:start_index] + rand_value + value[stop_index + 1 :]
mm_eth/cli/cli.py CHANGED
@@ -1,4 +1,5 @@
1
- from enum import Enum
1
+ import asyncio
2
+ import importlib.metadata
2
3
  from pathlib import Path
3
4
  from typing import Annotated
4
5
 
@@ -6,28 +7,11 @@ import typer
6
7
  from mm_std import PrintFormat, print_plain
7
8
 
8
9
  from mm_eth.account import DEFAULT_DERIVATION_PATH
9
-
10
- from . import cli_utils
11
- from .cmd import (
12
- balance_cmd,
13
- balances_cmd,
14
- call_contract_cmd,
15
- deploy_cmd,
16
- encode_input_data_cmd,
17
- example_cmd,
18
- node_cmd,
19
- rpc_cmd,
20
- solc_cmd,
21
- token_cmd,
22
- transfer_cmd,
23
- tx_cmd,
24
- vault_cmd,
25
- )
26
- from .cmd.balances_cmd import BalancesCmdParams
27
- from .cmd.call_contract_cmd import CallContractCmdParams
28
- from .cmd.deploy_cmd import DeployCmdParams
29
- from .cmd.transfer_cmd import TransferCmdParams
30
- from .cmd.wallet import mnemonic_cmd, private_key_cmd
10
+ from mm_eth.cli.cmd import balance_cmd, balances_cmd, deploy_cmd, node_cmd, solc_cmd, transfer_cmd
11
+ from mm_eth.cli.cmd.balances_cmd import BalancesCmdParams
12
+ from mm_eth.cli.cmd.deploy_cmd import DeployCmdParams
13
+ from mm_eth.cli.cmd.transfer_cmd import TransferCmdParams
14
+ from mm_eth.cli.cmd.wallet import mnemonic_cmd, private_key_cmd
31
15
 
32
16
  app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
33
17
 
@@ -36,50 +20,6 @@ app.add_typer(wallet_app, name="wallet")
36
20
  app.add_typer(wallet_app, name="w", hidden=True)
37
21
 
38
22
 
39
- class ConfigExample(str, Enum):
40
- TRANSFER = "transfer"
41
- BALANCES = "balances"
42
- CALL_CONTRACT = "call-contract"
43
-
44
-
45
- @app.command(name="balance", help="Gen account balance")
46
- def balance_command(
47
- wallet_address: Annotated[str, typer.Argument()],
48
- token_address: Annotated[str | None, typer.Option("--token", "-t")] = None,
49
- rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL")] = "", # nosec
50
- wei: bool = typer.Option(False, "--wei", "-w", help="Print balances in wei units"),
51
- print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
52
- ) -> None:
53
- balance_cmd.run(rpc_url, wallet_address, token_address, wei, print_format)
54
-
55
-
56
- @app.command(name="balances", help="Print base and ERC20 token balances")
57
- def balances_command(
58
- config_path: Path,
59
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
60
- nonce: bool = typer.Option(False, "--nonce", "-n", help="Print nonce also"),
61
- wei: bool = typer.Option(False, "--wei", "-w", help="Show balances in WEI"),
62
- ) -> None:
63
- balances_cmd.run(BalancesCmdParams(config_path=config_path, print_config=print_config, wei=wei, show_nonce=nonce))
64
-
65
-
66
- @app.command(name="token", help="Get token info")
67
- def token_command(
68
- token_address: Annotated[str, typer.Argument()],
69
- rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL")] = "",
70
- ) -> None:
71
- token_cmd.run(rpc_url, token_address)
72
-
73
-
74
- @app.command(name="node", help="Check RPC url")
75
- def node_command(
76
- urls: Annotated[list[str], typer.Argument()],
77
- proxy: Annotated[str | None, typer.Option("--proxy", "-p", help="Proxy")] = None,
78
- print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.TABLE,
79
- ) -> None:
80
- node_cmd.run(urls, proxy, print_format)
81
-
82
-
83
23
  @wallet_app.command(name="mnemonic", help="Generate eth accounts based on a mnemonic")
84
24
  def mnemonic_command( # nosec
85
25
  mnemonic: Annotated[str, typer.Option("--mnemonic", "-m")] = "",
@@ -106,47 +46,53 @@ def private_key_command(private_key: str) -> None:
106
46
  private_key_cmd.run(private_key)
107
47
 
108
48
 
109
- @app.command(name="solc", help="Compile a solidity file")
110
- def solc_command(
111
- contract_path: str,
112
- tmp_dir: str = "/tmp", # noqa: S108 # nosec
49
+ @app.command(name="node", help="Check RPC url")
50
+ def node_command(
51
+ urls: Annotated[list[str], typer.Argument()],
52
+ proxy: Annotated[str | None, typer.Option("--proxy", "-p", help="Proxy")] = None,
53
+ print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.TABLE,
54
+ ) -> None:
55
+ asyncio.run(node_cmd.run(urls, proxy, print_format))
56
+
57
+
58
+ @app.command(name="balance", help="Gen account balance")
59
+ def balance_command(
60
+ wallet_address: Annotated[str, typer.Argument()],
61
+ token_address: Annotated[str | None, typer.Option("--token", "-t")] = None,
62
+ rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL")] = "", # nosec
63
+ wei: bool = typer.Option(False, "--wei", "-w", help="Print balances in wei units"),
113
64
  print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
114
65
  ) -> None:
115
- solc_cmd.run(contract_path, tmp_dir, print_format)
66
+ asyncio.run(balance_cmd.run(rpc_url, wallet_address, token_address, wei, print_format))
116
67
 
117
68
 
118
- @app.command(name="vault", help="Save private keys to vault")
119
- def vault_command(
120
- keys_url: Annotated[
121
- str,
122
- typer.Option(..., "--url", "-u", help="Url to keys, for example https://vault.site.com:8200/v1/kv/keys1"),
123
- ],
124
- vault_token: Annotated[
125
- str,
126
- typer.Option(..., "--token", "-t", prompt=True, hide_input=True, prompt_required=False, help="A vault token"),
127
- ],
128
- keys_file: Annotated[Path, typer.Option(..., "--file", "-f", help="Path to a file with private keys")],
69
+ @app.command(name="balances", help="Print base and ERC20 token balances")
70
+ def balances_command(
71
+ config_path: Path,
72
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
73
+ nonce: bool = typer.Option(False, "--nonce", "-n", help="Print nonce also"),
74
+ wei: bool = typer.Option(False, "--wei", "-w", help="Show balances in WEI"),
129
75
  ) -> None:
130
- vault_cmd.run(keys_url, vault_token, keys_file)
76
+ asyncio.run(
77
+ balances_cmd.run(BalancesCmdParams(config_path=config_path, print_config=print_config, wei=wei, show_nonce=nonce))
78
+ )
131
79
 
132
80
 
133
- @app.command(name="rpc", help="Call a JSON-RPC method")
134
- def rpc_command(
135
- method: Annotated[str, typer.Argument()] = "",
136
- params: Annotated[str, typer.Argument()] = "[]",
137
- rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL", help="RPC node url")] = "",
138
- hex2dec: Annotated[bool, typer.Option("--hex2dec", "-d", help="Print result in decimal value")] = False,
81
+ @app.command(name="solc", help="Compile a solidity file")
82
+ def solc_command(
83
+ contract_path: Path,
84
+ tmp_dir: Path = Path("/tmp"), # noqa: S108 # nosec
85
+ print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
139
86
  ) -> None:
140
- rpc_cmd.run(rpc_url, method, params, hex2dec)
87
+ solc_cmd.run(contract_path, tmp_dir, print_format)
141
88
 
142
89
 
143
- @app.command(name="tx", help="Get transaction info by hash")
144
- def tx_command(
145
- tx_hash: Annotated[str, typer.Argument()],
146
- rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL", help="RPC node url")] = "",
147
- get_receipt: Annotated[bool, typer.Option("--receipt", "-r", help="Get receipt")] = False,
90
+ @app.command(name="deploy", help="Deploy a smart contract onchain")
91
+ def deploy_command(
92
+ config_path: Path,
93
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
148
94
  ) -> None:
149
- tx_cmd.run(rpc_url, tx_hash, get_receipt)
95
+ asyncio.run(deploy_cmd.run(DeployCmdParams(config_path=config_path, print_config=print_config)))
150
96
 
151
97
 
152
98
  @app.command(
@@ -161,74 +107,24 @@ def transfer_command(
161
107
  skip_receipt: bool = typer.Option(False, "--skip-receipt", help="Don't wait for a tx receipt"),
162
108
  debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
163
109
  ) -> None:
164
- transfer_cmd.run(
165
- TransferCmdParams(
166
- config_path=config_path,
167
- print_balances=print_balances,
168
- print_transfers=print_transfers,
169
- print_config=print_config,
170
- debug=debug,
171
- skip_receipt=skip_receipt,
172
- emulate=emulate,
110
+ asyncio.run(
111
+ transfer_cmd.run(
112
+ TransferCmdParams(
113
+ config_path=config_path,
114
+ print_balances=print_balances,
115
+ print_transfers=print_transfers,
116
+ print_config=print_config,
117
+ debug=debug,
118
+ skip_receipt=skip_receipt,
119
+ emulate=emulate,
120
+ )
173
121
  )
174
122
  )
175
123
 
176
124
 
177
- # @app.command(name="send-contract", help="Send transactions to a contract")
178
- # def send_contract_command(
179
- # config_path: Path,
180
- # print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
181
- # print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
182
- # emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
183
- # no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
184
- # debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
185
- # ) -> None:
186
- # send_contract_cmd.run(
187
- # SendContractCmdParams(
188
- # config_path=config_path,
189
- # print_balances=print_balances,
190
- # print_config=print_config,
191
- # debug=debug,
192
- # no_receipt=no_receipt,
193
- # emulate=emulate,
194
- # )
195
- # )
196
-
197
-
198
- @app.command(name="call-contract", help="Call a method on a contract")
199
- def call_contract_command(
200
- config_path: Path,
201
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
202
- ) -> None:
203
- call_contract_cmd.run(CallContractCmdParams(config_path=config_path, print_config=print_config))
204
-
205
-
206
- @app.command(name="deploy", help="Deploy a smart contract onchain")
207
- def deploy_command(
208
- config_path: Path,
209
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
210
- ) -> None:
211
- deploy_cmd.run(DeployCmdParams(config_path=config_path, print_config=print_config))
212
-
213
-
214
- @app.command(name="example", help="Displays an example configuration for a command")
215
- def example_command(command: Annotated[ConfigExample, typer.Argument()]) -> None:
216
- example_cmd.run(command)
217
-
218
-
219
- @app.command(name="encode-input-data", help="Encode input data by a function signature")
220
- def encode_input_data(
221
- function_signature: str = typer.Argument(help="Function signature, for example: transfer(address, uint256)"),
222
- args_str: str = typer.Argument(
223
- help="""Function arguments, as an array string. For example: '["0xA659FB44eB5d4bFaC1074Cb426b1b11D58D28308", 123]' """,
224
- ),
225
- ) -> None:
226
- encode_input_data_cmd.run(function_signature, args_str)
227
-
228
-
229
125
  def version_callback(value: bool) -> None:
230
126
  if value:
231
- print_plain(f"mm-eth: {cli_utils.get_version()}")
127
+ print_plain(f"mm-eth: {importlib.metadata.version('mm-eth')}")
232
128
  raise typer.Exit
233
129
 
234
130
 
mm_eth/cli/cli_utils.py CHANGED
@@ -1,28 +1,22 @@
1
1
  import importlib.metadata
2
- import time
3
2
  from pathlib import Path
4
- from typing import Literal
5
3
 
6
- from mm_crypto_utils import Nodes, Proxies
7
- from mm_std import BaseConfig, print_json
4
+ from mm_std import fatal
8
5
  from pydantic import BaseModel
6
+ from rich.table import Table
9
7
 
10
8
  from mm_eth import rpc
11
9
 
12
10
 
13
- def get_version() -> str:
14
- return importlib.metadata.version("mm-eth")
15
-
16
-
17
11
  def public_rpc_url(url: str | None) -> str:
18
12
  if not url or url == "1":
19
- return "https://ethereum.publicnode.com"
13
+ return "https://ethereum-rpc.publicnode.com"
20
14
  if url.startswith(("http://", "https://", "ws://", "wss://")):
21
15
  return url
22
16
 
23
17
  match url.lower():
24
18
  case "mainnet" | "1":
25
- return "https://ethereum.publicnode.com"
19
+ return "https://ethereum-rpc.publicnode.com"
26
20
  case "sepolia" | "11155111":
27
21
  return "https://ethereum-sepolia-rpc.publicnode.com"
28
22
  case "opbnb" | "204":
@@ -40,23 +34,16 @@ class BaseConfigParams(BaseModel):
40
34
  print_config: bool
41
35
 
42
36
 
43
- def print_config(config: BaseConfig, exclude: set[str] | None = None, count: set[str] | None = None) -> None:
44
- data = config.model_dump(exclude=exclude)
45
- if count:
46
- for k in count:
47
- data[k] = len(data[k])
48
- print_json(data)
37
+ async def check_nodes_for_chain_id(nodes: list[str], chain_id: int) -> None:
38
+ for node in nodes:
39
+ res = (await rpc.eth_chain_id(node)).unwrap_or_exit("can't get chain_id")
40
+ if res != chain_id:
41
+ fatal(f"node {node} has a wrong chain_id: {res}")
49
42
 
50
43
 
51
- def wait_tx_status(nodes: Nodes, proxies: Proxies, tx_hash: str, timeout: int) -> Literal["OK", "FAIL", "TIMEOUT"]:
52
- started_at = time.perf_counter()
53
- count = 0
54
- while True:
55
- res = rpc.get_tx_status(nodes, tx_hash, proxies=proxies, attempts=5)
56
- if res.is_ok():
57
- return "OK" if res.ok == 1 else "FAIL"
44
+ def add_table_raw(table: Table, *row: object) -> None:
45
+ table.add_row(*[str(cell) for cell in row])
58
46
 
59
- time.sleep(1)
60
- count += 1
61
- if time.perf_counter() - started_at > timeout:
62
- return "TIMEOUT"
47
+
48
+ def get_version() -> str:
49
+ return importlib.metadata.version("mm-eth")
@@ -1,45 +1,44 @@
1
+ import eth_utils
1
2
  from mm_std import PrintFormat, print_json, print_plain
2
3
 
3
- from mm_eth import erc20, rpc
4
- from mm_eth.cli.cli_utils import public_rpc_url
5
- from mm_eth.utils import from_wei_str
4
+ from mm_eth import converters, rpc
5
+ from mm_eth.cli import cli_utils
6
6
 
7
7
 
8
- def run(rpc_url: str, wallet_address: str, token_address: str | None, wei: bool, print_format: PrintFormat) -> None:
8
+ async def run(rpc_url: str, wallet_address: str, token_address: str | None, wei: bool, print_format: PrintFormat) -> None:
9
9
  result: dict[str, object] = {}
10
- rpc_url = public_rpc_url(rpc_url)
10
+ rpc_url = cli_utils.public_rpc_url(rpc_url)
11
11
 
12
12
  # nonce
13
- result["nonce"] = rpc.eth_get_transaction_count(rpc_url, wallet_address).ok_or_err()
13
+ result["nonce"] = (await rpc.eth_get_transaction_count(rpc_url, wallet_address)).value_or_error()
14
14
  if print_format == PrintFormat.PLAIN:
15
15
  print_plain(f"nonce: {result['nonce']}")
16
16
 
17
17
  # eth balance
18
18
  result["eth_balance"] = (
19
- rpc.eth_get_balance(rpc_url, wallet_address).map(lambda x: str(x) if wei else from_wei_str(x, "eth")).ok_or_err()
19
+ (await rpc.eth_get_balance(rpc_url, wallet_address))
20
+ .map(lambda value: value if wei else eth_utils.from_wei(value, "ether"))
21
+ .value_or_error()
20
22
  )
21
23
  if print_format == PrintFormat.PLAIN:
22
24
  print_plain(f"eth_balance: {result['eth_balance']}")
23
25
 
24
26
  if token_address:
25
27
  # token decimal
26
- result["token_decimal"] = erc20.get_decimals(rpc_url, token_address).ok_or_err()
28
+ result["token_decimal"] = (await rpc.erc20_decimals(rpc_url, token_address)).value_or_error()
27
29
  if print_format == PrintFormat.PLAIN:
28
30
  print_plain(f"token_decimal: {result['token_decimal']}")
29
31
 
30
32
  # token symbol
31
- result["token_symbol"] = erc20.get_symbol(rpc_url, token_address).ok_or_err()
33
+ result["token_symbol"] = (await rpc.erc20_symbol(rpc_url, token_address)).value_or_error()
32
34
  if print_format == PrintFormat.PLAIN:
33
35
  print_plain(f"token_symbol: {result['token_symbol']}")
34
36
 
35
37
  # token balance
36
- result["token_balance"] = (
37
- erc20.get_balance(rpc_url, token_address, wallet_address)
38
- .map(
39
- lambda x: str(x) if wei or not result["token_decimal"] else from_wei_str(x, "t", decimals=result["token_decimal"]) # type: ignore[arg-type]
40
- )
41
- .ok_or_err()
42
- )
38
+ result["token_balance"] = (await rpc.erc20_balance(rpc_url, token_address, wallet_address)).value_or_error()
39
+ if isinstance(result["token_balance"], int) and not wei and isinstance(result["token_decimal"], int):
40
+ result["token_balance"] = converters.from_wei(result["token_balance"], "t", decimals=result["token_decimal"])
41
+
43
42
  if print_format == PrintFormat.PLAIN:
44
43
  print_plain(f"token_balance: {result['token_balance']}")
45
44