mm-sol 0.2.5__py3-none-any.whl → 0.2.7__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_sol/account.py +9 -7
- mm_sol/balance.py +83 -6
- mm_sol/cli/calcs.py +101 -0
- mm_sol/cli/cli.py +37 -7
- mm_sol/cli/cli_utils.py +28 -16
- mm_sol/cli/cmd/balance_cmd.py +5 -3
- mm_sol/cli/cmd/balances_cmd.py +36 -26
- mm_sol/cli/cmd/node_cmd.py +2 -2
- mm_sol/cli/cmd/transfer_sol_cmd.py +185 -30
- mm_sol/cli/cmd/transfer_token_cmd.py +135 -0
- mm_sol/cli/cmd/wallet/keypair_cmd.py +2 -2
- mm_sol/cli/cmd/wallet/new_cmd.py +2 -2
- mm_sol/cli/examples/transfer-sol.yml +9 -6
- mm_sol/cli/validators.py +17 -0
- mm_sol/converters.py +33 -0
- mm_sol/solana_cli.py +0 -2
- mm_sol/token.py +12 -112
- mm_sol/transfer.py +140 -41
- mm_sol/utils.py +7 -26
- {mm_sol-0.2.5.dist-info → mm_sol-0.2.7.dist-info}/METADATA +4 -4
- mm_sol-0.2.7.dist-info/RECORD +32 -0
- mm_sol/types.py +0 -4
- mm_sol-0.2.5.dist-info/RECORD +0 -29
- {mm_sol-0.2.5.dist-info → mm_sol-0.2.7.dist-info}/WHEEL +0 -0
- {mm_sol-0.2.5.dist-info → mm_sol-0.2.7.dist-info}/entry_points.txt +0 -0
mm_sol/account.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import random
|
|
2
3
|
|
|
3
4
|
import base58
|
|
@@ -35,8 +36,10 @@ def get_keypair(private_key: str | list[int]) -> Keypair:
|
|
|
35
36
|
return Keypair.from_bytes(private_key_)
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
def check_private_key(
|
|
39
|
-
|
|
39
|
+
def check_private_key(public_key: str | Pubkey, private_key: str | list[int]) -> bool:
|
|
40
|
+
if isinstance(public_key, str):
|
|
41
|
+
public_key = Pubkey.from_string(public_key)
|
|
42
|
+
return get_keypair(private_key).pubkey() == public_key
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
def get_public_key(private_key: str) -> str:
|
|
@@ -82,9 +85,8 @@ def is_empty_account(*, address: str, node: str | None = None, nodes: list[str]
|
|
|
82
85
|
return Err(error or "unknown response", data=data)
|
|
83
86
|
|
|
84
87
|
|
|
85
|
-
def
|
|
86
|
-
|
|
88
|
+
def is_address(pubkey: str) -> bool:
|
|
89
|
+
with contextlib.suppress(Exception):
|
|
87
90
|
Pubkey.from_string(pubkey)
|
|
88
|
-
return True
|
|
89
|
-
|
|
90
|
-
return False
|
|
91
|
+
return True
|
|
92
|
+
return False
|
mm_sol/balance.py
CHANGED
|
@@ -1,18 +1,95 @@
|
|
|
1
|
-
|
|
1
|
+
import httpx
|
|
2
|
+
from mm_crypto_utils import Nodes, Proxies, random_node, random_proxy
|
|
3
|
+
from mm_std import Err, Ok, Result
|
|
4
|
+
from solana.exceptions import SolanaRpcException
|
|
5
|
+
from solana.rpc.types import TokenAccountOpts
|
|
6
|
+
from solders.pubkey import Pubkey
|
|
2
7
|
|
|
3
8
|
from mm_sol import rpc
|
|
4
|
-
from mm_sol.
|
|
5
|
-
from mm_sol.utils import get_node, get_proxy
|
|
9
|
+
from mm_sol.utils import get_client
|
|
6
10
|
|
|
7
11
|
|
|
8
|
-
def
|
|
12
|
+
def get_sol_balance(node: str, address: str, timeout: int = 10, proxy: str | None = None) -> Result[int]:
|
|
9
13
|
return rpc.get_balance(node, address, timeout, proxy)
|
|
10
14
|
|
|
11
15
|
|
|
12
|
-
def
|
|
16
|
+
def get_sol_balance_with_retries(
|
|
17
|
+
nodes: Nodes, address: str, retries: int, timeout: int = 10, proxies: Proxies = None
|
|
18
|
+
) -> Result[int]:
|
|
13
19
|
res: Result[int] = Err("not started yet")
|
|
14
20
|
for _ in range(retries):
|
|
15
|
-
res =
|
|
21
|
+
res = get_sol_balance(random_node(nodes), address, timeout=timeout, proxy=random_proxy(proxies))
|
|
22
|
+
if res.is_ok():
|
|
23
|
+
return res
|
|
24
|
+
return res
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_token_balance(
|
|
28
|
+
node: str,
|
|
29
|
+
owner_address: str,
|
|
30
|
+
token_mint_address: str,
|
|
31
|
+
token_account: str | None = None,
|
|
32
|
+
timeout: float = 10,
|
|
33
|
+
proxy: str | None = None,
|
|
34
|
+
no_token_accounts_return_zero: bool = True,
|
|
35
|
+
) -> Result[int]:
|
|
36
|
+
data: list[object] = []
|
|
37
|
+
try:
|
|
38
|
+
client = get_client(node, proxy=proxy, timeout=timeout)
|
|
39
|
+
if token_account:
|
|
40
|
+
res_balance = client.get_token_account_balance(Pubkey.from_string(token_account))
|
|
41
|
+
data.append(res_balance)
|
|
42
|
+
return Ok(int(res_balance.value.amount))
|
|
43
|
+
|
|
44
|
+
res_accounts = client.get_token_accounts_by_owner(
|
|
45
|
+
Pubkey.from_string(owner_address),
|
|
46
|
+
TokenAccountOpts(mint=Pubkey.from_string(token_mint_address)),
|
|
47
|
+
)
|
|
48
|
+
data.append(res_accounts)
|
|
49
|
+
|
|
50
|
+
if no_token_accounts_return_zero and not res_accounts.value:
|
|
51
|
+
return Ok(0)
|
|
52
|
+
if not res_accounts.value:
|
|
53
|
+
return Err("no_token_accounts")
|
|
54
|
+
|
|
55
|
+
token_accounts = [a.pubkey for a in res_accounts.value]
|
|
56
|
+
balances = []
|
|
57
|
+
for token_account_ in token_accounts:
|
|
58
|
+
res = client.get_token_account_balance(token_account_)
|
|
59
|
+
data.append(res)
|
|
60
|
+
if res.value: # type:ignore[truthy-bool]
|
|
61
|
+
balances.append(int(res.value.amount))
|
|
62
|
+
|
|
63
|
+
return Ok(sum(balances))
|
|
64
|
+
except httpx.HTTPStatusError as e:
|
|
65
|
+
return Err(f"http error: {e}", data=data)
|
|
66
|
+
except SolanaRpcException as e:
|
|
67
|
+
return Err(e.error_msg, data=data)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
return Err(e, data=data)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_token_balance_with_retries(
|
|
73
|
+
nodes: Nodes,
|
|
74
|
+
owner_address: str,
|
|
75
|
+
token_mint_address: str,
|
|
76
|
+
retries: int,
|
|
77
|
+
token_account: str | None = None,
|
|
78
|
+
timeout: float = 10,
|
|
79
|
+
proxies: Proxies = None,
|
|
80
|
+
no_token_accounts_return_zero: bool = True,
|
|
81
|
+
) -> Result[int]:
|
|
82
|
+
res: Result[int] = Err("not started yet")
|
|
83
|
+
for _ in range(retries):
|
|
84
|
+
res = get_token_balance(
|
|
85
|
+
random_node(nodes),
|
|
86
|
+
owner_address,
|
|
87
|
+
token_mint_address,
|
|
88
|
+
token_account,
|
|
89
|
+
timeout=timeout,
|
|
90
|
+
proxy=random_proxy(proxies),
|
|
91
|
+
no_token_accounts_return_zero=no_token_accounts_return_zero,
|
|
92
|
+
)
|
|
16
93
|
if res.is_ok():
|
|
17
94
|
return res
|
|
18
95
|
return res
|
mm_sol/cli/calcs.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
import mm_crypto_utils
|
|
5
|
+
from loguru import logger
|
|
6
|
+
from mm_crypto_utils import Nodes, Proxies
|
|
7
|
+
from mm_std import Ok, Result
|
|
8
|
+
from mm_std.str import split_on_plus_minus_tokens
|
|
9
|
+
|
|
10
|
+
from mm_sol.balance import get_sol_balance_with_retries, get_token_balance_with_retries
|
|
11
|
+
from mm_sol.converters import lamports_to_sol, sol_to_lamports, to_lamports
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def calc_var_value(value: str, *, var_name: str = "var", var_value: int | None = None, decimals: int | None = None) -> int:
|
|
15
|
+
if not isinstance(value, str):
|
|
16
|
+
raise TypeError(f"value is not str: {value}")
|
|
17
|
+
try:
|
|
18
|
+
var_name = var_name.lower()
|
|
19
|
+
result = 0
|
|
20
|
+
for token in split_on_plus_minus_tokens(value.lower()):
|
|
21
|
+
operator = token[0]
|
|
22
|
+
item = token[1:]
|
|
23
|
+
if item.isdigit():
|
|
24
|
+
item_value = int(item)
|
|
25
|
+
elif item.endswith("sol"):
|
|
26
|
+
item = item.removesuffix("sol")
|
|
27
|
+
item_value = sol_to_lamports(Decimal(item))
|
|
28
|
+
elif item.endswith("t"):
|
|
29
|
+
if decimals is None:
|
|
30
|
+
raise ValueError("t without decimals") # noqa: TRY301
|
|
31
|
+
item = item.removesuffix("t")
|
|
32
|
+
item_value = int(Decimal(item) * 10**decimals)
|
|
33
|
+
elif item.endswith(var_name):
|
|
34
|
+
if var_value is None:
|
|
35
|
+
raise ValueError("base value is not set") # noqa: TRY301
|
|
36
|
+
item = item.removesuffix(var_name)
|
|
37
|
+
k = Decimal(item) if item else Decimal(1)
|
|
38
|
+
item_value = int(k * var_value)
|
|
39
|
+
elif item.startswith("random(") and item.endswith(")"):
|
|
40
|
+
item = item.lstrip("random(").rstrip(")")
|
|
41
|
+
arr = item.split(",")
|
|
42
|
+
if len(arr) != 2:
|
|
43
|
+
raise ValueError(f"wrong value, random part: {value}") # noqa: TRY301
|
|
44
|
+
from_value = to_lamports(arr[0], decimals=decimals)
|
|
45
|
+
to_value = to_lamports(arr[1], decimals=decimals)
|
|
46
|
+
if from_value > to_value:
|
|
47
|
+
raise ValueError(f"wrong value, random part: {value}") # noqa: TRY301
|
|
48
|
+
item_value = random.randint(from_value, to_value)
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError(f"wrong value: {value}") # noqa: TRY301
|
|
51
|
+
|
|
52
|
+
if operator == "+":
|
|
53
|
+
result += item_value
|
|
54
|
+
if operator == "-":
|
|
55
|
+
result -= item_value
|
|
56
|
+
|
|
57
|
+
return result # noqa: TRY300
|
|
58
|
+
except Exception as err:
|
|
59
|
+
raise ValueError(f"wrong value: {value}, error={err}") from err
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_sol_value_less_min_limit(value_min_limit: str | None, value: int, log_prefix: str | None = None) -> bool:
|
|
63
|
+
if value_min_limit is None:
|
|
64
|
+
return False
|
|
65
|
+
if value < calc_var_value(value_min_limit):
|
|
66
|
+
prefix = mm_crypto_utils.get_log_prefix(log_prefix)
|
|
67
|
+
logger.info("{}value is less min limit, value={}", prefix, lamports_to_sol(value))
|
|
68
|
+
return True
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def calc_sol_value(*, nodes: Nodes, value_str: str, address: str, proxies: Proxies, fee: int = 5000) -> Result[int]:
|
|
73
|
+
balance_value = None
|
|
74
|
+
if "balance" in value_str.lower():
|
|
75
|
+
res = get_sol_balance_with_retries(nodes, address, proxies=proxies, retries=5)
|
|
76
|
+
if res.is_err():
|
|
77
|
+
return res
|
|
78
|
+
balance_value = res.ok
|
|
79
|
+
value = calc_var_value(value_str, var_name="balance", var_value=balance_value)
|
|
80
|
+
if "balance" in value_str.lower():
|
|
81
|
+
value = value - fee
|
|
82
|
+
return Ok(value)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def calc_token_value(
|
|
86
|
+
*, nodes: Nodes, value_str: str, wallet_address: str, token_mint_address: str, token_decimals: int, proxies: Proxies
|
|
87
|
+
) -> Result[int]:
|
|
88
|
+
balance_value = None
|
|
89
|
+
if "balance" in value_str.lower():
|
|
90
|
+
res = get_token_balance_with_retries(
|
|
91
|
+
nodes=nodes,
|
|
92
|
+
owner_address=wallet_address,
|
|
93
|
+
token_mint_address=token_mint_address,
|
|
94
|
+
proxies=proxies,
|
|
95
|
+
retries=5,
|
|
96
|
+
)
|
|
97
|
+
if res.is_err():
|
|
98
|
+
return res
|
|
99
|
+
balance_value = res.ok
|
|
100
|
+
value = calc_var_value(value_str, var_name="balance", var_value=balance_value, decimals=token_decimals)
|
|
101
|
+
return Ok(value)
|
mm_sol/cli/cli.py
CHANGED
|
@@ -4,7 +4,8 @@ from typing import Annotated
|
|
|
4
4
|
import typer
|
|
5
5
|
from mm_std import print_plain
|
|
6
6
|
|
|
7
|
-
from .
|
|
7
|
+
from . import cli_utils
|
|
8
|
+
from .cmd import balance_cmd, balances_cmd, example_cmd, node_cmd, transfer_sol_cmd, transfer_token_cmd
|
|
8
9
|
from .cmd.wallet import keypair_cmd, new_cmd
|
|
9
10
|
|
|
10
11
|
app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
|
|
@@ -16,9 +17,7 @@ app.add_typer(wallet_app, name="w", hidden=True)
|
|
|
16
17
|
|
|
17
18
|
def version_callback(value: bool) -> None:
|
|
18
19
|
if value:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
print_plain(f"mm-sol: {importlib.metadata.version('mm-sol')}")
|
|
20
|
+
print_plain(f"mm-sol: {cli_utils.get_version()}")
|
|
22
21
|
raise typer.Exit
|
|
23
22
|
|
|
24
23
|
|
|
@@ -55,11 +54,42 @@ def balances_command(
|
|
|
55
54
|
balances_cmd.run(config_path, print_config)
|
|
56
55
|
|
|
57
56
|
|
|
58
|
-
@app.command(name="transfer-sol", help="Transfer SOL")
|
|
57
|
+
@app.command(name="transfer-sol", help="Transfer SOL from one or many accounts")
|
|
59
58
|
def transfer_sol_command(
|
|
60
|
-
config_path: str,
|
|
59
|
+
config_path: str,
|
|
60
|
+
print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
|
|
61
|
+
print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
62
|
+
emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
|
|
63
|
+
no_confirmation: bool = typer.Option(False, "--no-confirmation", "-nc", help="Do not wait for confirmation"),
|
|
64
|
+
debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
|
|
65
|
+
) -> None:
|
|
66
|
+
transfer_sol_cmd.run(
|
|
67
|
+
config_path,
|
|
68
|
+
print_balances=print_balances,
|
|
69
|
+
print_config=print_config,
|
|
70
|
+
debug=debug,
|
|
71
|
+
no_confirmation=no_confirmation,
|
|
72
|
+
emulate=emulate,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@app.command(name="transfer-token", help="Transfer token from one or many accounts")
|
|
77
|
+
def transfer_token_command(
|
|
78
|
+
config_path: str,
|
|
79
|
+
print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
|
|
80
|
+
print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
|
|
81
|
+
emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
|
|
82
|
+
no_confirmation: bool = typer.Option(False, "--no-confirmation", "-nc", help="Do not wait for confirmation"),
|
|
83
|
+
debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
|
|
61
84
|
) -> None:
|
|
62
|
-
|
|
85
|
+
transfer_token_cmd.run(
|
|
86
|
+
config_path,
|
|
87
|
+
print_balances=print_balances,
|
|
88
|
+
print_config=print_config,
|
|
89
|
+
debug=debug,
|
|
90
|
+
no_confirmation=no_confirmation,
|
|
91
|
+
emulate=emulate,
|
|
92
|
+
)
|
|
63
93
|
|
|
64
94
|
|
|
65
95
|
@app.command(name="node", help="Check RPC urls")
|
mm_sol/cli/cli_utils.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import importlib.metadata
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from
|
|
3
|
+
from mm_crypto_utils import Proxies
|
|
4
|
+
from rich.live import Live
|
|
5
|
+
from rich.table import Table
|
|
5
6
|
|
|
7
|
+
from mm_sol.balance import get_sol_balance_with_retries
|
|
8
|
+
from mm_sol.converters import lamports_to_sol
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
sys.exit(0)
|
|
10
|
+
|
|
11
|
+
def get_version() -> str:
|
|
12
|
+
return importlib.metadata.version("mm-sol")
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
def public_rpc_url(url: str | None) -> str:
|
|
@@ -25,12 +27,22 @@ def public_rpc_url(url: str | None) -> str:
|
|
|
25
27
|
return url
|
|
26
28
|
|
|
27
29
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
def print_balances(
|
|
31
|
+
rpc_nodes: list[str],
|
|
32
|
+
addresses: list[str],
|
|
33
|
+
*,
|
|
34
|
+
proxies: Proxies = None,
|
|
35
|
+
round_ndigits: int = 5,
|
|
36
|
+
) -> None:
|
|
37
|
+
table = Table(title="balances")
|
|
38
|
+
table.add_column("n")
|
|
39
|
+
table.add_column("address")
|
|
40
|
+
table.add_column("balance, sol")
|
|
41
|
+
with Live(table, refresh_per_second=0.5):
|
|
42
|
+
for count, address in enumerate(addresses):
|
|
43
|
+
balance = get_sol_balance_with_retries(rpc_nodes, address, proxies=proxies, retries=5).map_or_else(
|
|
44
|
+
lambda err: err,
|
|
45
|
+
lambda ok: str(lamports_to_sol(ok, round_ndigits)),
|
|
46
|
+
)
|
|
47
|
+
row: list[str] = [str(count), address, balance]
|
|
48
|
+
table.add_row(*row)
|
mm_sol/cli/cmd/balance_cmd.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
2
|
|
|
3
|
+
import mm_crypto_utils
|
|
3
4
|
from mm_std import Ok, print_json
|
|
4
5
|
from pydantic import BaseModel, Field
|
|
5
6
|
|
|
7
|
+
import mm_sol.balance
|
|
6
8
|
from mm_sol import balance, token
|
|
7
9
|
from mm_sol.cli import cli_utils
|
|
8
10
|
|
|
@@ -40,10 +42,10 @@ def run(
|
|
|
40
42
|
result = BalanceResult()
|
|
41
43
|
|
|
42
44
|
rpc_url = cli_utils.public_rpc_url(rpc_url)
|
|
43
|
-
proxies =
|
|
45
|
+
proxies = mm_crypto_utils.fetch_proxies_or_fatal(proxies_url) if proxies_url else None
|
|
44
46
|
|
|
45
47
|
# sol balance
|
|
46
|
-
sol_balance_res = balance.
|
|
48
|
+
sol_balance_res = balance.get_sol_balance_with_retries(rpc_url, wallet_address, retries=3, proxies=proxies)
|
|
47
49
|
if isinstance(sol_balance_res, Ok):
|
|
48
50
|
result.sol_balance = sol_balance_res.ok
|
|
49
51
|
else:
|
|
@@ -51,7 +53,7 @@ def run(
|
|
|
51
53
|
|
|
52
54
|
# token balance
|
|
53
55
|
if token_address:
|
|
54
|
-
token_balance_res =
|
|
56
|
+
token_balance_res = mm_sol.balance.get_token_balance_with_retries(
|
|
55
57
|
nodes=rpc_url,
|
|
56
58
|
owner_address=wallet_address,
|
|
57
59
|
token_mint_address=token_address,
|
mm_sol/cli/cmd/balances_cmd.py
CHANGED
|
@@ -1,62 +1,72 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import random
|
|
2
3
|
from decimal import Decimal
|
|
3
|
-
from typing import Any
|
|
4
|
+
from typing import Annotated, Any, Self
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
from
|
|
6
|
+
import mm_crypto_utils
|
|
7
|
+
from mm_crypto_utils import ConfigValidators
|
|
8
|
+
from mm_std import BaseConfig, print_json
|
|
9
|
+
from pydantic import BeforeValidator, Field, model_validator
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
from mm_sol
|
|
10
|
-
from mm_sol.
|
|
11
|
+
import mm_sol.converters
|
|
12
|
+
from mm_sol import balance
|
|
13
|
+
from mm_sol.account import is_address
|
|
14
|
+
from mm_sol.balance import get_token_balance_with_retries
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
class Config(BaseConfig):
|
|
14
|
-
accounts: list[
|
|
15
|
-
nodes: list[
|
|
16
|
-
tokens: list[
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def to_list_validator(cls, v: list[str] | str | None) -> list[str]:
|
|
20
|
-
return str_to_list(v)
|
|
18
|
+
accounts: Annotated[list[str], BeforeValidator(ConfigValidators.addresses(unique=True, is_address=is_address))]
|
|
19
|
+
nodes: Annotated[list[str], BeforeValidator(ConfigValidators.nodes())]
|
|
20
|
+
tokens: Annotated[list[str], BeforeValidator(ConfigValidators.addresses(unique=True, is_address=is_address))]
|
|
21
|
+
proxies_url: str | None = None
|
|
22
|
+
proxies: list[str] = Field(default_factory=list)
|
|
21
23
|
|
|
22
24
|
@property
|
|
23
25
|
def random_node(self) -> str:
|
|
24
26
|
return random.choice(self.nodes)
|
|
25
27
|
|
|
28
|
+
@model_validator(mode="after")
|
|
29
|
+
def final_validator(self) -> Self:
|
|
30
|
+
# fetch proxies from proxies_url
|
|
31
|
+
proxies_url = self.proxies_url or os.getenv("MM_SOL_PROXIES_URL", "")
|
|
32
|
+
if proxies_url:
|
|
33
|
+
self.proxies += mm_crypto_utils.fetch_proxies_or_fatal(proxies_url)
|
|
34
|
+
|
|
35
|
+
return self
|
|
36
|
+
|
|
26
37
|
|
|
27
38
|
def run(config_path: str, print_config: bool) -> None:
|
|
28
39
|
config = Config.read_config_or_exit(config_path)
|
|
29
|
-
|
|
40
|
+
if print_config:
|
|
41
|
+
config.print_and_exit()
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
# print_config_and_exit(ctx, config)
|
|
33
|
-
result: dict[str, Any] = {"sol": _get_sol_balances(config.accounts, config.nodes)}
|
|
43
|
+
result: dict[str, Any] = {"sol": _get_sol_balances(config.accounts, config)}
|
|
34
44
|
result["sol_sum"] = sum([v for v in result["sol"].values() if v is not None])
|
|
35
45
|
|
|
36
46
|
if config.tokens:
|
|
37
47
|
for token in config.tokens:
|
|
38
|
-
result[token] = _get_token_balances(token, config.accounts, config
|
|
48
|
+
result[token] = _get_token_balances(token, config.accounts, config)
|
|
39
49
|
result[token + "_sum"] = sum([v for v in result[token].values() if v is not None])
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
print_json(result)
|
|
42
52
|
|
|
43
53
|
|
|
44
|
-
def _get_token_balances(token: str, accounts: list[str],
|
|
54
|
+
def _get_token_balances(token: str, accounts: list[str], config: Config) -> dict[str, int | None]:
|
|
45
55
|
result = {}
|
|
46
56
|
for account in accounts:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
nodes=nodes,
|
|
57
|
+
result[account] = get_token_balance_with_retries(
|
|
58
|
+
nodes=config.nodes,
|
|
50
59
|
owner_address=account,
|
|
51
60
|
token_mint_address=token,
|
|
52
61
|
retries=3,
|
|
62
|
+
proxies=config.proxies,
|
|
53
63
|
).ok_or_none()
|
|
54
64
|
return result
|
|
55
65
|
|
|
56
66
|
|
|
57
|
-
def _get_sol_balances(accounts: list[str],
|
|
67
|
+
def _get_sol_balances(accounts: list[str], config: Config) -> dict[str, Decimal | None]:
|
|
58
68
|
result = {}
|
|
59
69
|
for account in accounts:
|
|
60
|
-
res = balance.
|
|
61
|
-
result[account] =
|
|
70
|
+
res = balance.get_sol_balance_with_retries(nodes=config.nodes, address=account, retries=3, proxies=config.proxies)
|
|
71
|
+
result[account] = mm_sol.converters.lamports_to_sol(res.unwrap(), ndigits=2) if res.is_ok() else None
|
|
62
72
|
return result
|
mm_sol/cli/cmd/node_cmd.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from mm_std import
|
|
1
|
+
from mm_std import print_json
|
|
2
2
|
|
|
3
3
|
from mm_sol import rpc
|
|
4
4
|
|
|
@@ -6,4 +6,4 @@ from mm_sol import rpc
|
|
|
6
6
|
def run(urls: list[str], proxy: str | None) -> None:
|
|
7
7
|
for url in urls:
|
|
8
8
|
res = rpc.get_block_height(url, proxy=proxy, timeout=10)
|
|
9
|
-
|
|
9
|
+
print_json({url: res.ok_or_err()})
|