mm-eth 0.7.3__py3-none-any.whl → 0.8.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.
@@ -1,25 +1,29 @@
1
+ """CLI command: compile Solidity contracts."""
2
+
1
3
  import json
2
4
  from pathlib import Path
3
5
 
4
- import mm_print
6
+ from mm_print import print_json, print_plain
5
7
 
6
8
  from mm_eth.cli.cli import PrintFormat
9
+ from mm_eth.cli.cli_utils import fatal
7
10
  from mm_eth.solc import solc
8
11
 
9
12
 
10
13
  def run(contract_path: Path, tmp_dir: Path, print_format: PrintFormat) -> None:
14
+ """Compile a Solidity file and print the ABI and bytecode."""
11
15
  contract_name = contract_path.stem
12
16
  res = solc(contract_name, contract_path, tmp_dir)
13
17
  if res.is_err():
14
- mm_print.exit_with_error(res.unwrap_err())
18
+ fatal(res.unwrap_err())
15
19
 
16
20
  bin_ = res.unwrap().bin
17
21
  abi = res.unwrap().abi
18
22
 
19
23
  if print_format == PrintFormat.JSON:
20
- mm_print.json({"bin": bin_, "abi": json.loads(abi)})
24
+ print_json({"bin": bin_, "abi": json.loads(abi)})
21
25
  else:
22
- mm_print.plain("bin:")
23
- mm_print.plain(bin_)
24
- mm_print.plain("abi:")
25
- mm_print.plain(abi)
26
+ print_plain("bin:")
27
+ print_plain(bin_)
28
+ print_plain("abi:")
29
+ print_plain(abi)
@@ -1,3 +1,5 @@
1
+ """CLI command: transfer ETH or ERC-20 tokens."""
2
+
1
3
  import asyncio
2
4
  import sys
3
5
  import time
@@ -20,6 +22,8 @@ from mm_eth.converters import from_wei
20
22
 
21
23
 
22
24
  class Config(Web3CliConfig):
25
+ """Configuration for the transfer command."""
26
+
23
27
  nodes: Annotated[list[str], BeforeValidator(Validators.nodes())]
24
28
  chain_id: int
25
29
  transfers: Annotated[list[Transfer], BeforeValidator(Validators.eth_transfers())]
@@ -41,10 +45,12 @@ class Config(Web3CliConfig):
41
45
 
42
46
  @property
43
47
  def from_addresses(self) -> list[str]:
48
+ """Return the list of sender addresses from all transfers."""
44
49
  return [r.from_address for r in self.transfers]
45
50
 
46
51
  @model_validator(mode="after")
47
52
  def final_validator(self) -> Self:
53
+ """Validate private keys coverage, set default values, and check expression types."""
48
54
  if not self.private_keys.contains_all_addresses(self.from_addresses):
49
55
  raise ValueError("private keys are not set for all addresses")
50
56
 
@@ -69,6 +75,7 @@ class Config(Web3CliConfig):
69
75
  return self
70
76
 
71
77
  async def async_init(self) -> None:
78
+ """Fetch token decimals from the network if a token address is configured."""
72
79
  if self.token:
