mm-eth 0.5.16__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.16.dist-info → mm_eth-0.6.0.dist-info}/METADATA +3 -3
- 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 -170
- mm_eth/vault.py +0 -38
- mm_eth-0.5.16.dist-info/RECORD +0 -49
- {mm_eth-0.5.16.dist-info → mm_eth-0.6.0.dist-info}/WHEEL +0 -0
- {mm_eth-0.5.16.dist-info → mm_eth-0.6.0.dist-info}/entry_points.txt +0 -0
mm_eth/solc.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import random
|
|
2
|
+
import re
|
|
2
3
|
import shutil
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
|
-
from mm_std import
|
|
7
|
+
from mm_std import Result, run_command
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
@dataclass
|
|
@@ -12,22 +13,34 @@ class SolcResult:
|
|
|
12
13
|
abi: str
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
def solc(contract_name: str, contract_path:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
def solc(contract_name: str, contract_path: Path, tmp_dir: Path) -> Result[SolcResult]:
|
|
17
|
+
# Sanitize contract name to avoid unsafe characters in directory name
|
|
18
|
+
safe_name = re.sub(r"[^a-zA-Z0-9_\-]", "_", contract_name)
|
|
19
|
+
|
|
20
|
+
# Expand ~ in paths if present
|
|
21
|
+
contract_path = contract_path.expanduser().resolve()
|
|
22
|
+
tmp_dir = tmp_dir.expanduser().resolve()
|
|
23
|
+
|
|
24
|
+
work_dir = tmp_dir / f"solc_{safe_name}_{random.randint(0, 100_000_000)}"
|
|
25
|
+
abi_path = work_dir / f"{contract_name}.abi"
|
|
26
|
+
bin_path = work_dir / f"{contract_name}.bin"
|
|
27
|
+
|
|
28
|
+
work_dir_created = False
|
|
23
29
|
try:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
work_dir.mkdir(parents=True, exist_ok=False)
|
|
31
|
+
work_dir_created = True
|
|
32
|
+
|
|
33
|
+
cmd = f"solc -o '{work_dir}' --abi --bin --optimize '{contract_path}'"
|
|
34
|
+
result = run_command(cmd)
|
|
35
|
+
if result.code != 0:
|
|
36
|
+
return Result.err(f"solc error: {result.stderr}")
|
|
37
|
+
|
|
38
|
+
abi = abi_path.read_text()
|
|
39
|
+
bin_ = bin_path.read_text()
|
|
40
|
+
|
|
41
|
+
return Result.ok(SolcResult(bin=bin_, abi=abi))
|
|
30
42
|
except Exception as e:
|
|
31
|
-
return
|
|
43
|
+
return Result.err(e)
|
|
32
44
|
finally:
|
|
33
|
-
|
|
45
|
+
if work_dir_created:
|
|
46
|
+
shutil.rmtree(work_dir, ignore_errors=True)
|
mm_eth/tx.py
CHANGED
|
@@ -2,15 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
+
import eth_utils
|
|
5
6
|
import rlp
|
|
6
7
|
from eth_account import Account
|
|
8
|
+
from eth_typing import HexStr
|
|
7
9
|
from eth_utils import keccak, to_hex
|
|
8
10
|
from pydantic import BaseModel
|
|
9
11
|
from rlp.sedes import Binary, big_endian_int, binary
|
|
10
12
|
from web3 import Web3
|
|
11
|
-
from web3.auto import w3
|
|
12
|
-
|
|
13
|
-
from mm_eth.utils import hex_to_bytes
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class SignedTx(BaseModel):
|
|
@@ -45,9 +44,9 @@ class RPLTransaction(rlp.Serializable): # type: ignore[misc]
|
|
|
45
44
|
to: str | None = None,
|
|
46
45
|
) -> RPLTransaction:
|
|
47
46
|
if to:
|
|
48
|
-
to =
|
|
47
|
+
to = eth_utils.to_bytes(hexstr=HexStr(to)) # type:ignore[assignment]
|
|
49
48
|
if data:
|
|
50
|
-
data =
|
|
49
|
+
data = eth_utils.to_bytes(hexstr=HexStr(data)) # type:ignore[assignment]
|
|
51
50
|
if not value:
|
|
52
51
|
value = 0
|
|
53
52
|
return RPLTransaction(nonce, gas_price, gas, to, value, data, v, int(r, 16), int(s, 16))
|
|
@@ -103,7 +102,7 @@ def sign_legacy_tx(
|
|
|
103
102
|
if data:
|
|
104
103
|
tx["data"] = data
|
|
105
104
|
|
|
106
|
-
signed =
|
|
105
|
+
signed = Account.sign_transaction(tx, private_key)
|
|
107
106
|
return SignedTx(tx_hash=to_hex(signed.hash), raw_tx=to_hex(signed.raw_transaction))
|
|
108
107
|
|
|
109
108
|
|
|
@@ -139,9 +138,9 @@ def sign_tx(
|
|
|
139
138
|
|
|
140
139
|
|
|
141
140
|
def decode_raw_tx(raw_tx: str) -> DecodedRawTx:
|
|
142
|
-
tx: Any = rlp.decode(
|
|
143
|
-
tx_hash = Web3.to_hex(keccak(
|
|
144
|
-
from_ =
|
|
141
|
+
tx: Any = rlp.decode(eth_utils.to_bytes(hexstr=HexStr(raw_tx)), RPLTransaction)
|
|
142
|
+
tx_hash = Web3.to_hex(keccak(eth_utils.to_bytes(hexstr=HexStr(raw_tx))))
|
|
143
|
+
from_ = Account.recover_transaction(raw_tx)
|
|
145
144
|
to = Web3.to_checksum_address(tx.to) if tx.to else None
|
|
146
145
|
data = Web3.to_hex(tx.data)
|
|
147
146
|
r = hex(tx.r)
|
mm_eth/utils.py
CHANGED
|
@@ -1,216 +1,3 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from decimal import Decimal, localcontext
|
|
3
|
-
from typing import Any, cast
|
|
4
|
-
|
|
5
|
-
import aiohttp
|
|
6
|
-
import eth_utils
|
|
7
|
-
import pydash
|
|
8
|
-
from aiohttp_socks import ProxyConnector
|
|
9
|
-
from eth_typing import HexStr
|
|
10
|
-
from hexbytes import HexBytes
|
|
11
|
-
from mm_std import Err, Ok, Result, number_with_separator
|
|
12
|
-
from pydantic import BaseModel
|
|
13
|
-
from web3 import AsyncWeb3, Web3
|
|
14
|
-
from web3.types import Wei
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def parse_addresses(data: str) -> list[str]:
|
|
18
|
-
result = []
|
|
19
|
-
for word in data.lower().split():
|
|
20
|
-
if len(word) == 42 and re.match("0x[a-f0-9]{40}", word):
|
|
21
|
-
result.append(word) # noqa: PERF401
|
|
22
|
-
return pydash.uniq(result)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def to_token_wei(value: str | int, decimals: int) -> int:
|
|
26
|
-
if isinstance(value, int):
|
|
27
|
-
return value
|
|
28
|
-
if isinstance(value, str):
|
|
29
|
-
value = value.lower().replace(" ", "").strip()
|
|
30
|
-
if value.endswith("t"):
|
|
31
|
-
value = value.replace("t", "")
|
|
32
|
-
return int(Decimal(value) * 10**decimals)
|
|
33
|
-
if value.isdigit():
|
|
34
|
-
return int(value)
|
|
35
|
-
|
|
36
|
-
raise ValueError("wrong value" + value)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def to_wei(value: str | int | Decimal, decimals: int | None = None) -> Wei:
|
|
40
|
-
if isinstance(value, int):
|
|
41
|
-
return Wei(value)
|
|
42
|
-
if isinstance(value, Decimal):
|
|
43
|
-
if value != value.to_integral_value():
|
|
44
|
-
raise ValueError(f"value must be integral number: {value}")
|
|
45
|
-
return Wei(int(value))
|
|
46
|
-
if isinstance(value, str):
|
|
47
|
-
value = value.lower().replace(" ", "").strip()
|
|
48
|
-
if value.endswith("navax"): # https://snowtrace.io/unitconverter
|
|
49
|
-
value = value.replace("navax", "")
|
|
50
|
-
return Wei(int(Decimal(value) * 10**9))
|
|
51
|
-
if value.endswith("gwei"):
|
|
52
|
-
value = value.replace("gwei", "")
|
|
53
|
-
return Wei(int(Decimal(value) * 1000000000))
|
|
54
|
-
if value.endswith("ether"):
|
|
55
|
-
value = value.replace("ether", "")
|
|
56
|
-
return Wei(int(Decimal(value) * 1000000000000000000))
|
|
57
|
-
if value.endswith("eth"):
|
|
58
|
-
value = value.replace("eth", "")
|
|
59
|
-
return Wei(int(Decimal(value) * 1000000000000000000))
|
|
60
|
-
if value.endswith("t"):
|
|
61
|
-
if decimals is None:
|
|
62
|
-
raise ValueError("t without decimals")
|
|
63
|
-
value = value.removesuffix("t")
|
|
64
|
-
return Wei(int(Decimal(value) * 10**decimals))
|
|
65
|
-
if value.isdigit():
|
|
66
|
-
return Wei(int(value))
|
|
67
|
-
raise ValueError("wrong value " + value)
|
|
68
|
-
|
|
69
|
-
raise ValueError(f"value has a wrong type: {type(value)}")
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def from_wei(
|
|
73
|
-
value: int,
|
|
74
|
-
unit: str,
|
|
75
|
-
round_ndigits: int | None = None,
|
|
76
|
-
decimals: int | None = None,
|
|
77
|
-
) -> Decimal:
|
|
78
|
-
if value == 0:
|
|
79
|
-
return Decimal(0)
|
|
80
|
-
|
|
81
|
-
is_negative = value < 0
|
|
82
|
-
if unit.lower() == "eth":
|
|
83
|
-
unit = "ether"
|
|
84
|
-
|
|
85
|
-
if unit.lower() == "t":
|
|
86
|
-
if decimals is None:
|
|
87
|
-
raise ValueError("t without decimals")
|
|
88
|
-
with localcontext() as ctx:
|
|
89
|
-
ctx.prec = 999
|
|
90
|
-
res = Decimal(value=abs(value), context=ctx) / Decimal(10**decimals)
|
|
91
|
-
else:
|
|
92
|
-
res = cast(Decimal, eth_utils.from_wei(abs(value), unit))
|
|
93
|
-
if round_ndigits is not None:
|
|
94
|
-
res = round(res, ndigits=round_ndigits)
|
|
95
|
-
return -1 * res if is_negative else res
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def from_wei_str(
|
|
99
|
-
value: int,
|
|
100
|
-
unit: str,
|
|
101
|
-
round_ndigits: int | None = None,
|
|
102
|
-
print_unit_name: bool = True,
|
|
103
|
-
decimals: int | None = None,
|
|
104
|
-
) -> str:
|
|
105
|
-
res = format(from_wei(value, unit, round_ndigits, decimals=decimals), "f")
|
|
106
|
-
if unit == "ether":
|
|
107
|
-
unit = "eth"
|
|
108
|
-
if print_unit_name:
|
|
109
|
-
res += unit
|
|
110
|
-
return res
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def from_token_wei_str(value: int, decimals: int, symbol: str = "", round_ndigits: int | None = None) -> str:
|
|
114
|
-
res = value / 10**decimals
|
|
115
|
-
if round_ndigits is not None:
|
|
116
|
-
res = round(res, ndigits=round_ndigits)
|
|
117
|
-
if symbol:
|
|
118
|
-
res = f"{res} {symbol}"
|
|
119
|
-
return str(res)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def to_wei_token(value: str | int | Decimal, symbol: str, decimals: int) -> int:
|
|
123
|
-
if isinstance(value, int):
|
|
124
|
-
return value
|
|
125
|
-
if isinstance(value, Decimal):
|
|
126
|
-
if value != value.to_integral_value():
|
|
127
|
-
raise ValueError(f"value must be integral number: {value}")
|
|
128
|
-
return int(value)
|
|
129
|
-
if isinstance(value, str):
|
|
130
|
-
value = value.lower().replace(" ", "").strip()
|
|
131
|
-
if value.isdigit():
|
|
132
|
-
return int(value)
|
|
133
|
-
try:
|
|
134
|
-
return int(Decimal(value.replace(symbol.lower(), "").strip()) * (10**decimals))
|
|
135
|
-
except Exception as e:
|
|
136
|
-
raise ValueError from e
|
|
137
|
-
else:
|
|
138
|
-
raise TypeError(f"value has a wrong type: {type(value)}")
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def to_checksum_address(address: str) -> str:
|
|
142
|
-
return Web3.to_checksum_address(address)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def hex_to_bytes(data: str) -> bytes:
|
|
146
|
-
return eth_utils.to_bytes(hexstr=HexStr(data))
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def get_chain_name(chain_id: int | str) -> str:
|
|
150
|
-
chain_id = str(chain_id)
|
|
151
|
-
if chain_id == "1":
|
|
152
|
-
return "mainnet"
|
|
153
|
-
if chain_id == "3":
|
|
154
|
-
return "ropsten"
|
|
155
|
-
if chain_id == "5":
|
|
156
|
-
return "goerli"
|
|
157
|
-
return chain_id
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def to_human_readable_tx(tx: dict[str, Any] | BaseModel) -> dict[str, object]:
|
|
161
|
-
if isinstance(tx, BaseModel):
|
|
162
|
-
tx = tx.model_dump()
|
|
163
|
-
tx["human_readable"] = {}
|
|
164
|
-
tx["human_readable"]["gas_price"] = str(tx["gas_price"] / 10**9) + " gwei"
|
|
165
|
-
tx["human_readable"]["value"] = str(tx["value"] / 10**18) + " ether"
|
|
166
|
-
tx["human_readable"]["gas"] = number_with_separator(tx["gas"])
|
|
167
|
-
if tx.get("chain_id") is not None:
|
|
168
|
-
tx["human_readable"]["chain_id"] = get_chain_name(tx["chain_id"])
|
|
169
|
-
|
|
170
|
-
return tx
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def truncate_hex_str(hex_str: str, digits: int = 4, replace_str: str = "...") -> str:
|
|
174
|
-
if not hex_str.startswith("0x") and not hex_str.startswith("0X"):
|
|
175
|
-
raise ValueError("truncate_hex_str: hex_str must start with 0x")
|
|
176
|
-
if digits <= 0:
|
|
177
|
-
raise ValueError("truncate_hex_str: digits must be more than zero")
|
|
178
|
-
hex_str = hex_str.removeprefix("0x").removeprefix("0X")
|
|
179
|
-
if digits * 2 >= len(hex_str):
|
|
180
|
-
raise ValueError("truncate_hex_str: digits is too large")
|
|
181
|
-
return "0x" + hex_str[:digits] + replace_str + hex_str[-1 * digits :]
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def log_topic_to_address(topic: HexBytes | str) -> str:
|
|
185
|
-
result = topic.hex()[-40:] if isinstance(topic, HexBytes) else topic[-40:]
|
|
186
|
-
if not result.startswith("0x"):
|
|
187
|
-
result = f"0x{result}"
|
|
188
|
-
return result
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def get_w3(rpc_url: str, timeout: float | None = None, proxy: str | None = None) -> Web3:
|
|
192
|
-
request_kwargs: dict[str, object] = {"timeout": timeout}
|
|
193
|
-
if proxy:
|
|
194
|
-
request_kwargs["proxies"] = {"http": proxy, "https": proxy}
|
|
195
|
-
return Web3(Web3.HTTPProvider(rpc_url, request_kwargs=request_kwargs))
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
async def get_async_w3(rpc_url: str, timeout: float | None = None, proxy: str | None = None) -> AsyncWeb3:
|
|
199
|
-
# TODO: Don't use async w3. AsyncHTTPProvider uses threads
|
|
200
|
-
# check its constructor: self._request_session_manager = HTTPSessionManager()
|
|
201
|
-
request_kwargs: dict[str, object] = {"timeout": timeout}
|
|
202
|
-
if proxy and proxy.startswith("http"):
|
|
203
|
-
request_kwargs["proxy"] = proxy
|
|
204
|
-
provider = AsyncWeb3.AsyncHTTPProvider(rpc_url, request_kwargs=request_kwargs, exception_retry_configuration=None)
|
|
205
|
-
w3 = AsyncWeb3(provider)
|
|
206
|
-
|
|
207
|
-
if proxy and proxy.startswith("socks"):
|
|
208
|
-
session = aiohttp.ClientSession(connector=ProxyConnector.from_url(proxy))
|
|
209
|
-
await provider.cache_async_session(session)
|
|
210
|
-
|
|
211
|
-
return w3
|
|
212
|
-
|
|
213
|
-
|
|
214
1
|
def name_network(chain_id: int) -> str:
|
|
215
2
|
match chain_id:
|
|
216
3
|
case 1:
|
|
@@ -235,14 +22,3 @@ def name_network(chain_id: int) -> str:
|
|
|
235
22
|
return "Arbitrum Goerli"
|
|
236
23
|
case _:
|
|
237
24
|
return ""
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def hex_str_to_int(value: str) -> Result[int]:
|
|
241
|
-
try:
|
|
242
|
-
return Ok(int(value, 16))
|
|
243
|
-
except Exception:
|
|
244
|
-
return Err(f"can't convert to int: {value}")
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def to_hex(data: bytes | int | bool) -> str:
|
|
248
|
-
return Web3.to_hex(data)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mm-eth
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Requires-Python: >=3.12
|
|
5
5
|
Requires-Dist: aiohttp-socks~=0.10.1
|
|
6
|
-
Requires-Dist: mm-crypto-utils>=0.
|
|
7
|
-
Requires-Dist: mm-std~=0.
|
|
6
|
+
Requires-Dist: mm-crypto-utils>=0.3.5
|
|
7
|
+
Requires-Dist: mm-std~=0.4.10
|
|
8
8
|
Requires-Dist: typer>=0.15.2
|
|
9
9
|
Requires-Dist: web3~=7.10.0
|
|
10
10
|
Requires-Dist: websocket-client~=1.8.0
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
mm_eth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
mm_eth/abi.py,sha256=gsIsaq5V8QGdYY424c_t_uVRfg0of7Gk9h0LcW3tkjU,4806
|
|
3
|
+
mm_eth/account.py,sha256=syA89ihOBTA2FiDK3o55oLBVEgPN1rQT_3bUQN05vuk,3044
|
|
4
|
+
mm_eth/anvil.py,sha256=NY1TxYnamfm_47HqCbgUCRdQxPgmAcJ-9YkK3NZ0310,1631
|
|
5
|
+
mm_eth/converters.py,sha256=smL3Bsky1pYEre2kPhsb4arXoQC_u80P5ilU9NRvr44,2043
|
|
6
|
+
mm_eth/deploy.py,sha256=SB3ruY808_5UnG8kHR34uVP66P3zOWZu0ImKD7UUv2s,691
|
|
7
|
+
mm_eth/erc20.py,sha256=Pxs_w95flqEUF4pJMoaHTfvud8x5Fb2UwU7iwMjdGCw,1143
|
|
8
|
+
mm_eth/retry.py,sha256=93MzazJnDZraLavtkBSPoUT-ZZWsLYq4HMf42OlLlBw,4799
|
|
9
|
+
mm_eth/rpc.py,sha256=3LE7ZwefYqwfb1RHcAhdNK99--0r1SW5Rfr9Wg9h50Q,9816
|
|
10
|
+
mm_eth/solc.py,sha256=2FGCJpbkCwIs8hGc8APpvC62gyAXGLeyQMrn4f9Bnow,1348
|
|
11
|
+
mm_eth/tx.py,sha256=zf5fieuKB-CTOiNNjz3siXU7yga7tlh0vkZTDAHvhnQ,4102
|
|
12
|
+
mm_eth/utils.py,sha256=TLyapZZ1lp_kW3vdkHmgR8gUtIQ1aEy_0GScxB6v5IQ,659
|
|
13
|
+
mm_eth/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
mm_eth/cli/calcs.py,sha256=N063Qw85iBNFbDtIjOSYJKHL0vU6wyfRfYAlI9TKeK0,496
|
|
15
|
+
mm_eth/cli/cli.py,sha256=Qac0HizKAyF1wocVvNK2578ntB2JT9kLHr1U5jfQj6E,5580
|
|
16
|
+
mm_eth/cli/cli_utils.py,sha256=jYDuSo-osvKPkd_OJFCHZDfGlFy3LKs-MVKHEnkCPdM,1416
|
|
17
|
+
mm_eth/cli/rpc_helpers.py,sha256=5fxiwFb16V0udY2MOb3bTpUrkf3Lhn-mMXFrdbmAKo4,1669
|
|
18
|
+
mm_eth/cli/validators.py,sha256=74AxIzryQ-D3f5xJYrOnh4AMFugTpsYhUJ_pzhHd2ek,1675
|
|
19
|
+
mm_eth/cli/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
mm_eth/cli/cmd/balance_cmd.py,sha256=hujMv4Fvt4ZMZ3iPOZKdI2u2iN-ClYBmfMn_2Y4lRew,1946
|
|
21
|
+
mm_eth/cli/cmd/balances_cmd.py,sha256=VGCqcPCKPFF2O3WNlryg0nFnCkkKRAaubvpbtH0XTV0,4230
|
|
22
|
+
mm_eth/cli/cmd/deploy_cmd.py,sha256=IItdoOgYt1Q6SpSTxlOecxWikTn3b0_w1-MZkYIBKKI,1266
|
|
23
|
+
mm_eth/cli/cmd/node_cmd.py,sha256=JJSiKGG5OL9p7FuHhNeerjfes6Eo_StgLgpZEy3EUU0,2559
|
|
24
|
+
mm_eth/cli/cmd/solc_cmd.py,sha256=HDLThGAxk8MlJh_OBTsivoXYCKDlGnljRFp-BbYDlU0,714
|
|
25
|
+
mm_eth/cli/cmd/transfer_cmd.py,sha256=kKNoU_kNl2blz9ydbvhlEY1Kck0fCMv4RKwU84HyOSw,16933
|
|
26
|
+
mm_eth/cli/cmd/wallet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
mm_eth/cli/cmd/wallet/mnemonic_cmd.py,sha256=xE-5Ux9BdYsTZYBy0dMn9jupGhW4ced-AgYscy_wU_4,1007
|
|
28
|
+
mm_eth/cli/cmd/wallet/private_key_cmd.py,sha256=wuW_LvmIZiT9T4R8ir0wH1VQ_CEPjmrLgS-vzf0yv70,272
|
|
29
|
+
mm_eth-0.6.0.dist-info/METADATA,sha256=cLC3hqliuUgipT2KS3MSt4JYA-fEZX43oNaY-f2iRuc,275
|
|
30
|
+
mm_eth-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
31
|
+
mm_eth-0.6.0.dist-info/entry_points.txt,sha256=aGhpsozl8NIrkuUcX5fSgURCcDhr3ShUdeTSIrJq4oc,46
|
|
32
|
+
mm_eth-0.6.0.dist-info/RECORD,,
|
mm_eth/async_rpc.py
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from collections.abc import Sequence
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
import websockets
|
|
6
|
-
from mm_crypto_utils import Nodes, Proxies, random_node, random_proxy
|
|
7
|
-
from mm_std import Err, Ok, Result, hra
|
|
8
|
-
|
|
9
|
-
from mm_eth.utils import hex_str_to_int
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
async def rpc_call(
|
|
13
|
-
*,
|
|
14
|
-
nodes: Nodes,
|
|
15
|
-
method: str,
|
|
16
|
-
params: Sequence[object],
|
|
17
|
-
id_: int = 1,
|
|
18
|
-
timeout: float = 10,
|
|
19
|
-
proxies: Proxies = None,
|
|
20
|
-
attempts: int = 1,
|
|
21
|
-
) -> Result[Any]:
|
|
22
|
-
data = {"jsonrpc": "2.0", "method": method, "params": params, "id": id_}
|
|
23
|
-
res: Result[Any] = Err("not started yet")
|
|
24
|
-
for _ in range(attempts):
|
|
25
|
-
node = random_node(nodes)
|
|
26
|
-
res = (
|
|
27
|
-
await _http_call(node, data, timeout, random_proxy(proxies))
|
|
28
|
-
if node.startswith("http")
|
|
29
|
-
else await _ws_call(node, data, timeout)
|
|
30
|
-
)
|
|
31
|
-
if isinstance(res, Ok):
|
|
32
|
-
return res
|
|
33
|
-
return res
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
async def _http_call(node: str, data: dict[str, object], timeout: float, proxy: str | None) -> Result[Any]:
|
|
37
|
-
res = await hra(node, method="POST", proxy=proxy, timeout=timeout, params=data, json_params=True)
|
|
38
|
-
if res.is_error():
|
|
39
|
-
return res.to_err_result()
|
|
40
|
-
try:
|
|
41
|
-
err = res.json.get("error", {}).get("message", "")
|
|
42
|
-
if err:
|
|
43
|
-
return res.to_err_result(f"service_error: {err}")
|
|
44
|
-
if "result" in res.json:
|
|
45
|
-
return res.to_ok_result(res.json["result"])
|
|
46
|
-
return res.to_err_result("unknown_response")
|
|
47
|
-
except Exception as err:
|
|
48
|
-
return res.to_err_result(f"exception: {err}")
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
async def _ws_call(node: str, data: dict[str, object], timeout: float) -> Result[Any]:
|
|
52
|
-
try:
|
|
53
|
-
async with websockets.connect(node, timeout=timeout) as ws:
|
|
54
|
-
await ws.send(json.dumps(data))
|
|
55
|
-
response = json.loads(await ws.recv())
|
|
56
|
-
|
|
57
|
-
err = response.get("error", {}).get("message", "")
|
|
58
|
-
if err:
|
|
59
|
-
return Err(f"service_error: {err}")
|
|
60
|
-
if "result" in response:
|
|
61
|
-
return Ok(response["result"], response)
|
|
62
|
-
return Err(f"unknown_response: {response}")
|
|
63
|
-
except TimeoutError:
|
|
64
|
-
return Err("timeout")
|
|
65
|
-
except Exception as err:
|
|
66
|
-
return Err(f"exception: {err}")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
async def eth_block_number(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
|
|
70
|
-
return (
|
|
71
|
-
await rpc_call(
|
|
72
|
-
nodes=rpc_urls,
|
|
73
|
-
method="eth_blockNumber",
|
|
74
|
-
params=[],
|
|
75
|
-
timeout=timeout,
|
|
76
|
-
proxies=proxies,
|
|
77
|
-
attempts=attempts,
|
|
78
|
-
)
|
|
79
|
-
).and_then(hex_str_to_int)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
async def eth_get_balance(
|
|
83
|
-
rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1
|
|
84
|
-
) -> Result[int]:
|
|
85
|
-
return (
|
|
86
|
-
await rpc_call(
|
|
87
|
-
nodes=rpc_urls,
|
|
88
|
-
method="eth_getBalance",
|
|
89
|
-
params=[address, "latest"],
|
|
90
|
-
timeout=timeout,
|
|
91
|
-
proxies=proxies,
|
|
92
|
-
attempts=attempts,
|
|
93
|
-
)
|
|
94
|
-
).and_then(hex_str_to_int)
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from logging import fatal
|
|
3
|
-
|
|
4
|
-
from mm_std import BaseConfig, Err, print_plain
|
|
5
|
-
from pydantic import StrictStr
|
|
6
|
-
|
|
7
|
-
from mm_eth import abi, rpc
|
|
8
|
-
from mm_eth.cli.cli_utils import BaseConfigParams
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Config(BaseConfig):
|
|
12
|
-
contract_address: StrictStr
|
|
13
|
-
function_signature: str
|
|
14
|
-
function_args: StrictStr = "[]"
|
|
15
|
-
outputs_types: str | None = None
|
|
16
|
-
node: str
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class CallContractCmdParams(BaseConfigParams):
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def run(cli_params: CallContractCmdParams) -> None:
|
|
24
|
-
config = Config.read_toml_config_or_exit(cli_params.config_path)
|
|
25
|
-
if cli_params.print_config:
|
|
26
|
-
config.print_and_exit()
|
|
27
|
-
|
|
28
|
-
input_data = abi.encode_function_input_by_signature(
|
|
29
|
-
config.function_signature,
|
|
30
|
-
json.loads(config.function_args.replace("'", '"')),
|
|
31
|
-
)
|
|
32
|
-
res = rpc.eth_call(config.node, config.contract_address, input_data)
|
|
33
|
-
if isinstance(res, Err):
|
|
34
|
-
return fatal(f"error: {res.err}")
|
|
35
|
-
|
|
36
|
-
result = res.ok
|
|
37
|
-
if config.outputs_types is not None:
|
|
38
|
-
decode_res = abi.decode_data(_get_types(config.outputs_types), result)
|
|
39
|
-
result = decode_res[0] if len(decode_res) == 1 else str(decode_res)
|
|
40
|
-
print_plain(result)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _get_types(data: str) -> list[str]:
|
|
44
|
-
return [t.strip() for t in data.split(",") if t.strip()]
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
|
-
from mm_std import print_plain
|
|
4
|
-
|
|
5
|
-
from mm_eth import abi
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def run(function_signature: str, args_str: str) -> None:
|
|
9
|
-
args_str = args_str.replace("'", '"')
|
|
10
|
-
print_plain(abi.encode_function_input_by_signature(function_signature, json.loads(args_str)))
|
mm_eth/cli/cmd/example_cmd.py
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from mm_std import pretty_print_toml
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def run(command: str) -> None:
|
|
7
|
-
command = command.replace("-", "_")
|
|
8
|
-
example_file = Path(Path(__file__).parent.absolute(), "../examples", f"{command}.toml")
|
|
9
|
-
pretty_print_toml(example_file.read_text())
|
mm_eth/cli/cmd/rpc_cmd.py
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import cast
|
|
3
|
-
|
|
4
|
-
from mm_std import fatal, hr, print_console, str_starts_with_any
|
|
5
|
-
from rich import print_json
|
|
6
|
-
|
|
7
|
-
from mm_eth.cli.cli_utils import public_rpc_url
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def run(rpc_url: str, method: str, params: str, hex2dec: bool) -> None:
|
|
11
|
-
rpc_url = public_rpc_url(rpc_url)
|
|
12
|
-
if not method:
|
|
13
|
-
return list_all_methods()
|
|
14
|
-
if not str_starts_with_any(rpc_url, ["http://", "https://"]):
|
|
15
|
-
fatal(f"invalid rpc_url: {rpc_url}")
|
|
16
|
-
params = params.replace("'", '"')
|
|
17
|
-
data = {"jsonrpc": "2.0", "method": method, "params": parse_method_params(method, params), "id": 1}
|
|
18
|
-
res = hr(rpc_url, method="POST", params=data, json_params=True)
|
|
19
|
-
if res.json:
|
|
20
|
-
print_json(data=res.json)
|
|
21
|
-
result_value: str = res.json.get("result", "")
|
|
22
|
-
if hex2dec and result_value.startswith(("0x", "0X")):
|
|
23
|
-
print_console("hex2dec", int(result_value, 16))
|
|
24
|
-
else:
|
|
25
|
-
fatal(str(res))
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def parse_method_params(method: str, params_str: str) -> list[object]:
|
|
29
|
-
params = json.loads(params_str) if params_str.startswith("[") else params_str.split()
|
|
30
|
-
if method in ["eth_getBalance", "eth_getTransactionCount", "eth_getCode"] and len(params) == 1:
|
|
31
|
-
params.append("latest")
|
|
32
|
-
return cast(list[object], params)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def list_all_methods() -> None:
|
|
36
|
-
all_methods = """
|
|
37
|
-
web3_clientVersion
|
|
38
|
-
web3_sha3
|
|
39
|
-
net_version
|
|
40
|
-
net_listening
|
|
41
|
-
net_peerCount
|
|
42
|
-
eth_protocolVersion
|
|
43
|
-
eth_syncing
|
|
44
|
-
eth_chainId
|
|
45
|
-
eth_gasPrice
|
|
46
|
-
eth_accounts
|
|
47
|
-
eth_blockNumber
|
|
48
|
-
eth_getBalance
|
|
49
|
-
eth_getStorageAt
|
|
50
|
-
eth_getTransactionCount
|
|
51
|
-
eth_getBlockTransactionCountByHash
|
|
52
|
-
eth_getBlockTransactionCountByNumber
|
|
53
|
-
eth_getUncleCountByBlockHash
|
|
54
|
-
eth_getUncleCountByBlockNumber
|
|
55
|
-
eth_getCode
|
|
56
|
-
eth_sign
|
|
57
|
-
eth_signTransaction
|
|
58
|
-
eth_sendTransaction
|
|
59
|
-
eth_sendRawTransaction
|
|
60
|
-
eth_call
|
|
61
|
-
eth_estimateGas
|
|
62
|
-
eth_getBlockByHash
|
|
63
|
-
eth_getBlockByNumber
|
|
64
|
-
eth_getTransactionByHash
|
|
65
|
-
eth_getTransactionByBlockHashAndIndex
|
|
66
|
-
eth_getTransactionByBlockNumberAndIndex
|
|
67
|
-
eth_getTransactionReceipt
|
|
68
|
-
eth_getUncleByBlockHashAndIndex
|
|
69
|
-
eth_getUncleByBlockNumberAndIndex
|
|
70
|
-
eth_newFilter
|
|
71
|
-
eth_newBlockFilter
|
|
72
|
-
eth_newPendingTransactionFilter
|
|
73
|
-
eth_uninstallFilter
|
|
74
|
-
eth_getFilterChanges
|
|
75
|
-
eth_getFilterLogs
|
|
76
|
-
eth_getLogs
|
|
77
|
-
""".strip()
|
|
78
|
-
print_console(all_methods)
|
mm_eth/cli/cmd/token_cmd.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from mm_std import Err, Ok, print_plain
|
|
2
|
-
|
|
3
|
-
from mm_eth import erc20, rpc
|
|
4
|
-
from mm_eth.cli import cli_utils
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def run(rpc_url: str, token_address: str) -> None:
|
|
8
|
-
rpc_url = cli_utils.public_rpc_url(rpc_url)
|
|
9
|
-
name = erc20.get_name(rpc_url, token_address).ok_or_err()
|
|
10
|
-
symbol = erc20.get_symbol(rpc_url, token_address).ok_or_err()
|
|
11
|
-
decimals = erc20.get_decimals(rpc_url, token_address).ok_or_err()
|
|
12
|
-
transfer_count = _calc_transfer_events(rpc_url, 100, token_address)
|
|
13
|
-
|
|
14
|
-
print_plain(f"name: {name}")
|
|
15
|
-
print_plain(f"symbol: {symbol}")
|
|
16
|
-
print_plain(f"decimals: {decimals}")
|
|
17
|
-
print_plain(f"transfer_count: {transfer_count}")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _calc_transfer_events(rpc_url: str, last_block_limit: int, token_address: str) -> int | str:
|
|
21
|
-
current_block_res = rpc.eth_block_number(rpc_url)
|
|
22
|
-
if isinstance(current_block_res, Err):
|
|
23
|
-
return current_block_res.err
|
|
24
|
-
current_block = current_block_res.ok
|
|
25
|
-
|
|
26
|
-
res = erc20.get_transfer_event_logs(rpc_url, token_address, current_block - last_block_limit, current_block)
|
|
27
|
-
if isinstance(res, Ok):
|
|
28
|
-
return len(res.ok)
|
|
29
|
-
return res.err
|
mm_eth/cli/cmd/tx_cmd.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
from eth_typing import HexStr
|
|
2
|
-
from rich.pretty import pprint
|
|
3
|
-
from web3 import Web3
|
|
4
|
-
|
|
5
|
-
from mm_eth.cli.cli_utils import public_rpc_url
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def run(rpc_url: str, tx_hash: str, get_receipt: bool) -> None:
|
|
9
|
-
rpc_url = public_rpc_url(rpc_url)
|
|
10
|
-
w3 = Web3(Web3.HTTPProvider(rpc_url))
|
|
11
|
-
transaction = w3.eth.get_transaction(HexStr(tx_hash))
|
|
12
|
-
pprint(dict(transaction), expand_all=True)
|
|
13
|
-
|
|
14
|
-
if get_receipt:
|
|
15
|
-
receipt = w3.eth.get_transaction_receipt(HexStr(tx_hash))
|
|
16
|
-
pprint(dict(receipt), expand_all=True)
|