From a15924be186b3c1c53739005b6efe491905f5d55 Mon Sep 17 00:00:00 2001 From: floor-licker Date: Mon, 22 Jun 2026 21:12:05 -0400 Subject: [PATCH] fix: fail fast on invalid api credentials --- benches/comparison_benchmarks.rs | 2 +- examples/performance_benchmark.rs | 2 +- src/auth.rs | 15 ++++---- src/client.rs | 59 ++++++++++++++++++++++++++----- 4 files changed, 58 insertions(+), 20 deletions(-) diff --git a/benches/comparison_benchmarks.rs b/benches/comparison_benchmarks.rs index e53cbc2..307fac1 100644 --- a/benches/comparison_benchmarks.rs +++ b/benches/comparison_benchmarks.rs @@ -133,7 +133,7 @@ fn benchmark_order_submit_payload_auth(c: &mut Criterion) { secret: "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1".to_string(), passphrase: "benchmark-passphrase".to_string(), }; - let prepared_api_creds = PreparedApiCredentials::new(api_creds.clone()); + let prepared_api_creds = PreparedApiCredentials::try_new(api_creds.clone()).unwrap(); c.bench_function("order_submit_body_and_l2_headers", |b| { b.iter(|| { diff --git a/examples/performance_benchmark.rs b/examples/performance_benchmark.rs index e8252f7..f201100 100644 --- a/examples/performance_benchmark.rs +++ b/examples/performance_benchmark.rs @@ -128,7 +128,7 @@ async fn main() -> Result<(), Box> { // Create client with API credentials only (no private key needed for custodial trading) let mut client = ClobClient::new("https://clob.polymarket.com"); - client.set_api_creds(api_creds); + client.set_api_creds(api_creds)?; println!("✅ Client configured for custodial API trading"); diff --git a/src/auth.rs b/src/auth.rs index 6917ebb..67c71d6 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -38,20 +38,20 @@ pub trait HmacApiCredentials { #[derive(Debug, Clone)] pub struct PreparedApiCredentials { credentials: ApiCredentials, - decoded_secret: std::result::Result, String>, + decoded_secret: Arc<[u8]>, } impl PreparedApiCredentials { - pub fn new(credentials: ApiCredentials) -> Self { + pub fn try_new(credentials: ApiCredentials) -> Result { let decoded_secret = base64::engine::general_purpose::URL_SAFE .decode(&credentials.secret) .map(Into::into) - .map_err(|e| format!("Failed to decode base64 secret: {}", e)); + .map_err(|e| PolyfillError::crypto(format!("Failed to decode base64 secret: {e}")))?; - Self { + Ok(Self { credentials, decoded_secret, - } + }) } pub fn credentials(&self) -> &ApiCredentials { @@ -91,10 +91,7 @@ impl HmacApiCredentials for PreparedApiCredentials { } fn decoded_secret_bytes(&self) -> Result> { - match &self.decoded_secret { - Ok(decoded_secret) => Ok(Cow::Borrowed(decoded_secret.as_ref())), - Err(err) => Err(PolyfillError::crypto(err.clone())), - } + Ok(Cow::Borrowed(self.decoded_secret.as_ref())) } } diff --git a/src/client.rs b/src/client.rs index 10c1fbc..eb65d51 100644 --- a/src/client.rs +++ b/src/client.rs @@ -124,7 +124,7 @@ pub struct ClobClient { #[derive(Default)] struct ClientAuthConfig { signer: Option, - api_creds: Option, + api_creds: Option, builder_code: Option, sig_type: Option, funder: Option
, @@ -162,14 +162,12 @@ impl ClobClient { .clone() .map(|signer| crate::orders::OrderBuilder::new(signer, auth.sig_type, auth.funder)); - let api_creds = auth.api_creds.map(PreparedApiCredentials::new); - Self { http_client, base_url: host.to_string(), chain_id, signer: auth.signer, - api_creds, + api_creds: auth.api_creds, builder_code: auth.builder_code, order_builder, connection_manager, @@ -223,7 +221,10 @@ impl ClobClient { http_client, ClientAuthConfig { signer, - api_creds: config.api_credentials, + api_creds: config + .api_credentials + .map(PreparedApiCredentials::try_new) + .transpose()?, builder_code: config.builder_code, sig_type, funder, @@ -284,8 +285,9 @@ impl ClobClient { } /// Set API credentials - pub fn set_api_creds(&mut self, api_creds: ApiCreds) { - self.api_creds = Some(PreparedApiCredentials::new(api_creds)); + pub fn set_api_creds(&mut self, api_creds: ApiCreds) -> Result<()> { + self.api_creds = Some(PreparedApiCredentials::try_new(api_creds)?); + Ok(()) } /// Start background keep-alive to maintain warm connection @@ -2637,15 +2639,54 @@ mod tests { let api_creds = ApiCredentials { api_key: "test_key".to_string(), - secret: "test_secret".to_string(), + secret: "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1".to_string(), passphrase: "test_passphrase".to_string(), }; - client.set_api_creds(api_creds.clone()); + client.set_api_creds(api_creds.clone()).unwrap(); assert!(client.api_creds.is_some()); assert_eq!(client.api_creds.unwrap().api_key, "test_key"); } + #[tokio::test(flavor = "multi_thread")] + async fn test_from_config_rejects_invalid_api_secret() { + let api_creds = ApiCredentials { + api_key: "test_key".to_string(), + secret: "not valid base64!".to_string(), + passphrase: "test_passphrase".to_string(), + }; + + let err = match ClobClient::from_config(ClientConfig { + base_url: "https://test.example.com".to_string(), + chain: 137, + private_key: Some( + "0x1234567890123456789012345678901234567890123456789012345678901234".to_string(), + ), + api_credentials: Some(api_creds), + ..ClientConfig::default() + }) { + Ok(_) => panic!("expected invalid API credentials to fail"), + Err(err) => err, + }; + + assert!(err.to_string().contains("Failed to decode base64 secret")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_set_api_creds_rejects_invalid_api_secret() { + let mut client = create_test_client("https://test.example.com"); + let api_creds = ApiCredentials { + api_key: "test_key".to_string(), + secret: "not valid base64!".to_string(), + passphrase: "test_passphrase".to_string(), + }; + + let err = client.set_api_creds(api_creds).unwrap_err(); + + assert!(client.api_creds.is_none()); + assert!(err.to_string().contains("Failed to decode base64 secret")); + } + #[tokio::test(flavor = "multi_thread")] async fn test_get_sampling_markets_success() { let mut server = Server::new_async().await;