1use crate::error::BotSdkError;
2use secp256k1::ecdsa::{self, SerializedSignature};
3use serde::{Deserialize, Serialize};
4
5pub const DEFAULT_URL: &str = "https://api.release.crypdefi.eu";
6fn set_url(base_url: &str, endpoint: &str) -> String {
7 let joinded_url: String = format!("{base_url}{endpoint}");
8
9 joinded_url
10}
11
12#[derive(Serialize)]
13pub enum AuthMethod {
14 #[serde(rename(serialize = "cra"))]
15 Cra,
16}
17#[derive(Serialize)]
18pub struct LoginRequest {
19 pub user_id: String,
20 pub auth_method: String,
21}
22
23#[derive(Deserialize, Serialize, Debug)]
24pub struct LoginResponse {
25 pub challenge: String,
26 pub nonce: String,
27 pub timestamp: i64,
28 pub deadline: usize,
29}
30
31pub async fn login(
33 client: &reqwest::Client,
34 req: LoginRequest,
35 base_url: &str,
36) -> Result<LoginResponse, BotSdkError> {
37 let url = set_url(base_url, "/auth/login");
38 let response = client.post(url).json(&req).send().await?;
39
40 if response.status().is_success() {
41 let res = response.json::<LoginResponse>().await?;
42 return Ok(res);
43 }
44
45 let status = response.status();
46 let res = response.text().await?;
47
48 Err(BotSdkError::Custom(format!(
49 "{}.\n Status: {} \n Body: {}",
50 "Could not make login request", status, res
51 )))
52}
53
54pub async fn warmup_connection(client: &reqwest::Client, url: &str) -> Result<(), BotSdkError> {
55 let url = set_url(url, "/health");
56 let response = client.get(url).send().await?;
57
58 if response.status().is_success() {
59 return Ok(());
60 }
61
62 let status = response.status();
63 let res: String = response.text().await?;
64
65 Err(BotSdkError::Custom(format!(
66 "{}.\n Status: {} \n Body: {}",
67 "Could not warmup connection", status, res
68 )))
69}
70
71#[derive(Serialize)]
72pub enum HashAlgo {
73 #[serde(rename(serialize = "sha256"))]
74 Sha256,
75}
76
77#[derive(Serialize)]
78pub struct CraRequest {
79 pub user_id: String,
80 pub challenge: String,
81 pub response: String,
82 pub hash_algorithm: HashAlgo,
83}
84
85#[derive(Deserialize, Debug)]
86pub struct CraResponse {
87 pub token: String,
88 pub refresh_token: String,
89 pub expires_at: u64,
90 pub seconds: u64,
91}
92
93pub async fn cra_login(
95 client: &reqwest::Client,
96 req: CraRequest,
97 base_url: &str,
98) -> Result<CraResponse, BotSdkError> {
99 let url = set_url(base_url, "/auth/cra");
100 let response = client.post(url).json(&req).send().await?;
101
102 if response.status().is_success() {
103 let res = response.json::<CraResponse>().await?;
104 return Ok(res);
105 }
106
107 let status = response.status();
108 let res = response.text().await?;
109
110 Err(BotSdkError::Custom(format!(
111 "{}.\n Status: {} \n Body: {}",
112 "Could not make CRA login", status, res
113 )))
114}
115
116#[derive(Deserialize, Debug, Clone, Serialize)]
117pub enum WalletState {
118 #[serde(rename = "active")]
119 Active,
120 #[serde(rename = "inactive")]
121 Inactive,
122}
123
124#[allow(dead_code)]
125#[derive(Deserialize, Clone, Debug)]
126pub struct ChainSimple {
127 pub chain_id: String,
129 pub name: String,
131}
132
133#[allow(dead_code)]
134#[derive(Deserialize, Clone, Debug)]
135pub struct Wallet {
136 pub wallet_id: String,
138 pub name: String,
140 pub created_at: String,
142 pub state: WalletState,
144
145 pub address: String,
146 pub chain: ChainSimple,
147}
148
149pub async fn get_wallets(
151 client: &reqwest::Client,
152 access_token: &str,
153 base_url: &str,
154) -> Result<Vec<Wallet>, BotSdkError> {
155 let url = set_url(base_url, "/wallets?accessOnly=true");
156
157 let response = client.get(url).bearer_auth(access_token).send().await?;
158
159 if response.status().is_success() {
160 let res = response.json::<Vec<Wallet>>().await?;
161 return Ok(res);
162 }
163
164 let status = response.status();
165 let res = response.text().await?;
166
167 Err(BotSdkError::Custom(format!(
168 "{}.\n Status: {} \n Body: {}",
169 "Could not get wallets", status, res
170 )))
171}
172
173#[derive(Serialize, Deserialize, Debug)]
174pub struct SignRequest {
175 pub kind: SignatureRequestKind,
176 pub data: String,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub raw_bytes: Option<String>,
180}
181
182#[allow(dead_code)]
183#[derive(Deserialize, Clone, Debug)]
184pub struct Signature {
185 pub r: String,
186 pub s: String,
187 pub recovery_id: Option<u64>,
188}
189
190impl Signature {
191 pub fn to_der(&self) -> Result<SerializedSignature, BotSdkError> {
192 let mut data = [0u8; 64];
193 const_hex::decode_to_slice(&self.r, &mut data[..32])?;
194 const_hex::decode_to_slice(&self.s, &mut data[32..])?;
195
196 let sig = ecdsa::Signature::from_compact(&data)?;
197 Ok(sig.serialize_der())
198 }
199}
200
201#[derive(Deserialize, Debug, Clone, Serialize)]
202pub enum KeyAlgorithm {
203 #[serde(rename = "ECDSA_SECP256k1")]
204 EcdsaSecp256k1,
205 #[serde(rename = "EDDSA_ED25519")]
206 EddsaEd25519,
207}
208
209#[allow(dead_code)]
210#[derive(Deserialize, Clone, Debug)]
211pub struct PublicKey {
212 pub algorithm: KeyAlgorithm,
213 pub public_key: String,
214}
215
216#[allow(dead_code)]
217#[derive(Deserialize, Debug)]
218pub struct SigResponse {
219 pub payload: SignRequest,
220 pub key: PublicKey,
221 pub signature: Signature,
222}
223
224#[derive(Deserialize, Debug, Clone, Serialize)]
225#[serde(rename_all = "snake_case")]
226pub enum SignatureRequestKind {
227 Raw,
228 Transaction,
229 Message,
230 RdxAuthenticationRequest,
231 EvmEip712,
232 CantonTopology,
233}
234
235pub async fn sign(
237 client: &reqwest::Client,
238 access_token: &str,
239 wallet_id: &str,
240 tx_type: SignatureRequestKind,
241 hex: String,
242 base_url: &str,
243) -> Result<SigResponse, BotSdkError> {
244 let url = set_url(base_url, &format!("/wallets/{wallet_id}/sign"));
245
246 let req = SignRequest {
247 kind: tx_type,
248 data: hex,
249 raw_bytes: None,
250 };
251
252 let response = client
253 .post(url)
254 .json(&req)
255 .bearer_auth(access_token)
256 .send()
257 .await?;
258
259 if response.status().is_success() {
260 let res = response.json::<SigResponse>().await?;
261 return Ok(res);
262 }
263
264 let status = response.status();
265 let res = response.text().await?;
266
267 Err(BotSdkError::Custom(format!(
268 "{}.\n Status: {} \n Body: {}",
269 "Could not get signature", status, res
270 )))
271}
272
273pub async fn logout(
275 client: &reqwest::Client,
276 access_token: &str,
277 base_url: &str,
278) -> Result<(), BotSdkError> {
279 let url = set_url(base_url, "/auth/logout");
280
281 let response = client.post(url).bearer_auth(access_token).send().await?;
282
283 if response.status().is_success() {
284 return Ok(());
285 }
286 let status = response.status();
287 let res = response.text().await?;
288
289 Err(BotSdkError::Custom(format!(
290 "{}.\n Status: {} \n Body: {}",
291 "Could not logout", status, res
292 )))
293}
294
295#[derive(Serialize, Debug)]
296struct RefreshRequest {
297 refresh_token: String,
298}
299
300pub async fn refresh_auth(
302 client: &reqwest::Client,
303 refresh_token: &str,
304 access_token: &str,
305 base_url: &str,
306) -> Result<CraResponse, BotSdkError> {
307 let url = set_url(base_url, "/auth/refresh");
308
309 let req_body = RefreshRequest {
310 refresh_token: refresh_token.to_owned(),
311 };
312
313 let response = client
314 .post(url)
315 .json(&req_body)
316 .bearer_auth(access_token)
317 .send()
318 .await?;
319 if response.status().is_success() {
320 let res = response.json::<CraResponse>().await?;
321 return Ok(res);
322 }
323
324 let status = response.status();
325 let res = response.text().await?;
326
327 Err(BotSdkError::Custom(format!(
328 "{}.\n Status: {} \n Body: {}",
329 "Could not refresh auth", status, res
330 )))
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn test_signature_to_der() {
339 let signature = Signature {
340 r: "bc87e27ae505b41bab7f228a60205f61756b3f5ba67ad33fd35664731720a0c9".to_string(),
341 s: "4a28dc564d87e36871d032a208ccef1229a8c95875928f867d77daa21991ca2e".to_string(),
342 recovery_id: Some(0),
343 };
344
345 let der_result = signature.to_der().unwrap();
346 let der_hex = const_hex::encode(&der_result);
347
348 let expected_der_hex = "3045022100bc87e27ae505b41bab7f228a60205f61756b3f5ba67ad33fd35664731720a0c902204a28dc564d87e36871d032a208ccef1229a8c95875928f867d77daa21991ca2e";
349
350 assert_eq!(der_hex, expected_der_hex);
351 }
352 #[test]
353 fn test_signature_to_der_2() {
354 let signature = Signature {
355 r: "d12c949a67ebdaf38350fda2b6d4e0e2bb8a8c8160cfbc6487a3719e2187dd02".to_string(),
356 s: "18f9b3eb182ef670832698f57dc663ddf0501e202aa90865d13abe12e2cd7559".to_string(),
357 recovery_id: Some(1),
358 };
359
360 let der_result = signature.to_der().unwrap();
361 let der_hex = const_hex::encode(&der_result);
362
363 let expected_der_hex = "3045022100d12c949a67ebdaf38350fda2b6d4e0e2bb8a8c8160cfbc6487a3719e2187dd02022018f9b3eb182ef670832698f57dc663ddf0501e202aa90865d13abe12e2cd7559";
364
365 assert_eq!(der_hex, expected_der_hex);
366 }
367}