renegade_sdk/renegade_wallet_client/actions/
place_order.rs1use std::str::FromStr;
4
5use alloy::primitives::Address;
6use renegade_circuit_types::{Amount, fixed_point::FixedPoint};
7use renegade_crypto::fields::scalar_to_u256;
8use renegade_darkpool_types::{
9 balance::{DarkpoolBalance, DarkpoolStateBalance},
10 intent::DarkpoolStateIntent,
11};
12use renegade_external_api::{
13 http::order::{CREATE_ORDER_ROUTE, CreateOrderRequest, CreateOrderResponse},
14 types::{
15 ApiIntent, ApiOrderCore, ApiPublicIntentPermit, OrderAuth, OrderType,
16 SignatureWithNonce as ApiSignatureWithNonce,
17 },
18};
19use renegade_solidity_abi::v2::IDarkpoolV2::{PublicIntentPermit, SignatureWithNonce};
20use uuid::Uuid;
21
22use crate::{
23 RenegadeClientError,
24 actions::{NON_BLOCKING_PARAM, construct_http_path},
25 client::RenegadeClient,
26 utils::unwrap_field,
27 websocket::{DEFAULT_TASK_TIMEOUT, TaskWaiter},
28};
29
30impl RenegadeClient {
32 pub fn new_order_builder(&self) -> OrderBuilder {
35 OrderBuilder::new(self.get_account_address())
36 }
37
38 pub async fn place_order(&self, built_order: BuiltOrder) -> Result<(), RenegadeClientError> {
45 let request = self.build_create_order_request(built_order).await?;
46
47 let path = self.build_create_order_request_path(false)?;
48
49 self.relayer_client.post::<_, CreateOrderResponse>(&path, request).await?;
50
51 Ok(())
52 }
53
54 pub async fn enqueue_order_placement(
62 &self,
63 built_order: BuiltOrder,
64 ) -> Result<TaskWaiter, RenegadeClientError> {
65 let request = self.build_create_order_request(built_order).await?;
66
67 let path = self.build_create_order_request_path(true)?;
68
69 let CreateOrderResponse { task_id, .. } = self.relayer_client.post(&path, request).await?;
70
71 let task_waiter = self.watch_task(task_id, DEFAULT_TASK_TIMEOUT).await?;
73 Ok(task_waiter)
74 }
75}
76
77impl RenegadeClient {
79 async fn build_create_order_request(
81 &self,
82 built_order: BuiltOrder,
83 ) -> Result<CreateOrderRequest, RenegadeClientError> {
84 let auth = self.build_order_auth(&built_order.order).await?;
85
86 Ok(CreateOrderRequest {
87 order: built_order.order,
88 auth,
89 precompute_cancellation_proof: built_order.precompute_cancellation_proof,
90 })
91 }
92
93 pub(crate) async fn build_order_auth(
95 &self,
96 order: &ApiOrderCore,
97 ) -> Result<OrderAuth, RenegadeClientError> {
98 let intent = order.get_intent();
99
100 if matches!(order.order_type, OrderType::PublicOrder) {
103 let sol_permit =
104 PublicIntentPermit { intent: intent.into(), executor: self.get_executor_address() };
105 let chain_id = self.get_chain_id();
106 let intent_signature = sol_permit
107 .sign(chain_id, self.get_account_signer())
108 .map_err(RenegadeClientError::signing)?
109 .into();
110 let permit: ApiPublicIntentPermit = sol_permit.into();
111
112 return Ok(OrderAuth::PublicOrder { permit, intent_signature });
113 }
114
115 let (mut recovery_seed_csprng, mut share_seed_csprng) = self.get_account_seeds().await?;
118 let intent_recovery_stream_seed = recovery_seed_csprng.next().unwrap();
119 let intent_share_stream_seed = share_seed_csprng.next().unwrap();
120
121 match order.order_type {
122 OrderType::NativelySettledPrivateOrder => {
123 let mut state_intent = DarkpoolStateIntent::new(
126 intent,
127 intent_share_stream_seed,
128 intent_recovery_stream_seed,
129 );
130 state_intent.compute_recovery_id();
131 let commitment = state_intent.compute_commitment();
132
133 let commitment_u256 = scalar_to_u256(&commitment);
136 let chain_id = self.get_chain_id();
137 let sig = SignatureWithNonce::sign(
138 &commitment_u256.to_be_bytes::<32>(),
139 chain_id,
140 self.get_account_signer(),
141 )
142 .map_err(RenegadeClientError::signing)?;
143 let intent_signature: ApiSignatureWithNonce = sig.into();
144 Ok(OrderAuth::NativelySettledPrivateOrder { intent_signature })
145 },
146 OrderType::RenegadeSettledPublicFillOrder
147 | OrderType::RenegadeSettledPrivateFillOrder => {
148 let state_intent = DarkpoolStateIntent::new(
151 intent,
152 intent_share_stream_seed,
153 intent_recovery_stream_seed,
154 );
155 let commitment = state_intent.compute_commitment();
156
157 let intent_signature = self.schnorr_sign(&commitment)?.into();
159
160 let out_token = order.intent.out_token;
164 let owner = order.intent.owner;
165
166 let new_output_balance = DarkpoolBalance::new(
167 out_token,
168 owner,
169 self.get_relayer_fee_recipient(),
170 self.get_schnorr_public_key(),
171 );
172
173 let balance_recovery_stream_seed = recovery_seed_csprng.next().unwrap();
174 let balance_share_stream_seed = share_seed_csprng.next().unwrap();
175
176 let state_output_balance = DarkpoolStateBalance::new(
177 new_output_balance,
178 balance_share_stream_seed,
179 balance_recovery_stream_seed,
180 );
181
182 let balance_commitment = state_output_balance.compute_commitment();
183 let new_output_balance_signature = self.schnorr_sign(&balance_commitment)?.into();
184
185 Ok(OrderAuth::RenegadeSettledOrder {
186 intent_signature,
187 new_output_balance_signature,
188 })
189 },
190 OrderType::PublicOrder => unreachable!(),
191 }
192 }
193
194 fn build_create_order_request_path(
196 &self,
197 non_blocking: bool,
198 ) -> Result<String, RenegadeClientError> {
199 let path = construct_http_path!(CREATE_ORDER_ROUTE, "account_id" => self.get_account_id());
200 let query_string =
201 serde_urlencoded::to_string(&[(NON_BLOCKING_PARAM, non_blocking.to_string())])
202 .map_err(RenegadeClientError::serde)?;
203
204 Ok(format!("{path}?{query_string}"))
205 }
206}
207
208#[derive(Debug)]
214pub struct BuiltOrder {
215 pub order: ApiOrderCore,
217 pub precompute_cancellation_proof: bool,
219}
220
221#[derive(Debug)]
223pub struct OrderBuilder {
224 owner: Address,
226 id: Option<Uuid>,
229 input_mint: Option<Address>,
231 output_mint: Option<Address>,
233 amount_in: Option<Amount>,
235 min_output_amount: Option<Amount>,
240 min_fill_size: Option<Amount>,
242 order_type: Option<OrderType>,
244 allow_external_matches: Option<bool>,
246 precompute_cancellation_proof: Option<bool>,
248}
249
250impl OrderBuilder {
251 pub fn new(owner: Address) -> Self {
253 Self {
254 owner,
255 id: None,
256 input_mint: None,
257 output_mint: None,
258 amount_in: None,
259 min_output_amount: None,
260 min_fill_size: None,
261 order_type: None,
262 allow_external_matches: None,
263 precompute_cancellation_proof: None,
264 }
265 }
266
267 pub fn with_id(mut self, id: Uuid) -> Self {
269 self.id = Some(id);
270 self
271 }
272
273 pub fn with_input_mint(mut self, input_mint: &str) -> Result<Self, RenegadeClientError> {
275 let input_mint_address =
276 Address::from_str(input_mint).map_err(RenegadeClientError::invalid_order)?;
277
278 self.input_mint = Some(input_mint_address);
279 Ok(self)
280 }
281
282 pub fn with_output_mint(mut self, output_mint: &str) -> Result<Self, RenegadeClientError> {
284 let output_mint_address =
285 Address::from_str(output_mint).map_err(RenegadeClientError::invalid_order)?;
286
287 self.output_mint = Some(output_mint_address);
288 Ok(self)
289 }
290
291 pub fn with_input_amount(mut self, amount: Amount) -> Self {
293 self.amount_in = Some(amount);
294 self
295 }
296
297 pub fn with_min_output_amount(mut self, amount: Amount) -> Self {
303 self.min_output_amount = Some(amount);
304 self
305 }
306
307 pub fn with_min_fill_size(mut self, min_fill: Amount) -> Self {
309 self.min_fill_size = Some(min_fill);
310 self
311 }
312
313 pub fn with_allow_external_matches(mut self, allow: bool) -> Self {
315 self.allow_external_matches = Some(allow);
316 self
317 }
318
319 pub fn with_order_type(mut self, order_type: OrderType) -> Self {
321 self.order_type = Some(order_type);
322 self
323 }
324
325 pub fn with_precompute_cancellation_proof(mut self, precompute: bool) -> Self {
327 self.precompute_cancellation_proof = Some(precompute);
328 self
329 }
330
331 pub fn build(self) -> Result<BuiltOrder, RenegadeClientError> {
333 let amount_in = unwrap_field!(self, amount_in);
334
335 let min_output_amount: FixedPoint = self.min_output_amount.unwrap_or_default().into();
336 let min_price = if min_output_amount == FixedPoint::from(0u64) {
337 FixedPoint::from(0u64)
338 } else {
339 min_output_amount.ceil_div_int(amount_in).into()
340 };
341
342 let order = ApiOrderCore {
343 id: self.id.unwrap_or_else(Uuid::new_v4),
344 intent: ApiIntent {
345 in_token: unwrap_field!(self, input_mint),
346 out_token: unwrap_field!(self, output_mint),
347 owner: self.owner,
348 amount_in,
349 min_price,
350 },
351 min_fill_size: self.min_fill_size.unwrap_or(0),
352 order_type: unwrap_field!(self, order_type),
353 allow_external_matches: self.allow_external_matches.unwrap_or(true),
354 };
355
356 let precompute_cancellation_proof = self.precompute_cancellation_proof.unwrap_or(false);
357
358 Ok(BuiltOrder { order, precompute_cancellation_proof })
359 }
360}