mirror of
https://github.com/floor-licker/polyfill-rs.git
synced 2026-08-26 10:58:07 +00:00
perf: reduce repeated order auth work
This commit is contained in:
+110
-26
@@ -14,6 +14,7 @@ use hmac::{Hmac, Mac};
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, LazyLock, RwLock};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
// Header constants
|
// Header constants
|
||||||
@@ -25,6 +26,8 @@ const POLY_API_KEY_HEADER: &str = "poly_api_key";
|
|||||||
const POLY_PASS_HEADER: &str = "poly_passphrase";
|
const POLY_PASS_HEADER: &str = "poly_passphrase";
|
||||||
|
|
||||||
type Headers = HashMap<&'static str, String>;
|
type Headers = HashMap<&'static str, String>;
|
||||||
|
static DECODED_SECRET_CACHE: LazyLock<RwLock<HashMap<String, Arc<[u8]>>>> =
|
||||||
|
LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
// EIP-712 struct for CLOB authentication
|
// EIP-712 struct for CLOB authentication
|
||||||
sol! {
|
sol! {
|
||||||
@@ -156,41 +159,78 @@ pub fn build_hmac_signature<T>(
|
|||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
// Apply inverse transformation to key material for digest initialization
|
let decoded_secret = decoded_secret_bytes(secret)?;
|
||||||
// This ensures compatibility with the expected cryptographic envelope format
|
let body_bytes =
|
||||||
let decoded_secret = base64::engine::general_purpose::URL_SAFE
|
match body {
|
||||||
.decode(secret)
|
Some(b) => Some(serde_json::to_vec(b).map_err(|e| {
|
||||||
.map_err(|e| PolyfillError::crypto(format!("Failed to decode base64 secret: {}", e)))?;
|
PolyfillError::parse(format!("Failed to serialize body: {}", e), None)
|
||||||
|
})?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize MAC with transformed key material to maintain protocol coherence
|
build_hmac_signature_bytes(
|
||||||
let mut mac = Hmac::<Sha256>::new_from_slice(&decoded_secret)
|
&decoded_secret,
|
||||||
|
timestamp,
|
||||||
|
method,
|
||||||
|
request_path,
|
||||||
|
body_bytes.as_deref(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_hmac_signature_bytes(
|
||||||
|
decoded_secret: &[u8],
|
||||||
|
timestamp: u64,
|
||||||
|
method: &str,
|
||||||
|
request_path: &str,
|
||||||
|
body_bytes: Option<&[u8]>,
|
||||||
|
) -> Result<String> {
|
||||||
|
let mut mac = Hmac::<Sha256>::new_from_slice(decoded_secret)
|
||||||
.map_err(|e| PolyfillError::crypto(format!("Invalid HMAC key: {}", e)))?;
|
.map_err(|e| PolyfillError::crypto(format!("Invalid HMAC key: {}", e)))?;
|
||||||
|
|
||||||
// Construct canonical message representation for signature verification
|
let timestamp = timestamp.to_string();
|
||||||
// Message components are concatenated in strict order to preserve cryptographic binding
|
mac.update(timestamp.as_bytes());
|
||||||
let message = format!(
|
let method_upper;
|
||||||
"{}{}{}{}",
|
let method_bytes = if method.bytes().all(|b| !b.is_ascii_lowercase()) {
|
||||||
timestamp,
|
method.as_bytes()
|
||||||
method.to_uppercase(),
|
} else {
|
||||||
request_path,
|
method_upper = method.to_ascii_uppercase();
|
||||||
match body {
|
method_upper.as_bytes()
|
||||||
Some(b) => serde_json::to_string(b).map_err(|e| PolyfillError::parse(
|
};
|
||||||
format!("Failed to serialize body: {}", e),
|
mac.update(method_bytes);
|
||||||
None
|
mac.update(request_path.as_bytes());
|
||||||
))?,
|
if let Some(body_bytes) = body_bytes {
|
||||||
None => String::new(),
|
mac.update(body_bytes);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// Compute authentication tag over canonical message form
|
|
||||||
mac.update(message.as_bytes());
|
|
||||||
let result = mac.finalize();
|
let result = mac.finalize();
|
||||||
|
|
||||||
// Apply URL-safe encoding transformation for transport layer compatibility
|
|
||||||
// This encoding scheme ensures proper signature validation across network boundaries
|
|
||||||
Ok(base64::engine::general_purpose::URL_SAFE.encode(result.into_bytes()))
|
Ok(base64::engine::general_purpose::URL_SAFE.encode(result.into_bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decoded_secret_bytes(secret: &str) -> Result<Arc<[u8]>> {
|
||||||
|
if let Some(decoded) = DECODED_SECRET_CACHE
|
||||||
|
.read()
|
||||||
|
.map_err(|_| PolyfillError::internal_simple("Decoded secret cache lock poisoned"))?
|
||||||
|
.get(secret)
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
return Ok(decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded: Arc<[u8]> = base64::engine::general_purpose::URL_SAFE
|
||||||
|
.decode(secret)
|
||||||
|
.map_err(|e| PolyfillError::crypto(format!("Failed to decode base64 secret: {}", e)))?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let mut cache = DECODED_SECRET_CACHE
|
||||||
|
.write()
|
||||||
|
.map_err(|_| PolyfillError::internal_simple("Decoded secret cache lock poisoned"))?;
|
||||||
|
Ok(cache
|
||||||
|
.entry(secret.to_string())
|
||||||
|
.or_insert_with(|| decoded.clone())
|
||||||
|
.clone())
|
||||||
|
}
|
||||||
|
|
||||||
/// Create L1 headers for authentication (using private key signature)
|
/// Create L1 headers for authentication (using private key signature)
|
||||||
///
|
///
|
||||||
/// Generates initial authentication envelope using elliptic curve cryptography
|
/// Generates initial authentication envelope using elliptic curve cryptography
|
||||||
@@ -245,6 +285,28 @@ where
|
|||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_l2_headers_with_body_bytes(
|
||||||
|
signer: &PrivateKeySigner,
|
||||||
|
api_creds: &ApiCredentials,
|
||||||
|
method: &str,
|
||||||
|
req_path: &str,
|
||||||
|
body_bytes: Option<&[u8]>,
|
||||||
|
) -> Result<Headers> {
|
||||||
|
let address = encode_prefixed(signer.address().as_slice());
|
||||||
|
let timestamp = get_current_unix_time_secs();
|
||||||
|
let decoded_secret = decoded_secret_bytes(&api_creds.secret)?;
|
||||||
|
let hmac_signature =
|
||||||
|
build_hmac_signature_bytes(&decoded_secret, timestamp, method, req_path, body_bytes)?;
|
||||||
|
|
||||||
|
Ok(HashMap::from([
|
||||||
|
(POLY_ADDR_HEADER, address),
|
||||||
|
(POLY_SIG_HEADER, hmac_signature),
|
||||||
|
(POLY_TS_HEADER, timestamp.to_string()),
|
||||||
|
(POLY_API_KEY_HEADER, api_creds.api_key.clone()),
|
||||||
|
(POLY_PASS_HEADER, api_creds.passphrase.clone()),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -296,6 +358,28 @@ mod tests {
|
|||||||
assert_eq!(sig1, sig2);
|
assert_eq!(sig1, sig2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hmac_signature_bytes_matches_serialized_body() {
|
||||||
|
let secret = "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1";
|
||||||
|
let timestamp = 1234567890;
|
||||||
|
let body = serde_json::json!({"orderID": "abc123"});
|
||||||
|
let body_bytes = serde_json::to_vec(&body).unwrap();
|
||||||
|
let decoded_secret = decoded_secret_bytes(secret).unwrap();
|
||||||
|
|
||||||
|
let object_signature =
|
||||||
|
build_hmac_signature(secret, timestamp, "delete", "/order", Some(&body)).unwrap();
|
||||||
|
let bytes_signature = build_hmac_signature_bytes(
|
||||||
|
&decoded_secret,
|
||||||
|
timestamp,
|
||||||
|
"DELETE",
|
||||||
|
"/order",
|
||||||
|
Some(&body_bytes),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(object_signature, bytes_signature);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hmac_signature_different_inputs() {
|
fn test_hmac_signature_different_inputs() {
|
||||||
let secret = "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1";
|
let secret = "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1";
|
||||||
|
|||||||
+132
-51
@@ -3,7 +3,7 @@
|
|||||||
//! This module provides a production-ready client for interacting with
|
//! This module provides a production-ready client for interacting with
|
||||||
//! Polymarket, optimized for high-frequency trading environments.
|
//! Polymarket, optimized for high-frequency trading environments.
|
||||||
|
|
||||||
use crate::auth::{create_l1_headers, create_l2_headers};
|
use crate::auth::{create_l1_headers, create_l2_headers, create_l2_headers_with_body_bytes};
|
||||||
use crate::errors::{PolyfillError, Result};
|
use crate::errors::{PolyfillError, Result};
|
||||||
use crate::http_config::{create_colocated_client, create_internet_client, prewarm_connections};
|
use crate::http_config::{create_colocated_client, create_internet_client, prewarm_connections};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
@@ -13,11 +13,12 @@ use crate::types::{
|
|||||||
};
|
};
|
||||||
use alloy_primitives::{Address, U256};
|
use alloy_primitives::{Address, U256};
|
||||||
use alloy_signer_local::PrivateKeySigner;
|
use alloy_signer_local::PrivateKeySigner;
|
||||||
use reqwest::header::HeaderName;
|
use reqwest::header::{HeaderName, CONTENT_TYPE};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use reqwest::{Method, RequestBuilder};
|
use reqwest::{Method, RequestBuilder};
|
||||||
use rust_decimal::prelude::FromPrimitive;
|
use rust_decimal::prelude::FromPrimitive;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
|
use serde::Serialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -863,6 +864,23 @@ impl ClobClient {
|
|||||||
headers.fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v))
|
headers.fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialize_json_body<T: ?Sized + Serialize>(body: &T) -> Result<Vec<u8>> {
|
||||||
|
serde_json::to_vec(body)
|
||||||
|
.map_err(|e| PolyfillError::parse(format!("Failed to serialize body: {e}"), None))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_request_with_json_bytes(
|
||||||
|
&self,
|
||||||
|
method: Method,
|
||||||
|
endpoint: &str,
|
||||||
|
headers: impl Iterator<Item = (&'static str, String)>,
|
||||||
|
body_bytes: Vec<u8>,
|
||||||
|
) -> RequestBuilder {
|
||||||
|
self.create_request_with_headers(method, endpoint, headers)
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.body(body_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get neg risk for a token
|
/// Get neg risk for a token
|
||||||
pub async fn get_neg_risk(&self, token_id: &str) -> Result<bool> {
|
pub async fn get_neg_risk(&self, token_id: &str) -> Result<bool> {
|
||||||
let response = self
|
let response = self
|
||||||
@@ -1140,11 +1158,23 @@ impl ClobClient {
|
|||||||
// Owner field must reference the credential principal identifier
|
// Owner field must reference the credential principal identifier
|
||||||
// to maintain consistency with the authentication context layer
|
// to maintain consistency with the authentication context layer
|
||||||
let body = PostOrder::new(order, api_creds.api_key.clone(), options);
|
let body = PostOrder::new(order, api_creds.api_key.clone(), options);
|
||||||
|
let body_bytes = Self::serialize_json_body(&body)?;
|
||||||
|
|
||||||
let headers = create_l2_headers(signer, api_creds, "POST", "/order", Some(&body))?;
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
let req = self.create_request_with_headers(Method::POST, "/order", headers.into_iter());
|
signer,
|
||||||
|
api_creds,
|
||||||
|
"POST",
|
||||||
|
"/order",
|
||||||
|
Some(&body_bytes),
|
||||||
|
)?;
|
||||||
|
let req = self.create_request_with_json_bytes(
|
||||||
|
Method::POST,
|
||||||
|
"/order",
|
||||||
|
headers.into_iter(),
|
||||||
|
body_bytes,
|
||||||
|
);
|
||||||
|
|
||||||
let response = req.json(&body).send().await?;
|
let response = req.send().await?;
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
let status = response.status().as_u16();
|
let status = response.status().as_u16();
|
||||||
let body = response.text().await.unwrap_or_default();
|
let body = response.text().await.unwrap_or_default();
|
||||||
@@ -1201,11 +1231,23 @@ impl ClobClient {
|
|||||||
.ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
|
.ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
|
||||||
|
|
||||||
let body = std::collections::HashMap::from([("orderID", order_id)]);
|
let body = std::collections::HashMap::from([("orderID", order_id)]);
|
||||||
|
let body_bytes = Self::serialize_json_body(&body)?;
|
||||||
|
|
||||||
let headers = create_l2_headers(signer, api_creds, "DELETE", "/order", Some(&body))?;
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
let req = self.create_request_with_headers(Method::DELETE, "/order", headers.into_iter());
|
signer,
|
||||||
|
api_creds,
|
||||||
|
"DELETE",
|
||||||
|
"/order",
|
||||||
|
Some(&body_bytes),
|
||||||
|
)?;
|
||||||
|
let req = self.create_request_with_json_bytes(
|
||||||
|
Method::DELETE,
|
||||||
|
"/order",
|
||||||
|
headers.into_iter(),
|
||||||
|
body_bytes,
|
||||||
|
);
|
||||||
|
|
||||||
let response = req.json(&body).send().await?;
|
let response = req.send().await?;
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
return Err(PolyfillError::api(
|
return Err(PolyfillError::api(
|
||||||
response.status().as_u16(),
|
response.status().as_u16(),
|
||||||
@@ -1230,10 +1272,22 @@ impl ClobClient {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
|
.ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
|
||||||
|
|
||||||
let headers = create_l2_headers(signer, api_creds, "DELETE", "/orders", Some(order_ids))?;
|
let body_bytes = Self::serialize_json_body(order_ids)?;
|
||||||
let req = self.create_request_with_headers(Method::DELETE, "/orders", headers.into_iter());
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
|
signer,
|
||||||
|
api_creds,
|
||||||
|
"DELETE",
|
||||||
|
"/orders",
|
||||||
|
Some(&body_bytes),
|
||||||
|
)?;
|
||||||
|
let req = self.create_request_with_json_bytes(
|
||||||
|
Method::DELETE,
|
||||||
|
"/orders",
|
||||||
|
headers.into_iter(),
|
||||||
|
body_bytes,
|
||||||
|
);
|
||||||
|
|
||||||
let response = req.json(order_ids).send().await?;
|
let response = req.send().await?;
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
return Err(PolyfillError::api(
|
return Err(PolyfillError::api(
|
||||||
response.status().as_u16(),
|
response.status().as_u16(),
|
||||||
@@ -1741,19 +1795,18 @@ impl ClobClient {
|
|||||||
("market", market.unwrap_or("")),
|
("market", market.unwrap_or("")),
|
||||||
("asset_id", asset_id.unwrap_or("")),
|
("asset_id", asset_id.unwrap_or("")),
|
||||||
]);
|
]);
|
||||||
|
let body_bytes = Self::serialize_json_body(&body)?;
|
||||||
|
|
||||||
let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(&body))?;
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
|
signer,
|
||||||
|
api_creds,
|
||||||
|
method.as_str(),
|
||||||
|
endpoint,
|
||||||
|
Some(&body_bytes),
|
||||||
|
)?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.http_client
|
.create_request_with_json_bytes(method, endpoint, headers.into_iter(), body_bytes)
|
||||||
.request(method, format!("{}{}", self.base_url, endpoint))
|
|
||||||
.headers(
|
|
||||||
headers
|
|
||||||
.into_iter()
|
|
||||||
.map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.json(&body)
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
||||||
@@ -1905,24 +1958,17 @@ impl ClobClient {
|
|||||||
|
|
||||||
let method = Method::POST;
|
let method = Method::POST;
|
||||||
let endpoint = "/orders-scoring";
|
let endpoint = "/orders-scoring";
|
||||||
let headers = create_l2_headers(
|
let body_bytes = Self::serialize_json_body(order_ids)?;
|
||||||
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
signer,
|
signer,
|
||||||
api_creds,
|
api_creds,
|
||||||
method.as_str(),
|
method.as_str(),
|
||||||
endpoint,
|
endpoint,
|
||||||
Some(order_ids),
|
Some(&body_bytes),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.http_client
|
.create_request_with_json_bytes(method, endpoint, headers.into_iter(), body_bytes)
|
||||||
.request(method, format!("{}{}", self.base_url, endpoint))
|
|
||||||
.headers(
|
|
||||||
headers
|
|
||||||
.into_iter()
|
|
||||||
.map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.json(order_ids)
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
||||||
@@ -1953,12 +1999,17 @@ impl ClobClient {
|
|||||||
|
|
||||||
let method = Method::POST;
|
let method = Method::POST;
|
||||||
let endpoint = "/rfq/request";
|
let endpoint = "/rfq/request";
|
||||||
let headers =
|
let body_bytes = Self::serialize_json_body(request)?;
|
||||||
create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(request))?;
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
|
signer,
|
||||||
|
api_creds,
|
||||||
|
method.as_str(),
|
||||||
|
endpoint,
|
||||||
|
Some(&body_bytes),
|
||||||
|
)?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.create_request_with_headers(method, endpoint, headers.into_iter())
|
.create_request_with_json_bytes(method, endpoint, headers.into_iter(), body_bytes)
|
||||||
.json(request)
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
||||||
@@ -1992,11 +2043,17 @@ impl ClobClient {
|
|||||||
let body = crate::types::RfqCancelRequest {
|
let body = crate::types::RfqCancelRequest {
|
||||||
request_id: request_id.to_string(),
|
request_id: request_id.to_string(),
|
||||||
};
|
};
|
||||||
let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(&body))?;
|
let body_bytes = Self::serialize_json_body(&body)?;
|
||||||
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
|
signer,
|
||||||
|
api_creds,
|
||||||
|
method.as_str(),
|
||||||
|
endpoint,
|
||||||
|
Some(&body_bytes),
|
||||||
|
)?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.create_request_with_headers(method, endpoint, headers.into_iter())
|
.create_request_with_json_bytes(method, endpoint, headers.into_iter(), body_bytes)
|
||||||
.json(&body)
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
||||||
@@ -2068,11 +2125,17 @@ impl ClobClient {
|
|||||||
|
|
||||||
let method = Method::POST;
|
let method = Method::POST;
|
||||||
let endpoint = "/rfq/quote";
|
let endpoint = "/rfq/quote";
|
||||||
let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(quote))?;
|
let body_bytes = Self::serialize_json_body(quote)?;
|
||||||
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
|
signer,
|
||||||
|
api_creds,
|
||||||
|
method.as_str(),
|
||||||
|
endpoint,
|
||||||
|
Some(&body_bytes),
|
||||||
|
)?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.create_request_with_headers(method, endpoint, headers.into_iter())
|
.create_request_with_json_bytes(method, endpoint, headers.into_iter(), body_bytes)
|
||||||
.json(quote)
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
||||||
@@ -2106,11 +2169,17 @@ impl ClobClient {
|
|||||||
let body = crate::types::RfqCancelQuote {
|
let body = crate::types::RfqCancelQuote {
|
||||||
quote_id: quote_id.to_string(),
|
quote_id: quote_id.to_string(),
|
||||||
};
|
};
|
||||||
let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(&body))?;
|
let body_bytes = Self::serialize_json_body(&body)?;
|
||||||
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
|
signer,
|
||||||
|
api_creds,
|
||||||
|
method.as_str(),
|
||||||
|
endpoint,
|
||||||
|
Some(&body_bytes),
|
||||||
|
)?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.create_request_with_headers(method, endpoint, headers.into_iter())
|
.create_request_with_json_bytes(method, endpoint, headers.into_iter(), body_bytes)
|
||||||
.json(&body)
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
||||||
@@ -2259,11 +2328,17 @@ impl ClobClient {
|
|||||||
|
|
||||||
let method = Method::POST;
|
let method = Method::POST;
|
||||||
let endpoint = "/rfq/request/accept";
|
let endpoint = "/rfq/request/accept";
|
||||||
let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(body))?;
|
let body_bytes = Self::serialize_json_body(body)?;
|
||||||
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
|
signer,
|
||||||
|
api_creds,
|
||||||
|
method.as_str(),
|
||||||
|
endpoint,
|
||||||
|
Some(&body_bytes),
|
||||||
|
)?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.create_request_with_headers(method, endpoint, headers.into_iter())
|
.create_request_with_json_bytes(method, endpoint, headers.into_iter(), body_bytes)
|
||||||
.json(body)
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
||||||
@@ -2294,11 +2369,17 @@ impl ClobClient {
|
|||||||
|
|
||||||
let method = Method::POST;
|
let method = Method::POST;
|
||||||
let endpoint = "/rfq/quote/approve";
|
let endpoint = "/rfq/quote/approve";
|
||||||
let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(body))?;
|
let body_bytes = Self::serialize_json_body(body)?;
|
||||||
|
let headers = create_l2_headers_with_body_bytes(
|
||||||
|
signer,
|
||||||
|
api_creds,
|
||||||
|
method.as_str(),
|
||||||
|
endpoint,
|
||||||
|
Some(&body_bytes),
|
||||||
|
)?;
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.create_request_with_headers(method, endpoint, headers.into_iter())
|
.create_request_with_json_bytes(method, endpoint, headers.into_iter(), body_bytes)
|
||||||
.json(body)
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
|
||||||
|
|||||||
+36
-44
@@ -6,16 +6,15 @@
|
|||||||
use crate::auth::{sign_order_message, SignedOrderMessage};
|
use crate::auth::{sign_order_message, SignedOrderMessage};
|
||||||
use crate::errors::{PolyfillError, Result};
|
use crate::errors::{PolyfillError, Result};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
CreateOrderOptions, MarketOrderArgs, OrderArgs, OrderType, Side, SignedOrderRequest,
|
decimal_to_price, CreateOrderOptions, MarketOrderArgs, OrderArgs, OrderType, Side,
|
||||||
|
SignedOrderRequest,
|
||||||
};
|
};
|
||||||
use alloy_primitives::{keccak256, Address, B256, U256};
|
use alloy_primitives::{keccak256, Address, B256, U256};
|
||||||
use alloy_signer_local::PrivateKeySigner;
|
use alloy_signer_local::PrivateKeySigner;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use rust_decimal::RoundingStrategy::{AwayFromZero, MidpointTowardZero, ToZero};
|
use rust_decimal::RoundingStrategy::{AwayFromZero, MidpointTowardZero, ToZero};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::LazyLock;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
pub const BYTES32_ZERO: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
pub const BYTES32_ZERO: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
||||||
@@ -61,43 +60,27 @@ const PROXY_INIT_CODE_HASH: &str =
|
|||||||
const SAFE_INIT_CODE_HASH: &str =
|
const SAFE_INIT_CODE_HASH: &str =
|
||||||
"0x2bce2127ff07fb632d16c8347c4ebf501f4841168bed00d9e6ef715ddb6fcecf";
|
"0x2bce2127ff07fb632d16c8347c4ebf501f4841168bed00d9e6ef715ddb6fcecf";
|
||||||
|
|
||||||
/// Rounding configurations for different tick sizes
|
const ROUND_CONFIG_0_1: RoundConfig = RoundConfig {
|
||||||
static ROUNDING_CONFIG: LazyLock<HashMap<Decimal, RoundConfig>> = LazyLock::new(|| {
|
price: 1,
|
||||||
HashMap::from([
|
size: 2,
|
||||||
(
|
amount: 3,
|
||||||
Decimal::from_str("0.1").unwrap(),
|
};
|
||||||
RoundConfig {
|
const ROUND_CONFIG_0_01: RoundConfig = RoundConfig {
|
||||||
price: 1,
|
price: 2,
|
||||||
size: 2,
|
size: 2,
|
||||||
amount: 3,
|
amount: 4,
|
||||||
},
|
};
|
||||||
),
|
const ROUND_CONFIG_0_001: RoundConfig = RoundConfig {
|
||||||
(
|
price: 3,
|
||||||
Decimal::from_str("0.01").unwrap(),
|
size: 2,
|
||||||
RoundConfig {
|
amount: 5,
|
||||||
price: 2,
|
};
|
||||||
size: 2,
|
const ROUND_CONFIG_0_0001: RoundConfig = RoundConfig {
|
||||||
amount: 4,
|
price: 4,
|
||||||
},
|
size: 2,
|
||||||
),
|
amount: 6,
|
||||||
(
|
};
|
||||||
Decimal::from_str("0.001").unwrap(),
|
const TOKEN_UNIT_SCALE: Decimal = Decimal::from_parts(1_000_000, 0, 0, false, 0);
|
||||||
RoundConfig {
|
|
||||||
price: 3,
|
|
||||||
size: 2,
|
|
||||||
amount: 5,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Decimal::from_str("0.0001").unwrap(),
|
|
||||||
RoundConfig {
|
|
||||||
price: 4,
|
|
||||||
size: 2,
|
|
||||||
amount: 6,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
])
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Get contract configuration for chain
|
/// Get contract configuration for chain
|
||||||
pub fn get_contract_config(chain_id: u64, neg_risk: bool) -> Option<ContractConfig> {
|
pub fn get_contract_config(chain_id: u64, neg_risk: bool) -> Option<ContractConfig> {
|
||||||
@@ -193,7 +176,7 @@ fn generate_seed() -> u64 {
|
|||||||
|
|
||||||
/// Convert decimal to token units (multiply by 1e6)
|
/// Convert decimal to token units (multiply by 1e6)
|
||||||
fn decimal_to_token_u32(amt: Decimal) -> u32 {
|
fn decimal_to_token_u32(amt: Decimal) -> u32 {
|
||||||
let mut amt = Decimal::from_scientific("1e6").expect("1e6 is not scientific") * amt;
|
let mut amt = TOKEN_UNIT_SCALE * amt;
|
||||||
if amt.scale() > 0 {
|
if amt.scale() > 0 {
|
||||||
amt = amt.round_dp_with_strategy(0, MidpointTowardZero);
|
amt = amt.round_dp_with_strategy(0, MidpointTowardZero);
|
||||||
}
|
}
|
||||||
@@ -201,9 +184,18 @@ fn decimal_to_token_u32(amt: Decimal) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_round_config(tick_size: Decimal) -> Result<&'static RoundConfig> {
|
fn parse_round_config(tick_size: Decimal) -> Result<&'static RoundConfig> {
|
||||||
ROUNDING_CONFIG
|
let tick_size_ticks = decimal_to_price(tick_size)
|
||||||
.get(&tick_size)
|
.map_err(|_| PolyfillError::validation(format!("Unsupported tick size {tick_size}")))?;
|
||||||
.ok_or_else(|| PolyfillError::validation(format!("Unsupported tick size {tick_size}")))
|
|
||||||
|
match tick_size_ticks {
|
||||||
|
1000 => Ok(&ROUND_CONFIG_0_1),
|
||||||
|
100 => Ok(&ROUND_CONFIG_0_01),
|
||||||
|
10 => Ok(&ROUND_CONFIG_0_001),
|
||||||
|
1 => Ok(&ROUND_CONFIG_0_0001),
|
||||||
|
_ => Err(PolyfillError::validation(format!(
|
||||||
|
"Unsupported tick size {tick_size}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn validate_bytes32_hex(field: &str, value: &str) -> Result<()> {
|
pub(crate) fn validate_bytes32_hex(field: &str, value: &str) -> Result<()> {
|
||||||
|
|||||||
Reference in New Issue
Block a user