mm-balance 0.1.11__py3-none-any.whl → 0.1.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mm_balance/balances.py CHANGED
@@ -32,9 +32,10 @@ class Balances:
32
32
  self.tasks[group.network].extend(task_list)
33
33
 
34
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
- )
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
+ )
38
39
 
39
40
  def process(self) -> None:
40
41
  with self.progress_bar:
@@ -57,14 +58,14 @@ class Balances:
57
58
  proxies = self.config.proxies
58
59
  token_decimals = self.token_decimals[network][token_address] if token_address else -1
59
60
  match network:
60
- case Network.BTC:
61
+ case Network.BITCOIN:
61
62
  res = btc.get_balance(wallet_address, proxies, round_ndigits)
62
- case Network.ETH:
63
+ case Network.ETHEREUM | Network.ARBITRUM_ONE | Network.OP_MAINNET:
63
64
  if token_address is None:
64
65
  res = eth.get_native_balance(nodes, wallet_address, proxies, round_ndigits)
65
66
  else:
66
67
  res = eth.get_token_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
67
- case Network.SOL:
68
+ case Network.SOLANA:
68
69
  if token_address is None:
69
70
  res = solana.get_native_balance(nodes, wallet_address, proxies, round_ndigits)
70
71
  else:
@@ -80,90 +81,10 @@ class Balances:
80
81
  # TODO: can we get network by group_index?
81
82
  return [b for b in self.tasks[network] if b.group_index == group_index]
