|
@@ -1,13 +1,19 @@
|
|
|
|
|
+use std::str::FromStr;
|
|
|
use chrono::Utc;
|
|
use chrono::Utc;
|
|
|
-use anyhow::{anyhow, Result};
|
|
|
|
|
|
|
+use anyhow::{anyhow, bail, Result};
|
|
|
use reqwest::Client;
|
|
use reqwest::Client;
|
|
|
use reqwest::header::HeaderMap;
|
|
use reqwest::header::HeaderMap;
|
|
|
|
|
+use rust_decimal::Decimal;
|
|
|
use serde_json::{json, Value};
|
|
use serde_json::{json, Value};
|
|
|
-use tracing::{error};
|
|
|
|
|
|
|
+use starknet::core::types::Felt;
|
|
|
|
|
+use tracing::{error, info, warn};
|
|
|
|
|
+use tracing_subscriber::fmt::format;
|
|
|
use uuid::{Uuid};
|
|
use uuid::{Uuid};
|
|
|
use crate::exchange::extended_account::ExtendedAccount;
|
|
use crate::exchange::extended_account::ExtendedAccount;
|
|
|
|
|
+use crate::utils::lib::{get_order_hash, sign_message};
|
|
|
use crate::utils::response::Response;
|
|
use crate::utils::response::Response;
|
|
|
use crate::utils::rest_utils::RestUtils;
|
|
use crate::utils::rest_utils::RestUtils;
|
|
|
|
|
+use crate::utils::starknet_messages::{AssetId, Order, PositionId, Timestamp};
|
|
|
|
|
|
|
|
pub struct ExtendedRestClient {
|
|
pub struct ExtendedRestClient {
|
|
|
pub tag: String,
|
|
pub tag: String,
|
|
@@ -45,11 +51,46 @@ impl ExtendedRestClient {
|
|
|
|
|
|
|
|
// 获取该client要操作的market的info
|
|
// 获取该client要操作的market的info
|
|
|
let response = client.get_market_info(None).await;
|
|
let response = client.get_market_info(None).await;
|
|
|
- client.market_info = response.data;
|
|
|
|
|
-
|
|
|
|
|
- Err(anyhow!("固定报错不要慌张"))
|
|
|
|
|
|
|
|
|
|
- // Ok(client)
|
|
|
|
|
|
|
+ // 进一步解析
|
|
|
|
|
+ if response.code != 200 {
|
|
|
|
|
+ bail!(response.message);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let market_info = response.data;
|
|
|
|
|
+
|
|
|
|
|
+ match market_info.get("status") {
|
|
|
|
|
+ None => {
|
|
|
|
|
+ bail!(serde_json::to_string_pretty(&market_info)?);
|
|
|
|
|
+ }
|
|
|
|
|
+ Some(status_value) => {
|
|
|
|
|
+ match status_value.as_str() {
|
|
|
|
|
+ None => {
|
|
|
|
|
+ bail!(serde_json::to_string_pretty(&market_info)?);
|
|
|
|
|
+ }
|
|
|
|
|
+ Some(status) => {
|
|
|
|
|
+ match status {
|
|
|
|
|
+ "OK" => {
|
|
|
|
|
+ client.market_info = market_info.get("data").unwrap().as_array().unwrap().get(0).unwrap().clone();
|
|
|
|
|
+
|
|
|
|
|
+ let status = client.market_info.get("status").unwrap().as_str().unwrap();
|
|
|
|
|
+ if status != "ACTIVE" {
|
|
|
|
|
+ warn!("{}", serde_json::to_string_pretty(&client.market_info)?);
|
|
|
|
|
+ bail!("交易对状态异常,期待:ACTIVE,实际:{}", status);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ _ => {
|
|
|
|
|
+ bail!(serde_json::to_string_pretty(&market_info)?);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // bail!("固定报错不要慌张")
|
|
|
|
|
+
|
|
|
|
|
+ Ok(client)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// =================================== 公共方法区 ====================================
|
|
// =================================== 公共方法区 ====================================
|
|
@@ -108,9 +149,11 @@ impl ExtendedRestClient {
|
|
|
).await
|
|
).await
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- pub async fn post_order(&mut self, order_type: &str, side: &str, qty: &str, price: &str) -> Response {
|
|
|
|
|
|
|
+ pub async fn post_order(&mut self, order_type: &str, side: &str, qty: &str, price: &str) -> Result<Response> {
|
|
|
|
|
+ let account = self.account.clone().ok_or_else(|| anyhow!("请将账户传入再进行下单操作"))?;
|
|
|
|
|
+
|
|
|
// 需要传给extended的参数整理
|
|
// 需要传给extended的参数整理
|
|
|
- let id = Uuid::new_v4().to_string().as_str();
|
|
|
|
|
|
|
+ let id = Uuid::new_v4().to_string();
|
|
|
let market = self.market.as_str();
|
|
let market = self.market.as_str();
|
|
|
// type
|
|
// type
|
|
|
// side
|
|
// side
|
|
@@ -118,29 +161,90 @@ impl ExtendedRestClient {
|
|
|
// price
|
|
// price
|
|
|
let time_in_force = "GTT";
|
|
let time_in_force = "GTT";
|
|
|
let expiry_epoch_millis = Utc::now().timestamp_millis() + (24 * 60 * 60 * 1000);
|
|
let expiry_epoch_millis = Utc::now().timestamp_millis() + (24 * 60 * 60 * 1000);
|
|
|
- let fee = "";
|
|
|
|
|
let nonce_u32: u32 = rand::random();
|
|
let nonce_u32: u32 = rand::random();
|
|
|
- let nonce = nonce_u32.to_string().as_str();
|
|
|
|
|
|
|
+ let nonce = nonce_u32.to_string();
|
|
|
let self_trade_protection_level = "ACCOUNT";
|
|
let self_trade_protection_level = "ACCOUNT";
|
|
|
|
|
|
|
|
-
|
|
|
|
|
// 准备OrderHash
|
|
// 准备OrderHash
|
|
|
|
|
+ let l2_config = self.market_info.get("l2Config").unwrap();
|
|
|
|
|
+
|
|
|
|
|
+ // 涉及到计算的
|
|
|
|
|
+ let is_buying_synthetic = side == "BUY";
|
|
|
|
|
+ let synthetic_amount = Decimal::from_str(qty)?;
|
|
|
|
|
+ let price = Decimal::from_str(price)?;
|
|
|
|
|
+ let collateral_amount = synthetic_amount.checked_mul(price)
|
|
|
|
|
+ .ok_or_else(|| anyhow!("Collateral amount multiplication overflowed"))?;
|
|
|
|
|
+
|
|
|
|
|
+ // 其余参数
|
|
|
|
|
+ let position_id = format!("{}", account.vault_number);
|
|
|
|
|
+ let base_asset_id_hex = l2_config.get("syntheticId").unwrap().as_str().unwrap().to_string();
|
|
|
|
|
+ let base_amount = "100".to_string();
|
|
|
|
|
+ let quote_asset_id_hex = l2_config.get("collateralId").unwrap().as_str().unwrap().to_string();
|
|
|
|
|
+ let quote_amount = "-156".to_string();
|
|
|
|
|
+ let fee_asset_id_hex = l2_config.get("collateralId").unwrap().as_str().unwrap().to_string();
|
|
|
|
|
+ let fee_amount = "74".to_string();
|
|
|
|
|
+ let expiration = format!("{}", (expiry_epoch_millis / 1000) as u64);
|
|
|
|
|
+ let salt = nonce.clone();
|
|
|
|
|
+ let user_public_key_hex = account.stark_public_key.clone();
|
|
|
|
|
+ let domain_name = "Perpetuals".to_string();
|
|
|
|
|
+ let domain_version = "v0".to_string();
|
|
|
|
|
+ let domain_chain_id = "SN_SEPOLIA".to_string();
|
|
|
|
|
+ let domain_revision = "1".to_string();
|
|
|
|
|
+
|
|
|
|
|
+ let hash = get_order_hash(
|
|
|
|
|
+ position_id.clone(),
|
|
|
|
|
+ base_asset_id_hex,
|
|
|
|
|
+ base_amount,
|
|
|
|
|
+ quote_asset_id_hex,
|
|
|
|
|
+ quote_amount,
|
|
|
|
|
+ fee_asset_id_hex,
|
|
|
|
|
+ fee_amount.clone(),
|
|
|
|
|
+ expiration,
|
|
|
|
|
+ salt,
|
|
|
|
|
+ user_public_key_hex,
|
|
|
|
|
+ domain_name,
|
|
|
|
|
+ domain_version,
|
|
|
|
|
+ domain_chain_id,
|
|
|
|
|
+ domain_revision,
|
|
|
|
|
+ ).unwrap();
|
|
|
|
|
+ let private_key = Felt::from_hex(account.stark_private_key.as_str())?;
|
|
|
|
|
+
|
|
|
|
|
+ // 签名
|
|
|
|
|
+ let signature = sign_message(&hash, &private_key).unwrap();
|
|
|
|
|
|
|
|
// 生成settlement
|
|
// 生成settlement
|
|
|
- let settlement = json!({});
|
|
|
|
|
|
|
+ let settlement = json!({
|
|
|
|
|
+ "signature": {
|
|
|
|
|
+ "r": format!("0x{:x}", signature.r),
|
|
|
|
|
+ "s": format!("0x{:x}", signature.s)
|
|
|
|
|
+ },
|
|
|
|
|
+ "starkKey": account.stark_public_key,
|
|
|
|
|
+ "collateralPosition": position_id,
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
// 组装最后参数
|
|
// 组装最后参数
|
|
|
let params = json!({
|
|
let params = json!({
|
|
|
-
|
|
|
|
|
|
|
+ "id": id,
|
|
|
|
|
+ "market": market,
|
|
|
|
|
+ "type": order_type,
|
|
|
|
|
+ "side": side,
|
|
|
|
|
+ "qty": qty,
|
|
|
|
|
+ "price": price,
|
|
|
|
|
+ "timeInForce": time_in_force,
|
|
|
|
|
+ "expiryEpochMillis": expiry_epoch_millis,
|
|
|
|
|
+ "fee": fee_amount,
|
|
|
|
|
+ "nonce": nonce,
|
|
|
|
|
+ "settlement": settlement,
|
|
|
|
|
+ "selfTradeProtectionLevel": self_trade_protection_level,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// 发送订单
|
|
// 发送订单
|
|
|
- self.request("POST",
|
|
|
|
|
|
|
+ Ok(self.request("POST",
|
|
|
"/api/v1",
|
|
"/api/v1",
|
|
|
"/user/order",
|
|
"/user/order",
|
|
|
true,
|
|
true,
|
|
|
params,
|
|
params,
|
|
|
- ).await
|
|
|
|
|
|
|
+ ).await)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// =================================== 网络层基础 ====================================
|
|
// =================================== 网络层基础 ====================================
|
|
@@ -272,8 +376,8 @@ impl ExtendedRestClient {
|
|
|
error
|
|
error
|
|
|
}
|
|
}
|
|
|
Err(e) => {
|
|
Err(e) => {
|
|
|
- error!("解析错误:{:?}", e);
|
|
|
|
|
- let error = Response::error("".to_string(), "请检查apikey配置是否正确".to_string());
|
|
|
|
|
|
|
+ error!("解析错误,请检查apikey或参数配置是否正确:{:?}", e);
|
|
|
|
|
+ let error = Response::error("".to_string(), "请检查apikey或参数配置是否正确".to_string());
|
|
|
error
|
|
error
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -310,7 +414,7 @@ impl ExtendedRestClient {
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
mod tests {
|
|
|
- use tracing::info;
|
|
|
|
|
|
|
+ use tracing::{info, warn};
|
|
|
use crate::exchange::extended_account::ExtendedAccount;
|
|
use crate::exchange::extended_account::ExtendedAccount;
|
|
|
use crate::exchange::extended_rest_client::ExtendedRestClient;
|
|
use crate::exchange::extended_rest_client::ExtendedRestClient;
|
|
|
use crate::utils::log_setup::setup_logging;
|
|
use crate::utils::log_setup::setup_logging;
|
|
@@ -320,6 +424,7 @@ mod tests {
|
|
|
"a7b197d06d35de11387b8b71f34c87e4",
|
|
"a7b197d06d35de11387b8b71f34c87e4",
|
|
|
"0x41efadf5ceebc77b0798b0af797fb97e610c87c669494bea54338c5ef8c0f19",
|
|
"0x41efadf5ceebc77b0798b0af797fb97e610c87c669494bea54338c5ef8c0f19",
|
|
|
"0x484b399394c4d76cdc62a1dc490f96cf5197f0e307832e59fdeec2e16c50078",
|
|
"0x484b399394c4d76cdc62a1dc490f96cf5197f0e307832e59fdeec2e16c50078",
|
|
|
|
|
+ 500089,
|
|
|
);
|
|
);
|
|
|
let tag = "Extended";
|
|
let tag = "Extended";
|
|
|
let market = "BTC-USD";
|
|
let market = "BTC-USD";
|
|
@@ -354,6 +459,22 @@ mod tests {
|
|
|
info!("{}", serde_json::to_string_pretty(&response.data).unwrap());
|
|
info!("{}", serde_json::to_string_pretty(&response.data).unwrap());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ #[tokio::test]
|
|
|
|
|
+ async fn test_create_order() {
|
|
|
|
|
+ let _guard = setup_logging().unwrap();
|
|
|
|
|
+ let mut client = get_client().await;
|
|
|
|
|
+ let response_result = client.post_order("limit", "BUY", "0.0001", "90000").await;
|
|
|
|
|
+
|
|
|
|
|
+ match response_result {
|
|
|
|
|
+ Ok(response) => {
|
|
|
|
|
+ info!("{}", serde_json::to_string_pretty(&response.data).unwrap());
|
|
|
|
|
+ }
|
|
|
|
|
+ Err(error) => {
|
|
|
|
|
+ warn!("{}", error.to_string());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
#[tokio::test]
|
|
#[tokio::test]
|
|
|
async fn test_cancel_order() {
|
|
async fn test_cancel_order() {
|
|
|
let _guard = setup_logging().unwrap();
|
|
let _guard = setup_logging().unwrap();
|