mm-balance 0.5.0__py3-none-any.whl → 0.6.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/cli.py +2 -2
- mm_balance/command_runner.py +1 -1
- mm_balance/config/example.toml +1 -1
- mm_balance/config.py +13 -9
- mm_balance/diff.py +1 -1
- mm_balance/output/formats/table_format.py +8 -8
- mm_balance/price.py +2 -2
- mm_balance/result.py +5 -5
- mm_balance/rpc.py +1 -1
- mm_balance/token_decimals.py +2 -2
- mm_balance/utils.py +133 -0
- mm_balance-0.6.0.dist-info/METADATA +10 -0
- mm_balance-0.6.0.dist-info/RECORD +22 -0
- mm_balance-0.5.0.dist-info/METADATA +0 -10
- mm_balance-0.5.0.dist-info/RECORD +0 -22
- {mm_balance-0.5.0.dist-info → mm_balance-0.6.0.dist-info}/WHEEL +0 -0
- {mm_balance-0.5.0.dist-info → mm_balance-0.6.0.dist-info}/entry_points.txt +0 -0
mm_balance/cli.py
CHANGED
|
@@ -25,8 +25,8 @@ def example_callback(value: bool) -> None:
|
|
|
25
25
|
if value:
|
|
26
26
|
data = pkgutil.get_data(__name__, "config/example.toml")
|
|
27
27
|
if data is None:
|
|
28
|
-
mm_print.
|
|
29
|
-
mm_print.toml(
|
|
28
|
+
mm_print.exit_with_error("Example config not found")
|
|
29
|
+
mm_print.toml(data.decode("utf-8"))
|
|
30
30
|
raise typer.Exit
|
|
31
31
|
|
|
32
32
|
|
mm_balance/command_runner.py
CHANGED
|
@@ -63,7 +63,7 @@ async def run(params: CommandParameters) -> None:
|
|
|
63
63
|
elif config.settings.print_format is PrintFormat.JSON:
|
|
64
64
|
json_format.print_result(config, token_decimals, prices, workers, result)
|
|
65
65
|
else:
|
|
66
|
-
mm_print.
|
|
66
|
+
mm_print.exit_with_error("Unsupported print format")
|
|
67
67
|
|
|
68
68
|
if params.save_balances:
|
|
69
69
|
BalancesDict.from_balances_result(result).save_to_path(params.save_balances)
|
mm_balance/config/example.toml
CHANGED
|
@@ -17,7 +17,7 @@ addresses = """
|
|
|
17
17
|
bc1qgdjqv0av3q56jvd82tkdjpy7gdp9ut8tlqmgrpmv24sq90ecnvqqjwvw97 # bitfinex
|
|
18
18
|
bc1ql49ydapnjafl5t2cp9zqpjwe6pdgmxy98859v2 # robinhood
|
|
19
19
|
"""
|
|
20
|
-
share = 0.
|
|
20
|
+
share = "0.5(total - 100.5)" # or: "total - 1000", "0.3total + 50", etc.
|
|
21
21
|
|
|
22
22
|
[[coins]]
|
|
23
23
|
ticker = "ETH"
|
mm_balance/config.py
CHANGED
|
@@ -6,18 +6,18 @@ from typing import Annotated, Self
|
|
|
6
6
|
|
|
7
7
|
import mm_print
|
|
8
8
|
import pydash
|
|
9
|
-
from
|
|
9
|
+
from mm_web3 import ConfigValidators, Web3CliConfig
|
|
10
10
|
from pydantic import BeforeValidator, Field, StringConstraints, model_validator
|
|
11
11
|
|
|
12
12
|
from mm_balance.constants import DEFAULT_NODES, TOKEN_ADDRESS, Network
|
|
13
|
-
from mm_balance.utils import PrintFormat
|
|
13
|
+
from mm_balance.utils import PrintFormat, evaluate_share_expression
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class Validators(ConfigValidators):
|
|
17
17
|
pass
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class AssetGroup(
|
|
20
|
+
class AssetGroup(Web3CliConfig):
|
|
21
21
|
"""
|
|
22
22
|
Represents a group of cryptocurrency assets of the same type.
|
|
23
23
|
|
|
@@ -32,7 +32,7 @@ class AssetGroup(CryptocurrencyConfig):
|
|
|
32
32
|
decimals: int | None = None
|
|
33
33
|
coingecko_id: str | None = None
|
|
34
34
|
addresses: Annotated[list[str], BeforeValidator(Validators.addresses(deduplicate=True))]
|
|
35
|
-
share:
|
|
35
|
+
share: str = "total"
|
|
36
36
|
|
|
37
37
|
@property
|
|
38
38
|
def name(self) -> str:
|
|
@@ -42,6 +42,10 @@ class AssetGroup(CryptocurrencyConfig):
|
|
|
42
42
|
result += " / " + self.network
|
|
43
43
|
return result
|
|
44
44
|
|
|
45
|
+
def evaluate_share(self, balance_sum: Decimal) -> Decimal:
|
|
46
|
+
"""Evaluate share expression with actual balance_sum value."""
|
|
47
|
+
return evaluate_share_expression(self.share, balance_sum)
|
|
48
|
+
|
|
45
49
|
@model_validator(mode="after")
|
|
46
50
|
def final_validator(self) -> Self:
|
|
47
51
|
if self.token is None:
|
|
@@ -58,7 +62,7 @@ class AssetGroup(CryptocurrencyConfig):
|
|
|
58
62
|
if path.is_file():
|
|
59
63
|
result += path.read_text().strip().splitlines()
|
|
60
64
|
else:
|
|
61
|
-
mm_print.
|
|
65
|
+
mm_print.exit_with_error(f"File with addresses not found: {path}")
|
|
62
66
|
elif line.startswith("group:"):
|
|
63
67
|
group_name = line.removeprefix("group:").strip()
|
|
64
68
|
address_group = next((ag for ag in address_groups if ag.name == group_name), None)
|
|
@@ -73,12 +77,12 @@ class AssetGroup(CryptocurrencyConfig):
|
|
|
73
77
|
self.addresses = pydash.uniq(result)
|
|
74
78
|
|
|
75
79
|
|
|
76
|
-
class AddressCollection(
|
|
80
|
+
class AddressCollection(Web3CliConfig):
|
|
77
81
|
name: str
|
|
78
82
|
addresses: Annotated[list[str], BeforeValidator(Validators.addresses(deduplicate=True))]
|
|
79
83
|
|
|
80
84
|
|
|
81
|
-
class Settings(
|
|
85
|
+
class Settings(Web3CliConfig):
|
|
82
86
|
proxies: Annotated[list[str], Field(default_factory=list), BeforeValidator(Validators.proxies())]
|
|
83
87
|
round_ndigits: int = 4
|
|
84
88
|
print_format: PrintFormat = PrintFormat.TABLE
|
|
@@ -88,7 +92,7 @@ class Settings(CryptocurrencyConfig):
|
|
|
88
92
|
format_number_separator: str = "," # as thousands separators
|
|
89
93
|
|
|
90
94
|
|
|
91
|
-
class Config(
|
|
95
|
+
class Config(Web3CliConfig):
|
|
92
96
|
groups: list[AssetGroup] = Field(alias="coins")
|
|
93
97
|
addresses: list[AddressCollection] = Field(default_factory=list)
|
|
94
98
|
nodes: dict[Network, list[str]] = Field(default_factory=dict)
|
|
@@ -96,7 +100,7 @@ class Config(CryptocurrencyConfig):
|
|
|
96
100
|
settings: Settings = Field(default_factory=Settings) # type: ignore[arg-type]
|
|
97
101
|
|
|
98
102
|
def has_share(self) -> bool:
|
|
99
|
-
return any(g.share !=
|
|
103
|
+
return any(g.share != "total" for g in self.groups)
|
|
100
104
|
|
|
101
105
|
def networks(self) -> list[Network]:
|
|
102
106
|
return pydash.uniq([group.network for group in self.groups])
|
mm_balance/diff.py
CHANGED
|
@@ -97,7 +97,7 @@ class Diff(BaseModel):
|
|
|
97
97
|
for address in self.balance_changed[network][ticker]:
|
|
98
98
|
old_value, new_value = self.balance_changed[network][ticker][address]
|
|
99
99
|
rows.append([network, ticker, address, old_value, new_value, new_value - old_value])
|
|
100
|
-
mm_print.table(
|
|
100
|
+
mm_print.table(["Network", "Ticker", "Address", "Old", "New", "Change"], rows)
|
|
101
101
|
|
|
102
102
|
def _print_json(self) -> None:
|
|
103
103
|
# mm_print.json(data=self.model_dump(), type_handlers=str) ?? default?
|
|
@@ -14,18 +14,18 @@ def print_nodes(config: Config) -> None:
|
|
|
14
14
|
rows = []
|
|
15
15
|
for network, nodes in config.nodes.items():
|
|
16
16
|
rows.append([network, "\n".join(nodes)])
|
|
17
|
-
mm_print.table(
|
|
17
|
+
mm_print.table(["network", "nodes"], rows, title="Nodes")
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def print_proxy_count(config: Config) -> None:
|
|
21
|
-
mm_print.table(
|
|
21
|
+
mm_print.table(["count"], [[len(config.settings.proxies)]], title="Proxies")
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def print_token_decimals(token_decimals: TokenDecimals) -> None:
|
|
25
25
|
rows = []
|
|
26
26
|
for network, decimals in token_decimals.items():
|
|
27
27
|
rows.append([network, decimals])
|
|
28
|
-
mm_print.table(
|
|
28
|
+
mm_print.table(["network", "decimals"], rows, title="Token Decimals")
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def print_prices(config: Config, prices: Prices) -> None:
|
|
@@ -35,7 +35,7 @@ def print_prices(config: Config, prices: Prices) -> None:
|
|
|
35
35
|
rows.append(
|
|
36
36
|
[ticker, format_number(price, config.settings.format_number_separator, "$", config.settings.round_ndigits)]
|
|
37
37
|
)
|
|
38
|
-
mm_print.table(
|
|
38
|
+
mm_print.table(["coin", "usd"], rows, title="Prices")
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
def print_result(config: Config, result: BalancesResult, workers: BalanceFetcher) -> None:
|
|
@@ -57,7 +57,7 @@ def _print_errors(config: Config, workers: BalanceFetcher) -> None:
|
|
|
57
57
|
for task in error_tasks:
|
|
58
58
|
group = config.groups[task.group_index]
|
|
59
59
|
rows.append([group.ticker + " / " + group.network, task.wallet_address, task.balance.error]) # type: ignore[union-attr]
|
|
60
|
-
mm_print.table(
|
|
60
|
+
mm_print.table(["coin", "address", "error"], rows, title="Errors")
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def _print_total(config: Config, total: Total, is_share_total: bool) -> None:
|
|
@@ -80,7 +80,7 @@ def _print_total(config: Config, total: Total, is_share_total: bool) -> None:
|
|
|
80
80
|
rows.append(["stablecoin_sum", format_number(total.stablecoin_sum, config.settings.format_number_separator, "$")])
|
|
81
81
|
rows.append(["total_usd_sum", format_number(total.total_usd_sum, config.settings.format_number_separator, "$")])
|
|
82
82
|
|
|
83
|
-
mm_print.table(
|
|
83
|
+
mm_print.table(headers, rows, title=table_name)
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
def _print_group(config: Config, group: GroupResult) -> None:
|
|
@@ -108,7 +108,7 @@ def _print_group(config: Config, group: GroupResult) -> None:
|
|
|
108
108
|
sum_row.append(format_number(group.usd_sum, config.settings.format_number_separator, "$"))
|
|
109
109
|
rows.append(sum_row)
|
|
110
110
|
|
|
111
|
-
if group.share
|
|
111
|
+
if group.share != "total":
|
|
112
112
|
sum_share_str = format_number(group.balance_sum_share, config.settings.format_number_separator)
|
|
113
113
|
sum_share_row = [f"sum_share, {group.share}", sum_share_str]
|
|
114
114
|
if config.settings.price:
|
|
@@ -118,4 +118,4 @@ def _print_group(config: Config, group: GroupResult) -> None:
|
|
|
118
118
|
table_headers = ["address", "balance"]
|
|
119
119
|
if config.settings.price:
|
|
120
120
|
table_headers += ["usd"]
|
|
121
|
-
mm_print.table(
|
|
121
|
+
mm_print.table(table_headers, rows, title=group_name)
|
mm_balance/price.py
CHANGED
|
@@ -2,8 +2,8 @@ from collections import defaultdict
|
|
|
2
2
|
from decimal import Decimal
|
|
3
3
|
|
|
4
4
|
import pydash
|
|
5
|
-
from mm_cryptocurrency import random_proxy
|
|
6
5
|
from mm_http import http_request
|
|
6
|
+
from mm_web3 import random_proxy
|
|
7
7
|
|
|
8
8
|
from mm_balance.config import AssetGroup, Config
|
|
9
9
|
from mm_balance.constants import RETRIES_COINGECKO_PRICES, TICKER_TO_COINGECKO_ID
|
|
@@ -34,7 +34,7 @@ async def get_prices(config: Config) -> Prices:
|
|
|
34
34
|
if res.status_code != 200:
|
|
35
35
|
continue
|
|
36
36
|
|
|
37
|
-
json_body = res.
|
|
37
|
+
json_body = res.parse_json()
|
|
38
38
|
|
|
39
39
|
for ticker, coingecko_id in coingecko_map.items():
|
|
40
40
|
if coingecko_id in json_body:
|
mm_balance/result.py
CHANGED
|
@@ -26,12 +26,12 @@ class GroupResult:
|
|
|
26
26
|
ticker: str
|
|
27
27
|
network: Network
|
|
28
28
|
comment: str
|
|
29
|
-
share:
|
|
29
|
+
share: str
|
|
30
30
|
addresses: list[AddressBalance]
|
|
31
31
|
balance_sum: Decimal # sum of all balances in the group
|
|
32
32
|
usd_sum: Decimal # sum of all usd values in the group
|
|
33
|
-
balance_sum_share: Decimal #
|
|
34
|
-
usd_sum_share: Decimal #
|
|
33
|
+
balance_sum_share: Decimal # calculated from share expression
|
|
34
|
+
usd_sum_share: Decimal # proportional to balance_sum_share
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
@dataclass
|
|
@@ -113,8 +113,8 @@ def _create_group_result(config: Config, group: AssetGroup, tasks: list[Task], p
|
|
|
113
113
|
balance = task.balance.unwrap_err()
|
|
114
114
|
addresses.append(AddressBalance(address=task.wallet_address, balance=balance))
|
|
115
115
|
|
|
116
|
-
balance_sum_share =
|
|
117
|
-
usd_sum_share = usd_sum *
|
|
116
|
+
balance_sum_share = group.evaluate_share(balance_sum)
|
|
117
|
+
usd_sum_share = usd_sum * (balance_sum_share / balance_sum) if balance_sum > 0 else Decimal(0)
|
|
118
118
|
|
|
119
119
|
return GroupResult(
|
|
120
120
|
ticker=group.ticker,
|
mm_balance/rpc.py
CHANGED
|
@@ -2,10 +2,10 @@ from decimal import Decimal
|
|
|
2
2
|
|
|
3
3
|
from mm_apt import retry as apt_retry
|
|
4
4
|
from mm_btc.blockstream import BlockstreamClient
|
|
5
|
-
from mm_cryptocurrency import Nodes, Proxies
|
|
6
5
|
from mm_eth import retry as eth_retry
|
|
7
6
|
from mm_result import Result
|
|
8
7
|
from mm_sol import retry as sol_retry
|
|
8
|
+
from mm_web3 import Nodes, Proxies
|
|
9
9
|
|
|
10
10
|
from mm_balance.constants import (
|
|
11
11
|
NETWORK_APTOS,
|
mm_balance/token_decimals.py
CHANGED
|
@@ -30,7 +30,7 @@ async def get_token_decimals(config: Config) -> TokenDecimals:
|
|
|
30
30
|
elif group.network in (NETWORK_BITCOIN, NETWORK_APTOS):
|
|
31
31
|
result[group.network][None] = 8
|
|
32
32
|
else:
|
|
33
|
-
mm_print.
|
|
33
|
+
mm_print.exit_with_error(f"Can't get token decimals for native token on network: {group.network}")
|
|
34
34
|
continue
|
|
35
35
|
|
|
36
36
|
# get token_decimals via RPC
|
|
@@ -46,7 +46,7 @@ async def get_token_decimals(config: Config) -> TokenDecimals:
|
|
|
46
46
|
msg = f"can't get decimals for token {group.ticker} / {group.token}, error={res.unwrap_err()}"
|
|
47
47
|
if config.settings.print_debug:
|
|
48
48
|
msg += f"\n{res.extra}"
|
|
49
|
-
mm_print.
|
|
49
|
+
mm_print.exit_with_error(msg)
|
|
50
50
|
|
|
51
51
|
result[group.network][group.token] = res.unwrap()
|
|
52
52
|
|
mm_balance/utils.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from decimal import Decimal
|
|
2
3
|
from enum import StrEnum, unique
|
|
3
4
|
|
|
@@ -23,6 +24,138 @@ def round_decimal(value: Decimal, round_ndigits: int) -> Decimal:
|
|
|
23
24
|
return round(value, round_ndigits)
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
class _ExpressionParser:
|
|
28
|
+
"""Recursive descent parser for arithmetic expressions.
|
|
29
|
+
|
|
30
|
+
Parses expressions by reading left-to-right, respecting operator precedence:
|
|
31
|
+
1. Parentheses and unary +/- (highest)
|
|
32
|
+
2. Multiplication and division
|
|
33
|
+
3. Addition and subtraction (lowest)
|
|
34
|
+
|
|
35
|
+
Uses self.pos as a cursor tracking current position in the string.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, expr: str) -> None:
|
|
39
|
+
self.expr = expr.replace(" ", "") # Expression string with spaces removed
|
|
40
|
+
self.pos = 0 # Current position/index in the string (starts at beginning)
|
|
41
|
+
|
|
42
|
+
def parse(self) -> Decimal:
|
|
43
|
+
result = self._parse_expression()
|
|
44
|
+
if self.pos < len(self.expr):
|
|
45
|
+
raise ValueError(f"Unexpected character at position {self.pos}: '{self.expr[self.pos]}'")
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
def _parse_expression(self) -> Decimal:
|
|
49
|
+
"""Parse addition and subtraction (lowest precedence)."""
|
|
50
|
+
result = self._parse_term()
|
|
51
|
+
|
|
52
|
+
while self.pos < len(self.expr):
|
|
53
|
+
if self._peek() == "+":
|
|
54
|
+
self.pos += 1
|
|
55
|
+
result = result + self._parse_term()
|
|
56
|
+
elif self._peek() == "-":
|
|
57
|
+
self.pos += 1
|
|
58
|
+
result = result - self._parse_term()
|
|
59
|
+
else:
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
def _parse_term(self) -> Decimal:
|
|
65
|
+
"""Parse multiplication and division (medium precedence)."""
|
|
66
|
+
result = self._parse_factor()
|
|
67
|
+
|
|
68
|
+
while self.pos < len(self.expr):
|
|
69
|
+
if self._peek() == "*":
|
|
70
|
+
self.pos += 1
|
|
71
|
+
result = result * self._parse_factor()
|
|
72
|
+
elif self._peek() == "/":
|
|
73
|
+
self.pos += 1
|
|
74
|
+
divisor = self._parse_factor()
|
|
75
|
+
if divisor == 0:
|
|
76
|
+
raise ValueError("Division by zero")
|
|
77
|
+
result = result / divisor
|
|
78
|
+
else:
|
|
79
|
+
break
|
|
80
|
+
|
|
81
|
+
return result
|
|
82
|
+
|
|
83
|
+
def _parse_factor(self) -> Decimal:
|
|
84
|
+
"""Parse unary +/-, numbers, and parentheses (highest precedence)."""
|
|
85
|
+
if self._peek() == "+":
|
|
86
|
+
self.pos += 1
|
|
87
|
+
return self._parse_factor()
|
|
88
|
+
if self._peek() == "-":
|
|
89
|
+
self.pos += 1
|
|
90
|
+
return -self._parse_factor()
|
|
91
|
+
|
|
92
|
+
if self._peek() == "(":
|
|
93
|
+
self.pos += 1
|
|
94
|
+
result = self._parse_expression()
|
|
95
|
+
if self._peek() != ")":
|
|
96
|
+
raise ValueError(f"Expected ')' at position {self.pos}")
|
|
97
|
+
self.pos += 1
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
return self._parse_number()
|
|
101
|
+
|
|
102
|
+
def _parse_number(self) -> Decimal:
|
|
103
|
+
"""Parse a numeric value.
|
|
104
|
+
|
|
105
|
+
Scans digits, optionally followed by a decimal point and more digits.
|
|
106
|
+
Advances self.pos past the entire number.
|
|
107
|
+
"""
|
|
108
|
+
start = self.pos
|
|
109
|
+
|
|
110
|
+
# Scan integer part: consecutive digits
|
|
111
|
+
while self.pos < len(self.expr) and self.expr[self.pos].isdigit():
|
|
112
|
+
self.pos += 1
|
|
113
|
+
|
|
114
|
+
# If there's a decimal point, scan fractional part
|
|
115
|
+
if self.pos < len(self.expr) and self.expr[self.pos] == ".":
|
|
116
|
+
self.pos += 1
|
|
117
|
+
while self.pos < len(self.expr) and self.expr[self.pos].isdigit():
|
|
118
|
+
self.pos += 1
|
|
119
|
+
|
|
120
|
+
# Ensure we consumed at least one digit
|
|
121
|
+
if start == self.pos:
|
|
122
|
+
raise ValueError(f"Expected number at position {self.pos}")
|
|
123
|
+
|
|
124
|
+
# Extract the substring from start to current position
|
|
125
|
+
return Decimal(self.expr[start : self.pos])
|
|
126
|
+
|
|
127
|
+
def _peek(self) -> str | None:
|
|
128
|
+
"""Peek at the current character without consuming it."""
|
|
129
|
+
if self.pos < len(self.expr):
|
|
130
|
+
return self.expr[self.pos]
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def evaluate_share_expression(expression: str, balance_sum: Decimal) -> Decimal:
|
|
135
|
+
"""Evaluate share expression with actual balance_sum value.
|
|
136
|
+
|
|
137
|
+
Supports expressions like:
|
|
138
|
+
- "total" -> full balance
|
|
139
|
+
- "0.5total" -> 50% of balance
|
|
140
|
+
- "0.5(total - 100)" -> 50% of (balance - 100)
|
|
141
|
+
- "total - 1000" -> balance minus 1000
|
|
142
|
+
"""
|
|
143
|
+
if not re.match(r"^[0-9+\-*/.() total]+$", expression):
|
|
144
|
+
raise ValueError(f"Invalid share expression '{expression}': contains invalid characters")
|
|
145
|
+
|
|
146
|
+
# Insert * before ( when preceded by digit or )
|
|
147
|
+
expr = re.sub(r"(\d|\))\(", r"\1*(", expression)
|
|
148
|
+
# Insert * before 'total' when preceded by digit or )
|
|
149
|
+
expr = re.sub(r"(\d|\))total", r"\1*total", expr)
|
|
150
|
+
# Replace 'total' with actual value in parentheses
|
|
151
|
+
expr = expr.replace("total", f"({balance_sum})")
|
|
152
|
+
try:
|
|
153
|
+
parser = _ExpressionParser(expr)
|
|
154
|
+
return parser.parse()
|
|
155
|
+
except Exception as e:
|
|
156
|
+
raise ValueError(f"Invalid share expression '{expression}': {e}") from e
|
|
157
|
+
|
|
158
|
+
|
|
26
159
|
@unique
|
|
27
160
|
class PrintFormat(StrEnum):
|
|
28
161
|
PLAIN = "plain"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mm-balance
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Requires-Python: >=3.13
|
|
5
|
+
Requires-Dist: deepdiff==8.6.1
|
|
6
|
+
Requires-Dist: mm-apt==0.5.0
|
|
7
|
+
Requires-Dist: mm-btc==0.5.5
|
|
8
|
+
Requires-Dist: mm-concurrency~=0.1.0
|
|
9
|
+
Requires-Dist: mm-eth==0.7.3
|
|
10
|
+
Requires-Dist: mm-sol==0.7.3
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
mm_balance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
mm_balance/balance_fetcher.py,sha256=OCRQz_8lYxBKIicwXR3ysQOYxbLfYKRfaLWiFLnR0c8,3239
|
|
3
|
+
mm_balance/cli.py,sha256=t_pshfbHEtzkmV9U6HrTAQy9HBVUqdf4jStUJ_s5Omg,2714
|
|
4
|
+
mm_balance/command_runner.py,sha256=JvFHDzG6cEOic648PGlsNa3PwnA_rHRPB940WGPmWZo,2885
|
|
5
|
+
mm_balance/config.py,sha256=eeE44dXnhekN4c4ZYcIpwQZiYr54Lku97RYasoE2IkY,5153
|
|
6
|
+
mm_balance/constants.py,sha256=LhyW1EfNP5E8st-RPvD7p3QoIowFH2W14tkCf6NzveU,2468
|
|
7
|
+
mm_balance/diff.py,sha256=CLaqXvFFZjxv9zJUE0O-rWyvSZNDi-jM1Md0LfgNOf8,7168
|
|
8
|
+
mm_balance/price.py,sha256=jOwjD-7pIdo894iNH4pK-7LPuz6KnHDR23ghfmOedvU,1567
|
|
9
|
+
mm_balance/result.py,sha256=qW2MRNiPt1JlFUSOJCFCcp-dEirZ2RK3RG5wyE1JAGQ,5193
|
|
10
|
+
mm_balance/rpc.py,sha256=Z-rHJdhD8ScghUchV2M6rb8jyiPfB3nChEWFXjcBQOo,6116
|
|
11
|
+
mm_balance/token_decimals.py,sha256=g8z2ahdVY5nlgmHyvlK9Dk9ush6G7eBkp31k74e9rKE,2087
|
|
12
|
+
mm_balance/utils.py,sha256=GElNtCgE5jb2idak8l6_TnL7JhvmK9mdwObjVwdgE9o,5459
|
|
13
|
+
mm_balance/config/example.toml,sha256=tqYx9gxhXYa5uKWFi-MDHYkPOgxqgrO_ZuU7T5tLzLM,1864
|
|
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=uNs2P1J6QhxCsHo8TOqG6aQbDfBkVjIwYyo96d2K17Q,910
|
|
18
|
+
mm_balance/output/formats/table_format.py,sha256=2Y-21O7q-ghTrVSVewRPlhM-M86MoKw_arIYP5wALWE,4828
|
|
19
|
+
mm_balance-0.6.0.dist-info/METADATA,sha256=3F0EMDxp6X84Wf1sb4cBnIUJ0QfnAk0K7Orsa6VtLUA,262
|
|
20
|
+
mm_balance-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
21
|
+
mm_balance-0.6.0.dist-info/entry_points.txt,sha256=rSnP0ZW1a3ACNwTWM7T53CmOycKbzhG43m2_wseENng,50
|
|
22
|
+
mm_balance-0.6.0.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: mm-balance
|
|
3
|
-
Version: 0.5.0
|
|
4
|
-
Requires-Python: >=3.13
|
|
5
|
-
Requires-Dist: deepdiff==8.5.0
|
|
6
|
-
Requires-Dist: mm-apt==0.4.1
|
|
7
|
-
Requires-Dist: mm-btc==0.5.2
|
|
8
|
-
Requires-Dist: mm-concurrency>=0.0.1
|
|
9
|
-
Requires-Dist: mm-eth==0.7.0
|
|
10
|
-
Requires-Dist: mm-sol==0.7.0
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
mm_balance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
mm_balance/balance_fetcher.py,sha256=OCRQz_8lYxBKIicwXR3ysQOYxbLfYKRfaLWiFLnR0c8,3239
|
|
3
|
-
mm_balance/cli.py,sha256=6qif4QExl4vZDYrasc2MAizmwe1xtvsncbxEdJkqK-M,2709
|
|
4
|
-
mm_balance/command_runner.py,sha256=chA43ZrWBrSKnL7VBuc_gIFs3zgmWm2XK66Vqz_3QKs,2875
|
|
5
|
-
mm_balance/config.py,sha256=r_tFtgRv17SJXoCKpiTLCk3ukcONG-TDFdo88vjdE9E,4970
|
|
6
|
-
mm_balance/constants.py,sha256=LhyW1EfNP5E8st-RPvD7p3QoIowFH2W14tkCf6NzveU,2468
|
|
7
|
-
mm_balance/diff.py,sha256=1NLzfzgOR34FTUFulWjOhUxNsMjt3W5u_7106FLBuaY,7172
|
|
8
|
-
mm_balance/price.py,sha256=s8B9TgbwAyB5x1bgOTbcftivCvCr40UmFmoOaXjBKyQ,1588
|
|
9
|
-
mm_balance/result.py,sha256=vBz_V-ZCkEhrf-Utc7VB_3eP0cuL9pAmEkVJbOpdKIc,5173
|
|
10
|
-
mm_balance/rpc.py,sha256=nNJoXIBen4xe0WS6nFYvL_7VyRzNe7we__swj6xN-bo,6126
|
|
11
|
-
mm_balance/token_decimals.py,sha256=leLKHJDmQzt8gnLvkARN96pYoEnus20_WNBPKDXDpKw,2067
|
|
12
|
-
mm_balance/utils.py,sha256=9vsm84f5MUp0s3Auy3JAHJ7saSWlLIlXkwoxqsuZrPU,765
|
|
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=uNs2P1J6QhxCsHo8TOqG6aQbDfBkVjIwYyo96d2K17Q,910
|
|
18
|
-
mm_balance/output/formats/table_format.py,sha256=sXVoKqDoVQXzt_iNyxfcQwG5lH-l8Ko5bm4_OZGKUfg,4788
|
|
19
|
-
mm_balance-0.5.0.dist-info/METADATA,sha256=Pjj-dEDVcj_fe1vklSG_ALmxFv44ngOpGpoJjF0ZWxY,262
|
|
20
|
-
mm_balance-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
21
|
-
mm_balance-0.5.0.dist-info/entry_points.txt,sha256=rSnP0ZW1a3ACNwTWM7T53CmOycKbzhG43m2_wseENng,50
|
|
22
|
-
mm_balance-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|