mm-sol 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,41 +1,196 @@
1
- import random
2
- from decimal import Decimal
1
+ import os
2
+ import sys
3
+ import time
4
+ from pathlib import Path
5
+ from typing import Annotated, Self
3
6
 
4
- from mm_std import BaseConfig, print_console, str_to_list
5
- from pydantic import StrictStr, field_validator
7
+ import mm_crypto_utils
8
+ from loguru import logger
9
+ from mm_crypto_utils import AddressToPrivate, TxRoute
10
+ from mm_std import BaseConfig, Err, utc_now
11
+ from pydantic import BeforeValidator, Field, model_validator
12
+ from solders.signature import Signature
6
13
 
7
- from mm_sol.cli import cli_utils
8
- from mm_sol.transfer import transfer_sol
14
+ from mm_sol import transfer
15
+ from mm_sol.account import get_public_key, is_address
16
+ from mm_sol.cli import calcs, cli_utils, validators
17
+ from mm_sol.cli.validators import Validators
18
+ from mm_sol.converters import lamports_to_sol
19
+ from mm_sol.utils import get_client
9
20
 
10
21
 
11
22
  class Config(BaseConfig):
12
- from_address: StrictStr
13
- private_key: StrictStr
14
- recipients: list[StrictStr]
15
- nodes: list[StrictStr]
16
- amount: Decimal
17
-
18
- @field_validator("recipients", "nodes", mode="before")
19
- def to_list_validator(cls, v: list[str] | str | None) -> list[str]:
20
- return str_to_list(v)
23
+ nodes: Annotated[list[str], BeforeValidator(Validators.nodes())]
24
+ routes: Annotated[list[TxRoute], BeforeValidator(Validators.routes(is_address))]
25
+ routes_from_file: Path | None = None
26
+ routes_to_file: Path | None = None
27
+ private_keys: Annotated[
28
+ AddressToPrivate, Field(default_factory=AddressToPrivate), BeforeValidator(Validators.private_keys(get_public_key))
29
+ ]
30
+ private_keys_file: Path | None = None
31
+ proxies_url: str | None = None
32
+ proxies: list[str] = Field(default_factory=list)
33
+ value: str
34
+ value_min_limit: str | None = None
35
+ delay: str | None = None # in seconds
36
+ round_ndigits: int = 5
37
+ log_debug: Annotated[Path | None, BeforeValidator(Validators.log_file())] = None
38
+ log_info: Annotated[Path | None, BeforeValidator(Validators.log_file())] = None
21
39
 
22
40
  @property
23
- def random_node(self) -> str:
24
- return random.choice(self.nodes)
41
+ def from_addresses(self) -> list[str]:
42
+ return [r.from_address for r in self.routes]
43
+
44
+ @model_validator(mode="after")
45
+ def final_validator(self) -> Self:
46
+ # routes_files
47
+ if self.routes_from_file and self.routes_to_file:
48
+ self.routes += TxRoute.from_files(self.routes_from_file, self.routes_to_file, is_address)
49
+ if not self.routes:
50
+ raise ValueError("routes is empty")
51
+
52
+ # load private keys from file
53
+ if self.private_keys_file:
54
+ self.private_keys.update(AddressToPrivate.from_file(self.private_keys_file, get_public_key))
55
+
56
+ # check all private keys exist
57
+ if not self.private_keys.contains_all_addresses(self.from_addresses):
58
+ raise ValueError("private keys are not set for all addresses")
59
+
60
+ # fetch proxies from proxies_url
61
+ proxies_url = self.proxies_url or os.getenv("MM_PROXIES_URL", "")
62
+ if proxies_url:
63
+ self.proxies += mm_crypto_utils.fetch_proxies_or_fatal(proxies_url)
64
+
65
+ # value
66
+ if not validators.is_valid_var_lamports(self.value, "balance"):
67
+ raise ValueError(f"wrong value: {self.value}")
68
+
69
+ # value_min_limit
70
+ if not validators.is_valid_var_lamports(self.value_min_limit):
71
+ raise ValueError(f"wrong value_min_limit: {self.value_min_limit}")
25
72
 
