Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
67e0a3c
x402/MPP
johnpmitsch Jul 13, 2026
fa106fb
wip
johnpmitsch Jul 13, 2026
fe0c018
Error update
johnpmitsch Jul 16, 2026
15b1053
fix(rpc): reject over-u64 x402/Solana amounts with a clear error
johnpmitsch Jul 16, 2026
0f59f6d
feat(payments): generate fresh payment wallets + expose address
johnpmitsch Jul 16, 2026
6c0ffe6
feat(payments): add GeneratedWallet::into_key for persistence
johnpmitsch Jul 16, 2026
6b7af3d
feat(payments): surface the gateway's rejection reason
johnpmitsch Jul 17, 2026
e6416f4
feat(payments): x402 credit drawdown lane
johnpmitsch Jul 17, 2026
6da9adc
feat(payments): MPP session (payment-channel) lane
johnpmitsch Jul 17, 2026
06b5bc4
feat(payments): carry the per-call price in ChannelState
johnpmitsch Jul 17, 2026
376a048
fix(payments): SIWX Chain ID must be the numeric EIP-155 id
johnpmitsch Jul 17, 2026
2578805
fix(payments): use the gateway's exact required SIWX ToS statement
johnpmitsch Jul 17, 2026
229d37a
fix(payments): checksum the SIWX address + millisecond issuedAt
johnpmitsch Jul 17, 2026
dd96429
fix(payments): drip returns the funding tx; buy credits via network path
johnpmitsch Jul 17, 2026
d314a92
More updates
johnpmitsch Aug 3, 2026
6e4b734
fix(payments): fail fast on bad credentials + cover the session lifec…
johnpmitsch Aug 3, 2026
f5235bd
feat(payments): expose the payment lanes to Python
johnpmitsch Aug 3, 2026
1561c41
feat(payments): expose the payment lanes to Node and Ruby
johnpmitsch Aug 3, 2026
eacc34f
docs(payments): document the drawdown and channel lanes
johnpmitsch Aug 3, 2026
cf77d3c
SDK fixes
johnpmitsch Aug 3, 2026
39d5305
Docs and types updates
johnpmitsch Aug 3, 2026
333fce2
refactor: tighten payment comments
johnpmitsch Aug 3, 2026
781f2eb
x402 fix
johnpmitsch Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,45 @@ jobs:
- name: cargo test
run: cargo test -p quicknode-sdk --lib

# Payment lanes are feature-gated and add crypto deps + #[cfg]'d types, so
# each combo must build and test independently, plus a features-off build to
# prove the base crate is unaffected.
payment-features:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
features:
- "" # features-off baseline
- "payments"
- "payments-svm"
- "payments-tempo"
- "payments,payments-svm,payments-tempo"
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
with:
components: clippy

- uses: Swatinem/rust-cache@v2

- name: cargo clippy (features="${{ matrix.features }}")
run: |
if [ -z "${{ matrix.features }}" ]; then
cargo clippy -p quicknode-sdk --lib --tests -- -D warnings
else
cargo clippy -p quicknode-sdk --lib --tests --features "${{ matrix.features }}" -- -D warnings
fi

- name: cargo test (features="${{ matrix.features }}")
run: |
if [ -z "${{ matrix.features }}" ]; then
cargo test -p quicknode-sdk --lib
else
cargo test -p quicknode-sdk --lib --features "${{ matrix.features }}"
fi

