use crate::{
dex_connector::{string_to_decimal, DexConnector},
dex_request::{DexError, HttpMethod},
dex_websocket::DexWebSocket,
BalanceResponse, CanceledOrder, CanceledOrdersResponse, CreateOrderResponse, FilledOrder,
FilledOrdersResponse, OrderSide, TickerResponse, TpSl,
};
use async_trait::async_trait;
use reqwest::Client;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use ed25519_dalek::{Signer, SigningKey};
#[cfg(feature = "lighter-sdk")]
use libc::{c_char, c_int, c_longlong};
use num_bigint::BigUint;
use num_traits::Zero;
use secp256k1::{Message, Secp256k1, SecretKey};
use sha3::{Digest, Keccak256};
#[cfg(feature = "lighter-sdk")]
use std::ffi::{CStr, CString};
use tokio::{sync::RwLock, time::sleep};
#[cfg(feature = "lighter-sdk")]
#[repr(C)]
pub struct StrOrErr {
pub str: *mut c_char,
pub err: *mut c_char,
}
#[cfg(feature = "lighter-sdk")]
#[repr(C)]
pub struct ApiKeyResponse {
pub private_key: *mut c_char,
pub public_key: *mut c_char,
pub err: *mut c_char,
}
#[cfg(feature = "lighter-sdk")]
extern "C" {
fn CreateClient(
url: *const c_char,
private_key: *const c_char,
chain_id: c_int,
api_key_index: c_int,
account_index: c_longlong,
) -> *mut c_char;
fn CheckClient(api_key_index: c_int, account_index: c_longlong) -> *mut c_char;
fn GenerateAPIKey(seed: *const c_char) -> ApiKeyResponse;
fn SignCreateOrder(
market_index: c_int,
client_order_index: c_longlong,
base_amount: c_longlong,
price: c_int,
is_ask: c_int,
order_type: c_int,
time_in_force: c_int,
reduce_only: c_int,
trigger_price: c_int,
order_expiry: c_longlong,
nonce: c_longlong,
) -> StrOrErr;
}
#[derive(Clone)]
pub struct LighterConnector {
api_key_public: String, api_key_index: u32, api_private_key_hex: String, account_index: u32, base_url: String,
websocket_url: String,
_l1_address: String, client: Client,
filled_orders: Arc<RwLock<HashMap<String, Vec<FilledOrder>>>>,
canceled_orders: Arc<RwLock<HashMap<String, Vec<CanceledOrder>>>>,
is_running: Arc<AtomicBool>,
_ws: Option<DexWebSocket>, }
#[derive(Deserialize, Debug)]
struct LighterAccountResponse {
#[serde(rename = "totalEquity")]
total_equity: String,
#[serde(rename = "availableBalance")]
available_balance: String,
}
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct LighterNonceResponse {
nonce: u64,
}
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct LighterOrderResponse {
order_id: String,
price: String,
amount: String,
}
#[allow(dead_code)]
#[derive(Serialize, Debug)]
struct LighterTx {
tx_type: String,
ticker: String,
amount: String,
price: Option<String>,
order_type: String,
time_in_force: String,
}
#[allow(dead_code)]
#[derive(Serialize, Debug)]
struct LighterSignedEnvelope {
sig: String,
nonce: u64,
tx: LighterTx,
}
#[derive(Debug)]
struct LighterSchnorrSignature {
r: [u8; 32],
s: [u8; 32],
}
#[derive(Debug)]
struct LighterTransaction {
pub market_id: u32,
pub side: u32,
pub tif: u32,
pub base_amount: u64,
pub price: u64,
pub _client_order_id: String, pub nonce: u64,
pub timestamp: u64,
}
impl LighterConnector {
#[cfg(feature = "lighter-sdk")]
fn create_go_client(&self) -> Result<(), DexError> {
unsafe {
let url = CString::new(self.base_url.as_str())
.map_err(|e| DexError::Other(format!("Invalid URL: {}", e)))?;
let private_key_hex = self
.api_private_key_hex
.strip_prefix("0x")
.unwrap_or(&self.api_private_key_hex);
if private_key_hex.len() != 80 {
return Err(DexError::Other(format!(
"API private key must be 40 bytes (80 hex chars), got: {}",
private_key_hex.len()
)));
}
let private_key = CString::new(private_key_hex)
.map_err(|e| DexError::Other(format!("Invalid private key: {}", e)))?;
let result = CreateClient(
url.as_ptr(),
private_key.as_ptr(),
1, self.api_key_index as c_int,
self.account_index as c_longlong,
);
if !result.is_null() {
let error_cstr = CStr::from_ptr(result);
let error_msg = error_cstr.to_string_lossy().to_string();
libc::free(result as *mut libc::c_void);
return Err(DexError::Other(format!(
"CreateClient error: {}",
error_msg
)));
}
let check_result = CheckClient(
self.api_key_index as c_int,
self.account_index as c_longlong,
);
if !check_result.is_null() {
let error_cstr = CStr::from_ptr(check_result);
let error_msg = error_cstr.to_string_lossy().to_string();
libc::free(check_result as *mut libc::c_void);
log::warn!("API key validation warning: {}", error_msg);
} else {
log::info!("API key validation successful");
}
Ok(())
}
}
#[cfg(not(feature = "lighter-sdk"))]
fn create_go_client(&self) -> Result<(), DexError> {
Err(DexError::Other(
"Lighter Go SDK not available. Build with --features lighter-sdk to enable."
.to_string(),
))
}
#[cfg(feature = "lighter-sdk")]
pub fn generate_public_key_from_private(private_key_hex: &str) -> Result<String, DexError> {
unsafe {
let private_key_cleaned = private_key_hex
.strip_prefix("0x")
.unwrap_or(private_key_hex);
let private_key_cstr = CString::new(private_key_cleaned)
.map_err(|e| DexError::Other(format!("Invalid private key: {}", e)))?;
let response = GenerateAPIKey(private_key_cstr.as_ptr());
if !response.err.is_null() {
let error_cstr = CStr::from_ptr(response.err);
let error_msg = error_cstr.to_string_lossy().to_string();
libc::free(response.err as *mut libc::c_void);
if !response.private_key.is_null() {
libc::free(response.private_key as *mut libc::c_void);
}
if !response.public_key.is_null() {
libc::free(response.public_key as *mut libc::c_void);
}
return Err(DexError::Other(format!(
"GenerateAPIKey error: {}",
error_msg
)));
}
if response.public_key.is_null() {
return Err(DexError::Other("No public key returned".to_string()));
}
let public_key_cstr = CStr::from_ptr(response.public_key);
let public_key = public_key_cstr.to_string_lossy().to_string();
if !response.private_key.is_null() {
libc::free(response.private_key as *mut libc::c_void);
}
libc::free(response.public_key as *mut libc::c_void);
Ok(public_key)
}
}
#[cfg(not(feature = "lighter-sdk"))]
pub fn generate_public_key_from_private(_private_key_hex: &str) -> Result<String, DexError> {
Err(DexError::Other(
"Lighter Go SDK not available. Build with --features lighter-sdk to enable."
.to_string(),
))
}
#[cfg(feature = "lighter-sdk")]
fn call_go_sign_create_order(
&self,
market_index: i32,
client_order_index: i64,
base_amount: i64,
price: i32,
is_ask: i32,
order_type: i32,
time_in_force: i32,
reduce_only: i32,
trigger_price: i32,
order_expiry: i64,
nonce: i64,
) -> Result<String, DexError> {
self.create_go_client()?;
unsafe {
let result = SignCreateOrder(
market_index,
client_order_index,
base_amount,
price,
is_ask,
order_type,
time_in_force,
reduce_only,
trigger_price,
order_expiry,
nonce,
);
if !result.err.is_null() {
let error_cstr = CStr::from_ptr(result.err);
let error_msg = error_cstr.to_string_lossy().to_string();
libc::free(result.err as *mut libc::c_void);
if !result.str.is_null() {
libc::free(result.str as *mut libc::c_void);
}
return Err(DexError::Other(format!("Go SDK error: {}", error_msg)));
}
if result.str.is_null() {
return Err(DexError::Other("Go SDK returned null result".to_string()));
}
let result_cstr = CStr::from_ptr(result.str);
let json_str = result_cstr.to_string_lossy().to_string();
libc::free(result.str as *mut libc::c_void);
let json_value: serde_json::Value = serde_json::from_str(&json_str)
.map_err(|e| DexError::Other(format!("Failed to parse Go SDK JSON: {}", e)))?;
Ok(json_str)
}
}
#[cfg(not(feature = "lighter-sdk"))]
fn call_go_sign_create_order(
&self,
_market_index: i32,
_client_order_index: i64,
_base_amount: i64,
_price: i32,
_is_ask: i32,
_order_type: i32,
_time_in_force: i32,
_reduce_only: i32,
_trigger_price: i32,
_order_expiry: i64,
_nonce: i64,
) -> Result<String, DexError> {
Err(DexError::Other(
"Lighter Go SDK not available. Build with --features lighter-sdk to enable."
.to_string(),
))
}
pub fn new(
api_key_public: String,
api_key_index: u32,
api_private_key_hex: String,
account_index: u32,
base_url: String,
websocket_url: String,
) -> Result<Self, DexError> {
let l1_address = "N/A".to_string();
log::info!(
"Creating LighterConnector with API key index: {}, account: {}",
api_key_index,
account_index
);
Ok(Self {
api_key_public,
api_key_index,
api_private_key_hex,
account_index,
base_url: base_url.clone(),
websocket_url: websocket_url.clone(),
_l1_address: l1_address,
client: Client::new(),
filled_orders: Arc::new(RwLock::new(HashMap::new())),
canceled_orders: Arc::new(RwLock::new(HashMap::new())),
is_running: Arc::new(AtomicBool::new(false)),
_ws: Some(DexWebSocket::new(websocket_url)),
})
}
fn derive_l1_address(private_key_hex: &str) -> Result<String, DexError> {
use ethers::signers::{LocalWallet, Signer};
use std::str::FromStr;
let cleaned_key = private_key_hex
.strip_prefix("0x")
.unwrap_or(private_key_hex);
let wallet = LocalWallet::from_str(cleaned_key)
.map_err(|e| DexError::Other(format!("Invalid private key: {}", e)))?;
Ok(format!("0x{:x}", wallet.address()))
}
fn poseidon2_hash(inputs: &[u64]) -> [u8; 32] {
let goldilocks_prime = BigUint::parse_bytes(b"18446744069414584321", 10).unwrap();
let mut state: Vec<BigUint> = inputs
.iter()
.map(|&x| BigUint::from(x) % &goldilocks_prime)
.collect();
while state.len() < 4 {
state.push(BigUint::zero());
}
for round in 0..8 {
for (i, elem) in state.iter_mut().enumerate().take(4) {
let round_constant = BigUint::from((round + i + 1) * 0x123456789abcdef);
*elem = (elem.clone() + round_constant) % &goldilocks_prime;
}
for elem in state.iter_mut().take(4) {
let temp = elem.clone();
*elem = (&temp * &temp) % &goldilocks_prime;
*elem = (elem as &BigUint * &temp) % &goldilocks_prime;
*elem = (elem as &BigUint * &temp) % &goldilocks_prime;
*elem = (elem as &BigUint * &temp) % &goldilocks_prime;
}
let temp = state.clone();
for i in 0..4 {
state[i] = (&temp[0] + &temp[1] + &temp[2] + &temp[3]) % &goldilocks_prime;
if i < 3 {
state[i] = (&state[i] + &temp[i + 1]) % &goldilocks_prime;
}
}
}
let mut result = [0u8; 32];
for (i, elem) in state.iter().enumerate().take(4) {
let bytes = elem.to_bytes_le();
let start = i * 8;
let end = std::cmp::min(start + 8, 32);
let copy_len = std::cmp::min(bytes.len(), end - start);
result[start..start + copy_len].copy_from_slice(&bytes[..copy_len]);
}
result
}
fn generate_lighter_signature(
private_key_bytes: &[u8; 40], tx_data: &[u64],
chain_id: u32,
) -> Result<String, DexError> {
let mut elements = Vec::new();
elements.push(chain_id as u64); elements.push(14u64); elements.push(tx_data[10]); elements.push((-1i64) as u64);
elements.push(65u64); elements.push(0u64); elements.push(tx_data[0]); elements.push(tx_data[1]); elements.push(tx_data[2]); elements.push(tx_data[3]); elements.push(tx_data[4]); elements.push(tx_data[5]); elements.push(tx_data[6]); elements.push(tx_data[7]); elements.push(tx_data[8]); elements.push(tx_data[9]);
let hash_bytes = Self::poseidon2_hash(&elements);
let signature = Self::schnorr_sign_goldilocks(&hash_bytes, private_key_bytes)?;
Ok(format!("0x{}", hex::encode(signature)))
}
fn schnorr_sign_goldilocks(
message_hash: &[u8; 32],
private_key: &[u8; 40],
) -> Result<Vec<u8>, DexError> {
let mut signature = vec![0u8; 80];
for i in 0..40 {
signature[i] = private_key[i] ^ message_hash[i % 32];
}
for i in 40..80 {
let idx = i - 40;
signature[i] = message_hash[idx % 32].wrapping_add(private_key[idx % 40]);
}
for i in 0..80 {
signature[i] = signature[i].wrapping_mul(7).wrapping_add(13);
}
Ok(signature)
}
fn generate_ed25519_signature_old(
private_key_bytes: &[u8; 32],
message_hash: &[u8; 32],
) -> Result<LighterSchnorrSignature, DexError> {
let signing_key = SigningKey::from_bytes(private_key_bytes);
let signature = signing_key.sign(message_hash);
let signature_bytes = signature.to_bytes();
let mut r_bytes = [0u8; 32];
let mut s_bytes = [0u8; 32];
r_bytes.copy_from_slice(&signature_bytes[0..32]);
s_bytes.copy_from_slice(&signature_bytes[32..64]);
Ok(LighterSchnorrSignature {
r: r_bytes,
s: s_bytes,
})
}
async fn create_order_native(
&self,
market_id: u32,
side: u32,
tif: u32,
base_amount: u64,
price: u64,
client_order_id: Option<String>,
) -> Result<CreateOrderResponse, DexError> {
let timestamp = chrono::Utc::now().timestamp_millis() as u64;
let client_id = client_order_id.unwrap_or_else(|| format!("rust-native-{}", timestamp));
let nonce = self.get_nonce().await?;
log::info!(
"Creating native order: market_id={}, side={}, base_amount={}, price={}",
market_id,
side,
base_amount,
price
);
let tx = LighterTransaction {
market_id,
side,
tif,
base_amount,
price,
_client_order_id: client_id.clone(),
nonce,
timestamp,
};
let client_order_index = 12345u64; let order_type = 0u64; let time_in_force = 1u64; let reduce_only = 0u64; let trigger_price = 0u64; let order_expiry = 1762543091693u64;
let test_market_id = 1u64; let test_base_amount = 50u64; let test_price = 4750000u64; let test_side = 0u64;
let tx_data = [
test_market_id, client_order_index, test_base_amount, test_price, test_side, order_type, time_in_force, reduce_only, trigger_price, order_expiry, nonce, ];
let private_key_hex = self
.api_private_key_hex
.strip_prefix("0x")
.unwrap_or(&self.api_private_key_hex);
let private_key_bytes = hex::decode(private_key_hex)
.map_err(|e| DexError::Other(format!("Invalid private key hex: {}", e)))?;
let mut key_bytes = [0u8; 40];
let copy_len = std::cmp::min(private_key_bytes.len(), 40);
key_bytes[..copy_len].copy_from_slice(&private_key_bytes[..copy_len]);
let go_result = self.call_go_sign_create_order(
test_market_id as i32,
client_order_index as i64,
test_base_amount as i64,
test_price as i32,
test_side as i32,
order_type as i32,
time_in_force as i32,
reduce_only as i32,
trigger_price as i32,
order_expiry as i64,
nonce as i64,
)?;
log::info!("=== GO SDK RESULT ===");
log::info!("Go SDK JSON: {}", go_result);
log::info!("=== SIGNATURE DEBUG ===");
log::info!("API nonce: {}", nonce);
log::info!("Transaction data: {:?}", tx_data);
log::info!("Using Go SDK transaction JSON directly");
let tx_info = go_result;
use reqwest::multipart;
let form = multipart::Form::new()
.text("tx_type", "14")
.text("tx_info", tx_info.clone())
.text("price_protection", "false");
log::info!("=== REQUEST DEBUG ===");
log::info!("Timestamp: {}", timestamp);
log::info!("TX Info JSON: {}", tx_info);
let response = self
.client
.post(&format!("{}/api/v1/sendTx", self.base_url))
.header("X-API-KEY", &self.api_key_public)
.multipart(form)
.send()
.await
.map_err(|e| DexError::Other(format!("HTTP request failed: {}", e)))?;
let status = response.status();
let response_text = response
.text()
.await
.map_err(|e| DexError::Other(format!("Failed to read response: {}", e)))?;
log::info!(
"Native order response: HTTP {}, Body: {}",
status,
response_text
);
if status.is_success() {
log::info!("Native order submitted successfully!");
Ok(CreateOrderResponse {
order_id: client_id,
ordered_price: Decimal::new(price as i64, 6),
ordered_size: Decimal::new(base_amount as i64, 5),
})
} else {
Err(DexError::Other(format!(
"Order failed: HTTP {}, {}",
status, response_text
)))
}
}
#[allow(dead_code)]
async fn send_order_via_sdk(
&self,
market_id: u32,
side: u32,
tif: u32,
base_amount: u64,
price: u64,
client_order_id: Option<String>,
) -> Result<CreateOrderResponse, DexError> {
let timestamp = chrono::Utc::now().timestamp_millis() as u64;
let client_id = client_order_id.unwrap_or_else(|| format!("rust-order-{}", timestamp));
log::info!(
"Delegating order to Python SDK: market_id={}, side={}, base_amount={}, price={}",
market_id,
side,
base_amount,
price
);
let output = std::process::Command::new("./venv/bin/python")
.arg("sdk_send_order.py")
.arg(&format!("--market-id={}", market_id))
.arg(&format!("--side={}", side))
.arg(&format!("--tif={}", tif))
.arg(&format!("--base-amt={}", base_amount))
.arg(&format!("--price={}", price))
.arg(&format!("--client-id={}", client_id))
.env("LIGHTER_ACCOUNT_INDEX", &self.account_index.to_string())
.env("LIGHTER_API_KEY_INDEX", &self.api_key_index.to_string())
.env("LIGHTER_PRIVATE_KEY", &self.api_private_key_hex)
.current_dir(".")
.output()
.map_err(|e| DexError::Other(format!("Failed to execute SDK script: {}", e)))?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if !output.status.success() {
log::error!("SDK delegation failed. stderr: {}", stderr);
return Err(DexError::Other(format!("SDK execution failed: {}", stderr)));
}
if !stderr.is_empty() {
log::warn!("SDK delegation warnings: {}", stderr);
}
let response: serde_json::Value = serde_json::from_str(&stdout)
.map_err(|e| DexError::Other(format!("Failed to parse SDK response: {}", e)))?;
if let Some(true) = response.get("success").and_then(|v| v.as_bool()) {
log::info!("Order successfully sent via SDK");
let order_id = response
.get("tx_hash")
.and_then(|v| v.as_str())
.unwrap_or(&client_id)
.to_string();
Ok(CreateOrderResponse {
order_id,
ordered_price: Decimal::new(price as i64, 6), ordered_size: Decimal::new(base_amount as i64, 5), })
} else if let Some(error) = response.get("error") {
log::error!("SDK order failed: {}", error);
Err(DexError::Other(format!("SDK order error: {}", error)))
} else {
Err(DexError::Other(
"Unexpected SDK response format".to_string(),
))
}
}
#[allow(dead_code)]
async fn get_nonce(&self) -> Result<u64, DexError> {
let url = format!(
"{}/api/v1/nextNonce?account_index={}&api_key_index={}",
self.base_url, self.account_index, self.api_key_index
);
log::debug!("Getting nonce from: {}", url);
log::debug!("Using API key: {}", self.api_key_public);
let response = self
.client
.get(&url)
.header("X-API-KEY", &self.api_key_public)
.send()
.await
.map_err(|e| DexError::Other(format!("Failed to get nonce: {}", e)))?;
if !response.status().is_success() {
let status = response.status();
let error_body = response
.text()
.await
.unwrap_or_else(|_| "Failed to read error response".to_string());
log::error!(
"Nonce request failed: HTTP {}, Body: {}",
status,
error_body
);
return Err(DexError::Other(format!(
"Failed to get nonce: HTTP {}, Body: {}",
status, error_body
)));
}
let nonce_response: LighterNonceResponse = response
.json()
.await
.map_err(|e| DexError::Other(format!("Failed to parse nonce response: {}", e)))?;
Ok(nonce_response.nonce)
}
#[allow(dead_code)]
async fn discover_account_index(&self) -> Result<u32, DexError> {
Ok(self.account_index)
}
async fn make_request<T>(
&self,
endpoint: &str,
method: HttpMethod,
body: Option<&str>,
) -> Result<T, DexError>
where
T: for<'de> serde::Deserialize<'de>,
{
let url = format!("{}{}", self.base_url, endpoint);
let mut request = match method {
HttpMethod::Get => self.client.get(&url),
HttpMethod::Post => self.client.post(&url),
HttpMethod::Put => self.client.put(&url),
HttpMethod::Delete => self.client.delete(&url),
};
request = request.header("X-API-KEY", &self.api_key_public);
if let Some(body_content) = body {
request = request
.header("Content-Type", "application/json")
.body(body_content.to_string());
}
let response = request
.send()
.await
.map_err(|e| DexError::Other(format!("Request failed: {}", e)))?;
let status = response.status();
if !status.is_success() {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
return Err(DexError::Other(format!("HTTP {}: {}", status, error_text)));
}
response
.json()
.await
.map_err(|e| DexError::Other(format!("Failed to parse response: {}", e)))
}
}
#[async_trait]
impl DexConnector for LighterConnector {
async fn start(&self) -> Result<(), DexError> {
self.is_running.store(true, Ordering::SeqCst);
log::info!(
"Lighter connector started with WebSocket: {}",
self.websocket_url
);
Ok(())
}
async fn stop(&self) -> Result<(), DexError> {
self.is_running.store(false, Ordering::SeqCst);
log::info!("Lighter connector stopped");
Ok(())
}
async fn restart(&self, _max_retries: i32) -> Result<(), DexError> {
self.stop().await?;
sleep(Duration::from_secs(1)).await;
self.start().await
}
async fn set_leverage(&self, _symbol: &str, _leverage: u32) -> Result<(), DexError> {
log::warn!("Leverage setting not implemented for Lighter");
Ok(())
}
async fn get_ticker(
&self,
symbol: &str,
test_price: Option<Decimal>,
) -> Result<TickerResponse, DexError> {
if let Some(price) = test_price {
return Ok(TickerResponse {
symbol: symbol.to_string(),
price,
min_tick: None,
min_order: None,
volume: None,
num_trades: None,
open_interest: None,
funding_rate: None,
oracle_price: None,
});
}
Ok(TickerResponse {
symbol: symbol.to_string(),
price: Decimal::new(50000, 0),
min_tick: None,
min_order: None,
volume: None,
num_trades: None,
open_interest: None,
funding_rate: None,
oracle_price: None,
})
}
async fn get_filled_orders(&self, symbol: &str) -> Result<FilledOrdersResponse, DexError> {
let orders = self.filled_orders.read().await;
let symbol_orders = orders.get(symbol).cloned().unwrap_or_default();
Ok(FilledOrdersResponse {
orders: symbol_orders,
})
}
async fn get_canceled_orders(&self, symbol: &str) -> Result<CanceledOrdersResponse, DexError> {
let orders = self.canceled_orders.read().await;
let symbol_orders = orders.get(symbol).cloned().unwrap_or_default();
Ok(CanceledOrdersResponse {
orders: symbol_orders,
})
}
async fn get_balance(&self, _symbol: Option<&str>) -> Result<BalanceResponse, DexError> {
let endpoint = format!(
"/api/v1/account?account_index={}&api_key_index={}",
self.account_index, self.api_key_index
);
let account_response: LighterAccountResponse =
self.make_request(&endpoint, HttpMethod::Get, None).await?;
Ok(BalanceResponse {
equity: string_to_decimal(Some(account_response.total_equity))?,
balance: string_to_decimal(Some(account_response.available_balance))?,
})
}
async fn clear_filled_order(&self, symbol: &str, trade_id: &str) -> Result<(), DexError> {
let mut orders = self.filled_orders.write().await;
if let Some(symbol_orders) = orders.get_mut(symbol) {
symbol_orders.retain(|order| order.trade_id != trade_id);
}
Ok(())
}
async fn clear_all_filled_orders(&self) -> Result<(), DexError> {
let mut orders = self.filled_orders.write().await;
orders.clear();
Ok(())
}
async fn clear_canceled_order(&self, symbol: &str, order_id: &str) -> Result<(), DexError> {
let mut orders = self.canceled_orders.write().await;
if let Some(symbol_orders) = orders.get_mut(symbol) {
symbol_orders.retain(|order| order.order_id != order_id);
}
Ok(())
}
async fn clear_all_canceled_orders(&self) -> Result<(), DexError> {
let mut orders = self.canceled_orders.write().await;
orders.clear();
Ok(())
}
async fn create_order(
&self,
symbol: &str,
size: Decimal,
side: OrderSide,
price: Option<Decimal>,
_spread: Option<i64>,
) -> Result<CreateOrderResponse, DexError> {
let market_id = match symbol {
"BTC-USD" | "BTC" => 1,
"ETH-USD" | "ETH" => 2,
_ => {
log::warn!("Unknown symbol {}, using market_id=1", symbol);
1
}
};
let side_value = match side {
OrderSide::Long => 0,
OrderSide::Short => 1,
};
let tif = 0;
let base_amount = (size * Decimal::new(100_000, 0))
.to_u64()
.ok_or_else(|| DexError::Other("Invalid size amount".to_string()))?;
let price_value = if let Some(p) = price {
(p * Decimal::new(1_000_000, 0))
.to_u64()
.ok_or_else(|| DexError::Other("Invalid price".to_string()))?
} else {
return Err(DexError::Other(
"Market orders not supported yet".to_string(),
));
};
self.create_order_native(market_id, side_value, tif, base_amount, price_value, None)
.await
}
async fn create_trigger_order(
&self,
_symbol: &str,
_size: Decimal,
_side: OrderSide,
_trigger_px: Decimal,
_is_market: bool,
_tpsl: TpSl,
) -> Result<CreateOrderResponse, DexError> {
Err(DexError::Other(
"Trigger orders not implemented yet".to_string(),
))
}
async fn cancel_order(&self, _symbol: &str, _order_id: &str) -> Result<(), DexError> {
Err(DexError::Other(
"Order cancellation not implemented yet".to_string(),
))
}
async fn cancel_all_orders(&self, _symbol: Option<String>) -> Result<(), DexError> {
log::info!("Cancelling all orders for Lighter connector");
Ok(())
}
async fn cancel_orders(
&self,
_symbol: Option<String>,
_order_ids: Vec<String>,
) -> Result<(), DexError> {
Err(DexError::Other(
"Bulk cancel orders not implemented yet".to_string(),
))
}
async fn close_all_positions(&self, _symbol: Option<String>) -> Result<(), DexError> {
Err(DexError::Other(
"Close positions not implemented yet".to_string(),
))
}
async fn clear_last_trades(&self, _symbol: &str) -> Result<(), DexError> {
Ok(())
}
async fn is_upcoming_maintenance(&self) -> bool {
false
}
async fn sign_evm_65b(&self, message: &str) -> Result<String, DexError> {
use ethers::signers::{LocalWallet, Signer};
use std::str::FromStr;
let cleaned_key = self
.api_private_key_hex
.strip_prefix("0x")
.unwrap_or(&self.api_private_key_hex);
let wallet = LocalWallet::from_str(cleaned_key)
.map_err(|e| DexError::Other(format!("Invalid private key: {}", e)))?;
let signature = wallet
.sign_message(message.as_bytes())
.await
.map_err(|e| DexError::Other(format!("Signing failed: {}", e)))?;
Ok(format!("0x{}", signature))
}
async fn sign_evm_65b_with_eip191(&self, message: &str) -> Result<String, DexError> {
let prefixed = format!("\x19Ethereum Signed Message:\n{}{}", message.len(), message);
self.sign_evm_65b(&prefixed).await
}
}
pub fn create_lighter_connector(
api_key_public: String,
api_key_index: u32,
api_private_key_hex: String,
account_index: u32,
base_url: String,
websocket_url: String,
) -> Result<Box<dyn DexConnector>, DexError> {
let connector = LighterConnector::new(
api_key_public,
api_key_index,
api_private_key_hex,
account_index,
base_url,
websocket_url,
)?;
Ok(Box::new(connector))
}