mm-balance 0.3.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mm_balance/{workers.py → balance_fetcher.py} +24 -31
- mm_balance/cli.py +17 -55
- mm_balance/command_runner.py +74 -0
- mm_balance/config.py +13 -6
- mm_balance/constants.py +0 -3
- mm_balance/output/formats/json_format.py +4 -2
- mm_balance/output/formats/table_format.py +4 -4
- mm_balance/price.py +10 -9
- mm_balance/result.py +20 -22
- mm_balance/rpc.py +183 -0
- mm_balance/token_decimals.py +10 -14
- mm_balance-0.4.0.dist-info/METADATA +10 -0
- mm_balance-0.4.0.dist-info/RECORD +22 -0
- mm_balance/rpc/__init__.py +0 -0
- mm_balance/rpc/aptos.py +0 -23
- mm_balance/rpc/btc.py +0 -15
- mm_balance/rpc/evm.py +0 -28
- mm_balance/rpc/solana.py +0 -27
- mm_balance-0.3.1.dist-info/METADATA +0 -10
- mm_balance-0.3.1.dist-info/RECORD +0 -25
- {mm_balance-0.3.1.dist-info → mm_balance-0.4.0.dist-info}/WHEEL +0 -0
- {mm_balance-0.3.1.dist-info → mm_balance-0.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from decimal import Decimal
|
|
3
3
|
|
|
4
|
-
from mm_std import
|
|
4
|
+
from mm_std import AsyncTaskRunner, PrintFormat, Result
|
|
5
5
|
from rich.progress import TaskID
|
|
6
6
|
|
|
7
|
+
from mm_balance import rpc
|
|
7
8
|
from mm_balance.config import Config
|
|
8
|
-
from mm_balance.constants import
|
|
9
|
+
from mm_balance.constants import Network
|
|
9
10
|
from mm_balance.output import utils
|
|
10
|
-
from mm_balance.rpc import aptos, btc, evm, solana
|
|
11
11
|
from mm_balance.token_decimals import TokenDecimals
|
|
12
12
|
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@ class Task:
|
|
|
19
19
|
balance: Result[Decimal] | None = None
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
class
|
|
22
|
+
class BalanceFetcher:
|
|
23
23
|
def __init__(self, config: Config, token_decimals: TokenDecimals) -> None:
|
|
24
24
|
self.config = config
|
|
25
25
|
self.token_decimals = token_decimals
|
|
@@ -35,15 +35,14 @@ class Workers:
|
|
|
35
35
|
if self.tasks[network]:
|
|
36
36
|
self.progress_bar_task[network] = utils.create_progress_task(self.progress_bar, network, len(self.tasks[network]))
|
|
37
37
|
|
|
38
|
-
def process(self) -> None:
|
|
38
|
+
async def process(self) -> None:
|
|
39
39
|
with self.progress_bar:
|
|
40
|
-
|
|
40
|
+
runner = AsyncTaskRunner(max_concurrent_tasks=10)
|
|
41
41
|
for network in self.config.networks():
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
runner.add_task(f"process_{network}", self._process_network(network))
|
|
43
|
+
await runner.run()
|
|
44
44
|
|
|
45
45
|
def get_group_tasks(self, group_index: int, network: Network) -> list[Task]:
|
|
46
|
-
# TODO: can we get network by group_index?
|
|
47
46
|
return [b for b in self.tasks[network] if b.group_index == group_index]
|
|
48
47
|
|
|
49
48
|
def get_errors(self) -> list[Task]:
|
|
@@ -52,31 +51,25 @@ class Workers:
|
|
|
52
51
|
result.extend([task for task in self.tasks[network] if task.balance is not None and task.balance.is_err()])
|
|
53
52
|
return result
|
|
54
53
|
|
|
55
|
-
def _process_network(self, network: Network) -> None:
|
|
56
|
-
|
|
54
|
+
async def _process_network(self, network: Network) -> None:
|
|
55
|
+
runner = AsyncTaskRunner(max_concurrent_tasks=self.config.workers[network])
|
|
57
56
|
for idx, task in enumerate(self.tasks[network]):
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
runner.add_task(str(idx), self._get_balance(network, task.wallet_address, task.token_address))
|
|
58
|
+
res = await runner.run()
|
|
59
|
+
|
|
60
60
|
# TODO: print job.exceptions if present
|
|
61
61
|
for idx, _task in enumerate(self.tasks[network]):
|
|
62
|
-
self.tasks[network][idx].balance =
|
|
63
|
-
|
|
64
|
-
def _get_balance(self, network: Network, wallet_address: str, token_address: str | None) -> Result[Decimal]:
|
|
65
|
-
nodes = self.config.nodes[network]
|
|
66
|
-
round_ndigits = self.config.settings.round_ndigits
|
|
67
|
-
proxies = self.config.settings.proxies
|
|
68
|
-
token_decimals = self.token_decimals[network][token_address]
|
|
69
|
-
|
|
70
|
-
if network.is_evm_network():
|
|
71
|
-
res = evm.get_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
|
|
72
|
-
elif network == NETWORK_BITCOIN:
|
|
73
|
-
res = btc.get_balance(wallet_address, proxies, round_ndigits)
|
|
74
|
-
elif network == NETWORK_APTOS:
|
|
75
|
-
res = aptos.get_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
|
|
76
|
-
elif network == NETWORK_SOLANA:
|
|
77
|
-
res = solana.get_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
|
|
78
|
-
else:
|
|
79
|
-
raise ValueError(f"Unsupported network: {network}")
|
|
62
|
+
self.tasks[network][idx].balance = res.results.get(str(idx))
|
|
80
63
|
|
|
64
|
+
async def _get_balance(self, network: Network, wallet_address: str, token_address: str | None) -> Result[Decimal]:
|
|
65
|
+
res = await rpc.get_balance(
|
|
66
|
+
network=network,
|
|
67
|
+
nodes=self.config.nodes[network],
|
|
68
|
+
proxies=self.config.settings.proxies,
|
|
69
|
+
wallet_address=wallet_address,
|
|
70
|
+
token_address=token_address,
|
|
71
|
+
token_decimals=self.token_decimals[network][token_address],
|
|
72
|
+
ndigits=self.config.settings.round_ndigits,
|
|
73
|
+
)
|
|
81
74
|
self.progress_bar.update(self.progress_bar_task[network], advance=1)
|
|
82
75
|
return res
|
mm_balance/cli.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import asyncio
|
|
2
2
|
import importlib.metadata
|
|
3
3
|
import pkgutil
|
|
4
4
|
from pathlib import Path
|
|
@@ -7,14 +7,9 @@ from typing import Annotated
|
|
|
7
7
|
import typer
|
|
8
8
|
from mm_std import PrintFormat, fatal, pretty_print_toml
|
|
9
9
|
|
|
10
|
-
from mm_balance
|
|
10
|
+
from mm_balance import command_runner
|
|
11
|
+
from mm_balance.command_runner import CommandParameters
|
|
11
12
|
from mm_balance.constants import NETWORKS
|
|
12
|
-
from mm_balance.diff import BalancesDict, Diff
|
|
13
|
-
from mm_balance.output.formats import json_format, table_format
|
|
14
|
-
from mm_balance.price import Prices, get_prices
|
|
15
|
-
from mm_balance.result import create_balances_result
|
|
16
|
-
from mm_balance.token_decimals import get_token_decimals
|
|
17
|
-
from mm_balance.workers import Workers
|
|
18
13
|
|
|
19
14
|
app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
|
|
20
15
|
|
|
@@ -57,53 +52,20 @@ def cli(
|
|
|
57
52
|
] = None,
|
|
58
53
|
_version: bool = typer.Option(None, "--version", callback=version_callback, is_eager=True),
|
|
59
54
|
) -> None:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
config.settings.price = price
|
|
75
|
-
|
|
76
|
-
if config.settings.print_debug and config.settings.print_format is PrintFormat.TABLE:
|
|
77
|
-
table_format.print_nodes(config)
|
|
78
|
-
table_format.print_proxy_count(config)
|
|
79
|
-
|
|
80
|
-
token_decimals = get_token_decimals(config)
|
|
81
|
-
if config.settings.print_debug and config.settings.print_format is PrintFormat.TABLE:
|
|
82
|
-
table_format.print_token_decimals(token_decimals)
|
|
83
|
-
|
|
84
|
-
prices = get_prices(config) if config.settings.price else Prices()
|
|
85
|
-
if config.settings.print_format is PrintFormat.TABLE:
|
|
86
|
-
table_format.print_prices(config, prices)
|
|
87
|
-
|
|
88
|
-
workers = Workers(config, token_decimals)
|
|
89
|
-
workers.process()
|
|
90
|
-
|
|
91
|
-
result = create_balances_result(config, prices, workers)
|
|
92
|
-
if config.settings.print_format is PrintFormat.TABLE:
|
|
93
|
-
table_format.print_result(config, result, workers)
|
|
94
|
-
elif config.settings.print_format is PrintFormat.JSON:
|
|
95
|
-
json_format.print_result(config, token_decimals, prices, workers, result)
|
|
96
|
-
else:
|
|
97
|
-
fatal("Unsupported print format")
|
|
98
|
-
|
|
99
|
-
if save_balances:
|
|
100
|
-
BalancesDict.from_balances_result(result).save_to_path(save_balances)
|
|
101
|
-
|
|
102
|
-
if diff_from_balances:
|
|
103
|
-
old_balances = BalancesDict.from_file(diff_from_balances)
|
|
104
|
-
new_balances = BalancesDict.from_balances_result(result)
|
|
105
|
-
diff = Diff.calc(old_balances, new_balances)
|
|
106
|
-
diff.print(config.settings.print_format)
|
|
55
|
+
asyncio.run(
|
|
56
|
+
command_runner.run(
|
|
57
|
+
CommandParameters(
|
|
58
|
+
config_path=config_path,
|
|
59
|
+
print_format=print_format,
|
|
60
|
+
skip_empty=skip_empty,
|
|
61
|
+
debug=debug,
|
|
62
|
+
print_config=print_config,
|
|
63
|
+
price=price,
|
|
64
|
+
save_balances=save_balances,
|
|
65
|
+
diff_from_balances=diff_from_balances,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
)
|
|
107
69
|
|
|
108
70
|
|
|
109
71
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from mm_std import PrintFormat, fatal
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from mm_balance.balance_fetcher import BalanceFetcher
|
|
8
|
+
from mm_balance.config import Config
|
|
9
|
+
from mm_balance.diff import BalancesDict, Diff
|
|
10
|
+
from mm_balance.output.formats import json_format, table_format
|
|
11
|
+
from mm_balance.price import Prices, get_prices
|
|
12
|
+
from mm_balance.result import create_balances_result
|
|
13
|
+
from mm_balance.token_decimals import get_token_decimals
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CommandParameters(BaseModel):
|
|
17
|
+
config_path: Path
|
|
18
|
+
print_format: PrintFormat | None
|
|
19
|
+
skip_empty: bool | None
|
|
20
|
+
debug: bool | None
|
|
21
|
+
print_config: bool | None
|
|
22
|
+
price: bool | None
|
|
23
|
+
save_balances: Path | None
|
|
24
|
+
diff_from_balances: Path | None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def run(params: CommandParameters) -> None:
|
|
28
|
+
zip_password = "" # nosec
|
|
29
|
+
if params.config_path.name.endswith(".zip"):
|
|
30
|
+
zip_password = getpass.getpass("zip password")
|
|
31
|
+
config = Config.read_toml_config_or_exit(params.config_path, zip_password=zip_password)
|
|
32
|
+
if params.print_config:
|
|
33
|
+
config.print_and_exit()
|
|
34
|
+
|
|
35
|
+
if params.print_format is not None:
|
|
36
|
+
config.settings.print_format = params.print_format
|
|
37
|
+
if params.debug is not None:
|
|
38
|
+
config.settings.print_debug = params.debug
|
|
39
|
+
if params.skip_empty is not None:
|
|
40
|
+
config.settings.skip_empty = params.skip_empty
|
|
41
|
+
if params.price is not None:
|
|
42
|
+
config.settings.price = params.price
|
|
43
|
+
|
|
44
|
+
if config.settings.print_debug and config.settings.print_format is PrintFormat.TABLE:
|
|
45
|
+
table_format.print_nodes(config)
|
|
46
|
+
table_format.print_proxy_count(config)
|
|
47
|
+
|
|
48
|
+
token_decimals = await get_token_decimals(config)
|
|
49
|
+
if config.settings.print_debug and config.settings.print_format is PrintFormat.TABLE:
|
|
50
|
+
table_format.print_token_decimals(token_decimals)
|
|
51
|
+
|
|
52
|
+
prices = await get_prices(config) if config.settings.price else Prices()
|
|
53
|
+
if config.settings.print_format is PrintFormat.TABLE:
|
|
54
|
+
table_format.print_prices(config, prices)
|
|
55
|
+
|
|
56
|
+
workers = BalanceFetcher(config, token_decimals)
|
|
57
|
+
await workers.process()
|
|
58
|
+
|
|
59
|
+
result = create_balances_result(config, prices, workers)
|
|
60
|
+
if config.settings.print_format is PrintFormat.TABLE:
|
|
61
|
+
table_format.print_result(config, result, workers)
|
|
62
|
+
elif config.settings.print_format is PrintFormat.JSON:
|
|
63
|
+
json_format.print_result(config, token_decimals, prices, workers, result)
|
|
64
|
+
else:
|
|
65
|
+
fatal("Unsupported print format")
|
|
66
|
+
|
|
67
|
+
if params.save_balances:
|
|
68
|
+
BalancesDict.from_balances_result(result).save_to_path(params.save_balances)
|
|
69
|
+
|
|
70
|
+
if params.diff_from_balances:
|
|
71
|
+
old_balances = BalancesDict.from_file(params.diff_from_balances)
|
|
72
|
+
new_balances = BalancesDict.from_balances_result(result)
|
|
73
|
+
diff = Diff.calc(old_balances, new_balances)
|
|
74
|
+
diff.print(config.settings.print_format)
|
mm_balance/config.py
CHANGED
|
@@ -16,11 +16,18 @@ class Validators(ConfigValidators):
|
|
|
16
16
|
pass
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
class
|
|
19
|
+
class AssetGroup(BaseConfig):
|
|
20
|
+
"""
|
|
21
|
+
Represents a group of cryptocurrency assets of the same type.
|
|
22
|
+
|
|
23
|
+
An asset group contains information about a specific cryptocurrency (token)
|
|
24
|
+
across multiple addresses/wallets.
|
|
25
|
+
"""
|
|
26
|
+
|
|
20
27
|
comment: str = ""
|
|
21
28
|
ticker: Annotated[str, StringConstraints(to_upper=True)]
|
|
22
29
|
network: Network
|
|
23
|
-
token: str | None = None # Token address. If None, it's a native token
|
|
30
|
+
token: str | None = None # Token address. If None, it's a native token
|
|
24
31
|
decimals: int | None = None
|
|
25
32
|
coingecko_id: str | None = None
|
|
26
33
|
addresses: Annotated[list[str], BeforeValidator(Validators.addresses(unique=True))]
|
|
@@ -42,7 +49,7 @@ class Group(BaseConfig):
|
|
|
42
49
|
self.token = self.token.lower()
|
|
43
50
|
return self
|
|
44
51
|
|
|
45
|
-
def process_addresses(self, address_groups: list[
|
|
52
|
+
def process_addresses(self, address_groups: list[AddressCollection]) -> None:
|
|
46
53
|
result = []
|
|
47
54
|
for line in self.addresses:
|
|
48
55
|
if line.startswith("file:"):
|
|
@@ -65,7 +72,7 @@ class Group(BaseConfig):
|
|
|
65
72
|
self.addresses = pydash.uniq(result)
|
|
66
73
|
|
|
67
74
|
|
|
68
|
-
class
|
|
75
|
+
class AddressCollection(BaseConfig):
|
|
69
76
|
name: str
|
|
70
77
|
addresses: Annotated[list[str], BeforeValidator(ConfigValidators.addresses(unique=True))]
|
|
71
78
|
|
|
@@ -81,8 +88,8 @@ class Settings(BaseConfig):
|
|
|
81
88
|
|
|
82
89
|
|
|
83
90
|
class Config(BaseConfig):
|
|
84
|
-
groups: list[
|
|
85
|
-
addresses: list[
|
|
91
|
+
groups: list[AssetGroup] = Field(alias="coins")
|
|
92
|
+
addresses: list[AddressCollection] = Field(default_factory=list)
|
|
86
93
|
nodes: dict[Network, list[str]] = Field(default_factory=dict)
|
|
87
94
|
workers: dict[Network, int] = Field(default_factory=dict)
|
|
88
95
|
settings: Settings = Field(default_factory=Settings) # type: ignore[arg-type]
|
mm_balance/constants.py
CHANGED
|
@@ -22,9 +22,6 @@ class Network(str):
|
|
|
22
22
|
return core_schema.no_info_after_validator_function(cls, handler(str))
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
# evm networks
|
|
26
|
-
|
|
27
|
-
# other networks
|
|
28
25
|
NETWORK_APTOS = Network("aptos")
|
|
29
26
|
NETWORK_ARBITRUM_ONE = Network("arbitrum-one")
|
|
30
27
|
NETWORK_BITCOIN = Network("bitcoin")
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
from mm_std import print_json
|
|
2
2
|
|
|
3
|
+
from mm_balance.balance_fetcher import BalanceFetcher
|
|
3
4
|
from mm_balance.config import Config
|
|
4
5
|
from mm_balance.price import Prices
|
|
5
6
|
from mm_balance.result import BalancesResult
|
|
6
7
|
from mm_balance.token_decimals import TokenDecimals
|
|
7
|
-
from mm_balance.workers import Workers
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def print_result(
|
|
10
|
+
def print_result(
|
|
11
|
+
config: Config, token_decimals: TokenDecimals, prices: Prices, workers: BalanceFetcher, result: BalancesResult
|
|
12
|
+
) -> None:
|
|
11
13
|
data: dict[str, object] = {}
|
|
12
14
|
if config.settings.print_debug:
|
|
13
15
|
data["nodes"] = config.nodes
|
|
@@ -2,12 +2,12 @@ from decimal import Decimal
|
|
|
2
2
|
|
|
3
3
|
from mm_std import print_table
|
|
4
4
|
|
|
5
|
+
from mm_balance.balance_fetcher import BalanceFetcher
|
|
5
6
|
from mm_balance.config import Config
|
|
6
7
|
from mm_balance.output.utils import format_number
|
|
7
8
|
from mm_balance.price import Prices
|
|
8
9
|
from mm_balance.result import BalancesResult, GroupResult, Total
|
|
9
10
|
from mm_balance.token_decimals import TokenDecimals
|
|
10
|
-
from mm_balance.workers import Workers
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def print_nodes(config: Config) -> None:
|
|
@@ -38,7 +38,7 @@ def print_prices(config: Config, prices: Prices) -> None:
|
|
|
38
38
|
print_table("Prices", ["coin", "usd"], rows)
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def print_result(config: Config, result: BalancesResult, workers:
|
|
41
|
+
def print_result(config: Config, result: BalancesResult, workers: BalanceFetcher) -> None:
|
|
42
42
|
for group in result.groups:
|
|
43
43
|
_print_group(config, group)
|
|
44
44
|
|
|
@@ -49,14 +49,14 @@ def print_result(config: Config, result: BalancesResult, workers: Workers) -> No
|
|
|
49
49
|
_print_errors(config, workers)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
def _print_errors(config: Config, workers:
|
|
52
|
+
def _print_errors(config: Config, workers: BalanceFetcher) -> None:
|
|
53
53
|
error_tasks = workers.get_errors()
|
|
54
54
|
if not error_tasks:
|
|
55
55
|
return
|
|
56
56
|
rows = []
|
|
57
57
|
for task in error_tasks:
|
|
58
58
|
group = config.groups[task.group_index]
|
|
59
|
-
rows.append([group.ticker + " / " + group.network, task.wallet_address, task.balance.
|
|
59
|
+
rows.append([group.ticker + " / " + group.network, task.wallet_address, task.balance.error]) # type: ignore[union-attr]
|
|
60
60
|
print_table("Errors", ["coin", "address", "error"], rows)
|
|
61
61
|
|
|
62
62
|
|
mm_balance/price.py
CHANGED
|
@@ -2,10 +2,9 @@ from collections import defaultdict
|
|
|
2
2
|
from decimal import Decimal
|
|
3
3
|
|
|
4
4
|
import pydash
|
|
5
|
-
from mm_std import
|
|
6
|
-
from mm_std.random_ import random_str_choice
|
|
5
|
+
from mm_std import http_request, random_str_choice
|
|
7
6
|
|
|
8
|
-
from mm_balance.config import
|
|
7
|
+
from mm_balance.config import AssetGroup, Config
|
|
9
8
|
from mm_balance.constants import RETRIES_COINGECKO_PRICES, TICKER_TO_COINGECKO_ID
|
|
10
9
|
|
|
11
10
|
|
|
@@ -18,7 +17,7 @@ class Prices(defaultdict[str, Decimal]):
|
|
|
18
17
|
"""
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
def get_prices(config: Config) -> Prices:
|
|
20
|
+
async def get_prices(config: Config) -> Prices:
|
|
22
21
|
result = Prices()
|
|
23
22
|
|
|
24
23
|
coingecko_map: dict[str, str] = {} # ticker -> coingecko_id
|
|
@@ -30,19 +29,21 @@ def get_prices(config: Config) -> Prices:
|
|
|
30
29
|
|
|
31
30
|
url = f"https://api.coingecko.com/api/v3/simple/price?ids={','.join(coingecko_map.values())}&vs_currencies=usd"
|
|
32
31
|
for _ in range(RETRIES_COINGECKO_PRICES):
|
|
33
|
-
res =
|
|
34
|
-
if res.
|
|
32
|
+
res = await http_request(url, proxy=random_str_choice(config.settings.proxies))
|
|
33
|
+
if res.status_code != 200:
|
|
35
34
|
continue
|
|
36
35
|
|
|
36
|
+
json_body = res.parse_json_body() or {}
|
|
37
|
+
|
|
37
38
|
for ticker, coingecko_id in coingecko_map.items():
|
|
38
|
-
if coingecko_id in
|
|
39
|
-
result[ticker] = Decimal(str(pydash.get(
|
|
39
|
+
if coingecko_id in json_body:
|
|
40
|
+
result[ticker] = Decimal(str(pydash.get(json_body, f"{coingecko_id}.usd")))
|
|
40
41
|
break
|
|
41
42
|
|
|
42
43
|
return result
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
def get_coingecko_id(group:
|
|
46
|
+
def get_coingecko_id(group: AssetGroup) -> str | None:
|
|
46
47
|
if group.coingecko_id:
|
|
47
48
|
return group.coingecko_id
|
|
48
49
|
return TICKER_TO_COINGECKO_ID.get(group.ticker)
|
mm_balance/result.py
CHANGED
|
@@ -2,13 +2,11 @@ from collections import defaultdict
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from decimal import Decimal
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
from mm_balance.config import Config, Group
|
|
5
|
+
from mm_balance.balance_fetcher import BalanceFetcher, Task
|
|
6
|
+
from mm_balance.config import AssetGroup, Config
|
|
8
7
|
from mm_balance.constants import USD_STABLECOINS, Network
|
|
9
8
|
from mm_balance.price import Prices
|
|
10
9
|
from mm_balance.utils import round_decimal
|
|
11
|
-
from mm_balance.workers import Task, Workers
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
@dataclass
|
|
@@ -52,7 +50,7 @@ class BalancesResult:
|
|
|
52
50
|
total_share: Total
|
|
53
51
|
|
|
54
52
|
|
|
55
|
-
def create_balances_result(config: Config, prices: Prices, workers:
|
|
53
|
+
def create_balances_result(config: Config, prices: Prices, workers: BalanceFetcher) -> BalancesResult:
|
|
56
54
|
groups = []
|
|
57
55
|
for group_index, group in enumerate(config.groups):
|
|
58
56
|
tasks = workers.get_group_tasks(group_index, group.network)
|
|
@@ -63,19 +61,6 @@ def create_balances_result(config: Config, prices: Prices, workers: Workers) ->
|
|
|
63
61
|
return BalancesResult(groups=groups, total=total, total_share=total_share)
|
|
64
62
|
|
|
65
63
|
|
|
66
|
-
# def save_balances_file(result: BalancesResult, balances_file: Path) -> None:
|
|
67
|
-
# data = {}
|
|
68
|
-
# for group in result.groups:
|
|
69
|
-
# if group.network not in data:
|
|
70
|
-
# data[group.network] = {}
|
|
71
|
-
# if group.ticker not in data[group.network]:
|
|
72
|
-
# data[group.network][group.ticker] = {}
|
|
73
|
-
# for address in group.addresses:
|
|
74
|
-
# if isinstance(address.balance, Balance):
|
|
75
|
-
# data[group.network][group.ticker][address.address] = float(address.balance.balance)
|
|
76
|
-
# json.dump(data, balances_file.open("w"), indent=2)
|
|
77
|
-
|
|
78
|
-
|
|
79
64
|
def _create_total(use_share: bool, groups: list[GroupResult]) -> Total:
|
|
80
65
|
coin_balances: dict[str, Decimal] = defaultdict(Decimal) # ticker -> balance
|
|
81
66
|
coin_usd_values: dict[str, Decimal] = defaultdict(Decimal) # ticker -> usd value
|
|
@@ -108,7 +93,7 @@ def _create_total(use_share: bool, groups: list[GroupResult]) -> Total:
|
|
|
108
93
|
)
|
|
109
94
|
|
|
110
95
|
|
|
111
|
-
def _create_group_result(config: Config, group:
|
|
96
|
+
def _create_group_result(config: Config, group: AssetGroup, tasks: list[Task], prices: Prices) -> GroupResult:
|
|
112
97
|
addresses = []
|
|
113
98
|
balance_sum = Decimal(0)
|
|
114
99
|
usd_sum = Decimal(0)
|
|
@@ -116,8 +101,8 @@ def _create_group_result(config: Config, group: Group, tasks: list[Task], prices
|
|
|
116
101
|
balance: Balance | str
|
|
117
102
|
if task.balance is None:
|
|
118
103
|
balance = "balance is None! Something went wrong."
|
|
119
|
-
elif
|
|
120
|
-
coin_value = task.balance.
|
|
104
|
+
elif task.balance.is_ok():
|
|
105
|
+
coin_value = task.balance.unwrap()
|
|
121
106
|
usd_value = Decimal(0)
|
|
122
107
|
if group.ticker in prices:
|
|
123
108
|
usd_value = round_decimal(coin_value * prices[group.ticker], config.settings.round_ndigits)
|
|
@@ -125,7 +110,7 @@ def _create_group_result(config: Config, group: Group, tasks: list[Task], prices
|
|
|
125
110
|
balance_sum += balance.balance
|
|
126
111
|
usd_sum += balance.usd_value
|
|
127
112
|
else:
|
|
128
|
-
balance = task.balance.
|
|
113
|
+
balance = task.balance.unwrap_error()
|
|
129
114
|
addresses.append(AddressBalance(address=task.wallet_address, balance=balance))
|
|
130
115
|
|
|
131
116
|
balance_sum_share = balance_sum * group.share
|
|
@@ -142,3 +127,16 @@ def _create_group_result(config: Config, group: Group, tasks: list[Task], prices
|
|
|
142
127
|
balance_sum_share=balance_sum_share,
|
|
143
128
|
usd_sum_share=usd_sum_share,
|
|
144
129
|
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# def save_balances_file(result: BalancesResult, balances_file: Path) -> None:
|
|
133
|
+
# data = {}
|
|
134
|
+
# for group in result.groups:
|
|
135
|
+
# if group.network not in data:
|
|
136
|
+
# data[group.network] = {}
|
|
137
|
+
# if group.ticker not in data[group.network]:
|
|
138
|
+
# data[group.network][group.ticker] = {}
|
|
139
|
+
# for address in group.addresses:
|
|
140
|
+
# if isinstance(address.balance, Balance):
|
|
141
|
+
# data[group.network][group.ticker][address.address] = float(address.balance.balance)
|
|
142
|
+
# json.dump(data, balances_file.open("w"), indent=2)
|
mm_balance/rpc.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
from mm_apt import retry as apt_retry
|
|
4
|
+
from mm_btc.blockstream import BlockstreamClient
|
|
5
|
+
from mm_crypto_utils import Nodes, Proxies
|
|
6
|
+
from mm_eth import retry as eth_retry
|
|
7
|
+
from mm_sol import retry as sol_retry
|
|
8
|
+
from mm_std import Result
|
|
9
|
+
|
|
10
|
+
from mm_balance.constants import (
|
|
11
|
+
NETWORK_APTOS,
|
|
12
|
+
NETWORK_BITCOIN,
|
|
13
|
+
NETWORK_SOLANA,
|
|
14
|
+
RETRIES_BALANCE,
|
|
15
|
+
RETRIES_DECIMALS,
|
|
16
|
+
TIMEOUT_BALANCE,
|
|
17
|
+
TIMEOUT_DECIMALS,
|
|
18
|
+
Network,
|
|
19
|
+
)
|
|
20
|
+
from mm_balance.utils import scale_and_round
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def get_balance(
|
|
24
|
+
*,
|
|
25
|
+
network: Network,
|
|
26
|
+
nodes: Nodes,
|
|
27
|
+
proxies: Proxies,
|
|
28
|
+
wallet_address: str,
|
|
29
|
+
token_address: str | None,
|
|
30
|
+
token_decimals: int,
|
|
31
|
+
ndigits: int,
|
|
32
|
+
) -> Result[Decimal]:
|
|
33
|
+
"""
|
|
34
|
+
Fetch balance for a wallet on specified network.
|
|
35
|
+
|
|
36
|
+
This function retrieves the balance of a wallet address on a given network.
|
|
37
|
+
It supports multiple networks including EVM-compatible chains (Ethereum,
|
|
38
|
+
Arbitrum, etc.), Bitcoin, Aptos, and Solana. For EVM networks and Solana,
|
|
39
|
+
it can fetch both native coin and token balances.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
network: The blockchain network to query
|
|
43
|
+
nodes: RPC nodes to use for the request
|
|
44
|
+
proxies: Proxy configuration for the request
|
|
45
|
+
wallet_address: The address of the wallet to check
|
|
46
|
+
token_address: The address of the token (None for native coin)
|
|
47
|
+
token_decimals: Number of decimal places for the token
|
|
48
|
+
ndigits: Number of digits to round the result to
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Result containing the balance as a Decimal on success, or an error message
|
|
52
|
+
"""
|
|
53
|
+
if network.is_evm_network():
|
|
54
|
+
return await _get_evm_balance(
|
|
55
|
+
nodes=nodes,
|
|
56
|
+
proxies=proxies,
|
|
57
|
+
wallet_address=wallet_address,
|
|
58
|
+
token_address=token_address,
|
|
59
|
+
token_decimals=token_decimals,
|
|
60
|
+
ndigits=ndigits,
|
|
61
|
+
)
|
|
62
|
+
if network == NETWORK_BITCOIN:
|
|
63
|
+
return await _get_bitcoin_balance(
|
|
64
|
+
proxies=proxies,
|
|
65
|
+
wallet_address=wallet_address,
|
|
66
|
+
token_decimals=token_decimals,
|
|
67
|
+
ndigits=ndigits,
|
|
68
|
+
)
|
|
69
|
+
if network == NETWORK_APTOS:
|
|
70
|
+
return await _get_aptos_balance(
|
|
71
|
+
nodes=nodes,
|
|
72
|
+
proxies=proxies,
|
|
73
|
+
wallet_address=wallet_address,
|
|
74
|
+
token_address=token_address,
|
|
75
|
+
token_decimals=token_decimals,
|
|
76
|
+
ndigits=ndigits,
|
|
77
|
+
)
|
|
78
|
+
if network == NETWORK_SOLANA:
|
|
79
|
+
return await _get_solana_balance(
|
|
80
|
+
nodes=nodes,
|
|
81
|
+
proxies=proxies,
|
|
82
|
+
wallet_address=wallet_address,
|
|
83
|
+
token_address=token_address,
|
|
84
|
+
token_decimals=token_decimals,
|
|
85
|
+
ndigits=ndigits,
|
|
86
|
+
)
|
|
87
|
+
return Result.err("Unsupported network")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def _get_evm_balance(
|
|
91
|
+
*,
|
|
92
|
+
nodes: Nodes,
|
|
93
|
+
proxies: Proxies,
|
|
94
|
+
wallet_address: str,
|
|
95
|
+
token_address: str | None,
|
|
96
|
+
token_decimals: int,
|
|
97
|
+
ndigits: int,
|
|
98
|
+
) -> Result[Decimal]:
|
|
99
|
+
"""Fetch balance for EVM-compatible networks."""
|
|
100
|
+
if token_address is None:
|
|
101
|
+
res = await eth_retry.eth_get_balance(RETRIES_BALANCE, nodes, proxies, address=wallet_address, timeout=TIMEOUT_BALANCE)
|
|
102
|
+
else:
|
|
103
|
+
res = await eth_retry.erc20_balance(
|
|
104
|
+
RETRIES_BALANCE, nodes, proxies, token=token_address, wallet=wallet_address, timeout=TIMEOUT_BALANCE
|
|
105
|
+
)
|
|
106
|
+
return res.map(lambda value: scale_and_round(value, token_decimals, ndigits))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def _get_bitcoin_balance(
|
|
110
|
+
*,
|
|
111
|
+
proxies: Proxies,
|
|
112
|
+
wallet_address: str,
|
|
113
|
+
token_decimals: int,
|
|
114
|
+
ndigits: int,
|
|
115
|
+
) -> Result[Decimal]:
|
|
116
|
+
"""Fetch balance for Bitcoin network."""
|
|
117
|
+
res = await BlockstreamClient(proxies=proxies, attempts=RETRIES_BALANCE).get_confirmed_balance(wallet_address)
|
|
118
|
+
return res.map(lambda value: scale_and_round(value, token_decimals, ndigits))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def _get_aptos_balance(
|
|
122
|
+
*,
|
|
123
|
+
nodes: Nodes,
|
|
124
|
+
proxies: Proxies,
|
|
125
|
+
wallet_address: str,
|
|
126
|
+
token_address: str | None,
|
|
127
|
+
token_decimals: int,
|
|
128
|
+
ndigits: int,
|
|
129
|
+
) -> Result[Decimal]:
|
|
130
|
+
"""Fetch balance for Aptos network."""
|
|
131
|
+
actual_token_address = token_address if token_address is not None else "0x1::aptos_coin::AptosCoin"
|
|
132
|
+
res = await apt_retry.get_balance(
|
|
133
|
+
RETRIES_BALANCE, nodes, proxies, account=wallet_address, coin_type=actual_token_address, timeout=TIMEOUT_BALANCE
|
|
134
|
+
)
|
|
135
|
+
return res.map(lambda value: scale_and_round(value, token_decimals, ndigits))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
async def _get_solana_balance(
|
|
139
|
+
*,
|
|
140
|
+
nodes: Nodes,
|
|
141
|
+
proxies: Proxies,
|
|
142
|
+
wallet_address: str,
|
|
143
|
+
token_address: str | None,
|
|
144
|
+
token_decimals: int,
|
|
145
|
+
ndigits: int,
|
|
146
|
+
) -> Result[Decimal]:
|
|
147
|
+
"""Fetch balance for Solana network."""
|
|
148
|
+
if token_address is None:
|
|
149
|
+
res = await sol_retry.get_sol_balance(RETRIES_BALANCE, nodes, proxies, address=wallet_address, timeout=TIMEOUT_BALANCE)
|
|
150
|
+
else:
|
|
151
|
+
res = await sol_retry.get_token_balance(
|
|
152
|
+
RETRIES_BALANCE, nodes, proxies, owner=wallet_address, token=token_address, timeout=TIMEOUT_BALANCE
|
|
153
|
+
)
|
|
154
|
+
return res.map(lambda value: scale_and_round(value, token_decimals, ndigits))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def get_token_decimals(
|
|
158
|
+
*,
|
|
159
|
+
network: Network,
|
|
160
|
+
nodes: Nodes,
|
|
161
|
+
proxies: Proxies,
|
|
162
|
+
token_address: str,
|
|
163
|
+
) -> Result[int]:
|
|
164
|
+
"""
|
|
165
|
+
Fetch the number of decimal places for a token.
|
|
166
|
+
|
|
167
|
+
This function retrieves the decimal precision for a token on a given network.
|
|
168
|
+
Currently supports EVM-compatible networks and Solana.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
network: The blockchain network the token exists on
|
|
172
|
+
nodes: RPC nodes to use for the request
|
|
173
|
+
proxies: Proxy configuration for the request
|
|
174
|
+
token_address: The address of the token
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Result containing the number of decimals as an integer on success, or an error message
|
|
178
|
+
"""
|
|
179
|
+
if network.is_evm_network():
|
|
180
|
+
return await eth_retry.erc20_decimals(RETRIES_DECIMALS, nodes, proxies, token=token_address, timeout=TIMEOUT_DECIMALS)
|
|
181
|
+
if network == NETWORK_SOLANA:
|
|
182
|
+
return await sol_retry.get_token_decimals(RETRIES_DECIMALS, nodes, proxies, token=token_address, timeout=TIMEOUT_DECIMALS)
|
|
183
|
+
return Result.err("Unsupported network")
|
mm_balance/token_decimals.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from mm_std import
|
|
1
|
+
from mm_std import fatal
|
|
2
2
|
|
|
3
|
+
from mm_balance import rpc
|
|
3
4
|
from mm_balance.config import Config
|
|
4
5
|
from mm_balance.constants import NETWORK_APTOS, NETWORK_BITCOIN, NETWORK_SOLANA, Network
|
|
5
|
-
from mm_balance.rpc import evm, solana
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TokenDecimals(dict[Network, dict[str | None, int]]): # {network: {None: 18}} -- None is for native token, ex. ETH
|
|
@@ -12,9 +12,8 @@ class TokenDecimals(dict[Network, dict[str | None, int]]): # {network: {None: 1
|
|
|
12
12
|
self[network] = {}
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def get_token_decimals(config: Config) -> TokenDecimals:
|
|
15
|
+
async def get_token_decimals(config: Config) -> TokenDecimals:
|
|
16
16
|
result = TokenDecimals(config.networks())
|
|
17
|
-
proxies = config.settings.proxies
|
|
18
17
|
|
|
19
18
|
for group in config.groups:
|
|
20
19
|
# token_decimals is already known
|
|
@@ -39,15 +38,12 @@ def get_token_decimals(config: Config) -> TokenDecimals:
|
|
|
39
38
|
if group.token in result[group.network]:
|
|
40
39
|
continue # don't request for a token_decimals twice
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if isinstance(res, Err):
|
|
50
|
-
fatal(f"can't get decimals for token {group.ticker} / {group.token}, error={res.err}")
|
|
51
|
-
result[group.network][group.token] = res.ok
|
|
41
|
+
res = await rpc.get_token_decimals(
|
|
42
|
+
network=group.network, nodes=config.nodes[group.network], proxies=config.settings.proxies, token_address=group.token
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if res.is_err():
|
|
46
|
+
fatal(f"can't get decimals for token {group.ticker} / {group.token}, error={res.unwrap_error()}")
|
|
47
|
+
result[group.network][group.token] = res.unwrap()
|
|
52
48
|
|
|
53
49
|
return result
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mm-balance
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Requires-Python: >=3.12
|
|
5
|
+
Requires-Dist: deepdiff==8.4.2
|
|
6
|
+
Requires-Dist: mm-apt==0.3.4
|
|
7
|
+
Requires-Dist: mm-btc==0.4.2
|
|
8
|
+
Requires-Dist: mm-eth==0.6.1
|
|
9
|
+
Requires-Dist: mm-sol==0.6.2
|
|
10
|
+
Requires-Dist: typer==0.15.2
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
mm_balance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
mm_balance/balance_fetcher.py,sha256=HUYoZkBdRSPb3AQ5MANOf1H9PQEyizaftXM4oCwp0Bs,3192
|
|
3
|
+
mm_balance/cli.py,sha256=0ZD0fEhzEgrBrXyxUVfuMikrcqJ5w9LSJST4U8DlSIE,2699
|
|
4
|
+
mm_balance/command_runner.py,sha256=4rWzQfGEznTNTm_tfVlp46FGlpUD0Wd7zFr0UyL1aws,2845
|
|
5
|
+
mm_balance/config.py,sha256=yVB-46HR7saXSsGKbHoxsu6cRMUVTSiuoEPSpSSiOv0,4886
|
|
6
|
+
mm_balance/constants.py,sha256=K1qOTHZNmv3_AsLAWzrRG5XCDBMOhVr1NLnL1_2bC08,2446
|
|
7
|
+
mm_balance/diff.py,sha256=GPRbykty2TIBBM8jpYXOV9Itjyd_mz0BTUsQ8KX7cNo,7099
|
|
8
|
+
mm_balance/price.py,sha256=QVnd0PTn-1ylVLJu230zVpGv5Bs8MgBkantU0pq2osE,1568
|
|
9
|
+
mm_balance/result.py,sha256=yx691T0E6FAe-NSiFMnf1KHBgbCAZPBBQ9xLgKs4_1U,5175
|
|
10
|
+
mm_balance/rpc.py,sha256=-alZDHjpcxna3fHDu_qDGCRMBupFU9x2NeOLGoUQM7U,6121
|
|
11
|
+
mm_balance/token_decimals.py,sha256=di4-LVkXmqAFGc9vrXF_Kq1Oqx3YHWZhavjXrXmigC0,1953
|
|
12
|
+
mm_balance/utils.py,sha256=_UMX3TV350Sr222tAnxGUf0R5McpwloNTQC-U-xiuHc,636
|
|
13
|
+
mm_balance/config/example.toml,sha256=f3Jr40ziOCv_Txf-BysS89c9r7uS-IYHuvwbQ-iftUs,1802
|
|
14
|
+
mm_balance/output/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
mm_balance/output/utils.py,sha256=zyN9igdaXGY_vKfc-3dJ13mH1T7JDke3AeB4MY_3AsA,842
|
|
16
|
+
mm_balance/output/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
mm_balance/output/formats/json_format.py,sha256=ki0AK7Azft5SF9v14_8X_QT-NtyNWqw0MlQmxtV-VH8,921
|
|
18
|
+
mm_balance/output/formats/table_format.py,sha256=aVf34CK0oVyzm5_mYWnV5BG7eFUah4BYE6DJD1-8bRk,4782
|
|
19
|
+
mm_balance-0.4.0.dist-info/METADATA,sha256=IBj1ayGa0HlsY9yLV-qL5eSdmLGeCN5k74n_xh9sf7k,254
|
|
20
|
+
mm_balance-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
21
|
+
mm_balance-0.4.0.dist-info/entry_points.txt,sha256=rSnP0ZW1a3ACNwTWM7T53CmOycKbzhG43m2_wseENng,50
|
|
22
|
+
mm_balance-0.4.0.dist-info/RECORD,,
|
mm_balance/rpc/__init__.py
DELETED
|
File without changes
|
mm_balance/rpc/aptos.py
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
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" # noqa: S105 # 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
|
-
)
|
mm_balance/rpc/btc.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from decimal import Decimal
|
|
2
|
-
|
|
3
|
-
from mm_btc.blockstream import BlockstreamClient
|
|
4
|
-
from mm_std import Ok, Result
|
|
5
|
-
|
|
6
|
-
from mm_balance.constants import RETRIES_BALANCE
|
|
7
|
-
from mm_balance.utils import scale_and_round
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def get_balance(address: str, proxies: list[str], round_ndigits: int) -> Result[Decimal]:
|
|
11
|
-
return (
|
|
12
|
-
BlockstreamClient(proxies=proxies, attempts=RETRIES_BALANCE)
|
|
13
|
-
.get_confirmed_balance(address)
|
|
14
|
-
.and_then(lambda b: Ok(scale_and_round(b, 8, round_ndigits)))
|
|
15
|
-
)
|
mm_balance/rpc/evm.py
DELETED
|
@@ -1,28 +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
|
-
from mm_balance.utils import scale_and_round
|
|
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 not None:
|
|
14
|
-
res = erc20.get_balance(
|
|
15
|
-
nodes,
|
|
16
|
-
token,
|
|
17
|
-
wallet,
|
|
18
|
-
proxies=proxies,
|
|
19
|
-
attempts=RETRIES_BALANCE,
|
|
20
|
-
timeout=TIMEOUT_BALANCE,
|
|
21
|
-
)
|
|
22
|
-
else:
|
|
23
|
-
res = rpc.eth_get_balance(nodes, wallet, proxies=proxies, attempts=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE)
|
|
24
|
-
return res.and_then(lambda b: Ok(scale_and_round(b, decimals, round_ndigits)))
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
|
|
28
|
-
return erc20.get_decimals(nodes, token_address, timeout=TIMEOUT_DECIMALS, proxies=proxies, attempts=RETRIES_DECIMALS)
|
mm_balance/rpc/solana.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
from decimal import Decimal
|
|
2
|
-
|
|
3
|
-
from mm_sol 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
|
-
from mm_balance.utils import scale_and_round
|
|
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_sol_balance_with_retries(
|
|
15
|
-
nodes, wallet, retries=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE, proxies=proxies
|
|
16
|
-
)
|
|
17
|
-
else:
|
|
18
|
-
res = balance.get_token_balance_with_retries(
|
|
19
|
-
nodes, wallet, token, retries=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE, proxies=proxies
|
|
20
|
-
)
|
|
21
|
-
return res.and_then(lambda b: Ok(scale_and_round(b, decimals, round_ndigits)))
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
|
|
25
|
-
return token.get_decimals_with_retries(
|
|
26
|
-
nodes, token_address, retries=RETRIES_DECIMALS, timeout=TIMEOUT_DECIMALS, proxies=proxies
|
|
27
|
-
)
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: mm-balance
|
|
3
|
-
Version: 0.3.1
|
|
4
|
-
Requires-Python: >=3.12
|
|
5
|
-
Requires-Dist: deepdiff==8.2.0
|
|
6
|
-
Requires-Dist: mm-aptos==0.2.0
|
|
7
|
-
Requires-Dist: mm-btc==0.3.0
|
|
8
|
-
Requires-Dist: mm-eth==0.5.4
|
|
9
|
-
Requires-Dist: mm-sol==0.5.2
|
|
10
|
-
Requires-Dist: typer==0.15.1
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
mm_balance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
mm_balance/cli.py,sha256=ROfCm1h0KUm2Uh0a74wNCPIGMgQAOAZKIYBBFl7gcKc,4444
|
|
3
|
-
mm_balance/config.py,sha256=DsADA8jxCZSOT4-acQorKYdn0QbN10B-5GUGEiW9mfk,4675
|
|
4
|
-
mm_balance/constants.py,sha256=2rINgkMYX4SDju21Ep5Djp5WirvvjYbasQgSvtdb3yM,2479
|
|
5
|
-
mm_balance/diff.py,sha256=GPRbykty2TIBBM8jpYXOV9Itjyd_mz0BTUsQ8KX7cNo,7099
|
|
6
|
-
mm_balance/price.py,sha256=DzvcQngS6wgi_4YWoXxGvOuOkwJvUbN0KI8DeIxbB5A,1494
|
|
7
|
-
mm_balance/result.py,sha256=-Ebq07JMLcQAmRs82cA6aYMbsT1qbZSyAOixr9K_wbg,5157
|
|
8
|
-
mm_balance/token_decimals.py,sha256=N3YppB2F3J_OuNkawpAHLVD-4MRCoVjBI01_OoRg5sY,2201
|
|
9
|
-
mm_balance/utils.py,sha256=_UMX3TV350Sr222tAnxGUf0R5McpwloNTQC-U-xiuHc,636
|
|
10
|
-
mm_balance/workers.py,sha256=eg0Ve1xVu3Kd_thfVmPsp6tEdJsYYvs1ipXiu5rKItY,3758
|
|
11
|
-
mm_balance/config/example.toml,sha256=f3Jr40ziOCv_Txf-BysS89c9r7uS-IYHuvwbQ-iftUs,1802
|
|
12
|
-
mm_balance/output/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
mm_balance/output/utils.py,sha256=zyN9igdaXGY_vKfc-3dJ13mH1T7JDke3AeB4MY_3AsA,842
|
|
14
|
-
mm_balance/output/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
mm_balance/output/formats/json_format.py,sha256=japDhWBVaZ9Znwrhi6BLsUEyn2zyNVxPFYhH7SiILb8,893
|
|
16
|
-
mm_balance/output/formats/table_format.py,sha256=QtWq6ETIccfBTw-9SPypeZrX2zfZwpoCFi_Qe_qOLAA,4751
|
|
17
|
-
mm_balance/rpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
mm_balance/rpc/aptos.py,sha256=1JCYCqDim4tk1axXscaAJRXPd4J6vV1ABFbwMbPgrL0,641
|
|
19
|
-
mm_balance/rpc/btc.py,sha256=wBMxUjbqdQipVsTFVkj4tk7loErA2czLVfvG8vjFLeE,493
|
|
20
|
-
mm_balance/rpc/evm.py,sha256=LaU2csGL-VlQauCiTX_WFnstvTyZLMP5gDw2LyV53m8,1048
|
|
21
|
-
mm_balance/rpc/solana.py,sha256=10rJ4eEr9sfEfhXx-X2R7bdJ5dL7bVMwHHfJ4R3QR7U,1071
|
|
22
|
-
mm_balance-0.3.1.dist-info/METADATA,sha256=3itefi1OrREJ0RuW8V_ziuWz5ehJHTMQYU-qwS4cXAE,256
|
|
23
|
-
mm_balance-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
24
|
-
mm_balance-0.3.1.dist-info/entry_points.txt,sha256=rSnP0ZW1a3ACNwTWM7T53CmOycKbzhG43m2_wseENng,50
|
|
25
|
-
mm_balance-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|