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