mm-balance 0.1.14__tar.gz → 0.1.16__tar.gz

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 (31) hide show
  1. {mm_balance-0.1.14 → mm_balance-0.1.16}/PKG-INFO +1 -1
  2. {mm_balance-0.1.14 → mm_balance-0.1.16}/justfile +1 -1
  3. {mm_balance-0.1.14 → mm_balance-0.1.16}/pyproject.toml +4 -4
  4. mm_balance-0.1.16/src/mm_balance/cli.py +85 -0
  5. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/config/example.yml +3 -2
  6. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/config.py +4 -1
  7. mm_balance-0.1.16/src/mm_balance/output/formats/json_format.py +28 -0
  8. mm_balance-0.1.16/src/mm_balance/output/formats/table_format.py +119 -0
  9. mm_balance-0.1.16/src/mm_balance/output/utils.py +20 -0
  10. mm_balance-0.1.16/src/mm_balance/result.py +131 -0
  11. mm_balance-0.1.14/src/mm_balance/balances.py → mm_balance-0.1.16/src/mm_balance/workers.py +28 -30
  12. mm_balance-0.1.16/tests/conftest.py +0 -0
  13. {mm_balance-0.1.14 → mm_balance-0.1.16}/uv.lock +72 -72
  14. mm_balance-0.1.14/src/mm_balance/cli.py +0 -64
  15. mm_balance-0.1.14/src/mm_balance/output.py +0 -109
  16. mm_balance-0.1.14/src/mm_balance/total.py +0 -122
  17. {mm_balance-0.1.14 → mm_balance-0.1.16}/.gitignore +0 -0
  18. {mm_balance-0.1.14 → mm_balance-0.1.16}/README.md +0 -0
  19. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/__init__.py +0 -0
  20. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/constants.py +0 -0
  21. {mm_balance-0.1.14/src/mm_balance/rpc → mm_balance-0.1.16/src/mm_balance/output}/__init__.py +0 -0
  22. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/price.py +0 -0
  23. {mm_balance-0.1.14/tests → mm_balance-0.1.16/src/mm_balance/rpc}/__init__.py +0 -0
  24. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/rpc/aptos.py +0 -0
  25. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/rpc/btc.py +0 -0
  26. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/rpc/evm.py +0 -0
  27. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/rpc/solana.py +0 -0
  28. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/token_decimals.py +0 -0
  29. {mm_balance-0.1.14 → mm_balance-0.1.16}/src/mm_balance/utils.py +0 -0
  30. /mm_balance-0.1.14/tests/conftest.py → /mm_balance-0.1.16/tests/__init__.py +0 -0
  31. {mm_balance-0.1.14 → mm_balance-0.1.16}/tests/test_dummy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mm-balance
3
- Version: 0.1.14
3
+ Version: 0.1.16
4
4
  Requires-Python: >=3.12
5
5
  Requires-Dist: mm-aptos==0.1.2
6
6
  Requires-Dist: mm-btc==0.1.0
@@ -20,7 +20,7 @@ lint: format
20
20
  uv run mypy src
21
21
 
22
22
  audit:
23
- uv run pip-audit
23
+ uv run pip-audit --ignore-vuln GHSA-f9vj-2wh5-fj8j --ignore-vuln GHSA-q34m-jh98-gwm2
24
24
  uv run bandit -r -c "pyproject.toml" src
25
25
 
26
26
  publish: build
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mm-balance"
3
- version = "0.1.14"
3
+ version = "0.1.16"
4
4
  description = ""
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -22,11 +22,11 @@ dev-dependencies = [
22
22
  "pytest~=8.3.3",
23
23
  "pytest-xdist~=3.6.1",
24
24
  "pytest-httpserver~=1.1.0",
25
- "coverage~=7.6.3",
26
- "ruff~=0.7.0",
25
+ "coverage~=7.6.4",
26
+ "ruff~=0.7.3",
27
27
  "pip-audit~=2.7.0",
28
28
  "bandit~=1.7.10",
29
- "mypy~=1.12.1",
29
+ "mypy~=1.13.0",
30
30
  "types-python-dateutil~=2.9.0.20241003",
31
31
  "types-PyYAML~=6.0.12.20240917",
32
32
  ]
@@ -0,0 +1,85 @@
1
+ import getpass
2
+ import pathlib
3
+ import pkgutil
4
+ from typing import Annotated
5
+
6
+ import typer
7
+ from mm_std import PrintFormat, fatal
8
+
9
+ from mm_balance.config import Config
10
+ from mm_balance.constants import NETWORKS
11
+ from mm_balance.output.formats import json_format, table_format
12
+ from mm_balance.price import Prices, get_prices
13
+ from mm_balance.result import create_balances_result
14
+ from mm_balance.token_decimals import get_token_decimals
15
+ from mm_balance.workers import Workers
16
+
17
+ app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
18
+
19
+
20
+ def example_callback(value: bool) -> None:
21
+ if value:
22
+ data = pkgutil.get_data(__name__, "config/example.yml")
23
+ typer.echo(data)
24
+ raise typer.Exit
25
+
26
+
27
+ def networks_callback(value: bool) -> None:
28
+ if value:
29
+ for network in NETWORKS:
30
+ typer.echo(network)
31
+ raise typer.Exit
32
+
33
+
34
+ @app.command()
35
+ def cli(
36
+ config_path: Annotated[pathlib.Path, typer.Argument()],
37
+ print_format: Annotated[PrintFormat | None, typer.Option("--format", "-f", help="Print format.")] = None,
38
+ skip_empty: Annotated[bool | None, typer.Option("--skip-empty", "-s", help="Skip empty balances.")] = None,
39
+ debug: Annotated[bool | None, typer.Option("--debug", "-d", help="Print debug info.")] = None,
40
+ price: Annotated[bool | None, typer.Option("--price/--no-price", help="Print prices.")] = None,
41
+ _example: Annotated[bool | None, typer.Option("--example", callback=example_callback, help="Print a config example.")] = None,
42
+ _networks: Annotated[
43
+ bool | None, typer.Option("--networks", callback=networks_callback, help="Print supported networks.")
44
+ ] = None,
45
+ ) -> None:
46
+ zip_password = "" # nosec
47
+ if config_path.name.endswith(".zip"):
48
+ zip_password = getpass.getpass("zip password")
49
+ config = Config.read_config(config_path, zip_password=zip_password)
50
+
51
+ if print_format is not None:
52
+ config.print_format = print_format
53
+ if debug is not None:
54
+ config.print_debug = debug
55
+ if skip_empty is not None:
56
+ config.skip_empty = skip_empty
57
+ if price is not None:
58
+ config.price = price
59
+
60
+ if config.print_debug and config.print_format is PrintFormat.TABLE:
61
+ table_format.print_nodes(config)
62
+ table_format.print_proxy_count(config)
63
+
64
+ token_decimals = get_token_decimals(config)
65
+ if config.print_debug and config.print_format is PrintFormat.TABLE:
66
+ table_format.print_token_decimals(token_decimals)
67
+
68
+ prices = get_prices(config) if config.price else Prices()
69
+ if config.print_format is PrintFormat.TABLE:
70
+ table_format.print_prices(config, prices)
71
+
72
+ workers = Workers(config, token_decimals)
73
+ workers.process()
74
+
75
+ result = create_balances_result(config, prices, workers)
76
+ if config.print_format is PrintFormat.TABLE:
77
+ table_format.print_result(config, result, workers)
78
+ elif config.print_format is PrintFormat.JSON:
79
+ json_format.print_result(config, token_decimals, prices, workers, result)
80
+ else:
81
+ fatal("Unsupported print format")
82
+
83
+
84
+ if __name__ == "__main__":
85
+ app()
@@ -39,11 +39,11 @@ coins:
39
39
  addresses: binance_eth
40
40
 
41
41
  - ticker: USDC
42
- comment: okx aptos
42
+ comment: swap.thala.apt
43
43
  network: aptos
44
44
  token_address: 0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC
45
45
  token_decimals: 6
46
- addresses: "0x834d639b10d20dcb894728aa4b9b572b2ea2d97073b10eacb111f338b20ea5d7" # for a single line it's necessary to use quotes
46
+ addresses: "0x48271d39d0b05bd6efca2278f22277d6fcc375504f9839fd73f74ace240861af" # for a single line it's necessary to use quotes
47
47
 
48
48
 
49
49
  addresses:
@@ -70,4 +70,5 @@ addresses:
70
70
  #price: yes
71
71
  #skip_empty: no
72
72
  #print_debug: no
73
+ #print_format: table # table, json
73
74
  #format_number_separator: ","
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
3
4
  from decimal import Decimal
4
5
  from pathlib import Path
5
- from typing import Self
6
+ from typing import Self, cast
6
7
 
7
8
  import pydash
