helius-python 0.3.4__tar.gz → 0.4.0__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.
Files changed (52) hide show
  1. {helius_python-0.3.4 → helius_python-0.4.0}/PKG-INFO +50 -11
  2. {helius_python-0.3.4 → helius_python-0.4.0}/README.md +49 -10
  3. {helius_python-0.3.4 → helius_python-0.4.0}/TODO.md +2 -0
  4. helius_python-0.4.0/examples/webhooks/webhook_crud.py +190 -0
  5. helius_python-0.4.0/examples/webhooks/webhook_receiver.py +109 -0
  6. {helius_python-0.3.4 → helius_python-0.4.0}/pyproject.toml +1 -1
  7. {helius_python-0.3.4 → helius_python-0.4.0}/src/helius/laserstream/websockets.py +1 -1
  8. {helius_python-0.3.4 → helius_python-0.4.0}/src/helius/solana_rpc/client.py +1 -1
  9. {helius_python-0.3.4 → helius_python-0.4.0}/src/helius/solana_rpc/models.py +0 -2
  10. helius_python-0.4.0/src/helius/utils/__init__.py +3 -0
  11. helius_python-0.4.0/src/helius/webhooks/__init__.py +0 -0
  12. helius_python-0.4.0/src/helius/webhooks/webhooks.py +722 -0
  13. {helius_python-0.3.4 → helius_python-0.4.0}/test_examples.py +10 -0
  14. {helius_python-0.3.4/tests/unit/rpc → helius_python-0.4.0/tests/unit/utils}/test_json_rpc_request.py +1 -1
  15. helius_python-0.4.0/tests/unit/webhooks/test_webhook.py +26 -0
  16. helius_python-0.4.0/tests/unit/webhooks/test_webhooks_api_client.py +168 -0
  17. helius_python-0.3.4/examples/README.md +0 -4
  18. helius_python-0.3.4/src/helius/rpc/__init__.py +0 -3
  19. {helius_python-0.3.4 → helius_python-0.4.0}/.editorconfig +0 -0
  20. {helius_python-0.3.4 → helius_python-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  21. {helius_python-0.3.4 → helius_python-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  22. {helius_python-0.3.4 → helius_python-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  23. {helius_python-0.3.4 → helius_python-0.4.0}/.github/workflows/python-package.yml +0 -0
  24. {helius_python-0.3.4 → helius_python-0.4.0}/.github/workflows/python-publish.yml +0 -0
  25. {helius_python-0.3.4 → helius_python-0.4.0}/.gitignore +0 -0
  26. {helius_python-0.3.4 → helius_python-0.4.0}/AGENTS.md +0 -0
  27. {helius_python-0.3.4 → helius_python-0.4.0}/CLAUDE.md +0 -0
  28. {helius_python-0.3.4 → helius_python-0.4.0}/CONTRIBUTING.md +0 -0
  29. {helius_python-0.3.4 → helius_python-0.4.0}/LICENSE +0 -0
  30. {helius_python-0.3.4 → helius_python-0.4.0}/examples/laserstream/websocket_logs.py +0 -0
  31. {helius_python-0.3.4 → helius_python-0.4.0}/examples/solana_rpc/address_transactions.py +0 -0
  32. {helius_python-0.3.4 → helius_python-0.4.0}/examples/solana_rpc/address_transfers.py +0 -0
  33. {helius_python-0.3.4 → helius_python-0.4.0}/examples/solana_rpc/block_explorer.py +0 -0
  34. {helius_python-0.3.4 → helius_python-0.4.0}/examples/solana_rpc/devnet_airdrop.py +0 -0
  35. {helius_python-0.3.4 → helius_python-0.4.0}/examples/solana_rpc/network_status.py +0 -0
  36. {helius_python-0.3.4 → helius_python-0.4.0}/examples/solana_rpc/priority_fees.py +0 -0
  37. {helius_python-0.3.4 → helius_python-0.4.0}/examples/solana_rpc/stake_overview.py +0 -0
  38. {helius_python-0.3.4 → helius_python-0.4.0}/examples/solana_rpc/token_inspector.py +0 -0
  39. {helius_python-0.3.4 → helius_python-0.4.0}/examples/solana_rpc/transaction_inspector.py +0 -0
  40. {helius_python-0.3.4 → helius_python-0.4.0}/examples/solana_rpc/wallet_tracker.py +0 -0
  41. {helius_python-0.3.4 → helius_python-0.4.0}/requirements.txt +0 -0
  42. {helius_python-0.3.4 → helius_python-0.4.0}/src/helius/__init__.py +0 -0
  43. {helius_python-0.3.4 → helius_python-0.4.0}/src/helius/admin/__init__.py +0 -0
  44. {helius_python-0.3.4 → helius_python-0.4.0}/src/helius/admin/admin.py +0 -0
  45. {helius_python-0.3.4 → helius_python-0.4.0}/src/helius/solana_rpc/__init__.py +0 -0
  46. {helius_python-0.3.4/src/helius/rpc → helius_python-0.4.0/src/helius/utils}/json_rpc_request.py +0 -0
  47. {helius_python-0.3.4 → helius_python-0.4.0}/tests/fixtures/account.json +0 -0
  48. {helius_python-0.3.4 → helius_python-0.4.0}/tests/fixtures/supply.json +0 -0
  49. {helius_python-0.3.4 → helius_python-0.4.0}/tests/unit/admin/test_admin.py +0 -0
  50. {helius_python-0.3.4 → helius_python-0.4.0}/tests/unit/lasterstream/test_websockets.py +0 -0
  51. {helius_python-0.3.4 → helius_python-0.4.0}/tests/unit/solana_rpc/test_client.py +0 -0
  52. {helius_python-0.3.4 → helius_python-0.4.0}/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.4
3
+ Version: 0.4.0
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
@@ -135,9 +135,10 @@ wraps it.
135
135
  - 🚧 **All Helius-specific RPC extensions** — enhanced transactions, DAS
136
136
  (Digital Asset Standard) methods, priority fee estimation, and the
137
137
  rest of the Helius-only RPC namespace. **(in progress)**
138
- - 🚧 **Every Helius REST endpoint** — Enhanced Transactions API,
139
- Webhooks API, Mint API, token metadata, address lookups, and beyond.
140
- **(in progress)**
138
+ - **Webhooks API** — create, fetch, update, enable/disable, and delete
139
+ Helius webhooks. **(supported today)**
140
+ - 🚧 **Remaining Helius REST endpoints** — Enhanced Transactions API,
141
+ Mint API, token metadata, address lookups, and beyond. **(in progress)**
141
142
  - 🚧 **Platform features** — streaming, websockets, and any new
142
143
  capability Helius adds to its API. The full WebSocket subscription
143
144
  surface (`accountSubscribe`, `transactionSubscribe`, `logsSubscribe`,
@@ -145,10 +146,10 @@ wraps it.
145
146
  platform features are **in progress**.
146
147
 
147
148
  **Current status:** the standard Solana JSON-RPC surface, the
148
- WebSocket subscription surface, and the Admin (account management)
149
- usage endpoint are implemented today. Support for Helius RPC
150
- extensions and the remaining REST endpoints is actively being worked
151
- on.
149
+ WebSocket subscription surface, the Webhooks API, and the Admin
150
+ (account management) usage endpoint are implemented today. Support for
151
+ Helius RPC extensions and the remaining REST endpoints is actively
152
+ being worked on.
152
153
 
153
154
  ## Goals
154
155
 
@@ -266,9 +267,9 @@ The method names map 1:1 to the Solana JSON-RPC spec, just converted to
266
267
  ## Status
267
268
 
268
269
  Actively expanding toward full coverage of the Helius API. See
269
- [`src/helius/solana_rpc.py`](src/helius/solana_rpc.py) for the current list of
270
- implemented methods; missing endpoints are tracked as issues and added
271
- continuously.
270
+ [`src/helius/solana_rpc/client.py`](src/helius/solana_rpc/client.py) for the
271
+ current Solana JSON-RPC implementation; supported surfaces are listed below,
272
+ and missing endpoints are tracked as issues and added continuously.
272
273
 
273
274
  | Solana JSON-RPC method | Python method | Helius docs |
274
275
  | ----------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -372,6 +373,44 @@ them; both yield a `(context, notification, subscription)` tuple where
372
373
  | `vote_subscribe()` | `vote_unsubscribe(...)` | `VoteNotification` | [reference](https://www.helius.dev/docs/api-reference/rpc/websocket/votesubscribe) |
373
374
  | `transaction_subscribe(...)` | `transaction_unsubscribe(...)` | `TransactionNotification` | [reference](https://www.helius.dev/docs/api-reference/rpc/websocket/slotunsubscribe) |
374
375
 
376
+ ## Webhooks API
377
+
378
+ `WebhooksApiClient` wraps the Helius Webhooks REST API. It can create,
379
+ fetch, list, update, enable/disable, and delete webhook subscriptions,
380
+ returning typed `Webhook` models where the endpoint returns a webhook.
381
+
382
+ ```python
383
+ from helius.webhooks.webhooks import WebhooksApiClient
384
+
385
+ with WebhooksApiClient(api_key="YOUR_HELIUS_API_KEY") as webhooks:
386
+ webhook = webhooks.create_webhook(
387
+ webhook_url="https://example.com/helius-webhook",
388
+ transaction_types=["TRANSFER"],
389
+ account_addresses=["So11111111111111111111111111111111111111112"],
390
+ webhook_type="enhanced",
391
+ auth_header="Bearer your-shared-secret",
392
+ encoding="jsonParsed",
393
+ txn_status="all",
394
+ )
395
+
396
+ webhooks.toggle_webhook(webhook.webhook_id, active=False)
397
+ ```
398
+
399
+ Like the other clients, it supports the context-manager protocol and a
400
+ manual `close()`. The constructor defaults to
401
+ `https://mainnet.helius-rpc.com/v0/webhooks` and reads `HELIUS_API_KEY`
402
+ from the environment or `.env` when `api_key` is omitted; optional
403
+ `headers` and `proxy` arguments are also accepted.
404
+
405
+ | REST operation | Python method | Helius docs |
406
+ | -------------- | -------------------------- | ------------------------------------------------------------------------------------------------- |
407
+ | Create webhook | `create_webhook(...)` | [reference](https://www.helius.dev/docs/api-reference/webhooks/create-webhook) |
408
+ | Get webhook | `get_webhook(...)` | [reference](https://www.helius.dev/docs/api-reference/webhooks/get-webhook) |
409
+ | List webhooks | `get_all_webhooks()` | [reference](https://www.helius.dev/docs/api-reference/webhooks/get-all-webhooks) |
410
+ | Update webhook | `update_webhook(...)` | [reference](https://www.helius.dev/docs/api-reference/webhooks/update-webhook) |
411
+ | Toggle webhook | `toggle_webhook(...)` | [reference](https://www.helius.dev/docs/api-reference/webhooks/toggle-webhook) |
412
+ | Delete webhook | `delete_webhook(...)` | [reference](https://www.helius.dev/docs/api-reference/webhooks/delete-webhook) |
413
+
375
414
  ## Admin API
376
415
 
377
416
  `AccountManagementClient` wraps the Helius admin API. Today it exposes
@@ -113,9 +113,10 @@ wraps it.
113
113
  - 🚧 **All Helius-specific RPC extensions** — enhanced transactions, DAS
114
114
  (Digital Asset Standard) methods, priority fee estimation, and the
115
115
  rest of the Helius-only RPC namespace. **(in progress)**
116
- - 🚧 **Every Helius REST endpoint** — Enhanced Transactions API,
117
- Webhooks API, Mint API, token metadata, address lookups, and beyond.
118
- **(in progress)**
116
+ - **Webhooks API** — create, fetch, update, enable/disable, and delete
117
+ Helius webhooks. **(supported today)**
118
+ - 🚧 **Remaining Helius REST endpoints** — Enhanced Transactions API,
119
+ Mint API, token metadata, address lookups, and beyond. **(in progress)**
119
120
  - 🚧 **Platform features** — streaming, websockets, and any new
120
121
  capability Helius adds to its API. The full WebSocket subscription
121
122
  surface (`accountSubscribe`, `transactionSubscribe`, `logsSubscribe`,
@@ -123,10 +124,10 @@ wraps it.
123
124
  platform features are **in progress**.
124
125
 
125
126
  **Current status:** the standard Solana JSON-RPC surface, the
126
- WebSocket subscription surface, and the Admin (account management)
127
- usage endpoint are implemented today. Support for Helius RPC
128
- extensions and the remaining REST endpoints is actively being worked
129
- on.
127
+ WebSocket subscription surface, the Webhooks API, and the Admin
128
+ (account management) usage endpoint are implemented today. Support for
129
+ Helius RPC extensions and the remaining REST endpoints is actively
130
+ being worked on.
130
131
 
131
132
  ## Goals
132
133
 
@@ -244,9 +245,9 @@ The method names map 1:1 to the Solana JSON-RPC spec, just converted to
244
245
  ## Status
245
246
 
246
247
  Actively expanding toward full coverage of the Helius API. See
247
- [`src/helius/solana_rpc.py`](src/helius/solana_rpc.py) for the current list of
248
- implemented methods; missing endpoints are tracked as issues and added
249
- continuously.
248
+ [`src/helius/solana_rpc/client.py`](src/helius/solana_rpc/client.py) for the
249
+ current Solana JSON-RPC implementation; supported surfaces are listed below,
250
+ and missing endpoints are tracked as issues and added continuously.
250
251
 
251
252
  | Solana JSON-RPC method | Python method | Helius docs |
252
253
  | ----------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -350,6 +351,44 @@ them; both yield a `(context, notification, subscription)` tuple where
350
351
  | `vote_subscribe()` | `vote_unsubscribe(...)` | `VoteNotification` | [reference](https://www.helius.dev/docs/api-reference/rpc/websocket/votesubscribe) |
351
352
  | `transaction_subscribe(...)` | `transaction_unsubscribe(...)` | `TransactionNotification` | [reference](https://www.helius.dev/docs/api-reference/rpc/websocket/slotunsubscribe) |
352
353
 
354
+ ## Webhooks API
355
+
356
+ `WebhooksApiClient` wraps the Helius Webhooks REST API. It can create,
357
+ fetch, list, update, enable/disable, and delete webhook subscriptions,
358
+ returning typed `Webhook` models where the endpoint returns a webhook.
359
+
360
+ ```python
361
+ from helius.webhooks.webhooks import WebhooksApiClient
362
+
363
+ with WebhooksApiClient(api_key="YOUR_HELIUS_API_KEY") as webhooks:
364
+ webhook = webhooks.create_webhook(
365
+ webhook_url="https://example.com/helius-webhook",
366
+ transaction_types=["TRANSFER"],
367
+ account_addresses=["So11111111111111111111111111111111111111112"],
368
+ webhook_type="enhanced",
369
+ auth_header="Bearer your-shared-secret",
370
+ encoding="jsonParsed",
371
+ txn_status="all",
372
+ )
373
+
374
+ webhooks.toggle_webhook(webhook.webhook_id, active=False)
375
+ ```
376
+
377
+ Like the other clients, it supports the context-manager protocol and a
378
+ manual `close()`. The constructor defaults to
379
+ `https://mainnet.helius-rpc.com/v0/webhooks` and reads `HELIUS_API_KEY`
380
+ from the environment or `.env` when `api_key` is omitted; optional
381
+ `headers` and `proxy` arguments are also accepted.
382
+
383
+ | REST operation | Python method | Helius docs |
384
+ | -------------- | -------------------------- | ------------------------------------------------------------------------------------------------- |
385
+ | Create webhook | `create_webhook(...)` | [reference](https://www.helius.dev/docs/api-reference/webhooks/create-webhook) |
386
+ | Get webhook | `get_webhook(...)` | [reference](https://www.helius.dev/docs/api-reference/webhooks/get-webhook) |
387
+ | List webhooks | `get_all_webhooks()` | [reference](https://www.helius.dev/docs/api-reference/webhooks/get-all-webhooks) |
388
+ | Update webhook | `update_webhook(...)` | [reference](https://www.helius.dev/docs/api-reference/webhooks/update-webhook) |
389
+ | Toggle webhook | `toggle_webhook(...)` | [reference](https://www.helius.dev/docs/api-reference/webhooks/toggle-webhook) |
390
+ | Delete webhook | `delete_webhook(...)` | [reference](https://www.helius.dev/docs/api-reference/webhooks/delete-webhook) |
391
+
353
392
  ## Admin API
354
393
 
355
394
  `AccountManagementClient` wraps the Helius admin API. Today it exposes
@@ -2,3 +2,5 @@
2
2
  - WebSocket subscription manager
3
3
  - Use typeddicts where useful
4
4
  - When Python 3.10 and 3.11 go out of support we should use TypedDict from typing instead of typing_extensions
5
+ - Improve camelCase <-> snake_case in typing
6
+ - Handle HTTP response codes and idiomatic error messages in responses
@@ -0,0 +1,190 @@
1
+ """Create, list, update, toggle, and delete Helius webhooks.
2
+
3
+ Usage:
4
+
5
+ export HELIUS_API_KEY=your_helius_api_key
6
+
7
+ # Print all configured webhooks.
8
+ python examples/webhooks/webhook_crud.py list
9
+
10
+ # Inspect one webhook.
11
+ python examples/webhooks/webhook_crud.py show <WEBHOOK_ID>
12
+
13
+ # Create a webhook that watches one or more accounts.
14
+ python examples/webhooks/webhook_crud.py create \
15
+ https://example.com/helius/webhook \
16
+ <ACCOUNT_ADDRESS> [<ACCOUNT_ADDRESS> ...] \
17
+ --transaction-type TRANSFER \
18
+ --auth-header "Bearer shared-secret"
19
+
20
+ # Update every mutable field on an existing webhook.
21
+ python examples/webhooks/webhook_crud.py update <WEBHOOK_ID> \
22
+ https://example.com/helius/webhook \
23
+ <ACCOUNT_ADDRESS> [<ACCOUNT_ADDRESS> ...]
24
+
25
+ # Pause, resume, or delete.
26
+ python examples/webhooks/webhook_crud.py toggle <WEBHOOK_ID> --inactive
27
+ python examples/webhooks/webhook_crud.py delete <WEBHOOK_ID> --yes
28
+
29
+ Docs:
30
+ https://www.helius.dev/docs/api-reference/webhooks/create-webhook
31
+ https://www.helius.dev/docs/api-reference/webhooks/get-all-webhooks
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import argparse
37
+ import json
38
+ import sys
39
+ from typing import Any
40
+
41
+ import httpx
42
+
43
+ from helius.webhooks.webhooks import Webhook, WebhooksApiClient
44
+
45
+
46
+ def print_webhook(webhook: Webhook) -> None:
47
+ print(json.dumps(webhook.model_dump(mode="json"), indent=2, sort_keys=True))
48
+
49
+
50
+ def print_dry_run(action: str, payload: dict[str, Any]) -> int:
51
+ print(f"Would {action} with:")
52
+ print(json.dumps(payload, indent=2, sort_keys=True))
53
+ return 0
54
+
55
+
56
+ def add_webhook_payload_args(parser: argparse.ArgumentParser) -> None:
57
+ parser.add_argument("webhook_url", help="Public HTTPS endpoint Helius will POST to")
58
+ parser.add_argument(
59
+ "account_addresses",
60
+ nargs="+",
61
+ help="One or more account addresses to watch",
62
+ )
63
+ parser.add_argument(
64
+ "--transaction-type",
65
+ dest="transaction_types",
66
+ action="append",
67
+ help="Transaction type to subscribe to; repeat for multiple (default TRANSFER)",
68
+ )
69
+ parser.add_argument(
70
+ "--webhook-type",
71
+ default="enhanced",
72
+ choices=(
73
+ "enhanced",
74
+ "raw",
75
+ "discord",
76
+ "enhancedDevnet",
77
+ "rawDevnet",
78
+ "discordDevnet",
79
+ ),
80
+ help="Webhook payload type (default enhanced)",
81
+ )
82
+ parser.add_argument(
83
+ "--auth-header",
84
+ default="",
85
+ help="Value Helius should include in the Authorization header",
86
+ )
87
+ parser.add_argument(
88
+ "--encoding",
89
+ default="jsonParsed",
90
+ help="Webhook transaction encoding (default jsonParsed)",
91
+ )
92
+ parser.add_argument(
93
+ "--txn-status",
94
+ default="all",
95
+ choices=("all", "success", "failed"),
96
+ help="Transaction status filter (default all)",
97
+ )
98
+ parser.add_argument(
99
+ "--dry-run",
100
+ action="store_true",
101
+ help="Print the request payload without sending it",
102
+ )
103
+
104
+
105
+ def webhook_payload(args: argparse.Namespace) -> dict[str, Any]:
106
+ return {
107
+ "webhook_url": args.webhook_url,
108
+ "transaction_types": args.transaction_types or ["TRANSFER"],
109
+ "account_addresses": args.account_addresses,
110
+ "webhook_type": args.webhook_type,
111
+ "auth_header": args.auth_header,
112
+ "encoding": args.encoding,
113
+ "txn_status": args.txn_status,
114
+ }
115
+
116
+
117
+ def build_parser() -> argparse.ArgumentParser:
118
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
119
+ subparsers = parser.add_subparsers(dest="command", required=True)
120
+
121
+ subparsers.add_parser("list", help="List all webhooks")
122
+
123
+ show = subparsers.add_parser("show", help="Show one webhook")
124
+ show.add_argument("webhook_id")
125
+
126
+ create = subparsers.add_parser("create", help="Create a webhook")
127
+ add_webhook_payload_args(create)
128
+
129
+ update = subparsers.add_parser("update", help="Replace an existing webhook")
130
+ update.add_argument("webhook_id")
131
+ add_webhook_payload_args(update)
132
+
133
+ toggle = subparsers.add_parser("toggle", help="Activate or deactivate a webhook")
134
+ toggle.add_argument("webhook_id")
135
+ status = toggle.add_mutually_exclusive_group(required=True)
136
+ status.add_argument("--active", action="store_true", help="Resume the webhook")
137
+ status.add_argument("--inactive", action="store_true", help="Pause the webhook")
138
+ toggle.add_argument("--dry-run", action="store_true")
139
+
140
+ delete = subparsers.add_parser("delete", help="Delete a webhook")
141
+ delete.add_argument("webhook_id")
142
+ delete.add_argument("--yes", action="store_true", help="Confirm permanent deletion")
143
+ delete.add_argument("--dry-run", action="store_true")
144
+
145
+ return parser
146
+
147
+
148
+ def main() -> int:
149
+ parser = build_parser()
150
+ args = parser.parse_args()
151
+
152
+ if args.command in {"create", "update"} and args.dry_run:
153
+ return print_dry_run(args.command, webhook_payload(args))
154
+ if args.command == "toggle" and args.dry_run:
155
+ return print_dry_run(
156
+ "toggle", {"webhook_id": args.webhook_id, "active": args.active}
157
+ )
158
+ if args.command == "delete":
159
+ if args.dry_run:
160
+ return print_dry_run("delete", {"webhook_id": args.webhook_id})
161
+ if not args.yes:
162
+ print("Refusing to delete without --yes", file=sys.stderr)
163
+ return 2
164
+
165
+ try:
166
+ with WebhooksApiClient() as client:
167
+ if args.command == "list":
168
+ webhooks = client.get_all_webhooks()
169
+ print(json.dumps([w.model_dump(mode="json") for w in webhooks], indent=2))
170
+ elif args.command == "show":
171
+ print_webhook(client.get_webhook(args.webhook_id))
172
+ elif args.command == "create":
173
+ print_webhook(client.create_webhook(**webhook_payload(args)))
174
+ elif args.command == "update":
175
+ print_webhook(
176
+ client.update_webhook(args.webhook_id, **webhook_payload(args))
177
+ )
178
+ elif args.command == "toggle":
179
+ print_webhook(client.toggle_webhook(args.webhook_id, active=args.active))
180
+ elif args.command == "delete":
181
+ print(client.delete_webhook(args.webhook_id))
182
+ except httpx.HTTPStatusError as exc:
183
+ print(f"HTTP {exc.response.status_code}: {exc.response.text}", file=sys.stderr)
184
+ return 1
185
+
186
+ return 0
187
+
188
+
189
+ if __name__ == "__main__":
190
+ raise SystemExit(main())
@@ -0,0 +1,109 @@
1
+ """Run a tiny local HTTP server that prints Helius webhook payloads.
2
+
3
+ This receiver uses only the Python standard library. It is useful for local
4
+ development with a tunnel such as ngrok, Cloudflare Tunnel, or `ssh -R`.
5
+
6
+ Usage:
7
+
8
+ python examples/webhooks/webhook_receiver.py --port 8080
9
+
10
+ # In another shell, expose it publicly and use that URL in Helius:
11
+ ngrok http 8080
12
+
13
+ # If your webhook was created with authHeader="Bearer shared-secret":
14
+ python examples/webhooks/webhook_receiver.py \
15
+ --auth-header "Bearer shared-secret"
16
+
17
+ The handler accepts POST requests on any path, verifies the optional
18
+ Authorization header, pretty-prints JSON payloads, and replies with 200 OK.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import json
25
+ import sys
26
+ from http import HTTPStatus
27
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
28
+ from typing import Any
29
+
30
+
31
+ class WebhookHandler(BaseHTTPRequestHandler):
32
+ expected_auth_header: str | None = None
33
+
34
+ def do_POST(self) -> None: # noqa: N802 - stdlib handler hook name
35
+ if self.expected_auth_header is not None:
36
+ actual = self.headers.get("Authorization")
37
+ if actual != self.expected_auth_header:
38
+ self.respond(HTTPStatus.UNAUTHORIZED, {"ok": False})
39
+ print("Rejected request with missing or invalid Authorization header")
40
+ return
41
+
42
+ length = int(self.headers.get("Content-Length", "0"))
43
+ raw_body = self.rfile.read(length)
44
+
45
+ try:
46
+ payload: Any = json.loads(raw_body)
47
+ except json.JSONDecodeError:
48
+ payload = raw_body.decode("utf-8", errors="replace")
49
+
50
+ print("\n=== Helius webhook received ===")
51
+ print(f"Path: {self.path}")
52
+ print(f"User-Agent: {self.headers.get('User-Agent', '-')}")
53
+ if isinstance(payload, (dict, list)):
54
+ print(json.dumps(payload, indent=2, sort_keys=True))
55
+ else:
56
+ print(payload)
57
+
58
+ self.respond(HTTPStatus.OK, {"ok": True})
59
+
60
+ def do_GET(self) -> None: # noqa: N802 - stdlib handler hook name
61
+ self.respond(
62
+ HTTPStatus.OK, {"ok": True, "message": "POST webhook payloads here"}
63
+ )
64
+
65
+ def log_message(self, format: str, *args: Any) -> None:
66
+ # Keep the default access log, but send it to stdout next to payload logs.
67
+ print(f"{self.address_string()} - {format % args}")
68
+
69
+ def respond(self, status: HTTPStatus, payload: dict[str, Any]) -> None:
70
+ body = json.dumps(payload).encode("utf-8")
71
+ self.send_response(status)
72
+ self.send_header("Content-Type", "application/json")
73
+ self.send_header("Content-Length", str(len(body)))
74
+ self.end_headers()
75
+ self.wfile.write(body)
76
+
77
+
78
+ def main() -> int:
79
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
80
+ parser.add_argument(
81
+ "--host", default="127.0.0.1", help="Bind host (default 127.0.0.1)"
82
+ )
83
+ parser.add_argument("--port", type=int, default=8080, help="Bind port (default 8080)")
84
+ parser.add_argument(
85
+ "--auth-header",
86
+ help="Required Authorization header value, matching the webhook authHeader",
87
+ )
88
+ args = parser.parse_args()
89
+
90
+ WebhookHandler.expected_auth_header = args.auth_header
91
+ server = ThreadingHTTPServer((args.host, args.port), WebhookHandler)
92
+
93
+ print(f"Listening on http://{args.host}:{args.port}")
94
+ if args.auth_header:
95
+ print("Authorization header verification is enabled")
96
+ print("Press Ctrl+C to stop", flush=True)
97
+
98
+ try:
99
+ server.serve_forever()
100
+ except KeyboardInterrupt:
101
+ print("\nStopping receiver", file=sys.stderr)
102
+ finally:
103
+ server.server_close()
104
+
105
+ return 0
106
+
107
+
108
+ if __name__ == "__main__":
109
+ raise SystemExit(main())
@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
10
10
 
11
11
  [project]
12
12
  name = "helius-python"
13
- version = "0.3.4"
13
+ version = "0.4.0"
14
14
  authors = [
15
15
  { name="Markos Narinian", email="manarinian@gmail.com" },
16
16
  ]
@@ -9,7 +9,7 @@ from pydantic.alias_generators import to_camel
9
9
  from typing_extensions import TypedDict
10
10
  from websockets.sync.client import connect
11
11
 
12
- from helius.rpc import JsonRpcRequest
12
+ from helius.utils import JsonRpcRequest
13
13
 
14
14
 
15
15
  class Notification(BaseModel):
@@ -5,7 +5,6 @@ import httpx
5
5
  from dotenv import dotenv_values
6
6
  from pydantic import Field, TypeAdapter, validate_call
7
7
 
8
- from helius.rpc import JsonRpcRequest
9
8
  from helius.solana_rpc.models import (
10
9
  Account,
11
10
  Block,
@@ -33,6 +32,7 @@ from helius.solana_rpc.models import (
33
32
  TransferFilters,
34
33
  VotingAccount,
35
34
  )
35
+ from helius.utils import JsonRpcRequest
36
36
 
37
37
 
38
38
  # TODO: Use Pydantic typed dict where useful
@@ -4,8 +4,6 @@ from pydantic import AliasGenerator, BaseModel, ConfigDict
4
4
  from pydantic.alias_generators import to_camel
5
5
  from typing_extensions import TypedDict
6
6
 
7
- # TODO: Improve camelCase <-> snake_case
8
-
9
7
 
10
8
  class Account(BaseModel):
11
9
  model_config = ConfigDict(alias_generator=AliasGenerator(validation_alias=to_camel))
@@ -0,0 +1,3 @@
1
+ from helius.utils.json_rpc_request import JsonRpcRequest
2
+
3
+ __all__ = ["JsonRpcRequest"]
File without changes