use crate::{FillSubmitter, OrderSource};
use alloy::{primitives::Address, signers::Signer};
use chrono::Utc;
use futures_util::{Stream, StreamExt};
use signet_constants::SignetSystemConstants;
use signet_types::{SignedFill, SignedOrder, SigningError, UnsignedFill};
use std::collections::HashMap;
use tracing::instrument;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum FillerError {
#[error("failed to get orders: {0}")]
Source(#[source] Box<dyn core::error::Error + Send + Sync>),
#[error("no orders to fill")]
NoOrders,
#[error("cannot specify 0 target blocks")]
ZeroTargetBlocks,
#[error("failed to sign fills: {0}")]
Signing(#[from] SigningError),
#[error("failed to submit fills: {0}")]
Submission(#[source] Box<dyn core::error::Error + Send + Sync>),
}
#[derive(Debug, Clone, Copy, Default)]
pub struct FillerOptions {
pub deadline_offset: Option<u64>,
pub nonce: Option<u64>,
}
impl FillerOptions {
pub const fn new() -> Self {
Self { deadline_offset: None, nonce: None }
}
pub const fn with_deadline_offset(mut self, offset: u64) -> Self {
self.deadline_offset = Some(offset);
self
}
pub const fn with_nonce(mut self, nonce: u64) -> Self {
self.nonce = Some(nonce);
self
}
}
#[derive(Debug, Clone)]
pub struct OrdersAndFills {
pub(crate) orders: Vec<SignedOrder>,
pub(crate) fills: HashMap<u64, SignedFill>,
pub(crate) signer_address: Address,
}
impl OrdersAndFills {
pub fn orders(&self) -> &[SignedOrder] {
&self.orders
}
pub const fn fills(&self) -> &HashMap<u64, SignedFill> {
&self.fills
}
pub const fn signer_address(&self) -> Address {
self.signer_address
}
}
#[derive(Debug, Clone)]
pub struct Filler<Sign, Source, Submit> {
signer: Sign,
order_source: Source,
submitter: Submit,
constants: SignetSystemConstants,
options: FillerOptions,
}
impl<Sign, Source, Submit> Filler<Sign, Source, Submit> {
pub const fn new(
signer: Sign,
order_source: Source,
submitter: Submit,
constants: SignetSystemConstants,
options: FillerOptions,
) -> Self {
Self { signer, order_source, submitter, constants, options }
}
pub const fn signer(&self) -> &Sign {
&self.signer
}
pub const fn order_source(&self) -> &Source {
&self.order_source
}
pub const fn submitter(&self) -> &Submit {
&self.submitter
}
pub const fn constants(&self) -> &SignetSystemConstants {
&self.constants
}
pub const fn options(&self) -> &FillerOptions {
&self.options
}
}
impl<Sign, Source, Submit> Filler<Sign, Source, Submit>
where
Source: OrderSource + Send + Sync,
{
pub fn get_orders(
&self,
) -> impl Stream<Item = Result<SignedOrder, FillerError>> + Send + use<'_, Sign, Source, Submit>
{
self.order_source
.get_orders()
.map(|result| result.map_err(|e| FillerError::Source(Box::new(e))))
}
}
impl<Sign, Source, Submit> Filler<Sign, Source, Submit>
where
Sign: Signer + Send + Sync,
{
pub async fn sign_fills(
&self,
orders: Vec<SignedOrder>,
) -> Result<OrdersAndFills, FillerError> {
let mut unsigned_fill = UnsignedFill::new().with_chain(self.constants.clone());
if let Some(deadline_offset) = self.options.deadline_offset {
let deadline = Utc::now().timestamp() as u64 + deadline_offset;
unsigned_fill = unsigned_fill.with_deadline(deadline);
}
if let Some(nonce) = self.options.nonce {
unsigned_fill = unsigned_fill.with_nonce(nonce);
}
for order in &orders {
unsigned_fill = unsigned_fill.fill(order);
}
let fills = unsigned_fill.sign(&self.signer).await?;
let signer_address = self.signer.address();
Ok(OrdersAndFills { orders, fills, signer_address })
}
}
impl<Sign, Source, Submit> Filler<Sign, Source, Submit>
where
Sign: Signer + Send + Sync,
Submit: FillSubmitter + Send + Sync,
{
#[instrument(skip_all, fields(order_count = orders.len(), target_block_count))]
pub async fn fill(
&self,
orders: Vec<SignedOrder>,
target_block_count: u8,
) -> Result<Submit::Response, FillerError> {
if orders.is_empty() {
return Err(FillerError::NoOrders);
}
if target_block_count == 0 {
return Err(FillerError::ZeroTargetBlocks);
}
let orders_and_fills = self.sign_fills(orders).await?;
self.submitter
.submit_fills(orders_and_fills, target_block_count)
.await
.map_err(|error| FillerError::Submission(Box::new(error)))
}
}