1use polyoxide_core::{HttpClient, QueryBuilder};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5 account::{Credentials, Signer, Wallet},
6 error::ClobError,
7 request::{AuthMode, Request},
8};
9
10#[derive(Clone)]
12pub struct Auth {
13 pub(crate) http_client: HttpClient,
14 pub(crate) wallet: Wallet,
15 pub(crate) credentials: Credentials,
16 pub(crate) signer: Signer,
17 pub(crate) chain_id: u64,
18}
19
20impl Auth {
21 fn l1_auth(&self, nonce: u32) -> AuthMode {
22 AuthMode::L1 {
23 wallet: self.wallet.clone(),
24 nonce,
25 }
26 }
27
28 fn l2_auth(&self) -> AuthMode {
29 AuthMode::L2 {
30 address: self.wallet.address(),
31 credentials: self.credentials.clone(),
32 signer: self.signer.clone(),
33 }
34 }
35
36 pub fn create_api_key(&self, nonce: u32) -> Request<ApiKeyResponse> {
40 Request::post(
41 self.http_client.clone(),
42 "/auth/api-key".to_string(),
43 self.l1_auth(nonce),
44 self.chain_id,
45 )
46 }
47
48 pub fn derive_api_key(&self, nonce: u32) -> Request<ApiKeyResponse> {
50 Request::get(
51 self.http_client.clone(),
52 "/auth/derive-api-key",
53 self.l1_auth(nonce),
54 self.chain_id,
55 )
56 }
57
58 pub fn list_api_keys(&self) -> Request<Vec<ApiKeyInfo>> {
60 Request::get(
61 self.http_client.clone(),
62 "/auth/api-keys",
63 self.l2_auth(),
64 self.chain_id,
65 )
66 }
67
68 pub fn delete_api_key(&self) -> Request<serde_json::Value> {
70 Request::delete(
71 self.http_client.clone(),
72 "/auth/api-key",
73 self.l2_auth(),
74 self.chain_id,
75 )
76 }
77
78 pub fn create_readonly_key(&self, nonce: u32) -> Request<ReadonlyApiKeyResponse> {
82 Request::post(
83 self.http_client.clone(),
84 "/auth/readonly-api-key".to_string(),
85 self.l1_auth(nonce),
86 self.chain_id,
87 )
88 }
89
90 pub fn list_readonly_keys(&self) -> Request<Vec<ReadonlyApiKeyResponse>> {
92 Request::get(
93 self.http_client.clone(),
94 "/auth/readonly-api-keys",
95 self.l2_auth(),
96 self.chain_id,
97 )
98 }
99
100 pub async fn delete_readonly_key(
102 &self,
103 key: impl Into<String>,
104 ) -> Result<serde_json::Value, ClobError> {
105 #[derive(Serialize)]
106 #[serde(rename_all = "camelCase")]
107 struct Body {
108 api_key: String,
109 }
110
111 Request::<serde_json::Value>::delete(
112 self.http_client.clone(),
113 "/auth/readonly-api-key",
114 self.l2_auth(),
115 self.chain_id,
116 )
117 .body(&Body {
118 api_key: key.into(),
119 })?
120 .send()
121 .await
122 }
123
124 pub fn validate_readonly_key(
126 &self,
127 address: impl Into<String>,
128 key: impl Into<String>,
129 ) -> Request<ValidateKeyResponse> {
130 Request::get(
131 self.http_client.clone(),
132 "/auth/validate-readonly-api-key",
133 AuthMode::None,
134 self.chain_id,
135 )
136 .query("address", address.into())
137 .query("api_key", key.into())
138 }
139
140 pub fn create_builder_key(&self, nonce: u32) -> Request<ApiKeyResponse> {
144 Request::post(
145 self.http_client.clone(),
146 "/auth/builder-api-key".to_string(),
147 self.l1_auth(nonce),
148 self.chain_id,
149 )
150 }
151
152 pub fn list_builder_keys(&self) -> Request<Vec<ApiKeyInfo>> {
154 Request::get(
155 self.http_client.clone(),
156 "/auth/builder-api-key",
157 self.l2_auth(),
158 self.chain_id,
159 )
160 }
161
162 pub fn delete_builder_key(&self) -> Request<serde_json::Value> {
164 Request::delete(
165 self.http_client.clone(),
166 "/auth/builder-api-key",
167 self.l2_auth(),
168 self.chain_id,
169 )
170 }
171
172 pub fn closed_only_status(&self) -> Request<ClosedOnlyResponse> {
176 Request::get(
177 self.http_client.clone(),
178 "/auth/ban-status/closed-only",
179 self.l2_auth(),
180 self.chain_id,
181 )
182 }
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187#[serde(rename_all(deserialize = "camelCase"))]
188pub struct ApiKeyResponse {
189 pub api_key: String,
190 pub secret: String,
191 pub passphrase: String,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196#[serde(rename_all(deserialize = "camelCase"))]
197pub struct ApiKeyInfo {
198 pub api_key: String,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203#[serde(rename_all(deserialize = "camelCase"))]
204pub struct ReadonlyApiKeyResponse {
205 pub api_key: String,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct ValidateKeyResponse {
211 pub valid: bool,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct ClosedOnlyResponse {
217 pub closed_only: bool,
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn api_key_response_deserializes() {
226 let json = r#"{
227 "apiKey": "key-123",
228 "secret": "secret-456",
229 "passphrase": "pass-789"
230 }"#;
231 let resp: ApiKeyResponse = serde_json::from_str(json).unwrap();
232 assert_eq!(resp.api_key, "key-123");
233 assert_eq!(resp.secret, "secret-456");
234 assert_eq!(resp.passphrase, "pass-789");
235 }
236
237 #[test]
238 fn readonly_api_key_response_deserializes() {
239 let json = r#"{"apiKey": "readonly-key"}"#;
240 let resp: ReadonlyApiKeyResponse = serde_json::from_str(json).unwrap();
241 assert_eq!(resp.api_key, "readonly-key");
242 }
243
244 #[test]
245 fn validate_key_response_deserializes() {
246 let json = r#"{"valid": true}"#;
247 let resp: ValidateKeyResponse = serde_json::from_str(json).unwrap();
248 assert!(resp.valid);
249
250 let json = r#"{"valid": false}"#;
251 let resp: ValidateKeyResponse = serde_json::from_str(json).unwrap();
252 assert!(!resp.valid);
253 }
254
255 #[test]
256 fn api_key_info_deserializes() {
257 let json = r#"{"apiKey": "key-abc"}"#;
258 let info: ApiKeyInfo = serde_json::from_str(json).unwrap();
259 assert_eq!(info.api_key, "key-abc");
260 }
261
262 #[test]
263 fn api_key_response_rejects_missing_fields() {
264 let json = r#"{"apiKey": "key-123"}"#;
266 assert!(serde_json::from_str::<ApiKeyResponse>(json).is_err());
267
268 let json = r#"{"apiKey": "k", "secret": "s"}"#;
270 assert!(serde_json::from_str::<ApiKeyResponse>(json).is_err());
271 }
272
273 #[test]
274 fn api_key_response_list_deserializes() {
275 let json = r#"[{"apiKey": "k1"}, {"apiKey": "k2"}]"#;
276 let list: Vec<ApiKeyInfo> = serde_json::from_str(json).unwrap();
277 assert_eq!(list.len(), 2);
278 assert_eq!(list[0].api_key, "k1");
279 assert_eq!(list[1].api_key, "k2");
280 }
281
282 #[test]
283 fn closed_only_response_deserializes() {
284 let json = r#"{"closed_only": true}"#;
285 let resp: ClosedOnlyResponse = serde_json::from_str(json).unwrap();
286 assert!(resp.closed_only);
287
288 let json = r#"{"closed_only": false}"#;
289 let resp: ClosedOnlyResponse = serde_json::from_str(json).unwrap();
290 assert!(!resp.closed_only);
291 }
292}