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/abi.py ADDED
@@ -0,0 +1,130 @@
1
+ import string
2
+ from collections.abc import Sequence
3
+ from dataclasses import dataclass
4
+ from typing import Any, cast
5
+
6
+ import eth_abi
7
+ import eth_utils
8
+ import pydash
9
+ from eth_typing import HexStr
10
+ from pydantic import BaseModel
11
+ from web3 import Web3
12
+ from web3.auto import w3
13
+ from web3.types import ABI, ABIFunction
14
+
15
+ from mm_eth.utils import hex_to_bytes
16
+
17
+
18
+ @dataclass
19
+ class NameTypeValue:
20
+ name: str
21
+ type: str
22
+ value: Any
23
+
24
+
25
+ class FunctionInput(BaseModel):
26
+ function_abi: ABIFunction
27
+ params: dict[str, Any]
28
+
29
+ def decode_params_bytes(self) -> dict[str, Any]:
30
+ result: dict[str, Any] = {}
31
+ for k, v in self.params.items():
32
+ if isinstance(v, bytes):
33
+ try:
34
+ str_value = eth_utils.to_text(v)
35
+ except UnicodeDecodeError:
36
+ str_value = eth_utils.to_hex(v)
37
+ result[k] = "".join(filter(lambda x: x in string.printable, str_value))
38
+ else:
39
+ result[k] = v
40
+ return result
41
+
42
+ def function_signature(self) -> str:
43
+ inputs = [i["name"] for i in self.function_abi["inputs"]]
44
+ return self.function_abi["name"] + f"({','.join(inputs)})"
45
+
46
+ def to_list(self, decode_bytes: bool = False) -> list[NameTypeValue]:
47
+ result = []
48
+ for param in self.function_abi["inputs"]:
49
+ name = param["name"]
50
+ type_ = param["type"]
51
+ value = self.params[name]
52
+ if decode_bytes and isinstance(value, bytes):
53
+ try:
54
+ value = eth_utils.to_text(value)
55
+ except UnicodeDecodeError:
56
+ value = eth_utils.to_hex(value)
57
+ result.append(NameTypeValue(name, type_, value))
58
+ return result
59
+
60
+
61
+ def decode_function_input(contract_abi: ABI, tx_input: str) -> FunctionInput:
62
+ contract = w3.eth.contract(abi=contract_abi)
63
+ func, params = contract.decode_function_input(HexStr(tx_input))
64
+ return FunctionInput(function_abi=func.abi, params=params)
65
+
66
+
67
+ def get_function_abi(contr_abi: ABI, fn_name: str) -> ABIFunction:
68
+ abi = pydash.find(contr_abi, lambda x: x.get("name", None) == fn_name and x.get("type", None) == "function") # type:ignore
69
+ if not abi:
70
+ raise ValueError("can't find abi for function: " + fn_name)
71
+ return cast(ABIFunction, abi)
72
+
73
+
74
+ def encode_function_input_by_abi(abi: ABI | ABIFunction, fn_name: str, args: list[Any]) -> HexStr:
75
+ # if abi is contract_abi, get function_abi
76
+ if isinstance(abi, Sequence):
77
+ abi = get_function_abi(abi, fn_name)
78
+ abi = cast(ABIFunction, abi)
79
+
80
+ # need update all address values to checkSum version
81
+ processed_args = []
82
+ for idx, arg in enumerate(abi["inputs"]):
83
+ if arg["type"] == "address":
84
+ processed_args.append(eth_utils.to_checksum_address(args[idx]))
85
+ else:
86
+ processed_args.append(args[idx])
87
+
88
+ return Web3().eth.contract(abi=[abi]).encodeABI(fn_name=fn_name, args=processed_args) # type: ignore[no-any-return]
89
+
90
+
91
+ def encode_function_input_by_signature(func_signature: str, args: list[Any]) -> HexStr:
92
+ if not func_signature.endswith(")"):
93
+ raise ValueError(f"wrong func_signature={func_signature}. example: func1(uint256,address)")
94
+ func_signature = func_signature.removesuffix(")")
95
+ arr = func_signature.split("(")
96
+ if len(arr) != 2:
97
+ raise ValueError(f"wrong func_signature={func_signature}. example: func1(uint256,address)")
98
+ func_name = arr[0]
99
+ arg_types = [t.strip() for t in arr[1].split(",") if t.strip()]
100
+ func_abi: ABIFunction = {
101
+ "name": func_name,
102
+ "type": "function",
103
+ "inputs": [{"type": t} for t in arg_types],
104
+ }
105
+ return encode_function_input_by_abi(func_abi, func_name, args)
106
+
107
+
108
+ def encode_function_signature(func_name_with_types: str) -> HexStr:
109
+ """input example 'transfer(address,uint256)'"""
110
+ return HexStr(eth_utils.to_hex(Web3.keccak(text=func_name_with_types))[0:10])
111
+
112
+
113
+ def decode_data(types: list[str], data: str) -> tuple[Any, ...]:
114
+ return eth_abi.decode(types, hex_to_bytes(data))
115
+
116
+
117
+ def encode_data(types: list[str], args: list[Any]) -> str:
118
+ return eth_utils.to_hex(eth_abi.encode(types, args))
119
+
120
+
121
+ def parse_function_signatures(contract_abi: ABI) -> dict[str, str]:
122
+ """returns dict, key: function_name_and_types, value: 4bytes signature"""
123
+ result: dict[str, str] = {}
124
+ for item in contract_abi:
125
+ if item.get("type", None) == "function":
126
+ function_name = item["name"]
127
+ types = ",".join([i["type"] for i in item["inputs"]])
128
+ function_name_and_types = f"{function_name}({types})"
129
+ result[function_name_and_types] = encode_function_signature(function_name_and_types)
130
+ return result
mm_eth/account.py ADDED
@@ -0,0 +1,70 @@
1
+ from dataclasses import dataclass
2
+
3
+ import eth_utils
4
+ from eth_account import Account
5
+ from eth_account.hdaccount import Mnemonic
6
+ from eth_keys import KeyAPI
7
+ from eth_typing import ChecksumAddress
8
+ from eth_utils import decode_hex
9
+
10
+ Account.enable_unaudited_hdwallet_features() # type: ignore[no-untyped-call]
11
+
12
+ key_api = KeyAPI()
13
+
14
+
15
+ @dataclass
16
+ class NewAccount:
17
+ path: str
18
+ address: str
19
+ private_key: str
20
+
21
+
22
+ def to_checksum_address(address: str) -> ChecksumAddress:
23
+ return eth_utils.to_checksum_address(address)
24
+
25
+
26
+ def generate_mnemonic(num_words: int = 24) -> str:
27
+ mnemonic = Mnemonic("english") # type: ignore[no-untyped-call]
28
+ return mnemonic.generate(num_words=num_words)
29
+
30
+
31
+ def generate_accounts( # nosec
32
+ mnemonic: str,
33
+ passphrase: str = "",
34
+ path_prefix: str = "m/44'/60'/0'/0",
35
+ limit: int = 12,
36
+ ) -> list[NewAccount]:
37
+ result: list[NewAccount] = []
38
+ for i in range(limit):
39
+ path = f"{path_prefix}/{i}"
40
+ acc = Account.from_mnemonic(mnemonic=mnemonic, account_path=path, passphrase=passphrase)
41
+ result.append(NewAccount(path, acc.address, acc.key.hex()))
42
+ return result
43
+
44
+
45
+ def private_to_address(private_key: str) -> str | None:
46
+ """returns address in lower case"""
47
+ # noinspection PyBroadException
48
+ try:
49
+ return key_api.PrivateKey(decode_hex(private_key)).public_key.to_address().lower()
50
+ except Exception:
51
+ return None
52
+
53
+
54
+ def create_private_keys_dict(private_keys: list[str]) -> dict[str, str]: # address in lower
55
+ result = {}
56
+ for private_key in private_keys:
57
+ address = private_to_address(private_key)
58
+ if address is None:
59
+ raise ValueError("wrong private key")
60
+ result[address.lower()] = private_key
61
+ return result
62
+
63
+
64
+ def is_private_key(private_key: str) -> bool:
65
+ # noinspection PyBroadException
66
+ try:
67
+ key_api.PrivateKey(decode_hex(private_key)).public_key.to_address()
68
+ return True
69
+ except Exception:
70
+ return False
mm_eth/anvil.py ADDED
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from subprocess import Popen # nosec
5
+
6
+ from mm_std import Err, Ok, Result
7
+ from mm_std.net import get_free_local_port
8
+
9
+ from mm_eth import account, rpc
10
+
11
+
12
+ class Anvil:
13
+ def __init__(self, *, chain_id: int, port: int, mnemonic: str):
14
+ self.chain_id = chain_id
15
+ self.port = port
16
+ self.mnemonic = mnemonic
17
+ self.process: Popen | None = None # type: ignore[type-arg]
18
+
19
+ def start_process(self) -> None:
20
+ cmd = f"anvil -m '{self.mnemonic}' -p {self.port} --chain-id {self.chain_id}"
21
+ self.process = Popen(cmd, shell=True) # nosec
22
+ time.sleep(3)
23
+
24
+ def stop(self) -> None:
25
+ if self.process:
26
+ self.process.kill()
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
31
+
32
+ @property
33
+ def rpc_url(self) -> str:
34
+ return f"http://localhost:{self.port}"
35
+
36
+ @classmethod
37
+ def launch(
38
+ cls,
39
+ chain_id: int = 31337,
40
+ port: int | None = None,
41
+ mnemonic: str = "",
42
+ attempts: int = 3,
43
+ ) -> Result[Anvil]:
44
+ if not mnemonic:
45
+ mnemonic = account.generate_mnemonic()
46
+
47
+ for _ in range(attempts):
48
+ if not port:
49
+ port = get_free_local_port()
50
+ anvil = Anvil(chain_id=chain_id, port=port, mnemonic=mnemonic)
51
+ anvil.start_process()
52
+ if anvil.check():
53
+ return Ok(anvil)
54
+ port = get_free_local_port()
55
+
56
+ return Err("can't lauch anvil")
mm_eth/cli/__init__.py ADDED
File without changes
mm_eth/cli/calcs.py ADDED
@@ -0,0 +1,88 @@
1
+ import random
2
+ from decimal import Decimal
3
+
4
+ from mm_std.random_ import random_decimal
5
+ from mm_std.str import split_on_plus_minus_tokens
6
+
7
+ from mm_eth.utils import to_wei
8
+
9
+
10
+ def calc_var_wei_value(value: str, *, var_name: str = "var", var_value: int | None = None, decimals: int | None = None) -> int:
11
+ if not isinstance(value, str):
12
+ raise ValueError(f"value is not str: {value}")
13
+ try:
14
+ var_name = var_name.lower()
15
+ result = 0
16
+ for item in split_on_plus_minus_tokens(value.lower()):
17
+ operator = item[0]
18
+ item = item[1:]
19
+ if item.isdigit():
20
+ item_value = int(item)
21
+ elif item.endswith("eth"):
22
+ item = item.removesuffix("eth")
23
+ item_value = int(Decimal(item) * 10**18)
24
+ elif item.endswith("ether"):
25
+ item = item.removesuffix("ether")
26
+ item_value = int(Decimal(item) * 10**18)
27
+ elif item.endswith("gwei"):
28
+ item = item.removesuffix("gwei")
29
+ item_value = int(Decimal(item) * 10**9)
30
+ elif item.endswith("t"):
31
+ if decimals is None:
32
+ raise ValueError("t without decimals")
33
+ item = item.removesuffix("t")
34
+ item_value = int(Decimal(item) * 10**decimals)
35
+ elif item.endswith(var_name):
36
+ if var_value is None:
37
+ raise ValueError("base value is not set")
38
+ item = item.removesuffix(var_name)
39
+ k = Decimal(item) if item else Decimal(1)
40
+ item_value = int(k * var_value)
41
+ elif item.startswith("random(") and item.endswith(")"):
42
+ item = item.lstrip("random(").rstrip(")")
43
+ arr = item.split(",")
44
+ if len(arr) != 2:
45
+ raise ValueError(f"wrong value, random part: {value}")
46
+ from_value = to_wei(arr[0], decimals=decimals)
47
+ to_value = to_wei(arr[1], decimals=decimals)
48
+ if from_value > to_value:
49
+ raise ValueError(f"wrong value, random part: {value}")
50
+ item_value = random.randint(from_value, to_value)
51
+ else:
52
+ raise ValueError(f"wrong value: {value}")
53
+
54
+ if operator == "+":
55
+ result += item_value
56
+ if operator == "-":
57
+ result -= item_value
58
+
59
+ return result
60
+ except Exception as err:
61
+ raise ValueError(f"wrong value: {value}, error={err}") from err
62
+
63
+
64
+ def calc_decimal_value(value: str) -> Decimal:
65
+ value = value.lower().strip()
66
+ if value.startswith("random(") and value.endswith(")"):
67
+ arr = value.lstrip("random(").rstrip(")").split(",")
68
+ if len(arr) != 2:
69
+ raise ValueError(f"wrong value, random part: {value}")
70
+ from_value = Decimal(arr[0])
71
+ to_value = Decimal(arr[1])
72
+ if from_value > to_value:
73
+ raise ValueError(f"wrong value, random part: {value}")
74
+ return random_decimal(from_value, to_value)
75
+ return Decimal(value)
76
+
77
+
78
+ def calc_function_args(value: str) -> str:
79
+ while True:
80
+ if "random(" not in value:
81
+ return value
82
+ start_index = value.index("random(")
83
+ stop_index = value.index(")", start_index)
84
+ random_range = [int(v.strip()) for v in value[start_index + 7 : stop_index].split(",")]
85
+ if len(random_range) != 2:
86
+ raise ValueError("wrong random(from,to) template")
87
+ rand_value = str(random.randint(random_range[0], random_range[1]))
88
+ value = value[0:start_index] + rand_value + value[stop_index + 1 :]
mm_eth/cli/cli.py ADDED
@@ -0,0 +1,233 @@
1
+ from enum import Enum
2
+ from typing import Annotated, Union
3
+
4
+ import typer
5
+ from mm_std import PrintFormat, print_plain
6
+
7
+ from mm_eth.cli import cli_helpers
8
+ from mm_eth.cli.cmd import (
9
+ balance_cmd,
10
+ balances_cmd,
11
+ call_contract_cmd,
12
+ config_example_cmd,
13
+ deploy_cmd,
14
+ encode_input_data_cmd,
15
+ mnemonic_cmd,
16
+ node_cmd,
17
+ private_key_cmd,
18
+ rpc_cmd,
19
+ send_contract_cmd,
20
+ solc_cmd,
21
+ token_cmd,
22
+ transfer_erc20_cmd,
23
+ transfer_eth_cmd,
24
+ vault_cmd,
25
+ )
26
+
27
+ app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
28
+
29
+
30
+ class ConfigExample(str, Enum):
31
+ TRANSFER_ETH = "transfer-eth"
32
+ TRANSFER_ERC20 = "transfer-erc20"
33
+ BALANCES = "balances"
34
+ CALL_CONTRACT = "call-contract"
35
+
36
+
37
+ @app.command(name="balance", help="Gen account balance")
38
+ def balance_command(
39
+ wallet_address: Annotated[str, typer.Argument()],
40
+ token_address: Annotated[Union[str | None], typer.Option("--token", "-t")] = None, # noqa UP007
41
+ rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL")] = "", # nosec
42
+ wei: bool = typer.Option(False, "--wei", "-w", help="Print balances in wei units"),
43
+ print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
44
+ ) -> None:
45
+ balance_cmd.run(rpc_url, wallet_address, token_address, wei, print_format)
46
+
47
+
48
+ @app.command(name="token", help="Get token info")
49
+ def token_command(
50
+ token_address: Annotated[str, typer.Argument()],
51
+ rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL")] = "",
52
+ ) -> None:
53
+ token_cmd.run(rpc_url, token_address)
54
+
55
+
56
+ @app.command(name="node", help="Check RPC url")
57
+ def node_command(
58
+ urls: Annotated[list[str], typer.Argument()],
59
+ print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.TABLE,
60
+ proxy: Annotated[str, typer.Option("--proxy", "-p", help="Proxy")] = "",
61
+ ) -> None:
62
+ node_cmd.run(urls, print_format, proxy)
63
+
64
+
65
+ @app.command(name="mnemonic", help="Generate eth accounts based on a mnemonic")
66
+ def mnemonic_command( # nosec
67
+ mnemonic: Annotated[str, typer.Option("--mnemonic", "-m")] = "",
68
+ passphrase: Annotated[str, typer.Option("--passphrase", "-pass")] = "",
69
+ print_path: bool = typer.Option(False, "--print_path"),
70
+ path_prefix: Annotated[str, typer.Option("--path")] = "m/44'/60'/0'/0",
71
+ limit: int = typer.Option(10, "--limit", "-l"),
72
+ save_file: str = typer.Option("", "--save", "-s", help="Save private keys to a file"),
73
+ ) -> None:
74
+ mnemonic_cmd.run(
75
+ mnemonic,
76
+ passphrase=passphrase,
77
+ print_path=print_path,
78
+ limit=limit,
79
+ path_prefix=path_prefix,
80
+ save_file=save_file,
81
+ )
82
+
83
+
84
+ @app.command(name="private-key", help="Print an address for a private key")
85
+ def private_key_command(private_key: str) -> None:
86
+ private_key_cmd.run(private_key)
87
+
88
+
89
+ @app.command(name="solc", help="Compile a solidity file")
90
+ def solc_command(
91
+ contract_path: str,
92
+ tmp_dir: str = "/tmp", # nosec
93
+ print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
94
+ ) -> None:
95
+ solc_cmd.run(contract_path, tmp_dir, print_format)
96
+
97
+
98
+ @app.command(name="vault", help="Save private keys to vault")
99
+ def vault_command(
100
+ keys_url: Annotated[
101
+ str,
102
+ typer.Option(..., "--url", "-u", help="Url to keys, for example https://vault.site.com:8200/v1/kv/keys1"),
103
+ ],
104
+ vault_token: Annotated[
105
+ str,
106
+ typer.Option(..., "--token", "-t", prompt=True, hide_input=True, prompt_required=False, help="A vault token"),
107
+ ],
108
+ keys_file: Annotated[str, typer.Option(..., "--file", "-f", help="Path to a file with private keys")],
109
+ ) -> None:
110
+ vault_cmd.run(keys_url, vault_token, keys_file)
111
+
112
+
113
+ @app.command(name="rpc", help="Call a JSON-RPC method")
114
+ def rpc_command(
115
+ method: Annotated[str, typer.Argument()] = "",
116
+ params: Annotated[str, typer.Argument()] = "[]",
117
+ rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL", help="RPC node url")] = "",
118
+ hex2dec: Annotated[bool, typer.Option("--hex2dec", "-d", help="Print result in decimal value")] = False,
119
+ ) -> None:
120
+ rpc_cmd.run(rpc_url, method, params, hex2dec)
121
+
122
+
123
+ @app.command(name="transfer-eth", help="Transfer eth / base token from one or many accounts")
124
+ def transfer_eth_command(
125
+ config_path: str,
126
+ print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
127
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
128
+ emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
129
+ no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
130
+ debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
131
+ ) -> None:
132
+ transfer_eth_cmd.run(
133
+ config_path,
134
+ print_balances=print_balances,
135
+ print_config=print_config,
136
+ debug=debug,
137
+ no_receipt=no_receipt,
138
+ emulate=emulate,
139
+ )
140
+
141
+
142
+ @app.command(name="transfer-erc20", help="Transfer ERC20 token from one or many accounts")
143
+ def transfer_erc20_command(
144
+ config_path: str,
145
+ print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
146
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
147
+ emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
148
+ no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
149
+ debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
150
+ ) -> None:
151
+ transfer_erc20_cmd.run(
152
+ config_path,
153
+ print_balances=print_balances,
154
+ print_config=print_config,
155
+ debug=debug,
156
+ no_receipt=no_receipt,
157
+ emulate=emulate,
158
+ )
159
+
160
+
161
+ @app.command(name="send-contract", help="Send transactions to a contract")
162
+ def send_contract_command(
163
+ config_path: str,
164
+ print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
165
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
166
+ emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
167
+ no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
168
+ debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
169
+ ) -> None:
170
+ send_contract_cmd.run(
171
+ config_path,
172
+ print_balances=print_balances,
173
+ print_config=print_config,
174
+ debug=debug,
175
+ no_receipt=no_receipt,
176
+ emulate=emulate,
177
+ )
178
+
179
+
180
+ @app.command(name="balances", help="Print base and ERC20 token balances")
181
+ def balances_command(
182
+ config_path: str,
183
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
184
+ nonce: bool = typer.Option(False, "--nonce", "-n", help="Print nonce also"),
185
+ wei: bool = typer.Option(False, "--wei", "-w", help="Show balances in WEI"),
186
+ ) -> None:
187
+ balances_cmd.run(config_path, print_config, wei, nonce)
188
+
189
+
190
+ @app.command(name="call-contract", help="Call a method on a contract")
191
+ def call_contract_command(
192
+ config_path: str,
193
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
194
+ ) -> None:
195
+ call_contract_cmd.run(config_path, print_config)
196
+
197
+
198
+ @app.command(name="deploy", help="Deploy a smart contract onchain")
199
+ def deploy_command(
200
+ config_path: str,
201
+ print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
202
+ ) -> None:
203
+ deploy_cmd.run(config_path, print_config=print_config)
204
+
205
+
206
+ @app.command(name="config-example", help="Print an example of config for a command")
207
+ def config_example_command(command: Annotated[ConfigExample, typer.Argument()]) -> None:
208
+ config_example_cmd.run(command)
209
+
210
+
211
+ @app.command(name="encode-input-data", help="Encode input data by a function signature")
212
+ def encode_input_data(
213
+ funtion_signature: str = typer.Argument(help="Function signature, for example: transfer(address, uint256)"),
214
+ args_str: str = typer.Argument(
215
+ help="""Function arguments, as an array string. For example: '["0xA659FB44eB5d4bFaC1074Cb426b1b11D58D28308", 123]' """,
216
+ ),
217
+ ) -> None:
218
+ encode_input_data_cmd.run(funtion_signature, args_str)
219
+
220
+
221
+ def version_callback(value: bool) -> None:
222
+ if value:
223
+ print_plain(f"mm-eth version: {cli_helpers.get_version()}")
224
+ raise typer.Exit()
225
+
226
+
227
+ @app.callback()
228
+ def main(_version: bool = typer.Option(None, "--version", callback=version_callback, is_eager=True)) -> None:
229
+ pass
230
+
231
+
232
+ if __name__ == "__main_":
233
+ app()