python:
runs-on: ubuntu-latest
steps:
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,9 @@ notes.md
# Ruby native extension (built locally by `just ruby-build`)
ruby/lib/quicknode_sdk/*.bundle
ruby/lib/quicknode_sdk/*.so

# Local scratch
scratch/

# Local working notes
IMPLEMENTATION_PLAN.md
9 changes: 8 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,14 @@ pub struct SomeRequest { ... }
- `rust` feature — `bon` builder pattern for ergonomic Rust usage

### Error Handling
`SdkError` (`crates/core/src/errors.rs`) uses `thiserror` with five variants:
`SdkError` (`crates/core/src/errors.rs`) uses `thiserror`:
- `Http` — wraps `reqwest::Error` (further classified via `SdkError::http_kind()` → `HttpKind::{Timeout, Connect, Other}`)
- `Api` — non-2xx response with status code and raw body
- `Decode` — JSON parse failure with raw body for debugging
- `UrlParse` — invalid URL (wraps `url::ParseError`)
- `Config` — invalid configuration (string message)
- `Rpc` — JSON-RPC `error` member (code + message)
- `PaymentUnsupported` / `PaymentRejected` / `PaymentIndeterminate` — crypto-micropayment lane (the `payments*` features; see the payment-lane docs)

Each binding exposes a typed exception hierarchy rooted at a shared base class so callers can `rescue` / `catch` / `except` by category. The mapping is:

Expand All @@ -124,6 +126,10 @@ Each binding exposes a typed exception hierarchy rooted at a shared base class s
| `Http` + `HttpKind::Other` | `HttpError` | `HttpError` | `QuicknodeError` |
| `Api { status, body }` | `ApiError` (with `.status`, `.body`) | `ApiError` (with `.status`, `.body`) | `QuicknodeError` |
| `Decode { body, .. }` | `DecodeError` (with `.body`) | `DecodeError` (with `.body`) | `QuicknodeError` |
| `Rpc { code, message }` | `RpcError` (with `.code`, `.message`) | `RpcError` (with `.code`) | `QuicknodeError` |
| `PaymentUnsupported` | `PaymentUnsupportedError` | `PaymentUnsupportedError` | `PaymentError` |
| `PaymentRejected { status, body }` | `PaymentRejectedError` (with `.status`, `.body`) | `PaymentRejectedError` (with `.status`, `.body`) | `PaymentError` |
| `PaymentIndeterminate` | `PaymentIndeterminateError` | `PaymentIndeterminateError` | `PaymentError` |

Each binding owns its mapping in a dedicated `errors.rs` file:
- **Python** — `crates/python/src/errors.rs` uses `create_exception!` macros; `map_sdk_err` sets `.status` / `.body` attributes via `setattr` on the exception instance. Exceptions are registered on the module in `add_to_module`.
Expand Down Expand Up @@ -198,6 +204,7 @@ Core clients are tested using mocked API calls with wiremock. All functions maki
- Any user-facing change to a method, parameter, return type, error class, or environment variable must be reflected in **all four** per-language READMEs in the same PR. This matches the polyglot consistency rule for `__init__.py`, `sdk.d.ts`, and `quicknode_sdk.rbs` documented in §SDK-Specific Guidelines → Polyglot consistency.
- The Configuration env-var table and the Error Handling class table are duplicated verbatim across all four per-language READMEs. When one changes, update all four — keep them byte-identical.
- Per-language READMEs are wired into package metadata (`crates/core/Cargo.toml` `readme`, `pyproject.toml` `readme`, `npm/package.json` `files`, `ruby/quicknode_sdk.gemspec` `s.files`). When adding a new language or moving a README, update the corresponding manifest.
- Every README has a **manually maintained Table of Contents** — it is NOT auto-generated. When you add, remove, rename, or reorder any `##`/`###`/`####` heading, update that file's TOC in the same PR. The TOC covers every heading below `## Table of Contents`, nested by level, and each anchor must match the GitHub slug of its heading (lowercase, spaces to hyphens, punctuation and backticks dropped, em dashes dropped — so `### Option A — Pass config directly` becomes `#option-a--pass-config-directly` with a double hyphen). Because the per-language READMEs share almost all their headings, a heading change in one usually needs the same TOC change in the other three.

### Platform support

Expand Down
41 changes: 41 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ ruby = ["magnus"]
rust = ["bon"]
extension-module = ["pyo3/extension-module"]

# Crypto-micropayment lanes for rpc.call. Layered so a crates.io consumer can
# opt into only the pay-chains it needs; wheels/npm/gems ship features-on, so
# "zero cost when off" is true only for the crate itself.
# payments = x402/EVM (EIP-712 TransferWithAuthorization).
# payments-svm = + x402/Solana (hand-rolled v0 tx: compute budget, SPL
# TransferChecked, memo; ed25519).
# payments-tempo = + MPP/Tempo (native type-0x76 tx via tempo-primitives).
payments = ["dep:k256", "dep:sha3", "dep:hex", "dep:base64", "dep:rand"]
payments-svm = ["payments", "dep:ed25519-dalek", "dep:bs58", "dep:sha2"]
# tempo-primitives MUST stay default-features=false: its defaults pull revm +
# aws-lc-rs (C/cmake) which break cross+zig at glibc-2.17/musl. The base64 dep
# is force-included with the `alloc` feature to work around an upstream no_std
# feature-unification bug (tempo-primitives no-default-features won't compile
# without base64/alloc present in the tree).
payments-tempo = ["payments", "dep:tempo-primitives", "dep:alloy-primitives", "dep:alloy-consensus", "dep:alloy-rlp"]

[dependencies]
thiserror = "1.0"
serde = { version = "1.0", features = ["derive"] }
Expand All @@ -39,6 +55,27 @@ secrecy = "0.8"
# supplied by the caller (bindings or the user's own runtime).
tokio = { version = "1", default-features = false, features = ["sync"] }

# ── Payment lane crypto (feature-gated) ──────────────────────────────────────
# secp256k1 signing for EIP-712 (x402/EVM) and Tempo (MPP). k256 0.14's
# sign_prehash_recoverable returns the tuple directly (no Result).
k256 = { version = "0.14", optional = true }
sha3 = { version = "0.10", optional = true } # keccak256 for EIP-712 + Tempo memo
hex = { version = "0.4", optional = true }
rand = { version = "0.8", optional = true } # random EIP-3009 nonce
# base64 is also force-enabled with `alloc` to unify features for
# tempo-primitives' no_std build (see the payments-tempo feature comment).
base64 = { version = "0.22", optional = true, features = ["alloc"] }
# x402/Solana: ed25519 signing + base58 addresses + sha256 for PDA/ATA derivation.
ed25519-dalek = { version = "2", optional = true }
bs58 = { version = "0.5", optional = true }
sha2 = { version = "0.10", optional = true }
# MPP/Tempo native type-0x76 tx. default-features=false is REQUIRED (see feature
# comment). alloy-* pinned to the versions tempo-primitives 1.8.1 builds against.
tempo-primitives = { version = "1.8.1", optional = true, default-features = false }
alloy-primitives = { version = "1.6", optional = true }
alloy-consensus = { version = "2.1", optional = true }
alloy-rlp = { version = "0.3", optional = true }

[[example]]
name = "admin"
required-features = ["rust"]
Expand All @@ -63,6 +100,10 @@ required-features = ["rust"]
name = "rpc"
required-features = ["rust"]

[[example]]
name = "rpc_payment"
required-features = ["rust", "payments", "payments-svm", "payments-tempo"]

[dev-dependencies]
tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }
wiremock = "0.6"
Loading
Loading