helius-python 0.3.2__tar.gz → 0.3.3__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.
- {helius_python-0.3.2 → helius_python-0.3.3}/AGENTS.md +1 -12
- {helius_python-0.3.2 → helius_python-0.3.3}/PKG-INFO +4 -1
- {helius_python-0.3.2 → helius_python-0.3.3}/README.md +2 -0
- helius_python-0.3.3/TODO.md +4 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/pyproject.toml +2 -1
- {helius_python-0.3.2 → helius_python-0.3.3}/src/helius/laserstream/websockets.py +2 -1
- {helius_python-0.3.2 → helius_python-0.3.3}/src/helius/solana_rpc/client.py +85 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/src/helius/solana_rpc/models.py +98 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/tests/unit/solana_rpc/test_client.py +333 -0
- helius_python-0.3.2/TODO.md +0 -3
- {helius_python-0.3.2 → helius_python-0.3.3}/.editorconfig +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/.github/workflows/python-package.yml +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/.github/workflows/python-publish.yml +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/.gitignore +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/CLAUDE.md +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/CONTRIBUTING.md +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/LICENSE +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/examples/block_explorer.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/examples/devnet_airdrop.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/examples/network_status.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/examples/priority_fees.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/examples/stake_overview.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/examples/token_inspector.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/examples/transaction_inspector.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/examples/wallet_tracker.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/requirements.txt +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/src/helius/__init__.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/src/helius/admin/__init__.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/src/helius/admin/admin.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/src/helius/rpc/__init__.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/src/helius/rpc/json_rpc_request.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/src/helius/solana_rpc/__init__.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/tests/fixtures/account.json +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/tests/fixtures/supply.json +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/tests/unit/admin/test_admin.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/tests/unit/lasterstream/test_websockets.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/tests/unit/rpc/test_json_rpc_request.py +0 -0
- {helius_python-0.3.2 → helius_python-0.3.3}/tests/unit/solana_rpc/test_models.py +0 -0
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
## Conventions
|
|
3
|
-
|
|
4
|
-
- Use **double-quoted triple strings** (`"""..."""`).
|
|
5
|
-
- Wrap docstring lines at 88 columns.
|
|
6
|
-
- Refer to parameters in backticks: `` `commitment` ``, `` `min_context_slot` ``.
|
|
7
|
-
- Refer to other client methods with their snake_case name in backticks: `` `get_balance` ``.
|
|
8
|
-
- Do NOT include the upstream JSON-RPC method name in the summary — that's already in the See Also URLs.
|
|
9
|
-
- Do NOT copy-paste large chunks from the Helius docs. Summarize and link.
|
|
10
|
-
- Examples (`Example:` section) are encouraged for methods with non-trivial argument combinations (e.g. `get_block_production`, `get_token_accounts_by_owner`), optional for everything else.
|
|
11
|
-
|
|
12
1
|
## Implementation conventions
|
|
13
2
|
|
|
14
3
|
- **If the RPC returns an `RpcResponse` wrapper (`{context, value}`), the Python method MUST return `(context, value)`** — never silently drop `context`. For methods whose `value` is itself a small composite, flatten the tuple (e.g. `get_latest_blockhash` returns `tuple[dict, str, int]`, not `tuple[dict, tuple[str, int]]`). Check the upstream Helius API reference page to see whether the response is wrapped.
|
|
@@ -124,7 +113,7 @@ For methods with branching logic (e.g. `get_block_production`, `get_token_accoun
|
|
|
124
113
|
## Running
|
|
125
114
|
|
|
126
115
|
```bash
|
|
127
|
-
pytest
|
|
116
|
+
.venv/bin/pytest
|
|
128
117
|
```
|
|
129
118
|
|
|
130
119
|
All tests must pass and there must be no real network traffic. If a test fails because it tried to hit the network, that's a bug in the test — add the missing `@respx.mock` or `respx` route.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: helius-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Typed Python client for the Helius API
|
|
5
5
|
Project-URL: Homepage, https://github.com/markosnarinian/helius-python
|
|
6
6
|
Project-URL: Issues, https://github.com/markosnarinian/helius-python/issues
|
|
@@ -13,6 +13,7 @@ Requires-Python: >=3.10
|
|
|
13
13
|
Requires-Dist: httpx
|
|
14
14
|
Requires-Dist: pydantic
|
|
15
15
|
Requires-Dist: python-dotenv
|
|
16
|
+
Requires-Dist: typing-extensions
|
|
16
17
|
Requires-Dist: websockets
|
|
17
18
|
Provides-Extra: dev
|
|
18
19
|
Requires-Dist: pytest; extra == 'dev'
|
|
@@ -322,6 +323,8 @@ continuously.
|
|
|
322
323
|
| `minimumLedgerSlot` | `minimum_ledger_slot()` | [guide](https://www.helius.dev/docs/rpc/guides/minimumledgerslot), [reference](https://www.helius.dev/docs/api-reference/rpc/http/minimumledgerslot) |
|
|
323
324
|
| `requestAirdrop` | `request_airdrop(...)` | [guide](https://www.helius.dev/docs/rpc/guides/requestairdrop), [reference](https://www.helius.dev/docs/api-reference/rpc/http/requestairdrop) |
|
|
324
325
|
| `sendTransaction` | `send_transaction(...)` | [guide](https://www.helius.dev/docs/rpc/guides/sendtransaction), [reference](https://www.helius.dev/docs/api-reference/rpc/http/sendtransaction) |
|
|
326
|
+
| `getTransactionsForAddress` | `get_transactions_for_address(...)` | [reference](https://www.helius.dev/docs/api-reference/rpc/http/gettransactionsforaddress) |
|
|
327
|
+
| `getTransfersByAddress` | `get_transfers_by_address(...)` | [guide](https://www.helius.dev/docs/rpc/gettransfersbyaddress), [reference](https://www.helius.dev/docs/api-reference/rpc/http/gettransfersbyaddress) |
|
|
325
328
|
|
|
326
329
|
## WebSocket subscriptions
|
|
327
330
|
|
|
@@ -301,6 +301,8 @@ continuously.
|
|
|
301
301
|
| `minimumLedgerSlot` | `minimum_ledger_slot()` | [guide](https://www.helius.dev/docs/rpc/guides/minimumledgerslot), [reference](https://www.helius.dev/docs/api-reference/rpc/http/minimumledgerslot) |
|
|
302
302
|
| `requestAirdrop` | `request_airdrop(...)` | [guide](https://www.helius.dev/docs/rpc/guides/requestairdrop), [reference](https://www.helius.dev/docs/api-reference/rpc/http/requestairdrop) |
|
|
303
303
|
| `sendTransaction` | `send_transaction(...)` | [guide](https://www.helius.dev/docs/rpc/guides/sendtransaction), [reference](https://www.helius.dev/docs/api-reference/rpc/http/sendtransaction) |
|
|
304
|
+
| `getTransactionsForAddress` | `get_transactions_for_address(...)` | [reference](https://www.helius.dev/docs/api-reference/rpc/http/gettransactionsforaddress) |
|
|
305
|
+
| `getTransfersByAddress` | `get_transfers_by_address(...)` | [guide](https://www.helius.dev/docs/rpc/gettransfersbyaddress), [reference](https://www.helius.dev/docs/api-reference/rpc/http/gettransfersbyaddress) |
|
|
304
306
|
|
|
305
307
|
## WebSocket subscriptions
|
|
306
308
|
|
|
@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "helius-python"
|
|
13
|
-
version = "0.3.
|
|
13
|
+
version = "0.3.3"
|
|
14
14
|
authors = [
|
|
15
15
|
{ name="Markos Narinian", email="manarinian@gmail.com" },
|
|
16
16
|
]
|
|
@@ -21,6 +21,7 @@ dependencies = [
|
|
|
21
21
|
"httpx",
|
|
22
22
|
"pydantic",
|
|
23
23
|
"python-dotenv",
|
|
24
|
+
"typing-extensions",
|
|
24
25
|
"websockets",
|
|
25
26
|
]
|
|
26
27
|
classifiers = [
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from os import environ
|
|
3
|
-
from typing import Annotated, Literal
|
|
3
|
+
from typing import Annotated, Literal
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
6
|
from dotenv import dotenv_values
|
|
7
7
|
from pydantic import AliasGenerator, BaseModel, ConfigDict, Field, model_validator
|
|
8
8
|
from pydantic.alias_generators import to_camel
|
|
9
|
+
from typing_extensions import TypedDict
|
|
9
10
|
from websockets.sync.client import connect
|
|
10
11
|
|
|
11
12
|
from helius.rpc import JsonRpcRequest
|
|
@@ -25,7 +25,12 @@ from helius.solana_rpc.models import (
|
|
|
25
25
|
TokenAccountBalance,
|
|
26
26
|
TokenSupply,
|
|
27
27
|
Transaction,
|
|
28
|
+
TransactionFilters,
|
|
29
|
+
TransactionFullDetail,
|
|
28
30
|
TransactionSignature,
|
|
31
|
+
TransactionSignaturesDetail,
|
|
32
|
+
Transfer,
|
|
33
|
+
TransferFilters,
|
|
29
34
|
VotingAccount,
|
|
30
35
|
)
|
|
31
36
|
|
|
@@ -983,3 +988,83 @@ class SolanaRpcClient:
|
|
|
983
988
|
)
|
|
984
989
|
response = self._send(request)
|
|
985
990
|
return response["result"]
|
|
991
|
+
|
|
992
|
+
@validate_call
|
|
993
|
+
def get_transactions_for_address(
|
|
994
|
+
self,
|
|
995
|
+
*,
|
|
996
|
+
address: str,
|
|
997
|
+
transaction_details: Literal["signatures", "full"] | None = None,
|
|
998
|
+
sort_order: Literal["asc", "desc"] | None = None,
|
|
999
|
+
commitment: Literal["confirmed", "finalized"] | None = None,
|
|
1000
|
+
min_context_slot: int | None = None,
|
|
1001
|
+
limit: Annotated[int, Field(ge=1, le=1000)] | None = None,
|
|
1002
|
+
pagination_token: str | None = None,
|
|
1003
|
+
encoding: Literal["json", "jsonParsed", "base58", "base64"] | None = None,
|
|
1004
|
+
max_supported_transaction_version: int | None = None,
|
|
1005
|
+
filters: TransactionFilters | None = None,
|
|
1006
|
+
) -> tuple[
|
|
1007
|
+
list[TransactionSignaturesDetail] | list[TransactionFullDetail], str | None
|
|
1008
|
+
]:
|
|
1009
|
+
request = (
|
|
1010
|
+
JsonRpcRequest(method="getTransactionsForAddress")
|
|
1011
|
+
.add(address)
|
|
1012
|
+
.set("transactionDetails", transaction_details)
|
|
1013
|
+
.set("sortOrder", sort_order)
|
|
1014
|
+
.set("commitment", commitment)
|
|
1015
|
+
.set("minContextSlot", min_context_slot)
|
|
1016
|
+
.set("limit", limit)
|
|
1017
|
+
.set("paginationToken", pagination_token)
|
|
1018
|
+
.set("encoding", encoding)
|
|
1019
|
+
.set("maxSupportedTransactionVersion", max_supported_transaction_version)
|
|
1020
|
+
.set("filters", filters)
|
|
1021
|
+
.build()
|
|
1022
|
+
)
|
|
1023
|
+
response = self._send(request)
|
|
1024
|
+
pagination_token = response["result"]["paginationToken"]
|
|
1025
|
+
data = response["result"]["data"]
|
|
1026
|
+
ta = TypeAdapter(
|
|
1027
|
+
list[TransactionSignaturesDetail]
|
|
1028
|
+
if transaction_details == "signatures"
|
|
1029
|
+
else list[TransactionFullDetail]
|
|
1030
|
+
)
|
|
1031
|
+
transactions = ta.validate_python(data)
|
|
1032
|
+
return transactions, pagination_token
|
|
1033
|
+
|
|
1034
|
+
@validate_call
|
|
1035
|
+
def get_transfers_by_address(
|
|
1036
|
+
self,
|
|
1037
|
+
*,
|
|
1038
|
+
address: str,
|
|
1039
|
+
with_address: str | None = None,
|
|
1040
|
+
direction: Literal["in", "out", "any"] | None = None,
|
|
1041
|
+
mint: str | None = None,
|
|
1042
|
+
sol_mode: Literal["merged", "separate"] | None = None,
|
|
1043
|
+
filters: TransferFilters | None = None,
|
|
1044
|
+
limit: Annotated[int, Field(ge=1, le=100)] | None = None,
|
|
1045
|
+
pagination_token: str | None = None,
|
|
1046
|
+
commitment: Literal["finalized", "confirmed"] | None = None,
|
|
1047
|
+
min_context_slot: int | None = None,
|
|
1048
|
+
sort_order: Literal["asc", "desc"] | None = None,
|
|
1049
|
+
) -> tuple[list[Transfer], str | None]:
|
|
1050
|
+
request = (
|
|
1051
|
+
JsonRpcRequest(method="getTransfersByAddress")
|
|
1052
|
+
.add(address)
|
|
1053
|
+
.set("with", with_address)
|
|
1054
|
+
.set("direction", direction)
|
|
1055
|
+
.set("mint", mint)
|
|
1056
|
+
.set("solMode", sol_mode)
|
|
1057
|
+
.set("filters", filters)
|
|
1058
|
+
.set("limit", limit)
|
|
1059
|
+
.set("paginationToken", pagination_token)
|
|
1060
|
+
.set("commitment", commitment)
|
|
1061
|
+
.set("minContextSlot", min_context_slot)
|
|
1062
|
+
.set("sortOrder", sort_order)
|
|
1063
|
+
.build()
|
|
1064
|
+
)
|
|
1065
|
+
response = self._send(request)
|
|
1066
|
+
pagination_token = response["result"]["paginationToken"]
|
|
1067
|
+
data = response["result"]["data"]
|
|
1068
|
+
ta = TypeAdapter(list[Transfer])
|
|
1069
|
+
transfers = ta.validate_python(data)
|
|
1070
|
+
return transfers, pagination_token
|
|
@@ -2,6 +2,9 @@ from typing import Literal
|
|
|
2
2
|
|
|
3
3
|
from pydantic import AliasGenerator, BaseModel, ConfigDict
|
|
4
4
|
from pydantic.alias_generators import to_camel
|
|
5
|
+
from typing_extensions import TypedDict
|
|
6
|
+
|
|
7
|
+
# TODO: Improve camelCase <-> snake_case
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
class Account(BaseModel):
|
|
@@ -221,3 +224,98 @@ class Transaction(BaseModel):
|
|
|
221
224
|
meta: TransactionMetadata | None
|
|
222
225
|
transaction: dict | list
|
|
223
226
|
version: Literal["legacy"] | int | None = None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class RangeFilter(TypedDict, total=False):
|
|
230
|
+
gt: int
|
|
231
|
+
gte: int
|
|
232
|
+
lt: int
|
|
233
|
+
lte: int
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class ComparisonFilter(TypedDict, total=False):
|
|
237
|
+
gte: int
|
|
238
|
+
gt: int
|
|
239
|
+
lte: int
|
|
240
|
+
lt: int
|
|
241
|
+
eq: int
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
TokenTransferFilter = TypedDict(
|
|
245
|
+
"TokenTransferFilter",
|
|
246
|
+
{
|
|
247
|
+
"with": str,
|
|
248
|
+
"direction": Literal["in", "out", "any"],
|
|
249
|
+
"mint": str,
|
|
250
|
+
"amount": RangeFilter,
|
|
251
|
+
},
|
|
252
|
+
total=False,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class TransactionFilters(TypedDict, total=False):
|
|
257
|
+
slot: RangeFilter
|
|
258
|
+
blockTime: ComparisonFilter
|
|
259
|
+
signature: RangeFilter
|
|
260
|
+
status: Literal["succeeded", "failed", "any"]
|
|
261
|
+
tokenAccounts: Literal["none", "balanceChanged", "all"]
|
|
262
|
+
tokenTransfer: TokenTransferFilter
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class TransferFilters(TypedDict, total=False):
|
|
266
|
+
amount: RangeFilter
|
|
267
|
+
blockTime: RangeFilter
|
|
268
|
+
slot: RangeFilter
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class Transfer(BaseModel):
|
|
272
|
+
model_config = ConfigDict(alias_generator=AliasGenerator(validation_alias=to_camel))
|
|
273
|
+
|
|
274
|
+
signature: str
|
|
275
|
+
slot: int
|
|
276
|
+
block_time: int
|
|
277
|
+
type: Literal[
|
|
278
|
+
"transfer",
|
|
279
|
+
"mint",
|
|
280
|
+
"burn",
|
|
281
|
+
"wrap",
|
|
282
|
+
"unwrap",
|
|
283
|
+
"changeOwner",
|
|
284
|
+
"withdrawWithheldFee",
|
|
285
|
+
]
|
|
286
|
+
from_user_account: str | None
|
|
287
|
+
to_user_account: str | None
|
|
288
|
+
mint: str
|
|
289
|
+
amount: str
|
|
290
|
+
decimals: int
|
|
291
|
+
ui_amount: str
|
|
292
|
+
confirmation_status: Literal["finalized", "confirmed"]
|
|
293
|
+
transaction_idx: int
|
|
294
|
+
instruction_idx: int
|
|
295
|
+
inner_instruction_idx: int
|
|
296
|
+
from_token_account: str | None = None
|
|
297
|
+
to_token_account: str | None = None
|
|
298
|
+
fee_amount: str | None = None
|
|
299
|
+
fee_ui_amount: str | None = None
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class TransactionSignaturesDetail(BaseModel):
|
|
303
|
+
model_config = ConfigDict(alias_generator=AliasGenerator(validation_alias=to_camel))
|
|
304
|
+
|
|
305
|
+
signature: str
|
|
306
|
+
slot: int
|
|
307
|
+
transaction_index: int
|
|
308
|
+
err: dict | None
|
|
309
|
+
memo: str | None
|
|
310
|
+
block_time: int | None
|
|
311
|
+
confirmation_status: Literal["finalized", "confirmed"] | None
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class TransactionFullDetail(BaseModel):
|
|
315
|
+
model_config = ConfigDict(alias_generator=AliasGenerator(validation_alias=to_camel))
|
|
316
|
+
|
|
317
|
+
slot: int
|
|
318
|
+
transaction_index: int
|
|
319
|
+
transaction: dict
|
|
320
|
+
meta: dict | list
|
|
321
|
+
block_time: int | None
|
|
@@ -1333,6 +1333,339 @@ def test_get_transaction_count():
|
|
|
1333
1333
|
assert_api_key(route)
|
|
1334
1334
|
|
|
1335
1335
|
|
|
1336
|
+
# ---------------------------------------------------------------------------
|
|
1337
|
+
# get_transactions_for_address
|
|
1338
|
+
# ---------------------------------------------------------------------------
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
@respx.mock
|
|
1342
|
+
def test_get_transactions_for_address_signatures():
|
|
1343
|
+
route = mock_rpc(
|
|
1344
|
+
{
|
|
1345
|
+
"data": [
|
|
1346
|
+
{
|
|
1347
|
+
"signature": "5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv",
|
|
1348
|
+
"slot": 1054,
|
|
1349
|
+
"transactionIndex": 42,
|
|
1350
|
+
"err": None,
|
|
1351
|
+
"memo": None,
|
|
1352
|
+
"blockTime": 1641038400,
|
|
1353
|
+
"confirmationStatus": "finalized",
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
"signature": "kwjd820slPK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv",
|
|
1357
|
+
"slot": 1055,
|
|
1358
|
+
"transactionIndex": 15,
|
|
1359
|
+
"err": None,
|
|
1360
|
+
"memo": None,
|
|
1361
|
+
"blockTime": 1641038460,
|
|
1362
|
+
"confirmationStatus": "finalized",
|
|
1363
|
+
},
|
|
1364
|
+
],
|
|
1365
|
+
"paginationToken": "1055:5",
|
|
1366
|
+
}
|
|
1367
|
+
)
|
|
1368
|
+
with SolanaRpcClient(api_key="test") as client:
|
|
1369
|
+
data, pagination_token = client.get_transactions_for_address(
|
|
1370
|
+
address="Vote111111111111111111111111111111111111111",
|
|
1371
|
+
transaction_details="signatures",
|
|
1372
|
+
sort_order="desc",
|
|
1373
|
+
limit=50,
|
|
1374
|
+
filters={
|
|
1375
|
+
"status": "succeeded",
|
|
1376
|
+
"slot": {"gte": 1000, "lt": 2000},
|
|
1377
|
+
"tokenTransfer": {
|
|
1378
|
+
"direction": "in",
|
|
1379
|
+
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
1380
|
+
},
|
|
1381
|
+
},
|
|
1382
|
+
)
|
|
1383
|
+
assert len(data) == 2
|
|
1384
|
+
assert data[0].signature.startswith("5h6x")
|
|
1385
|
+
assert data[0].slot == 1054
|
|
1386
|
+
assert data[0].transaction_index == 42
|
|
1387
|
+
assert data[0].err is None
|
|
1388
|
+
assert data[0].memo is None
|
|
1389
|
+
assert data[0].block_time == 1641038400
|
|
1390
|
+
assert data[0].confirmation_status == "finalized"
|
|
1391
|
+
assert data[1].slot == 1055
|
|
1392
|
+
assert data[1].transaction_index == 15
|
|
1393
|
+
assert pagination_token == "1055:5"
|
|
1394
|
+
assert body(route)["method"] == "getTransactionsForAddress"
|
|
1395
|
+
assert body(route)["params"] == [
|
|
1396
|
+
"Vote111111111111111111111111111111111111111",
|
|
1397
|
+
{
|
|
1398
|
+
"transactionDetails": "signatures",
|
|
1399
|
+
"sortOrder": "desc",
|
|
1400
|
+
"limit": 50,
|
|
1401
|
+
"filters": {
|
|
1402
|
+
"status": "succeeded",
|
|
1403
|
+
"slot": {"gte": 1000, "lt": 2000},
|
|
1404
|
+
"tokenTransfer": {
|
|
1405
|
+
"direction": "in",
|
|
1406
|
+
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
1407
|
+
},
|
|
1408
|
+
},
|
|
1409
|
+
},
|
|
1410
|
+
]
|
|
1411
|
+
assert_api_key(route)
|
|
1412
|
+
|
|
1413
|
+
|
|
1414
|
+
@respx.mock
|
|
1415
|
+
def test_get_transactions_for_address_full():
|
|
1416
|
+
route = mock_rpc(
|
|
1417
|
+
{
|
|
1418
|
+
"data": [
|
|
1419
|
+
{
|
|
1420
|
+
"slot": 1054,
|
|
1421
|
+
"transactionIndex": 42,
|
|
1422
|
+
"transaction": {
|
|
1423
|
+
"signatures": [
|
|
1424
|
+
"5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv"
|
|
1425
|
+
],
|
|
1426
|
+
"message": {
|
|
1427
|
+
"accountKeys": ["AccountA", "AccountB"],
|
|
1428
|
+
"instructions": [],
|
|
1429
|
+
},
|
|
1430
|
+
},
|
|
1431
|
+
"meta": {
|
|
1432
|
+
"fee": 5000,
|
|
1433
|
+
"preBalances": [1000000, 2000000],
|
|
1434
|
+
"postBalances": [999995000, 2000000],
|
|
1435
|
+
},
|
|
1436
|
+
"blockTime": 1641038400,
|
|
1437
|
+
}
|
|
1438
|
+
],
|
|
1439
|
+
"paginationToken": "1055:5",
|
|
1440
|
+
}
|
|
1441
|
+
)
|
|
1442
|
+
with SolanaRpcClient(api_key="test") as client:
|
|
1443
|
+
data, pagination_token = client.get_transactions_for_address(
|
|
1444
|
+
address="Vote111111111111111111111111111111111111111",
|
|
1445
|
+
transaction_details="full",
|
|
1446
|
+
encoding="jsonParsed",
|
|
1447
|
+
max_supported_transaction_version=0,
|
|
1448
|
+
)
|
|
1449
|
+
assert len(data) == 1
|
|
1450
|
+
assert data[0].slot == 1054
|
|
1451
|
+
assert data[0].transaction_index == 42
|
|
1452
|
+
assert data[0].block_time == 1641038400
|
|
1453
|
+
assert data[0].meta["fee"] == 5000
|
|
1454
|
+
assert data[0].transaction["message"]["accountKeys"] == ["AccountA", "AccountB"]
|
|
1455
|
+
assert pagination_token == "1055:5"
|
|
1456
|
+
assert body(route)["method"] == "getTransactionsForAddress"
|
|
1457
|
+
assert body(route)["params"] == [
|
|
1458
|
+
"Vote111111111111111111111111111111111111111",
|
|
1459
|
+
{
|
|
1460
|
+
"transactionDetails": "full",
|
|
1461
|
+
"encoding": "jsonParsed",
|
|
1462
|
+
"maxSupportedTransactionVersion": 0,
|
|
1463
|
+
},
|
|
1464
|
+
]
|
|
1465
|
+
assert_api_key(route)
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
@respx.mock
|
|
1469
|
+
def test_get_transactions_for_address_minimal():
|
|
1470
|
+
route = mock_rpc({"data": [], "paginationToken": None})
|
|
1471
|
+
with SolanaRpcClient(api_key="test") as client:
|
|
1472
|
+
data, pagination_token = client.get_transactions_for_address(address="Addr")
|
|
1473
|
+
assert data == []
|
|
1474
|
+
assert pagination_token is None
|
|
1475
|
+
assert body(route)["params"] == ["Addr"]
|
|
1476
|
+
assert_api_key(route)
|
|
1477
|
+
|
|
1478
|
+
|
|
1479
|
+
@respx.mock
|
|
1480
|
+
def test_get_transactions_for_address_rejects_out_of_range_limit():
|
|
1481
|
+
mock_rpc({"data": [], "paginationToken": None})
|
|
1482
|
+
with SolanaRpcClient(api_key="test") as client:
|
|
1483
|
+
with pytest.raises(Exception):
|
|
1484
|
+
client.get_transactions_for_address(address="Addr", limit=1001)
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
# ---------------------------------------------------------------------------
|
|
1488
|
+
# get_transfers_by_address
|
|
1489
|
+
# ---------------------------------------------------------------------------
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
@respx.mock
|
|
1493
|
+
def test_get_transfers_by_address():
|
|
1494
|
+
route = mock_rpc(
|
|
1495
|
+
{
|
|
1496
|
+
"data": [
|
|
1497
|
+
{
|
|
1498
|
+
"signature": "5GEX7Q3X5Q8yJGbKYoR7mtzQmG8tpoEwzjPgqVmn3y5xg3yKwqXcDdN5YVcc9V6vA4TuH5iM6FHRVhTxvz4AX2zG",
|
|
1499
|
+
"slot": 315073428,
|
|
1500
|
+
"blockTime": 1736159420,
|
|
1501
|
+
"type": "transfer",
|
|
1502
|
+
"fromUserAccount": "7hPhaUpydpvm8wtiS3k4LPZKUmivQRs7YQmpE1hFshHx",
|
|
1503
|
+
"toUserAccount": "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
|
|
1504
|
+
"fromTokenAccount": "HcvK3EJ74iM9g11cUgsaPvLSrhCvCwcrWxBNd87LsC1x",
|
|
1505
|
+
"toTokenAccount": "CBcYniR9G9CN3zGMnwNE4SWbqkYWvCFVreEob9xHnQCY",
|
|
1506
|
+
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
1507
|
+
"amount": "2500000",
|
|
1508
|
+
"decimals": 6,
|
|
1509
|
+
"uiAmount": "2.5",
|
|
1510
|
+
"confirmationStatus": "finalized",
|
|
1511
|
+
"transactionIdx": 35,
|
|
1512
|
+
"instructionIdx": 1,
|
|
1513
|
+
"innerInstructionIdx": 0,
|
|
1514
|
+
}
|
|
1515
|
+
],
|
|
1516
|
+
"paginationToken": "315073428:35:1:0:splTransfer",
|
|
1517
|
+
}
|
|
1518
|
+
)
|
|
1519
|
+
with SolanaRpcClient(api_key="test") as client:
|
|
1520
|
+
data, pagination_token = client.get_transfers_by_address(
|
|
1521
|
+
address="86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
|
|
1522
|
+
with_address="7hPhaUpydpvm8wtiS3k4LPZKUmivQRs7YQmpE1hFshHx",
|
|
1523
|
+
direction="in",
|
|
1524
|
+
mint="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
1525
|
+
sol_mode="separate",
|
|
1526
|
+
filters={
|
|
1527
|
+
"amount": {"gte": 1000000000, "lt": 10000000000},
|
|
1528
|
+
"blockTime": {"gte": 1735718400},
|
|
1529
|
+
},
|
|
1530
|
+
limit=50,
|
|
1531
|
+
pagination_token="315069220:308:2:1:splTransfer",
|
|
1532
|
+
sort_order="desc",
|
|
1533
|
+
)
|
|
1534
|
+
assert len(data) == 1
|
|
1535
|
+
transfer = data[0]
|
|
1536
|
+
assert transfer.signature.startswith("5GEX")
|
|
1537
|
+
assert transfer.slot == 315073428
|
|
1538
|
+
assert transfer.block_time == 1736159420
|
|
1539
|
+
assert transfer.type == "transfer"
|
|
1540
|
+
assert transfer.from_user_account == "7hPhaUpydpvm8wtiS3k4LPZKUmivQRs7YQmpE1hFshHx"
|
|
1541
|
+
assert transfer.to_user_account == "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY"
|
|
1542
|
+
assert transfer.from_token_account == "HcvK3EJ74iM9g11cUgsaPvLSrhCvCwcrWxBNd87LsC1x"
|
|
1543
|
+
assert transfer.to_token_account == "CBcYniR9G9CN3zGMnwNE4SWbqkYWvCFVreEob9xHnQCY"
|
|
1544
|
+
assert transfer.mint == "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
|
1545
|
+
assert transfer.amount == "2500000"
|
|
1546
|
+
assert transfer.decimals == 6
|
|
1547
|
+
assert transfer.ui_amount == "2.5"
|
|
1548
|
+
assert transfer.confirmation_status == "finalized"
|
|
1549
|
+
assert transfer.transaction_idx == 35
|
|
1550
|
+
assert transfer.instruction_idx == 1
|
|
1551
|
+
assert transfer.inner_instruction_idx == 0
|
|
1552
|
+
assert transfer.fee_amount is None
|
|
1553
|
+
assert transfer.fee_ui_amount is None
|
|
1554
|
+
assert pagination_token == "315073428:35:1:0:splTransfer"
|
|
1555
|
+
assert body(route)["method"] == "getTransfersByAddress"
|
|
1556
|
+
assert body(route)["params"] == [
|
|
1557
|
+
"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
|
|
1558
|
+
{
|
|
1559
|
+
"with": "7hPhaUpydpvm8wtiS3k4LPZKUmivQRs7YQmpE1hFshHx",
|
|
1560
|
+
"direction": "in",
|
|
1561
|
+
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
1562
|
+
"solMode": "separate",
|
|
1563
|
+
"filters": {
|
|
1564
|
+
"amount": {"gte": 1000000000, "lt": 10000000000},
|
|
1565
|
+
"blockTime": {"gte": 1735718400},
|
|
1566
|
+
},
|
|
1567
|
+
"limit": 50,
|
|
1568
|
+
"paginationToken": "315069220:308:2:1:splTransfer",
|
|
1569
|
+
"sortOrder": "desc",
|
|
1570
|
+
},
|
|
1571
|
+
]
|
|
1572
|
+
assert_api_key(route)
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
@respx.mock
|
|
1576
|
+
def test_get_transfers_by_address_native_sol_transfer():
|
|
1577
|
+
"""Native SOL transfers omit token-account fields."""
|
|
1578
|
+
route = mock_rpc(
|
|
1579
|
+
{
|
|
1580
|
+
"data": [
|
|
1581
|
+
{
|
|
1582
|
+
"signature": "5GEX7Q3X5Q8yJGbKYoR7mtzQmG8tpoEwzjPgqVmn3y5xg3yKwqXcDdN5YVcc9V6vA4TuH5iM6FHRVhTxvz4AX2zG",
|
|
1583
|
+
"slot": 315073428,
|
|
1584
|
+
"blockTime": 1736159420,
|
|
1585
|
+
"type": "transfer",
|
|
1586
|
+
"fromUserAccount": "7hPhaUpydpvm8wtiS3k4LPZKUmivQRs7YQmpE1hFshHx",
|
|
1587
|
+
"toUserAccount": "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
|
|
1588
|
+
"mint": "So11111111111111111111111111111111111111111",
|
|
1589
|
+
"amount": "1000000000",
|
|
1590
|
+
"decimals": 9,
|
|
1591
|
+
"uiAmount": "1",
|
|
1592
|
+
"confirmationStatus": "finalized",
|
|
1593
|
+
"transactionIdx": 0,
|
|
1594
|
+
"instructionIdx": 0,
|
|
1595
|
+
"innerInstructionIdx": 0,
|
|
1596
|
+
}
|
|
1597
|
+
],
|
|
1598
|
+
"paginationToken": None,
|
|
1599
|
+
}
|
|
1600
|
+
)
|
|
1601
|
+
with SolanaRpcClient(api_key="test") as client:
|
|
1602
|
+
data, pagination_token = client.get_transfers_by_address(
|
|
1603
|
+
address="86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
|
|
1604
|
+
sol_mode="merged",
|
|
1605
|
+
)
|
|
1606
|
+
assert pagination_token is None
|
|
1607
|
+
assert data[0].from_token_account is None
|
|
1608
|
+
assert data[0].to_token_account is None
|
|
1609
|
+
assert data[0].mint == "So11111111111111111111111111111111111111111"
|
|
1610
|
+
assert body(route)["params"][1] == {"solMode": "merged"}
|
|
1611
|
+
|
|
1612
|
+
|
|
1613
|
+
@respx.mock
|
|
1614
|
+
def test_get_transfers_by_address_fee_bearing_transfer():
|
|
1615
|
+
"""Token-2022 fee-bearing transfers include feeAmount and feeUiAmount."""
|
|
1616
|
+
route = mock_rpc(
|
|
1617
|
+
{
|
|
1618
|
+
"data": [
|
|
1619
|
+
{
|
|
1620
|
+
"signature": "5GEX7Q3X5Q8yJGbKYoR7mtzQmG8tpoEwzjPgqVmn3y5xg3yKwqXcDdN5YVcc9V6vA4TuH5iM6FHRVhTxvz4AX2zG",
|
|
1621
|
+
"slot": 315073428,
|
|
1622
|
+
"blockTime": 1736159420,
|
|
1623
|
+
"type": "transfer",
|
|
1624
|
+
"fromUserAccount": "7hPhaUpydpvm8wtiS3k4LPZKUmivQRs7YQmpE1hFshHx",
|
|
1625
|
+
"toUserAccount": "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
|
|
1626
|
+
"fromTokenAccount": "HcvK3EJ74iM9g11cUgsaPvLSrhCvCwcrWxBNd87LsC1x",
|
|
1627
|
+
"toTokenAccount": "CBcYniR9G9CN3zGMnwNE4SWbqkYWvCFVreEob9xHnQCY",
|
|
1628
|
+
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
1629
|
+
"amount": "2500000",
|
|
1630
|
+
"feeAmount": "13450000",
|
|
1631
|
+
"decimals": 6,
|
|
1632
|
+
"uiAmount": "2.5",
|
|
1633
|
+
"feeUiAmount": "13.45",
|
|
1634
|
+
"confirmationStatus": "finalized",
|
|
1635
|
+
"transactionIdx": 35,
|
|
1636
|
+
"instructionIdx": 1,
|
|
1637
|
+
"innerInstructionIdx": 0,
|
|
1638
|
+
}
|
|
1639
|
+
],
|
|
1640
|
+
"paginationToken": None,
|
|
1641
|
+
}
|
|
1642
|
+
)
|
|
1643
|
+
with SolanaRpcClient(api_key="test") as client:
|
|
1644
|
+
data, _ = client.get_transfers_by_address(address="Addr")
|
|
1645
|
+
assert data[0].fee_amount == "13450000"
|
|
1646
|
+
assert data[0].fee_ui_amount == "13.45"
|
|
1647
|
+
assert_api_key(route)
|
|
1648
|
+
|
|
1649
|
+
|
|
1650
|
+
@respx.mock
|
|
1651
|
+
def test_get_transfers_by_address_minimal():
|
|
1652
|
+
route = mock_rpc({"data": [], "paginationToken": None})
|
|
1653
|
+
with SolanaRpcClient(api_key="test") as client:
|
|
1654
|
+
data, pagination_token = client.get_transfers_by_address(address="Addr")
|
|
1655
|
+
assert data == []
|
|
1656
|
+
assert pagination_token is None
|
|
1657
|
+
assert body(route)["params"] == ["Addr"]
|
|
1658
|
+
assert_api_key(route)
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
@respx.mock
|
|
1662
|
+
def test_get_transfers_by_address_rejects_out_of_range_limit():
|
|
1663
|
+
mock_rpc({"data": [], "paginationToken": None})
|
|
1664
|
+
with SolanaRpcClient(api_key="test") as client:
|
|
1665
|
+
with pytest.raises(Exception):
|
|
1666
|
+
client.get_transfers_by_address(address="Addr", limit=101)
|
|
1667
|
+
|
|
1668
|
+
|
|
1336
1669
|
# ---------------------------------------------------------------------------
|
|
1337
1670
|
# get_version
|
|
1338
1671
|
# ---------------------------------------------------------------------------
|
helius_python-0.3.2/TODO.md
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|