mm-balance 0.1.13__tar.gz → 0.1.14__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 (29) hide show
  1. {mm_balance-0.1.13 → mm_balance-0.1.14}/PKG-INFO +2 -1
  2. {mm_balance-0.1.13 → mm_balance-0.1.14}/pyproject.toml +4 -3
  3. {mm_balance-0.1.13 → mm_balance-0.1.14}/src/mm_balance/balances.py +19 -23
  4. {mm_balance-0.1.13 → mm_balance-0.1.14}/src/mm_balance/cli.py +9 -3
  5. {mm_balance-0.1.13 → mm_balance-0.1.14}/src/mm_balance/config/example.yml +10 -0
  6. {mm_balance-0.1.13 → mm_balance-0.1.14}/src/mm_balance/config.py +16 -6
  7. mm_balance-0.1.14/src/mm_balance/constants.py +69 -0
  8. {mm_balance-0.1.13 → mm_balance-0.1.14}/src/mm_balance/output.py +40 -13
  9. {mm_balance-0.1.13 → mm_balance-0.1.14}/src/mm_balance/price.py +5 -23
  10. mm_balance-0.1.14/src/mm_balance/rpc/aptos.py +23 -0
  11. mm_balance-0.1.14/src/mm_balance/rpc/evm.py +27 -0
  12. mm_balance-0.1.14/src/mm_balance/rpc/solana.py +26 -0
  13. mm_balance-0.1.14/src/mm_balance/token_decimals.py +55 -0
  14. {mm_balance-0.1.13 → mm_balance-0.1.14}/src/mm_balance/total.py +18 -8
  15. mm_balance-0.1.14/src/mm_balance/utils.py +10 -0
  16. {mm_balance-0.1.13 → mm_balance-0.1.14}/uv.lock +74 -60
  17. mm_balance-0.1.13/src/mm_balance/constants.py +0 -56
  18. mm_balance-0.1.13/src/mm_balance/rpc/eth.py +0 -31
  19. mm_balance-0.1.13/src/mm_balance/rpc/solana.py +0 -26
  20. mm_balance-0.1.13/src/mm_balance/token_decimals.py +0 -37
  21. {mm_balance-0.1.13 → mm_balance-0.1.14}/.gitignore +0 -0
  22. {mm_balance-0.1.13 → mm_balance-0.1.14}/README.md +0 -0
  23. {mm_balance-0.1.13 → mm_balance-0.1.14}/justfile +0 -0
  24. {mm_balance-0.1.13 → mm_balance-0.1.14}/src/mm_balance/__init__.py +0 -0
  25. {mm_balance-0.1.13 → mm_balance-0.1.14}/src/mm_balance/rpc/__init__.py +0 -0
  26. {mm_balance-0.1.13 → mm_balance-0.1.14}/src/mm_balance/rpc/btc.py +0 -0
  27. {mm_balance-0.1.13 → mm_balance-0.1.14}/tests/__init__.py +0 -0
  28. {mm_balance-0.1.13 → mm_balance-0.1.14}/tests/conftest.py +0 -0
  29. {mm_balance-0.1.13 → mm_balance-0.1.14}/tests/test_dummy.py +0 -0
@@ -1,7 +1,8 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mm-balance
3
- Version: 0.1.13
3
+ Version: 0.1.14
4
4
  Requires-Python: >=3.12
5
+ Requires-Dist: mm-aptos==0.1.2
5
6
  Requires-Dist: mm-btc==0.1.0
6
7
  Requires-Dist: mm-eth==0.1.3
7
8
  Requires-Dist: mm-solana==0.1.5
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "mm-balance"
3
- version = "0.1.13"
3
+ version = "0.1.14"
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
9
  "mm-solana==0.1.5",
10
+ "mm-aptos==0.1.2",
10
11
  "typer>=0.12.5",
11
12
  ]
12
13
  [project.scripts]
@@ -22,10 +23,10 @@ dev-dependencies = [
22
23
  "pytest-xdist~=3.6.1",
23
24
  "pytest-httpserver~=1.1.0",
24
25
  "coverage~=7.6.3",
25
- "ruff~=0.6.9",
26
+ "ruff~=0.7.0",
26
27
  "pip-audit~=2.7.0",
27
28
  "bandit~=1.7.10",
28
- "mypy~=1.12.0",
29
+ "mypy~=1.12.1",
29
30
  "types-python-dateutil~=2.9.0.20241003",
30
31
  "types-PyYAML~=6.0.12.20240917",
31
32
  ]
@@ -8,8 +8,8 @@ from rich.progress import TaskID
8
8
 
9
9
  from mm_balance import output
10
10
  from mm_balance.config import Config
11
- from mm_balance.constants import Network
12
- from mm_balance.rpc import btc, eth, solana
11
+ from mm_balance.constants import NETWORK_APTOS, NETWORK_BITCOIN, NETWORK_SOLANA, Network
12
+ from mm_balance.rpc import aptos, btc, evm, solana
13
13
  from mm_balance.token_decimals import TokenDecimals
14
14
 
15
15
 
@@ -23,7 +23,7 @@ class Balances:
23
23
  def __init__(self, config: Config, token_decimals: TokenDecimals):
24
24
  self.config = config
25
25
  self.token_decimals = token_decimals
26
- self.tasks: dict[Network, list[Balances.Balance]] = {network: [] for network in Network}
26
+ self.tasks: dict[Network, list[Balances.Balance]] = {network: [] for network in config.networks()}
27
27
  self.progress_bar = output.create_progress_bar()
28
28
  self.progress_bar_task: dict[Network, TaskID] = {}
29
29
 
@@ -31,17 +31,17 @@ class Balances:
31
31
  task_list = [Balances.Balance(group_index=idx, address=a, token_address=group.token_address) for a in group.addresses]
32
32
  self.tasks[group.network].extend(task_list)
33
33
 
34
- for network in Network:
34
+ for network in config.networks():
35
35
  if self.tasks[network]:
36
36
  self.progress_bar_task[network] = output.create_progress_task(
37
- self.progress_bar, network.value, len(self.tasks[network])
37
+ self.progress_bar, network, len(self.tasks[network])
38
38
  )
39
39
 
40
40
  def process(self) -> None:
41
41
  with self.progress_bar:
42
42
  job = ConcurrentTasks(max_workers=10)
43
- for network in Network:
44
- job.add_task(network.value, self._process_network, args=(network,))
43
+ for network in self.config.networks():
44
+ job.add_task(network, self._process_network, args=(network,))
45
45
  job.execute()
46
46
 
47
47
  def _process_network(self, network: Network) -> None:
@@ -49,6 +49,7 @@ class Balances:
49
49
  for idx, task in enumerate(self.tasks[network]):
50
50
  job.add_task(str(idx), self._get_balance, args=(network, task.address, task.token_address))
51
51
  job.execute()
52
+ # TODO: print job.exceptions if present
52
53
  for idx, _task in enumerate(self.tasks[network]):
53
54
  self.tasks[network][idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
54
55
 
@@ -56,23 +57,18 @@ class Balances:
56
57
  nodes = self.config.nodes[network]
57
58
  round_ndigits = self.config.round_ndigits
58
59
  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)
60
+ token_decimals = self.token_decimals[network][token_address]
73
61
 
