Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/00_symbols.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Example showing how to use the Symbol type

use ferrofluid::types::{Symbol, symbols};
use ferrofluid::types::{symbols, Symbol};

fn main() {
// Using predefined constants
Expand Down
2 changes: 1 addition & 1 deletion examples/03_exchange_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use alloy::primitives::Address;
use alloy::signers::local::PrivateKeySigner;
use ferrofluid::constants::TIF_GTC;
use ferrofluid::types::requests::OrderRequest;
use ferrofluid::{ExchangeProvider, signers::AlloySigner};
use ferrofluid::{signers::AlloySigner, ExchangeProvider};
use uuid::Uuid;

#[tokio::main]
Expand Down
2 changes: 1 addition & 1 deletion examples/04_websocket.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Example of using the WebSocket provider for real-time data

use ferrofluid::{Network, providers::WsProvider, types::ws::Message};
use ferrofluid::{providers::WsProvider, types::ws::Message, Network};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
Expand Down
2 changes: 1 addition & 1 deletion examples/05_builder_orders.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Example of using builder functionality for orders

use alloy::primitives::{B256, address};
use alloy::primitives::{address, B256};
use alloy::signers::local::PrivateKeySigner;
use ferrofluid::{providers::ExchangeProvider, signers::AlloySigner};

Expand Down
5 changes: 2 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ pub mod utils;
pub use constants::Network;
pub use errors::HyperliquidError;
pub use providers::{
ExchangeProvider, InfoProvider,
WsProvider, RawWsProvider, ManagedWsProvider, WsConfig,
ManagedExchangeProvider
ExchangeProvider, InfoProvider, ManagedExchangeProvider, ManagedWsProvider,
RawWsProvider, WsConfig, WsProvider,
};
83 changes: 49 additions & 34 deletions src/providers/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

use std::sync::Arc;
use std::time::{Duration, Instant};

use alloy::primitives::Address;
use alloy::signers::local::PrivateKeySigner;
use tokio::sync::RwLock;

use crate::{
signers::HyperliquidSigner,
errors::HyperliquidError,
providers::nonce::NonceManager,
errors::HyperliquidError, providers::nonce::NonceManager, signers::HyperliquidSigner,
Network,
};

Expand Down Expand Up @@ -48,15 +48,15 @@ impl AgentWallet {
status: AgentStatus::Active,
}
}

/// Check if agent should be rotated based on TTL
pub fn should_rotate(&self, ttl: Duration) -> bool {
match self.status {
AgentStatus::Active => self.created_at.elapsed() > ttl,
AgentStatus::PendingRotation | AgentStatus::Deregistered => true,
}
}

/// Get next nonce for this agent
pub fn next_nonce(&self) -> u64 {
self.nonce_manager.next_nonce(None)
Expand Down Expand Up @@ -106,78 +106,93 @@ impl<S: HyperliquidSigner + Clone> AgentManager<S> {
network,
}
}

/// Get or create an agent, rotating if necessary
pub async fn get_or_rotate_agent(&self, name: &str) -> Result<AgentWallet, HyperliquidError> {
pub async fn get_or_rotate_agent(
&self,
name: &str,
) -> Result<AgentWallet, HyperliquidError> {
let mut agents = self.agents.write().await;

// Check if we have an active agent
if let Some(agent) = agents.get(name) {
let effective_ttl = self.config.ttl.saturating_sub(self.config.proactive_rotation_buffer);

let effective_ttl = self
.config
.ttl
.saturating_sub(self.config.proactive_rotation_buffer);

if !agent.should_rotate(effective_ttl) {
return Ok(agent.clone());
}

// Mark for rotation
let mut agent_mut = agent.clone();
agent_mut.status = AgentStatus::PendingRotation;
agents.insert(name.to_string(), agent_mut);
}

// Create new agent
let new_agent = self.create_new_agent(name).await?;
agents.insert(name.to_string(), new_agent.clone());

Ok(new_agent)
}

/// Create and approve a new agent
async fn create_new_agent(&self, name: &str) -> Result<AgentWallet, HyperliquidError> {
async fn create_new_agent(
&self,
name: &str,
) -> Result<AgentWallet, HyperliquidError> {
// Generate new key for agent
let agent_signer = PrivateKeySigner::random();
let agent_wallet = AgentWallet::new(agent_signer.clone());

// We need to approve this agent using the exchange provider
// This is a bit circular, but we'll handle it carefully
self.approve_agent_internal(agent_wallet.address, Some(name.to_string())).await?;

self.approve_agent_internal(agent_wallet.address, Some(name.to_string()))
.await?;

Ok(agent_wallet)
}

/// Internal method to approve agent (will use exchange provider)
async fn approve_agent_internal(&self, agent_address: Address, name: Option<String>) -> Result<(), HyperliquidError> {
async fn approve_agent_internal(
&self,
agent_address: Address,
name: Option<String>,
) -> Result<(), HyperliquidError> {
use crate::providers::RawExchangeProvider;

// Create a temporary raw provider just for agent approval
let raw_provider = match self.network {
Network::Mainnet => RawExchangeProvider::mainnet(self.master_signer.clone()),
Network::Testnet => RawExchangeProvider::testnet(self.master_signer.clone()),
};

// Approve the agent
raw_provider.approve_agent(agent_address, name).await?;

Ok(())
}

/// Get all active agents
pub async fn get_active_agents(&self) -> Vec<(String, AgentWallet)> {
let agents = self.agents.read().await;
agents.iter()
agents
.iter()
.filter(|(_, agent)| agent.status == AgentStatus::Active)
.map(|(name, agent)| (name.clone(), agent.clone()))
.collect()
}

/// Mark an agent as deregistered
pub async fn mark_deregistered(&self, name: &str) {
let mut agents = self.agents.write().await;
if let Some(agent) = agents.get_mut(name) {
agent.status = AgentStatus::Deregistered;
}
}

/// Clean up deregistered agents
pub async fn cleanup_deregistered(&self) {
let mut agents = self.agents.write().await;
Expand All @@ -188,27 +203,27 @@ impl<S: HyperliquidSigner + Clone> AgentManager<S> {
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_agent_rotation_check() {
let signer = PrivateKeySigner::random();
let agent = AgentWallet::new(signer);

// Should not rotate immediately
assert!(!agent.should_rotate(Duration::from_secs(24 * 60 * 60)));

// Test with zero duration (should always rotate)
assert!(agent.should_rotate(Duration::ZERO));
}

#[test]
fn test_agent_nonce_generation() {
let signer = PrivateKeySigner::random();
let agent = AgentWallet::new(signer);

let nonce1 = agent.next_nonce();
let nonce2 = agent.next_nonce();

assert!(nonce2 > nonce1);
}
}
}
Loading