1use alloy::primitives::Address;
23use alloy::providers::{DynProvider, Provider, ProviderBuilder};
24use alloy::signers::local::PrivateKeySigner;
25use std::sync::Arc;
26use tokio::sync::Mutex;
27
28use crate::chain::markets::MarketsClient;
29use crate::chain::orders::OrdersClient;
30use crate::chain::redeem::RedeemClient;
31use crate::chain::tokens::TokensClient;
32use crate::chain::vault::VaultClient;
33use crate::config::StrikeConfig;
34use crate::error::{Result, StrikeError};
35use crate::events::subscribe::EventStream;
36use crate::indexer::client::IndexerClient;
37use crate::nonce::NonceSender;
38
39pub type NonceSenderRef = Option<Arc<Mutex<NonceSender>>>;
41
42pub struct StrikeClientBuilder {
44 config: StrikeConfig,
45 rpc_url: Option<String>,
46 wss_url: Option<String>,
47 indexer_url: Option<String>,
48 private_key: Option<String>,
49}
50
51impl StrikeClientBuilder {
52 pub fn with_rpc(mut self, url: &str) -> Self {
54 self.rpc_url = Some(url.to_string());
55 self
56 }
57
58 pub fn with_wss(mut self, url: &str) -> Self {
60 self.wss_url = Some(url.to_string());
61 self
62 }
63
64 pub fn with_indexer(mut self, url: &str) -> Self {
66 self.indexer_url = Some(url.to_string());
67 self
68 }
69
70 pub fn with_private_key(mut self, key: &str) -> Self {
75 self.private_key = Some(key.to_string());
76 self
77 }
78
79 pub fn build(self) -> Result<StrikeClient> {
83 let rpc_url = self.rpc_url.unwrap_or(self.config.rpc_url.clone());
84 let wss_url = self.wss_url.unwrap_or(self.config.wss_url.clone());
85 let indexer_url = self.indexer_url.unwrap_or(self.config.indexer_url.clone());
86
87 if rpc_url.is_empty() {
88 return Err(StrikeError::Config("RPC URL is required".into()));
89 }
90
91 let rpc_parsed: reqwest::Url = rpc_url
92 .parse()
93 .map_err(|e| StrikeError::Config(format!("invalid RPC URL: {e}")))?;
94
95 let (provider, signer_addr) = if let Some(key) = &self.private_key {
96 let signer: PrivateKeySigner = key
97 .parse()
98 .map_err(|e| StrikeError::Config(format!("invalid private key: {e}")))?;
99 let addr = signer.address();
100 let wallet = alloy::network::EthereumWallet::from(signer);
101 let p = ProviderBuilder::new()
102 .wallet(wallet)
103 .connect_http(rpc_parsed);
104 (DynProvider::new(p), Some(addr))
105 } else {
106 let p = ProviderBuilder::new().connect_http(rpc_parsed);
107 (DynProvider::new(p), None)
108 };
109
110 Ok(StrikeClient {
111 provider,
112 config: self.config,
113 signer_addr,
114 wss_url,
115 indexer_url,
116 nonce_sender: None,
117 })
118 }
119}
120
121pub struct StrikeClient {
132 provider: DynProvider,
133 config: StrikeConfig,
134 signer_addr: Option<Address>,
135 wss_url: String,
136 indexer_url: String,
137 nonce_sender: NonceSenderRef,
138}
139
140impl Clone for StrikeClient {
141 fn clone(&self) -> Self {
142 Self {
143 provider: self.provider.clone(),
144 config: self.config.clone(),
145 signer_addr: self.signer_addr,
146 wss_url: self.wss_url.clone(),
147 indexer_url: self.indexer_url.clone(),
148 nonce_sender: self.nonce_sender.clone(),
149 }
150 }
151}
152
153impl StrikeClient {
154 #[allow(clippy::new_ret_no_self)]
156 pub fn new(config: StrikeConfig) -> StrikeClientBuilder {
157 StrikeClientBuilder {
158 config,
159 rpc_url: None,
160 wss_url: None,
161 indexer_url: None,
162 private_key: None,
163 }
164 }
165
166 pub async fn init_nonce_sender(&mut self) -> Result<()> {
174 let signer = self.signer_addr.ok_or(StrikeError::NoWallet)?;
175 let ns = NonceSender::new(self.provider.clone(), signer)
176 .await
177 .map_err(StrikeError::from)?;
178 self.nonce_sender = Some(Arc::new(Mutex::new(ns)));
179 Ok(())
180 }
181
182 pub fn nonce_sender(&self) -> NonceSenderRef {
184 self.nonce_sender.clone()
185 }
186
187 pub fn orders(&self) -> OrdersClient<'_> {
189 OrdersClient::new(
190 &self.provider,
191 self.signer_addr,
192 &self.config,
193 self.nonce_sender.clone(),
194 )
195 }
196
197 pub fn vault(&self) -> VaultClient<'_> {
199 VaultClient::new(
200 &self.provider,
201 self.signer_addr,
202 &self.config,
203 self.nonce_sender.clone(),
204 )
205 }
206
207 pub fn redeem(&self) -> RedeemClient<'_> {
209 RedeemClient::new(
210 &self.provider,
211 self.signer_addr,
212 &self.config,
213 self.nonce_sender.clone(),
214 )
215 }
216
217 pub fn tokens(&self) -> TokensClient<'_> {
219 TokensClient::new(&self.provider, self.signer_addr, &self.config)
220 }
221
222 pub fn markets(&self) -> MarketsClient<'_> {
224 MarketsClient::new(&self.provider, &self.config)
225 }
226
227 pub async fn events(&self) -> Result<EventStream> {
231 EventStream::connect(
232 &self.wss_url,
233 self.config.addresses.market_factory,
234 self.config.addresses.batch_auction,
235 )
236 .await
237 }
238
239 pub async fn scan_orders(
243 &self,
244 from_block: u64,
245 owner: Address,
246 ) -> Result<
247 std::collections::HashMap<
248 u64,
249 (Vec<alloy::primitives::U256>, Vec<alloy::primitives::U256>),
250 >,
251 > {
252 crate::events::scan::scan_live_orders(
253 &self.provider,
254 self.config.addresses.order_book,
255 owner,
256 from_block,
257 )
258 .await
259 }
260
261 pub fn indexer(&self) -> IndexerClient {
263 IndexerClient::new(&self.indexer_url)
264 }
265
266 pub async fn block_number(&self) -> Result<u64> {
268 self.provider
269 .get_block_number()
270 .await
271 .map_err(StrikeError::Rpc)
272 }
273
274 pub fn signer_address(&self) -> Option<Address> {
276 self.signer_addr
277 }
278
279 pub fn config(&self) -> &StrikeConfig {
281 &self.config
282 }
283
284 pub fn provider(&self) -> &DynProvider {
286 &self.provider
287 }
288}