diff --git a/crates/provider/src/provider/trait.rs b/crates/provider/src/provider/trait.rs index d603ac7cf9b..e03eb8f8d70 100644 --- a/crates/provider/src/provider/trait.rs +++ b/crates/provider/src/provider/trait.rs @@ -86,181 +86,240 @@ pub trait Provider: self.root().weak_client() } - /// Watch for the confirmation of a single pending transaction with the given configuration. - /// - /// Note that this is handled internally rather than calling any specific RPC method, and as - /// such should not be overridden. - #[inline] - async fn watch_pending_transaction( - &self, - config: PendingTransactionConfig, - ) -> TransportResult { - self.root().watch_pending_transaction(config).await + /// Gets the accounts in the remote node. This is usually empty unless you're using a local + /// node. + async fn get_accounts(&self) -> TransportResult> { + self.client().request("eth_accounts", ()).await } - /// Subscribe to a stream of new block headers. - /// - /// # Errors - /// - /// This method is only available on `pubsub` clients, such as WebSockets or IPC, and will - /// return a [`PubsubUnavailable`](TransportErrorKind::PubsubUnavailable) transport error if the - /// client does not support it. + /// Returns the base fee per blob gas (blob gas price) in wei. + async fn get_blob_base_fee(&self) -> TransportResult { + self.client().request("eth_blobBaseFee", ()).await.map(|fee: U128| fee.to::()) + } + + /// Get the last block number available. + fn get_block_number(&self) -> RpcCall { + self.client().request("eth_blockNumber", ()).map_resp(crate::utils::convert_u64) + } + + /// Execute a smart contract call with a transaction request and state + /// overrides, without publishing a transaction. /// - /// For a polling alternative available over HTTP, use [`Provider::watch_blocks`]. - /// However, be aware that polling increases RPC usage drastically. + /// This function returns [`EthCall`] which can be used to execute the + /// call, or to add [`StateOverride`] or a [`BlockId`]. If no overrides + /// or block ID is provided, the call will be executed on the latest block + /// with the current state. /// - /// # Examples + /// [`StateOverride`]: alloy_rpc_types_eth::state::StateOverride /// - /// ```no_run - /// # async fn example(provider: impl alloy_provider::Provider) -> Result<(), Box> { - /// use futures::StreamExt; + /// ## Example /// - /// let sub = provider.subscribe_blocks().await?; - /// let mut stream = sub.into_stream().take(5); - /// while let Some(block) = stream.next().await { - /// println!("new block: {block:#?}"); - /// } + /// ``` + /// # use alloy_provider::Provider; + /// # use alloy_eips::BlockId; + /// # use alloy_rpc_types_eth::state::StateOverride; + /// # use alloy_transport::BoxTransport; + /// # async fn example>( + /// # provider: P, + /// # my_overrides: StateOverride + /// # ) -> Result<(), Box> { + /// # let tx = alloy_rpc_types_eth::transaction::TransactionRequest::default(); + /// // Execute a call on the latest block, with no state overrides + /// let output = provider.call(&tx).await?; + /// // Execute a call with a block ID. + /// let output = provider.call(&tx).block(1.into()).await?; + /// // Execute a call with state overrides. + /// let output = provider.call(&tx).overrides(&my_overrides).await?; /// # Ok(()) /// # } /// ``` - #[cfg(feature = "pubsub")] - async fn subscribe_blocks(&self) -> TransportResult> { - self.root().pubsub_frontend()?; - let id = self.client().request("eth_subscribe", ("newHeads",)).await?; - self.root().get_subscription(id).await - } - - /// Subscribe to a stream of pending transaction hashes. - /// - /// # Errors - /// - /// This method is only available on `pubsub` clients, such as WebSockets or IPC, and will - /// return a [`PubsubUnavailable`](TransportErrorKind::PubsubUnavailable) transport error if the - /// client does not support it. - /// - /// For a polling alternative available over HTTP, use [`Provider::watch_pending_transactions`]. - /// However, be aware that polling increases RPC usage drastically. /// - /// # Examples + /// # Note /// - /// ```no_run - /// # async fn example(provider: impl alloy_provider::Provider) -> Result<(), Box> { - /// use futures::StreamExt; + /// Not all client implementations support state overrides. + #[doc(alias = "eth_call")] + #[doc(alias = "call_with_overrides")] + fn call<'req>(&self, tx: &'req N::TransactionRequest) -> EthCall<'req, 'static, T, N, Bytes> { + EthCall::new(self.weak_client(), tx) + } + + /// Gets the chain ID. + fn get_chain_id(&self) -> RpcCall { + self.client().request("eth_chainId", ()).map_resp(crate::utils::convert_u64) + } + + /// Create an [EIP-2930] access list. /// - /// let sub = provider.subscribe_pending_transactions().await?; - /// let mut stream = sub.into_stream().take(5); - /// while let Some(tx_hash) = stream.next().await { - /// println!("new pending transaction hash: {tx_hash}"); - /// } - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "pubsub")] - async fn subscribe_pending_transactions( + /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + fn create_access_list<'a>( &self, - ) -> TransportResult> { - self.root().pubsub_frontend()?; - let id = self.client().request("eth_subscribe", ("newPendingTransactions",)).await?; - self.root().get_subscription(id).await + request: &'a N::TransactionRequest, + ) -> RpcWithBlock { + RpcWithBlock::new(self.weak_client(), "eth_createAccessList", request) } - /// Subscribe to a stream of pending transaction bodies. - /// - /// # Support - /// - /// This endpoint is compatible only with Geth client version 1.11.0 or later. - /// - /// # Errors - /// - /// This method is only available on `pubsub` clients, such as WebSockets or IPC, and will - /// return a [`PubsubUnavailable`](TransportErrorKind::PubsubUnavailable) transport error if the - /// client does not support it. - /// - /// For a polling alternative available over HTTP, use - /// [`Provider::watch_full_pending_transactions`]. However, be aware that polling increases - /// RPC usage drastically. + /// This function returns an [`EthCall`] which can be used to get a gas estimate, + /// or to add [`StateOverride`] or a [`BlockId`]. If no overrides + /// or block ID is provided, the gas estimate will be computed for the latest block + /// with the current state. /// - /// # Examples + /// [`StateOverride`]: alloy_rpc_types_eth::state::StateOverride /// - /// ```no_run - /// # async fn example(provider: impl alloy_provider::Provider) -> Result<(), Box> { - /// use futures::StreamExt; + /// # Note /// - /// let sub = provider.subscribe_full_pending_transactions().await?; - /// let mut stream = sub.into_stream().take(5); - /// while let Some(tx) = stream.next().await { - /// println!("{tx:#?}"); - /// } - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "pubsub")] - async fn subscribe_full_pending_transactions( + /// Not all client implementations support state overrides for eth_estimateGas. + fn estimate_gas<'req>( &self, - ) -> TransportResult> { - self.root().pubsub_frontend()?; - let id = self.client().request("eth_subscribe", ("newPendingTransactions", true)).await?; - self.root().get_subscription(id).await + tx: &'req N::TransactionRequest, + ) -> EthCall<'req, 'static, T, N, U128, u128> { + EthCall::gas_estimate(self.weak_client(), tx).map_resp(crate::utils::convert_u128) } - /// Subscribe to a stream of logs matching given filter. - /// - /// # Errors - /// - /// This method is only available on `pubsub` clients, such as WebSockets or IPC, and will - /// return a [`PubsubUnavailable`](TransportErrorKind::PubsubUnavailable) transport error if the - /// client does not support it. - /// - /// For a polling alternative available over HTTP, use - /// [`Provider::watch_logs`]. However, be aware that polling increases - /// RPC usage drastically. - /// - /// # Examples - /// - /// ```no_run - /// # async fn example(provider: impl alloy_provider::Provider) -> Result<(), Box> { - /// use futures::StreamExt; - /// use alloy_primitives::keccak256; - /// use alloy_rpc_types_eth::Filter; - /// - /// let signature = keccak256("Transfer(address,address,uint256)".as_bytes()); + /// Estimates the EIP1559 `maxFeePerGas` and `maxPriorityFeePerGas` fields. /// - /// let sub = provider.subscribe_logs(&Filter::new().event_signature(signature)).await?; - /// let mut stream = sub.into_stream().take(5); - /// while let Some(tx) = stream.next().await { - /// println!("{tx:#?}"); - /// } - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "pubsub")] - async fn subscribe_logs( + /// Receives an optional [EstimatorFunction] that can be used to modify + /// how to estimate these fees. + async fn estimate_eip1559_fees( + &self, + estimator: Option, + ) -> TransportResult { + let fee_history = self + .get_fee_history( + utils::EIP1559_FEE_ESTIMATION_PAST_BLOCKS, + BlockNumberOrTag::Latest, + &[utils::EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], + ) + .await?; + + // if the base fee of the Latest block is 0 then we need check if the latest block even has + // a base fee/supports EIP1559 + let base_fee_per_gas = match fee_history.latest_block_base_fee() { + Some(base_fee) if (base_fee != 0) => base_fee, + _ => { + // empty response, fetch basefee from latest block directly + self.get_block_by_number(BlockNumberOrTag::Latest, false) + .await? + .ok_or(RpcError::NullResp)? + .header + .base_fee_per_gas + .ok_or(RpcError::UnsupportedFeature("eip1559"))? + } + }; + + Ok(estimator.unwrap_or(utils::eip1559_default_estimator)( + base_fee_per_gas, + &fee_history.reward.unwrap_or_default(), + )) + } + + /// Returns a collection of historical gas information [FeeHistory] which + /// can be used to calculate the EIP1559 fields `maxFeePerGas` and `maxPriorityFeePerGas`. + /// `block_count` can range from 1 to 1024 blocks in a single request. + async fn get_fee_history( + &self, + block_count: u64, + last_block: BlockNumberOrTag, + reward_percentiles: &[f64], + ) -> TransportResult { + self.client() + .request("eth_feeHistory", (U64::from(block_count), last_block, reward_percentiles)) + .await + } + + /// Gets the current gas price in wei. + fn get_gas_price(&self) -> RpcCall { + self.client().request("eth_gasPrice", ()).map_resp(crate::utils::convert_u128) + } + + /// Retrieves account information ([Account](alloy_consensus::Account)) for the given [Address] + /// at the particular [BlockId]. + async fn get_account( + &self, + address: Address, + ) -> RpcWithBlock { + RpcWithBlock::new(self.weak_client(), "eth_getAccount", address) + } + + /// Gets the balance of the account at the specified tag, which defaults to latest. + fn get_balance(&self, address: Address) -> RpcWithBlock { + RpcWithBlock::new(self.weak_client(), "eth_getBalance", address) + } + + /// Gets a block by either its hash, tag, or number, with full transactions or only hashes. + async fn get_block( + &self, + id: BlockId, + kind: BlockTransactionsKind, + ) -> TransportResult> { + match id { + BlockId::Hash(hash) => self.get_block_by_hash(hash.into(), kind).await, + BlockId::Number(number) => { + let full = matches!(kind, BlockTransactionsKind::Full); + self.get_block_by_number(number, full).await + } + } + } + + /// Gets a block by its [BlockHash], with full transactions or only hashes. + async fn get_block_by_hash( &self, - filter: &Filter, - ) -> TransportResult> { - self.root().pubsub_frontend()?; - let id = self.client().request("eth_subscribe", ("logs", filter)).await?; - self.root().get_subscription(id).await + hash: BlockHash, + kind: BlockTransactionsKind, + ) -> TransportResult> { + let full = match kind { + BlockTransactionsKind::Full => true, + BlockTransactionsKind::Hashes => false, + }; + + let block = self + .client() + .request::<_, Option>("eth_getBlockByHash", (hash, full)) + .await? + .map(|mut block| { + if !full { + // this ensures an empty response for `Hashes` has the expected form + // this is required because deserializing [] is ambiguous + block.transactions.convert_to_hashes(); + } + block + }); + + Ok(block) } - /// Subscribe to an RPC event. - #[cfg(feature = "pubsub")] - #[auto_impl(keep_default_for(&, &mut, Rc, Arc, Box))] - async fn subscribe(&self, params: P) -> TransportResult> - where - P: RpcParam, - R: RpcReturn, - Self: Sized, - { - self.root().pubsub_frontend()?; - let id = self.client().request("eth_subscribe", params).await?; - self.root().get_subscription(id).await + /// Get a block by its number. + // TODO: Network associate + async fn get_block_by_number( + &self, + number: BlockNumberOrTag, + hydrate: bool, + ) -> TransportResult> { + let block = self + .client() + .request::<_, Option>("eth_getBlockByNumber", (number, hydrate)) + .await? + .map(|mut block| { + if !hydrate { + // this ensures an empty response for `Hashes` has the expected form + // this is required because deserializing [] is ambiguous + block.transactions.convert_to_hashes(); + } + block + }); + Ok(block) } - /// Cancels a subscription given the subscription ID. - #[cfg(feature = "pubsub")] - async fn unsubscribe(&self, id: U256) -> TransportResult<()> { - self.root().unsubscribe(id) + /// Gets the selected block [BlockNumberOrTag] receipts. + async fn get_block_receipts( + &self, + block: BlockNumberOrTag, + ) -> TransportResult>> { + self.client().request("eth_getBlockReceipts", (block,)).await + } + + /// Gets the bytecode located at the corresponding [Address]. + fn get_code_at(&self, address: Address) -> RpcWithBlock { + RpcWithBlock::new(self.weak_client(), "eth_getCode", address) } /// Watch for new blocks by polling the provider with @@ -317,79 +376,208 @@ pub trait Provider: Ok(PollerBuilder::new(self.weak_client(), "eth_getFilterChanges", (id,))) } - /// Watch for new pending transaction bodies by polling the provider with + /// Watch for new logs using the given filter by polling the provider with /// [`eth_getFilterChanges`](Self::get_filter_changes). /// /// Returns a builder that is used to configure the poller. See [`PollerBuilder`] for more /// details. /// - /// # Support - /// - /// This endpoint might not be supported by all clients. - /// /// # Examples /// - /// Get the next 5 pending transaction bodies: + /// Get the next 5 USDC transfer logs: /// /// ```no_run /// # async fn example(provider: impl alloy_provider::Provider) -> Result<(), Box> { + /// use alloy_primitives::{address, b256}; + /// use alloy_rpc_types_eth::Filter; /// use futures::StreamExt; /// - /// let poller = provider.watch_full_pending_transactions().await?; + /// let address = address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); + /// let transfer_signature = b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); + /// let filter = Filter::new().address(address).event_signature(transfer_signature); + /// + /// let poller = provider.watch_logs(&filter).await?; /// let mut stream = poller.into_stream().flat_map(futures::stream::iter).take(5); - /// while let Some(tx) = stream.next().await { - /// println!("new pending transaction: {tx:#?}"); + /// while let Some(log) = stream.next().await { + /// println!("new log: {log:#?}"); /// } /// # Ok(()) /// # } /// ``` - async fn watch_full_pending_transactions( - &self, - ) -> TransportResult> { - let id = self.new_pending_transactions_filter(true).await?; + async fn watch_logs(&self, filter: &Filter) -> TransportResult> { + let id = self.new_filter(filter).await?; Ok(PollerBuilder::new(self.weak_client(), "eth_getFilterChanges", (id,))) } - /// Watch for new logs using the given filter by polling the provider with + /// Watch for new pending transaction bodies by polling the provider with /// [`eth_getFilterChanges`](Self::get_filter_changes). /// /// Returns a builder that is used to configure the poller. See [`PollerBuilder`] for more /// details. /// + /// # Support + /// + /// This endpoint might not be supported by all clients. + /// /// # Examples /// - /// Get the next 5 USDC transfer logs: + /// Get the next 5 pending transaction bodies: /// /// ```no_run /// # async fn example(provider: impl alloy_provider::Provider) -> Result<(), Box> { - /// use alloy_primitives::{address, b256}; - /// use alloy_rpc_types_eth::Filter; /// use futures::StreamExt; /// - /// let address = address!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); - /// let transfer_signature = b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); - /// let filter = Filter::new().address(address).event_signature(transfer_signature); - /// - /// let poller = provider.watch_logs(&filter).await?; + /// let poller = provider.watch_full_pending_transactions().await?; /// let mut stream = poller.into_stream().flat_map(futures::stream::iter).take(5); - /// while let Some(log) = stream.next().await { - /// println!("new log: {log:#?}"); + /// while let Some(tx) = stream.next().await { + /// println!("new pending transaction: {tx:#?}"); /// } /// # Ok(()) /// # } /// ``` - async fn watch_logs(&self, filter: &Filter) -> TransportResult> { - let id = self.new_filter(filter).await?; + async fn watch_full_pending_transactions( + &self, + ) -> TransportResult> { + let id = self.new_pending_transactions_filter(true).await?; Ok(PollerBuilder::new(self.weak_client(), "eth_getFilterChanges", (id,))) } + /// Get a list of values that have been added since the last poll. + /// + /// The return value depends on what stream `id` corresponds to. + /// See [`FilterChanges`] for all possible return values. + #[auto_impl(keep_default_for(&, &mut, Rc, Arc, Box))] + async fn get_filter_changes(&self, id: U256) -> TransportResult> + where + Self: Sized, + { + self.client().request("eth_getFilterChanges", (id,)).await + } + + /// Get a list of values that have been added since the last poll. + /// + /// This returns an enum over all possible return values. You probably want to use + /// [`get_filter_changes`](Self::get_filter_changes) instead. + async fn get_filter_changes_dyn(&self, id: U256) -> TransportResult { + self.client().request("eth_getFilterChanges", (id,)).await + } + + /// Watch for the confirmation of a single pending transaction with the given configuration. + /// + /// Note that this is handled internally rather than calling any specific RPC method, and as + /// such should not be overridden. + #[inline] + async fn watch_pending_transaction( + &self, + config: PendingTransactionConfig, + ) -> TransportResult { + self.root().watch_pending_transaction(config).await + } + + /// Retrieves a [`Vec`] with the given [Filter]. + async fn get_logs(&self, filter: &Filter) -> TransportResult> { + self.client().request("eth_getLogs", (filter,)).await + } + + /// Get the account and storage values of the specified account including the merkle proofs. + /// + /// This call can be used to verify that the data has not been tampered with. + fn get_proof( + &self, + address: Address, + keys: Vec, + ) -> RpcWithBlock), EIP1186AccountProofResponse> { + RpcWithBlock::new(self.weak_client(), "eth_getProof", (address, keys)) + } + + /// Gets the specified storage value from [Address]. + fn get_storage_at( + &self, + address: Address, + key: U256, + ) -> RpcWithBlock { + RpcWithBlock::new(self.weak_client(), "eth_getStorageAt", (address, key)) + } + + /// Gets a transaction by its [TxHash]. + async fn get_transaction_by_hash( + &self, + hash: TxHash, + ) -> TransportResult> { + self.client().request("eth_getTransactionByHash", (hash,)).await + } + + /// Gets the transaction count (AKA "nonce") of the corresponding address. + #[doc(alias = "get_nonce")] + #[doc(alias = "get_account_nonce")] + fn get_transaction_count(&self, address: Address) -> RpcWithBlock { + RpcWithBlock::new(self.weak_client(), "eth_getTransactionCount", address) + .map_resp(crate::utils::convert_u64) + } + + /// Gets a transaction receipt if it exists, by its [TxHash]. + async fn get_transaction_receipt( + &self, + hash: TxHash, + ) -> TransportResult> { + self.client().request("eth_getTransactionReceipt", (hash,)).await + } + + /// Gets an uncle block through the tag [BlockId] and index [u64]. + async fn get_uncle(&self, tag: BlockId, idx: u64) -> TransportResult> { + let idx = U64::from(idx); + match tag { + BlockId::Hash(hash) => { + self.client() + .request("eth_getUncleByBlockHashAndIndex", (hash.block_hash, idx)) + .await + } + BlockId::Number(number) => { + self.client().request("eth_getUncleByBlockNumberAndIndex", (number, idx)).await + } + } + } + + /// Gets the number of uncles for the block specified by the tag [BlockId]. + async fn get_uncle_count(&self, tag: BlockId) -> TransportResult { + match tag { + BlockId::Hash(hash) => self + .client() + .request("eth_getUncleCountByBlockHash", (hash.block_hash,)) + .await + .map(|count: U64| count.to::()), + BlockId::Number(number) => self + .client() + .request("eth_getUncleCountByBlockNumber", (number,)) + .await + .map(|count: U64| count.to::()), + } + } + + /// Returns a suggestion for the current `maxPriorityFeePerGas` in wei. + async fn get_max_priority_fee_per_gas(&self) -> TransportResult { + self.client() + .request("eth_maxPriorityFeePerGas", ()) + .await + .map(|fee: U128| fee.to::()) + } + /// Notify the provider that we are interested in new blocks. /// /// Returns the ID to use with [`eth_getFilterChanges`](Self::get_filter_changes). /// - /// See also [`watch_blocks`](Self::watch_blocks) to configure a poller. - async fn new_block_filter(&self) -> TransportResult { - self.client().request("eth_newBlockFilter", ()).await + /// See also [`watch_blocks`](Self::watch_blocks) to configure a poller. + async fn new_block_filter(&self) -> TransportResult { + self.client().request("eth_newBlockFilter", ()).await + } + + /// Notify the provider that we are interested in logs that match the given filter. + /// + /// Returns the ID to use with [`eth_getFilterChanges`](Self::get_filter_changes). + /// + /// See also [`watch_logs`](Self::watch_logs) to configure a poller. + async fn new_filter(&self, filter: &Filter) -> TransportResult { + self.client().request("eth_newFilter", (filter,)).await } /// Notify the provider that we are interested in new pending transactions. @@ -407,68 +595,16 @@ pub trait Provider: self.client().request("eth_newPendingTransactionFilter", param).await } - /// Notify the provider that we are interested in logs that match the given filter. - /// - /// Returns the ID to use with [`eth_getFilterChanges`](Self::get_filter_changes). - /// - /// See also [`watch_logs`](Self::watch_logs) to configure a poller. - async fn new_filter(&self, filter: &Filter) -> TransportResult { - self.client().request("eth_newFilter", (filter,)).await - } - - /// Get a list of values that have been added since the last poll. - /// - /// The return value depends on what stream `id` corresponds to. - /// See [`FilterChanges`] for all possible return values. - #[auto_impl(keep_default_for(&, &mut, Rc, Arc, Box))] - async fn get_filter_changes(&self, id: U256) -> TransportResult> - where - Self: Sized, - { - self.client().request("eth_getFilterChanges", (id,)).await - } - - /// Get a list of values that have been added since the last poll. + /// Broadcasts a raw transaction RLP bytes to the network. /// - /// This returns an enum over all possible return values. You probably want to use - /// [`get_filter_changes`](Self::get_filter_changes) instead. - async fn get_filter_changes_dyn(&self, id: U256) -> TransportResult { - self.client().request("eth_getFilterChanges", (id,)).await - } - - /// Get the last block number available. - fn get_block_number(&self) -> RpcCall { - self.client().request("eth_blockNumber", ()).map_resp(crate::utils::convert_u64) - } - - /// Gets the transaction count (AKA "nonce") of the corresponding address. - #[doc(alias = "get_nonce")] - #[doc(alias = "get_account_nonce")] - fn get_transaction_count(&self, address: Address) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_getTransactionCount", address) - .map_resp(crate::utils::convert_u64) - } - - /// Get a block by its number. - // TODO: Network associate - async fn get_block_by_number( + /// See [`send_transaction`](Self::send_transaction) for more details. + async fn send_raw_transaction( &self, - number: BlockNumberOrTag, - hydrate: bool, - ) -> TransportResult> { - let block = self - .client() - .request::<_, Option>("eth_getBlockByNumber", (number, hydrate)) - .await? - .map(|mut block| { - if !hydrate { - // this ensures an empty response for `Hashes` has the expected form - // this is required because deserializing [] is ambiguous - block.transactions.convert_to_hashes(); - } - block - }); - Ok(block) + encoded_tx: &[u8], + ) -> TransportResult> { + let rlp_hex = hex::encode_prefixed(encoded_tx); + let tx_hash = self.client().request("eth_sendRawTransaction", (rlp_hex,)).await?; + Ok(PendingTransactionBuilder::new(self.root(), tx_hash)) } /// Broadcasts a transaction to the network. @@ -509,7 +645,7 @@ pub trait Provider: self.send_transaction_internal(SendableTx::Envelope(tx)).await } - /// This method allows [`ProviderLayer`] and [`TxFiller`] to build the + /// This method allows [`ProviderLayer`] and [`TxFiller`] to bulid the /// transaction and send it to the network without changing user-facing /// APIs. Generally implementors should NOT override this method. /// @@ -535,320 +671,184 @@ pub trait Provider: } } - /// Broadcasts a raw transaction RLP bytes to the network. - /// - /// See [`send_transaction`](Self::send_transaction) for more details. - async fn send_raw_transaction( - &self, - encoded_tx: &[u8], - ) -> TransportResult> { - let rlp_hex = hex::encode_prefixed(encoded_tx); - let tx_hash = self.client().request("eth_sendRawTransaction", (rlp_hex,)).await?; - Ok(PendingTransactionBuilder::new(self.root(), tx_hash)) - } - - /// Gets the balance of the account at the specified tag, which defaults to latest. - fn get_balance(&self, address: Address) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_getBalance", address) - } - - /// Gets a block by either its hash, tag, or number, with full transactions or only hashes. - async fn get_block( - &self, - id: BlockId, - kind: BlockTransactionsKind, - ) -> TransportResult> { - match id { - BlockId::Hash(hash) => self.get_block_by_hash(hash.into(), kind).await, - BlockId::Number(number) => { - let full = matches!(kind, BlockTransactionsKind::Full); - self.get_block_by_number(number, full).await - } - } - } - - /// Gets a block by its [BlockHash], with full transactions or only hashes. - async fn get_block_by_hash( - &self, - hash: BlockHash, - kind: BlockTransactionsKind, - ) -> TransportResult> { - let full = match kind { - BlockTransactionsKind::Full => true, - BlockTransactionsKind::Hashes => false, - }; - - let block = self - .client() - .request::<_, Option>("eth_getBlockByHash", (hash, full)) - .await? - .map(|mut block| { - if !full { - // this ensures an empty response for `Hashes` has the expected form - // this is required because deserializing [] is ambiguous - block.transactions.convert_to_hashes(); - } - block - }); - - Ok(block) - } - - /// Gets the client version of the chain client(). - async fn get_client_version(&self) -> TransportResult { - self.client().request("web3_clientVersion", ()).await - } - - /// Gets the chain ID. - fn get_chain_id(&self) -> RpcCall { - self.client().request("eth_chainId", ()).map_resp(crate::utils::convert_u64) - } - - /// Gets the network ID. Same as `eth_chainId`. - fn get_net_version(&self) -> RpcCall { - self.client().request("net_version", ()).map_resp(crate::utils::convert_u64) - } - - /// Gets the specified storage value from [Address]. - fn get_storage_at( - &self, - address: Address, - key: U256, - ) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_getStorageAt", (address, key)) - } - - /// Gets the bytecode located at the corresponding [Address]. - fn get_code_at(&self, address: Address) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_getCode", address) - } - - /// Gets a transaction by its [TxHash]. - async fn get_transaction_by_hash( - &self, - hash: TxHash, - ) -> TransportResult> { - self.client().request("eth_getTransactionByHash", (hash,)).await - } - - /// Retrieves a [`Vec`] with the given [Filter]. - async fn get_logs(&self, filter: &Filter) -> TransportResult> { - self.client().request("eth_getLogs", (filter,)).await - } - - /// Retrieves account information ([Account](alloy_consensus::Account)) for the given [Address] - /// at the particular [BlockId]. - async fn get_account( - &self, - address: Address, - ) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_getAccount", address) - } - - /// Gets the accounts in the remote node. This is usually empty unless you're using a local - /// node. - async fn get_accounts(&self) -> TransportResult> { - self.client().request("eth_accounts", ()).await - } - - /// Gets the current gas price in wei. - fn get_gas_price(&self) -> RpcCall { - self.client().request("eth_gasPrice", ()).map_resp(crate::utils::convert_u128) - } - - /// Returns a suggestion for the current `maxPriorityFeePerGas` in wei. - async fn get_max_priority_fee_per_gas(&self) -> TransportResult { - self.client() - .request("eth_maxPriorityFeePerGas", ()) - .await - .map(|fee: U128| fee.to::()) - } - - /// Returns the base fee per blob gas (blob gas price) in wei. - async fn get_blob_base_fee(&self) -> TransportResult { - self.client().request("eth_blobBaseFee", ()).await.map(|fee: U128| fee.to::()) - } - - /// Gets a transaction receipt if it exists, by its [TxHash]. - async fn get_transaction_receipt( - &self, - hash: TxHash, - ) -> TransportResult> { - self.client().request("eth_getTransactionReceipt", (hash,)).await - } - - /// Gets the selected block [BlockNumberOrTag] receipts. - async fn get_block_receipts( - &self, - block: BlockNumberOrTag, - ) -> TransportResult>> { - self.client().request("eth_getBlockReceipts", (block,)).await - } - - /// Gets an uncle block through the tag [BlockId] and index [u64]. - async fn get_uncle(&self, tag: BlockId, idx: u64) -> TransportResult> { - let idx = U64::from(idx); - match tag { - BlockId::Hash(hash) => { - self.client() - .request("eth_getUncleByBlockHashAndIndex", (hash.block_hash, idx)) - .await - } - BlockId::Number(number) => { - self.client().request("eth_getUncleByBlockNumberAndIndex", (number, idx)).await - } - } - } - - /// Gets the number of uncles for the block specified by the tag [BlockId]. - async fn get_uncle_count(&self, tag: BlockId) -> TransportResult { - match tag { - BlockId::Hash(hash) => self - .client() - .request("eth_getUncleCountByBlockHash", (hash.block_hash,)) - .await - .map(|count: U64| count.to::()), - BlockId::Number(number) => self - .client() - .request("eth_getUncleCountByBlockNumber", (number,)) - .await - .map(|count: U64| count.to::()), - } - } - - /// Gets syncing info. - async fn syncing(&self) -> TransportResult { - self.client().request("eth_syncing", ()).await - } - - /// Execute a smart contract call with a transaction request and state - /// overrides, without publishing a transaction. + /// Subscribe to a stream of new block headers. /// - /// This function returns [`EthCall`] which can be used to execute the - /// call, or to add [`StateOverride`] or a [`BlockId`]. If no overrides - /// or block ID is provided, the call will be executed on the latest block - /// with the current state. + /// # Errors /// - /// [`StateOverride`]: alloy_rpc_types_eth::state::StateOverride + /// This method is only available on `pubsub` clients, such as WebSockets or IPC, and will + /// return a [`PubsubUnavailable`](TransportErrorKind::PubsubUnavailable) transport error if the + /// client does not support it. /// - /// ## Example + /// For a polling alternative available over HTTP, use [`Provider::watch_blocks`]. + /// However, be aware that polling increases RPC usage drastically. /// - /// ``` - /// # use alloy_provider::Provider; - /// # use alloy_eips::BlockId; - /// # use alloy_rpc_types_eth::state::StateOverride; - /// # use alloy_transport::BoxTransport; - /// # async fn example>( - /// # provider: P, - /// # my_overrides: StateOverride - /// # ) -> Result<(), Box> { - /// # let tx = alloy_rpc_types_eth::transaction::TransactionRequest::default(); - /// // Execute a call on the latest block, with no state overrides - /// let output = provider.call(&tx).await?; - /// // Execute a call with a block ID. - /// let output = provider.call(&tx).block(1.into()).await?; - /// // Execute a call with state overrides. - /// let output = provider.call(&tx).overrides(&my_overrides).await?; + /// # Examples + /// + /// ```no_run + /// # async fn example(provider: impl alloy_provider::Provider) -> Result<(), Box> { + /// use futures::StreamExt; + /// + /// let sub = provider.subscribe_blocks().await?; + /// let mut stream = sub.into_stream().take(5); + /// while let Some(block) = stream.next().await { + /// println!("new block: {block:#?}"); + /// } /// # Ok(()) /// # } /// ``` - /// - /// # Note - /// - /// Not all client implementations support state overrides. - #[doc(alias = "eth_call")] - #[doc(alias = "call_with_overrides")] - fn call<'req>(&self, tx: &'req N::TransactionRequest) -> EthCall<'req, 'static, T, N, Bytes> { - EthCall::new(self.weak_client(), tx) + #[cfg(feature = "pubsub")] + async fn subscribe_blocks(&self) -> TransportResult> { + self.root().pubsub_frontend()?; + let id = self.client().request("eth_subscribe", ("newHeads",)).await?; + self.root().get_subscription(id).await } - /// Returns a collection of historical gas information [FeeHistory] which - /// can be used to calculate the EIP1559 fields `maxFeePerGas` and `maxPriorityFeePerGas`. - /// `block_count` can range from 1 to 1024 blocks in a single request. - async fn get_fee_history( + /// Subscribe to a stream of pending transaction hashes. + /// + /// # Errors + /// + /// This method is only available on `pubsub` clients, such as WebSockets or IPC, and will + /// return a [`PubsubUnavailable`](TransportErrorKind::PubsubUnavailable) transport error if the + /// client does not support it. + /// + /// For a polling alternative available over HTTP, use [`Provider::watch_pending_transactions`]. + /// However, be aware that polling increases RPC usage drastically. + /// + /// # Examples + /// + /// ```no_run + /// # async fn example(provider: impl alloy_provider::Provider) -> Result<(), Box> { + /// use futures::StreamExt; + /// + /// let sub = provider.subscribe_pending_transactions().await?; + /// let mut stream = sub.into_stream().take(5); + /// while let Some(tx_hash) = stream.next().await { + /// println!("new pending transaction hash: {tx_hash}"); + /// } + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "pubsub")] + async fn subscribe_pending_transactions( &self, - block_count: u64, - last_block: BlockNumberOrTag, - reward_percentiles: &[f64], - ) -> TransportResult { - self.client() - .request("eth_feeHistory", (U64::from(block_count), last_block, reward_percentiles)) - .await + ) -> TransportResult> { + self.root().pubsub_frontend()?; + let id = self.client().request("eth_subscribe", ("newPendingTransactions",)).await?; + self.root().get_subscription(id).await } - /// This function returns an [`EthCall`] which can be used to get a gas estimate, - /// or to add [`StateOverride`] or a [`BlockId`]. If no overrides - /// or block ID is provided, the gas estimate will be computed for the latest block - /// with the current state. + /// Subscribe to a stream of pending transaction bodies. /// - /// [`StateOverride`]: alloy_rpc_types_eth::state::StateOverride + /// # Support /// - /// # Note + /// This endpoint is compatible only with Geth client version 1.11.0 or later. /// - /// Not all client implementations support state overrides for eth_estimateGas. - fn estimate_gas<'req>( + /// # Errors + /// + /// This method is only available on `pubsub` clients, such as WebSockets or IPC, and will + /// return a [`PubsubUnavailable`](TransportErrorKind::PubsubUnavailable) transport error if the + /// client does not support it. + /// + /// For a polling alternative available over HTTP, use + /// [`Provider::watch_full_pending_transactions`]. However, be aware that polling increases + /// RPC usage drastically. + /// + /// # Examples + /// + /// ```no_run + /// # async fn example(provider: impl alloy_provider::Provider) -> Result<(), Box> { + /// use futures::StreamExt; + /// + /// let sub = provider.subscribe_full_pending_transactions().await?; + /// let mut stream = sub.into_stream().take(5); + /// while let Some(tx) = stream.next().await { + /// println!("{tx:#?}"); + /// } + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "pubsub")] + async fn subscribe_full_pending_transactions( &self, - tx: &'req N::TransactionRequest, - ) -> EthCall<'req, 'static, T, N, U128, u128> { - EthCall::gas_estimate(self.weak_client(), tx).map_resp(crate::utils::convert_u128) + ) -> TransportResult> { + self.root().pubsub_frontend()?; + let id = self.client().request("eth_subscribe", ("newPendingTransactions", true)).await?; + self.root().get_subscription(id).await } - /// Estimates the EIP1559 `maxFeePerGas` and `maxPriorityFeePerGas` fields. + /// Subscribe to a stream of logs matching given filter. /// - /// Receives an optional [EstimatorFunction] that can be used to modify - /// how to estimate these fees. - async fn estimate_eip1559_fees( + /// # Errors + /// + /// This method is only available on `pubsub` clients, such as WebSockets or IPC, and will + /// return a [`PubsubUnavailable`](TransportErrorKind::PubsubUnavailable) transport error if the + /// client does not support it. + /// + /// For a polling alternative available over HTTP, use + /// [`Provider::watch_logs`]. However, be aware that polling increases + /// RPC usage drastically. + /// + /// # Examples + /// + /// ```no_run + /// # async fn example(provider: impl alloy_provider::Provider) -> Result<(), Box> { + /// use futures::StreamExt; + /// use alloy_primitives::keccak256; + /// use alloy_rpc_types_eth::Filter; + /// + /// let signature = keccak256("Transfer(address,address,uint256)".as_bytes()); + /// + /// let sub = provider.subscribe_logs(&Filter::new().event_signature(signature)).await?; + /// let mut stream = sub.into_stream().take(5); + /// while let Some(tx) = stream.next().await { + /// println!("{tx:#?}"); + /// } + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "pubsub")] + async fn subscribe_logs( &self, - estimator: Option, - ) -> TransportResult { - let fee_history = self - .get_fee_history( - utils::EIP1559_FEE_ESTIMATION_PAST_BLOCKS, - BlockNumberOrTag::Latest, - &[utils::EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], - ) - .await?; + filter: &Filter, + ) -> TransportResult> { + self.root().pubsub_frontend()?; + let id = self.client().request("eth_subscribe", ("logs", filter)).await?; + self.root().get_subscription(id).await + } - // If the base fee of the Latest block is 0 then we need check if the latest block even has - // a base fee/supports EIP1559. - let base_fee_per_gas = match fee_history.latest_block_base_fee() { - Some(base_fee) if (base_fee != 0) => base_fee, - _ => { - // empty response, fetch basefee from latest block directly - self.get_block_by_number(BlockNumberOrTag::Latest, false) - .await? - .ok_or(RpcError::NullResp)? - .header - .base_fee_per_gas - .ok_or(RpcError::UnsupportedFeature("eip1559"))? - } - }; + /// Subscribe to an RPC event. + #[cfg(feature = "pubsub")] + #[auto_impl(keep_default_for(&, &mut, Rc, Arc, Box))] + async fn subscribe(&self, params: P) -> TransportResult> + where + P: RpcParam, + R: RpcReturn, + Self: Sized, + { + self.root().pubsub_frontend()?; + let id = self.client().request("eth_subscribe", params).await?; + self.root().get_subscription(id).await + } - Ok(estimator.unwrap_or(utils::eip1559_default_estimator)( - base_fee_per_gas, - &fee_history.reward.unwrap_or_default(), - )) + /// Cancels a subscription given the subscription ID. + #[cfg(feature = "pubsub")] + async fn unsubscribe(&self, id: U256) -> TransportResult<()> { + self.root().unsubscribe(id) } - /// Get the account and storage values of the specified account including the Merkle proofs. - /// - /// This call can be used to verify that the data has not been tampered with. - fn get_proof( - &self, - address: Address, - keys: Vec, - ) -> RpcWithBlock), EIP1186AccountProofResponse> { - RpcWithBlock::new(self.weak_client(), "eth_getProof", (address, keys)) + /// Gets syncing info. + async fn syncing(&self) -> TransportResult { + self.client().request("eth_syncing", ()).await } - /// Create an [EIP-2930] access list. - /// - /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 - fn create_access_list<'a>( - &self, - request: &'a N::TransactionRequest, - ) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_createAccessList", request) + /// Gets the client version of the chain client(). + async fn get_client_version(&self) -> TransportResult { + self.client().request("web3_clientVersion", ()).await + } + + /// Gets the network ID. Same as `eth_chainId`. + fn get_net_version(&self) -> RpcCall { + self.client().request("net_version", ()).map_resp(crate::utils::convert_u64) } /* ---------------------------------------- raw calls --------------------------------------- */