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 if !receipt.status() {
78 return Err(StrikeError::Contract(format!(
79 "placeOrders reverted (market_id={market_id}, tx={tx_hash}, gas_used={})",
80 receipt.gas_used
81 )));
82 }
83
84 let placed = parse_placed_orders(&receipt, market_id);
85 info!(market_id, tx = %tx_hash, gas_used = receipt.gas_used, placed = placed.len(), "placeOrders confirmed");
86
87 Ok(placed)
88 }
89
90 pub async fn replace(
95 &self,
96 cancel_ids: &[U256],
97 market_id: u64,
98 params: &[OrderParam],
99 ) -> Result<Vec<PlacedOrder>> {
100 self.require_wallet()?;
101
102 let contract_params: Vec<OrderBook::OrderParam> =
103 params.iter().map(|p| p.to_contract_param()).collect();
104
105 let calldata = OrderBook::replaceOrdersCall {
106 cancelIds: cancel_ids.to_vec(),
107 marketId: U256::from(market_id),
108 params: contract_params,
109 }
110 .abi_encode();
111
112 let total_ops = cancel_ids.len() + params.len();
113 let mut tx = TransactionRequest::default()
114 .to(self.config.addresses.order_book)
115 .input(Bytes::from(calldata).into());
116 tx.gas = Some(350_000 * total_ops as u64);
117
118 let pending = send_tx(self.provider, &self.nonce_sender, tx).await?;
119
120 let tx_hash = *pending.tx_hash();
121 info!(market_id, cancels = cancel_ids.len(), places = params.len(), tx = %tx_hash, "replaceOrders tx sent");
122
123 let receipt = pending
124 .get_receipt()
125 .await
126 .map_err(|e| StrikeError::Contract(e.to_string()))?;
127
128 if !receipt.status() {
129 return Err(StrikeError::Contract(format!(
130 "replaceOrders reverted (market_id={market_id}, tx={tx_hash}, gas_used={})",
131 receipt.gas_used
132 )));
133 }
134
135 let placed = parse_placed_orders(&receipt, market_id);
136 info!(market_id, tx = %tx_hash, gas_used = receipt.gas_used, cancelled = cancel_ids.len(), placed = placed.len(), "replaceOrders confirmed");
137
138 Ok(placed)
139 }
140
141 pub async fn cancel(&self, order_ids: &[U256]) -> Result<()> {
145 self.require_wallet()?;
146
147 if order_ids.is_empty() {
148 return Ok(());
149 }
150
151 let calldata = OrderBook::cancelOrdersCall {
152 orderIds: order_ids.to_vec(),
153 }
154 .abi_encode();
155
156 let mut tx = TransactionRequest::default()
157 .to(self.config.addresses.order_book)
158 .input(Bytes::from(calldata).into());
159 tx.gas = Some(100_000 * order_ids.len() as u64);
160
161 let pending = send_tx(self.provider, &self.nonce_sender, tx).await?;
162
163 let tx_hash = *pending.tx_hash();
164 info!(count = order_ids.len(), tx = %tx_hash, "cancelOrders tx sent");
165
166 let receipt = pending
167 .get_receipt()
168 .await
169 .map_err(|e| StrikeError::Contract(e.to_string()))?;
170
171 info!(tx = %tx_hash, gas_used = receipt.gas_used, count = order_ids.len(), "cancelOrders confirmed");
172 Ok(())
173 }
174
175 pub async fn cancel_one(&self, order_id: U256) -> Result<()> {
177 self.require_wallet()?;
178
179 let calldata = OrderBook::cancelOrderCall { orderId: order_id }.abi_encode();
180 let mut tx = TransactionRequest::default()
181 .to(self.config.addresses.order_book)
182 .input(Bytes::from(calldata).into());
183 tx.gas = Some(200_000);
184
185 let pending = send_tx(self.provider, &self.nonce_sender, tx).await?;
186
187 let tx_hash = *pending.tx_hash();
188 let receipt = pending
189 .get_receipt()
190 .await
191 .map_err(|e| StrikeError::Contract(e.to_string()))?;
192
193 info!(order_id = %order_id, tx = %tx_hash, gas_used = receipt.gas_used, "cancelOrder confirmed");
194 Ok(())
195 }
196}
197
198fn parse_placed_orders(
200 receipt: &alloy::rpc::types::TransactionReceipt,
201 market_id: u64,
202) -> Vec<PlacedOrder> {
203 let mut placed = Vec::new();
204 for log in receipt.inner.logs() {
205 if let Ok(event) = OrderBook::OrderPlaced::decode_log(&log.inner) {
206 placed.push(PlacedOrder {
207 order_id: event.orderId,
208 side: Side::try_from(event.side).unwrap_or(Side::Bid),
209 market_id,
210 });
211 }
212 }
213 placed
214}