From a9e5c63df448fa1ef1b1ebd7a5ba19c159caaa16 Mon Sep 17 00:00:00 2001 From: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:35:43 +0000 Subject: [PATCH 1/5] feat(signet-orders): implement filler --- crates/orders/Cargo.toml | 2 + crates/orders/src/fee_policy.rs | 158 +++++++++++++++++ crates/orders/src/filler.rs | 214 +++++++++++++++++++++++ crates/orders/src/impls/fill_provider.rs | 20 +++ crates/orders/src/impls/mod.rs | 1 + crates/orders/src/lib.rs | 8 +- crates/orders/src/order_sender.rs | 4 +- crates/orders/src/traits.rs | 42 ++++- 8 files changed, 444 insertions(+), 5 deletions(-) create mode 100644 crates/orders/src/fee_policy.rs create mode 100644 crates/orders/src/filler.rs create mode 100644 crates/orders/src/impls/fill_provider.rs diff --git a/crates/orders/Cargo.toml b/crates/orders/Cargo.toml index 1683482..7311081 100644 --- a/crates/orders/Cargo.toml +++ b/crates/orders/Cargo.toml @@ -21,5 +21,7 @@ signet-types.workspace = true signet-zenith.workspace = true alloy.workspace = true +chrono.workspace = true futures-util.workspace = true thiserror.workspace = true +tracing.workspace = true diff --git a/crates/orders/src/fee_policy.rs b/crates/orders/src/fee_policy.rs new file mode 100644 index 0000000..04e09bd --- /dev/null +++ b/crates/orders/src/fee_policy.rs @@ -0,0 +1,158 @@ +use crate::{BundleSubmitter, FillSubmitter, OrdersAndFills, TxBuilder}; +use alloy::primitives::Address; +use alloy::{ + eips::eip2718::Encodable2718, + network::{Ethereum, Network, TransactionBuilder}, + primitives::Bytes, + providers::SendableTx, + rpc::types::mev::EthSendBundle, + transports::{RpcError, TransportErrorKind}, +}; +use signet_bundle::SignetEthBundle; +use signet_constants::SignetSystemConstants; +use tracing::instrument; + +/// Errors returned by [`FeePolicySubmitter`]. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum FeePolicyError { + /// No fills provided for submission. + #[error("no fills provided for submission")] + NoFills, + /// Failed to get block number from provider. + #[error("failed to get block number: {0}")] + BlockNumber(#[source] RpcError), + /// Failed to fill transaction. + #[error("failed to fill transaction: {0}")] + FillTransaction(#[source] RpcError), + /// Transaction fill returned builder instead of envelope. + #[error("transaction fill did not return signed envelope")] + NotEnvelope, + /// Bundle submission failed. + #[error("failed to submit bundle: {0}")] + Submission(#[source] Box), +} + +/// A [`FillSubmitter`] that wraps a [`BundleSubmitter`] and handles fee policy. +/// +/// This submitter converts [`SignedFill`]s into transactions with appropriate gas pricing, builds +/// a [`SignetEthBundle`], and submits via the wrapped submitter. +/// +/// The providers must be configured with appropriate fillers for gas, nonce, chain ID, and wallet +/// signing (e.g., via `ProviderBuilder::wallet()`). +#[derive(Debug, Clone)] +pub struct FeePolicySubmitter { + ru_provider: RuP, + host_provider: HostP, + submitter: B, + constants: SignetSystemConstants, +} + +impl FeePolicySubmitter { + /// Create a new `FeePolicySubmitter`. + pub const fn new( + ru_provider: RuP, + host_provider: HostP, + submitter: B, + constants: SignetSystemConstants, + ) -> Self { + Self { ru_provider, host_provider, submitter, constants } + } + + /// Get a reference to the rollup provider. + pub const fn ru_provider(&self) -> &RuP { + &self.ru_provider + } + + /// Get a reference to the host provider. + pub const fn host_provider(&self) -> &HostP { + &self.host_provider + } + + /// Get a reference to the inner submitter. + pub const fn submitter(&self) -> &B { + &self.submitter + } + + /// Get a reference to the system constants. + pub const fn constants(&self) -> &SignetSystemConstants { + &self.constants + } +} + +impl FillSubmitter for FeePolicySubmitter +where + RuP: TxBuilder, + HostP: TxBuilder, + B: BundleSubmitter + Send + Sync, +{ + type Response = B::Response; + type Error = FeePolicyError; + + #[instrument(skip_all, fields(order_count = orders.len(), fill_count = fills.len()))] + async fn submit_fills( + &self, + OrdersAndFills { orders, fills, signer_address }: OrdersAndFills, + ) -> Result { + if fills.is_empty() { + return Err(FeePolicyError::NoFills); + } + + // Build rollup transaction requests: fill (if present, must come first) then initiates + let mut rollup_txs = Vec::with_capacity(orders.len() + 1); + if let Some(fill) = fills.get(&self.constants.ru_chain_id()) { + let tx_request = fill.to_fill_tx(self.constants.ru_orders()); + rollup_txs + .push(sign_and_encode_tx(&self.ru_provider, tx_request, signer_address).await?); + } + for order in &orders { + let tx_request = order.to_initiate_tx(signer_address, self.constants.ru_orders()); + rollup_txs + .push(sign_and_encode_tx(&self.ru_provider, tx_request, signer_address).await?); + } + + // Build host transaction request: fill only (if present) + let host_txs = match fills.get(&self.constants.host_chain_id()) { + Some(fill) => { + let tx_request = fill.to_fill_tx(self.constants.host_orders()); + vec![sign_and_encode_tx(&self.host_provider, tx_request, signer_address).await?] + } + None => vec![], + }; + + let target_block = + self.ru_provider.get_block_number().await.map_err(FeePolicyError::BlockNumber)? + 1; + + let bundle = SignetEthBundle::new( + EthSendBundle { txs: rollup_txs, block_number: target_block, ..Default::default() }, + host_txs, + ); + + self.submitter + .submit_bundle(bundle) + .await + .map_err(|error| FeePolicyError::Submission(Box::new(error))) + } +} + +/// Sign and encode a transaction request for inclusion in a bundle. +#[instrument(skip_all)] +async fn sign_and_encode_tx( + provider: &P, + mut tx_request: N::TransactionRequest, + signer_address: Address, +) -> Result +where + N: Network, + P: TxBuilder, + N::TxEnvelope: Encodable2718, +{ + tx_request = tx_request.with_from(signer_address); + let sendable = provider.fill(tx_request).await.map_err(FeePolicyError::FillTransaction)?; + + let SendableTx::Envelope(envelope) = sendable else { + return Err(FeePolicyError::NotEnvelope); + }; + + Ok(Bytes::from(envelope.encoded_2718())) +} diff --git a/crates/orders/src/filler.rs b/crates/orders/src/filler.rs new file mode 100644 index 0000000..ac2ffc1 --- /dev/null +++ b/crates/orders/src/filler.rs @@ -0,0 +1,214 @@ +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; + +/// Errors returned by [`Filler`]. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum FillerError { + /// Order source error. + #[error("failed to get orders: {0}")] + Source(#[source] Box), + /// No orders to fill. + #[error("no orders to fill")] + NoOrders, + /// Failed to sign fills for orders. + #[error("failed to sign fills: {0}")] + Signing(#[from] SigningError), + /// Fill submission failed. + #[error("failed to submit fills: {0}")] + Submission(#[source] Box), +} + +/// Options for configuring the [`Filler`]. +#[derive(Debug, Clone, Copy, Default)] +pub struct FillerOptions { + /// Optional deadline offset in seconds for fills. + pub deadline_offset: Option, + /// Optional nonce to use for permit2 signatures. + pub nonce: Option, +} + +impl FillerOptions { + /// Create a new [`FillerOptions`] with default values. + pub const fn new() -> Self { + Self { deadline_offset: None, nonce: None } + } + + /// Set the deadline offset. + pub const fn with_deadline_offset(mut self, offset: u64) -> Self { + self.deadline_offset = Some(offset); + self + } + + /// Set the nonce. + pub const fn with_nonce(mut self, nonce: u64) -> Self { + self.nonce = Some(nonce); + self + } +} + +/// A small struct to ensure the relevant orders remain paired with the fills generated from them +/// and with the signer's address. +#[derive(Debug)] +pub struct OrdersAndFills { + pub(crate) orders: Vec, + pub(crate) fills: HashMap, + pub(crate) signer_address: Address, +} + +/// Fills orders by fetching from a source, signing fills, and submitting them. +/// +/// `Filler` is generic over: +/// - `Sign`: A [`Signer`] for signing fills +/// - `Source`: An [`OrderSource`] for fetching orders +/// - `Submit`: A [`FillSubmitter`] for submitting signed fills +#[derive(Debug, Clone)] +pub struct Filler { + signer: Sign, + order_source: Source, + submitter: Submit, + constants: SignetSystemConstants, + options: FillerOptions, +} + +impl Filler { + /// Create a new filler instance. + pub const fn new( + signer: Sign, + order_source: Source, + submitter: Submit, + constants: SignetSystemConstants, + options: FillerOptions, + ) -> Self { + Self { signer, order_source, submitter, constants, options } + } + + /// Get a reference to the signer. + pub const fn signer(&self) -> &Sign { + &self.signer + } + + /// Get a reference to the order source. + pub const fn order_source(&self) -> &Source { + &self.order_source + } + + /// Get a reference to the submitter. + pub const fn submitter(&self) -> &Submit { + &self.submitter + } + + /// Get a reference to the system constants. + pub const fn constants(&self) -> &SignetSystemConstants { + &self.constants + } + + /// Get a reference to the filler options. + pub const fn options(&self) -> &FillerOptions { + &self.options + } +} + +impl Filler +where + Source: OrderSource + Send + Sync, +{ + /// Query the source for signed orders. + pub fn get_orders( + &self, + ) -> impl Stream> + Send + use<'_, Sign, Source, Submit> + { + self.order_source + .get_orders() + .map(|result| result.map_err(|e| FillerError::Source(Box::new(e)))) + } +} + +impl Filler +where + Sign: Signer + Send + Sync, +{ + /// Sign fills for the given orders. + /// + /// Returns a map of chain ID to signed fill for each target chain. + pub async fn sign_fills( + &self, + orders: Vec, + ) -> Result { + 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 Filler +where + Sign: Signer + Send + Sync, + Submit: FillSubmitter + Send + Sync, +{ + /// Fill one or more orders. + /// + /// Signs fills for all orders and submits them via the [`FillSubmitter`]. + /// + /// Returns an error if `orders` is empty, or if signing or submission fails. + #[instrument(skip_all, fields(order_count = orders.len()))] + pub async fn fill(&self, orders: Vec) -> Result { + if orders.is_empty() { + return Err(FillerError::NoOrders); + } + + let orders_and_fills = self.sign_fills(orders).await?; + self.submitter + .submit_fills(orders_and_fills) + .await + .map_err(|error| FillerError::Submission(Box::new(error))) + } + + /// Fill orders individually, returning a response per submission. + /// + /// Returns an error if `orders` is empty, or if signing or submission fails. + #[instrument(skip_all, fields(order_count = orders.len()))] + pub async fn fill_individually( + &self, + orders: Vec, + ) -> Result, FillerError> { + if orders.is_empty() { + return Err(FillerError::NoOrders); + } + + let mut responses = Vec::with_capacity(orders.len()); + + for order in orders { + let orders_and_fills = self.sign_fills(vec![order]).await?; + let response = self + .submitter + .submit_fills(orders_and_fills) + .await + .map_err(|error| FillerError::Submission(Box::new(error)))?; + responses.push(response); + } + + Ok(responses) + } +} diff --git a/crates/orders/src/impls/fill_provider.rs b/crates/orders/src/impls/fill_provider.rs new file mode 100644 index 0000000..5d20d05 --- /dev/null +++ b/crates/orders/src/impls/fill_provider.rs @@ -0,0 +1,20 @@ +use crate::TxBuilder; +use alloy::{ + network::Network, + providers::{ + fillers::{FillProvider, TxFiller}, + Provider, SendableTx, + }, + transports::TransportResult, +}; + +impl TxBuilder for FillProvider +where + F: TxFiller, + P: Provider, + N: Network, +{ + async fn fill(&self, tx: N::TransactionRequest) -> TransportResult> { + FillProvider::fill(self, tx).await + } +} diff --git a/crates/orders/src/impls/mod.rs b/crates/orders/src/impls/mod.rs index c0e5a22..0edcadc 100644 --- a/crates/orders/src/impls/mod.rs +++ b/crates/orders/src/impls/mod.rs @@ -1 +1,2 @@ +mod fill_provider; mod tx_cache; diff --git a/crates/orders/src/lib.rs b/crates/orders/src/lib.rs index 002c02c..5167db3 100644 --- a/crates/orders/src/lib.rs +++ b/crates/orders/src/lib.rs @@ -16,8 +16,14 @@ mod impls; +mod fee_policy; +pub use fee_policy::{FeePolicyError, FeePolicySubmitter}; + +mod filler; +pub use filler::{Filler, FillerError, FillerOptions, OrdersAndFills}; + mod order_sender; pub use order_sender::{OrderSender, OrderSenderError}; mod traits; -pub use traits::{BundleSubmitter, OrderSource, OrderSubmitter}; +pub use traits::{BundleSubmitter, FillSubmitter, OrderSource, OrderSubmitter, TxBuilder}; diff --git a/crates/orders/src/order_sender.rs b/crates/orders/src/order_sender.rs index 39a5944..f7f8974 100644 --- a/crates/orders/src/order_sender.rs +++ b/crates/orders/src/order_sender.rs @@ -9,10 +9,10 @@ use signet_zenith::RollupOrders::Order; #[non_exhaustive] pub enum OrderSenderError { /// Order signing failed. - #[error("order signing error: {0}")] + #[error("failed to sign order: {0}")] Signing(#[from] SigningError), /// Order submission failed. - #[error("order submission error: {0}")] + #[error("failed to submit order: {0}")] Submission(#[source] Box), } diff --git a/crates/orders/src/traits.rs b/crates/orders/src/traits.rs index ec39646..ea66ec4 100644 --- a/crates/orders/src/traits.rs +++ b/crates/orders/src/traits.rs @@ -1,3 +1,9 @@ +use crate::OrdersAndFills; +use alloy::{ + network::{Ethereum, Network}, + providers::{Provider, SendableTx}, + transports::TransportResult, +}; use core::future::Future; use futures_util::Stream; use signet_bundle::SignetEthBundle; @@ -23,7 +29,7 @@ pub trait OrderSubmitter { /// Implementors of this trait provide access to signed orders, typically from a transaction cache. pub trait OrderSource { /// The error type returned by the stream. - type Error; + type Error: core::error::Error + Send + Sync + 'static; /// Fetch orders from the source as a stream. /// @@ -40,7 +46,7 @@ pub trait BundleSubmitter { /// The response type returned on successful submission. type Response; /// The error type returned by submission operations. - type Error; + type Error: core::error::Error + Send + Sync + 'static; /// Submit a bundle to the backend. fn submit_bundle( @@ -48,3 +54,35 @@ pub trait BundleSubmitter { bundle: SignetEthBundle, ) -> impl Future> + Send; } + +/// A provider that can fill transactions. +/// +/// This trait abstracts over [`FillProvider`] to allow filling transaction requests. +pub trait TxBuilder: Provider + Send + Sync { + /// Fill a transaction request, returning a sendable transaction. + fn fill( + &self, + tx: N::TransactionRequest, + ) -> impl Future>> + Send; +} + +/// A trait for submitting signed fills to a backend. +/// +/// Implementors handle transaction construction, gas pricing, and target block +/// determination. This decouples the [`Filler`] from provider and fee concerns. +/// +/// [`Filler`]: crate::Filler +pub trait FillSubmitter { + /// The response type returned on successful submission. + type Response; + /// The error type returned by submission operations. + type Error: core::error::Error + Send + Sync + 'static; + + /// Submit signed fills to the backend. + /// + /// The fills map contains one [`SignedFill`] per destination chain ID. + fn submit_fills( + &self, + orders_and_fills: OrdersAndFills, + ) -> impl Future> + Send; +} From 1e2dc73e03a4ae8afeeedd8aedb6fa55456d4d3b Mon Sep 17 00:00:00 2001 From: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> Date: Fri, 30 Jan 2026 19:14:34 +0000 Subject: [PATCH 2/5] tweak the docs --- crates/orders/src/fee_policy.rs | 2 +- crates/orders/src/traits.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/orders/src/fee_policy.rs b/crates/orders/src/fee_policy.rs index 04e09bd..5d2324f 100644 --- a/crates/orders/src/fee_policy.rs +++ b/crates/orders/src/fee_policy.rs @@ -39,7 +39,7 @@ pub enum FeePolicyError { /// a [`SignetEthBundle`], and submits via the wrapped submitter. /// /// The providers must be configured with appropriate fillers for gas, nonce, chain ID, and wallet -/// signing (e.g., via `ProviderBuilder::wallet()`). +/// signing (e.g., via `ProviderBuilder::with_gas_estimation()` and `ProviderBuilder::wallet()`). #[derive(Debug, Clone)] pub struct FeePolicySubmitter { ru_provider: RuP, diff --git a/crates/orders/src/traits.rs b/crates/orders/src/traits.rs index ea66ec4..d3ef84e 100644 --- a/crates/orders/src/traits.rs +++ b/crates/orders/src/traits.rs @@ -1,3 +1,5 @@ +#[cfg(doc)] +use crate::Filler; use crate::OrdersAndFills; use alloy::{ network::{Ethereum, Network}, @@ -68,10 +70,8 @@ pub trait TxBuilder: Provider + Send + Sync { /// A trait for submitting signed fills to a backend. /// -/// Implementors handle transaction construction, gas pricing, and target block -/// determination. This decouples the [`Filler`] from provider and fee concerns. -/// -/// [`Filler`]: crate::Filler +/// Implementors handle transaction construction, gas pricing, and target block determination. +/// This decouples the [`Filler`] from provider and fee concerns. pub trait FillSubmitter { /// The response type returned on successful submission. type Response; From ac29948f2e5f078c86b14da5c0019c9b2611b8e5 Mon Sep 17 00:00:00 2001 From: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:41:33 +0000 Subject: [PATCH 3/5] improver error variants for filler --- crates/orders/src/fee_policy.rs | 46 ++++++++++++++++-------- crates/orders/src/impls/fill_provider.rs | 6 +++- crates/orders/src/traits.rs | 5 ++- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/crates/orders/src/fee_policy.rs b/crates/orders/src/fee_policy.rs index 5d2324f..f106caa 100644 --- a/crates/orders/src/fee_policy.rs +++ b/crates/orders/src/fee_policy.rs @@ -4,13 +4,13 @@ use alloy::{ eips::eip2718::Encodable2718, network::{Ethereum, Network, TransactionBuilder}, primitives::Bytes, - providers::SendableTx, + providers::{fillers::FillerControlFlow, SendableTx}, rpc::types::mev::EthSendBundle, transports::{RpcError, TransportErrorKind}, }; use signet_bundle::SignetEthBundle; use signet_constants::SignetSystemConstants; -use tracing::instrument; +use tracing::{error, instrument}; /// Errors returned by [`FeePolicySubmitter`]. #[derive(Debug, thiserror::Error)] @@ -19,20 +19,29 @@ pub enum FeePolicyError { /// No fills provided for submission. #[error("no fills provided for submission")] NoFills, - /// Failed to get block number from provider. - #[error("failed to get block number: {0}")] - BlockNumber(#[source] RpcError), - /// Failed to fill transaction. - #[error("failed to fill transaction: {0}")] - FillTransaction(#[source] RpcError), - /// Transaction fill returned builder instead of envelope. - #[error("transaction fill did not return signed envelope")] - NotEnvelope, + /// RPC call failed. + #[error("RPC error: {0}")] + Rpc(#[source] RpcError), + /// Transaction is incomplete (missing required properties). + #[error("transaction missing required properties: {0:?}")] + IncompleteTransaction(Vec<(&'static str, Vec<&'static str>)>), /// Bundle submission failed. #[error("failed to submit bundle: {0}")] Submission(#[source] Box), } +impl From for FeePolicyError { + fn from(filler_control_flow: FillerControlFlow) -> Self { + match filler_control_flow { + FillerControlFlow::Missing(missing) => Self::IncompleteTransaction(missing), + FillerControlFlow::Finished | FillerControlFlow::Ready => { + error!("fill returned Builder but status is {filler_control_flow:?}"); + Self::IncompleteTransaction(Vec::new()) + } + } + } +} + /// A [`FillSubmitter`] that wraps a [`BundleSubmitter`] and handles fee policy. /// /// This submitter converts [`SignedFill`]s into transactions with appropriate gas pricing, builds @@ -40,6 +49,10 @@ pub enum FeePolicyError { /// /// The providers must be configured with appropriate fillers for gas, nonce, chain ID, and wallet /// signing (e.g., via `ProviderBuilder::with_gas_estimation()` and `ProviderBuilder::wallet()`). +/// Note that the provider's nonce filler must correctly increment nonces across all transactions +/// built within a single [`submit_fills`] call. +/// +/// [`submit_fills`]: FillSubmitter::submit_fills #[derive(Debug, Clone)] pub struct FeePolicySubmitter { ru_provider: RuP, @@ -121,7 +134,7 @@ where }; let target_block = - self.ru_provider.get_block_number().await.map_err(FeePolicyError::BlockNumber)? + 1; + self.ru_provider.get_block_number().await.map_err(FeePolicyError::Rpc)? + 1; let bundle = SignetEthBundle::new( EthSendBundle { txs: rollup_txs, block_number: target_block, ..Default::default() }, @@ -148,10 +161,13 @@ where N::TxEnvelope: Encodable2718, { tx_request = tx_request.with_from(signer_address); - let sendable = provider.fill(tx_request).await.map_err(FeePolicyError::FillTransaction)?; + let sendable = provider.fill(tx_request).await.map_err(FeePolicyError::Rpc)?; - let SendableTx::Envelope(envelope) = sendable else { - return Err(FeePolicyError::NotEnvelope); + let envelope = match sendable { + SendableTx::Envelope(envelope) => envelope, + SendableTx::Builder(tx) => { + return Err(FeePolicyError::from(provider.status(&tx))); + } }; Ok(Bytes::from(envelope.encoded_2718())) diff --git a/crates/orders/src/impls/fill_provider.rs b/crates/orders/src/impls/fill_provider.rs index 5d20d05..5ef59dd 100644 --- a/crates/orders/src/impls/fill_provider.rs +++ b/crates/orders/src/impls/fill_provider.rs @@ -2,7 +2,7 @@ use crate::TxBuilder; use alloy::{ network::Network, providers::{ - fillers::{FillProvider, TxFiller}, + fillers::{FillProvider, FillerControlFlow, TxFiller}, Provider, SendableTx, }, transports::TransportResult, @@ -17,4 +17,8 @@ where async fn fill(&self, tx: N::TransactionRequest) -> TransportResult> { FillProvider::fill(self, tx).await } + + fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow { + self.filler().status(tx) + } } diff --git a/crates/orders/src/traits.rs b/crates/orders/src/traits.rs index d3ef84e..1b29c78 100644 --- a/crates/orders/src/traits.rs +++ b/crates/orders/src/traits.rs @@ -3,7 +3,7 @@ use crate::Filler; use crate::OrdersAndFills; use alloy::{ network::{Ethereum, Network}, - providers::{Provider, SendableTx}, + providers::{fillers::FillerControlFlow, Provider, SendableTx}, transports::TransportResult, }; use core::future::Future; @@ -66,6 +66,9 @@ pub trait TxBuilder: Provider + Send + Sync { &self, tx: N::TransactionRequest, ) -> impl Future>> + Send; + + /// Return the filler's status for the given transaction request. + fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow; } /// A trait for submitting signed fills to a backend. From f2240aff92d66611a37579ee8dd9285ff94ce9eb Mon Sep 17 00:00:00 2001 From: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> Date: Mon, 2 Feb 2026 21:59:47 +0000 Subject: [PATCH 4/5] remove Filler::fill_individually and other tweaks --- crates/orders/src/fee_policy.rs | 25 ++++++++++++++----------- crates/orders/src/filler.rs | 27 --------------------------- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/crates/orders/src/fee_policy.rs b/crates/orders/src/fee_policy.rs index f106caa..83e382d 100644 --- a/crates/orders/src/fee_policy.rs +++ b/crates/orders/src/fee_policy.rs @@ -8,6 +8,7 @@ use alloy::{ rpc::types::mev::EthSendBundle, transports::{RpcError, TransportErrorKind}, }; +use futures_util::{stream, StreamExt, TryStreamExt}; use signet_bundle::SignetEthBundle; use signet_constants::SignetSystemConstants; use tracing::{error, instrument}; @@ -112,17 +113,17 @@ where } // Build rollup transaction requests: fill (if present, must come first) then initiates - let mut rollup_txs = Vec::with_capacity(orders.len() + 1); - if let Some(fill) = fills.get(&self.constants.ru_chain_id()) { - let tx_request = fill.to_fill_tx(self.constants.ru_orders()); - rollup_txs - .push(sign_and_encode_tx(&self.ru_provider, tx_request, signer_address).await?); - } - for order in &orders { - let tx_request = order.to_initiate_tx(signer_address, self.constants.ru_orders()); - rollup_txs - .push(sign_and_encode_tx(&self.ru_provider, tx_request, signer_address).await?); - } + let fill_iter = fills + .get(&self.constants.ru_chain_id()) + .map(|fill| fill.to_fill_tx(self.constants.ru_orders())) + .into_iter(); + let order_iter = orders + .iter() + .map(|order| order.to_initiate_tx(signer_address, self.constants.ru_orders())); + let rollup_txs: Vec = stream::iter(fill_iter.chain(order_iter)) + .then(|tx_request| sign_and_encode_tx(&self.ru_provider, tx_request, signer_address)) + .try_collect() + .await?; // Build host transaction request: fill only (if present) let host_txs = match fills.get(&self.constants.host_chain_id()) { @@ -133,6 +134,8 @@ where None => vec![], }; + // NOTE: We could retrieve a header up front, then use number+1. We could also check that + // the timestamp in the orders are valid for current.timestamp + calculator.slot_duration. let target_block = self.ru_provider.get_block_number().await.map_err(FeePolicyError::Rpc)? + 1; diff --git a/crates/orders/src/filler.rs b/crates/orders/src/filler.rs index ac2ffc1..1f3dd71 100644 --- a/crates/orders/src/filler.rs +++ b/crates/orders/src/filler.rs @@ -184,31 +184,4 @@ where .await .map_err(|error| FillerError::Submission(Box::new(error))) } - - /// Fill orders individually, returning a response per submission. - /// - /// Returns an error if `orders` is empty, or if signing or submission fails. - #[instrument(skip_all, fields(order_count = orders.len()))] - pub async fn fill_individually( - &self, - orders: Vec, - ) -> Result, FillerError> { - if orders.is_empty() { - return Err(FillerError::NoOrders); - } - - let mut responses = Vec::with_capacity(orders.len()); - - for order in orders { - let orders_and_fills = self.sign_fills(vec![order]).await?; - let response = self - .submitter - .submit_fills(orders_and_fills) - .await - .map_err(|error| FillerError::Submission(Box::new(error)))?; - responses.push(response); - } - - Ok(responses) - } } From 43e926076b4924438217fdaf2d4d396c4a77af39 Mon Sep 17 00:00:00 2001 From: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:33:03 +0000 Subject: [PATCH 5/5] fix doc links --- crates/orders/src/fee_policy.rs | 6 +++--- crates/orders/src/traits.rs | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/orders/src/fee_policy.rs b/crates/orders/src/fee_policy.rs index 83e382d..6079b57 100644 --- a/crates/orders/src/fee_policy.rs +++ b/crates/orders/src/fee_policy.rs @@ -11,6 +11,8 @@ use alloy::{ use futures_util::{stream, StreamExt, TryStreamExt}; use signet_bundle::SignetEthBundle; use signet_constants::SignetSystemConstants; +#[cfg(doc)] +use signet_types::SignedFill; use tracing::{error, instrument}; /// Errors returned by [`FeePolicySubmitter`]. @@ -51,9 +53,7 @@ impl From for FeePolicyError { /// The providers must be configured with appropriate fillers for gas, nonce, chain ID, and wallet /// signing (e.g., via `ProviderBuilder::with_gas_estimation()` and `ProviderBuilder::wallet()`). /// Note that the provider's nonce filler must correctly increment nonces across all transactions -/// built within a single [`submit_fills`] call. -/// -/// [`submit_fills`]: FillSubmitter::submit_fills +/// built within a single [`FillSubmitter::submit_fills`] call. #[derive(Debug, Clone)] pub struct FeePolicySubmitter { ru_provider: RuP, diff --git a/crates/orders/src/traits.rs b/crates/orders/src/traits.rs index 1b29c78..6b05e96 100644 --- a/crates/orders/src/traits.rs +++ b/crates/orders/src/traits.rs @@ -1,6 +1,8 @@ #[cfg(doc)] use crate::Filler; use crate::OrdersAndFills; +#[cfg(doc)] +use alloy::providers::fillers::FillProvider; use alloy::{ network::{Ethereum, Network}, providers::{fillers::FillerControlFlow, Provider, SendableTx}, @@ -9,6 +11,8 @@ use alloy::{ use core::future::Future; use futures_util::Stream; use signet_bundle::SignetEthBundle; +#[cfg(doc)] +use signet_types::SignedFill; use signet_types::SignedOrder; /// A trait for submitting signed orders to a backend.