8
9
  from mm_std import BaseConfig, PrintFormat, fatal, hr
@@ -97,6 +98,8 @@ class Config(BaseConfig):
97
98
  # load from proxies_url
98
99
  if self.proxies_url is not None:
99
100
  self.proxies = get_proxies(self.proxies_url)
101
+ elif os.getenv("MM_BALANCE_PROXIES_URL"):
102
+ self.proxies = get_proxies(cast(str, os.getenv("MM_BALANCE_PROXIES_URL")))
100
103
 
101
104
  # load addresses from address_group
102
105
  for group in self.groups:
@@ -0,0 +1,28 @@
1
+ from mm_std import print_json
2
+
3
+ from mm_balance.config import Config
4
+ from mm_balance.price import Prices
5
+ from mm_balance.result import BalancesResult
6
+ from mm_balance.token_decimals import TokenDecimals
7
+ from mm_balance.workers import Workers
8
+
9
+
10
+ def print_result(config: Config, token_decimals: TokenDecimals, prices: Prices, workers: Workers, result: BalancesResult) -> None:
11
+ data: dict[str, object] = {}
12
+ if config.print_debug:
13
+ data["nodes"] = config.nodes
14
+ data["token_decimals"] = token_decimals
15
+ data["proxies"] = len(config.proxies)
16
+ if config.price:
17
+ data["prices"] = prices
18
+
19
+ data["groups"] = result.groups
20
+ data["total"] = result.total
21
+ if config.has_share():
22
+ data["total_share"] = result.total_share
23
+
24
+ errors = workers.get_errors()
25
+ if errors:
26
+ data["errors"] = errors
27
+
28
+ print_json(data)
@@ -0,0 +1,119 @@
1
+ from decimal import Decimal
2
+
3
+ from mm_std import print_table
4
+
5
+ from mm_balance.config import Config
6
+ from mm_balance.output.utils import format_number
7
+ from mm_balance.price import Prices
8
+ from mm_balance.result import BalancesResult, GroupResult, Total
9
+ from mm_balance.token_decimals import TokenDecimals
10
+ from mm_balance.workers import Workers
11
+
12
+
13
+ def print_nodes(config: Config) -> None:
14
+ rows = []
15
+ for network, nodes in config.nodes.items():
16
+ rows.append([network, "\n".join(nodes)])
17
+ print_table("Nodes", ["network", "nodes"], rows)
18
+
19
+
20
+ def print_proxy_count(config: Config) -> None:
21
+ print_table("Proxies", ["count"], [[len(config.proxies)]])
22
+
23
+
24
+ def print_token_decimals(token_decimals: TokenDecimals) -> None:
25
+ rows = []
26
+ for network, decimals in token_decimals.items():
27
+ rows.append([network, decimals])
28
+ print_table("Token Decimals", ["network", "decimals"], rows)
29
+
30
+
31
+ def print_prices(config: Config, prices: Prices) -> None:
32
+ if config.price:
33
+ rows = []
34
+ for ticker, price in prices.items():
35
+ rows.append([ticker, format_number(round(price, config.round_ndigits), config.format_number_separator, "$")])
36
+ print_table("Prices", ["coin", "usd"], rows)
37
+
38
+
39
+ def print_result(config: Config, result: BalancesResult, workers: Workers) -> None:
40
+ for group in result.groups:
41
+ _print_group(config, group)
42
+
43
+ _print_total(config, result.total, False)
44
+ if config.has_share():
45
+ _print_total(config, result.total_share, True)
46
+
47
+ _print_errors(config, workers)
48
+
49
+
50
+ def _print_errors(config: Config, workers: Workers) -> None:
51
+ error_tasks = workers.get_errors()
52
+ if not error_tasks:
53
+ return
54
+ rows = []
55
+ for task in error_tasks:
56
+ group = config.groups[task.group_index]
57
+ rows.append([group.ticker + " / " + group.network, task.wallet_address, task.balance.err]) # type: ignore[union-attr]
58
+ print_table("Errors", ["coin", "address", "error"], rows)
59
+
60
+
61
+ def _print_total(config: Config, total: Total, is_share_total: bool) -> None:
62
+ table_name = "Total, share" if is_share_total else "Total"
63
+ headers = ["coin", "balance"]
64
+
65
+ rows = []
66
+ for ticker, balance in total.coin_balances.items():
67
+ balance_str = format_number(balance, config.format_number_separator)
68
+ row = [ticker, balance_str]
69
+ if config.price:
70
+ usd_value_str = format_number(total.coin_usd_values[ticker], config.format_number_separator, "$")
71
+ portfolio_share = total.portfolio_share[ticker]
72
+ row += [usd_value_str, f"{portfolio_share}%"]
73
+ rows.append(row)
74
+
75
+ if config.price:
76
+ headers += ["usd", "portfolio_share"]
77
+ if total.stablecoin_sum > 0:
78
+ rows.append(["stablecoin_sum", format_number(total.stablecoin_sum, config.format_number_separator, "$")])
79
+ rows.append(["total_usd_sum", format_number(total.total_usd_sum, config.format_number_separator, "$")])
80
+
81
+ print_table(table_name, headers, rows)
82
+
83
+
84
+ def _print_group(config: Config, group: GroupResult) -> None:
85
+ group_name = group.ticker
86
+ if group.comment:
87
+ group_name += " / " + group.comment
88
+ group_name += " / " + group.network
89
+
90
+ rows = []
91
+ for address in group.addresses:
92
+ if isinstance(address.balance, str):
93
+ rows.append([address.address, address.balance])
94
+ else:
95
+ if config.skip_empty and address.balance.balance == Decimal(0):
96
+ continue
97
+ balance_str = format_number(address.balance.balance, config.format_number_separator)
98
+ row = [address.address, balance_str]
99
+ if config.price:
100
+ usd_value_str = format_number(address.balance.usd_value, config.format_number_separator, "$")
101
+ row.append(usd_value_str)
102
+ rows.append(row)
103
+
104
+ sum_row = ["sum", format_number(group.balance_sum, config.format_number_separator)]
105
+ if config.price:
106
+ sum_row.append(format_number(group.usd_sum, config.format_number_separator, "$"))
107
+ rows.append(sum_row)
108
+
109
+ if group.share < Decimal(1):
110
+ sum_share_str = format_number(group.balance_sum_share, config.format_number_separator)
111
+ sum_share_row = [f"sum_share, {group.share}", sum_share_str]
112
+ if config.price:
113
+ sum_share_row.append(format_number(group.usd_sum_share, config.format_number_separator, "$"))
114
+ rows.append(sum_share_row)
115
+
116
+ table_headers = ["address", "balance"]
117
+ if config.price:
118
+ table_headers += ["usd"]
119
+ print_table(group_name, table_headers, rows)
@@ -0,0 +1,20 @@
1
+ from decimal import Decimal
2
+
3
+ from rich.progress import BarColumn, MofNCompleteColumn, Progress, TaskID, TextColumn
4
+
5
+
6
+ def format_number(value: Decimal, separator: str, extra: str | None = None) -> str:
7
+ str_value = f"{value:,}".replace(",", separator)
8
+ if extra == "$":
9
+ return "$" + str_value
10
+ elif extra == "%":
11
+ return str_value + "%"
12
+ return str_value
13
+
14
+
15
+ def create_progress_bar(disable: bool) -> Progress:
16
+ return Progress(TextColumn("[progress.description]{task.description}"), BarColumn(), MofNCompleteColumn(), disable=disable)
17
+
18
+
19
+ def create_progress_task(progress: Progress, description: str, total: int) -> TaskID:
20
+ return progress.add_task("[green]" + description, total=total)
@@ -0,0 +1,131 @@
1
+ from collections import defaultdict
2
+ from dataclasses import dataclass
3
+ from decimal import Decimal
4
+
5
+ from mm_std import Ok
6
+
7
+ from mm_balance.config import Config, Group
8
+ from mm_balance.constants import USD_STABLECOINS, Network
9
+ from mm_balance.price import Prices
10
+ from mm_balance.workers import Task, Workers
11
+
12
+
13
+ @dataclass
14
+ class Balance:
15
+ balance: Decimal
16
+ usd_value: Decimal # 0 if config.price is False
17
+
18
+
19
+ @dataclass
20
+ class AddressBalance:
21
+ address: str
22
+ balance: Balance | str # balance value or error message
23
+
24
+
25
+ @dataclass
26
+ class GroupResult:
27
+ ticker: str
28
+ network: Network
29
+ comment: str
30
+ share: Decimal
31
+ addresses: list[AddressBalance]
32
+ balance_sum: Decimal # sum of all balances in the group
33
+ usd_sum: Decimal # sum of all usd values in the group
34
+ balance_sum_share: Decimal # sum of all balances in the group multiplied by share
35
+ usd_sum_share: Decimal # sum of all usd values in the group multiplied by share
36
+
37
+
38
+ @dataclass
39
+ class Total:
40
+ coin_balances: dict[str, Decimal]
41
+ coin_usd_values: dict[str, Decimal]
42
+ portfolio_share: dict[str, Decimal] # ticker -> usd value % from total usd value
43
+ stablecoin_sum: Decimal # sum of usd stablecoins: usdt, usdc, etc..
44
+ total_usd_sum: Decimal # sum of all coins in USD
45
+
46
+
47
+ @dataclass
48
+ class BalancesResult:
49
+ groups: list[GroupResult]
50
+ total: Total
51
+ total_share: Total
52
+
53
+
54
+ def create_balances_result(config: Config, prices: Prices, workers: Workers) -> BalancesResult:
55
+ groups = []
56
+ for group_index, group in enumerate(config.groups):
57
+ tasks = workers.get_group_tasks(group_index, group.network)
58
+ groups.append(_create_group_result(config, group, tasks, prices))
59
+
60
+ total = _create_total(False, groups)
61
+ total_share = _create_total(True, groups)
62
+ return BalancesResult(groups=groups, total=total, total_share=total_share)
63
+
64
+
65
+ def _create_total(use_share: bool, groups: list[GroupResult]) -> Total:
66
+ coin_balances: dict[str, Decimal] = defaultdict(Decimal) # ticker -> balance
67
+ coin_usd_values: dict[str, Decimal] = defaultdict(Decimal) # ticker -> usd value
68
+ portfolio_share: dict[str, Decimal] = defaultdict(Decimal) # ticker -> usd value % from total usd value
69
+ total_usd_sum = Decimal(0)
70
+ stablecoin_sum = Decimal(0)
71
+
72
+ for group in groups:
73
+ balance_value = group.balance_sum_share if use_share else group.balance_sum
74
+ usd_value = group.usd_sum_share if use_share else group.usd_sum
75
+ coin_balances[group.ticker] += balance_value
76
+ coin_usd_values[group.ticker] += usd_value
77
+ if group.ticker in USD_STABLECOINS:
78
+ stablecoin_sum += usd_value # TODO: or balance_value?
79
+ total_usd_sum += usd_value
80
+
81
+ if total_usd_sum > 0:
82
+ for ticker, usd_value in coin_usd_values.items():
83
+ if ticker in USD_STABLECOINS:
84
+ portfolio_share[ticker] = round(stablecoin_sum * 100 / total_usd_sum, 2)
85
+ else:
86
+ portfolio_share[ticker] = round(usd_value * 100 / total_usd_sum, 2)
87
+
88
+ return Total(
89
+ coin_balances=coin_balances,
90
+ coin_usd_values=coin_usd_values,
91
+ portfolio_share=portfolio_share,
92
+ stablecoin_sum=stablecoin_sum,
93
+ total_usd_sum=total_usd_sum,
94
+ )
95
+
96
+
97
+ def _create_group_result(config: Config, group: Group, tasks: list[Task], prices: Prices) -> GroupResult:
98
+ addresses = []
99
+ balance_sum = Decimal(0)
100
+ usd_sum = Decimal(0)
101
+ for task in tasks:
102
+ balance: Balance | str
103
+ if task.balance is None:
104
+ balance = "balance is None! Something went wrong."
105
+ else:
106
+ if isinstance(task.balance, Ok):
107
+ coin_value = task.balance.ok
108
+ usd_value = Decimal(0)
109
+ if group.ticker in prices:
110
+ usd_value = round(coin_value * prices[group.ticker], config.round_ndigits)
111
+ balance = Balance(balance=coin_value, usd_value=usd_value)
112
+ balance_sum += balance.balance
113
+ usd_sum += balance.usd_value
114
+ else:
115
+ balance = task.balance.err
116
+ addresses.append(AddressBalance(address=task.wallet_address, balance=balance))
117
+
118
+ balance_sum_share = balance_sum * group.share
119
+ usd_sum_share = usd_sum * group.share
120
+
121
+ return GroupResult(
122
+ ticker=group.ticker,
123
+ network=group.network,
124
+ comment=group.comment,
125
+ share=group.share,
126
+ addresses=addresses,
127
+ balance_sum=balance_sum,
128
+ usd_sum=usd_sum,
129
+ balance_sum_share=balance_sum_share,
130
+ usd_sum_share=usd_sum_share,
131
+ )
@@ -1,41 +1,39 @@
1
- from __future__ import annotations
2
-
1
+ from dataclasses import dataclass
3
2
  from decimal import Decimal
