okx-sdk 0.1.0

library for okx
Documentation
use crate::client::OkxClient;
use anyhow::Result;
use http::{HeaderMap, HeaderValue};
use ring::hmac;
use serde::de::DeserializeOwned;
use std::collections::BTreeMap;

impl OkxClient {
    pub async fn get<T>(
        &self,
        request_path: &str,
        parameters: &BTreeMap<String, String>,
    ) -> Result<T>
    where
        T: DeserializeOwned + std::fmt::Debug,
    {
        // # 获取本地时间
        let timestamp = self.get_timestamp();

        let mut get_url_params: String = String::from(request_path);

        // 有数据就格式化参数
        if parameters.len() != 0 {
            get_url_params = format!("{}?{}", request_path, self.parse_params_to_str(parameters));
        }
        // println!("url {}",get_url_params);
        // OK-ACCESS-SIGN的请求头是对timestamp + method + requestPath + body字符串(+表示字符串连接),以及SecretKey
        let message = format!("{}GET{}", timestamp, get_url_params);
        let sign = self.sign(&message);
        let headers = self.create_header(&sign, &timestamp);

        let client = reqwest::Client::new();
        if self.debug {
            println!(
                "[*] Debug:\nUrl:{}\nparameters:{:?}",
                format!("{}{}", self.domain, request_path),
                parameters
            );
        }

        let res = client
            .get(format!("{}{}", self.domain, get_url_params))
            .headers(headers)
            .send()
            .await?
            .text()
            .await?;

        if self.debug {
            println!("[*] Response {:#?}", res);
        }
        Ok(serde_json::from_str::<T>(&res)?)
    }

    pub async fn post<T>(
        &self,
        request_path: &str,
        parameters: &BTreeMap<String, String>,
    ) -> Result<T>
    where
        T: DeserializeOwned + std::fmt::Debug,
    {
        // # 获取本地时间
        let timestamp = self.get_timestamp();
        let data = serde_json::to_string(&parameters).unwrap();
        let message = format!("{}POST{}{}", timestamp, request_path, &data);
        let sign = self.sign(&message);
        let headers = self.create_header(&sign, &timestamp);
        let client = reqwest::Client::new();

        if self.debug {
            println!("[*] Debug:parameters {:?}", parameters);
        }
        if self.debug {
            println!(
                "[*] Debug:\nUrl:{}\nparameters:{:?}",
                format!("{}{}", self.domain, request_path),
                parameters
            );
        }

        let res = client
            .post(format!("{}{}", self.domain, request_path))
            .headers(headers)
            .json(&parameters)
            .send()
            .await?
            .text()
            .await?;

        if self.debug {
            println!("[*] Response {:#?}", res);
        }

        Ok(serde_json::from_str::<T>(&res)?)
    }

    pub async fn post_vec<T>(
        &self,
        request_path: &str,
        parameters: &Vec<BTreeMap<String, String>>,
    ) -> Result<T>
    where
        T: DeserializeOwned + std::fmt::Debug,
    {
        // # 获取本地时间
        let timestamp = self.get_timestamp();
        let data = serde_json::to_string(parameters).unwrap();
        let message = format!("{}POST{}{}", timestamp, request_path, &data);
        let sign = self.sign(&message);

        let headers = self.create_header(&sign, &timestamp);
        let client = reqwest::Client::new();
        if self.debug {
            println!(
                "[*] Debug:\nUrl:{}\nparameters:{:?}",
                format!("{}{}", self.domain, request_path),
                parameters
            );
        }
        let res = client
            .post(format!("{}{}", self.domain, request_path))
            .headers(headers)
            .json(&parameters)
            .send()
            .await?
            .text()
            .await?;

        if self.debug {
            println!("[*] Response {:#?}", res);
        }

        Ok(serde_json::from_str::<T>(&res)?)
    }

    fn create_header(&self, sign: &str, timestamp: &str) -> HeaderMap {
        // 处理请求头 headers
        let mut header_map = HeaderMap::new();
        header_map.insert(
            "OK-ACCESS-KEY",
            HeaderValue::from_str(&self.api_key).unwrap(),
        );
        header_map.insert("OK-ACCESS-SIGN", HeaderValue::from_str(&sign).unwrap());
        header_map.insert(
            "OK-ACCESS-TIMESTAMP",
            HeaderValue::from_str(&timestamp).unwrap(),
        );
        header_map.insert(
            "OK-ACCESS-PASSPHRASE",
            HeaderValue::from_str(&self.passphrase).unwrap(),
        );
        header_map.insert(
            "CONTENT_TYPE",
            HeaderValue::from_static("application/json; charset=UTF-8"),
        );
        // 如果是测试网
        if self.testnet {
            header_map.insert("x-simulated-trading", HeaderValue::from_static("1"));
        }
        header_map
    }

    fn parse_params_to_str(&self, parameters: &BTreeMap<String, String>) -> String {
        parameters
            .into_iter()
            .map(|(key, value)| format!("{}={}", key, value))
            .collect::<Vec<String>>()
            .join("&")
    }
    // 做签名
    fn sign(&self, message: &String) -> String {
        let hmac_key = ring::hmac::Key::new(hmac::HMAC_SHA256, &self.secret_key.as_bytes());
        let result = ring::hmac::sign(&hmac_key, &message.as_bytes());
        base64::encode(result)
    }

    pub fn get_timestamp(&self) -> String {
        chrono::Utc::now()
            .format("%Y-%m-%dT%H:%M:%S%.3fZ")
            .to_string()
    }
}