mm-balance 0.1.9__py3-none-any.whl → 0.1.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mm_balance/balances.py +141 -70
- mm_balance/cli.py +5 -2
- mm_balance/config/example.yml +7 -2
- mm_balance/config.py +70 -91
- mm_balance/{types.py → constants.py} +19 -6
- mm_balance/output.py +2 -2
- mm_balance/price.py +15 -43
- mm_balance/rpc/btc.py +5 -9
- mm_balance/rpc/eth.py +23 -30
- mm_balance/rpc/solana.py +19 -12
- mm_balance/token_decimals.py +37 -0
- mm_balance/total.py +1 -4
- {mm_balance-0.1.9.dist-info → mm_balance-0.1.11.dist-info}/METADATA +2 -2
- mm_balance-0.1.11.dist-info/RECORD +18 -0
- mm_balance-0.1.9.dist-info/RECORD +0 -17
- {mm_balance-0.1.9.dist-info → mm_balance-0.1.11.dist-info}/WHEEL +0 -0
- {mm_balance-0.1.9.dist-info → mm_balance-0.1.11.dist-info}/entry_points.txt +0 -0
mm_balance/balances.py
CHANGED
|
@@ -4,95 +4,166 @@ from decimal import Decimal
|
|
|
4
4
|
|
|
5
5
|
from mm_std import ConcurrentTasks, Result
|
|
6
6
|
from pydantic import BaseModel
|
|
7
|
-
from rich.progress import
|
|
7
|
+
from rich.progress import TaskID
|
|
8
8
|
|
|
9
9
|
from mm_balance import output
|
|
10
10
|
from mm_balance.config import Config
|
|
11
|
+
from mm_balance.constants import Network
|
|
11
12
|
from mm_balance.rpc import btc, eth, solana
|
|
12
|
-
from mm_balance.
|
|
13
|
+
from mm_balance.token_decimals import TokenDecimals
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
class Balances
|
|
16
|
+
class Balances:
|
|
16
17
|
class Balance(BaseModel):
|
|
17
18
|
group_index: int
|
|
18
19
|
address: str
|
|
19
20
|
token_address: str | None
|
|
20
21
|
balance: Result[Decimal] | None = None
|
|
21
22
|
|
|
22
|
-
config: Config
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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] = {}
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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)
|
|
48
33
|
|
|
49
|
-
|
|
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
|
+
)
|
|
50
38
|
|
|
51
39
|
def process(self) -> None:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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))
|
|
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,))
|
|
61
44
|
job.execute()
|
|
62
45
|
|
|
63
|
-
def
|
|
64
|
-
job = ConcurrentTasks(max_workers=self.config.workers
|
|
65
|
-
for idx, task in enumerate(self.
|
|
66
|
-
job.add_task(str(idx),
|
|
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))
|
|
67
50
|
job.execute()
|
|
68
|
-
for idx, _task in enumerate(self.
|
|
69
|
-
self.
|
|
51
|
+
for idx, _task in enumerate(self.tasks[network]):
|
|
52
|
+
self.tasks[network][idx].balance = job.result.get(str(idx)) # type: ignore[assignment]
|
|
70
53
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
54
|
+
def _get_balance(self, network: Network, wallet_address: str, token_address: str | None) -> Result[Decimal]:
|
|
55
|
+
nodes = self.config.nodes[network]
|
|
56
|
+
round_ndigits = self.config.round_ndigits
|
|
57
|
+
proxies = self.config.proxies
|
|
58
|
+
token_decimals = self.token_decimals[network][token_address] if token_address else -1
|
|
59
|
+
match network:
|
|
60
|
+
case Network.BTC:
|
|
61
|
+
res = btc.get_balance(wallet_address, proxies, round_ndigits)
|
|
62
|
+
case Network.ETH:
|
|
63
|
+
if token_address is None:
|
|
64
|
+
res = eth.get_native_balance(nodes, wallet_address, proxies, round_ndigits)
|
|
65
|
+
else:
|
|
66
|
+
res = eth.get_token_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
|
|
67
|
+
case Network.SOL:
|
|
68
|
+
if token_address is None:
|
|
69
|
+
res = solana.get_native_balance(nodes, wallet_address, proxies, round_ndigits)
|
|
70
|
+
else:
|
|
71
|
+
res = solana.get_token_balance(nodes, wallet_address, token_address, token_decimals, proxies, round_ndigits)
|
|
78
72
|
|
|
79
|
-
|
|
80
|
-
|
|
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]
|
|
73
|
+
case _:
|
|
74
|
+
raise ValueError
|
|
86
75
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
mm_balance/cli.py
CHANGED
|
@@ -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,10 +32,12 @@ 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
|
+
output.print_prices(config, prices)
|
|
36
|
+
|
|
37
|
+
token_decimals = get_token_decimals(config)
|
|
38
|
+
balances = Balances(config, token_decimals)
|
|
35
39
|
balances.process()
|
|
36
40
|
|
|
37
|
-
output.print_prices(config, prices)
|
|
38
41
|
output.print_groups(balances, config, prices)
|
|
39
42
|
output.print_total(config, balances, prices)
|
|
40
43
|
|
mm_balance/config/example.yml
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
coins:
|
|
2
2
|
- coin: sol
|
|
3
3
|
addresses:
|
|
4
4
|
- 2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S # binance
|
|
5
5
|
|
|
6
|
+
- coin: usdt
|
|
7
|
+
network: sol
|
|
8
|
+
addresses:
|
|
9
|
+
- 2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S # binance
|
|
10
|
+
|
|
6
11
|
- coin: btc
|
|
7
12
|
comment: coldwallets
|
|
8
13
|
addresses: |
|
|
@@ -48,4 +53,4 @@ addresses:
|
|
|
48
53
|
#- http://123.123.123.123
|
|
49
54
|
#- http://123.123.123.124
|
|
50
55
|
#round_ndigits: 4
|
|
51
|
-
#price:
|
|
56
|
+
#price: yes
|
mm_balance/config.py
CHANGED
|
@@ -4,78 +4,72 @@ 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.
|
|
10
|
+
from mm_balance.constants import DEFAULT_ETH_NODES, DEFAULT_SOL_NODES, EthTokenAddress, Network, SolTokenAddress
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Group(BaseConfig):
|
|
14
|
+
comment: str = ""
|
|
15
|
+
coin: str
|
|
16
|
+
network: Network
|
|
17
|
+
token_address: str | None = None
|
|
18
|
+
coingecko_id: str | None = None
|
|
19
|
+
addresses: list[str] = Field(default_factory=list)
|
|
20
|
+
share: Decimal = Decimal(1)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def name(self) -> str:
|
|
24
|
+
result = self.coin
|
|
25
|
+
if self.comment:
|
|
26
|
+
result += " / " + self.comment
|
|
27
|
+
return result
|
|
28
|
+
|
|
29
|
+
@field_validator("coin", mode="after")
|
|
30
|
+
def coin_validator(cls, v: str) -> str:
|
|
31
|
+
return v.upper()
|
|
32
|
+
|
|
33
|
+
@field_validator("addresses", mode="before")
|
|
34
|
+
def to_list_validator(cls, v: str | list[str] | None) -> list[str]:
|
|
35
|
+
return cls.to_list_str_validator(v, unique=True, remove_comments=True, split_line=True)
|
|
36
|
+
|
|
37
|
+
@model_validator(mode="before")
|
|
38
|
+
def before_all(cls, data: Any) -> Any:
|
|
39
|
+
if "network" not in data:
|
|
40
|
+
data["network"] = detect_network(data["coin"])
|
|
41
|
+
return data
|
|
42
|
+
|
|
43
|
+
@model_validator(mode="after")
|
|
44
|
+
def final_validator(self) -> Self:
|
|
45
|
+
if self.token_address is None:
|
|
46
|
+
self.token_address = detect_token_address(self.coin, self.network)
|
|
47
|
+
if self.token_address is not None and self.network is Network.ETH:
|
|
48
|
+
self.token_address = self.token_address.lower()
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def process_addresses(self, address_groups: list[AddressGroup]) -> None:
|
|
52
|
+
addresses: list[str] = []
|
|
53
|
+
for address in self.addresses:
|
|
54
|
+
if address_group := pydash.find(address_groups, lambda g: g.name == address): # noqa: B023
|
|
55
|
+
addresses.extend(address_group.addresses)
|
|
56
|
+
else:
|
|
57
|
+
# TODO: check address is valid
|
|
58
|
+
addresses.append(address)
|
|
59
|
+
self.addresses = addresses
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AddressGroup(BaseConfig):
|
|
63
|
+
name: str
|
|
64
|
+
addresses: list[str]
|
|
65
|
+
|
|
66
|
+
@field_validator("addresses", mode="before")
|
|
67
|
+
def to_list_validator(cls, v: str | list[str] | None) -> list[str]:
|
|
68
|
+
return cls.to_list_str_validator(v, unique=True, remove_comments=True, split_line=True)
|
|
11
69
|
|
|
12
70
|
|
|
13
71
|
class Config(BaseConfig):
|
|
14
|
-
|
|
15
|
-
comment: str = ""
|
|
16
|
-
coin: str
|
|
17
|
-
network: Network
|
|
18
|
-
token_address: str | None = None
|
|
19
|
-
coingecko_id: str | None = None
|
|
20
|
-
addresses: list[str] = Field(default_factory=list)
|
|
21
|
-
share: Decimal = Decimal(1)
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def name(self) -> str:
|
|
25
|
-
result = self.coin
|
|
26
|
-
if self.comment:
|
|
27
|
-
result += " / " + self.comment
|
|
28
|
-
return result
|
|
29
|
-
|
|
30
|
-
@field_validator("coin", mode="after")
|
|
31
|
-
def coin_validator(cls, v: str) -> str:
|
|
32
|
-
return v.upper()
|
|
33
|
-
|
|
34
|
-
@field_validator("addresses", mode="before")
|
|
35
|
-
def to_list_validator(cls, v: str | list[str] | None) -> list[str]:
|
|
36
|
-
return cls.to_list_str_validator(v, unique=True, remove_comments=True, split_line=True)
|
|
37
|
-
|
|
38
|
-
@model_validator(mode="before")
|
|
39
|
-
def before_all(cls, data: Any) -> Any:
|
|
40
|
-
if "network" not in data:
|
|
41
|
-
data["network"] = detect_network(data["coin"])
|
|
42
|
-
return data
|
|
43
|
-
|
|
44
|
-
@model_validator(mode="after")
|
|
45
|
-
def final_validator(self) -> Self:
|
|
46
|
-
if self.token_address is None:
|
|
47
|
-
self.token_address = detect_token_address(self.coin, self.network)
|
|
48
|
-
if self.token_address is not None and self.network is Network.ETH:
|
|
49
|
-
self.token_address = self.token_address.lower()
|
|
50
|
-
return self
|
|
51
|
-
|
|
52
|
-
def process_addresses(self, address_groups: list[Config.AddressGroup]) -> None:
|
|
53
|
-
addresses: list[str] = []
|
|
54
|
-
for address in self.addresses:
|
|
55
|
-
if address_group := pydash.find(address_groups, lambda g: g.name == address): # noqa: B023
|
|
56
|
-
addresses.extend(address_group.addresses)
|
|
57
|
-
else:
|
|
58
|
-
# TODO: check address is valid
|
|
59
|
-
addresses.append(address)
|
|
60
|
-
self.addresses = addresses
|
|
61
|
-
|
|
62
|
-
class AddressGroup(BaseConfig):
|
|
63
|
-
name: str
|
|
64
|
-
addresses: list[str]
|
|
65
|
-
|
|
66
|
-
@field_validator("addresses", mode="before")
|
|
67
|
-
def to_list_validator(cls, v: str | list[str] | None) -> list[str]:
|
|
68
|
-
return cls.to_list_str_validator(v, unique=True, remove_comments=True, split_line=True)
|
|
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
|
-
groups: list[Group]
|
|
72
|
+
groups: list[Group] = Field(alias="coins")
|
|
79
73
|
addresses: list[AddressGroup] = Field(default_factory=list)
|
|
80
74
|
|
|
81
75
|
proxies_url: str | None = None
|
|
@@ -85,18 +79,7 @@ class Config(BaseConfig):
|
|
|
85
79
|
print_format: PrintFormat = PrintFormat.TABLE
|
|
86
80
|
price: bool = True
|
|
87
81
|
|
|
88
|
-
|
|
89
|
-
workers: Workers = Workers()
|
|
90
|
-
token_decimals: TokenDecimals = TokenDecimals()
|
|
91
|
-
|
|
92
|
-
def btc_groups(self) -> list[Group]:
|
|
93
|
-
return [g for g in self.groups if g.network == Network.BTC]
|
|
94
|
-
|
|
95
|
-
def eth_groups(self) -> list[Group]:
|
|
96
|
-
return [g for g in self.groups if g.network == Network.ETH]
|
|
97
|
-
|
|
98
|
-
def sol_groups(self) -> list[Group]:
|
|
99
|
-
return [g for g in self.groups if g.network == Network.SOL]
|
|
82
|
+
workers: dict[Network, int] = {network: 5 for network in Network}
|
|
100
83
|
|
|
101
84
|
def has_share(self) -> bool:
|
|
102
85
|
return any(g.share != Decimal(1) for g in self.groups)
|
|
@@ -119,16 +102,6 @@ class Config(BaseConfig):
|
|
|
119
102
|
if Network.SOL not in self.nodes:
|
|
120
103
|
self.nodes[Network.SOL] = DEFAULT_SOL_NODES
|
|
121
104
|
|
|
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
105
|
return self
|
|
133
106
|
|
|
134
107
|
|
|
@@ -141,7 +114,7 @@ def detect_network(coin: str) -> Network:
|
|
|
141
114
|
if coin == "sol":
|
|
142
115
|
return Network.SOL
|
|
143
116
|
return Network.ETH
|
|
144
|
-
# raise ValueError(f"can't get network for the coin: {coin}")
|
|
117
|
+
# TODO: raise ValueError(f"can't get network for the coin: {coin}")
|
|
145
118
|
|
|
146
119
|
|
|
147
120
|
def detect_token_address(coin: str, network: str) -> str | None:
|
|
@@ -151,6 +124,12 @@ def detect_token_address(coin: str, network: str) -> str | None:
|
|
|
151
124
|
if coin.lower() == "usdc":
|
|
152
125
|
return EthTokenAddress.USDC
|
|
153
126
|
|
|
127
|
+
if network == Network.SOL.lower():
|
|
128
|
+
if coin.lower() == "usdt":
|
|
129
|
+
return SolTokenAddress.USDT
|
|
130
|
+
if coin.lower() == "usdc":
|
|
131
|
+
return SolTokenAddress.USDC
|
|
132
|
+
|
|
154
133
|
|
|
155
134
|
def get_proxies(proxies_url: str) -> list[str]:
|
|
156
135
|
try:
|
|
@@ -163,5 +142,5 @@ def get_proxies(proxies_url: str) -> list[str]:
|
|
|
163
142
|
fatal(f"Can't get proxies: {err}")
|
|
164
143
|
|
|
165
144
|
|
|
166
|
-
def get_address_group_by_name(address_groups: list[
|
|
145
|
+
def get_address_group_by_name(address_groups: list[AddressGroup], name: str) -> AddressGroup | None:
|
|
167
146
|
return pydash.find(address_groups, lambda g: g.name == name)
|
|
@@ -2,6 +2,25 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from enum import Enum, unique
|
|
4
4
|
|
|
5
|
+
RETRIES_BALANCE = 5
|
|
6
|
+
RETRIES_DECIMALS = 5
|
|
7
|
+
RETRIES_COINGECKO_PRICES = 5
|
|
8
|
+
TIMEOUT_BALANCE = 5
|
|
9
|
+
TIMEOUT_DECIMALS = 5
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@unique
|
|
13
|
+
class EthTokenAddress(str, Enum):
|
|
14
|
+
USDT = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
|
15
|
+
USDC = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@unique
|
|
19
|
+
class SolTokenAddress(str, Enum):
|
|
20
|
+
USDT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
|
|
21
|
+
USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
|
22
|
+
|
|
23
|
+
|
|
5
24
|
DEFAULT_ETH_NODES = ["https://ethereum.publicnode.com", "https://rpc.ankr.com/eth"]
|
|
6
25
|
DEFAULT_SOL_NODES = ["https://api.mainnet-beta.solana.com"]
|
|
7
26
|
|
|
@@ -24,9 +43,3 @@ class Network(str, Enum):
|
|
|
24
43
|
BTC = "btc"
|
|
25
44
|
ETH = "eth"
|
|
26
45
|
SOL = "sol"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@unique
|
|
30
|
-
class EthTokenAddress(str, Enum):
|
|
31
|
-
USDT = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
|
32
|
-
USDC = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
mm_balance/output.py
CHANGED
|
@@ -4,7 +4,7 @@ from mm_std import Ok, print_table
|
|
|
4
4
|
from rich.progress import BarColumn, MofNCompleteColumn, Progress, TaskID, TextColumn
|
|
5
5
|
|
|
6
6
|
from mm_balance.balances import Balances
|
|
7
|
-
from mm_balance.config import Config
|
|
7
|
+
from mm_balance.config import Config, Group
|
|
8
8
|
from mm_balance.price import Prices
|
|
9
9
|
from mm_balance.total import Total
|
|
10
10
|
|
|
@@ -15,7 +15,7 @@ def print_groups(balances: Balances, config: Config, prices: Prices) -> None:
|
|
|
15
15
|
_print_group(group, group_balances, config, prices)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def _print_group(group:
|
|
18
|
+
def _print_group(group: Group, group_balances: list[Balances.Balance], config: Config, prices: Prices) -> None:
|
|
19
19
|
rows = []
|
|
20
20
|
balance_sum = Decimal(0)
|
|
21
21
|
usd_sum = Decimal(0)
|
mm_balance/price.py
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import time
|
|
2
1
|
from decimal import Decimal
|
|
3
2
|
|
|
4
3
|
import pydash
|
|
5
|
-
from mm_std import
|
|
4
|
+
from mm_std import fatal, hr
|
|
6
5
|
from mm_std.random_ import random_str_choice
|
|
7
6
|
|
|
8
|
-
from mm_balance import
|
|
9
|
-
from mm_balance.
|
|
10
|
-
from mm_balance.types import EthTokenAddress, Network
|
|
7
|
+
from mm_balance.config import Config, Group
|
|
8
|
+
from mm_balance.constants import RETRIES_COINGECKO_PRICES, EthTokenAddress, Network
|
|
11
9
|
|
|
12
10
|
|
|
13
11
|
class Prices(dict[str, Decimal]):
|
|
@@ -21,52 +19,26 @@ class Prices(dict[str, Decimal]):
|
|
|
21
19
|
|
|
22
20
|
def get_prices(config: Config) -> Prices:
|
|
23
21
|
result = Prices()
|
|
24
|
-
coins_total = len(pydash.uniq([group.coin for group in config.groups]))
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
coins = pydash.uniq([group.coin for group in config.groups])
|
|
24
|
+
coingecko_ids = pydash.uniq([get_coingecko_id(group) for group in config.groups])
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
url = f"https://api.coingecko.com/api/v3/simple/price?ids={",".join(coingecko_ids)}&vs_currencies=usd"
|
|
27
|
+
for _ in range(RETRIES_COINGECKO_PRICES):
|
|
28
|
+
res = hr(url, proxy=random_str_choice(config.proxies))
|
|
29
|
+
if res.code != 200:
|
|
30
|
+
continue
|
|
30
31
|
|
|
31
|
-
for
|
|
32
|
-
if
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
coingecko_id = get_coingecko_id(group)
|
|
36
|
-
res = get_asset_price(coingecko_id, config.proxies)
|
|
37
|
-
if isinstance(res, Ok):
|
|
38
|
-
result[group.coin] = res.ok
|
|
39
|
-
progress.update(task_id, advance=1)
|
|
32
|
+
for idx, coin in enumerate(coins):
|
|
33
|
+
if coingecko_ids[idx] in res.json:
|
|
34
|
+
result[coin] = Decimal(str(pydash.get(res.json, f"{coingecko_ids[idx]}.usd")))
|
|
40
35
|
else:
|
|
41
|
-
fatal(
|
|
42
|
-
# raise ValueError(res.err)
|
|
36
|
+
fatal("Can't get price for {coin} from coingecko, coingecko_id={coingecko_ids[idx]}")
|
|
43
37
|
|
|
44
38
|
return result
|
|
45
39
|
|
|
46
40
|
|
|
47
|
-
def
|
|
48
|
-
url = f"https://api.coingecko.com/api/v3/simple/price?ids={coingecko_asset_id}&vs_currencies=usd"
|
|
49
|
-
data = None
|
|
50
|
-
error = f"error: can't get price for {coingecko_asset_id} from coingecko"
|
|
51
|
-
for _ in range(3):
|
|
52
|
-
res = hr(url, proxy=random_str_choice(proxies))
|
|
53
|
-
|
|
54
|
-
# Check for Rate Limit
|
|
55
|
-
if res.code == 429:
|
|
56
|
-
error = f"error: can't get price for {coingecko_asset_id} from coingecko. You've exceeded the Rate Limit. Please add more proxies." # noqa: E501
|
|
57
|
-
if not proxies:
|
|
58
|
-
fatal(error) # Exit immidiately if no proxies are provided
|
|
59
|
-
|
|
60
|
-
data = res.to_dict()
|
|
61
|
-
if res.json and coingecko_asset_id in coingecko_asset_id in res.json:
|
|
62
|
-
return Ok(Decimal(pydash.get(res.json, f"{coingecko_asset_id}.usd")))
|
|
63
|
-
|
|
64
|
-
if not proxies:
|
|
65
|
-
time.sleep(10)
|
|
66
|
-
return Err(error, data=data)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def get_coingecko_id(group: Config.Group) -> str:
|
|
41
|
+
def get_coingecko_id(group: Group) -> str:
|
|
70
42
|
if group.coingecko_id:
|
|
71
43
|
return group.coingecko_id
|
|
72
44
|
elif group.network is Network.BTC:
|
mm_balance/rpc/btc.py
CHANGED
|
@@ -2,19 +2,15 @@ from decimal import Decimal
|
|
|
2
2
|
|
|
3
3
|
from mm_btc.blockstream import BlockstreamClient
|
|
4
4
|
from mm_std import Ok, Result
|
|
5
|
-
from rich.progress import Progress, TaskID
|
|
6
5
|
|
|
7
|
-
from mm_balance.
|
|
6
|
+
from mm_balance.constants import RETRIES_BALANCE
|
|
8
7
|
|
|
9
8
|
|
|
10
|
-
def get_balance(address: str,
|
|
11
|
-
|
|
12
|
-
BlockstreamClient(proxies=
|
|
9
|
+
def get_balance(address: str, proxies: list[str], round_ndigits: int) -> Result[Decimal]:
|
|
10
|
+
return (
|
|
11
|
+
BlockstreamClient(proxies=proxies, attempts=RETRIES_BALANCE)
|
|
13
12
|
.get_confirmed_balance(address)
|
|
14
13
|
.and_then(
|
|
15
|
-
lambda b: Ok(round(Decimal(b / 100_000_000),
|
|
14
|
+
lambda b: Ok(round(Decimal(b / 100_000_000), round_ndigits)),
|
|
16
15
|
)
|
|
17
16
|
)
|
|
18
|
-
if task_id is not None and progress is not None:
|
|
19
|
-
progress.update(task_id, advance=1)
|
|
20
|
-
return res
|
mm_balance/rpc/eth.py
CHANGED
|
@@ -2,37 +2,30 @@ from decimal import Decimal
|
|
|
2
2
|
|
|
3
3
|
from mm_eth import erc20, rpc
|
|
4
4
|
from mm_std import Ok, Result
|
|
5
|
-
from rich.progress import Progress, TaskID
|
|
6
5
|
|
|
7
|
-
from mm_balance.
|
|
8
|
-
from mm_balance.types import Network
|
|
6
|
+
from mm_balance.constants import RETRIES_BALANCE, RETRIES_DECIMALS, TIMEOUT_BALANCE, TIMEOUT_DECIMALS
|
|
9
7
|
|
|
10
8
|
|
|
11
|
-
def
|
|
12
|
-
|
|
9
|
+
def get_native_balance(nodes: list[str], address: str, proxies: list[str], round_ndigits: int) -> Result[Decimal]:
|
|
10
|
+
return rpc.eth_get_balance(nodes, address, proxies=proxies, attempts=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE).and_then(
|
|
11
|
+
lambda b: Ok(round(Decimal(b / 10**18), round_ndigits)),
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_token_balance(
|
|
16
|
+
nodes: list[str], wallet_address: str, token_address: str, decimals: int, proxies: list[str], round_ndigits: int
|
|
13
17
|
) -> Result[Decimal]:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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)
|
|
18
|
+
return erc20.get_balance(
|
|
19
|
+
nodes,
|
|
20
|
+
token_address,
|
|
21
|
+
wallet_address,
|
|
22
|
+
proxies=proxies,
|
|
23
|
+
attempts=RETRIES_BALANCE,
|
|
24
|
+
timeout=TIMEOUT_BALANCE,
|
|
25
|
+
).and_then(
|
|
26
|
+
lambda b: Ok(round(Decimal(b / 10**decimals), round_ndigits)),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
|
|
31
|
+
return erc20.get_decimals(nodes, token_address, timeout=TIMEOUT_DECIMALS, proxies=proxies, attempts=RETRIES_DECIMALS)
|
mm_balance/rpc/solana.py
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
2
|
|
|
3
|
-
from mm_solana
|
|
3
|
+
from mm_solana import balance, token
|
|
4
4
|
from mm_std import Ok, Result
|
|
5
|
-
from rich.progress import Progress, TaskID
|
|
6
5
|
|
|
7
|
-
from mm_balance.
|
|
8
|
-
from mm_balance.types import Network
|
|
6
|
+
from mm_balance.constants import RETRIES_BALANCE, RETRIES_DECIMALS, TIMEOUT_BALANCE, TIMEOUT_DECIMALS
|
|
9
7
|
|
|
10
8
|
|
|
11
|
-
def
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
).and_then(
|
|
15
|
-
|
|
9
|
+
def get_native_balance(nodes: list[str], address: str, proxies: list[str], round_ndigits: int) -> Result[Decimal]:
|
|
10
|
+
return balance.get_balance_with_retries(
|
|
11
|
+
nodes, address, retries=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE, proxies=proxies
|
|
12
|
+
).and_then(lambda b: Ok(round(Decimal(b / 1_000_000_000), round_ndigits)))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_token_balance(
|
|
16
|
+
nodes: list[str], wallet_address: str, token_address: str, decimals: int, proxies: list[str], round_ndigits: int
|
|
17
|
+
) -> Result[Decimal]:
|
|
18
|
+
return token.get_balance_with_retries(
|
|
19
|
+
nodes, wallet_address, token_address, retries=RETRIES_BALANCE, timeout=TIMEOUT_BALANCE, proxies=proxies
|
|
20
|
+
).and_then(lambda b: Ok(round(Decimal(b / 10**decimals), round_ndigits)))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_token_decimals(nodes: list[str], token_address: str, proxies: list[str]) -> Result[int]:
|
|
24
|
+
return token.get_decimals_with_retries(
|
|
25
|
+
nodes, token_address, retries=RETRIES_DECIMALS, timeout=TIMEOUT_DECIMALS, proxies=proxies
|
|
16
26
|
)
|
|
17
|
-
if task_id is not None and progress is not None:
|
|
18
|
-
progress.update(task_id, advance=1)
|
|
19
|
-
return res
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from mm_std import Err, fatal
|
|
2
|
+
|
|
3
|
+
from mm_balance.config import Config
|
|
4
|
+
from mm_balance.constants import Network
|
|
5
|
+
from mm_balance.rpc import eth, solana
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TokenDecimals(dict[Network, dict[str, int]]):
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
super().__init__()
|
|
11
|
+
for network in Network:
|
|
12
|
+
self[network] = {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_token_decimals(config: Config) -> TokenDecimals:
|
|
16
|
+
result = TokenDecimals()
|
|
17
|
+
|
|
18
|
+
for group in config.groups:
|
|
19
|
+
if group.token_address is None or group.token_address in result[group.network]:
|
|
20
|
+
continue
|
|
21
|
+
|
|
22
|
+
nodes = config.nodes[group.network]
|
|
23
|
+
proxies = config.proxies
|
|
24
|
+
|
|
25
|
+
match group.network:
|
|
26
|
+
case Network.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
|
mm_balance/total.py
CHANGED
|
@@ -7,15 +7,14 @@ from mm_std import Ok, PrintFormat, print_table
|
|
|
7
7
|
|
|
8
8
|
from mm_balance.balances import Balances
|
|
9
9
|
from mm_balance.config import Config
|
|
10
|
+
from mm_balance.constants import Coin
|
|
10
11
|
from mm_balance.price import Prices
|
|
11
|
-
from mm_balance.types import Coin
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass
|
|
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
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mm-balance
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
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
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
mm_balance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
mm_balance/balances.py,sha256=fawnQIfjWsljXo1MdvSxs3Xo7j19hBCa9jlyDXXWO7M,7587
|
|
3
|
+
mm_balance/cli.py,sha256=tjMXQhtKZiCzqoTw5rIygMaVCOwNMiNbkr4nzOY06wA,1360
|
|
4
|
+
mm_balance/config.py,sha256=lDizIS2qmZpiOWbrdPvn0DKUdN0FWuNdv2qKPJBfWEI,4866
|
|
5
|
+
mm_balance/constants.py,sha256=GnyqtIfxIpWLuzB80Y0mYrzrRk8XT3fMHh63Fek5LEA,935
|
|
6
|
+
mm_balance/output.py,sha256=GH5ESychKEDOc2gtaUH_JtKdNX1ZkXZD6kCk2mxd8_4,2568
|
|
7
|
+
mm_balance/price.py,sha256=uAm7pVAwMyzZ7bE4xxz4PD0mzLR7zFk6UtYSwGqWjAs,1996
|
|
8
|
+
mm_balance/token_decimals.py,sha256=8tAZiN5RpSFRtYb1VODABGg9x3JbLY73EA5BV0RqVv4,1297
|
|
9
|
+
mm_balance/total.py,sha256=3IDNBrcqGFaUYMlAzLpQCEBcWi1zdT0RSm7o7t1b4Tw,4700
|
|
10
|
+
mm_balance/config/example.yml,sha256=j3UzCgCIImkkzyMT_68LW7lRChsQOlwowhKjcwRBlyc,1336
|
|
11
|
+
mm_balance/rpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
mm_balance/rpc/btc.py,sha256=ugp90H7YW0kiXIh98bQWk9mQTW20yE-jBiCpRvfoH-U,481
|
|
13
|
+
mm_balance/rpc/eth.py,sha256=G7aYjTw6xJwcsAyIp9eVW8NRVDUGeTCpdbd-CqgqHyw,1167
|
|
14
|
+
mm_balance/rpc/solana.py,sha256=vmCAaeQKwxg95qdSmUEjFS9bAPUJVx8irtWv6nlRBvU,1174
|
|
15
|
+
mm_balance-0.1.11.dist-info/METADATA,sha256=ExgagHu4tCqpAHPjqivCaQ7-2Dj3HwrwOeyJct7DsXw,198
|
|
16
|
+
mm_balance-0.1.11.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
17
|
+
mm_balance-0.1.11.dist-info/entry_points.txt,sha256=rSnP0ZW1a3ACNwTWM7T53CmOycKbzhG43m2_wseENng,50
|
|
18
|
+
mm_balance-0.1.11.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
mm_balance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
mm_balance/balances.py,sha256=lagu1y8OosgOPnMnp7DpY1vZmaarQZZABvvM3fK8Zjw,4053
|
|
3
|
-
mm_balance/cli.py,sha256=VpiNJdwels7qWuklpV7kNEYm0lhsN4NJ7yENIsQLdK8,1250
|
|
4
|
-
mm_balance/config.py,sha256=WH1YjOJxofv2jgklupd7sCzgz2nZtHDYCYhoSaZTHl0,5918
|
|
5
|
-
mm_balance/output.py,sha256=Sb0pccEBNOCR9fuMigO9GJcyTNw9XPRZXTg8iznJKFQ,2568
|
|
6
|
-
mm_balance/price.py,sha256=KyMx1T57SczKbYmbghpGty9BrPebrZdjBW0_BaabKDk,2926
|
|
7
|
-
mm_balance/total.py,sha256=gdH9BzFgc-akVVrsCJ6NqN6cIS9mkEfp-TTKcn01I0U,4944
|
|
8
|
-
mm_balance/types.py,sha256=8TflwL3KJ8HQW31qa8xrh-gyJT232lN1XznuNnIR6zM,662
|
|
9
|
-
mm_balance/config/example.yml,sha256=6_S0hBdh7gMyO1ZPTGxmL21bBSzzmXuJHB0eWqKGueU,1225
|
|
10
|
-
mm_balance/rpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
mm_balance/rpc/btc.py,sha256=OozGSF8CzR13jxTYjvBqkC8d0pdKkyEHxM7e7M_WbV0,681
|
|
12
|
-
mm_balance/rpc/eth.py,sha256=yx31pwjvlu6MJUuWxtZtiK1vQvIkfMFwu5cNKaz1Yks,1317
|
|
13
|
-
mm_balance/rpc/solana.py,sha256=tL51r_T1hNUDSB8vdjp9QB4ToLjd6yRLXE1RW0-eauw,710
|
|
14
|
-
mm_balance-0.1.9.dist-info/METADATA,sha256=evs4MY6DDuZgIJKPbuVSqG87m7AguX5ExfdABT1gnk0,197
|
|
15
|
-
mm_balance-0.1.9.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
16
|
-
mm_balance-0.1.9.dist-info/entry_points.txt,sha256=rSnP0ZW1a3ACNwTWM7T53CmOycKbzhG43m2_wseENng,50
|
|
17
|
-
mm_balance-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|