Skip to main content

sof_tx/submit/
client_builder.rs

1//! High-level builder for configuring `TxSubmitClient`.
2
3use std::sync::Arc;
4
5use super::{
6    DirectSubmitConfig, DirectSubmitTransport, JitoJsonRpcTransport, JitoSubmitConfig,
7    JitoSubmitTransport, JsonRpcTransport, RpcSubmitConfig, RpcSubmitTransport, SubmitReliability,
8    SubmitTransportError, TxFlowSafetySource, TxSubmitClient, TxSubmitGuardPolicy,
9    TxSubmitOutcomeReporter, UdpDirectTransport,
10};
11use crate::{
12    providers::{
13        LeaderProvider, RecentBlockhashProvider, RpcRecentBlockhashProvider, StaticLeaderProvider,
14        StaticRecentBlockhashProvider,
15    },
16    routing::RoutingPolicy,
17};
18
19/// High-level builder for common `TxSubmitClient` configurations.
20pub struct TxSubmitClientBuilder {
21    /// Source used by unsigned submit paths.
22    blockhash_provider: Arc<dyn RecentBlockhashProvider>,
23    /// Optional RPC-backed blockhash source refreshed on demand.
24    on_demand_blockhash_provider: Option<Arc<RpcRecentBlockhashProvider>>,
25    /// Source used by direct and hybrid paths.
26    leader_provider: Arc<dyn LeaderProvider>,
27    /// Optional backup validators.
28    backups: Vec<crate::providers::LeaderTarget>,
29    /// Direct routing policy.
30    policy: RoutingPolicy,
31    /// Optional RPC submit transport.
32    rpc_transport: Option<Arc<dyn RpcSubmitTransport>>,
33    /// Optional Jito submit transport.
34    jito_transport: Option<Arc<dyn JitoSubmitTransport>>,
35    /// Optional direct submit transport.
36    direct_transport: Option<Arc<dyn DirectSubmitTransport>>,
37    /// RPC submit tuning.
38    rpc_config: RpcSubmitConfig,
39    /// Jito submit tuning.
40    jito_config: JitoSubmitConfig,
41    /// Direct submit tuning.
42    direct_config: DirectSubmitConfig,
43    /// Optional direct/hybrid reliability profile.
44    reliability: Option<SubmitReliability>,
45    /// Optional toxic-flow guard source.
46    flow_safety_source: Option<Arc<dyn TxFlowSafetySource>>,
47    /// Toxic-flow guard policy.
48    guard_policy: TxSubmitGuardPolicy,
49    /// Optional external outcome reporter.
50    outcome_reporter: Option<Arc<dyn TxSubmitOutcomeReporter>>,
51}
52
53impl Default for TxSubmitClientBuilder {
54    fn default() -> Self {
55        Self {
56            blockhash_provider: Arc::new(StaticRecentBlockhashProvider::new(None)),
57            on_demand_blockhash_provider: None,
58            leader_provider: Arc::new(StaticLeaderProvider::default()),
59            backups: Vec::new(),
60            policy: RoutingPolicy::default(),
61            rpc_transport: None,
62            jito_transport: None,
63            direct_transport: None,
64            rpc_config: RpcSubmitConfig::default(),
65            jito_config: JitoSubmitConfig::default(),
66            direct_config: DirectSubmitConfig::default(),
67            reliability: None,
68            flow_safety_source: None,
69            guard_policy: TxSubmitGuardPolicy::default(),
70            outcome_reporter: None,
71        }
72    }
73}
74
75impl TxSubmitClientBuilder {
76    /// Creates a new builder with empty static providers.
77    #[must_use]
78    pub fn new() -> Self {
79        Self::default()
80    }
81
82    /// Sets the explicit blockhash provider.
83    #[must_use]
84    pub fn with_blockhash_provider(mut self, provider: Arc<dyn RecentBlockhashProvider>) -> Self {
85        self.blockhash_provider = provider;
86        self.on_demand_blockhash_provider = None;
87        self
88    }
89
90    /// Uses one RPC endpoint as an on-demand blockhash source for unsigned submits.
91    ///
92    /// # Errors
93    ///
94    /// Returns [`SubmitTransportError`] when the RPC-backed provider cannot be created.
95    pub fn with_blockhash_via_rpc(
96        mut self,
97        rpc_url: impl Into<String>,
98    ) -> Result<Self, SubmitTransportError> {
99        let provider = Arc::new(RpcRecentBlockhashProvider::new(rpc_url.into())?);
100        self.blockhash_provider = provider.clone();
101        self.on_demand_blockhash_provider = Some(provider);
102        Ok(self)
103    }
104
105    /// Sets the explicit leader provider.
106    #[must_use]
107    pub fn with_leader_provider(mut self, provider: Arc<dyn LeaderProvider>) -> Self {
108        self.leader_provider = provider;
109        self
110    }
111
112    /// Resets the leader source to an empty static provider.
113    #[must_use]
114    pub fn without_leaders(mut self) -> Self {
115        self.leader_provider = Arc::new(StaticLeaderProvider::default());
116        self
117    }
118
119    /// Sets optional backup validators.
120    #[must_use]
121    pub fn with_backups(mut self, backups: Vec<crate::providers::LeaderTarget>) -> Self {
122        self.backups = backups;
123        self
124    }
125
126    /// Sets the direct routing policy.
127    #[must_use]
128    pub const fn with_routing_policy(mut self, policy: RoutingPolicy) -> Self {
129        self.policy = policy;
130        self
131    }
132
133    /// Uses one RPC endpoint for both on-demand blockhash refresh and RPC submission.
134    ///
135    /// # Errors
136    ///
137    /// Returns [`SubmitTransportError`] when the blockhash provider or RPC transport cannot be
138    /// created.
139    pub fn with_rpc_defaults(
140        self,
141        rpc_url: impl Into<String>,
142    ) -> Result<Self, SubmitTransportError> {
143        let rpc_url = rpc_url.into();
144        let transport = Arc::new(JsonRpcTransport::new(rpc_url.clone())?);
145        Ok(self
146            .with_blockhash_via_rpc(rpc_url)?
147            .with_rpc_transport(transport))
148    }
149
150    /// Sets the explicit RPC submit transport.
151    #[must_use]
152    pub fn with_rpc_transport(mut self, transport: Arc<dyn RpcSubmitTransport>) -> Self {
153        self.rpc_transport = Some(transport);
154        self
155    }
156
157    /// Uses the default Jito JSON-RPC transport and one RPC endpoint for on-demand blockhashes.
158    ///
159    /// # Errors
160    ///
161    /// Returns [`SubmitTransportError`] when the blockhash provider or Jito transport cannot be
162    /// created.
163    pub fn with_jito_defaults(
164        self,
165        rpc_url: impl Into<String>,
166    ) -> Result<Self, SubmitTransportError> {
167        Ok(self
168            .with_blockhash_via_rpc(rpc_url)?
169            .with_jito_transport(Arc::new(JitoJsonRpcTransport::new()?)))
170    }
171
172    /// Sets the explicit Jito submit transport.
173    #[must_use]
174    pub fn with_jito_transport(mut self, transport: Arc<dyn JitoSubmitTransport>) -> Self {
175        self.jito_transport = Some(transport);
176        self
177    }
178
179    /// Uses the default UDP direct transport.
180    #[must_use]
181    pub fn with_direct_udp(mut self) -> Self {
182        self.direct_transport = Some(Arc::new(UdpDirectTransport::new()));
183        self
184    }
185
186    /// Sets the explicit direct submit transport.
187    #[must_use]
188    pub fn with_direct_transport(mut self, transport: Arc<dyn DirectSubmitTransport>) -> Self {
189        self.direct_transport = Some(transport);
190        self
191    }
192
193    /// Sets the RPC submit tuning.
194    #[must_use]
195    pub fn with_rpc_config(mut self, config: RpcSubmitConfig) -> Self {
196        self.rpc_config = config;
197        self
198    }
199
200    /// Sets the Jito submit tuning.
201    #[must_use]
202    pub const fn with_jito_config(mut self, config: JitoSubmitConfig) -> Self {
203        self.jito_config = config;
204        self
205    }
206
207    /// Sets the direct submit tuning.
208    #[must_use]
209    pub const fn with_direct_config(mut self, config: DirectSubmitConfig) -> Self {
210        self.direct_config = config;
211        self.reliability = None;
212        self
213    }
214
215    /// Applies a direct/hybrid reliability preset.
216    #[must_use]
217    pub const fn with_reliability(mut self, reliability: SubmitReliability) -> Self {
218        self.reliability = Some(reliability);
219        self
220    }
221
222    /// Sets the toxic-flow guard source.
223    #[must_use]
224    pub fn with_flow_safety_source(mut self, source: Arc<dyn TxFlowSafetySource>) -> Self {
225        self.flow_safety_source = Some(source);
226        self
227    }
228
229    /// Sets the toxic-flow guard policy.
230    #[must_use]
231    pub const fn with_guard_policy(mut self, policy: TxSubmitGuardPolicy) -> Self {
232        self.guard_policy = policy;
233        self
234    }
235
236    /// Sets an optional external outcome reporter.
237    #[must_use]
238    pub fn with_outcome_reporter(mut self, reporter: Arc<dyn TxSubmitOutcomeReporter>) -> Self {
239        self.outcome_reporter = Some(reporter);
240        self
241    }
242
243    /// Builds the configured `TxSubmitClient`.
244    #[must_use]
245    pub fn build(self) -> TxSubmitClient {
246        let mut client = TxSubmitClient::new(self.blockhash_provider, self.leader_provider)
247            .with_backups(self.backups)
248            .with_routing_policy(self.policy)
249            .with_rpc_config(self.rpc_config)
250            .with_jito_config(self.jito_config)
251            .with_guard_policy(self.guard_policy);
252        if let Some(provider) = self.on_demand_blockhash_provider {
253            client = client.with_rpc_blockhash_provider(provider);
254        }
255        if let Some(reliability) = self.reliability {
256            client = client.with_reliability(reliability);
257        } else {
258            client = client.with_direct_config(self.direct_config);
259        }
260        if let Some(transport) = self.rpc_transport {
261            client = client.with_rpc_transport(transport);
262        }
263        if let Some(transport) = self.jito_transport {
264            client = client.with_jito_transport(transport);
265        }
266        if let Some(transport) = self.direct_transport {
267            client = client.with_direct_transport(transport);
268        }
269        if let Some(source) = self.flow_safety_source {
270            client = client.with_flow_safety_source(source);
271        }
272        if let Some(reporter) = self.outcome_reporter {
273            client = client.with_outcome_reporter(reporter);
274        }
275        client
276    }
277}