82
83
 
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
84
+ def get_errors(self) -> list[Balance]:
85
+ result = []
86
+ for network in self.tasks:
87
+ for task in self.tasks[network]:
88
+ if task.balance is not None and task.balance.is_err():
89
+ result.append(task)
90
+ return result
mm_balance/cli.py CHANGED
@@ -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"):
@@ -40,6 +51,7 @@ def cli(
40
51
 
41
52
  output.print_groups(balances, config, prices)
42
53
  output.print_total(config, balances, prices)
54
+ output.print_errors(config, balances)
43
55
 
44
56
 
45
57
  if __name__ == "__main__":
@@ -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
 
@@ -54,3 +60,4 @@ addresses:
54
60
  #- http://123.123.123.124
55
61
  #round_ndigits: 4
56
62
  #price: yes
63
+ #skip_empty: no
mm_balance/config.py CHANGED
@@ -1,18 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from decimal import Decimal
4
- from typing import Any, Self
4
+ from pathlib import Path
5
+ from typing import Self
5
6
 
6
7
  import pydash
7
8
  from mm_std import BaseConfig, PrintFormat, fatal, hr
8
9
  from pydantic import Field, field_validator, model_validator
9
10
 
10
- from mm_balance.constants import DEFAULT_ETH_NODES, DEFAULT_SOL_NODES, EthTokenAddress, Network, SolTokenAddress
11
+ from mm_balance.constants import DEFAULT_NODES, TOKEN_ADDRESS, Network
11
12
 
12
13
 
13
14
  class Group(BaseConfig):
14
15
  comment: str = ""
15
- coin: str
16
+ ticker: str
16
17
  network: Network
17
18
  token_address: str | None = None
18
19
  coingecko_id: str | None = None
@@ -21,30 +22,31 @@ class Group(BaseConfig):
21
22
 
22
23
  @property
23
24
  def name(self) -> str:
24
- result = self.coin
25
+ result = self.ticker
25
26
  if self.comment:
26
27
  result += " / " + self.comment
28
+ result += " / " + self.network.value
27
29
  return result
28
30
 
29
- @field_validator("coin", mode="after")
30
- def coin_validator(cls, v: str) -> str:
31
+ @field_validator("ticker", mode="after")
32
+ def ticker_validator(cls, v: str) -> str:
31
33
  return v.upper()
32
34
 
33
35
  @field_validator("addresses", mode="before")
34
36
  def to_list_validator(cls, v: str | list[str] | None) -> list[str]:
35
37
  return cls.to_list_str_validator(v, unique=True, remove_comments=True, split_line=True)
36
38
 
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
39
+ # @model_validator(mode="before")
40
+ # def before_all(cls, data: Any) -> Any:
41
+ # if "network" not in data:
42
+ # data["network"] = detect_network(data["coin"])
43
+ # return data
42
44
 
43
45
  @model_validator(mode="after")
44
46
  def final_validator(self) -> Self:
45
47
  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:
48
+ self.token_address = detect_token_address(self.ticker, self.network)
49
+ if self.token_address is not None and self.network is Network.ETHEREUM:
48
50
  self.token_address = self.token_address.lower()
49
51
  return self
50
52
 
@@ -56,7 +58,7 @@ class Group(BaseConfig):
56
58
  else:
57
59
  # TODO: check address is valid
58
60
  addresses.append(address)
59
- self.addresses = addresses
61
+ self.addresses = pydash.uniq(process_file_addresses(addresses))
60
62
 
61
63
 
62
64
  class AddressGroup(BaseConfig):
@@ -78,6 +80,7 @@ class Config(BaseConfig):
78
80
  nodes: dict[Network, list[str]] = Field(default_factory=dict)
79
81
  print_format: PrintFormat = PrintFormat.TABLE
80
82
  price: bool = True
83
+ skip_empty: bool = False # don't print the address with an empty balance
81
84
 
82
85
  workers: dict[Network, int] = {network: 5 for network in Network}
83
86
 
@@ -95,40 +98,16 @@ class Config(BaseConfig):
95
98
  group.process_addresses(self.addresses)
96
99
 
97
100
  # 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
101
+ for network in Network:
102
+ if network not in self.nodes:
103
+ self.nodes[network] = DEFAULT_NODES[network]
104
104
 
105
105
  return self
106
106
 
107
107
 
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
108
+ def detect_token_address(ticker: str, network: Network) -> str | None:
109
+ if network in TOKEN_ADDRESS:
110
+ return TOKEN_ADDRESS[network].get(ticker)
132
111
 
133
112
 
134
113
  def get_proxies(proxies_url: str) -> list[str]:
@@ -144,3 +123,17 @@ def get_proxies(proxies_url: str) -> list[str]:
144
123
 
145
124
  def get_address_group_by_name(address_groups: list[AddressGroup], name: str) -> AddressGroup | None:
146
125
  return pydash.find(address_groups, lambda g: g.name == name)
126
+
127
+
128
+ def process_file_addresses(addresses: list[str]) -> list[str]:
129
+ result = []
130
+ for address in addresses:
131
+ if address.startswith("file://"):
132
+ path = Path(address.removeprefix("file://"))
133
+ if path.is_file():
134
+ result.extend(path.read_text().strip().splitlines())
135
+ else:
136
+ fatal(f"File with addresses not found: {path}")
137
+ else:
138
+ result.append(address)
139
+ return result
mm_balance/constants.py CHANGED
@@ -9,37 +9,48 @@ TIMEOUT_BALANCE = 5
9
9
  TIMEOUT_DECIMALS = 5
10
10
 
11
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
12
  @unique
42
13
  class Network(str, Enum):
43
- BTC = "btc"
44
- ETH = "eth"
45
- SOL = "sol"
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
+ Network.ARBITRUM_ONE: {
31
+ "USDT": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9",
32
+ "USDC": "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8",
33
+ },
34
+ Network.OP_MAINNET: {
35
+ "USDT": "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
36
+ "USDC": "0x7f5c764cbc14f9669b88837ca1490cca17c31607",
37
+ },
38
+ }
39
+
40
+ TICKER_TO_COINGECKO_ID = {
41
+ "BTC": "bitcoin",
42
+ "ETH": "ethereum",
43
+ "USDT": "tether",
44
+ "USDC": "usd-coin",
45
+ "SOL": "solana",
46
+ }
47
+
48
+ USD_STABLECOINS = ["USDT", "USDC"]
49
+
50
+ DEFAULT_NODES: dict[Network, list[str]] = {
51
+ Network.ARBITRUM_ONE: ["https://arb1.arbitrum.io/rpc", "https://arbitrum.llamarpc.com"],
52
+ Network.BITCOIN: [],
53
+ Network.ETHEREUM: ["https://ethereum.publicnode.com", "https://rpc.ankr.com/eth"],
54
+ Network.SOLANA: ["https://api.mainnet-beta.solana.com"],
55
+ Network.OP_MAINNET: ["https://mainnet.optimism.io", "https://optimism.llamarpc.com"],
56
+ }
mm_balance/output.py CHANGED
@@ -20,11 +20,13 @@ def _print_group(group: Group, group_balances: list[Balances.Balance], config: C
20
20
  balance_sum = Decimal(0)
21
21
  usd_sum = Decimal(0)
22
22
  for address_task in group_balances:
23
+ if config.skip_empty and isinstance(address_task.balance, Ok) and address_task.balance.ok == Decimal(0):
24
+ continue
23
25
  row = [address_task.address, address_task.balance.ok_or_err()] # type: ignore[union-attr]
24
26
  if isinstance(address_task.balance, Ok):
25
27
  balance_sum += address_task.balance.ok
26
28
  if config.price:
27
- balance_usd = round(address_task.balance.ok * prices[group.coin], config.round_ndigits)
29
+ balance_usd = round(address_task.balance.ok * prices[group.ticker], config.round_ndigits)
28
30
  usd_sum += balance_usd
29
31
  row.append(f"${balance_usd}")
30
32
  rows.append(row)
@@ -57,6 +59,17 @@ def print_total(config: Config, balances: Balances, prices: Prices) -> None:
57
59
  total.print()
58
60
 
59
61
 
62
+ def print_errors(config: Config, balances: Balances) -> None:
63
+ error_balances = balances.get_errors()
64
+ if not error_balances:
65
+ return
66
+ rows = []
67
+ for balance in error_balances:
68
+ group = config.groups[balance.group_index]
69
+ rows.append([group.ticker + " / " + group.network, balance.address, balance.balance.err]) # type: ignore[union-attr]
70
+ print_table("Errors", ["coin", "address", "error"], rows)
71
+
72
+
60
73
  def create_progress_bar() -> Progress:
61
74
  return Progress(
62
75
  TextColumn("[progress.description]{task.description}"),
mm_balance/price.py CHANGED
@@ -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
mm_balance/total.py CHANGED
@@ -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)
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mm-balance
3
- Version: 0.1.11
3
+ Version: 0.1.13
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.4
7
+ Requires-Dist: mm-solana==0.1.5
8
8
  Requires-Dist: typer>=0.12.5
@@ -0,0 +1,18 @@
1
+ mm_balance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ mm_balance/balances.py,sha256=yk4gqfdBiBuq8SX4BzObBz_yYh3d6DJ-iDcvaSZFrFM,3939
3
+ mm_balance/cli.py,sha256=B8YDx2ltqIu1jJoxLA514Y1A-bcrNPEN6nK73ZYIyZc,1742
4
+ mm_balance/config.py,sha256=4NOp8CploUsDCYAJedBBEfgyGtREhcmmg3r2q2emeX4,4744
5
+ mm_balance/constants.py,sha256=vtISLm13dAVaJEtZYAyNlvZp8LwiYoEsXjcMBmr1xzE,1632
6
+ mm_balance/output.py,sha256=r2h3NQ5cZ9uWyGP0bfzYwVgS8TnNwj5zTcJbcY01zBo,3141
7
+ mm_balance/price.py,sha256=wlClQNW03BSzq8gxQxH9f5LnwRFZBqeI-tfY9vtlSJE,2005
8
+ mm_balance/token_decimals.py,sha256=iNhAEEPfg8MvjxBGnFdxh82pYg3v26w4s9xro2Emoi8,1307
9
+ mm_balance/total.py,sha256=PERRR9m77LBgu30tUEdyfGdtmVDFa-dICq8S9zcH1eA,4716
10
+ mm_balance/config/example.yml,sha256=oMg0bBygKFprcf3anW5nb0lkpl0Bc3cQ5eKTeEpiM_k,1498
11
+ mm_balance/rpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ mm_balance/rpc/btc.py,sha256=ugp90H7YW0kiXIh98bQWk9mQTW20yE-jBiCpRvfoH-U,481
13
+ mm_balance/rpc/eth.py,sha256=G7aYjTw6xJwcsAyIp9eVW8NRVDUGeTCpdbd-CqgqHyw,1167
14
+ mm_balance/rpc/solana.py,sha256=vmCAaeQKwxg95qdSmUEjFS9bAPUJVx8irtWv6nlRBvU,1174
15
+ mm_balance-0.1.13.dist-info/METADATA,sha256=pEBa6u5nBDTruCLZzIDBjSub0hk6jaLzZuWTvE5v9dQ,198
16
+ mm_balance-0.1.13.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
17
+ mm_balance-0.1.13.dist-info/entry_points.txt,sha256=rSnP0ZW1a3ACNwTWM7T53CmOycKbzhG43m2_wseENng,50
18
+ mm_balance-0.1.13.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- mm_balance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- mm_balance/balances.py,sha256=fawnQIfjWsljXo1MdvSxs3Xo7j19hBCa9jlyDXXWO7M,7587
3
- mm_balance/cli.py,sha256=tjMXQhtKZiCzqoTw5rIygMaVCOwNMiNbkr4nzOY06wA,1360
4
- mm_balance/config.py,sha256=lDizIS2qmZpiOWbrdPvn0DKUdN0FWuNdv2qKPJBfWEI,4866
5
- mm_balance/constants.py,sha256=GnyqtIfxIpWLuzB80Y0mYrzrRk8XT3fMHh63Fek5LEA,935
6
- mm_balance/output.py,sha256=GH5ESychKEDOc2gtaUH_JtKdNX1ZkXZD6kCk2mxd8_4,2568
7
- mm_balance/price.py,sha256=uAm7pVAwMyzZ7bE4xxz4PD0mzLR7zFk6UtYSwGqWjAs,1996
8
- mm_balance/token_decimals.py,sha256=8tAZiN5RpSFRtYb1VODABGg9x3JbLY73EA5BV0RqVv4,1297
9
- mm_balance/total.py,sha256=3IDNBrcqGFaUYMlAzLpQCEBcWi1zdT0RSm7o7t1b4Tw,4700
10
- mm_balance/config/example.yml,sha256=j3UzCgCIImkkzyMT_68LW7lRChsQOlwowhKjcwRBlyc,1336
11
- mm_balance/rpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- mm_balance/rpc/btc.py,sha256=ugp90H7YW0kiXIh98bQWk9mQTW20yE-jBiCpRvfoH-U,481
13
- mm_balance/rpc/eth.py,sha256=G7aYjTw6xJwcsAyIp9eVW8NRVDUGeTCpdbd-CqgqHyw,1167
14
- mm_balance/rpc/solana.py,sha256=vmCAaeQKwxg95qdSmUEjFS9bAPUJVx8irtWv6nlRBvU,1174
15
- mm_balance-0.1.11.dist-info/METADATA,sha256=ExgagHu4tCqpAHPjqivCaQ7-2Dj3HwrwOeyJct7DsXw,198
16
- mm_balance-0.1.11.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
17
- mm_balance-0.1.11.dist-info/entry_points.txt,sha256=rSnP0ZW1a3ACNwTWM7T53CmOycKbzhG43m2_wseENng,50
18
- mm_balance-0.1.11.dist-info/RECORD,,