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.
- {mm_eth-0.2.5 → mm_eth-0.3.1}/PKG-INFO +2 -3
- mm_eth-0.3.1/dict.dic +7 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/pyproject.toml +3 -5
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/account.py +11 -10
- mm_eth-0.3.1/src/mm_eth/cli/calcs.py +93 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cli.py +60 -37
- mm_eth-0.3.1/src/mm_eth/cli/cli_utils.py +32 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/balance_cmd.py +1 -1
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/balances_cmd.py +23 -23
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/call_contract_cmd.py +9 -4
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/config_example_cmd.py +1 -1
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/deploy_cmd.py +10 -4
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/node_cmd.py +1 -1
- mm_eth-0.3.1/src/mm_eth/cli/cmd/send_contract_cmd.py +197 -0
- mm_eth-0.3.1/src/mm_eth/cli/cmd/transfer_erc20_cmd.py +200 -0
- mm_eth-0.3.1/src/mm_eth/cli/cmd/transfer_eth_cmd.py +182 -0
- mm_eth-0.3.1/src/mm_eth/cli/cmd/tx_cmd.py +16 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/vault_cmd.py +6 -3
- 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
- mm_eth-0.3.1/src/mm_eth/cli/config_examples/call_contract.toml +5 -0
- mm_eth-0.3.1/src/mm_eth/cli/config_examples/transfer_erc20.toml +27 -0
- mm_eth-0.3.1/src/mm_eth/cli/config_examples/transfer_eth.toml +26 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/rpc_helpers.py +43 -50
- mm_eth-0.3.1/src/mm_eth/cli/validators.py +44 -0
- mm_eth-0.3.1/src/mm_eth/constants.py +1 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/ens.py +2 -2
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/erc20.py +1 -1
- mm_eth-0.3.1/src/mm_eth/json_encoder.py +15 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/rpc.py +2 -2
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/utils.py +1 -15
- mm_eth-0.3.1/tests/cli/test_calcs.py +8 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/uv.lock +42 -36
- mm_eth-0.2.5/src/mm_eth/cli/calcs.py +0 -112
- mm_eth-0.2.5/src/mm_eth/cli/cli_utils.py +0 -106
- mm_eth-0.2.5/src/mm_eth/cli/cmd/send_contract_cmd.py +0 -245
- mm_eth-0.2.5/src/mm_eth/cli/cmd/transfer_erc20_cmd.py +0 -272
- mm_eth-0.2.5/src/mm_eth/cli/cmd/transfer_eth_cmd.py +0 -249
- mm_eth-0.2.5/src/mm_eth/cli/config_examples/call_contract.yml +0 -5
- mm_eth-0.2.5/src/mm_eth/cli/config_examples/transfer_erc20.yml +0 -26
- mm_eth-0.2.5/src/mm_eth/cli/config_examples/transfer_eth.yml +0 -24
- mm_eth-0.2.5/src/mm_eth/cli/validators.py +0 -85
- mm_eth-0.2.5/src/mm_eth/types.py +0 -4
- {mm_eth-0.2.5 → mm_eth-0.3.1}/.gitignore +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/README.txt +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/justfile +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/__init__.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/abi.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/anvil.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/__init__.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/__init__.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/encode_input_data_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/mnemonic_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/private_key_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/rpc_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/solc_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/cmd/token_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/cli/print_helpers.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/deploy.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/ethernodes.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/py.typed +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/solc.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/tx.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/src/mm_eth/vault.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/__init__.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/__init__.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/__init__.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/test_balance_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/test_mnemonic_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/test_node_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/test_private_key_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/cli/cmd/test_solc_cmd.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/conftest.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/contracts/ERC20.sol +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/contracts/abi/ERC20.json +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_abi.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_account.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_ethernodes.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_rpc.py +0 -0
- {mm_eth-0.2.5 → mm_eth-0.3.1}/tests/test_tx.py +0 -0
- {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.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Requires-Python: >=3.12
|
|
5
|
-
Requires-Dist:
|
|
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
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mm-eth"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.1"
|
|
4
4
|
description = ""
|
|
5
5
|
requires-python = ">=3.12"
|
|
6
6
|
dependencies = [
|
|
7
|
-
"mm-
|
|
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.
|
|
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="
|
|
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="
|
|
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[
|
|
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="
|
|
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:
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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:
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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:
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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:
|
|
187
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config
|
|
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:
|
|
197
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config
|
|
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:
|
|
205
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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]
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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}.
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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)
|