mm-eth 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. mm_eth/__init__.py +0 -0
  2. mm_eth/abi/zksync.json +2092 -0
  3. mm_eth/abi.py +130 -0
  4. mm_eth/account.py +70 -0
  5. mm_eth/anvil.py +56 -0
  6. mm_eth/cli/__init__.py +0 -0
  7. mm_eth/cli/calcs.py +88 -0
  8. mm_eth/cli/cli.py +233 -0
  9. mm_eth/cli/cli_helpers.py +195 -0
  10. mm_eth/cli/cli_utils.py +150 -0
  11. mm_eth/cli/cmd/__init__.py +0 -0
  12. mm_eth/cli/cmd/balance_cmd.py +59 -0
  13. mm_eth/cli/cmd/balances_cmd.py +121 -0
  14. mm_eth/cli/cmd/call_contract_cmd.py +44 -0
  15. mm_eth/cli/cmd/config_example_cmd.py +9 -0
  16. mm_eth/cli/cmd/deploy_cmd.py +41 -0
  17. mm_eth/cli/cmd/encode_input_data_cmd.py +10 -0
  18. mm_eth/cli/cmd/mnemonic_cmd.py +27 -0
  19. mm_eth/cli/cmd/node_cmd.py +47 -0
  20. mm_eth/cli/cmd/private_key_cmd.py +10 -0
  21. mm_eth/cli/cmd/rpc_cmd.py +81 -0
  22. mm_eth/cli/cmd/send_contract_cmd.py +247 -0
  23. mm_eth/cli/cmd/solc_cmd.py +25 -0
  24. mm_eth/cli/cmd/token_cmd.py +29 -0
  25. mm_eth/cli/cmd/transfer_erc20_cmd.py +275 -0
  26. mm_eth/cli/cmd/transfer_eth_cmd.py +252 -0
  27. mm_eth/cli/cmd/vault_cmd.py +16 -0
  28. mm_eth/cli/config_examples/balances.yml +15 -0
  29. mm_eth/cli/config_examples/call_contract.yml +5 -0
  30. mm_eth/cli/config_examples/transfer_erc20.yml +26 -0
  31. mm_eth/cli/config_examples/transfer_eth.yml +24 -0
  32. mm_eth/cli/validators.py +84 -0
  33. mm_eth/deploy.py +20 -0
  34. mm_eth/ens.py +16 -0
  35. mm_eth/erc20.py +240 -0
  36. mm_eth/ethernodes.py +34 -0
  37. mm_eth/py.typed +0 -0
  38. mm_eth/rpc.py +478 -0
  39. mm_eth/services/__init__.py +0 -0
  40. mm_eth/solc.py +34 -0
  41. mm_eth/tx.py +164 -0
  42. mm_eth/types.py +5 -0
  43. mm_eth/utils.py +245 -0
  44. mm_eth/vault.py +38 -0
  45. mm_eth/zksync.py +203 -0
  46. mm_eth-0.1.0.dist-info/METADATA +24 -0
  47. mm_eth-0.1.0.dist-info/RECORD +50 -0
  48. mm_eth-0.1.0.dist-info/WHEEL +5 -0
  49. mm_eth-0.1.0.dist-info/entry_points.txt +2 -0
  50. mm_eth-0.1.0.dist-info/top_level.txt +1 -0
