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.
Files changed (49) hide show
  1. {mm_sol-0.6.0 → mm_sol-0.6.1}/PKG-INFO +2 -3
  2. {mm_sol-0.6.0 → mm_sol-0.6.1}/justfile +7 -4
  3. {mm_sol-0.6.0 → mm_sol-0.6.1}/pyproject.toml +3 -3
  4. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/calcs.py +7 -7
  5. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/balance_cmd.py +5 -6
  6. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/balances_cmd.py +8 -7
  7. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/transfer_cmd.py +12 -12
  8. mm_sol-0.6.1/src/mm_sol/retry.py +108 -0
  9. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/rpc.py +11 -26
  10. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/rpc_sync.py +9 -26
  11. mm_sol-0.6.1/src/mm_sol/spl_token.py +48 -0
  12. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/transfer.py +9 -70
  13. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_rpc_sync.py +0 -5
  14. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_spl_token.py +1 -1
  15. {mm_sol-0.6.0 → mm_sol-0.6.1}/uv.lock +10 -11
  16. mm_sol-0.6.0/src/mm_sol/spl_token.py +0 -85
  17. {mm_sol-0.6.0 → mm_sol-0.6.1}/.env.example +0 -0
  18. {mm_sol-0.6.0 → mm_sol-0.6.1}/.gitignore +0 -0
  19. {mm_sol-0.6.0 → mm_sol-0.6.1}/README.md +0 -0
  20. {mm_sol-0.6.0 → mm_sol-0.6.1}/dict.dic +0 -0
  21. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/__init__.py +0 -0
  22. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/account.py +0 -0
  23. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/__init__.py +0 -0
  24. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cli.py +0 -0
  25. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cli_utils.py +0 -0
  26. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/__init__.py +0 -0
  27. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/example_cmd.py +0 -0
  28. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/node_cmd.py +0 -0
  29. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/wallet/__init__.py +0 -0
  30. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/wallet/keypair_cmd.py +0 -0
  31. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/cmd/wallet/mnemonic_cmd.py +0 -0
  32. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/examples/balances.toml +0 -0
  33. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/examples/transfer.toml +0 -0
  34. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/cli/validators.py +0 -0
  35. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/constants.py +0 -0
  36. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/converters.py +0 -0
  37. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/py.typed +0 -0
  38. {mm_sol-0.6.0 → mm_sol-0.6.1}/src/mm_sol/utils.py +0 -0
  39. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/__init__.py +0 -0
  40. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/cli/__init__.py +0 -0
  41. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/cli/cmd/__init__.py +0 -0
  42. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/cli/cmd/wallet/__init__.py +0 -0
  43. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/cli/cmd/wallet/test_keypair_cmd.py +0 -0
  44. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/cli/cmd/wallet/test_mnemonic_cmd.py +0 -0
  45. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/conftest.py +0 -0
  46. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_account.py +0 -0
  47. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_client.py +0 -0
  48. {mm_sol-0.6.0 → mm_sol-0.6.1}/tests/test_converters.py +0 -0
  49. {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.0
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.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.0"
3
+ version = "0.6.1"
4
4
  description = ""
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
7
- "mm-crypto-utils>=0.3.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 rpc, spl_token
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 rpc.get_balance_with_retries(5, nodes, proxies, address=address)
24
- if res.is_error():
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.success(value)
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 spl_token.get_balance_with_retries(5, nodes, proxies, owner=owner, token=token)
41
- if res.is_error():
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.success(value)
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
- from mm_sol import rpc, spl_token
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 rpc.get_balance_with_retries(3, rpc_url, proxies, address=wallet_address)
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 spl_token.get_balance_with_retries(
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 spl_token.get_decimals_with_retries(3, rpc_url, proxies, token=token_address)
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
- from mm_sol import converters, rpc, spl_token
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 spl_token.get_decimals_with_retries(3, config.nodes, config.proxies, token=token_address)
36
- if res.is_error():
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 spl_token.get_balance_with_retries(3, config.nodes, config.proxies, owner=account, token=token_address))
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
- .ok
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 rpc.get_balance_with_retries(3, config.nodes, config.proxies, address=account))
66
+ (await retry.get_sol_balance(3, config.nodes, config.proxies, address=account))
66
67
  .map(lambda v: converters.lamports_to_sol(v))
67
- .ok
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
- from mm_sol import rpc, spl_token
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 spl_token.get_decimals_with_retries(3, self.nodes, self.proxies, token=self.token)
67
- if res.is_error():
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.is_error():
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.ok
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 transfer_token_with_retries(
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 transfer_sol_with_retries(
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.is_error():
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.ok
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 rpc.get_balance_with_retries(5, config.nodes, config.proxies, address=address)
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 spl_token.get_balance_with_retries(5, config.nodes, config.proxies, owner=address, token=config.token)
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.is_error():
27
- return res.to_result_failure()
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.to_result_failure(f"service_error: {err}")
31
+ return res.to_err_result(f"service_error: {err}")
33
32
  if "result" in parsed_body:
34
- return res.to_result_success(parsed_body["result"])
35
- return res.to_result_failure("unknown_response")
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.to_result_failure(e)
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.failure(f"service_error: {err}", {"response": response})
48
+ return Result.err(f"service_error: {err}", {"response": response})
50
49
  if "result" in response:
51
- return Result.success(response["result"], {"response": response})
52
- return Result.failure("unknown_response", {"response": response})
50
+ return Result.ok(response["result"], {"response": response})
51
+ return Result.err("unknown_response", {"response": response})
53
52
  except TimeoutError:
54
- return Result.failure("timeout", {"response": response})
53
+ return Result.err("timeout", {"response": response})
55
54
  except Exception as e:
56
- return Result.failure(e, {"response": response})
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.is_error():
95
- return res.to_result_failure()
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.to_result_failure(f"service_error: {err}")
100
+ return res.to_err_result(f"service_error: {err}")
101
101
  if "result" in json_body:
102
- return res.to_result_success(json_body["result"])
102
+ return res.to_ok_result(json_body["result"])
103
103
 
104
- return res.to_result_failure("unknown_response")
104
+ return res.to_err_result("unknown_response")
105
105
  except Exception as e:
106
- return res.to_result_failure(e)
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.is_error():
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 Result.success(result, res.extra)
177
+ return res.with_value(result)
178
178
  except Exception as e:
179
- return Result.failure(e, res.extra)
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.failure("invalid_private_key")
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.failure("no_token_account")
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.success(res.value, {"logs": logs})
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.failure("invalid_private_key")
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.success(res.value, {"response": data})
90
+ return Result.ok(res.value, {"response": data})
152
91
  except Exception as e:
153
- return Result.failure(e, {"response": data})
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.is_error():
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 Result.success(result, res.extra)
116
+ return res.with_value(result)
178
117
  except Exception as e:
179
- return Result.failure(e, res.extra)
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.ok == 0
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.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/2b/3b/584e21e4a471f6cbc7237e924c550ab4a19be8e0d2064e76a4a5f2f4fe1a/mm_crypto_utils-0.3.3-py3-none-any.whl", hash = "sha256:4d53940267a84f4a4dd00b832cedb76264d1d6fcacccdd0acc3c2ec2f79eacc7", size = 10092 },
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.0"
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.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.5"
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/ee/c3/22c61044cc48eaacc9b8dcf61d72de5085539d354ab10996c16de2979aed/mm_std-0.4.5-py3-none-any.whl", hash = "sha256:02b7269ad2c74c8551a906246b66fdd91ea5fb83f6108d5690da64780e31354e", size = 23604 },
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.8.1"
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/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 }
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/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 },
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