mm-eth 0.2.5__tar.gz → 0.3.1__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.
Files changed (80) hide show
  1. {mm_eth-0.2.5 → mm_eth-0.3.1}/PKG-INFO +2 -3
  2. mm_eth-0.3.1/dict.dic +7 -0
  3. {mm_eth-0.2.5 → mm_eth-0.3.1}/pyproject.toml +3 -5
  4. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/account.py +11 -10
  5. mm_eth-0.3.1/src/mm_eth/cli/calcs.py +93 -0
  6. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cli.py +60 -37
  7. mm_eth-0.3.1/src/mm_eth/cli/cli_utils.py +32 -0
  8. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/balance_cmd.py +1 -1
  9. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/balances_cmd.py +23 -23
  10. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/call_contract_cmd.py +9 -4
  11. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/config_example_cmd.py +1 -1
  12. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/deploy_cmd.py +10 -4
  13. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/node_cmd.py +1 -1
  14. mm_eth-0.3.1/src/mm_eth/cli/cmd/send_contract_cmd.py +197 -0
  15. mm_eth-0.3.1/src/mm_eth/cli/cmd/transfer_erc20_cmd.py +200 -0
  16. mm_eth-0.3.1/src/mm_eth/cli/cmd/transfer_eth_cmd.py +182 -0
  17. mm_eth-0.3.1/src/mm_eth/cli/cmd/tx_cmd.py +16 -0
  18. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/vault_cmd.py +6 -3
  19. mm_eth-0.2.5/src/mm_eth/cli/config_examples/balances.yml → mm_eth-0.3.1/src/mm_eth/cli/config_examples/balances.toml +7 -7
  20. mm_eth-0.3.1/src/mm_eth/cli/config_examples/call_contract.toml +5 -0
  21. mm_eth-0.3.1/src/mm_eth/cli/config_examples/transfer_erc20.toml +27 -0
  22. mm_eth-0.3.1/src/mm_eth/cli/config_examples/transfer_eth.toml +26 -0
  23. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/rpc_helpers.py +43 -50
  24. mm_eth-0.3.1/src/mm_eth/cli/validators.py +44 -0
  25. mm_eth-0.3.1/src/mm_eth/constants.py +1 -0
  26. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/ens.py +2 -2
  27. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/erc20.py +1 -1
  28. mm_eth-0.3.1/src/mm_eth/json_encoder.py +15 -0
  29. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/rpc.py +2 -2
  30. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/utils.py +1 -15
  31. mm_eth-0.3.1/tests/cli/test_calcs.py +8 -0
  32. {mm_eth-0.2.5 → mm_eth-0.3.1}/uv.lock +42 -36
  33. mm_eth-0.2.5/src/mm_eth/cli/calcs.py +0 -112
  34. mm_eth-0.2.5/src/mm_eth/cli/cli_utils.py +0 -106
  35. mm_eth-0.2.5/src/mm_eth/cli/cmd/send_contract_cmd.py +0 -245
  36. mm_eth-0.2.5/src/mm_eth/cli/cmd/transfer_erc20_cmd.py +0 -272
  37. mm_eth-0.2.5/src/mm_eth/cli/cmd/transfer_eth_cmd.py +0 -249
  38. mm_eth-0.2.5/src/mm_eth/cli/config_examples/call_contract.yml +0 -5
  39. mm_eth-0.2.5/src/mm_eth/cli/config_examples/transfer_erc20.yml +0 -26
  40. mm_eth-0.2.5/src/mm_eth/cli/config_examples/transfer_eth.yml +0 -24
  41. mm_eth-0.2.5/src/mm_eth/cli/validators.py +0 -85
  42. mm_eth-0.2.5/src/mm_eth/types.py +0 -4
  43. {mm_eth-0.2.5 → mm_eth-0.3.1}/.gitignore +0 -0
  44. {mm_eth-0.2.5 → mm_eth-0.3.1}/README.txt +0 -0
  45. {mm_eth-0.2.5 → mm_eth-0.3.1}/justfile +0 -0
  46. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/__init__.py +0 -0
  47. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/abi.py +0 -0
  48. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/anvil.py +0 -0
  49. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/__init__.py +0 -0
  50. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/__init__.py +0 -0
  51. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/encode_input_data_cmd.py +0 -0
  52. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/mnemonic_cmd.py +0 -0
  53. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/private_key_cmd.py +0 -0
  54. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/rpc_cmd.py +0 -0
  55. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/solc_cmd.py +0 -0
  56. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/token_cmd.py +0 -0
  57. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/print_helpers.py +0 -0
  58. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/deploy.py +0 -0
  59. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/ethernodes.py +0 -0
  60. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/py.typed +0 -0
  61. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/solc.py +0 -0
  62. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/tx.py +0 -0
  63. {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/vault.py +0 -0
  64. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/__init__.py +0 -0
  65. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/__init__.py +0 -0
  66. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/__init__.py +0 -0
  67. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/test_balance_cmd.py +0 -0
  68. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/test_mnemonic_cmd.py +0 -0
  69. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/test_node_cmd.py +0 -0
  70. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/test_private_key_cmd.py +0 -0
  71. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/test_solc_cmd.py +0 -0
  72. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/conftest.py +0 -0
  73. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/contracts/ERC20.sol +0 -0
  74. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/contracts/abi/ERC20.json +0 -0
  75. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_abi.py +0 -0
  76. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_account.py +0 -0
  77. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_ethernodes.py +0 -0
  78. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_rpc.py +0 -0
  79. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_tx.py +0 -0
  80. {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_utils.py +0 -0
@@ -1,9 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mm-eth
3
- Version: 0.2.5
3
+ Version: 0.3.1
4
4
  Requires-Python: >=3.12
5
- Requires-Dist: loguru~=0.7.3
6
- Requires-Dist: mm-std~=0.1.12
5
+ Requires-Dist: mm-crypto-utils>=0.1.4
7
6
  Requires-Dist: typer>=0.15.1
8
7
  Requires-Dist: web3~=7.7.0
9
8
  Requires-Dist: websocket-client~=1.8.0
mm_eth-0.3.1/dict.dic ADDED
@@ -0,0 +1,7 @@
1
+ gwei
2
+ ndigits
3
+ opbnb
4
+ sepolia
5
+ nosec
6
+ goerli
7
+ arbitrum
@@ -1,15 +1,13 @@
1
1
  [project]
2
2
  name = "mm-eth"
3
- version = "0.2.5"
3
+ version = "0.3.1"
4
4
  description = ""
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
7
- "mm-std~=0.1.12",
7
+ "mm-crypto-utils>=0.1.4",
8
8
  "websocket-client~=1.8.0",
9
9
  "web3~=7.7.0",
10
- "loguru~=0.7.3",
11
10
  "typer>=0.15.1",
12
-
13
11
  ]
14
12
  [project.scripts]
15
13
  mm-eth = "mm_eth.cli.cli:app"
@@ -22,7 +20,7 @@ build-backend = "hatchling.build"
22
20
  dev-dependencies = [
23
21
  "pytest~=8.3.4",
24
22
  "pytest-xdist~=3.6.1",
25
- "ruff~=0.9.2",
23
+ "ruff~=0.9.4",
26
24
  "pip-audit~=2.7.3",
27
25
  "bandit~=1.8.2",
28
26
  "mypy~=1.14.1",
@@ -3,6 +3,7 @@ from dataclasses import dataclass
3
3
  import eth_utils
4
4
  from eth_account import Account
5
5
  from eth_account.hdaccount import Mnemonic
6
+ from eth_account.signers.local import LocalAccount
6
7
  from eth_account.types import Language
7
8
  from eth_keys import KeyAPI
8
9
  from eth_typing import ChecksumAddress
@@ -44,6 +45,12 @@ def generate_accounts( # nosec
44
45
  return result
45
46
 
46
47
 
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()
52
+
53
+
47
54
  def private_to_address(private_key: str) -> str | None:
48
55
  """returns address in lower case"""
49
56
  try:
@@ -52,19 +59,13 @@ def private_to_address(private_key: str) -> str | None:
52
59
  return None
53
60
 
54
61
 
55
- def create_private_keys_dict(private_keys: list[str]) -> dict[str, str]: # address in lower
56
- result = {}
57
- for private_key in private_keys:
58
- address = private_to_address(private_key)
59
- if address is None:
60
- raise ValueError("wrong private key")
61
- result[address.lower()] = private_key
62
- return result
63
-
64
-
65
62
  def is_private_key(private_key: str) -> bool:
66
63
  try:
67
64
  key_api.PrivateKey(decode_hex(private_key)).public_key.to_address()
68
65
  return True # noqa: TRY300
69
66
  except Exception:
70
67
  return False
68
+
69
+
70
+ def is_address(address: str) -> bool:
71
+ return eth_utils.is_address(address)
@@ -0,0 +1,93 @@
1
+ import random
2
+
3
+ import mm_crypto_utils
4
+ from mm_crypto_utils import VarInt
5
+
6
+ from mm_eth.constants import SUFFIX_DECIMALS
7
+
8
+
9
+ def calc_eth_expression(expression: str, var: VarInt | None = None) -> int:
10
+ return mm_crypto_utils.calc_int_expression(expression, var=var, suffix_decimals=SUFFIX_DECIMALS)
11
+
12
+
13
+ # def calc_var_value(value: str, *, var_name: str = "var", var_value: int | None = None, decimals: int | None = None) -> int:
14
+ # if not isinstance(value, str):
15
+ # raise TypeError(f"value is not str: {value}")
16
+ # try:
17
+ # var_name = var_name.lower()
18
+ # result = 0
19
+ # for token in split_on_plus_minus_tokens(value.lower()):
20
+ # operator = token[0]
21
+ # item = token[1:]
22
+ # if item.isdigit():
23
+ # item_value = int(item)
24
+ # elif item.endswith("eth"):
25
+ # item = item.removesuffix("eth")
26
+ # item_value = int(Decimal(item) * 10**18)
27
+ # elif item.endswith("ether"):
28
+ # item = item.removesuffix("ether")
29
+ # item_value = int(Decimal(item) * 10**18)
30
+ # elif item.endswith("gwei"):
31
+ # item = item.removesuffix("gwei")
32
+ # item_value = int(Decimal(item) * 10**9)
33
+ # elif item.endswith("t"):
34
+ # if decimals is None:
35
+ # raise ValueError("t without decimals")
36
+ # item = item.removesuffix("t")
37
+ # item_value = int(Decimal(item) * 10**decimals)
38
+ # elif item.endswith(var_name):
39
+ # if var_value is None:
40
+ # raise ValueError("var_value is not set")
41
+ # item = item.removesuffix(var_name)
42
+ # k = Decimal(item) if item else Decimal(1)
43
+ # item_value = int(k * var_value)
44
+ # elif item.startswith("random(") and item.endswith(")"):
45
+ # item = item.lstrip("random(").rstrip(")")
46
+ # arr = item.split(",")
47
+ # if len(arr) != 2:
48
+ # raise ValueError(f"wrong value, random part: {value}")
49
+ # from_value = to_wei(arr[0], decimals=decimals)
50
+ # to_value = to_wei(arr[1], decimals=decimals)
51
+ # if from_value > to_value:
52
+ # raise ValueError(f"wrong value, random part: {value}")
53
+ # item_value = random.randint(from_value, to_value)
54
+ # else:
55
+ # raise ValueError(f"wrong value: {value}")
56
+ #
57
+ # if operator == "+":
58
+ # result += item_value
59
+ # if operator == "-":
60
+ # result -= item_value
61
+ #
62
+ # return result
63
+ # except Exception as err:
64
+ # raise ValueError(f"illegal ee: {value}, error={err}") from err
65
+
66
+
67
+ def calc_function_args(value: str) -> str:
68
+ while True:
69
+ if "random(" not in value:
70
+ return value
71
+ start_index = value.index("random(")
72
+ stop_index = value.index(")", start_index)
73
+ random_range = [int(v.strip()) for v in value[start_index + 7 : stop_index].split(",")]
74
+ if len(random_range) != 2:
75
+ raise ValueError("wrong random(from,to) template")
76
+ rand_value = str(random.randint(random_range[0], random_range[1]))
77
+ value = value[0:start_index] + rand_value + value[stop_index + 1 :]
78
+
79
+
80
+ # def is_value_less_min_limit(
81
+ # value_min_limit: str | None,
82
+ # value: int,
83
+ # value_unit: str,
84
+ # decimals: int | None = None,
85
+ # log_prefix: str | None = None,
86
+ # ) -> bool:
87
+ # if value_min_limit is None:
88
+ # return False
89
+ # if value < calc_eth_expression(value_min_limit):
90
+ # prefix = get_log_prefix(log_prefix)
91
+ # logger.info("{}value is less min limit, value={}", prefix, from_wei_str(value, value_unit, decimals=decimals))
92
+ # return True
93
+ # return False
@@ -1,4 +1,5 @@
1
1
  from enum import Enum
2
+ from pathlib import Path
2
3
  from typing import Annotated
3
4
 
4
5
  import typer
@@ -21,8 +22,15 @@ from .cmd import (
21
22
  token_cmd,
22
23
  transfer_erc20_cmd,
23
24
  transfer_eth_cmd,
25
+ tx_cmd,
24
26
  vault_cmd,
25
27
  )
28
+ from .cmd.balances_cmd import BalancesCmdParams
29
+ from .cmd.call_contract_cmd import CallContractCmdParams
30
+ from .cmd.deploy_cmd import DeployCmdParams
31
+ from .cmd.send_contract_cmd import SendContractCmdParams
32
+ from .cmd.transfer_erc20_cmd import TransferErc20CmdParams
33
+ from .cmd.transfer_eth_cmd import TransferEthCmdParams
26
34
 
27
35
  app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
28
36
 
@@ -42,7 +50,7 @@ class ConfigExample(str, Enum):
42
50
  def balance_command(
43
51
  wallet_address: Annotated[str, typer.Argument()],
44
52
  token_address: Annotated[str | None, typer.Option("--token", "-t")] = None,
45
- rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL")] = "", # nosec
53
+ rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL")] = "", # nosec
46
54
  wei: bool = typer.Option(False, "--wei", "-w", help="Print balances in wei units"),
47
55
  print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
48
56
  ) -> None:
@@ -52,7 +60,7 @@ def balance_command(
52
60
  @app.command(name="token", help="Get token info")
53
61
  def token_command(
54
62
  token_address: Annotated[str, typer.Argument()],
55
- rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL")] = "",
63
+ rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL")] = "",
56
64
  ) -> None:
57
65
  token_cmd.run(rpc_url, token_address)
58
66
 
@@ -109,7 +117,7 @@ def vault_command(
109
117
  str,
110
118
  typer.Option(..., "--token", "-t", prompt=True, hide_input=True, prompt_required=False, help="A vault token"),
111
119
  ],
112
- keys_file: Annotated[str, typer.Option(..., "--file", "-f", help="Path to a file with private keys")],
120
+ keys_file: Annotated[Path, typer.Option(..., "--file", "-f", help="Path to a file with private keys")],
113
121
  ) -> None:
114
122
  vault_cmd.run(keys_url, vault_token, keys_file)
115
123
 
@@ -118,15 +126,24 @@ def vault_command(
118
126
  def rpc_command(
119
127
  method: Annotated[str, typer.Argument()] = "",
120
128
  params: Annotated[str, typer.Argument()] = "[]",
121
- rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL", help="RPC node url")] = "",
129
+ rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL", help="RPC node url")] = "",
122
130
  hex2dec: Annotated[bool, typer.Option("--hex2dec", "-d", help="Print result in decimal value")] = False,
123
131
  ) -> None:
124
132
  rpc_cmd.run(rpc_url, method, params, hex2dec)
125
133
 
126
134
 
135
+ @app.command(name="tx", help="Get transaction info by hash")
136
+ def tx_command(
137
+ tx_hash: Annotated[str, typer.Argument()],
138
+ rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL", help="RPC node url")] = "",
139
+ get_receipt: Annotated[bool, typer.Option("--receipt", "-r", help="Get receipt")] = False,
140
+ ) -> None:
141
+ tx_cmd.run(rpc_url, tx_hash, get_receipt)
142
+
143
+
127
144
  @app.command(name="transfer-eth", help="Transfer eth / base token from one or many accounts")
128
145
  def transfer_eth_command(
129
- config_path: str,
146
+ config_path: Path,
130
147
  print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
131
148
  print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
132
149
  emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
@@ -134,18 +151,20 @@ def transfer_eth_command(
134
151
  debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
135
152
  ) -> None:
136
153
  transfer_eth_cmd.run(
137
- config_path,
138
- print_balances=print_balances,
139
- print_config=print_config,
140
- debug=debug,
141
- no_receipt=no_receipt,
142
- emulate=emulate,
154
+ TransferEthCmdParams(
155
+ config_path=config_path,
156
+ print_balances=print_balances,
157
+ print_config_and_exit=print_config,
158
+ debug=debug,
159
+ no_receipt=no_receipt,
160
+ emulate=emulate,
161
+ )
143
162
  )
144
163
 
145
164
 
146
165
  @app.command(name="transfer-erc20", help="Transfer ERC20 token from one or many accounts")
147
166
  def transfer_erc20_command(
148
- config_path: str,
167
+ config_path: Path,
149
168
  print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
150
169
  print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
151
170
  emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
@@ -153,18 +172,20 @@ def transfer_erc20_command(
153
172
  debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
154
173
  ) -> None:
155
174
  transfer_erc20_cmd.run(
156
- config_path,
157
- print_balances=print_balances,
158
- print_config=print_config,
159
- debug=debug,
160
- no_receipt=no_receipt,
161
- emulate=emulate,
175
+ TransferErc20CmdParams(
176
+ config_path=config_path,
177
+ print_balances=print_balances,
178
+ print_config_and_exit=print_config,
179
+ debug=debug,
180
+ no_receipt=no_receipt,
181
+ emulate=emulate,
182
+ )
162
183
  )
163
184
 
164
185
 
165
186
  @app.command(name="send-contract", help="Send transactions to a contract")
166
187
  def send_contract_command(
167
- config_path: str,
188
+ config_path: Path,
168
189
  print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
169
190
  print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
170
191
  emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
@@ -172,39 +193,41 @@ def send_contract_command(
172
193
  debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
173
194
  ) -> None:
174
195
  send_contract_cmd.run(
175
- config_path,
176
- print_balances=print_balances,
177
- print_config=print_config,
178
- debug=debug,
179
- no_receipt=no_receipt,
180
- emulate=emulate,
196
+ SendContractCmdParams(
197
+ config_path=config_path,
198
+ print_balances=print_balances,
199
+ print_config_and_exit=print_config,
200
+ debug=debug,
201
+ no_receipt=no_receipt,
202
+ emulate=emulate,
203
+ )
181
204
  )
182
205
 
183
206
 
184
207
  @app.command(name="balances", help="Print base and ERC20 token balances")
185
208
  def balances_command(
186
- config_path: str,
187
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
209
+ config_path: Path,
210
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
188
211
  nonce: bool = typer.Option(False, "--nonce", "-n", help="Print nonce also"),
189
212
  wei: bool = typer.Option(False, "--wei", "-w", help="Show balances in WEI"),
190
213
  ) -> None:
191
- balances_cmd.run(config_path, print_config, wei, nonce)
214
+ balances_cmd.run(BalancesCmdParams(config_path=config_path, print_config_and_exit=print_config, wei=wei, show_nonce=nonce))
192
215
 
193
216
 
194
217
  @app.command(name="call-contract", help="Call a method on a contract")
195
218
  def call_contract_command(
196
- config_path: str,
197
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
219
+ config_path: Path,
220
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
198
221
  ) -> None:
199
- call_contract_cmd.run(config_path, print_config)
222
+ call_contract_cmd.run(CallContractCmdParams(config_path=config_path, print_config_and_exit=print_config))
200
223
 
201
224
 
202
225
  @app.command(name="deploy", help="Deploy a smart contract onchain")
203
226
  def deploy_command(
204
- config_path: str,
205
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
227
+ config_path: Path,
228
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
206
229
  ) -> None:
207
- deploy_cmd.run(config_path, print_config=print_config)
230
+ deploy_cmd.run(DeployCmdParams(config_path=config_path, print_config_and_exit=print_config))
208
231
 
209
232
 
210
233
  @app.command(name="config-example", help="Print an example of config for a command")
@@ -214,17 +237,17 @@ def config_example_command(command: Annotated[ConfigExample, typer.Argument()])
214
237
 
215
238
  @app.command(name="encode-input-data", help="Encode input data by a function signature")
216
239
  def encode_input_data(
217
- funtion_signature: str = typer.Argument(help="Function signature, for example: transfer(address, uint256)"),
240
+ function_signature: str = typer.Argument(help="Function signature, for example: transfer(address, uint256)"),
218
241
  args_str: str = typer.Argument(
219
242
  help="""Function arguments, as an array string. For example: '["0xA659FB44eB5d4bFaC1074Cb426b1b11D58D28308", 123]' """,
220
243
  ),
221
244
  ) -> None:
222
- encode_input_data_cmd.run(funtion_signature, args_str)
245
+ encode_input_data_cmd.run(function_signature, args_str)
223
246
 
224
247
 
225
248
  def version_callback(value: bool) -> None:
226
249
  if value:
227
- print_plain(f"mm-eth-cli version: {cli_utils.get_version()}")
250
+ print_plain(f"mm-eth: {cli_utils.get_version()}")
228
251
  raise typer.Exit
229
252
 
230
253
 
@@ -0,0 +1,32 @@
1
+ import importlib.metadata
2
+ from pathlib import Path
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ def get_version() -> str:
8
+ return importlib.metadata.version("mm-eth")
9
+
10
+
11
+ def public_rpc_url(url: str | None) -> str:
12
+ if not url or url == "1":
13
+ return "https://ethereum.publicnode.com"
14
+ if url.startswith(("http://", "https://", "ws://", "wss://")):
15
+ return url
16
+
17
+ match url.lower():
18
+ case "mainnet" | "1":
19
+ return "https://ethereum.publicnode.com"
20
+ case "opbnb" | "204":
21
+ return "https://opbnb-mainnet-rpc.bnbchain.org"
22
+ case "base" | "8453":
23
+ return "https://mainnet.base.org"
24
+ case "base-sepolia" | "84532":
25
+ return "https://sepolia.base.org"
26
+ case _:
27
+ return url
28
+
29
+
30
+ class BaseConfigParams(BaseModel):
31
+ config_path: Path
32
+ print_config_and_exit: bool
@@ -48,4 +48,4 @@ def run(rpc_url: str, wallet_address: str, token_address: str | None, wei: bool,
48
48
  print_plain(f"token_balance: {balance}", print_format)
49
49
  json_result["token_balance"] = balance
50
50
 
51
- print_json(json_result, print_format)
51
+ print_json(json_result, print_format=print_format)
@@ -1,29 +1,23 @@
1
1
  from dataclasses import dataclass
2
+ from typing import Annotated
2
3
 
3
4
  from mm_std import BaseConfig, Err, Ok, fatal
4
- from pydantic import Field, field_validator
5
+ from pydantic import BeforeValidator
5
6
  from rich.live import Live
6
7
  from rich.table import Table
7
8
 
8
9
  from mm_eth import erc20, rpc
9
- from mm_eth.cli import cli_utils, validators
10
+ from mm_eth.cli.cli_utils import BaseConfigParams
11
+ from mm_eth.cli.validators import Validators
10
12
  from mm_eth.utils import from_token_wei_str, from_wei_str
11
13
 
12
14
 
13
15
  class Config(BaseConfig):
14
- addresses: list[str]
15
- tokens: list[str] = Field(default_factory=list)
16
- nodes: list[str]
16
+ addresses: Annotated[list[str], BeforeValidator(Validators.eth_addresses(unique=True))]
17
+ tokens: Annotated[list[str], BeforeValidator(Validators.eth_addresses(unique=True))]
18
+ nodes: Annotated[list[str], BeforeValidator(Validators.nodes())]
17
19
  round_ndigits: int = 5
18
20
 
19
- @field_validator("nodes", mode="before")
20
- def nodes_validator(cls, v: str | list[str] | None) -> list[str]:
21
- return validators.nodes_validator(v)
22
-
23
- @field_validator("tokens", "addresses", mode="before")
24
- def addresses_validator(cls, v: str | list[str] | None) -> list[str]:
25
- return validators.addresses_validator(v)
26
-
27
21
 
28
22
  @dataclass
29
23
  class Token:
@@ -32,17 +26,23 @@ class Token:
32
26
  symbol: str
33
27
 
34
28
 
35
- def run(config_path: str, print_config: bool, wei: bool, show_nonce: bool) -> None:
36
- config = Config.read_config_or_exit(config_path)
37
- cli_utils.print_config_and_exit(print_config, config)
29
+ class BalancesCmdParams(BaseConfigParams):
30
+ wei: bool
31
+ show_nonce: bool
32
+
33
+
34
+ def run(params: BalancesCmdParams) -> None:
35
+ config = Config.read_toml_config_or_exit(params.config_path)
36
+ if params.print_config_and_exit:
37
+ config.print_and_exit()
38
38
 
39
39
  tokens = _get_tokens_info(config)
40
40
 
41
41
  table = Table(title="balances")
42
42
  table.add_column("address")
43
- if show_nonce:
43
+ if params.show_nonce:
44
44
  table.add_column("nonce")
45
- table.add_column("wei" if wei else "eth")
45
+ table.add_column("wei" if params.wei else "eth")
46
46
  for t in tokens:
47
47
  table.add_column(t.symbol)
48
48
 
@@ -51,13 +51,13 @@ def run(config_path: str, print_config: bool, wei: bool, show_nonce: bool) -> No
51
51
  with Live(table, refresh_per_second=0.5):
52
52
  for address in config.addresses:
53
53
  row = [address]
54
- if show_nonce:
54
+ if params.show_nonce:
55
55
  row.append(str(rpc.eth_get_transaction_count(config.nodes, address, attempts=5).ok_or_err()))
56
56
 
57
57
  base_balance_res = rpc.eth_get_balance(config.nodes, address, attempts=5)
58
58
  if isinstance(base_balance_res, Ok):
59
59
  base_sum += base_balance_res.ok
60
- if wei:
60
+ if params.wei:
61
61
  row.append(str(base_balance_res.ok))
62
62
  else:
63
63
  row.append(
@@ -70,7 +70,7 @@ def run(config_path: str, print_config: bool, wei: bool, show_nonce: bool) -> No
70
70
  token_balance_res = erc20.get_balance(config.nodes, t.address, address, attempts=5)
71
71
  if isinstance(token_balance_res, Ok):
72
72
  token_sum[t.address] += token_balance_res.ok
73
- if wei:
73
+ if params.wei:
74
74
  row.append(str(token_balance_res.ok))
75
75
  else:
76
76
  row.append(
@@ -86,9 +86,9 @@ def run(config_path: str, print_config: bool, wei: bool, show_nonce: bool) -> No
86
86
  table.add_row(*row)
87
87
 
88
88
  sum_row = ["sum"]
89
- if show_nonce:
89
+ if params.show_nonce:
90
90
  sum_row.append("")
91
- if wei:
91
+ if params.wei:
92
92
  sum_row.append(str(base_sum))
93
93
  sum_row.extend([str(token_sum[t.address]) for t in tokens])
94
94
  else:
@@ -5,7 +5,7 @@ from mm_std import BaseConfig, Err, print_plain
5
5
  from pydantic import StrictStr
6
6
 
7
7
  from mm_eth import abi, rpc
8
- from mm_eth.cli import cli_utils
8
+ from mm_eth.cli.cli_utils import BaseConfigParams
9
9
 
10
10
 
11
11
  class Config(BaseConfig):
@@ -16,9 +16,14 @@ class Config(BaseConfig):
16
16
  node: str
17
17
 
18
18
 
19
- def run(config_path: str, print_config: bool) -> None:
20
- config = Config.read_config_or_exit(config_path)
21
- cli_utils.print_config_and_exit(print_config, config)
19
+ class CallContractCmdParams(BaseConfigParams):
20
+ pass
21
+
22
+
23
+ def run(cli_params: CallContractCmdParams) -> None:
24
+ config = Config.read_toml_config_or_exit(cli_params.config_path)
25
+ if cli_params.print_config_and_exit:
26
+ config.print_and_exit()
22
27
 
23
28
  input_data = abi.encode_function_input_by_signature(
24
29
  config.function_signature,
@@ -5,5 +5,5 @@ from mm_std import print_plain
5
5
 
6
6
  def run(command: str) -> None:
7
7
  command = command.replace("-", "_")
8
- example_file = Path(Path(__file__).parent.absolute(), "../config_examples", f"{command}.yml")
8
+ example_file = Path(Path(__file__).parent.absolute(), "../config_examples", f"{command}.toml")
9
9
  print_plain(example_file.read_text())
@@ -3,7 +3,8 @@ from mm_std import BaseConfig, fatal
3
3
  from pydantic import StrictStr
4
4
 
5
5
  from mm_eth import account, deploy
6
- from mm_eth.cli import cli_utils, rpc_helpers
6
+ from mm_eth.cli import rpc_helpers
7
+ from mm_eth.cli.cli_utils import BaseConfigParams
7
8
 
8
9
 
9
10
  class Config(BaseConfig):
@@ -20,9 +21,14 @@ class Config(BaseConfig):
20
21
  node: str
21
22
 
22
23
 
23
- def run(config_path: str, *, print_config: bool) -> None:
24
- config = Config.read_config_or_exit(config_path)
25
- cli_utils.print_config_and_exit(print_config, config, {"private_key"})
24
+ class DeployCmdParams(BaseConfigParams):
25
+ pass
26
+
27
+
28
+ def run(cli_params: DeployCmdParams) -> None:
29
+ config = Config.read_toml_config_or_exit(cli_params.config_path)
30
+ if cli_params.print_config_and_exit:
31
+ config.print_and_exit({"private_key"})
26
32
 
27
33
  constructor_types = yaml.full_load(config.constructor_types)
28
34
  constructor_values = yaml.full_load(config.constructor_values)
@@ -44,4 +44,4 @@ def run(urls: list[str], print_format: PrintFormat, proxy: str | None) -> None:
44
44
  print_plain(f"base_fee: {base_fee}", print_format)
45
45
  print_plain("", print_format)
46
46
 
47
- print_json(json_result, print_format)
47
+ print_json(json_result, print_format=print_format)