mm-eth 0.1.5__py3-none-any.whl → 0.2.1__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/abi.py +3 -3
- mm_eth/account.py +1 -1
- mm_eth/anvil.py +2 -2
- mm_eth/erc20.py +8 -7
- mm_eth/rpc.py +8 -8
- mm_eth/solc.py +2 -3
- mm_eth/tx.py +3 -5
- mm_eth/utils.py +11 -16
- mm_eth/vault.py +5 -5
- mm_eth-0.2.1.dist-info/METADATA +7 -0
- mm_eth-0.2.1.dist-info/RECORD +18 -0
- {mm_eth-0.1.5.dist-info → mm_eth-0.2.1.dist-info}/WHEEL +1 -1
- mm_eth/abi/zksync.json +0 -2092
- mm_eth/cli/__init__.py +0 -0
- mm_eth/cli/calcs.py +0 -88
- mm_eth/cli/cli.py +0 -237
- mm_eth/cli/cli_helpers.py +0 -195
- mm_eth/cli/cli_utils.py +0 -148
- mm_eth/cli/cmd/__init__.py +0 -0
- mm_eth/cli/cmd/balance_cmd.py +0 -59
- mm_eth/cli/cmd/balances_cmd.py +0 -121
- mm_eth/cli/cmd/call_contract_cmd.py +0 -44
- mm_eth/cli/cmd/config_example_cmd.py +0 -9
- mm_eth/cli/cmd/deploy_cmd.py +0 -41
- mm_eth/cli/cmd/encode_input_data_cmd.py +0 -10
- mm_eth/cli/cmd/mnemonic_cmd.py +0 -27
- mm_eth/cli/cmd/node_cmd.py +0 -47
- mm_eth/cli/cmd/private_key_cmd.py +0 -10
- mm_eth/cli/cmd/rpc_cmd.py +0 -81
- mm_eth/cli/cmd/send_contract_cmd.py +0 -247
- mm_eth/cli/cmd/solc_cmd.py +0 -25
- mm_eth/cli/cmd/token_cmd.py +0 -29
- mm_eth/cli/cmd/transfer_erc20_cmd.py +0 -275
- mm_eth/cli/cmd/transfer_eth_cmd.py +0 -252
- mm_eth/cli/cmd/vault_cmd.py +0 -16
- mm_eth/cli/config_examples/balances.yml +0 -15
- mm_eth/cli/config_examples/call_contract.yml +0 -5
- mm_eth/cli/config_examples/transfer_erc20.yml +0 -26
- mm_eth/cli/config_examples/transfer_eth.yml +0 -24
- mm_eth/cli/validators.py +0 -84
- mm_eth/services/__init__.py +0 -0
- mm_eth/zksync.py +0 -203
- mm_eth-0.1.5.dist-info/METADATA +0 -9
- mm_eth-0.1.5.dist-info/RECORD +0 -49
- mm_eth-0.1.5.dist-info/entry_points.txt +0 -2
mm_eth/cli/__init__.py
DELETED
|
File without changes
|
mm_eth/cli/calcs.py
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
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
|
-
wallet_app = typer.Typer(no_args_is_help=True, help="Wallet commands: generate mnemonic, private to address")
|
|
30
|
-
app.add_typer(wallet_app, name="wallet")
|
|
31
|
-
app.add_typer(wallet_app, name="w", hidden=True)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class ConfigExample(str, Enum):
|
|
35
|
-
TRANSFER_ETH = "transfer-eth"
|
|
36
|
-
TRANSFER_ERC20 = "transfer-erc20"
|
|
37
|
-
BALANCES = "balances"
|
|
38
|
-
CALL_CONTRACT = "call-contract"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@app.command(name="balance", help="Gen account balance")
|
|
42
|
-
def balance_command(
|
|
43
|
-
wallet_address: Annotated[str, typer.Argument()],
|
|
44
|
-
token_address: Annotated[Union[str | None], typer.Option("--token", "-t")] = None, # noqa UP007
|
|
45
|
-
rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL")] = "", # nosec
|
|
46
|
-
wei: bool = typer.Option(False, "--wei", "-w", help="Print balances in wei units"),
|
|
47
|
-
print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
|
|
48
|
-
) -> None:
|
|
49
|
-
balance_cmd.run(rpc_url, wallet_address, token_address, wei, print_format)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@app.command(name="token", help="Get token info")
|
|
53
|
-
def token_command(
|
|
54
|
-
token_address: Annotated[str, typer.Argument()],
|
|
55
|
-
rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL")] = "",
|
|
56
|
-
) -> None:
|
|
57
|
-
token_cmd.run(rpc_url, token_address)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@app.command(name="node", help="Check RPC url")
|
|
61
|
-
def node_command(
|
|
62
|
-
urls: Annotated[list[str], typer.Argument()],
|
|
63
|
-
print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.TABLE,
|
|
64
|
-
proxy: Annotated[str | None, typer.Option("--proxy", "-p", help="Proxy")] = None,
|
|
65
|
-
) -> None:
|
|
66
|
-
node_cmd.run(urls, print_format, proxy)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@wallet_app.command(name="mnemonic", help="Generate eth accounts based on a mnemonic")
|
|
70
|
-
def mnemonic_command( # nosec
|
|
71
|
-
mnemonic: Annotated[str, typer.Option("--mnemonic", "-m")] = "",
|
|
72
|
-
passphrase: Annotated[str, typer.Option("--passphrase", "-pass")] = "",
|
|
73
|
-
print_path: bool = typer.Option(False, "--print_path"),
|
|
74
|
-
path_prefix: Annotated[str, typer.Option("--path")] = "m/44'/60'/0'/0",
|
|
75
|
-
limit: int = typer.Option(10, "--limit", "-l"),
|
|
76
|
-
save_file: str = typer.Option("", "--save", "-s", help="Save private keys to a file"),
|
|
77
|
-
) -> None:
|
|
78
|
-
mnemonic_cmd.run(
|
|
79
|
-
mnemonic,
|
|
80
|
-
passphrase=passphrase,
|
|
81
|
-
print_path=print_path,
|
|
82
|
-
limit=limit,
|
|
83
|
-
path_prefix=path_prefix,
|
|
84
|
-
save_file=save_file,
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@wallet_app.command(name="private-key", help="Print an address for a private key")
|
|
89
|
-
def private_key_command(private_key: str) -> None:
|
|
90
|
-
private_key_cmd.run(private_key)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@app.command(name="solc", help="Compile a solidity file")
|
|
94
|
-
def solc_command(
|
|
95
|
-
contract_path: str,
|
|
96
|
-
tmp_dir: str = "/tmp", # nosec
|
|
97
|
-
print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
|
|
98
|
-
) -> None:
|
|
99
|
-
solc_cmd.run(contract_path, tmp_dir, print_format)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@app.command(name="vault", help="Save private keys to vault")
|
|
103
|
-
def vault_command(
|
|
104
|
-
keys_url: Annotated[
|
|
105
|
-
str,
|
|
106
|
-
typer.Option(..., "--url", "-u", help="Url to keys, for example https://vault.site.com:8200/v1/kv/keys1"),
|
|
107
|
-
],
|
|
108
|
-
vault_token: Annotated[
|
|
109
|
-
str,
|
|
110
|
-
typer.Option(..., "--token", "-t", prompt=True, hide_input=True, prompt_required=False, help="A vault token"),
|
|
111
|
-
],
|
|
112
|
-
keys_file: Annotated[str, typer.Option(..., "--file", "-f", help="Path to a file with private keys")],
|
|
113
|
-
) -> None:
|
|
114
|
-
vault_cmd.run(keys_url, vault_token, keys_file)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
@app.command(name="rpc", help="Call a JSON-RPC method")
|
|
118
|
-
def rpc_command(
|
|
119
|
-
method: Annotated[str, typer.Argument()] = "",
|
|
120
|
-
params: Annotated[str, typer.Argument()] = "[]",
|
|
121
|
-
rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL", help="RPC node url")] = "",
|
|
122
|
-
hex2dec: Annotated[bool, typer.Option("--hex2dec", "-d", help="Print result in decimal value")] = False,
|
|
123
|
-
) -> None:
|
|
124
|
-
rpc_cmd.run(rpc_url, method, params, hex2dec)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
@app.command(name="transfer-eth", help="Transfer eth / base token from one or many accounts")
|
|
128
|
-
def transfer_eth_command(
|
|
129
|
-
config_path: str,
|
|
130
|
-
print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
|
|
131
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
132
|
-
emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
|
|
133
|
-
no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
|
|
134
|
-
debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
|
|
135
|
-
) -> None:
|
|
136
|
-
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,
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
@app.command(name="transfer-erc20", help="Transfer ERC20 token from one or many accounts")
|
|
147
|
-
def transfer_erc20_command(
|
|
148
|
-
config_path: str,
|
|
149
|
-
print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
|
|
150
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
151
|
-
emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
|
|
152
|
-
no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
|
|
153
|
-
debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
|
|
154
|
-
) -> None:
|
|
155
|
-
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,
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
@app.command(name="send-contract", help="Send transactions to a contract")
|
|
166
|
-
def send_contract_command(
|
|
167
|
-
config_path: str,
|
|
168
|
-
print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
|
|
169
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
170
|
-
emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
|
|
171
|
-
no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
|
|
172
|
-
debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
|
|
173
|
-
) -> None:
|
|
174
|
-
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,
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
@app.command(name="balances", help="Print base and ERC20 token balances")
|
|
185
|
-
def balances_command(
|
|
186
|
-
config_path: str,
|
|
187
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
|
|
188
|
-
nonce: bool = typer.Option(False, "--nonce", "-n", help="Print nonce also"),
|
|
189
|
-
wei: bool = typer.Option(False, "--wei", "-w", help="Show balances in WEI"),
|
|
190
|
-
) -> None:
|
|
191
|
-
balances_cmd.run(config_path, print_config, wei, nonce)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
@app.command(name="call-contract", help="Call a method on a contract")
|
|
195
|
-
def call_contract_command(
|
|
196
|
-
config_path: str,
|
|
197
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
|
|
198
|
-
) -> None:
|
|
199
|
-
call_contract_cmd.run(config_path, print_config)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
@app.command(name="deploy", help="Deploy a smart contract onchain")
|
|
203
|
-
def deploy_command(
|
|
204
|
-
config_path: str,
|
|
205
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
|
|
206
|
-
) -> None:
|
|
207
|
-
deploy_cmd.run(config_path, print_config=print_config)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
@app.command(name="config-example", help="Print an example of config for a command")
|
|
211
|
-
def config_example_command(command: Annotated[ConfigExample, typer.Argument()]) -> None:
|
|
212
|
-
config_example_cmd.run(command)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
@app.command(name="encode-input-data", help="Encode input data by a function signature")
|
|
216
|
-
def encode_input_data(
|
|
217
|
-
funtion_signature: str = typer.Argument(help="Function signature, for example: transfer(address, uint256)"),
|
|
218
|
-
args_str: str = typer.Argument(
|
|
219
|
-
help="""Function arguments, as an array string. For example: '["0xA659FB44eB5d4bFaC1074Cb426b1b11D58D28308", 123]' """,
|
|
220
|
-
),
|
|
221
|
-
) -> None:
|
|
222
|
-
encode_input_data_cmd.run(funtion_signature, args_str)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def version_callback(value: bool) -> None:
|
|
226
|
-
if value:
|
|
227
|
-
print_plain(f"mm-eth version: {cli_helpers.get_version()}")
|
|
228
|
-
raise typer.Exit()
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
@app.callback()
|
|
232
|
-
def main(_version: bool = typer.Option(None, "--version", callback=version_callback, is_eager=True)) -> None:
|
|
233
|
-
pass
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if __name__ == "__main_":
|
|
237
|
-
app()
|
mm_eth/cli/cli_helpers.py
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import importlib.metadata
|
|
2
|
-
import sys
|
|
3
|
-
from typing import NoReturn
|
|
4
|
-
|
|
5
|
-
from loguru import logger
|
|
6
|
-
from mm_std import Err
|
|
7
|
-
from rich.live import Live
|
|
8
|
-
from rich.table import Table
|
|
9
|
-
|
|
10
|
-
from mm_eth import erc20, rpc
|
|
11
|
-
from mm_eth.cli import calcs
|
|
12
|
-
from mm_eth.utils import from_wei_str
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def get_nonce(nodes: list[str] | str, address: str, log_prefix: str | None = None) -> int | None:
|
|
16
|
-
res = rpc.eth_get_transaction_count(nodes, address, attempts=5)
|
|
17
|
-
prefix = log_prefix or address
|
|
18
|
-
logger.debug(f"{prefix}: nonce={res.ok_or_err()}")
|
|
19
|
-
if isinstance(res, Err):
|
|
20
|
-
logger.info(f"{prefix}: nonce error, {res.err}")
|
|
21
|
-
return None
|
|
22
|
-
return res.ok
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def get_base_fee(nodes: list[str], log_prefix: str | None = None) -> int | None:
|
|
26
|
-
res = rpc.get_base_fee_per_gas(nodes)
|
|
27
|
-
prefix = _get_prefix(log_prefix)
|
|
28
|
-
logger.debug(f"{prefix}base_fee={res.ok_or_err()}")
|
|
29
|
-
if isinstance(res, Err):
|
|
30
|
-
logger.info(f"{prefix}base_fee error, {res.err}")
|
|
31
|
-
return None
|
|
32
|
-
return res.ok
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def calc_max_fee_per_gas(nodes: list[str], max_fee_per_gas: str, log_prefix: str | None = None) -> int | None:
|
|
36
|
-
if "base" in max_fee_per_gas.lower():
|
|
37
|
-
base_fee = get_base_fee(nodes, log_prefix)
|
|
38
|
-
if base_fee is None:
|
|
39
|
-
return None
|
|
40
|
-
return calcs.calc_var_wei_value(max_fee_per_gas, var_name="base", var_value=base_fee)
|
|
41
|
-
return calcs.calc_var_wei_value(max_fee_per_gas)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def is_max_fee_per_gas_limit_exceeded(
|
|
45
|
-
max_fee_per_gas: int,
|
|
46
|
-
max_fee_per_gas_limit: str | None,
|
|
47
|
-
log_prefix: str | None = None,
|
|
48
|
-
) -> bool:
|
|
49
|
-
if max_fee_per_gas_limit is None:
|
|
50
|
-
return False
|
|
51
|
-
max_fee_per_gas_limit_value = calcs.calc_var_wei_value(max_fee_per_gas_limit)
|
|
52
|
-
if max_fee_per_gas > max_fee_per_gas_limit_value:
|
|
53
|
-
prefix = _get_prefix(log_prefix)
|
|
54
|
-
logger.info(
|
|
55
|
-
"{}max_fee_per_gas_limit is exeeded, max_fee_per_gas={}, max_fee_per_gas_limit={}",
|
|
56
|
-
prefix,
|
|
57
|
-
from_wei_str(max_fee_per_gas, "gwei"),
|
|
58
|
-
from_wei_str(max_fee_per_gas_limit_value, "gwei"),
|
|
59
|
-
)
|
|
60
|
-
return True
|
|
61
|
-
return False
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def is_value_less_min_limit(
|
|
65
|
-
value_min_limit: str | None,
|
|
66
|
-
value: int,
|
|
67
|
-
value_unit: str,
|
|
68
|
-
decimals: int | None = None,
|
|
69
|
-
log_prefix: str | None = None,
|
|
70
|
-
) -> bool:
|
|
71
|
-
if value_min_limit is None:
|
|
72
|
-
return False
|
|
73
|
-
if value < calcs.calc_var_wei_value(value_min_limit, decimals=decimals):
|
|
74
|
-
prefix = _get_prefix(log_prefix)
|
|
75
|
-
logger.info("{}value is less min limit, value={}", prefix, from_wei_str(value, value_unit, decimals=decimals))
|
|
76
|
-
return True
|
|
77
|
-
return False
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def calc_gas(
|
|
81
|
-
*,
|
|
82
|
-
nodes: list[str],
|
|
83
|
-
gas: str,
|
|
84
|
-
from_address: str,
|
|
85
|
-
to_address: str,
|
|
86
|
-
value: int | None = None,
|
|
87
|
-
data: str | None = None,
|
|
88
|
-
log_prefix: str | None = None,
|
|
89
|
-
) -> int | None:
|
|
90
|
-
estimate_value = None
|
|
91
|
-
if "estimate" in gas.lower():
|
|
92
|
-
prefix = _get_prefix(log_prefix)
|
|
93
|
-
res = rpc.eth_estimate_gas(nodes, from_address, to_address, data=data, value=value, attempts=5)
|
|
94
|
-
logger.debug(f"{prefix}gas_estimate={res.ok_or_err()}")
|
|
95
|
-
if isinstance(res, Err):
|
|
96
|
-
logger.info(f"{prefix}estimate_gas error, {res.err}")
|
|
97
|
-
return None
|
|
98
|
-
estimate_value = res.ok
|
|
99
|
-
return calcs.calc_var_wei_value(gas, var_name="estimate", var_value=estimate_value)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def calc_eth_value(
|
|
103
|
-
*,
|
|
104
|
-
nodes: list[str],
|
|
105
|
-
value_str: str,
|
|
106
|
-
address: str,
|
|
107
|
-
gas: int | None = None,
|
|
108
|
-
max_fee_per_gas: int | None = None,
|
|
109
|
-
log_prefix: str | None = None,
|
|
110
|
-
) -> int | None:
|
|
111
|
-
balance_value = None
|
|
112
|
-
if "balance" in value_str.lower():
|
|
113
|
-
prefix = _get_prefix(log_prefix)
|
|
114
|
-
res = rpc.eth_get_balance(nodes, address, attempts=5)
|
|
115
|
-
logger.debug(f"{prefix}balance={res.ok_or_err()}")
|
|
116
|
-
if isinstance(res, Err):
|
|
117
|
-
logger.info(f"{prefix}balance error, {res.err}")
|
|
118
|
-
return None
|
|
119
|
-
balance_value = res.ok
|
|
120
|
-
value = calcs.calc_var_wei_value(value_str, var_name="balance", var_value=balance_value)
|
|
121
|
-
if "balance" in value_str.lower() and gas is not None and max_fee_per_gas is not None:
|
|
122
|
-
value = value - gas * max_fee_per_gas
|
|
123
|
-
return value
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def calc_erc20_value(
|
|
127
|
-
*,
|
|
128
|
-
nodes: list[str],
|
|
129
|
-
value_str: str,
|
|
130
|
-
wallet_address: str,
|
|
131
|
-
token_address: str,
|
|
132
|
-
decimals: int,
|
|
133
|
-
log_prefix: str | None = None,
|
|
134
|
-
) -> int | None:
|
|
135
|
-
value_str = value_str.lower()
|
|
136
|
-
balance_value = None
|
|
137
|
-
if "balance" in value_str:
|
|
138
|
-
prefix = _get_prefix(log_prefix)
|
|
139
|
-
res = erc20.get_balance(nodes, token_address, wallet_address, attempts=5)
|
|
140
|
-
logger.debug(f"{prefix}balance={res.ok_or_err()}")
|
|
141
|
-
if isinstance(res, Err):
|
|
142
|
-
logger.info(f"{prefix}balance error, {res.err}")
|
|
143
|
-
return None
|
|
144
|
-
balance_value = res.ok
|
|
145
|
-
return calcs.calc_var_wei_value(value_str, var_name="balance", var_value=balance_value, decimals=decimals)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def print_balances(
|
|
149
|
-
rpc_nodes: list[str],
|
|
150
|
-
addresses: list[str],
|
|
151
|
-
*,
|
|
152
|
-
token_address: str | None = None,
|
|
153
|
-
token_decimals: int | None = None,
|
|
154
|
-
round_ndigits: int = 5,
|
|
155
|
-
) -> None:
|
|
156
|
-
table = Table(title="balances")
|
|
157
|
-
table.add_column("n")
|
|
158
|
-
table.add_column("address")
|
|
159
|
-
table.add_column("nonce")
|
|
160
|
-
table.add_column("balance, eth")
|
|
161
|
-
if token_address is not None and token_decimals is not None:
|
|
162
|
-
table.add_column("token, t")
|
|
163
|
-
with Live(table, refresh_per_second=0.5):
|
|
164
|
-
count = 0
|
|
165
|
-
for address in addresses:
|
|
166
|
-
count += 1
|
|
167
|
-
nonce = str(rpc.eth_get_transaction_count(rpc_nodes, address, attempts=5).ok_or_err())
|
|
168
|
-
balance = rpc.eth_get_balance(rpc_nodes, address, attempts=5).map_or_else(
|
|
169
|
-
lambda err: err,
|
|
170
|
-
lambda ok: from_wei_str(ok, "eth", round_ndigits),
|
|
171
|
-
)
|
|
172
|
-
row: list[str] = [str(count), address, nonce, balance]
|
|
173
|
-
if token_address is not None and token_decimals is not None:
|
|
174
|
-
erc20_balance = erc20.get_balance(rpc_nodes, token_address, address, attempts=5).map_or_else(
|
|
175
|
-
lambda err: err,
|
|
176
|
-
lambda ok: from_wei_str(ok, "t", decimals=token_decimals, round_ndigits=round_ndigits),
|
|
177
|
-
)
|
|
178
|
-
row.append(erc20_balance)
|
|
179
|
-
table.add_row(*row)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def fatal(message: str) -> NoReturn:
|
|
183
|
-
print(f"error: {message}", file=sys.stderr) # noqa: T201
|
|
184
|
-
sys.exit(1)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def get_version() -> str:
|
|
188
|
-
return importlib.metadata.version("mm-eth")
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def _get_prefix(log_prefix: str | None) -> str:
|
|
192
|
-
prefix = log_prefix or ""
|
|
193
|
-
if prefix:
|
|
194
|
-
prefix += ": "
|
|
195
|
-
return prefix
|
mm_eth/cli/cli_utils.py
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import time
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
import eth_utils
|
|
6
|
-
import yaml
|
|
7
|
-
from loguru import logger
|
|
8
|
-
from mm_std import Err, fatal, str_to_list, utc_now
|
|
9
|
-
from pydantic import BaseModel, ConfigDict, ValidationError
|
|
10
|
-
from rich.console import Console
|
|
11
|
-
from rich.table import Table
|
|
12
|
-
|
|
13
|
-
from mm_eth import account, rpc
|
|
14
|
-
from mm_eth.account import is_private_key
|
|
15
|
-
from mm_eth.cli import calcs
|
|
16
|
-
|
|
17
|
-
# from typing import TypeVar
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class BaseConfig(BaseModel):
|
|
21
|
-
model_config = ConfigDict(extra="forbid")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def check_nodes_for_chain_id(nodes: list[str], chain_id: int) -> None:
|
|
25
|
-
for node in nodes:
|
|
26
|
-
res = rpc.eth_chain_id(node, timeout=7)
|
|
27
|
-
if isinstance(res, Err):
|
|
28
|
-
fatal(f"can't get chain_id for {node}, error={res.err}")
|
|
29
|
-
if res.ok != chain_id:
|
|
30
|
-
fatal(f"node {node} has a wrong chain_id: {res.ok}")
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def check_private_keys(addresses: list[str], private_keys: dict[str, str]) -> None:
|
|
34
|
-
for address in addresses:
|
|
35
|
-
address = address.lower()
|
|
36
|
-
if address not in private_keys:
|
|
37
|
-
fatal(f"no private key for {address}")
|
|
38
|
-
if account.private_to_address(private_keys[address]) != address:
|
|
39
|
-
fatal(f"no private key for {address}")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def delay(value: str | None) -> None:
|
|
43
|
-
if value is None:
|
|
44
|
-
return
|
|
45
|
-
time.sleep(float(calcs.calc_decimal_value(value)))
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def read_config[T](config_cls: type[T], config_path: str) -> T:
|
|
49
|
-
try:
|
|
50
|
-
with open(config_path) as f:
|
|
51
|
-
config = config_cls(**yaml.full_load(f))
|
|
52
|
-
return config
|
|
53
|
-
except ValidationError as err:
|
|
54
|
-
table = Table(title="config validation errors")
|
|
55
|
-
table.add_column("field")
|
|
56
|
-
table.add_column("message")
|
|
57
|
-
for e in err.errors():
|
|
58
|
-
loc = e["loc"]
|
|
59
|
-
field = str(loc[0]) if len(loc) > 0 else ""
|
|
60
|
-
table.add_row(field, e["msg"])
|
|
61
|
-
console = Console()
|
|
62
|
-
console.print(table)
|
|
63
|
-
exit(1)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def log(log_path: str | None, *messages: object) -> None:
|
|
67
|
-
if log_path is None:
|
|
68
|
-
return
|
|
69
|
-
message = ", ".join([str(m) for m in messages])
|
|
70
|
-
message = f"{utc_now()}, {message}\n"
|
|
71
|
-
with open(Path(log_path).expanduser(), "a") as f:
|
|
72
|
-
f.write(message)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def load_tx_addresses_from_str(v: str | None) -> list[tuple[str, str]]:
|
|
76
|
-
result: list[tuple[str, str]] = []
|
|
77
|
-
if v is None:
|
|
78
|
-
return result
|
|
79
|
-
for line in str_to_list(v, remove_comments=True):
|
|
80
|
-
arr = line.split()
|
|
81
|
-
if len(arr) == 2 and eth_utils.is_address(arr[0]) and eth_utils.is_address(arr[1]):
|
|
82
|
-
result.append((arr[0].lower(), arr[1].lower()))
|
|
83
|
-
return result
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def load_tx_addresses_from_files(addresses_from_file: str, addresses_to_file: str) -> list[tuple[str, str]]:
|
|
87
|
-
from_file = Path(addresses_from_file).expanduser()
|
|
88
|
-
to_file = Path(addresses_to_file).expanduser()
|
|
89
|
-
if not from_file.is_file():
|
|
90
|
-
raise ValueError(f"can't read addresses from 'addresses_from_file={addresses_from_file}")
|
|
91
|
-
if not to_file.is_file():
|
|
92
|
-
raise ValueError(f"can't read addresses from 'addresses_to_file={addresses_to_file}")
|
|
93
|
-
|
|
94
|
-
# get addresses_from
|
|
95
|
-
addresses_from = []
|
|
96
|
-
for line in from_file.read_text().strip().split("\n"):
|
|
97
|
-
if not eth_utils.is_address(line):
|
|
98
|
-
raise ValueError(f"illigal address in addresses_from_file: {line}")
|
|
99
|
-
addresses_from.append(line.lower())
|
|
100
|
-
|
|
101
|
-
# get addresses_to
|
|
102
|
-
addresses_to = []
|
|
103
|
-
for line in to_file.read_text().strip().split("\n"):
|
|
104
|
-
if not eth_utils.is_address(line):
|
|
105
|
-
raise ValueError(f"illigal address in addresses_to_file: {line}")
|
|
106
|
-
addresses_to.append(line.lower())
|
|
107
|
-
|
|
108
|
-
if len(addresses_from) != len(addresses_to):
|
|
109
|
-
raise ValueError("len(addresses_from) != len(addresses_to)")
|
|
110
|
-
|
|
111
|
-
return list(zip(addresses_from, addresses_to, strict=True))
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def load_private_keys_from_file(private_keys_file: str) -> list[str]:
|
|
115
|
-
result: list[str] = []
|
|
116
|
-
for item in Path(private_keys_file).expanduser().read_text().split():
|
|
117
|
-
if is_private_key(item):
|
|
118
|
-
result.append(item)
|
|
119
|
-
return result
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def init_logger(debug: bool, log_debug_file: str | None, log_info_file: str | None) -> None:
|
|
123
|
-
if debug:
|
|
124
|
-
level = "DEBUG"
|
|
125
|
-
format_ = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> <level>{level}</level> {message}"
|
|
126
|
-
else:
|
|
127
|
-
level = "INFO"
|
|
128
|
-
format_ = "{message}"
|
|
129
|
-
|
|
130
|
-
logger.remove()
|
|
131
|
-
logger.add(sys.stderr, format=format_, colorize=True, level=level)
|
|
132
|
-
if log_debug_file:
|
|
133
|
-
logger.add(Path(log_debug_file).expanduser(), format="{time:YYYY-MM-DD HH:mm:ss} {level} {message}")
|
|
134
|
-
if log_info_file:
|
|
135
|
-
logger.add(Path(log_info_file).expanduser(), format="{message}", level="INFO")
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def public_rpc_url(url: str | None) -> str:
|
|
139
|
-
if not url or url == "1":
|
|
140
|
-
return "https://ethereum.publicnode.com"
|
|
141
|
-
if url.startswith(("http://", "https://", "ws://", "wss://")):
|
|
142
|
-
return url
|
|
143
|
-
|
|
144
|
-
match url.lower():
|
|
145
|
-
case "opbnb" | "204":
|
|
146
|
-
return "https://opbnb-mainnet-rpc.bnbchain.org"
|
|
147
|
-
case _:
|
|
148
|
-
return url
|
mm_eth/cli/cmd/__init__.py
DELETED
|
File without changes
|