73
+ # delay
74
+ if not validators.is_valid_var_lamports(self.delay):
75
+ raise ValueError(f"wrong delay: {self.delay}")
26
76
 
27
- def run(config_path: str, print_config: bool) -> None:
77
+ return self
78
+
79
+
80
+ def run(
81
+ config_path: str,
82
+ *,
83
+ print_balances: bool,
84
+ print_config: bool,
85
+ debug: bool,
86
+ no_confirmation: bool,
87
+ emulate: bool,
88
+ ) -> None:
28
89
  config = Config.read_config_or_exit(config_path)
29
- cli_utils.print_config_and_exit(print_config, config)
30
-
31
- result = {}
32
- for recipient in config.recipients:
33
- res = transfer_sol(
34
- from_address=config.from_address,
35
- private_key_base58=config.private_key,
36
- recipient_address=recipient,
37
- amount_sol=config.amount,
38
- nodes=config.nodes,
90
+
91
+ if print_config:
92
+ config.print_and_exit({"private_keys", "proxies"})
93
+
94
+ mm_crypto_utils.init_logger(debug, config.log_debug, config.log_info)
95
+
96
+ if print_balances:
97
+ cli_utils.print_balances(config.nodes, config.from_addresses, round_ndigits=config.round_ndigits, proxies=config.proxies)
98
+ sys.exit(0)
99
+
100
+ _run_transfers(config, no_confirmation=no_confirmation, emulate=emulate)
101
+
102
+
103
+ def _run_transfers(config: Config, *, no_confirmation: bool, emulate: bool) -> None:
104
+ logger.info(f"started at {utc_now()} UTC")
105
+ logger.debug(f"config={config.model_dump(exclude={'private_keys'}) | {'version': cli_utils.get_version()}}")
106
+ for i, route in enumerate(config.routes):
107
+ _transfer(
108
+ from_address=route.from_address,
109
+ to_address=route.to_address,
110
+ config=config,
111
+ no_confirmation=no_confirmation,
112
+ emulate=emulate,
39
113
  )
40
- result[recipient] = res.ok_or_err()
41
- print_console(result, print_json=True)
114
+ if not emulate and config.delay is not None and i < len(config.routes) - 1:
115
+ delay_value = mm_crypto_utils.calc_decimal_value(config.delay)
116
+ logger.debug(f"delay {delay_value} seconds")
117
+ time.sleep(float(delay_value))
118
+ logger.info(f"finished at {utc_now()} UTC")
119
+
120
+
121
+ def _transfer(*, from_address: str, to_address: str, config: Config, no_confirmation: bool, emulate: bool) -> None:
122
+ log_prefix = f"{from_address}->{to_address}"
123
+ fee = 5000
124
+ # get value
125
+ value_res = calcs.calc_sol_value(
126
+ nodes=config.nodes, value_str=config.value, address=from_address, proxies=config.proxies, fee=fee
127
+ )
128
+ logger.debug(f"{log_prefix}value={value_res.ok_or_err()}")
129
+ if isinstance(value_res, Err):
130
+ logger.info(f"{log_prefix}calc value error, {value_res.err}")
131
+ return
132
+ value = value_res.ok
133
+
134
+ # value_min_limit
135
+ if calcs.is_sol_value_less_min_limit(config.value_min_limit, value, log_prefix=log_prefix):
136
+ return
137
+
138
+ tx_params = {
139
+ "fee": fee,
140
+ "value": value,
141
+ "to": to_address,
142
+ }
143
+
144
+ # emulate?
145
+ if emulate:
146
+ msg = f"{log_prefix}: emulate, value={lamports_to_sol(value, config.round_ndigits)}SOL,"
147
+ msg += f" fee={fee}"
148
+ logger.info(msg)
149
+ return
150
+
151
+ logger.debug(f"{log_prefix}: tx_params={tx_params}")
152
+
153
+ res = transfer.transfer_sol_with_retries(
154
+ nodes=config.nodes,
155
+ from_address=from_address,
156
+ private_key=config.private_keys[from_address],
157
+ to_address=to_address,
158
+ lamports=value,
159
+ proxies=config.proxies,
160
+ retries=3,
161
+ )
162
+
163
+ if isinstance(res, Err):
164
+ logger.info(f"{log_prefix}: send_error: {res.err}")
165
+ return
166
+ signature = res.ok
167
+
168
+ if no_confirmation:
169
+ msg = f"{log_prefix}: sig={signature}, value={lamports_to_sol(value, config.round_ndigits)}"
170
+ logger.info(msg)
171
+ else:
172
+ logger.debug(f"{log_prefix}: sig={signature}, waiting for confirmation")
173
+ status = "UNKNOWN"
174
+ if _wait_confirmation(config, signature, log_prefix):
175
+ status = "OK"
176
+ msg = f"{log_prefix}: sig={signature}, value={lamports_to_sol(value, config.round_ndigits)}, status={status}"
177
+ logger.info(msg)
178
+
179
+
180
+ def _wait_confirmation(config: Config, signature: Signature, log_prefix: str) -> bool:
181
+ count = 0
182
+ while True:
183
+ try:
184
+ node = mm_crypto_utils.random_node(config.nodes)
185
+ proxy = mm_crypto_utils.random_proxy(config.proxies)
186
+ client = get_client(node, proxy=proxy)
187
+ res = client.get_transaction(signature)
188
+ if res.value and res.value.slot: # check for tx error
189
+ return True
190
+ except Exception as e:
191
+ logger.error(f"{log_prefix}: can't get confirmation, error={e}")
192
+ time.sleep(1)
193
+ count += 1
194
+ if count > 30:
195
+ logger.error(f"{log_prefix}: can't get confirmation, timeout")
196
+ return False
@@ -0,0 +1,135 @@
1
+ import os
2
+ import sys
3
+ import time
4
+ from pathlib import Path
5
+ from typing import Annotated, Self
6
+
7
+ import mm_crypto_utils
8
+ import typer
9
+ from loguru import logger
10
+ from mm_crypto_utils import AddressToPrivate, TxRoute
11
+ from mm_std import BaseConfig, Err, fatal, utc_now
12
+ from pydantic import AfterValidator, BeforeValidator, Field, model_validator
13
+
14
+ from mm_sol.account import get_public_key, is_address
15
+ from mm_sol.cli import calcs, cli_utils
16
+ from mm_sol.cli.validators import Validators
17
+ from mm_sol.token import get_decimals_with_retries
18
+
19
+
20
+ class Config(BaseConfig):
21
+ nodes: Annotated[list[str], BeforeValidator(Validators.nodes())]
22
+ routes: Annotated[list[TxRoute], BeforeValidator(Validators.routes(is_address))]
23
+ routes_from_file: Path | None = None
24
+ routes_to_file: Path | None = None
25
+ private_keys: Annotated[
26
+ AddressToPrivate, Field(default_factory=AddressToPrivate), BeforeValidator(Validators.private_keys(get_public_key))
27
+ ]
28
+ private_keys_file: Path | None = None
29
+ proxies_url: str | None = None
30
+ proxies: list[str] = Field(default_factory=list)
31
+ token: Annotated[str, AfterValidator(Validators.address(is_address))]
32
+ value: str
33
+ value_min_limit: str | None = None
34
+ delay: str | None = None # in seconds
35
+ round_ndigits: int = 5
36
+ log_debug: Annotated[Path | None, BeforeValidator(Validators.log_file())] = None
37
+ log_info: Annotated[Path | None, BeforeValidator(Validators.log_file())] = None
38
+
39
+ @property
40
+ def from_addresses(self) -> list[str]:
41
+ return [r.from_address for r in self.routes]
42
+
43
+ @model_validator(mode="after")
44
+ def final_validator(self) -> Self:
45
+ # routes_files
46
+ if self.routes_from_file and self.routes_to_file:
47
+ self.routes += TxRoute.from_files(self.routes_from_file, self.routes_to_file, is_address)
48
+ if not self.routes:
49
+ raise ValueError("routes is empty")
50
+
51
+ # load private keys from file
52
+ if self.private_keys_file:
53
+ self.private_keys.update(AddressToPrivate.from_file(self.private_keys_file, get_public_key))
54
+
55
+ # check all private keys exist
56
+ if not self.private_keys.contains_all_addresses(self.from_addresses):
57
+ raise ValueError("private keys are not set for all addresses")
58
+
59
+ # fetch proxies from proxies_url
60
+ proxies_url = self.proxies_url or os.getenv("MM_PROXIES_URL", "")
61
+ if proxies_url:
62
+ self.proxies += mm_crypto_utils.fetch_proxies_or_fatal(proxies_url)
63
+
64
+ return self
65
+
66
+
67
+ def run(
68
+ config_path: str,
69
+ *,
70
+ print_balances: bool,
71
+ print_config: bool,
72
+ debug: bool,
73
+ no_confirmation: bool,
74
+ emulate: bool,
75
+ ) -> None:
76
+ config = Config.read_config_or_exit(config_path)
77
+
78
+ if print_config:
79
+ config.print_and_exit({"private_keys", "proxies"})
80
+
81
+ mm_crypto_utils.init_logger(debug, config.log_debug, config.log_info)
82
+
83
+ decimals_res = get_decimals_with_retries(config.nodes, config.token, retries=3, proxies=config.proxies)
84
+ if isinstance(decimals_res, Err):
85
+ fatal(f"can't get decimals for token={config.token}, error={decimals_res.err}")
86
+
87
+ token_decimals = decimals_res.ok
88
+ logger.debug(f"token decimals={token_decimals}")
89
+
90
+ if print_balances:
91
+ # cli_utils.print_balances(config.nodes, config.from_addresses, round_ndigits=config.round_ndigits, proxies=config.proxies) # noqa: E501
92
+ typer.echo("Not implemented yet")
93
+ sys.exit(0)
94
+
95
+ _run_transfers(config, token_decimals, no_confirmation=no_confirmation, emulate=emulate)
96
+
97
+
98
+ def _run_transfers(config: Config, token_decimals: int, *, no_confirmation: bool, emulate: bool) -> None:
99
+ logger.info(f"started at {utc_now()} UTC")
100
+ logger.debug(f"config={config.model_dump(exclude={'private_keys'}) | {'version': cli_utils.get_version()}}")
101
+ for i, route in enumerate(config.routes):
102
+ _transfer(
103
+ route=route,
104
+ token_decimals=token_decimals,
105
+ config=config,
106
+ no_confirmation=no_confirmation,
107
+ emulate=emulate,
108
+ )
109
+ if not emulate and config.delay is not None and i < len(config.routes) - 1:
110
+ delay_value = mm_crypto_utils.calc_decimal_value(config.delay)
111
+ logger.debug(f"delay {delay_value} seconds")
112
+ time.sleep(float(delay_value))
113
+ logger.info(f"finished at {utc_now()} UTC")
114
+
115
+
116
+ def _transfer(*, route: TxRoute, config: Config, token_decimals: int, no_confirmation: bool, emulate: bool) -> None:
117
+ log_prefix = f"{route.from_address}->{route.to_address}"
118
+ fee = 5000
119
+
120
+ # get value
121
+ value_res = calcs.calc_token_value(
122
+ nodes=config.nodes,
123
+ value_str=config.value,
124
+ wallet_address=route.from_address,
125
+ proxies=config.proxies,
126
+ token_mint_address=config.token,
127
+ token_decimals=token_decimals,
128
+ )
129
+ logger.debug(f"{log_prefix}: value={value_res.ok_or_err()}")
130
+ if isinstance(value_res, Err):
131
+ logger.info(f"{log_prefix}: calc value error, {value_res.err}")
132
+ return
133
+ value = value_res.ok
134
+
135
+ logger.debug(f"{log_prefix}: value={value}, fee={fee}, no_confirmation={no_confirmation}, emulate={emulate}")
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- from mm_std import print_console
3
+ from mm_std import print_json
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
- print_console({"public": public, "private_base58": private_base58, "private_arr": private_arr}, print_json=True)
19
+ print_json({"public": public, "private_base58": private_base58, "private_arr": private_arr})
@@ -1,4 +1,4 @@
1
- from mm_std import print_console
1
+ from mm_std import print_json
2
2
 
3
3
  from mm_sol.account import generate_account, get_private_key_arr_str
4
4
 
@@ -11,4 +11,4 @@ def run(limit: int, array: bool) -> None:
11
11
  if array:
12
12
  private_key = get_private_key_arr_str(acc.private_key_base58)
13
13
  result[acc.public_key] = private_key
14
- print_console(result, print_json=True)
14
+ print_json(result)
@@ -1,8 +1,11 @@
1
- from_address: ERB7SPx1XxH35pLu4Lkg9ChPQgsnwJGLZG8RsEE87XNw
2
- private_key: 3h92ZLyibvqQ9f923s66eN7V1iyG5WRRo3y9nciH4swUiLrkrYRdRM8q3DrtJ9JjTjiU9BT2r2qNzuiCmcZPVvqV
3
- amount: 1.2 # in SOL
4
- recipients:
5
- - Rjg3K9PPDm1B5bmUP7eXZNfmApgvDPF9SrTh12dLRH9
1
+ tx_routes: |
2
+ Bd8CxCTLez2ckVTqEJjuZkWjYFSRbo8fA1qYbd7yFVP9 Eaft9xXzfgbRqsHd65WspoaxTtH7pkznM9YA8tsDKGwj
3
+ Fc2TRJVCpFZpRz56mFnQETctib1zwFnwHcS7HoQSgUzZ EVJctTWikt29rUXBf49tyQdK87x837HtvpCwqeSjp1Ur
6
4
 
5
+
6
+ private_keys_file: ./path/to/privates.txt
7
+ value: 0.012 sol
8
+
9
+ proxies_url: https://site.com/api/get-proxies
7
10
  nodes: |
8
- https://api.mainnet-beta.solana.com
11
+ https://api.devnet.solana.com
@@ -0,0 +1,17 @@
1
+ from mm_crypto_utils import ConfigValidators
2
+
3
+ from mm_sol.cli import calcs
4
+
5
+
6
+ def is_valid_var_lamports(value: str | None, base_name: str = "var", decimals: int | None = None) -> bool:
7
+ if value is None:
8
+ return True # check for None on BaseModel.field type level
9
+ try:
10
+ calcs.calc_var_value(value, var_value=123, var_name=base_name, decimals=decimals)
11
+ return True # noqa: TRY300
12
+ except ValueError:
13
+ return False
14
+
15
+
16
+ class Validators(ConfigValidators):
17
+ pass
mm_sol/converters.py ADDED
@@ -0,0 +1,33 @@
1
+ from decimal import Decimal
2
+
3
+
4
+ def lamports_to_sol(lamports: int, ndigits: int = 4) -> Decimal:
5
+ return Decimal(str(round(lamports / 10**9, ndigits=ndigits)))
6
+
7
+
8
+ def sol_to_lamports(sol: Decimal) -> int:
9
+ return int(sol * 10**9)
10
+
11
+
12
+ def to_lamports(value: str | int | Decimal, decimals: int | None = None) -> int:
13
+ if isinstance(value, int):
14
+ return value
15
+ if isinstance(value, Decimal):
16
+ if value != value.to_integral_value():
17
+ raise ValueError(f"value must be integral number: {value}")
18
+ return int(value)
19
+ if isinstance(value, str):
20
+ value = value.lower().replace(" ", "").strip()
21
+ if value.endswith("sol"):
22
+ value = value.replace("sol", "")
23
+ return sol_to_lamports(Decimal(value))
24
+ if value.endswith("t"):
25
+ if decimals is None:
26
+ raise ValueError("t without decimals")
27
+ value = value.removesuffix("t")
28
+ return int(Decimal(value) * 10**decimals)
29
+ if value.isdigit():
30
+ return int(value)
31
+ raise ValueError("wrong value " + value)
32
+
33
+ raise ValueError(f"value has a wrong type: {type(value)}")
mm_sol/solana_cli.py CHANGED
@@ -26,7 +26,6 @@ class StakeAccount(BaseModel):
26
26
  vote: str | None = Field(None, alias="delegatedVoteAccountAddress")
27
27
 
28
28
  @field_validator("balance")
29
- @classmethod
30
29
  def from_lamports_to_sol(cls, v: int | None) -> float | None:
31
30
  if v:
32
31
  return v / 1_000_000_000
@@ -44,7 +43,6 @@ class Stake(BaseModel):
44
43
  lock_time: int | None = Field(None, alias="unixTimestamp")
45
44
 
46
45
  @field_validator("balance", "delegated", "active")
47
- @classmethod
48
46
  def from_lamports_to_sol(cls, v: int | None) -> float | None:
49
47
  if v:
50
48
  return v / 1_000_000_000
mm_sol/token.py CHANGED
@@ -1,86 +1,20 @@
1
+ import mm_crypto_utils
2
+ from mm_crypto_utils import Nodes, Proxies
1
3
  from mm_std import Err, Ok, Result
2
- from solana.exceptions import SolanaRpcException
3
- from solana.rpc.types import TokenAccountOpts
4
4
  from solders.pubkey import Pubkey
5
5
 
6
- from mm_sol.types import Nodes, Proxies
7
- from mm_sol.utils import get_client, get_node, get_proxy
8
-
9
-
10
- def get_balance(
11
- node: str,
12
- owner_address: str,
13
- token_mint_address: str,
14
- token_account: str | None = None,
15
- timeout: float = 10,
16
- proxy: str | None = None,
17
- no_token_accounts_return_zero: bool = True,
18
- ) -> Result[int]:
19
- try:
20
- client = get_client(node, proxy=proxy, timeout=timeout)
21
- if token_account:
22
- res_balance = client.get_token_account_balance(Pubkey.from_string(token_account))
23
- return Ok(int(res_balance.value.amount))
24
-
25
- res_accounts = client.get_token_accounts_by_owner(
26
- Pubkey.from_string(owner_address),
27
- TokenAccountOpts(mint=Pubkey.from_string(token_mint_address)),
28
- )
29
-
30
- if no_token_accounts_return_zero and not res_accounts.value:
31
- return Ok(0)
32
- if not res_accounts.value:
33
- return Err("no_token_accounts")
34
-
35
- token_accounts = [a.pubkey for a in res_accounts.value]
36
- balances = []
37
- for token_account_ in token_accounts:
38
- res = client.get_token_account_balance(token_account_)
39
- if res.value: # type:ignore[truthy-bool]
40
- balances.append(int(res.value.amount))
41
-
42
- if len(balances) > 1:
43
- return Err("there are many non empty token accounts, set token_account explicitly")
44
- return Ok(balances[0])
45
- except SolanaRpcException as e:
46
- return Err(e.error_msg)
47
- except Exception as e:
48
- return Err(e)
49
-
50
-
51
- def get_balance_with_retries(
52
- nodes: Nodes,
53
- owner_address: str,
54
- token_mint_address: str,
55
- retries: int,
56
- token_account: str | None = None,
57
- timeout: float = 10,
58
- proxies: Proxies = None,
59
- no_token_accounts_return_zero: bool = True,
60
- ) -> Result[int]:
61
- res: Result[int] = Err("not started yet")
62
- for _ in range(retries):
63
- res = get_balance(
64
- get_node(nodes),
65
- owner_address,
66
- token_mint_address,
67
- token_account,
68
- timeout=timeout,
69
- proxy=get_proxy(proxies),
70
- no_token_accounts_return_zero=no_token_accounts_return_zero,
71
- )
72
- if res.is_ok():
73
- return res
74
- return res
6
+ from mm_sol.utils import get_client
75
7
 
76
8
 
77
9
  def get_decimals(node: str, token_mint_address: str, timeout: float = 10, proxy: str | None = None) -> Result[int]:
10
+ data = None
78
11
  try:
79
12
  client = get_client(node, proxy=proxy, timeout=timeout)
80
13
  res = client.get_token_supply(Pubkey.from_string(token_mint_address))
14
+ data = res
81
15
  return Ok(res.value.decimals)
82
16
  except Exception as e:
83
- return Err(e)
17
+ return Err(e, data=data)
84
18
 
85
19
 
86
20
  def get_decimals_with_retries(
@@ -88,46 +22,12 @@ def get_decimals_with_retries(
88
22
  ) -> Result[int]:
89
23
  res: Result[int] = Err("not started yet")
90
24
  for _ in range(retries):
91
- res = get_decimals(get_node(nodes), token_mint_address, timeout=timeout, proxy=get_proxy(proxies))
25
+ res = get_decimals(
26
+ node=mm_crypto_utils.random_node(nodes),
27
+ token_mint_address=token_mint_address,
28
+ timeout=timeout,
29
+ proxy=mm_crypto_utils.random_proxy(proxies),
30
+ )
92
31
  if res.is_ok():
93
32
  return res
94
33
  return res
95
-
96
-
97
- # def transfer_to_wallet_address(
98
- # *,
99
- # node: str,
100
- # private_key: str,
101
- # recipient_wallet_address: str,
102
- # token_mint_address: str,
103
- # amount: int,
104
- # ) -> Result[str]:
105
- # try:
106
- # keypair = account.get_keypair(private_key)
107
- # token_client = Token(Client(node), Pubkey.from_string(token_mint_address), program_id=TOKEN_PROGRAM_ID, payer=keypair)
108
- #
109
- # # get from_token_account
110
- # res = token_client.get_accounts(keypair.public_key)
111
- # token_accounts = res["result"]["value"]
112
- # if len(token_accounts) > 1:
113
- # return Result(error="many_from_token_accounts", data=res)
114
- # from_token_account = Pubkey.from_string(token_accounts[0]["pubkey"])
115
- #
116
- # # get to_token_account
117
- # res = token_client.get_accounts(Pubkey.from_string(recipient_wallet_address))
118
- # token_accounts = res["result"]["value"]
119
- # if len(token_accounts) > 1:
120
- # return Result(error="many_to_token_accounts", data=res)
121
- # elif len(token_accounts) == 1:
122
- # to_token_account = Pubkey.from_string(token_accounts[0]["pubkey"])
123
- # else: # create a new to_token_account
124
- # to_token_account = token_client.create_account(owner=Pubkey.from_string(recipient_wallet_address))
125
- #
126
- # res = token_client.transfer(source=from_token_account, dest=to_token_account, owner=keypair, amount=amount)
127
- # if res.get("result"):
128
- # return Result(ok=res.get("result"), data=res)
129
- # return Result(error="unknown_response", data=res)
130
- # except RPCException as e:
131
- # return Result(error="rcp_exception", data=str(e))
132
- # except Exception as e:
133
- # return Result(error="exception", data=str(e))