quicknode-sdk 0.8.0__tar.gz → 0.8.1__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 (61) hide show
  1. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/Cargo.lock +5 -5
  2. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/Cargo.toml +1 -1
  3. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/PKG-INFO +5 -3
  4. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/Cargo.toml +1 -1
  5. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/README.md +4 -2
  6. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/examples/rpc_payment.rs +3 -0
  7. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/rpc/payment/drawdown.rs +167 -21
  8. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/rpc/payment/signer/mod.rs +16 -0
  9. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/rpc/payment/signer/svm.rs +7 -0
  10. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/pyproject.toml +1 -1
  11. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/python/README.md +4 -2
  12. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/LICENSE +0 -0
  13. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/LICENSE +0 -0
  14. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/examples/admin.rs +0 -0
  15. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/examples/admin_e2e.rs +0 -0
  16. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/examples/kvstore_e2e.rs +0 -0
  17. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/examples/rpc.rs +0 -0
  18. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/examples/sql.rs +0 -0
  19. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/examples/streams.rs +0 -0
  20. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/examples/streams_e2e.rs +0 -0
  21. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/examples/webhooks_e2e.rs +0 -0
  22. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/account.rs +0 -0
  23. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/api_credits.rs +0 -0
  24. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/billing.rs +0 -0
  25. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/bulk.rs +0 -0
  26. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/chains.rs +0 -0
  27. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/endpoint_metrics.rs +0 -0
  28. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/endpoint_rate_limits.rs +0 -0
  29. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/endpoint_security.rs +0 -0
  30. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/endpoint_urls.rs +0 -0
  31. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/endpoints.rs +0 -0
  32. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/logs.rs +0 -0
  33. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/mod.rs +0 -0
  34. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/tags.rs +0 -0
  35. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/teams.rs +0 -0
  36. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/tooling_access.rs +0 -0
  37. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/admin/usage.rs +0 -0
  38. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/config.rs +0 -0
  39. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/errors.rs +0 -0
  40. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/kvstore/mod.rs +0 -0
  41. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/lib.rs +0 -0
  42. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/rpc/mod.rs +0 -0
  43. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/rpc/payment/mod.rs +0 -0
  44. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/rpc/payment/session.rs +0 -0
  45. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/rpc/payment/signer/tempo.rs +0 -0
  46. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/sql/mod.rs +0 -0
  47. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/streams/mod.rs +0 -0
  48. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/streams/stream.rs +0 -0
  49. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/webhooks/mod.rs +0 -0
  50. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/core/src/webhooks/webhook.rs +0 -0
  51. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/python/Cargo.toml +0 -0
  52. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/python/src/errors.rs +0 -0
  53. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/python/src/lib.rs +0 -0
  54. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/python/src/sql.rs +0 -0
  55. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/python/src/streams_destination.rs +0 -0
  56. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/crates/python/src/webhooks_template.rs +0 -0
  57. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/python/quicknode_sdk/__init__.py +0 -0
  58. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/python/quicknode_sdk/__init__.pyi +0 -0
  59. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/python/quicknode_sdk/_core/__init__.pyi +0 -0
  60. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/python/quicknode_sdk/init_manual_override.pyi +0 -0
  61. {quicknode_sdk-0.8.0 → quicknode_sdk-0.8.1}/python/quicknode_sdk/py.typed +0 -0
