quicknode_sdk 0.7.0 → 0.8.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e3ff0b06165dc1014255f5cb518cc2fe4cebc2a77aaf689cc52aac5638951b7
4
- data.tar.gz: edd43ef70ecb2948887ed7cb9668ec4e19eb325bcf0d2ace387dc8007a239724
3
+ metadata.gz: 2afee73b31e4a4f431a7ab86b558494e0ceeb517e8d9d63e7d1aa3c9a14ee6b3
4
+ data.tar.gz: 92170453b26ae7adaeded2f165c8af400f5b4069f8bfd6f3aa2b01db352433ac
5
5
  SHA512:
6
- metadata.gz: 33cac6ddae2a468b0b721e91124846c9dd5d0bc9c1a907e43ec4d7833d34715603427bbfb11b6ab20c0a4b4f9bd163a220b97e3b0d14a8cf9db29c59de439626
7
- data.tar.gz: 87e8aac27445983d1fc63ece74ae63929bd92dcbfcb0df155fc377d75f839397b4eb9843d341c233f43cdda77464af72313cdbdf856bebd2d10edd54ec021fd7
6
+ metadata.gz: 9203bd871fed1d37968af3a7fec28b806ea1b71031fce35ede41046fa6b3ee8ece3ab437af56e9e26c5122ecb008a0d8492042a076e9b0c3b2bcd5e7364dca25
7
+ data.tar.gz: 7992a01650e089989aefbf02763b3148109dc236651dec8d42d9018bba525296c9ab655e34cdfe65f7c7bd20cf4c470dd2127dd3715ae565d174056c3597366c
data/README.md CHANGED
@@ -11,8 +11,12 @@ This is one of four language bindings published from the same Rust core. See the
11
11
  - [Installation](#installation)
12
12
  - [Quick Start](#quick-start)
13
13
  - [Configuration](#configuration)
14
+ - [Option A — Pass config directly](#option-a--pass-config-directly)
15
+ - [Option B — Load from environment (`from_env()`)](#option-b--load-from-environment-from_env)
16
+ - [Custom headers and `User-Agent`](#custom-headers-and-user-agent)
14
17
  - [Platform Support](#platform-support)
15
18
  - [API Reference](#api-reference)
19
+ - [Language conventions](#language-conventions)
16
20
  - [Admin Client](#admin-client)
17
21
  - [Endpoints](#endpoints)
18
22
  - [Endpoint Tags](#endpoint-tags)
@@ -38,6 +42,7 @@ This is one of four language bindings published from the same Rust core. See the
38
42
  - [Billing](#billing)
39
43
  - [Bulk Operations](#bulk-operations)
40
44
  - [Account Tags](#account-tags)
45
+ - [Tag / delete method parameter quick-reference](#tag--delete-method-parameter-quick-reference)
41
46
  - [Streams Client](#streams-client)
42
47
  - [Datasets, Regions, and Destinations](#datasets-regions-and-destinations)
43
48
  - [Streams methods](#streams-methods)
@@ -48,6 +53,12 @@ This is one of four language bindings published from the same Rust core. See the
48
53
  - [Sets](#sets)
49
54
  - [Lists](#lists)
50
55
  - [SQL Client](#sql-client)
56
+ - [RPC & Tooling Access](#rpc--tooling-access)
57
+ - [Crypto-micropayment lane (`rpc.call`)](#crypto-micropayment-lane-rpccall)
58
+ - [Wallet generation](#wallet-generation)
59
+ - [x402 credit drawdown (authenticate once, then draw one credit per call)](#x402-credit-drawdown-authenticate-once-then-draw-one-credit-per-call)
60
+ - [Testnet faucet](#testnet-faucet)
61
+ - [MPP payment channel (deposit once, then vouchers)](#mpp-payment-channel-deposit-once-then-vouchers)
51
62
  - [Error Handling](#error-handling)
52
63
  - [License](#license)
53
64
 
@@ -76,6 +87,10 @@ There are two ways to configure the SDK.
76
87
 
77
88
  ```ruby
78
89
  qn = QuicknodeSdk::SDK.from_config(api_key: "your-key")
90
+
91
+ # api_key is optional: the crypto-micropayment lane pays per request instead, so
92
+ # api_key: nil builds a usable SDK. Every other client still needs one, and
93
+ # from_env always requires QN_SDK__API_KEY.
79
94
  ```
80
95
 
81
96
  ### Option B — Load from environment (`from_env()`)
@@ -1730,6 +1745,169 @@ re-seed it via the `rpc: { seed: ... }` config key; `refresh_margin_secs` (defau
1730
1745
  tunes how early the token is refreshed. Set `rpc: { endpoint_url: ... }` to route every
1731
1746
  call to a custom HTTP URL by default (no JWT minted); a per-call `endpoint_url` overrides it.
1732
1747
 
1748
+ ## Crypto-micropayment lane (`rpc.call`)
1749
+
1750
+ Pay per RPC request with a stablecoin instead of a provisioned account + API key,
1751
+ against Quicknode's `x402.quicknode.com` and `mpp.quicknode.com` gateways. Configure
1752
+ it by setting `payment` on the RPC config; the SDK runs the `402` → sign → resend
1753
+ handshake for you. An API key is **not** required for this lane — build a keyless SDK.
1754
+
1755
+ There are four payment paths. Two pay per request; two amortize one signature over many
1756
+ calls.
1757
+
1758
+ | Path | Entry point | Gateway | Signs |
1759
+ |---|---|---|---|
1760
+ | Per-request x402 | `call` / `call_with_receipt` with `scheme: "x402"` | x402 | once per call |
1761
+ | Per-request MPP charge | `call` / `call_with_receipt` with `scheme: "mpp"` | mpp | once per call |
1762
+ | [x402 credit drawdown](#x402-credit-drawdown-authenticate-once-then-draw-one-credit-per-call) | `gateway_authenticate` → `gateway_drawdown_call` | x402 | once per session |
1763
+ | [MPP payment channel](#mpp-payment-channel-deposit-once-then-vouchers) | `mpp_open` → `mpp_session_call` | mpp | once per channel |
1764
+
1765
+ The signer construction is derived from the scheme and pay network, never stated directly:
1766
+ **x402/EVM** signs an EIP-712 `TransferWithAuthorization`, **x402/Solana** an SPL
1767
+ `TransferChecked` in a v0 tx (the gateway sponsors gas), and **MPP/Tempo** a native Tempo
1768
+ transaction.
1769
+
1770
+ `scheme` selects the gateway for `call` only. The `gateway_*` drawdown methods always use
1771
+ the x402 gateway and the `mpp_*` channel methods always use the MPP gateway, whatever
1772
+ `scheme` is set to.
1773
+
1774
+ `PaymentConfig` fields:
1775
+
1776
+ | Field | Meaning |
1777
+ |---|---|
1778
+ | `scheme` | `"x402"` (pay-per-request) or `"mpp"` (MPP charge; `"mpp-charge"` is accepted too) |
1779
+ | `key` | raw private key — EVM/Tempo: hex; Solana: base58 64-byte secret |
1780
+ | `pay_network` | CAIP-2 pay network, e.g. `eip155:84532`, `solana:5eykt4…` |
1781
+ | `asset` | token address/mint to pay in (matches the offered menu entry) |
1782
+ | `max_amount` | **required** spend ceiling in integer base units of `asset` |
1783
+ | `svm_rpc_url` | optional Solana RPC for x402/Solana payment-build reads (mint + blockhash) |
1784
+ | `base_url_override` | optional gateway base (testing) |
1785
+
1786
+ `network` on the call is the **query** chain (gateway path slug), independent of the
1787
+ pay network. Use `call_with_receipt` to also get the settlement receipt (`reference` =
1788
+ settlement tx hash) — populated on the MPP lane, `null`/`None`/`nil` for x402.
1789
+
1790
+ **Things to know:**
1791
+
1792
+ - **Do not log your own `PaymentConfig`** — the `key` field is readable. The SDK
1793
+ never prints it in its own errors/`Debug`, but a plain `p config` will show it.
1794
+ - **`max_amount` is integer base units of the selected asset.** The SDK skips any offered
1795
+ entry above it and refuses to sign one — a guard against an overcharging gateway.
1796
+ - **`PaymentIndeterminateError` means the paid request was sent but the response was lost.**
1797
+ You MAY have been charged — do **not** blindly retry.
1798
+ - **x402/Solana: one payment per call.** Building a payment reads the mint and a recent
1799
+ blockhash from a Solana RPC. The default is a public RPC that **rate-limits
1800
+ aggressively** — set `svm_rpc_url` to your own endpoint at any volume.
1801
+
1802
+ ```ruby
1803
+ sdk = QuicknodeSdk::SDK.from_config(
1804
+ api_key: nil,
1805
+ rpc: {
1806
+ payment: {
1807
+ scheme: "x402",
1808
+ key: ENV.fetch("QN_PAYMENT_KEY"),
1809
+ pay_network: "eip155:84532",
1810
+ asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
1811
+ max_amount: "10000"
1812
+ }
1813
+ }
1814
+ )
1815
+ resp = sdk.rpc.call_with_receipt(method: "eth_blockNumber", params: [], network: "base-sepolia")
1816
+ puts resp["result"]
1817
+ ```
1818
+
1819
+ ### Wallet generation
1820
+
1821
+ `QuicknodeSdk.generate_payment_wallet(chain: "evm")` creates a fresh keypair offline — no network call, no funds — for
1822
+ `"evm"`, `"svm"`, or `"tempo"`. The private key is returned **exactly once**, at
1823
+ generation; nothing in the SDK stores or re-derives it, so persist it immediately.
1824
+
1825
+ ```ruby
1826
+ wallet = QuicknodeSdk.generate_payment_wallet(chain: "evm")
1827
+ puts "fund this address: #{wallet[:address]}"
1828
+ File.write("payment.key", wallet[:key]) # returned exactly once
1829
+ ```
1830
+
1831
+ ### x402 credit drawdown (authenticate once, then draw one credit per call)
1832
+
1833
+ Cheaper per call than paying per request: one SIWE signature mints a session JWT, then
1834
+ each call draws a single credit from the account balance instead of signing a fresh
1835
+ settlement. Minting the session is free and moves no funds, so a host can re-authenticate
1836
+ transparently. Persist it between processes.
1837
+
1838
+ Fund the payment wallet out of band — the testnet faucet below, or by sending funds to
1839
+ `payment_address` directly. Credits are provisioned against the account gateway-side.
1840
+
1841
+ EVM signers only: SIWE is an EIP-4361 construction, so an x402/Solana key errors here.
1842
+
1843
+ | Method | Cost | Returns |
1844
+ |---|---|---|
1845
+ | `payment_address` | free, offline | the wallet address derived from the key |
1846
+ | `gateway_authenticate` | free | a Hash `{token:, exp_unix:, account_id:}` |
1847
+ | `gateway_credits(session:)` | free | a Hash `{account_id:, credits:}` |
1848
+ | `gateway_drip(session:)` | free (testnet) | a Hash `{account_id:, transaction_hash:}` |
1849
+ | `gateway_drawdown_call(method:, session:, network:, params:)` | 1 credit | the JSON-RPC `result` |
1850
+
1851
+ ```ruby
1852
+ session = sdk.rpc.gateway_authenticate
1853
+ balance = sdk.rpc.gateway_credits(session: session)
1854
+ puts "credits: #{balance[:credits]}"
1855
+ result = sdk.rpc.gateway_drawdown_call(
1856
+ method: "eth_blockNumber", session: session, network: "base-sepolia"
1857
+ )
1858
+ ```
1859
+
1860
+ A `token_expired` surfaces as an `ApiError` with status 401/403; re-authenticate and retry
1861
+ that call.
1862
+
1863
+ #### Testnet faucet
1864
+
1865
+ `gateway_drip` requests testnet tokens for the payment **wallet** on Base Sepolia. The
1866
+ gateway allows one drip per account, and it returns the on-chain funding transaction hash
1867
+ — not a credit balance.
1868
+
1869
+ ### MPP payment channel (deposit once, then vouchers)
1870
+
1871
+ Open a payment channel by depositing into the escrow, then authorize each call with a
1872
+ cumulative voucher — one `ecrecover` server-side, no on-chain transaction per call.
1873
+
1874
+ | Method | Cost | Returns |
1875
+ |---|---|---|
1876
+ | `mpp_open(deposit:)` | **moves funds** | the channel state Hash |
1877
+ | `mpp_top_up(channel:, additional_deposit:)` | **moves funds** | the updated channel state |
1878
+ | `mpp_status(channel:)` | **1 request unit** | a Hash `{channel_id:, accepted_cumulative:, spent:}` |
1879
+ | `mpp_session_call(method:, network:, channel:, new_cumulative:, params:)` | 1 request unit | the JSON-RPC `result` |
1880
+ | `mpp_close(channel:)` | settles on-chain | nothing — refunds the unused deposit |
1881
+
1882
+ ```ruby
1883
+ channel = sdk.rpc.mpp_open(deposit: "1000000") # persist this Hash
1884
+ new_total = (channel[:cumulative_spent].to_i + channel[:per_call].to_i).to_s
1885
+ result = sdk.rpc.mpp_session_call(
1886
+ method: "eth_blockNumber", network: "base-sepolia",
1887
+ channel: channel, new_cumulative: new_total
1888
+ )
1889
+ # On success, store new_total as the channel's cumulative_spent.
1890
+ ```
1891
+
1892
+ **Things to know:**
1893
+
1894
+ - **Persist the channel state.** The gateway exposes no read-only channel endpoint, so a
1895
+ lost local record means opening (and funding) a new channel.
1896
+ - **`mpp_status` is not free.** The gateway prices every session POST as a chargeable
1897
+ request and computes the balance from the *new* spend a voucher authorizes, so the
1898
+ probe advances `cumulative_spent` by `per_call` exactly like a call. Re-persist the
1899
+ advanced total. It raises `PaymentUnsupportedError` before any network I/O when the
1900
+ channel has no room left for the probe.
1901
+ - **The lifecycle takes no query network.** A channel is scoped by the configured pay
1902
+ network and asset, so one channel funds calls to every supported network. Only
1903
+ `mpp_session_call` takes a network, because it routes an RPC method.
1904
+ - **Amounts are decimal strings, not numbers.** They are `u128` in the core; magnus has no `u128` conversion, so pass and store them as Strings.
1905
+ - **Advance `cumulative_spent` only after a success.** A voucher authorizes the running total
1906
+ *after* the call; re-presenting the current high-water mark authorizes zero and is
1907
+ always refused with `insufficient-balance`.
1908
+
1909
+
1910
+
1733
1911
  ## Error Handling
1734
1912
 
1735
1913
  Every binding exposes a typed exception hierarchy derived from the core `SdkError`
@@ -1746,8 +1924,12 @@ subclass to branch on transport vs. API semantics.
1746
1924
  | `ApiError` | non-2xx HTTP response | `status`, `body` |
1747
1925
  | `DecodeError` | 2xx response but JSON parse failed | `body` |
1748
1926
  | `RpcError` | JSON-RPC call returned an `error` member | `code`, `message` |
1927
+ | `PaymentError` | base class for the crypto-micropayment lane | — |
1928
+ | `PaymentUnsupportedError` | no offered payment option matched your selector (or all were over `max_amount`/unsupported) | — |
1929
+ | `PaymentRejectedError` | the gateway rejected a signed payment (terminal, one resend only) | `status`, `body` |
1930
+ | `PaymentIndeterminateError` | paid request sent but response lost — MAY have been charged; do NOT blindly retry | — |
1749
1931
 
1750
- Class names: `QuicknodeSdk::Error`, `QuicknodeSdk::ConfigError`, `QuicknodeSdk::HttpError`, `QuicknodeSdk::TimeoutError`, `QuicknodeSdk::ConnectionError`, `QuicknodeSdk::ApiError`, `QuicknodeSdk::DecodeError`, `QuicknodeSdk::RpcError`. All extend `StandardError`. Hash-key validation still raises `ArgumentError`.
1932
+ Class names: `QuicknodeSdk::Error`, `QuicknodeSdk::ConfigError`, `QuicknodeSdk::HttpError`, `QuicknodeSdk::TimeoutError`, `QuicknodeSdk::ConnectionError`, `QuicknodeSdk::ApiError`, `QuicknodeSdk::DecodeError`, `QuicknodeSdk::RpcError`, `QuicknodeSdk::PaymentError`, `QuicknodeSdk::PaymentUnsupportedError`, `QuicknodeSdk::PaymentRejectedError`, `QuicknodeSdk::PaymentIndeterminateError`. All extend `StandardError`. Hash-key validation still raises `ArgumentError`.
1751
1933
 
1752
1934
  ```ruby
1753
1935
  # Ruby
data/lib/quicknode_sdk.rb CHANGED
@@ -16,3 +16,15 @@ require_relative "quicknode_sdk/clients/kvstore"
16
16
  require_relative "quicknode_sdk/clients/sql"
17
17
  require_relative "quicknode_sdk/clients/rpc"
18
18
  require_relative "quicknode_sdk/sdk"
19
+
20
+ module QuicknodeSdk
21
+ # Generates a fresh payment keypair for :evm, :svm, or :tempo. Offline — no
22
+ # network call, no funds. Returns {address:, chain:, key:}; `key` is the raw
23
+ # private key in the format the payment config's key: accepts.
24
+ #
25
+ # The key is returned exactly once, at generation: nothing in the SDK stores
26
+ # or re-derives it, so persist it before discarding the Hash.
27
+ def self.generate_payment_wallet(**opts)
28
+ wrap(Native.generate_payment_wallet(opts))
29
+ end
30
+ end
@@ -1,6 +1,11 @@
1
1
  module QuicknodeSdk
2
2
  def self.wrap: (untyped v) -> untyped
3
3
 
4
+ # Generates a fresh payment keypair for "evm", "svm", or "tempo". Offline: no
5
+ # network call. Returns {address:, chain:, key:} — `key` is the raw private
6
+ # key, returned exactly once at generation.
7
+ def self.generate_payment_wallet: (chain: String) -> untyped
8
+
4
9
  class Error < StandardError
5
10
  end
6
11
 
@@ -30,6 +35,35 @@ module QuicknodeSdk
30
35
  attr_reader message: String
31
36
  end
32
37
 
38
+ # Payment-lane errors. PaymentIndeterminateError means the paid request was
39
+ # sent but its response was lost — do not retry (may have been charged).
40
+ class PaymentError < Error
41
+ end
42
+
43
+ class PaymentUnsupportedError < PaymentError
44
+ end
45
+
46
+ class PaymentRejectedError < PaymentError
47
+ attr_reader status: Integer
48
+ attr_reader body: String
49
+ end
50
+
51
+ class PaymentIndeterminateError < PaymentError
52
+ end
53
+
54
+ # The `rpc: {payment: {...}}` sub-hash accepted by SDK.from_config. An alias
55
+ # rather than a class because the config crosses the boundary as a plain
56
+ # Hash, not a wrapper object.
57
+ type payment_config = {
58
+ scheme: String,
59
+ key: String,
60
+ pay_network: String,
61
+ asset: String,
62
+ max_amount: String,
63
+ ?svm_rpc_url: String,
64
+ ?base_url_override: String
65
+ }
66
+
33
67
  class SDK
34
68
  def self.from_env: () -> SDK
35
69
  def self.from_config: (Hash[Symbol | String, untyped] opts) -> SDK
@@ -183,8 +217,25 @@ module QuicknodeSdk
183
217
  def initialize: (untyped native) -> void
184
218
 
185
219
  def call: (method: String, ?params: untyped, ?network: String, ?endpoint_url: String) -> untyped
220
+ def call_with_receipt: (method: String, ?params: untyped, ?network: String, ?endpoint_url: String) -> untyped
186
221
  def set_networks: (networks: Hash[String, String]) -> void
187
222
  def clear_cached_token: () -> void
188
223
  def current_token: () -> untyped
224
+
225
+ # Payment lanes. Base-unit amounts are decimal Strings, not Integers: they
226
+ # are u128 in the core and magnus has no u128 conversion. `session` and
227
+ # `channel` are the Hashes returned by gateway_authenticate and
228
+ # mpp_open/mpp_top_up — persist them and hand them straight back.
229
+ def payment_address: () -> String
230
+ def gateway_authenticate: () -> untyped
231
+ def gateway_credits: (session: untyped) -> untyped
232
+ def gateway_buy_credits: (session: untyped, network: String) -> untyped
233
+ def gateway_drip: (session: untyped) -> untyped
234
+ def gateway_drawdown_call: (method: String, session: untyped, network: String, ?params: untyped) -> untyped
235
+ def mpp_open: (deposit: String) -> untyped
236
+ def mpp_top_up: (channel: untyped, additional_deposit: String) -> untyped
237
+ def mpp_close: (channel: untyped) -> void
238
+ def mpp_status: (channel: untyped) -> untyped
239
+ def mpp_session_call: (method: String, network: String, channel: untyped, new_cumulative: String, ?params: untyped) -> untyped
189
240
  end
190
241
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quicknode_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Quicknode
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-10 00:00:00.000000000 Z
11
+ date: 2026-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashie