mm-balance 0.1.9__tar.gz → 0.1.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mm_balance-0.1.9 → mm_balance-0.1.10}/PKG-INFO +2 -2
- {mm_balance-0.1.9 → mm_balance-0.1.10}/pyproject.toml +6 -5
- mm_balance-0.1.10/src/mm_balance/balances.py +169 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/cli.py +3 -1
- {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/config/example.yml +5 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/config.py +9 -23
- mm_balance-0.1.10/src/mm_balance/rpc/eth.py +46 -0
- mm_balance-0.1.10/src/mm_balance/rpc/solana.py +22 -0
- mm_balance-0.1.10/src/mm_balance/token_decimals.py +37 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/total.py +0 -3
- {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/types.py +6 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/uv.lock +79 -63
- mm_balance-0.1.9/src/mm_balance/balances.py +0 -98
- mm_balance-0.1.9/src/mm_balance/rpc/eth.py +0 -38
- mm_balance-0.1.9/src/mm_balance/rpc/solana.py +0 -19
- {mm_balance-0.1.9 → mm_balance-0.1.10}/.gitignore +0 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/README.md +0 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/justfile +0 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/__init__.py +0 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/output.py +0 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/price.py +0 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/rpc/__init__.py +0 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/src/mm_balance/rpc/btc.py +0 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/tests/__init__.py +0 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/tests/conftest.py +0 -0
- {mm_balance-0.1.9 → mm_balance-0.1.10}/tests/test_dummy.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mm-balance
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Requires-Python: >=3.12
|
|
5
5
|
Requires-Dist: mm-btc==0.1.0
|
|
6
6
|
Requires-Dist: mm-eth==0.1.3
|
|
7
|
-
Requires-Dist: mm-solana==0.1.
|
|
7
|
+
Requires-Dist: mm-solana==0.1.4
|
|
8
8
|
Requires-Dist: typer>=0.12.5
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mm-balance"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.10"
|
|
4
4
|
description = ""
|
|
5
5
|
requires-python = ">=3.12"
|
|
6
6
|
dependencies = [
|
|
7
7
|
"mm-btc==0.1.0",
|
|
8
8
|
"mm-eth==0.1.3",
|
|
9
|
-
"mm-solana==0.1.
|
|
9
|
+
"mm-solana==0.1.4",
|
|
10
10
|
"typer>=0.12.5",
|
|
11
11
|
]
|
|
12
12
|
[project.scripts]
|
|
@@ -21,11 +21,11 @@ dev-dependencies = [
|
|
|
21
21
|
"pytest~=8.3.3",
|
|
22
22
|
"pytest-xdist~=3.6.1",
|
|
23
23
|
"pytest-httpserver~=1.1.0",
|
|
24
|
-
"coverage~=7.6.
|
|
24
|
+
"coverage~=7.6.3",
|
|
25
25
|
"ruff~=0.6.9",
|
|
26
26
|
"pip-audit~=2.7.0",
|
|
27
27
|
"bandit~=1.7.10",
|
|
28
|
-
"mypy~=1.
|
|
28
|
+
"mypy~=1.12.0",
|
|
29
29
|
"types-python-dateutil~=2.9.0.20241003",
|
|
30
30
|
"types-PyYAML~=6.0.12.20240917",
|
|
31
31
|
]
|
|
@@ -56,7 +56,8 @@ lint.ignore = [
|
|
|
56
56
|
"A003", # builtin-attribute-shadowing
|
|
57
57
|
# "B008", # function-call-argument-default
|
|
58
58
|
"UP040", # non-pep695-type-alias
|
|
59
|
-
"COM812"
|
|
59
|
+
"COM812",
|
|
60
|
+
"RUF012"
|
|
60
61
|
]
|
|
61
62
|
|
|
62
63
|
[tool.bandit]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from mm_std import ConcurrentTasks, Result
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from rich.progress import TaskID
|
|
8
|
+
|
|
9
|
+
from mm_balance import output
|
|
10
|
+
from mm_balance.config import Config
|
|
11
|
+
from mm_balance.rpc import btc, eth, solana
|
|
12
|
+
from mm_balance.token_decimals import TokenDecimals
|
|
13
|
+
from mm_balance.types import Network
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Balances:
|
|
17
|
+
class Balance(BaseModel):
|
|
18
|
+
group_index: int
|
|
19
|
+
address: str
|
|
20
|
+
token_address: str | None
|
|
21
|
+
balance: Result[Decimal] | None = None
|
|
22
|
+
|
|
23
|
+
def __init__(self, config: Config, token_decimals: TokenDecimals):
|
|
24
|
+
self.config = config
|
|
25
|
+
self.token_decimals = token_decimals
|
|
26
|
+
self.tasks: dict[Network, list[Balances.Balance]] = {network: [] for network in Network}
|
|
27
|
+
self.progress_bar = output.create_progress_bar()
|
|
28
|
+
self.progress_bar_task: dict[Network, TaskID] = {}
|
|
29
|
+
|
|
30
|
+
for idx, group in enumerate(config.groups):
|
|
31
|
+
task_list = [Balances.Balance(group_index=idx, address=a, token_address=group.token_address) for a in group.addresses]
|
|
32
|
+
self.tasks[group.network].extend(task_list)
|
|
33
|
+
|
|
34
|
+
for network in Network:
|
|
35
|
+
self.progress_bar_task[network] = output.create_progress_task(
|
|
36
|
+
self.progress_bar, network.value, len(self.tasks[network])
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def process(self) -> None:
|
|
40
|
+
with self.progress_bar:
|
|
41
|
+
job = ConcurrentTasks(max_workers=10)
|
|
42
|
+
for network in Network:
|
|
43
|
+
job.add_task(network.value, self._process_network, args=(network,))
|
|
44
|
+
job.execute()
|
|
45
|
+
|
|
46
|
+
def _process_network(self, network: Network) -> None:
|
|
47
|
+
job = ConcurrentTasks(max_workers=self.config.workers[network])
|
|
48
|
+
for idx, task in enumerate(self.tasks[network]):
|
|
49
|
+
job.add_task(str(idx), self._get_balance, args=(network, task.address, task.token_address))
|
|
50
|
+
job.execute()
|
|
51
|
+
for idx, _task in enumerate(self.tasks[network]):
|
|
52
|
+
self.tasks[network][idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
|
|
53
|
+
|
|
54
|
+
def _get_balance(self, network: Network, wallet_address: str, token_address: str | None) -> Result[Decimal]:
|
|
55
|
+
nodes = self.config.nodes[network]
|
|
56
|
+
round_ndigits = self.config.round_ndigits
|
|
57
|
+
proxies = self.config.proxies
|
|
58
|
+
token_decimals = self.token_decimals[network][token_address] if token_address else -1
|
|
59
|
+
match network:
|
|
60
|
+
case Network.BTC:
|
|
61
|
+
res = btc.get_balance(wallet_address, self.config)
|
|
62
|
+
case Network.ETH:
|
|
63
|
+
if token_address is None:
|
|
64
|
+
res = eth.get_native_balance(nodes, wallet_address, proxies, round_ndigits)
|
|
65
|
+
else:
|
|
66
|
+
res = eth.get_token_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
|
|
67
|
+
case Network.SOL:
|
|
68
|
+
if token_address is None:
|
|
69
|
+
res = solana.get_native_balance(nodes, wallet_address, proxies, round_ndigits)
|
|
70
|
+
else:
|
|
71
|
+
res = solana.get_token_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
|
|
72
|
+
|
|
73
|
+
case _:
|
|
74
|
+
raise ValueError
|
|
75
|
+
|
|
76
|
+
self.progress_bar.update(self.progress_bar_task[network], advance=1)
|
|
77
|
+
return res
|
|
78
|
+
|
|
79
|
+
def get_group_balances(self, group_index: int, network: Network) -> list[Balance]:
|
|
80
|
+
# TODO: can we get network by group_index?
|
|
81
|
+
return [b for b in self.tasks[network] if b.group_index == group_index]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# class Balances2(BaseModel):
|
|
85
|
+
# class Balance(BaseModel):
|
|
86
|
+
# group_index: int
|
|
87
|
+
# address: str
|
|
88
|
+
# token_address: str | None
|
|
89
|
+
# balance: Result[Decimal] | None = None
|
|
90
|
+
#
|
|
91
|
+
# config: Config
|
|
92
|
+
# token_decimals: TokenDecimals
|
|
93
|
+
# # separate balance tasks on networks
|
|
94
|
+
# btc: list[Balance]
|
|
95
|
+
# eth: list[Balance]
|
|
96
|
+
# sol: list[Balance]
|
|
97
|
+
#
|
|
98
|
+
# def network_tasks(self, network: Network) -> list[Balance]:
|
|
99
|
+
# if network == Network.BTC:
|
|
100
|
+
# return self.btc
|
|
101
|
+
# elif network == Network.ETH:
|
|
102
|
+
# return self.eth
|
|
103
|
+
# elif network == Network.SOL:
|
|
104
|
+
# return self.sol
|
|
105
|
+
# else:
|
|
106
|
+
# raise ValueError
|
|
107
|
+
#
|
|
108
|
+
# def get_group_balances(self, group_index: int, network: Network) -> list[Balance]:
|
|
109
|
+
# # TODO: can we get network by group_index?
|
|
110
|
+
# if network == Network.BTC:
|
|
111
|
+
# network_balances = self.btc
|
|
112
|
+
# elif network == Network.ETH:
|
|
113
|
+
# network_balances = self.eth
|
|
114
|
+
# elif network == Network.SOL:
|
|
115
|
+
# network_balances = self.sol
|
|
116
|
+
# else:
|
|
117
|
+
# raise ValueError
|
|
118
|
+
#
|
|
119
|
+
# return [b for b in network_balances if b.group_index == group_index]
|
|
120
|
+
#
|
|
121
|
+
# def process(self) -> None:
|
|
122
|
+
# progress = output.create_progress_bar()
|
|
123
|
+
# task_btc = output.create_progress_task(progress, "btc", len(self.btc))
|
|
124
|
+
# task_eth = output.create_progress_task(progress, "eth", len(self.eth))
|
|
125
|
+
# task_sol = output.create_progress_task(progress, "sol", len(self.sol))
|
|
126
|
+
# with progress:
|
|
127
|
+
# job = ConcurrentTasks()
|
|
128
|
+
# job.add_task("btc", self._process_btc, args=(progress, task_btc))
|
|
129
|
+
# job.add_task("eth", self._process_eth, args=(progress, task_eth))
|
|
130
|
+
# job.add_task("sol", self._process_sol, args=(progress, task_sol))
|
|
131
|
+
# job.execute()
|
|
132
|
+
#
|
|
133
|
+
# def _process_btc(self, progress: Progress, task_id: TaskID) -> None:
|
|
134
|
+
# job = ConcurrentTasks(max_workers=self.config.workers.btc)
|
|
135
|
+
# for idx, task in enumerate(self.btc):
|
|
136
|
+
# job.add_task(str(idx), btc.get_balance, args=(task.address, self.config, progress, task_id))
|
|
137
|
+
# job.execute()
|
|
138
|
+
# for idx, _task in enumerate(self.btc):
|
|
139
|
+
# self.btc[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
|
|
140
|
+
#
|
|
141
|
+
# def _process_eth(self, progress: Progress, task_id: TaskID) -> None:
|
|
142
|
+
# job = ConcurrentTasks(max_workers=self.config.workers.eth)
|
|
143
|
+
# for idx, task in enumerate(self.eth):
|
|
144
|
+
# job.add_task(str(idx), self._get_balance,
|
|
145
|
+
# args=(Network.ETH, task.address, task.token_address, self.config, progress, task_id))
|
|
146
|
+
# job.execute()
|
|
147
|
+
# for idx, _task in enumerate(self.eth):
|
|
148
|
+
# self.eth[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
|
|
149
|
+
#
|
|
150
|
+
# def _process_sol(self, progress: Progress, task_id: TaskID) -> None:
|
|
151
|
+
# job = ConcurrentTasks(max_workers=self.config.workers.sol)
|
|
152
|
+
# for idx, task in enumerate(self.sol):
|
|
153
|
+
# job.add_task(str(idx), solana.get_balance, args=(task.address, self.config, progress, task_id))
|
|
154
|
+
# job.execute()
|
|
155
|
+
# for idx, _task in enumerate(self.sol):
|
|
156
|
+
# self.sol[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
|
|
157
|
+
#
|
|
158
|
+
# @staticmethod
|
|
159
|
+
# def from_config(config: Config, token_decimals: TokenDecimals) -> Balances:
|
|
160
|
+
# tasks = Balances(config=config, btc=[], eth=[], sol=[], token_decimals=token_decimals)
|
|
161
|
+
# for idx, group in enumerate(config.groups):
|
|
162
|
+
# task_list = [Balances.Balance(group_index=idx, address=a, token_address=group.token_address) for a in group.addresses] # noqa
|
|
163
|
+
# if group.network == Network.BTC:
|
|
164
|
+
# tasks.btc.extend(task_list)
|
|
165
|
+
# elif group.network == Network.ETH:
|
|
166
|
+
# tasks.eth.extend(task_list)
|
|
167
|
+
# elif group.network == Network.SOL:
|
|
168
|
+
# tasks.sol.extend(task_list)
|
|
169
|
+
# return tasks
|
|
@@ -9,6 +9,7 @@ from mm_balance import output
|
|
|
9
9
|
from mm_balance.balances import Balances
|
|
10
10
|
from mm_balance.config import Config
|
|
11
11
|
from mm_balance.price import Prices, get_prices
|
|
12
|
+
from mm_balance.token_decimals import get_token_decimals
|
|
12
13
|
|
|
13
14
|
app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
|
|
14
15
|
|
|
@@ -31,7 +32,8 @@ def cli(
|
|
|
31
32
|
config = Config.read_config(config_path, zip_password=zip_password)
|
|
32
33
|
|
|
33
34
|
prices = get_prices(config) if config.price else Prices()
|
|
34
|
-
|
|
35
|
+
token_decimals = get_token_decimals(config)
|
|
36
|
+
balances = Balances(config, token_decimals)
|
|
35
37
|
balances.process()
|
|
36
38
|
|
|
37
39
|
output.print_prices(config, prices)
|
|
@@ -4,10 +4,10 @@ from decimal import Decimal
|
|
|
4
4
|
from typing import Any, Self
|
|
5
5
|
|
|
6
6
|
import pydash
|
|
7
|
-
from mm_std import BaseConfig,
|
|
7
|
+
from mm_std import BaseConfig, PrintFormat, fatal, hr
|
|
8
8
|
from pydantic import Field, field_validator, model_validator
|
|
9
9
|
|
|
10
|
-
from mm_balance.types import DEFAULT_ETH_NODES, DEFAULT_SOL_NODES, EthTokenAddress, Network
|
|
10
|
+
from mm_balance.types import DEFAULT_ETH_NODES, DEFAULT_SOL_NODES, EthTokenAddress, Network, SolTokenAddress
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Config(BaseConfig):
|
|
@@ -67,14 +67,6 @@ class Config(BaseConfig):
|
|
|
67
67
|
def to_list_validator(cls, v: str | list[str] | None) -> list[str]:
|
|
68
68
|
return cls.to_list_str_validator(v, unique=True, remove_comments=True, split_line=True)
|
|
69
69
|
|
|
70
|
-
class Workers(BaseConfig):
|
|
71
|
-
btc: int = 5
|
|
72
|
-
eth: int = 5
|
|
73
|
-
sol: int = 5
|
|
74
|
-
|
|
75
|
-
class TokenDecimals(BaseConfig):
|
|
76
|
-
eth: dict[str, int] = Field(default_factory=dict)
|
|
77
|
-
|
|
78
70
|
groups: list[Group]
|
|
79
71
|
addresses: list[AddressGroup] = Field(default_factory=list)
|
|
80
72
|
|
|
@@ -85,9 +77,7 @@ class Config(BaseConfig):
|
|
|
85
77
|
print_format: PrintFormat = PrintFormat.TABLE
|
|
86
78
|
price: bool = True
|
|
87
79
|
|
|
88
|
-
|
|
89
|
-
workers: Workers = Workers()
|
|
90
|
-
token_decimals: TokenDecimals = TokenDecimals()
|
|
80
|
+
workers: dict[Network, int] = {network: 5 for network in Network}
|
|
91
81
|
|
|
92
82
|
def btc_groups(self) -> list[Group]:
|
|
93
83
|
return [g for g in self.groups if g.network == Network.BTC]
|
|
@@ -119,16 +109,6 @@ class Config(BaseConfig):
|
|
|
119
109
|
if Network.SOL not in self.nodes:
|
|
120
110
|
self.nodes[Network.SOL] = DEFAULT_SOL_NODES
|
|
121
111
|
|
|
122
|
-
# load token decimals
|
|
123
|
-
for group in self.groups:
|
|
124
|
-
if group.network == Network.ETH and group.token_address is not None:
|
|
125
|
-
from mm_balance.rpc import eth
|
|
126
|
-
|
|
127
|
-
decimals_res = eth.get_token_decimals(group.token_address, self)
|
|
128
|
-
if isinstance(decimals_res, Err):
|
|
129
|
-
fatal(f"can't get decimals for token {group.coin} / {group.token_address}, error={decimals_res.err}")
|
|
130
|
-
self.token_decimals.eth[group.token_address] = decimals_res.ok
|
|
131
|
-
|
|
132
112
|
return self
|
|
133
113
|
|
|
134
114
|
|
|
@@ -151,6 +131,12 @@ def detect_token_address(coin: str, network: str) -> str | None:
|
|
|
151
131
|
if coin.lower() == "usdc":
|
|
152
132
|
return EthTokenAddress.USDC
|
|
153
133
|
|
|
134
|
+
if network == Network.SOL.lower():
|
|
135
|
+
if coin.lower() == "usdt":
|
|
136
|
+
return SolTokenAddress.USDT
|
|
137
|
+
if coin.lower() == "usdc":
|
|
138
|
+
return SolTokenAddress.USDC
|
|
139
|
+
|
|
154
140
|
|
|
155
141
|
def get_proxies(proxies_url: str) -> list[str]:
|
|
156
142
|
try:
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
from mm_eth import erc20, rpc
|
|
4
|
+
from mm_std import Ok, Result
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_native_balance(nodes: list[str], address: str, proxies: list[str], round_ndigits: int) -> Result[Decimal]:
|
|
8
|
+
return rpc.eth_get_balance(nodes, address, proxies=proxies, attempts=5, timeout=10).and_then(
|
|
9
|
+
lambda b: Ok(round(Decimal(b / 10**18), round_ndigits)),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_token_balance(
|
|
14
|
+
nodes: list[str], wallet_address: str, token_address: str, decimals: int, proxies: list[str], round_ndigits: int
|
|
15
|
+
) -> Result[Decimal]:
|
|
16
|
+
return erc20.get_balance(
|
|
17
|
+
nodes,
|
|
18
|
+
token_address,
|
|
19
|
+
wallet_address,
|
|
20
|
+
proxies=proxies,
|
|
21
|
+
attempts=5,
|
|
22
|
+
timeout=10,
|
|
23
|
+
).and_then(
|
|
24
|
+
lambda b: Ok(round(Decimal(b / 10**decimals), round_ndigits)),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
|
|
29
|
+
return erc20.get_decimals(nodes, token_address, timeout=10, proxies=proxies, attempts=5)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# def get_balance(
|
|
33
|
+
# address: str, token_address: str | None, config: Config, progress: Progress | None = None, task_id: TaskID | None = None
|
|
34
|
+
# ) -> Result[Decimal]:
|
|
35
|
+
# res: Result[Decimal]
|
|
36
|
+
#
|
|
37
|
+
# if token_address is not None:
|
|
38
|
+
#
|
|
39
|
+
# else:
|
|
40
|
+
# res = rpc.eth_get_balance(config.nodes[Network.ETH], address, proxies=config.proxies, attempts=5, timeout=10).and_then(
|
|
41
|
+
# lambda b: Ok(round(Decimal(b / 10 ** 18), config.round_ndigits)),
|
|
42
|
+
# )
|
|
43
|
+
#
|
|
44
|
+
# if task_id is not None and progress is not None:
|
|
45
|
+
# progress.update(task_id, advance=1)
|
|
46
|
+
# return res
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
from mm_solana import balance, token
|
|
4
|
+
from mm_std import Ok, Result
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_native_balance(nodes: list[str], address: str, proxies: list[str], round_ndigits: int) -> Result[Decimal]:
|
|
8
|
+
return balance.get_balance_with_retries(nodes, address, retries=5, timeout=5, proxies=proxies).and_then(
|
|
9
|
+
lambda b: Ok(round(Decimal(b / 1_000_000_000), round_ndigits)),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_token_balance(
|
|
14
|
+
nodes: list[str], wallet_address: str, token_address: str, decimals: int, proxies: list[str], round_ndigits: int
|
|
15
|
+
) -> Result[Decimal]:
|
|
16
|
+
return token.get_balance_with_retries(nodes, wallet_address, token_address, retries=5, timeout=5, proxies=proxies).and_then(
|
|
17
|
+
lambda b: Ok(round(Decimal(b / 10**decimals), round_ndigits))
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
|
|
22
|
+
return token.get_decimals_with_retries(nodes, token_address, retries=5, timeout=5, proxies=proxies)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from mm_std import Err, fatal
|
|
2
|
+
|
|
3
|
+
from mm_balance.config import Config
|
|
4
|
+
from mm_balance.rpc import eth, solana
|
|
5
|
+
from mm_balance.types import Network
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TokenDecimals(dict[Network, dict[str, int]]):
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
super().__init__()
|
|
11
|
+
for network in Network:
|
|
12
|
+
self[network] = {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_token_decimals(config: Config) -> TokenDecimals:
|
|
16
|
+
result = TokenDecimals()
|
|
17
|
+
|
|
18
|
+
for group in config.groups:
|
|
19
|
+
if group.token_address is None or group.token_address in result[group.network]:
|
|
20
|
+
continue
|
|
21
|
+
|
|
22
|
+
nodes = config.nodes[group.network]
|
|
23
|
+
proxies = config.proxies
|
|
24
|
+
|
|
25
|
+
match group.network:
|
|
26
|
+
case Network.ETH:
|
|
27
|
+
decimals_res = eth.get_token_decimals(nodes, group.token_address, proxies)
|
|
28
|
+
case Network.SOL:
|
|
29
|
+
decimals_res = solana.get_token_decimals(nodes, group.token_address, proxies)
|
|
30
|
+
case _:
|
|
31
|
+
raise ValueError(f"unsupported network: {group.network}. Cant get token decimals for {group.token_address}")
|
|
32
|
+
|
|
33
|
+
if isinstance(decimals_res, Err):
|
|
34
|
+
fatal(f"can't get decimals for token {group.coin} / {group.token_address}, error={decimals_res.err}")
|
|
35
|
+
result[group.network][group.token_address] = decimals_res.ok
|
|
36
|
+
|
|
37
|
+
return result
|
|
@@ -15,7 +15,6 @@ from mm_balance.types import Coin
|
|
|
15
15
|
class Total:
|
|
16
16
|
coins: dict[str, Decimal]
|
|
17
17
|
coins_share: dict[str, Decimal]
|
|
18
|
-
# usd_share: dict[str, Decimal] # all stablecoins have key 'usd'
|
|
19
18
|
usd_sum: Decimal # sum of all coins in USD
|
|
20
19
|
usd_sum_share: Decimal
|
|
21
20
|
|
|
@@ -29,7 +28,6 @@ class Total:
|
|
|
29
28
|
def calc(cls, balances: Balances, prices: Prices, config: Config) -> Self:
|
|
30
29
|
coins: dict[str, Decimal] = defaultdict(Decimal)
|
|
31
30
|
coins_share: dict[str, Decimal] = defaultdict(Decimal)
|
|
32
|
-
# usd_share: dict[str, Decimal] = defaultdict(Decimal)
|
|
33
31
|
usd_sum = Decimal(0)
|
|
34
32
|
usd_sum_share = Decimal(0)
|
|
35
33
|
|
|
@@ -37,7 +35,6 @@ class Total:
|
|
|
37
35
|
stablecoin_sum_share = Decimal(0)
|
|
38
36
|
for group_index, group in enumerate(config.groups):
|
|
39
37
|
balance_sum = Decimal(0)
|
|
40
|
-
# for address_task in [t for t in tasks.network_tasks(group.network) if t.group_index == group_index]:
|
|
41
38
|
for address_task in balances.get_group_balances(group_index, group.network):
|
|
42
39
|
if isinstance(address_task.balance, Ok):
|
|
43
40
|
balance_sum += address_task.balance.ok
|
|
@@ -30,3 +30,9 @@ class Network(str, Enum):
|
|
|
30
30
|
class EthTokenAddress(str, Enum):
|
|
31
31
|
USDT = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
|
32
32
|
USDC = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@unique
|
|
36
|
+
class SolTokenAddress(str, Enum):
|
|
37
|
+
USDT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
|
|
38
|
+
USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
|
@@ -320,18 +320,6 @@ wheels = [
|
|
|
320
320
|
{ url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
|
|
321
321
|
]
|
|
322
322
|
|
|
323
|
-
[[package]]
|
|
324
|
-
name = "click-aliases"
|
|
325
|
-
version = "1.0.4"
|
|
326
|
-
source = { registry = "https://pypi.org/simple" }
|
|
327
|
-
dependencies = [
|
|
328
|
-
{ name = "click" },
|
|
329
|
-
]
|
|
330
|
-
sdist = { url = "https://files.pythonhosted.org/packages/f6/77/7ff07d287ae8613ce4cc6656bb7d415e6f2b987510b6b7d962b758150633/click_aliases-1.0.4.tar.gz", hash = "sha256:384313c5dc4c4bd64d9eadaff39ad91352747e8cdfd2f95d504d914c01eb4eda", size = 2904 }
|
|
331
|
-
wheels = [
|
|
332
|
-
{ url = "https://files.pythonhosted.org/packages/7e/b7/dd8bf2f67acf74707e97782e40188c3911799d69d53cb71113e82be8caa2/click_aliases-1.0.4-py3-none-any.whl", hash = "sha256:27b416fd9117c05a744459ea13d01db8fa489e6efbc7e23a8ef5a744b7afbfb9", size = 3202 },
|
|
333
|
-
]
|
|
334
|
-
|
|
335
323
|
[[package]]
|
|
336
324
|
name = "coincurve"
|
|
337
325
|
version = "20.0.0"
|
|
@@ -383,40 +371,40 @@ wheels = [
|
|
|
383
371
|
|
|
384
372
|
[[package]]
|
|
385
373
|
name = "coverage"
|
|
386
|
-
version = "7.6.
|
|
387
|
-
source = { registry = "https://pypi.org/simple" }
|
|
388
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
389
|
-
wheels = [
|
|
390
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
391
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
392
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
393
|
-
{ url = "https://files.pythonhosted.org/packages/0f/
|
|
394
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
395
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
396
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
397
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
398
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
399
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
400
|
-
{ url = "https://files.pythonhosted.org/packages/8c/
|
|
401
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
402
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
403
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
404
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
405
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
406
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
407
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
408
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
409
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
410
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
411
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
412
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
413
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
414
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
415
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
416
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
417
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
418
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
419
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
374
|
+
version = "7.6.3"
|
|
375
|
+
source = { registry = "https://pypi.org/simple" }
|
|
376
|
+
sdist = { url = "https://files.pythonhosted.org/packages/12/50/51ec496dd2ad84ca3e5f67de23f6de630be923dd6f5aed31bb60eda540e5/coverage-7.6.3.tar.gz", hash = "sha256:bb7d5fe92bd0dc235f63ebe9f8c6e0884f7360f88f3411bfed1350c872ef2054", size = 798337 }
|
|
377
|
+
wheels = [
|
|
378
|
+
{ url = "https://files.pythonhosted.org/packages/11/3d/b88ca9d63404ed1d2942236757d876c5fb30ab300fc6e68d6e1e8928dc34/coverage-7.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27bd5f18d8f2879e45724b0ce74f61811639a846ff0e5c0395b7818fae87aec6", size = 206890 },
|
|
379
|
+
{ url = "https://files.pythonhosted.org/packages/90/d1/11307a6f28dc496ff8ee17258e20ecca1c48b537113146a114aa4e29d4e7/coverage-7.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d546cfa78844b8b9c1c0533de1851569a13f87449897bbc95d698d1d3cb2a30f", size = 207132 },
|
|
380
|
+
{ url = "https://files.pythonhosted.org/packages/61/5d/be9f27811e767b8bd8b5836c5f05708263cc1f2c24d8244f36a5f7baee75/coverage-7.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9975442f2e7a5cfcf87299c26b5a45266ab0696348420049b9b94b2ad3d40234", size = 240146 },
|
|
381
|
+
{ url = "https://files.pythonhosted.org/packages/37/0f/0b61bfa7aada98936cc433c8bd2e9f90c3fdc02607bdade04ffa1b8f83ba/coverage-7.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583049c63106c0555e3ae3931edab5669668bbef84c15861421b94e121878d3f", size = 237177 },
|
|
382
|
+
{ url = "https://files.pythonhosted.org/packages/d3/d4/83dfe1cdabf7f574217ce431c8506c80c6afa5c4056b1159282ba54bb6f2/coverage-7.6.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2341a78ae3a5ed454d524206a3fcb3cec408c2a0c7c2752cd78b606a2ff15af4", size = 239248 },
|
|
383
|
+
{ url = "https://files.pythonhosted.org/packages/05/0a/f53ab3bf80bcf6a17bece8851d4687cf7e424505cf4ce7a3c4cb41b52d96/coverage-7.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4fb91d5f72b7e06a14ff4ae5be625a81cd7e5f869d7a54578fc271d08d58ae3", size = 238737 },
|
|
384
|
+
{ url = "https://files.pythonhosted.org/packages/f7/81/74d797dbedf62b3d7f66af3277b7fc6be6430ed45df701e1ae1197ceb70d/coverage-7.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e279f3db904e3b55f520f11f983cc8dc8a4ce9b65f11692d4718ed021ec58b83", size = 237039 },
|
|
385
|
+
{ url = "https://files.pythonhosted.org/packages/ae/71/1750be153f73eb0e8b9a0f08c8cdb90f6a7c2a25b1795d35e313dd2d78f5/coverage-7.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa23ce39661a3e90eea5f99ec59b763b7d655c2cada10729ed920a38bfc2b167", size = 238703 },
|
|
386
|
+
{ url = "https://files.pythonhosted.org/packages/f6/2e/a60711bb0adcc849c61d9db9574f5d10419cfc73c43cee26a7de6c92f2e4/coverage-7.6.3-cp312-cp312-win32.whl", hash = "sha256:52ac29cc72ee7e25ace7807249638f94c9b6a862c56b1df015d2b2e388e51dbd", size = 209521 },
|
|
387
|
+
{ url = "https://files.pythonhosted.org/packages/d9/26/67a744fcc5de4433a1ebae2b227c66f744fb0d17ad4725b47cf24f7a4c2f/coverage-7.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:40e8b1983080439d4802d80b951f4a93d991ef3261f69e81095a66f86cf3c3c6", size = 210304 },
|
|
388
|
+
{ url = "https://files.pythonhosted.org/packages/8c/8f/265a5f18ab2cb1cae3cf6d64e8fd2708ac66a57b7068963077456ec85294/coverage-7.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9134032f5aa445ae591c2ba6991d10136a1f533b1d2fa8f8c21126468c5025c6", size = 206919 },
|
|
389
|
+
{ url = "https://files.pythonhosted.org/packages/d0/d8/59b41c21237da09c2c09cfd594883f43e3f437d9d602afe09a23d8c3b768/coverage-7.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99670790f21a96665a35849990b1df447993880bb6463a0a1d757897f30da929", size = 207175 },
|
|
390
|
+
{ url = "https://files.pythonhosted.org/packages/19/e8/9a18a78779e5c9d51c8c60de4e9e06c91a03f529fa5b31993b85f364a114/coverage-7.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc7d6b380ca76f5e817ac9eef0c3686e7834c8346bef30b041a4ad286449990", size = 239695 },
|
|
391
|
+
{ url = "https://files.pythonhosted.org/packages/23/d5/6100bb2b104365634068aad82ec332663a32c7572f4bbe24825e79ecb712/coverage-7.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7b26757b22faf88fcf232f5f0e62f6e0fd9e22a8a5d0d5016888cdfe1f6c1c4", size = 236840 },
|
|
392
|
+
{ url = "https://files.pythonhosted.org/packages/d8/11/7e5ac48885f4fed8edb4624425b60405c96c5cf92c2260305eeb6d179897/coverage-7.6.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c59d6a4a4633fad297f943c03d0d2569867bd5372eb5684befdff8df8522e39", size = 238829 },
|
|
393
|
+
{ url = "https://files.pythonhosted.org/packages/31/6c/4943c562bc8f541dbc466181c615743fe7987433b9ac00301b4f2c8bce60/coverage-7.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f263b18692f8ed52c8de7f40a0751e79015983dbd77b16906e5b310a39d3ca21", size = 238495 },
|
|
394
|
+
{ url = "https://files.pythonhosted.org/packages/b2/21/ff415e195eafc86ad6e3e6bdb04e6ebd2caa432d27ec261c0a6d849a171e/coverage-7.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79644f68a6ff23b251cae1c82b01a0b51bc40c8468ca9585c6c4b1aeee570e0b", size = 236578 },
|
|
395
|
+
{ url = "https://files.pythonhosted.org/packages/28/81/08c1c7d2ecc14036b6fb71433ce51fb0942b39ef2bbb654fda82e4e16004/coverage-7.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71967c35828c9ff94e8c7d405469a1fb68257f686bca7c1ed85ed34e7c2529c4", size = 238418 },
|
|
396
|
+
{ url = "https://files.pythonhosted.org/packages/c6/50/912a47a6fa3582c6694e0acdb4c5cf1264950a400a3a9d35a5552e7a91cd/coverage-7.6.3-cp313-cp313-win32.whl", hash = "sha256:e266af4da2c1a4cbc6135a570c64577fd3e6eb204607eaff99d8e9b710003c6f", size = 209584 },
|
|
397
|
+
{ url = "https://files.pythonhosted.org/packages/49/cb/44402ef105d8a77849fa019b975c9e35b184498ec7a6070c30deaad47fab/coverage-7.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:ea52bd218d4ba260399a8ae4bb6b577d82adfc4518b93566ce1fddd4a49d1dce", size = 210316 },
|
|
398
|
+
{ url = "https://files.pythonhosted.org/packages/f6/c4/1e9b42abe9d381585d6f9384bbfbfce464234261e3e331ab98eeef3fc11a/coverage-7.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8d4c6ea0f498c7c79111033a290d060c517853a7bcb2f46516f591dab628ddd3", size = 207664 },
|
|
399
|
+
{ url = "https://files.pythonhosted.org/packages/29/74/62605f094604b85c98962fe29134e8f9df200106b5100a2db236f5c03993/coverage-7.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:331b200ad03dbaa44151d74daeb7da2cf382db424ab923574f6ecca7d3b30de3", size = 207936 },
|
|
400
|
+
{ url = "https://files.pythonhosted.org/packages/ff/8c/bbeadb2f3236fdd62c6c267096c2524af9a59ee0a124b6b237a943d274e8/coverage-7.6.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54356a76b67cf8a3085818026bb556545ebb8353951923b88292556dfa9f812d", size = 250658 },
|
|
401
|
+
{ url = "https://files.pythonhosted.org/packages/3a/a0/46fe77ef9d133867bf639ee68ebbcae86aa340d9e46900fbdf566557c9bf/coverage-7.6.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebec65f5068e7df2d49466aab9128510c4867e532e07cb6960075b27658dca38", size = 246370 },
|
|
402
|
+
{ url = "https://files.pythonhosted.org/packages/9c/ba/b5722bec74017eaa1c5d35377f40a2a434e6c864cf3f1b46cddb62657642/coverage-7.6.3-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33a785ea8354c480515e781554d3be582a86297e41ccbea627a5c632647f2cd", size = 248732 },
|
|
403
|
+
{ url = "https://files.pythonhosted.org/packages/a1/d1/1264cc9ad5079439f438fd49080cbb57a8c6c589919872eaacdbc30d3b1a/coverage-7.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f7ddb920106bbbbcaf2a274d56f46956bf56ecbde210d88061824a95bdd94e92", size = 248221 },
|
|
404
|
+
{ url = "https://files.pythonhosted.org/packages/9d/b5/eacd2cfcb3406725e98bc6b9f1ac6794188b8231148f71281ffdcf5968e0/coverage-7.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:70d24936ca6c15a3bbc91ee9c7fc661132c6f4c9d42a23b31b6686c05073bde5", size = 246109 },
|
|
405
|
+
{ url = "https://files.pythonhosted.org/packages/56/71/0f0a713bf452ae3e6dd126841a25a0bd3a75105f2666c32ad1fb28b791da/coverage-7.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c30e42ea11badb147f0d2e387115b15e2bd8205a5ad70d6ad79cf37f6ac08c91", size = 247391 },
|
|
406
|
+
{ url = "https://files.pythonhosted.org/packages/ad/dd/29fb9c6b94a52da04613e5005d0a8875e57ca76b570d2625964fd92d7fab/coverage-7.6.3-cp313-cp313t-win32.whl", hash = "sha256:365defc257c687ce3e7d275f39738dcd230777424117a6c76043459db131dd43", size = 210230 },
|
|
407
|
+
{ url = "https://files.pythonhosted.org/packages/61/17/efb9ca2a5f9ccf8af267ff2c02ad976a2dc29f9b3c63209e2a89588d7f95/coverage-7.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:23bb63ae3f4c645d2d82fa22697364b0046fbafb6261b258a58587441c5f7bd0", size = 211380 },
|
|
420
408
|
]
|
|
421
409
|
|
|
422
410
|
[[package]]
|
|
@@ -809,6 +797,20 @@ socks = [
|
|
|
809
797
|
{ name = "socksio" },
|
|
810
798
|
]
|
|
811
799
|
|
|
800
|
+
[[package]]
|
|
801
|
+
name = "httpx-socks"
|
|
802
|
+
version = "0.9.1"
|
|
803
|
+
source = { registry = "https://pypi.org/simple" }
|
|
804
|
+
dependencies = [
|
|
805
|
+
{ name = "httpcore" },
|
|
806
|
+
{ name = "httpx" },
|
|
807
|
+
{ name = "python-socks" },
|
|
808
|
+
]
|
|
809
|
+
sdist = { url = "https://files.pythonhosted.org/packages/13/34/23ed81d0511acee5b3ec1ba899162714a17bd3854ad4461528cb45518d35/httpx-socks-0.9.1.tar.gz", hash = "sha256:80ab86bad96fdcbb44b59940f2d3218577a7f09a6d4fdeb2ebaf9ccdff4748a9", size = 12425 }
|
|
810
|
+
wheels = [
|
|
811
|
+
{ url = "https://files.pythonhosted.org/packages/f0/5e/92a52ffdacf1a3a254e062c4c41fb371680cc774e82b33ea853b3c5eb327/httpx_socks-0.9.1-py3-none-any.whl", hash = "sha256:d01dabfdf4da2a8d6c82986ddcfdbb5799a32a21eda0a0639934caf9411bf4a5", size = 12745 },
|
|
812
|
+
]
|
|
813
|
+
|
|
812
814
|
[[package]]
|
|
813
815
|
name = "hyperframe"
|
|
814
816
|
version = "6.0.1"
|
|
@@ -923,7 +925,7 @@ wheels = [
|
|
|
923
925
|
|
|
924
926
|
[[package]]
|
|
925
927
|
name = "mm-balance"
|
|
926
|
-
version = "0.1.
|
|
928
|
+
version = "0.1.10"
|
|
927
929
|
source = { editable = "." }
|
|
928
930
|
dependencies = [
|
|
929
931
|
{ name = "mm-btc" },
|
|
@@ -950,15 +952,15 @@ dev = [
|
|
|
950
952
|
requires-dist = [
|
|
951
953
|
{ name = "mm-btc", specifier = "==0.1.0" },
|
|
952
954
|
{ name = "mm-eth", specifier = "==0.1.3" },
|
|
953
|
-
{ name = "mm-solana", specifier = "==0.1.
|
|
955
|
+
{ name = "mm-solana", specifier = "==0.1.4" },
|
|
954
956
|
{ name = "typer", specifier = ">=0.12.5" },
|
|
955
957
|
]
|
|
956
958
|
|
|
957
959
|
[package.metadata.requires-dev]
|
|
958
960
|
dev = [
|
|
959
961
|
{ name = "bandit", specifier = "~=1.7.10" },
|
|
960
|
-
{ name = "coverage", specifier = "~=7.6.
|
|
961
|
-
{ name = "mypy", specifier = "~=1.
|
|
962
|
+
{ name = "coverage", specifier = "~=7.6.3" },
|
|
963
|
+
{ name = "mypy", specifier = "~=1.12.0" },
|
|
962
964
|
{ name = "pip-audit", specifier = "~=2.7.0" },
|
|
963
965
|
{ name = "pytest", specifier = "~=8.3.3" },
|
|
964
966
|
{ name = "pytest-httpserver", specifier = "~=1.1.0" },
|
|
@@ -1002,20 +1004,20 @@ wheels = [
|
|
|
1002
1004
|
|
|
1003
1005
|
[[package]]
|
|
1004
1006
|
name = "mm-solana"
|
|
1005
|
-
version = "0.1.
|
|
1007
|
+
version = "0.1.4"
|
|
1006
1008
|
source = { registry = "https://pypi.org/simple" }
|
|
1007
1009
|
dependencies = [
|
|
1008
1010
|
{ name = "base58" },
|
|
1009
|
-
{ name = "
|
|
1010
|
-
{ name = "click-aliases" },
|
|
1011
|
+
{ name = "httpx-socks" },
|
|
1011
1012
|
{ name = "jinja2" },
|
|
1012
1013
|
{ name = "mm-std" },
|
|
1013
1014
|
{ name = "pyyaml" },
|
|
1014
1015
|
{ name = "solana" },
|
|
1016
|
+
{ name = "typer" },
|
|
1015
1017
|
]
|
|
1016
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1018
|
+
sdist = { url = "https://files.pythonhosted.org/packages/99/9d/307c404ec6718192faebab8860fb352a667aa9903e0e58489be5f9270af1/mm_solana-0.1.4.tar.gz", hash = "sha256:fd719306c26a4f1347cc63f58dd3561b416da66fe7a615cb2a9b383b3d986268", size = 51285 }
|
|
1017
1019
|
wheels = [
|
|
1018
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1020
|
+
{ url = "https://files.pythonhosted.org/packages/db/ea/901bd6a050fc888c3264d5cf87c37d13b9ffe6373d3319639c9a5c39d5c9/mm_solana-0.1.4-py3-none-any.whl", hash = "sha256:84c966bdbc12e85430ef26237d23ef296f5df9ae915b3153ad952537780f1e83", size = 18913 },
|
|
1019
1021
|
]
|
|
1020
1022
|
|
|
1021
1023
|
[[package]]
|
|
@@ -1116,20 +1118,25 @@ wheels = [
|
|
|
1116
1118
|
|
|
1117
1119
|
[[package]]
|
|
1118
1120
|
name = "mypy"
|
|
1119
|
-
version = "1.
|
|
1121
|
+
version = "1.12.0"
|
|
1120
1122
|
source = { registry = "https://pypi.org/simple" }
|
|
1121
1123
|
dependencies = [
|
|
1122
1124
|
{ name = "mypy-extensions" },
|
|
1123
1125
|
{ name = "typing-extensions" },
|
|
1124
1126
|
]
|
|
1125
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1127
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/70/196a3339459fe22296ac9a883bbd998fcaf0db3e8d9a54cf4f53b722cad4/mypy-1.12.0.tar.gz", hash = "sha256:65a22d87e757ccd95cbbf6f7e181e6caa87128255eb2b6be901bb71b26d8a99d", size = 3149879 }
|
|
1126
1128
|
wheels = [
|
|
1127
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1128
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1129
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1130
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1131
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1132
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1129
|
+
{ url = "https://files.pythonhosted.org/packages/e7/c8/ef6e2a11f0de6cf4359552bf71f07a89f302d27e25bf4c9761649bf1b5a8/mypy-1.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8462655b6694feb1c99e433ea905d46c478041a8b8f0c33f1dab00ae881b2164", size = 11072079 },
|
|
1130
|
+
{ url = "https://files.pythonhosted.org/packages/61/e7/1f9ba3965c3c445d863290d3f8521a7a726b878784f5ad642e82c038261f/mypy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:923ea66d282d8af9e0f9c21ffc6653643abb95b658c3a8a32dca1eff09c06475", size = 10071930 },
|
|
1131
|
+
{ url = "https://files.pythonhosted.org/packages/3a/11/c84fb4c3a42ffd460c2a9b27105fbd538ec501e5aa34671fd3d14a1b94ba/mypy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ebf9e796521f99d61864ed89d1fb2926d9ab6a5fab421e457cd9c7e4dd65aa9", size = 12588227 },
|
|
1132
|
+
{ url = "https://files.pythonhosted.org/packages/f0/ad/b55d070d2001e47c4c6c7d00b13f8dafb16b74db5a99904a183e3c0a3bd6/mypy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e478601cc3e3fa9d6734d255a59c7a2e5c2934da4378f3dd1e3411ea8a248642", size = 13037186 },
|
|
1133
|
+
{ url = "https://files.pythonhosted.org/packages/28/c8/5fc9ef8d3ea89490939ecdfea7a84cede31a69534d468c34807941f5a79f/mypy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:c72861b7139a4f738344faa0e150834467521a3fba42dc98264e5aa9507dd601", size = 9727738 },
|
|
1134
|
+
{ url = "https://files.pythonhosted.org/packages/a6/07/0df1b099a4a725e61782f7d9a34947fc93be688f9dfa011d86e411b2f036/mypy-1.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b9e1492e47e1790360a43755fa04101a7ac72287b1a53ce817f35899ba0521", size = 11071648 },
|
|
1135
|
+
{ url = "https://files.pythonhosted.org/packages/9a/60/2a8bdb4f822bcdb0fa4599b83c1ae9f9ab0e10c1bee262dd9c1ff4607b12/mypy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48d3e37dd7d9403e38fa86c46191de72705166d40b8c9f91a3de77350daa0893", size = 10065760 },
|
|
1136
|
+
{ url = "https://files.pythonhosted.org/packages/cc/d9/065ec6bd21a0ae14b520574d531dc1aa23fdc30fd276dea25f71945172d2/mypy-1.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f106db5ccb60681b622ac768455743ee0e6a857724d648c9629a9bd2ac3f721", size = 12584005 },
|
|
1137
|
+
{ url = "https://files.pythonhosted.org/packages/e6/a8/31449fc5698d1a55062614790a885128e3b2a21de0f82a426942a5ae6a00/mypy-1.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:233e11b3f73ee1f10efada2e6da0f555b2f3a5316e9d8a4a1224acc10e7181d3", size = 13030941 },
|
|
1138
|
+
{ url = "https://files.pythonhosted.org/packages/b5/8e/2347814cffccfb52fc02cbe457ae4a3fb5b660c5b361cdf72374266c231b/mypy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ae8959c21abcf9d73aa6c74a313c45c0b5a188752bf37dace564e29f06e9c1b", size = 9734383 },
|
|
1139
|
+
{ url = "https://files.pythonhosted.org/packages/85/fd/2cc64da1ce9fada64b5d023dfbaf763548429145d08c958c78c02876c7f6/mypy-1.12.0-py3-none-any.whl", hash = "sha256:fd313226af375d52e1e36c383f39bf3836e1f192801116b31b090dfcd3ec5266", size = 2645791 },
|
|
1133
1140
|
]
|
|
1134
1141
|
|
|
1135
1142
|
[[package]]
|
|
@@ -1460,6 +1467,15 @@ wheels = [
|
|
|
1460
1467
|
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
|
|
1461
1468
|
]
|
|
1462
1469
|
|
|
1470
|
+
[[package]]
|
|
1471
|
+
name = "python-socks"
|
|
1472
|
+
version = "2.5.3"
|
|
1473
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1474
|
+
sdist = { url = "https://files.pythonhosted.org/packages/26/a2/dac502b749429cec85086ad8077e32a77eef100730dbc13b37ad658079fc/python_socks-2.5.3.tar.gz", hash = "sha256:4414b2a24777e439758a5f4644f81509ee634e6893115fe7fd1ec482b4b5c979", size = 201519 }
|
|
1475
|
+
wheels = [
|
|
1476
|
+
{ url = "https://files.pythonhosted.org/packages/7e/54/6b907fd2bac4db271902a7a885d4521313b3b754973cfcedb2fb06bf9a00/python_socks-2.5.3-py3-none-any.whl", hash = "sha256:6bc428d0e19f8043e7b8fbc8af33417e690238bd8c9c1e9215871ac18c6136ad", size = 52874 },
|
|
1477
|
+
]
|
|
1478
|
+
|
|
1463
1479
|
[[package]]
|
|
1464
1480
|
name = "pyunormalize"
|
|
1465
1481
|
version = "15.1.0"
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from decimal import Decimal
|
|
4
|
-
|
|
5
|
-
from mm_std import ConcurrentTasks, Result
|
|
6
|
-
from pydantic import BaseModel
|
|
7
|
-
from rich.progress import Progress, TaskID
|
|
8
|
-
|
|
9
|
-
from mm_balance import output
|
|
10
|
-
from mm_balance.config import Config
|
|
11
|
-
from mm_balance.rpc import btc, eth, solana
|
|
12
|
-
from mm_balance.types import Network
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Balances(BaseModel):
|
|
16
|
-
class Balance(BaseModel):
|
|
17
|
-
group_index: int
|
|
18
|
-
address: str
|
|
19
|
-
token_address: str | None
|
|
20
|
-
balance: Result[Decimal] | None = None
|
|
21
|
-
|
|
22
|
-
config: Config
|
|
23
|
-
# separate balance tasks on networks
|
|
24
|
-
btc: list[Balance]
|
|
25
|
-
eth: list[Balance]
|
|
26
|
-
sol: list[Balance]
|
|
27
|
-
|
|
28
|
-
def network_tasks(self, network: Network) -> list[Balance]:
|
|
29
|
-
if network == Network.BTC:
|
|
30
|
-
return self.btc
|
|
31
|
-
elif network == Network.ETH:
|
|
32
|
-
return self.eth
|
|
33
|
-
elif network == Network.SOL:
|
|
34
|
-
return self.sol
|
|
35
|
-
else:
|
|
36
|
-
raise ValueError
|
|
37
|
-
|
|
38
|
-
def get_group_balances(self, group_index: int, network: Network) -> list[Balance]:
|
|
39
|
-
# TODO: can we get network by group_index?
|
|
40
|
-
if network == Network.BTC:
|
|
41
|
-
network_balances = self.btc
|
|
42
|
-
elif network == Network.ETH:
|
|
43
|
-
network_balances = self.eth
|
|
44
|
-
elif network == Network.SOL:
|
|
45
|
-
network_balances = self.sol
|
|
46
|
-
else:
|
|
47
|
-
raise ValueError
|
|
48
|
-
|
|
49
|
-
return [b for b in network_balances if b.group_index == group_index]
|
|
50
|
-
|
|
51
|
-
def process(self) -> None:
|
|
52
|
-
progress = output.create_progress_bar()
|
|
53
|
-
task_btc = output.create_progress_task(progress, "btc", len(self.btc))
|
|
54
|
-
task_eth = output.create_progress_task(progress, "eth", len(self.eth))
|
|
55
|
-
task_sol = output.create_progress_task(progress, "sol", len(self.sol))
|
|
56
|
-
with progress:
|
|
57
|
-
job = ConcurrentTasks()
|
|
58
|
-
job.add_task("btc", self._process_btc, args=(progress, task_btc))
|
|
59
|
-
job.add_task("eth", self._process_eth, args=(progress, task_eth))
|
|
60
|
-
job.add_task("sol", self._process_sol, args=(progress, task_sol))
|
|
61
|
-
job.execute()
|
|
62
|
-
|
|
63
|
-
def _process_btc(self, progress: Progress, task_id: TaskID) -> None:
|
|
64
|
-
job = ConcurrentTasks(max_workers=self.config.workers.btc)
|
|
65
|
-
for idx, task in enumerate(self.btc):
|
|
66
|
-
job.add_task(str(idx), btc.get_balance, args=(task.address, self.config, progress, task_id))
|
|
67
|
-
job.execute()
|
|
68
|
-
for idx, _task in enumerate(self.btc):
|
|
69
|
-
self.btc[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
|
|
70
|
-
|
|
71
|
-
def _process_eth(self, progress: Progress, task_id: TaskID) -> None:
|
|
72
|
-
job = ConcurrentTasks(max_workers=self.config.workers.eth)
|
|
73
|
-
for idx, task in enumerate(self.eth):
|
|
74
|
-
job.add_task(str(idx), eth.get_balance, args=(task.address, task.token_address, self.config, progress, task_id))
|
|
75
|
-
job.execute()
|
|
76
|
-
for idx, _task in enumerate(self.eth):
|
|
77
|
-
self.eth[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
|
|
78
|
-
|
|
79
|
-
def _process_sol(self, progress: Progress, task_id: TaskID) -> None:
|
|
80
|
-
job = ConcurrentTasks(max_workers=self.config.workers.sol)
|
|
81
|
-
for idx, task in enumerate(self.sol):
|
|
82
|
-
job.add_task(str(idx), solana.get_balance, args=(task.address, self.config, progress, task_id))
|
|
83
|
-
job.execute()
|
|
84
|
-
for idx, _task in enumerate(self.sol):
|
|
85
|
-
self.sol[idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
|
|
86
|
-
|
|
87
|
-
@staticmethod
|
|
88
|
-
def from_config(config: Config) -> Balances:
|
|
89
|
-
tasks = Balances(config=config, btc=[], eth=[], sol=[])
|
|
90
|
-
for idx, group in enumerate(config.groups):
|
|
91
|
-
task_list = [Balances.Balance(group_index=idx, address=a, token_address=group.token_address) for a in group.addresses]
|
|
92
|
-
if group.network == Network.BTC:
|
|
93
|
-
tasks.btc.extend(task_list)
|
|
94
|
-
elif group.network == Network.ETH:
|
|
95
|
-
tasks.eth.extend(task_list)
|
|
96
|
-
elif group.network == Network.SOL:
|
|
97
|
-
tasks.sol.extend(task_list)
|
|
98
|
-
return tasks
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
from decimal import Decimal
|
|
2
|
-
|
|
3
|
-
from mm_eth import erc20, rpc
|
|
4
|
-
from mm_std import Ok, Result
|
|
5
|
-
from rich.progress import Progress, TaskID
|
|
6
|
-
|
|
7
|
-
from mm_balance.config import Config
|
|
8
|
-
from mm_balance.types import Network
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def get_balance(
|
|
12
|
-
address: str, token_address: str | None, config: Config, progress: Progress | None = None, task_id: TaskID | None = None
|
|
13
|
-
) -> Result[Decimal]:
|
|
14
|
-
res: Result[Decimal]
|
|
15
|
-
|
|
16
|
-
if token_address is not None:
|
|
17
|
-
res = erc20.get_balance(
|
|
18
|
-
config.nodes[Network.ETH],
|
|
19
|
-
token_address,
|
|
20
|
-
address,
|
|
21
|
-
proxies=config.proxies,
|
|
22
|
-
attempts=5,
|
|
23
|
-
timeout=10,
|
|
24
|
-
).and_then(
|
|
25
|
-
lambda b: Ok(round(Decimal(b / 10 ** config.token_decimals.eth[token_address]), config.round_ndigits)),
|
|
26
|
-
)
|
|
27
|
-
else:
|
|
28
|
-
res = rpc.eth_get_balance(config.nodes[Network.ETH], address, proxies=config.proxies, attempts=5, timeout=10).and_then(
|
|
29
|
-
lambda b: Ok(round(Decimal(b / 10**18), config.round_ndigits)),
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
if task_id is not None and progress is not None:
|
|
33
|
-
progress.update(task_id, advance=1)
|
|
34
|
-
return res
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def get_token_decimals(token_address: str, config: Config) -> Result[int]:
|
|
38
|
-
return erc20.get_decimals(config.nodes[Network.ETH], token_address, timeout=10, proxies=config.proxies, attempts=5)
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from decimal import Decimal
|
|
2
|
-
|
|
3
|
-
from mm_solana.balance import sol_balance
|
|
4
|
-
from mm_std import Ok, Result
|
|
5
|
-
from rich.progress import Progress, TaskID
|
|
6
|
-
|
|
7
|
-
from mm_balance.config import Config
|
|
8
|
-
from mm_balance.types import Network
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def get_balance(address: str, config: Config, progress: Progress | None = None, task_id: TaskID | None = None) -> Result[Decimal]:
|
|
12
|
-
res: Result[Decimal] = sol_balance(
|
|
13
|
-
address=address, nodes=config.nodes[Network.SOL], proxies=config.proxies, attempts=5, timeout=10
|
|
14
|
-
).and_then(
|
|
15
|
-
lambda b: Ok(round(Decimal(b / 1_000_000_000), config.round_ndigits)),
|
|
16
|
-
)
|
|
17
|
-
if task_id is not None and progress is not None:
|
|
18
|
-
progress.update(task_id, advance=1)
|
|
19
|
-
return res
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|