use std::sync::Arc;
use alloy::primitives::Address;
use tokio::sync::Mutex as TokioMutex;
use crate::{
constants::Network,
errors::HyperliquidError,
providers::{
agent::{AgentConfig, AgentManager, AgentWallet},
batcher::{BatchConfig, OrderBatcher, OrderHandle},
nonce::NonceManager,
},
signers::HyperliquidSigner,
types::{
requests::{CancelRequest, OrderRequest},
responses::ExchangeResponseStatus,
},
};
use super::RawExchangeProvider;
type Result<T> = std::result::Result<T, HyperliquidError>;
#[derive(Clone, Debug)]
pub struct ManagedExchangeConfig {
pub batch_orders: bool,
pub batch_config: BatchConfig,
pub auto_rotate_agents: bool,
pub agent_config: AgentConfig,
pub isolate_subaccount_nonces: bool,
pub prevent_agent_address_queries: bool,
pub warn_on_high_nonce_velocity: bool,
}
impl Default for ManagedExchangeConfig {
fn default() -> Self {
Self {
batch_orders: false,
batch_config: BatchConfig::default(),
auto_rotate_agents: true,
agent_config: AgentConfig::default(),
isolate_subaccount_nonces: true,
prevent_agent_address_queries: true,
warn_on_high_nonce_velocity: true,
}
}
}
pub struct ManagedExchangeProvider<S: HyperliquidSigner> {
inner: Arc<RawExchangeProvider<S>>,
agent_manager: Option<Arc<AgentManager<S>>>,
nonce_manager: Arc<NonceManager>,
batcher: Option<Arc<OrderBatcher>>,
batcher_handle: Option<Arc<TokioMutex<Option<tokio::task::JoinHandle<()>>>>>,
config: ManagedExchangeConfig,
}
impl<S: HyperliquidSigner + Clone + 'static> ManagedExchangeProvider<S> {
pub fn builder(signer: S) -> ManagedExchangeProviderBuilder<S> {
ManagedExchangeProviderBuilder::new(signer)
}
pub async fn mainnet(signer: S) -> Result<Arc<Self>> {
Self::builder(signer)
.with_network(Network::Mainnet)
.build()
.await
}
pub async fn testnet(signer: S) -> Result<Arc<Self>> {
Self::builder(signer)
.with_network(Network::Testnet)
.build()
.await
}
pub async fn place_order(&self, order: &OrderRequest) -> Result<OrderHandle> {
let nonce = if self.config.auto_rotate_agents {
if let Some(agent_mgr) = &self.agent_manager {
let agent = agent_mgr.get_or_rotate_agent("default").await?;
agent.next_nonce()
} else {
self.nonce_manager.next_nonce(None)
}
} else {
if self.config.isolate_subaccount_nonces {
self.nonce_manager.next_nonce(None)
} else {
self.nonce_manager.next_nonce(None)
}
};
if !NonceManager::is_valid_nonce(nonce) {
return Err(HyperliquidError::InvalidRequest(
"Generated nonce is outside valid time bounds".to_string(),
));
}
if self.config.batch_orders {
if let Some(batcher) = &self.batcher {
Ok(batcher.add_order(order.clone(), nonce).await)
} else {
let result = self.inner.place_order(order).await?;
Ok(OrderHandle::Immediate(Ok(result)))
}
} else {
let result = self.inner.place_order(order).await?;
Ok(OrderHandle::Immediate(Ok(result)))
}
}
pub async fn place_order_immediate(
&self,
order: &OrderRequest,
) -> Result<ExchangeResponseStatus> {
self.inner.place_order(order).await
}
pub fn raw(&self) -> &RawExchangeProvider<S> {
&self.inner
}
pub async fn get_agent_status(&self) -> Option<Vec<(String, AgentWallet)>> {
if let Some(agent_mgr) = &self.agent_manager {
Some(agent_mgr.get_active_agents().await)
} else {
None
}
}
pub async fn shutdown(self: Arc<Self>) {
if let Some(handle_mutex) = &self.batcher_handle {
if let Some(handle) = handle_mutex.lock().await.take() {
handle.abort();
}
}
}
}
pub struct ManagedExchangeProviderBuilder<S: HyperliquidSigner> {
signer: S,
network: Network,
config: ManagedExchangeConfig,
vault_address: Option<Address>,
initial_agent: Option<String>,
builder_address: Option<Address>,
}
impl<S: HyperliquidSigner + Clone + 'static> ManagedExchangeProviderBuilder<S> {
fn new(signer: S) -> Self {
Self {
signer,
network: Network::Mainnet,
config: ManagedExchangeConfig::default(),
vault_address: None,
initial_agent: None,
builder_address: None,
}
}
pub fn with_network(mut self, network: Network) -> Self {
self.network = network;
self
}
pub fn with_auto_batching(mut self, interval: std::time::Duration) -> Self {
self.config.batch_orders = true;
self.config.batch_config.interval = interval;
self
}
pub fn with_agent_rotation(mut self, ttl: std::time::Duration) -> Self {
self.config.auto_rotate_agents = true;
self.config.agent_config.ttl = ttl;
self
}
pub fn with_agent(mut self, name: Option<String>) -> Self {
self.initial_agent = name;
self.config.auto_rotate_agents = true;
self
}
pub fn with_vault(mut self, vault: Address) -> Self {
self.vault_address = Some(vault);
self
}
pub fn with_builder(mut self, builder: Address) -> Self {
self.builder_address = Some(builder);
self
}
pub fn without_agent_rotation(mut self) -> Self {
self.config.auto_rotate_agents = false;
self
}
pub async fn build(self) -> Result<Arc<ManagedExchangeProvider<S>>> {
let raw = match self.network {
Network::Mainnet => {
if let Some(vault) = self.vault_address {
RawExchangeProvider::mainnet_vault(self.signer.clone(), vault)
} else if let Some(builder) = self.builder_address {
RawExchangeProvider::mainnet_builder(self.signer.clone(), builder)
} else {
RawExchangeProvider::mainnet(self.signer.clone())
}
}
Network::Testnet => {
if let Some(vault) = self.vault_address {
RawExchangeProvider::testnet_vault(self.signer.clone(), vault)
} else if let Some(builder) = self.builder_address {
RawExchangeProvider::testnet_builder(self.signer.clone(), builder)
} else {
RawExchangeProvider::testnet(self.signer.clone())
}
}
};
let inner = Arc::new(raw);
let agent_manager = if self.config.auto_rotate_agents {
Some(Arc::new(AgentManager::new(
self.signer,
self.config.agent_config.clone(),
self.network,
)))
} else {
None
};
let nonce_manager =
Arc::new(NonceManager::new(self.config.isolate_subaccount_nonces));
let (batcher, batcher_handle) = if self.config.batch_orders {
let (batcher, handle) = OrderBatcher::new(self.config.batch_config.clone());
let batcher = Arc::new(batcher);
let inner_clone = inner.clone();
let inner_clone2 = inner.clone();
let handle_future = tokio::spawn(async move {
handle
.run(
move |orders| {
let inner = inner_clone.clone();
Box::pin(async move {
let order_requests: Vec<OrderRequest> =
orders.iter().map(|o| o.order.clone()).collect();
match inner.bulk_orders(order_requests).await {
Ok(status) => {
orders
.iter()
.map(|_| Ok(status.clone()))
.collect()
}
Err(e) => {
let err_str = e.to_string();
orders
.iter()
.map(|_| {
Err(HyperliquidError::InvalidResponse(
err_str.clone(),
))
})
.collect()
}
}
})
},
move |cancels| {
let inner = inner_clone2.clone();
Box::pin(async move {
let cancel_requests: Vec<CancelRequest> =
cancels.iter().map(|c| c.cancel.clone()).collect();
match inner.bulk_cancel(cancel_requests).await {
Ok(status) => {
cancels
.iter()
.map(|_| Ok(status.clone()))
.collect()
}
Err(e) => {
let err_str = e.to_string();
cancels
.iter()
.map(|_| {
Err(HyperliquidError::InvalidResponse(
err_str.clone(),
))
})
.collect()
}
}
})
},
)
.await;
});
(
Some(batcher),
Some(Arc::new(TokioMutex::new(Some(handle_future)))),
)
} else {
(None, None)
};
let provider = Arc::new(ManagedExchangeProvider {
inner,
agent_manager,
nonce_manager,
batcher,
batcher_handle,
config: self.config,
});
if let Some(agent_name) = self.initial_agent {
if let Some(agent_mgr) = &provider.agent_manager {
agent_mgr.get_or_rotate_agent(&agent_name).await?;
}
}
Ok(provider)
}
}