quicknode-sdk 0.6.0__tar.gz → 0.7.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 (54) hide show
  1. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/Cargo.lock +17 -17
  2. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/Cargo.toml +1 -1
  3. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/PKG-INFO +52 -2
  4. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/Cargo.toml +9 -1
  5. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/README.md +63 -1
  6. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/examples/admin_e2e.rs +3 -0
  7. quicknode_sdk-0.7.0/crates/core/examples/rpc.rs +83 -0
  8. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/mod.rs +4 -0
  9. quicknode_sdk-0.7.0/crates/core/src/admin/tooling_access.rs +386 -0
  10. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/config.rs +102 -1
  11. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/errors.rs +3 -0
  12. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/kvstore/mod.rs +1 -0
  13. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/lib.rs +62 -21
  14. quicknode_sdk-0.7.0/crates/core/src/rpc/mod.rs +766 -0
  15. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/sql/mod.rs +1 -0
  16. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/streams/mod.rs +1 -0
  17. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/webhooks/mod.rs +1 -0
  18. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/python/Cargo.toml +2 -0
  19. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/python/src/errors.rs +20 -0
  20. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/python/src/lib.rs +151 -1
  21. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/pyproject.toml +1 -1
  22. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/python/README.md +51 -1
  23. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/python/quicknode_sdk/__init__.py +10 -0
  24. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/python/quicknode_sdk/__init__.pyi +10 -0
  25. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/python/quicknode_sdk/_core/__init__.pyi +200 -1
  26. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/python/quicknode_sdk/init_manual_override.pyi +10 -0
  27. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/LICENSE +0 -0
  28. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/LICENSE +0 -0
  29. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/examples/admin.rs +0 -0
  30. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/examples/kvstore_e2e.rs +0 -0
  31. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/examples/sql.rs +0 -0
  32. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/examples/streams.rs +0 -0
  33. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/examples/streams_e2e.rs +0 -0
  34. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/examples/webhooks_e2e.rs +0 -0
  35. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/account.rs +0 -0
  36. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/api_credits.rs +0 -0
  37. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/billing.rs +0 -0
  38. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/bulk.rs +0 -0
  39. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/chains.rs +0 -0
  40. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/endpoint_metrics.rs +0 -0
  41. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/endpoint_rate_limits.rs +0 -0
  42. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/endpoint_security.rs +0 -0
  43. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/endpoint_urls.rs +0 -0
  44. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/endpoints.rs +0 -0
  45. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/logs.rs +0 -0
  46. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/tags.rs +0 -0
  47. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/teams.rs +0 -0
  48. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/admin/usage.rs +0 -0
  49. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/streams/stream.rs +0 -0
  50. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/core/src/webhooks/webhook.rs +0 -0
  51. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/python/src/sql.rs +0 -0
  52. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/python/src/streams_destination.rs +0 -0
  53. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/crates/python/src/webhooks_template.rs +0 -0
  54. {quicknode_sdk-0.6.0 → quicknode_sdk-0.7.0}/python/quicknode_sdk/py.typed +0 -0
@@ -154,9 +154,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
154
154
 
155
155
  [[package]]
156
156
  name = "bytes"
157
- version = "1.12.0"
157
+ version = "1.12.1"
158
158
  source = "registry+https://github.com/rust-lang/crates.io-index"
159
- checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
159
+ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
160
160
 
161
161
  [[package]]
162
162
  name = "cc"
