renegade_sdk/renegade_wallet_client/actions/
cancel_order.rs

1//! Cancels an order in the wallet
2
3use alloy::primitives::U256;
4use renegade_darkpool_types::intent::DarkpoolStateIntent;
5use renegade_external_api::{
6    http::order::{CANCEL_ORDER_ROUTE, CancelOrderRequest, CancelOrderResponse},
7    types::{ApiOrder, ApiPublicIntentPermit, OrderAuth, SignatureWithNonce},
8};
9use renegade_solidity_abi::v2::IDarkpoolV2::{
10    PublicIntentPermit, SignatureWithNonce as SolSignatureWithNonce,
11};
12use uuid::Uuid;
13
14use crate::{
15    RenegadeClientError,
16    actions::{NON_BLOCKING_PARAM, construct_http_path},
17    client::RenegadeClient,
18    websocket::{DEFAULT_TASK_TIMEOUT, TaskWaiter},
19};
20
21/// The cancel domain separator, matching the contract's `CANCEL_DOMAIN`
22const CANCEL_DOMAIN: &[u8] = b"cancel";
23
24// --- Public Actions --- //
25impl RenegadeClient {
26    /// Cancels the order with the given ID. Waits for the order cancellation
27    /// task to complete before returning.
28    pub async fn cancel_order(&self, order_id: Uuid) -> Result<(), RenegadeClientError> {
29        let request = self.build_cancel_order_request(order_id).await?;
30
31        let path = self.build_cancel_order_request_path(order_id, false)?;
32
33        self.relayer_client.post::<_, CancelOrderResponse>(&path, request).await?;
34
35        Ok(())
36    }
37
38    /// Enqueues an order cancellation task in the relayer. Returns a
39    /// `TaskWaiter` that can be used to await task completion.
40    pub async fn enqueue_order_cancellation(
41        &self,
42        order_id: Uuid,
43    ) -> Result<TaskWaiter, RenegadeClientError> {
44        let request = self.build_cancel_order_request(order_id).await?;
45
46        let path = self.build_cancel_order_request_path(order_id, true)?;
47
48        let CancelOrderResponse { task_id, .. } = self.relayer_client.post(&path, request).await?;
49
50        // Create a task waiter for the task
51        let task_waiter = self.watch_task(task_id, DEFAULT_TASK_TIMEOUT).await?;
52
53        Ok(task_waiter)
54    }
55}
56
57// --- Private Helpers --- //
58impl RenegadeClient {
59    /// Builds the order cancellation request from the given order ID
60    async fn build_cancel_order_request(
61        &self,
62        order_id: Uuid,
63    ) -> Result<CancelOrderRequest, RenegadeClientError> {
64        let (order, auth) = self.get_order_with_auth(order_id).await?;
65
66        let cancel_signature = if let OrderAuth::PublicOrder { permit, intent_signature } = auth {
67            self.build_ring0_cancel_signature(permit, intent_signature)?
68        } else {
69            self.build_private_cancel_signature(order)?
70        };
71
72        Ok(CancelOrderRequest { cancel_signature })
73    }
74
75    /// Builds the request path for the cancel order endpoint
76    fn build_cancel_order_request_path(
77        &self,
78        order_id: Uuid,
79        non_blocking: bool,
80    ) -> Result<String, RenegadeClientError> {
81        let path = construct_http_path!(CANCEL_ORDER_ROUTE, "account_id" => self.get_account_id(), "order_id" => order_id);
82        let query_string =
83            serde_urlencoded::to_string(&[(NON_BLOCKING_PARAM, non_blocking.to_string())])
84                .map_err(RenegadeClientError::serde)?;
85
86        Ok(format!("{path}?{query_string}"))
87    }
88
89    // --- Signing Helpers --- //
90
91    /// Build the cancel signature for a ring0 (public) order
92    ///
93    /// The cancel digest is: sign over `"cancel" || intentNullifier` where
94    /// `intentNullifier = keccak256(abi.encode(permit) ||
95    /// originalIntentNonce)`.
96    fn build_ring0_cancel_signature(
97        &self,
98        permit: ApiPublicIntentPermit,
99        intent_signature: SignatureWithNonce,
100    ) -> Result<SignatureWithNonce, RenegadeClientError> {
101        let chain_id = self.get_chain_id();
102        let signer = self.get_account_signer();
103
104        // Convert API types to solidity-abi types
105        let permit: PublicIntentPermit = permit.into();
106        let sol_intent_sig: SolSignatureWithNonce = intent_signature.into();
107
108        // Compute intent nullifier: H(intentHash || originalNonce)
109        let intent_nullifier = permit.compute_nullifier(sol_intent_sig.nonce);
110
111        // Build cancel payload: CANCEL_DOMAIN || intentNullifier
112        let nullifier_bytes = intent_nullifier.to_be_bytes::<{ U256::BYTES }>();
113        let cancel_payload = [CANCEL_DOMAIN, nullifier_bytes.as_slice()].concat();
114
115        // Sign: H(H(cancel_payload) || nonce || chainId)
116        let sol_sig = SolSignatureWithNonce::sign(&cancel_payload, chain_id, signer)
117            .map_err(RenegadeClientError::signing)?;
118
119        Ok(sol_sig.into())
120    }
121
122    /// Build the cancel signature for a ring1+ (private) order
123    ///
124    /// Ring1+ cancel: sign the Poseidon nullifier bytes, properly incorporating
125    /// nonce + chainId via `SignatureWithNonce::sign`.
126    fn build_private_cancel_signature(
127        &self,
128        order: ApiOrder,
129    ) -> Result<SignatureWithNonce, RenegadeClientError> {
130        let chain_id = self.get_chain_id();
131        let signer = self.get_account_signer();
132
133        let intent: DarkpoolStateIntent = order.into();
134        let nullifier = intent.compute_nullifier();
135        let nullifier_bytes = nullifier.to_bytes_be();
136
137        let sol_sig = SolSignatureWithNonce::sign(&nullifier_bytes, chain_id, signer)
138            .map_err(RenegadeClientError::signing)?;
139
140        Ok(sol_sig.into())
141    }
142}