mm-sol 0.6.0__tar.gz → 0.6.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.6.0 → mm_sol-0.6.1}/PKG-INFO +2 -3
- {mm_sol-0.6.0 → mm_sol-0.6.1}/justfile +7 -4
- {mm_sol-0.6.0 → mm_sol-0.6.1}/pyproject.toml +3 -3
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/calcs.py +7 -7
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/balance_cmd.py +5 -6
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/balances_cmd.py +8 -7
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/transfer_cmd.py +12 -12
- mm_sol-0.6.1/src/mm_sol/retry.py +108 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/rpc.py +11 -26
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/rpc_sync.py +9 -26
- mm_sol-0.6.1/src/mm_sol/spl_token.py +48 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/transfer.py +9 -70
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_rpc_sync.py +0 -5
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_spl_token.py +1 -1
- {mm_sol-0.6.0 → mm_sol-0.6.1}/uv.lock +10 -11
- mm_sol-0.6.0/src/mm_sol/spl_token.py +0 -85
- {mm_sol-0.6.0 → mm_sol-0.6.1}/.env.example +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/.gitignore +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/README.md +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/dict.dic +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/__init__.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/account.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/__init__.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cli.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cli_utils.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/__init__.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/example_cmd.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/node_cmd.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/wallet/__init__.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/wallet/keypair_cmd.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/wallet/mnemonic_cmd.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/examples/balances.toml +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/examples/transfer.toml +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/validators.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/constants.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/converters.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/py.typed +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/utils.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/__init__.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/cli/__init__.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/cli/cmd/__init__.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/cli/cmd/wallet/__init__.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/cli/cmd/wallet/test_keypair_cmd.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/cli/cmd/wallet/test_mnemonic_cmd.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/conftest.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_account.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_client.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_converters.py +0 -0
- {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_rpc.py +0 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mm-sol
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Requires-Python: >=3.12
|
|
5
5
|
Requires-Dist: base58~=2.1.1
|
|
6
6
|
Requires-Dist: jinja2>=3.1.6
|
|
7
|
-
Requires-Dist: mm-crypto-utils>=0.3.
|
|
8
|
-
Requires-Dist: mm-std>=0.4.5
|
|
7
|
+
Requires-Dist: mm-crypto-utils>=0.3.4
|
|
9
8
|
Requires-Dist: mnemonic==0.21
|
|
10
9
|
Requires-Dist: socksio>=1.0.0
|
|
11
10
|
Requires-Dist: solana~=0.36.6
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
set dotenv-load
|
|
2
|
-
version := `uv run python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])'`
|
|
1
|
+
set dotenv-load := true
|
|
3
2
|
|
|
3
|
+
version := `uv run python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])'`
|
|
4
4
|
|
|
5
5
|
clean:
|
|
6
6
|
rm -rf .pytest_cache .mypy_cache .ruff_cache .coverage dist build src/*.egg-info
|
|
@@ -19,6 +19,9 @@ lint: format
|
|
|
19
19
|
uv run ruff check src tests
|
|
20
20
|
uv run mypy src
|
|
21
21
|
|
|
22
|
+
lint-fix: format
|
|
23
|
+
uv run ruff check --fix src tests
|
|
24
|
+
|
|
22
25
|
audit:
|
|
23
26
|
uv run pip-audit
|
|
24
27
|
uv run bandit -r -c "pyproject.toml" src
|
|
@@ -26,8 +29,8 @@ audit:
|
|
|
26
29
|
publish: build
|
|
27
30
|
git diff-index --quiet HEAD
|
|
28
31
|
uvx twine upload dist/**
|
|
29
|
-
git tag -a 'v{{version}}' -m 'v{{version}}'
|
|
30
|
-
git push origin v{{version}}
|
|
32
|
+
git tag -a 'v{{ version }}' -m 'v{{ version }}'
|
|
33
|
+
git push origin v{{ version }}
|
|
31
34
|
|
|
32
35
|
sync:
|
|
33
36
|
uv sync
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mm-sol"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.1"
|
|
4
4
|
description = ""
|
|
5
5
|
requires-python = ">=3.12"
|
|
6
6
|
dependencies = [
|
|
7
|
-
"mm-crypto-utils>=0.3.
|
|
7
|
+
"mm-crypto-utils>=0.3.4",
|
|
8
8
|
"solana~=0.36.6",
|
|
9
9
|
"solders~=0.26.0",
|
|
10
10
|
"base58~=2.1.1",
|
|
@@ -12,7 +12,6 @@ dependencies = [
|
|
|
12
12
|
"typer>=0.15.2",
|
|
13
13
|
"jinja2>=3.1.6",
|
|
14
14
|
"socksio>=1.0.0",
|
|
15
|
-
"mm-std>=0.4.5",
|
|
16
15
|
]
|
|
17
16
|
[project.scripts]
|
|
18
17
|
mm-sol = "mm_sol.cli.cli:app"
|
|
@@ -82,4 +81,5 @@ exclude_dirs = ["tests"]
|
|
|
82
81
|
skips = ["B311"]
|
|
83
82
|
|
|
84
83
|
[tool.pytest.ini_options]
|
|
84
|
+
asyncio_mode = "auto"
|
|
85
85
|
asyncio_default_fixture_loop_scope = "function"
|
|
@@ -2,7 +2,7 @@ import mm_crypto_utils
|
|
|
2
2
|
from mm_crypto_utils import Nodes, Proxies, VarInt
|
|
3
3
|
from mm_std import Result
|
|
4
4
|
|
|
5
|
-
from mm_sol import
|
|
5
|
+
from mm_sol import retry
|
|
6
6
|
from mm_sol.constants import SUFFIX_DECIMALS
|
|
7
7
|
|
|
8
8
|
|
|
@@ -20,15 +20,15 @@ async def calc_sol_value_for_address(
|
|
|
20
20
|
value_expression = value_expression.lower()
|
|
21
21
|
var = None
|
|
22
22
|
if "balance" in value_expression:
|
|
23
|
-
res = await
|
|
24
|
-
if res.
|
|
23
|
+
res = await retry.get_sol_balance(5, nodes, proxies, address=address)
|
|
24
|
+
if res.is_err():
|
|
25
25
|
return res
|
|
26
26
|
var = VarInt("balance", res.unwrap())
|
|
27
27
|
|
|
28
28
|
value = calc_sol_expression(value_expression, var)
|
|
29
29
|
if "balance" in value_expression:
|
|
30
30
|
value = value - fee
|
|
31
|
-
return Result.
|
|
31
|
+
return Result.ok(value)
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
async def calc_token_value_for_address(
|
|
@@ -37,9 +37,9 @@ async def calc_token_value_for_address(
|
|
|
37
37
|
var = None
|
|
38
38
|
value_expression = value_expression.lower()
|
|
39
39
|
if "balance" in value_expression:
|
|
40
|
-
res = await
|
|
41
|
-
if res.
|
|
40
|
+
res = await retry.get_token_balance(5, nodes, proxies, owner=owner, token=token)
|
|
41
|
+
if res.is_err():
|
|
42
42
|
return res
|
|
43
43
|
var = VarInt("balance", res.unwrap())
|
|
44
44
|
value = calc_token_expression(value_expression, token_decimals, var)
|
|
45
|
-
return Result.
|
|
45
|
+
return Result.ok(value)
|
|
@@ -4,7 +4,8 @@ import mm_crypto_utils
|
|
|
4
4
|
from mm_std import print_json
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import mm_sol.retry
|
|
8
|
+
from mm_sol import retry
|
|
8
9
|
from mm_sol.cli import cli_utils
|
|
9
10
|
|
|
10
11
|
|
|
@@ -45,7 +46,7 @@ async def run(
|
|
|
45
46
|
proxies = await mm_crypto_utils.fetch_proxies_or_fatal(proxies_url) if proxies_url else None
|
|
46
47
|
|
|
47
48
|
# sol balance
|
|
48
|
-
sol_balance_res = await
|
|
49
|
+
sol_balance_res = await retry.get_sol_balance(3, rpc_url, proxies, address=wallet_address)
|
|
49
50
|
if sol_balance_res.is_ok():
|
|
50
51
|
result.sol_balance = sol_balance_res.unwrap()
|
|
51
52
|
else:
|
|
@@ -53,16 +54,14 @@ async def run(
|
|
|
53
54
|
|
|
54
55
|
# token balance
|
|
55
56
|
if token_address:
|
|
56
|
-
token_balance_res = await
|
|
57
|
-
3, rpc_url, proxies, owner=wallet_address, token=token_address
|
|
58
|
-
)
|
|
57
|
+
token_balance_res = await mm_sol.retry.get_token_balance(3, rpc_url, proxies, owner=wallet_address, token=token_address)
|
|
59
58
|
|
|
60
59
|
if token_balance_res.is_ok():
|
|
61
60
|
result.token_balance = token_balance_res.unwrap()
|
|
62
61
|
else:
|
|
63
62
|
result.errors.append("token_balance: " + token_balance_res.unwrap_error())
|
|
64
63
|
|
|
65
|
-
decimals_res = await
|
|
64
|
+
decimals_res = await mm_sol.retry.get_token_decimals(3, rpc_url, proxies, token=token_address)
|
|
66
65
|
if decimals_res.is_ok():
|
|
67
66
|
result.token_decimals = decimals_res.unwrap()
|
|
68
67
|
else:
|
|
@@ -7,7 +7,8 @@ from mm_crypto_utils import ConfigValidators
|
|
|
7
7
|
from mm_std import BaseConfig, fatal, print_json
|
|
8
8
|
from pydantic import BeforeValidator, Field
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
import mm_sol.retry
|
|
11
|
+
from mm_sol import converters, retry
|
|
11
12
|
from mm_sol.cli.validators import Validators
|
|
12
13
|
|
|
13
14
|
|
|
@@ -32,8 +33,8 @@ async def run(config_path: Path, print_config: bool) -> None:
|
|
|
32
33
|
|
|
33
34
|
if config.tokens:
|
|
34
35
|
for token_address in config.tokens:
|
|
35
|
-
res = await
|
|
36
|
-
if res.
|
|
36
|
+
res = await mm_sol.retry.get_token_decimals(3, config.nodes, config.proxies, token=token_address)
|
|
37
|
+
if res.is_err():
|
|
37
38
|
fatal(f"Failed to get decimals for token {token_address}: {res.unwrap_error()}")
|
|
38
39
|
|
|
39
40
|
token_decimals = res.unwrap()
|
|
@@ -50,9 +51,9 @@ async def _get_token_balances(
|
|
|
50
51
|
result: dict[str, Decimal | None] = {}
|
|
51
52
|
for account in accounts:
|
|
52
53
|
result[account] = (
|
|
53
|
-
(await
|
|
54
|
+
(await mm_sol.retry.get_token_balance(3, config.nodes, config.proxies, owner=account, token=token_address))
|
|
54
55
|
.map(lambda v: converters.to_token(v, token_decimals))
|
|
55
|
-
.
|
|
56
|
+
.value
|
|
56
57
|
)
|
|
57
58
|
|
|
58
59
|
return result
|
|
@@ -62,9 +63,9 @@ async def _get_sol_balances(accounts: list[str], config: Config) -> dict[str, De
|
|
|
62
63
|
result = {}
|
|
63
64
|
for account in accounts:
|
|
64
65
|
result[account] = (
|
|
65
|
-
(await
|
|
66
|
+
(await retry.get_sol_balance(3, config.nodes, config.proxies, address=account))
|
|
66
67
|
.map(lambda v: converters.lamports_to_sol(v))
|
|
67
|
-
.
|
|
68
|
+
.value
|
|
68
69
|
)
|
|
69
70
|
|
|
70
71
|
return result
|
|
@@ -13,12 +13,12 @@ from rich.live import Live
|
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
from solders.signature import Signature
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
import mm_sol.retry
|
|
17
|
+
from mm_sol import retry
|
|
17
18
|
from mm_sol.cli import calcs, cli_utils
|
|
18
19
|
from mm_sol.cli.cli_utils import BaseConfigParams
|
|
19
20
|
from mm_sol.cli.validators import Validators
|
|
20
21
|
from mm_sol.converters import lamports_to_sol, to_token
|
|
21
|
-
from mm_sol.transfer import transfer_sol_with_retries, transfer_token_with_retries
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class Config(BaseConfig):
|
|
@@ -63,8 +63,8 @@ class Config(BaseConfig):
|
|
|
63
63
|
Validators.valid_sol_expression()(self.value_min_limit)
|
|
64
64
|
|
|
65
65
|
if self.token:
|
|
66
|
-
res = await
|
|
67
|
-
if res.
|
|
66
|
+
res = await mm_sol.retry.get_token_decimals(3, self.nodes, self.proxies, token=self.token)
|
|
67
|
+
if res.is_err():
|
|
68
68
|
fatal(f"can't get decimals for token={self.token}, error={res.unwrap_error()}")
|
|
69
69
|
self.token_decimals = res.unwrap()
|
|
70
70
|
|
|
@@ -131,10 +131,10 @@ async def _calc_value(transfer: Transfer, config: Config, transfer_sol_fee: int)
|
|
|
131
131
|
fee=transfer_sol_fee,
|
|
132
132
|
)
|
|
133
133
|
logger.debug(f"{transfer.log_prefix}: value={value_res.ok_or_error()}")
|
|
134
|
-
if value_res.
|
|
134
|
+
if value_res.is_err():
|
|
135
135
|
logger.info(f"{transfer.log_prefix}: calc value error, {value_res.unwrap_error()}")
|
|
136
136
|
|
|
137
|
-
return value_res.
|
|
137
|
+
return value_res.value
|
|
138
138
|
|
|
139
139
|
|
|
140
140
|
def _check_value_min_limit(transfer: Transfer, value: int, config: Config) -> bool:
|
|
@@ -158,7 +158,7 @@ def _value_with_suffix(value: int, config: Config) -> str:
|
|
|
158
158
|
async def _send_tx(transfer: Transfer, value: int, config: Config) -> Signature | None:
|
|
159
159
|
logger.debug(f"{transfer.log_prefix}: value={_value_with_suffix(value, config)}")
|
|
160
160
|
if config.token:
|
|
161
|
-
res = await
|
|
161
|
+
res = await retry.transfer_token(
|
|
162
162
|
3,
|
|
163
163
|
config.nodes,
|
|
164
164
|
config.proxies,
|
|
@@ -170,7 +170,7 @@ async def _send_tx(transfer: Transfer, value: int, config: Config) -> Signature
|
|
|
170
170
|
decimals=config.token_decimals,
|
|
171
171
|
)
|
|
172
172
|
else:
|
|
173
|
-
res = await
|
|
173
|
+
res = await retry.transfer_sol(
|
|
174
174
|
3,
|
|
175
175
|
config.nodes,
|
|
176
176
|
config.proxies,
|
|
@@ -180,10 +180,10 @@ async def _send_tx(transfer: Transfer, value: int, config: Config) -> Signature
|
|
|
180
180
|
lamports=value,
|
|
181
181
|
)
|
|
182
182
|
|
|
183
|
-
if res.
|
|
183
|
+
if res.is_err():
|
|
184
184
|
logger.info(f"{transfer.log_prefix}: tx error {res.unwrap_error()}")
|
|
185
185
|
return None
|
|
186
|
-
return res.
|
|
186
|
+
return res.value
|
|
187
187
|
|
|
188
188
|
|
|
189
189
|
async def _transfer(transfer: Transfer, config: Config, cmd_params: TransferCmdParams) -> None:
|
|
@@ -255,12 +255,12 @@ async def _print_balances(config: Config) -> None:
|
|
|
255
255
|
|
|
256
256
|
|
|
257
257
|
async def _get_sol_balance_str(address: str, config: Config) -> str:
|
|
258
|
-
res = await
|
|
258
|
+
res = await retry.get_sol_balance(5, config.nodes, config.proxies, address=address)
|
|
259
259
|
return res.map(lambda ok: str(lamports_to_sol(ok, config.round_ndigits))).ok_or_error()
|
|
260
260
|
|
|
261
261
|
|
|
262
262
|
async def _get_token_balance_str(address: str, config: Config) -> str:
|
|
263
263
|
if not config.token:
|
|
264
264
|
raise ValueError("token is not set")
|
|
265
|
-
res = await
|
|
265
|
+
res = await mm_sol.retry.get_token_balance(5, config.nodes, config.proxies, owner=address, token=config.token)
|
|
266
266
|
return res.map(lambda ok: str(to_token(ok, config.token_decimals, ndigits=config.round_ndigits))).ok_or_error()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from mm_crypto_utils import Nodes, Proxies, retry_with_node_and_proxy
|
|
2
|
+
from mm_std import Result
|
|
3
|
+
from solders.solders import Pubkey, Signature
|
|
4
|
+
|
|
5
|
+
from mm_sol import rpc, spl_token, transfer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def get_sol_balance(retries: int, nodes: Nodes, proxies: Proxies, *, address: str, timeout: float = 5) -> Result[int]:
|
|
9
|
+
return await retry_with_node_and_proxy(
|
|
10
|
+
retries,
|
|
11
|
+
nodes,
|
|
12
|
+
proxies,
|
|
13
|
+
lambda node, proxy: rpc.get_balance(node=node, address=address, timeout=timeout, proxy=proxy),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def get_token_balance(
|
|
18
|
+
retries: int,
|
|
19
|
+
nodes: Nodes,
|
|
20
|
+
proxies: Proxies,
|
|
21
|
+
*,
|
|
22
|
+
owner: str,
|
|
23
|
+
token: str,
|
|
24
|
+
token_account: str | None = None,
|
|
25
|
+
timeout: float = 5,
|
|
26
|
+
) -> Result[int]:
|
|
27
|
+
return await retry_with_node_and_proxy(
|
|
28
|
+
retries,
|
|
29
|
+
nodes,
|
|
30
|
+
proxies,
|
|
31
|
+
lambda node, proxy: spl_token.get_balance(
|
|
32
|
+
node,
|
|
33
|
+
owner=owner,
|
|
34
|
+
token=token,
|
|
35
|
+
token_account=token_account,
|
|
36
|
+
timeout=timeout,
|
|
37
|
+
proxy=proxy,
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def transfer_token(
|
|
43
|
+
retries: int,
|
|
44
|
+
nodes: Nodes,
|
|
45
|
+
proxies: Proxies,
|
|
46
|
+
*,
|
|
47
|
+
token_mint_address: str | Pubkey,
|
|
48
|
+
from_address: str | Pubkey,
|
|
49
|
+
private_key: str,
|
|
50
|
+
to_address: str | Pubkey,
|
|
51
|
+
amount: int, # smallest unit
|
|
52
|
+
decimals: int,
|
|
53
|
+
timeout: float = 10,
|
|
54
|
+
create_token_account_if_not_exists: bool = True,
|
|
55
|
+
) -> Result[Signature]:
|
|
56
|
+
return await retry_with_node_and_proxy(
|
|
57
|
+
retries,
|
|
58
|
+
nodes,
|
|
59
|
+
proxies,
|
|
60
|
+
lambda node, proxy: transfer.transfer_token(
|
|
61
|
+
node=node,
|
|
62
|
+
token_mint_address=token_mint_address,
|
|
63
|
+
from_address=from_address,
|
|
64
|
+
private_key=private_key,
|
|
65
|
+
to_address=to_address,
|
|
66
|
+
amount=amount,
|
|
67
|
+
decimals=decimals,
|
|
68
|
+
proxy=proxy,
|
|
69
|
+
timeout=timeout,
|
|
70
|
+
create_token_account_if_not_exists=create_token_account_if_not_exists,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def transfer_sol(
|
|
76
|
+
retries: int,
|
|
77
|
+
nodes: Nodes,
|
|
78
|
+
proxies: Proxies,
|
|
79
|
+
*,
|
|
80
|
+
from_address: str,
|
|
81
|
+
private_key: str,
|
|
82
|
+
to_address: str,
|
|
83
|
+
lamports: int,
|
|
84
|
+
timeout: float = 10,
|
|
85
|
+
) -> Result[Signature]:
|
|
86
|
+
return await retry_with_node_and_proxy(
|
|
87
|
+
retries,
|
|
88
|
+
nodes,
|
|
89
|
+
proxies,
|
|
90
|
+
lambda node, proxy: transfer.transfer_sol(
|
|
91
|
+
node=node,
|
|
92
|
+
proxy=proxy,
|
|
93
|
+
from_address=from_address,
|
|
94
|
+
to_address=to_address,
|
|
95
|
+
lamports=lamports,
|
|
96
|
+
private_key=private_key,
|
|
97
|
+
timeout=timeout,
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def get_token_decimals(retries: int, nodes: Nodes, proxies: Proxies, *, token: str, timeout: float = 5) -> Result[int]:
|
|
103
|
+
return await retry_with_node_and_proxy(
|
|
104
|
+
retries,
|
|
105
|
+
nodes,
|
|
106
|
+
proxies,
|
|
107
|
+
lambda node, proxy: spl_token.get_decimals(node, token=token, proxy=proxy, timeout=timeout),
|
|
108
|
+
)
|
|
@@ -3,7 +3,6 @@ from collections.abc import Sequence
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
import websockets
|
|
6
|
-
from mm_crypto_utils import Nodes, Proxies, retry_with_node_and_proxy
|
|
7
6
|
from mm_std import Result, http_request
|
|
8
7
|
|
|
9
8
|
|
|
@@ -23,18 +22,18 @@ async def rpc_call(
|
|
|
23
22
|
|
|
24
23
|
async def _http_call(node: str, data: dict[str, object], timeout: float, proxy: str | None) -> Result[Any]:
|
|
25
24
|
res = await http_request(node, method="POST", proxy=proxy, timeout=timeout, json=data)
|
|
26
|
-
if res.
|
|
27
|
-
return res.
|
|
25
|
+
if res.is_err():
|
|
26
|
+
return res.to_err_result()
|
|
28
27
|
try:
|
|
29
28
|
parsed_body = res.parse_json_body()
|
|
30
29
|
err = parsed_body.get("error", {}).get("message", "")
|
|
31
30
|
if err:
|
|
32
|
-
return res.
|
|
31
|
+
return res.to_err_result(f"service_error: {err}")
|
|
33
32
|
if "result" in parsed_body:
|
|
34
|
-
return res.
|
|
35
|
-
return res.
|
|
33
|
+
return res.to_ok_result(parsed_body["result"])
|
|
34
|
+
return res.to_err_result("unknown_response")
|
|
36
35
|
except Exception as e:
|
|
37
|
-
return res.
|
|
36
|
+
return res.to_err_result(e)
|
|
38
37
|
|
|
39
38
|
|
|
40
39
|
async def _ws_call(node: str, data: dict[str, object], timeout: float) -> Result[Any]:
|
|
@@ -46,14 +45,14 @@ async def _ws_call(node: str, data: dict[str, object], timeout: float) -> Result
|
|
|
46
45
|
|
|
47
46
|
err = response.get("error", {}).get("message", "")
|
|
48
47
|
if err:
|
|
49
|
-
return Result.
|
|
48
|
+
return Result.err(f"service_error: {err}", {"response": response})
|
|
50
49
|
if "result" in response:
|
|
51
|
-
return Result.
|
|
52
|
-
return Result.
|
|
50
|
+
return Result.ok(response["result"], {"response": response})
|
|
51
|
+
return Result.err("unknown_response", {"response": response})
|
|
53
52
|
except TimeoutError:
|
|
54
|
-
return Result.
|
|
53
|
+
return Result.err("timeout", {"response": response})
|
|
55
54
|
except Exception as e:
|
|
56
|
-
return Result.
|
|
55
|
+
return Result.err(e, {"response": response})
|
|
57
56
|
|
|
58
57
|
|
|
59
58
|
async def get_block_height(node: str, timeout: float = 10, proxy: str | None = None) -> Result[int]:
|
|
@@ -65,17 +64,3 @@ async def get_balance(node: str, address: str, timeout: float = 5, proxy: str |
|
|
|
65
64
|
return (await rpc_call(node=node, method="getBalance", params=[address], timeout=timeout, proxy=proxy)).map(
|
|
66
65
|
lambda r: r["value"]
|
|
67
66
|
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
async def get_balance_with_retries(
|
|
71
|
-
retries: int, nodes: Nodes, proxies: Proxies, *, address: str, timeout: float = 5
|
|
72
|
-
) -> Result[int]:
|
|
73
|
-
"""
|
|
74
|
-
Retry get_balance with different nodes and proxies
|
|
75
|
-
"""
|
|
76
|
-
return await retry_with_node_and_proxy(
|
|
77
|
-
retries,
|
|
78
|
-
nodes,
|
|
79
|
-
proxies,
|
|
80
|
-
lambda node, proxy: get_balance(node=node, address=address, timeout=timeout, proxy=proxy),
|
|
81
|
-
)
|
|
@@ -91,19 +91,19 @@ def rpc_call(
|
|
|
91
91
|
def _http_call(node: str, data: dict[str, object], timeout: float, proxy: str | None) -> Result[Any]:
|
|
92
92
|
res = http_request_sync(node, method="POST", proxy=proxy, timeout=timeout, json=data)
|
|
93
93
|
try:
|
|
94
|
-
if res.
|
|
95
|
-
return res.
|
|
94
|
+
if res.is_err():
|
|
95
|
+
return res.to_err_result()
|
|
96
96
|
|
|
97
97
|
json_body = res.parse_json_body()
|
|
98
98
|
err = pydash.get(json_body, "error.message")
|
|
99
99
|
if err:
|
|
100
|
-
return res.
|
|
100
|
+
return res.to_err_result(f"service_error: {err}")
|
|
101
101
|
if "result" in json_body:
|
|
102
|
-
return res.
|
|
102
|
+
return res.to_ok_result(json_body["result"])
|
|
103
103
|
|
|
104
|
-
return res.
|
|
104
|
+
return res.to_err_result("unknown_response")
|
|
105
105
|
except Exception as e:
|
|
106
|
-
return res.
|
|
106
|
+
return res.to_err_result(e)
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
def get_balance(node: str, address: str, timeout: float = 10, proxy: str | None = None) -> Result[int]:
|
|
@@ -137,7 +137,7 @@ def get_cluster_nodes(node: str, timeout: float = 30, proxy: str | None = None)
|
|
|
137
137
|
|
|
138
138
|
def get_vote_accounts(node: str, timeout: float = 30, proxy: str | None = None) -> Result[list[VoteAccount]]:
|
|
139
139
|
res = rpc_call(node=node, method="getVoteAccounts", timeout=timeout, proxy=proxy, params=[])
|
|
140
|
-
if res.
|
|
140
|
+
if res.is_err():
|
|
141
141
|
return res
|
|
142
142
|
try:
|
|
143
143
|
data = res.unwrap()
|
|
@@ -174,9 +174,9 @@ def get_vote_accounts(node: str, timeout: float = 30, proxy: str | None = None)
|
|
|
174
174
|
last_vote=a["lastVote"],
|
|
175
175
|
),
|
|
176
176
|
)
|
|
177
|
-
return
|
|
177
|
+
return res.with_value(result)
|
|
178
178
|
except Exception as e:
|
|
179
|
-
return
|
|
179
|
+
return res.with_error(e)
|
|
180
180
|
|
|
181
181
|
|
|
182
182
|
def get_leader_scheduler(
|
|
@@ -194,23 +194,6 @@ def get_leader_scheduler(
|
|
|
194
194
|
)
|
|
195
195
|
|
|
196
196
|
|
|
197
|
-
def get_block_production(node: str, timeout: float = 60, proxy: str | None = None) -> Result[BlockProduction]:
|
|
198
|
-
res = rpc_call(node=node, method="getBlockProduction", timeout=timeout, proxy=proxy, params=[])
|
|
199
|
-
if res.is_error():
|
|
200
|
-
return res
|
|
201
|
-
try:
|
|
202
|
-
res_ok = res.unwrap()
|
|
203
|
-
slot = res_ok["context"]["slot"]
|
|
204
|
-
first_slot = res_ok["value"]["range"]["firstSlot"]
|
|
205
|
-
last_slot = res_ok["value"]["range"]["lastSlot"]
|
|
206
|
-
leaders = []
|
|
207
|
-
for address, (leader, produced) in res.ok["value"]["byIdentity"].items(): # type: ignore[index]
|
|
208
|
-
leaders.append(BlockProduction.Leader(address=address, produced=produced, skipped=leader - produced))
|
|
209
|
-
return Result.success(BlockProduction(slot=slot, first_slot=first_slot, last_slot=last_slot, leaders=leaders), res.extra)
|
|
210
|
-
except Exception as e:
|
|
211
|
-
return Result.failure(e, res.extra)
|
|
212
|
-
|
|
213
|
-
|
|
214
197
|
def get_stake_activation(node: str, address: str, timeout: float = 60, proxy: str | None = None) -> Result[StakeActivation]:
|
|
215
198
|
return rpc_call(node=node, method="getStakeActivation", timeout=timeout, proxy=proxy, params=[address]).map(
|
|
216
199
|
lambda ok: StakeActivation(**ok),
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from mm_std import Result
|
|
2
|
+
from solana.exceptions import SolanaRpcException
|
|
3
|
+
from solana.rpc.core import RPCException
|
|
4
|
+
from solders.solders import InvalidParamsMessage, Pubkey, get_associated_token_address
|
|
5
|
+
|
|
6
|
+
from mm_sol.utils import get_async_client
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def get_balance(
|
|
10
|
+
node: str,
|
|
11
|
+
owner: str,
|
|
12
|
+
token: str,
|
|
13
|
+
token_account: str | None = None,
|
|
14
|
+
timeout: float = 5,
|
|
15
|
+
proxy: str | None = None,
|
|
16
|
+
) -> Result[int]:
|
|
17
|
+
response = None
|
|
18
|
+
try:
|
|
19
|
+
client = get_async_client(node, proxy=proxy, timeout=timeout)
|
|
20
|
+
if not token_account:
|
|
21
|
+
token_account = str(get_associated_token_address(Pubkey.from_string(owner), Pubkey.from_string(token)))
|
|
22
|
+
|
|
23
|
+
res = await client.get_token_account_balance(Pubkey.from_string(token_account))
|
|
24
|
+
response = res.to_json()
|
|
25
|
+
|
|
26
|
+
# Sometimes it not raise an error, but it returns this :(
|
|
27
|
+
if isinstance(res, InvalidParamsMessage) and "could not find account" in res.message:
|
|
28
|
+
return Result.ok(0, {"response": response})
|
|
29
|
+
return Result.ok(int(res.value.amount), {"response": response})
|
|
30
|
+
except RPCException as e:
|
|
31
|
+
if "could not find account" in str(e):
|
|
32
|
+
return Result.ok(0, {"response": response, "rpc_exception": str(e)})
|
|
33
|
+
return Result.err(e, {"response": response})
|
|
34
|
+
except SolanaRpcException as e:
|
|
35
|
+
return Result.err((e.error_msg, e), {"response": response})
|
|
36
|
+
except Exception as e:
|
|
37
|
+
return Result.err(e, {"response": response})
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def get_decimals(node: str, token: str, timeout: float = 5, proxy: str | None = None) -> Result[int]:
|
|
41
|
+
response = None
|
|
42
|
+
try:
|
|
43
|
+
client = get_async_client(node, proxy=proxy, timeout=timeout)
|
|
44
|
+
res = await client.get_token_supply(Pubkey.from_string(token))
|
|
45
|
+
response = res.to_json()
|
|
46
|
+
return Result.ok(res.value.decimals, {"response": response})
|
|
47
|
+
except Exception as e:
|
|
48
|
+
return Result.err(e, {"response": response})
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import pydash
|
|
2
|
-
from mm_crypto_utils import Nodes, Proxies, retry_with_node_and_proxy
|
|
3
2
|
from mm_std import Result
|
|
4
3
|
from pydantic import BaseModel
|
|
5
4
|
from solders.message import Message
|
|
@@ -15,39 +14,6 @@ from mm_sol import rpc_sync, utils
|
|
|
15
14
|
from mm_sol.account import check_private_key, get_keypair
|
|
16
15
|
|
|
17
16
|
|
|
18
|
-
async def transfer_token_with_retries(
|
|
19
|
-
retries: int,
|
|
20
|
-
nodes: Nodes,
|
|
21
|
-
proxies: Proxies,
|
|
22
|
-
*,
|
|
23
|
-
token_mint_address: str | Pubkey,
|
|
24
|
-
from_address: str | Pubkey,
|
|
25
|
-
private_key: str,
|
|
26
|
-
to_address: str | Pubkey,
|
|
27
|
-
amount: int, # smallest unit
|
|
28
|
-
decimals: int,
|
|
29
|
-
timeout: float = 10,
|
|
30
|
-
create_token_account_if_not_exists: bool = True,
|
|
31
|
-
) -> Result[Signature]:
|
|
32
|
-
return await retry_with_node_and_proxy(
|
|
33
|
-
retries,
|
|
34
|
-
nodes,
|
|
35
|
-
proxies,
|
|
36
|
-
lambda node, proxy: transfer_token(
|
|
37
|
-
node=node,
|
|
38
|
-
token_mint_address=token_mint_address,
|
|
39
|
-
from_address=from_address,
|
|
40
|
-
private_key=private_key,
|
|
41
|
-
to_address=to_address,
|
|
42
|
-
amount=amount,
|
|
43
|
-
decimals=decimals,
|
|
44
|
-
proxy=proxy,
|
|
45
|
-
timeout=timeout,
|
|
46
|
-
create_token_account_if_not_exists=create_token_account_if_not_exists,
|
|
47
|
-
),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
17
|
async def transfer_token(
|
|
52
18
|
*,
|
|
53
19
|
node: str,
|
|
@@ -64,7 +30,7 @@ async def transfer_token(
|
|
|
64
30
|
# TODO: try/except this function!!!
|
|
65
31
|
acc = get_keypair(private_key)
|
|
66
32
|
if not check_private_key(from_address, private_key):
|
|
67
|
-
return Result.
|
|
33
|
+
return Result.err("invalid_private_key")
|
|
68
34
|
|
|
69
35
|
from_address = utils.pubkey(from_address)
|
|
70
36
|
token_mint_address = utils.pubkey(token_mint_address)
|
|
@@ -83,7 +49,7 @@ async def transfer_token(
|
|
|
83
49
|
create_account_res = token_client.create_associated_token_account(to_address, skip_confirmation=False)
|
|
84
50
|
logs.append(create_account_res)
|
|
85
51
|
else:
|
|
86
|
-
return Result.
|
|
52
|
+
return Result.err("no_token_account")
|
|
87
53
|
|
|
88
54
|
res = await token_client.transfer_checked(
|
|
89
55
|
source=from_token_account,
|
|
@@ -95,34 +61,7 @@ async def transfer_token(
|
|
|
95
61
|
)
|
|
96
62
|
logs.append(res)
|
|
97
63
|
|
|
98
|
-
return Result.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
async def transfer_sol_with_retries(
|
|
102
|
-
retries: int,
|
|
103
|
-
nodes: Nodes,
|
|
104
|
-
proxies: Proxies,
|
|
105
|
-
*,
|
|
106
|
-
from_address: str,
|
|
107
|
-
private_key: str,
|
|
108
|
-
to_address: str,
|
|
109
|
-
lamports: int,
|
|
110
|
-
timeout: float = 10,
|
|
111
|
-
) -> Result[Signature]:
|
|
112
|
-
return await retry_with_node_and_proxy(
|
|
113
|
-
retries,
|
|
114
|
-
nodes,
|
|
115
|
-
proxies,
|
|
116
|
-
lambda node, proxy: transfer_sol(
|
|
117
|
-
node=node,
|
|
118
|
-
proxy=proxy,
|
|
119
|
-
from_address=from_address,
|
|
120
|
-
to_address=to_address,
|
|
121
|
-
lamports=lamports,
|
|
122
|
-
private_key=private_key,
|
|
123
|
-
timeout=timeout,
|
|
124
|
-
),
|
|
125
|
-
)
|
|
64
|
+
return Result.ok(res.value, {"logs": logs})
|
|
126
65
|
|
|
127
66
|
|
|
128
67
|
async def transfer_sol(
|
|
@@ -137,7 +76,7 @@ async def transfer_sol(
|
|
|
137
76
|
) -> Result[Signature]:
|
|
138
77
|
acc = get_keypair(private_key)
|
|
139
78
|
if not check_private_key(from_address, private_key):
|
|
140
|
-
return Result.
|
|
79
|
+
return Result.err("invalid_private_key")
|
|
141
80
|
|
|
142
81
|
client = utils.get_async_client(node, proxy=proxy, timeout=timeout)
|
|
143
82
|
data = None
|
|
@@ -148,9 +87,9 @@ async def transfer_sol(
|
|
|
148
87
|
tx = Transaction([acc], msg, blockhash.value.blockhash)
|
|
149
88
|
res = await client.send_transaction(tx)
|
|
150
89
|
data = res.to_json()
|
|
151
|
-
return Result.
|
|
90
|
+
return Result.ok(res.value, {"response": data})
|
|
152
91
|
except Exception as e:
|
|
153
|
-
return Result.
|
|
92
|
+
return Result.err(e, {"response": data})
|
|
154
93
|
|
|
155
94
|
|
|
156
95
|
class SolTransferInfo(BaseModel):
|
|
@@ -161,7 +100,7 @@ class SolTransferInfo(BaseModel):
|
|
|
161
100
|
|
|
162
101
|
def find_sol_transfers(node: str, tx_signature: str) -> Result[list[SolTransferInfo]]:
|
|
163
102
|
res = rpc_sync.get_transaction(node, tx_signature, encoding="jsonParsed")
|
|
164
|
-
if res.
|
|
103
|
+
if res.is_err():
|
|
165
104
|
return res # type: ignore[return-value]
|
|
166
105
|
result = []
|
|
167
106
|
try:
|
|
@@ -174,6 +113,6 @@ def find_sol_transfers(node: str, tx_signature: str) -> Result[list[SolTransferI
|
|
|
174
113
|
lamports = pydash.get(ix, "parsed.info.lamports")
|
|
175
114
|
if source and destination and lamports:
|
|
176
115
|
result.append(SolTransferInfo(source=source, destination=destination, lamports=lamports))
|
|
177
|
-
return
|
|
116
|
+
return res.with_value(result)
|
|
178
117
|
except Exception as e:
|
|
179
|
-
return Result.
|
|
118
|
+
return Result.err(e, res.extra)
|
|
@@ -44,11 +44,6 @@ def test_get_leader_scheduler(testnet_node, random_proxy):
|
|
|
44
44
|
assert len(res.unwrap()) > 10
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def test_get_block_production(testnet_node, random_proxy):
|
|
48
|
-
res = rpc_sync.get_block_production(testnet_node, proxy=random_proxy)
|
|
49
|
-
assert res.unwrap().slot > 1000
|
|
50
|
-
|
|
51
|
-
|
|
52
47
|
def test_get_transaction(mainnet_node, random_proxy):
|
|
53
48
|
tx_hash = "2vifJ5g4inS4spZLQMUyVstvMrCM2mg1QC9xjD6bgsiMUwp8sTE5waCdshJ8SVaH95WGtexjH3q8ot1GoKe9yK3h"
|
|
54
49
|
res = rpc_sync.get_transaction(mainnet_node, tx_hash, 0, proxy=random_proxy)
|
|
@@ -14,7 +14,7 @@ async def test_get_balance(mainnet_node, usdt_token_address, usdt_owner_address,
|
|
|
14
14
|
async def test_get_balance_no_token_account(mainnet_node, usdt_token_address, random_proxy):
|
|
15
15
|
res = await spl_token.get_balance(mainnet_node, generate_account().public_key, usdt_token_address, proxy=random_proxy)
|
|
16
16
|
|
|
17
|
-
assert res.
|
|
17
|
+
assert res.unwrap() == 0
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
async def test_get_decimals(mainnet_node, usdt_token_address, random_proxy):
|
|
@@ -562,25 +562,24 @@ wheels = [
|
|
|
562
562
|
|
|
563
563
|
[[package]]
|
|
564
564
|
name = "mm-crypto-utils"
|
|
565
|
-
version = "0.3.
|
|
565
|
+
version = "0.3.4"
|
|
566
566
|
source = { registry = "https://pypi.org/simple" }
|
|
567
567
|
dependencies = [
|
|
568
568
|
{ name = "loguru" },
|
|
569
569
|
{ name = "mm-std" },
|
|
570
570
|
]
|
|
571
571
|
wheels = [
|
|
572
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
572
|
+
{ url = "https://files.pythonhosted.org/packages/e8/53/d89b21605698bfe087b8768d808c61ebac9d299694bf4a11bd84460691b3/mm_crypto_utils-0.3.4-py3-none-any.whl", hash = "sha256:964f81c53406ca58b0627dfd13c5743f1d919a94580b1683025bd40518983dd9", size = 10075 },
|
|
573
573
|
]
|
|
574
574
|
|
|
575
575
|
[[package]]
|
|
576
576
|
name = "mm-sol"
|
|
577
|
-
version = "0.6.
|
|
577
|
+
version = "0.6.1"
|
|
578
578
|
source = { editable = "." }
|
|
579
579
|
dependencies = [
|
|
580
580
|
{ name = "base58" },
|
|
581
581
|
{ name = "jinja2" },
|
|
582
582
|
{ name = "mm-crypto-utils" },
|
|
583
|
-
{ name = "mm-std" },
|
|
584
583
|
{ name = "mnemonic" },
|
|
585
584
|
{ name = "socksio" },
|
|
586
585
|
{ name = "solana" },
|
|
@@ -603,8 +602,7 @@ dev = [
|
|
|
603
602
|
requires-dist = [
|
|
604
603
|
{ name = "base58", specifier = "~=2.1.1" },
|
|
605
604
|
{ name = "jinja2", specifier = ">=3.1.6" },
|
|
606
|
-
{ name = "mm-crypto-utils", specifier = ">=0.3.
|
|
607
|
-
{ name = "mm-std", specifier = ">=0.4.5" },
|
|
605
|
+
{ name = "mm-crypto-utils", specifier = ">=0.3.4" },
|
|
608
606
|
{ name = "mnemonic", specifier = "==0.21" },
|
|
609
607
|
{ name = "socksio", specifier = ">=1.0.0" },
|
|
610
608
|
{ name = "solana", specifier = "~=0.36.6" },
|
|
@@ -625,7 +623,7 @@ dev = [
|
|
|
625
623
|
|
|
626
624
|
[[package]]
|
|
627
625
|
name = "mm-std"
|
|
628
|
-
version = "0.4.
|
|
626
|
+
version = "0.4.6"
|
|
629
627
|
source = { registry = "https://pypi.org/simple" }
|
|
630
628
|
dependencies = [
|
|
631
629
|
{ name = "aiohttp" },
|
|
@@ -641,7 +639,7 @@ dependencies = [
|
|
|
641
639
|
{ name = "tomlkit" },
|
|
642
640
|
]
|
|
643
641
|
wheels = [
|
|
644
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
642
|
+
{ url = "https://files.pythonhosted.org/packages/df/7c/bf7484f886905fd0c045b3ef617eac97cf4128e85191cc232df41c44b218/mm_std-0.4.6-py3-none-any.whl", hash = "sha256:f9f74ed75bda4569d8ed3fbb61b7f0add9668e800e93724cf84b61b599b77a68", size = 23821 },
|
|
645
643
|
]
|
|
646
644
|
|
|
647
645
|
[[package]]
|
|
@@ -1016,15 +1014,16 @@ wheels = [
|
|
|
1016
1014
|
|
|
1017
1015
|
[[package]]
|
|
1018
1016
|
name = "pydantic-settings"
|
|
1019
|
-
version = "2.
|
|
1017
|
+
version = "2.9.1"
|
|
1020
1018
|
source = { registry = "https://pypi.org/simple" }
|
|
1021
1019
|
dependencies = [
|
|
1022
1020
|
{ name = "pydantic" },
|
|
1023
1021
|
{ name = "python-dotenv" },
|
|
1022
|
+
{ name = "typing-inspection" },
|
|
1024
1023
|
]
|
|
1025
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1024
|
+
sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234 }
|
|
1026
1025
|
wheels = [
|
|
1027
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1026
|
+
{ url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356 },
|
|
1028
1027
|
]
|
|
1029
1028
|
|
|
1030
1029
|
[[package]]
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
from mm_crypto_utils import Nodes, Proxies, retry_with_node_and_proxy
|
|
2
|
-
from mm_std import Result
|
|
3
|
-
from solana.exceptions import SolanaRpcException
|
|
4
|
-
from solana.rpc.core import RPCException
|
|
5
|
-
from solders.solders import InvalidParamsMessage, Pubkey, get_associated_token_address
|
|
6
|
-
|
|
7
|
-
from mm_sol.utils import get_async_client
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
async def get_balance(
|
|
11
|
-
node: str,
|
|
12
|
-
owner: str,
|
|
13
|
-
token: str,
|
|
14
|
-
token_account: str | None = None,
|
|
15
|
-
timeout: float = 5,
|
|
16
|
-
proxy: str | None = None,
|
|
17
|
-
) -> Result[int]:
|
|
18
|
-
response = None
|
|
19
|
-
try:
|
|
20
|
-
client = get_async_client(node, proxy=proxy, timeout=timeout)
|
|
21
|
-
if not token_account:
|
|
22
|
-
token_account = str(get_associated_token_address(Pubkey.from_string(owner), Pubkey.from_string(token)))
|
|
23
|
-
|
|
24
|
-
res = await client.get_token_account_balance(Pubkey.from_string(token_account))
|
|
25
|
-
response = res.to_json()
|
|
26
|
-
|
|
27
|
-
# Sometimes it not raise an error, but it returns this :(
|
|
28
|
-
if isinstance(res, InvalidParamsMessage) and "could not find account" in res.message:
|
|
29
|
-
return Result.success(0, {"response": response})
|
|
30
|
-
return Result.success(int(res.value.amount), {"response": response})
|
|
31
|
-
except RPCException as e:
|
|
32
|
-
if "could not find account" in str(e):
|
|
33
|
-
return Result.success(0, {"response": response, "rpc_exception": str(e)})
|
|
34
|
-
return Result.failure(e, {"response": response})
|
|
35
|
-
except SolanaRpcException as e:
|
|
36
|
-
return Result.failure((e.error_msg, e), {"response": response})
|
|
37
|
-
except Exception as e:
|
|
38
|
-
return Result.failure(e, {"response": response})
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
async def get_balance_with_retries(
|
|
42
|
-
retries: int,
|
|
43
|
-
nodes: Nodes,
|
|
44
|
-
proxies: Proxies,
|
|
45
|
-
*,
|
|
46
|
-
owner: str,
|
|
47
|
-
token: str,
|
|
48
|
-
token_account: str | None = None,
|
|
49
|
-
timeout: float = 5,
|
|
50
|
-
) -> Result[int]:
|
|
51
|
-
return await retry_with_node_and_proxy(
|
|
52
|
-
retries,
|
|
53
|
-
nodes,
|
|
54
|
-
proxies,
|
|
55
|
-
lambda node, proxy: get_balance(
|
|
56
|
-
node,
|
|
57
|
-
owner=owner,
|
|
58
|
-
token=token,
|
|
59
|
-
token_account=token_account,
|
|
60
|
-
timeout=timeout,
|
|
61
|
-
proxy=proxy,
|
|
62
|
-
),
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
async def get_decimals(node: str, token: str, timeout: float = 5, proxy: str | None = None) -> Result[int]:
|
|
67
|
-
response = None
|
|
68
|
-
try:
|
|
69
|
-
client = get_async_client(node, proxy=proxy, timeout=timeout)
|
|
70
|
-
res = await client.get_token_supply(Pubkey.from_string(token))
|
|
71
|
-
response = res.to_json()
|
|
72
|
-
return Result.success(res.value.decimals, {"response": response})
|
|
73
|
-
except Exception as e:
|
|
74
|
-
return Result.failure(e, {"response": response})
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
async def get_decimals_with_retries(
|
|
78
|
-
retries: int, nodes: Nodes, proxies: Proxies, *, token: str, timeout: float = 5
|
|
79
|
-
) -> Result[int]:
|
|
80
|
-
return await retry_with_node_and_proxy(
|
|
81
|
-
retries,
|
|
82
|
-
nodes,
|
|
83
|
-
proxies,
|
|
84
|
-
lambda node, proxy: get_decimals(node, token=token, proxy=proxy, timeout=timeout),
|
|
85
|
-
)
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|