Skip to content

Commit e1cd987

Browse files
authored
feat(rpc): qn rpc — JSON-RPC calls with managed or custom endpoints (#52)
* RPC support * wip * feat(rpc): add call/list-networks subcommands and custom endpoint URL Restructure `qn rpc` into explicit subcommands and add a way to target a custom RPC endpoint instead of the account's Tooling Access endpoint. - `qn rpc call <method> [params]` replaces the bare `qn rpc <method>` form, leaving room for future subcommands. Bare `qn rpc` prints help. - `qn rpc list-networks` (alias `ls`) replaces the `--list-networks` flag. - `qn rpc call --endpoint-url <URL>` sends a call to a fully-formed HTTP URL. The URL is self-authenticating: no session token is minted or attached, and the Tooling Access enable/probe recovery is bypassed entirely. - `[rpc] endpoint_url` in config sets a client-wide default; a per-call `--endpoint-url` overrides it. - `--endpoint-url` and `--network` are mutually exclusive (a custom URL is not multichain-routed), enforced at parse time. An unknown `--network` key now surfaces a CLI error pointing at `qn rpc list-networks` instead of the SDK's internal wording. Consumes the published quicknode-sdk 0.7 (4-arg `rpc.call` + `RpcConfig.endpoint_url`). Docs (README, agent context) and tests updated; rpc.rs test count 18. * feat(rpc): accept JSON params from a file via --params-file Add `--params-file <PATH>` / `-f` to `qn rpc call`, mirroring `qn sql query --file`: read the JSON-RPC params from a file, or from stdin when the path is `-`. Mutually exclusive with the positional params argument (clap ArgGroup). The positional params and its `-` stdin form are unchanged. A missing file surfaces "could not read params file '<path>': ...". Tests: file happy path, missing-file error, positional/--params-file conflict. * fix(rpc): gate tooling-access disable and name RPC call positionals Disabling Tooling Access cuts off blockchain access for all Quicknode developer tooling on the account, so route it through confirm_mild (Severity::Mild): --yes skips, TTY prompts, non-TTY without --yes returns exit 5. Prompt names the blast radius. Also set explicit value_name on 'qn rpc call' positionals (METHOD, PARAMS) per the CLI value-name convention. Tests: disable now needs --yes (exit 0); no --yes non-TTY returns exit 5 and reaches the mock zero times.
1 parent 424dab4 commit e1cd987

13 files changed

Lines changed: 2284 additions & 100 deletions

File tree

‎Cargo.lock‎

Lines changed: 15 additions & 87 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ name = "qn"
2121
path = "src/lib.rs"
2222

2323
[dependencies]
24-
quicknode-sdk = "0.6"
24+
quicknode-sdk = "0.7"
2525
clap = { version = "4", features = ["derive", "env", "wrap_help"] }
2626
clap_complete = "4"
2727
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
@@ -39,6 +39,9 @@ time = { version = "0.3", features = ["formatting", "parsing", "macros"] }
3939
base64 = "0.22"
4040
tempfile = "3"
4141
url = "2"
42+
# Stable fingerprint of the API key for scoping the JWT token cache to an
43+
# account. The key itself is never written to the cache — only this hash.
44+
sha2 = "0.10"
4245

4346
[dev-dependencies]
4447
reqwest = { version = "0.12", default-features = false }

‎README.md‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,55 @@ qn sql schema hyperliquid-core-mainnet
278278
Queries are read-only (SELECT) and capped at 1000 rows per request; page through
279279
larger result sets with `LIMIT`/`OFFSET` in the SQL.
280280

281+
### On-chain RPC
282+
283+
Make JSON-RPC calls with no endpoint to provision. `qn rpc call` mints and
284+
refreshes a short-lived session JWT automatically; the only one-time step is
285+
enabling Tooling Access (or pass `--yes` to enable on first use).
286+
287+
```sh
288+
qn tooling-access enable # one-time; idempotent, requires an admin role
289+
qn tooling-access status
290+
291+
qn rpc call eth_blockNumber
292+
qn rpc call eth_getBalance '["0xabc...", "latest"]'
293+
qn rpc call eth_call '{"to":"0x..."}'
294+
qn rpc call eth_call --params-file params.json # read params from a file (-f)
295+
echo '[...]' | qn rpc call eth_call - # read params from stdin
296+
297+
qn rpc call eth_blockNumber --yes # auto-enable Tooling Access if needed
298+
299+
# Multichain: the endpoint serves many chains. Target one by its network key.
300+
qn rpc list-networks # list available network keys (alias: ls)
301+
qn rpc call getSlot --network solana-mainnet
302+
qn rpc call eth_chainId --network polygon
303+
304+
# Custom endpoint: send the call to a fully-formed HTTP URL instead of Tooling
305+
# Access. The URL is self-authenticating (no session token is minted or sent).
306+
qn rpc call eth_blockNumber --endpoint-url https://my-endpoint.example/rpc
307+
```
308+
309+
Set a default custom endpoint in `~/.config/qn/config.toml` so every `qn rpc call`
310+
uses it without the flag (a per-call `--endpoint-url` still overrides it):
311+
312+
```toml
313+
[rpc]
314+
endpoint_url = "https://my-endpoint.example/rpc"
315+
```
316+
317+
`--endpoint-url` and `--network` are mutually exclusive: a custom URL is not
318+
multichain-routed.
319+
320+
The network map is cached in `~/.config/qn/networks.toml` (per endpoint, 24h TTL),
321+
so `--network` calls reuse it without re-fetching. Network keys are the endpoint's
322+
own `multichain_urls` keys (note these can differ from chain slugs, e.g. `polygon`
323+
not `matic`); `qn rpc list-networks` shows the exact set.
324+
325+
The session token is cached under `~/.config/qn/tokens.toml` (0600), scoped to the
326+
API key, so subsequent calls skip the mint round trip while it's valid. Results are
327+
schemaless JSON; `-o json|yaml|toon` controls the format (`table`/`md` fall back to
328+
JSON).
329+
281330
### Other
282331

283332
```sh

‎src/cli.rs‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ pub struct Cli {
9494
#[arg(long, global = true, hide = true)]
9595
pub base_url: Option<String>,
9696

97+
/// Path prefix inserted between the host and each sub-client's path (e.g.
98+
/// `/console-api`). For reverse-proxy / gateway environments. Requires
99+
/// `--base-url`.
100+
#[arg(long, global = true, hide = true)]
101+
pub base_prefix: Option<String>,
102+
97103
/// Print help (see a summary with '-h').
98104
#[arg(short = 'h', long, global = true, action = ArgAction::Help)]
99105
pub help: Option<bool>,
@@ -149,6 +155,13 @@ pub enum Command {
149155
/// Run SQL queries and inspect cluster schemas.
150156
Sql(commands::sql::Args),
151157

158+
/// Make JSON-RPC calls against your Tooling Access endpoint.
159+
Rpc(commands::rpc::Args),
160+
161+
/// Manage Tooling Access (the endpoint `qn rpc` uses).
162+
#[command(name = "tooling-access")]
163+
ToolingAccess(commands::tooling_access::Args),
164+
152165
/// Generate shell completion scripts.
153166
///
154167
/// When installing qn through a package manager, it's possible that no
@@ -206,6 +219,7 @@ impl Cli {
206219
yes_count: self.yes,
207220
retries: self.retries,
208221
base_url: self.base_url.clone(),
222+
base_prefix: self.base_prefix.clone(),
209223
}
210224
}
211225

@@ -239,6 +253,12 @@ impl Cli {
239253
Command::Webhook(args) => commands::webhook::run(args, Ctx::from_global(global)?).await,
240254
Command::Kv(args) => commands::kv::run(args, Ctx::from_global(global)?).await,
241255
Command::Sql(args) => commands::sql::run(args, Ctx::from_global(global)?).await,
256+
// rpc builds its own Ctx (it seeds the SDK from the on-disk token
257+
// cache before construction), so it takes `global` directly.
258+
Command::Rpc(args) => commands::rpc::run(args, global).await,
259+
Command::ToolingAccess(args) => {
260+
commands::tooling_access::run(args, Ctx::from_global(global)?).await
261+
}
242262
}
243263
}
244264
}

‎src/commands/agent/context.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ Top-level nouns (plurals like `endpoints`/`streams` and `ls` are accepted aliase
117117
- `kv` — `set` (put, get, list, delete, bulk) and `list` (list, get, create, append,
118118
contains, remove-item, update, delete)
119119
- `sql` — query (inline SQL, `--file <path>`, or `--file -` for stdin), schema
120+
- `tooling-access` — status, enable, disable (provisions the endpoint `rpc` uses)
121+
- `rpc` — make JSON-RPC calls. `qn rpc call <method> [json-params]` calls the
122+
account's Tooling Access endpoint (params is a JSON array or object inline, or
123+
`--params-file <PATH>` / `-f` to read from a file, or `-` for stdin); the
124+
session JWT is minted and refreshed automatically. On a
125+
not-yet-enabled account it auto-enables with `--yes` (or prompts on a TTY).
126+
Multichain: `--network <key>` targets a specific chain by its key (e.g.
127+
`solana-mainnet`, `polygon`); `qn rpc list-networks` (alias `ls`) lists the
128+
available keys. Custom endpoint: `--endpoint-url <URL>` (or `[rpc] endpoint_url`
129+
in config) sends the call to a fully-formed HTTP URL that authenticates itself
130+
(no token minted); it's mutually exclusive with `--network`.
120131

121132
Drill into any level with `--help`: `qn endpoint --help`, `qn endpoint security --help`,
122133
`qn endpoint rate-limit --help`. Shell completions: `qn completions <bash|zsh|fish|...>`.
@@ -171,6 +182,21 @@ qn kv set get my-key
171182
qn kv set list
172183
```
173184

185+
**Make on-chain calls (no endpoint to provision):**
186+
187+
```sh
188+
qn tooling-access enable --yes # one-time; idempotent, admin role required
189+
qn rpc call eth_blockNumber # → "0x…" (default network)
190+
qn rpc call eth_getBalance '["0xabc...", "latest"]'
191+
qn rpc list-networks # available network keys for this endpoint
192+
qn rpc call getSlot --network solana-mainnet
193+
qn rpc call eth_blockNumber --endpoint-url https://my-endpoint.example/rpc
194+
```
195+
196+
`qn rpc call` mints and refreshes the session JWT for you; the only one-time step
197+
is enabling Tooling Access (or pass `--yes` to enable on first use). A custom
198+
`--endpoint-url` (or `[rpc] endpoint_url` in config) bypasses that entirely.
199+
174200
## 8. Gotchas & safety rails
175201

176202
- Mutations are never retried; re-running a failed create can double-provision (§5).

‎src/commands/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ pub mod chain;
1010
pub mod endpoint;
1111
pub mod kv;
1212
pub mod metrics;
13+
pub mod rpc;
1314
pub mod sql;
1415
pub mod stream;
1516
pub mod team;
17+
pub mod tooling_access;
1618
pub mod usage;
1719
pub mod webhook;

0 commit comments

Comments
 (0)