@@ -3327,7 +3327,7 @@ dependencies = [
3327
3327
 
3328
3328
  [[package]]
3329
3329
  name = "quicknode-sdk"
3330
- version = "0.8.0"
3330
+ version = "0.8.1"
3331
3331
  dependencies = [
3332
3332
  "alloy-consensus",
3333
3333
  "alloy-primitives",
@@ -3999,7 +3999,7 @@ dependencies = [
3999
3999
 
4000
4000
  [[package]]
4001
4001
  name = "sdk-node"
4002
- version = "0.8.0"
4002
+ version = "0.8.1"
4003
4003
  dependencies = [
4004
4004
  "napi",
4005
4005
  "napi-build",
@@ -4011,7 +4011,7 @@ dependencies = [
4011
4011
 
4012
4012
  [[package]]
4013
4013
  name = "sdk-python"
4014
- version = "0.8.0"
4014
+ version = "0.8.1"
4015
4015
  dependencies = [
4016
4016
  "pyo3",
4017
4017
  "pyo3-async-runtimes",
@@ -4023,7 +4023,7 @@ dependencies = [
4023
4023
 
4024
4024
  [[package]]
4025
4025
  name = "sdk-python-stubs"
4026
- version = "0.8.0"
4026
+ version = "0.8.1"
4027
4027
  dependencies = [
4028
4028
  "pyo3-stub-gen",
4029
4029
  "sdk-python",
@@ -4031,7 +4031,7 @@ dependencies = [
4031
4031
 
4032
4032
  [[package]]
4033
4033
  name = "sdk-ruby"
4034
- version = "0.8.0"
4034
+ version = "0.8.1"
4035
4035
  dependencies = [
4036
4036
  "magnus",
4037
4037
  "quicknode-sdk",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/core", "crates/python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.8.0"
6
+ version = "0.8.1"
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.8.0
3
+ Version: 0.8.1
4
4
  Classifier: Intended Audience :: Developers
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Operating System :: POSIX :: Linux
@@ -1846,7 +1846,7 @@ open("payment.key", "w").write(wallet["key"]) # returned exactly once
1846
1846
 
1847
1847
  ### x402 credit drawdown (authenticate once, then draw one credit per call)
1848
1848
 
1849
- Cheaper per call than paying per request: one SIWE signature mints a session JWT, then
1849
+ Cheaper per call than paying per request: one SIWE or SIWS signature mints a session JWT, then
1850
1850
  each call draws a single credit from the account balance instead of signing a fresh
1851
1851
  settlement. Minting the session is free and moves no funds, so a host can re-authenticate
1852
1852
  transparently. Persist it between processes.
@@ -1854,7 +1854,9 @@ transparently. Persist it between processes.
1854
1854
  Fund the payment wallet out of band — the testnet faucet below, or by sending funds to
1855
1855
  `payment_address()` directly. Credits are provisioned against the account gateway-side.
1856
1856
 
1857
- EVM signers only: SIWE is an EIP-4361 construction, so an x402/Solana key errors here.
1857
+ EVM payment networks use SIWE. Solana payment networks use SIWS with an Ed25519
1858
+ signature encoded as Base58. Solana wallets must be funded out of band; the faucet
1859
+ is available for Base Sepolia only.
1858
1860
 
1859
1861
  | Method | Cost | Returns |
1860
1862
  |---|---|---|
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "quicknode-sdk"
3
- version = "0.8.0"
3
+ version = "0.8.1"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  description = "Core library for quicknode sdk"
@@ -1945,7 +1945,7 @@ std::fs::write("payment.key", wallet.into_key())?; // consuming: a deliberate,
1945
1945
 
1946
1946
  ### x402 credit drawdown (authenticate once, then draw one credit per call)
1947
1947
 
1948
- Cheaper per call than paying per request: one SIWE signature mints a session JWT, then
1948
+ Cheaper per call than paying per request: one SIWE or SIWS signature mints a session JWT, then
1949
1949
  each call draws a single credit from the account balance instead of signing a fresh
1950
1950
  settlement. Minting the JWT is free and moves no funds, so a host can re-authenticate
1951
1951
  transparently. Persist the session between processes.
@@ -1953,7 +1953,9 @@ transparently. Persist the session between processes.
1953
1953
  Fund the payment wallet out of band — the testnet faucet below, or by sending funds to
1954
1954
  `payment_address()` directly. Credits are provisioned against the account gateway-side.
1955
1955
 
1956
- EVM signers only: SIWE is an EIP-4361 construction, so an x402/Solana key errors here.
1956
+ EVM payment networks use SIWE. Solana payment networks use SIWS with an Ed25519
1957
+ signature encoded as Base58. Solana wallets must be funded out of band; the faucet
1958
+ is available for Base Sepolia only.
1957
1959
 
1958
1960
  | Method | Cost | Returns |
1959
1961
  |---|---|---|
@@ -9,6 +9,9 @@
9
9
  //! QN_PAYMENT_KEY=0x<throwaway-key> \
10
10
  //! cargo run --example rpc_payment -p quicknode-sdk \
11
11
  //! --features rust,payments,payments-svm,payments-tempo
12
+ //!
13
+ //! For Solana drawdown, use a base58 Solana key, a `solana:<genesis-hash>`
14
+ //! pay network, and the Solana USDC mint; authentication uses SIWS.
12
15
 
13
16
  use quicknode_sdk::{PaymentConfig, QuicknodeSdk, RpcConfig, SdkFullConfig};
14
17
 
@@ -7,13 +7,13 @@
7
7
  //! response — no per-call signing.
8
8
  //!
9
9
  //! The flow:
10
- //! 1. [`authenticate`] — build a SIWE (EIP-4361) message, sign it with the
10
+ //! 1. [`authenticate`] — build a SIWE or SIWS message, sign it with the
11
11
  //! payment key, POST `/auth`, and cache the returned [`GatewaySession`].
12
12
  //! 2. [`drawdown_call`] — POST `/:network` with the Bearer JWT; returns the raw
13
13
  //! JSON-RPC envelope text.
14
14
  //! 3. [`credits`] — GET `/credits` with the Bearer JWT → the current balance.
15
- //! 4. [`drip`] — POST `/drip` (testnet faucet, once per account) — funds the
16
- //! wallet, not the credit ledger.
15
+ //! 4. [`drip`] — POST `/drip` (Base Sepolia faucet, once per account) — funds
16
+ //! the wallet, not the credit ledger.
17
17
  //!
18
18
  //! [`buy_credits`] settles a credit block by signing the gateway's credit-tier
19
19
  //! offer. It is reachable only where that offer's construction is signable; see
@@ -100,31 +100,52 @@ const SIWX_STATEMENT: &str =
100
100
  /// returns a cached [`GatewaySession`]. Free — no funds move — so a caller may
101
101
  /// (re)auth transparently on a missing/expired session without user consent.
102
102
  ///
103
- /// EVM signers only (SIWE). An SVM signer errors SIWS is a separate
104
- /// construction.
103
+ /// Selects SIWE for EVM payment networks and SIWS for Solana payment networks.
105
104
  pub async fn authenticate(
106
105
  client: &reqwest::Client,
107
106
  payment: &ResolvedPayment,
108
107
  ) -> Result<GatewaySession, SdkError> {
109
108
  let base = super::PaymentScheme::X402.host_base(payment.base_url_override.as_deref());
110
- // SIWE compares the recovered address case-sensitively.
111
- let address = to_checksum_address(&payment.signer.address()?);
112
- // SIWE requires the decimal EIP-155 id, not the CAIP-2 string.
113
- let chain_id = eip155_chain_id(&payment.pay_network)?;
114
-
115
- // Build the gateway's fixed SIWE message with a fresh nonce and timestamp.
109
+ let address = payment.signer.address()?;
116
110
  let host = host_only(base);
117
111
  let nonce = hex::encode(&random_nonce()[..8]);
118
112
  let issued_at = rfc3339_now();
119
- let message = siwe_message(
120
- &host,
121
- &address,
122
- chain_id,
123
- &nonce,
124
- &issued_at,
125
- SIWX_STATEMENT,
126
- );
127
- let signature = payment.signer.sign_siwe(&message)?;
113
+ let (message, signature) = match payment.signer.kind() {
114
+ super::signer::ChainKind::Svm => {
115
+ let chain_id = solana_chain_id(&payment.pay_network)?;
116
+ let message = siws_message(
117
+ &host,
118
+ &address,
119
+ chain_id,
120
+ &nonce,
121
+ &issued_at,
122
+ SIWX_STATEMENT,
123
+ );
124
+ let signature = payment.signer.sign_siws(&message)?;
125
+ (message, signature)
126
+ }
127
+ super::signer::ChainKind::Evm => {
128
+ // SIWE compares the recovered address case-sensitively and uses
129
+ // the decimal EIP-155 id rather than the CAIP-2 string.
130
+ let address = to_checksum_address(&address);
131
+ let chain_id = eip155_chain_id(&payment.pay_network)?;
132
+ let message = siwe_message(
133
+ &host,
134
+ &address,
135
+ chain_id,
136
+ &nonce,
137
+ &issued_at,
138
+ SIWX_STATEMENT,
139
+ );
140
+ let signature = payment.signer.sign_siwe(&message)?;
141
+ (message, signature)
142
+ }
143
+ super::signer::ChainKind::Tempo => {
144
+ return Err(SdkError::Config(
145
+ "x402 drawdown requires an EVM or Solana signer".into(),
146
+ ));
147
+ }
148
+ };
128
149
 
129
150
  let url = format!("{}/auth", base.trim_end_matches('/'));
130
151
  let resp = client
@@ -224,7 +245,7 @@ pub struct DripReceipt {
224
245
 
225
246
  /// Requests testnet tokens from the faucet (POST `/drip`, Bearer JWT). The
226
247
  /// gateway allows this once per account on Base Sepolia and returns the funding
227
- /// transaction (NOT a balance).
248
+ /// transaction (NOT a balance). Solana wallets must be funded out of band.
228
249
  pub async fn drip(
229
250
  client: &reqwest::Client,
230
251
  payment: &ResolvedPayment,
@@ -367,6 +388,29 @@ pub(super) fn siwe_message(
367
388
  )
368
389
  }
369
390
 
391
+ /// Build the CAIP-122 Sign-In-With-Solana message expected by the gateway.
392
+ pub(super) fn siws_message(
393
+ host: &str,
394
+ address: &str,
395
+ chain_id: &str,
396
+ nonce: &str,
397
+ issued_at: &str,
398
+ statement: &str,
399
+ ) -> String {
400
+ format!(
401
+ "{host} wants you to sign in with your Solana account:\n\
402
+ {address}\n\
403
+ \n\
404
+ {statement}\n\
405
+ \n\
406
+ URI: https://{host}\n\
407
+ Version: 1\n\
408
+ Chain ID: {chain_id}\n\
409
+ Nonce: {nonce}\n\
410
+ Issued At: {issued_at}"
411
+ )
412
+ }
413
+
370
414
  // Apply the EIP-55 checksum required by SIWE.
371
415
  fn to_checksum_address(addr: &str) -> String {
372
416
  use sha3::{Digest, Keccak256};
@@ -403,6 +447,18 @@ fn eip155_chain_id(pay_network: &str) -> Result<u64, SdkError> {
403
447
  SdkError::Config(format!(
404
448
  "x402 drawdown requires an eip155 pay network (e.g. eip155:84532), got {pay_network:?}"
405
449
  ))
450
+ })
451
+ }
452
+
453
+ // SIWS displays the Solana genesis hash without the CAIP-2 namespace prefix.
454
+ fn solana_chain_id(pay_network: &str) -> Result<&str, SdkError> {
455
+ pay_network
456
+ .strip_prefix("solana:")
457
+ .filter(|chain_id| !chain_id.is_empty())
458
+ .ok_or_else(|| {
459
+ SdkError::Config(format!(
460
+ "x402 Solana drawdown requires a solana pay network, got {pay_network:?}"
461
+ ))
406
462
  })
407
463
  }
408
464
 
@@ -466,6 +522,28 @@ mod tests {
466
522
  }
467
523
  }
468
524
 
525
+ #[cfg(feature = "payments-svm")]
526
+ fn svm_payment(base: &str) -> ResolvedPayment {
527
+ use ed25519_dalek::SigningKey;
528
+
529
+ let seed = [7u8; 32];
530
+ let signing_key = SigningKey::from_bytes(&seed);
531
+ let mut secret = Vec::with_capacity(64);
532
+ secret.extend_from_slice(&seed);
533
+ secret.extend_from_slice(&signing_key.verifying_key().to_bytes());
534
+ ResolvedPayment {
535
+ scheme: super::super::PaymentScheme::X402,
536
+ signer: super::super::signer::Signer::Svm(SecretString::new(
537
+ bs58::encode(secret).into_string(),
538
+ )),
539
+ pay_network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1".into(),
540
+ asset: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU".into(),
541
+ max_amount: 10_000_000,
542
+ base_url_override: Some(base.to_string()),
543
+ svm_rpc_url: None,
544
+ }
545
+ }
546
+
469
547
  fn x402_credit_offer(amount: &str) -> Value {
470
548
  json!({
471
549
  "x402Version": 2,
@@ -504,6 +582,30 @@ mod tests {
504
582
  assert_eq!(msg, expected);
505
583
  }
506
584
 
585
+ #[cfg(feature = "payments-svm")]
586
+ #[test]
587
+ fn siws_message_is_byte_exact() {
588
+ let msg = siws_message(
589
+ "x402.quicknode.com",
590
+ "11111111111111111111111111111111",
591
+ "EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
592
+ "abc12345",
593
+ "2026-07-17T12:00:00Z",
594
+ SIWX_STATEMENT,
595
+ );
596
+ let expected = "x402.quicknode.com wants you to sign in with your Solana account:\n\
597
+ 11111111111111111111111111111111\n\
598
+ \n\
599
+ I accept the Quicknode Terms of Service: https://www.quicknode.com/terms\n\
600
+ \n\
601
+ URI: https://x402.quicknode.com\n\
602
+ Version: 1\n\
603
+ Chain ID: EtWTRABZaYq6iMfeYKouRu166VU2xqa1\n\
604
+ Nonce: abc12345\n\
605
+ Issued At: 2026-07-17T12:00:00Z";
606
+ assert_eq!(msg, expected);
607
+ }
608
+
507
609
  // Verify the generated timestamp uses the gateway's millisecond format.
508
610
  #[test]
509
611
  fn issued_at_carries_millisecond_precision() {
@@ -609,6 +711,50 @@ mod tests {
609
711
  assert!(session.is_fresh(60));
610
712
  }
611
713
 
714
+ #[cfg(feature = "payments-svm")]
715
+ #[tokio::test]
716
+ async fn authenticate_posts_solana_siwx_with_base58_signature() {
717
+ use ed25519_dalek::{Signature, SigningKey, Verifier};
718
+
719
+ struct AuthResponder;
720
+ impl Respond for AuthResponder {
721
+ fn respond(&self, req: &Request) -> ResponseTemplate {
722
+ let body: Value = serde_json::from_slice(&req.body).unwrap();
723
+ assert_eq!(body["type"], "siwx");
724
+ let message = body["message"].as_str().unwrap();
725
+ assert!(message.contains("sign in with your Solana account"));
726
+ assert!(message.contains("Chain ID: EtWTRABZaYq6iMfeYKouRu166VU2xqa1"));
727
+
728
+ let signature = bs58::decode(body["signature"].as_str().unwrap())
729
+ .into_vec()
730
+ .unwrap();
731
+ let signature: [u8; 64] = signature.try_into().unwrap();
732
+ let public_key = SigningKey::from_bytes(&[7u8; 32]).verifying_key();
733
+ public_key
734
+ .verify(message.as_bytes(), &Signature::from_bytes(&signature))
735
+ .unwrap();
736
+
737
+ ResponseTemplate::new(200).set_body_json(json!({
738
+ "token": "jwt-solana",
739
+ "expiresAt": "2099-01-01T00:00:00Z",
740
+ "accountId": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1:11111111111111111111111111111111"
741
+ }))
742
+ }
743
+ }
744
+
745
+ let server = MockServer::start().await;
746
+ Mock::given(method("POST"))
747
+ .and(path("/auth"))
748
+ .respond_with(AuthResponder)
749
+ .mount(&server)
750
+ .await;
751
+
752
+ let payment = svm_payment(&server.uri());
753
+ let client = reqwest::Client::new();
754
+ let session = authenticate(&client, &payment).await.unwrap();
755
+ assert_eq!(session.token, "jwt-solana");
756
+ }
757
+
612
758
  #[tokio::test]
613
759
  async fn authenticate_error_surfaces_as_api() {
614
760
  let server = MockServer::start().await;
@@ -205,6 +205,22 @@ impl Signer {
205
205
  }
206
206
  }
207
207
 
208
+ /// Sign a Sign-In-With-Solana (CAIP-122) message with Ed25519 and return
209
+ /// the Base58 signature. Solana only; EVM signers use [`Self::sign_siwe`].
210
+ pub fn sign_siws(&self, _message: &str) -> Result<String, SdkError> {
211
+ match self {
212
+ #[cfg(feature = "payments-svm")]
213
+ Signer::Svm(_) => svm::sign_siws(self, _message),
214
+ Signer::Evm(_) | Signer::Tempo(_) => Err(SdkError::Config(
215
+ "SIWS signing requires an SVM signer".into(),
216
+ )),
217
+ #[cfg(not(feature = "payments-svm"))]
218
+ Signer::Svm(_) => Err(SdkError::Config(
219
+ "SIWS signing requires the `payments-svm` feature".into(),
220
+ )),
221
+ }
222
+ }
223
+
208
224
  /// Sign an MPP session voucher (`Voucher(bytes32 channelId,uint128
209
225
  /// cumulativeAmount)`) against the legacy escrow contract's EIP-712 domain
210
226
  /// ("Tempo Stream Channel"), returning the `0x`-prefixed 65-byte `r||s||v`
@@ -194,6 +194,13 @@ impl Signer {
194
194
  }
195
195
  }
196
196
 
197
+ /// Sign a CAIP-122 SIWS message and return the Base58 Ed25519 signature.
198
+ pub(super) fn sign_siws(signer: &Signer, message: &str) -> Result<String, SdkError> {
199
+ let key = svm_signing_key(signer)?;
200
+ let signature = key.sign(message.as_bytes());
201
+ Ok(bs58::encode(signature.to_bytes()).into_string())
202
+ }
203
+
197
204
  /// A v0 message's account-permission counts.
198
205
  struct MessageHeader {
199
206
  num_required_signatures: u8,
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "quicknode-sdk"
7
- version = "0.8.0"
7
+ version = "0.8.1"
8
8
  description = "Quicknode SDK"
9
9
  readme = "python/README.md"
10
10
  requires-python = ">=3.11"
@@ -1822,7 +1822,7 @@ open("payment.key", "w").write(wallet["key"]) # returned exactly once
1822
1822
 
1823
1823
  ### x402 credit drawdown (authenticate once, then draw one credit per call)
1824
1824
 
1825
- Cheaper per call than paying per request: one SIWE signature mints a session JWT, then
1825
+ Cheaper per call than paying per request: one SIWE or SIWS signature mints a session JWT, then
1826
1826
  each call draws a single credit from the account balance instead of signing a fresh
1827
1827
  settlement. Minting the session is free and moves no funds, so a host can re-authenticate
1828
1828
  transparently. Persist it between processes.
@@ -1830,7 +1830,9 @@ transparently. Persist it between processes.
1830
1830
  Fund the payment wallet out of band — the testnet faucet below, or by sending funds to
1831
1831
  `payment_address()` directly. Credits are provisioned against the account gateway-side.
1832
1832
 
1833
- EVM signers only: SIWE is an EIP-4361 construction, so an x402/Solana key errors here.
1833
+ EVM payment networks use SIWE. Solana payment networks use SIWS with an Ed25519
1834
+ signature encoded as Base58. Solana wallets must be funded out of band; the faucet
1835
+ is available for Base Sepolia only.
1834
1836
 
1835
1837
  | Method | Cost | Returns |
1836
1838
  |---|---|---|
File without changes