use alloy::primitives::Address;
use alloy::providers::{DynProvider, Provider, ProviderBuilder};
use alloy::signers::local::PrivateKeySigner;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use crate::chain::markets::MarketsClient;
use crate::chain::orders::OrdersClient;
use crate::chain::redeem::RedeemClient;
use crate::chain::tokens::TokensClient;
use crate::chain::vault::VaultClient;
use crate::config::StrikeConfig;
use crate::error::{Result, StrikeError};
use crate::events::subscribe::EventStream;
use crate::indexer::client::IndexerClient;
use crate::nonce::NonceSender;
pub type NonceSenderRef = Option<Arc<Mutex<NonceSender>>>;
pub struct StrikeClientBuilder {
config: StrikeConfig,
rpc_url: Option<String>,
wss_url: Option<String>,
indexer_url: Option<String>,
private_key: Option<String>,
}
impl StrikeClientBuilder {
pub fn with_rpc(mut self, url: &str) -> Self {
self.rpc_url = Some(url.to_string());
self
}
pub fn with_wss(mut self, url: &str) -> Self {
self.wss_url = Some(url.to_string());
self
}
pub fn with_indexer(mut self, url: &str) -> Self {
self.indexer_url = Some(url.to_string());
self
}
pub fn with_private_key(mut self, key: &str) -> Self {
self.private_key = Some(key.to_string());
self
}
pub fn build(self) -> Result<StrikeClient> {
let rpc_url = self.rpc_url.unwrap_or(self.config.rpc_url.clone());
let wss_url = self.wss_url.unwrap_or(self.config.wss_url.clone());
let indexer_url = self.indexer_url.unwrap_or(self.config.indexer_url.clone());
if rpc_url.is_empty() {
return Err(StrikeError::Config("RPC URL is required".into()));
}
let rpc_parsed: reqwest::Url = rpc_url
.parse()
.map_err(|e| StrikeError::Config(format!("invalid RPC URL: {e}")))?;
let (provider, signer_addr) = if let Some(key) = &self.private_key {
let signer: PrivateKeySigner = key
.parse()
.map_err(|e| StrikeError::Config(format!("invalid private key: {e}")))?;
let addr = signer.address();
let wallet = alloy::network::EthereumWallet::from(signer);
let p = ProviderBuilder::new()
.wallet(wallet)
.connect_http(rpc_parsed);
(DynProvider::new(p), Some(addr))
} else {
let p = ProviderBuilder::new().connect_http(rpc_parsed);
(DynProvider::new(p), None)
};
provider.client().set_poll_interval(Duration::from_millis(
self.config.tx.receipt_poll_interval_ms,
));
Ok(StrikeClient {
provider,
config: self.config,
signer_addr,
wss_url,
indexer_url,
nonce_sender: None,
})
}
}
pub struct StrikeClient {
provider: DynProvider,
config: StrikeConfig,
signer_addr: Option<Address>,
wss_url: String,
indexer_url: String,
nonce_sender: NonceSenderRef,
}
impl Clone for StrikeClient {
fn clone(&self) -> Self {
Self {
provider: self.provider.clone(),
config: self.config.clone(),
signer_addr: self.signer_addr,
wss_url: self.wss_url.clone(),
indexer_url: self.indexer_url.clone(),
nonce_sender: self.nonce_sender.clone(),
}
}
}
impl StrikeClient {
#[allow(clippy::new_ret_no_self)]
pub fn new(config: StrikeConfig) -> StrikeClientBuilder {
StrikeClientBuilder {
config,
rpc_url: None,
wss_url: None,
indexer_url: None,
private_key: None,
}
}
pub async fn init_nonce_sender(&mut self) -> Result<()> {
let signer = self.signer_addr.ok_or(StrikeError::NoWallet)?;
let ns = NonceSender::new(self.provider.clone(), signer, self.config.tx.clone())
.await
.map_err(StrikeError::from)?;
self.nonce_sender = Some(Arc::new(Mutex::new(ns)));
Ok(())
}
pub fn nonce_sender(&self) -> NonceSenderRef {
self.nonce_sender.clone()
}
pub fn orders(&self) -> OrdersClient<'_> {
OrdersClient::new(
&self.provider,
self.signer_addr,
&self.config,
self.nonce_sender.clone(),
)
}
pub fn vault(&self) -> VaultClient<'_> {
VaultClient::new(
&self.provider,
self.signer_addr,
&self.config,
self.nonce_sender.clone(),
)
}
pub fn redeem(&self) -> RedeemClient<'_> {
RedeemClient::new(
&self.provider,
self.signer_addr,
&self.config,
self.nonce_sender.clone(),
)
}
pub fn tokens(&self) -> TokensClient<'_> {
TokensClient::new(&self.provider, self.signer_addr, &self.config)
}
pub fn markets(&self) -> MarketsClient<'_> {
MarketsClient::new(&self.provider, &self.config)
}
pub async fn events(&self) -> Result<EventStream> {
EventStream::connect(
&self.wss_url,
self.config.addresses.market_factory,
self.config.addresses.batch_auction,
)
.await
}
pub async fn scan_orders(
&self,
from_block: u64,
owner: Address,
) -> Result<
std::collections::HashMap<
u64,
(Vec<alloy::primitives::U256>, Vec<alloy::primitives::U256>),
>,
> {
crate::events::scan::scan_live_orders(
&self.provider,
self.config.addresses.order_book,
owner,
from_block,
)
.await
}
pub fn indexer(&self) -> IndexerClient {
IndexerClient::new(&self.indexer_url)
}
pub async fn block_number(&self) -> Result<u64> {
self.provider
.get_block_number()
.await
.map_err(StrikeError::Rpc)
}
pub fn signer_address(&self) -> Option<Address> {
self.signer_addr
}
pub fn config(&self) -> &StrikeConfig {
&self.config
}
pub fn provider(&self) -> &DynProvider {
&self.provider
}
}