4
3
 
5
- from mm_std import ConcurrentTasks, Result
6
- from pydantic import BaseModel
4
+ from mm_std import ConcurrentTasks, PrintFormat, Result
7
5
  from rich.progress import TaskID
8
6
 
9
- from mm_balance import output
10
7
  from mm_balance.config import Config
11
8
  from mm_balance.constants import NETWORK_APTOS, NETWORK_BITCOIN, NETWORK_SOLANA, Network
9
+ from mm_balance.output import utils
12
10
  from mm_balance.rpc import aptos, btc, evm, solana
13
11
  from mm_balance.token_decimals import TokenDecimals
14
12
 
15
13
 
16
- class Balances:
17
- class Balance(BaseModel):
18
- group_index: int
19
- address: str
20
- token_address: str | None
21
- balance: Result[Decimal] | None = None
14
+ @dataclass
15
+ class Task:
16
+ group_index: int
17
+ wallet_address: str
18
+ token_address: str | None
19
+ balance: Result[Decimal] | None = None
20
+
22
21
 
22
+ class Workers:
23
23
  def __init__(self, config: Config, token_decimals: TokenDecimals):
24
24
  self.config = config
25
25
  self.token_decimals = token_decimals
26
- self.tasks: dict[Network, list[Balances.Balance]] = {network: [] for network in config.networks()}
27
- self.progress_bar = output.create_progress_bar()
26
+ self.tasks: dict[Network, list[Task]] = {network: [] for network in config.networks()}
27
+ self.progress_bar = utils.create_progress_bar(config.print_format is not PrintFormat.TABLE)
28
28
  self.progress_bar_task: dict[Network, TaskID] = {}
29
29
 
30
30
  for idx, group in enumerate(config.groups):
31
- task_list = [Balances.Balance(group_index=idx, address=a, token_address=group.token_address) for a in group.addresses]
31
+ task_list = [Task(group_index=idx, wallet_address=a, token_address=group.token_address) for a in group.addresses]
32
32
  self.tasks[group.network].extend(task_list)
33
33
 
34
34
  for network in config.networks():
35
35
  if self.tasks[network]:
36
- self.progress_bar_task[network] = output.create_progress_task(
37
- self.progress_bar, network, len(self.tasks[network])
38
- )
36
+ self.progress_bar_task[network] = utils.create_progress_task(self.progress_bar, network, len(self.tasks[network]))
39
37
 
40
38
  def process(self) -> None:
41
39
  with self.progress_bar:
@@ -44,10 +42,22 @@ class Balances:
44
42
  job.add_task(network, self._process_network, args=(network,))
45
43
  job.execute()
46
44
 
45
+ def get_group_tasks(self, group_index: int, network: Network) -> list[Task]:
46
+ # TODO: can we get network by group_index?
47
+ return [b for b in self.tasks[network] if b.group_index == group_index]
48
+
49
+ def get_errors(self) -> list[Task]:
50
+ result = []
51
+ for network in self.tasks:
52
+ for task in self.tasks[network]:
53
+ if task.balance is not None and task.balance.is_err():
54
+ result.append(task)
55
+ return result
56
+
47
57
  def _process_network(self, network: Network) -> None:
48
58
  job = ConcurrentTasks(max_workers=self.config.workers[network])
49
59
  for idx, task in enumerate(self.tasks[network]):
50
- job.add_task(str(idx), self._get_balance, args=(network, task.address, task.token_address))
60
+ job.add_task(str(idx), self._get_balance, args=(network, task.wallet_address, task.token_address))
51
61
  job.execute()
52
62
  # TODO: print job.exceptions if present
53
63
  for idx, _task in enumerate(self.tasks[network]):
@@ -72,15 +82,3 @@ class Balances:
72
82
 
73
83
  self.progress_bar.update(self.progress_bar_task[network], advance=1)
74
84
  return res
