strike_sdk/chain/
orders.rs1use alloy::primitives::{Address, Bytes, U256};
4use alloy::providers::DynProvider;
5use alloy::rpc::types::TransactionRequest;
6use alloy::sol_types::{SolCall, SolEvent};
7use std::sync::Arc;
8use tokio::sync::Mutex;
9use tracing::info;
10
11use crate::chain::send_tx;
12use crate::config::StrikeConfig;
13use crate::contracts::OrderBook;
14use crate::error::{Result, StrikeError};
15use crate::nonce::NonceSender;
16use crate::types::{OrderParam, PlacedOrder, Side};
17
18pub struct OrdersClient<'a> {
20 provider: &'a DynProvider,
21 signer_addr: Option<Address>,
22 config: &'a StrikeConfig,
23 nonce_sender: Option<Arc<Mutex<NonceSender>>>,
24}
25
26impl<'a> OrdersClient<'a> {
27 pub(crate) fn new(
28 provider: &'a DynProvider,
29 signer_addr: Option<Address>,
30 config: &'a StrikeConfig,
31 nonce_sender: Option<Arc<Mutex<NonceSender>>>,
32 ) -> Self {
33 Self {
34 provider,
35 signer_addr,
36 config,
37 nonce_sender,
38 }
39 }
40
41 fn require_wallet(&self) -> Result<Address> {
42 self.signer_addr.ok_or(StrikeError::NoWallet)
43 }
44
45 pub async fn place(&self, market_id: u64, params: &[OrderParam]) -> Result<Vec<PlacedOrder>> {
50 self.require_wallet()?;
51
52 let contract_params: Vec<OrderBook::OrderParam> =
53 params.iter().map(|p| p.to_contract_param()).collect();
54
55 let calldata = OrderBook::placeOrdersCall {
56 marketId: U256::from(market_id),
57 params: contract_params,
58 }
59 .abi_encode();
60
61 let order_count = params.len();
62 let mut tx = TransactionRequest::default()
63 .to(self.config.addresses.order_book)
64 .input(Bytes::from(calldata).into());
65 tx.gas = Some(350_000 * order_count as u64);
66
67 let pending = send_tx(self.provider, &self.nonce_sender, tx).await?;
68
69 let tx_hash = *pending.tx_hash();
70 info!(market_id, order_count, tx = %tx_hash, "placeOrders tx sent");
71
72 let receipt = pending
73 .get_receipt()
74 .await
75 .map_err(|e| StrikeError::Contract(e.to_string()))?;
76
77 let placed = parse_placed_orders(&receipt, market_id);
78 info!(market_id, tx = %tx_hash, gas_used = receipt.gas_used, placed = placed.len(), "placeOrders confirmed");
79
80 Ok(placed)
81 }
82
83 pub async fn replace(
88 &self,
89 cancel_ids: &[U256],
90 market_id: u64,
91 params: &[OrderParam],
92 ) -> Result<Vec<PlacedOrder>> {
93 self.require_wallet()?;
94
95 let contract_params: Vec<OrderBook::OrderParam> =
96 params.iter().map(|p| p.to_contract_param()).collect();
97
98 let calldata = OrderBook::replaceOrdersCall {
99 cancelIds: cancel_ids.to_vec(),
100 marketId: U256::from(market_id),
101 params: contract_params,
102 }
103 .abi_encode();
104
105 let total_ops = cancel_ids.len() + params.len();
106 let mut tx = TransactionRequest::default()
107 .to(self.config.addresses.order_book)
108 .input(Bytes::from(calldata).into());
109 tx.gas = Some(350_000 * total_ops as u64);
110
111 let pending = send_tx(self.provider, &self.nonce_sender, tx).await?;
112
113 let tx_hash = *pending.tx_hash();
114 info!(market_id, cancels = cancel_ids.len(), places = params.len(), tx = %tx_hash, "replaceOrders tx sent");
115
116 let receipt = pending
117 .get_receipt()
118 .await
119 .map_err(|e| StrikeError::Contract(e.to_string()))?;
120
121 let placed = parse_placed_orders(&receipt, market_id);
122 info!(market_id, tx = %tx_hash, gas_used = receipt.gas_used, cancelled = cancel_ids.len(), placed = placed.len(), "replaceOrders confirmed");
123
124 Ok(placed)
125 }
126
127 pub async fn cancel(&self, order_ids: &[U256]) -> Result<()> {
131 self.require_wallet()?;
132
133 if order_ids.is_empty() {
134 return Ok(());
135 }
136
137 let calldata = OrderBook::cancelOrdersCall {
138 orderIds: order_ids.to_vec(),
139 }
140 .abi_encode();
141
142 let mut tx = TransactionRequest::default()
143 .to(self.config.addresses.order_book)
144 .input(Bytes::from(calldata).into());
145 tx.gas = Some(100_000 * order_ids.len() as u64);
146
147 let pending = send_tx(self.provider, &self.nonce_sender, tx).await?;
148
149 let tx_hash = *pending.tx_hash();
150 info!(count = order_ids.len(), tx = %tx_hash, "cancelOrders tx sent");
151
152 let receipt = pending
153 .get_receipt()
154 .await
155 .map_err(|e| StrikeError::Contract(e.to_string()))?;
156
157 info!(tx = %tx_hash, gas_used = receipt.gas_used, count = order_ids.len(), "cancelOrders confirmed");
158 Ok(())
159 }
160
161 pub async fn cancel_one(&self, order_id: U256) -> Result<()> {
163 self.require_wallet()?;
164
165 let calldata = OrderBook::cancelOrderCall { orderId: order_id }.abi_encode();
166 let mut tx = TransactionRequest::default()
167 .to(self.config.addresses.order_book)
168 .input(Bytes::from(calldata).into());
169 tx.gas = Some(200_000);
170
171 let pending = send_tx(self.provider, &self.nonce_sender, tx).await?;
172
173 let tx_hash = *pending.tx_hash();
174 let receipt = pending
175 .get_receipt()
176 .await
177 .map_err(|e| StrikeError::Contract(e.to_string()))?;
178
179 info!(order_id = %order_id, tx = %tx_hash, gas_used = receipt.gas_used, "cancelOrder confirmed");
180 Ok(())
181 }
182}
183
184fn parse_placed_orders(
186 receipt: &alloy::rpc::types::TransactionReceipt,
187 market_id: u64,
188) -> Vec<PlacedOrder> {
189 let mut placed = Vec::new();
190 for log in receipt.inner.logs() {
191 if let Ok(event) = OrderBook::OrderPlaced::decode_log(&log.inner) {
192 placed.push(PlacedOrder {
193 order_id: event.orderId,
194 side: Side::try_from(event.side).unwrap_or(Side::Bid),
195 market_id,
196 });
197 }
198 }
199 placed
200}