mirror of
https://github.com/floor-licker/polyfill-rs.git
synced 2026-08-16 22:18:07 +00:00
Merge pull request #70 from floor-licker/fix/fail-fast-api-credentials
fix: fail fast on invalid api credentials
This commit is contained in:
@@ -133,7 +133,7 @@ fn benchmark_order_submit_payload_auth(c: &mut Criterion) {
|
|||||||
secret: "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1".to_string(),
|
secret: "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1".to_string(),
|
||||||
passphrase: "benchmark-passphrase".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| {
|
c.bench_function("order_submit_body_and_l2_headers", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
// Create client with API credentials only (no private key needed for custodial trading)
|
// Create client with API credentials only (no private key needed for custodial trading)
|
||||||
let mut client = ClobClient::new("https://clob.polymarket.com");
|
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");
|
println!("✅ Client configured for custodial API trading");
|
||||||
|
|
||||||
|
|||||||
+6
-9
@@ -38,20 +38,20 @@ pub trait HmacApiCredentials {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PreparedApiCredentials {
|
pub struct PreparedApiCredentials {
|
||||||
credentials: ApiCredentials,
|
credentials: ApiCredentials,
|
||||||
decoded_secret: std::result::Result<Arc<[u8]>, String>,
|
decoded_secret: Arc<[u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreparedApiCredentials {
|
impl PreparedApiCredentials {
|
||||||
pub fn new(credentials: ApiCredentials) -> Self {
|
pub fn try_new(credentials: ApiCredentials) -> Result<Self> {
|
||||||
let decoded_secret = base64::engine::general_purpose::URL_SAFE
|
let decoded_secret = base64::engine::general_purpose::URL_SAFE
|
||||||
.decode(&credentials.secret)
|
.decode(&credentials.secret)
|
||||||
.map(Into::into)
|
.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,
|
credentials,
|
||||||
decoded_secret,
|
decoded_secret,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn credentials(&self) -> &ApiCredentials {
|
pub fn credentials(&self) -> &ApiCredentials {
|
||||||
@@ -91,10 +91,7 @@ impl HmacApiCredentials for PreparedApiCredentials {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn decoded_secret_bytes(&self) -> Result<Cow<'_, [u8]>> {
|
fn decoded_secret_bytes(&self) -> Result<Cow<'_, [u8]>> {
|
||||||
match &self.decoded_secret {
|
Ok(Cow::Borrowed(self.decoded_secret.as_ref()))
|
||||||
Ok(decoded_secret) => Ok(Cow::Borrowed(decoded_secret.as_ref())),
|
|
||||||
Err(err) => Err(PolyfillError::crypto(err.clone())),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+50
-9
@@ -124,7 +124,7 @@ pub struct ClobClient {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct ClientAuthConfig {
|
struct ClientAuthConfig {
|
||||||
signer: Option<PrivateKeySigner>,
|
signer: Option<PrivateKeySigner>,
|
||||||
api_creds: Option<ApiCreds>,
|
api_creds: Option<PreparedApiCredentials>,
|
||||||
builder_code: Option<String>,
|
builder_code: Option<String>,
|
||||||
sig_type: Option<crate::orders::SigType>,
|
sig_type: Option<crate::orders::SigType>,
|
||||||
funder: Option<Address>,
|
funder: Option<Address>,
|
||||||
@@ -162,14 +162,12 @@ impl ClobClient {
|
|||||||
.clone()
|
.clone()
|
||||||
.map(|signer| crate::orders::OrderBuilder::new(signer, auth.sig_type, auth.funder));
|
.map(|signer| crate::orders::OrderBuilder::new(signer, auth.sig_type, auth.funder));
|
||||||
|
|
||||||
let api_creds = auth.api_creds.map(PreparedApiCredentials::new);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
http_client,
|
http_client,
|
||||||
base_url: host.to_string(),
|
base_url: host.to_string(),
|
||||||
chain_id,
|
chain_id,
|
||||||
signer: auth.signer,
|
signer: auth.signer,
|
||||||
api_creds,
|
api_creds: auth.api_creds,
|
||||||
builder_code: auth.builder_code,
|
builder_code: auth.builder_code,
|
||||||
order_builder,
|
order_builder,
|
||||||
connection_manager,
|
connection_manager,
|
||||||
@@ -223,7 +221,10 @@ impl ClobClient {
|
|||||||
http_client,
|
http_client,
|
||||||
ClientAuthConfig {
|
ClientAuthConfig {
|
||||||
signer,
|
signer,
|
||||||
api_creds: config.api_credentials,
|
api_creds: config
|
||||||
|
.api_credentials
|
||||||
|
.map(PreparedApiCredentials::try_new)
|
||||||
|
.transpose()?,
|
||||||
builder_code: config.builder_code,
|
builder_code: config.builder_code,
|
||||||
sig_type,
|
sig_type,
|
||||||
funder,
|
funder,
|
||||||
@@ -284,8 +285,9 @@ impl ClobClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set API credentials
|
/// Set API credentials
|
||||||
pub fn set_api_creds(&mut self, api_creds: ApiCreds) {
|
pub fn set_api_creds(&mut self, api_creds: ApiCreds) -> Result<()> {
|
||||||
self.api_creds = Some(PreparedApiCredentials::new(api_creds));
|
self.api_creds = Some(PreparedApiCredentials::try_new(api_creds)?);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start background keep-alive to maintain warm connection
|
/// Start background keep-alive to maintain warm connection
|
||||||
@@ -2637,15 +2639,54 @@ mod tests {
|
|||||||
|
|
||||||
let api_creds = ApiCredentials {
|
let api_creds = ApiCredentials {
|
||||||
api_key: "test_key".to_string(),
|
api_key: "test_key".to_string(),
|
||||||
secret: "test_secret".to_string(),
|
secret: "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1".to_string(),
|
||||||
passphrase: "test_passphrase".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!(client.api_creds.is_some());
|
||||||
assert_eq!(client.api_creds.unwrap().api_key, "test_key");
|
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")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_get_sampling_markets_success() {
|
async fn test_get_sampling_markets_success() {
|
||||||
let mut server = Server::new_async().await;
|
let mut server = Server::new_async().await;
|
||||||
|
|||||||
Reference in New Issue
Block a user