73
80
  self.token_decimals = (await retry.erc20_decimals(5, self.nodes, self.proxies, token=self.token)).unwrap(
74
81
  "can't get token decimals"
@@ -76,6 +83,8 @@ class Config(Web3CliConfig):
76
83
 
77
84
 
78
85
  class TransferCmdParams(BaseConfigParams):
86
+ """Parameters for the transfer command."""
87
+
79
88
  print_balances: bool
80
89
  print_transfers: bool
81
90
  debug: bool
@@ -84,6 +93,7 @@ class TransferCmdParams(BaseConfigParams):
84
93
 
85
94
 
86
95
  async def run(params: TransferCmdParams) -> None:
96
+ """Read config and execute transfers, or print balances/transfers for inspection."""
87
97
  config = await Config.read_toml_config_or_exit_async(params.config_path)
88
98
  await config.async_init()
89
99
  if params.print_config:
@@ -103,6 +113,7 @@ async def run(params: TransferCmdParams) -> None:
103
113
 
104
114
 
105
115
  async def _run_transfers(config: Config, cmd_params: TransferCmdParams) -> None:
116
+ """Execute all configured transfers sequentially with optional delays."""
106
117
  init_loguru(cmd_params.debug, config.log_debug, config.log_info)
107
118
  logger.info(f"transfer {cmd_params.config_path}: started at {utc_now()} UTC")
108
119
  logger.debug(f"config={config.model_dump(exclude={'private_keys'}) | {'version': cli_utils.get_version()}}")
@@ -117,6 +128,7 @@ async def _run_transfers(config: Config, cmd_params: TransferCmdParams) -> None:
117
128
 
118
129
 
119
130
  async def _get_nonce(t: Transfer, config: Config) -> int | None:
131
+ """Fetch the nonce for the transfer's sender address."""
120
132
  res = await retry.eth_get_transaction_count(5, config.nodes, config.proxies, address=t.from_address)
121
133
  if res.is_err():
122
134
  logger.error(f"{t.log_prefix}: nonce error: {res.unwrap_err()}")
@@ -126,6 +138,7 @@ async def _get_nonce(t: Transfer, config: Config) -> int | None:
126
138
 
127
139
 
128
140
  async def _calc_max_fee(t: Transfer, config: Config) -> int | None:
141
+ """Calculate the max fee, fetching base_fee from the network if needed."""
129
142
  if "base_fee" in config.max_fee.lower():
130
143
  base_fee_res = await retry.get_base_fee_per_gas(5, config.nodes, config.proxies)
131
144
  if base_fee_res.is_err():
@@ -137,6 +150,7 @@ async def _calc_max_fee(t: Transfer, config: Config) -> int | None:
137
150
 
138
151
 
139
152
  def check_max_fee_limit(t: Transfer, config: Config, max_fee: int) -> bool:
153
+ """Return False if max_fee exceeds the configured limit."""
140
154
  if config.max_fee_limit:
141
155
  max_fee_limit = calcs.calc_eth_expression(config.max_fee_limit)
142
156
  if max_fee > max_fee_limit:
@@ -148,6 +162,7 @@ def check_max_fee_limit(t: Transfer, config: Config, max_fee: int) -> bool:
148
162
 
149
163
 
150
164
  async def _calc_gas(t: Transfer, config: Config) -> int | None:
165
+ """Calculate the gas limit, optionally using on-chain estimation."""
151
166
  variables: dict[str, int] | None = None
152
167
  if "estimate" in config.gas.lower():
153
168
  if config.token:
@@ -172,6 +187,7 @@ async def _calc_gas(t: Transfer, config: Config) -> int | None:
172
187
 
173
188
 
174
189
  async def _calc_eth_value(t: Transfer, max_fee: int, gas: int, config: Config) -> int | None:
190
+ """Calculate the ETH transfer value, subtracting gas costs if using balance."""
175
191
  value_expression = t.value.lower()
176
192
  variables: dict[str, int] | None = None
177
193
  if "balance" in value_expression:
@@ -189,6 +205,7 @@ async def _calc_eth_value(t: Transfer, max_fee: int, gas: int, config: Config) -
189
205
 
190
206
 
191
207
  async def _calc_token_value(t: Transfer, config: Config) -> int | None:
208
+ """Calculate the ERC-20 token transfer value from a value expression."""
192
209
  value_expression = t.value.lower()
193
210
  variables: dict[str, int] | None = None
194
211
  if "balance" in value_expression:
@@ -202,13 +219,14 @@ async def _calc_token_value(t: Transfer, config: Config) -> int | None:
202
219
 
203
220
 
204
221
  async def _calc_value(t: Transfer, max_fee: int, gas: int, config: Config) -> int | None:
222
+ """Calculate the transfer value, dispatching to ETH or token calculation."""
205
223
  if config.token:
206
224
  return await _calc_token_value(t, config)
207
225
  return await _calc_eth_value(t, max_fee, gas, config)
208
226
 
209
227
 
210
228
  def _check_value_min_limit(t: Transfer, value: int, config: Config) -> bool:
211
- """Returns False if the transfer should be skipped."""
229
+ """Return False if the transfer should be skipped."""
212
230
  if config.value_min_limit:
213
231
  if config.token:
214
232
  value_min_limit = calcs.calc_token_expression(config.value_min_limit, config.token_decimals)
@@ -220,6 +238,7 @@ def _check_value_min_limit(t: Transfer, value: int, config: Config) -> bool:
220
238
 
221
239
 
222
240
  async def _transfer(t: Transfer, config: Config, cmd_params: TransferCmdParams) -> None:
241
+ """Execute a single transfer: calculate params, sign, send, and wait for receipt."""
223
242
  nonce = await _get_nonce(t, config)
224
243
  if nonce is None:
225
244
  return
@@ -268,6 +287,7 @@ async def _transfer(t: Transfer, config: Config, cmd_params: TransferCmdParams)
268
287
 
269
288
 
270
289
  async def wait_tx_status(t: Transfer, tx_hash: str, config: Config) -> Literal["OK", "FAIL", "TIMEOUT"]:
290
+ """Poll for a transaction receipt until success, failure, or timeout."""
271
291
  logger.debug(f"{t.log_prefix}: waiting for receipt, tx_hash={tx_hash}")
272
292
  started_at = time.perf_counter()
273
293
  count = 0
@@ -286,6 +306,7 @@ async def wait_tx_status(t: Transfer, tx_hash: str, config: Config) -> Literal["
286
306
  async def _send_tx(
287
307
  *, transfer: Transfer, nonce: int, max_fee: int, priority_fee: int, gas: int, value: int, config: Config
288
308
  ) -> str | None:
309
+ """Sign and broadcast a transaction, returning the tx hash or None on failure."""
289
310
  debug_tx_params = {
290
311
  "nonce": nonce,
291
312
  "max_fee": max_fee,
@@ -330,6 +351,7 @@ async def _send_tx(
330
351
 
331
352
 
332
353
  def _print_transfers(config: Config) -> None:
354
+ """Print the configured transfers as a table."""
333
355
  table = Table("n", "from_address", "to_address", "value", title="transfers")
334
356
  for count, transfer in enumerate(config.transfers, start=1):
335
357
  table.add_row(str(count), transfer.from_address, transfer.to_address, transfer.value)
@@ -338,6 +360,7 @@ def _print_transfers(config: Config) -> None:
338
360
 
339
361
 
340
362
  async def _print_balances(config: Config) -> None:
363
+ """Fetch and print ETH (and token) balances for all transfer routes."""
341
364
  if config.token:
342
365
  headers = ["n", "from_address", "nonce", "eth", "t", "to_address", "nonce", "eth", "t"]
343
366
  else:
@@ -414,6 +437,7 @@ async def _print_balances(config: Config) -> None:
414
437
 
415
438
 
416
439
  def _value_with_suffix(value: int, config: Config) -> str:
440
+ """Format a value with its unit suffix (eth or t) for display."""
417
441
  if config.token:
418
442
  return f"{from_wei(value, 't', config.round_ndigits, decimals=config.token_decimals)}t"
419
443
  return f"{from_wei(value, 'eth', config.round_ndigits)}eth"
@@ -0,0 +1 @@
1
+ """Wallet-related CLI commands."""
@@ -1,12 +1,15 @@
1
+ """CLI command: generate or derive accounts from a mnemonic."""
2
+
1
3
  from pathlib import Path
2
4
  from typing import Any
3
5
 
4
- import mm_print
6
+ from mm_print import print_json
5
7
 
6
8
  from mm_eth.account import derive_accounts, generate_mnemonic
7
9
 
8
10
 
9
11
  def run(mnemonic: str, passphrase: str, words: int, derivation_path: str, limit: int, print_path: bool, save_file: str) -> None: # nosec
12
+ """Generate a mnemonic (if not provided) and derive accounts from it."""
10
13
  result: dict[str, Any] = {}
11
14
  if not mnemonic:
12
15
  mnemonic = generate_mnemonic(num_words=words)
@@ -19,7 +22,7 @@ def run(mnemonic: str, passphrase: str, words: int, derivation_path: str, limit:
19
22
  if print_path:
20
23
  new_account["path"] = acc.path
21
24
  result["accounts"].append(new_account)
22
- mm_print.json(result)
25
+ print_json(result)
23
26
 
24
27
  if save_file:
25
28
  data = [acc["address"] + "\t" + acc["private"] for acc in result["accounts"]]
@@ -1,11 +1,15 @@
1
- import mm_print
1
+ """CLI command: derive address from a private key."""
2
+
3
+ from mm_print import print_plain
2
4
 
3
5
  from mm_eth import account
6
+ from mm_eth.cli.cli_utils import fatal
4
7
 
5
8
 
6
9
  def run(private_key: str) -> None:
10
+ """Print the Ethereum address for the given private key."""
7
11
  res = account.private_to_address(private_key)
8
12
  if res.is_ok():
9
- mm_print.plain(res.unwrap())
13
+ print_plain(res.unwrap())
10
14
  else:
11
- mm_print.exit_with_error(f"invalid private key: '{private_key}'")
15
+ fatal(f"invalid private key: '{private_key}'")
mm_eth/cli/rpc_helpers.py CHANGED
@@ -1,3 +1,5 @@
1
+ """RPC helper functions with logging for CLI commands."""
2
+
1
3
  import logging
2
4
 
3
5
  from mm_web3 import Nodes, Proxies
@@ -11,6 +13,7 @@ logger = logging.getLogger(__name__)
11
13
  async def get_nonce_with_logging(
12
14
  log_prefix: str | None, retries: int, nodes: Nodes, proxies: Proxies, *, address: str
13
15
  ) -> int | None:
16
+ """Fetch the nonce for an address, logging errors and debug info."""
14
17
  res = await retry.eth_get_transaction_count(retries, nodes, proxies, address=address)
15
18
  prefix = log_prefix or address
16
19
  if res.is_err():
@@ -21,6 +24,7 @@ async def get_nonce_with_logging(
21
24
 
22
25
 
23
26
  async def get_base_fee_with_logging(log_prefix: str | None, retries: int, nodes: Nodes, proxies: Proxies) -> int | None:
27
+ """Fetch the base fee, logging errors and debug info."""
24
28
  prefix = get_log_prefix(log_prefix)
25
29
  res = await retry.get_base_fee_per_gas(retries, nodes, proxies)
26
30
  if res.is_err():
@@ -34,6 +38,7 @@ async def get_base_fee_with_logging(log_prefix: str | None, retries: int, nodes:
34
38
  async def calc_max_fee_with_logging(
35
39
  log_prefix: str | None, retries: int, nodes: Nodes, proxies: Proxies, *, max_fee_expression: str
36
40
  ) -> int | None:
41
+ """Evaluate a max fee expression, fetching base_fee from the network if needed."""
37
42
  if "base_fee" in max_fee_expression.lower():
38
43
  base_fee = await get_base_fee_with_logging(log_prefix, retries, nodes, proxies)
39
44
  if base_fee is None:
@@ -44,6 +49,7 @@ async def calc_max_fee_with_logging(
44
49
 
45
50
 
46
51
  def get_log_prefix(log_prefix: str | None) -> str:
52
+ """Format a log prefix string, appending ': ' if non-empty."""
47
53
  prefix = log_prefix or ""
48
54
  if prefix:
49
55
  prefix += ": "
mm_eth/cli/validators.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Pydantic validators for Ethereum addresses, keys, and expressions."""
2
+
1
3
  from collections.abc import Callable
2
4
 
3
5
  import eth_utils
@@ -9,6 +11,7 @@ SUFFIX_DECIMALS = {"eth": 18, "gwei": 9, "ether": 18}
9
11
 
10
12
 
11
13
  def address_from_private(private_key: str) -> str:
14
+ """Derive a lowercase Ethereum address from a private key."""
12
15
  res = account.private_to_address(private_key)
13
16
  if res.is_err():
14
17
  raise ValueError("invalid private key")
@@ -16,30 +19,39 @@ def address_from_private(private_key: str) -> str:
16
19
 
17
20
 
18
21
  class Validators(ConfigValidators):
22
+ """Ethereum-specific config validators for addresses, keys, and expressions."""
23
+
19
24
  @staticmethod
20
25
  def valid_eth_expression(var_name: str | None = None) -> Callable[[str], str]:
26
+ """Return a validator for expressions with ETH unit suffixes."""
21
27
  return ConfigValidators.expression_with_vars(var_name, SUFFIX_DECIMALS)
22
28
 
23
29
  @staticmethod
24
30
  def valid_token_expression(var_name: str | None = None) -> Callable[[str], str]:
31
+ """Return a validator for expressions with token unit suffix."""
25
32
  return ConfigValidators.expression_with_vars(var_name, {"t": 6})
26
33
 
27
34
  @staticmethod
28
35
  def valid_eth_or_token_expression(var_name: str | None = None) -> Callable[[str], str]:
36
+ """Return a validator for expressions with ETH or token unit suffixes."""
29
37
  return ConfigValidators.expression_with_vars(var_name, SUFFIX_DECIMALS | {"t": 6})
30
38
 
31
39
  @staticmethod
32
40
  def eth_transfers() -> Callable[[str], list[Transfer]]:
41
+ """Return a validator for Ethereum transfer definitions."""
33
42
  return ConfigValidators.transfers(is_address=eth_utils.is_address, lowercase=True)
34
43
 
35
44
  @staticmethod
36
45
  def eth_private_keys() -> Callable[[str], PrivateKeyMap]:
46
+ """Return a validator for Ethereum private key mappings."""
37
47
  return ConfigValidators.private_keys(address_from_private)
38
48
 
39
49
  @staticmethod
40
50
  def eth_address() -> Callable[[str], str]:
51
+ """Return a validator for a single Ethereum address."""
41
52
  return ConfigValidators.address(eth_utils.is_address, lowercase=True)
42
53
 
43
54
  @staticmethod
44
55
  def eth_addresses(unique: bool) -> Callable[[str], list[str]]:
56
+ """Return a validator for a list of Ethereum addresses."""
45
57
  return ConfigValidators.addresses(unique, lowercase=True, is_address=eth_utils.is_address)
mm_eth/converters.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Wei/ether/gwei/token unit conversion utilities."""
2
+
1
3
  from decimal import Decimal, localcontext
2
4
  from typing import cast
3
5
 
@@ -6,6 +8,7 @@ from web3.types import Wei
6
8
 
7
9
 
8
10
  def to_wei(value: str | int | Decimal, decimals: int | None = None) -> Wei:
11
+ """Convert a value with optional unit suffix (gwei, ether, eth, t) to wei."""
9
12
  if isinstance(value, int):
10
13
  return Wei(value)
11
14
  if isinstance(value, Decimal):
@@ -36,6 +39,7 @@ def to_wei(value: str | int | Decimal, decimals: int | None = None) -> Wei:
36
39
 
37
40
 
38
41
  def from_wei(value: int, unit: str, round_ndigits: int | None = None, decimals: int | None = None) -> Decimal:
42
+ """Convert a wei value to the specified unit (eth, gwei, t) with optional rounding."""
39
43
  if value == 0:
40
44
  return Decimal(0)
41
45
 
mm_eth/deploy.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Smart contract deployment helpers."""
2
+
1
3
  import eth_utils
2
4
  import rlp
3
5
  from eth_utils import keccak
@@ -6,6 +8,7 @@ from mm_eth import abi
6
8
 
7
9
 
8
10
  def get_deploy_contract_data(contract_bin: str, constructor_types: list[str], constructor_values: list[object]) -> str:
11
+ """Build deployment bytecode by appending ABI-encoded constructor arguments to contract binary."""
9
12
  constructor_data = ""
10
13
  if constructor_types and constructor_values:
11
14
  constructor_data = abi.encode_data(constructor_types, constructor_values)[2:]
@@ -13,6 +16,7 @@ def get_deploy_contract_data(contract_bin: str, constructor_types: list[str], co
13
16
 
14
17
 
15
18
  def get_contract_address(sender_address: str, nonce: int) -> str:
19
+ """Compute the contract address that would be created by a given sender and nonce."""
16
20
  sender_bytes = eth_utils.to_bytes(hexstr=sender_address)
17
21
  raw = rlp.encode([sender_bytes, nonce])
18
22
  h = keccak(raw)
mm_eth/erc20.py CHANGED
@@ -1,3 +1,5 @@
1
+ """ERC-20 token transfer encoding and signing."""
2
+
1
3
  import eth_abi
2
4
  import eth_utils
3
5
  from eth_typing import HexStr
@@ -10,6 +12,7 @@ TRANSFER_TOPIC = HexStr("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55
10
12
 
11
13
 
12
14
  def encode_transfer_input_data(recipient: str, value: int) -> str:
15
+ """Encode ERC-20 transfer(address,uint256) call data."""
13
16
  recipient = eth_utils.to_checksum_address(recipient)
14
17
  input_data = eth_utils.to_bytes(hexstr=HexStr(TRANSFER_METHOD)) + eth_abi.encode(["address", "uint256"], [recipient, value])
15
18
  return eth_utils.to_hex(input_data)
@@ -27,6 +30,7 @@ def sign_transfer_tx(
27
30
  private_key: str,
28
31
  chain_id: int,
29
32
  ) -> SignedTx:
33
+ """Sign an ERC-20 transfer transaction (EIP-1559)."""
30
34
  input_data = encode_transfer_input_data(recipient_address, value)
31
35
  return tx.sign_tx(
32
36
  nonce=nonce,
mm_eth/retry.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Retry-enabled wrappers around RPC calls using node and proxy rotation."""
2
+
1
3
  from typing import Any, Literal
2
4
 
3
5
  from eth_typing import BlockIdentifier
@@ -14,18 +16,21 @@ TIMEOUT = 5.0
14
16
 
15
17
 
16
18
  async def eth_block_number(retries: int, nodes: Nodes, proxies: Proxies, *, timeout: float = TIMEOUT) -> Result[int]:
19
+ """Return the current block number with retry logic."""
17
20
  return await retry_with_node_and_proxy(
18
21
  retries, nodes, proxies, lambda node, proxy: rpc.eth_block_number(node, timeout, proxy)
19
22
  )
20
23
 
21
24
 
22
25
  async def eth_get_balance(retries: int, nodes: Nodes, proxies: Proxies, *, address: str, timeout: float = TIMEOUT) -> Result[int]:
26
+ """Return the ETH balance of an address with retry logic."""
23
27
  return await retry_with_node_and_proxy(
24
28
  retries, nodes, proxies, lambda node, proxy: rpc.eth_get_balance(node, address, timeout, proxy)
25
29
  )
26
30
 
27
31
 
28
32
  async def eth_chain_id(retries: int, nodes: Nodes, proxies: Proxies, *, timeout: float = TIMEOUT) -> Result[int]:
33
+ """Return the chain ID with retry logic."""
29
34
  return await retry_with_node_and_proxy(retries, nodes, proxies, lambda node, proxy: rpc.eth_chain_id(node, timeout, proxy))
30
35
 
31
36
 
@@ -38,6 +43,7 @@ async def eth_get_block_by_number(
38
43
  full_transaction: bool = False,
39
44
  timeout: float = TIMEOUT,
40
45
  ) -> Result[dict[str, Any]]:
46
+ """Return block data for a given block number with retry logic."""
41
47
  return await retry_with_node_and_proxy(
42
48
  retries,
43
49
  nodes,
@@ -49,6 +55,7 @@ async def eth_get_block_by_number(
49
55
  async def eth_get_transaction_count(
50
56
  retries: int, nodes: Nodes, proxies: Proxies, *, address: str, timeout: float = TIMEOUT
51
57
  ) -> Result[int]:
58
+ """Return the nonce for an address with retry logic."""
52
59
  return await retry_with_node_and_proxy(
53
60
  retries, nodes, proxies, lambda node, proxy: rpc.eth_get_transaction_count(node, address, timeout, proxy)
54
61
  )
@@ -66,6 +73,7 @@ async def eth_estimate_gas(
66
73
  type_: Literal["0x0", "0x2"] | None = None,
67
74
  timeout: float = TIMEOUT,
68
75
  ) -> Result[int]:
76
+ """Estimate gas for a transaction with retry logic."""
69
77
  return await retry_with_node_and_proxy(
70
78
  retries, nodes, proxies, lambda node, proxy: rpc.eth_estimate_gas(node, from_, to, value, data, type_, timeout, proxy)
71
79
  )
@@ -74,6 +82,7 @@ async def eth_estimate_gas(
74
82
  async def eth_send_raw_transaction(
75
83
  retries: int, nodes: Nodes, proxies: Proxies, *, raw_tx: str, timeout: float = TIMEOUT
76
84
  ) -> Result[str]:
85
+ """Broadcast a raw transaction with retry logic."""
77
86
  return await retry_with_node_and_proxy(
78
87
  retries, nodes, proxies, lambda node, proxy: rpc.eth_send_raw_transaction(node, raw_tx, timeout, proxy)
79
88
  )
@@ -82,6 +91,7 @@ async def eth_send_raw_transaction(
82
91
  async def eth_get_transaction_receipt(
83
92
  retries: int, nodes: Nodes, proxies: Proxies, *, tx_hash: str, timeout: float = TIMEOUT
84
93
  ) -> Result[TxReceipt]:
94
+ """Fetch a transaction receipt with retry logic."""
85
95
  return await retry_with_node_and_proxy(
86
96
  retries, nodes, proxies, lambda node, proxy: rpc.eth_get_transaction_receipt(node, tx_hash, timeout, proxy)
87
97
  )
@@ -96,6 +106,7 @@ async def eth_get_transaction_receipt(
96
106
  async def erc20_balance(
97
107
  retries: int, nodes: Nodes, proxies: Proxies, *, token: str, wallet: str, timeout: float = TIMEOUT
98
108
  ) -> Result[int]:
109
+ """Return an ERC-20 token balance with retry logic."""
99
110
  return await retry_with_node_and_proxy(
100
111
  retries,
101
112
  nodes,
@@ -105,18 +116,21 @@ async def erc20_balance(
105
116
 
106
117
 
107
118
  async def erc20_name(retries: int, nodes: Nodes, proxies: Proxies, *, token: str, timeout: float = TIMEOUT) -> Result[str]:
119
+ """Return the name of an ERC-20 token with retry logic."""
108
120
  return await retry_with_node_and_proxy(
109
121
  retries, nodes, proxies, lambda node, proxy: rpc.erc20_name(node, token, timeout, proxy)
110
122
  )
111
123
 
112
124
 
113
125
  async def erc20_symbol(retries: int, nodes: Nodes, proxies: Proxies, *, token: str, timeout: float = TIMEOUT) -> Result[str]:
126
+ """Return the symbol of an ERC-20 token with retry logic."""
114
127
  return await retry_with_node_and_proxy(
115
128
  retries, nodes, proxies, lambda node, proxy: rpc.erc20_symbol(node, token, timeout, proxy)
116
129
  )
117
130
 
118
131
 
119
132
  async def erc20_decimals(retries: int, nodes: Nodes, proxies: Proxies, *, token: str, timeout: float = TIMEOUT) -> Result[int]:
133
+ """Return the decimals of an ERC-20 token with retry logic."""
120
134
  return await retry_with_node_and_proxy(
121
135
  retries, nodes, proxies, lambda node, proxy: rpc.erc20_decimals(node, token, timeout, proxy)
122
136
  )
@@ -128,6 +142,7 @@ async def erc20_decimals(retries: int, nodes: Nodes, proxies: Proxies, *, token:
128
142
 
129
143
 
130
144
  async def ens_name(retries: int, nodes: Nodes, proxies: Proxies, *, address: str, timeout: float = TIMEOUT) -> Result[str | None]:
145
+ """Perform reverse ENS resolution with retry logic."""
131
146
  return await retry_with_node_and_proxy(
132
147
  retries, nodes, proxies, lambda node, proxy: rpc.ens_name(node, address, timeout, proxy)
133
148
  )
@@ -139,12 +154,14 @@ async def ens_name(retries: int, nodes: Nodes, proxies: Proxies, *, address: str
139
154
 
140
155
 
141
156
  async def get_base_fee_per_gas(retries: int, nodes: Nodes, proxies: Proxies, *, timeout: float = TIMEOUT) -> Result[int]:
157
+ """Return the base fee per gas from the latest block with retry logic."""
142
158
  return await retry_with_node_and_proxy(
143
159
  retries, nodes, proxies, lambda node, proxy: rpc.get_base_fee_per_gas(node, timeout, proxy)
144
160
  )
145
161
 
146
162
 
147
163
  async def get_tx_status(retries: int, nodes: Nodes, proxies: Proxies, *, tx_hash: str, timeout: float = TIMEOUT) -> Result[int]:
164
+ """Return the transaction status (0=fail, 1=success) with retry logic."""
148
165
  return await retry_with_node_and_proxy(
149
166
  retries, nodes, proxies, lambda node, proxy: rpc.get_tx_status(node, tx_hash, timeout, proxy)
150
167
  )