mm-sol 0.5.9__py3-none-any.whl → 0.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mm_sol/account.py +0 -26
- mm_sol/cli/calcs.py +15 -19
- mm_sol/cli/cli.py +16 -13
- mm_sol/cli/cmd/balance_cmd.py +19 -23
- mm_sol/cli/cmd/balances_cmd.py +29 -29
- mm_sol/cli/cmd/node_cmd.py +2 -2
- mm_sol/cli/cmd/transfer_cmd.py +53 -58
- mm_sol/retry.py +108 -0
- mm_sol/rpc.py +40 -206
- mm_sol/rpc_sync.py +215 -0
- mm_sol/spl_token.py +48 -0
- mm_sol/transfer.py +27 -84
- {mm_sol-0.5.9.dist-info → mm_sol-0.6.1.dist-info}/METADATA +2 -2
- mm_sol-0.6.1.dist-info/RECORD +31 -0
- mm_sol/async_rpc.py +0 -42
- mm_sol/balance.py +0 -158
- mm_sol/block.py +0 -58
- mm_sol/rpc_async.py +0 -65
- mm_sol/solana_cli.py +0 -252
- mm_sol/token.py +0 -33
- mm_sol/token_async.py +0 -40
- mm_sol-0.5.9.dist-info/RECORD +0 -35
- {mm_sol-0.5.9.dist-info → mm_sol-0.6.1.dist-info}/WHEEL +0 -0
- {mm_sol-0.5.9.dist-info → mm_sol-0.6.1.dist-info}/entry_points.txt +0 -0
mm_sol/transfer.py
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import mm_crypto_utils
|
|
2
1
|
import pydash
|
|
3
|
-
from
|
|
4
|
-
from mm_std import Err, Ok, Result
|
|
2
|
+
from mm_std import Result
|
|
5
3
|
from pydantic import BaseModel
|
|
6
4
|
from solders.message import Message
|
|
7
5
|
from solders.pubkey import Pubkey
|
|
8
6
|
from solders.signature import Signature
|
|
9
7
|
from solders.system_program import TransferParams, transfer
|
|
10
8
|
from solders.transaction import Transaction
|
|
11
|
-
from spl.token.
|
|
9
|
+
from spl.token.async_client import AsyncToken
|
|
12
10
|
from spl.token.constants import TOKEN_PROGRAM_ID
|
|
13
11
|
from spl.token.instructions import get_associated_token_address
|
|
14
12
|
|
|
15
|
-
from mm_sol import
|
|
13
|
+
from mm_sol import rpc_sync, utils
|
|
16
14
|
from mm_sol.account import check_private_key, get_keypair
|
|
17
15
|
|
|
18
16
|
|
|
19
|
-
def transfer_token(
|
|
17
|
+
async def transfer_token(
|
|
20
18
|
*,
|
|
21
19
|
node: str,
|
|
22
20
|
token_mint_address: str | Pubkey,
|
|
@@ -29,73 +27,44 @@ def transfer_token(
|
|
|
29
27
|
timeout: float = 10,
|
|
30
28
|
create_token_account_if_not_exists: bool = True,
|
|
31
29
|
) -> Result[Signature]:
|
|
30
|
+
# TODO: try/except this function!!!
|
|
32
31
|
acc = get_keypair(private_key)
|
|
33
32
|
if not check_private_key(from_address, private_key):
|
|
34
|
-
return
|
|
33
|
+
return Result.err("invalid_private_key")
|
|
35
34
|
|
|
36
35
|
from_address = utils.pubkey(from_address)
|
|
37
36
|
token_mint_address = utils.pubkey(token_mint_address)
|
|
38
37
|
to_address = utils.pubkey(to_address)
|
|
39
38
|
|
|
40
|
-
client = utils.
|
|
41
|
-
token_client =
|
|
39
|
+
client = utils.get_async_client(node, proxy=proxy, timeout=timeout)
|
|
40
|
+
token_client = AsyncToken(conn=client, pubkey=token_mint_address, program_id=TOKEN_PROGRAM_ID, payer=acc)
|
|
42
41
|
|
|
43
42
|
recipient_token_account = get_associated_token_address(to_address, token_mint_address, token_program_id=TOKEN_PROGRAM_ID)
|
|
44
43
|
from_token_account = get_associated_token_address(from_address, token_mint_address, token_program_id=TOKEN_PROGRAM_ID)
|
|
45
|
-
|
|
44
|
+
logs: list[object] = []
|
|
46
45
|
|
|
47
|
-
account_info_res = client.get_account_info(recipient_token_account)
|
|
46
|
+
account_info_res = await client.get_account_info(recipient_token_account)
|
|
48
47
|
if account_info_res.value is None:
|
|
49
48
|
if create_token_account_if_not_exists:
|
|
50
49
|
create_account_res = token_client.create_associated_token_account(to_address, skip_confirmation=False)
|
|
51
|
-
|
|
50
|
+
logs.append(create_account_res)
|
|
52
51
|
else:
|
|
53
|
-
return
|
|
52
|
+
return Result.err("no_token_account")
|
|
54
53
|
|
|
55
|
-
res = token_client.transfer_checked(
|
|
54
|
+
res = await token_client.transfer_checked(
|
|
56
55
|
source=from_token_account,
|
|
57
56
|
dest=recipient_token_account,
|
|
58
57
|
owner=from_address,
|
|
59
58
|
amount=amount,
|
|
60
59
|
decimals=decimals,
|
|
60
|
+
multi_signers=None,
|
|
61
61
|
)
|
|
62
|
-
|
|
62
|
+
logs.append(res)
|
|
63
63
|
|
|
64
|
-
return
|
|
64
|
+
return Result.ok(res.value, {"logs": logs})
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def
|
|
68
|
-
*,
|
|
69
|
-
nodes: Nodes,
|
|
70
|
-
token_mint_address: str | Pubkey,
|
|
71
|
-
from_address: str | Pubkey,
|
|
72
|
-
private_key: str,
|
|
73
|
-
to_address: str | Pubkey,
|
|
74
|
-
amount: int, # smallest unit
|
|
75
|
-
decimals: int,
|
|
76
|
-
proxies: Proxies = None,
|
|
77
|
-
timeout: float = 10,
|
|
78
|
-
retries: int = 3,
|
|
79
|
-
) -> Result[Signature]:
|
|
80
|
-
res: Result[Signature] = Err("not started yet")
|
|
81
|
-
for _ in range(retries):
|
|
82
|
-
res = transfer_token(
|
|
83
|
-
node=mm_crypto_utils.random_node(nodes),
|
|
84
|
-
token_mint_address=token_mint_address,
|
|
85
|
-
from_address=from_address,
|
|
86
|
-
private_key=private_key,
|
|
87
|
-
to_address=to_address,
|
|
88
|
-
amount=amount,
|
|
89
|
-
decimals=decimals,
|
|
90
|
-
proxy=mm_crypto_utils.random_proxy(proxies),
|
|
91
|
-
timeout=timeout,
|
|
92
|
-
)
|
|
93
|
-
if res.is_ok():
|
|
94
|
-
return res
|
|
95
|
-
return res
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def transfer_sol(
|
|
67
|
+
async def transfer_sol(
|
|
99
68
|
*,
|
|
100
69
|
node: str,
|
|
101
70
|
from_address: str,
|
|
@@ -107,46 +76,20 @@ def transfer_sol(
|
|
|
107
76
|
) -> Result[Signature]:
|
|
108
77
|
acc = get_keypair(private_key)
|
|
109
78
|
if not check_private_key(from_address, private_key):
|
|
110
|
-
return
|
|
79
|
+
return Result.err("invalid_private_key")
|
|
111
80
|
|
|
112
|
-
client = utils.
|
|
81
|
+
client = utils.get_async_client(node, proxy=proxy, timeout=timeout)
|
|
113
82
|
data = None
|
|
114
83
|
try:
|
|
115
84
|
ixs = [transfer(TransferParams(from_pubkey=acc.pubkey(), to_pubkey=Pubkey.from_string(to_address), lamports=lamports))]
|
|
116
85
|
msg = Message(ixs, acc.pubkey())
|
|
117
|
-
|
|
118
|
-
|
|
86
|
+
blockhash = await client.get_latest_blockhash()
|
|
87
|
+
tx = Transaction([acc], msg, blockhash.value.blockhash)
|
|
88
|
+
res = await client.send_transaction(tx)
|
|
119
89
|
data = res.to_json()
|
|
120
|
-
return
|
|
90
|
+
return Result.ok(res.value, {"response": data})
|
|
121
91
|
except Exception as e:
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def transfer_sol_with_retries(
|
|
126
|
-
*,
|
|
127
|
-
nodes: Nodes,
|
|
128
|
-
from_address: str,
|
|
129
|
-
private_key: str,
|
|
130
|
-
to_address: str,
|
|
131
|
-
lamports: int,
|
|
132
|
-
proxies: Proxies = None,
|
|
133
|
-
timeout: float = 10,
|
|
134
|
-
retries: int = 3,
|
|
135
|
-
) -> Result[Signature]:
|
|
136
|
-
res: Result[Signature] = Err("not started yet")
|
|
137
|
-
for _ in range(retries):
|
|
138
|
-
res = transfer_sol(
|
|
139
|
-
node=mm_crypto_utils.random_node(nodes),
|
|
140
|
-
from_address=from_address,
|
|
141
|
-
private_key=private_key,
|
|
142
|
-
to_address=to_address,
|
|
143
|
-
lamports=lamports,
|
|
144
|
-
proxy=mm_crypto_utils.random_proxy(proxies),
|
|
145
|
-
timeout=timeout,
|
|
146
|
-
)
|
|
147
|
-
if res.is_ok():
|
|
148
|
-
return res
|
|
149
|
-
return res
|
|
92
|
+
return Result.err(e, {"response": data})
|
|
150
93
|
|
|
151
94
|
|
|
152
95
|
class SolTransferInfo(BaseModel):
|
|
@@ -156,7 +99,7 @@ class SolTransferInfo(BaseModel):
|
|
|
156
99
|
|
|
157
100
|
|
|
158
101
|
def find_sol_transfers(node: str, tx_signature: str) -> Result[list[SolTransferInfo]]:
|
|
159
|
-
res =
|
|
102
|
+
res = rpc_sync.get_transaction(node, tx_signature, encoding="jsonParsed")
|
|
160
103
|
if res.is_err():
|
|
161
104
|
return res # type: ignore[return-value]
|
|
162
105
|
result = []
|
|
@@ -170,6 +113,6 @@ def find_sol_transfers(node: str, tx_signature: str) -> Result[list[SolTransferI
|
|
|
170
113
|
lamports = pydash.get(ix, "parsed.info.lamports")
|
|
171
114
|
if source and destination and lamports:
|
|
172
115
|
result.append(SolTransferInfo(source=source, destination=destination, lamports=lamports))
|
|
173
|
-
return
|
|
116
|
+
return res.with_value(result)
|
|
174
117
|
except Exception as e:
|
|
175
|
-
return
|
|
118
|
+
return Result.err(e, res.extra)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mm-sol
|
|
3
|
-
Version: 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.
|
|
7
|
+
Requires-Dist: mm-crypto-utils>=0.3.4
|
|
8
8
|
Requires-Dist: mnemonic==0.21
|
|
9
9
|
Requires-Dist: socksio>=1.0.0
|
|
10
10
|
Requires-Dist: solana~=0.36.6
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
mm_sol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
mm_sol/account.py,sha256=cVcxRQBuV_Gfm2WgQIwaYuAQijeIJqDDxLC22PN0XSs,3493
|
|
3
|
+
mm_sol/constants.py,sha256=WSpfz5_cq_8XbIrNFJGu9okwbfPTL00zsyR_k9-7O0o,29
|
|
4
|
+
mm_sol/converters.py,sha256=rBxe3SIADZS8hG7TYl4FgjmvKH-ykaTmNbnWWQDiFZ4,1430
|
|
5
|
+
mm_sol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
mm_sol/retry.py,sha256=9ejCcrHbJFOcZVKdbUASynLs1YHw04VAIKGH8mIJ1OI,2894
|
|
7
|
+
mm_sol/rpc.py,sha256=sYZ6y47qQa2WOHul9H3gcM4tJ61Z2yy6UpITR2DnX8s,2457
|
|
8
|
+
mm_sol/rpc_sync.py,sha256=vxWh1nmTgDelvfBDo9haxQmpDnXFZzeInsKIw3LLyy0,7183
|
|
9
|
+
mm_sol/spl_token.py,sha256=hEWVch17EdudlL_tj3ZInCa9wB0iK8hwwpFucmVi7Pc,1941
|
|
10
|
+
mm_sol/transfer.py,sha256=6CXzeMe91JtG8SuTadYQ-DLHJFyskytmBItGVEPh7Os,4410
|
|
11
|
+
mm_sol/utils.py,sha256=oD06NsMSMhN6lqsM6mSgLTtiKwA1uAsen9WR82ofRTE,923
|
|
12
|
+
mm_sol/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
mm_sol/cli/calcs.py,sha256=TR-mWQWYyHPdJYi_rvqiebqBqRsXsaqaSdvwCoSax1g,1675
|
|
14
|
+
mm_sol/cli/cli.py,sha256=0MflYgO-UzFdoaXFCIBuQ1Y6AOQUquX__AhSWOO64KY,4735
|
|
15
|
+
mm_sol/cli/cli_utils.py,sha256=nFdY8tJFZxyssEBEFCc3VTNJt447e6vMnugx4GBPL4o,1840
|
|
16
|
+
mm_sol/cli/validators.py,sha256=M_Rr7JoG3TUYTDAGkjQLDH6l9i9FOrSpss30KdY3UlM,1379
|
|
17
|
+
mm_sol/cli/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
mm_sol/cli/cmd/balance_cmd.py,sha256=ot6X7gTSoMcjapVkAgDlA9Wyo4YeFBIRA7GLjTXsu98,2469
|
|
19
|
+
mm_sol/cli/cmd/balances_cmd.py,sha256=XNNyU02mvSxPdfD3usxucNiiHagxSnLe1rKihj-ya78,2676
|
|
20
|
+
mm_sol/cli/cmd/example_cmd.py,sha256=ZLTy1-cmapiCyYvjFInVE-pQCGKZzDgYKUhsOwtbSIY,234
|
|
21
|
+
mm_sol/cli/cmd/node_cmd.py,sha256=xKjaMdv3_C4kePo-exjE4dQqUHtHWC4eHGROWLkWpS0,347
|
|
22
|
+
mm_sol/cli/cmd/transfer_cmd.py,sha256=Q1GvT1NPDqOESZwfwPYnLqOSQ_VNJCj9ihi7hZhj8xw,10913
|
|
23
|
+
mm_sol/cli/cmd/wallet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
mm_sol/cli/cmd/wallet/keypair_cmd.py,sha256=cRHVVTs9zNYmUozZ8ZlJoutn9V6r8I1AEHBrszR7dTE,538
|
|
25
|
+
mm_sol/cli/cmd/wallet/mnemonic_cmd.py,sha256=IiON_fJT5AFfIr_E1LR6_iDYZ3c_jWCFc-wSYqk61V8,648
|
|
26
|
+
mm_sol/cli/examples/balances.toml,sha256=333g2EkyYBDW7OWFGMIWVZGkdFQMMo0Ag-bg-BvS4Zg,349
|
|
27
|
+
mm_sol/cli/examples/transfer.toml,sha256=kOCdmuwmhlOam4LVtlcYTKF0PoZYHWMlv9gWxNSXMOk,1624
|
|
28
|
+
mm_sol-0.6.1.dist-info/METADATA,sha256=SQ0YVupNyQKaMg7eZ7_NWj6u2oU63HL-ExQztVQbW3c,320
|
|
29
|
+
mm_sol-0.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
30
|
+
mm_sol-0.6.1.dist-info/entry_points.txt,sha256=MrYnosumy9nsITSAw5TiR3WXDwsdoF0YvUIlZ38TLLs,46
|
|
31
|
+
mm_sol-0.6.1.dist-info/RECORD,,
|
mm_sol/async_rpc.py
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
from mm_std import Result, hra
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
async def rpc_call(
|
|
7
|
-
*,
|
|
8
|
-
node: str,
|
|
9
|
-
method: str,
|
|
10
|
-
params: list[Any],
|
|
11
|
-
id_: int = 1,
|
|
12
|
-
timeout: float = 10,
|
|
13
|
-
proxy: str | None = None,
|
|
14
|
-
) -> Result[Any]:
|
|
15
|
-
data = {"jsonrpc": "2.0", "method": method, "params": params, "id": id_}
|
|
16
|
-
if node.startswith("http"):
|
|
17
|
-
return await _http_call(node, data, timeout, proxy)
|
|
18
|
-
raise NotImplementedError("ws is not implemented")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
async def _http_call(node: str, data: dict[str, object], timeout: float, proxy: str | None) -> Result[Any]:
|
|
22
|
-
res = await hra(node, method="POST", proxy=proxy, timeout=timeout, params=data, json_params=True)
|
|
23
|
-
try:
|
|
24
|
-
if res.is_error():
|
|
25
|
-
return res.to_err_result()
|
|
26
|
-
|
|
27
|
-
err = res.json.get("error", {}).get("message", "")
|
|
28
|
-
if err:
|
|
29
|
-
return res.to_err_result(f"service_error: {err}")
|
|
30
|
-
if "result" in res.json:
|
|
31
|
-
return res.to_ok_result(res.json["result"])
|
|
32
|
-
|
|
33
|
-
return res.to_err_result("unknown_response")
|
|
34
|
-
except Exception as e:
|
|
35
|
-
return res.to_err_result(f"exception: {e!s}")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
async def get_balance(node: str, address: str, timeout: float = 10, proxy: str | None = None) -> Result[int]:
|
|
39
|
-
"""Returns balance in lamports"""
|
|
40
|
-
return (await rpc_call(node=node, method="getBalance", params=[address], timeout=timeout, proxy=proxy)).and_then(
|
|
41
|
-
lambda r: r["value"]
|
|
42
|
-
)
|
mm_sol/balance.py
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import httpx
|
|
2
|
-
from mm_crypto_utils import Nodes, Proxies, random_node, random_proxy
|
|
3
|
-
from mm_std import Err, Ok, Result
|
|
4
|
-
from solana.exceptions import SolanaRpcException
|
|
5
|
-
from solana.rpc.core import RPCException
|
|
6
|
-
from solders.pubkey import Pubkey
|
|
7
|
-
from solders.rpc.errors import InvalidParamsMessage
|
|
8
|
-
from spl.token.instructions import get_associated_token_address
|
|
9
|
-
|
|
10
|
-
from mm_sol import async_rpc, rpc
|
|
11
|
-
from mm_sol.utils import get_async_client, get_client
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def get_sol_balance(node: str, address: str, timeout: float = 10, proxy: str | None = None) -> Result[int]:
|
|
15
|
-
return rpc.get_balance(node, address, timeout, proxy)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
async def get_sol_balance_async(node: str, address: str, timeout: float = 10, proxy: str | None = None) -> Result[int]:
|
|
19
|
-
return await async_rpc.get_balance(node, address, timeout, proxy)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def get_sol_balance_with_retries(
|
|
23
|
-
nodes: Nodes, address: str, retries: int, timeout: float = 10, proxies: Proxies = None
|
|
24
|
-
) -> Result[int]:
|
|
25
|
-
res: Result[int] = Err("not started yet")
|
|
26
|
-
for _ in range(retries):
|
|
27
|
-
res = get_sol_balance(random_node(nodes), address, timeout=timeout, proxy=random_proxy(proxies))
|
|
28
|
-
if res.is_ok():
|
|
29
|
-
return res
|
|
30
|
-
return res
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
async def get_sol_balance_with_retries_async(
|
|
34
|
-
nodes: Nodes, address: str, retries: int, timeout: float = 10, proxies: Proxies = None
|
|
35
|
-
) -> Result[int]:
|
|
36
|
-
res: Result[int] = Err("not started yet")
|
|
37
|
-
for _ in range(retries):
|
|
38
|
-
res = await get_sol_balance_async(random_node(nodes), address, timeout=timeout, proxy=random_proxy(proxies))
|
|
39
|
-
if res.is_ok():
|
|
40
|
-
return res
|
|
41
|
-
return res
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def get_token_balance(
|
|
45
|
-
node: str,
|
|
46
|
-
owner_address: str,
|
|
47
|
-
token_mint_address: str,
|
|
48
|
-
token_account: str | None = None,
|
|
49
|
-
timeout: float = 10,
|
|
50
|
-
proxy: str | None = None,
|
|
51
|
-
) -> Result[int]:
|
|
52
|
-
try:
|
|
53
|
-
client = get_client(node, proxy=proxy, timeout=timeout)
|
|
54
|
-
if not token_account:
|
|
55
|
-
token_account = str(
|
|
56
|
-
get_associated_token_address(Pubkey.from_string(owner_address), Pubkey.from_string(token_mint_address))
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
res = client.get_token_account_balance(Pubkey.from_string(token_account))
|
|
60
|
-
|
|
61
|
-
# Sometimes it not raise an error, but it returns this :(
|
|
62
|
-
if isinstance(res, InvalidParamsMessage) and "could not find account" in res.message:
|
|
63
|
-
return Ok(0)
|
|
64
|
-
return Ok(int(res.value.amount), data=res.to_json())
|
|
65
|
-
except RPCException as e:
|
|
66
|
-
if "could not find account" in str(e):
|
|
67
|
-
return Ok(0)
|
|
68
|
-
return Err(e)
|
|
69
|
-
except httpx.HTTPStatusError as e:
|
|
70
|
-
return Err(f"http error: {e}")
|
|
71
|
-
except SolanaRpcException as e:
|
|
72
|
-
return Err(e.error_msg)
|
|
73
|
-
except Exception as e:
|
|
74
|
-
return Err(e)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
async def get_token_balance_async(
|
|
78
|
-
node: str,
|
|
79
|
-
owner_address: str,
|
|
80
|
-
token_mint_address: str,
|
|
81
|
-
token_account: str | None = None,
|
|
82
|
-
timeout: float = 10,
|
|
83
|
-
proxy: str | None = None,
|
|
84
|
-
) -> Result[int]:
|
|
85
|
-
try:
|
|
86
|
-
client = get_async_client(node, proxy=proxy, timeout=timeout)
|
|
87
|
-
if not token_account:
|
|
88
|
-
token_account = str(
|
|
89
|
-
get_associated_token_address(Pubkey.from_string(owner_address), Pubkey.from_string(token_mint_address))
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
res = await client.get_token_account_balance(Pubkey.from_string(token_account))
|
|
93
|
-
|
|
94
|
-
# Sometimes it not raise an error, but it returns this :(
|
|
95
|
-
if isinstance(res, InvalidParamsMessage) and "could not find account" in res.message:
|
|
96
|
-
return Ok(0)
|
|
97
|
-
return Ok(int(res.value.amount), data=res.to_json())
|
|
98
|
-
except RPCException as e:
|
|
99
|
-
if "could not find account" in str(e):
|
|
100
|
-
return Ok(0)
|
|
101
|
-
return Err(e)
|
|
102
|
-
|
|
103
|
-
except httpx.HTTPStatusError as e:
|
|
104
|
-
return Err(f"http error: {e}")
|
|
105
|
-
except SolanaRpcException as e:
|
|
106
|
-
return Err(e.error_msg)
|
|
107
|
-
except Exception as e:
|
|
108
|
-
return Err(e)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def get_token_balance_with_retries(
|
|
112
|
-
nodes: Nodes,
|
|
113
|
-
owner_address: str,
|
|
114
|
-
token_mint_address: str,
|
|
115
|
-
retries: int,
|
|
116
|
-
token_account: str | None = None,
|
|
117
|
-
timeout: float = 10,
|
|
118
|
-
proxies: Proxies = None,
|
|
119
|
-
) -> Result[int]:
|
|
120
|
-
res: Result[int] = Err("not started yet")
|
|
121
|
-
for _ in range(retries):
|
|
122
|
-
res = get_token_balance(
|
|
123
|
-
random_node(nodes),
|
|
124
|
-
owner_address,
|
|
125
|
-
token_mint_address,
|
|
126
|
-
token_account,
|
|
127
|
-
timeout=timeout,
|
|
128
|
-
proxy=random_proxy(proxies),
|
|
129
|
-
)
|
|
130
|
-
if res.is_ok():
|
|
131
|
-
return res
|
|
132
|
-
|
|
133
|
-
return res
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
async def get_token_balance_with_retries_async(
|
|
137
|
-
nodes: Nodes,
|
|
138
|
-
owner_address: str,
|
|
139
|
-
token_mint_address: str,
|
|
140
|
-
retries: int,
|
|
141
|
-
token_account: str | None = None,
|
|
142
|
-
timeout: float = 10,
|
|
143
|
-
proxies: Proxies = None,
|
|
144
|
-
) -> Result[int]:
|
|
145
|
-
res: Result[int] = Err("not started yet")
|
|
146
|
-
for _ in range(retries):
|
|
147
|
-
res = await get_token_balance_async(
|
|
148
|
-
random_node(nodes),
|
|
149
|
-
owner_address,
|
|
150
|
-
token_mint_address,
|
|
151
|
-
token_account,
|
|
152
|
-
timeout=timeout,
|
|
153
|
-
proxy=random_proxy(proxies),
|
|
154
|
-
)
|
|
155
|
-
if res.is_ok():
|
|
156
|
-
return res
|
|
157
|
-
|
|
158
|
-
return res
|
mm_sol/block.py
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
from mm_std import Err, Ok, Result
|
|
2
|
-
from pydantic import BaseModel
|
|
3
|
-
|
|
4
|
-
from mm_sol.rpc import rpc_call
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class BlockTxCount(BaseModel):
|
|
8
|
-
slot: int
|
|
9
|
-
block_time: int | None
|
|
10
|
-
vote_tx_ok: int
|
|
11
|
-
vote_tx_error: int
|
|
12
|
-
non_vote_tx_ok: int
|
|
13
|
-
non_vote_tx_error: int
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def calc_block_tx_count(node: str, slot: int, timeout: int = 10, proxy: str | None = None) -> Result[BlockTxCount]:
|
|
17
|
-
res = rpc_call(node=node, method="getBlock", params=[slot], timeout=timeout, proxy=proxy)
|
|
18
|
-
if res.is_err():
|
|
19
|
-
return res
|
|
20
|
-
vote_tx_ok = 0
|
|
21
|
-
vote_tx_error = 0
|
|
22
|
-
non_vote_tx_ok = 0
|
|
23
|
-
non_vote_tx_error = 0
|
|
24
|
-
vote_tx_keys = [
|
|
25
|
-
"SysvarS1otHashes111111111111111111111111111",
|
|
26
|
-
"SysvarC1ock11111111111111111111111111111111",
|
|
27
|
-
"Vote111111111111111111111111111111111111111",
|
|
28
|
-
]
|
|
29
|
-
try:
|
|
30
|
-
res_ok = res.unwrap()
|
|
31
|
-
txs = res_ok["transactions"]
|
|
32
|
-
block_time = res_ok["blockTime"]
|
|
33
|
-
for tx in txs:
|
|
34
|
-
is_error = tx["meta"]["err"] is not None
|
|
35
|
-
account_keys = tx["transaction"]["message"]["accountKeys"]
|
|
36
|
-
if len(account_keys) == 5 and vote_tx_keys == account_keys[2:]:
|
|
37
|
-
if is_error:
|
|
38
|
-
vote_tx_error += 1
|
|
39
|
-
else:
|
|
40
|
-
vote_tx_ok += 1
|
|
41
|
-
elif is_error:
|
|
42
|
-
non_vote_tx_error += 1
|
|
43
|
-
else:
|
|
44
|
-
non_vote_tx_ok += 1
|
|
45
|
-
|
|
46
|
-
return Ok(
|
|
47
|
-
BlockTxCount(
|
|
48
|
-
slot=slot,
|
|
49
|
-
vote_tx_ok=vote_tx_ok,
|
|
50
|
-
vote_tx_error=vote_tx_error,
|
|
51
|
-
non_vote_tx_ok=non_vote_tx_ok,
|
|
52
|
-
non_vote_tx_error=non_vote_tx_error,
|
|
53
|
-
block_time=block_time,
|
|
54
|
-
),
|
|
55
|
-
res.data,
|
|
56
|
-
)
|
|
57
|
-
except Exception as e:
|
|
58
|
-
return Err(e, data=res.data)
|
mm_sol/rpc_async.py
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from collections.abc import Sequence
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
import websockets
|
|
6
|
-
from mm_std import DataResult, http_request
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
async def rpc_call(
|
|
10
|
-
node: str,
|
|
11
|
-
method: str,
|
|
12
|
-
params: Sequence[object],
|
|
13
|
-
timeout: float,
|
|
14
|
-
proxy: str | None,
|
|
15
|
-
id_: int = 1,
|
|
16
|
-
) -> DataResult[Any]:
|
|
17
|
-
data = {"jsonrpc": "2.0", "method": method, "params": params, "id": id_}
|
|
18
|
-
if node.startswith("http"):
|
|
19
|
-
return await _http_call(node, data, timeout, proxy)
|
|
20
|
-
return await _ws_call(node, data, timeout)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
async def _http_call(node: str, data: dict[str, object], timeout: float, proxy: str | None) -> DataResult[Any]:
|
|
24
|
-
res = await http_request(node, method="POST", proxy=proxy, timeout=timeout, json=data)
|
|
25
|
-
if res.is_error():
|
|
26
|
-
return res.to_data_result_err()
|
|
27
|
-
try:
|
|
28
|
-
parsed_body = res.parse_json_body()
|
|
29
|
-
err = parsed_body.get("error", {}).get("message", "")
|
|
30
|
-
if err:
|
|
31
|
-
return res.to_data_result_err(f"service_error: {err}")
|
|
32
|
-
if "result" in parsed_body:
|
|
33
|
-
return res.to_data_result_ok(parsed_body["result"])
|
|
34
|
-
return res.to_data_result_err("unknown_response")
|
|
35
|
-
except Exception as err:
|
|
36
|
-
return res.to_data_result_err(f"exception: {err}")
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
async def _ws_call(node: str, data: dict[str, object], timeout: float) -> DataResult[Any]:
|
|
40
|
-
try:
|
|
41
|
-
async with websockets.connect(node, open_timeout=timeout) as ws:
|
|
42
|
-
await ws.send(json.dumps(data))
|
|
43
|
-
response = json.loads(await ws.recv())
|
|
44
|
-
|
|
45
|
-
err = response.get("error", {}).get("message", "")
|
|
46
|
-
if err:
|
|
47
|
-
return DataResult.err(f"service_error: {err}", {"res": response})
|
|
48
|
-
if "result" in response:
|
|
49
|
-
return DataResult.ok(response["result"], {"res": response})
|
|
50
|
-
return DataResult.err("unknown_response", {"res": response})
|
|
51
|
-
except TimeoutError:
|
|
52
|
-
return DataResult.err("timeout")
|
|
53
|
-
except Exception as err:
|
|
54
|
-
return DataResult.exception(err)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
async def get_block_height(node: str, timeout: float = 10, proxy: str | None = None) -> DataResult[int]:
|
|
58
|
-
return await rpc_call(node=node, method="getBlockHeight", params=[], timeout=timeout, proxy=proxy)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
async def get_balance(node: str, address: str, timeout: float = 10, proxy: str | None = None) -> DataResult[int]:
|
|
62
|
-
"""Returns balance in lamports"""
|
|
63
|
-
return (await rpc_call(node=node, method="getBalance", params=[address], timeout=timeout, proxy=proxy)).map(
|
|
64
|
-
lambda r: r["value"]
|
|
65
|
-
)
|