mm-balance 0.1.11__tar.gz → 0.1.12__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 (25) hide show
  1. {mm_balance-0.1.11 → mm_balance-0.1.12}/PKG-INFO +1 -1
  2. {mm_balance-0.1.11 → mm_balance-0.1.12}/pyproject.toml +1 -1
  3. mm_balance-0.1.12/src/mm_balance/balances.py +82 -0
  4. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/cli.py +11 -0
  5. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/config/example.yml +14 -8
  6. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/config.py +45 -42
  7. mm_balance-0.1.12/src/mm_balance/constants.py +46 -0
  8. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/output.py +1 -1
  9. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/price.py +19 -8
  10. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/token_decimals.py +3 -3
  11. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/total.py +7 -7
  12. {mm_balance-0.1.11 → mm_balance-0.1.12}/uv.lock +1 -1
  13. mm_balance-0.1.11/src/mm_balance/balances.py +0 -169
  14. mm_balance-0.1.11/src/mm_balance/constants.py +0 -45
  15. {mm_balance-0.1.11 → mm_balance-0.1.12}/.gitignore +0 -0
  16. {mm_balance-0.1.11 → mm_balance-0.1.12}/README.md +0 -0
  17. {mm_balance-0.1.11 → mm_balance-0.1.12}/justfile +0 -0
  18. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/__init__.py +0 -0
  19. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/rpc/__init__.py +0 -0
  20. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/rpc/btc.py +0 -0
  21. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/rpc/eth.py +0 -0
  22. {mm_balance-0.1.11 → mm_balance-0.1.12}/src/mm_balance/rpc/solana.py +0 -0
  23. {mm_balance-0.1.11 → mm_balance-0.1.12}/tests/__init__.py +0 -0
  24. {mm_balance-0.1.11 → mm_balance-0.1.12}/tests/conftest.py +0 -0
  25. {mm_balance-0.1.11 → mm_balance-0.1.12}/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.11
3
+ Version: 0.1.12
4
4
  Requires-Python: >=3.12
5
5
  Requires-Dist: mm-btc==0.1.0