mm_eth/utils.py ADDED
@@ -0,0 +1,245 @@
1
+ import re
2
+ from decimal import Decimal, localcontext
3
+ from typing import Any, cast
4
+
5
+ import eth_utils
6
+ import pydash
7
+ from eth_typing import HexStr
8
+ from hexbytes import HexBytes
9
+ from mm_std import Err, Ok, Result, number_with_separator, random_choice
10
+ from mm_std.random_ import random_str_choice
11
+ from pydantic import BaseModel
12
+ from web3 import Web3
13
+ from web3.types import Wei
14
+
15
+ from mm_eth.types import Nodes, Proxies
16
+
17
+
18
+ def parse_addresses(data: str) -> list[str]:
19
+ data = data.lower()
20
+ result = []
21
+ for word in data.split():
22
+ if len(word) == 42 and re.match("0x[a-f0-9]{40}", word):
23
+ result.append(word)
24
+ return pydash.uniq(result)
25
+
26
+
27
+ def to_token_wei(value: str | int, decimals: int) -> int:
28
+ if isinstance(value, int):
29
+ return value
30
+ elif isinstance(value, str):
31
+ value = value.lower().replace(" ", "").strip()
32
+ if value.endswith("t"):
33
+ value = value.replace("t", "")
34
+ return int(Decimal(value) * 10**decimals)
35
+ elif value.isdigit():
36
+ return int(value)
37
+
38
+ raise ValueError("wrong value" + value)
39
+
40
+
41
+ def to_wei(value: str | int | Decimal, decimals: int | None = None) -> Wei:
42
+ if isinstance(value, int):
43
+ return Wei(value)
44
+ elif isinstance(value, Decimal):
45
+ if value != value.to_integral_value():
46
+ raise ValueError(f"value must be integral number: {value}")
47
+ return Wei(int(value))
48
+ elif isinstance(value, str):
49
+ value = value.lower().replace(" ", "").strip()
50
+ if value.endswith("navax"): # https://snowtrace.io/unitconverter
51
+ value = value.replace("navax", "")
52
+ return Wei(int(Decimal(value) * 10**9))
53
+ if value.endswith("gwei"):
54
+ value = value.replace("gwei", "")
55
+ return Wei(int(Decimal(value) * 1000000000))
56
+ if value.endswith("ether"):
57
+ value = value.replace("ether", "")
58
+ return Wei(int(Decimal(value) * 1000000000000000000))
59
+ if value.endswith("eth"):
60
+ value = value.replace("eth", "")
61
+ return Wei(int(Decimal(value) * 1000000000000000000))
62
+ if value.endswith("t"):
63
+ if decimals is None:
64
+ raise ValueError("t without decimals")
65
+ value = value.removesuffix("t")
66
+ return Wei(int(Decimal(value) * 10**decimals))
67
+ if value.isdigit():
68
+ return Wei(int(value))
69
+ raise ValueError("wrong value " + value)
70
+
71
+ else:
72
+ raise ValueError(f"value has a wrong type: {type(value)}")
73
+
74
+
75
+ def from_wei(
76
+ value: int,
77
+ unit: str,
78
+ round_ndigits: int | None = None,
79
+ decimals: int | None = None,
80
+ ) -> Decimal:
81
+ if value == 0:
82
+ return Decimal(0)
83
+
84
+ is_negative = value < 0
85
+ if unit.lower() == "eth":
86
+ unit = "ether"
87
+
88
+ if unit.lower() == "t":
89
+ if decimals is None:
90
+ raise ValueError("t without decimals")
91
+ with localcontext() as ctx:
92
+ ctx.prec = 999
93
+ res = Decimal(value=abs(value), context=ctx) / Decimal(10**decimals)
94
+ else:
95
+ res = cast(Decimal, eth_utils.from_wei(abs(value), unit))
96
+ if round_ndigits is not None:
97
+ res = round(res, ndigits=round_ndigits)
98
+ return -1 * res if is_negative else res
99
+
100
+
101
+ def from_wei_str(
102
+ value: int,
103
+ unit: str,
104
+ round_ndigits: int | None = None,
105
+ print_unit_name: bool = True,
106
+ decimals: int | None = None,
107
+ ) -> str:
108
+ res = format(from_wei(value, unit, round_ndigits, decimals=decimals), "f")
109
+ if unit == "ether":
110
+ unit = "eth"
111
+ if print_unit_name:
112
+ res += unit
113
+ return res
114
+
115
+
116
+ def from_token_wei_str(value: int, decimals: int, symbol: str = "", round_ndigits: int | None = None) -> str:
117
+ res = value / 10**decimals
118
+ if round_ndigits is not None:
119
+ res = round(res, ndigits=round_ndigits)
120
+ if symbol:
121
+ res = f"{res} {symbol}"
122
+ return str(res)
123
+
124
+
125
+ def to_wei_token(value: str | int | Decimal, symbol: str, decimals: int) -> int:
126
+ if isinstance(value, int):
127
+ return value
128
+ elif isinstance(value, Decimal):
129
+ if value != value.to_integral_value():
130
+ raise ValueError(f"value must be integral number: {value}")
131
+ return int(value)
132
+ elif isinstance(value, str):
133
+ value = value.lower().replace(" ", "").strip()
134
+ if value.isdigit():
135
+ return int(value)
136
+ try:
137
+ return int(Decimal(value.replace(symbol.lower(), "").strip()) * (10**decimals))
138
+ except Exception as e:
139
+ raise ValueError from e
140
+ else:
141
+ raise ValueError(f"value has a wrong type: {type(value)}")
142
+
143
+
144
+ def to_checksum_address(address: str) -> str:
145
+ return Web3.to_checksum_address(address)
146
+
147
+
148
+ def hex_to_bytes(data: str) -> bytes:
149
+ return eth_utils.to_bytes(hexstr=HexStr(data))
150
+
151
+
152
+ def get_chain_name(chain_id: int | str) -> str:
153
+ chain_id = str(chain_id)
154
+ if chain_id == "1":
155
+ return "mainnet"
156
+ if chain_id == "3":
157
+ return "ropsten"
158
+ if chain_id == "5":
159
+ return "goerli"
160
+ return chain_id
161
+
162
+
163
+ def to_human_readable_tx(tx: dict[str, Any] | BaseModel) -> dict[str, object]:
164
+ if isinstance(tx, BaseModel):
165
+ tx = tx.model_dump()
166
+ tx["human_readable"] = {}
167
+ tx["human_readable"]["gas_price"] = str(tx["gas_price"] / 10**9) + " gwei"
168
+ tx["human_readable"]["value"] = str(tx["value"] / 10**18) + " ether"
169
+ tx["human_readable"]["gas"] = number_with_separator(tx["gas"])
170
+ if tx.get("chain_id") is not None:
171
+ tx["human_readable"]["chain_id"] = get_chain_name(tx["chain_id"])
172
+
173
+ return tx
174
+
175
+
176
+ def truncate_hex_str(hex_str: str, digits: int = 4, replace_str: str = "...") -> str:
177
+ if not hex_str.startswith("0x") and not hex_str.startswith("0X"):
178
+ raise ValueError("truncate_hex_str: hex_str must start with 0x")
179
+ if digits <= 0:
180
+ raise ValueError("truncate_hex_str: digits must be more than zero")
181
+ hex_str = hex_str.removeprefix("0x").removeprefix("0X")
182
+ if digits * 2 >= len(hex_str):
183
+ raise ValueError("truncate_hex_str: digits is too large")
184
+ return "0x" + hex_str[:digits] + replace_str + hex_str[-1 * digits :]
185
+
186
+
187
+ def log_topic_to_address(topic: HexBytes | str) -> str:
188
+ if isinstance(topic, HexBytes):
189
+ result = topic.hex()[-40:]
190
+ else:
191
+ result = topic[-40:]
192
+ if not result.startswith("0x"):
193
+ result = f"0x{result}"
194
+ return result
195
+
196
+
197
+ def get_w3(rpc_url: str, timeout: int | None = None, proxy: str | None = None) -> Web3:
198
+ request_kwargs: dict[str, object] = {"timeout": timeout}
199
+ if proxy:
200
+ request_kwargs["proxies"] = {"http": proxy, "https": proxy}
201
+ return Web3(Web3.HTTPProvider(rpc_url, request_kwargs=request_kwargs))
202
+
203
+
204
+ def name_network(chain_id: int) -> str:
205
+ match chain_id:
206
+ case 1:
207
+ return "Ethereum Mainnet"
208
+ case 5:
209
+ return "Goerli"
210
+ case 10:
211
+ return "OP Mainnet"
212
+ case 280:
213
+ return "zkSync Era Testnet"
214
+ case 324:
215
+ return "zkSync Era Mainnet"
216
+ case 420:
217
+ return "Optimism Goerli Testnet"
218
+ case 42161:
219
+ return "Arbitrum One"
220
+ case 43113:
221
+ return "Avalanche Fuji Testnet"
222
+ case 43114:
223
+ return "Avalanche C-Chain"
224
+ case 421613:
225
+ return "Arbitrum Goerli"
226
+ case _:
227
+ return ""
228
+
229
+
230
+ def hex_str_to_int(value: str) -> Result[int]:
231
+ try:
232
+ return Ok(int(value, 16))
233
+ except Exception:
234
+ return Err(f"can't convert to int: {value}")
235
+
236
+
237
+ def random_node(nodes: Nodes, remove_slash: bool = True) -> str:
238
+ node = cast(str, random_choice(nodes))
239
+ if remove_slash and node.endswith("/"):
240
+ node = node.removesuffix("/")
241
+ return node
242
+
243
+
244
+ def random_proxy(proxies: Proxies) -> str | None:
245
+ return random_str_choice(proxies)
mm_eth/vault.py ADDED
@@ -0,0 +1,38 @@
1
+ from mm_std import Err, Ok, Result, hr
2
+
3
+ from mm_eth.account import private_to_address
4
+
5
+
6
+ def read_keys_from_vault(keys_url: str, token: str) -> Result[dict[str, str]]:
7
+ data = None
8
+ try:
9
+ # keys_url example, https://vault.site.com:8200/v1/kv/keys1
10
+ res = hr(keys_url, headers={"X-Vault-Token": token})
11
+ data = res.json
12
+ return Ok(res.json["data"], data=data)
13
+ except Exception as e:
14
+ return Err(f"exception: {e}", data=data)
15
+
16
+
17
+ def set_keys_from_vault(keys_url: str, token: str, private_keys: list[str], verify_tls: bool = True) -> Result[bool]:
18
+ """It works with KV version=1 only!!!""" # TODO: check that keys_url is kv1 version and error if it's kv2
19
+ data = None
20
+ try:
21
+ # keys_url example, https://vault.site.com:8200/v1/kv/keys1
22
+ keys: dict[str, str] = {}
23
+ for private_key in private_keys:
24
+ address = private_to_address(private_key)
25
+ if address is None:
26
+ raise ValueError("there is a wrong private key")
27
+ keys[address] = private_key
28
+
29
+ res = hr(keys_url, method="post", headers={"X-Vault-Token": token}, params=keys, verify=verify_tls)
30
+ data = res.json
31
+ if res.code == 204:
32
+ return Ok(res.code == 204, data=data)
33
+ elif res.code == 403:
34
+ return Err("permission denied", data=data)
35
+ else:
36
+ return Err(res.error or "error", data=data)
37
+ except Exception as e:
38
+ return Err(f"exception: {e}", data=data)
mm_eth/zksync.py ADDED
@@ -0,0 +1,203 @@
1
+ import json
2
+ import pkgutil
3
+ from typing import cast
4
+
5
+ from eth_typing import ChecksumAddress, HexStr
6
+ from hexbytes import HexBytes
7
+ from mm_std import Err, Ok, Result
8
+ from pydantic import BaseModel, Field
9
+ from web3 import Web3
10
+ from web3.types import ABI, Nonce, TxParams, Wei
11
+
12
+ from mm_eth import abi, rpc
13
+ from mm_eth.rpc import rpc_call
14
+ from mm_eth.types import Nodes, Proxies
15
+ from mm_eth.utils import hex_str_to_int
16
+
17
+ L2_ETH_TOKEN_ADDRESS = Web3.to_checksum_address("0x000000000000000000000000000000000000800a")
18
+
19
+
20
+ class BridgeContracts(BaseModel):
21
+ l1_erc20_Default_bridge: str = Field(..., alias="l1Erc20DefaultBridge")
22
+ l2_erc20_Default_bridge: str = Field(..., alias="l2Erc20DefaultBridge")
23
+
24
+
25
+ def zks_get_bridge_contracts(
26
+ rpc_urls: Nodes,
27
+ timeout: int = 10,
28
+ proxies: Proxies = None,
29
+ attempts: int = 1,
30
+ ) -> Result[BridgeContracts]:
31
+ res = rpc_call(
32
+ nodes=rpc_urls,
33
+ method="zks_getBridgeContracts",
34
+ params=[],
35
+ timeout=timeout,
36
+ proxies=proxies,
37
+ attempts=attempts,
38
+ )
39
+ if isinstance(res, Err):
40
+ return res
41
+
42
+ try:
43
+ return Ok(BridgeContracts(**res.ok), data=res.data)
44
+ except Exception as e:
45
+ return Err(f"exception: {e}", data=res.data)
46
+
47
+
48
+ def zks_get_main_contract(
49
+ rpc_urls: Nodes,
50
+ timeout: int = 10,
51
+ proxies: Proxies = None,
52
+ attempts: int = 1,
53
+ ) -> Result[str]:
54
+ return rpc_call(
55
+ nodes=rpc_urls,
56
+ method="zks_getMainContract",
57
+ params=[],
58
+ timeout=timeout,
59
+ proxies=proxies,
60
+ attempts=attempts,
61
+ )
62
+
63
+
64
+ def zks_estimate_gas_l1_to_l2(
65
+ rpc_urls: Nodes,
66
+ timeout: int = 10,
67
+ proxies: Proxies = None,
68
+ attempts: int = 1,
69
+ ) -> Result[int]:
70
+ return rpc_call(
71
+ nodes=rpc_urls,
72
+ method="zks_estimateGasL1ToL2",
73
+ params=[
74
+ [
75
+ {
76
+ "from": "0x1111111111111111111111111111111111111111",
77
+ "to": "0x2222222222222222222222222222222222222222",
78
+ "data": "0xffffffff",
79
+ },
80
+ ],
81
+ ],
82
+ timeout=timeout,
83
+ proxies=proxies,
84
+ attempts=attempts,
85
+ ).and_then(hex_str_to_int)
86
+
87
+
88
+ def zksync_contract_abi() -> ABI:
89
+ data = pkgutil.get_data(__name__, "abi/zksync.json")
90
+ if data is None:
91
+ raise RuntimeError("can't read abi/zksync.json")
92
+ return cast(ABI, json.loads(data.decode()))
93
+
94
+
95
+ def get_l2_transactio_base_cost(
96
+ eth_rpc: str,
97
+ contract_address: ChecksumAddress,
98
+ gas_price: Wei,
99
+ l2_gas_limit: int = 10000000,
100
+ l2_gas_per_pubdata_byte_limit: int = 800,
101
+ ) -> Result[Wei]:
102
+ w3 = Web3(Web3.HTTPProvider(eth_rpc))
103
+ try:
104
+ contract = w3.eth.contract(address=contract_address, abi=zksync_contract_abi())
105
+ res = contract.functions.l2TransactionBaseCost(gas_price, l2_gas_limit, l2_gas_per_pubdata_byte_limit).call()
106
+ return Ok(Wei(res))
107
+ except Exception as e:
108
+ return Err(str(e))
109
+
110
+
111
+ def deposit(
112
+ *,
113
+ eth_rpc: str,
114
+ contract_address: ChecksumAddress,
115
+ wallet_address: ChecksumAddress,
116
+ private_key: str,
117
+ gas_price: Wei,
118
+ value: Wei,
119
+ l2_gas_limit: int = 1_300_000,
120
+ l2_gas_per_pubdata_byte_limit: int = 800,
121
+ gas: int = 150096,
122
+ nonce: Nonce | None = None,
123
+ ) -> Result[str]:
124
+ w3 = Web3(Web3.HTTPProvider(eth_rpc))
125
+ try:
126
+ # get nonce
127
+ if nonce is None:
128
+ res_nonce = rpc.eth_get_transaction_count(eth_rpc, wallet_address)
129
+ if isinstance(res_nonce, Err):
130
+ return res_nonce
131
+
132
+ nonce = Nonce(res_nonce.ok)
133
+
134
+ # get base_cost
135
+ res = get_l2_transactio_base_cost(eth_rpc, contract_address, gas_price, l2_gas_limit, l2_gas_per_pubdata_byte_limit)
136
+ if isinstance(res, Err):
137
+ return res
138
+ base_cost = Wei(res.ok)
139
+
140
+ contract_address_l2 = wallet_address
141
+ l2_value = value
142
+ calldata = b""
143
+ factory_deps = [] # type: ignore[var-annotated]
144
+ refund_recipient = wallet_address
145
+
146
+ contract = w3.eth.contract(address=contract_address, abi=zksync_contract_abi())
147
+ function_call = contract.functions.requestL2Transaction(
148
+ contract_address_l2,
149
+ l2_value,
150
+ calldata,
151
+ l2_gas_limit,
152
+ l2_gas_per_pubdata_byte_limit,
153
+ factory_deps,
154
+ refund_recipient,
155
+ )
156
+
157
+ transaction_data = function_call.build_transaction(
158
+ {"from": wallet_address, "gas": gas, "gasPrice": gas_price, "nonce": nonce, "value": Wei(value + base_cost)},
159
+ )
160
+ signed_transaction = w3.eth.account.sign_transaction(transaction_data, private_key)
161
+ txn = w3.eth.send_raw_transaction(signed_transaction.rawTransaction)
162
+ return Ok(str(txn))
163
+ except Exception as e:
164
+ return Err(f"exception: {e}")
165
+
166
+
167
+ def withdraw(
168
+ *,
169
+ zksync_rpc: str,
170
+ wallet_address: ChecksumAddress,
171
+ private_key: str,
172
+ value: Wei,
173
+ chain_id: int,
174
+ ) -> Result[HexBytes]:
175
+ try:
176
+ w3 = Web3(Web3.HTTPProvider(zksync_rpc))
177
+
178
+ # get nonce
179
+ nonce = w3.eth.get_transaction_count(wallet_address)
180
+
181
+ input_data = abi.encode_function_signature("withdraw(address)") + abi.encode_data(
182
+ ["address"],
183
+ [wallet_address],
184
+ ).removeprefix("0x")
185
+
186
+ transaction_data: TxParams = {
187
+ "from": wallet_address,
188
+ "to": L2_ETH_TOKEN_ADDRESS,
189
+ "nonce": nonce,
190
+ "data": HexStr(input_data),
191
+ "value": value,
192
+ "chainId": chain_id,
193
+ }
194
+ gas = w3.eth.estimate_gas(transaction_data)
195
+ transaction_data["gas"] = gas
196
+ gas_price = w3.eth.gas_price
197
+ transaction_data["gasPrice"] = gas_price
198
+ signed_transaction = w3.eth.account.sign_transaction(transaction_data, private_key)
199
+ txn = w3.eth.send_raw_transaction(signed_transaction.rawTransaction)
200
+ return Ok(txn)
201
+
202
+ except Exception as e:
203
+ return Err(f"exception: {e}")
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.1
2
+ Name: mm-eth
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.12
5
+ Requires-Dist: mm-std ~=0.1.0
6
+ Requires-Dist: websocket-client ~=1.8.0
7
+ Requires-Dist: web3 ~=6.20.0
8
+ Requires-Dist: typer ~=0.12.0
9
+ Requires-Dist: loguru ~=0.7.2
10
+ Provides-Extra: dev
11
+ Requires-Dist: build ~=1.2.1 ; extra == 'dev'
12
+ Requires-Dist: twine ~=5.1.0 ; extra == 'dev'
13
+ Requires-Dist: pytest ~=8.3.2 ; extra == 'dev'
14
+ Requires-Dist: pytest-xdist ~=3.6.1 ; extra == 'dev'
15
+ Requires-Dist: pytest-httpserver ~=1.0.8 ; extra == 'dev'
16
+ Requires-Dist: coverage ~=7.6.0 ; extra == 'dev'
17
+ Requires-Dist: ruff ~=0.5.2 ; extra == 'dev'
18
+ Requires-Dist: pip-audit ~=2.7.0 ; extra == 'dev'
19
+ Requires-Dist: bandit ~=1.7.7 ; extra == 'dev'
20
+ Requires-Dist: mypy ~=1.11.0 ; extra == 'dev'
21
+ Requires-Dist: types-python-dateutil ~=2.9.0 ; extra == 'dev'
22
+ Requires-Dist: types-requests ~=2.32.0.20240523 ; extra == 'dev'
23
+ Requires-Dist: types-PyYAML ~=6.0.12.12 ; extra == 'dev'
24
+
@@ -0,0 +1,50 @@
1
+ mm_eth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ mm_eth/abi.py,sha256=VpZETb7jh8Ovghxit-UI13cxAWP7siv5UqIzC70GIB0,4736
3
+ mm_eth/account.py,sha256=ZpmcYRmJVa6oJMtSlLSkpjgbA8M-g6alaHQaeQT1I9Y,1982
4
+ mm_eth/anvil.py,sha256=9CWXWyK7tVrvs6NeOy7QnVwMTrUke-nBMFwZeJ4VULA,1582
5
+ mm_eth/deploy.py,sha256=SB3ruY808_5UnG8kHR34uVP66P3zOWZu0ImKD7UUv2s,691
6
+ mm_eth/ens.py,sha256=WMxqC1v3zwDDuLH_oWekm22qrNYxCNcvZumQMT7SYds,623
7
+ mm_eth/erc20.py,sha256=m62j00zizSZzXgLUEih2Q1iVFehU9qrqOMfc3gyix7k,6844
8
+ mm_eth/ethernodes.py,sha256=9y_poTmFUj6cnWaT9mtfc6S9lAfVXTwLGRqxMQ8hT0Y,3080
9
+ mm_eth/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ mm_eth/rpc.py,sha256=kQnYllp5ZHGuIUUX2Vw-ejLwL3zXQgka3ukwDndv4AM,13881
11
+ mm_eth/solc.py,sha256=JcBCel-LO-lBvz3_1f7s3pVhECsUittavBvXqxYIP2c,1066
12
+ mm_eth/tx.py,sha256=XkyafbEepG5VtJwVlM2NNPuFGit8pldKdOPh_R_1iJE,4032
13
+ mm_eth/types.py,sha256=w_B1o_XggFz2cHXa36tI-wjfE4r-BFhvKIF9nb2YI5g,154
14
+ mm_eth/utils.py,sha256=TMvQMtP7YyuBnv-bsjbcIzRfBUyVFBI8IjeolCXXlVg,7785
15
+ mm_eth/vault.py,sha256=1ahABcgyYAMUlE4ELMGdlG_uV4irmwzrfzUZTpo3rdY,1500
16
+ mm_eth/zksync.py,sha256=XLop9r5_gVEi9tm1MUfuSl0wUmbE27m7x0pir7T3hZA,5997
17
+ mm_eth/abi/zksync.json,sha256=aHp6-Y5W6wRz0hrC0aPTjAHb-Q2ipDikcAB5FtlxWWA,45503
18
+ mm_eth/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ mm_eth/cli/calcs.py,sha256=nHfn_Q9T_E8_hP7ExMyWlUGvAMzxQs6KgzdNg1CUnvU,3651
20
+ mm_eth/cli/cli.py,sha256=cxkSistUsdalp3xuOxgMYBp5u9jxnNitgv8Oc-AyVww,8768
21
+ mm_eth/cli/cli_helpers.py,sha256=0NapwmrRVQ1PWDP6iqwaX1fnzrIdD_TwvcZXIcq5QzM,6572
22
+ mm_eth/cli/cli_utils.py,sha256=4I12cJV0deKfarRpd2wNhW42L_jQab4oxa9DX-WxicM,5041
23
+ mm_eth/cli/validators.py,sha256=bE4G-OL6W6S30P4FK5pVn_LiY2G2w8aeA3R1kQaX0gY,2379
24
+ mm_eth/cli/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ mm_eth/cli/cmd/balance_cmd.py,sha256=kDV_KAPrUY_fsGiVLcGGHspuU2zqpqsVnTe2btjtCxs,2254
26
+ mm_eth/cli/cmd/balances_cmd.py,sha256=P3zyaJrghLXAYE7mnT3ReKnPYHiAqA7kZbkvLPioVUE,4326
27
+ mm_eth/cli/cmd/call_contract_cmd.py,sha256=pKuWXZ6i4Taml_AnEJlffSsgjd_ktRoLDqmyAzwuDXk,1245
28
+ mm_eth/cli/cmd/config_example_cmd.py,sha256=hMfrFxjIP0xZllJRRxi99qhF8tzJI3lrp-XudFEWF1g,270
29
+ mm_eth/cli/cmd/deploy_cmd.py,sha256=MoPxZXNB_YlPh9qprA4EZ96Su-A2n_jeVJkhWVpsz1c,1258
30
+ mm_eth/cli/cmd/encode_input_data_cmd.py,sha256=9UQ1MKPEFQJ8j_COsP3KGKhwOf9tT3feBezI8vvxTlw,267
31
+ mm_eth/cli/cmd/mnemonic_cmd.py,sha256=BfcNSflEr5R8eINc1ATDc6w2pErfKfzL-Zloqbk3oio,1006
32
+ mm_eth/cli/cmd/node_cmd.py,sha256=mUqixPGNHzuKCJvk1Fd5VaUGumpbR2AE7raGmXZscg4,1943
33
+ mm_eth/cli/cmd/private_key_cmd.py,sha256=Fv_2OLog1h32pIP7PJITwl_pHdy3BXvaDRcXZsxY1xo,241
34
+ mm_eth/cli/cmd/rpc_cmd.py,sha256=dCEbAF9bmJQq1pKf8JGcaIB-iorPz-tT4Tc7CweaEmY,2217
35
+ mm_eth/cli/cmd/send_contract_cmd.py,sha256=xT1RVgIUvJIvEtvPxrqnCK0St5HoL022CXStxLEdriM,9047
36
+ mm_eth/cli/cmd/solc_cmd.py,sha256=Xu6rdn15zQeHEEFpGxbD7ofojT_o6SVqg1dTpbock-Q,717
37
+ mm_eth/cli/cmd/token_cmd.py,sha256=Sm4PiAABwnfgchBgj1VnmYrzF7oll8js9gKh6YNdwnE,1107
38
+ mm_eth/cli/cmd/transfer_erc20_cmd.py,sha256=DA4Az9rINKDx7Pjrr4v9v-QNYpOc03TgnpaNEVGDVr4,10245
39
+ mm_eth/cli/cmd/transfer_eth_cmd.py,sha256=dbXt2Ngb--RyA5HdvISwSlr129cd674GvGYLLuPaG3U,9429
40
+ mm_eth/cli/cmd/vault_cmd.py,sha256=rRTclz0U6N_87KFsssdvZhi3_f6tmkHiYxMsIoVeBGc,532
41
+ mm_eth/cli/config_examples/balances.yml,sha256=fpXJfoOSqOrkoWpqO7-HrTTb5OBs9PM-PpZ1ulqWUnY,399
42
+ mm_eth/cli/config_examples/call_contract.yml,sha256=E0XuWuBnbhyTYfxNqaoxH6Cy7UCYqMLDwoEu_4OSV3M,233
43
+ mm_eth/cli/config_examples/transfer_erc20.yml,sha256=mCUpUfqzZjTvst8kqd3EGzJY0S7cr48ySqS6yZhzLdI,1324
44
+ mm_eth/cli/config_examples/transfer_eth.yml,sha256=i4hh3-LTcpl7JsrSuuM6gDi23B5ZnCeEnaYazQO3bzQ,1181
45
+ mm_eth/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ mm_eth-0.1.0.dist-info/METADATA,sha256=QdbiQBGZqJ9j6C3lB4X4zX49ytobR1CkR-ThN6YFUMw,925
47
+ mm_eth-0.1.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
48
+ mm_eth-0.1.0.dist-info/entry_points.txt,sha256=aGhpsozl8NIrkuUcX5fSgURCcDhr3ShUdeTSIrJq4oc,46
49
+ mm_eth-0.1.0.dist-info/top_level.txt,sha256=hNXi_YDQ5FI_Qn4MC4AX0KeWf8KMAcTIk7jsGhoRRMA,7
50
+ mm_eth-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (72.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mm-eth = mm_eth.cli.cli:app
@@ -0,0 +1 @@
1
+ mm_eth