@@ -1175,9 +1175,9 @@ dependencies = [
1175
1175
 
1176
1176
  [[package]]
1177
1177
  name = "memchr"
1178
- version = "2.8.2"
1178
+ version = "2.8.3"
1179
1179
  source = "registry+https://github.com/rust-lang/crates.io-index"
1180
- checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
1180
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
1181
1181
 
1182
1182
  [[package]]
1183
1183
  name = "memoffset"
@@ -1684,7 +1684,7 @@ dependencies = [
1684
1684
 
1685
1685
  [[package]]
1686
1686
  name = "quicknode-sdk"
1687
- version = "0.6.0"
1687
+ version = "0.7.0"
1688
1688
  dependencies = [
1689
1689
  "bon",
1690
1690
  "config",
@@ -1870,9 +1870,9 @@ checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
1870
1870
 
1871
1871
  [[package]]
1872
1872
  name = "regex"
1873
- version = "1.12.4"
1873
+ version = "1.13.0"
1874
1874
  source = "registry+https://github.com/rust-lang/crates.io-index"
1875
- checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
1875
+ checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
1876
1876
  dependencies = [
1877
1877
  "aho-corasick",
1878
1878
  "memchr",
@@ -1882,9 +1882,9 @@ dependencies = [
1882
1882
 
1883
1883
  [[package]]
1884
1884
  name = "regex-automata"
1885
- version = "0.4.14"
1885
+ version = "0.4.15"
1886
1886
  source = "registry+https://github.com/rust-lang/crates.io-index"
1887
- checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
1887
+ checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db"
1888
1888
  dependencies = [
1889
1889
  "aho-corasick",
1890
1890
  "memchr",
@@ -2159,7 +2159,7 @@ dependencies = [
2159
2159
 
2160
2160
  [[package]]
2161
2161
  name = "sdk-node"
2162
- version = "0.6.0"
2162
+ version = "0.7.0"
2163
2163
  dependencies = [
2164
2164
  "napi",
2165
2165
  "napi-build",
@@ -2171,7 +2171,7 @@ dependencies = [
2171
2171
 
2172
2172
  [[package]]
2173
2173
  name = "sdk-python"
2174
- version = "0.6.0"
2174
+ version = "0.7.0"
2175
2175
  dependencies = [
2176
2176
  "pyo3",
2177
2177
  "pyo3-async-runtimes",
@@ -2183,7 +2183,7 @@ dependencies = [
2183
2183
 
2184
2184
  [[package]]
2185
2185
  name = "sdk-python-stubs"
2186
- version = "0.6.0"
2186
+ version = "0.7.0"
2187
2187
  dependencies = [
2188
2188
  "pyo3-stub-gen",
2189
2189
  "sdk-python",
@@ -2191,7 +2191,7 @@ dependencies = [
2191
2191
 
2192
2192
  [[package]]
2193
2193
  name = "sdk-ruby"
2194
- version = "0.6.0"
2194
+ version = "0.7.0"
2195
2195
  dependencies = [
2196
2196
  "magnus",
2197
2197
  "quicknode-sdk",
@@ -3180,18 +3180,18 @@ dependencies = [
3180
3180
 
3181
3181
  [[package]]
3182
3182
  name = "zerocopy"
3183
- version = "0.8.53"
3183
+ version = "0.8.54"
3184
3184
  source = "registry+https://github.com/rust-lang/crates.io-index"
3185
- checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1"
3185
+ checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
3186
3186
  dependencies = [
3187
3187
  "zerocopy-derive",
3188
3188
  ]
3189
3189
 
3190
3190
  [[package]]
3191
3191
  name = "zerocopy-derive"
3192
- version = "0.8.53"
3192
+ version = "0.8.54"
3193
3193
  source = "registry+https://github.com/rust-lang/crates.io-index"
3194
- checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71"
3194
+ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
3195
3195
  dependencies = [
3196
3196
  "proc-macro2",
3197
3197
  "quote",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/core", "crates/python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.6.0"
6
+ version = "0.7.0"
7
7
  edition = "2021"
8
8
  license = "MIT"
9
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quicknode-sdk
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Classifier: Intended Audience :: Developers
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Operating System :: POSIX :: Linux
@@ -1698,6 +1698,55 @@ schema = await qn.sql.get_schema("hyperliquid-core-mainnet")
1698
1698
  print(len(schema.tables))
1699
1699
  ```
1700
1700
 
1701
+ ---
1702
+
1703
+ ### RPC & Tooling Access
1704
+
1705
+ Tooling Access provisions a single multichain, read-only endpoint per account and
1706
+ mints short-lived session JWTs. `qn.rpc` makes JSON-RPC calls directly against that
1707
+ endpoint, minting and refreshing the JWT automatically — no endpoint URL or token to
1708
+ manage.
1709
+
1710
+ Tooling Access must be enabled once (admin role + eligible plan). The control-plane
1711
+ methods live on `qn.admin`:
1712
+
1713
+ ```python
1714
+ # Python
1715
+ status = await qn.admin.tooling_access_status()
1716
+ if not status.enabled:
1717
+ await qn.admin.enable_tooling_access() # idempotent; admin role required
1718
+
1719
+ # Make on-chain calls. params defaults to []; pass a list (positional) or dict.
1720
+ block_number = await qn.rpc.call("eth_blockNumber")
1721
+ balance = await qn.rpc.call("eth_getBalance", ["0xabc...", "latest"])
1722
+
1723
+ # Multichain: select a network by its multichain_urls key. Seed the map first
1724
+ # (from admin.get_endpoint_urls), then pass network=.
1725
+ urls = await qn.admin.get_endpoint_urls(endpoint_id)
1726
+ mc = urls.data.multichain_urls if urls.data else {}
1727
+ qn.rpc.set_networks({k: v.http_url for k, v in (mc or {}).items()})
1728
+ slot = await qn.rpc.call("getSlot", network="solana-mainnet")
1729
+
1730
+ # Custom endpoint URL: send to a fully-formed HTTP URL, bypassing Tooling Access
1731
+ # and the JWT (no Authorization header). Per-call via endpoint_url=, or
1732
+ # client-wide via RpcConfig(endpoint_url=...). endpoint_url and network are
1733
+ # mutually exclusive (a custom URL is not multichain-routed).
1734
+ block = await qn.rpc.call("eth_blockNumber", endpoint_url="https://my-endpoint.example/rpc")
1735
+
1736
+ # A JSON-RPC error member is raised as RpcError (with .code and .message).
1737
+ from quicknode_sdk import RpcError
1738
+ try:
1739
+ await qn.rpc.call("eth_getBalance", ["bad"])
1740
+ except RpcError as e:
1741
+ print(e.code, e.message)
1742
+ ```
1743
+
1744
+ A host that persists across processes can snapshot the cached token with
1745
+ `qn.rpc.current_token()` and re-seed it via `RpcConfig(seed=...)` on the next
1746
+ construction; `refresh_margin_secs` (default 60) tunes how early the token is
1747
+ refreshed. Set `RpcConfig(endpoint_url=...)` to route every call to a custom
1748
+ HTTP URL by default (no JWT minted); a per-call `endpoint_url` overrides it.
1749
+
1701
1750
  ## Error Handling
1702
1751
 
1703
1752
  Every binding exposes a typed exception hierarchy derived from the core `SdkError`
@@ -1713,8 +1762,9 @@ subclass to branch on transport vs. API semantics.
1713
1762
  | `ConnectionError` | connection refused / DNS / TLS (subclass of `HttpError`) | — |
1714
1763
  | `ApiError` | non-2xx HTTP response | `status`, `body` |
1715
1764
  | `DecodeError` | 2xx response but JSON parse failed | `body` |
1765
+ | `RpcError` | JSON-RPC call returned an `error` member | `code`, `message` |
1716
1766
 
1717
- Class names: Importable from `quicknode_sdk`: `QuicknodeError`, `ConfigError`, `HttpError`, `TimeoutError`, `ConnectionError`, `ApiError`, `DecodeError`.
1767
+ Class names: Importable from `quicknode_sdk`: `QuicknodeError`, `ConfigError`, `HttpError`, `TimeoutError`, `ConnectionError`, `ApiError`, `DecodeError`, `RpcError`.
1718
1768
 
1719
1769
  ```python
1720
1770
  # Python
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "quicknode-sdk"
3
- version = "0.6.0"
3
+ version = "0.7.0"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  description = "Core library for quicknode sdk"
@@ -34,6 +34,10 @@ bon = { version = "3.9", optional = true }
34
34
  config = "0.15"
35
35
  url = "2.5.8"
36
36
  secrecy = "0.8"
37
+ # sync-only: provides tokio::sync::Mutex for single-flight JWT refresh in the
38
+ # rpc client without pulling in the tokio runtime. The async runtime itself is
39
+ # supplied by the caller (bindings or the user's own runtime).
40
+ tokio = { version = "1", default-features = false, features = ["sync"] }
37
41
 
38
42
  [[example]]
39
43
  name = "admin"
@@ -55,6 +59,10 @@ required-features = ["rust"]
55
59
  name = "webhooks_e2e"
56
60
  required-features = ["rust"]
57
61
 
62
+ [[example]]
63
+ name = "rpc"
64
+ required-features = ["rust"]
65
+
58
66
  [dev-dependencies]
59
67
  tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }
60
68
  wiremock = "0.6"
@@ -1760,6 +1760,67 @@ let schema = qn.sql.get_schema("hyperliquid-core-mainnet").await?;
1760
1760
  println!("{} tables", schema.tables.len());
1761
1761
  ```
1762
1762
 
1763
+ ---
1764
+
1765
+ ### RPC & Tooling Access
1766
+
1767
+ Tooling Access provisions a single multichain, read-only endpoint per account and
1768
+ mints short-lived session JWTs. `qn.rpc` makes JSON-RPC calls directly against that
1769
+ endpoint, minting and refreshing the JWT automatically — no endpoint URL or token to
1770
+ manage.
1771
+
1772
+ Tooling Access must be enabled once (admin role + eligible plan). The control-plane
1773
+ methods live on `qn.admin`:
1774
+
1775
+ ```rust
1776
+ // Rust
1777
+ let status = qn.admin.tooling_access_status().await?;
1778
+ if !status.enabled {
1779
+ qn.admin.enable_tooling_access().await?; // idempotent; admin role required
1780
+ }
1781
+
1782
+ // call(method, params, network, endpoint_url). params is Option<serde_json::Value>;
1783
+ // None defaults to []. Both trailing args are Option and independently omittable.
1784
+ let block_number = qn.rpc.call("eth_blockNumber", None, None, None).await?;
1785
+ let balance = qn
1786
+ .rpc
1787
+ .call(
1788
+ "eth_getBalance",
1789
+ Some(serde_json::json!(["0xabc...", "latest"])),
1790
+ None,
1791
+ None,
1792
+ )
1793
+ .await?;
1794
+
1795
+ // Multichain: seed the per-network URL map (from get_endpoint_urls), then pass
1796
+ // the network key as the third arg.
1797
+ let urls = qn.admin.get_endpoint_urls(endpoint_id).await?;
1798
+ if let Some(data) = urls.data {
1799
+ if let Some(mc) = data.multichain_urls {
1800
+ qn.rpc
1801
+ .set_networks(mc.into_iter().map(|(k, v)| (k, v.http_url)).collect());
1802
+ }
1803
+ }
1804
+ let slot = qn.rpc.call("getSlot", None, Some("solana-mainnet".into()), None).await?;
1805
+
1806
+ // Custom endpoint URL: send to a fully-formed HTTP URL, bypassing Tooling Access
1807
+ // and the JWT (no Authorization header). Per-call via the 4th arg, or client-wide
1808
+ // via RpcConfig { endpoint_url, .. }. endpoint_url and network are mutually
1809
+ // exclusive (a custom URL is not multichain-routed).
1810
+ let block = qn
1811
+ .rpc
1812
+ .call("eth_blockNumber", None, None, Some("https://my-endpoint.example/rpc".into()))
1813
+ .await?;
1814
+
1815
+ // A JSON-RPC error member is returned as SdkError::Rpc { code, message }.
1816
+ ```
1817
+
1818
+ A host that persists across processes can snapshot the cached token with
1819
+ `qn.rpc.current_token()` and re-seed it via `RpcConfig { seed, .. }`;
1820
+ `refresh_margin_secs` (default 60) tunes how early the token is refreshed. Set
1821
+ `RpcConfig { endpoint_url, .. }` to route every call to a custom HTTP URL by
1822
+ default (no JWT minted); a per-call `endpoint_url` overrides it.
1823
+
1763
1824
  ## Error Handling
1764
1825
 
1765
1826
  Every binding exposes a typed exception hierarchy derived from the core `SdkError`
@@ -1775,8 +1836,9 @@ subclass to branch on transport vs. API semantics.
1775
1836
  | `ConnectionError` | connection refused / DNS / TLS (subclass of `HttpError`) | — |
1776
1837
  | `ApiError` | non-2xx HTTP response | `status`, `body` |
1777
1838
  | `DecodeError` | 2xx response but JSON parse failed | `body` |
1839
+ | `RpcError` | JSON-RPC call returned an `error` member | `code`, `message` |
1778
1840
 
1779
- Variants: pattern-match on `SdkError { Http, Api, Decode, UrlParse, Config }`; use `err.http_kind()` to classify `Http` into `Timeout`, `Connect`, or `Other`.
1841
+ Variants: pattern-match on `SdkError { Http, Api, Decode, UrlParse, Config, Rpc }`; use `err.http_kind()` to classify `Http` into `Timeout`, `Connect`, or `Other`.
1780
1842
 
1781
1843
  ```rust
1782
1844
  // Rust
@@ -874,6 +874,7 @@ async fn main() {
874
874
  webhooks: None,
875
875
  kvstore: None,
876
876
  sql: None,
877
+ rpc: None,
877
878
  };
878
879
  let headered = QuicknodeSdk::new(&with_headers).expect("build sdk with custom headers");
879
880
  match headered
@@ -900,6 +901,8 @@ async fn main() {
900
901
  streams: None,
901
902
  webhooks: None,
902
903
  kvstore: None,
904
+ sql: None,
905
+ rpc: None,
903
906
  };
904
907
  let tiny = QuicknodeSdk::new(&blackhole).expect("build tiny sdk");
905
908
  match tiny
@@ -0,0 +1,83 @@
1
+ use quicknode_sdk::{errors::SdkError, QuicknodeSdk, SdkFullConfig};
2
+
3
+ #[tokio::main]
4
+ #[allow(clippy::unwrap_used, clippy::expect_used)]
5
+ async fn main() {
6
+ let config = SdkFullConfig::from_env().expect("Config from env failed");
7
+ let qn = QuicknodeSdk::new(&config).expect("sdk failed to initialize");
8
+
9
+ // Ensure Tooling Access is provisioned (idempotent; requires admin role).
10
+ let status = qn
11
+ .admin
12
+ .tooling_access_status()
13
+ .await
14
+ .expect("tooling_access_status failed");
15
+ println!("tooling access enabled: {}", status.enabled);
16
+ if !status.enabled {
17
+ match qn.admin.enable_tooling_access().await {
18
+ Ok(s) => println!("enabled tooling access: {}", s.enabled),
19
+ Err(e) => {
20
+ eprintln!("could not enable tooling access: {e}");
21
+ return;
22
+ }
23
+ }
24
+ }
25
+
26
+ // Make a JSON-RPC call. The SDK mints and refreshes the session JWT.
27
+ match qn.rpc.call("eth_blockNumber", None, None, None).await {
28
+ Ok(result) => println!("eth_blockNumber => {result}"),
29
+ Err(e) => eprintln!("rpc call error: {e}"),
30
+ }
31
+
32
+ // Multichain: seed the per-network URL map from the endpoint id (returned by
33
+ // status), then route a call to a specific network by its key.
34
+ if let Some(id) = &status.endpoint_id {
35
+ if let Ok(urls) = qn.admin.get_endpoint_urls(id).await {
36
+ if let Some(mc) = urls.data.and_then(|d| d.multichain_urls) {
37
+ qn.rpc
38
+ .set_networks(mc.into_iter().map(|(k, v)| (k, v.http_url)).collect());
39
+ match qn
40
+ .rpc
41
+ .call("getSlot", None, Some("solana-mainnet".to_string()), None)
42
+ .await
43
+ {
44
+ Ok(result) => println!("solana getSlot => {result}"),
45
+ Err(e) => eprintln!("solana rpc error: {e}"),
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ // Demonstrate the typed JSON-RPC error path.
52
+ match qn
53
+ .rpc
54
+ .call(
55
+ "eth_getBalance",
56
+ Some(serde_json::json!(["not-an-address"])),
57
+ None,
58
+ None,
59
+ )
60
+ .await
61
+ {
62
+ Ok(result) => println!("unexpected ok: {result}"),
63
+ Err(SdkError::Rpc { code, message }) => {
64
+ println!("got expected RpcError: code={code} message={message}");
65
+ }
66
+ Err(e) => eprintln!("other error: {e}"),
67
+ }
68
+
69
+ // Custom endpoint URL: send a call to a fully-formed HTTP URL, bypassing the
70
+ // Tooling Access endpoint and the session JWT entirely. Useful for a
71
+ // provisioned `.quiknode.pro` URL or a self-hosted node. Set it per-call
72
+ // here, or client-wide via `RpcConfig::endpoint_url`.
73
+ if let Ok(custom_url) = std::env::var("QN_RPC_ENDPOINT_URL") {
74
+ match qn
75
+ .rpc
76
+ .call("eth_blockNumber", None, None, Some(custom_url))
77
+ .await
78
+ {
79
+ Ok(result) => println!("custom endpoint eth_blockNumber => {result}"),
80
+ Err(e) => eprintln!("custom endpoint rpc error: {e}"),
81
+ }
82
+ }
83
+ }
@@ -11,6 +11,7 @@ pub mod endpoints;
11
11
  pub mod logs;
12
12
  pub mod tags;
13
13
  pub mod teams;
14
+ pub mod tooling_access;
14
15
  pub mod usage;
15
16
 
16
17
  pub use account::{AccountInfo, AccountInfoResponse, AccountSubscription};
@@ -66,6 +67,7 @@ pub use teams::{
66
67
  TeamDetail, TeamEndpoint, TeamMessageData, TeamSummary, TeamUser, UpdateTeamEndpointsData,
67
68
  UpdateTeamEndpointsRequest, UpdateTeamEndpointsResponse,
68
69
  };
70
+ pub use tooling_access::ToolingAccessStatus;
69
71
 
70
72
  pub use usage::{
71
73
  ChainUsage, EndpointUsage, GetUsageByChainResponse, GetUsageByEndpointResponse,
@@ -1957,6 +1959,7 @@ mod tests {
1957
1959
  webhooks: None,
1958
1960
  kvstore: None,
1959
1961
  sql: None,
1962
+ rpc: None,
1960
1963
  })
1961
1964
  .unwrap()
1962
1965
  }
@@ -3847,6 +3850,7 @@ mod tests {
3847
3850
  webhooks: None,
3848
3851
  kvstore: None,
3849
3852
  sql: None,
3853
+ rpc: None,
3850
3854
  });
3851
3855
  assert!(matches!(result, Err(crate::errors::SdkError::Config(_))));
3852
3856
  }