|
|
@@ -0,0 +1,426 @@
|
|
|
+use std::collections::BTreeMap;
|
|
|
+use std::ops::Index;
|
|
|
+use reqwest::header::HeaderMap;
|
|
|
+use ring::{digest};
|
|
|
+use hex;
|
|
|
+use hmac::{Hmac, Mac, NewMac};
|
|
|
+use reqwest::{Client};
|
|
|
+use sha2::Sha256;
|
|
|
+use crate::http_tool::RestTool;
|
|
|
+use crate::response_base::ResponseData;
|
|
|
+
|
|
|
+pub struct KucoinSwapRest {
|
|
|
+ base_url: String,
|
|
|
+ client: reqwest::Client,
|
|
|
+ /*******参数*/
|
|
|
+ //是否需要登陆
|
|
|
+ is_login: bool,
|
|
|
+ //登陆所需参数
|
|
|
+ login_param: BTreeMap<String, String>,
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+impl KucoinSwapRest {
|
|
|
+ /*******************************************************************************************************/
|
|
|
+ /*****************************************获取一个对象****************************************************/
|
|
|
+ /*******************************************************************************************************/
|
|
|
+ pub fn new(is_colo: bool, is_login: bool, login_param: BTreeMap<String, String>) -> KucoinSwapRest
|
|
|
+ {
|
|
|
+ let mut base_url = String::from("");
|
|
|
+ if is_colo {
|
|
|
+ println!("不支持colo高速线路");
|
|
|
+ base_url = "https://api-futures.kucoin.com".to_string()
|
|
|
+ } else {
|
|
|
+ base_url = "https://api-futures.kucoin.com".to_string()
|
|
|
+ }
|
|
|
+
|
|
|
+ /*****返回结构体*******/
|
|
|
+ KucoinSwapRest {
|
|
|
+ base_url: base_url.to_string(),
|
|
|
+ client: Client::new(),
|
|
|
+ is_login,
|
|
|
+ login_param,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*******************************************************************************************************/
|
|
|
+ /*****************************************rest请求函数********************************************************/
|
|
|
+ /*******************************************************************************************************/
|
|
|
+ pub async fn get_server_time(&self) -> ResponseData {
|
|
|
+ let params = serde_json::json!({
|
|
|
+ });
|
|
|
+ let data = self.request("GET".to_string(),
|
|
|
+ "/api/v1".to_string(),
|
|
|
+ "/timestamp".to_string(),
|
|
|
+ true,
|
|
|
+ params.to_string(),
|
|
|
+ ).await;
|
|
|
+ data
|
|
|
+ }
|
|
|
+ //查询合约账户
|
|
|
+ pub async fn get_account(&self, contract: String) -> ResponseData {
|
|
|
+ let params = serde_json::json!({
|
|
|
+ "currency":contract
|
|
|
+ });
|
|
|
+ let data = self.request("GET".to_string(),
|
|
|
+ "/api/v1".to_string(),
|
|
|
+ "/account-overview".to_string(),
|
|
|
+ true,
|
|
|
+ params.to_string(),
|
|
|
+ ).await;
|
|
|
+ data
|
|
|
+ }
|
|
|
+ //获取仓位信息
|
|
|
+ pub async fn get_position(&self, symbol: String) -> ResponseData {
|
|
|
+ let params = serde_json::json!({
|
|
|
+ "symbol":symbol
|
|
|
+ });
|
|
|
+ let data = self.request("GET".to_string(),
|
|
|
+ "/api/v1".to_string(),
|
|
|
+ "/position".to_string(),
|
|
|
+ true,
|
|
|
+ params.to_string(),
|
|
|
+ ).await;
|
|
|
+ data
|
|
|
+ }
|
|
|
+ //查询所有的合约信息
|
|
|
+ pub async fn get_market_details(&self) -> ResponseData {
|
|
|
+ let mut params = serde_json::json!({});
|
|
|
+
|
|
|
+ let data = self.request("GET".to_string(),
|
|
|
+ "/api/v1".to_string(),
|
|
|
+ format!("/contracts/active"),
|
|
|
+ true,
|
|
|
+ params.to_string(),
|
|
|
+ ).await;
|
|
|
+ data
|
|
|
+ }
|
|
|
+ //实时行情
|
|
|
+ pub async fn get_ticker(&self, symbol: String) -> ResponseData {
|
|
|
+ let params = serde_json::json!({
|
|
|
+ "symbol":symbol
|
|
|
+ });
|
|
|
+ let data = self.request("GET".to_string(),
|
|
|
+ "/api/v1".to_string(),
|
|
|
+ format!("/ticker"),
|
|
|
+ true,
|
|
|
+ params.to_string(),
|
|
|
+ ).await;
|
|
|
+ data
|
|
|
+ }
|
|
|
+ //查看订单列表
|
|
|
+ pub async fn get_orders(&self, status: String, trade_type: String) -> ResponseData {
|
|
|
+ let params = serde_json::json!({
|
|
|
+ "status":status,
|
|
|
+ "tradeType":trade_type
|
|
|
+ });
|
|
|
+ let data = self.request("GET".to_string(),
|
|
|
+ "/api/v1".to_string(),
|
|
|
+ format!("/orders"),
|
|
|
+ true,
|
|
|
+ params.to_string(),
|
|
|
+ ).await;
|
|
|
+ data
|
|
|
+ }
|
|
|
+ //获取用户仓位列表
|
|
|
+ pub async fn get_positions(&self, currency: String) -> ResponseData {
|
|
|
+ let params = serde_json::json!({
|
|
|
+ "currency":currency
|
|
|
+ });
|
|
|
+ let data = self.request("GET".to_string(),
|
|
|
+ "/api/v1".to_string(),
|
|
|
+ format!("/positions"),
|
|
|
+ true,
|
|
|
+ params.to_string(),
|
|
|
+ ).await;
|
|
|
+ data
|
|
|
+ }
|
|
|
+ //单个订单详情
|
|
|
+ pub async fn get_orders_details(&self, order_id: String, client_id: String) -> ResponseData {
|
|
|
+ let mut params = serde_json::json!({ });
|
|
|
+ let mut url = String::from("");
|
|
|
+ if order_id != "" {
|
|
|
+ url = format!("/orders/{}", order_id);
|
|
|
+ } else if client_id != "" {
|
|
|
+ url = format!("/orders/byClientOid");
|
|
|
+ params.as_object_mut().unwrap().insert("clientOid".parse().unwrap(), client_id.parse().unwrap());
|
|
|
+ }
|
|
|
+
|
|
|
+ let data = self.request("GET".to_string(),
|
|
|
+ "/api/v1".to_string(),
|
|
|
+ url,
|
|
|
+ true,
|
|
|
+ params.to_string(),
|
|
|
+ ).await;
|
|
|
+ data
|
|
|
+ }
|
|
|
+ async fn swap_order(&self, params: serde_json::Value) -> ResponseData {
|
|
|
+ let data = self.request("POST".to_string(),
|
|
|
+ "/api/v1".to_string(),
|
|
|
+ format!("/orders"),
|
|
|
+ true,
|
|
|
+ params.to_string(),
|
|
|
+ ).await;
|
|
|
+ data
|
|
|
+ }
|
|
|
+ //下单
|
|
|
+ pub async fn swap_bazaar_order(&self,
|
|
|
+ client_oid: String,
|
|
|
+ symbol: String,
|
|
|
+ origin_side: String,
|
|
|
+ size: u64,
|
|
|
+ leverage: String,
|
|
|
+ price: String,
|
|
|
+ order_type: String) -> ResponseData
|
|
|
+ {
|
|
|
+ let mut side = String::from("");
|
|
|
+ let mut params = serde_json::json!({
|
|
|
+ "clientOid":client_oid,
|
|
|
+ "symbol": symbol,
|
|
|
+ "size":size,
|
|
|
+ "leverage":leverage,
|
|
|
+ "reduceOnly":false,
|
|
|
+ "price":price,
|
|
|
+ "type":order_type,
|
|
|
+ });
|
|
|
+
|
|
|
+ let req = match origin_side.as_str() {
|
|
|
+ "kd" => {
|
|
|
+ side = "buy".to_string();
|
|
|
+ true
|
|
|
+ }
|
|
|
+ "pd" => {
|
|
|
+ side = "sell".to_string();
|
|
|
+ true
|
|
|
+ }
|
|
|
+ "kk" => {
|
|
|
+ side = "sell".to_string();
|
|
|
+ true
|
|
|
+ }
|
|
|
+ "pk" => {
|
|
|
+ side = "buy".to_string();
|
|
|
+ true
|
|
|
+ }
|
|
|
+ _ => { false } // 处理未知请求类型
|
|
|
+ };
|
|
|
+ if req {
|
|
|
+ params.as_object_mut().unwrap().insert("side".to_string(), serde_json::json!(side));
|
|
|
+ }
|
|
|
+
|
|
|
+ let data = self.swap_order(params).await;
|
|
|
+ data
|
|
|
+ }
|
|
|
+
|
|
|
+ //单个撤单
|
|
|
+ // pub async fn cancel_order(&self, order_id: String) -> ResponseData {
|
|
|
+ // let mut params = serde_json::json!({ });
|
|
|
+ // let data = self.request("DELETE".to_string(),
|
|
|
+ // "/api/v1".to_string(),
|
|
|
+ // format!("/orders/{}", order_id),
|
|
|
+ // true,
|
|
|
+ // params.to_string(),
|
|
|
+ // ).await;
|
|
|
+ // data
|
|
|
+ // }
|
|
|
+
|
|
|
+ /*******************************************************************************************************/
|
|
|
+ /*****************************************工具函数********************************************************/
|
|
|
+ /*******************************************************************************************************/
|
|
|
+ //调用请求
|
|
|
+ async fn request(&self,
|
|
|
+ method: String,
|
|
|
+ prefix_url: String,
|
|
|
+ request_url: String,
|
|
|
+ is_login: bool,
|
|
|
+ params: String) -> ResponseData
|
|
|
+ {
|
|
|
+ println!("login_param:{:?}", self.login_param);
|
|
|
+ //解析账号信息
|
|
|
+ let mut access_key = "".to_string();
|
|
|
+ let mut secret_key = "".to_string();
|
|
|
+ let mut pass_key = "".to_string();
|
|
|
+ if self.login_param.contains_key("access_key") {
|
|
|
+ access_key = self.login_param.get("access_key").unwrap().to_string();
|
|
|
+ }
|
|
|
+ if self.login_param.contains_key("secret_key") {
|
|
|
+ secret_key = self.login_param.get("secret_key").unwrap().to_string();
|
|
|
+ }
|
|
|
+ if self.login_param.contains_key("pass_key") {
|
|
|
+ pass_key = self.login_param.get("pass_key").unwrap().to_string();
|
|
|
+ }
|
|
|
+ let mut is_login_param = true;
|
|
|
+ if access_key == "" || secret_key == "" || pass_key == "" {
|
|
|
+ is_login_param = false
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //请求头配置-如果需要登陆则存在额外配置
|
|
|
+ let mut body = "".to_string();
|
|
|
+ let mut sing = "".to_string();
|
|
|
+ let mut passphrase = "".to_string();
|
|
|
+ let timestamp = chrono::Utc::now().timestamp_millis().to_string();
|
|
|
+
|
|
|
+ let mut headers = HeaderMap::new();
|
|
|
+ headers.insert("Content-Type", "application/json".parse().unwrap());
|
|
|
+ if method == "POST" {
|
|
|
+ body = params.clone();
|
|
|
+ }
|
|
|
+
|
|
|
+ //是否需要登陆-- 组装sing
|
|
|
+ if is_login {
|
|
|
+ if !is_login_param {
|
|
|
+ let e = ResponseData::error("登陆参数错误!".to_string());
|
|
|
+ return e;
|
|
|
+ } else {//需要登陆-且登陆参数齐全
|
|
|
+ println!("param:{}", params);
|
|
|
+ println!("body:{}", body);
|
|
|
+ //组装sing
|
|
|
+ sing = Self::sign(secret_key.clone(),
|
|
|
+ method.clone(),
|
|
|
+ prefix_url.clone(),
|
|
|
+ request_url.clone(),
|
|
|
+ params.clone(),
|
|
|
+ body.clone(),
|
|
|
+ timestamp.clone(),
|
|
|
+ );
|
|
|
+ println!("sing:{}", sing);
|
|
|
+ passphrase = Self::passphrase(secret_key, pass_key);
|
|
|
+ println!("passphrase:{}", passphrase);
|
|
|
+ //组装header
|
|
|
+ headers.extend(Self::headers(sing, timestamp, passphrase, access_key));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // println!("headers:{:?}", headers);
|
|
|
+ let mut get_response = self.http_toll(
|
|
|
+ format!("{}{}", prefix_url.clone(), request_url.clone()),
|
|
|
+ method.to_string(),
|
|
|
+ params,
|
|
|
+ headers,
|
|
|
+ ).await;
|
|
|
+
|
|
|
+ let mut req_data = Self::req_data_analysis(get_response);
|
|
|
+ req_data
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn headers(sign: String, timestamp: String, passphrase: String, access_key: String) -> HeaderMap {
|
|
|
+ let mut headers = HeaderMap::new();
|
|
|
+ headers.insert("KC-API-KEY", access_key.parse().unwrap());
|
|
|
+ headers.insert("KC-API-SIGN", sign.parse().unwrap());
|
|
|
+ headers.insert("KC-API-TIMESTAMP", timestamp.parse().unwrap());
|
|
|
+ headers.insert("KC-API-PASSPHRASE", passphrase.parse().unwrap());
|
|
|
+ headers.insert("KC-API-KEY-VERSION", "2".parse().unwrap());
|
|
|
+ headers
|
|
|
+ }
|
|
|
+ pub fn sign(secret_key: String,
|
|
|
+ method: String, prefix_url: String, request_url: String,
|
|
|
+ params: String, body_data: String, timestamp: String) -> String
|
|
|
+ {
|
|
|
+ let url = format!("{}{}", prefix_url, request_url);
|
|
|
+ let params_str = RestTool::parse_params_to_str(params);
|
|
|
+ println!("body_data:{}", body_data);
|
|
|
+ // let body = Some(body_data);
|
|
|
+ // let hashed_payload = if let Some(body) = body {
|
|
|
+ // let mut m = digest::Context::new(&digest::SHA256);
|
|
|
+ // m.update(body.as_bytes());
|
|
|
+ // hex::encode(m.finish().as_ref())
|
|
|
+ // } else {
|
|
|
+ // String::new()
|
|
|
+ // };
|
|
|
+ // println!("hashed_payload:{}", hashed_payload);
|
|
|
+
|
|
|
+ let mut message = format!("{}{}{}",
|
|
|
+ timestamp,
|
|
|
+ method,
|
|
|
+ url
|
|
|
+ );
|
|
|
+ if method == "GET" || method == "DELETE" {
|
|
|
+ message = format!("{}?{}", message, params_str);
|
|
|
+ } else if method == "POST" || method == "PUT" {
|
|
|
+ message = format!("{}{}", message, body_data);
|
|
|
+ }
|
|
|
+
|
|
|
+ println!("**********", );
|
|
|
+ println!("组装数据:{}", message);
|
|
|
+ println!("**********", );
|
|
|
+
|
|
|
+ let mut mac = Hmac::<Sha256>::new_varkey(secret_key.as_bytes()).expect("Failed to create HMAC");
|
|
|
+ mac.update(message.as_bytes());
|
|
|
+ let result = mac.finalize().into_bytes();
|
|
|
+ let base64_encoded = base64::encode(result);
|
|
|
+ base64_encoded
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn passphrase(secret_key: String, pass_key: String) -> String
|
|
|
+ {
|
|
|
+ let mut mac = Hmac::<Sha256>::new_varkey(secret_key.as_bytes()).expect("Failed to create HMAC");
|
|
|
+ mac.update(pass_key.as_bytes());
|
|
|
+ let result = mac.finalize().into_bytes();
|
|
|
+ let base64_encoded = base64::encode(result);
|
|
|
+ base64_encoded
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ async fn http_toll(&self, request_path: String, request_type: String, params: String, headers: HeaderMap) -> Result<ResponseData, reqwest::Error> {
|
|
|
+ let req_data: ResponseData;
|
|
|
+ /****请求接口与 地址*/
|
|
|
+ let url = format!("{}{}", self.base_url.to_string(), request_path);
|
|
|
+ let request_type = request_type.clone().to_uppercase();
|
|
|
+ let addrs_url = format!("{}?{}", url.clone(), RestTool::parse_params_to_str(params.clone()));
|
|
|
+ println!("url:{}", url);
|
|
|
+ println!("addrs_url:{}", addrs_url);
|
|
|
+
|
|
|
+ let req = match request_type.as_str() {
|
|
|
+ "GET" => self.client.get(addrs_url.clone()).headers(headers),
|
|
|
+ "POST" => self.client.post(url.clone()).body(params).headers(headers),
|
|
|
+ "DELETE" => self.client.delete(addrs_url.clone()).headers(headers),
|
|
|
+ // "PUT" => self.client.put(url.clone()).json(¶ms),
|
|
|
+ _ => return Ok(ResponseData::error(format!("错误的请求类型:{}", request_type.clone()))), // 处理未知请求类型
|
|
|
+ };
|
|
|
+
|
|
|
+ let response = req.send().await?;
|
|
|
+ if response.status().is_success() {
|
|
|
+ // 读取响应的内容
|
|
|
+ let body = response.text().await?;
|
|
|
+ println!("ok-----{}", body);
|
|
|
+ req_data = ResponseData::new("200".to_string(), "success".to_string(), body);
|
|
|
+ } else {
|
|
|
+ let body = response.text().await?;
|
|
|
+ println!("error-----{}", body);
|
|
|
+ req_data = ResponseData::error(body.to_string())
|
|
|
+ }
|
|
|
+ Ok(req_data)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //req_data 解析
|
|
|
+ pub fn req_data_analysis(result: Result<ResponseData, reqwest::Error>) -> ResponseData {
|
|
|
+ match result {
|
|
|
+ Ok(req_data) => {
|
|
|
+ if req_data.code != "200" {
|
|
|
+ req_data
|
|
|
+ } else {
|
|
|
+ let body: String = req_data.data;
|
|
|
+ let json_value: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
|
+
|
|
|
+ let code = json_value["code"].as_str().unwrap();
|
|
|
+
|
|
|
+ if code != "200000" {
|
|
|
+ let msg = json_value["msg"].as_str().unwrap();
|
|
|
+ let error = ResponseData::new(code.to_string(), msg.to_string(), "".parse().unwrap());
|
|
|
+ error
|
|
|
+ } else {
|
|
|
+ let data = serde_json::to_string(&json_value["data"]).unwrap();
|
|
|
+ let success = ResponseData::new("200".to_string(), "success".to_string(), data.parse().unwrap());
|
|
|
+ success
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Err(err) => {
|
|
|
+ let error = ResponseData::error(format!("json 解析失败:{}", err));
|
|
|
+ error
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|