alloy_flashblocks/provider_ext.rs
1//! Extension trait for streaming flashblocks and querying flashblock state.
2
3use alloy::{
4 eips::BlockNumberOrTag,
5 network::Network,
6 primitives::{Address, Bytes, TxHash, U256},
7 providers::Provider,
8 rpc::types::{Filter, Log, simulate::SimulatePayload},
9 transports::TransportResult,
10};
11use std::future::Future;
12
13use crate::FlashblockPoller;
14
15/// Preconfirmation polling interval (50ms for responsive feedback).
16pub const PRECONF_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(50);
17
18/// Extension trait for streaming Base flashblocks and querying flashblock state.
19///
20/// Flashblocks provide ~200ms preconfirmations on Base L2. This trait adds
21/// methods to stream flashblocks, query pending state, and wait for preconfirmations.
22///
23/// # State Query Methods
24///
25/// All `flashblock_*` methods query against the pending/flashblock state:
26/// - [`flashblock`](Self::flashblock) - Get current flashblock with full txs
27/// - [`flashblock_balance`](Self::flashblock_balance) - Balance including preconfirmed txs
28/// - [`flashblock_nonce`](Self::flashblock_nonce) - Nonce for sending next tx
29/// - [`flashblock_call`](Self::flashblock_call) - Simulate against flashblock state
30/// - [`flashblock_estimate_gas`](Self::flashblock_estimate_gas) - Gas estimate against flashblock
31/// - [`flashblock_logs`](Self::flashblock_logs) - Logs up to flashblock
32/// - [`flashblock_simulate`](Self::flashblock_simulate) - Multi-call simulation
33///
34/// # Preconfirmation Helpers
35///
36/// - [`wait_for_preconfirmation`](Self::wait_for_preconfirmation) - Poll until tx is preconfirmed
37/// - [`is_preconfirmed`](Self::is_preconfirmed) - Check if tx has preconfirmed receipt
38pub trait FlashblocksProviderExt<N: Network>: Provider<N> + Clone + Send + Sync + 'static {
39 // === Streaming ===
40
41 /// Watch for flashblock updates via RPC polling.
42 ///
43 /// Polls `eth_getBlockByNumber("pending")` every 120ms and yields new blocks
44 /// when the block hash changes.
45 ///
46 /// # Example
47 ///
48 /// ```ignore
49 /// use alloy::providers::ProviderBuilder;
50 /// use alloy_flashblocks::FlashblocksProviderExt;
51 /// use futures_util::StreamExt;
52 ///
53 /// let provider = ProviderBuilder::new()
54 /// .connect_http("https://mainnet-preconf.base.org".parse()?);
55 ///
56 /// let mut stream = provider.watch_flashblocks().into_stream();
57 ///
58 /// while let Some(block) = stream.next().await {
59 /// println!("Block {}: {} txs", block.header.number, block.transactions.len());
60 /// }
61 /// ```
62 fn watch_flashblocks(&self) -> FlashblockPoller<N, Self>
63 where
64 Self: Sized,
65 {
66 FlashblockPoller::new(self.clone())
67 }
68
69 // === Flashblock State Queries ===
70
71 /// Get the current flashblock.
72 ///
73 /// Returns the pending block with transaction hashes. Use the individual
74 /// transaction hashes to fetch full transaction details if needed.
75 ///
76 /// This is equivalent to `eth_getBlockByNumber("pending", false)`.
77 ///
78 /// # Example
79 ///
80 /// ```ignore
81 /// if let Some(block) = provider.flashblock().await? {
82 /// println!("Flashblock {} | {} txs", block.header.number, block.transactions.len());
83 /// }
84 /// ```
85 fn flashblock(&self) -> impl Future<Output = TransportResult<Option<N::BlockResponse>>> + Send {
86 async move {
87 self.get_block_by_number(BlockNumberOrTag::Pending).await
88 }
89 }
90
91 /// Get balance including preconfirmed transactions.
92 ///
93 /// This is equivalent to `eth_getBalance(addr, "pending")`.
94 ///
95 /// # Example
96 ///
97 /// ```ignore
98 /// let balance = provider.flashblock_balance(addr).await?;
99 /// println!("Balance (with preconfirmed txs): {} wei", balance);
100 /// ```
101 fn flashblock_balance(
102 &self,
103 addr: Address,
104 ) -> impl Future<Output = TransportResult<U256>> + Send {
105 async move {
106 self.get_balance(addr)
107 .block_id(BlockNumberOrTag::Pending.into())
108 .await
109 }
110 }
111
112 /// Get nonce including preconfirmed transactions.
113 ///
114 /// Use this to determine the nonce for sending your next transaction,
115 /// accounting for any pending transactions already in the flashblock.
116 ///
117 /// This is equivalent to `eth_getTransactionCount(addr, "pending")`.
118 ///
119 /// # Example
120 ///
121 /// ```ignore
122 /// let nonce = provider.flashblock_nonce(addr).await?;
123 /// let tx = TransactionRequest::default().nonce(nonce);
124 /// ```
125 fn flashblock_nonce(&self, addr: Address) -> impl Future<Output = TransportResult<u64>> + Send {
126 async move {
127 self.get_transaction_count(addr)
128 .block_id(BlockNumberOrTag::Pending.into())
129 .await
130 }
131 }
132
133 /// Execute a call against flashblock state.
134 ///
135 /// Simulates the transaction against the current pending state, including
136 /// any preconfirmed transactions.
137 ///
138 /// This is equivalent to `eth_call(tx, "pending")`.
139 ///
140 /// # Example
141 ///
142 /// ```ignore
143 /// let result = provider.flashblock_call(&tx).await?;
144 /// ```
145 fn flashblock_call(
146 &self,
147 tx: &N::TransactionRequest,
148 ) -> impl Future<Output = TransportResult<Bytes>> + Send {
149 let tx = tx.clone();
150 async move {
151 self.call(tx)
152 .block(BlockNumberOrTag::Pending.into())
153 .await
154 }
155 }
156
157 /// Estimate gas against flashblock state.
158 ///
159 /// Estimates gas for the transaction against the current pending state.
160 ///
161 /// This is equivalent to `eth_estimateGas(tx, "pending")`.
162 ///
163 /// # Example
164 ///
165 /// ```ignore
166 /// let gas = provider.flashblock_estimate_gas(&tx).await?;
167 /// let tx = tx.gas_limit(gas);
168 /// ```
169 fn flashblock_estimate_gas(
170 &self,
171 tx: &N::TransactionRequest,
172 ) -> impl Future<Output = TransportResult<u64>> + Send {
173 let tx = tx.clone();
174 async move {
175 self.estimate_gas(tx)
176 .block(BlockNumberOrTag::Pending.into())
177 .await
178 }
179 }
180
181 /// Get logs up to the current flashblock.
182 ///
183 /// Returns logs matching the filter, including logs from preconfirmed
184 /// transactions in the current flashblock.
185 ///
186 /// # Example
187 ///
188 /// ```ignore
189 /// let filter = Filter::new().address(contract_addr);
190 /// let logs = provider.flashblock_logs(filter).await?;
191 /// ```
192 fn flashblock_logs(
193 &self,
194 filter: Filter,
195 ) -> impl Future<Output = TransportResult<Vec<Log>>> + Send {
196 async move {
197 let filter = filter.to_block(BlockNumberOrTag::Pending);
198 self.get_logs(&filter).await
199 }
200 }
201
202 /// Simulate multiple transactions against flashblock state.
203 ///
204 /// Executes a multi-call simulation against the current pending state.
205 ///
206 /// This is equivalent to `eth_simulateV1` with pending block.
207 ///
208 /// # Example
209 ///
210 /// ```ignore
211 /// use alloy::rpc::types::simulate::{SimulatePayload, SimBlock};
212 ///
213 /// let payload = SimulatePayload::default().extend(SimBlock::default());
214 /// let results = provider.flashblock_simulate(&payload).await?;
215 /// ```
216 fn flashblock_simulate(
217 &self,
218 payload: &SimulatePayload,
219 ) -> impl Future<Output = TransportResult<Vec<alloy::rpc::types::simulate::SimulatedBlock<N::BlockResponse>>>>
220 + Send
221 {
222 let payload = payload.clone();
223 async move {
224 self.simulate(&payload)
225 .block_id(BlockNumberOrTag::Pending.into())
226 .await
227 }
228 }
229
230 // === Preconfirmation Helpers ===
231
232 /// Wait for a transaction to be preconfirmed.
233 ///
234 /// Polls for the transaction receipt every 50ms until it appears.
235 /// This is the Rust equivalent of ethers.js `tx.wait(0)` for instant
236 /// preconfirmation feedback on flashblocks-enabled chains.
237 ///
238 /// # Example
239 ///
240 /// ```ignore
241 /// let pending = provider.send_transaction(tx).await?;
242 /// let receipt = provider.wait_for_preconfirmation(*pending.tx_hash()).await?;
243 /// println!("Preconfirmed! Status: {:?}", receipt.status());
244 /// ```
245 fn wait_for_preconfirmation(
246 &self,
247 tx_hash: TxHash,
248 ) -> impl Future<Output = TransportResult<N::ReceiptResponse>> + Send {
249 async move {
250 loop {
251 if let Some(receipt) = self.get_transaction_receipt(tx_hash).await? {
252 return Ok(receipt);
253 }
254 tokio::time::sleep(PRECONF_POLL_INTERVAL).await;
255 }
256 }
257 }
258
259 /// Check if a transaction has been preconfirmed.
260 ///
261 /// Returns `true` if the transaction has a receipt (preconfirmed or finalized).
262 ///
263 /// # Example
264 ///
265 /// ```ignore
266 /// if provider.is_preconfirmed(tx_hash).await? {
267 /// println!("Transaction is preconfirmed!");
268 /// }
269 /// ```
270 fn is_preconfirmed(
271 &self,
272 tx_hash: TxHash,
273 ) -> impl Future<Output = TransportResult<bool>> + Send {
274 async move { Ok(self.get_transaction_receipt(tx_hash).await?.is_some()) }
275 }
276}
277
278impl<N: Network, P: Provider<N> + Clone + Send + Sync + 'static> FlashblocksProviderExt<N> for P {}