mm-sol 0.5.8__py3-none-any.whl → 0.6.0__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 -22
- mm_sol/cli/cmd/balances_cmd.py +28 -29
- mm_sol/cli/cmd/node_cmd.py +2 -2
- mm_sol/cli/cmd/transfer_cmd.py +50 -55
- mm_sol/rpc.py +55 -206
- mm_sol/rpc_sync.py +232 -0
- mm_sol/spl_token.py +85 -0
- mm_sol/transfer.py +80 -76
- {mm_sol-0.5.8.dist-info → mm_sol-0.6.0.dist-info}/METADATA +3 -2
- mm_sol-0.6.0.dist-info/RECORD +30 -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.8.dist-info/RECORD +0 -35
- {mm_sol-0.5.8.dist-info → mm_sol-0.6.0.dist-info}/WHEEL +0 -0
- {mm_sol-0.5.8.dist-info → mm_sol-0.6.0.dist-info}/entry_points.txt +0 -0
mm_sol/transfer.py
CHANGED
|
@@ -1,22 +1,54 @@
|
|
|
1
|
-
import mm_crypto_utils
|
|
2
1
|
import pydash
|
|
3
|
-
from mm_crypto_utils import Nodes, Proxies
|
|
4
|
-
from mm_std import
|
|
2
|
+
from mm_crypto_utils import Nodes, Proxies, retry_with_node_and_proxy
|
|
3
|
+
from mm_std import Result
|
|
5
4
|
from pydantic import BaseModel
|
|
6
5
|
from solders.message import Message
|
|
7
6
|
from solders.pubkey import Pubkey
|
|
8
7
|
from solders.signature import Signature
|
|
9
8
|
from solders.system_program import TransferParams, transfer
|
|
10
9
|
from solders.transaction import Transaction
|
|
11
|
-
from spl.token.
|
|
10
|
+
from spl.token.async_client import AsyncToken
|
|
12
11
|
from spl.token.constants import TOKEN_PROGRAM_ID
|
|
13
12
|
from spl.token.instructions import get_associated_token_address
|
|
14
13
|
|
|
15
|
-
from mm_sol import
|
|
14
|
+
from mm_sol import rpc_sync, utils
|
|
16
15
|
from mm_sol.account import check_private_key, get_keypair
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
def
|
|
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
|
+
async def transfer_token(
|
|
20
52
|
*,
|
|
21
53
|
node: str,
|
|
22
54
|
token_mint_address: str | Pubkey,
|
|
@@ -29,73 +61,71 @@ def transfer_token(
|
|
|
29
61
|
timeout: float = 10,
|
|
30
62
|
create_token_account_if_not_exists: bool = True,
|
|
31
63
|
) -> Result[Signature]:
|
|
64
|
+
# TODO: try/except this function!!!
|
|
32
65
|
acc = get_keypair(private_key)
|
|
33
66
|
if not check_private_key(from_address, private_key):
|
|
34
|
-
return
|
|
67
|
+
return Result.failure("invalid_private_key")
|
|
35
68
|
|
|
36
69
|
from_address = utils.pubkey(from_address)
|
|
37
70
|
token_mint_address = utils.pubkey(token_mint_address)
|
|
38
71
|
to_address = utils.pubkey(to_address)
|
|
39
72
|
|
|
40
|
-
client = utils.
|
|
41
|
-
token_client =
|
|
73
|
+
client = utils.get_async_client(node, proxy=proxy, timeout=timeout)
|
|
74
|
+
token_client = AsyncToken(conn=client, pubkey=token_mint_address, program_id=TOKEN_PROGRAM_ID, payer=acc)
|
|
42
75
|
|
|
43
76
|
recipient_token_account = get_associated_token_address(to_address, token_mint_address, token_program_id=TOKEN_PROGRAM_ID)
|
|
44
77
|
from_token_account = get_associated_token_address(from_address, token_mint_address, token_program_id=TOKEN_PROGRAM_ID)
|
|
45
|
-
|
|
78
|
+
logs: list[object] = []
|
|
46
79
|
|
|
47
|
-
account_info_res = client.get_account_info(recipient_token_account)
|
|
80
|
+
account_info_res = await client.get_account_info(recipient_token_account)
|
|
48
81
|
if account_info_res.value is None:
|
|
49
82
|
if create_token_account_if_not_exists:
|
|
50
83
|
create_account_res = token_client.create_associated_token_account(to_address, skip_confirmation=False)
|
|
51
|
-
|
|
84
|
+
logs.append(create_account_res)
|
|
52
85
|
else:
|
|
53
|
-
return
|
|
86
|
+
return Result.failure("no_token_account")
|
|
54
87
|
|
|
55
|
-
res = token_client.transfer_checked(
|
|
88
|
+
res = await token_client.transfer_checked(
|
|
56
89
|
source=from_token_account,
|
|
57
90
|
dest=recipient_token_account,
|
|
58
91
|
owner=from_address,
|
|
59
92
|
amount=amount,
|
|
60
93
|
decimals=decimals,
|
|
94
|
+
multi_signers=None,
|
|
61
95
|
)
|
|
62
|
-
|
|
96
|
+
logs.append(res)
|
|
63
97
|
|
|
64
|
-
return
|
|
98
|
+
return Result.success(res.value, {"logs": logs})
|
|
65
99
|
|
|
66
100
|
|
|
67
|
-
def
|
|
68
|
-
|
|
101
|
+
async def transfer_sol_with_retries(
|
|
102
|
+
retries: int,
|
|
69
103
|
nodes: Nodes,
|
|
70
|
-
|
|
71
|
-
|
|
104
|
+
proxies: Proxies,
|
|
105
|
+
*,
|
|
106
|
+
from_address: str,
|
|
72
107
|
private_key: str,
|
|
73
|
-
to_address: str
|
|
74
|
-
|
|
75
|
-
decimals: int,
|
|
76
|
-
proxies: Proxies = None,
|
|
108
|
+
to_address: str,
|
|
109
|
+
lamports: int,
|
|
77
110
|
timeout: float = 10,
|
|
78
|
-
retries: int = 3,
|
|
79
111
|
) -> Result[Signature]:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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,
|
|
85
119
|
from_address=from_address,
|
|
86
|
-
private_key=private_key,
|
|
87
120
|
to_address=to_address,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
proxy=mm_crypto_utils.random_proxy(proxies),
|
|
121
|
+
lamports=lamports,
|
|
122
|
+
private_key=private_key,
|
|
91
123
|
timeout=timeout,
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
return res
|
|
95
|
-
return res
|
|
124
|
+
),
|
|
125
|
+
)
|
|
96
126
|
|
|
97
127
|
|
|
98
|
-
def transfer_sol(
|
|
128
|
+
async def transfer_sol(
|
|
99
129
|
*,
|
|
100
130
|
node: str,
|
|
101
131
|
from_address: str,
|
|
@@ -107,46 +137,20 @@ def transfer_sol(
|
|
|
107
137
|
) -> Result[Signature]:
|
|
108
138
|
acc = get_keypair(private_key)
|
|
109
139
|
if not check_private_key(from_address, private_key):
|
|
110
|
-
return
|
|
140
|
+
return Result.failure("invalid_private_key")
|
|
111
141
|
|
|
112
|
-
client = utils.
|
|
142
|
+
client = utils.get_async_client(node, proxy=proxy, timeout=timeout)
|
|
113
143
|
data = None
|
|
114
144
|
try:
|
|
115
145
|
ixs = [transfer(TransferParams(from_pubkey=acc.pubkey(), to_pubkey=Pubkey.from_string(to_address), lamports=lamports))]
|
|
116
146
|
msg = Message(ixs, acc.pubkey())
|
|
117
|
-
|
|
118
|
-
|
|
147
|
+
blockhash = await client.get_latest_blockhash()
|
|
148
|
+
tx = Transaction([acc], msg, blockhash.value.blockhash)
|
|
149
|
+
res = await client.send_transaction(tx)
|
|
119
150
|
data = res.to_json()
|
|
120
|
-
return
|
|
151
|
+
return Result.success(res.value, {"response": data})
|
|
121
152
|
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
|
|
153
|
+
return Result.failure(e, {"response": data})
|
|
150
154
|
|
|
151
155
|
|
|
152
156
|
class SolTransferInfo(BaseModel):
|
|
@@ -156,8 +160,8 @@ class SolTransferInfo(BaseModel):
|
|
|
156
160
|
|
|
157
161
|
|
|
158
162
|
def find_sol_transfers(node: str, tx_signature: str) -> Result[list[SolTransferInfo]]:
|
|
159
|
-
res =
|
|
160
|
-
if res.
|
|
163
|
+
res = rpc_sync.get_transaction(node, tx_signature, encoding="jsonParsed")
|
|
164
|
+
if res.is_error():
|
|
161
165
|
return res # type: ignore[return-value]
|
|
162
166
|
result = []
|
|
163
167
|
try:
|
|
@@ -170,6 +174,6 @@ def find_sol_transfers(node: str, tx_signature: str) -> Result[list[SolTransferI
|
|
|
170
174
|
lamports = pydash.get(ix, "parsed.info.lamports")
|
|
171
175
|
if source and destination and lamports:
|
|
172
176
|
result.append(SolTransferInfo(source=source, destination=destination, lamports=lamports))
|
|
173
|
-
return
|
|
177
|
+
return Result.success(result, res.extra)
|
|
174
178
|
except Exception as e:
|
|
175
|
-
return
|
|
179
|
+
return Result.failure(e, res.extra)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mm-sol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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.3
|
|
8
|
+
Requires-Dist: mm-std>=0.4.5
|
|
8
9
|
Requires-Dist: mnemonic==0.21
|
|
9
10
|
Requires-Dist: socksio>=1.0.0
|
|
10
11
|
Requires-Dist: solana~=0.36.6
|
|
@@ -0,0 +1,30 @@
|
|
|
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/rpc.py,sha256=1rSGu8UKYug9-ScvK1zkaR_VdWUkXYJm2E8RTY2Hfhg,2982
|
|
7
|
+
mm_sol/rpc_sync.py,sha256=w-xP3Hl_whBcZZJv5vw08oiB1GTPfbVRvsDjU_k-UTA,8122
|
|
8
|
+
mm_sol/spl_token.py,sha256=NnTUWaefbvuZX9pDmRLHir2UUaiSWKZamyGsfKwQ2p4,2907
|
|
9
|
+
mm_sol/transfer.py,sha256=CbUOFPjAoPf0KRPI-z5W0cSnTRFENbcNjLXDPKPrVBg,6049
|
|
10
|
+
mm_sol/utils.py,sha256=oD06NsMSMhN6lqsM6mSgLTtiKwA1uAsen9WR82ofRTE,923
|
|
11
|
+
mm_sol/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
mm_sol/cli/calcs.py,sha256=uxnlGPU3UmDvZ567VLzcEyEWvM8CLcoTBt7YcILtaOA,1716
|
|
13
|
+
mm_sol/cli/cli.py,sha256=0MflYgO-UzFdoaXFCIBuQ1Y6AOQUquX__AhSWOO64KY,4735
|
|
14
|
+
mm_sol/cli/cli_utils.py,sha256=nFdY8tJFZxyssEBEFCc3VTNJt447e6vMnugx4GBPL4o,1840
|
|
15
|
+
mm_sol/cli/validators.py,sha256=M_Rr7JoG3TUYTDAGkjQLDH6l9i9FOrSpss30KdY3UlM,1379
|
|
16
|
+
mm_sol/cli/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
mm_sol/cli/cmd/balance_cmd.py,sha256=0Rp6cUqfQLpNlqY6YtrpG-oYYz_llPhWpJkte804PL4,2495
|
|
18
|
+
mm_sol/cli/cmd/balances_cmd.py,sha256=dArwI2oJaTMft73mN0EgDQWVSwRJmsAA0HyT8_GpBiA,2676
|
|
19
|
+
mm_sol/cli/cmd/example_cmd.py,sha256=ZLTy1-cmapiCyYvjFInVE-pQCGKZzDgYKUhsOwtbSIY,234
|
|
20
|
+
mm_sol/cli/cmd/node_cmd.py,sha256=xKjaMdv3_C4kePo-exjE4dQqUHtHWC4eHGROWLkWpS0,347
|
|
21
|
+
mm_sol/cli/cmd/transfer_cmd.py,sha256=Qbgi7FvA8NoUAItv1NZxsLu_oFNgoWlxaALy1eFAs8M,11014
|
|
22
|
+
mm_sol/cli/cmd/wallet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
mm_sol/cli/cmd/wallet/keypair_cmd.py,sha256=cRHVVTs9zNYmUozZ8ZlJoutn9V6r8I1AEHBrszR7dTE,538
|
|
24
|
+
mm_sol/cli/cmd/wallet/mnemonic_cmd.py,sha256=IiON_fJT5AFfIr_E1LR6_iDYZ3c_jWCFc-wSYqk61V8,648
|
|
25
|
+
mm_sol/cli/examples/balances.toml,sha256=333g2EkyYBDW7OWFGMIWVZGkdFQMMo0Ag-bg-BvS4Zg,349
|
|
26
|
+
mm_sol/cli/examples/transfer.toml,sha256=kOCdmuwmhlOam4LVtlcYTKF0PoZYHWMlv9gWxNSXMOk,1624
|
|
27
|
+
mm_sol-0.6.0.dist-info/METADATA,sha256=vE1UQwE3DBr3gw8RLgkmFL3hbShOAlKbacEIx7fbM_8,349
|
|
28
|
+
mm_sol-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
29
|
+
mm_sol-0.6.0.dist-info/entry_points.txt,sha256=MrYnosumy9nsITSAw5TiR3WXDwsdoF0YvUIlZ38TLLs,46
|
|
30
|
+
mm_sol-0.6.0.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}", data=response)
|
|
48
|
-
if "result" in response:
|
|
49
|
-
return DataResult(ok=response["result"], data=response)
|
|
50
|
-
return DataResult(err="unknown_response", data=response)
|
|
51
|
-
except TimeoutError:
|
|
52
|
-
return DataResult(err="timeout")
|
|
53
|
-
except Exception as err:
|
|
54
|
-
return DataResult(err=f"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
|
-
)
|