use crate::core::types::AccountType;
#[derive(Debug, Clone)]
pub struct LighterUrls {
pub rest: &'static str,
pub ws: &'static str,
pub explorer: &'static str,
}
impl LighterUrls {
pub const MAINNET: Self = Self {
rest: "https://mainnet.zklighter.elliot.ai",
ws: "wss://mainnet.zklighter.elliot.ai/stream",
explorer: "https://explorer.elliot.ai",
};
pub const TESTNET: Self = Self {
rest: "https://testnet.zklighter.elliot.ai",
ws: "wss://testnet.zklighter.elliot.ai/stream",
explorer: "https://explorer.elliot.ai",
};
pub fn rest_url(&self) -> &str {
self.rest
}
pub fn ws_url(&self) -> &str {
self.ws
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LighterEndpoint {
Status,
Info,
CurrentHeight,
OrderBooks,
OrderBookDetails,
OrderBookOrders,
RecentTrades,
Trades,
Candlesticks,
Fundings,
ExchangeStats,
SendTx,
SendTxBatch,
NextNonce,
Account,
AccountsByL1Address,
ApiKeys,
AccountActiveOrders,
AccountInactiveOrders,
AccountTxs,
Pnl,
DepositHistory,
DepositLatest,
WithdrawHistory,
Block,
Blocks,
Transaction,
Transactions,
BlockTxs,
TxFromL1TxHash,
PublicPools,
TransferFeeInfo,
FundingRates,
ExchangeMetrics,
AccountLimits,
AccountMetadata,
PositionFunding,
Liquidations,
WithdrawalDelays,
}
impl LighterEndpoint {
pub fn path(&self) -> &'static str {
match self {
Self::Status => "/",
Self::Info => "/info",
Self::CurrentHeight => "/api/v1/currentHeight",
Self::OrderBooks => "/api/v1/orderBooks",
Self::OrderBookDetails => "/api/v1/orderBookDetails",
Self::OrderBookOrders => "/api/v1/orderBookOrders",
Self::RecentTrades => "/api/v1/recentTrades",
Self::Trades => "/api/v1/trades",
Self::Candlesticks => "/api/v1/candles",
Self::Fundings => "/api/v1/fundings",
Self::ExchangeStats => "/api/v1/exchangeStats",
Self::SendTx => "/api/v1/sendTx",
Self::SendTxBatch => "/api/v1/sendTxBatch",
Self::NextNonce => "/api/v1/nextNonce",
Self::Account => "/api/v1/account",
Self::AccountsByL1Address => "/api/v1/accountsByL1Address",
Self::ApiKeys => "/api/v1/apikeys",
Self::AccountActiveOrders => "/api/v1/accountActiveOrders",
Self::AccountInactiveOrders => "/api/v1/accountInactiveOrders",
Self::AccountTxs => "/api/v1/accountTxs",
Self::Pnl => "/api/v1/pnl",
Self::DepositHistory => "/api/v1/deposit/history",
Self::DepositLatest => "/api/v1/deposit/latest",
Self::WithdrawHistory => "/api/v1/withdraw/history",
Self::Block => "/api/v1/block",
Self::Blocks => "/api/v1/blocks",
Self::Transaction => "/api/v1/tx",
Self::Transactions => "/api/v1/txs",
Self::BlockTxs => "/api/v1/blockTxs",
Self::TxFromL1TxHash => "/api/v1/txFromL1TxHash",
Self::PublicPools => "/api/v1/publicPools",
Self::TransferFeeInfo => "/api/v1/transferFeeInfo",
Self::FundingRates => "/api/v1/funding-rates",
Self::ExchangeMetrics => "/api/v1/exchangeMetrics",
Self::AccountLimits => "/api/v1/accountLimits",
Self::AccountMetadata => "/api/v1/accountMetadata",
Self::PositionFunding => "/api/v1/positionFunding",
Self::Liquidations => "/api/v1/liquidations",
Self::WithdrawalDelays => "/api/v1/withdrawalDelays",
}
}
pub fn requires_auth(&self) -> bool {
match self {
Self::Status
| Self::Info
| Self::CurrentHeight
| Self::OrderBooks
| Self::OrderBookDetails
| Self::OrderBookOrders
| Self::RecentTrades
| Self::Trades
| Self::Candlesticks
| Self::Fundings
| Self::ExchangeStats
| Self::Block
| Self::Blocks
| Self::Transaction
| Self::Transactions
| Self::BlockTxs
| Self::TxFromL1TxHash
| Self::PublicPools
| Self::TransferFeeInfo
| Self::FundingRates
| Self::ExchangeMetrics => false,
_ => true,
}
}
pub fn method(&self) -> &'static str {
match self {
Self::SendTx | Self::SendTxBatch => "POST",
_ => "GET",
}
}
}
pub fn format_symbol(base: &str, quote: &str, account_type: AccountType) -> String {
match account_type {
AccountType::Spot | AccountType::Margin => {
format!("{}/{}", base.to_uppercase(), quote.to_uppercase())
}
AccountType::FuturesCross | AccountType::FuturesIsolated => {
base.to_uppercase()
}
_ => {
format!("{}/{}", base.to_uppercase(), quote.to_uppercase())
}
}
}
pub fn normalize_symbol(input: &str) -> String {
let upper = input.to_uppercase();
let clean = upper.replace(['-', '_'], "");
let clean = if clean.ends_with("PERP") {
&clean[..clean.len() - 4]
} else {
&clean
};
if clean.ends_with("USDC") && clean.len() > 4 {
let base = &clean[..clean.len() - 4];
format!("{}/USDC", base)
} else {
clean.to_string()
}
}
pub fn symbol_to_market_id(base: &str) -> Option<u16> {
match base.to_uppercase().as_str() {
"ETH" => Some(0),
"BTC" => Some(1),
"SOL" => Some(2),
"ARB" => Some(3),
"OP" => Some(4),
"DOGE" => Some(5),
"MATIC" | "POL" => Some(6),
"AVAX" => Some(7),
"LINK" => Some(8),
"SUI" => Some(9),
"1000PEPE" | "PEPE" => Some(10),
"WIF" => Some(11),
"SEI" => Some(12),
"AAVE" => Some(13),
"NEAR" => Some(14),
"WLD" => Some(15),
"FTM" | "S" => Some(16),
"BONK" => Some(17),
"APT" => Some(19),
"BNB" => Some(25),
_ => None,
}
}
pub fn map_kline_interval(interval: &str) -> &'static str {
match interval {
"1m" => "1m",
"5m" => "5m",
"15m" => "15m",
"1h" | "60m" => "1h",
"4h" | "240m" => "4h",
"1d" | "1D" => "1d",
_ => "1h", }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_symbol() {
assert_eq!(normalize_symbol("BTCUSDC"), "BTC/USDC");
assert_eq!(normalize_symbol("ETH-PERP"), "ETH");
assert_eq!(normalize_symbol("eth"), "ETH");
assert_eq!(normalize_symbol("SOL-USDC"), "SOL/USDC");
assert_eq!(normalize_symbol("ETHUSDC"), "ETH/USDC");
}
#[test]
fn test_format_symbol() {
assert_eq!(
format_symbol("ETH", "USDC", AccountType::Spot),
"ETH/USDC"
);
assert_eq!(
format_symbol("ETH", "USDC", AccountType::FuturesCross),
"ETH"
);
}
#[test]
fn test_map_kline_interval() {
assert_eq!(map_kline_interval("1m"), "1m");
assert_eq!(map_kline_interval("1h"), "1h");
assert_eq!(map_kline_interval("1d"), "1d");
assert_eq!(map_kline_interval("invalid"), "1h");
}
}