df_hyperliquid_core/account/keys/agents/
approve.rs1use crate::account::Signature;
5use crate::account::keys::agents::name::{AgentName, VALID_UNTIL};
6use crate::client::Network;
7use crate::client::http::body;
8use crate::utils::Address;
9use crate::utils::sign::{Field, Map, NonceKey, UserAction, UserSigned, Value};
10
11pub const MAX_AGENT_VALIDITY_MS: u64 = 180 * 86_400_000;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
16#[error("an agent's approval ends ahead of now ({now}) and within 180 days, which {until} does not")]
17pub struct BadExpiry {
18 pub until: u64,
19 pub now: u64,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct ApproveAgent {
27 agent: Address,
28 name: String,
29 null: bool,
31 nonce: u64,
32}
33
34impl ApproveAgent {
35 #[must_use]
37 pub fn new(agent: Address, name: &str, nonce: u64) -> ApproveAgent {
38 ApproveAgent { agent, name: name.to_owned(), null: false, nonce }
39 }
40
41 #[must_use]
44 pub const fn unnamed(agent: Address, nonce: u64) -> ApproveAgent {
45 ApproveAgent { agent, name: String::new(), null: true, nonce }
46 }
47
48 #[must_use]
50 pub fn named(agent: Address, name: &AgentName, nonce: u64) -> ApproveAgent {
51 ApproveAgent::new(agent, name.as_str(), nonce)
52 }
53
54 pub fn expiring(
57 agent: Address,
58 name: &AgentName,
59 until: u64,
60 now: u64,
61 nonce: u64,
62 ) -> Result<ApproveAgent, BadExpiry> {
63 let ahead = until.checked_sub(now).filter(|ahead| *ahead > 0 && *ahead <= MAX_AGENT_VALIDITY_MS);
64 match ahead {
65 Some(_) => Ok(ApproveAgent::new(agent, &format!("{name}{VALID_UNTIL}{until}"), nonce)),
66 None => Err(BadExpiry { until, now }),
67 }
68 }
69
70 #[must_use]
71 pub const fn agent(&self) -> Address {
72 self.agent
73 }
74
75 #[must_use]
77 pub fn name(&self) -> Option<&str> {
78 Some(self.name.as_str()).filter(|name| !name.is_empty())
79 }
80}
81
82impl UserAction for ApproveAgent {
83 fn user_signed(&self) -> UserSigned<'_> {
84 UserSigned::new("approveAgent", "HyperliquidTransaction:ApproveAgent", NonceKey::Nonce, self.nonce)
85 .field("agentAddress", Field::Address(self.agent))
86 .field("agentName", Field::String(&self.name))
87 }
88
89 fn action(&self, network: Network) -> Map {
90 let action = self.user_signed().action(network);
91 if !self.null {
92 return action;
93 }
94 action.into_pairs().fold(Map::new(), |map, (key, value)| match key == "agentName" {
95 true => map.put(key, Value::Nil),
96 false => map.put(key, value),
97 })
98 }
99
100 fn wire(&self, network: Network) -> String {
101 self.action(network).to_json().compact()
102 }
103
104 fn body(&self, signature: &Signature, network: Network) -> String {
105 body::user_signed(&self.action(network), self.nonce, signature)
106 }
107}
108
109#[cfg(test)]
110#[path = "tests/approve.rs"]
111mod tests;