Skip to content

Commit 3664609

Browse files
committed
Formatting updates
1 parent b89808e commit 3664609

9 files changed

Lines changed: 232 additions & 86 deletions

File tree

IMPLEMENTATION_PLAN.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Implementation plan: x402 drawdown + MPP session
2+
3+
Source of truth for the UX design:
4+
`~/.claude/plans/research-quicknode-gateway-payment-vast-babbage.md`.
5+
This file tracks execution status only; do not re-litigate the 14 decisions.
6+
7+
Branch `x402_MPP`, one PR, both models, phased commits (x402 drawdown first,
8+
then MPP session). Do NOT commit this file (public repo).
9+
10+
Pre-stage (done): popped stash and committed the per-request `--payment-*`
11+
flag rename + asset-name resolution. Plan naming is now real.
12+
13+
## Stage 1: SDK x402 drawdown
14+
**Goal**: SIWX auth (POST /auth), JWT seed/export, drawdown call (Bearer, no
15+
signing), GET /credits, POST /drip, credit purchase via existing 402 signer.
16+
New `PaymentScheme` variant(s) in `../sdk/crates/core/src/rpc/payment/mod.rs`.
17+
**Success criteria**: SDK wiremock unit tests; per-request paths untouched.
18+
**Status**: Complete (SDK commit e6416f4)
19+
20+
## Stage 2: CLI `qn rpc x402` noun
21+
**Goal**: buy-credits/balance/drip in `src/commands/rpc/x402.rs`; JWT cache
22+
(0600, wallet-address keyed); Mild gating w/ ceiling-naming prompt; next hints.
23+
**Success criteria**: happy + error + both gating tests in tests/rpc_payment.rs.
24+
**Status**: Complete (CLI commit 21ef1b1)
25+
26+
## Stage 3: CLI `--x402-drawdown` on call
27+
**Goal**: ArgGroup member; auto re-auth; error mapping (empty credits, monthly
28+
limit). context.md + README in same commits.
29+
**Success criteria**: wiremock tests incl. expired-JWT re-auth; single-attempt.
30+
**Status**: Complete (CLI commit 7b11106)
31+
32+
## Stage 4: SDK MPP session
33+
**Goal**: escrow deposit tx (Tempo first), open/top-up/close/status, cumulative
34+
EIP-712 voucher signer, `/session/:network` prefix via `host_base`.
35+
**Success criteria**: SDK tests; charge-intent path untouched.
36+
**Status**: Complete (SDK commit 6da9adc; byte-exact voucher+channelId vectors)
37+
38+
## Stage 5: CLI `qn rpc mpp` noun + `--mpp-session` on call
39+
**Goal**: open/top-up/close/status in `src/commands/rpc/mpp.rs`; channel state
40+
file + recovery-via-status; Mild gating incl. close; voucher call mode.
41+
**Success criteria**: full test matrix; snapshot review for table output.
42+
**Status**: Complete (SDK 06b5bc4; CLI ab0c188)
43+
44+
## Stage 6: README Micropayments refactor + docs polish
45+
**Goal**: one Micropayments section, four zero-to-call walkthroughs + shared
46+
Get-a-wallet preamble; call --help examples; deferral notes.
47+
**Success criteria**: all four walkthroughs copy-pasteable; full verify clean.
48+
**Status**: In Progress

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,8 @@ Every path needs a payment wallet. Two options:
353353
referenced by name with `--payment-wallet`):
354354

355355
```sh
356-
qn wallet generate --chain evm --name payer # evm also covers MPP/Tempo
357-
qn wallet generate --chain svm --name sol-payer # svm for x402/Solana
356+
qn wallet generate --vm evm --name payer # evm also covers MPP/Tempo
357+
qn wallet generate --vm svm --name sol-payer # svm for x402/Solana
358358
```
359359

360360
`generate` prints the address (and a QR on a terminal) to fund. The key is
@@ -377,7 +377,7 @@ is a ready `--payment-asset`. Cached at `~/.config/qn/pay-networks.toml` (24h).
377377
Sign an x402 payment on each call (EVM or Solana stablecoin):
378378

379379
```sh
380-
qn wallet generate --chain evm --name payer # fund the printed address with Base Sepolia USDC
380+
qn wallet generate --vm evm --name payer # fund the printed address with Base Sepolia USDC
381381
qn rpc call eth_blockNumber \
382382
--network base-sepolia --x402 \
383383
--payment-wallet payer \
@@ -389,7 +389,7 @@ qn rpc call eth_blockNumber \
389389
`--network` is the chain you *query*; `--payment-network` is the chain the
390390
payment *settles* on (independent). `--max-amount` is the per-call ceiling in
391391
integer base units (e.g. `1000` = 0.001 USDC); an offer above it is refused
392-
before anything is signed. For Solana, generate an `--chain svm` wallet and use
392+
before anything is signed. For Solana, generate a `--vm svm` wallet and use
393393
`--network solana-devnet --payment-network solana-devnet` (add `--svm-rpc-url`
394394
at volume; the public default rate-limits).
395395

@@ -400,7 +400,7 @@ uses the same secp256k1 key format); `--receipt` wraps the result with the
400400
settlement transaction hash:
401401

402402
```sh
403-
qn wallet generate --chain evm --name payer # fund on Tempo testnet
403+
qn wallet generate --vm evm --name payer # fund on Tempo testnet
404404
qn rpc call eth_blockNumber \
405405
--network tempo-testnet --mpp --receipt \
406406
--payment-wallet payer \
@@ -419,7 +419,7 @@ Buy a block of prepaid credits once, then spend them with no per-call signing
419419
(one credit per successful response):
420420

