1use crate::account::{ActionKind, Keeping, Signers};
6use crate::client::Network;
7use crate::transfer::send::amount::Amount;
8use crate::utils::Address;
9use crate::utils::sign::{Field, L1Action, Map, NonceKey, UserAction, UserSigned};
10
11pub const SPOT_DEX: &str = "spot";
13pub const PERP_DEX: &str = "";
15
16#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct SendAsset {
20 destination: String,
21 source_dex: String,
22 destination_dex: String,
23 token: String,
24 amount: Amount,
25 from_sub_account: String,
26 nonce: u64,
27}
28
29impl SendAsset {
30 pub const KEY: Signers = ActionKind::SendAsset.signers();
32
33 #[must_use]
35 pub fn new(
36 destination: Address,
37 source_dex: &str,
38 destination_dex: &str,
39 token: &str,
40 amount: Amount,
41 nonce: u64,
42 ) -> SendAsset {
43 SendAsset {
44 destination: destination.to_hex(),
45 source_dex: source_dex.to_owned(),
46 destination_dex: destination_dex.to_owned(),
47 token: token.to_owned(),
48 amount,
49 from_sub_account: String::new(),
50 nonce,
51 }
52 }
53
54 #[must_use]
58 pub fn usdc(
59 network: Network,
60 destination: Address,
61 source_dex: &str,
62 destination_dex: &str,
63 amount: Amount,
64 nonce: u64,
65 ) -> SendAsset {
66 SendAsset::new(destination, source_dex, destination_dex, network.usdc().wire, amount, nonce)
67 }
68
69 #[must_use]
71 pub fn from_sub_account(mut self, sub_account: Address) -> SendAsset {
72 self.from_sub_account = sub_account.to_hex();
73 self
74 }
75
76 #[must_use]
77 pub const fn amount(&self) -> &Amount {
78 &self.amount
79 }
80}
81
82impl UserAction for SendAsset {
83 fn user_signed(&self) -> UserSigned<'_> {
84 UserSigned::new("sendAsset", "HyperliquidTransaction:SendAsset", NonceKey::Nonce, self.nonce)
85 .field("destination", Field::String(&self.destination))
86 .field("sourceDex", Field::String(&self.source_dex))
87 .field("destinationDex", Field::String(&self.destination_dex))
88 .field("token", Field::String(&self.token))
89 .field("amount", Field::String(self.amount.as_str()))
90 .field("fromSubAccount", Field::String(&self.from_sub_account))
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct SpotSend {
97 destination: String,
98 token: String,
99 amount: Amount,
100 time: u64,
101}
102
103impl SpotSend {
104 pub const KEY: Signers = ActionKind::SpotSend.signers();
106
107 #[must_use]
108 pub fn new(destination: Address, token: &str, amount: Amount, time: u64) -> SpotSend {
109 SpotSend { destination: destination.to_hex(), token: token.to_owned(), amount, time }
110 }
111}
112
113impl UserAction for SpotSend {
114 fn user_signed(&self) -> UserSigned<'_> {
115 UserSigned::new("spotSend", "HyperliquidTransaction:SpotSend", NonceKey::Time, self.time)
116 .field("destination", Field::String(&self.destination))
117 .field("token", Field::String(&self.token))
118 .field("amount", Field::String(self.amount.as_str()))
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct UsdSend {
125 destination: String,
126 amount: Amount,
127 time: u64,
128}
129
130impl UsdSend {
131 pub const KEY: Signers = ActionKind::UsdSend.signers();
133
134 #[must_use]
135 pub fn new(destination: Address, amount: Amount, time: u64) -> UsdSend {
136 UsdSend { destination: destination.to_hex(), amount, time }
137 }
138}
139
140impl UserAction for UsdSend {
141 fn user_signed(&self) -> UserSigned<'_> {
142 UserSigned::new("usdSend", "HyperliquidTransaction:UsdSend", NonceKey::Time, self.time)
143 .field("destination", Field::String(&self.destination))
144 .field("amount", Field::String(self.amount.as_str()))
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct UsdClassTransfer {
151 amount: Amount,
152 to_perp: bool,
153 nonce: u64,
154}
155
156impl UsdClassTransfer {
157 pub const KEY: Signers = ActionKind::UsdClassTransfer.signers();
159
160 #[must_use]
161 pub const fn new(amount: Amount, to_perp: bool, nonce: u64) -> UsdClassTransfer {
162 UsdClassTransfer { amount, to_perp, nonce }
163 }
164}
165
166impl UserAction for UsdClassTransfer {
167 fn user_signed(&self) -> UserSigned<'_> {
168 UserSigned::new("usdClassTransfer", "HyperliquidTransaction:UsdClassTransfer", NonceKey::Nonce, self.nonce)
169 .field("amount", Field::String(self.amount.as_str()))
170 .field("toPerp", Field::Bool(self.to_perp))
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct Withdraw3 {
178 destination: String,
179 amount: Amount,
180 time: u64,
181}
182
183impl Withdraw3 {
184 pub const KEY: Signers = ActionKind::Withdraw3.signers();
186
187 #[must_use]
188 pub fn new(destination: Address, amount: Amount, time: u64) -> Withdraw3 {
189 Withdraw3 { destination: destination.to_hex(), amount, time }
190 }
191
192 #[must_use]
193 pub const fn amount(&self) -> &Amount {
194 &self.amount
195 }
196}
197
198impl UserAction for Withdraw3 {
199 fn user_signed(&self) -> UserSigned<'_> {
200 UserSigned::new("withdraw3", "HyperliquidTransaction:Withdraw", NonceKey::Time, self.time)
201 .field("destination", Field::String(&self.destination))
202 .field("amount", Field::String(self.amount.as_str()))
203 }
204}
205
206#[derive(Debug, Clone, PartialEq, Eq)]
210pub enum SpotOut {
211 SpotSend(SpotSend),
212 SendAsset(SendAsset),
213}
214
215impl SpotOut {
216 pub const KEY: Signers = SpotSend::KEY;
218
219 #[must_use]
221 pub fn new(destination: Address, token: &str, amount: Amount, keeping: Keeping, nonce: u64) -> SpotOut {
222 match keeping {
223 Keeping::Apart | Keeping::Native => SpotOut::SpotSend(SpotSend::new(destination, token, amount, nonce)),
224 Keeping::Unified | Keeping::Portfolio | Keeping::Unknown => {
225 SpotOut::SendAsset(SendAsset::new(destination, SPOT_DEX, SPOT_DEX, token, amount, nonce))
226 }
227 }
228 }
229}
230
231impl UserAction for SpotOut {
232 fn user_signed(&self) -> UserSigned<'_> {
233 match self {
234 SpotOut::SpotSend(send) => send.user_signed(),
235 SpotOut::SendAsset(send) => send.user_signed(),
236 }
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct AgentSendAsset {
245 account: Address,
246 source_dex: String,
247 destination_dex: String,
248 token: String,
249 amount: Amount,
250 nonce: u64,
251}
252
253impl AgentSendAsset {
254 pub const KEY: Signers = ActionKind::AgentSendAsset.signers();
256
257 #[must_use]
260 pub fn new(
261 account: Address,
262 source_dex: &str,
263 destination_dex: &str,
264 token: &str,
265 amount: Amount,
266 nonce: u64,
267 ) -> AgentSendAsset {
268 AgentSendAsset {
269 account,
270 source_dex: source_dex.to_owned(),
271 destination_dex: destination_dex.to_owned(),
272 token: token.to_owned(),
273 amount,
274 nonce,
275 }
276 }
277
278 #[must_use]
280 pub const fn nonce(&self) -> u64 {
281 self.nonce
282 }
283
284 #[must_use]
285 pub const fn amount(&self) -> &Amount {
286 &self.amount
287 }
288}
289
290impl L1Action for AgentSendAsset {
291 fn action(&self) -> Map {
292 Map::new()
293 .put("type", "agentSendAsset")
294 .put("destination", self.account.to_hex())
295 .put("sourceDex", self.source_dex.clone())
296 .put("destinationDex", self.destination_dex.clone())
297 .put("token", self.token.clone())
298 .put("amount", self.amount.as_str().to_owned())
299 .put("fromSubAccount", "")
300 .put("nonce", self.nonce)
301 }
302}
303
304#[cfg(test)]
305#[path = "tests/actions.rs"]
306mod tests;