Skip to content

Commit 9f76b8c

Browse files
authored
[feat] Implement schedule cancel (#123)
This PR implements the schedule_cancel functionality. https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#schedule-cancel-dead-mans-switch
1 parent 9f7c0cd commit 9f76b8c

File tree

3 files changed

+93
-2
lines changed

3 files changed

+93
-2
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use ethers::signers::LocalWallet;
2+
use log::info;
3+
4+
use hyperliquid_rust_sdk::{
5+
BaseUrl, ClientLimit, ClientOrder, ClientOrderRequest, ExchangeClient, ExchangeDataStatus,
6+
ExchangeResponseStatus,
7+
};
8+
use std::{thread::sleep, time::Duration};
9+
10+
#[tokio::main]
11+
async fn main() {
12+
env_logger::init();
13+
// Key was randomly generated for testing and shouldn't be used with any real funds
14+
let wallet: LocalWallet = "84f77cc056fb165d8081d0f218748122481dfe3d4dfa80ad9420d11f4b7faba5"
15+
.parse()
16+
.unwrap();
17+
18+
let exchange_client = ExchangeClient::new(None, wallet, Some(BaseUrl::Testnet), None, None)
19+
.await
20+
.unwrap();
21+
22+
info!("Testing Schedule Cancel Dead Man's Switch functionality...");
23+
24+
// First, place a test order that we can cancel later
25+
let order = ClientOrderRequest {
26+
asset: "ETH".to_string(),
27+
is_buy: true,
28+
reduce_only: false,
29+
limit_px: 100.0,
30+
sz: 0.01,
31+
cloid: None,
32+
order_type: ClientOrder::Limit(ClientLimit {
33+
tif: "Gtc".to_string(),
34+
}),
35+
};
36+
37+
let response = exchange_client.order(order, None).await.unwrap();
38+
info!("Test order placed: {response:?}");
39+
40+
match response {
41+
ExchangeResponseStatus::Ok(exchange_response) => {
42+
let status = &exchange_response.data.unwrap().statuses[0];
43+
match status {
44+
ExchangeDataStatus::Filled(_) => info!("Order was filled"),
45+
ExchangeDataStatus::Resting(_) => info!("Order is resting"),
46+
_ => info!("Order status: {status:?}"),
47+
}
48+
}
49+
ExchangeResponseStatus::Err(e) => {
50+
info!("Error placing order: {e}");
51+
return;
52+
}
53+
}
54+
55+
// Schedule a cancel operation 15 seconds in the future
56+
// Use chrono to for UTC timestamp
57+
let current_time = chrono::Utc::now().timestamp_millis() as u64;
58+
let cancel_time = current_time + 15000; // 15 seconds from now
59+
60+
let response = exchange_client
61+
.schedule_cancel(Some(cancel_time), None)
62+
.await
63+
.unwrap();
64+
info!("schedule_cancel response: {:?}", response);
65+
sleep(Duration::from_secs(20));
66+
}

src/exchange/actions.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,13 @@ pub struct ApproveBuilderFee {
313313
pub hyperliquid_chain: String,
314314
}
315315

316+
#[derive(Serialize, Deserialize, Debug, Clone)]
317+
#[serde(rename_all = "camelCase")]
318+
pub struct ScheduleCancel {
319+
#[serde(skip_serializing_if = "Option::is_none")]
320+
pub time: Option<u64>,
321+
}
322+
316323
impl Eip712 for ApproveBuilderFee {
317324
type Error = Eip712Error;
318325

src/exchange/exchange_client.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::signature::sign_typed_data;
22
use crate::{
33
exchange::{
44
actions::{
5-
ApproveAgent, ApproveBuilderFee, BulkCancel, BulkModify, BulkOrder, SetReferrer,
6-
UpdateIsolatedMargin, UpdateLeverage, UsdSend,
5+
ApproveAgent, ApproveBuilderFee, BulkCancel, BulkModify, BulkOrder, ScheduleCancel,
6+
SetReferrer, UpdateIsolatedMargin, UpdateLeverage, UsdSend,
77
},
88
cancel::{CancelRequest, CancelRequestCloid},
99
modify::{ClientModifyRequest, ModifyRequest},
@@ -69,6 +69,7 @@ pub enum Actions {
6969
SetReferrer(SetReferrer),
7070
ApproveBuilderFee(ApproveBuilderFee),
7171
EvmUserModify(EvmUserModify),
72+
ScheduleCancel(ScheduleCancel),
7273
}
7374

7475
impl Actions {
@@ -781,6 +782,23 @@ impl ExchangeClient {
781782

782783
self.post(action, signature, timestamp).await
783784
}
785+
786+
pub async fn schedule_cancel(
787+
&self,
788+
time: Option<u64>,
789+
wallet: Option<&LocalWallet>,
790+
) -> Result<ExchangeResponseStatus> {
791+
let wallet = wallet.unwrap_or(&self.wallet);
792+
let timestamp = next_nonce();
793+
794+
let action = Actions::ScheduleCancel(ScheduleCancel { time });
795+
let connection_id = action.hash(timestamp, self.vault_address)?;
796+
let action = serde_json::to_value(&action).map_err(|e| Error::JsonParse(e.to_string()))?;
797+
let is_mainnet = self.http_client.is_mainnet();
798+
let signature = sign_l1_action(wallet, connection_id, is_mainnet)?;
799+
800+
self.post(action, signature, timestamp).await
801+
}
784802
}
785803

786804
fn round_to_decimals(value: f64, decimals: u32) -> f64 {

0 commit comments

Comments
 (0)