nautilus_polymarket/http/
auth.rs1use std::collections::HashMap;
19
20use nautilus_core::time::get_atomic_clock_realtime;
21use nautilus_network::http::{HttpClient, Method};
22use serde::Deserialize;
23
24use crate::{
25 common::{credential::EvmPrivateKey, urls::clob_http_url},
26 http::error::{Error, Result},
27 signing::eip712::sign_clob_auth,
28};
29
30#[derive(Debug, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct ApiCredentials {
34 pub api_key: String,
35 pub secret: String,
36 pub passphrase: String,
37}
38
39pub async fn create_api_key(
45 private_key: &EvmPrivateKey,
46 nonce: u64,
47 base_url: Option<&str>,
48) -> Result<ApiCredentials> {
49 let (client, headers, base) = prepare_l1_request(private_key, nonce, base_url)?;
50
51 let url = format!("{base}/auth/api-key");
52 let response = client
53 .request(Method::POST, url, None, Some(headers), None, None, None)
54 .await
55 .map_err(Error::from_http_client)?;
56
57 if response.status.is_success() {
58 serde_json::from_slice(&response.body).map_err(Error::Serde)
59 } else {
60 Err(Error::from_status_code(
61 response.status.as_u16(),
62 &response.body,
63 ))
64 }
65}
66
67pub async fn derive_api_key(
73 private_key: &EvmPrivateKey,
74 nonce: u64,
75 base_url: Option<&str>,
76) -> Result<ApiCredentials> {
77 let (client, headers, base) = prepare_l1_request(private_key, nonce, base_url)?;
78
79 let url = format!("{base}/auth/derive-api-key");
80 let response = client
81 .request(Method::GET, url, None, Some(headers), None, None, None)
82 .await
83 .map_err(Error::from_http_client)?;
84
85 if response.status.is_success() {
86 serde_json::from_slice(&response.body).map_err(Error::Serde)
87 } else {
88 Err(Error::from_status_code(
89 response.status.as_u16(),
90 &response.body,
91 ))
92 }
93}
94
95pub async fn create_or_derive_api_key(
102 private_key: &EvmPrivateKey,
103 nonce: u64,
104 base_url: Option<&str>,
105) -> Result<ApiCredentials> {
106 match create_api_key(private_key, nonce, base_url).await {
107 Ok(creds) => Ok(creds),
108 Err(e) if e.is_http_status_error() => derive_api_key(private_key, nonce, base_url).await,
109 Err(e) => Err(e),
110 }
111}
112
113fn prepare_l1_request(
114 private_key: &EvmPrivateKey,
115 nonce: u64,
116 base_url: Option<&str>,
117) -> Result<(HttpClient, HashMap<String, String>, String)> {
118 let base = base_url
119 .unwrap_or_else(|| clob_http_url())
120 .trim_end_matches('/')
121 .to_string();
122 let timestamp =
123 (get_atomic_clock_realtime().get_time_ns().as_u64() / 1_000_000_000).to_string();
124 let (address, signature) = sign_clob_auth(private_key, ×tamp, nonce)?;
125 let headers = l1_headers(&address, &signature, ×tamp, nonce);
126 let client = HttpClient::new(HashMap::new(), vec![], vec![], None, None, None)
127 .map_err(Error::from_http_client)?;
128 Ok((client, headers, base))
129}
130
131fn l1_headers(
132 address: &str,
133 signature: &str,
134 timestamp: &str,
135 nonce: u64,
136) -> HashMap<String, String> {
137 HashMap::from([
138 ("POLY_ADDRESS".to_string(), address.to_string()),
139 ("POLY_SIGNATURE".to_string(), signature.to_string()),
140 ("POLY_TIMESTAMP".to_string(), timestamp.to_string()),
141 ("POLY_NONCE".to_string(), nonce.to_string()),
142 ])
143}