6
6
  Requires-Dist: mm-eth==0.1.3
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mm-balance"
3
- version = "0.1.11"
3
+ version = "0.1.12"
4
4
  description = ""
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -0,0 +1,82 @@
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.constants import Network
12
+ from mm_balance.rpc import btc, eth, solana
13
+ from mm_balance.token_decimals import TokenDecimals
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
+ if self.tasks[network]:
36
+ self.progress_bar_task[network] = output.create_progress_task(
37
+ self.progress_bar, network.value, len(self.tasks[network])
38
+ )
39
+
40
+ def process(self) -> None:
41
+ with self.progress_bar:
42
+ job = ConcurrentTasks(max_workers=10)
43
+ for network in Network:
44
+ job.add_task(network.value, self._process_network, args=(network,))
45
+ job.execute()
46
+
47
+ def _process_network(self, network: Network) -> None:
48
+ job = ConcurrentTasks(max_workers=self.config.workers[network])
49
+ for idx, task in enumerate(self.tasks[network]):
50
+ job.add_task(str(idx), self._get_balance, args=(network, task.address, task.token_address))
51
+ job.execute()
52
+ for idx, _task in enumerate(self.tasks[network]):
53
+ self.tasks[network][idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
54
+
55
+ def _get_balance(self, network: Network, wallet_address: str, token_address: str | None) -> Result[Decimal]:
56
+ nodes = self.config.nodes[network]
57
+ round_ndigits = self.config.round_ndigits
58
+ proxies = self.config.proxies
59
+ token_decimals = self.token_decimals[network][token_address] if token_address else -1
60
+ match network:
61
+ case Network.BITCOIN:
62
+ res = btc.get_balance(wallet_address, proxies, round_ndigits)
63
+ case Network.ETHEREUM | Network.ARBITRUM_ONE | Network.OP_MAINNET:
64
+ if token_address is None:
65
+ res = eth.get_native_balance(nodes, wallet_address, proxies, round_ndigits)
66
+ else:
67
+ res = eth.get_token_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
68
+ case Network.SOLANA:
69
+ if token_address is None:
70
+ res = solana.get_native_balance(nodes, wallet_address, proxies, round_ndigits)
71
+ else:
72
+ res = solana.get_token_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
73
+
74
+ case _:
75
+ raise ValueError
76
+
77
+ self.progress_bar.update(self.progress_bar_task[network], advance=1)
78
+ return res
79
+
80
+ def get_group_balances(self, group_index: int, network: Network) -> list[Balance]:
81
+ # TODO: can we get network by group_index?
82
+ return [b for b in self.tasks[network] if b.group_index == group_index]
@@ -8,6 +8,7 @@ import typer
8
8
  from mm_balance import output
9
9
  from mm_balance.balances import Balances
10
10
  from mm_balance.config import Config
11
+ from mm_balance.constants import Network
11
12
  from mm_balance.price import Prices, get_prices
12
13
  from mm_balance.token_decimals import get_token_decimals
13
14
 
@@ -21,10 +22,20 @@ def example_callback(value: bool) -> None:
21
22
  raise typer.Exit
22
23
 
23
24
 
25
+ def networks_callback(value: bool) -> None:
26
+ if value:
27
+ for network in Network:
28
+ typer.echo(network)
29
+ raise typer.Exit
30
+
31
+
24
32
  @app.command()
25
33
  def cli(
26
34
  config_path: Annotated[pathlib.Path, typer.Argument()],
27
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,
28
39
  ) -> None:
29
40
  zip_password = "" # nosec
30
41
  if config_path.name.endswith(".zip"):
@@ -1,14 +1,16 @@
1
1
  coins:
2
- - coin: sol
2
+ - ticker: SOL
3
+ network: solana
3
4
  addresses:
4
5
  - 2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S # binance
5
6
 
6
- - coin: usdt
7
- network: sol
7
+ - ticker: USDT
8
+ network: solana
8
9
  addresses:
9
10
  - 2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S # binance
10
11
 
11
- - coin: btc
12
+ - ticker: BTC
13
+ network: bitcoin
12
14
  comment: coldwallets
13
15
  addresses: |
14
16
  34xp4vRoCGJym3xR7yCVPFHoCNxv4Twseo # binance
@@ -16,19 +18,23 @@ coins:
16
18
  bc1ql49ydapnjafl5t2cp9zqpjwe6pdgmxy98859v2 # robinhood
17
19
  share: 0.1 # 10%
18
20
 
19
- - coin: eth
21
+ - ticker: ETH
22
+ network: ethereum
20
23
  comment: okx
21
24
  addresses: okx_eth
22
25
 
23
- - coin: usdt
26
+ - ticker: USDT
27
+ network: ethereum
24
28
  comment: okx
25
29
  addresses: okx_eth
26
30
 
27
- - coin: eth
31
+ - ticker: ETH
32
+ network: ethereum
28
33
  comment: binance
29
34
  addresses: binance_eth
30
35
 
31
- - coin: usdt
36
+ - ticker: USDT
37
+ network: ethereum
32
38
  comment: binance
33
39
  addresses: binance_eth
34
40
 
@@ -1,18 +1,24 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from decimal import Decimal
4
- from typing import Any, Self
4
+ from typing import Self
5
5
 
6
6
  import pydash
7
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.constants import DEFAULT_ETH_NODES, DEFAULT_SOL_NODES, EthTokenAddress, Network, SolTokenAddress
10
+ from mm_balance.constants import (
11
+ DEFAULT_ARBITRUM_ONE_NODES,
12
+ DEFAULT_ETHEREUM_NODES,
13
+ DEFAULT_SOLANA_NODES,
14
+ TOKEN_ADDRESS,
15
+ Network,
16
+ )
11
17
 
12
18
 
13
19
  class Group(BaseConfig):
14
20
  comment: str = ""
15
- coin: str
21
+ ticker: str
16
22
  network: Network
17
23
  token_address: str | None = None
18
24
  coingecko_id: str | None = None
@@ -21,12 +27,13 @@ class Group(BaseConfig):
21
27
 
22
28
  @property
23
29
  def name(self) -> str:
24
- result = self.coin
30
+ result = self.ticker
25
31
  if self.comment:
26
32
  result += " / " + self.comment
33
+ result += " / " + self.network.value
27
34
  return result
28
35
 
29
- @field_validator("coin", mode="after")
36
+ @field_validator("ticker", mode="after")
30
37
  def coin_validator(cls, v: str) -> str:
31
38
  return v.upper()
32
39
 
@@ -34,17 +41,17 @@ class Group(BaseConfig):
34
41
  def to_list_validator(cls, v: str | list[str] | None) -> list[str]:
35
42
  return cls.to_list_str_validator(v, unique=True, remove_comments=True, split_line=True)
36
43
 
37
- @model_validator(mode="before")
38
- def before_all(cls, data: Any) -> Any:
39
- if "network" not in data:
40
- data["network"] = detect_network(data["coin"])
41
- return data
44
+ # @model_validator(mode="before")
45
+ # def before_all(cls, data: Any) -> Any:
46
+ # if "network" not in data:
47
+ # data["network"] = detect_network(data["coin"])
48
+ # return data
42
49
 
43
50
  @model_validator(mode="after")
44
51
  def final_validator(self) -> Self:
45
52
  if self.token_address is None:
46
- self.token_address = detect_token_address(self.coin, self.network)
47
- if self.token_address is not None and self.network is Network.ETH:
53
+ self.token_address = detect_token_address(self.ticker, self.network)
54
+ if self.token_address is not None and self.network is Network.ETHEREUM:
48
55
  self.token_address = self.token_address.lower()
49
56
  return self
50
57
 
@@ -95,40 +102,36 @@ class Config(BaseConfig):
95
102
  group.process_addresses(self.addresses)
96
103
 
97
104
  # load default rpc nodes
98
- if Network.BTC not in self.nodes:
99
- self.nodes[Network.BTC] = []
100
- if Network.ETH not in self.nodes:
101
- self.nodes[Network.ETH] = DEFAULT_ETH_NODES
102
- if Network.SOL not in self.nodes:
103
- self.nodes[Network.SOL] = DEFAULT_SOL_NODES
105
+ if Network.BITCOIN not in self.nodes:
106
+ self.nodes[Network.BITCOIN] = []
107
+ if Network.ETHEREUM not in self.nodes:
108
+ self.nodes[Network.ETHEREUM] = DEFAULT_ETHEREUM_NODES
109
+ if Network.ARBITRUM_ONE not in self.nodes:
110
+ self.nodes[Network.ARBITRUM_ONE] = DEFAULT_ARBITRUM_ONE_NODES
111
+ if Network.OP_MAINNET not in self.nodes:
112
+ self.nodes[Network.OP_MAINNET] = DEFAULT_ARBITRUM_ONE_NODES
113
+ if Network.SOLANA not in self.nodes:
114
+ self.nodes[Network.SOLANA] = DEFAULT_SOLANA_NODES
104
115
 
105
116
  return self
106
117
 
107
118
 
108
- def detect_network(coin: str) -> Network:
109
- coin = coin.lower()
110
- if coin == "btc":
111
- return Network.BTC
112
- if coin == "eth":
113
- return Network.ETH
114
- if coin == "sol":
115
- return Network.SOL
116
- return Network.ETH
117
- # TODO: raise ValueError(f"can't get network for the coin: {coin}")
118
-
119
-
120
- def detect_token_address(coin: str, network: str) -> str | None:
121
- if network == Network.ETH.lower():
122
- if coin.lower() == "usdt":
123
- return EthTokenAddress.USDT
124
- if coin.lower() == "usdc":
125
- return EthTokenAddress.USDC
126
-
127
- if network == Network.SOL.lower():
128
- if coin.lower() == "usdt":
129
- return SolTokenAddress.USDT
130
- if coin.lower() == "usdc":
131
- return SolTokenAddress.USDC
119
+ # def detect_network(coin: str) -> Network:
120
+ #
121
+ # # coin = coin.lower()
122
+ # # if coin == "btc":
123
+ # # return Network.BTC
124
+ # # if coin == "eth":
125
+ # # return Network.ETH
126
+ # # if coin == "sol":
127
+ # # return Network.SOL
128
+ # # return Network.ETH
129
+ # # # TODO: raise ValueError(f"can't get network for the coin: {coin}")
130
+
131
+
132
+ def detect_token_address(coin: str, network: Network) -> str | None:
133
+ if network in TOKEN_ADDRESS:
134
+ return TOKEN_ADDRESS[network].get(coin)
132
135
 
133
136
 
134
137
  def get_proxies(proxies_url: str) -> list[str]:
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum, unique
4
+
5
+ RETRIES_BALANCE = 5
6
+ RETRIES_DECIMALS = 5
7
+ RETRIES_COINGECKO_PRICES = 5
8
+ TIMEOUT_BALANCE = 5
9
+ TIMEOUT_DECIMALS = 5
10
+
11
+
12
+ @unique
13
+ class Network(str, Enum):
14
+ ARBITRUM_ONE = "arbitrum-one"
15
+ BITCOIN = "bitcoin"
16
+ ETHEREUM = "ethereum"
17
+ SOLANA = "solana"
18
+ OP_MAINNET = "op-mainnet" # Optimism mainnet
19
+
20
+
21
+ TOKEN_ADDRESS: dict[Network, dict[str, str]] = {
22
+ Network.ETHEREUM: {
23
+ "USDT": "0xdac17f958d2ee523a2206206994597c13d831ec7",
24
+ "USDC": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
25
+ },
26
+ Network.SOLANA: {
27
+ "USDT": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
28
+ "USDC": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
29
+ },
30
+ # TODO: Add for Arbitrum and Optimism, usdt + usdc
31
+ }
32
+
33
+ TICKER_TO_COINGECKO_ID = {
34
+ "BTC": "bitcoin",
35
+ "ETH": "ethereum",
36
+ "USDT": "tether",
37
+ "USDC": "usd-coin",
38
+ "SOL": "solana",
39
+ }
40
+
41
+ USD_STABLECOINS = ["USDT", "USDC"]
42
+
43
+ DEFAULT_ETHEREUM_NODES = ["https://ethereum.publicnode.com", "https://rpc.ankr.com/eth"]
44
+ DEFAULT_SOLANA_NODES = ["https://api.mainnet-beta.solana.com"]
45
+ DEFAULT_ARBITRUM_ONE_NODES = ["https://arb1.arbitrum.io/rpc", "https://arbitrum.llamarpc.com"]
46
+ DEFAULT_OP_MAINNET_NODES = ["https://mainnet.optimism.io", "https://optimism.llamarpc.com"]
@@ -24,7 +24,7 @@ def _print_group(group: Group, group_balances: list[Balances.Balance], config: C
24
24
  if isinstance(address_task.balance, Ok):
25
25
  balance_sum += address_task.balance.ok
26
26
  if config.price:
27
- balance_usd = round(address_task.balance.ok * prices[group.coin], config.round_ndigits)
27
+ balance_usd = round(address_task.balance.ok * prices[group.ticker], config.round_ndigits)
28
28
  usd_sum += balance_usd
29
29
  row.append(f"${balance_usd}")
30
30
  rows.append(row)
@@ -5,7 +5,7 @@ from mm_std import fatal, hr
5
5
  from mm_std.random_ import random_str_choice
6
6
 
7
7
  from mm_balance.config import Config, Group
8
- from mm_balance.constants import RETRIES_COINGECKO_PRICES, EthTokenAddress, Network
8
+ from mm_balance.constants import RETRIES_COINGECKO_PRICES, Network
9
9
 
10
10
 
11
11
  class Prices(dict[str, Decimal]):
@@ -20,7 +20,7 @@ class Prices(dict[str, Decimal]):
20
20
  def get_prices(config: Config) -> Prices:
21
21
  result = Prices()
22
22
 
23
- coins = pydash.uniq([group.coin for group in config.groups])
23
+ coins = pydash.uniq([group.ticker for group in config.groups])
24
24
  coingecko_ids = pydash.uniq([get_coingecko_id(group) for group in config.groups])
25
25
 
26
26
  url = f"https://api.coingecko.com/api/v3/simple/price?ids={",".join(coingecko_ids)}&vs_currencies=usd"
@@ -38,18 +38,29 @@ def get_prices(config: Config) -> Prices:
38
38
  return result
39
39
 
40
40
 
41
+ TICKER_TO_COINGECKO_ID = {
42
+ "BTC": "bitcoin",
43
+ "ETH": "ethereum",
44
+ "USDT": "tether",
45
+ "USDC": "usd-coin",
46
+ "SOL": "solana",
47
+ }
48
+
49
+
41
50
  def get_coingecko_id(group: Group) -> str:
42
51
  if group.coingecko_id:
43
52
  return group.coingecko_id
44
- elif group.network is Network.BTC:
53
+ elif group.network is Network.BITCOIN:
45
54
  return "bitcoin"
46
- elif group.network is Network.ETH and group.token_address is None:
55
+ elif group.network is Network.ETHEREUM and group.token_address is None:
56
+ return "ethereum"
57
+ elif group.ticker == "ETH":
47
58
  return "ethereum"
48
- elif group.coin.lower() == "usdt" or (group.token_address is not None and group.token_address == EthTokenAddress.USDT):
59
+ elif group.ticker == "USDT":
49
60
  return "tether"
50
- elif group.coin.lower() == "usdc" or (group.token_address is not None and group.token_address == EthTokenAddress.USDC):
61
+ elif group.ticker == "USDC":
51
62
  return "usd-coin"
52
- elif group.coin.lower() == "sol":
63
+ elif group.ticker == "SOL":
53
64
  return "solana"
54
65
 
55
- raise ValueError(f"can't get coingecko_id for {group.coin}")
66
+ raise ValueError(f"can't get coingecko_id for {group.ticker}")
@@ -23,15 +23,15 @@ def get_token_decimals(config: Config) -> TokenDecimals:
23
23
  proxies = config.proxies
24
24
 
25
25
  match group.network:
26
- case Network.ETH:
26
+ case Network.ETHEREUM:
27
27
  decimals_res = eth.get_token_decimals(nodes, group.token_address, proxies)
28
- case Network.SOL:
28
+ case Network.SOLANA:
29
29
  decimals_res = solana.get_token_decimals(nodes, group.token_address, proxies)
30
30
  case _:
31
31
  raise ValueError(f"unsupported network: {group.network}. Cant get token decimals for {group.token_address}")
32
32
 
33
33
  if isinstance(decimals_res, Err):
34
- fatal(f"can't get decimals for token {group.coin} / {group.token_address}, error={decimals_res.err}")
34
+ fatal(f"can't get decimals for token {group.ticker} / {group.token_address}, error={decimals_res.err}")
35
35
  result[group.network][group.token_address] = decimals_res.ok
36
36
 
37
37
  return result
@@ -7,7 +7,7 @@ from mm_std import Ok, PrintFormat, print_table
7
7
 
8
8
  from mm_balance.balances import Balances
9
9
  from mm_balance.config import Config
10
- from mm_balance.constants import Coin
10
+ from mm_balance.constants import USD_STABLECOINS
11
11
  from mm_balance.price import Prices
12
12
 
13
13
 
@@ -38,16 +38,16 @@ class Total:
38
38
  for address_task in balances.get_group_balances(group_index, group.network):
39
39
  if isinstance(address_task.balance, Ok):
40
40
  balance_sum += address_task.balance.ok
41
- if group.coin in Coin.usd_coins():
41
+ if group.ticker in USD_STABLECOINS:
42
42
  stablecoin_sum += address_task.balance.ok
43
43
  stablecoin_sum_share += address_task.balance.ok * group.share
44
44
  if config.price:
45
- balance_usd = round(address_task.balance.ok * prices[group.coin], config.round_ndigits)
45
+ balance_usd = round(address_task.balance.ok * prices[group.ticker], config.round_ndigits)
46
46
  usd_sum += balance_usd
47
47
  usd_sum_share += group.share * balance_usd
48
48
 
49
- coins[group.coin] += balance_sum
50
- coins_share[group.coin] += round(balance_sum * group.share, config.round_ndigits)
49
+ coins[group.ticker] += balance_sum
50
+ coins_share[group.ticker] += round(balance_sum * group.share, config.round_ndigits)
51
51
  return cls(
52
52
  coins=coins,
53
53
  coins_share=coins_share,
@@ -78,7 +78,7 @@ class Total:
78
78
  rows = []
79
79
  for key, value in self.coins.items():
80
80
  usd_value = round(value * self.prices[key], self.config.round_ndigits)
81
- if key in Coin.usd_coins():
81
+ if key in USD_STABLECOINS:
82
82
  usd_share = round(self.stablecoin_sum * 100 / self.usd_sum, self.config.round_ndigits)
83
83
  else:
84
84
  usd_share = round(usd_value * 100 / self.usd_sum, self.config.round_ndigits)
@@ -97,7 +97,7 @@ class Total:
97
97
  rows = []
98
98
  for key, _ in self.coins.items():
99
99
  usd_value = round(self.coins_share[key] * self.prices[key], self.config.round_ndigits)
100
- if key in Coin.usd_coins():
100
+ if key in USD_STABLECOINS:
101
101
  usd_share = round(self.stablecoin_sum_share * 100 / self.usd_sum_share, self.config.round_ndigits)
102
102
  else:
103
103
  usd_share = round(usd_value * 100 / self.usd_sum_share, self.config.round_ndigits)
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "mm-balance"
928
- version = "0.1.11"
928
+ version = "0.1.12"
929
929
  source = { editable = "." }
930
930
  dependencies = [
931
931
  { name = "mm-btc" },
@@ -1,169 +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 TaskID
8
-
9
- from mm_balance import output
10
- from mm_balance.config import Config
11
- from mm_balance.constants import Network
12
- from mm_balance.rpc import btc, eth, solana
13
- from mm_balance.token_decimals import TokenDecimals
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, proxies, round_ndigits)
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
@@ -1,45 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from enum import Enum, unique
4
-
5
- RETRIES_BALANCE = 5
6
- RETRIES_DECIMALS = 5
7
- RETRIES_COINGECKO_PRICES = 5
8
- TIMEOUT_BALANCE = 5
9
- TIMEOUT_DECIMALS = 5
10
-
11
-
12
- @unique
13
- class EthTokenAddress(str, Enum):
14
- USDT = "0xdac17f958d2ee523a2206206994597c13d831ec7"
15
- USDC = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
16
-
17
-
18
- @unique
19
- class SolTokenAddress(str, Enum):
20
- USDT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
21
- USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
22
-
23
-
24
- DEFAULT_ETH_NODES = ["https://ethereum.publicnode.com", "https://rpc.ankr.com/eth"]
25
- DEFAULT_SOL_NODES = ["https://api.mainnet-beta.solana.com"]
26
-
27
-
28
- @unique
29
- class Coin(str, Enum):
30
- BTC = "BTC"
31
- ETH = "ETH"
32
- SOL = "SOL"
33
- USDT = "USDT"
34
- USDC = "USDC"
35
-
36
- @classmethod
37
- def usd_coins(cls) -> list[Coin]:
38
- return [Coin.USDT, Coin.USDC]
39
-
40
-
41
- @unique
42
- class Network(str, Enum):
43
- BTC = "btc"
44
- ETH = "eth"
45
- SOL = "sol"
File without changes
File without changes
File without changes