74
- case _:
75
- raise ValueError
62
+ if network.is_evm_network():
63
+ res = evm.get_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
64
+ elif network == NETWORK_BITCOIN:
65
+ res = btc.get_balance(wallet_address, proxies, round_ndigits)
66
+ elif network == NETWORK_APTOS:
67
+ res = aptos.get_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
68
+ elif network == NETWORK_SOLANA:
69
+ res = solana.get_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
70
+ else:
71
+ raise ValueError(f"Unsupported network: {network}")
76
72
 
77
73
  self.progress_bar.update(self.progress_bar_task[network], advance=1)
78
74
  return res
@@ -8,7 +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
+ from mm_balance.constants import NETWORKS
12
12
  from mm_balance.price import Prices, get_prices
13
13
  from mm_balance.token_decimals import get_token_decimals
14
14
 
@@ -24,7 +24,7 @@ def example_callback(value: bool) -> None:
24
24
 
25
25
  def networks_callback(value: bool) -> None:
26
26
  if value:
27
- for network in Network:
27
+ for network in NETWORKS:
28
28
  typer.echo(network)
29
29
  raise typer.Exit
30
30
 
@@ -42,10 +42,16 @@ def cli(
42
42
  zip_password = getpass.getpass("zip password")
43
43
  config = Config.read_config(config_path, zip_password=zip_password)
44
44
 
45
+ if config.print_debug:
46
+ output.print_nodes(config)
47
+
48
+ token_decimals = get_token_decimals(config)
49
+ if config.print_debug:
50
+ output.print_token_decimals(token_decimals)
51
+
45
52
  prices = get_prices(config) if config.price else Prices()
46
53
  output.print_prices(config, prices)
47
54
 
48
- token_decimals = get_token_decimals(config)
49
55
  balances = Balances(config, token_decimals)
50
56
  balances.process()
51
57
 
@@ -38,6 +38,14 @@ coins:
38
38
  comment: binance
39
39
  addresses: binance_eth
40
40
 
41
+ - ticker: USDC
42
+ comment: okx aptos
43
+ network: aptos
44
+ token_address: 0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC
45
+ token_decimals: 6
46
+ addresses: "0x834d639b10d20dcb894728aa4b9b572b2ea2d97073b10eacb111f338b20ea5d7" # for a single line it's necessary to use quotes
47
+
48
+
41
49
  addresses:
42
50
  - name: okx_eth
43
51
  addresses: |
@@ -61,3 +69,5 @@ addresses:
61
69
  #round_ndigits: 4
62
70
  #price: yes
63
71
  #skip_empty: no
72
+ #print_debug: no
73
+ #format_number_separator: ","
@@ -15,7 +15,8 @@ class Group(BaseConfig):
15
15
  comment: str = ""
16
16
  ticker: str
17
17
  network: Network
18
- token_address: str | None = None
18
+ token_address: str | None = None # If None, it's a native token, for example ETH
19
+ token_decimals: int | None = None
19
20
  coingecko_id: str | None = None
20
21
  addresses: list[str] = Field(default_factory=list)
21
22
  share: Decimal = Decimal(1)
@@ -25,7 +26,7 @@ class Group(BaseConfig):
25
26
  result = self.ticker
26
27
  if self.comment:
27
28
  result += " / " + self.comment
28
- result += " / " + self.network.value
29
+ result += " / " + self.network
29
30
  return result
30
31
 
31
32
  @field_validator("ticker", mode="after")
@@ -46,7 +47,7 @@ class Group(BaseConfig):
46
47
  def final_validator(self) -> Self:
47
48
  if self.token_address is None:
48
49
  self.token_address = detect_token_address(self.ticker, self.network)
49
- if self.token_address is not None and self.network is Network.ETHEREUM:
50
+ if self.token_address is not None and self.network.is_evm_network():
50
51
  self.token_address = self.token_address.lower()
51
52
  return self
52
53
 
@@ -81,12 +82,16 @@ class Config(BaseConfig):
81
82
  print_format: PrintFormat = PrintFormat.TABLE
82
83
  price: bool = True
83
84
  skip_empty: bool = False # don't print the address with an empty balance
84
-
85
- workers: dict[Network, int] = {network: 5 for network in Network}
85
+ print_debug: bool = False # print debug info: nodes, token_decimals
86
+ format_number_separator: str = "," # as thousands separators
87
+ workers: dict[Network, int] = Field(default_factory=dict)
86
88
 
87
89
  def has_share(self) -> bool:
88
90
  return any(g.share != Decimal(1) for g in self.groups)
89
91
 
92
+ def networks(self) -> list[Network]:
93
+ return pydash.uniq([group.network for group in self.groups])
94
+
90
95
  @model_validator(mode="after")
91
96
  def final_validator(self) -> Self:
92
97
  # load from proxies_url
@@ -98,10 +103,15 @@ class Config(BaseConfig):
98
103
  group.process_addresses(self.addresses)
99
104
 
100
105
  # load default rpc nodes
101
- for network in Network:
106
+ for network in self.networks():
102
107
  if network not in self.nodes:
103
108
  self.nodes[network] = DEFAULT_NODES[network]
104
109
 
110
+ # load default workers
111
+ for network in self.networks():
112
+ if network not in self.workers:
113
+ self.workers[network] = 5
114
+
105
115
  return self
106
116
 
107
117
 
@@ -0,0 +1,69 @@
1
+ from typing import Any
2
+
3
+ from pydantic import GetCoreSchemaHandler
4
+ from pydantic_core import CoreSchema, core_schema
5
+
6
+ RETRIES_BALANCE = 5
7
+ RETRIES_DECIMALS = 5
8
+ RETRIES_COINGECKO_PRICES = 5
9
+ TIMEOUT_BALANCE = 5
10
+ TIMEOUT_DECIMALS = 5
11
+
12
+
13
+ class Network(str):
14
+ def is_evm_network(self) -> bool:
15
+ return self in [NETWORK_ETHEREUM, NETWORK_ARBITRUM_ONE, NETWORK_OP_MAINNET] or self.startswith("evm-")
16
+
17
+ @classmethod
18
+ def __get_pydantic_core_schema__(cls, _source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema:
19
+ return core_schema.no_info_after_validator_function(cls, handler(str))
20
+
21
+
22
+ NETWORK_APTOS = Network("aptos")
23
+ NETWORK_ARBITRUM_ONE = Network("arbitrum-one")
24
+ NETWORK_BITCOIN = Network("bitcoin")
25
+ NETWORK_ETHEREUM = Network("ethereum")
26
+ NETWORK_SOLANA = Network("solana")
27
+ NETWORK_OP_MAINNET = Network("op-mainnet")
28
+ NETWORKS = [NETWORK_APTOS, NETWORK_ARBITRUM_ONE, NETWORK_BITCOIN, NETWORK_ETHEREUM, NETWORK_SOLANA, NETWORK_OP_MAINNET]
29
+
30
+
31
+ TOKEN_ADDRESS: dict[Network, dict[str, str]] = {
32
+ NETWORK_ETHEREUM: {
33
+ "USDT": "0xdac17f958d2ee523a2206206994597c13d831ec7",
34
+ "USDC": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
35
+ },
36
+ NETWORK_SOLANA: {
37
+ "USDT": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
38
+ "USDC": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
39
+ },
40
+ NETWORK_ARBITRUM_ONE: {
41
+ "USDT": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9",
42
+ "USDC": "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8",
43
+ },
44
+ NETWORK_OP_MAINNET: {
45
+ "USDT": "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
46
+ "USDC": "0x7f5c764cbc14f9669b88837ca1490cca17c31607",
47
+ },
48
+ }
49
+
50
+ TICKER_TO_COINGECKO_ID = {
51
+ "BTC": "bitcoin",
52
+ "ETH": "ethereum",
53
+ "USDT": "tether",
54
+ "USDC": "usd-coin",
55
+ "SOL": "solana",
56
+ "APT": "aptos",
57
+ "POL": "matic-network",
58
+ }
59
+
60
+ USD_STABLECOINS = ["USDT", "USDC"]
61
+
62
+ DEFAULT_NODES: dict[Network, list[str]] = {
63
+ NETWORK_ARBITRUM_ONE: ["https://arb1.arbitrum.io/rpc", "https://arbitrum.llamarpc.com"],
64
+ NETWORK_BITCOIN: [],
65
+ NETWORK_ETHEREUM: ["https://ethereum.publicnode.com", "https://rpc.ankr.com/eth"],
66
+ NETWORK_SOLANA: ["https://api.mainnet-beta.solana.com"],
67
+ NETWORK_OP_MAINNET: ["https://mainnet.optimism.io", "https://optimism.llamarpc.com"],
68
+ NETWORK_APTOS: ["https://fullnode.mainnet.aptoslabs.com/v1"],
69
+ }
@@ -1,12 +1,14 @@
1
1
  from decimal import Decimal
2
2
 
3
- from mm_std import Ok, print_table
3
+ from mm_std import Err, Ok, fatal, print_table
4
4
  from rich.progress import BarColumn, MofNCompleteColumn, Progress, TaskID, TextColumn
5
5
 
6
6
  from mm_balance.balances import Balances
7
7
  from mm_balance.config import Config, Group
8
8
  from mm_balance.price import Prices
9
+ from mm_balance.token_decimals import TokenDecimals
9
10
  from mm_balance.total import Total
11
+ from mm_balance.utils import fnumber
10
12
 
11
13
 
12
14
  def print_groups(balances: Balances, config: Config, prices: Prices) -> None:
@@ -20,26 +22,35 @@ def _print_group(group: Group, group_balances: list[Balances.Balance], config: C
20
22
  balance_sum = Decimal(0)
21
23
  usd_sum = Decimal(0)
22
24
  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
25
- row = [address_task.address, address_task.balance.ok_or_err()] # type: ignore[union-attr]
26
- if isinstance(address_task.balance, Ok):
27
- balance_sum += address_task.balance.ok
25
+ if isinstance(address_task.balance, Err):
26
+ row = [address_task.address, address_task.balance.err]
27
+ elif isinstance(address_task.balance, Ok):
28
+ balance = address_task.balance.ok
29
+ balance_sum += balance
30
+ if config.skip_empty and balance == Decimal(0):
31
+ continue
32
+ row = [address_task.address, fnumber(balance, config.format_number_separator)]
28
33
  if config.price:
29
- balance_usd = round(address_task.balance.ok * prices[group.ticker], config.round_ndigits)
34
+ balance_usd = round(balance * prices[group.ticker], config.round_ndigits)
30
35
  usd_sum += balance_usd
31
- row.append(f"${balance_usd}")
36
+ row.append(fnumber(balance_usd, config.format_number_separator, "$"))
37
+ else:
38
+ fatal("address_task is None!")
32
39
  rows.append(row)
33
40
 
34
- sum_row = ["sum", round(balance_sum, config.round_ndigits)]
41
+ balance_sum_str = fnumber(round(balance_sum, config.round_ndigits), config.format_number_separator)
42
+ sum_row = ["sum", balance_sum_str]
35
43
  if config.price:
36
- sum_row.append(f"${round(usd_sum, config.round_ndigits)}")
44
+ usd_sum_str = fnumber(round(usd_sum, config.round_ndigits), config.format_number_separator, "$")
45
+ sum_row.append(usd_sum_str)
37
46
  rows.append(sum_row)
38
47
 
39
48
  if group.share < Decimal(1):
40
- sum_share_row = [f"sum_share, {group.share}", round(balance_sum * group.share, config.round_ndigits)]
49
+ sum_share_str = fnumber(round(balance_sum * group.share, config.round_ndigits), config.format_number_separator)
50
+ sum_share_row = [f"sum_share, {group.share}", sum_share_str]
41
51
  if config.price:
42
- sum_share_row.append(f"${round(usd_sum * group.share, config.round_ndigits)}")
52
+ usd_sum_share_str = fnumber(round(usd_sum * group.share, config.round_ndigits), config.format_number_separator, "$")
53
+ sum_share_row.append(usd_sum_share_str)
43
54
  rows.append(sum_share_row)
44
55
 
45
56
  table_headers = ["address", "balance"]
@@ -48,9 +59,25 @@ def _print_group(group: Group, group_balances: list[Balances.Balance], config: C
48
59
  print_table(group.name, table_headers, rows)
49
60
 
50
61
 
62
+ def print_nodes(config: Config) -> None:
63
+ rows = []
64
+ for network, nodes in config.nodes.items():
65
+ rows.append([network, "\n".join(nodes)])
66
+ print_table("Nodes", ["network", "nodes"], rows)
67
+
68
+
69
+ def print_token_decimals(token_decimals: TokenDecimals) -> None:
70
+ rows = []
71
+ for network, decimals in token_decimals.items():
72
+ rows.append([network, decimals])
73
+ print_table("Token Decimals", ["network", "decimals"], rows)
74
+
75
+
51
76
  def print_prices(config: Config, prices: Prices) -> None:
52
77
  if config.price:
53
- rows = [[k, round(v, config.round_ndigits)] for (k, v) in prices.items()]
78
+ rows = []
79
+ for ticker, price in prices.items():
80
+ rows.append([ticker, fnumber(round(price, config.round_ndigits), config.format_number_separator, "$")])
54
81
  print_table("Prices", ["coin", "usd"], rows)
55
82
 
56
83
 
@@ -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, Network
8
+ from mm_balance.constants import RETRIES_COINGECKO_PRICES, TICKER_TO_COINGECKO_ID
9
9
 
10
10
 
11
11
  class Prices(dict[str, Decimal]):
@@ -38,29 +38,11 @@ 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
-
50
41
  def get_coingecko_id(group: Group) -> str:
51
42
  if group.coingecko_id:
52
43
  return group.coingecko_id
53
- elif group.network is Network.BITCOIN:
54
- return "bitcoin"
55
- elif group.network is Network.ETHEREUM and group.token_address is None:
56
- return "ethereum"
57
- elif group.ticker == "ETH":
58
- return "ethereum"
59
- elif group.ticker == "USDT":
60
- return "tether"
61
- elif group.ticker == "USDC":
62
- return "usd-coin"
63
- elif group.ticker == "SOL":
64
- return "solana"
44
+ coingecko_id = TICKER_TO_COINGECKO_ID.get(group.ticker)
45
+ if coingecko_id:
46
+ return coingecko_id
65
47
 
66
- raise ValueError(f"can't get coingecko_id for {group.ticker}")
48
+ fatal(f"Can't get coingecko_id for {group.ticker}. Please add coingecko_id to the config.")
@@ -0,0 +1,23 @@
1
+ from decimal import Decimal
2
+
3
+ from mm_aptos import balance
4
+ from mm_std import Result
5
+
6
+ from mm_balance.constants import RETRIES_BALANCE, TIMEOUT_BALANCE
7
+
8
+
9
+ def get_balance(
10
+ nodes: list[str], wallet: str, token: str | None, decimals: int, proxies: list[str], round_ndigits: int
11
+ ) -> Result[Decimal]:
12
+ if token is None:
13
+ token = "0x1::aptos_coin::AptosCoin" # nosec
14
+ return balance.get_decimal_balance_with_retries(
15
+ RETRIES_BALANCE,
16
+ nodes,
17
+ wallet,
18
+ token,
19
+ decimals=decimals,
20
+ timeout=TIMEOUT_BALANCE,
21
+ proxies=proxies,
22
+ round_ndigits=round_ndigits,
23
+ )
@@ -0,0 +1,27 @@
1
+ from decimal import Decimal
2
+
3
+ from mm_eth import erc20, rpc
4
+ from mm_std import Ok, Result
5
+
6
+ from mm_balance.constants import RETRIES_BALANCE, RETRIES_DECIMALS, TIMEOUT_BALANCE, TIMEOUT_DECIMALS
7
+
8
+
9
+ def get_balance(
10
+ nodes: list[str], wallet: str, token: str | None, decimals: int, proxies: list[str], round_ndigits: int
11
+ ) -> Result[Decimal]:
12
+ if token is not None:
13
+ res = erc20.get_balance(
14
+ nodes,
15
+ token,
16
+ wallet,
17
+ proxies=proxies,
18
+ attempts=RETRIES_BALANCE,
19
+ timeout=TIMEOUT_BALANCE,
20
+ )
21
+ else:
22
+ res = rpc.eth_get_balance(nodes, wallet, proxies=proxies, attempts=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE)
23
+ return res.and_then(lambda b: Ok(round(Decimal(b / 10**decimals), round_ndigits)))
24
+
25
+
26
+ def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
27
+ return erc20.get_decimals(nodes, token_address, timeout=TIMEOUT_DECIMALS, proxies=proxies, attempts=RETRIES_DECIMALS)
@@ -0,0 +1,26 @@
1
+ from decimal import Decimal
2
+
3
+ from mm_solana import balance
4
+ from mm_solana import token as solana_token
5
+ from mm_std import Ok, Result
6
+
7
+ from mm_balance.constants import RETRIES_BALANCE, RETRIES_DECIMALS, TIMEOUT_BALANCE, TIMEOUT_DECIMALS
8
+
9
+
10
+ def get_balance(
11
+ nodes: list[str], wallet: str, token: str | None, decimals: int, proxies: list[str], round_ndigits: int
12
+ ) -> Result[Decimal]:
13
+ if token is None:
14
+ res = balance.get_balance_with_retries(nodes, wallet, retries=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE, proxies=proxies)
15
+ else:
16
+ res = solana_token.get_balance_with_retries(
17
+ nodes, wallet, token, retries=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE, proxies=proxies
18
+ )
19
+
20
+ return res.and_then(lambda b: Ok(round(Decimal(b / 10**decimals), round_ndigits)))
21
+
22
+
23
+ def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
24
+ return solana_token.get_decimals_with_retries(
25
+ nodes, token_address, retries=RETRIES_DECIMALS, timeout=TIMEOUT_DECIMALS, proxies=proxies
26
+ )
@@ -0,0 +1,55 @@
1
+ from mm_std import Err, fatal
2
+
3
+ from mm_balance.config import Config
4
+ from mm_balance.constants import NETWORK_APTOS, NETWORK_BITCOIN, NETWORK_SOLANA, Network
5
+ from mm_balance.rpc import evm, solana
6
+
7
+
8
+ class TokenDecimals(dict[Network, dict[str | None, int]]): # {network: {None: 18}} -- None is for native token, ex. ETH
9
+ def __init__(self, networks: list[Network]) -> None:
10
+ super().__init__()
11
+ for network in networks:
12
+ self[network] = {}
13
+
14
+
15
+ def get_token_decimals(config: Config) -> TokenDecimals:
16
+ result = TokenDecimals(config.networks())
17
+ proxies = config.proxies
18
+
19
+ for group in config.groups:
20
+ # token_decimals is already known
21
+ if group.token_decimals is not None:
22
+ result[group.network][group.token_address] = group.token_decimals
23
+ continue
24
+
25
+ # get token_decimals for known native tokens
26
+ if group.token_address is None:
27
+ if group.network.is_evm_network():
28
+ result[group.network][None] = 18
29
+ elif group.network == NETWORK_SOLANA:
30
+ result[group.network][None] = 9
31
+ elif group.network == NETWORK_BITCOIN:
32
+ result[group.network][None] = 8
33
+ elif group.network == NETWORK_APTOS:
34
+ result[group.network][None] = 8
35
+ else:
36
+ fatal(f"Can't get token decimals for native token on network: {group.network}")
37
+ continue
38
+
39
+ # get token_decimals via RPC
40
+ # TODO: group.token_address must be in normalized form, otherwise it can be different for the same token
41
+ if group.token_address in result[group.network]:
42
+ continue # don't request for a token_decimals twice
43
+
44
+ nodes = config.nodes[group.network]
45
+ if group.network.is_evm_network():
46
+ res = evm.get_token_decimals(nodes, group.token_address, proxies)
47
+ elif group.network == NETWORK_SOLANA:
48
+ res = solana.get_token_decimals(nodes, group.token_address, proxies)
49
+ else:
50
+ fatal(f"unsupported network: {group.network}. Cant get token decimals for {group.token_address}")
51
+ if isinstance(res, Err):
52
+ fatal(f"can't get decimals for token {group.ticker} / {group.token_address}, error={res.err}")
53
+ result[group.network][group.token_address] = res.ok
54
+
55
+ return result
@@ -9,6 +9,7 @@ from mm_balance.balances import Balances
9
9
  from mm_balance.config import Config
10
10
  from mm_balance.constants import USD_STABLECOINS
11
11
  from mm_balance.price import Prices
12
+ from mm_balance.utils import fnumber
12
13
 
13
14
 
14
15
  @dataclass
@@ -18,7 +19,7 @@ class Total:
18
19
  usd_sum: Decimal # sum of all coins in USD
19
20
  usd_sum_share: Decimal
20
21
 
21
- stablecoin_sum: Decimal # sum of usd stablecoins: usdt, usdc
22
+ stablecoin_sum: Decimal # sum of usd stablecoins: usdt, usdc, etc..
22
23
  stablecoin_sum_share: Decimal
23
24
 
24
25
  config: Config
@@ -53,7 +54,6 @@ class Total:
53
54
  coins_share=coins_share,
54
55
  usd_sum=usd_sum,
55
56
  usd_sum_share=usd_sum_share,
56
- # usd_share=usd_share,
57
57
  stablecoin_sum=stablecoin_sum,
58
58
  stablecoin_sum_share=stablecoin_sum_share,
59
59
  config=config,
@@ -82,15 +82,21 @@ class Total:
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)
85
- rows.append([key, value, f"${usd_value}", f"{usd_share}%"])
86
- rows.append(["usd_sum", f"${self.usd_sum}"])
85
+
86
+ value_str = fnumber(value, self.config.format_number_separator)
87
+ usd_value_str = fnumber(usd_value, self.config.format_number_separator, "$")
88
+
89
+ rows.append([key, value_str, usd_value_str, f"{usd_share}%"])
90
+
91
+ rows.append(["stablecoin_sum", fnumber(self.stablecoin_sum, self.config.format_number_separator, "$")])
92
+ rows.append(["usd_sum", fnumber(self.usd_sum, self.config.format_number_separator, "$")])
87
93
  print_table("Total", ["coin", "balance", "usd", "usd_share"], rows)
88
94
 
89
95
  def _print_total_total_without_price(self) -> None:
90
96
  if self.config.print_format == PrintFormat.TABLE:
91
97
  rows = []
92
98
  for key, value in self.coins.items():
93
- rows.append([key, value])
99
+ rows.append([key, fnumber(value, self.config.format_number_separator)])
94
100
  print_table("Total", ["coin", "balance"], rows)
95
101
 
96
102
  def _print_share_total_with_price(self) -> None:
@@ -101,12 +107,16 @@ class Total:
101
107
  usd_share = round(self.stablecoin_sum_share * 100 / self.usd_sum_share, self.config.round_ndigits)
102
108
  else:
103
109
  usd_share = round(usd_value * 100 / self.usd_sum_share, self.config.round_ndigits)
104
- rows.append([key, self.coins_share[key], f"${usd_value}", f"{usd_share}%"])
105
- rows.append(["usd_sum", f"${self.usd_sum_share}"])
110
+ value = fnumber(self.coins_share[key], self.config.format_number_separator)
111
+ usd_value_str = fnumber(usd_value, self.config.format_number_separator, "$")
112
+ rows.append([key, value, usd_value_str, f"{usd_share}%"])
113
+ rows.append(["stablecoin_sum", fnumber(self.stablecoin_sum_share, self.config.format_number_separator, "$")])
114
+ rows.append(["usd_sum", fnumber(self.usd_sum_share, self.config.format_number_separator, "$")])
106
115
  print_table("Total, share", ["coin", "balance", "usd", "usd_share"], rows)
107
116
 
108
117
  def _print_share_total_without_price(self) -> None:
109
118
  rows = []
110
119
  for key, _ in self.coins.items():
111
- rows.append([key, self.coins_share[key]])
120
+ value = fnumber(self.coins_share[key], self.config.format_number_separator)
121
+ rows.append([key, value])
112
122
  print_table("Total, share", ["coin", "balance"], rows)
@@ -0,0 +1,10 @@
1
+ from decimal import Decimal
2
+
3
+
4
+ def fnumber(value: Decimal, separator: str, extra: str | None = None) -> str:
5
+ str_value = f"{value:,}".replace(",", separator)
6
+ if extra == "$":
7
+ return "$" + str_value
8
+ elif extra == "%":
9
+ return str_value + "%"
10
+ return str_value
@@ -409,31 +409,31 @@ wheels = [
409
409
 
410
410
  [[package]]
411
411
  name = "cryptography"
412
- version = "43.0.1"
412
+ version = "43.0.3"
413
413
  source = { registry = "https://pypi.org/simple" }
414
414
  dependencies = [
415
415
  { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
416
416
  ]
417
- sdist = { url = "https://files.pythonhosted.org/packages/de/ba/0664727028b37e249e73879348cc46d45c5c1a2a2e81e8166462953c5755/cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", size = 686927 }
418
- wheels = [
419
- { url = "https://files.pythonhosted.org/packages/58/28/b92c98a04ba762f8cdeb54eba5c4c84e63cac037a7c5e70117d337b15ad6/cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", size = 6223222 },
420
- { url = "https://files.pythonhosted.org/packages/33/13/1193774705783ba364121aa2a60132fa31a668b8ababd5edfa1662354ccd/cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", size = 3794751 },
421
- { url = "https://files.pythonhosted.org/packages/5e/4b/39bb3c4c8cfb3e94e736b8d8859ce5c81536e91a1033b1d26770c4249000/cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", size = 3981827 },
422
- { url = "https://files.pythonhosted.org/packages/ce/dc/1471d4d56608e1013237af334b8a4c35d53895694fbb73882d1c4fd3f55e/cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", size = 3780034 },
423
- { url = "https://files.pythonhosted.org/packages/ad/43/7a9920135b0d5437cc2f8f529fa757431eb6a7736ddfadfdee1cc5890800/cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", size = 3993407 },
424
- { url = "https://files.pythonhosted.org/packages/cc/42/9ab8467af6c0b76f3d9b8f01d1cf25b9c9f3f2151f4acfab888d21c55a72/cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", size = 3886457 },
425
- { url = "https://files.pythonhosted.org/packages/a4/65/430509e31700286ec02868a2457d2111d03ccefc20349d24e58d171ae0a7/cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", size = 4081499 },
426
- { url = "https://files.pythonhosted.org/packages/bb/18/a04b6467e6e09df8c73b91dcee8878f4a438a43a3603dc3cd6f8003b92d8/cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", size = 2616504 },
427
- { url = "https://files.pythonhosted.org/packages/cc/73/0eacbdc437202edcbdc07f3576ed8fb8b0ab79d27bf2c5d822d758a72faa/cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", size = 3067456 },
428
- { url = "https://files.pythonhosted.org/packages/8a/b6/bc54b371f02cffd35ff8dc6baba88304d7cf8e83632566b4b42e00383e03/cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", size = 6225263 },
429
- { url = "https://files.pythonhosted.org/packages/00/0e/8217e348a1fa417ec4c78cd3cdf24154f5e76fd7597343a35bd403650dfd/cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", size = 3794368 },
430
- { url = "https://files.pythonhosted.org/packages/3d/ed/38b6be7254d8f7251fde8054af597ee8afa14f911da67a9410a45f602fc3/cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", size = 3981750 },
431
- { url = "https://files.pythonhosted.org/packages/64/f3/b7946c3887cf7436f002f4cbb1e6aec77b8d299b86be48eeadfefb937c4b/cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", size = 3778925 },
432
- { url = "https://files.pythonhosted.org/packages/ac/7e/ebda4dd4ae098a0990753efbb4b50954f1d03003846b943ea85070782da7/cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", size = 3993152 },
433
- { url = "https://files.pythonhosted.org/packages/43/f6/feebbd78a3e341e3913846a3bb2c29d0b09b1b3af1573c6baabc2533e147/cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", size = 3886392 },
434
- { url = "https://files.pythonhosted.org/packages/bd/4c/ab0b9407d5247576290b4fd8abd06b7f51bd414f04eef0f2800675512d61/cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", size = 4082606 },
435
- { url = "https://files.pythonhosted.org/packages/05/36/e532a671998d6fcfdb9122da16434347a58a6bae9465e527e450e0bc60a5/cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", size = 2617948 },
436
- { url = "https://files.pythonhosted.org/packages/b3/c6/c09cee6968add5ff868525c3815e5dccc0e3c6e89eec58dc9135d3c40e88/cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", size = 3070445 },
417
+ sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989 }
418
+ wheels = [
419
+ { url = "https://files.pythonhosted.org/packages/1f/f3/01fdf26701a26f4b4dbc337a26883ad5bccaa6f1bbbdd29cd89e22f18a1c/cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", size = 6225303 },
420
+ { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905 },
421
+ { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271 },
422
+ { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606 },
423
+ { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484 },
424
+ { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131 },
425
+ { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647 },
426
+ { url = "https://files.pythonhosted.org/packages/56/48/7b6b190f1462818b324e674fa20d1d5ef3e24f2328675b9b16189cbf0b3c/cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", size = 2623873 },
427
+ { url = "https://files.pythonhosted.org/packages/eb/b1/0ebff61a004f7f89e7b65ca95f2f2375679d43d0290672f7713ee3162aff/cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", size = 3068039 },
428
+ { url = "https://files.pythonhosted.org/packages/30/d5/c8b32c047e2e81dd172138f772e81d852c51f0f2ad2ae8a24f1122e9e9a7/cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", size = 6222984 },
429
+ { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968 },
430
+ { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754 },
431
+ { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458 },
432
+ { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220 },
433
+ { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898 },
434
+ { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592 },
435
+ { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145 },
436
+ { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026 },
437
437
  ]
438
438
 
439
439
  [[package]]
@@ -923,11 +923,24 @@ wheels = [
923
923
  { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
924
924
  ]
925
925
 
926
+ [[package]]
927
+ name = "mm-aptos"
928
+ version = "0.1.2"
929
+ source = { registry = "https://pypi.org/simple" }
930
+ dependencies = [
931
+ { name = "mm-std" },
932
+ ]
933
+ sdist = { url = "https://files.pythonhosted.org/packages/12/e9/8feb4cd4593b85b1d0d8ddf3926a5337485c3112a071f7fa502e10050c14/mm_aptos-0.1.2.tar.gz", hash = "sha256:114aa336a0547bd9b5867c53fd28865bb23271de1bd65b6d899d41ebd2d81cb0", size = 35543 }
934
+ wheels = [
935
+ { url = "https://files.pythonhosted.org/packages/c5/ad/c4527bdb312f5927217eb120c073af467601beb1eefc2dfbcda6864e41d2/mm_aptos-0.1.2-py3-none-any.whl", hash = "sha256:2f16737699473ba554d640b6a32a7c6bf4643d471f9fed6c44d6e6fcd747874a", size = 3659 },
936
+ ]
937
+
926
938
  [[package]]
927
939
  name = "mm-balance"
928
- version = "0.1.13"
940
+ version = "0.1.14"
929
941
  source = { editable = "." }
930
942
  dependencies = [
943
+ { name = "mm-aptos" },
931
944
  { name = "mm-btc" },
932
945
  { name = "mm-eth" },
933
946
  { name = "mm-solana" },
@@ -950,6 +963,7 @@ dev = [
950
963
 
951
964
  [package.metadata]
952
965
  requires-dist = [
966
+ { name = "mm-aptos", specifier = "==0.1.2" },
953
967
  { name = "mm-btc", specifier = "==0.1.0" },
954
968
  { name = "mm-eth", specifier = "==0.1.3" },
955
969
  { name = "mm-solana", specifier = "==0.1.5" },
@@ -960,12 +974,12 @@ requires-dist = [
960
974
  dev = [
961
975
  { name = "bandit", specifier = "~=1.7.10" },
962
976
  { name = "coverage", specifier = "~=7.6.3" },
963
- { name = "mypy", specifier = "~=1.12.0" },
977
+ { name = "mypy", specifier = "~=1.12.1" },
964
978
  { name = "pip-audit", specifier = "~=2.7.0" },
965
979
  { name = "pytest", specifier = "~=8.3.3" },
966
980
  { name = "pytest-httpserver", specifier = "~=1.1.0" },
967
981
  { name = "pytest-xdist", specifier = "~=3.6.1" },
968
- { name = "ruff", specifier = "~=0.6.9" },
982
+ { name = "ruff", specifier = "~=0.7.0" },
969
983
  { name = "types-python-dateutil", specifier = "~=2.9.0.20241003" },
970
984
  { name = "types-pyyaml", specifier = "~=6.0.12.20240917" },
971
985
  ]
@@ -1022,7 +1036,7 @@ wheels = [
1022
1036
 
1023
1037
  [[package]]
1024
1038
  name = "mm-std"
1025
- version = "0.1.4"
1039
+ version = "0.1.5"
1026
1040
  source = { registry = "https://pypi.org/simple" }
1027
1041
  dependencies = [
1028
1042
  { name = "cryptography" },
@@ -1035,7 +1049,7 @@ dependencies = [
1035
1049
  { name = "rich" },
1036
1050
  ]
1037
1051
  wheels = [
1038
- { url = "https://files.pythonhosted.org/packages/d6/f6/83a31c17bea16fa4c605631b23a0f76292a9e644ee8fd1d298f85b504ba7/mm_std-0.1.4-py3-none-any.whl", hash = "sha256:77e3423868b44376c973430e8bcc932fce70f29ec970b37d4070d271539ca740", size = 15058 },
1052
+ { url = "https://files.pythonhosted.org/packages/fd/75/3cf498d8d0fc7354d8d94e634d2c668d62209c11e4944cd0917ae92f3cd6/mm_std-0.1.5-py3-none-any.whl", hash = "sha256:603422030d87adec5d9d53fe161a207b8f6dd04f84b51bfabf242fef75955c16", size = 15061 },
1039
1053
  ]
1040
1054
 
1041
1055
  [[package]]
@@ -1118,25 +1132,25 @@ wheels = [
1118
1132
 
1119
1133
  [[package]]
1120
1134
  name = "mypy"
1121
- version = "1.12.0"
1135
+ version = "1.12.1"
1122
1136
  source = { registry = "https://pypi.org/simple" }
1123
1137
  dependencies = [
1124
1138
  { name = "mypy-extensions" },
1125
1139
  { name = "typing-extensions" },
1126
1140
  ]
1127
- sdist = { url = "https://files.pythonhosted.org/packages/f9/70/196a3339459fe22296ac9a883bbd998fcaf0db3e8d9a54cf4f53b722cad4/mypy-1.12.0.tar.gz", hash = "sha256:65a22d87e757ccd95cbbf6f7e181e6caa87128255eb2b6be901bb71b26d8a99d", size = 3149879 }
1141
+ sdist = { url = "https://files.pythonhosted.org/packages/17/03/744330105a74dc004578f47ec27e1bf66b1dd5664ea444d18423e41343bd/mypy-1.12.1.tar.gz", hash = "sha256:f5b3936f7a6d0e8280c9bdef94c7ce4847f5cdfc258fbb2c29a8c1711e8bb96d", size = 3150767 }
1128
1142
  wheels = [
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 },
1143
+ { url = "https://files.pythonhosted.org/packages/8a/74/b9e0e4f06e951e277058f878302faa154d282ca11274c59fe08353f52949/mypy-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9fb83a7be97c498176fb7486cafbb81decccaef1ac339d837c377b0ce3743a7f", size = 11079902 },
1144
+ { url = "https://files.pythonhosted.org/packages/9f/62/fcad290769db3eb0de265094cef5c94d6075c70bc1e42b67eee4ca192dcc/mypy-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:389e307e333879c571029d5b93932cf838b811d3f5395ed1ad05086b52148fb0", size = 10072373 },
1145
+ { url = "https://files.pythonhosted.org/packages/cb/27/9ac78349c2952e4446288ec1174675ab9e0160ed18c2cb1154fa456c54e8/mypy-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b2048a95a21f7a9ebc9fbd075a4fcd310410d078aa0228dbbad7f71335e042", size = 12589779 },
1146
+ { url = "https://files.pythonhosted.org/packages/7c/4a/58cebd122cf1cba95680ac51303fbeb508392413ca64e3e711aa7d4877aa/mypy-1.12.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5932370ccf7ebf83f79d1c157a5929d7ea36313027b0d70a488493dc1b179", size = 13044459 },
1147
+ { url = "https://files.pythonhosted.org/packages/5b/c7/672935e2a3f9bcc07b1b870395a653f665657bef3cdaa504ad99f56eadf0/mypy-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:19bf51f87a295e7ab2894f1d8167622b063492d754e69c3c2fed6563268cb42a", size = 9731919 },
1148
+ { url = "https://files.pythonhosted.org/packages/bb/b0/092be5094840a401940c95224f63bb2a8f09bce9251ac1df180ec523830c/mypy-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d34167d43613ffb1d6c6cdc0cc043bb106cac0aa5d6a4171f77ab92a3c758bcc", size = 11068611 },
1149
+ { url = "https://files.pythonhosted.org/packages/9a/86/f20f53b8f062876c39602243d7a59b5cabd6b24315d8de511d607fa4de6a/mypy-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:427878aa54f2e2c5d8db31fa9010c599ed9f994b3b49e64ae9cd9990c40bd635", size = 10068036 },
1150
+ { url = "https://files.pythonhosted.org/packages/84/c7/1dbd6575785522da1d4c1ac2c419505fcf23bee74811880cac447a4a77ab/mypy-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5fcde63ea2c9f69d6be859a1e6dd35955e87fa81de95bc240143cf00de1f7f81", size = 12585671 },
1151
+ { url = "https://files.pythonhosted.org/packages/46/8a/f6ae18b446eb2bccce54c4bd94065bcfe417d6c67021dcc032bf1e720aff/mypy-1.12.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d54d840f6c052929f4a3d2aab2066af0f45a020b085fe0e40d4583db52aab4e4", size = 13036083 },
1152
+ { url = "https://files.pythonhosted.org/packages/59/e6/fc65fde3dc7156fce8d49ba21c7b1f5d866ad50467bf196ca94a7f6d2c9e/mypy-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:20db6eb1ca3d1de8ece00033b12f793f1ea9da767334b7e8c626a4872090cf02", size = 9735467 },
1153
+ { url = "https://files.pythonhosted.org/packages/84/6b/1db9de4e0764778251fb2d64cb7455cf6db75dc99c9f72c8b7e74b6a8a17/mypy-1.12.1-py3-none-any.whl", hash = "sha256:ce561a09e3bb9863ab77edf29ae3a50e65685ad74bba1431278185b7e5d5486e", size = 2646060 },
1140
1154
  ]
1141
1155
 
1142
1156
  [[package]]
@@ -1598,27 +1612,27 @@ wheels = [
1598
1612
 
1599
1613
  [[package]]
1600
1614
  name = "ruff"
1601
- version = "0.6.9"
1602
- source = { registry = "https://pypi.org/simple" }
1603
- sdist = { url = "https://files.pythonhosted.org/packages/26/0d/6148a48dab5662ca1d5a93b7c0d13c03abd3cc7e2f35db08410e47cef15d/ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2", size = 3095355 }
1604
- wheels = [
1605
- { url = "https://files.pythonhosted.org/packages/6e/8f/f7a0a0ef1818662efb32ed6df16078c95da7a0a3248d64c2410c1e27799f/ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd", size = 10440526 },
1606
- { url = "https://files.pythonhosted.org/packages/8b/69/b179a5faf936a9e2ab45bb412a668e4661eded964ccfa19d533f29463ef6/ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec", size = 10034612 },
1607
- { url = "https://files.pythonhosted.org/packages/c7/ef/fd1b4be979c579d191eeac37b5cfc0ec906de72c8bcd8595e2c81bb700c1/ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c", size = 9706197 },
1608
- { url = "https://files.pythonhosted.org/packages/29/61/b376d775deb5851cb48d893c568b511a6d3625ef2c129ad5698b64fb523c/ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e", size = 10751855 },
1609
- { url = "https://files.pythonhosted.org/packages/13/d7/def9e5f446d75b9a9c19b24231a3a658c075d79163b08582e56fa5dcfa38/ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577", size = 10200889 },
1610
- { url = "https://files.pythonhosted.org/packages/6c/d6/7f34160818bcb6e84ce293a5966cba368d9112ff0289b273fbb689046047/ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829", size = 11038678 },
1611
- { url = "https://files.pythonhosted.org/packages/13/34/a40ff8ae62fb1b26fb8e6fa7e64bc0e0a834b47317880de22edd6bfb54fb/ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5", size = 11808682 },
1612
- { url = "https://files.pythonhosted.org/packages/2e/6d/25a4386ae4009fc798bd10ba48c942d1b0b3e459b5403028f1214b6dd161/ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7", size = 11330446 },
1613
- { url = "https://files.pythonhosted.org/packages/f7/f6/bdf891a9200d692c94ebcd06ae5a2fa5894e522f2c66c2a12dd5d8cb2654/ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f", size = 12483048 },
1614
- { url = "https://files.pythonhosted.org/packages/a7/86/96f4252f41840e325b3fa6c48297e661abb9f564bd7dcc0572398c8daa42/ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa", size = 10936855 },
1615
- { url = "https://files.pythonhosted.org/packages/45/87/801a52d26c8dbf73424238e9908b9ceac430d903c8ef35eab1b44fcfa2bd/ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb", size = 10713007 },
1616
- { url = "https://files.pythonhosted.org/packages/be/27/6f7161d90320a389695e32b6ebdbfbedde28ccbf52451e4b723d7ce744ad/ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0", size = 10274594 },
1617
- { url = "https://files.pythonhosted.org/packages/00/52/dc311775e7b5f5b19831563cb1572ecce63e62681bccc609867711fae317/ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625", size = 10608024 },
1618
- { url = "https://files.pythonhosted.org/packages/98/b6/be0a1ddcbac65a30c985cf7224c4fce786ba2c51e7efeb5178fe410ed3cf/ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039", size = 10982085 },
1619
- { url = "https://files.pythonhosted.org/packages/bb/a4/c84bc13d0b573cf7bb7d17b16d6d29f84267c92d79b2f478d4ce322e8e72/ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d", size = 8522088 },
1620
- { url = "https://files.pythonhosted.org/packages/74/be/fc352bd8ca40daae8740b54c1c3e905a7efe470d420a268cd62150248c91/ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117", size = 9359275 },
1621
- { url = "https://files.pythonhosted.org/packages/3e/14/fd026bc74ded05e2351681545a5f626e78ef831f8edce064d61acd2e6ec7/ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93", size = 8679879 },
1615
+ version = "0.7.0"
1616
+ source = { registry = "https://pypi.org/simple" }
1617
+ sdist = { url = "https://files.pythonhosted.org/packages/2c/c7/f3367d1da5d568192968c5c9e7f3d51fb317b9ac04828493b23d8fce8ce6/ruff-0.7.0.tar.gz", hash = "sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b", size = 3146645 }
1618
+ wheels = [
1619
+ { url = "https://files.pythonhosted.org/packages/48/59/a0275a0913f3539498d116046dd679cd657fe3b7caf5afe1733319414932/ruff-0.7.0-py3-none-linux_armv6l.whl", hash = "sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628", size = 10434007 },
1620
+ { url = "https://files.pythonhosted.org/packages/cd/94/da0ba5f956d04c90dd899209904210600009dcda039ce840d83eb4298c7d/ruff-0.7.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737", size = 10048066 },
1621
+ { url = "https://files.pythonhosted.org/packages/57/1d/e5cc149ecc46e4f203403a79ccd170fad52d316f98b87d0f63b1945567db/ruff-0.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06", size = 9711389 },
1622
+ { url = "https://files.pythonhosted.org/packages/05/67/fb7ea2c869c539725a16c5bc294e9aa34f8b1b6fe702f1d173a5da517c2b/ruff-0.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be", size = 10755174 },
1623
+ { url = "https://files.pythonhosted.org/packages/5f/f0/13703bc50536a0613ea3dce991116e5f0917a1f05528c6ab738b33c08d3f/ruff-0.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa", size = 10196040 },
1624
+ { url = "https://files.pythonhosted.org/packages/99/c1/77b04ab20324ab03d333522ee55fb0f1c38e3ca0d326b4905f82ce6b6c70/ruff-0.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495", size = 11033684 },
1625
+ { url = "https://files.pythonhosted.org/packages/f2/97/f463334dc4efeea3551cd109163df15561c18a1c3ec13d51643740fd36ba/ruff-0.7.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598", size = 11803700 },
1626
+ { url = "https://files.pythonhosted.org/packages/b4/f8/a31d40c4bb92933d376a53e7c5d0245d9b27841357e4820e96d38f54b480/ruff-0.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e", size = 11347848 },
1627
+ { url = "https://files.pythonhosted.org/packages/83/62/0c133b35ddaf91c65c30a56718b80bdef36bfffc35684d29e3a4878e0ea3/ruff-0.7.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914", size = 12480632 },
1628
+ { url = "https://files.pythonhosted.org/packages/46/96/464058dd1d980014fb5aa0a1254e78799efb3096fc7a4823cd66a1621276/ruff-0.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9", size = 10941919 },
1629
+ { url = "https://files.pythonhosted.org/packages/a0/f7/bda37ec77986a435dde44e1f59374aebf4282a5fa9cf17735315b847141f/ruff-0.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4", size = 10745519 },
1630
+ { url = "https://files.pythonhosted.org/packages/c2/33/5f77fc317027c057b61a848020a47442a1cbf12e592df0e41e21f4d0f3bd/ruff-0.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9", size = 10284872 },
1631
+ { url = "https://files.pythonhosted.org/packages/ff/50/98aec292bc9537f640b8d031c55f3414bf15b6ed13b3e943fed75ac927b9/ruff-0.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d", size = 10600334 },
1632
+ { url = "https://files.pythonhosted.org/packages/f2/85/12607ae3201423a179b8cfadc7cb1e57d02cd0135e45bd0445acb4cef327/ruff-0.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11", size = 11017333 },
1633
+ { url = "https://files.pythonhosted.org/packages/d4/7f/3b85a56879e705d5f46ec14daf8a439fca05c3081720fe3dc3209100922d/ruff-0.7.0-py3-none-win32.whl", hash = "sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec", size = 8570962 },
1634
+ { url = "https://files.pythonhosted.org/packages/39/9f/c5ee2b40d377354dabcc23cff47eb299de4b4d06d345068f8f8cc1eadac8/ruff-0.7.0-py3-none-win_amd64.whl", hash = "sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2", size = 9365544 },
1635
+ { url = "https://files.pythonhosted.org/packages/89/8b/ee1509f60148cecba644aa718f6633216784302458340311898aaf0b1bed/ruff-0.7.0-py3-none-win_arm64.whl", hash = "sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e", size = 8695763 },
1622
1636
  ]
1623
1637
 
1624
1638
  [[package]]
@@ -1,56 +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 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
- 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
- }
@@ -1,31 +0,0 @@
1
- from decimal import Decimal
2
-
3
- from mm_eth import erc20, rpc
4
- from mm_std import Ok, Result
5
-
6
- from mm_balance.constants import RETRIES_BALANCE, RETRIES_DECIMALS, TIMEOUT_BALANCE, TIMEOUT_DECIMALS
7
-
8
-
9
- def get_native_balance(nodes: list[str], address: str, proxies: list[str], round_ndigits: int) -> Result[Decimal]:
10
- return rpc.eth_get_balance(nodes, address, proxies=proxies, attempts=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE).and_then(
11
- lambda b: Ok(round(Decimal(b / 10**18), round_ndigits)),
12
- )
13
-
14
-
15
- def get_token_balance(
16
- nodes: list[str], wallet_address: str, token_address: str, decimals: int, proxies: list[str], round_ndigits: int
17
- ) -> Result[Decimal]:
18
- return erc20.get_balance(
19
- nodes,
20
- token_address,
21
- wallet_address,
22
- proxies=proxies,
23
- attempts=RETRIES_BALANCE,
24
- timeout=TIMEOUT_BALANCE,
25
- ).and_then(
26
- lambda b: Ok(round(Decimal(b / 10**decimals), round_ndigits)),
27
- )
28
-
29
-
30
- def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
31
- return erc20.get_decimals(nodes, token_address, timeout=TIMEOUT_DECIMALS, proxies=proxies, attempts=RETRIES_DECIMALS)
@@ -1,26 +0,0 @@
1
- from decimal import Decimal
2
-
3
- from mm_solana import balance, token
4
- from mm_std import Ok, Result
5
-
6
- from mm_balance.constants import RETRIES_BALANCE, RETRIES_DECIMALS, TIMEOUT_BALANCE, TIMEOUT_DECIMALS
7
-
8
-
9
- def get_native_balance(nodes: list[str], address: str, proxies: list[str], round_ndigits: int) -> Result[Decimal]:
10
- return balance.get_balance_with_retries(
11
- nodes, address, retries=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE, proxies=proxies
12
- ).and_then(lambda b: Ok(round(Decimal(b / 1_000_000_000), round_ndigits)))
13
-
14
-
15
- def get_token_balance(
16
- nodes: list[str], wallet_address: str, token_address: str, decimals: int, proxies: list[str], round_ndigits: int
17
- ) -> Result[Decimal]:
18
- return token.get_balance_with_retries(
19
- nodes, wallet_address, token_address, retries=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE, proxies=proxies
20
- ).and_then(lambda b: Ok(round(Decimal(b / 10**decimals), round_ndigits)))
21
-
22
-
23
- def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
24
- return token.get_decimals_with_retries(
25
- nodes, token_address, retries=RETRIES_DECIMALS, timeout=TIMEOUT_DECIMALS, proxies=proxies
26
- )
@@ -1,37 +0,0 @@
1
- from mm_std import Err, fatal
2
-
3
- from mm_balance.config import Config
4
- from mm_balance.constants import Network
5
- from mm_balance.rpc import eth, solana
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.ETHEREUM:
27
- decimals_res = eth.get_token_decimals(nodes, group.token_address, proxies)
28
- case Network.SOLANA:
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.ticker} / {group.token_address}, error={decimals_res.err}")
35
- result[group.network][group.token_address] = decimals_res.ok
36
-
37
- return result
File without changes
File without changes
File without changes