mm-eth 0.5.17__py3-none-any.whl → 0.6.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/abi.py +2 -4
- mm_eth/account.py +51 -18
- mm_eth/anvil.py +8 -8
- mm_eth/cli/calcs.py +1 -16
- mm_eth/cli/cli.py +55 -159
- mm_eth/cli/cli_utils.py +14 -27
- mm_eth/cli/cmd/balance_cmd.py +15 -16
- mm_eth/cli/cmd/balances_cmd.py +35 -36
- mm_eth/cli/cmd/deploy_cmd.py +9 -11
- mm_eth/cli/cmd/node_cmd.py +20 -15
- mm_eth/cli/cmd/solc_cmd.py +7 -6
- mm_eth/cli/cmd/transfer_cmd.py +210 -128
- mm_eth/cli/cmd/wallet/private_key_cmd.py +5 -4
- mm_eth/cli/rpc_helpers.py +32 -115
- mm_eth/cli/validators.py +13 -16
- mm_eth/converters.py +56 -0
- mm_eth/erc20.py +6 -224
- mm_eth/retry.py +143 -0
- mm_eth/rpc.py +226 -428
- mm_eth/solc.py +30 -17
- mm_eth/tx.py +8 -9
- mm_eth/utils.py +0 -224
- {mm_eth-0.5.17.dist-info → mm_eth-0.6.0.dist-info}/METADATA +3 -2
- mm_eth-0.6.0.dist-info/RECORD +32 -0
- mm_eth/async_rpc.py +0 -94
- mm_eth/cli/cmd/call_contract_cmd.py +0 -44
- mm_eth/cli/cmd/encode_input_data_cmd.py +0 -10
- mm_eth/cli/cmd/example_cmd.py +0 -9
- mm_eth/cli/cmd/rpc_cmd.py +0 -78
- mm_eth/cli/cmd/token_cmd.py +0 -29
- mm_eth/cli/cmd/tx_cmd.py +0 -16
- mm_eth/cli/cmd/vault_cmd.py +0 -19
- mm_eth/cli/examples/balances.toml +0 -18
- mm_eth/cli/examples/call_contract.toml +0 -9
- mm_eth/cli/examples/transfer.toml +0 -46
- mm_eth/cli/print_helpers.py +0 -37
- mm_eth/constants.py +0 -1
- mm_eth/ens.py +0 -106
- mm_eth/ethernodes.py +0 -34
- mm_eth/json_encoder.py +0 -15
- mm_eth/py.typed +0 -0
- mm_eth/rpc_async.py +0 -160
- mm_eth/vault.py +0 -38
- mm_eth-0.5.17.dist-info/RECORD +0 -49
- {mm_eth-0.5.17.dist-info → mm_eth-0.6.0.dist-info}/WHEEL +0 -0
- {mm_eth-0.5.17.dist-info → mm_eth-0.6.0.dist-info}/entry_points.txt +0 -0
mm_eth/abi.py
CHANGED
|
@@ -11,8 +11,6 @@ from pydantic import BaseModel
|
|
|
11
11
|
from web3 import Web3
|
|
12
12
|
from web3.auto import w3
|
|
13
13
|
|
|
14
|
-
from mm_eth.utils import hex_to_bytes
|
|
15
|
-
|
|
16
14
|
|
|
17
15
|
@dataclass
|
|
18
16
|
class NameTypeValue:
|
|
@@ -99,7 +97,7 @@ def encode_function_input_by_signature(func_signature: str, args: list[Any]) ->
|
|
|
99
97
|
func_abi: ABIFunction = {
|
|
100
98
|
"name": func_name,
|
|
101
99
|
"type": "function",
|
|
102
|
-
"inputs": [{"type": t} for t in arg_types],
|
|
100
|
+
"inputs": [{"type": t} for t in arg_types],
|
|
103
101
|
}
|
|
104
102
|
return encode_function_input_by_abi(func_abi, func_name, args)
|
|
105
103
|
|
|
@@ -110,7 +108,7 @@ def encode_function_signature(func_name_with_types: str) -> HexStr:
|
|
|
110
108
|
|
|
111
109
|
|
|
112
110
|
def decode_data(types: list[str], data: str) -> tuple[Any, ...]:
|
|
113
|
-
return eth_abi.decode(types,
|
|
111
|
+
return eth_abi.decode(types, eth_utils.to_bytes(hexstr=HexStr(data)))
|
|
114
112
|
|
|
115
113
|
|
|
116
114
|
def encode_data(types: list[str], args: list[Any]) -> str:
|
mm_eth/account.py
CHANGED
|
@@ -1,41 +1,64 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
|
|
3
|
-
import eth_utils
|
|
4
3
|
from eth_account import Account
|
|
5
4
|
from eth_account.hdaccount import Mnemonic
|
|
6
5
|
from eth_account.signers.local import LocalAccount
|
|
7
6
|
from eth_account.types import Language
|
|
8
7
|
from eth_keys import KeyAPI
|
|
9
|
-
from eth_typing import ChecksumAddress
|
|
10
8
|
from eth_utils import decode_hex
|
|
9
|
+
from mm_std import Result
|
|
11
10
|
|
|
12
11
|
Account.enable_unaudited_hdwallet_features()
|
|
13
12
|
|
|
14
13
|
key_api = KeyAPI()
|
|
15
14
|
|
|
15
|
+
# Default derivation path template for Ethereum HD wallets
|
|
16
16
|
DEFAULT_DERIVATION_PATH = "m/44'/60'/0'/0/{i}"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@dataclass
|
|
20
20
|
class DerivedAccount:
|
|
21
|
+
"""Represents an account derived from a mnemonic phrase."""
|
|
22
|
+
|
|
21
23
|
index: int
|
|
22
24
|
path: str
|
|
23
25
|
address: str
|
|
24
26
|
private_key: str
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
+
def generate_mnemonic(num_words: int = 24) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Generates a BIP39 mnemonic phrase in English.
|
|
29
32
|
|
|
33
|
+
Args:
|
|
34
|
+
num_words (int): Number of words in the mnemonic (12, 15, 18, 21, or 24).
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
Returns:
|
|
37
|
+
str: Generated mnemonic phrase.
|
|
38
|
+
"""
|
|
32
39
|
mnemonic = Mnemonic(Language.ENGLISH)
|
|
33
40
|
return mnemonic.generate(num_words=num_words)
|
|
34
41
|
|
|
35
42
|
|
|
36
43
|
def derive_accounts(mnemonic: str, passphrase: str, derivation_path: str, limit: int) -> list[DerivedAccount]:
|
|
44
|
+
"""
|
|
45
|
+
Derives multiple Ethereum accounts from a given mnemonic phrase.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
mnemonic (str): BIP39 mnemonic phrase.
|
|
49
|
+
passphrase (str): Optional BIP39 passphrase.
|
|
50
|
+
derivation_path (str): Path template with '{i}' as index placeholder.
|
|
51
|
+
limit (int): Number of accounts to derive.
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If derivation_path does not contain '{i}'.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
list[DerivedAccount]: List of derived Ethereum accounts.
|
|
58
|
+
"""
|
|
37
59
|
if "{i}" not in derivation_path:
|
|
38
60
|
raise ValueError("derivation_path must contain {i}, for example: " + DEFAULT_DERIVATION_PATH)
|
|
61
|
+
|
|
39
62
|
result: list[DerivedAccount] = []
|
|
40
63
|
for i in range(limit):
|
|
41
64
|
path = derivation_path.replace("{i}", str(i))
|
|
@@ -45,27 +68,37 @@ def derive_accounts(mnemonic: str, passphrase: str, derivation_path: str, limit:
|
|
|
45
68
|
return result
|
|
46
69
|
|
|
47
70
|
|
|
48
|
-
def
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
return acc.address.lower()
|
|
71
|
+
def private_to_address(private_key: str, lower: bool = False) -> Result[str]:
|
|
72
|
+
"""
|
|
73
|
+
Converts a private key to its corresponding Ethereum address.
|
|
52
74
|
|
|
75
|
+
Args:
|
|
76
|
+
private_key (str): Hex-encoded private key.
|
|
77
|
+
lower (bool): Whether to return address in lowercase.
|
|
53
78
|
|
|
54
|
-
|
|
55
|
-
|
|
79
|
+
Returns:
|
|
80
|
+
Result[str]: Ok(address) or Err(exception) on failure.
|
|
81
|
+
"""
|
|
56
82
|
try:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return
|
|
83
|
+
acc: LocalAccount = Account.from_key(private_key)
|
|
84
|
+
address = acc.address.lower() if lower else acc.address
|
|
85
|
+
return Result.ok(address)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
return Result.err(e)
|
|
60
88
|
|
|
61
89
|
|
|
62
90
|
def is_private_key(private_key: str) -> bool:
|
|
91
|
+
"""
|
|
92
|
+
Checks if a given hex string is a valid Ethereum private key.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
private_key (str): Hex-encoded private key.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
bool: True if valid, False otherwise.
|
|
99
|
+
"""
|
|
63
100
|
try:
|
|
64
101
|
key_api.PrivateKey(decode_hex(private_key)).public_key.to_address()
|
|
65
102
|
return True # noqa: TRY300
|
|
66
103
|
except Exception:
|
|
67
104
|
return False
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def is_address(address: str) -> bool:
|
|
71
|
-
return eth_utils.is_address(address)
|
mm_eth/anvil.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import time
|
|
4
4
|
from subprocess import Popen # nosec
|
|
5
5
|
|
|
6
|
-
from mm_std import
|
|
6
|
+
from mm_std import Result
|
|
7
7
|
from mm_std.net import get_free_local_port
|
|
8
8
|
|
|
9
9
|
from mm_eth import account, rpc
|
|
@@ -25,16 +25,16 @@ class Anvil:
|
|
|
25
25
|
if self.process:
|
|
26
26
|
self.process.kill()
|
|
27
27
|
|
|
28
|
-
def check(self) -> bool:
|
|
29
|
-
res = rpc.eth_chain_id(self.rpc_url)
|
|
30
|
-
return
|
|
28
|
+
async def check(self) -> bool:
|
|
29
|
+
res = await rpc.eth_chain_id(self.rpc_url)
|
|
30
|
+
return res.is_ok() and res.unwrap() == self.chain_id
|
|
31
31
|
|
|
32
32
|
@property
|
|
33
33
|
def rpc_url(self) -> str:
|
|
34
34
|
return f"http://localhost:{self.port}"
|
|
35
35
|
|
|
36
36
|
@classmethod
|
|
37
|
-
def launch(
|
|
37
|
+
async def launch(
|
|
38
38
|
cls,
|
|
39
39
|
chain_id: int = 31337,
|
|
40
40
|
port: int | None = None,
|
|
@@ -49,8 +49,8 @@ class Anvil:
|
|
|
49
49
|
port = get_free_local_port()
|
|
50
50
|
anvil = Anvil(chain_id=chain_id, port=port, mnemonic=mnemonic)
|
|
51
51
|
anvil.start_process()
|
|
52
|
-
if anvil.check():
|
|
53
|
-
return
|
|
52
|
+
if await anvil.check():
|
|
53
|
+
return Result.ok(anvil)
|
|
54
54
|
port = get_free_local_port()
|
|
55
55
|
|
|
56
|
-
return
|
|
56
|
+
return Result.err("can't launch anvil")
|
mm_eth/cli/calcs.py
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import random
|
|
2
|
-
|
|
3
1
|
import mm_crypto_utils
|
|
4
2
|
from mm_crypto_utils import VarInt
|
|
5
3
|
|
|
6
|
-
from mm_eth.
|
|
4
|
+
from mm_eth.cli.validators import SUFFIX_DECIMALS
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
def calc_eth_expression(expression: str, var: VarInt | None = None) -> int:
|
|
@@ -12,16 +10,3 @@ def calc_eth_expression(expression: str, var: VarInt | None = None) -> int:
|
|
|
12
10
|
|
|
13
11
|
def calc_token_expression(expression: str, token_decimals: int, var: VarInt | None = None) -> int:
|
|
14
12
|
return mm_crypto_utils.calc_int_expression(expression, var=var, suffix_decimals={"t": token_decimals})
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def calc_function_args(value: str) -> str:
|
|
18
|
-
while True:
|
|
19
|
-
if "random(" not in value:
|
|
20
|
-
return value
|
|
21
|
-
start_index = value.index("random(")
|
|
22
|
-
stop_index = value.index(")", start_index)
|
|
23
|
-
random_range = [int(v.strip()) for v in value[start_index + 7 : stop_index].split(",")]
|
|
24
|
-
if len(random_range) != 2:
|
|
25
|
-
raise ValueError("wrong random(from,to) template")
|
|
26
|
-
rand_value = str(random.randint(random_range[0], random_range[1]))
|
|
27
|
-
value = value[0:start_index] + rand_value + value[stop_index + 1 :]
|
mm_eth/cli/cli.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import asyncio
|
|
2
|
+
import importlib.metadata
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import Annotated
|
|
4
5
|
|
|
@@ -6,28 +7,11 @@ import typer
|
|
|
6
7
|
from mm_std import PrintFormat, print_plain
|
|
7
8
|
|
|
8
9
|
from mm_eth.account import DEFAULT_DERIVATION_PATH
|
|
9
|
-
|
|
10
|
-
from . import
|
|
11
|
-
from .cmd import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
call_contract_cmd,
|
|
15
|
-
deploy_cmd,
|
|
16
|
-
encode_input_data_cmd,
|
|
17
|
-
example_cmd,
|
|
18
|
-
node_cmd,
|
|
19
|
-
rpc_cmd,
|
|
20
|
-
solc_cmd,
|
|
21
|
-
token_cmd,
|
|
22
|
-
transfer_cmd,
|
|
23
|
-
tx_cmd,
|
|
24
|
-
vault_cmd,
|
|
25
|
-
)
|
|
26
|
-
from .cmd.balances_cmd import BalancesCmdParams
|
|
27
|
-
from .cmd.call_contract_cmd import CallContractCmdParams
|
|
28
|
-
from .cmd.deploy_cmd import DeployCmdParams
|
|
29
|
-
from .cmd.transfer_cmd import TransferCmdParams
|
|
30
|
-
from .cmd.wallet import mnemonic_cmd, private_key_cmd
|
|
10
|
+
from mm_eth.cli.cmd import balance_cmd, balances_cmd, deploy_cmd, node_cmd, solc_cmd, transfer_cmd
|
|
11
|
+
from mm_eth.cli.cmd.balances_cmd import BalancesCmdParams
|
|
12
|
+
from mm_eth.cli.cmd.deploy_cmd import DeployCmdParams
|
|
13
|
+
from mm_eth.cli.cmd.transfer_cmd import TransferCmdParams
|
|
14
|
+
from mm_eth.cli.cmd.wallet import mnemonic_cmd, private_key_cmd
|
|
31
15
|
|
|
32
16
|
app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
|
|
33
17
|
|
|
@@ -36,50 +20,6 @@ app.add_typer(wallet_app, name="wallet")
|
|
|
36
20
|
app.add_typer(wallet_app, name="w", hidden=True)
|
|
37
21
|
|
|
38
22
|
|
|
39
|
-
class ConfigExample(str, Enum):
|
|
40
|
-
TRANSFER = "transfer"
|
|
41
|
-
BALANCES = "balances"
|
|
42
|
-
CALL_CONTRACT = "call-contract"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@app.command(name="balance", help="Gen account balance")
|
|
46
|
-
def balance_command(
|
|
47
|
-
wallet_address: Annotated[str, typer.Argument()],
|
|
48
|
-
token_address: Annotated[str | None, typer.Option("--token", "-t")] = None,
|
|
49
|
-
rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL")] = "", # nosec
|
|
50
|
-
wei: bool = typer.Option(False, "--wei", "-w", help="Print balances in wei units"),
|
|
51
|
-
print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
|
|
52
|
-
) -> None:
|
|
53
|
-
balance_cmd.run(rpc_url, wallet_address, token_address, wei, print_format)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@app.command(name="balances", help="Print base and ERC20 token balances")
|
|
57
|
-
def balances_command(
|
|
58
|
-
config_path: Path,
|
|
59
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
60
|
-
nonce: bool = typer.Option(False, "--nonce", "-n", help="Print nonce also"),
|
|
61
|
-
wei: bool = typer.Option(False, "--wei", "-w", help="Show balances in WEI"),
|
|
62
|
-
) -> None:
|
|
63
|
-
balances_cmd.run(BalancesCmdParams(config_path=config_path, print_config=print_config, wei=wei, show_nonce=nonce))
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@app.command(name="token", help="Get token info")
|
|
67
|
-
def token_command(
|
|
68
|
-
token_address: Annotated[str, typer.Argument()],
|
|
69
|
-
rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL")] = "",
|
|
70
|
-
) -> None:
|
|
71
|
-
token_cmd.run(rpc_url, token_address)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
@app.command(name="node", help="Check RPC url")
|
|
75
|
-
def node_command(
|
|
76
|
-
urls: Annotated[list[str], typer.Argument()],
|
|
77
|
-
proxy: Annotated[str | None, typer.Option("--proxy", "-p", help="Proxy")] = None,
|
|
78
|
-
print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.TABLE,
|
|
79
|
-
) -> None:
|
|
80
|
-
node_cmd.run(urls, proxy, print_format)
|
|
81
|
-
|
|
82
|
-
|
|
83
23
|
@wallet_app.command(name="mnemonic", help="Generate eth accounts based on a mnemonic")
|
|
84
24
|
def mnemonic_command( # nosec
|
|
85
25
|
mnemonic: Annotated[str, typer.Option("--mnemonic", "-m")] = "",
|
|
@@ -106,47 +46,53 @@ def private_key_command(private_key: str) -> None:
|
|
|
106
46
|
private_key_cmd.run(private_key)
|
|
107
47
|
|
|
108
48
|
|
|
109
|
-
@app.command(name="
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
49
|
+
@app.command(name="node", help="Check RPC url")
|
|
50
|
+
def node_command(
|
|
51
|
+
urls: Annotated[list[str], typer.Argument()],
|
|
52
|
+
proxy: Annotated[str | None, typer.Option("--proxy", "-p", help="Proxy")] = None,
|
|
53
|
+
print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.TABLE,
|
|
54
|
+
) -> None:
|
|
55
|
+
asyncio.run(node_cmd.run(urls, proxy, print_format))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@app.command(name="balance", help="Gen account balance")
|
|
59
|
+
def balance_command(
|
|
60
|
+
wallet_address: Annotated[str, typer.Argument()],
|
|
61
|
+
token_address: Annotated[str | None, typer.Option("--token", "-t")] = None,
|
|
62
|
+
rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="MM_ETH_RPC_URL")] = "", # nosec
|
|
63
|
+
wei: bool = typer.Option(False, "--wei", "-w", help="Print balances in wei units"),
|
|
113
64
|
print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
|
|
114
65
|
) -> None:
|
|
115
|
-
|
|
66
|
+
asyncio.run(balance_cmd.run(rpc_url, wallet_address, token_address, wei, print_format))
|
|
116
67
|
|
|
117
68
|
|
|
118
|
-
@app.command(name="
|
|
119
|
-
def
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
vault_token: Annotated[
|
|
125
|
-
str,
|
|
126
|
-
typer.Option(..., "--token", "-t", prompt=True, hide_input=True, prompt_required=False, help="A vault token"),
|
|
127
|
-
],
|
|
128
|
-
keys_file: Annotated[Path, typer.Option(..., "--file", "-f", help="Path to a file with private keys")],
|
|
69
|
+
@app.command(name="balances", help="Print base and ERC20 token balances")
|
|
70
|
+
def balances_command(
|
|
71
|
+
config_path: Path,
|
|
72
|
+
print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
73
|
+
nonce: bool = typer.Option(False, "--nonce", "-n", help="Print nonce also"),
|
|
74
|
+
wei: bool = typer.Option(False, "--wei", "-w", help="Show balances in WEI"),
|
|
129
75
|
) -> None:
|
|
130
|
-
|
|
76
|
+
asyncio.run(
|
|
77
|
+
balances_cmd.run(BalancesCmdParams(config_path=config_path, print_config=print_config, wei=wei, show_nonce=nonce))
|
|
78
|
+
)
|
|
131
79
|
|
|
132
80
|
|
|
133
|
-
@app.command(name="
|
|
134
|
-
def
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
hex2dec: Annotated[bool, typer.Option("--hex2dec", "-d", help="Print result in decimal value")] = False,
|
|
81
|
+
@app.command(name="solc", help="Compile a solidity file")
|
|
82
|
+
def solc_command(
|
|
83
|
+
contract_path: Path,
|
|
84
|
+
tmp_dir: Path = Path("/tmp"), # noqa: S108 # nosec
|
|
85
|
+
print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
|
|
139
86
|
) -> None:
|
|
140
|
-
|
|
87
|
+
solc_cmd.run(contract_path, tmp_dir, print_format)
|
|
141
88
|
|
|
142
89
|
|
|
143
|
-
@app.command(name="
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
get_receipt: Annotated[bool, typer.Option("--receipt", "-r", help="Get receipt")] = False,
|
|
90
|
+
@app.command(name="deploy", help="Deploy a smart contract onchain")
|
|
91
|
+
def deploy_command(
|
|
92
|
+
config_path: Path,
|
|
93
|
+
print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
148
94
|
) -> None:
|
|
149
|
-
|
|
95
|
+
asyncio.run(deploy_cmd.run(DeployCmdParams(config_path=config_path, print_config=print_config)))
|
|
150
96
|
|
|
151
97
|
|
|
152
98
|
@app.command(
|
|
@@ -161,74 +107,24 @@ def transfer_command(
|
|
|
161
107
|
skip_receipt: bool = typer.Option(False, "--skip-receipt", help="Don't wait for a tx receipt"),
|
|
162
108
|
debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
|
|
163
109
|
) -> None:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
110
|
+
asyncio.run(
|
|
111
|
+
transfer_cmd.run(
|
|
112
|
+
TransferCmdParams(
|
|
113
|
+
config_path=config_path,
|
|
114
|
+
print_balances=print_balances,
|
|
115
|
+
print_transfers=print_transfers,
|
|
116
|
+
print_config=print_config,
|
|
117
|
+
debug=debug,
|
|
118
|
+
skip_receipt=skip_receipt,
|
|
119
|
+
emulate=emulate,
|
|
120
|
+
)
|
|
173
121
|
)
|
|
174
122
|
)
|
|
175
123
|
|
|
176
124
|
|
|
177
|
-
# @app.command(name="send-contract", help="Send transactions to a contract")
|
|
178
|
-
# def send_contract_command(
|
|
179
|
-
# config_path: Path,
|
|
180
|
-
# print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
|
|
181
|
-
# print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
182
|
-
# emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
|
|
183
|
-
# no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
|
|
184
|
-
# debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
|
|
185
|
-
# ) -> None:
|
|
186
|
-
# send_contract_cmd.run(
|
|
187
|
-
# SendContractCmdParams(
|
|
188
|
-
# config_path=config_path,
|
|
189
|
-
# print_balances=print_balances,
|
|
190
|
-
# print_config=print_config,
|
|
191
|
-
# debug=debug,
|
|
192
|
-
# no_receipt=no_receipt,
|
|
193
|
-
# emulate=emulate,
|
|
194
|
-
# )
|
|
195
|
-
# )
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
@app.command(name="call-contract", help="Call a method on a contract")
|
|
199
|
-
def call_contract_command(
|
|
200
|
-
config_path: Path,
|
|
201
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
202
|
-
) -> None:
|
|
203
|
-
call_contract_cmd.run(CallContractCmdParams(config_path=config_path, print_config=print_config))
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
@app.command(name="deploy", help="Deploy a smart contract onchain")
|
|
207
|
-
def deploy_command(
|
|
208
|
-
config_path: Path,
|
|
209
|
-
print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
210
|
-
) -> None:
|
|
211
|
-
deploy_cmd.run(DeployCmdParams(config_path=config_path, print_config=print_config))
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
@app.command(name="example", help="Displays an example configuration for a command")
|
|
215
|
-
def example_command(command: Annotated[ConfigExample, typer.Argument()]) -> None:
|
|
216
|
-
example_cmd.run(command)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
@app.command(name="encode-input-data", help="Encode input data by a function signature")
|
|
220
|
-
def encode_input_data(
|
|
221
|
-
function_signature: str = typer.Argument(help="Function signature, for example: transfer(address, uint256)"),
|
|
222
|
-
args_str: str = typer.Argument(
|
|
223
|
-
help="""Function arguments, as an array string. For example: '["0xA659FB44eB5d4bFaC1074Cb426b1b11D58D28308", 123]' """,
|
|
224
|
-
),
|
|
225
|
-
) -> None:
|
|
226
|
-
encode_input_data_cmd.run(function_signature, args_str)
|
|
227
|
-
|
|
228
|
-
|
|
229
125
|
def version_callback(value: bool) -> None:
|
|
230
126
|
if value:
|
|
231
|
-
print_plain(f"mm-eth: {
|
|
127
|
+
print_plain(f"mm-eth: {importlib.metadata.version('mm-eth')}")
|
|
232
128
|
raise typer.Exit
|
|
233
129
|
|
|
234
130
|
|
mm_eth/cli/cli_utils.py
CHANGED
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
import importlib.metadata
|
|
2
|
-
import time
|
|
3
2
|
from pathlib import Path
|
|
4
|
-
from typing import Literal
|
|
5
3
|
|
|
6
|
-
from
|
|
7
|
-
from mm_std import BaseConfig, print_json
|
|
4
|
+
from mm_std import fatal
|
|
8
5
|
from pydantic import BaseModel
|
|
6
|
+
from rich.table import Table
|
|
9
7
|
|
|
10
8
|
from mm_eth import rpc
|
|
11
9
|
|
|
12
10
|
|
|
13
|
-
def get_version() -> str:
|
|
14
|
-
return importlib.metadata.version("mm-eth")
|
|
15
|
-
|
|
16
|
-
|
|
17
11
|
def public_rpc_url(url: str | None) -> str:
|
|
18
12
|
if not url or url == "1":
|
|
19
|
-
return "https://ethereum.publicnode.com"
|
|
13
|
+
return "https://ethereum-rpc.publicnode.com"
|
|
20
14
|
if url.startswith(("http://", "https://", "ws://", "wss://")):
|
|
21
15
|
return url
|
|
22
16
|
|
|
23
17
|
match url.lower():
|
|
24
18
|
case "mainnet" | "1":
|
|
25
|
-
return "https://ethereum.publicnode.com"
|
|
19
|
+
return "https://ethereum-rpc.publicnode.com"
|
|
26
20
|
case "sepolia" | "11155111":
|
|
27
21
|
return "https://ethereum-sepolia-rpc.publicnode.com"
|
|
28
22
|
case "opbnb" | "204":
|
|
@@ -40,23 +34,16 @@ class BaseConfigParams(BaseModel):
|
|
|
40
34
|
print_config: bool
|
|
41
35
|
|
|
42
36
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
print_json(data)
|
|
37
|
+
async def check_nodes_for_chain_id(nodes: list[str], chain_id: int) -> None:
|
|
38
|
+
for node in nodes:
|
|
39
|
+
res = (await rpc.eth_chain_id(node)).unwrap_or_exit("can't get chain_id")
|
|
40
|
+
if res != chain_id:
|
|
41
|
+
fatal(f"node {node} has a wrong chain_id: {res}")
|
|
49
42
|
|
|
50
43
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
count = 0
|
|
54
|
-
while True:
|
|
55
|
-
res = rpc.get_tx_status(nodes, tx_hash, proxies=proxies, attempts=5)
|
|
56
|
-
if res.is_ok():
|
|
57
|
-
return "OK" if res.ok == 1 else "FAIL"
|
|
44
|
+
def add_table_raw(table: Table, *row: object) -> None:
|
|
45
|
+
table.add_row(*[str(cell) for cell in row])
|
|
58
46
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return "TIMEOUT"
|
|
47
|
+
|
|
48
|
+
def get_version() -> str:
|
|
49
|
+
return importlib.metadata.version("mm-eth")
|
mm_eth/cli/cmd/balance_cmd.py
CHANGED
|
@@ -1,45 +1,44 @@
|
|
|
1
|
+
import eth_utils
|
|
1
2
|
from mm_std import PrintFormat, print_json, print_plain
|
|
2
3
|
|
|
3
|
-
from mm_eth import
|
|
4
|
-
from mm_eth.cli
|
|
5
|
-
from mm_eth.utils import from_wei_str
|
|
4
|
+
from mm_eth import converters, rpc
|
|
5
|
+
from mm_eth.cli import cli_utils
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def run(rpc_url: str, wallet_address: str, token_address: str | None, wei: bool, print_format: PrintFormat) -> None:
|
|
8
|
+
async def run(rpc_url: str, wallet_address: str, token_address: str | None, wei: bool, print_format: PrintFormat) -> None:
|
|
9
9
|
result: dict[str, object] = {}
|
|
10
|
-
rpc_url = public_rpc_url(rpc_url)
|
|
10
|
+
rpc_url = cli_utils.public_rpc_url(rpc_url)
|
|
11
11
|
|
|
12
12
|
# nonce
|
|
13
|
-
result["nonce"] = rpc.eth_get_transaction_count(rpc_url, wallet_address).
|
|
13
|
+
result["nonce"] = (await rpc.eth_get_transaction_count(rpc_url, wallet_address)).value_or_error()
|
|
14
14
|
if print_format == PrintFormat.PLAIN:
|
|
15
15
|
print_plain(f"nonce: {result['nonce']}")
|
|
16
16
|
|
|
17
17
|
# eth balance
|
|
18
18
|
result["eth_balance"] = (
|
|
19
|
-
rpc.eth_get_balance(rpc_url, wallet_address)
|
|
19
|
+
(await rpc.eth_get_balance(rpc_url, wallet_address))
|
|
20
|
+
.map(lambda value: value if wei else eth_utils.from_wei(value, "ether"))
|
|
21
|
+
.value_or_error()
|
|
20
22
|
)
|
|
21
23
|
if print_format == PrintFormat.PLAIN:
|
|
22
24
|
print_plain(f"eth_balance: {result['eth_balance']}")
|
|
23
25
|
|
|
24
26
|
if token_address:
|
|
25
27
|
# token decimal
|
|
26
|
-
result["token_decimal"] =
|
|
28
|
+
result["token_decimal"] = (await rpc.erc20_decimals(rpc_url, token_address)).value_or_error()
|
|
27
29
|
if print_format == PrintFormat.PLAIN:
|
|
28
30
|
print_plain(f"token_decimal: {result['token_decimal']}")
|
|
29
31
|
|
|
30
32
|
# token symbol
|
|
31
|
-
result["token_symbol"] =
|
|
33
|
+
result["token_symbol"] = (await rpc.erc20_symbol(rpc_url, token_address)).value_or_error()
|
|
32
34
|
if print_format == PrintFormat.PLAIN:
|
|
33
35
|
print_plain(f"token_symbol: {result['token_symbol']}")
|
|
34
36
|
|
|
35
37
|
# token balance
|
|
36
|
-
result["token_balance"] = (
|
|
37
|
-
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
.ok_or_err()
|
|
42
|
-
)
|
|
38
|
+
result["token_balance"] = (await rpc.erc20_balance(rpc_url, token_address, wallet_address)).value_or_error()
|
|
39
|
+
if isinstance(result["token_balance"], int) and not wei and isinstance(result["token_decimal"], int):
|
|
40
|
+
result["token_balance"] = converters.from_wei(result["token_balance"], "t", decimals=result["token_decimal"])
|
|
41
|
+
|
|
43
42
|
if print_format == PrintFormat.PLAIN:
|
|
44
43
|
print_plain(f"token_balance: {result['token_balance']}")
|
|
45
44
|
|