75
-
76
- def get_group_balances(self, group_index: int, network: Network) -> list[Balance]:
77
- # TODO: can we get network by group_index?
78
- return [b for b in self.tasks[network] if b.group_index == group_index]
79
-
80
- def get_errors(self) -> list[Balance]:
81
- result = []
82
- for network in self.tasks:
83
- for task in self.tasks[network]:
84
- if task.balance is not None and task.balance.is_err():
85
- result.append(task)
86
- return result
File without changes
@@ -371,40 +371,40 @@ wheels = [
371
371
 
372
372
  [[package]]
373
373
  name = "coverage"
374
- version = "7.6.3"
375
- source = { registry = "https://pypi.org/simple" }
376
- sdist = { url = "https://files.pythonhosted.org/packages/12/50/51ec496dd2ad84ca3e5f67de23f6de630be923dd6f5aed31bb60eda540e5/coverage-7.6.3.tar.gz", hash = "sha256:bb7d5fe92bd0dc235f63ebe9f8c6e0884f7360f88f3411bfed1350c872ef2054", size = 798337 }
377
- wheels = [
378
- { url = "https://files.pythonhosted.org/packages/11/3d/b88ca9d63404ed1d2942236757d876c5fb30ab300fc6e68d6e1e8928dc34/coverage-7.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27bd5f18d8f2879e45724b0ce74f61811639a846ff0e5c0395b7818fae87aec6", size = 206890 },
379
- { url = "https://files.pythonhosted.org/packages/90/d1/11307a6f28dc496ff8ee17258e20ecca1c48b537113146a114aa4e29d4e7/coverage-7.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d546cfa78844b8b9c1c0533de1851569a13f87449897bbc95d698d1d3cb2a30f", size = 207132 },
380
- { url = "https://files.pythonhosted.org/packages/61/5d/be9f27811e767b8bd8b5836c5f05708263cc1f2c24d8244f36a5f7baee75/coverage-7.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9975442f2e7a5cfcf87299c26b5a45266ab0696348420049b9b94b2ad3d40234", size = 240146 },
381
- { url = "https://files.pythonhosted.org/packages/37/0f/0b61bfa7aada98936cc433c8bd2e9f90c3fdc02607bdade04ffa1b8f83ba/coverage-7.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583049c63106c0555e3ae3931edab5669668bbef84c15861421b94e121878d3f", size = 237177 },
382
- { url = "https://files.pythonhosted.org/packages/d3/d4/83dfe1cdabf7f574217ce431c8506c80c6afa5c4056b1159282ba54bb6f2/coverage-7.6.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2341a78ae3a5ed454d524206a3fcb3cec408c2a0c7c2752cd78b606a2ff15af4", size = 239248 },
383
- { url = "https://files.pythonhosted.org/packages/05/0a/f53ab3bf80bcf6a17bece8851d4687cf7e424505cf4ce7a3c4cb41b52d96/coverage-7.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4fb91d5f72b7e06a14ff4ae5be625a81cd7e5f869d7a54578fc271d08d58ae3", size = 238737 },
384
- { url = "https://files.pythonhosted.org/packages/f7/81/74d797dbedf62b3d7f66af3277b7fc6be6430ed45df701e1ae1197ceb70d/coverage-7.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e279f3db904e3b55f520f11f983cc8dc8a4ce9b65f11692d4718ed021ec58b83", size = 237039 },
385
- { url = "https://files.pythonhosted.org/packages/ae/71/1750be153f73eb0e8b9a0f08c8cdb90f6a7c2a25b1795d35e313dd2d78f5/coverage-7.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa23ce39661a3e90eea5f99ec59b763b7d655c2cada10729ed920a38bfc2b167", size = 238703 },
386
- { url = "https://files.pythonhosted.org/packages/f6/2e/a60711bb0adcc849c61d9db9574f5d10419cfc73c43cee26a7de6c92f2e4/coverage-7.6.3-cp312-cp312-win32.whl", hash = "sha256:52ac29cc72ee7e25ace7807249638f94c9b6a862c56b1df015d2b2e388e51dbd", size = 209521 },
387
- { url = "https://files.pythonhosted.org/packages/d9/26/67a744fcc5de4433a1ebae2b227c66f744fb0d17ad4725b47cf24f7a4c2f/coverage-7.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:40e8b1983080439d4802d80b951f4a93d991ef3261f69e81095a66f86cf3c3c6", size = 210304 },
388
- { url = "https://files.pythonhosted.org/packages/8c/8f/265a5f18ab2cb1cae3cf6d64e8fd2708ac66a57b7068963077456ec85294/coverage-7.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9134032f5aa445ae591c2ba6991d10136a1f533b1d2fa8f8c21126468c5025c6", size = 206919 },
389
- { url = "https://files.pythonhosted.org/packages/d0/d8/59b41c21237da09c2c09cfd594883f43e3f437d9d602afe09a23d8c3b768/coverage-7.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99670790f21a96665a35849990b1df447993880bb6463a0a1d757897f30da929", size = 207175 },
390
- { url = "https://files.pythonhosted.org/packages/19/e8/9a18a78779e5c9d51c8c60de4e9e06c91a03f529fa5b31993b85f364a114/coverage-7.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc7d6b380ca76f5e817ac9eef0c3686e7834c8346bef30b041a4ad286449990", size = 239695 },
391
- { url = "https://files.pythonhosted.org/packages/23/d5/6100bb2b104365634068aad82ec332663a32c7572f4bbe24825e79ecb712/coverage-7.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7b26757b22faf88fcf232f5f0e62f6e0fd9e22a8a5d0d5016888cdfe1f6c1c4", size = 236840 },
392
- { url = "https://files.pythonhosted.org/packages/d8/11/7e5ac48885f4fed8edb4624425b60405c96c5cf92c2260305eeb6d179897/coverage-7.6.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c59d6a4a4633fad297f943c03d0d2569867bd5372eb5684befdff8df8522e39", size = 238829 },
393
- { url = "https://files.pythonhosted.org/packages/31/6c/4943c562bc8f541dbc466181c615743fe7987433b9ac00301b4f2c8bce60/coverage-7.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f263b18692f8ed52c8de7f40a0751e79015983dbd77b16906e5b310a39d3ca21", size = 238495 },
394
- { url = "https://files.pythonhosted.org/packages/b2/21/ff415e195eafc86ad6e3e6bdb04e6ebd2caa432d27ec261c0a6d849a171e/coverage-7.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79644f68a6ff23b251cae1c82b01a0b51bc40c8468ca9585c6c4b1aeee570e0b", size = 236578 },
395
- { url = "https://files.pythonhosted.org/packages/28/81/08c1c7d2ecc14036b6fb71433ce51fb0942b39ef2bbb654fda82e4e16004/coverage-7.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71967c35828c9ff94e8c7d405469a1fb68257f686bca7c1ed85ed34e7c2529c4", size = 238418 },
396
- { url = "https://files.pythonhosted.org/packages/c6/50/912a47a6fa3582c6694e0acdb4c5cf1264950a400a3a9d35a5552e7a91cd/coverage-7.6.3-cp313-cp313-win32.whl", hash = "sha256:e266af4da2c1a4cbc6135a570c64577fd3e6eb204607eaff99d8e9b710003c6f", size = 209584 },
397
- { url = "https://files.pythonhosted.org/packages/49/cb/44402ef105d8a77849fa019b975c9e35b184498ec7a6070c30deaad47fab/coverage-7.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:ea52bd218d4ba260399a8ae4bb6b577d82adfc4518b93566ce1fddd4a49d1dce", size = 210316 },
398
- { url = "https://files.pythonhosted.org/packages/f6/c4/1e9b42abe9d381585d6f9384bbfbfce464234261e3e331ab98eeef3fc11a/coverage-7.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8d4c6ea0f498c7c79111033a290d060c517853a7bcb2f46516f591dab628ddd3", size = 207664 },
399
- { url = "https://files.pythonhosted.org/packages/29/74/62605f094604b85c98962fe29134e8f9df200106b5100a2db236f5c03993/coverage-7.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:331b200ad03dbaa44151d74daeb7da2cf382db424ab923574f6ecca7d3b30de3", size = 207936 },
400
- { url = "https://files.pythonhosted.org/packages/ff/8c/bbeadb2f3236fdd62c6c267096c2524af9a59ee0a124b6b237a943d274e8/coverage-7.6.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54356a76b67cf8a3085818026bb556545ebb8353951923b88292556dfa9f812d", size = 250658 },
401
- { url = "https://files.pythonhosted.org/packages/3a/a0/46fe77ef9d133867bf639ee68ebbcae86aa340d9e46900fbdf566557c9bf/coverage-7.6.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebec65f5068e7df2d49466aab9128510c4867e532e07cb6960075b27658dca38", size = 246370 },
402
- { url = "https://files.pythonhosted.org/packages/9c/ba/b5722bec74017eaa1c5d35377f40a2a434e6c864cf3f1b46cddb62657642/coverage-7.6.3-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33a785ea8354c480515e781554d3be582a86297e41ccbea627a5c632647f2cd", size = 248732 },
403
- { url = "https://files.pythonhosted.org/packages/a1/d1/1264cc9ad5079439f438fd49080cbb57a8c6c589919872eaacdbc30d3b1a/coverage-7.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f7ddb920106bbbbcaf2a274d56f46956bf56ecbde210d88061824a95bdd94e92", size = 248221 },
404
- { url = "https://files.pythonhosted.org/packages/9d/b5/eacd2cfcb3406725e98bc6b9f1ac6794188b8231148f71281ffdcf5968e0/coverage-7.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:70d24936ca6c15a3bbc91ee9c7fc661132c6f4c9d42a23b31b6686c05073bde5", size = 246109 },
405
- { url = "https://files.pythonhosted.org/packages/56/71/0f0a713bf452ae3e6dd126841a25a0bd3a75105f2666c32ad1fb28b791da/coverage-7.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c30e42ea11badb147f0d2e387115b15e2bd8205a5ad70d6ad79cf37f6ac08c91", size = 247391 },
406
- { url = "https://files.pythonhosted.org/packages/ad/dd/29fb9c6b94a52da04613e5005d0a8875e57ca76b570d2625964fd92d7fab/coverage-7.6.3-cp313-cp313t-win32.whl", hash = "sha256:365defc257c687ce3e7d275f39738dcd230777424117a6c76043459db131dd43", size = 210230 },
407
- { url = "https://files.pythonhosted.org/packages/61/17/efb9ca2a5f9ccf8af267ff2c02ad976a2dc29f9b3c63209e2a89588d7f95/coverage-7.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:23bb63ae3f4c645d2d82fa22697364b0046fbafb6261b258a58587441c5f7bd0", size = 211380 },
374
+ version = "7.6.4"
375
+ source = { registry = "https://pypi.org/simple" }
376
+ sdist = { url = "https://files.pythonhosted.org/packages/52/12/3669b6382792783e92046730ad3327f53b2726f0603f4c311c4da4824222/coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", size = 798716 }
377
+ wheels = [
378
+ { url = "https://files.pythonhosted.org/packages/7d/e7/9291de916d084f41adddfd4b82246e68d61d6a75747f075f7e64628998d2/coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", size = 207013 },
379
+ { url = "https://files.pythonhosted.org/packages/27/03/932c2c5717a7fa80cd43c6a07d3177076d97b79f12f40f882f9916db0063/coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", size = 207251 },
380
+ { url = "https://files.pythonhosted.org/packages/d5/3f/0af47dcb9327f65a45455fbca846fe96eb57c153af46c4754a3ba678938a/coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", size = 240268 },
381
+ { url = "https://files.pythonhosted.org/packages/8a/3c/37a9d81bbd4b23bc7d46ca820e16174c613579c66342faa390a271d2e18b/coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", size = 237298 },
382
+ { url = "https://files.pythonhosted.org/packages/c0/70/6b0627e5bd68204ee580126ed3513140b2298995c1233bd67404b4e44d0e/coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", size = 239367 },
383
+ { url = "https://files.pythonhosted.org/packages/3c/eb/634d7dfab24ac3b790bebaf9da0f4a5352cbc125ce6a9d5c6cf4c6cae3c7/coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", size = 238853 },
384
+ { url = "https://files.pythonhosted.org/packages/d9/0d/8e3ed00f1266ef7472a4e33458f42e39492e01a64281084fb3043553d3f1/coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", size = 237160 },
385
+ { url = "https://files.pythonhosted.org/packages/ce/9c/4337f468ef0ab7a2e0887a9c9da0e58e2eada6fc6cbee637a4acd5dfd8a9/coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", size = 238824 },
386
+ { url = "https://files.pythonhosted.org/packages/5e/09/3e94912b8dd37251377bb02727a33a67ee96b84bbbe092f132b401ca5dd9/coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", size = 209639 },
387
+ { url = "https://files.pythonhosted.org/packages/01/69/d4f3a4101171f32bc5b3caec8ff94c2c60f700107a6aaef7244b2c166793/coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", size = 210428 },
388
+ { url = "https://files.pythonhosted.org/packages/c2/4d/2dede4f7cb5a70fb0bb40a57627fddf1dbdc6b9c1db81f7c4dcdcb19e2f4/coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", size = 207039 },
389
+ { url = "https://files.pythonhosted.org/packages/3f/f9/d86368ae8c79e28f1fb458ebc76ae9ff3e8bd8069adc24e8f2fed03c58b7/coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", size = 207298 },
390
+ { url = "https://files.pythonhosted.org/packages/64/c5/b4cc3c3f64622c58fbfd4d8b9a7a8ce9d355f172f91fcabbba1f026852f6/coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", size = 239813 },
391
+ { url = "https://files.pythonhosted.org/packages/8a/86/14c42e60b70a79b26099e4d289ccdfefbc68624d096f4481163085aa614c/coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", size = 236959 },
392
+ { url = "https://files.pythonhosted.org/packages/7f/f8/4436a643631a2fbab4b44d54f515028f6099bfb1cd95b13cfbf701e7f2f2/coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", size = 238950 },
393
+ { url = "https://files.pythonhosted.org/packages/49/50/1571810ddd01f99a0a8be464a4ac8b147f322cd1e8e296a1528984fc560b/coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", size = 238610 },
394
+ { url = "https://files.pythonhosted.org/packages/f3/8c/6312d241fe7cbd1f0cade34a62fea6f333d1a261255d76b9a87074d8703c/coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", size = 236697 },
395
+ { url = "https://files.pythonhosted.org/packages/ce/5f/fef33dfd05d87ee9030f614c857deb6df6556b8f6a1c51bbbb41e24ee5ac/coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", size = 238541 },
396
+ { url = "https://files.pythonhosted.org/packages/a9/64/6a984b6e92e1ea1353b7ffa08e27f707a5e29b044622445859200f541e8c/coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", size = 209707 },
397
+ { url = "https://files.pythonhosted.org/packages/5c/60/ce5a9e942e9543783b3db5d942e0578b391c25cdd5e7f342d854ea83d6b7/coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", size = 210439 },
398
+ { url = "https://files.pythonhosted.org/packages/78/53/6719677e92c308207e7f10561a1b16ab8b5c00e9328efc9af7cfd6fb703e/coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", size = 207784 },
399
+ { url = "https://files.pythonhosted.org/packages/fa/dd/7054928930671fcb39ae6a83bb71d9ab5f0afb733172543ced4b09a115ca/coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", size = 208058 },
400
+ { url = "https://files.pythonhosted.org/packages/b5/7d/fd656ddc2b38301927b9eb3aae3fe827e7aa82e691923ed43721fd9423c9/coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", size = 250772 },
401
+ { url = "https://files.pythonhosted.org/packages/90/d0/eb9a3cc2100b83064bb086f18aedde3afffd7de6ead28f69736c00b7f302/coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", size = 246490 },
402
+ { url = "https://files.pythonhosted.org/packages/45/44/3f64f38f6faab8a0cfd2c6bc6eb4c6daead246b97cf5f8fc23bf3788f841/coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", size = 248848 },
403
+ { url = "https://files.pythonhosted.org/packages/5d/11/4c465a5f98656821e499f4b4619929bd5a34639c466021740ecdca42aa30/coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", size = 248340 },
404
+ { url = "https://files.pythonhosted.org/packages/f1/96/ebecda2d016cce9da812f404f720ca5df83c6b29f65dc80d2000d0078741/coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", size = 246229 },
405
+ { url = "https://files.pythonhosted.org/packages/16/d9/3d820c00066ae55d69e6d0eae11d6149a5ca7546de469ba9d597f01bf2d7/coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", size = 247510 },
406
+ { url = "https://files.pythonhosted.org/packages/8f/c3/4fa1eb412bb288ff6bfcc163c11700ff06e02c5fad8513817186e460ed43/coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", size = 210353 },
407
+ { url = "https://files.pythonhosted.org/packages/7e/77/03fc2979d1538884d921c2013075917fc927f41cd8526909852fe4494112/coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", size = 211502 },
408
408
  ]
409
409
 
410
410
  [[package]]
@@ -937,7 +937,7 @@ wheels = [
937
937
 
938
938
  [[package]]
939
939
  name = "mm-balance"
940
- version = "0.1.14"
940
+ version = "0.1.16"
941
941
  source = { editable = "." }
942
942
  dependencies = [
943
943
  { name = "mm-aptos" },
@@ -973,13 +973,13 @@ requires-dist = [
973
973
  [package.metadata.requires-dev]
974
974
  dev = [
975
975
  { name = "bandit", specifier = "~=1.7.10" },
976
- { name = "coverage", specifier = "~=7.6.3" },
977
- { name = "mypy", specifier = "~=1.12.1" },
976
+ { name = "coverage", specifier = "~=7.6.4" },
977
+ { name = "mypy", specifier = "~=1.13.0" },
978
978
  { name = "pip-audit", specifier = "~=2.7.0" },
979
979
  { name = "pytest", specifier = "~=8.3.3" },
980
980
  { name = "pytest-httpserver", specifier = "~=1.1.0" },
981
981
  { name = "pytest-xdist", specifier = "~=3.6.1" },
982
- { name = "ruff", specifier = "~=0.7.0" },
982
+ { name = "ruff", specifier = "~=0.7.3" },
983
983
  { name = "types-python-dateutil", specifier = "~=2.9.0.20241003" },
984
984
  { name = "types-pyyaml", specifier = "~=6.0.12.20240917" },
985
985
  ]
@@ -1132,25 +1132,25 @@ wheels = [
1132
1132
 
1133
1133
  [[package]]
1134
1134
  name = "mypy"
1135
- version = "1.12.1"
1135
+ version = "1.13.0"
1136
1136
  source = { registry = "https://pypi.org/simple" }
1137
1137
  dependencies = [
1138
1138
  { name = "mypy-extensions" },
1139
1139
  { name = "typing-extensions" },
1140
1140
  ]
1141
- sdist = { url = "https://files.pythonhosted.org/packages/17/03/744330105a74dc004578f47ec27e1bf66b1dd5664ea444d18423e41343bd/mypy-1.12.1.tar.gz", hash = "sha256:f5b3936f7a6d0e8280c9bdef94c7ce4847f5cdfc258fbb2c29a8c1711e8bb96d", size = 3150767 }
1141
+ sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 }
1142
1142
  wheels = [
1143
- { url = "https://files.pythonhosted.org/packages/8a/74/b9e0e4f06e951e277058f878302faa154d282ca11274c59fe08353f52949/mypy-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9fb83a7be97c498176fb7486cafbb81decccaef1ac339d837c377b0ce3743a7f", size = 11079902 },
1144
- { url = "https://files.pythonhosted.org/packages/9f/62/fcad290769db3eb0de265094cef5c94d6075c70bc1e42b67eee4ca192dcc/mypy-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:389e307e333879c571029d5b93932cf838b811d3f5395ed1ad05086b52148fb0", size = 10072373 },
1145
- { url = "https://files.pythonhosted.org/packages/cb/27/9ac78349c2952e4446288ec1174675ab9e0160ed18c2cb1154fa456c54e8/mypy-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b2048a95a21f7a9ebc9fbd075a4fcd310410d078aa0228dbbad7f71335e042", size = 12589779 },
1146
- { url = "https://files.pythonhosted.org/packages/7c/4a/58cebd122cf1cba95680ac51303fbeb508392413ca64e3e711aa7d4877aa/mypy-1.12.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5932370ccf7ebf83f79d1c157a5929d7ea36313027b0d70a488493dc1b179", size = 13044459 },
1147
- { url = "https://files.pythonhosted.org/packages/5b/c7/672935e2a3f9bcc07b1b870395a653f665657bef3cdaa504ad99f56eadf0/mypy-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:19bf51f87a295e7ab2894f1d8167622b063492d754e69c3c2fed6563268cb42a", size = 9731919 },
1148
- { url = "https://files.pythonhosted.org/packages/bb/b0/092be5094840a401940c95224f63bb2a8f09bce9251ac1df180ec523830c/mypy-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d34167d43613ffb1d6c6cdc0cc043bb106cac0aa5d6a4171f77ab92a3c758bcc", size = 11068611 },
1149
- { url = "https://files.pythonhosted.org/packages/9a/86/f20f53b8f062876c39602243d7a59b5cabd6b24315d8de511d607fa4de6a/mypy-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:427878aa54f2e2c5d8db31fa9010c599ed9f994b3b49e64ae9cd9990c40bd635", size = 10068036 },
1150
- { url = "https://files.pythonhosted.org/packages/84/c7/1dbd6575785522da1d4c1ac2c419505fcf23bee74811880cac447a4a77ab/mypy-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5fcde63ea2c9f69d6be859a1e6dd35955e87fa81de95bc240143cf00de1f7f81", size = 12585671 },
1151
- { url = "https://files.pythonhosted.org/packages/46/8a/f6ae18b446eb2bccce54c4bd94065bcfe417d6c67021dcc032bf1e720aff/mypy-1.12.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d54d840f6c052929f4a3d2aab2066af0f45a020b085fe0e40d4583db52aab4e4", size = 13036083 },
1152
- { url = "https://files.pythonhosted.org/packages/59/e6/fc65fde3dc7156fce8d49ba21c7b1f5d866ad50467bf196ca94a7f6d2c9e/mypy-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:20db6eb1ca3d1de8ece00033b12f793f1ea9da767334b7e8c626a4872090cf02", size = 9735467 },
1153
- { url = "https://files.pythonhosted.org/packages/84/6b/1db9de4e0764778251fb2d64cb7455cf6db75dc99c9f72c8b7e74b6a8a17/mypy-1.12.1-py3-none-any.whl", hash = "sha256:ce561a09e3bb9863ab77edf29ae3a50e65685ad74bba1431278185b7e5d5486e", size = 2646060 },
1143
+ { url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 },
1144
+ { url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 },
1145
+ { url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 },
1146
+ { url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 },
1147
+ { url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 },
1148
+ { url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 },
1149
+ { url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 },
1150
+ { url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 },
1151
+ { url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 },
1152
+ { url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 },
1153
+ { url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 },
1154
1154
  ]
1155
1155
 
1156
1156
  [[package]]
@@ -1612,27 +1612,27 @@ wheels = [
1612
1612
 
1613
1613
  [[package]]
1614
1614
  name = "ruff"
1615
- version = "0.7.0"
1616
- source = { registry = "https://pypi.org/simple" }
1617
- sdist = { url = "https://files.pythonhosted.org/packages/2c/c7/f3367d1da5d568192968c5c9e7f3d51fb317b9ac04828493b23d8fce8ce6/ruff-0.7.0.tar.gz", hash = "sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b", size = 3146645 }
1618
- wheels = [
1619
- { url = "https://files.pythonhosted.org/packages/48/59/a0275a0913f3539498d116046dd679cd657fe3b7caf5afe1733319414932/ruff-0.7.0-py3-none-linux_armv6l.whl", hash = "sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628", size = 10434007 },
1620
- { url = "https://files.pythonhosted.org/packages/cd/94/da0ba5f956d04c90dd899209904210600009dcda039ce840d83eb4298c7d/ruff-0.7.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737", size = 10048066 },
1621
- { url = "https://files.pythonhosted.org/packages/57/1d/e5cc149ecc46e4f203403a79ccd170fad52d316f98b87d0f63b1945567db/ruff-0.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06", size = 9711389 },
1622
- { url = "https://files.pythonhosted.org/packages/05/67/fb7ea2c869c539725a16c5bc294e9aa34f8b1b6fe702f1d173a5da517c2b/ruff-0.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be", size = 10755174 },
1623
- { url = "https://files.pythonhosted.org/packages/5f/f0/13703bc50536a0613ea3dce991116e5f0917a1f05528c6ab738b33c08d3f/ruff-0.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa", size = 10196040 },
1624
- { url = "https://files.pythonhosted.org/packages/99/c1/77b04ab20324ab03d333522ee55fb0f1c38e3ca0d326b4905f82ce6b6c70/ruff-0.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495", size = 11033684 },
1625
- { url = "https://files.pythonhosted.org/packages/f2/97/f463334dc4efeea3551cd109163df15561c18a1c3ec13d51643740fd36ba/ruff-0.7.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598", size = 11803700 },
1626
- { url = "https://files.pythonhosted.org/packages/b4/f8/a31d40c4bb92933d376a53e7c5d0245d9b27841357e4820e96d38f54b480/ruff-0.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e", size = 11347848 },
1627
- { url = "https://files.pythonhosted.org/packages/83/62/0c133b35ddaf91c65c30a56718b80bdef36bfffc35684d29e3a4878e0ea3/ruff-0.7.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914", size = 12480632 },
1628
- { url = "https://files.pythonhosted.org/packages/46/96/464058dd1d980014fb5aa0a1254e78799efb3096fc7a4823cd66a1621276/ruff-0.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9", size = 10941919 },
1629
- { url = "https://files.pythonhosted.org/packages/a0/f7/bda37ec77986a435dde44e1f59374aebf4282a5fa9cf17735315b847141f/ruff-0.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4", size = 10745519 },
1630
- { url = "https://files.pythonhosted.org/packages/c2/33/5f77fc317027c057b61a848020a47442a1cbf12e592df0e41e21f4d0f3bd/ruff-0.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9", size = 10284872 },
1631
- { url = "https://files.pythonhosted.org/packages/ff/50/98aec292bc9537f640b8d031c55f3414bf15b6ed13b3e943fed75ac927b9/ruff-0.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d", size = 10600334 },
1632
- { url = "https://files.pythonhosted.org/packages/f2/85/12607ae3201423a179b8cfadc7cb1e57d02cd0135e45bd0445acb4cef327/ruff-0.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11", size = 11017333 },
1633
- { url = "https://files.pythonhosted.org/packages/d4/7f/3b85a56879e705d5f46ec14daf8a439fca05c3081720fe3dc3209100922d/ruff-0.7.0-py3-none-win32.whl", hash = "sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec", size = 8570962 },
1634
- { url = "https://files.pythonhosted.org/packages/39/9f/c5ee2b40d377354dabcc23cff47eb299de4b4d06d345068f8f8cc1eadac8/ruff-0.7.0-py3-none-win_amd64.whl", hash = "sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2", size = 9365544 },
1635
- { url = "https://files.pythonhosted.org/packages/89/8b/ee1509f60148cecba644aa718f6633216784302458340311898aaf0b1bed/ruff-0.7.0-py3-none-win_arm64.whl", hash = "sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e", size = 8695763 },
1615
+ version = "0.7.3"
1616
+ source = { registry = "https://pypi.org/simple" }
1617
+ sdist = { url = "https://files.pythonhosted.org/packages/4b/06/09d1276df977eece383d0ed66052fc24ec4550a61f8fbc0a11200e690496/ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313", size = 3243664 }
1618
+ wheels = [
1619
+ { url = "https://files.pythonhosted.org/packages/c0/56/933d433c2489e4642487b835f53dd9ff015fb3d8fa459b09bb2ce42d7c4b/ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344", size = 10372090 },
1620
+ { url = "https://files.pythonhosted.org/packages/20/ea/1f0a22a6bcdd3fc26c73f63a025d05bd565901b729d56bcb093c722a6c4c/ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0", size = 10190037 },
1621
+ { url = "https://files.pythonhosted.org/packages/16/74/aca75666e0d481fe394e76a8647c44ea919087748024924baa1a17371e3e/ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9", size = 9811998 },
1622
+ { url = "https://files.pythonhosted.org/packages/20/a1/cf446a0d7f78ea1f0bd2b9171c11dfe746585c0c4a734b25966121eb4f5d/ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5", size = 10620626 },
1623
+ { url = "https://files.pythonhosted.org/packages/cd/c1/82b27d09286ae855f5d03b1ad37cf243f21eb0081732d4d7b0d658d439cb/ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299", size = 10177598 },
1624
+ { url = "https://files.pythonhosted.org/packages/b9/42/c0acac22753bf74013d035a5ef6c5c4c40ad4d6686bfb3fda7c6f37d9b37/ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e", size = 11171963 },
1625
+ { url = "https://files.pythonhosted.org/packages/43/18/bb0befb7fb9121dd9009e6a72eb98e24f1bacb07c6f3ecb55f032ba98aed/ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29", size = 11856157 },
1626
+ { url = "https://files.pythonhosted.org/packages/5e/91/04e98d7d6e32eca9d1372be595f9abc7b7f048795e32eb2edbd8794d50bd/ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5", size = 11440331 },
1627
+ { url = "https://files.pythonhosted.org/packages/f5/dc/3fe99f2ce10b76d389041a1b9f99e7066332e479435d4bebcceea16caff5/ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67", size = 12725354 },
1628
+ { url = "https://files.pythonhosted.org/packages/43/7b/1daa712de1c5bc6cbbf9fa60e9c41cc48cda962dc6d2c4f2a224d2c3007e/ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2", size = 11010091 },
1629
+ { url = "https://files.pythonhosted.org/packages/b6/db/1227a903587432eb569e57a95b15a4f191a71fe315cde4c0312df7bc85da/ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d", size = 10610687 },
1630
+ { url = "https://files.pythonhosted.org/packages/db/e2/dc41ee90c3085aadad4da614d310d834f641aaafddf3dfbba08210c616ce/ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2", size = 10254843 },
1631
+ { url = "https://files.pythonhosted.org/packages/6f/09/5f6cac1c91542bc5bd33d40b4c13b637bf64d7bb29e091dadb01b62527fe/ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2", size = 10730962 },
1632
+ { url = "https://files.pythonhosted.org/packages/d3/42/89a4b9a24ef7d00269e24086c417a006f9a3ffeac2c80f2629eb5ce140ee/ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16", size = 11101907 },
1633
+ { url = "https://files.pythonhosted.org/packages/b0/5c/efdb4777686683a8edce94ffd812783bddcd3d2454d38c5ac193fef7c500/ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc", size = 8611095 },
1634
+ { url = "https://files.pythonhosted.org/packages/bb/b8/28fbc6a4efa50178f973972d1c84b2d0a33cdc731588522ab751ac3da2f5/ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088", size = 9418283 },
1635
+ { url = "https://files.pythonhosted.org/packages/3f/77/b587cba6febd5e2003374f37eb89633f79f161e71084f94057c8653b7fb3/ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c", size = 8725228 },
1636
1636
  ]
1637
1637
 
1638
1638
  [[package]]
@@ -1,64 +0,0 @@
1
- import getpass
2
- import pathlib
3
- import pkgutil
4
- from typing import Annotated
5
-
6
- import typer
7
-
8
- from mm_balance import output
9
- from mm_balance.balances import Balances
10
- from mm_balance.config import Config
11
- from mm_balance.constants import NETWORKS
12
- from mm_balance.price import Prices, get_prices
13
- from mm_balance.token_decimals import get_token_decimals
14
-
15
- app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
16
-
17
-
18
- def example_callback(value: bool) -> None:
19
- if value:
20
- data = pkgutil.get_data(__name__, "config/example.yml")
21
- typer.echo(data)
22
- raise typer.Exit
23
-
24
-
25
- def networks_callback(value: bool) -> None:
26
- if value:
27
- for network in NETWORKS:
28
- typer.echo(network)
29
- raise typer.Exit
30
-
31
-
32
- @app.command()
33
- def cli(
34
- config_path: Annotated[pathlib.Path, typer.Argument()],
35
- _example: Annotated[bool | None, typer.Option("--example", callback=example_callback, help="Print a config example.")] = None,
36
- _networks: Annotated[
37
- bool | None, typer.Option("--networks", callback=networks_callback, help="Print supported networks.")
38
- ] = None,
39
- ) -> None:
40
- zip_password = "" # nosec
41
- if config_path.name.endswith(".zip"):
42
- zip_password = getpass.getpass("zip password")
43
- config = Config.read_config(config_path, zip_password=zip_password)
44
-
45
- if config.print_debug:
46
- output.print_nodes(config)
47
-
48
- token_decimals = get_token_decimals(config)
49
- if config.print_debug:
50
- output.print_token_decimals(token_decimals)
51
-
52
- prices = get_prices(config) if config.price else Prices()
53
- output.print_prices(config, prices)
54
-
55
- balances = Balances(config, token_decimals)
56
- balances.process()
57
-
58
- output.print_groups(balances, config, prices)
59
- output.print_total(config, balances, prices)
60
- output.print_errors(config, balances)
61
-
62
-
63
- if __name__ == "__main__":
64
- app()
@@ -1,109 +0,0 @@
1
- from decimal import Decimal
2
-
3
- from mm_std import Err, Ok, fatal, print_table
4
- from rich.progress import BarColumn, MofNCompleteColumn, Progress, TaskID, TextColumn
5
-
6
- from mm_balance.balances import Balances
7
- from mm_balance.config import Config, Group
8
- from mm_balance.price import Prices
9
- from mm_balance.token_decimals import TokenDecimals
10
- from mm_balance.total import Total
11
- from mm_balance.utils import fnumber
12
-
13
-
14
- def print_groups(balances: Balances, config: Config, prices: Prices) -> None:
15
- for group_index, group in enumerate(config.groups):
16
- group_balances = balances.get_group_balances(group_index, group.network)
17
- _print_group(group, group_balances, config, prices)
18
-
19
-
20
- def _print_group(group: Group, group_balances: list[Balances.Balance], config: Config, prices: Prices) -> None:
21
- rows = []
22
- balance_sum = Decimal(0)
23
- usd_sum = Decimal(0)
24
- for address_task in group_balances:
25
- if isinstance(address_task.balance, Err):
26
- row = [address_task.address, address_task.balance.err]
27
- elif isinstance(address_task.balance, Ok):
28
- balance = address_task.balance.ok
29
- balance_sum += balance
30
- if config.skip_empty and balance == Decimal(0):
31
- continue
32
- row = [address_task.address, fnumber(balance, config.format_number_separator)]
33
- if config.price:
34
- balance_usd = round(balance * prices[group.ticker], config.round_ndigits)
35
- usd_sum += balance_usd
36
- row.append(fnumber(balance_usd, config.format_number_separator, "$"))
37
- else:
38
- fatal("address_task is None!")
39
- rows.append(row)
40
-
41
- balance_sum_str = fnumber(round(balance_sum, config.round_ndigits), config.format_number_separator)
42
- sum_row = ["sum", balance_sum_str]
43
- if config.price:
44
- usd_sum_str = fnumber(round(usd_sum, config.round_ndigits), config.format_number_separator, "$")
45
- sum_row.append(usd_sum_str)
46
- rows.append(sum_row)
47
-
48
- if group.share < Decimal(1):
49
- sum_share_str = fnumber(round(balance_sum * group.share, config.round_ndigits), config.format_number_separator)
50
- sum_share_row = [f"sum_share, {group.share}", sum_share_str]
51
- if config.price:
52
- usd_sum_share_str = fnumber(round(usd_sum * group.share, config.round_ndigits), config.format_number_separator, "$")
53
- sum_share_row.append(usd_sum_share_str)
54
- rows.append(sum_share_row)
55
-
56
- table_headers = ["address", "balance"]
57
- if config.price:
58
- table_headers += ["usd"]
59
- print_table(group.name, table_headers, rows)
60
-
61
-
62
- def print_nodes(config: Config) -> None:
63
- rows = []
64
- for network, nodes in config.nodes.items():
65
- rows.append([network, "\n".join(nodes)])
66
- print_table("Nodes", ["network", "nodes"], rows)
67
-
68
-
69
- def print_token_decimals(token_decimals: TokenDecimals) -> None:
70
- rows = []
71
- for network, decimals in token_decimals.items():
72
- rows.append([network, decimals])
73
- print_table("Token Decimals", ["network", "decimals"], rows)
74
-
75
-
76
- def print_prices(config: Config, prices: Prices) -> None:
77
- if config.price:
78
- rows = []
79
- for ticker, price in prices.items():
80
- rows.append([ticker, fnumber(round(price, config.round_ndigits), config.format_number_separator, "$")])
81
- print_table("Prices", ["coin", "usd"], rows)
82
-
83
-
84
- def print_total(config: Config, balances: Balances, prices: Prices) -> None:
85
- total = Total.calc(balances, prices, config)
86
- total.print()
87
-
88
-
89
- def print_errors(config: Config, balances: Balances) -> None:
90
- error_balances = balances.get_errors()
91
- if not error_balances:
92
- return
93
- rows = []
94
- for balance in error_balances:
95
- group = config.groups[balance.group_index]
96
- rows.append([group.ticker + " / " + group.network, balance.address, balance.balance.err]) # type: ignore[union-attr]
97
- print_table("Errors", ["coin", "address", "error"], rows)
98
-
99
-
100
- def create_progress_bar() -> Progress:
101
- return Progress(
102
- TextColumn("[progress.description]{task.description}"),
103
- BarColumn(),
104
- MofNCompleteColumn(),
105
- )
106
-
107
-
108
- def create_progress_task(progress: Progress, description: str, total: int) -> TaskID:
109
- return progress.add_task("[green]" + description, total=total)
@@ -1,122 +0,0 @@
1
- from collections import defaultdict
2
- from dataclasses import dataclass
3
- from decimal import Decimal
4
- from typing import Self
5
-
6
- from mm_std import Ok, PrintFormat, print_table
7
-
8
- from mm_balance.balances import Balances
9
- from mm_balance.config import Config
10
- from mm_balance.constants import USD_STABLECOINS
11
- from mm_balance.price import Prices
12
- from mm_balance.utils import fnumber
13
-
14
-
15
- @dataclass
16
- class Total:
17
- coins: dict[str, Decimal]
18
- coins_share: dict[str, Decimal]
19
- usd_sum: Decimal # sum of all coins in USD
20
- usd_sum_share: Decimal
21
-
22
- stablecoin_sum: Decimal # sum of usd stablecoins: usdt, usdc, etc..
23
- stablecoin_sum_share: Decimal
24
-
25
- config: Config
26
- prices: Prices
27
-
28
- @classmethod
29
- def calc(cls, balances: Balances, prices: Prices, config: Config) -> Self:
30
- coins: dict[str, Decimal] = defaultdict(Decimal)
31
- coins_share: dict[str, Decimal] = defaultdict(Decimal)
32
- usd_sum = Decimal(0)
33
- usd_sum_share = Decimal(0)
34
-
35
- stablecoin_sum = Decimal(0)
36
- stablecoin_sum_share = Decimal(0)
37
- for group_index, group in enumerate(config.groups):
38
- balance_sum = Decimal(0)
39
- for address_task in balances.get_group_balances(group_index, group.network):
40
- if isinstance(address_task.balance, Ok):
41
- balance_sum += address_task.balance.ok
42
- if group.ticker in USD_STABLECOINS:
43
- stablecoin_sum += address_task.balance.ok
44
- stablecoin_sum_share += address_task.balance.ok * group.share
45
- if config.price:
46
- balance_usd = round(address_task.balance.ok * prices[group.ticker], config.round_ndigits)
47
- usd_sum += balance_usd
48
- usd_sum_share += group.share * balance_usd
49
-
50
- coins[group.ticker] += balance_sum
51
- coins_share[group.ticker] += round(balance_sum * group.share, config.round_ndigits)
52
- return cls(
53
- coins=coins,
54
- coins_share=coins_share,
55
- usd_sum=usd_sum,
56
- usd_sum_share=usd_sum_share,
57
- stablecoin_sum=stablecoin_sum,
58
- stablecoin_sum_share=stablecoin_sum_share,
59
- config=config,
60
- prices=prices,
61
- )
62
-
63
- def print(self) -> None:
64
- if self.config.print_format == PrintFormat.TABLE:
65
- if self.config.price:
66
- self._print_total_total_with_price()
67
-
68
- if self.config.has_share():
69
- self._print_share_total_with_price()
70
- else:
71
- self._print_total_total_without_price()
72
-
73
- if self.config.has_share():
74
- self._print_share_total_without_price()
75
-
76
- def _print_total_total_with_price(self) -> None:
77
- if self.config.print_format == PrintFormat.TABLE:
78
- rows = []
79
- for key, value in self.coins.items():
80
- usd_value = round(value * self.prices[key], self.config.round_ndigits)
81
- if key in USD_STABLECOINS:
82
- usd_share = round(self.stablecoin_sum * 100 / self.usd_sum, self.config.round_ndigits)
83
- else:
84
- usd_share = round(usd_value * 100 / self.usd_sum, self.config.round_ndigits)
85
-
86
- value_str = fnumber(value, self.config.format_number_separator)
87
- usd_value_str = fnumber(usd_value, self.config.format_number_separator, "$")
88
-
89
- rows.append([key, value_str, usd_value_str, f"{usd_share}%"])
90
-
91
- rows.append(["stablecoin_sum", fnumber(self.stablecoin_sum, self.config.format_number_separator, "$")])
92
- rows.append(["usd_sum", fnumber(self.usd_sum, self.config.format_number_separator, "$")])
93
- print_table("Total", ["coin", "balance", "usd", "usd_share"], rows)
94
-
95
- def _print_total_total_without_price(self) -> None:
96
- if self.config.print_format == PrintFormat.TABLE:
97
- rows = []
98
- for key, value in self.coins.items():
99
- rows.append([key, fnumber(value, self.config.format_number_separator)])
100
- print_table("Total", ["coin", "balance"], rows)
101
-
102
- def _print_share_total_with_price(self) -> None:
103
- rows = []
104
- for key, _ in self.coins.items():
105
- usd_value = round(self.coins_share[key] * self.prices[key], self.config.round_ndigits)
106
- if key in USD_STABLECOINS:
107
- usd_share = round(self.stablecoin_sum_share * 100 / self.usd_sum_share, self.config.round_ndigits)
108
- else:
109
- usd_share = round(usd_value * 100 / self.usd_sum_share, self.config.round_ndigits)
110
- value = fnumber(self.coins_share[key], self.config.format_number_separator)
111
- usd_value_str = fnumber(usd_value, self.config.format_number_separator, "$")
112
- rows.append([key, value, usd_value_str, f"{usd_share}%"])
113
- rows.append(["stablecoin_sum", fnumber(self.stablecoin_sum_share, self.config.format_number_separator, "$")])
114
- rows.append(["usd_sum", fnumber(self.usd_sum_share, self.config.format_number_separator, "$")])
115
- print_table("Total, share", ["coin", "balance", "usd", "usd_share"], rows)
116
-
117
- def _print_share_total_without_price(self) -> None:
118
- rows = []
119
- for key, _ in self.coins.items():
120
- value = fnumber(self.coins_share[key], self.config.format_number_separator)
121
- rows.append([key, value])
122
- print_table("Total, share", ["coin", "balance"], rows)
File without changes
File without changes