421421
```sh
422-
qn wallet generate --chain evm --name payer # dedicated wallet
422+
qn wallet generate --vm evm --name payer # dedicated wallet
423423

424424
# Testnet only: fund the wallet from the faucet (Base Sepolia, once per
425425
# account). Prints the funding tx; mainnet wallets are funded normally.
@@ -448,7 +448,7 @@ Open an on-chain escrow payment channel once, then pay per call with a
448448
cumulative EIP-712 voucher (no on-chain transaction per call):
449449

450450
```sh
451-
qn wallet generate --chain evm --name payer # evm covers Tempo; fund the address
451+
qn wallet generate --vm evm --name payer # evm covers Tempo; fund the address
452452

453453
# Open a channel by depositing into the escrow (moves real funds; gated).
454454
qn rpc mpp open --network tempo-testnet --deposit 1000000 \
@@ -512,8 +512,8 @@ paid RPC lane uses (see [Micropayments](#micropayments)). It needs no API key
512512
or login:
513513

514514
```sh
515-
qn wallet generate --chain evm --name payer # create + store; prints the address and a QR to fund
516-
qn wallet list # names, chain, address (never the key)
515+
qn wallet generate --vm evm --name payer # create + store; prints the address and a QR to fund
516+
qn wallet list # names, vm, address (never the key)
517517
qn wallet show payer # bare address to stdout; QR + key path to stderr
518518
qn wallet rm payer # gated: --yes to confirm; the key is unrecoverable
519519
```

src/commands/agent/context.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ Top-level nouns (plurals like `endpoints`/`streams` and `ls` are accepted aliase
209209
`--x402`/`--mpp`/`--x402-drawdown`/`--endpoint-url`, and points at
210210
`qn rpc mpp top-up` when the channel deposit is exhausted.
211211
- `wallet` — the local store of payment wallets for the paid RPC lane; no API
212-
key or login required. `qn wallet generate --chain <evm|svm> --name <NAME>`
212+
key or login required. `qn wallet generate --vm <evm|svm> --name <NAME>`
213213
creates and stores a dedicated payment wallet (raw key at 0600 under
214214
`<config-dir>/qn/wallets/`, `evm` also covers MPP/Tempo), printing its
215215
address (and a QR to fund it on a terminal); `qn wallet list`/`show <NAME>`
@@ -295,7 +295,7 @@ refused with HTTP 400/402 before anything settles.
295295

296296
```sh
297297
qn rpc pay-networks # which networks are payable, and the x402 asset
298-
qn wallet generate --chain evm --name payer # dedicated wallet; prints its address + a QR to fund
298+
qn wallet generate --vm evm --name payer # dedicated wallet; prints its address + a QR to fund
299299
# → fund THAT address, then pick a lane:
300300

301301
# x402 on EVM (Base Sepolia testnet, USDC):
@@ -309,7 +309,7 @@ qn rpc call eth_blockNumber --network tempo-testnet --mpp --receipt \
309309
--payment-asset USDC --max-amount 1000
310310

311311
# x402 on Solana (devnet); needs an SVM wallet:
312-
qn wallet generate --chain svm --name sol-payer
312+
qn wallet generate --vm svm --name sol-payer
313313
qn rpc call getSlot --network solana-devnet --x402 \
314314
--payment-wallet sol-payer --payment-network solana-devnet \
315315
--payment-asset USDC --max-amount 1000

src/commands/rpc/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub enum RpcCmd {
7777
qn rpc call eth_blockNumber --network tempo-testnet --mpp-session --payment-wallet payer\n\n\
7878
See payable networks and manage wallets:\n \
7979
qn rpc pay-networks\n \
80-
qn wallet generate --chain evm --name payer")]
80+
qn wallet generate --vm evm --name payer")]
8181
Call(Box<CallArgs>),
8282

8383
/// List the endpoint's available network keys (no RPC call).

src/commands/rpc/x402.rs

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use quicknode_sdk::{CreditBalance, PaymentConfig};
2424
use crate::config::{self, PaymentSection};
2525
use crate::context::{Ctx, GlobalArgs};
2626
use crate::errors::CliError;
27+
use crate::output::{style, Style};
2728

2829
use super::payment::{
2930
ensure_gateway_session, resolve_payment_params, resolve_session_params, PaymentParams,
@@ -256,23 +257,32 @@ async fn run_buy_credits(args: PaymentArgs, global: GlobalArgs) -> Result<(), Cl
256257
"✓ Bought credits (balance: {})",
257258
fmt_credits(balance.credits)
258259
));
259-
ctx.out
260-
.note(&format!(" Next: {}", drawdown_call_hint(&args, &network)));
260+
ctx.out.note(&format!(
261+
"\n{}\n\n{}",
262+
style(
263+
"Spend the credits on calls (signs nothing per request):",
264+
Style::Bold,
265+
ctx.out.color,
266+
),
267+
drawdown_call_hint(&args, &network)
268+
));
261269
emit_balance(&ctx, &balance)
262270
}
263271

264-
// A copy-pasteable `--x402-drawdown` call reflecting the flags the user just
265-
// used, so the chained next step runs as-is.
272+
// A copy-pasteable, multi-line `--x402-drawdown` call reflecting the flags the
273+
// user just used, so the chained next step runs as-is.
266274
fn drawdown_call_hint(args: &PaymentArgs, network: &str) -> String {
267275
let mut cmd = format!(
268-
"qn rpc call eth_blockNumber --network {network} --x402-drawdown --payment-wallet {}",
276+
" qn rpc call eth_blockNumber \\\n \
277+
--network {network} --x402-drawdown \\\n \
278+
--payment-wallet {}",
269279
args.payment_wallet.as_deref().unwrap_or("<NAME>")
270280
);
271281
// The drawdown call defaults its pay network to --network, so only append
272282
// --payment-network when the user set an explicit one that differs.
273283
if let Some(pn) = &args.payment_network {
274284
if pn != network {
275-
cmd.push_str(&format!(" --payment-network {pn}"));
285+
cmd.push_str(&format!(" \\\n --payment-network {pn}"));
276286
}
277287
}
278288
cmd
@@ -296,17 +306,54 @@ async fn run_drip(args: SessionArgs, global: GlobalArgs) -> Result<(), CliError>
296306
"✓ Faucet funded {} (tx: {})",
297307
receipt.account_id, receipt.transaction_hash
298308
));
299-
// Point at buy-credits with the flags the user already supplied.
300-
let net = args.network.as_deref().or(args.payment_network.as_deref());
301-
// buy-credits signs, so the hint carries the asset + ceiling placeholders
302-
// the user fills in for the purchase (drip itself collects neither).
303-
ctx.out.note(&format!(
304-
" Next: qn rpc x402 buy-credits --network {} --payment-wallet {} \
305-
--payment-network {} --payment-asset <ASSET> --max-amount 1000000",
306-
net.unwrap_or("<SLUG>"),
307-
args.payment_wallet.as_deref().unwrap_or("<NAME>"),
308-
args.payment_network.as_deref().unwrap_or("<NET>"),
309+
// Point at the two paid lanes with the flags the user already supplied;
310+
// USDC is the asset the faucet just funded.
311+
let query_net = args
312+
.network
313+
.as_deref()
314+
.or(args.payment_network.as_deref())
315+
.unwrap_or("<SLUG>");
316+
let wallet = args.payment_wallet.as_deref().unwrap_or("<NAME>");
317+
let pay_net = args.payment_network.as_deref().unwrap_or("<NET>");
318+
let c = ctx.out.color;
319+
320+
let mut block = String::new();
321+
block.push_str(
322+
"\nThis wallet now has funds that can be used to pay for blockchain calls\n\
323+
using micropayments.\n\n",
324+
);
325+
block.push_str(&style(
326+
"Pay per-request (sign a payment on each call):",
327+
Style::Bold,
328+
c,
329+
));
330+
block.push_str(&format!(
331+
"\n\n \
332+
qn rpc call eth_blockNumber \\\n \
333+
--network {query_net} --x402 \\\n \
334+
--payment-wallet {wallet} \\\n \
335+
--payment-network {pay_net} \\\n \
336+
--payment-asset USDC \\\n \
337+
--max-amount 1000\n\n"
338+
));
339+
block.push_str(&style(
340+
"Credit drawdown (buy prepaid credits once, then spend them):",
341+
Style::Bold,
342+
c,
343+
));
344+
block.push_str(&format!(
345+
"\n\n \
346+
qn rpc x402 buy-credits \\\n \
347+
--network {query_net} \\\n \
348+
--payment-wallet {wallet} \\\n \
349+
--payment-network {pay_net} \\\n \
350+
--payment-asset USDC \\\n \
351+
--max-amount 1000000\n\n \
352+
qn rpc call eth_blockNumber \\\n \
353+
--network {query_net} --x402-drawdown \\\n \
354+
--payment-wallet {wallet}"
309355
));
356+
ctx.out.note(&block);
310357
if matches!(ctx.global.format, Some(f) if f.is_structured()) {
311358
let v = serde_json::json!({
312359
"account_id": receipt.account_id,

0 commit comments

Comments
 (0)