helius-python 0.3.3__tar.gz → 0.3.4__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.3 → helius_python-0.3.4}/PKG-INFO +1 -1
- helius_python-0.3.4/examples/README.md +4 -0
- helius_python-0.3.4/examples/laserstream/websocket_logs.py +76 -0
- helius_python-0.3.4/examples/solana_rpc/address_transactions.py +125 -0
- helius_python-0.3.4/examples/solana_rpc/address_transfers.py +118 -0
- {helius_python-0.3.3/examples → helius_python-0.3.4/examples/solana_rpc}/block_explorer.py +4 -3
- {helius_python-0.3.3/examples → helius_python-0.3.4/examples/solana_rpc}/devnet_airdrop.py +26 -4
- {helius_python-0.3.3/examples → helius_python-0.3.4/examples/solana_rpc}/network_status.py +1 -1
- {helius_python-0.3.3/examples → helius_python-0.3.4/examples/solana_rpc}/priority_fees.py +2 -2
- {helius_python-0.3.3/examples → helius_python-0.3.4/examples/solana_rpc}/stake_overview.py +5 -2
- {helius_python-0.3.3/examples → helius_python-0.3.4/examples/solana_rpc}/token_inspector.py +12 -5
- {helius_python-0.3.3/examples → helius_python-0.3.4/examples/solana_rpc}/transaction_inspector.py +2 -2
- {helius_python-0.3.3/examples → helius_python-0.3.4/examples/solana_rpc}/wallet_tracker.py +4 -4
- {helius_python-0.3.3 → helius_python-0.3.4}/pyproject.toml +1 -1
- helius_python-0.3.4/test_examples.py +216 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/.editorconfig +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/.github/workflows/python-package.yml +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/.github/workflows/python-publish.yml +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/.gitignore +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/AGENTS.md +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/CLAUDE.md +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/CONTRIBUTING.md +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/LICENSE +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/README.md +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/TODO.md +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/requirements.txt +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/src/helius/__init__.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/src/helius/admin/__init__.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/src/helius/admin/admin.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/src/helius/laserstream/websockets.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/src/helius/rpc/__init__.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/src/helius/rpc/json_rpc_request.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/src/helius/solana_rpc/__init__.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/src/helius/solana_rpc/client.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/src/helius/solana_rpc/models.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/tests/fixtures/account.json +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/tests/fixtures/supply.json +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/tests/unit/admin/test_admin.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/tests/unit/lasterstream/test_websockets.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/tests/unit/rpc/test_json_rpc_request.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/tests/unit/solana_rpc/test_client.py +0 -0
- {helius_python-0.3.3 → helius_python-0.3.4}/tests/unit/solana_rpc/test_models.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: helius-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
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
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Listen for Solana log notifications over Helius WebSockets.
|
|
2
|
+
|
|
3
|
+
Subscribes with `logsSubscribe`, prints a small number of notifications,
|
|
4
|
+
then unsubscribes cleanly. Use `--mentions <ADDRESS>` to filter logs to a
|
|
5
|
+
single account or program; otherwise the example listens to all logs.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
|
|
9
|
+
export HELIUS_API_KEY=your_helius_api_key
|
|
10
|
+
python examples/laserstream/websocket_logs.py --mentions <ADDRESS>
|
|
11
|
+
python examples/laserstream/websocket_logs.py --count 3
|
|
12
|
+
|
|
13
|
+
Docs:
|
|
14
|
+
https://www.helius.dev/docs/api-reference/rpc/websocket/logssubscribe
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
from contextlib import suppress
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
from helius.laserstream.websockets import WebSocketClient
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main() -> int:
|
|
27
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--mentions",
|
|
30
|
+
help="Only receive logs mentioning this account or program address",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--count",
|
|
34
|
+
type=int,
|
|
35
|
+
default=5,
|
|
36
|
+
help="Number of notifications to print before exiting (default 5)",
|
|
37
|
+
)
|
|
38
|
+
args = parser.parse_args()
|
|
39
|
+
|
|
40
|
+
log_filter = {"mentions": [args.mentions]} if args.mentions else "all"
|
|
41
|
+
|
|
42
|
+
with WebSocketClient() as client:
|
|
43
|
+
subscription = client.logs_subscribe(
|
|
44
|
+
filter=log_filter,
|
|
45
|
+
commitment="confirmed",
|
|
46
|
+
)
|
|
47
|
+
print(f"Subscribed to logs with id {subscription}. Waiting...\n")
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
for index, (context, notification, _subscription) in enumerate(
|
|
51
|
+
client.listen(), start=1
|
|
52
|
+
):
|
|
53
|
+
slot = context.get("slot") if context else "unknown"
|
|
54
|
+
status = "ERR" if notification.err else "OK "
|
|
55
|
+
print(f"[{index}/{args.count}] slot={slot} {status} {notification.signature}")
|
|
56
|
+
for line in notification.logs[:5]:
|
|
57
|
+
print(f" {line}")
|
|
58
|
+
if len(notification.logs) > 5:
|
|
59
|
+
print(f" ... {len(notification.logs) - 5} more log lines")
|
|
60
|
+
print()
|
|
61
|
+
if index >= args.count:
|
|
62
|
+
break
|
|
63
|
+
finally:
|
|
64
|
+
# A busy `logsSubscribe` stream can deliver another notification
|
|
65
|
+
# between our unsubscribe request and the unsubscribe response. The
|
|
66
|
+
# client helper currently expects the very next frame to be the RPC
|
|
67
|
+
# response, so ignore that race here and let the context manager
|
|
68
|
+
# close the socket cleanly.
|
|
69
|
+
with suppress(KeyError):
|
|
70
|
+
client.logs_unsubscribe(subscription)
|
|
71
|
+
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
sys.exit(main())
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""List Helius enhanced transactions for an address.
|
|
2
|
+
|
|
3
|
+
Uses Helius's `getTransactionsForAddress` RPC method, which can return
|
|
4
|
+
either compact signature rows or full transaction payloads with pagination.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
|
|
8
|
+
export HELIUS_API_KEY=your_helius_api_key
|
|
9
|
+
python examples/solana_rpc/address_transactions.py <ADDRESS> [--limit 10]
|
|
10
|
+
python examples/solana_rpc/address_transactions.py <ADDRESS> --full --limit 5
|
|
11
|
+
|
|
12
|
+
Docs:
|
|
13
|
+
https://www.helius.dev/docs/getting-data/get-transactions-for-address
|
|
14
|
+
|
|
15
|
+
Note:
|
|
16
|
+
Helius documents this exclusive RPC method as requiring a Developer plan
|
|
17
|
+
or higher.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import datetime as dt
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
import httpx
|
|
27
|
+
|
|
28
|
+
from helius.solana_rpc import SolanaRpcClient
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def format_time(block_time: int | None) -> str:
|
|
32
|
+
if block_time is None:
|
|
33
|
+
return "(no time)"
|
|
34
|
+
return dt.datetime.fromtimestamp(block_time, tz=dt.timezone.utc).strftime(
|
|
35
|
+
"%Y-%m-%d %H:%M:%SZ"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main() -> int:
|
|
40
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
41
|
+
parser.add_argument("address", help="Wallet, account, or program address")
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--limit",
|
|
44
|
+
type=int,
|
|
45
|
+
default=10,
|
|
46
|
+
help="Number of rows to fetch (1-1000, default 10)",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--pagination-token",
|
|
50
|
+
help="Token returned by a previous page of results",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--full",
|
|
54
|
+
action="store_true",
|
|
55
|
+
help="Fetch full transaction details instead of signature rows",
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--dry-run",
|
|
59
|
+
action="store_true",
|
|
60
|
+
help="Validate arguments and print the planned request without sending it",
|
|
61
|
+
)
|
|
62
|
+
args = parser.parse_args()
|
|
63
|
+
|
|
64
|
+
details = "full" if args.full else "signatures"
|
|
65
|
+
if args.dry_run:
|
|
66
|
+
print("Would call get_transactions_for_address with:")
|
|
67
|
+
print(f" address={args.address}")
|
|
68
|
+
print(f" transaction_details={details}")
|
|
69
|
+
print(f" limit={args.limit}")
|
|
70
|
+
print(f" pagination_token={args.pagination_token}")
|
|
71
|
+
print(f" encoding={'jsonParsed' if args.full else None}")
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
with SolanaRpcClient() as client:
|
|
76
|
+
transactions, next_token = client.get_transactions_for_address(
|
|
77
|
+
address=args.address,
|
|
78
|
+
transaction_details=details,
|
|
79
|
+
sort_order="desc",
|
|
80
|
+
commitment="finalized",
|
|
81
|
+
limit=args.limit,
|
|
82
|
+
pagination_token=args.pagination_token,
|
|
83
|
+
encoding="jsonParsed" if args.full else None,
|
|
84
|
+
max_supported_transaction_version=0 if args.full else None,
|
|
85
|
+
)
|
|
86
|
+
except httpx.HTTPStatusError as exc:
|
|
87
|
+
print(
|
|
88
|
+
f"HTTP {exc.response.status_code}: getTransactionsForAddress was rejected. "
|
|
89
|
+
"This Helius-exclusive method may require a Developer plan or higher.",
|
|
90
|
+
file=sys.stderr,
|
|
91
|
+
)
|
|
92
|
+
return 2
|
|
93
|
+
|
|
94
|
+
print(f"\n=== Transactions for {args.address} ===\n")
|
|
95
|
+
if not transactions:
|
|
96
|
+
print("No transactions returned.")
|
|
97
|
+
return 0
|
|
98
|
+
|
|
99
|
+
for tx in transactions:
|
|
100
|
+
if details == "signatures":
|
|
101
|
+
status = "ERR" if tx.err else "OK "
|
|
102
|
+
print(
|
|
103
|
+
f"{format_time(tx.block_time)} slot={tx.slot:<12} "
|
|
104
|
+
f"idx={tx.transaction_index:<4} {status} {tx.signature}"
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
signature = "(signature unavailable)"
|
|
108
|
+
signatures = tx.transaction.get("signatures")
|
|
109
|
+
if isinstance(signatures, list) and signatures:
|
|
110
|
+
signature = signatures[0]
|
|
111
|
+
err = tx.meta.get("err") if isinstance(tx.meta, dict) else None
|
|
112
|
+
status = "ERR" if err else "OK "
|
|
113
|
+
print(
|
|
114
|
+
f"{format_time(tx.block_time)} slot={tx.slot:<12} "
|
|
115
|
+
f"idx={tx.transaction_index:<4} {status} {signature}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if next_token:
|
|
119
|
+
print(f"\nNext page token: {next_token}")
|
|
120
|
+
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
sys.exit(main())
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""List token and SOL transfers for an address.
|
|
2
|
+
|
|
3
|
+
Uses Helius's `getTransfersByAddress` RPC method exposed by this client as
|
|
4
|
+
`get_transfers_by_address`.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
|
|
8
|
+
export HELIUS_API_KEY=your_helius_api_key
|
|
9
|
+
python examples/solana_rpc/address_transfers.py <ADDRESS> [--limit 20]
|
|
10
|
+
python examples/solana_rpc/address_transfers.py <ADDRESS> --direction in --mint <MINT>
|
|
11
|
+
|
|
12
|
+
Docs:
|
|
13
|
+
https://www.helius.dev/docs/getting-data/get-transfers-by-address
|
|
14
|
+
|
|
15
|
+
Note:
|
|
16
|
+
Helius documents this exclusive RPC method as requiring a Developer plan
|
|
17
|
+
or higher.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import datetime as dt
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
import httpx
|
|
27
|
+
|
|
28
|
+
from helius.solana_rpc import SolanaRpcClient
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def format_time(block_time: int) -> str:
|
|
32
|
+
return dt.datetime.fromtimestamp(block_time, tz=dt.timezone.utc).strftime(
|
|
33
|
+
"%Y-%m-%d %H:%M:%SZ"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main() -> int:
|
|
38
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
39
|
+
parser.add_argument("address", help="Wallet, token account, or owner address")
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--limit",
|
|
42
|
+
type=int,
|
|
43
|
+
default=20,
|
|
44
|
+
help="Number of transfers to fetch (1-100, default 20)",
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--direction",
|
|
48
|
+
choices=("in", "out", "any"),
|
|
49
|
+
default="any",
|
|
50
|
+
help="Transfer direction relative to address (default any)",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument("--with-address", help="Counterparty address filter")
|
|
53
|
+
parser.add_argument("--mint", help="Mint address filter")
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--pagination-token",
|
|
56
|
+
help="Token returned by a previous page of results",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--dry-run",
|
|
60
|
+
action="store_true",
|
|
61
|
+
help="Validate arguments and print the planned request without sending it",
|
|
62
|
+
)
|
|
63
|
+
args = parser.parse_args()
|
|
64
|
+
|
|
65
|
+
if args.dry_run:
|
|
66
|
+
print("Would call get_transfers_by_address with:")
|
|
67
|
+
print(f" address={args.address}")
|
|
68
|
+
print(f" with_address={args.with_address}")
|
|
69
|
+
print(f" direction={args.direction}")
|
|
70
|
+
print(f" mint={args.mint}")
|
|
71
|
+
print(f" limit={args.limit}")
|
|
72
|
+
print(f" pagination_token={args.pagination_token}")
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
with SolanaRpcClient() as client:
|
|
77
|
+
transfers, next_token = client.get_transfers_by_address(
|
|
78
|
+
address=args.address,
|
|
79
|
+
with_address=args.with_address,
|
|
80
|
+
direction=args.direction,
|
|
81
|
+
mint=args.mint,
|
|
82
|
+
limit=args.limit,
|
|
83
|
+
pagination_token=args.pagination_token,
|
|
84
|
+
commitment="finalized",
|
|
85
|
+
sort_order="desc",
|
|
86
|
+
)
|
|
87
|
+
except httpx.HTTPStatusError as exc:
|
|
88
|
+
print(
|
|
89
|
+
f"HTTP {exc.response.status_code}: getTransfersByAddress was rejected. "
|
|
90
|
+
"This Helius-exclusive method may require a Developer plan or higher.",
|
|
91
|
+
file=sys.stderr,
|
|
92
|
+
)
|
|
93
|
+
return 2
|
|
94
|
+
|
|
95
|
+
print(f"\n=== Transfers for {args.address} ===\n")
|
|
96
|
+
if not transfers:
|
|
97
|
+
print("No transfers returned.")
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
for transfer in transfers:
|
|
101
|
+
from_acct = transfer.from_user_account or transfer.from_token_account or "-"
|
|
102
|
+
to_acct = transfer.to_user_account or transfer.to_token_account or "-"
|
|
103
|
+
print(
|
|
104
|
+
f"{format_time(transfer.block_time)} slot={transfer.slot:<12} "
|
|
105
|
+
f"{transfer.type:<11} {transfer.ui_amount:>18} mint={transfer.mint}"
|
|
106
|
+
)
|
|
107
|
+
print(f" from={from_acct}")
|
|
108
|
+
print(f" to ={to_acct}")
|
|
109
|
+
print(f" sig ={transfer.signature}")
|
|
110
|
+
|
|
111
|
+
if next_token:
|
|
112
|
+
print(f"\nNext page token: {next_token}")
|
|
113
|
+
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
sys.exit(main())
|
|
@@ -8,8 +8,8 @@ breakdown of successful vs. failed transactions plus total fees paid.
|
|
|
8
8
|
Usage:
|
|
9
9
|
|
|
10
10
|
export HELIUS_API_KEY=your_helius_api_key
|
|
11
|
-
python examples/block_explorer.py
|
|
12
|
-
python examples/block_explorer.py --slot 250000000
|
|
11
|
+
python examples/solana_rpc/block_explorer.py
|
|
12
|
+
python examples/solana_rpc/block_explorer.py --slot 250000000
|
|
13
13
|
|
|
14
14
|
Uses (with `with`):
|
|
15
15
|
get_slot, get_block.
|
|
@@ -43,9 +43,10 @@ def main() -> int:
|
|
|
43
43
|
else helius.get_slot(commitment="finalized")
|
|
44
44
|
)
|
|
45
45
|
block = helius.get_block(
|
|
46
|
-
slot,
|
|
46
|
+
slot=slot,
|
|
47
47
|
commitment="finalized",
|
|
48
48
|
encoding="jsonParsed",
|
|
49
|
+
rewards=False,
|
|
49
50
|
max_supported_transcation_version=0,
|
|
50
51
|
)
|
|
51
52
|
|
|
@@ -7,7 +7,7 @@ polls `getSignatureStatuses` until the airdrop transaction reaches a
|
|
|
7
7
|
Usage:
|
|
8
8
|
|
|
9
9
|
export HELIUS_API_KEY=your_helius_api_key
|
|
10
|
-
python examples/devnet_airdrop.py <WALLET_ADDRESS> [--sol 1.0]
|
|
10
|
+
python examples/solana_rpc/devnet_airdrop.py <WALLET_ADDRESS> [--sol 1.0]
|
|
11
11
|
|
|
12
12
|
Note:
|
|
13
13
|
`requestAirdrop` is only available on Devnet and Testnet — never on
|
|
@@ -24,6 +24,8 @@ import argparse
|
|
|
24
24
|
import sys
|
|
25
25
|
import time
|
|
26
26
|
|
|
27
|
+
import httpx
|
|
28
|
+
|
|
27
29
|
from helius.solana_rpc import SolanaRpcClient
|
|
28
30
|
|
|
29
31
|
LAMPORTS_PER_SOL = 1_000_000_000
|
|
@@ -45,20 +47,40 @@ def main() -> int:
|
|
|
45
47
|
default=30.0,
|
|
46
48
|
help="Seconds to wait for confirmation (default: 30)",
|
|
47
49
|
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--dry-run",
|
|
52
|
+
action="store_true",
|
|
53
|
+
help="Validate arguments and print the planned airdrop without sending it",
|
|
54
|
+
)
|
|
48
55
|
args = parser.parse_args()
|
|
49
56
|
lamports = int(args.sol * LAMPORTS_PER_SOL)
|
|
50
57
|
|
|
58
|
+
if args.dry_run:
|
|
59
|
+
print("Would request devnet airdrop with:")
|
|
60
|
+
print(f" address={args.address}")
|
|
61
|
+
print(f" sol={args.sol}")
|
|
62
|
+
print(f" lamports={lamports}")
|
|
63
|
+
print(f" timeout={args.timeout}")
|
|
64
|
+
return 0
|
|
65
|
+
|
|
51
66
|
client = SolanaRpcClient(base_url=DEVNET_URL)
|
|
52
67
|
try:
|
|
53
68
|
print(f"Requesting {args.sol} SOL airdrop to {args.address} on devnet...")
|
|
54
|
-
|
|
69
|
+
try:
|
|
70
|
+
signature = client.request_airdrop(public_key=args.address, lamports=lamports)
|
|
71
|
+
except httpx.HTTPStatusError as exc:
|
|
72
|
+
print(
|
|
73
|
+
f"HTTP {exc.response.status_code}: devnet airdrop request was rejected.",
|
|
74
|
+
file=sys.stderr,
|
|
75
|
+
)
|
|
76
|
+
return 2
|
|
55
77
|
print(f" signature: {signature}")
|
|
56
78
|
|
|
57
79
|
deadline = time.monotonic() + args.timeout
|
|
58
80
|
status = None
|
|
59
81
|
while time.monotonic() < deadline:
|
|
60
82
|
_ctx, statuses = client.get_signature_statuses(
|
|
61
|
-
[signature], search_transaction_history=True
|
|
83
|
+
signatures=[signature], search_transaction_history=True
|
|
62
84
|
)
|
|
63
85
|
status = statuses[0]
|
|
64
86
|
if status is not None and status.confirmation_status in (
|
|
@@ -79,7 +101,7 @@ def main() -> int:
|
|
|
79
101
|
return 1
|
|
80
102
|
print(f" status: {status.confirmation_status} at slot {status.slot}")
|
|
81
103
|
|
|
82
|
-
_ctx, balance = client.get_balance(args.address)
|
|
104
|
+
_ctx, balance = client.get_balance(public_key=args.address)
|
|
83
105
|
print(f"Post-airdrop balance: {balance / LAMPORTS_PER_SOL:.9f} SOL")
|
|
84
106
|
finally:
|
|
85
107
|
client.close()
|
|
@@ -7,7 +7,7 @@ recent-performance summary (avg TPS over the last samples).
|
|
|
7
7
|
Usage:
|
|
8
8
|
|
|
9
9
|
export HELIUS_API_KEY=your_helius_api_key
|
|
10
|
-
python examples/network_status.py
|
|
10
|
+
python examples/solana_rpc/network_status.py
|
|
11
11
|
|
|
12
12
|
Uses (with `with`):
|
|
13
13
|
get_health, get_version, get_slot, get_block_height, get_epoch_info,
|
|
@@ -12,8 +12,8 @@ sample only counts transactions that locked those accounts as writable
|
|
|
12
12
|
Usage:
|
|
13
13
|
|
|
14
14
|
export HELIUS_API_KEY=your_helius_api_key
|
|
15
|
-
python examples/priority_fees.py
|
|
16
|
-
python examples/priority_fees.py --account <PUBKEY> --account <PUBKEY>
|
|
15
|
+
python examples/solana_rpc/priority_fees.py
|
|
16
|
+
python examples/solana_rpc/priority_fees.py --account <PUBKEY> --account <PUBKEY>
|
|
17
17
|
|
|
18
18
|
Uses (with `with`):
|
|
19
19
|
get_recent_prioritization_fees.
|
|
@@ -7,7 +7,7 @@ validator set (top 10 by stake).
|
|
|
7
7
|
Usage:
|
|
8
8
|
|
|
9
9
|
export HELIUS_API_KEY=your_helius_api_key
|
|
10
|
-
python examples/stake_overview.py
|
|
10
|
+
python examples/solana_rpc/stake_overview.py
|
|
11
11
|
|
|
12
12
|
Uses (with `try/finally`):
|
|
13
13
|
get_inflation_rate, get_inflation_governor, get_supply,
|
|
@@ -18,6 +18,8 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
import sys
|
|
20
20
|
|
|
21
|
+
import httpx
|
|
22
|
+
|
|
21
23
|
from helius.solana_rpc import SolanaRpcClient
|
|
22
24
|
|
|
23
25
|
LAMPORTS_PER_SOL = 1_000_000_000
|
|
@@ -25,10 +27,11 @@ LAMPORTS_PER_SOL = 1_000_000_000
|
|
|
25
27
|
|
|
26
28
|
def main() -> int:
|
|
27
29
|
client = SolanaRpcClient()
|
|
30
|
+
client._client.timeout = httpx.Timeout(30.0)
|
|
28
31
|
try:
|
|
29
32
|
rate = client.get_inflation_rate()
|
|
30
33
|
gov = client.get_inflation_governor()
|
|
31
|
-
_ctx, supply = client.get_supply(exclude_non_circulating_accounts_list=
|
|
34
|
+
_ctx, supply = client.get_supply(exclude_non_circulating_accounts_list=False)
|
|
32
35
|
_ctx, min_stake = client.get_stake_minimum_delegation()
|
|
33
36
|
current, delinquent = client.get_vote_accounts()
|
|
34
37
|
finally:
|
|
@@ -7,11 +7,15 @@ Given a mint address, prints:
|
|
|
7
7
|
Usage:
|
|
8
8
|
|
|
9
9
|
export HELIUS_API_KEY=your_helius_api_key
|
|
10
|
-
python examples/token_inspector.py <MINT_ADDRESS>
|
|
10
|
+
python examples/solana_rpc/token_inspector.py <MINT_ADDRESS>
|
|
11
11
|
|
|
12
|
-
Example with
|
|
12
|
+
Example with a small-holder-count mint:
|
|
13
13
|
|
|
14
|
-
python examples/token_inspector.py
|
|
14
|
+
python examples/solana_rpc/token_inspector.py J5iyNuTa6zqqA62Xe4h1VBvcBW5CTSNNva3QPh8DU5RV
|
|
15
|
+
|
|
16
|
+
Note:
|
|
17
|
+
Very large mints may be rejected by `getTokenLargestAccounts` if the
|
|
18
|
+
upstream RPC would need to scan too many accounts.
|
|
15
19
|
|
|
16
20
|
Uses (with `try/finally`):
|
|
17
21
|
get_token_supply, get_token_largest_accounts.
|
|
@@ -22,6 +26,8 @@ from __future__ import annotations
|
|
|
22
26
|
import argparse
|
|
23
27
|
import sys
|
|
24
28
|
|
|
29
|
+
import httpx
|
|
30
|
+
|
|
25
31
|
from helius.solana_rpc import SolanaRpcClient
|
|
26
32
|
|
|
27
33
|
|
|
@@ -31,9 +37,10 @@ def main() -> int:
|
|
|
31
37
|
args = parser.parse_args()
|
|
32
38
|
|
|
33
39
|
client = SolanaRpcClient()
|
|
40
|
+
client._client.timeout = httpx.Timeout(30.0)
|
|
34
41
|
try:
|
|
35
|
-
_ctx, supply = client.get_token_supply(args.mint)
|
|
36
|
-
_ctx, holders = client.get_token_largest_accounts(args.mint)
|
|
42
|
+
_ctx, supply = client.get_token_supply(mint_address=args.mint)
|
|
43
|
+
_ctx, holders = client.get_token_largest_accounts(mint=args.mint)
|
|
37
44
|
finally:
|
|
38
45
|
client.close()
|
|
39
46
|
|
{helius_python-0.3.3/examples → helius_python-0.3.4/examples/solana_rpc}/transaction_inspector.py
RENAMED
|
@@ -7,7 +7,7 @@ involved, and any log messages emitted by the on-chain programs.
|
|
|
7
7
|
Usage:
|
|
8
8
|
|
|
9
9
|
export HELIUS_API_KEY=your_helius_api_key
|
|
10
|
-
python examples/transaction_inspector.py <SIGNATURE>
|
|
10
|
+
python examples/solana_rpc/transaction_inspector.py <SIGNATURE>
|
|
11
11
|
|
|
12
12
|
Uses (with `with`):
|
|
13
13
|
get_transaction.
|
|
@@ -31,7 +31,7 @@ def main() -> int:
|
|
|
31
31
|
|
|
32
32
|
with SolanaRpcClient() as helius:
|
|
33
33
|
tx = helius.get_transaction(
|
|
34
|
-
args.signature,
|
|
34
|
+
transaction_signature=args.signature,
|
|
35
35
|
encoding="jsonParsed",
|
|
36
36
|
max_supported_transaction_version=0,
|
|
37
37
|
)
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
Usage:
|
|
4
4
|
|
|
5
5
|
export HELIUS_API_KEY=your_helius_api_key
|
|
6
|
-
python examples/wallet_tracker.py <WALLET_ADDRESS> [--limit 20]
|
|
6
|
+
python examples/solana_rpc/wallet_tracker.py <WALLET_ADDRESS> [--limit 20]
|
|
7
7
|
|
|
8
8
|
Example (Helius's own treasury-ish address, replace with any):
|
|
9
9
|
|
|
10
|
-
python examples/wallet_tracker.py 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
|
|
10
|
+
python examples/solana_rpc/wallet_tracker.py 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
|
|
11
11
|
|
|
12
12
|
It prints:
|
|
13
13
|
- SOL balance (in SOL, not lamports)
|
|
@@ -43,7 +43,7 @@ def main() -> int:
|
|
|
43
43
|
|
|
44
44
|
with SolanaRpcClient() as client: # reads HELIUS_API_KEY from env / .env
|
|
45
45
|
# --- SOL balance ---------------------------------------------------
|
|
46
|
-
_ctx, lamports = client.get_balance(args.address)
|
|
46
|
+
_ctx, lamports = client.get_balance(public_key=args.address)
|
|
47
47
|
print(f"\n=== {args.address} ===\n")
|
|
48
48
|
print(
|
|
49
49
|
f"SOL balance: {lamports / LAMPORTS_PER_SOL:.9f} SOL "
|
|
@@ -75,7 +75,7 @@ def main() -> int:
|
|
|
75
75
|
print("SPL token accounts: none with non-zero balance.\n")
|
|
76
76
|
|
|
77
77
|
# --- Recent activity ----------------------------------------------
|
|
78
|
-
sigs = client.get_signatures_for_address(args.address, limit=args.limit)
|
|
78
|
+
sigs = client.get_signatures_for_address(address=args.address, limit=args.limit)
|
|
79
79
|
print(f"Last {len(sigs)} signatures:")
|
|
80
80
|
for sig in sigs:
|
|
81
81
|
ts = (
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Run every example script with known-safe arguments.
|
|
2
|
+
|
|
3
|
+
This is a live smoke-test runner, not a pytest test module. It intentionally
|
|
4
|
+
executes the examples as subprocesses so imports, argument parsing, and runtime
|
|
5
|
+
behavior match how users run them from the repository root.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
|
|
9
|
+
.venv/bin/python test_examples.py
|
|
10
|
+
|
|
11
|
+
The runner expects `HELIUS_API_KEY` to be available in the environment or in
|
|
12
|
+
`.env`, matching the examples themselves. Some Helius endpoints are plan-gated
|
|
13
|
+
or network-gated; those failures are reported as "external" instead of as
|
|
14
|
+
example runtime bugs.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
ROOT = Path(__file__).resolve().parent
|
|
27
|
+
PYTHONPATH = str(ROOT / "src")
|
|
28
|
+
USE_COLOR = "NO_COLOR" not in os.environ
|
|
29
|
+
|
|
30
|
+
GREEN = "\033[32m"
|
|
31
|
+
YELLOW = "\033[33m"
|
|
32
|
+
RED = "\033[31m"
|
|
33
|
+
BOLD = "\033[1m"
|
|
34
|
+
RESET = "\033[0m"
|
|
35
|
+
|
|
36
|
+
SYSTEM_PROGRAM = "11111111111111111111111111111111"
|
|
37
|
+
SMALL_MINT = "J5iyNuTa6zqqA62Xe4h1VBvcBW5CTSNNva3QPh8DU5RV"
|
|
38
|
+
KNOWN_SIGNATURE = (
|
|
39
|
+
"eqRntqi1tjXv1zEGBM5btQGWoxWc73XXGDJXjxLE65Atj6T6qzNnJf5LyTbUoGXHS9TzeAnQniAre48SjcJft9f"
|
|
40
|
+
)
|
|
41
|
+
DEVNET_ADDRESS = "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class ExampleTest:
|
|
46
|
+
name: str
|
|
47
|
+
args: list[str]
|
|
48
|
+
timeout: int = 60
|
|
49
|
+
external_failure_markers: tuple[str, ...] = field(default_factory=tuple)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
TESTS = [
|
|
53
|
+
ExampleTest(
|
|
54
|
+
name="solana_rpc/address_transactions",
|
|
55
|
+
args=[
|
|
56
|
+
"examples/solana_rpc/address_transactions.py",
|
|
57
|
+
SYSTEM_PROGRAM,
|
|
58
|
+
"--limit",
|
|
59
|
+
"1",
|
|
60
|
+
"--dry-run",
|
|
61
|
+
],
|
|
62
|
+
),
|
|
63
|
+
ExampleTest(
|
|
64
|
+
name="solana_rpc/address_transfers",
|
|
65
|
+
args=[
|
|
66
|
+
"examples/solana_rpc/address_transfers.py",
|
|
67
|
+
SYSTEM_PROGRAM,
|
|
68
|
+
"--limit",
|
|
69
|
+
"1",
|
|
70
|
+
"--dry-run",
|
|
71
|
+
],
|
|
72
|
+
),
|
|
73
|
+
ExampleTest(
|
|
74
|
+
name="solana_rpc/block_explorer",
|
|
75
|
+
args=["examples/solana_rpc/block_explorer.py", "--slot", "423563000"],
|
|
76
|
+
timeout=90,
|
|
77
|
+
),
|
|
78
|
+
ExampleTest(
|
|
79
|
+
name="solana_rpc/devnet_airdrop",
|
|
80
|
+
args=[
|
|
81
|
+
"examples/solana_rpc/devnet_airdrop.py",
|
|
82
|
+
DEVNET_ADDRESS,
|
|
83
|
+
"--sol",
|
|
84
|
+
"0.000000001",
|
|
85
|
+
"--timeout",
|
|
86
|
+
"5",
|
|
87
|
+
"--dry-run",
|
|
88
|
+
],
|
|
89
|
+
),
|
|
90
|
+
ExampleTest(
|
|
91
|
+
name="solana_rpc/network_status",
|
|
92
|
+
args=["examples/solana_rpc/network_status.py"],
|
|
93
|
+
),
|
|
94
|
+
ExampleTest(
|
|
95
|
+
name="solana_rpc/priority_fees",
|
|
96
|
+
args=["examples/solana_rpc/priority_fees.py"],
|
|
97
|
+
),
|
|
98
|
+
ExampleTest(
|
|
99
|
+
name="solana_rpc/stake_overview",
|
|
100
|
+
args=["examples/solana_rpc/stake_overview.py"],
|
|
101
|
+
timeout=120,
|
|
102
|
+
),
|
|
103
|
+
ExampleTest(
|
|
104
|
+
name="solana_rpc/token_inspector",
|
|
105
|
+
args=["examples/solana_rpc/token_inspector.py", SMALL_MINT],
|
|
106
|
+
timeout=90,
|
|
107
|
+
),
|
|
108
|
+
ExampleTest(
|
|
109
|
+
name="solana_rpc/transaction_inspector",
|
|
110
|
+
args=["examples/solana_rpc/transaction_inspector.py", KNOWN_SIGNATURE],
|
|
111
|
+
timeout=90,
|
|
112
|
+
),
|
|
113
|
+
ExampleTest(
|
|
114
|
+
name="solana_rpc/wallet_tracker",
|
|
115
|
+
args=["examples/solana_rpc/wallet_tracker.py", SYSTEM_PROGRAM, "--limit", "1"],
|
|
116
|
+
timeout=120,
|
|
117
|
+
),
|
|
118
|
+
ExampleTest(
|
|
119
|
+
name="laserstream/websocket_logs",
|
|
120
|
+
args=["examples/laserstream/websocket_logs.py", "--count", "1"],
|
|
121
|
+
timeout=45,
|
|
122
|
+
external_failure_markers=("TimeoutError: timed out", "403 Forbidden", "HTTP 403"),
|
|
123
|
+
),
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def color(text: str, ansi_color: str) -> str:
|
|
128
|
+
if not USE_COLOR:
|
|
129
|
+
return text
|
|
130
|
+
return f"{ansi_color}{text}{RESET}"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def run_example(test: ExampleTest) -> tuple[str, str]:
|
|
134
|
+
env = os.environ.copy()
|
|
135
|
+
env["PYTHONPATH"] = (
|
|
136
|
+
f"{PYTHONPATH}{os.pathsep}{env['PYTHONPATH']}"
|
|
137
|
+
if env.get("PYTHONPATH")
|
|
138
|
+
else PYTHONPATH
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
command = [sys.executable, *test.args]
|
|
142
|
+
try:
|
|
143
|
+
result = subprocess.run(
|
|
144
|
+
command,
|
|
145
|
+
cwd=ROOT,
|
|
146
|
+
env=env,
|
|
147
|
+
text=True,
|
|
148
|
+
capture_output=True,
|
|
149
|
+
timeout=test.timeout,
|
|
150
|
+
check=False,
|
|
151
|
+
)
|
|
152
|
+
except subprocess.TimeoutExpired as exc:
|
|
153
|
+
output = (exc.stdout or "") + (exc.stderr or "")
|
|
154
|
+
return "external" if "websocket" in test.name else "failed", output
|
|
155
|
+
|
|
156
|
+
output = result.stdout + result.stderr
|
|
157
|
+
if result.returncode == 0:
|
|
158
|
+
return "passed", output
|
|
159
|
+
if any(marker in output for marker in test.external_failure_markers):
|
|
160
|
+
return "external", output
|
|
161
|
+
return "failed", output
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def main() -> int:
|
|
165
|
+
passed: list[str] = []
|
|
166
|
+
external: list[str] = []
|
|
167
|
+
failed: list[tuple[str, str]] = []
|
|
168
|
+
|
|
169
|
+
for test in TESTS:
|
|
170
|
+
print(f"\n=== {test.name} ===", flush=True)
|
|
171
|
+
status, output = run_example(test)
|
|
172
|
+
if status == "passed":
|
|
173
|
+
passed.append(test.name)
|
|
174
|
+
print(color("PASS", GREEN + BOLD))
|
|
175
|
+
elif status == "external":
|
|
176
|
+
external.append(test.name)
|
|
177
|
+
print(
|
|
178
|
+
color(
|
|
179
|
+
"EXTERNAL",
|
|
180
|
+
YELLOW + BOLD,
|
|
181
|
+
)
|
|
182
|
+
+ ": endpoint, plan, or network prevented a live success"
|
|
183
|
+
)
|
|
184
|
+
else:
|
|
185
|
+
failed.append((test.name, output))
|
|
186
|
+
print(color("FAIL", RED + BOLD))
|
|
187
|
+
|
|
188
|
+
if output.strip():
|
|
189
|
+
lines = output.strip().splitlines()
|
|
190
|
+
preview = "\n".join(lines[:30])
|
|
191
|
+
if len(lines) > 30:
|
|
192
|
+
preview += f"\n... ({len(lines) - 30} more lines)"
|
|
193
|
+
print(preview)
|
|
194
|
+
|
|
195
|
+
print("\n=== Summary ===")
|
|
196
|
+
print(f"{color('Passed', GREEN)} : {len(passed)}")
|
|
197
|
+
print(f"{color('External', YELLOW)} : {len(external)}")
|
|
198
|
+
print(f"{color('Failed', RED)} : {len(failed)}")
|
|
199
|
+
|
|
200
|
+
if external:
|
|
201
|
+
print("\nExternal failures:")
|
|
202
|
+
for name in external:
|
|
203
|
+
print(f" - {name}")
|
|
204
|
+
|
|
205
|
+
if failed:
|
|
206
|
+
print("\nUnexpected failures:")
|
|
207
|
+
for name, output in failed:
|
|
208
|
+
print(f"\n--- {name} ---")
|
|
209
|
+
print(output.strip())
|
|
210
|
+
return 1
|
|
211
|
+
|
|
212
|
+
return 0
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
if __name__ == "__main__":
|
|
216
|
+
raise SystemExit(main())
|
|
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
|