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.
- mm_eth/__init__.py +0 -0
- mm_eth/abi/zksync.json +2092 -0
- mm_eth/abi.py +130 -0
- mm_eth/account.py +70 -0
- mm_eth/anvil.py +56 -0
- mm_eth/cli/__init__.py +0 -0
- mm_eth/cli/calcs.py +88 -0
- mm_eth/cli/cli.py +233 -0
- mm_eth/cli/cli_helpers.py +195 -0
- mm_eth/cli/cli_utils.py +150 -0
- mm_eth/cli/cmd/__init__.py +0 -0
- mm_eth/cli/cmd/balance_cmd.py +59 -0
- mm_eth/cli/cmd/balances_cmd.py +121 -0
- mm_eth/cli/cmd/call_contract_cmd.py +44 -0
- mm_eth/cli/cmd/config_example_cmd.py +9 -0
- mm_eth/cli/cmd/deploy_cmd.py +41 -0
- mm_eth/cli/cmd/encode_input_data_cmd.py +10 -0
- mm_eth/cli/cmd/mnemonic_cmd.py +27 -0
- mm_eth/cli/cmd/node_cmd.py +47 -0
- mm_eth/cli/cmd/private_key_cmd.py +10 -0
- mm_eth/cli/cmd/rpc_cmd.py +81 -0
- mm_eth/cli/cmd/send_contract_cmd.py +247 -0
- mm_eth/cli/cmd/solc_cmd.py +25 -0
- mm_eth/cli/cmd/token_cmd.py +29 -0
- mm_eth/cli/cmd/transfer_erc20_cmd.py +275 -0
- mm_eth/cli/cmd/transfer_eth_cmd.py +252 -0
- mm_eth/cli/cmd/vault_cmd.py +16 -0
- mm_eth/cli/config_examples/balances.yml +15 -0
- mm_eth/cli/config_examples/call_contract.yml +5 -0
- mm_eth/cli/config_examples/transfer_erc20.yml +26 -0
- mm_eth/cli/config_examples/transfer_eth.yml +24 -0
- mm_eth/cli/validators.py +84 -0
- mm_eth/deploy.py +20 -0
- mm_eth/ens.py +16 -0
- mm_eth/erc20.py +240 -0
- mm_eth/ethernodes.py +34 -0
- mm_eth/py.typed +0 -0
- mm_eth/rpc.py +478 -0
- mm_eth/services/__init__.py +0 -0
- mm_eth/solc.py +34 -0
- mm_eth/tx.py +164 -0
- mm_eth/types.py +5 -0
- mm_eth/utils.py +245 -0
- mm_eth/vault.py +38 -0
- mm_eth/zksync.py +203 -0
- mm_eth-0.1.0.dist-info/METADATA +24 -0
- mm_eth-0.1.0.dist-info/RECORD +50 -0
- mm_eth-0.1.0.dist-info/WHEEL +5 -0
- mm_eth-0.1.0.dist-info/entry_points.txt +2 -0
- 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()
|