hyperliquid_sdk_rs/providers/
agent.rs1use crate::{
4 errors::HyperliquidError, providers::nonce::NonceManager, signers::HyperliquidSigner,
5 Network,
6};
7use alloy::primitives::Address;
8use alloy::signers::local::PrivateKeySigner;
9use std::sync::Arc;
10use std::time::{Duration, Instant};
11use tokio::sync::RwLock;
12
13#[derive(Clone)]
15pub struct AgentWallet {
16 pub address: Address,
18 pub signer: PrivateKeySigner,
20 pub created_at: Instant,
22 pub nonce_manager: Arc<NonceManager>,
24 pub status: AgentStatus,
26}
27
28#[derive(Clone, Debug, PartialEq)]
29pub enum AgentStatus {
30 Active,
32 PendingRotation,
34 Deregistered,
36}
37
38impl AgentWallet {
39 pub fn new(signer: PrivateKeySigner) -> Self {
41 Self {
42 address: signer.address(),
43 signer,
44 created_at: Instant::now(),
45 nonce_manager: Arc::new(NonceManager::new(false)), status: AgentStatus::Active,
47 }
48 }
49
50 pub fn should_rotate(&self, ttl: Duration) -> bool {
52 match self.status {
53 AgentStatus::Active => self.created_at.elapsed() > ttl,
54 AgentStatus::PendingRotation | AgentStatus::Deregistered => true,
55 }
56 }
57
58 pub fn next_nonce(&self) -> u64 {
60 self.nonce_manager.next_nonce(None)
61 }
62}
63
64#[derive(Clone, Debug)]
66pub struct AgentConfig {
67 pub ttl: Duration,
69 pub health_check_interval: Duration,
71 pub proactive_rotation_buffer: Duration,
73}
74
75impl Default for AgentConfig {
76 fn default() -> Self {
77 Self {
78 ttl: Duration::from_secs(23 * 60 * 60), health_check_interval: Duration::from_secs(300), proactive_rotation_buffer: Duration::from_secs(60 * 60), }
82 }
83}
84
85pub struct AgentManager<S: HyperliquidSigner> {
87 master_signer: S,
89 agents: Arc<RwLock<std::collections::HashMap<String, AgentWallet>>>,
91 config: AgentConfig,
93 network: Network,
95}
96
97impl<S: HyperliquidSigner + Clone> AgentManager<S> {
98 pub fn new(master_signer: S, config: AgentConfig, network: Network) -> Self {
100 Self {
101 master_signer,
102 agents: Arc::new(RwLock::new(std::collections::HashMap::new())),
103 config,
104 network,
105 }
106 }
107
108 pub async fn get_or_rotate_agent(
110 &self,
111 name: &str,
112 ) -> Result<AgentWallet, HyperliquidError> {
113 let mut agents = self.agents.write().await;
114
115 if let Some(agent) = agents.get(name) {
117 let effective_ttl = self
118 .config
119 .ttl
120 .saturating_sub(self.config.proactive_rotation_buffer);
121
122 if !agent.should_rotate(effective_ttl) {
123 return Ok(agent.clone());
124 }
125
126 let mut agent_mut = agent.clone();
128 agent_mut.status = AgentStatus::PendingRotation;
129 agents.insert(name.to_string(), agent_mut);
130 }
131
132 let new_agent = self.create_new_agent(name).await?;
134 agents.insert(name.to_string(), new_agent.clone());
135
136 Ok(new_agent)
137 }
138
139 async fn create_new_agent(
141 &self,
142 name: &str,
143 ) -> Result<AgentWallet, HyperliquidError> {
144 let agent_signer = PrivateKeySigner::random();
146 let agent_wallet = AgentWallet::new(agent_signer.clone());
147
148 self.approve_agent_internal(agent_wallet.address, Some(name.to_string()))
151 .await?;
152
153 Ok(agent_wallet)
154 }
155
156 async fn approve_agent_internal(
158 &self,
159 agent_address: Address,
160 name: Option<String>,
161 ) -> Result<(), HyperliquidError> {
162 use crate::providers::RawExchangeProvider;
163
164 let raw_provider = match self.network {
166 Network::Mainnet => RawExchangeProvider::mainnet(self.master_signer.clone()),
167 Network::Testnet => RawExchangeProvider::testnet(self.master_signer.clone()),
168 };
169
170 raw_provider.approve_agent(agent_address, name).await?;
172
173 Ok(())
174 }
175
176 pub async fn get_active_agents(&self) -> Vec<(String, AgentWallet)> {
178 let agents = self.agents.read().await;
179 agents
180 .iter()
181 .filter(|(_, agent)| agent.status == AgentStatus::Active)
182 .map(|(name, agent)| (name.clone(), agent.clone()))
183 .collect()
184 }
185
186 pub async fn mark_deregistered(&self, name: &str) {
188 let mut agents = self.agents.write().await;
189 if let Some(agent) = agents.get_mut(name) {
190 agent.status = AgentStatus::Deregistered;
191 }
192 }
193
194 pub async fn cleanup_deregistered(&self) {
196 let mut agents = self.agents.write().await;
197 agents.retain(|_, agent| agent.status != AgentStatus::Deregistered);
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_agent_rotation_check() {
207 let signer = PrivateKeySigner::random();
208 let agent = AgentWallet::new(signer);
209
210 assert!(!agent.should_rotate(Duration::from_secs(24 * 60 * 60)));
212
213 assert!(agent.should_rotate(Duration::ZERO));
215 }
216
217 #[test]
218 fn test_agent_nonce_generation() {
219 let signer = PrivateKeySigner::random();
220 let agent = AgentWallet::new(signer);
221
222 let nonce1 = agent.next_nonce();
223 let nonce2 = agent.next_nonce();
224
225 assert!(nonce2 > nonce1);
226 }
227}