mm-balance 0.1.9__tar.gz → 0.1.10__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 (26) hide show
  1. {mm_balance-0.1.9 → mm_balance-0.1.10}/PKG-INFO +2 -2
  2. {mm_balance-0.1.9 → mm_balance-0.1.10}/pyproject.toml +6 -5
  3. mm_balance-0.1.10/src/mm_balance/balances.py +169 -0
  4. {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/cli.py +3 -1
  5. {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/config/example.yml +5 -0
  6. {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/config.py +9 -23
  7. mm_balance-0.1.10/src/mm_balance/rpc/eth.py +46 -0
  8. mm_balance-0.1.10/src/mm_balance/rpc/solana.py +22 -0
  9. mm_balance-0.1.10/src/mm_balance/token_decimals.py +37 -0
  10. {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/total.py +0 -3
  11. {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/types.py +6 -0
  12. {mm_balance-0.1.9 → mm_balance-0.1.10}/uv.lock +79 -63
  13. mm_balance-0.1.9/src/mm_balance/balances.py +0 -98
  14. mm_balance-0.1.9/src/mm_balance/rpc/eth.py +0 -38
  15. mm_balance-0.1.9/src/mm_balance/rpc/solana.py +0 -19
  16. {mm_balance-0.1.9 → mm_balance-0.1.10}/.gitignore +0 -0
  17. {mm_balance-0.1.9 → mm_balance-0.1.10}/README.md +0 -0
  18. {mm_balance-0.1.9 → mm_balance-0.1.10}/justfile +0 -0
  19. {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/__init__.py +0 -0
  20. {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/output.py +0 -0
  21. {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/price.py +0 -0
  22. {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/rpc/__init__.py +0 -0
  23. {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/rpc/btc.py +0 -0
  24. {mm_balance-0.1.9 → mm_balance-0.1.10}/tests/__init__.py +0 -0
  25. {mm_balance-0.1.9 → mm_balance-0.1.10}/tests/conftest.py +0 -0
  26. {mm_balance-0.1.9 → mm_balance-0.1.10}/tests/test_dummy.py +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mm-balance
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Requires-Python: >=3.12
5
5
  Requires-Dist: mm-btc==0.1.0
6
6
  Requires-Dist: mm-eth==0.1.3
7
- Requires-Dist: mm-solana==0.1.2
7
+ Requires-Dist: mm-solana==0.1.4
8
8
  Requires-Dist: typer>=0.12.5
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "mm-balance"
3
- version = "0.1.9"
3
+ version = "0.1.10"
4
4
  description = ""
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
7
7
  "mm-btc==0.1.0",
8
8
  "mm-eth==0.1.3",
9
- "mm-solana==0.1.2",
9
+ "mm-solana==0.1.4",
10
10
  "typer>=0.12.5",
11
11
  ]
12
12
  [project.scripts]
@@ -21,11 +21,11 @@ dev-dependencies = [
21
21
  "pytest~=8.3.3",
22
22
  "pytest-xdist~=3.6.1",
23
23
  "pytest-httpserver~=1.1.0",
24
- "coverage~=7.6.0",
24
+ "coverage~=7.6.3",
25
25
  "ruff~=0.6.9",
26
26
  "pip-audit~=2.7.0",
27
27
  "bandit~=1.7.10",
28
- "mypy~=1.11.2",
28
+ "mypy~=1.12.0",
29
29
  "types-python-dateutil~=2.9.0.20241003",
30
30
  "types-PyYAML~=6.0.12.20240917",
31
31
  ]
@@ -56,7 +56,8 @@ lint.ignore = [
56
56
  "A003", # builtin-attribute-shadowing
57
57
  # "B008", # function-call-argument-default
58
58
  "UP040", # non-pep695-type-alias
59
- "COM812"
59
+ "COM812",
60
+ "RUF012"
60
61
  ]
61
62
 
62
63
  [tool.bandit]
@@ -0,0 +1,169 @@
1
+ from __future__ import annotations
2
+
3
+ from decimal import Decimal
4
+
5
+ from mm_std import ConcurrentTasks, Result
6
+ from pydantic import BaseModel
7
+ from rich.progress import TaskID
8
+
9
+ from mm_balance import output
10
+ from mm_balance.config import Config
11
+ from mm_balance.rpc import btc, eth, solana
12
+ from mm_balance.token_decimals import TokenDecimals
13
+ from mm_balance.types import Network
14
+
15
+
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
22
+
23
+ def __init__(self, config: Config, token_decimals: TokenDecimals):
24
+ self.config = config
25
+ self.token_decimals = token_decimals
26
+ self.tasks: dict[Network, list[Balances.Balance]] = {network: [] for network in Network}
27
+ self.progress_bar = output.create_progress_bar()
28
+ self.progress_bar_task: dict[Network, TaskID] = {}
29
+
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]
32
+ self.tasks[group.network].extend(task_list)
33
+
34
+ for network in Network:
35
+ self.progress_bar_task[network] = output.create_progress_task(
36
+ self.progress_bar, network.value, len(self.tasks[network])
37
+ )
38
+
39
+ def process(self) -> None:
40
+ with self.progress_bar:
41
+ job = ConcurrentTasks(max_workers=10)
42
+ for network in Network:
43
+ job.add_task(network.value, self._process_network, args=(network,))
44
+ job.execute()
45
+
46
+ def _process_network(self, network: Network) -> None:
47
+ job = ConcurrentTasks(max_workers=self.config.workers[network])
48
+ for idx, task in enumerate(self.tasks[network]):
49
+ job.add_task(str(idx), self._get_balance, args=(network, task.address, task.token_address))
50
+ job.execute()
51
+ for idx, _task in enumerate(self.tasks[network]):
52
+ self.tasks[network][idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
53
+
54
+ def _get_balance(self, network: Network, wallet_address: str, token_address: str | None) -> Result[Decimal]:
55
+ nodes = self.config.nodes[network]
56
+ round_ndigits = self.config.round_ndigits
57
+ proxies = self.config.proxies
58
+ token_decimals = self.token_decimals[network][token_address] if token_address else -1
59
+ match network:
60
+ case Network.BTC:
61
+ res = btc.get_balance(wallet_address, self.config)
62
+ case Network.ETH:
63
+ if token_address is None:
64
+ res = eth.get_native_balance(nodes, wallet_address, proxies, round_ndigits)
65
+ else:
66
+ res = eth.get_token_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
67
+ case Network.SOL:
68
+ if token_address is None:
69
+ res = solana.get_native_balance(nodes, wallet_address, proxies, round_ndigits)
70
+ else:
71
+ res = solana.get_token_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
72
+
73
+ case _:
74
+ raise ValueError
75
+
76
+ self.progress_bar.update(self.progress_bar_task[network], advance=1)
77
+ return res
78
+
79
+ def get_group_balances(self, group_index: int, network: Network) -> list[Balance]:
80
+ # TODO: can we get network by group_index?
81
+ return [b for b in self.tasks[network] if b.group_index == group_index]
82
+
83
+
84
+ # class Balances2(BaseModel):
85
+ # class Balance(BaseModel):
86
+ # group_index: int
87
+ # address: str
88
+ # token_address: str | None
89
+ # balance: Result[Decimal] | None = None
90
+ #
91
+ # config: Config
92
+ # token_decimals: TokenDecimals
93
+ # # separate balance tasks on networks
94
+ # btc: list[Balance]
95
+ # eth: list[Balance]
96
+ # sol: list[Balance]
97
+ #
98
+ # def network_tasks(self, network: Network) -> list[Balance]:
99
+ # if network == Network.BTC:
100
+ # return self.btc
101
+ # elif network == Network.ETH:
102
+ # return self.eth
103
+ # elif network == Network.SOL:
104
+ # return self.sol
105
+ # else:
106
+ # raise ValueError
107
+ #
108
+ # def get_group_balances(self, group_index: int, network: Network) -> list[Balance]:
109
+ # # TODO: can we get network by group_index?
110
+ # if network == Network.BTC:
111
+ # network_balances = self.btc
112
+ # elif network == Network.ETH:
113
+ # network_balances = self.eth
114
+ # elif network == Network.SOL:
115
+ # network_balances = self.sol
116
+ # else:
117
+ # raise ValueError
118
+ #
119
+ # return [b for b in network_balances if b.group_index == group_index]
120
+ #
121
+ # def process(self) -> None:
122
+ # progress = output.create_progress_bar()
123
+ # task_btc = output.create_progress_task(progress, "btc", len(self.btc))
124
+ # task_eth = output.create_progress_task(progress, "eth", len(self.eth))
125
+ # task_sol = output.create_progress_task(progress, "sol", len(self.sol))
126
+ # with progress:
127
+ # job = ConcurrentTasks()
128
+ # job.add_task("btc", self._process_btc, args=(progress, task_btc))
129
+ # job.add_task("eth", self._process_eth, args=(progress, task_eth))
130
+ # job.add_task("sol", self._process_sol, args=(progress, task_sol))
131
+ # job.execute()
132
+ #
133
+ # def _process_btc(self, progress: Progress, task_id: TaskID) -> None:
134
+ # job = ConcurrentTasks(max_workers=self.config.workers.btc)
135
+ # for idx, task in enumerate(self.btc):
136
+ # job.add_task(str(idx), btc.get_balance, args=(task.address, self.config, progress, task_id))
137
+ # job.execute()
138
+ # for idx, _task in enumerate(self.btc):
139
+ # self.btc[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
140
+ #
141
+ # def _process_eth(self, progress: Progress, task_id: TaskID) -> None:
142
+ # job = ConcurrentTasks(max_workers=self.config.workers.eth)
143
+ # for idx, task in enumerate(self.eth):
144
+ # job.add_task(str(idx), self._get_balance,
145
+ # args=(Network.ETH, task.address, task.token_address, self.config, progress, task_id))
146
+ # job.execute()
147
+ # for idx, _task in enumerate(self.eth):
148
+ # self.eth[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
149
+ #
150
+ # def _process_sol(self, progress: Progress, task_id: TaskID) -> None:
151
+ # job = ConcurrentTasks(max_workers=self.config.workers.sol)
152
+ # for idx, task in enumerate(self.sol):
153
+ # job.add_task(str(idx), solana.get_balance, args=(task.address, self.config, progress, task_id))
154
+ # job.execute()
155
+ # for idx, _task in enumerate(self.sol):
156
+ # self.sol[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
157
+ #
158
+ # @staticmethod
159
+ # def from_config(config: Config, token_decimals: TokenDecimals) -> Balances:
160
+ # tasks = Balances(config=config, btc=[], eth=[], sol=[], token_decimals=token_decimals)
161
+ # for idx, group in enumerate(config.groups):
162
+ # task_list = [Balances.Balance(group_index=idx, address=a, token_address=group.token_address) for a in group.addresses] # noqa
163
+ # if group.network == Network.BTC:
164
+ # tasks.btc.extend(task_list)
165
+ # elif group.network == Network.ETH:
166
+ # tasks.eth.extend(task_list)
167
+ # elif group.network == Network.SOL:
168
+ # tasks.sol.extend(task_list)
169
+ # return tasks
@@ -9,6 +9,7 @@ from mm_balance import output
9
9
  from mm_balance.balances import Balances
10
10
  from mm_balance.config import Config
11
11
  from mm_balance.price import Prices, get_prices
12
+ from mm_balance.token_decimals import get_token_decimals
12
13
 
13
14
  app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
14
15
 
@@ -31,7 +32,8 @@ def cli(
31
32
  config = Config.read_config(config_path, zip_password=zip_password)
32
33
 
33
34
  prices = get_prices(config) if config.price else Prices()
34
- balances = Balances.from_config(config)
35
+ token_decimals = get_token_decimals(config)
36
+ balances = Balances(config, token_decimals)
35
37
  balances.process()
36
38
 
37
39
  output.print_prices(config, prices)
@@ -3,6 +3,11 @@ groups:
3
3
  addresses:
4
4
  - 2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S # binance
5
5
 
6
+ - coin: usdt
7
+ network: sol
8
+ addresses:
9
+ - 2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S # binance
10
+
6
11
  - coin: btc
7
12
  comment: coldwallets
8
13
  addresses: |
@@ -4,10 +4,10 @@ from decimal import Decimal
4
4
  from typing import Any, Self
5
5
 
6
6
  import pydash
7
- from mm_std import BaseConfig, Err, PrintFormat, fatal, hr
7
+ from mm_std import BaseConfig, PrintFormat, fatal, hr
8
8
  from pydantic import Field, field_validator, model_validator
9
9
 
10
- from mm_balance.types import DEFAULT_ETH_NODES, DEFAULT_SOL_NODES, EthTokenAddress, Network
10
+ from mm_balance.types import DEFAULT_ETH_NODES, DEFAULT_SOL_NODES, EthTokenAddress, Network, SolTokenAddress
11
11
 
12
12
 
13
13
  class Config(BaseConfig):
@@ -67,14 +67,6 @@ class Config(BaseConfig):
67
67
  def to_list_validator(cls, v: str | list[str] | None) -> list[str]:
68
68
  return cls.to_list_str_validator(v, unique=True, remove_comments=True, split_line=True)
69
69
 
70
- class Workers(BaseConfig):
71
- btc: int = 5
72
- eth: int = 5
73
- sol: int = 5
74
-
75
- class TokenDecimals(BaseConfig):
76
- eth: dict[str, int] = Field(default_factory=dict)
77
-
78
70
  groups: list[Group]
79
71
  addresses: list[AddressGroup] = Field(default_factory=list)
80
72
 
@@ -85,9 +77,7 @@ class Config(BaseConfig):
85
77
  print_format: PrintFormat = PrintFormat.TABLE
86
78
  price: bool = True
87
79
 
88
- # non configs
89
- workers: Workers = Workers()
90
- token_decimals: TokenDecimals = TokenDecimals()
80
+ workers: dict[Network, int] = {network: 5 for network in Network}
91
81
 
92
82
  def btc_groups(self) -> list[Group]:
93
83
  return [g for g in self.groups if g.network == Network.BTC]
@@ -119,16 +109,6 @@ class Config(BaseConfig):
119
109
  if Network.SOL not in self.nodes:
120
110
  self.nodes[Network.SOL] = DEFAULT_SOL_NODES
121
111
 
122
- # load token decimals
123
- for group in self.groups:
124
- if group.network == Network.ETH and group.token_address is not None:
125
- from mm_balance.rpc import eth
126
-
127
- decimals_res = eth.get_token_decimals(group.token_address, self)
128
- if isinstance(decimals_res, Err):
129
- fatal(f"can't get decimals for token {group.coin} / {group.token_address}, error={decimals_res.err}")
130
- self.token_decimals.eth[group.token_address] = decimals_res.ok
131
-
132
112
  return self
133
113
 
134
114
 
@@ -151,6 +131,12 @@ def detect_token_address(coin: str, network: str) -> str | None:
151
131
  if coin.lower() == "usdc":
152
132
  return EthTokenAddress.USDC
153
133
 
134
+ if network == Network.SOL.lower():
135
+ if coin.lower() == "usdt":
136
+ return SolTokenAddress.USDT
137
+ if coin.lower() == "usdc":
138
+ return SolTokenAddress.USDC
139
+
154
140
 
155
141
  def get_proxies(proxies_url: str) -> list[str]:
156
142
  try:
@@ -0,0 +1,46 @@
1
+ from decimal import Decimal
2
+
3
+ from mm_eth import erc20, rpc
4
+ from mm_std import Ok, Result
5
+
6
+
7
+ def get_native_balance(nodes: list[str], address: str, proxies: list[str], round_ndigits: int) -> Result[Decimal]:
8
+ return rpc.eth_get_balance(nodes, address, proxies=proxies, attempts=5, timeout=10).and_then(
9
+ lambda b: Ok(round(Decimal(b / 10**18), round_ndigits)),
10
+ )
11
+
12
+
13
+ def get_token_balance(
14
+ nodes: list[str], wallet_address: str, token_address: str, decimals: int, proxies: list[str], round_ndigits: int
15
+ ) -> Result[Decimal]:
16
+ return erc20.get_balance(
17
+ nodes,
18
+ token_address,
19
+ wallet_address,
20
+ proxies=proxies,
21
+ attempts=5,
22
+ timeout=10,
23
+ ).and_then(
24
+ lambda b: Ok(round(Decimal(b / 10**decimals), round_ndigits)),
25
+ )
26
+
27
+
28
+ def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
29
+ return erc20.get_decimals(nodes, token_address, timeout=10, proxies=proxies, attempts=5)
30
+
31
+
32
+ # def get_balance(
33
+ # address: str, token_address: str | None, config: Config, progress: Progress | None = None, task_id: TaskID | None = None
34
+ # ) -> Result[Decimal]:
35
+ # res: Result[Decimal]
36
+ #
37
+ # if token_address is not None:
38
+ #
39
+ # else:
40
+ # res = rpc.eth_get_balance(config.nodes[Network.ETH], address, proxies=config.proxies, attempts=5, timeout=10).and_then(
41
+ # lambda b: Ok(round(Decimal(b / 10 ** 18), config.round_ndigits)),
42
+ # )
43
+ #
44
+ # if task_id is not None and progress is not None:
45
+ # progress.update(task_id, advance=1)
46
+ # return res
@@ -0,0 +1,22 @@
1
+ from decimal import Decimal
2
+
3
+ from mm_solana import balance, token
4
+ from mm_std import Ok, Result
5
+
6
+
7
+ def get_native_balance(nodes: list[str], address: str, proxies: list[str], round_ndigits: int) -> Result[Decimal]:
8
+ return balance.get_balance_with_retries(nodes, address, retries=5, timeout=5, proxies=proxies).and_then(
9
+ lambda b: Ok(round(Decimal(b / 1_000_000_000), round_ndigits)),
10
+ )
11
+
12
+
13
+ def get_token_balance(
14
+ nodes: list[str], wallet_address: str, token_address: str, decimals: int, proxies: list[str], round_ndigits: int
15
+ ) -> Result[Decimal]:
16
+ return token.get_balance_with_retries(nodes, wallet_address, token_address, retries=5, timeout=5, proxies=proxies).and_then(
17
+ lambda b: Ok(round(Decimal(b / 10**decimals), round_ndigits))
18
+ )
19
+
20
+
21
+ def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
22
+ return token.get_decimals_with_retries(nodes, token_address, retries=5, timeout=5, proxies=proxies)
@@ -0,0 +1,37 @@
1
+ from mm_std import Err, fatal
2
+
3
+ from mm_balance.config import Config
4
+ from mm_balance.rpc import eth, solana
5
+ from mm_balance.types import Network
6
+
7
+
8
+ class TokenDecimals(dict[Network, dict[str, int]]):
9
+ def __init__(self) -> None:
10
+ super().__init__()
11
+ for network in Network:
12
+ self[network] = {}
13
+
14
+
15
+ def get_token_decimals(config: Config) -> TokenDecimals:
16
+ result = TokenDecimals()
17
+
18
+ for group in config.groups:
19
+ if group.token_address is None or group.token_address in result[group.network]:
20
+ continue
21
+
22
+ nodes = config.nodes[group.network]
23
+ proxies = config.proxies
24
+
25
+ match group.network:
26
+ case Network.ETH:
27
+ decimals_res = eth.get_token_decimals(nodes, group.token_address, proxies)
28
+ case Network.SOL:
29
+ decimals_res = solana.get_token_decimals(nodes, group.token_address, proxies)
30
+ case _:
31
+ raise ValueError(f"unsupported network: {group.network}. Cant get token decimals for {group.token_address}")
32
+
33
+ if isinstance(decimals_res, Err):
34
+ fatal(f"can't get decimals for token {group.coin} / {group.token_address}, error={decimals_res.err}")
35
+ result[group.network][group.token_address] = decimals_res.ok
36
+
37
+ return result
@@ -15,7 +15,6 @@ from mm_balance.types import Coin
15
15
  class Total:
16
16
  coins: dict[str, Decimal]
17
17
  coins_share: dict[str, Decimal]
18
- # usd_share: dict[str, Decimal] # all stablecoins have key 'usd'
19
18
  usd_sum: Decimal # sum of all coins in USD
20
19
  usd_sum_share: Decimal
21
20
 
@@ -29,7 +28,6 @@ class Total:
29
28
  def calc(cls, balances: Balances, prices: Prices, config: Config) -> Self:
30
29
  coins: dict[str, Decimal] = defaultdict(Decimal)
31
30
  coins_share: dict[str, Decimal] = defaultdict(Decimal)
32
- # usd_share: dict[str, Decimal] = defaultdict(Decimal)
33
31
  usd_sum = Decimal(0)
34
32
  usd_sum_share = Decimal(0)
35
33
 
@@ -37,7 +35,6 @@ class Total:
37
35
  stablecoin_sum_share = Decimal(0)
38
36
  for group_index, group in enumerate(config.groups):
39
37
  balance_sum = Decimal(0)
40
- # for address_task in [t for t in tasks.network_tasks(group.network) if t.group_index == group_index]:
41
38
  for address_task in balances.get_group_balances(group_index, group.network):
42
39
  if isinstance(address_task.balance, Ok):
43
40
  balance_sum += address_task.balance.ok
@@ -30,3 +30,9 @@ class Network(str, Enum):
30
30
  class EthTokenAddress(str, Enum):
31
31
  USDT = "0xdac17f958d2ee523a2206206994597c13d831ec7"
32
32
  USDC = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
33
+
34
+
35
+ @unique
36
+ class SolTokenAddress(str, Enum):
37
+ USDT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
38
+ USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
@@ -320,18 +320,6 @@ wheels = [
320
320
  { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
321
321
  ]
322
322
 
323
- [[package]]
324
- name = "click-aliases"
325
- version = "1.0.4"
326
- source = { registry = "https://pypi.org/simple" }
327
- dependencies = [
328
- { name = "click" },
329
- ]
330
- sdist = { url = "https://files.pythonhosted.org/packages/f6/77/7ff07d287ae8613ce4cc6656bb7d415e6f2b987510b6b7d962b758150633/click_aliases-1.0.4.tar.gz", hash = "sha256:384313c5dc4c4bd64d9eadaff39ad91352747e8cdfd2f95d504d914c01eb4eda", size = 2904 }
331
- wheels = [
332
- { url = "https://files.pythonhosted.org/packages/7e/b7/dd8bf2f67acf74707e97782e40188c3911799d69d53cb71113e82be8caa2/click_aliases-1.0.4-py3-none-any.whl", hash = "sha256:27b416fd9117c05a744459ea13d01db8fa489e6efbc7e23a8ef5a744b7afbfb9", size = 3202 },
333
- ]
334
-
335
323
  [[package]]
336
324
  name = "coincurve"
337
325
  version = "20.0.0"
@@ -383,40 +371,40 @@ wheels = [
383
371
 
384
372
  [[package]]
385
373
  name = "coverage"
386
- version = "7.6.1"
387
- source = { registry = "https://pypi.org/simple" }
388
- sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791 }
389
- wheels = [
390
- { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983 },
391
- { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221 },
392
- { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342 },
393
- { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371 },
394
- { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455 },
395
- { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924 },
396
- { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252 },
397
- { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897 },
398
- { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606 },
399
- { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373 },
400
- { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007 },
401
- { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269 },
402
- { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886 },
403
- { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037 },
404
- { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038 },
405
- { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690 },
406
- { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765 },
407
- { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611 },
408
- { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671 },
409
- { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368 },
410
- { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758 },
411
- { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035 },
412
- { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839 },
413
- { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569 },
414
- { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927 },
415
- { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401 },
416
- { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301 },
417
- { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598 },
418
- { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307 },
419
- { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453 },
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 },
420
408
  ]
421
409
 
422
410
  [[package]]
@@ -809,6 +797,20 @@ socks = [
809
797
  { name = "socksio" },
810
798
  ]
811
799
 
800
+ [[package]]
801
+ name = "httpx-socks"
802
+ version = "0.9.1"
803
+ source = { registry = "https://pypi.org/simple" }
804
+ dependencies = [
805
+ { name = "httpcore" },
806
+ { name = "httpx" },
807
+ { name = "python-socks" },
808
+ ]
809
+ sdist = { url = "https://files.pythonhosted.org/packages/13/34/23ed81d0511acee5b3ec1ba899162714a17bd3854ad4461528cb45518d35/httpx-socks-0.9.1.tar.gz", hash = "sha256:80ab86bad96fdcbb44b59940f2d3218577a7f09a6d4fdeb2ebaf9ccdff4748a9", size = 12425 }
810
+ wheels = [
811
+ { url = "https://files.pythonhosted.org/packages/f0/5e/92a52ffdacf1a3a254e062c4c41fb371680cc774e82b33ea853b3c5eb327/httpx_socks-0.9.1-py3-none-any.whl", hash = "sha256:d01dabfdf4da2a8d6c82986ddcfdbb5799a32a21eda0a0639934caf9411bf4a5", size = 12745 },
812
+ ]
813
+
812
814
  [[package]]
813
815
  name = "hyperframe"
814
816
  version = "6.0.1"
@@ -923,7 +925,7 @@ wheels = [
923
925
 
924
926
  [[package]]
925
927
  name = "mm-balance"
926
- version = "0.1.9"
928
+ version = "0.1.10"
927
929
  source = { editable = "." }
928
930
  dependencies = [
929
931
  { name = "mm-btc" },
@@ -950,15 +952,15 @@ dev = [
950
952
  requires-dist = [
951
953
  { name = "mm-btc", specifier = "==0.1.0" },
952
954
  { name = "mm-eth", specifier = "==0.1.3" },
953
- { name = "mm-solana", specifier = "==0.1.2" },
955
+ { name = "mm-solana", specifier = "==0.1.4" },
954
956
  { name = "typer", specifier = ">=0.12.5" },
955
957
  ]
956
958
 
957
959
  [package.metadata.requires-dev]
958
960
  dev = [
959
961
  { name = "bandit", specifier = "~=1.7.10" },
960
- { name = "coverage", specifier = "~=7.6.0" },
961
- { name = "mypy", specifier = "~=1.11.2" },
962
+ { name = "coverage", specifier = "~=7.6.3" },
963
+ { name = "mypy", specifier = "~=1.12.0" },
962
964
  { name = "pip-audit", specifier = "~=2.7.0" },
963
965
  { name = "pytest", specifier = "~=8.3.3" },
964
966
  { name = "pytest-httpserver", specifier = "~=1.1.0" },
@@ -1002,20 +1004,20 @@ wheels = [
1002
1004
 
1003
1005
  [[package]]
1004
1006
  name = "mm-solana"
1005
- version = "0.1.2"
1007
+ version = "0.1.4"
1006
1008
  source = { registry = "https://pypi.org/simple" }
1007
1009
  dependencies = [
1008
1010
  { name = "base58" },
1009
- { name = "click" },
1010
- { name = "click-aliases" },
1011
+ { name = "httpx-socks" },
1011
1012
  { name = "jinja2" },
1012
1013
  { name = "mm-std" },
1013
1014
  { name = "pyyaml" },
1014
1015
  { name = "solana" },
1016
+ { name = "typer" },
1015
1017
  ]
1016
- sdist = { url = "https://files.pythonhosted.org/packages/c0/9c/a4e4ab6d5acde5d69b8e4c63346eca1f99768d521f6adc7e54acf8f0f3e6/mm_solana-0.1.2.tar.gz", hash = "sha256:dd0c951609a70c0fde23b8844c3f780b99d62a1025a9f8f185d76a4177f102ec", size = 47753 }
1018
+ sdist = { url = "https://files.pythonhosted.org/packages/99/9d/307c404ec6718192faebab8860fb352a667aa9903e0e58489be5f9270af1/mm_solana-0.1.4.tar.gz", hash = "sha256:fd719306c26a4f1347cc63f58dd3561b416da66fe7a615cb2a9b383b3d986268", size = 51285 }
1017
1019
  wheels = [
1018
- { url = "https://files.pythonhosted.org/packages/a6/c9/2b15553ea35a9e8a6e6c19df77ca59fb255997565d45df729c25eeae0ccf/mm_solana-0.1.2-py3-none-any.whl", hash = "sha256:b591fbfe1e88101739cfde418d894fdc98056d387c27dbc6d52da42b78d1ffae", size = 16074 },
1020
+ { url = "https://files.pythonhosted.org/packages/db/ea/901bd6a050fc888c3264d5cf87c37d13b9ffe6373d3319639c9a5c39d5c9/mm_solana-0.1.4-py3-none-any.whl", hash = "sha256:84c966bdbc12e85430ef26237d23ef296f5df9ae915b3153ad952537780f1e83", size = 18913 },
1019
1021
  ]
1020
1022
 
1021
1023
  [[package]]
@@ -1116,20 +1118,25 @@ wheels = [
1116
1118
 
1117
1119
  [[package]]
1118
1120
  name = "mypy"
1119
- version = "1.11.2"
1121
+ version = "1.12.0"
1120
1122
  source = { registry = "https://pypi.org/simple" }
1121
1123
  dependencies = [
1122
1124
  { name = "mypy-extensions" },
1123
1125
  { name = "typing-extensions" },
1124
1126
  ]
1125
- sdist = { url = "https://files.pythonhosted.org/packages/5c/86/5d7cbc4974fd564550b80fbb8103c05501ea11aa7835edf3351d90095896/mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", size = 3078806 }
1127
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/70/196a3339459fe22296ac9a883bbd998fcaf0db3e8d9a54cf4f53b722cad4/mypy-1.12.0.tar.gz", hash = "sha256:65a22d87e757ccd95cbbf6f7e181e6caa87128255eb2b6be901bb71b26d8a99d", size = 3149879 }
1126
1128
  wheels = [
1127
- { url = "https://files.pythonhosted.org/packages/35/3a/ed7b12ecc3f6db2f664ccf85cb2e004d3e90bec928e9d7be6aa2f16b7cdf/mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", size = 10990335 },
1128
- { url = "https://files.pythonhosted.org/packages/04/e4/1a9051e2ef10296d206519f1df13d2cc896aea39e8683302f89bf5792a59/mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", size = 10007119 },
1129
- { url = "https://files.pythonhosted.org/packages/f3/3c/350a9da895f8a7e87ade0028b962be0252d152e0c2fbaafa6f0658b4d0d4/mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", size = 12506856 },
1130
- { url = "https://files.pythonhosted.org/packages/b6/49/ee5adf6a49ff13f4202d949544d3d08abb0ea1f3e7f2a6d5b4c10ba0360a/mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", size = 12952066 },
1131
- { url = "https://files.pythonhosted.org/packages/27/c0/b19d709a42b24004d720db37446a42abadf844d5c46a2c442e2a074d70d9/mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", size = 9664000 },
1132
- { url = "https://files.pythonhosted.org/packages/42/3a/bdf730640ac523229dd6578e8a581795720a9321399de494374afc437ec5/mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", size = 2619625 },
1129
+ { url = "https://files.pythonhosted.org/packages/e7/c8/ef6e2a11f0de6cf4359552bf71f07a89f302d27e25bf4c9761649bf1b5a8/mypy-1.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8462655b6694feb1c99e433ea905d46c478041a8b8f0c33f1dab00ae881b2164", size = 11072079 },
1130
+ { url = "https://files.pythonhosted.org/packages/61/e7/1f9ba3965c3c445d863290d3f8521a7a726b878784f5ad642e82c038261f/mypy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:923ea66d282d8af9e0f9c21ffc6653643abb95b658c3a8a32dca1eff09c06475", size = 10071930 },
1131
+ { url = "https://files.pythonhosted.org/packages/3a/11/c84fb4c3a42ffd460c2a9b27105fbd538ec501e5aa34671fd3d14a1b94ba/mypy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ebf9e796521f99d61864ed89d1fb2926d9ab6a5fab421e457cd9c7e4dd65aa9", size = 12588227 },
1132
+ { url = "https://files.pythonhosted.org/packages/f0/ad/b55d070d2001e47c4c6c7d00b13f8dafb16b74db5a99904a183e3c0a3bd6/mypy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e478601cc3e3fa9d6734d255a59c7a2e5c2934da4378f3dd1e3411ea8a248642", size = 13037186 },
1133
+ { url = "https://files.pythonhosted.org/packages/28/c8/5fc9ef8d3ea89490939ecdfea7a84cede31a69534d468c34807941f5a79f/mypy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:c72861b7139a4f738344faa0e150834467521a3fba42dc98264e5aa9507dd601", size = 9727738 },
1134
+ { url = "https://files.pythonhosted.org/packages/a6/07/0df1b099a4a725e61782f7d9a34947fc93be688f9dfa011d86e411b2f036/mypy-1.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b9e1492e47e1790360a43755fa04101a7ac72287b1a53ce817f35899ba0521", size = 11071648 },
1135
+ { url = "https://files.pythonhosted.org/packages/9a/60/2a8bdb4f822bcdb0fa4599b83c1ae9f9ab0e10c1bee262dd9c1ff4607b12/mypy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48d3e37dd7d9403e38fa86c46191de72705166d40b8c9f91a3de77350daa0893", size = 10065760 },
1136
+ { url = "https://files.pythonhosted.org/packages/cc/d9/065ec6bd21a0ae14b520574d531dc1aa23fdc30fd276dea25f71945172d2/mypy-1.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f106db5ccb60681b622ac768455743ee0e6a857724d648c9629a9bd2ac3f721", size = 12584005 },
1137
+ { url = "https://files.pythonhosted.org/packages/e6/a8/31449fc5698d1a55062614790a885128e3b2a21de0f82a426942a5ae6a00/mypy-1.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:233e11b3f73ee1f10efada2e6da0f555b2f3a5316e9d8a4a1224acc10e7181d3", size = 13030941 },
1138
+ { url = "https://files.pythonhosted.org/packages/b5/8e/2347814cffccfb52fc02cbe457ae4a3fb5b660c5b361cdf72374266c231b/mypy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ae8959c21abcf9d73aa6c74a313c45c0b5a188752bf37dace564e29f06e9c1b", size = 9734383 },
1139
+ { url = "https://files.pythonhosted.org/packages/85/fd/2cc64da1ce9fada64b5d023dfbaf763548429145d08c958c78c02876c7f6/mypy-1.12.0-py3-none-any.whl", hash = "sha256:fd313226af375d52e1e36c383f39bf3836e1f192801116b31b090dfcd3ec5266", size = 2645791 },
1133
1140
  ]
1134
1141
 
1135
1142
  [[package]]
@@ -1460,6 +1467,15 @@ wheels = [
1460
1467
  { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
1461
1468
  ]
1462
1469
 
1470
+ [[package]]
1471
+ name = "python-socks"
1472
+ version = "2.5.3"
1473
+ source = { registry = "https://pypi.org/simple" }
1474
+ sdist = { url = "https://files.pythonhosted.org/packages/26/a2/dac502b749429cec85086ad8077e32a77eef100730dbc13b37ad658079fc/python_socks-2.5.3.tar.gz", hash = "sha256:4414b2a24777e439758a5f4644f81509ee634e6893115fe7fd1ec482b4b5c979", size = 201519 }
1475
+ wheels = [
1476
+ { url = "https://files.pythonhosted.org/packages/7e/54/6b907fd2bac4db271902a7a885d4521313b3b754973cfcedb2fb06bf9a00/python_socks-2.5.3-py3-none-any.whl", hash = "sha256:6bc428d0e19f8043e7b8fbc8af33417e690238bd8c9c1e9215871ac18c6136ad", size = 52874 },
1477
+ ]
1478
+
1463
1479
  [[package]]
1464
1480
  name = "pyunormalize"
1465
1481
  version = "15.1.0"
@@ -1,98 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from decimal import Decimal
4
-
5
- from mm_std import ConcurrentTasks, Result
6
- from pydantic import BaseModel
7
- from rich.progress import Progress, TaskID
8
-
9
- from mm_balance import output
10
- from mm_balance.config import Config
11
- from mm_balance.rpc import btc, eth, solana
12
- from mm_balance.types import Network
13
-
14
-
15
- class Balances(BaseModel):
16
- class Balance(BaseModel):
17
- group_index: int
18
- address: str
19
- token_address: str | None
20
- balance: Result[Decimal] | None = None
21
-
22
- config: Config
23
- # separate balance tasks on networks
24
- btc: list[Balance]
25
- eth: list[Balance]
26
- sol: list[Balance]
27
-
28
- def network_tasks(self, network: Network) -> list[Balance]:
29
- if network == Network.BTC:
30
- return self.btc
31
- elif network == Network.ETH:
32
- return self.eth
33
- elif network == Network.SOL:
34
- return self.sol
35
- else:
36
- raise ValueError
37
-
38
- def get_group_balances(self, group_index: int, network: Network) -> list[Balance]:
39
- # TODO: can we get network by group_index?
40
- if network == Network.BTC:
41
- network_balances = self.btc
42
- elif network == Network.ETH:
43
- network_balances = self.eth
44
- elif network == Network.SOL:
45
- network_balances = self.sol
46
- else:
47
- raise ValueError
48
-
49
- return [b for b in network_balances if b.group_index == group_index]
50
-
51
- def process(self) -> None:
52
- progress = output.create_progress_bar()
53
- task_btc = output.create_progress_task(progress, "btc", len(self.btc))
54
- task_eth = output.create_progress_task(progress, "eth", len(self.eth))
55
- task_sol = output.create_progress_task(progress, "sol", len(self.sol))
56
- with progress:
57
- job = ConcurrentTasks()
58
- job.add_task("btc", self._process_btc, args=(progress, task_btc))
59
- job.add_task("eth", self._process_eth, args=(progress, task_eth))
60
- job.add_task("sol", self._process_sol, args=(progress, task_sol))
61
- job.execute()
62
-
63
- def _process_btc(self, progress: Progress, task_id: TaskID) -> None:
64
- job = ConcurrentTasks(max_workers=self.config.workers.btc)
65
- for idx, task in enumerate(self.btc):
66
- job.add_task(str(idx), btc.get_balance, args=(task.address, self.config, progress, task_id))
67
- job.execute()
68
- for idx, _task in enumerate(self.btc):
69
- self.btc[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
70
-
71
- def _process_eth(self, progress: Progress, task_id: TaskID) -> None:
72
- job = ConcurrentTasks(max_workers=self.config.workers.eth)
73
- for idx, task in enumerate(self.eth):
74
- job.add_task(str(idx), eth.get_balance, args=(task.address, task.token_address, self.config, progress, task_id))
75
- job.execute()
76
- for idx, _task in enumerate(self.eth):
77
- self.eth[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
78
-
79
- def _process_sol(self, progress: Progress, task_id: TaskID) -> None:
80
- job = ConcurrentTasks(max_workers=self.config.workers.sol)
81
- for idx, task in enumerate(self.sol):
82
- job.add_task(str(idx), solana.get_balance, args=(task.address, self.config, progress, task_id))
83
- job.execute()
84
- for idx, _task in enumerate(self.sol):
85
- self.sol[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
86
-
87
- @staticmethod
88
- def from_config(config: Config) -> Balances:
89
- tasks = Balances(config=config, btc=[], eth=[], sol=[])
90
- for idx, group in enumerate(config.groups):
91
- task_list = [Balances.Balance(group_index=idx, address=a, token_address=group.token_address) for a in group.addresses]
92
- if group.network == Network.BTC:
93
- tasks.btc.extend(task_list)
94
- elif group.network == Network.ETH:
95
- tasks.eth.extend(task_list)
96
- elif group.network == Network.SOL:
97
- tasks.sol.extend(task_list)
98
- return tasks
@@ -1,38 +0,0 @@
1
- from decimal import Decimal
2
-
3
- from mm_eth import erc20, rpc
4
- from mm_std import Ok, Result
5
- from rich.progress import Progress, TaskID
6
-
7
- from mm_balance.config import Config
8
- from mm_balance.types import Network
9
-
10
-
11
- def get_balance(
12
- address: str, token_address: str | None, config: Config, progress: Progress | None = None, task_id: TaskID | None = None
13
- ) -> Result[Decimal]:
14
- res: Result[Decimal]
15
-
16
- if token_address is not None:
17
- res = erc20.get_balance(
18
- config.nodes[Network.ETH],
19
- token_address,
20
- address,
21
- proxies=config.proxies,
22
- attempts=5,
23
- timeout=10,
24
- ).and_then(
25
- lambda b: Ok(round(Decimal(b / 10 ** config.token_decimals.eth[token_address]), config.round_ndigits)),
26
- )
27
- else:
28
- res = rpc.eth_get_balance(config.nodes[Network.ETH], address, proxies=config.proxies, attempts=5, timeout=10).and_then(
29
- lambda b: Ok(round(Decimal(b / 10**18), config.round_ndigits)),
30
- )
31
-
32
- if task_id is not None and progress is not None:
33
- progress.update(task_id, advance=1)
34
- return res
35
-
36
-
37
- def get_token_decimals(token_address: str, config: Config) -> Result[int]:
38
- return erc20.get_decimals(config.nodes[Network.ETH], token_address, timeout=10, proxies=config.proxies, attempts=5)
@@ -1,19 +0,0 @@
1
- from decimal import Decimal
2
-
3
- from mm_solana.balance import sol_balance
4
- from mm_std import Ok, Result
5
- from rich.progress import Progress, TaskID
6
-
7
- from mm_balance.config import Config
8
- from mm_balance.types import Network
9
-
10
-
11
- def get_balance(address: str, config: Config, progress: Progress | None = None, task_id: TaskID | None = None) -> Result[Decimal]:
12
- res: Result[Decimal] = sol_balance(
13
- address=address, nodes=config.nodes[Network.SOL], proxies=config.proxies, attempts=5, timeout=10
14
- ).and_then(
15
- lambda b: Ok(round(Decimal(b / 1_000_000_000), config.round_ndigits)),
16
- )
17
- if task_id is not None and progress is not None:
18
- progress.update(task_id, advance=1)
19
- return res
File without changes
File without changes
File without changes