mm-sol 0.6.3__tar.gz → 0.7.1__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_sol-0.7.1/PKG-INFO +11 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/justfile +4 -2
- {mm_sol-0.6.3 → mm_sol-0.7.1}/pyproject.toml +11 -12
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/calcs.py +14 -14
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cli.py +2 -2
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cli_utils.py +6 -7
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cmd/balance_cmd.py +8 -8
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cmd/balances_cmd.py +5 -5
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cmd/example_cmd.py +2 -2
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cmd/node_cmd.py +2 -2
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cmd/transfer_cmd.py +15 -11
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cmd/wallet/keypair_cmd.py +2 -2
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cmd/wallet/mnemonic_cmd.py +2 -2
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/validators.py +8 -6
- mm_sol-0.7.1/src/mm_sol/constants.py +1 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/retry.py +2 -2
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/rpc.py +7 -6
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/rpc_sync.py +7 -6
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/spl_token.py +1 -1
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/transfer.py +1 -1
- mm_sol-0.7.1/tests/conftest.py +125 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/uv.lock +145 -350
- mm_sol-0.6.3/PKG-INFO +0 -13
- mm_sol-0.6.3/src/mm_sol/constants.py +0 -1
- mm_sol-0.6.3/tests/conftest.py +0 -91
- {mm_sol-0.6.3 → mm_sol-0.7.1}/.env.example +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/.gitignore +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/README.md +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/dict.dic +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/__init__.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/account.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/__init__.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cmd/__init__.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/cmd/wallet/__init__.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/examples/balances.toml +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/cli/examples/transfer.toml +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/converters.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/py.typed +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/src/mm_sol/utils.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/__init__.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/cli/__init__.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/cli/cmd/__init__.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/cli/cmd/wallet/__init__.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/cli/cmd/wallet/test_keypair_cmd.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/cli/cmd/wallet/test_mnemonic_cmd.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/test_account.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/test_client.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/test_converters.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/test_rpc.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/test_rpc_sync.py +0 -0
- {mm_sol-0.6.3 → mm_sol-0.7.1}/tests/test_spl_token.py +0 -0
mm_sol-0.7.1/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mm-sol
|
|
3
|
+
Version: 0.7.1
|
|
4
|
+
Requires-Python: >=3.13
|
|
5
|
+
Requires-Dist: base58~=2.1.1
|
|
6
|
+
Requires-Dist: jinja2>=3.1.6
|
|
7
|
+
Requires-Dist: mm-web3~=0.5.1
|
|
8
|
+
Requires-Dist: mnemonic==0.21
|
|
9
|
+
Requires-Dist: socksio>=1.0.0
|
|
10
|
+
Requires-Dist: solana~=0.36.7
|
|
11
|
+
Requires-Dist: typer>=0.16.0
|
|
@@ -23,8 +23,10 @@ lint-fix: format
|
|
|
23
23
|
uv run ruff check --fix src tests
|
|
24
24
|
|
|
25
25
|
audit:
|
|
26
|
-
uv
|
|
27
|
-
uv run
|
|
26
|
+
uv export --no-dev --all-extras --format requirements-txt --no-emit-project > requirements.txt
|
|
27
|
+
uv run pip-audit -r requirements.txt --disable-pip
|
|
28
|
+
rm requirements.txt
|
|
29
|
+
uv run bandit --silent --recursive --configfile "pyproject.toml" src
|
|
28
30
|
|
|
29
31
|
publish: build
|
|
30
32
|
git diff-index --quiet HEAD
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mm-sol"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.1"
|
|
4
4
|
description = ""
|
|
5
|
-
requires-python = ">=3.
|
|
5
|
+
requires-python = ">=3.13"
|
|
6
6
|
dependencies = [
|
|
7
|
-
"mm-
|
|
8
|
-
"solana~=0.36.
|
|
9
|
-
"solders~=0.26.0",
|
|
7
|
+
"mm-web3~=0.5.1",
|
|
8
|
+
"solana~=0.36.7",
|
|
10
9
|
"base58~=2.1.1",
|
|
11
10
|
"mnemonic==0.21",
|
|
12
|
-
"typer>=0.
|
|
11
|
+
"typer>=0.16.0",
|
|
13
12
|
"jinja2>=3.1.6",
|
|
14
13
|
"socksio>=1.0.0",
|
|
15
|
-
"h11>=0.16.0", # secure fix, remove when solana updated
|
|
16
14
|
]
|
|
17
15
|
[project.scripts]
|
|
18
16
|
mm-sol = "mm_sol.cli.cli:app"
|
|
@@ -23,13 +21,14 @@ build-backend = "hatchling.build"
|
|
|
23
21
|
|
|
24
22
|
[tool.uv]
|
|
25
23
|
dev-dependencies = [
|
|
26
|
-
"pytest~=8.
|
|
27
|
-
"pytest-
|
|
28
|
-
"
|
|
24
|
+
"pytest~=8.4.0",
|
|
25
|
+
"pytest-asyncio~=1.0.0",
|
|
26
|
+
"pytest-xdist~=3.7.0",
|
|
27
|
+
"ruff~=0.11.13",
|
|
29
28
|
"pip-audit~=2.9.0",
|
|
30
29
|
"bandit~=1.8.3",
|
|
31
|
-
"mypy~=1.
|
|
32
|
-
"
|
|
30
|
+
"mypy~=1.16.0",
|
|
31
|
+
"python-dotenv>=1.1.0",
|
|
33
32
|
]
|
|
34
33
|
|
|
35
34
|
[tool.mypy]
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import
|
|
2
|
-
from
|
|
3
|
-
from
|
|
1
|
+
from mm_result import Result
|
|
2
|
+
from mm_web3 import Nodes, Proxies
|
|
3
|
+
from mm_web3.calcs import calc_expression_with_vars
|
|
4
4
|
|
|
5
5
|
from mm_sol import retry
|
|
6
|
-
from mm_sol.constants import
|
|
6
|
+
from mm_sol.constants import UNIT_DECIMALS
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def calc_sol_expression(expression: str,
|
|
10
|
-
return
|
|
9
|
+
def calc_sol_expression(expression: str, variables: dict[str, int] | None = None) -> int:
|
|
10
|
+
return calc_expression_with_vars(expression, variables, unit_decimals=UNIT_DECIMALS)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def calc_token_expression(expression: str, token_decimals: int,
|
|
14
|
-
return
|
|
13
|
+
def calc_token_expression(expression: str, token_decimals: int, variables: dict[str, int] | None = None) -> int:
|
|
14
|
+
return calc_expression_with_vars(expression, variables, unit_decimals={"t": token_decimals})
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
async def calc_sol_value_for_address(
|
|
18
18
|
*, nodes: Nodes, value_expression: str, address: str, proxies: Proxies, fee: int
|
|
19
19
|
) -> Result[int]:
|
|
20
20
|
value_expression = value_expression.lower()
|
|
21
|
-
|
|
21
|
+
variables: dict[str, int] | None = None
|
|
22
22
|
if "balance" in value_expression:
|
|
23
23
|
res = await retry.get_sol_balance(5, nodes, proxies, address=address)
|
|
24
24
|
if res.is_err():
|
|
25
25
|
return res
|
|
26
|
-
|
|
26
|
+
variables = {"balance": res.unwrap()}
|
|
27
27
|
|
|
28
|
-
value = calc_sol_expression(value_expression,
|
|
28
|
+
value = calc_sol_expression(value_expression, variables)
|
|
29
29
|
if "balance" in value_expression:
|
|
30
30
|
value = value - fee
|
|
31
31
|
return Result.ok(value)
|
|
@@ -34,12 +34,12 @@ async def calc_sol_value_for_address(
|
|
|
34
34
|
async def calc_token_value_for_address(
|
|
35
35
|
*, nodes: Nodes, value_expression: str, owner: str, token: str, token_decimals: int, proxies: Proxies
|
|
36
36
|
) -> Result[int]:
|
|
37
|
-
|
|
37
|
+
variables: dict[str, int] | None = None
|
|
38
38
|
value_expression = value_expression.lower()
|
|
39
39
|
if "balance" in value_expression:
|
|
40
40
|
res = await retry.get_token_balance(5, nodes, proxies, owner=owner, token=token)
|
|
41
41
|
if res.is_err():
|
|
42
42
|
return res
|
|
43
|
-
|
|
44
|
-
value = calc_token_expression(value_expression, token_decimals,
|
|
43
|
+
variables = {"balance": res.unwrap()}
|
|
44
|
+
value = calc_token_expression(value_expression, token_decimals, variables)
|
|
45
45
|
return Result.ok(value)
|
|
@@ -3,8 +3,8 @@ from enum import Enum
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Annotated
|
|
5
5
|
|
|
6
|
+
import mm_print
|
|
6
7
|
import typer
|
|
7
|
-
from mm_std import print_plain
|
|
8
8
|
|
|
9
9
|
from mm_sol.account import PHANTOM_DERIVATION_PATH
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ app.add_typer(wallet_app, name="w", hidden=True)
|
|
|
24
24
|
|
|
25
25
|
def version_callback(value: bool) -> None:
|
|
26
26
|
if value:
|
|
27
|
-
|
|
27
|
+
mm_print.plain(f"mm-sol: {cli_utils.get_version()}")
|
|
28
28
|
raise typer.Exit
|
|
29
29
|
|
|
30
30
|
|
|
@@ -2,10 +2,9 @@ import importlib.metadata
|
|
|
2
2
|
import time
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import mm_print
|
|
6
6
|
from loguru import logger
|
|
7
|
-
from
|
|
8
|
-
from mm_std import BaseConfig, print_json
|
|
7
|
+
from mm_web3 import Nodes, Proxies, Web3CliConfig, random_node, random_proxy
|
|
9
8
|
from pydantic import BaseModel
|
|
10
9
|
from solders.signature import Signature
|
|
11
10
|
|
|
@@ -21,12 +20,12 @@ class BaseConfigParams(BaseModel):
|
|
|
21
20
|
print_config_and_exit: bool
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
def print_config(config:
|
|
23
|
+
def print_config(config: Web3CliConfig, exclude: set[str] | None = None, count: set[str] | None = None) -> None:
|
|
25
24
|
data = config.model_dump(exclude=exclude)
|
|
26
25
|
if count:
|
|
27
26
|
for k in count:
|
|
28
27
|
data[k] = len(data[k])
|
|
29
|
-
|
|
28
|
+
mm_print.json(data)
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
def public_rpc_url(url: str | None) -> str:
|
|
@@ -48,8 +47,8 @@ def wait_confirmation(nodes: Nodes, proxies: Proxies, signature: Signature, log_
|
|
|
48
47
|
count = 0
|
|
49
48
|
while True:
|
|
50
49
|
try:
|
|
51
|
-
node =
|
|
52
|
-
proxy =
|
|
50
|
+
node = random_node(nodes)
|
|
51
|
+
proxy = random_proxy(proxies)
|
|
53
52
|
client = get_client(node, proxy=proxy)
|
|
54
53
|
res = client.get_transaction(signature)
|
|
55
54
|
if res.value and res.value.slot: # check for tx error
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from
|
|
3
|
+
import mm_print
|
|
4
|
+
from mm_web3 import fetch_proxies
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
7
|
import mm_sol.retry
|
|
@@ -43,14 +43,14 @@ async def run(
|
|
|
43
43
|
|
|
44
44
|
rpc_url = cli_utils.public_rpc_url(rpc_url)
|
|
45
45
|
|
|
46
|
-
proxies = await
|
|
46
|
+
proxies = (await fetch_proxies(proxies_url)).unwrap() if proxies_url else None
|
|
47
47
|
|
|
48
48
|
# sol balance
|
|
49
49
|
sol_balance_res = await retry.get_sol_balance(3, rpc_url, proxies, address=wallet_address)
|
|
50
50
|
if sol_balance_res.is_ok():
|
|
51
51
|
result.sol_balance = sol_balance_res.unwrap()
|
|
52
52
|
else:
|
|
53
|
-
result.errors.append("sol_balance: " + sol_balance_res.
|
|
53
|
+
result.errors.append("sol_balance: " + sol_balance_res.unwrap_err())
|
|
54
54
|
|
|
55
55
|
# token balance
|
|
56
56
|
if token_address:
|
|
@@ -59,15 +59,15 @@ async def run(
|
|
|
59
59
|
if token_balance_res.is_ok():
|
|
60
60
|
result.token_balance = token_balance_res.unwrap()
|
|
61
61
|
else:
|
|
62
|
-
result.errors.append("token_balance: " + token_balance_res.
|
|
62
|
+
result.errors.append("token_balance: " + token_balance_res.unwrap_err())
|
|
63
63
|
|
|
64
64
|
decimals_res = await mm_sol.retry.get_token_decimals(3, rpc_url, proxies, token=token_address)
|
|
65
65
|
if decimals_res.is_ok():
|
|
66
66
|
result.token_decimals = decimals_res.unwrap()
|
|
67
67
|
else:
|
|
68
|
-
result.errors.append("token_decimals: " + decimals_res.
|
|
68
|
+
result.errors.append("token_decimals: " + decimals_res.unwrap_err())
|
|
69
69
|
|
|
70
70
|
if lamport:
|
|
71
|
-
|
|
71
|
+
mm_print.json(result)
|
|
72
72
|
else:
|
|
73
|
-
|
|
73
|
+
mm_print.json(result.to_human_readable())
|
|
@@ -3,8 +3,8 @@ from decimal import Decimal
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Annotated, Any
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
from
|
|
6
|
+
import mm_print
|
|
7
|
+
from mm_web3 import ConfigValidators, Web3CliConfig
|
|
8
8
|
from pydantic import BeforeValidator, Field
|
|
9
9
|
|
|
10
10
|
import mm_sol.retry
|
|
@@ -12,7 +12,7 @@ from mm_sol import converters, retry
|
|
|
12
12
|
from mm_sol.cli.validators import Validators
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class Config(
|
|
15
|
+
class Config(Web3CliConfig):
|
|
16
16
|
accounts: Annotated[list[str], BeforeValidator(Validators.sol_addresses(unique=True))]
|
|
17
17
|
tokens: Annotated[list[str], BeforeValidator(Validators.sol_addresses(unique=True))]
|
|
18
18
|
nodes: Annotated[list[str], BeforeValidator(ConfigValidators.nodes())]
|
|
@@ -35,14 +35,14 @@ async def run(config_path: Path, print_config: bool) -> None:
|
|
|
35
35
|
for token_address in config.tokens:
|
|
36
36
|
res = await mm_sol.retry.get_token_decimals(3, config.nodes, config.proxies, token=token_address)
|
|
37
37
|
if res.is_err():
|
|
38
|
-
fatal(f"Failed to get decimals for token {token_address}: {res.
|
|
38
|
+
mm_print.fatal(f"Failed to get decimals for token {token_address}: {res.unwrap_err()}")
|
|
39
39
|
|
|
40
40
|
token_decimals = res.unwrap()
|
|
41
41
|
result[token_address] = await _get_token_balances(token_address, token_decimals, config.accounts, config)
|
|
42
42
|
result[token_address + "_decimals"] = token_decimals
|
|
43
43
|
result[token_address + "_sum"] = sum([v for v in result[token_address].values() if v is not None])
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
mm_print.json(result)
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
async def _get_token_balances(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import mm_print
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def run(module: str) -> None:
|
|
7
7
|
example_file = Path(Path(__file__).parent.absolute(), "../examples", f"{module}.toml")
|
|
8
|
-
|
|
8
|
+
mm_print.toml(toml=example_file.read_text())
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import mm_print
|
|
2
2
|
|
|
3
3
|
from mm_sol import rpc
|
|
4
4
|
from mm_sol.cli import cli_utils
|
|
@@ -8,4 +8,4 @@ async def run(urls: list[str], proxy: str | None) -> None:
|
|
|
8
8
|
result = {}
|
|
9
9
|
for url in [cli_utils.public_rpc_url(u) for u in urls]:
|
|
10
10
|
result[url] = (await rpc.get_block_height(url, proxy=proxy, timeout=10)).value_or_error()
|
|
11
|
-
|
|
11
|
+
mm_print.json(data=result)
|
|
@@ -3,10 +3,14 @@ import sys
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Annotated
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import mm_print
|
|
7
7
|
from loguru import logger
|
|
8
|
-
from
|
|
9
|
-
from
|
|
8
|
+
from mm_std import utc_now
|
|
9
|
+
from mm_web3 import Web3CliConfig
|
|
10
|
+
from mm_web3.account import PrivateKeyMap
|
|
11
|
+
from mm_web3.calcs import calc_decimal_expression
|
|
12
|
+
from mm_web3.log import init_loguru
|
|
13
|
+
from mm_web3.validators import Transfer
|
|
10
14
|
from pydantic import AfterValidator, BeforeValidator, Field, model_validator
|
|
11
15
|
from rich.console import Console
|
|
12
16
|
from rich.live import Live
|
|
@@ -21,16 +25,16 @@ from mm_sol.cli.validators import Validators
|
|
|
21
25
|
from mm_sol.converters import lamports_to_sol, to_token
|
|
22
26
|
|
|
23
27
|
|
|
24
|
-
class Config(
|
|
28
|
+
class Config(Web3CliConfig):
|
|
25
29
|
nodes: Annotated[list[str], BeforeValidator(Validators.nodes())]
|
|
26
30
|
transfers: Annotated[list[Transfer], BeforeValidator(Validators.sol_transfers())]
|
|
27
|
-
private_keys: Annotated[
|
|
31
|
+
private_keys: Annotated[PrivateKeyMap, BeforeValidator(Validators.sol_private_keys())]
|
|
28
32
|
proxies: Annotated[list[str], Field(default_factory=list), BeforeValidator(Validators.proxies())]
|
|
29
33
|
token: Annotated[str | None, AfterValidator(Validators.sol_address())] = None
|
|
30
34
|
token_decimals: int = -1
|
|
31
35
|
default_value: Annotated[str | None, AfterValidator(Validators.valid_sol_or_token_expression("balance"))] = None
|
|
32
36
|
value_min_limit: Annotated[str | None, AfterValidator(Validators.valid_sol_or_token_expression())] = None
|
|
33
|
-
delay: Annotated[str | None, AfterValidator(Validators.
|
|
37
|
+
delay: Annotated[str | None, AfterValidator(Validators.decimal_expression())] = None # in seconds
|
|
34
38
|
round_ndigits: int = 5
|
|
35
39
|
log_debug: Annotated[Path | None, BeforeValidator(Validators.log_file())] = None
|
|
36
40
|
log_info: Annotated[Path | None, BeforeValidator(Validators.log_file())] = None
|
|
@@ -65,7 +69,7 @@ class Config(BaseConfig):
|
|
|
65
69
|
if self.token:
|
|
66
70
|
res = await mm_sol.retry.get_token_decimals(3, self.nodes, self.proxies, token=self.token)
|
|
67
71
|
if res.is_err():
|
|
68
|
-
fatal(f"can't get decimals for token={self.token}, error={res.
|
|
72
|
+
mm_print.fatal(f"can't get decimals for token={self.token}, error={res.unwrap_err()}")
|
|
69
73
|
self.token_decimals = res.unwrap()
|
|
70
74
|
|
|
71
75
|
return self
|
|
@@ -99,13 +103,13 @@ async def run(cmd_params: TransferCmdParams) -> None:
|
|
|
99
103
|
|
|
100
104
|
|
|
101
105
|
async def _run_transfers(config: Config, cmd_params: TransferCmdParams) -> None:
|
|
102
|
-
|
|
106
|
+
init_loguru(cmd_params.debug, config.log_debug, config.log_info)
|
|
103
107
|
logger.info(f"transfer {cmd_params.config_path}: started at {utc_now()} UTC")
|
|
104
108
|
logger.debug(f"config={config.model_dump(exclude={'private_keys'}) | {'version': cli_utils.get_version()}}")
|
|
105
109
|
for i, route in enumerate(config.transfers):
|
|
106
110
|
await _transfer(route, config, cmd_params)
|
|
107
111
|
if config.delay is not None and i < len(config.transfers) - 1:
|
|
108
|
-
delay_value =
|
|
112
|
+
delay_value = calc_decimal_expression(config.delay)
|
|
109
113
|
logger.info(f"delay {delay_value} seconds")
|
|
110
114
|
if not cmd_params.emulate:
|
|
111
115
|
await asyncio.sleep(float(delay_value))
|
|
@@ -132,7 +136,7 @@ async def _calc_value(transfer: Transfer, config: Config, transfer_sol_fee: int)
|
|
|
132
136
|
)
|
|
133
137
|
logger.debug(f"{transfer.log_prefix}: value={value_res.value_or_error()}")
|
|
134
138
|
if value_res.is_err():
|
|
135
|
-
logger.info(f"{transfer.log_prefix}: calc value error, {value_res.
|
|
139
|
+
logger.info(f"{transfer.log_prefix}: calc value error, {value_res.unwrap_err()}")
|
|
136
140
|
|
|
137
141
|
return value_res.value
|
|
138
142
|
|
|
@@ -181,7 +185,7 @@ async def _send_tx(transfer: Transfer, value: int, config: Config) -> Signature
|
|
|
181
185
|
)
|
|
182
186
|
|
|
183
187
|
if res.is_err():
|
|
184
|
-
logger.info(f"{transfer.log_prefix}: tx error {res.
|
|
188
|
+
logger.info(f"{transfer.log_prefix}: tx error {res.unwrap_err()}")
|
|
185
189
|
return None
|
|
186
190
|
return res.value
|
|
187
191
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import mm_print
|
|
4
4
|
|
|
5
5
|
from mm_sol.account import (
|
|
6
6
|
get_private_key_arr_str,
|
|
@@ -16,4 +16,4 @@ def run(private_key: str) -> None:
|
|
|
16
16
|
public = get_public_key(private_key)
|
|
17
17
|
private_base58 = get_private_key_base58(private_key)
|
|
18
18
|
private_arr = get_private_key_arr_str(private_key)
|
|
19
|
-
|
|
19
|
+
mm_print.json({"public": public, "private_base58": private_base58, "private_arr": private_arr})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from dataclasses import asdict
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import mm_print
|
|
5
5
|
|
|
6
6
|
from mm_sol.account import derive_accounts, generate_mnemonic
|
|
7
7
|
|
|
@@ -16,4 +16,4 @@ def run(mnemonic: str, passphrase: str, words: int, derivation_path: str, limit:
|
|
|
16
16
|
accounts = derive_accounts(mnemonic=mnemonic, passphrase=passphrase, derivation_path=derivation_path, limit=limit)
|
|
17
17
|
|
|
18
18
|
result["accounts"] = [asdict(acc) for acc in accounts]
|
|
19
|
-
|
|
19
|
+
mm_print.json(result)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from mm_web3 import ConfigValidators
|
|
4
|
+
from mm_web3.account import PrivateKeyMap
|
|
5
|
+
from mm_web3.validators import Transfer
|
|
4
6
|
|
|
5
7
|
from mm_sol.account import get_public_key, is_address
|
|
6
|
-
from mm_sol.constants import
|
|
8
|
+
from mm_sol.constants import UNIT_DECIMALS
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class Validators(ConfigValidators):
|
|
@@ -20,17 +22,17 @@ class Validators(ConfigValidators):
|
|
|
20
22
|
return ConfigValidators.transfers(is_address)
|
|
21
23
|
|
|
22
24
|
@staticmethod
|
|
23
|
-
def sol_private_keys() -> Callable[[str],
|
|
25
|
+
def sol_private_keys() -> Callable[[str], PrivateKeyMap]:
|
|
24
26
|
return ConfigValidators.private_keys(get_public_key)
|
|
25
27
|
|
|
26
28
|
@staticmethod
|
|
27
29
|
def valid_sol_expression(var_name: str | None = None) -> Callable[[str], str]:
|
|
28
|
-
return ConfigValidators.
|
|
30
|
+
return ConfigValidators.expression_with_vars(var_name, UNIT_DECIMALS)
|
|
29
31
|
|
|
30
32
|
@staticmethod
|
|
31
33
|
def valid_token_expression(var_name: str | None = None) -> Callable[[str], str]:
|
|
32
|
-
return ConfigValidators.
|
|
34
|
+
return ConfigValidators.expression_with_vars(var_name, {"t": 6})
|
|
33
35
|
|
|
34
36
|
@staticmethod
|
|
35
37
|
def valid_sol_or_token_expression(var_name: str | None = None) -> Callable[[str], str]:
|
|
36
|
-
return ConfigValidators.
|
|
38
|
+
return ConfigValidators.expression_with_vars(var_name, UNIT_DECIMALS | {"t": 6})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
UNIT_DECIMALS = {"sol": 9}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
1
|
+
from mm_result import Result
|
|
2
|
+
from mm_web3 import Nodes, Proxies, retry_with_node_and_proxy
|
|
3
3
|
from solders.solders import Pubkey, Signature
|
|
4
4
|
|
|
5
5
|
from mm_sol import rpc, spl_token, transfer
|
|
@@ -3,7 +3,8 @@ from collections.abc import Sequence
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
import websockets
|
|
6
|
-
from
|
|
6
|
+
from mm_http import http_request
|
|
7
|
+
from mm_result import Result
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
async def rpc_call(
|
|
@@ -23,17 +24,17 @@ async def rpc_call(
|
|
|
23
24
|
async def _http_call(node: str, data: dict[str, object], timeout: float, proxy: str | None) -> Result[Any]:
|
|
24
25
|
res = await http_request(node, method="POST", proxy=proxy, timeout=timeout, json=data)
|
|
25
26
|
if res.is_err():
|
|
26
|
-
return res.
|
|
27
|
+
return res.to_result_err()
|
|
27
28
|
try:
|
|
28
29
|
parsed_body = res.parse_json_body()
|
|
29
30
|
err = parsed_body.get("error", {}).get("message", "")
|
|
30
31
|
if err:
|
|
31
|
-
return res.
|
|
32
|
+
return res.to_result_err(f"service_error: {err}")
|
|
32
33
|
if "result" in parsed_body:
|
|
33
|
-
return res.
|
|
34
|
-
return res.
|
|
34
|
+
return res.to_result_ok(parsed_body["result"])
|
|
35
|
+
return res.to_result_err("unknown_response")
|
|
35
36
|
except Exception as e:
|
|
36
|
-
return res.
|
|
37
|
+
return res.to_result_err(e)
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
async def _ws_call(node: str, data: dict[str, object], timeout: float) -> Result[Any]:
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
3
|
import pydash
|
|
4
|
-
from
|
|
4
|
+
from mm_http import http_request_sync
|
|
5
|
+
from mm_result import Result
|
|
5
6
|
from pydantic import BaseModel, ConfigDict, Field
|
|
6
7
|
|
|
7
8
|
DEFAULT_MAINNET_RPC = "https://api.mainnet-beta.solana.com"
|
|
@@ -92,18 +93,18 @@ def _http_call(node: str, data: dict[str, object], timeout: float, proxy: str |
|
|
|
92
93
|
res = http_request_sync(node, method="POST", proxy=proxy, timeout=timeout, json=data)
|
|
93
94
|
try:
|
|
94
95
|
if res.is_err():
|
|
95
|
-
return res.
|
|
96
|
+
return res.to_result_err()
|
|
96
97
|
|
|
97
98
|
json_body = res.parse_json_body()
|
|
98
99
|
err = pydash.get(json_body, "error.message")
|
|
99
100
|
if err:
|
|
100
|
-
return res.
|
|
101
|
+
return res.to_result_err(f"service_error: {err}")
|
|
101
102
|
if "result" in json_body:
|
|
102
|
-
return res.
|
|
103
|
+
return res.to_result_ok(json_body["result"])
|
|
103
104
|
|
|
104
|
-
return res.
|
|
105
|
+
return res.to_result_err("unknown_response")
|
|
105
106
|
except Exception as e:
|
|
106
|
-
return res.
|
|
107
|
+
return res.to_result_err(e)
|
|
107
108
|
|
|
108
109
|
|
|
109
110
|
def get_balance(node: str, address: str, timeout: float = 5, proxy: str | None = None) -> Result[int]:
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import mm_web3
|
|
4
|
+
import pytest
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
from mm_web3 import fetch_proxies_sync
|
|
7
|
+
from typer.testing import CliRunner
|
|
8
|
+
|
|
9
|
+
load_dotenv()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def anyio_backend():
|
|
14
|
+
return "asyncio"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def devnet_address_1() -> str:
|
|
19
|
+
value = os.getenv("DEVNET_ADDRESS_1")
|
|
20
|
+
if value is None:
|
|
21
|
+
pytest.skip("DEVNET_ADDRESS_1 environment variable not set")
|
|
22
|
+
return value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def devnet_address_2() -> str:
|
|
27
|
+
value = os.getenv("DEVNET_ADDRESS_2")
|
|
28
|
+
if value is None:
|
|
29
|
+
pytest.skip("DEVNET_ADDRESS_2 environment variable not set")
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def devnet_address_3() -> str:
|
|
35
|
+
value = os.getenv("DEVNET_ADDRESS_3")
|
|
36
|
+
if value is None:
|
|
37
|
+
pytest.skip("DEVNET_ADDRESS_3 environment variable not set")
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def devnet_private_1() -> str:
|
|
43
|
+
value = os.getenv("DEVNET_PRIVATE_1")
|
|
44
|
+
if value is None:
|
|
45
|
+
pytest.skip("DEVNET_PRIVATE_1 environment variable not set")
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.fixture
|
|
50
|
+
def devnet_private_2() -> str:
|
|
51
|
+
value = os.getenv("DEVNET_PRIVATE_2")
|
|
52
|
+
if value is None:
|
|
53
|
+
pytest.skip("DEVNET_PRIVATE_2 environment variable not set")
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.fixture
|
|
58
|
+
def devnet_private_3() -> str:
|
|
59
|
+
value = os.getenv("DEVNET_PRIVATE_3")
|
|
60
|
+
if value is None:
|
|
61
|
+
pytest.skip("DEVNET_PRIVATE_3 environment variable not set")
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.fixture
|
|
66
|
+
def mainnet_node() -> str:
|
|
67
|
+
value = os.getenv("MAINNET_NODE")
|
|
68
|
+
if value is None:
|
|
69
|
+
pytest.skip("MAINNET_NODE environment variable not set")
|
|
70
|
+
return value
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@pytest.fixture
|
|
74
|
+
def testnet_node() -> str:
|
|
75
|
+
value = os.getenv("TESTNET_NODE")
|
|
76
|
+
if value is None:
|
|
77
|
+
pytest.skip("TESTNET_NODE environment variable not set")
|
|
78
|
+
return value
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.fixture
|
|
82
|
+
def usdt_token_address() -> str:
|
|
83
|
+
value = os.getenv("USDT_TOKEN_ADDRESS")
|
|
84
|
+
if value is None:
|
|
85
|
+
pytest.skip("USDT_TOKEN_ADDRESS environment variable not set")
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.fixture
|
|
90
|
+
def usdt_owner_address() -> str:
|
|
91
|
+
value = os.getenv("USDT_OWNER_ADDRESS")
|
|
92
|
+
if value is None:
|
|
93
|
+
pytest.skip("USDT_OWNER_ADDRESS environment variable not set")
|
|
94
|
+
return value
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pytest.fixture
|
|
98
|
+
def binance_wallet():
|
|
99
|
+
return "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.fixture
|
|
103
|
+
def proxy() -> str:
|
|
104
|
+
value = os.getenv("PROXY")
|
|
105
|
+
if value is None:
|
|
106
|
+
pytest.skip("PROXY environment variable not set")
|
|
107
|
+
return value
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@pytest.fixture(scope="session")
|
|
111
|
+
def proxies() -> list[str]:
|
|
112
|
+
proxies_url = os.getenv("PROXIES_URL")
|
|
113
|
+
if proxies_url:
|
|
114
|
+
return fetch_proxies_sync(proxies_url).unwrap("Failed to fetch proxies from URL")
|
|
115
|
+
return []
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.fixture
|
|
119
|
+
def random_proxy(proxies) -> str | None:
|
|
120
|
+
return mm_web3.random_proxy(proxies)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@pytest.fixture
|
|
124
|
+
def cli_runner() -> CliRunner:
|
|
125
|
+
return CliRunner()
|