Repository: gakonst/ethers-rs
Pull request #2159 (2023-02-17 01:11:59)

Refactor: organize ethers-providers
Depends on #2122 

Cleans up the ethers-providers crate by organizing modules into a more clean layout.

TODONES:
- ~~update docs for existing modules~~
- ~~ensure all modules are documented~~

gakonst: concept ack, let's merge once the base is merged
gakonst:  needs rebase since we merged your middleware errors PR. lgtm otherwise and supportive
prestwich: rebased and gitpilled
prestwich: docs suck less now, this should be ready to go :)

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 975a3c51..190fa258 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -238,6 +238,7 @@
 
 ### Unreleased
 
+- Re-organize the crate. #[2150](https://github.com/gakonst/ethers-rs/pull/2159)
 - Convert provider errors to arbitrary middleware errors
   [#1920](https://github.com/gakonst/ethers-rs/pull/1920)
 - Add a subset of the `admin` namespace
diff --git a/ethers-core/src/types/chain.rs b/ethers-core/src/types/chain.rs
index 1d713548..c6fc0cc4 100644
--- a/ethers-core/src/types/chain.rs
+++ b/ethers-core/src/types/chain.rs
@@ -114,6 +114,9 @@ pub enum Chain {
 
     Aurora = 1313161554,
     AuroraTestnet = 1313161555,
+
+    Canto = 7700,
+    CantoTestnet = 740,
 }
 
 // === impl Chain ===
@@ -220,7 +223,7 @@ impl Chain {
             BinanceSmartChain | BinanceSmartChainTestnet => 3_000,
             Avalanche | AvalancheFuji => 2_000,
             Fantom | FantomTestnet => 1_200,
-            Cronos | CronosTestnet => 5_700,
+            Cronos | CronosTestnet | Canto | CantoTestnet => 5_700,
             Evmos | EvmosTestnet => 1_900,
             Aurora | AuroraTestnet => 1_100,
             Oasis => 5_500,
@@ -320,6 +323,8 @@ impl Chain {
             CeloBaklava => {
                 ("https://explorer.celo.org/baklava", "https://explorer.celo.org/baklava/api")
             }
+            Canto => ("https://evm.explorer.canto.io/", "https://evm.explorer.canto.io/api"),
+            CantoTestnet => ("https://testnet-explorer.canto.neobase.one/", "https://testnet-explorer.canto.neobase.one/api"),
             AnvilHardhat | Dev | Morden | MoonbeamDev => {
                 // this is explicitly exhaustive so we don't forget to add new urls when adding a
                 // new chain
@@ -363,7 +368,7 @@ impl Chain {
             // Unknown / not applicable, default to false for backwards compatibility
             Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan |
             Sokol | Poa | XDai | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos |
-            EvmosTestnet | Chiado | Aurora | AuroraTestnet => false,
+            EvmosTestnet | Chiado | Aurora | AuroraTestnet | Canto | CantoTestnet => false,
         }
     }
 }
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index ee8437b6..a16f9cd0 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -120,6 +120,9 @@ impl Client {
             Chain::Moonbeam | Chain::Moonbase | Chain::MoonbeamDev | Chain::Moonriver => {
                 std::env::var("MOONSCAN_API_KEY")?
             }
+            Chain::Canto | Chain::CantoTestnet => {
+                std::env::var("BLOCKSCOUT_API_KEY")?
+            }
             Chain::AnvilHardhat | Chain::Dev => {
                 return Err(EtherscanError::LocalNetworksNotSupported)
             }
diff --git a/ethers-middleware/src/gas_escalator/mod.rs b/ethers-middleware/src/gas_escalator/mod.rs
index f3ab1840..384f0291 100644
--- a/ethers-middleware/src/gas_escalator/mod.rs
+++ b/ethers-middleware/src/gas_escalator/mod.rs
@@ -7,7 +7,7 @@ pub use linear::LinearGasPrice;
 
 use async_trait::async_trait;
 use ethers_core::types::{BlockId, TransactionRequest, TxHash, U256};
-use ethers_providers::{interval, FromErr, Middleware, PendingTransaction, StreamExt};
+use ethers_providers::{interval, Middleware, MiddlewareError, PendingTransaction, StreamExt};
 use futures_util::lock::Mutex;
 use instant::Instant;
 use std::{pin::Pin, sync::Arc};
@@ -238,10 +238,19 @@ where
 }
 
 // Boilerplate
-impl<M: Middleware> FromErr<M::Error> for GasEscalatorError<M> {
-    fn from(src: M::Error) -> GasEscalatorError<M> {
+impl<M: Middleware> MiddlewareError for GasEscalatorError<M> {
+    type Inner = M::Error;
+
+    fn from_err(src: M::Error) -> GasEscalatorError<M> {
         GasEscalatorError::MiddlewareError(src)
     }
+
+    fn as_inner(&self) -> Option<&Self::Inner> {
+        match self {
+            GasEscalatorError::MiddlewareError(e) => Some(e),
+            _ => None,
+        }
+    }
 }
 
 #[derive(Error, Debug)]
diff --git a/ethers-middleware/src/gas_oracle/middleware.rs b/ethers-middleware/src/gas_oracle/middleware.rs
index 918ec4b0..6835fb13 100644
--- a/ethers-middleware/src/gas_oracle/middleware.rs
+++ b/ethers-middleware/src/gas_oracle/middleware.rs
@@ -1,7 +1,7 @@
 use super::{GasOracle, GasOracleError};
 use async_trait::async_trait;
 use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
-use ethers_providers::{FromErr, Middleware, PendingTransaction};
+use ethers_providers::{Middleware, MiddlewareError as METrait, PendingTransaction};
 use thiserror::Error;
 
 /// Middleware used for fetching gas prices over an API instead of `eth_gasPrice`.
@@ -33,10 +33,19 @@ pub enum MiddlewareError<M: Middleware> {
     UnsupportedTxType,
 }
 
-impl<M: Middleware> FromErr<M::Error> for MiddlewareError<M> {
-    fn from(src: M::Error) -> MiddlewareError<M> {
+impl<M: Middleware> METrait for MiddlewareError<M> {
+    type Inner = M::Error;
+
+    fn from_err(src: M::Error) -> MiddlewareError<M> {
         MiddlewareError::MiddlewareError(src)
     }
+
+    fn as_inner(&self) -> Option<&Self::Inner> {
+        match self {
+            MiddlewareError::MiddlewareError(e) => Some(e),
+            _ => None,
+        }
+    }
 }
 
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -86,7 +95,7 @@ where
             }
         };
 
-        self.inner().fill_transaction(tx, block).await.map_err(FromErr::from)
+        self.inner().fill_transaction(tx, block).await.map_err(METrait::from_err)
     }
 
     async fn get_gas_price(&self) -> Result<U256, Self::Error> {
diff --git a/ethers-middleware/src/nonce_manager.rs b/ethers-middleware/src/nonce_manager.rs
index 0a72ddd0..1e59c57a 100644
--- a/ethers-middleware/src/nonce_manager.rs
+++ b/ethers-middleware/src/nonce_manager.rs
@@ -1,6 +1,6 @@
 use async_trait::async_trait;
 use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
-use ethers_providers::{FromErr, Middleware, PendingTransaction};
+use ethers_providers::{Middleware, MiddlewareError, PendingTransaction};
 use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
 use thiserror::Error;
 
@@ -40,7 +40,7 @@ where
                 .inner
                 .get_transaction_count(self.address, block)
                 .await
-                .map_err(FromErr::from)?;
+                .map_err(MiddlewareError::from_err)?;
             self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
             self.initialized.store(true, Ordering::SeqCst);
             Ok(nonce)
@@ -60,7 +60,7 @@ where
                 .inner
                 .get_transaction_count(self.address, block)
                 .await
-                .map_err(FromErr::from)?;
+                .map_err(MiddlewareError::from_err)?;
             self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
             self.initialized.store(true, Ordering::SeqCst);
         }
@@ -77,10 +77,18 @@ pub enum NonceManagerError<M: Middleware> {
     MiddlewareError(M::Error),
 }
 
-impl<M: Middleware> FromErr<M::Error> for NonceManagerError<M> {
-    fn from(src: M::Error) -> Self {
+impl<M: Middleware> MiddlewareError for NonceManagerError<M> {
+    type Inner = M::Error;
+
+    fn from_err(src: M::Error) -> Self {
         NonceManagerError::MiddlewareError(src)
     }
+
+    fn as_inner(&self) -> Option<&Self::Inner> {
+        match self {
+            NonceManagerError::MiddlewareError(e) => Some(e),
+        }
+    }
 }
 
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -106,7 +114,7 @@ where
             tx.set_nonce(self.get_transaction_count_with_manager(block).await?);
         }
 
-        Ok(self.inner().fill_transaction(tx, block).await.map_err(FromErr::from)?)
+        Ok(self.inner().fill_transaction(tx, block).await.map_err(MiddlewareError::from_err)?)
     }
 
     /// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that
@@ -132,10 +140,10 @@ where
                     // was a nonce mismatch
                     self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
                     tx.set_nonce(nonce);
-                    self.inner.send_transaction(tx, block).await.map_err(FromErr::from)
+                    self.inner.send_transaction(tx, block).await.map_err(MiddlewareError::from_err)
                 } else {
                     // propagate the error otherwise
-                    Err(FromErr::from(err))
+                    Err(MiddlewareError::from_err(err))
                 }
             }
         }
diff --git a/ethers-middleware/src/policy.rs b/ethers-middleware/src/policy.rs
index fc69d52e..3eef11cc 100644
--- a/ethers-middleware/src/policy.rs
+++ b/ethers-middleware/src/policy.rs
@@ -1,5 +1,5 @@
 use ethers_core::types::{transaction::eip2718::TypedTransaction, BlockId};
-use ethers_providers::{FromErr, Middleware, PendingTransaction};
+use ethers_providers::{Middleware, MiddlewareError, PendingTransaction};
 
 use async_trait::async_trait;
 use std::fmt::Debug;
@@ -52,12 +52,6 @@ pub struct PolicyMiddleware<M, P> {
     pub(crate) policy: P,
 }
 
-impl<M: Middleware, P: Policy> FromErr<M::Error> for PolicyMiddlewareError<M, P> {
-    fn from(src: M::Error) -> PolicyMiddlewareError<M, P> {
-        PolicyMiddlewareError::MiddlewareError(src)
-    }
-}
-
 impl<M, P> PolicyMiddleware<M, P>
 where
     M: Middleware,
@@ -80,6 +74,21 @@ pub enum PolicyMiddlewareError<M: Middleware, P: Policy> {
     MiddlewareError(M::Error),
 }
 
+impl<M: Middleware, P: Policy> MiddlewareError for PolicyMiddlewareError<M, P> {
+    type Inner = M::Error;
+
+    fn from_err(src: M::Error) -> Self {
+        PolicyMiddlewareError::MiddlewareError(src)
+    }
+
+    fn as_inner(&self) -> Option<&Self::Inner> {
+        match self {
+            PolicyMiddlewareError::MiddlewareError(e) => Some(e),
+            _ => None,
+        }
+    }
+}
+
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 impl<M, P> Middleware for PolicyMiddleware<M, P>
diff --git a/ethers-middleware/src/signer.rs b/ethers-middleware/src/signer.rs
index a6c79e15..1a58129f 100644
--- a/ethers-middleware/src/signer.rs
+++ b/ethers-middleware/src/signer.rs
@@ -2,7 +2,7 @@ use ethers_core::types::{
     transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
     Address, BlockId, Bytes, Chain, Signature, TransactionRequest, U256,
 };
-use ethers_providers::{maybe, FromErr, Middleware, PendingTransaction};
+use ethers_providers::{maybe, Middleware, MiddlewareError, PendingTransaction};
 use ethers_signers::Signer;
 use std::convert::TryFrom;
 
@@ -67,12 +67,6 @@ pub struct SignerMiddleware<M, S> {
     pub(crate) address: Address,
 }
 
-impl<M: Middleware, S: Signer> FromErr<M::Error> for SignerMiddlewareError<M, S> {
-    fn from(src: M::Error) -> SignerMiddlewareError<M, S> {
-        SignerMiddlewareError::MiddlewareError(src)
-    }
-}
-
 #[derive(Error, Debug)]
 /// Error thrown when the client interacts with the blockchain
 pub enum SignerMiddlewareError<M: Middleware, S: Signer> {
@@ -101,6 +95,21 @@ pub enum SignerMiddlewareError<M: Middleware, S: Signer> {
     DifferentChainID,
 }
 
+impl<M: Middleware, S: Signer> MiddlewareError for SignerMiddlewareError<M, S> {
+    type Inner = M::Error;
+
+    fn from_err(src: M::Error) -> Self {
+        SignerMiddlewareError::MiddlewareError(src)
+    }
+
+    fn as_inner(&self) -> Option<&Self::Inner> {
+        match self {
+            SignerMiddlewareError::MiddlewareError(e) => Some(e),
+            _ => None,
+        }
+    }
+}
+
 // Helper functions for locally signing transactions
 impl<M, S> SignerMiddleware<M, S>
 where
diff --git a/ethers-middleware/src/timelag/mod.rs b/ethers-middleware/src/timelag/mod.rs
index 6c1e90b8..258fc15a 100644
--- a/ethers-middleware/src/timelag/mod.rs
+++ b/ethers-middleware/src/timelag/mod.rs
@@ -6,7 +6,7 @@ use ethers_core::types::{
 use std::sync::Arc;
 use thiserror::Error;
 
-use ethers_providers::{FromErr, Middleware};
+use ethers_providers::{Middleware, MiddlewareError};
 
 type TimeLagResult<T, M> = Result<T, TimeLagError<M>>;
 
@@ -25,12 +25,20 @@ where
 }
 
 // Boilerplate
-impl<M: Middleware> FromErr<M::Error> for TimeLagError<M> {
-    fn from(src: M::Error) -> TimeLagError<M> {
+impl<M: Middleware> MiddlewareError for TimeLagError<M> {
+    type Inner = M::Error;
+
+    fn from_err(src: M::Error) -> Self {
         TimeLagError::MiddlewareError(src)
     }
-}
 
+    fn as_inner(&self) -> Option<&Self::Inner> {
+        match self {
+            TimeLagError::MiddlewareError(e) => Some(e),
+            _ => None,
+        }
+    }
+}
 /// TimeLag Provider
 #[derive(Debug)]
 pub struct TimeLag<M> {
@@ -115,7 +123,7 @@ where
             .get_block_number()
             .await
             .map(|num| num - self.lag)
-            .map_err(ethers_providers::FromErr::from)
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
@@ -123,7 +131,10 @@ where
         tx: T,
         block: Option<BlockId>,
     ) -> Result<ethers_providers::PendingTransaction<'_, Self::Provider>, Self::Error> {
-        self.inner().send_transaction(tx, block).await.map_err(ethers_providers::FromErr::from)
+        self.inner()
+            .send_transaction(tx, block)
+            .await
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn get_block<T: Into<BlockId> + Send + Sync>(
@@ -135,7 +146,10 @@ where
             .await?
             .expect("Cannot return None if Some is passed in");
 
-        self.inner().get_block(block_hash_or_number).await.map_err(ethers_providers::FromErr::from)
+        self.inner()
+            .get_block(block_hash_or_number)
+            .await
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn get_block_with_txs<T: Into<BlockId> + Send + Sync>(
@@ -150,7 +164,7 @@ where
         self.inner()
             .get_block_with_txs(block_hash_or_number)
             .await
-            .map_err(ethers_providers::FromErr::from)
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn get_uncle_count<T: Into<BlockId> + Send + Sync>(
@@ -165,7 +179,7 @@ where
         self.inner()
             .get_uncle_count(block_hash_or_number)
             .await
-            .map_err(ethers_providers::FromErr::from)
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn get_uncle<T: Into<BlockId> + Send + Sync>(
@@ -181,7 +195,7 @@ where
         self.inner()
             .get_uncle(block_hash_or_number, idx)
             .await
-            .map_err(ethers_providers::FromErr::from)
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn get_transaction_count<T: Into<NameOrAddress> + Send + Sync>(
@@ -194,7 +208,7 @@ where
         self.inner()
             .get_transaction_count(from, block)
             .await
-            .map_err(ethers_providers::FromErr::from)
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn call(
@@ -204,7 +218,7 @@ where
     ) -> Result<Bytes, Self::Error> {
         let block = self.normalize_block_id(block).await?;
 
-        self.inner().call(tx, block).await.map_err(ethers_providers::FromErr::from)
+        self.inner().call(tx, block).await.map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn get_balance<T: Into<NameOrAddress> + Send + Sync>(
@@ -213,7 +227,10 @@ where
         block: Option<BlockId>,
     ) -> Result<U256, Self::Error> {
         let block = self.normalize_block_id(block).await?;
-        self.inner().get_balance(from, block).await.map_err(ethers_providers::FromErr::from)
+        self.inner()
+            .get_balance(from, block)
+            .await
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
@@ -224,7 +241,7 @@ where
             .inner()
             .get_transaction_receipt(transaction_hash)
             .await
-            .map_err(ethers_providers::FromErr::from)?;
+            .map_err(ethers_providers::MiddlewareError::from_err)?;
 
         if receipt.is_none() {
             return Ok(None)
@@ -251,7 +268,7 @@ where
     ) -> Result<Bytes, Self::Error> {
         let block = self.normalize_block_id(block).await?;
 
-        self.inner().get_code(at, block).await.map_err(ethers_providers::FromErr::from)
+        self.inner().get_code(at, block).await.map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn get_storage_at<T: Into<NameOrAddress> + Send + Sync>(
@@ -264,7 +281,7 @@ where
         self.inner()
             .get_storage_at(from, location, block)
             .await
-            .map_err(ethers_providers::FromErr::from)
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn fill_transaction(
@@ -272,7 +289,10 @@ where
         tx: &mut TypedTransaction,
         block: Option<BlockId>,
     ) -> Result<(), Self::Error> {
-        self.inner().fill_transaction(tx, block).await.map_err(ethers_providers::FromErr::from)
+        self.inner()
+            .fill_transaction(tx, block)
+            .await
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn get_block_receipts<T: Into<BlockNumber> + Send + Sync>(
@@ -285,7 +305,10 @@ where
             .await?
             .expect("Cannot return None if Some is passed in");
 
-        self.inner().get_block_receipts(block).await.map_err(ethers_providers::FromErr::from)
+        self.inner()
+            .get_block_receipts(block)
+            .await
+            .map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn get_logs(
@@ -295,7 +318,7 @@ where
         let mut filter = filter.clone();
         filter.block_option = self.normalize_filter_range(filter.block_option).await?;
 
-        self.inner().get_logs(&filter).await.map_err(ethers_providers::FromErr::from)
+        self.inner().get_logs(&filter).await.map_err(ethers_providers::MiddlewareError::from_err)
     }
 
     async fn new_filter(
diff --git a/ethers-middleware/src/transformer/middleware.rs b/ethers-middleware/src/transformer/middleware.rs
index 3847b941..39fddf38 100644
--- a/ethers-middleware/src/transformer/middleware.rs
+++ b/ethers-middleware/src/transformer/middleware.rs
@@ -1,7 +1,7 @@
 use super::{Transformer, TransformerError};
 use async_trait::async_trait;
 use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
-use ethers_providers::{FromErr, Middleware, PendingTransaction};
+use ethers_providers::{Middleware, MiddlewareError, PendingTransaction};
 use thiserror::Error;
 
 #[derive(Debug)]
@@ -33,10 +33,19 @@ pub enum TransformerMiddlewareError<M: Middleware> {
     MiddlewareError(M::Error),
 }
 
-impl<M: Middleware> FromErr<M::Error> for TransformerMiddlewareError<M> {
-    fn from(src: M::Error) -> TransformerMiddlewareError<M> {
+impl<M: Middleware> MiddlewareError for TransformerMiddlewareError<M> {
+    type Inner = M::Error;
+
+    fn from_err(src: M::Error) -> Self {
         TransformerMiddlewareError::MiddlewareError(src)
     }
+
+    fn as_inner(&self) -> Option<&Self::Inner> {
+        match self {
+            TransformerMiddlewareError::MiddlewareError(e) => Some(e),
+            _ => None,
+        }
+    }
 }
 
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
diff --git a/ethers-providers/src/errors.rs b/ethers-providers/src/errors.rs
new file mode 100644
index 00000000..ea5d5052
--- /dev/null
+++ b/ethers-providers/src/errors.rs
@@ -0,0 +1,219 @@
+use std::{error::Error, fmt::Debug};
+use thiserror::Error;
+
+use crate::JsonRpcError;
+
+/// An `RpcError` is an abstraction over error types returned by a
+/// [`crate::JsonRpcClient`].
+///
+/// All clients can return [`JsonRpcError`] responses, as
+/// well as serde deserialization errors. However, because client errors are
+/// typically type-erased via the [`ProviderError`], the error info can be
+/// difficult to access. This trait provides convenient access to the
+/// underlying error types.
+///
+/// This trait deals only with behavior that is common to all clients.
+/// Client-specific errorvariants cannot be accessed via this trait.
+pub trait RpcError: Error + Debug + Send + Sync {
+    /// Access an underlying JSON-RPC error (if any)
+    ///
+    /// Attempts to access an underlying [`JsonRpcError`]. If the underlying
+    /// error is not a JSON-RPC error response, this function will return
+    /// `None`.
+    fn as_error_response(&self) -> Option<&JsonRpcError>;
+
+    /// Returns `true` if the underlying error is a JSON-RPC error response
+    fn is_error_response(&self) -> bool {
+        self.as_error_response().is_some()
+    }
+
+    /// Access an underlying `serde_json` error (if any)
+    ///
+    /// Attempts to access an underlying [`serde_json::Error`]. If the
+    /// underlying error is not a serde_json error, this function will return
+    /// `None`.
+    ///
+    /// ### Implementor's Note
+    ///
+    /// When writing a stacked [`crate::JsonRpcClient`] abstraction (e.g. a quorum
+    /// provider or retrying provider), be sure to account for `serde_json`
+    /// errors at your layer, as well as at lower layers.
+    fn as_serde_error(&self) -> Option<&serde_json::Error>;
+
+    /// Returns `true` if the underlying error is a serde_json (de)serialization
+    /// error. This method can be used to identify
+    fn is_serde_error(&self) -> bool {
+        self.as_serde_error().is_some()
+    }
+}
+
+/// [`MiddlewareError`] is a companion trait to [`crate::Middleware`]. It
+/// describes error behavior that is common to all Middleware errors.
+///
+/// Like [`crate::Middleware`], it allows moving down through layered errors.
+///
+/// Like [`RpcError`] it exposes convenient accessors to useful underlying
+/// error information.
+///
+///
+/// ## Not to Devs:
+/// While this trait includes the same methods as [`RpcError`], it is not a
+/// supertrait. This is so that 3rd party developers do not need to learn and
+/// implement both traits. We provide default methods that delegate to inner
+/// middleware errors on the assumption that it will eventually reach a
+/// [`ProviderError`], which has correct behavior. This allows Middleware devs
+/// to ignore the methods' presence if they want. Middleware are already plenty
+/// complicated and we don't need to make it worse :)
+pub trait MiddlewareError: Error + Sized + Send + Sync {
+    /// The `Inner` type is the next lower middleware layer's error type.
+    type Inner: MiddlewareError;
+
+    /// Convert the next lower middleware layer's error to this layer's error
+    fn from_err(e: Self::Inner) -> Self;
+
+    /// Attempt to convert this error to the next lower middleware's error.
+    /// Conversion fails if the error is not from an inner layer (i.e. the
+    /// error originates at this middleware layer)
+    fn as_inner(&self) -> Option<&Self::Inner>;
+
+    /// Returns `true` if the underlying error stems from a lower middleware
+    /// layer
+    fn is_inner(&self) -> bool {
+        self.as_inner().is_some()
+    }
+
+    /// Access an underlying `serde_json` error (if any)
+    ///
+    /// Attempts to access an underlying [`serde_json::Error`]. If the
+    /// underlying error is not a serde_json error, this function will return
+    /// `None`.
+    ///
+    /// ### Implementor's Note:
+    ///
+    /// When writing a custom middleware, if your middleware uses `serde_json`
+    /// we recommend a custom implementation of this method. It should first
+    /// check your Middleware's error for local `serde_json` errors, and then
+    /// delegate to inner if none is found. Failing to implement this method may
+    /// result in missed `serde_json` errors.
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        self.as_inner()?.as_serde_error()
+    }
+
+    /// Returns `true` if the underlying error is a serde_json (de)serialization
+    /// error. This method can be used to identify
+    fn is_serde_error(&self) -> bool {
+        self.as_serde_error().is_some()
+    }
+
+    /// Attempts to access an underlying [`ProviderError`], usually by
+    /// traversing the entire middleware stack. Access fails if the underlying
+    /// error is not a [`ProviderError`]
+    fn as_provider_error(&self) -> Option<&ProviderError> {
+        self.as_inner()?.as_provider_error()
+    }
+
+    /// Convert a [`ProviderError`] to this type, by successively wrapping it
+    /// in the error types of all lower middleware
+    fn from_provider_err(p: ProviderError) -> Self {
+        Self::from_err(Self::Inner::from_provider_err(p))
+    }
+
+    /// Access an underlying JSON-RPC error (if any)
+    ///
+    /// Attempts to access an underlying [`JsonRpcError`]. If the underlying
+    /// error is not a JSON-RPC error response, this function will return
+    /// `None`.
+    fn as_error_response(&self) -> Option<&JsonRpcError> {
+        self.as_inner()?.as_error_response()
+    }
+
+    /// Returns `true` if the underlying error is a JSON-RPC error response
+    fn is_error_response(&self) -> bool {
+        self.as_error_response().is_some()
+    }
+}
+
+#[derive(Debug, Error)]
+/// An error thrown when making a call to the provider
+pub enum ProviderError {
+    /// An internal error in the JSON RPC Client
+    #[error("{0}")]
+    JsonRpcClientError(Box<dyn crate::RpcError + Send + Sync>),
+
+    /// An error during ENS name resolution
+    #[error("ens name not found: {0}")]
+    EnsError(String),
+
+    /// Invalid reverse ENS name
+    #[error("reverse ens name not pointing to itself: {0}")]
+    EnsNotOwned(String),
+
+    /// Error in underlying lib `serde_json`
+    #[error(transparent)]
+    SerdeJson(#[from] serde_json::Error),
+
+    /// Error in underlying lib `hex`
+    #[error(transparent)]
+    HexError(#[from] hex::FromHexError),
+
+    /// Error in underlying lib `reqwest`
+    #[error(transparent)]
+    HTTPError(#[from] reqwest::Error),
+
+    /// Custom error from unknown source
+    #[error("custom error: {0}")]
+    CustomError(String),
+
+    /// RPC method is not supported by this provider
+    #[error("unsupported RPC")]
+    UnsupportedRPC,
+
+    /// Node is not supported by this provider
+    #[error("unsupported node client")]
+    UnsupportedNodeClient,
+
+    /// Signer is not available to this provider.
+    #[error("Attempted to sign a transaction with no available signer. Hint: did you mean to use a SignerMiddleware?")]
+    SignerUnavailable,
+}
+
+impl RpcError for ProviderError {
+    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
+        if let ProviderError::JsonRpcClientError(err) = self {
+            err.as_error_response()
+        } else {
+            None
+        }
+    }
+
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        match self {
+            ProviderError::JsonRpcClientError(e) => e.as_serde_error(),
+            ProviderError::SerdeJson(e) => Some(e),
+            _ => None,
+        }
+    }
+}
+
+// Do not change these implementations, they are critical to proper middleware
+// error stack behavior.
+impl MiddlewareError for ProviderError {
+    type Inner = Self;
+
+    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
+        RpcError::as_error_response(self)
+    }
+
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        RpcError::as_serde_error(self)
+    }
+
+    fn from_err(e: Self::Inner) -> Self {
+        e
+    }
+
+    fn as_inner(&self) -> Option<&Self::Inner> {
+        // prevents infinite loops
+        None
+    }
+}
diff --git a/ethers-providers/src/admin.rs b/ethers-providers/src/ext/admin.rs
similarity index 97%
rename from ethers-providers/src/admin.rs
rename to ethers-providers/src/ext/admin.rs
index 3de1ed4e..2c99664d 100644
--- a/ethers-providers/src/admin.rs
+++ b/ethers-providers/src/ext/admin.rs
@@ -1,6 +1,8 @@
-use crate::{H256, U256};
 use enr::{k256::ecdsa::SigningKey, Enr};
-use ethers_core::utils::{from_int_or_hex, ChainConfig};
+use ethers_core::{
+    types::{H256, U256},
+    utils::{from_int_or_hex, ChainConfig},
+};
 use serde::{Deserialize, Serialize};
 use std::net::{IpAddr, SocketAddr};
 
@@ -49,9 +51,11 @@ pub struct Ports {
 /// This contains protocol information reported by the connected RPC node.
 #[derive(Clone, Debug, Deserialize, Serialize)]
 pub struct ProtocolInfo {
+    /// Details about the node's supported eth protocol. `None` if unsupported
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub eth: Option<EthProtocolInfo>,
 
+    /// Details about the node's supported snap protocol. `None` if unsupported
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub snap: Option<SnapProtocolInfo>,
 }
@@ -94,9 +98,11 @@ pub struct SnapProtocolInfo {}
 /// connected RPC node.
 #[derive(Clone, Debug, Deserialize, Serialize)]
 pub struct PeerProtocolInfo {
+    /// Details about the peer's supported eth protocol. `None` if unsupported
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub eth: Option<EthPeerInfo>,
 
+    /// Details about the peer's supported snap protocol. `None` if unsupported
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub snap: Option<SnapPeerInfo>,
 }
diff --git a/ethers-providers/src/ext/dev_rpc.rs b/ethers-providers/src/ext/dev_rpc.rs
new file mode 100644
index 00000000..6e72b485
--- /dev/null
+++ b/ethers-providers/src/ext/dev_rpc.rs
@@ -0,0 +1,193 @@
+//! A middleware supporting development-specific JSON RPC methods
+//!
+//! # Example
+//!
+//!```
+//! use ethers_providers::{Provider, Http, Middleware, DevRpcMiddleware};
+//! use ethers_core::types::TransactionRequest;
+//! use ethers_core::utils::Anvil;
+//! use std::convert::TryFrom;
+//!
+//! # #[tokio::main(flavor = "current_thread")]
+//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
+//! let anvil = Anvil::new().spawn();
+//! let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
+//! let client = DevRpcMiddleware::new(provider);
+//!
+//! // snapshot the initial state
+//! let block0 = client.get_block_number().await.unwrap();
+//! let snap_id = client.snapshot().await.unwrap();
+//!
+//! // send a transaction
+//! let accounts = client.get_accounts().await?;
+//! let from = accounts[0];
+//! let to = accounts[1];
+//! let balance_before = client.get_balance(to, None).await?;
+//! let tx = TransactionRequest::new().to(to).value(1000).from(from);
+//! client.send_transaction(tx, None).await?.await?;
+//! let balance_after = client.get_balance(to, None).await?;
+//! assert_eq!(balance_after, balance_before + 1000);
+//!
+//! // revert to snapshot
+//! client.revert_to_snapshot(snap_id).await.unwrap();
+//! let balance_after_revert = client.get_balance(to, None).await?;
+//! assert_eq!(balance_after_revert, balance_before);
+//! # Ok(())
+//! # }
+//! ```
+use crate::{Middleware, MiddlewareError, ProviderError};
+use async_trait::async_trait;
+use ethers_core::types::U256;
+use thiserror::Error;
+
+use std::fmt::Debug;
+
+/// `DevRpcMiddleware`
+#[derive(Clone, Debug)]
+pub struct DevRpcMiddleware<M>(M);
+
+/// DevRpcMiddleware Errors
+#[derive(Error, Debug)]
+pub enum DevRpcMiddlewareError<M: Middleware> {
+    /// Internal Middleware error
+    #[error("{0}")]
+    MiddlewareError(M::Error),
+
+    /// Internal Provider error
+    #[error("{0}")]
+    ProviderError(ProviderError),
+
+    /// Attempted to revert to unavailable snapshot
+    #[error("Could not revert to snapshot")]
+    NoSnapshot,
+}
+
+#[async_trait]
+impl<M: Middleware> Middleware for DevRpcMiddleware<M> {
+    type Error = DevRpcMiddlewareError<M>;
+    type Provider = M::Provider;
+    type Inner = M;
+
+    fn inner(&self) -> &M {
+        &self.0
+    }
+}
+
+impl<M: Middleware> MiddlewareError for DevRpcMiddlewareError<M> {
+    type Inner = M::Error;
+
+    fn from_err(src: M::Error) -> DevRpcMiddlewareError<M> {
+        DevRpcMiddlewareError::MiddlewareError(src)
+    }
+
+    fn as_inner(&self) -> Option<&Self::Inner> {
+        match self {
+            DevRpcMiddlewareError::MiddlewareError(e) => Some(e),
+            _ => None,
+        }
+    }
+}
+
+impl<M> From<ProviderError> for DevRpcMiddlewareError<M>
+where
+    M: Middleware,
+{
+    fn from(src: ProviderError) -> Self {
+        Self::ProviderError(src)
+    }
+}
+
+impl<M: Middleware> DevRpcMiddleware<M> {
+    /// Instantiate a new `DevRpcMiddleware`
+    pub fn new(inner: M) -> Self {
+        Self(inner)
+    }
+
+    /// Create a new snapshot on the DevRpc node. Return the Snapshot ID
+    ///
+    /// ### Note
+    ///
+    /// Ganache, Hardhat and Anvil increment snapshot ID even if no state has changed
+    pub async fn snapshot(&self) -> Result<U256, DevRpcMiddlewareError<M>> {
+        self.provider().request::<(), U256>("evm_snapshot", ()).await.map_err(From::from)
+    }
+
+    /// Revert the state of the DevRpc node to the Snapshot, specified by its ID
+    pub async fn revert_to_snapshot(&self, id: U256) -> Result<(), DevRpcMiddlewareError<M>> {
+        let ok = self
+            .provider()
+            .request::<[U256; 1], bool>("evm_revert", [id])
+            .await
+            .map_err(DevRpcMiddlewareError::ProviderError)?;
+        if ok {
+            Ok(())
+        } else {
+            Err(DevRpcMiddlewareError::NoSnapshot)
+        }
+    }
+}
+
+#[cfg(test)]
+// Celo blocks can not get parsed when used with Ganache
+#[cfg(not(feature = "celo"))]
+mod tests {
+    use super::*;
+    use crate::{Http, Provider};
+    use ethers_core::utils::Anvil;
+    use std::convert::TryFrom;
+
+    #[tokio::test]
+    async fn test_snapshot() {
+        let anvil = Anvil::new().spawn();
+        let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
+        let client = DevRpcMiddleware::new(provider);
+
+        // snapshot initial state
+        let block0 = client.get_block_number().await.unwrap();
+        let time0 = client.get_block(block0).await.unwrap().unwrap().timestamp;
+        let snap_id0 = client.snapshot().await.unwrap();
+
+        // mine a new block
+        client.provider().mine(1).await.unwrap();
+
+        // snapshot state
+        let block1 = client.get_block_number().await.unwrap();
+        let time1 = client.get_block(block1).await.unwrap().unwrap().timestamp;
+        let snap_id1 = client.snapshot().await.unwrap();
+
+        // mine some blocks
+        client.provider().mine(5).await.unwrap();
+
+        // snapshot state
+        let block2 = client.get_block_number().await.unwrap();
+        let time2 = client.get_block(block2).await.unwrap().unwrap().timestamp;
+        let snap_id2 = client.snapshot().await.unwrap();
+
+        // mine some blocks
+        client.provider().mine(5).await.unwrap();
+
+        // revert_to_snapshot should reset state to snap id
+        client.revert_to_snapshot(snap_id2).await.unwrap();
+        let block = client.get_block_number().await.unwrap();
+        let time = client.get_block(block).await.unwrap().unwrap().timestamp;
+        assert_eq!(block, block2);
+        assert_eq!(time, time2);
+
+        client.revert_to_snapshot(snap_id1).await.unwrap();
+        let block = client.get_block_number().await.unwrap();
+        let time = client.get_block(block).await.unwrap().unwrap().timestamp;
+        assert_eq!(block, block1);
+        assert_eq!(time, time1);
+
+        // revert_to_snapshot should throw given non-existent or
+        // previously used snapshot
+        let result = client.revert_to_snapshot(snap_id1).await;
+        assert!(result.is_err());
+
+        client.revert_to_snapshot(snap_id0).await.unwrap();
+        let block = client.get_block_number().await.unwrap();
+        let time = client.get_block(block).await.unwrap().unwrap().timestamp;
+        assert_eq!(block, block0);
+        assert_eq!(time, time0);
+    }
+}
diff --git a/ethers-providers/src/ens.rs b/ethers-providers/src/ext/ens.rs
similarity index 100%
rename from ethers-providers/src/ens.rs
rename to ethers-providers/src/ext/ens.rs
diff --git a/ethers-providers/src/erc.rs b/ethers-providers/src/ext/erc.rs
similarity index 92%
rename from ethers-providers/src/erc.rs
rename to ethers-providers/src/ext/erc.rs
index ac99b4f4..538ffe71 100644
--- a/ethers-providers/src/erc.rs
+++ b/ethers-providers/src/ext/erc.rs
@@ -15,8 +15,11 @@ const IPFS_GATEWAY: &str = "https://ipfs.io/ipfs/";
 
 /// An ERC 721 or 1155 token
 pub struct ERCNFT {
+    /// Type of the NFT
     pub type_: ERCNFTType,
+    /// Address of the NFT contract
     pub contract: Address,
+    /// NFT ID in that contract
     pub id: [u8; 32],
 }
 
@@ -56,7 +59,9 @@ impl FromStr for ERCNFT {
 /// Supported ERCs
 #[derive(PartialEq, Eq)]
 pub enum ERCNFTType {
+    /// ERC721
     ERC721,
+    /// ERC1155
     ERC1155,
 }
 
@@ -72,7 +77,8 @@ impl FromStr for ERCNFTType {
 }
 
 impl ERCNFTType {
-    pub fn resolution_selector(&self) -> Selector {
+    /// Get the method selector
+    pub const fn resolution_selector(&self) -> Selector {
         match self {
             // tokenURI(uint256)
             ERCNFTType::ERC721 => [0xc8, 0x7b, 0x56, 0xdd],
@@ -85,6 +91,7 @@ impl ERCNFTType {
 /// ERC-1155 and ERC-721 metadata document.
 #[derive(Deserialize)]
 pub struct Metadata {
+    /// The URL of the image for the NFT
     pub image: String,
 }
 
diff --git a/ethers-providers/src/ext/mod.rs b/ethers-providers/src/ext/mod.rs
new file mode 100644
index 00000000..210bfdfc
--- /dev/null
+++ b/ethers-providers/src/ext/mod.rs
@@ -0,0 +1,13 @@
+/// Types for the admin api
+pub mod admin;
+pub use admin::{NodeInfo, PeerInfo};
+
+pub mod ens;
+pub use ens::*;
+
+pub mod erc;
+
+#[cfg(feature = "dev-rpc")]
+pub mod dev_rpc;
+#[cfg(feature = "dev-rpc")]
+pub use dev_rpc::{DevRpcMiddleware, DevRpcMiddlewareError};
diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs
index ad7ce8c1..50c64bd2 100644
--- a/ethers-providers/src/lib.rs
+++ b/ethers-providers/src/lib.rs
@@ -3,772 +3,40 @@
 #![deny(rustdoc::broken_intra_doc_links)]
 #![allow(clippy::type_complexity)]
 #![doc = include_str!("../README.md")]
+#![warn(missing_docs)]
 
-mod transports;
-pub use transports::*;
+mod ext;
+pub use ext::*;
 
-mod provider;
-pub use provider::{is_local_endpoint, FilterKind, Provider, ProviderError, ProviderExt};
+mod rpc;
+pub use rpc::*;
 
-// types for the admin api
-pub mod admin;
-pub use admin::{NodeInfo, PeerInfo};
+mod toolbox;
+pub use toolbox::*;
 
-// ENS support
-pub mod ens;
+/// Crate utilities and type aliases
+mod utils;
+pub use utils::{interval, maybe, EscalationPolicy};
 
-mod pending_transaction;
-pub use pending_transaction::PendingTransaction;
-
-mod pending_escalator;
-pub use pending_escalator::EscalatingPending;
-
-mod log_query;
-pub use log_query::{LogQuery, LogQueryError};
+/// Errors
+mod errors;
+pub use errors::{MiddlewareError, ProviderError, RpcError};
 
 mod stream;
 pub use futures_util::StreamExt;
 pub use stream::{
-    interval, FilterWatcher, TransactionStream, DEFAULT_LOCAL_POLL_INTERVAL, DEFAULT_POLL_INTERVAL,
-};
-
-mod pubsub;
-pub use pubsub::{PubsubClient, SubscriptionStream};
-
-pub mod call_raw;
-pub mod erc;
-
-use async_trait::async_trait;
-use auto_impl::auto_impl;
-use ethers_core::types::{
-    transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
-    *,
+    tx_stream::TransactionStream, FilterWatcher, DEFAULT_LOCAL_POLL_INTERVAL, DEFAULT_POLL_INTERVAL,
 };
-use futures_util::future::join_all;
-use serde::{de::DeserializeOwned, Serialize};
-use std::{error::Error, fmt::Debug, future::Future, pin::Pin};
-use url::Url;
-
-// feature-enabled support for dev-rpc methods
-#[cfg(feature = "dev-rpc")]
-pub use provider::dev_rpc::DevRpcMiddleware;
-
-/// A simple gas escalation policy
-pub type EscalationPolicy = Box<dyn Fn(U256, usize) -> U256 + Send + Sync>;
-
-// Helper type alias
-#[cfg(target_arch = "wasm32")]
-pub(crate) type PinBoxFut<'a, T> = Pin<Box<dyn Future<Output = Result<T, ProviderError>> + 'a>>;
-#[cfg(not(target_arch = "wasm32"))]
-pub(crate) type PinBoxFut<'a, T> =
-    Pin<Box<dyn Future<Output = Result<T, ProviderError>> + Send + 'a>>;
-
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-#[auto_impl(&, Box, Arc)]
-/// Trait which must be implemented by data transports to be used with the Ethereum
-/// JSON-RPC provider.
-pub trait JsonRpcClient: Debug + Send + Sync {
-    /// A JSON-RPC Error
-    type Error: Error + Into<ProviderError>;
-
-    /// Sends a request with the provided JSON-RPC and parameters serialized as JSON
-    async fn request<T, R>(&self, method: &str, params: T) -> Result<R, Self::Error>
-    where
-        T: Debug + Serialize + Send + Sync,
-        R: DeserializeOwned + Send;
-}
-
-pub trait FromErr<T> {
-    fn from(src: T) -> Self;
-}
-
-/// Calls the future if `item` is None, otherwise returns a `futures::ok`
-pub async fn maybe<F, T, E>(item: Option<T>, f: F) -> Result<T, E>
-where
-    F: Future<Output = Result<T, E>>,
-{
-    if let Some(item) = item {
-        futures_util::future::ok(item).await
-    } else {
-        f.await
-    }
-}
-
-/// A middleware allows customizing requests send and received from an ethereum node.
-///
-/// Writing a middleware is as simple as:
-/// 1. implementing the [`inner`](crate::Middleware::inner) method to point to the next layer in the
-/// "middleware onion", 2. implementing the [`FromErr`](crate::FromErr) trait on your middleware's
-/// error type 3. implementing any of the methods you want to override
-///
-/// ```rust
-/// use ethers_providers::{Middleware, FromErr};
-/// use ethers_core::types::{U64, TransactionRequest, U256, transaction::eip2718::TypedTransaction, BlockId};
-/// use thiserror::Error;
-/// use async_trait::async_trait;
-///
-/// #[derive(Debug)]
-/// struct MyMiddleware<M>(M);
-///
-/// #[derive(Error, Debug)]
-/// pub enum MyError<M: Middleware> {
-///     #[error("{0}")]
-///     MiddlewareError(M::Error),
-///
-///     // Add your middleware's specific errors here
-/// }
-///
-/// impl<M: Middleware> FromErr<M::Error> for MyError<M> {
-///     fn from(src: M::Error) -> MyError<M> {
-///         MyError::MiddlewareError(src)
-///     }
-/// }
-///
-/// #[async_trait]
-/// impl<M> Middleware for MyMiddleware<M>
-/// where
-///     M: Middleware,
-/// {
-///     type Error = MyError<M>;
-///     type Provider = M::Provider;
-///     type Inner = M;
-///
-///     fn inner(&self) -> &M {
-///         &self.0
-///     }
-///
-///     /// Overrides the default `get_block_number` method to always return 0
-///     async fn get_block_number(&self) -> Result<U64, Self::Error> {
-///         Ok(U64::zero())
-///     }
-///
-///     /// Overrides the default `estimate_gas` method to log that it was called,
-///     /// before forwarding the call to the next layer.
-///     async fn estimate_gas(&self, tx: &TypedTransaction, block: Option<BlockId>) -> Result<U256, Self::Error> {
-///         println!("Estimating gas...");
-///         self.inner().estimate_gas(tx, block).await.map_err(FromErr::from)
-///     }
-/// }
-/// ```
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-#[auto_impl(&, Box, Arc)]
-pub trait Middleware: Sync + Send + Debug {
-    type Error: Sync + Send + Error + FromErr<<Self::Inner as Middleware>::Error>;
-    type Provider: JsonRpcClient;
-    type Inner: Middleware<Provider = Self::Provider>;
-
-    /// The next middleware in the stack
-    fn inner(&self) -> &Self::Inner;
-
-    /// Convert a provider error into the associated error type by successively
-    /// converting it to every intermediate middleware error
-    fn convert_err(p: ProviderError) -> Self::Error {
-        let e = <Self as Middleware>::Inner::convert_err(p);
-        FromErr::from(e)
-    }
-
-    /// The HTTP or Websocket provider.
-    fn provider(&self) -> &Provider<Self::Provider> {
-        self.inner().provider()
-    }
-
-    fn default_sender(&self) -> Option<Address> {
-        self.inner().default_sender()
-    }
-
-    async fn client_version(&self) -> Result<String, Self::Error> {
-        self.inner().client_version().await.map_err(FromErr::from)
-    }
-
-    /// Fill necessary details of a transaction for dispatch
-    ///
-    /// This function is defined on providers to behave as follows:
-    /// 1. populate the `from` field with the default sender
-    /// 2. resolve any ENS names in the tx `to` field
-    /// 3. Estimate gas usage
-    /// 4. Poll and set legacy or 1559 gas prices
-    /// 5. Set the chain_id with the provider's, if not already set
-    ///
-    /// It does NOT set the nonce by default.
-    ///
-    /// Middleware are encouraged to override any values _before_ delegating
-    /// to the inner implementation AND/OR modify the values provided by the
-    /// default implementation _after_ delegating.
-    ///
-    /// E.g. a middleware wanting to double gas prices should consider doing so
-    /// _after_ delegating and allowing the default implementation to poll gas.
-    async fn fill_transaction(
-        &self,
-        tx: &mut TypedTransaction,
-        block: Option<BlockId>,
-    ) -> Result<(), Self::Error> {
-        self.inner().fill_transaction(tx, block).await.map_err(FromErr::from)
-    }
-
-    async fn get_block_number(&self) -> Result<U64, Self::Error> {
-        self.inner().get_block_number().await.map_err(FromErr::from)
-    }
-
-    async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
-        &self,
-        tx: T,
-        block: Option<BlockId>,
-    ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
-        self.inner().send_transaction(tx, block).await.map_err(FromErr::from)
-    }
-
-    /// Send a transaction with a simple escalation policy.
-    ///
-    /// `policy` should be a boxed function that maps `original_gas_price`
-    /// and `number_of_previous_escalations` -> `new_gas_price`.
-    ///
-    /// e.g. `Box::new(|start, escalation_index| start * 1250.pow(escalations) /
-    /// 1000.pow(escalations))`
-    async fn send_escalating<'a>(
-        &'a self,
-        tx: &TypedTransaction,
-        escalations: usize,
-        policy: EscalationPolicy,
-    ) -> Result<EscalatingPending<'a, Self::Provider>, Self::Error> {
-        let mut original = tx.clone();
-        self.fill_transaction(&mut original, None).await?;
-
-        // set the nonce, if no nonce is found
-        if original.nonce().is_none() {
-            let nonce =
-                self.get_transaction_count(tx.from().copied().unwrap_or_default(), None).await?;
-            original.set_nonce(nonce);
-        }
-
-        let gas_price = original.gas_price().expect("filled");
-        let sign_futs: Vec<_> = (0..escalations)
-            .map(|i| {
-                let new_price = policy(gas_price, i);
-                let mut r = original.clone();
-                r.set_gas_price(new_price);
-                r
-            })
-            .map(|req| async move {
-                self.sign_transaction(&req, self.default_sender().unwrap_or_default())
-                    .await
-                    .map(|sig| req.rlp_signed(&sig))
-            })
-            .collect();
-
-        // we reverse for convenience. Ensuring that we can always just
-        // `pop()` the next tx off the back later
-        let mut signed = join_all(sign_futs).await.into_iter().collect::<Result<Vec<_>, _>>()?;
-        signed.reverse();
-
-        Ok(EscalatingPending::new(self.provider(), signed))
-    }
-
-    async fn resolve_name(&self, ens_name: &str) -> Result<Address, Self::Error> {
-        self.inner().resolve_name(ens_name).await.map_err(FromErr::from)
-    }
-
-    async fn lookup_address(&self, address: Address) -> Result<String, Self::Error> {
-        self.inner().lookup_address(address).await.map_err(FromErr::from)
-    }
-
-    async fn resolve_avatar(&self, ens_name: &str) -> Result<Url, Self::Error> {
-        self.inner().resolve_avatar(ens_name).await.map_err(FromErr::from)
-    }
-
-    async fn resolve_nft(&self, token: erc::ERCNFT) -> Result<Url, Self::Error> {
-        self.inner().resolve_nft(token).await.map_err(FromErr::from)
-    }
-
-    async fn resolve_field(&self, ens_name: &str, field: &str) -> Result<String, Self::Error> {
-        self.inner().resolve_field(ens_name, field).await.map_err(FromErr::from)
-    }
-
-    async fn get_block<T: Into<BlockId> + Send + Sync>(
-        &self,
-        block_hash_or_number: T,
-    ) -> Result<Option<Block<TxHash>>, Self::Error> {
-        self.inner().get_block(block_hash_or_number).await.map_err(FromErr::from)
-    }
-
-    async fn get_block_with_txs<T: Into<BlockId> + Send + Sync>(
-        &self,
-        block_hash_or_number: T,
-    ) -> Result<Option<Block<Transaction>>, Self::Error> {
-        self.inner().get_block_with_txs(block_hash_or_number).await.map_err(FromErr::from)
-    }
-
-    async fn get_uncle_count<T: Into<BlockId> + Send + Sync>(
-        &self,
-        block_hash_or_number: T,
-    ) -> Result<U256, Self::Error> {
-        self.inner().get_uncle_count(block_hash_or_number).await.map_err(FromErr::from)
-    }
-
-    async fn get_uncle<T: Into<BlockId> + Send + Sync>(
-        &self,
-        block_hash_or_number: T,
-        idx: U64,
-    ) -> Result<Option<Block<H256>>, Self::Error> {
-        self.inner().get_uncle(block_hash_or_number, idx).await.map_err(FromErr::from)
-    }
-
-    async fn get_transaction_count<T: Into<NameOrAddress> + Send + Sync>(
-        &self,
-        from: T,
-        block: Option<BlockId>,
-    ) -> Result<U256, Self::Error> {
-        self.inner().get_transaction_count(from, block).await.map_err(FromErr::from)
-    }
-
-    async fn estimate_gas(
-        &self,
-        tx: &TypedTransaction,
-        block: Option<BlockId>,
-    ) -> Result<U256, Self::Error> {
-        self.inner().estimate_gas(tx, block).await.map_err(FromErr::from)
-    }
-
-    async fn call(
-        &self,
-        tx: &TypedTransaction,
-        block: Option<BlockId>,
-    ) -> Result<Bytes, Self::Error> {
-        self.inner().call(tx, block).await.map_err(FromErr::from)
-    }
-
-    async fn syncing(&self) -> Result<SyncingStatus, Self::Error> {
-        self.inner().syncing().await.map_err(FromErr::from)
-    }
-
-    async fn get_chainid(&self) -> Result<U256, Self::Error> {
-        self.inner().get_chainid().await.map_err(FromErr::from)
-    }
-
-    async fn get_net_version(&self) -> Result<String, Self::Error> {
-        self.inner().get_net_version().await.map_err(FromErr::from)
-    }
-
-    async fn get_balance<T: Into<NameOrAddress> + Send + Sync>(
-        &self,
-        from: T,
-        block: Option<BlockId>,
-    ) -> Result<U256, Self::Error> {
-        self.inner().get_balance(from, block).await.map_err(FromErr::from)
-    }
-
-    async fn get_transaction<T: Send + Sync + Into<TxHash>>(
-        &self,
-        transaction_hash: T,
-    ) -> Result<Option<Transaction>, Self::Error> {
-        self.inner().get_transaction(transaction_hash).await.map_err(FromErr::from)
-    }
-
-    async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
-        &self,
-        transaction_hash: T,
-    ) -> Result<Option<TransactionReceipt>, Self::Error> {
-        self.inner().get_transaction_receipt(transaction_hash).await.map_err(FromErr::from)
-    }
-
-    async fn get_block_receipts<T: Into<BlockNumber> + Send + Sync>(
-        &self,
-        block: T,
-    ) -> Result<Vec<TransactionReceipt>, Self::Error> {
-        self.inner().get_block_receipts(block).await.map_err(FromErr::from)
-    }
-
-    async fn get_gas_price(&self) -> Result<U256, Self::Error> {
-        self.inner().get_gas_price().await.map_err(FromErr::from)
-    }
-
-    async fn estimate_eip1559_fees(
-        &self,
-        estimator: Option<fn(U256, Vec<Vec<U256>>) -> (U256, U256)>,
-    ) -> Result<(U256, U256), Self::Error> {
-        self.inner().estimate_eip1559_fees(estimator).await.map_err(FromErr::from)
-    }
-
-    async fn get_accounts(&self) -> Result<Vec<Address>, Self::Error> {
-        self.inner().get_accounts().await.map_err(FromErr::from)
-    }
-
-    async fn send_raw_transaction<'a>(
-        &'a self,
-        tx: Bytes,
-    ) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> {
-        self.inner().send_raw_transaction(tx).await.map_err(FromErr::from)
-    }
-
-    /// This returns true if either the middleware stack contains a `SignerMiddleware`, or the
-    /// JSON-RPC provider has an unlocked key that can sign using the `eth_sign` call. If none of
-    /// the above conditions are met, then the middleware stack is not capable of signing data.
-    async fn is_signer(&self) -> bool {
-        self.inner().is_signer().await
-    }
-
-    async fn sign<T: Into<Bytes> + Send + Sync>(
-        &self,
-        data: T,
-        from: &Address,
-    ) -> Result<Signature, Self::Error> {
-        self.inner().sign(data, from).await.map_err(FromErr::from)
-    }
-
-    /// Sign a transaction via RPC call
-    async fn sign_transaction(
-        &self,
-        tx: &TypedTransaction,
-        from: Address,
-    ) -> Result<Signature, Self::Error> {
-        self.inner().sign_transaction(tx, from).await.map_err(FromErr::from)
-    }
-
-    ////// Contract state
-
-    async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>, Self::Error> {
-        self.inner().get_logs(filter).await.map_err(FromErr::from)
-    }
-
-    /// Returns a stream of logs are loaded in pages of given page size
-    fn get_logs_paginated<'a>(
-        &'a self,
-        filter: &Filter,
-        page_size: u64,
-    ) -> LogQuery<'a, Self::Provider> {
-        self.inner().get_logs_paginated(filter, page_size)
-    }
-
-    async fn new_filter(&self, filter: FilterKind<'_>) -> Result<U256, Self::Error> {
-        self.inner().new_filter(filter).await.map_err(FromErr::from)
-    }
-
-    async fn uninstall_filter<T: Into<U256> + Send + Sync>(
-        &self,
-        id: T,
-    ) -> Result<bool, Self::Error> {
-        self.inner().uninstall_filter(id).await.map_err(FromErr::from)
-    }
-
-    async fn watch<'a>(
-        &'a self,
-        filter: &Filter,
-    ) -> Result<FilterWatcher<'a, Self::Provider, Log>, Self::Error> {
-        self.inner().watch(filter).await.map_err(FromErr::from)
-    }
-
-    async fn watch_pending_transactions(
-        &self,
-    ) -> Result<FilterWatcher<'_, Self::Provider, H256>, Self::Error> {
-        self.inner().watch_pending_transactions().await.map_err(FromErr::from)
-    }
-
-    async fn get_filter_changes<T, R>(&self, id: T) -> Result<Vec<R>, Self::Error>
-    where
-        T: Into<U256> + Send + Sync,
-        R: Serialize + DeserializeOwned + Send + Sync + Debug,
-    {
-        self.inner().get_filter_changes(id).await.map_err(FromErr::from)
-    }
-
-    async fn watch_blocks(&self) -> Result<FilterWatcher<'_, Self::Provider, H256>, Self::Error> {
-        self.inner().watch_blocks().await.map_err(FromErr::from)
-    }
-
-    async fn get_code<T: Into<NameOrAddress> + Send + Sync>(
-        &self,
-        at: T,
-        block: Option<BlockId>,
-    ) -> Result<Bytes, Self::Error> {
-        self.inner().get_code(at, block).await.map_err(FromErr::from)
-    }
-
-    async fn get_storage_at<T: Into<NameOrAddress> + Send + Sync>(
-        &self,
-        from: T,
-        location: H256,
-        block: Option<BlockId>,
-    ) -> Result<H256, Self::Error> {
-        self.inner().get_storage_at(from, location, block).await.map_err(FromErr::from)
-    }
-
-    async fn get_proof<T: Into<NameOrAddress> + Send + Sync>(
-        &self,
-        from: T,
-        locations: Vec<H256>,
-        block: Option<BlockId>,
-    ) -> Result<EIP1186ProofResponse, Self::Error> {
-        self.inner().get_proof(from, locations, block).await.map_err(FromErr::from)
-    }
-
-    /// Returns an indication if this node is currently mining.
-    async fn mining(&self) -> Result<bool, Self::Error> {
-        self.inner().mining().await.map_err(FromErr::from)
-    }
-
-    // Personal namespace
-
-    async fn import_raw_key(
-        &self,
-        private_key: Bytes,
-        passphrase: String,
-    ) -> Result<Address, Self::Error> {
-        self.inner().import_raw_key(private_key, passphrase).await.map_err(FromErr::from)
-    }
-
-    async fn unlock_account<T: Into<Address> + Send + Sync>(
-        &self,
-        account: T,
-        passphrase: String,
-        duration: Option<u64>,
-    ) -> Result<bool, Self::Error> {
-        self.inner().unlock_account(account, passphrase, duration).await.map_err(FromErr::from)
-    }
-
-    // Admin namespace
-
-    async fn add_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
-        self.inner().add_peer(enode_url).await.map_err(FromErr::from)
-    }
-
-    async fn add_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
-        self.inner().add_trusted_peer(enode_url).await.map_err(FromErr::from)
-    }
-
-    async fn node_info(&self) -> Result<NodeInfo, Self::Error> {
-        self.inner().node_info().await.map_err(FromErr::from)
-    }
-
-    async fn peers(&self) -> Result<Vec<PeerInfo>, Self::Error> {
-        self.inner().peers().await.map_err(FromErr::from)
-    }
-
-    async fn remove_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
-        self.inner().remove_peer(enode_url).await.map_err(FromErr::from)
-    }
-
-    async fn remove_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
-        self.inner().remove_trusted_peer(enode_url).await.map_err(FromErr::from)
-    }
-
-    // Miner namespace
-
-    /// Starts the miner with the given number of threads. If threads is nil, the number of workers
-    /// started is equal to the number of logical CPUs that are usable by this process. If mining
-    /// is already running, this method adjust the number of threads allowed to use and updates the
-    /// minimum price required by the transaction pool.
-    async fn start_mining(&self, threads: Option<usize>) -> Result<(), Self::Error> {
-        self.inner().start_mining(threads).await.map_err(FromErr::from)
-    }
-
-    /// Stop terminates the miner, both at the consensus engine level as well as at
-    /// the block creation level.
-    async fn stop_mining(&self) -> Result<(), Self::Error> {
-        self.inner().stop_mining().await.map_err(FromErr::from)
-    }
-
-    // Mempool inspection for Geth's API
-
-    async fn txpool_content(&self) -> Result<TxpoolContent, Self::Error> {
-        self.inner().txpool_content().await.map_err(FromErr::from)
-    }
-
-    async fn txpool_inspect(&self) -> Result<TxpoolInspect, Self::Error> {
-        self.inner().txpool_inspect().await.map_err(FromErr::from)
-    }
-
-    async fn txpool_status(&self) -> Result<TxpoolStatus, Self::Error> {
-        self.inner().txpool_status().await.map_err(FromErr::from)
-    }
-
-    // Geth `trace` support
-
-    /// After replaying any previous transactions in the same block,
-    /// Replays a transaction, returning the traces configured with passed options
-    async fn debug_trace_transaction(
-        &self,
-        tx_hash: TxHash,
-        trace_options: GethDebugTracingOptions,
-    ) -> Result<GethTrace, Self::Error> {
-        self.inner().debug_trace_transaction(tx_hash, trace_options).await.map_err(FromErr::from)
-    }
-
-    /// Executes the given call and returns a number of possible traces for it
-    async fn debug_trace_call<T: Into<TypedTransaction> + Send + Sync>(
-        &self,
-        req: T,
-        block: Option<BlockId>,
-        trace_options: GethDebugTracingCallOptions,
-    ) -> Result<GethTrace, Self::Error> {
-        self.inner().debug_trace_call(req, block, trace_options).await.map_err(FromErr::from)
-    }
-
-    // Parity `trace` support
-
-    /// Executes the given call and returns a number of possible traces for it
-    async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
-        &self,
-        req: T,
-        trace_type: Vec<TraceType>,
-        block: Option<BlockNumber>,
-    ) -> Result<BlockTrace, Self::Error> {
-        self.inner().trace_call(req, trace_type, block).await.map_err(FromErr::from)
-    }
-
-    async fn trace_call_many<T: Into<TypedTransaction> + Send + Sync>(
-        &self,
-        req: Vec<(T, Vec<TraceType>)>,
-        block: Option<BlockNumber>,
-    ) -> Result<Vec<BlockTrace>, Self::Error> {
-        self.inner().trace_call_many(req, block).await.map_err(FromErr::from)
-    }
-
-    /// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces
-    async fn trace_raw_transaction(
-        &self,
-        data: Bytes,
-        trace_type: Vec<TraceType>,
-    ) -> Result<BlockTrace, Self::Error> {
-        self.inner().trace_raw_transaction(data, trace_type).await.map_err(FromErr::from)
-    }
-
-    /// Replays a transaction, returning the traces
-    async fn trace_replay_transaction(
-        &self,
-        hash: H256,
-        trace_type: Vec<TraceType>,
-    ) -> Result<BlockTrace, Self::Error> {
-        self.inner().trace_replay_transaction(hash, trace_type).await.map_err(FromErr::from)
-    }
-
-    /// Replays all transactions in a block returning the requested traces for each transaction
-    async fn trace_replay_block_transactions(
-        &self,
-        block: BlockNumber,
-        trace_type: Vec<TraceType>,
-    ) -> Result<Vec<BlockTrace>, Self::Error> {
-        self.inner().trace_replay_block_transactions(block, trace_type).await.map_err(FromErr::from)
-    }
-
-    /// Returns traces created at given block
-    async fn trace_block(&self, block: BlockNumber) -> Result<Vec<Trace>, Self::Error> {
-        self.inner().trace_block(block).await.map_err(FromErr::from)
-    }
-
-    /// Return traces matching the given filter
-    async fn trace_filter(&self, filter: TraceFilter) -> Result<Vec<Trace>, Self::Error> {
-        self.inner().trace_filter(filter).await.map_err(FromErr::from)
-    }
-
-    /// Returns trace at the given position
-    async fn trace_get<T: Into<U64> + Send + Sync>(
-        &self,
-        hash: H256,
-        index: Vec<T>,
-    ) -> Result<Trace, Self::Error> {
-        self.inner().trace_get(hash, index).await.map_err(FromErr::from)
-    }
-
-    /// Returns all traces of a given transaction
-    async fn trace_transaction(&self, hash: H256) -> Result<Vec<Trace>, Self::Error> {
-        self.inner().trace_transaction(hash).await.map_err(FromErr::from)
-    }
-
-    // Parity namespace
-
-    /// Returns all receipts for that block. Must be done on a parity node.
-    async fn parity_block_receipts<T: Into<BlockNumber> + Send + Sync>(
-        &self,
-        block: T,
-    ) -> Result<Vec<TransactionReceipt>, Self::Error> {
-        self.inner().parity_block_receipts(block).await.map_err(FromErr::from)
-    }
-
-    async fn subscribe<T, R>(
-        &self,
-        params: T,
-    ) -> Result<SubscriptionStream<'_, Self::Provider, R>, Self::Error>
-    where
-        T: Debug + Serialize + Send + Sync,
-        R: DeserializeOwned + Send + Sync,
-        <Self as Middleware>::Provider: PubsubClient,
-    {
-        self.inner().subscribe(params).await.map_err(FromErr::from)
-    }
-
-    async fn unsubscribe<T>(&self, id: T) -> Result<bool, Self::Error>
-    where
-        T: Into<U256> + Send + Sync,
-        <Self as Middleware>::Provider: PubsubClient,
-    {
-        self.inner().unsubscribe(id).await.map_err(FromErr::from)
-    }
-
-    async fn subscribe_blocks(
-        &self,
-    ) -> Result<SubscriptionStream<'_, Self::Provider, Block<TxHash>>, Self::Error>
-    where
-        <Self as Middleware>::Provider: PubsubClient,
-    {
-        self.inner().subscribe_blocks().await.map_err(FromErr::from)
-    }
-
-    async fn subscribe_pending_txs(
-        &self,
-    ) -> Result<SubscriptionStream<'_, Self::Provider, TxHash>, Self::Error>
-    where
-        <Self as Middleware>::Provider: PubsubClient,
-    {
-        self.inner().subscribe_pending_txs().await.map_err(FromErr::from)
-    }
-
-    async fn subscribe_logs<'a>(
-        &'a self,
-        filter: &Filter,
-    ) -> Result<SubscriptionStream<'a, Self::Provider, Log>, Self::Error>
-    where
-        <Self as Middleware>::Provider: PubsubClient,
-    {
-        self.inner().subscribe_logs(filter).await.map_err(FromErr::from)
-    }
-
-    async fn fee_history<T: Into<U256> + serde::Serialize + Send + Sync>(
-        &self,
-        block_count: T,
-        last_block: BlockNumber,
-        reward_percentiles: &[f64],
-    ) -> Result<FeeHistory, Self::Error> {
-        self.inner()
-            .fee_history(block_count, last_block, reward_percentiles)
-            .await
-            .map_err(FromErr::from)
-    }
-
-    async fn create_access_list(
-        &self,
-        tx: &TypedTransaction,
-        block: Option<BlockId>,
-    ) -> Result<AccessListWithGasUsed, Self::Error> {
-        self.inner().create_access_list(tx, block).await.map_err(FromErr::from)
-    }
-}
 
+mod middleware;
 #[cfg(feature = "celo")]
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-pub trait CeloMiddleware: Middleware {
-    async fn get_validators_bls_public_keys<T: Into<BlockId> + Send + Sync>(
-        &self,
-        block_id: T,
-    ) -> Result<Vec<String>, ProviderError> {
-        self.provider().get_validators_bls_public_keys(block_id).await.map_err(FromErr::from)
-    }
-}
+pub use middleware::CeloMiddleware;
+pub use middleware::Middleware;
 
 #[allow(deprecated)]
 pub use test_provider::{GOERLI, MAINNET, ROPSTEN, SEPOLIA};
 
+#[allow(missing_docs)]
 /// Pre-instantiated Infura HTTP clients which rotate through multiple API keys
 /// to prevent rate limits
 pub mod test_provider {
diff --git a/ethers-providers/src/middleware.rs b/ethers-providers/src/middleware.rs
new file mode 100644
index 00000000..a5cc533a
--- /dev/null
+++ b/ethers-providers/src/middleware.rs
@@ -0,0 +1,962 @@
+use async_trait::async_trait;
+use auto_impl::auto_impl;
+use ethers_core::types::{
+    transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
+    *,
+};
+use futures_util::future::join_all;
+use serde::{de::DeserializeOwned, Serialize};
+use std::fmt::Debug;
+use url::Url;
+
+use crate::{
+    erc, EscalatingPending, EscalationPolicy, FilterKind, FilterWatcher, JsonRpcClient, LogQuery,
+    MiddlewareError, NodeInfo, PeerInfo, PendingTransaction, Provider, ProviderError, PubsubClient,
+    SubscriptionStream,
+};
+
+/// A middleware allows customizing requests send and received from an ethereum node.
+///
+/// Writing a middleware is as simple as:
+/// 1. implementing the [`inner`](crate::Middleware::inner) method to point to the next layer in the
+/// "middleware onion", 2. implementing the
+/// [`MiddlewareError`](crate::MiddlewareError) trait on your middleware's
+/// error type 3. implementing any of the methods you want to override
+///
+/// ```
+/// use ethers_providers::{Middleware, MiddlewareError};
+/// use ethers_core::types::{U64, TransactionRequest, U256, transaction::eip2718::TypedTransaction, BlockId};
+/// use thiserror::Error;
+/// use async_trait::async_trait;
+///
+/// #[derive(Debug)]
+/// struct MyMiddleware<M>(M);
+///
+/// #[derive(Error, Debug)]
+/// pub enum MyError<M: Middleware> {
+///     #[error("{0}")]
+///     MiddlewareError(M::Error),
+///
+///     // Add your middleware's specific errors here
+/// }
+///
+/// impl<M: Middleware> MiddlewareError for MyError<M> {
+///     type Inner = M::Error;
+///
+///     fn from_err(src: M::Error) -> MyError<M> {
+///         MyError::MiddlewareError(src)
+///     }
+///
+///     fn as_inner(&self) -> Option<&Self::Inner> {
+///         match self {
+///             MyError::MiddlewareError(e) => Some(e),
+///             _ => None,
+///         }
+///     }
+/// }
+///
+/// #[async_trait]
+/// impl<M> Middleware for MyMiddleware<M>
+/// where
+///     M: Middleware,
+/// {
+///     type Error = MyError<M>;
+///     type Provider = M::Provider;
+///     type Inner = M;
+///
+///     fn inner(&self) -> &M {
+///         &self.0
+///     }
+///
+///     /// Overrides the default `get_block_number` method to always return 0
+///     async fn get_block_number(&self) -> Result<U64, Self::Error> {
+///         Ok(U64::zero())
+///     }
+///
+///     /// Overrides the default `estimate_gas` method to log that it was called,
+///     /// before forwarding the call to the next layer.
+///     async fn estimate_gas(&self, tx: &TypedTransaction, block: Option<BlockId>) -> Result<U256, Self::Error> {
+///         println!("Estimating gas...");
+///         self.inner().estimate_gas(tx, block).await.map_err(MiddlewareError::from_err)
+///     }
+/// }
+/// ```
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+#[auto_impl(&, Box, Arc)]
+pub trait Middleware: Sync + Send + Debug {
+    /// Error type returned by most operations
+    type Error: MiddlewareError<Inner = <<Self as Middleware>::Inner as Middleware>::Error>;
+    /// The JSON-RPC client type at the bottom of the stack
+    type Provider: JsonRpcClient;
+    /// The next-lower middleware in the middleware stack
+    type Inner: Middleware<Provider = Self::Provider>;
+
+    /// Get a reference to the next-lower middleware in the middleware stack
+    fn inner(&self) -> &Self::Inner;
+
+    /// Convert a provider error into the associated error type by successively
+    /// converting it to every intermediate middleware error
+    fn convert_err(p: ProviderError) -> Self::Error {
+        Self::Error::from_provider_err(p)
+    }
+
+    /// The HTTP or Websocket provider.
+    fn provider(&self) -> &Provider<Self::Provider> {
+        self.inner().provider()
+    }
+
+    /// Return the default sender (if any). This will typically be the
+    /// connected node's first address, or the address of a Signer in a lower
+    /// middleware stack
+    fn default_sender(&self) -> Option<Address> {
+        self.inner().default_sender()
+    }
+
+    /// Returns the current client version using the `web3_clientVersion` RPC.
+    async fn client_version(&self) -> Result<String, Self::Error> {
+        self.inner().client_version().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Fill necessary details of a transaction for dispatch
+    ///
+    /// This function is defined on providers to behave as follows:
+    /// 1. populate the `from` field with the default sender
+    /// 2. resolve any ENS names in the tx `to` field
+    /// 3. Estimate gas usage
+    /// 4. Poll and set legacy or 1559 gas prices
+    /// 5. Set the chain_id with the provider's, if not already set
+    ///
+    /// It does NOT set the nonce by default.
+    ///
+    /// Middleware are encouraged to override any values _before_ delegating
+    /// to the inner implementation AND/OR modify the values provided by the
+    /// default implementation _after_ delegating.
+    ///
+    /// E.g. a middleware wanting to double gas prices should consider doing so
+    /// _after_ delegating and allowing the default implementation to poll gas.
+    async fn fill_transaction(
+        &self,
+        tx: &mut TypedTransaction,
+        block: Option<BlockId>,
+    ) -> Result<(), Self::Error> {
+        self.inner().fill_transaction(tx, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Get the block number
+    async fn get_block_number(&self) -> Result<U64, Self::Error> {
+        self.inner().get_block_number().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Sends the transaction to the entire Ethereum network and returns the
+    /// transaction's hash. This will consume gas from the account that signed
+    /// the transaction. This call will fail if no signer is available, and the
+    /// RPC node does  not have an unlocked accounts
+    async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
+        &self,
+        tx: T,
+        block: Option<BlockId>,
+    ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
+        self.inner().send_transaction(tx, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Send a transaction with a simple escalation policy.
+    ///
+    /// `policy` should be a boxed function that maps `original_gas_price`
+    /// and `number_of_previous_escalations` -> `new_gas_price`.
+    ///
+    /// e.g. `Box::new(|start, escalation_index| start * 1250.pow(escalations) /
+    /// 1000.pow(escalations))`
+    async fn send_escalating<'a>(
+        &'a self,
+        tx: &TypedTransaction,
+        escalations: usize,
+        policy: EscalationPolicy,
+    ) -> Result<EscalatingPending<'a, Self::Provider>, Self::Error> {
+        let mut original = tx.clone();
+        self.fill_transaction(&mut original, None).await?;
+
+        // set the nonce, if no nonce is found
+        if original.nonce().is_none() {
+            let nonce =
+                self.get_transaction_count(tx.from().copied().unwrap_or_default(), None).await?;
+            original.set_nonce(nonce);
+        }
+
+        let gas_price = original.gas_price().expect("filled");
+        let sign_futs: Vec<_> = (0..escalations)
+            .map(|i| {
+                let new_price = policy(gas_price, i);
+                let mut r = original.clone();
+                r.set_gas_price(new_price);
+                r
+            })
+            .map(|req| async move {
+                self.sign_transaction(&req, self.default_sender().unwrap_or_default())
+                    .await
+                    .map(|sig| req.rlp_signed(&sig))
+            })
+            .collect();
+
+        // we reverse for convenience. Ensuring that we can always just
+        // `pop()` the next tx off the back later
+        let mut signed = join_all(sign_futs).await.into_iter().collect::<Result<Vec<_>, _>>()?;
+        signed.reverse();
+
+        Ok(EscalatingPending::new(self.provider(), signed))
+    }
+
+    ////// Ethereum Naming Service
+    // The Ethereum Naming Service (ENS) allows easy to remember and use names to
+    // be assigned to Ethereum addresses. Any provider operation which takes an address
+    // may also take an ENS name.
+    //
+    // ENS also provides the ability for a reverse lookup, which determines the name for an address
+    // if it has been configured.
+
+    /// Returns the address that the `ens_name` resolves to (or None if not configured).
+    ///
+    /// # Panics
+    ///
+    /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
+    /// an address. This should theoretically never happen.
+    async fn resolve_name(&self, ens_name: &str) -> Result<Address, Self::Error> {
+        self.inner().resolve_name(ens_name).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the ENS name the `address` resolves to (or None if not configured).
+    /// # Panics
+    ///
+    /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
+    /// a string. This should theoretically never happen.
+    async fn lookup_address(&self, address: Address) -> Result<String, Self::Error> {
+        self.inner().lookup_address(address).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the avatar HTTP link of the avatar that the `ens_name` resolves to (or None
+    /// if not configured)
+    ///
+    /// # Example
+    /// ```no_run
+    /// # use ethers_providers::{Provider, Http as HttpProvider, Middleware};
+    /// # use std::convert::TryFrom;
+    /// # #[tokio::main(flavor = "current_thread")]
+    /// # async fn main() {
+    /// # let provider = Provider::<HttpProvider>::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").unwrap();
+    /// let avatar = provider.resolve_avatar("parishilton.eth").await.unwrap();
+    /// assert_eq!(avatar.to_string(), "https://i.imgur.com/YW3Hzph.jpg");
+    /// # }
+    /// ```
+    ///
+    /// # Panics
+    ///
+    /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
+    /// a string. This should theoretically never happen.
+    async fn resolve_avatar(&self, ens_name: &str) -> Result<Url, Self::Error> {
+        self.inner().resolve_avatar(ens_name).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the URL (not necesserily HTTP) of the image behind a token.
+    ///
+    /// # Example
+    /// ```no_run
+    /// # use ethers_providers::{Provider, Http as HttpProvider, Middleware};
+    /// # use std::{str::FromStr, convert::TryFrom};
+    /// # #[tokio::main(flavor = "current_thread")]
+    /// # async fn main() {
+    /// # let provider = Provider::<HttpProvider>::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").unwrap();
+    /// let token = ethers_providers::erc::ERCNFT::from_str("erc721:0xc92ceddfb8dd984a89fb494c376f9a48b999aafc/9018").unwrap();
+    /// let token_image = provider.resolve_nft(token).await.unwrap();
+    /// assert_eq!(token_image.to_string(), "https://creature.mypinata.cloud/ipfs/QmNwj3aUzXfG4twV3no7hJRYxLLAWNPk6RrfQaqJ6nVJFa/9018.jpg");
+    /// # }
+    /// ```
+    ///
+    /// # Panics
+    ///
+    /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
+    /// a string. This should theoretically never happen.
+    async fn resolve_nft(&self, token: erc::ERCNFT) -> Result<Url, Self::Error> {
+        self.inner().resolve_nft(token).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Fetch a field for the `ens_name` (no None if not configured).
+    ///
+    /// # Panics
+    ///
+    /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
+    /// a string. This should theoretically never happen.
+    async fn resolve_field(&self, ens_name: &str, field: &str) -> Result<String, Self::Error> {
+        self.inner().resolve_field(ens_name, field).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Gets the block at `block_hash_or_number` (transaction hashes only)
+    async fn get_block<T: Into<BlockId> + Send + Sync>(
+        &self,
+        block_hash_or_number: T,
+    ) -> Result<Option<Block<TxHash>>, Self::Error> {
+        self.inner().get_block(block_hash_or_number).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Gets the block at `block_hash_or_number` (full transactions included)
+    async fn get_block_with_txs<T: Into<BlockId> + Send + Sync>(
+        &self,
+        block_hash_or_number: T,
+    ) -> Result<Option<Block<Transaction>>, Self::Error> {
+        self.inner()
+            .get_block_with_txs(block_hash_or_number)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+
+    /// Gets the block uncle count at `block_hash_or_number`
+    async fn get_uncle_count<T: Into<BlockId> + Send + Sync>(
+        &self,
+        block_hash_or_number: T,
+    ) -> Result<U256, Self::Error> {
+        self.inner().get_uncle_count(block_hash_or_number).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Gets the block uncle at `block_hash_or_number` and `idx`
+    async fn get_uncle<T: Into<BlockId> + Send + Sync>(
+        &self,
+        block_hash_or_number: T,
+        idx: U64,
+    ) -> Result<Option<Block<H256>>, Self::Error> {
+        self.inner().get_uncle(block_hash_or_number, idx).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the nonce of the address
+    async fn get_transaction_count<T: Into<NameOrAddress> + Send + Sync>(
+        &self,
+        from: T,
+        block: Option<BlockId>,
+    ) -> Result<U256, Self::Error> {
+        self.inner().get_transaction_count(from, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Sends a transaction to a single Ethereum node and return the estimated amount of gas
+    /// required (as a U256) to send it This is free, but only an estimate. Providing too little
+    /// gas will result in a transaction being rejected (while still consuming all provided
+    /// gas).
+    async fn estimate_gas(
+        &self,
+        tx: &TypedTransaction,
+        block: Option<BlockId>,
+    ) -> Result<U256, Self::Error> {
+        self.inner().estimate_gas(tx, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Sends the read-only (constant) transaction to a single Ethereum node and return the result
+    /// (as bytes) of executing it. This is free, since it does not change any state on the
+    /// blockchain.
+    async fn call(
+        &self,
+        tx: &TypedTransaction,
+        block: Option<BlockId>,
+    ) -> Result<Bytes, Self::Error> {
+        self.inner().call(tx, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Return current client syncing status. If IsFalse sync is over.
+    async fn syncing(&self) -> Result<SyncingStatus, Self::Error> {
+        self.inner().syncing().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the currently configured chain id, a value used in replay-protected
+    /// transaction signing as introduced by EIP-155.
+    async fn get_chainid(&self) -> Result<U256, Self::Error> {
+        self.inner().get_chainid().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the network version.
+    async fn get_net_version(&self) -> Result<String, Self::Error> {
+        self.inner().get_net_version().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the account's balance
+    async fn get_balance<T: Into<NameOrAddress> + Send + Sync>(
+        &self,
+        from: T,
+        block: Option<BlockId>,
+    ) -> Result<U256, Self::Error> {
+        self.inner().get_balance(from, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Gets the transaction with `transaction_hash`
+    async fn get_transaction<T: Send + Sync + Into<TxHash>>(
+        &self,
+        transaction_hash: T,
+    ) -> Result<Option<Transaction>, Self::Error> {
+        self.inner().get_transaction(transaction_hash).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Gets the transaction receipt with `transaction_hash`
+    async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
+        &self,
+        transaction_hash: T,
+    ) -> Result<Option<TransactionReceipt>, Self::Error> {
+        self.inner()
+            .get_transaction_receipt(transaction_hash)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns all receipts for a block.
+    ///
+    /// Note that this uses the `eth_getBlockReceipts` RPC, which is
+    /// non-standard and currently supported by Erigon.
+    async fn get_block_receipts<T: Into<BlockNumber> + Send + Sync>(
+        &self,
+        block: T,
+    ) -> Result<Vec<TransactionReceipt>, Self::Error> {
+        self.inner().get_block_receipts(block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Gets the current gas price as estimated by the node
+    async fn get_gas_price(&self) -> Result<U256, Self::Error> {
+        self.inner().get_gas_price().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Gets a heuristic recommendation of max fee per gas and max priority fee per gas for
+    /// EIP-1559 compatible transactions.
+    async fn estimate_eip1559_fees(
+        &self,
+        estimator: Option<fn(U256, Vec<Vec<U256>>) -> (U256, U256)>,
+    ) -> Result<(U256, U256), Self::Error> {
+        self.inner().estimate_eip1559_fees(estimator).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Gets the accounts on the node
+    async fn get_accounts(&self) -> Result<Vec<Address>, Self::Error> {
+        self.inner().get_accounts().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Send the raw RLP encoded transaction to the entire Ethereum network and returns the
+    /// transaction's hash This will consume gas from the account that signed the transaction.
+    async fn send_raw_transaction<'a>(
+        &'a self,
+        tx: Bytes,
+    ) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> {
+        self.inner().send_raw_transaction(tx).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// This returns true if either the middleware stack contains a `SignerMiddleware`, or the
+    /// JSON-RPC provider has an unlocked key that can sign using the `eth_sign` call. If none of
+    /// the above conditions are met, then the middleware stack is not capable of signing data.
+    async fn is_signer(&self) -> bool {
+        self.inner().is_signer().await
+    }
+
+    /// Signs data using a specific account. This account needs to be unlocked,
+    /// or the middleware stack must contain a `SignerMiddleware`
+    async fn sign<T: Into<Bytes> + Send + Sync>(
+        &self,
+        data: T,
+        from: &Address,
+    ) -> Result<Signature, Self::Error> {
+        self.inner().sign(data, from).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Sign a transaction via RPC call
+    async fn sign_transaction(
+        &self,
+        tx: &TypedTransaction,
+        from: Address,
+    ) -> Result<Signature, Self::Error> {
+        self.inner().sign_transaction(tx, from).await.map_err(MiddlewareError::from_err)
+    }
+
+    ////// Contract state
+
+    /// Returns an array (possibly empty) of logs that match the filter
+    async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>, Self::Error> {
+        self.inner().get_logs(filter).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns a stream of logs are loaded in pages of given page size
+    fn get_logs_paginated<'a>(
+        &'a self,
+        filter: &Filter,
+        page_size: u64,
+    ) -> LogQuery<'a, Self::Provider> {
+        self.inner().get_logs_paginated(filter, page_size)
+    }
+
+    /// Install a new filter on the node.
+    ///
+    /// This method is hidden because filter lifecycle  should be managed by
+    /// the [`FilterWatcher`]
+    #[doc(hidden)]
+    async fn new_filter(&self, filter: FilterKind<'_>) -> Result<U256, Self::Error> {
+        self.inner().new_filter(filter).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Uninstalls a filter.
+    ///
+    /// This method is hidden because filter lifecycle  should be managed by
+    /// the [`FilterWatcher`]
+    #[doc(hidden)]
+    async fn uninstall_filter<T: Into<U256> + Send + Sync>(
+        &self,
+        id: T,
+    ) -> Result<bool, Self::Error> {
+        self.inner().uninstall_filter(id).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Streams event logs matching the filter.
+    ///
+    /// This function streams via a polling system, by repeatedly dispatching
+    /// RPC requests. If possible, prefer using a WS or IPC connection and the
+    /// `stream` interface
+    async fn watch<'a>(
+        &'a self,
+        filter: &Filter,
+    ) -> Result<FilterWatcher<'a, Self::Provider, Log>, Self::Error> {
+        self.inner().watch(filter).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Streams pending transactions.
+    ///
+    /// This function streams via a polling system, by repeatedly dispatching
+    /// RPC requests. If possible, prefer using a WS or IPC connection and the
+    /// `stream` interface
+    async fn watch_pending_transactions(
+        &self,
+    ) -> Result<FilterWatcher<'_, Self::Provider, H256>, Self::Error> {
+        self.inner().watch_pending_transactions().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Polling method for a filter, which returns an array of logs which occurred since last poll.
+    ///
+    /// This method must be called with one of the following return types, depending on the filter
+    /// type:
+    /// - `eth_newBlockFilter`: [`H256`], returns block hashes
+    /// - `eth_newPendingTransactionFilter`: [`H256`], returns transaction hashes
+    /// - `eth_newFilter`: [`Log`], returns raw logs
+    ///
+    /// If one of these types is not used, decoding will fail and the method will
+    /// return an error.
+    ///
+    /// [`H256`]: ethers_core::types::H256
+    /// [`Log`]: ethers_core::types::Log
+    ///
+    /// This method is hidden because filter lifecycle  should be managed by
+    /// the [`FilterWatcher`]
+    #[doc(hidden)]
+    async fn get_filter_changes<T, R>(&self, id: T) -> Result<Vec<R>, Self::Error>
+    where
+        T: Into<U256> + Send + Sync,
+        R: Serialize + DeserializeOwned + Send + Sync + Debug,
+    {
+        self.inner().get_filter_changes(id).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Streams new block hashes
+    ///
+    /// This function streams via a polling system, by repeatedly dispatching
+    /// RPC requests. If possible, prefer using a WS or IPC connection and the
+    /// `stream` interface
+    async fn watch_blocks(&self) -> Result<FilterWatcher<'_, Self::Provider, H256>, Self::Error> {
+        self.inner().watch_blocks().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the deployed code at a given address
+    async fn get_code<T: Into<NameOrAddress> + Send + Sync>(
+        &self,
+        at: T,
+        block: Option<BlockId>,
+    ) -> Result<Bytes, Self::Error> {
+        self.inner().get_code(at, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Get the storage of an address for a particular slot location
+    async fn get_storage_at<T: Into<NameOrAddress> + Send + Sync>(
+        &self,
+        from: T,
+        location: H256,
+        block: Option<BlockId>,
+    ) -> Result<H256, Self::Error> {
+        self.inner().get_storage_at(from, location, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the EIP-1186 proof response
+    /// <https://github.com/ethereum/EIPs/issues/1186>
+    async fn get_proof<T: Into<NameOrAddress> + Send + Sync>(
+        &self,
+        from: T,
+        locations: Vec<H256>,
+        block: Option<BlockId>,
+    ) -> Result<EIP1186ProofResponse, Self::Error> {
+        self.inner().get_proof(from, locations, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns an indication if this node is currently mining.
+    async fn mining(&self) -> Result<bool, Self::Error> {
+        self.inner().mining().await.map_err(MiddlewareError::from_err)
+    }
+
+    // Personal namespace
+    // NOTE: This will eventually need to be enabled by users explicitly because the personal
+    // namespace is being deprecated:
+    // Issue: https://github.com/ethereum/go-ethereum/issues/25948
+    // PR: https://github.com/ethereum/go-ethereum/pull/26390
+
+    /// Sends the given key to the node to be encrypted with the provided
+    /// passphrase and stored.
+    ///
+    /// The key represents a secp256k1 private key and should be 32 bytes.
+    async fn import_raw_key(
+        &self,
+        private_key: Bytes,
+        passphrase: String,
+    ) -> Result<Address, Self::Error> {
+        self.inner()
+            .import_raw_key(private_key, passphrase)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+
+    /// Prompts the node to decrypt the given account from its keystore.
+    ///
+    /// If the duration provided is `None`, then the account will be unlocked
+    /// indefinitely. Otherwise, the account will be unlocked for the provided
+    /// number of seconds.
+    async fn unlock_account<T: Into<Address> + Send + Sync>(
+        &self,
+        account: T,
+        passphrase: String,
+        duration: Option<u64>,
+    ) -> Result<bool, Self::Error> {
+        self.inner()
+            .unlock_account(account, passphrase, duration)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+
+    // Admin namespace
+
+    /// Requests adding the given peer, returning a boolean representing
+    /// whether or not the peer was accepted for tracking.
+    async fn add_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
+        self.inner().add_peer(enode_url).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Requests adding the given peer as a trusted peer, which the node will
+    /// always connect to even when its peer slots are full.
+    async fn add_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
+        self.inner().add_trusted_peer(enode_url).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns general information about the node as well as information about the running p2p
+    /// protocols (e.g. `eth`, `snap`).
+    async fn node_info(&self) -> Result<NodeInfo, Self::Error> {
+        self.inner().node_info().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the list of peers currently connected to the node.
+    async fn peers(&self) -> Result<Vec<PeerInfo>, Self::Error> {
+        self.inner().peers().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Requests to remove the given peer, returning true if the enode was successfully parsed and
+    /// the peer was removed.
+    async fn remove_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
+        self.inner().remove_peer(enode_url).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Requests to remove the given peer, returning a boolean representing whether or not the
+    /// enode url passed was validated. A return value of `true` does not necessarily mean that the
+    /// peer was disconnected.
+    async fn remove_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
+        self.inner().remove_trusted_peer(enode_url).await.map_err(MiddlewareError::from_err)
+    }
+
+    // Miner namespace
+
+    /// Starts the miner with the given number of threads. If threads is nil, the number of workers
+    /// started is equal to the number of logical CPUs that are usable by this process. If mining
+    /// is already running, this method adjust the number of threads allowed to use and updates the
+    /// minimum price required by the transaction pool.
+    async fn start_mining(&self, threads: Option<usize>) -> Result<(), Self::Error> {
+        self.inner().start_mining(threads).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Stop terminates the miner, both at the consensus engine level as well as at
+    /// the block creation level.
+    async fn stop_mining(&self) -> Result<(), Self::Error> {
+        self.inner().stop_mining().await.map_err(MiddlewareError::from_err)
+    }
+
+    // Mempool inspection for Geth's API
+
+    /// Returns the details of all transactions currently pending for inclusion in the next
+    /// block(s), as well as the ones that are being scheduled for future execution only.
+    /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content)
+    async fn txpool_content(&self) -> Result<TxpoolContent, Self::Error> {
+        self.inner().txpool_content().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns a summary of all the transactions currently pending for inclusion in the next
+    /// block(s), as well as the ones that are being scheduled for future execution only.
+    /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect)
+    async fn txpool_inspect(&self) -> Result<TxpoolInspect, Self::Error> {
+        self.inner().txpool_inspect().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns the number of transactions currently pending for inclusion in the next block(s), as
+    /// well as the ones that are being scheduled for future execution only.
+    /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status)
+    async fn txpool_status(&self) -> Result<TxpoolStatus, Self::Error> {
+        self.inner().txpool_status().await.map_err(MiddlewareError::from_err)
+    }
+
+    // Geth `trace` support
+
+    /// After replaying any previous transactions in the same block,
+    /// Replays a transaction, returning the traces configured with passed options
+    async fn debug_trace_transaction(
+        &self,
+        tx_hash: TxHash,
+        trace_options: GethDebugTracingOptions,
+    ) -> Result<GethTrace, Self::Error> {
+        self.inner()
+            .debug_trace_transaction(tx_hash, trace_options)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+
+    /// Executes the given call and returns a number of possible traces for it
+    async fn debug_trace_call<T: Into<TypedTransaction> + Send + Sync>(
+        &self,
+        req: T,
+        block: Option<BlockId>,
+        trace_options: GethDebugTracingCallOptions,
+    ) -> Result<GethTrace, Self::Error> {
+        self.inner()
+            .debug_trace_call(req, block, trace_options)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+
+    // Parity `trace` support
+
+    /// Executes the given call and returns a number of possible traces for it
+    async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
+        &self,
+        req: T,
+        trace_type: Vec<TraceType>,
+        block: Option<BlockNumber>,
+    ) -> Result<BlockTrace, Self::Error> {
+        self.inner().trace_call(req, trace_type, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Executes given calls and returns a number of possible traces for each
+    /// call
+    async fn trace_call_many<T: Into<TypedTransaction> + Send + Sync>(
+        &self,
+        req: Vec<(T, Vec<TraceType>)>,
+        block: Option<BlockNumber>,
+    ) -> Result<Vec<BlockTrace>, Self::Error> {
+        self.inner().trace_call_many(req, block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces
+    async fn trace_raw_transaction(
+        &self,
+        data: Bytes,
+        trace_type: Vec<TraceType>,
+    ) -> Result<BlockTrace, Self::Error> {
+        self.inner()
+            .trace_raw_transaction(data, trace_type)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+
+    /// Replays a transaction, returning the traces
+    async fn trace_replay_transaction(
+        &self,
+        hash: H256,
+        trace_type: Vec<TraceType>,
+    ) -> Result<BlockTrace, Self::Error> {
+        self.inner()
+            .trace_replay_transaction(hash, trace_type)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+
+    /// Replays all transactions in a block returning the requested traces for each transaction
+    async fn trace_replay_block_transactions(
+        &self,
+        block: BlockNumber,
+        trace_type: Vec<TraceType>,
+    ) -> Result<Vec<BlockTrace>, Self::Error> {
+        self.inner()
+            .trace_replay_block_transactions(block, trace_type)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns traces created at given block
+    async fn trace_block(&self, block: BlockNumber) -> Result<Vec<Trace>, Self::Error> {
+        self.inner().trace_block(block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Return traces matching the given filter
+    async fn trace_filter(&self, filter: TraceFilter) -> Result<Vec<Trace>, Self::Error> {
+        self.inner().trace_filter(filter).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns trace at the given position
+    async fn trace_get<T: Into<U64> + Send + Sync>(
+        &self,
+        hash: H256,
+        index: Vec<T>,
+    ) -> Result<Trace, Self::Error> {
+        self.inner().trace_get(hash, index).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Returns all traces of a given transaction
+    async fn trace_transaction(&self, hash: H256) -> Result<Vec<Trace>, Self::Error> {
+        self.inner().trace_transaction(hash).await.map_err(MiddlewareError::from_err)
+    }
+
+    // Parity namespace
+
+    /// Returns all receipts for that block. Must be done on a parity node.
+    async fn parity_block_receipts<T: Into<BlockNumber> + Send + Sync>(
+        &self,
+        block: T,
+    ) -> Result<Vec<TransactionReceipt>, Self::Error> {
+        self.inner().parity_block_receipts(block).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Create a new subscription
+    ///
+    /// This method is hidden as subscription lifecycles are intended to be
+    /// handled by a [`SubscriptionStream`] object.
+    #[doc(hidden)]
+    async fn subscribe<T, R>(
+        &self,
+        params: T,
+    ) -> Result<SubscriptionStream<'_, Self::Provider, R>, Self::Error>
+    where
+        T: Debug + Serialize + Send + Sync,
+        R: DeserializeOwned + Send + Sync,
+        <Self as Middleware>::Provider: PubsubClient,
+    {
+        self.inner().subscribe(params).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Instruct the RPC to cancel a subscription by its ID
+    ///
+    /// This method is hidden as subscription lifecycles are intended to be
+    /// handled by a [`SubscriptionStream`] object
+    #[doc(hidden)]
+    async fn unsubscribe<T>(&self, id: T) -> Result<bool, Self::Error>
+    where
+        T: Into<U256> + Send + Sync,
+        <Self as Middleware>::Provider: PubsubClient,
+    {
+        self.inner().unsubscribe(id).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Subscribe to a stream of incoming blocks.
+    ///
+    /// This function is only available on pubsub clients, such as Websockets
+    /// or IPC. For a polling alternative available over HTTP, use
+    /// [`Middleware::watch_blocks`]. However, be aware that polling increases
+    /// RPC usage drastically.
+    async fn subscribe_blocks(
+        &self,
+    ) -> Result<SubscriptionStream<'_, Self::Provider, Block<TxHash>>, Self::Error>
+    where
+        <Self as Middleware>::Provider: PubsubClient,
+    {
+        self.inner().subscribe_blocks().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Subscribe to a stream of pending transactions.
+    ///
+    /// This function is only available on pubsub clients, such as Websockets
+    /// or IPC. For a polling alternative available over HTTP, use
+    /// [`Middleware::watch_pending_transactions`]. However, be aware that
+    /// polling increases RPC usage drastically.
+    async fn subscribe_pending_txs(
+        &self,
+    ) -> Result<SubscriptionStream<'_, Self::Provider, TxHash>, Self::Error>
+    where
+        <Self as Middleware>::Provider: PubsubClient,
+    {
+        self.inner().subscribe_pending_txs().await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Subscribe to a stream of event logs matchin the provided [`Filter`].
+    ///
+    /// This function is only available on pubsub clients, such as Websockets
+    /// or IPC. For a polling alternative available over HTTP, use
+    /// [`Middleware::watch`]. However, be aware that polling increases
+    /// RPC usage drastically.
+    async fn subscribe_logs<'a>(
+        &'a self,
+        filter: &Filter,
+    ) -> Result<SubscriptionStream<'a, Self::Provider, Log>, Self::Error>
+    where
+        <Self as Middleware>::Provider: PubsubClient,
+    {
+        self.inner().subscribe_logs(filter).await.map_err(MiddlewareError::from_err)
+    }
+
+    /// Query the node for a [`FeeHistory`] object. This objct contains
+    /// information about the EIP-1559 base fee in past blocks, as well as gas
+    /// utilization within those blocks.
+    ///
+    /// See the
+    /// [EIP-1559 documentation](https://eips.ethereum.org/EIPS/eip-1559) for
+    /// details
+    async fn fee_history<T: Into<U256> + serde::Serialize + Send + Sync>(
+        &self,
+        block_count: T,
+        last_block: BlockNumber,
+        reward_percentiles: &[f64],
+    ) -> Result<FeeHistory, Self::Error> {
+        self.inner()
+            .fee_history(block_count, last_block, reward_percentiles)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+
+    /// Querty the node for an EIP-2930 Access List.
+    ///
+    /// See the
+    /// [EIP-2930 documentation](https://eips.ethereum.org/EIPS/eip-2930) for
+    /// details
+    async fn create_access_list(
+        &self,
+        tx: &TypedTransaction,
+        block: Option<BlockId>,
+    ) -> Result<AccessListWithGasUsed, Self::Error> {
+        self.inner().create_access_list(tx, block).await.map_err(MiddlewareError::from_err)
+    }
+}
+
+#[cfg(feature = "celo")]
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+/// Celo-specific extension trait
+pub trait CeloMiddleware: Middleware {
+    /// Get validator BLS public keys
+    async fn get_validators_bls_public_keys<T: Into<BlockId> + Send + Sync>(
+        &self,
+        block_id: T,
+    ) -> Result<Vec<String>, ProviderError> {
+        self.provider()
+            .get_validators_bls_public_keys(block_id)
+            .await
+            .map_err(MiddlewareError::from_err)
+    }
+}
+
+#[cfg(feature = "celo")]
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+impl<T> CeloMiddleware for T where T: Middleware {}
diff --git a/ethers-providers/src/rpc/connections.rs b/ethers-providers/src/rpc/connections.rs
new file mode 100644
index 00000000..e61b5e56
--- /dev/null
+++ b/ethers-providers/src/rpc/connections.rs
@@ -0,0 +1,37 @@
+use std::fmt::Debug;
+
+use async_trait::async_trait;
+use auto_impl::auto_impl;
+use ethers_core::types::U256;
+use serde::{de::DeserializeOwned, Serialize};
+use serde_json::value::RawValue;
+
+use crate::{ProviderError, RpcError};
+
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+#[auto_impl(&, Box, Arc)]
+/// Trait which must be implemented by data transports to be used with the Ethereum
+/// JSON-RPC provider.
+pub trait JsonRpcClient: Debug + Send + Sync {
+    /// A JSON-RPC Error
+    type Error: Into<ProviderError> + RpcError;
+
+    /// Sends a request with the provided JSON-RPC and parameters serialized as JSON
+    async fn request<T, R>(&self, method: &str, params: T) -> Result<R, Self::Error>
+    where
+        T: Debug + Serialize + Send + Sync,
+        R: DeserializeOwned + Send;
+}
+
+/// A transport implementation supporting pub sub subscriptions.
+pub trait PubsubClient: JsonRpcClient {
+    /// The type of stream this transport returns
+    type NotificationStream: futures_core::Stream<Item = Box<RawValue>> + Send + Unpin;
+
+    /// Add a subscription to this transport
+    fn subscribe<T: Into<U256>>(&self, id: T) -> Result<Self::NotificationStream, Self::Error>;
+
+    /// Remove a subscription from this transport
+    fn unsubscribe<T: Into<U256>>(&self, id: T) -> Result<(), Self::Error>;
+}
diff --git a/ethers-providers/src/rpc/mod.rs b/ethers-providers/src/rpc/mod.rs
new file mode 100644
index 00000000..bb5311a7
--- /dev/null
+++ b/ethers-providers/src/rpc/mod.rs
@@ -0,0 +1,11 @@
+mod provider;
+pub use provider::*;
+
+mod transports;
+pub use transports::*;
+
+mod connections;
+pub use connections::*;
+
+mod pubsub;
+pub use pubsub::{PubsubClient, SubscriptionStream};
diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/rpc/provider.rs
similarity index 79%
rename from ethers-providers/src/provider.rs
rename to ethers-providers/src/rpc/provider.rs
index 0ade5eb3..df657258 100644
--- a/ethers-providers/src/provider.rs
+++ b/ethers-providers/src/rpc/provider.rs
@@ -1,20 +1,25 @@
+use ethers_core::types::SyncingStatus;
+
 use crate::{
     call_raw::CallBuilder,
-    ens, erc, maybe,
-    pubsub::{PubsubClient, SubscriptionStream},
+    errors::ProviderError,
+    ext::{ens, erc},
+    rpc::pubsub::{PubsubClient, SubscriptionStream},
     stream::{FilterWatcher, DEFAULT_LOCAL_POLL_INTERVAL, DEFAULT_POLL_INTERVAL},
-    FromErr, Http as HttpProvider, JsonRpcClient, JsonRpcClientWrapper, LogQuery, MockProvider,
-    NodeInfo, PeerInfo, PendingTransaction, QuorumProvider, RwClient, SyncingStatus,
+    utils::maybe,
+    Http as HttpProvider, JsonRpcClient, JsonRpcClientWrapper, LogQuery, MiddlewareError,
+    MockProvider, NodeInfo, PeerInfo, PendingTransaction, QuorumProvider, RwClient,
 };
 
 #[cfg(all(not(target_arch = "wasm32"), feature = "ws"))]
-use crate::transports::Authorization;
+use crate::Authorization;
 #[cfg(not(target_arch = "wasm32"))]
-use crate::transports::{HttpRateLimitRetryPolicy, RetryClient};
+use crate::{HttpRateLimitRetryPolicy, RetryClient};
 
 #[cfg(feature = "celo")]
-use crate::CeloMiddleware;
-use crate::Middleware;
+pub use crate::CeloMiddleware;
+pub use crate::Middleware;
+
 use async_trait::async_trait;
 
 use ethers_core::{
@@ -35,17 +40,22 @@ use serde::{de::DeserializeOwned, Serialize};
 use std::{
     collections::VecDeque, convert::TryFrom, fmt::Debug, str::FromStr, sync::Arc, time::Duration,
 };
-use thiserror::Error;
 use tracing::trace;
 use tracing_futures::Instrument;
 use url::{ParseError, Url};
 
+/// Node Clients
 #[derive(Copy, Clone)]
 pub enum NodeClient {
+    /// Geth
     Geth,
+    /// Erigon
     Erigon,
+    /// OpenEthereum
     OpenEthereum,
+    /// Nethermind
     Nethermind,
+    /// Besu
     Besu,
 }
 
@@ -103,49 +113,6 @@ impl<P> AsRef<P> for Provider<P> {
     }
 }
 
-impl FromErr<ProviderError> for ProviderError {
-    fn from(src: ProviderError) -> Self {
-        src
-    }
-}
-
-#[derive(Debug, Error)]
-/// An error thrown when making a call to the provider
-pub enum ProviderError {
-    /// An internal error in the JSON RPC Client
-    #[error(transparent)]
-    JsonRpcClientError(#[from] Box<dyn std::error::Error + Send + Sync>),
-
-    /// An error during ENS name resolution
-    #[error("ens name not found: {0}")]
-    EnsError(String),
-
-    /// Invalid reverse ENS name
-    #[error("reverse ens name not pointing to itself: {0}")]
-    EnsNotOwned(String),
-
-    #[error(transparent)]
-    SerdeJson(#[from] serde_json::Error),
-
-    #[error(transparent)]
-    HexError(#[from] hex::FromHexError),
-
-    #[error(transparent)]
-    HTTPError(#[from] reqwest::Error),
-
-    #[error("custom error: {0}")]
-    CustomError(String),
-
-    #[error("unsupported RPC")]
-    UnsupportedRPC,
-
-    #[error("unsupported node client")]
-    UnsupportedNodeClient,
-
-    #[error("Attempted to sign a transaction with no available signer. Hint: did you mean to use a SignerMiddleware?")]
-    SignerUnavailable,
-}
-
 /// Types of filters supported by the JSON-RPC.
 #[derive(Clone, Debug)]
 pub enum FilterKind<'a> {
@@ -191,11 +158,13 @@ impl<P: JsonRpcClient> Provider<P> {
     }
 
     #[must_use]
+    /// Set the default sender on the provider
     pub fn with_sender(mut self, address: impl Into<Address>) -> Self {
         self.from = Some(address.into());
         self
     }
 
+    /// Make an RPC request via the internal connection, and return the result.
     pub async fn request<T, R>(&self, method: &str, params: T) -> Result<R, ProviderError>
     where
         T: Debug + Serialize + Send + Sync,
@@ -275,19 +244,6 @@ impl<P: JsonRpcClient> Provider<P> {
     }
 }
 
-#[cfg(feature = "celo")]
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-impl<P: JsonRpcClient> CeloMiddleware for Provider<P> {
-    async fn get_validators_bls_public_keys<T: Into<BlockId> + Send + Sync>(
-        &self,
-        block_id: T,
-    ) -> Result<Vec<String>, ProviderError> {
-        let block_id = utils::serialize(&block_id.into());
-        self.request("istanbul_getValidatorsBLSPublicKeys", [block_id]).await
-    }
-}
-
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 impl<P: JsonRpcClient> Middleware for Provider<P> {
@@ -312,11 +268,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.from
     }
 
-    ////// Blockchain Status
-    //
-    // Functions for querying the state of the blockchain
-
-    /// Returns the current client version using the `web3_clientVersion` RPC.
     async fn client_version(&self) -> Result<String, Self::Error> {
         self.request("web3_clientVersion", ()).await
     }
@@ -377,12 +328,10 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         Ok(())
     }
 
-    /// Gets the latest block number via the `eth_BlockNumber` API
     async fn get_block_number(&self) -> Result<U64, ProviderError> {
         self.request("eth_blockNumber", ()).await
     }
 
-    /// Gets the block at `block_hash_or_number` (transaction hashes only)
     async fn get_block<T: Into<BlockId> + Send + Sync>(
         &self,
         block_hash_or_number: T,
@@ -390,7 +339,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.get_block_gen(block_hash_or_number.into(), false).await
     }
 
-    /// Gets the block at `block_hash_or_number` (full transactions included)
     async fn get_block_with_txs<T: Into<BlockId> + Send + Sync>(
         &self,
         block_hash_or_number: T,
@@ -398,7 +346,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.get_block_gen(block_hash_or_number.into(), true).await
     }
 
-    /// Gets the block uncle count at `block_hash_or_number`
     async fn get_uncle_count<T: Into<BlockId> + Send + Sync>(
         &self,
         block_hash_or_number: T,
@@ -416,7 +363,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         })
     }
 
-    /// Gets the block uncle at `block_hash_or_number` and `idx`
     async fn get_uncle<T: Into<BlockId> + Send + Sync>(
         &self,
         block_hash_or_number: T,
@@ -436,7 +382,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         })
     }
 
-    /// Gets the transaction with `transaction_hash`
     async fn get_transaction<T: Send + Sync + Into<TxHash>>(
         &self,
         transaction_hash: T,
@@ -445,7 +390,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_getTransactionByHash", [hash]).await
     }
 
-    /// Gets the transaction receipt with `transaction_hash`
     async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
         &self,
         transaction_hash: T,
@@ -454,10 +398,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_getTransactionReceipt", [hash]).await
     }
 
-    /// Returns all receipts for a block.
-    ///
-    /// Note that this uses the `eth_getBlockReceipts` RPC, which is
-    /// non-standard and currently supported by Erigon.
     async fn get_block_receipts<T: Into<BlockNumber> + Send + Sync>(
         &self,
         block: T,
@@ -465,7 +405,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_getBlockReceipts", [block.into()]).await
     }
 
-    /// Returns all receipts for that block. Must be done on a parity node.
     async fn parity_block_receipts<T: Into<BlockNumber> + Send + Sync>(
         &self,
         block: T,
@@ -473,13 +412,10 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("parity_getBlockReceipts", vec![block.into()]).await
     }
 
-    /// Gets the current gas price as estimated by the node
     async fn get_gas_price(&self) -> Result<U256, ProviderError> {
         self.request("eth_gasPrice", ()).await
     }
 
-    /// Gets a heuristic recommendation of max fee per gas and max priority fee per gas for
-    /// EIP-1559 compatible transactions.
     async fn estimate_eip1559_fees(
         &self,
         estimator: Option<fn(U256, Vec<Vec<U256>>) -> (U256, U256)>,
@@ -509,12 +445,10 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         Ok((max_fee_per_gas, max_priority_fee_per_gas))
     }
 
-    /// Gets the accounts on the node
     async fn get_accounts(&self) -> Result<Vec<Address>, ProviderError> {
         self.request("eth_accounts", ()).await
     }
 
-    /// Returns the nonce of the address
     async fn get_transaction_count<T: Into<NameOrAddress> + Send + Sync>(
         &self,
         from: T,
@@ -530,7 +464,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_getTransactionCount", [from, block]).await
     }
 
-    /// Returns the account's balance
     async fn get_balance<T: Into<NameOrAddress> + Send + Sync>(
         &self,
         from: T,
@@ -546,29 +479,18 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_getBalance", [from, block]).await
     }
 
-    /// Returns the currently configured chain id, a value used in replay-protected
-    /// transaction signing as introduced by EIP-155.
     async fn get_chainid(&self) -> Result<U256, ProviderError> {
         self.request("eth_chainId", ()).await
     }
 
-    /// Return current client syncing status. If IsFalse sync is over.
     async fn syncing(&self) -> Result<SyncingStatus, Self::Error> {
         self.request("eth_syncing", ()).await
     }
 
-    /// Returns the network version.
     async fn get_net_version(&self) -> Result<String, ProviderError> {
         self.request("net_version", ()).await
     }
 
-    ////// Contract Execution
-    //
-    // These are relatively low-level calls. The Contracts API should usually be used instead.
-
-    /// Sends the read-only (constant) transaction to a single Ethereum node and return the result
-    /// (as bytes) of executing it. This is free, since it does not change any state on the
-    /// blockchain.
     async fn call(
         &self,
         tx: &TypedTransaction,
@@ -579,10 +501,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_call", [tx, block]).await
     }
 
-    /// Sends a transaction to a single Ethereum node and return the estimated amount of gas
-    /// required (as a U256) to send it This is free, but only an estimate. Providing too little
-    /// gas will result in a transaction being rejected (while still consuming all provided
-    /// gas).
     async fn estimate_gas(
         &self,
         tx: &TypedTransaction,
@@ -609,8 +527,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_createAccessList", [tx, block]).await
     }
 
-    /// Sends the transaction to the entire Ethereum network and returns the transaction's hash
-    /// This will consume gas from the account that signed the transaction.
     async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
         &self,
         tx: T,
@@ -623,8 +539,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         Ok(PendingTransaction::new(tx_hash, self))
     }
 
-    /// Send the raw RLP encoded transaction to the entire Ethereum network and returns the
-    /// transaction's hash This will consume gas from the account that signed the transaction.
     async fn send_raw_transaction<'a>(
         &'a self,
         tx: Bytes,
@@ -634,8 +548,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         Ok(PendingTransaction::new(tx_hash, self))
     }
 
-    /// The JSON-RPC provider is at the bottom-most position in the middleware stack. Here we check
-    /// if it has the key for the sender address unlocked, as well as supports the `eth_sign` call.
     async fn is_signer(&self) -> bool {
         match self.from {
             Some(sender) => self.sign(vec![], &sender).await.is_ok(),
@@ -643,7 +555,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         }
     }
 
-    /// Signs data using a specific account. This account needs to be unlocked.
     async fn sign<T: Into<Bytes> + Send + Sync>(
         &self,
         data: T,
@@ -668,12 +579,11 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         _tx: &TypedTransaction,
         _from: Address,
     ) -> Result<Signature, Self::Error> {
-        Err(ProviderError::SignerUnavailable).map_err(FromErr::from)
+        Err(ProviderError::SignerUnavailable).map_err(MiddlewareError::from_err)
     }
 
     ////// Contract state
 
-    /// Returns an array (possibly empty) of logs that match the filter
     async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>, ProviderError> {
         self.request("eth_getLogs", [filter]).await
     }
@@ -682,7 +592,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         LogQuery::new(self, filter).with_page_size(page_size)
     }
 
-    /// Streams matching filter logs
     async fn watch<'a>(
         &'a self,
         filter: &Filter,
@@ -692,7 +601,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         Ok(filter)
     }
 
-    /// Streams new block hashes
     async fn watch_blocks(&self) -> Result<FilterWatcher<'_, P, H256>, ProviderError> {
         let id = self.new_filter(FilterKind::NewBlocks).await?;
         let filter = FilterWatcher::new(id, self).interval(self.get_interval());
@@ -708,8 +616,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         Ok(filter)
     }
 
-    /// Creates a filter object, based on filter options, to notify when the state changes (logs).
-    /// To check if the state has changed, call `get_filter_changes` with the filter id.
     async fn new_filter(&self, filter: FilterKind<'_>) -> Result<U256, ProviderError> {
         let (method, args) = match filter {
             FilterKind::NewBlocks => ("eth_newBlockFilter", vec![]),
@@ -720,7 +626,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request(method, args).await
     }
 
-    /// Uninstalls a filter
     async fn uninstall_filter<T: Into<U256> + Send + Sync>(
         &self,
         id: T,
@@ -729,19 +634,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_uninstallFilter", [id]).await
     }
 
-    /// Polling method for a filter, which returns an array of logs which occurred since last poll.
-    ///
-    /// This method must be called with one of the following return types, depending on the filter
-    /// type:
-    /// - `eth_newBlockFilter`: [`H256`], returns block hashes
-    /// - `eth_newPendingTransactionFilter`: [`H256`], returns transaction hashes
-    /// - `eth_newFilter`: [`Log`], returns raw logs
-    ///
-    /// If one of these types is not used, decoding will fail and the method will
-    /// return an error.
-    ///
-    /// [`H256`]: ethers_core::types::H256
-    /// [`Log`]: ethers_core::types::Log
     async fn get_filter_changes<T, R>(&self, id: T) -> Result<Vec<R>, ProviderError>
     where
         T: Into<U256> + Send + Sync,
@@ -751,7 +643,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_getFilterChanges", [id]).await
     }
 
-    /// Get the storage of an address for a particular slot location
     async fn get_storage_at<T: Into<NameOrAddress> + Send + Sync>(
         &self,
         from: T,
@@ -777,7 +668,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         Ok(H256::from_slice(&Vec::from_hex(value)?))
     }
 
-    /// Returns the deployed code at a given address
     async fn get_code<T: Into<NameOrAddress> + Send + Sync>(
         &self,
         at: T,
@@ -793,8 +683,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_getCode", [at, block]).await
     }
 
-    /// Returns the EIP-1186 proof response
-    /// <https://github.com/ethereum/EIPs/issues/1186>
     async fn get_proof<T: Into<NameOrAddress> + Send + Sync>(
         &self,
         from: T,
@@ -818,15 +706,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("eth_mining", ()).await
     }
 
-    // Personal namespace
-    // NOTE: This will eventually need to be enabled by users explicitly because the personal
-    // namespace is being deprecated:
-    // Issue: https://github.com/ethereum/go-ethereum/issues/25948
-    // PR: https://github.com/ethereum/go-ethereum/pull/26390
-
-    /// Sends the given key to the node to be encrypted with the provided passphrase and stored.
-    ///
-    /// The key represents a secp256k1 private key and should be 32 bytes.
     async fn import_raw_key(
         &self,
         private_key: Bytes,
@@ -842,10 +721,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("personal_importRawKey", [private_key, passphrase]).await
     }
 
-    /// Prompts the node to decrypt the given account from its keystore.
-    ///
-    /// If the duration provided is `None`, then the account will be unlocked indefinitely.
-    /// Otherwise, the account will be unlocked for the provided number of seconds.
     async fn unlock_account<T: Into<Address> + Send + Sync>(
         &self,
         account: T,
@@ -858,88 +733,47 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("personal_unlockAccount", [account, passphrase, duration]).await
     }
 
-    // Admin namespace
-
-    /// Requests adding the given peer, returning a boolean representing whether or not the peer
-    /// was accepted for tracking.
     async fn add_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
         let enode_url = utils::serialize(&enode_url);
         self.request("admin_addPeer", [enode_url]).await
     }
 
-    /// Requests adding the given peer as a trusted peer, which the node will always connect to
-    /// even when its peer slots are full.
     async fn add_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
         let enode_url = utils::serialize(&enode_url);
         self.request("admin_addTrustedPeer", [enode_url]).await
     }
 
-    /// Returns general information about the node as well as information about the running p2p
-    /// protocols (e.g. `eth`, `snap`).
     async fn node_info(&self) -> Result<NodeInfo, Self::Error> {
         self.request("admin_nodeInfo", ()).await
     }
 
-    /// Returns the list of peers currently connected to the node.
     async fn peers(&self) -> Result<Vec<PeerInfo>, Self::Error> {
         self.request("admin_peers", ()).await
     }
 
-    /// Requests to remove the given peer, returning true if the enode was successfully parsed and
-    /// the peer was removed.
     async fn remove_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
         let enode_url = utils::serialize(&enode_url);
         self.request("admin_removePeer", [enode_url]).await
     }
 
-    /// Requests to remove the given peer, returning a boolean representing whether or not the
-    /// enode url passed was validated. A return value of `true` does not necessarily mean that the
-    /// peer was disconnected.
     async fn remove_trusted_peer(&self, enode_url: String) -> Result<bool, Self::Error> {
         let enode_url = utils::serialize(&enode_url);
         self.request("admin_removeTrustedPeer", [enode_url]).await
     }
 
-    // Miner namespace
-
-    /// Starts the miner with the given number of threads. If threads is nil, the number of workers
-    /// started is equal to the number of logical CPUs that are usable by this process. If mining
-    /// is already running, this method adjust the number of threads allowed to use and updates the
-    /// minimum price required by the transaction pool.
     async fn start_mining(&self, threads: Option<usize>) -> Result<(), Self::Error> {
         let threads = utils::serialize(&threads);
         self.request("miner_start", [threads]).await
     }
 
-    /// Stop terminates the miner, both at the consensus engine level as well as at the block
-    /// creation level.
     async fn stop_mining(&self) -> Result<(), Self::Error> {
         self.request("miner_stop", ()).await
     }
 
-    ////// Ethereum Naming Service
-    // The Ethereum Naming Service (ENS) allows easy to remember and use names to
-    // be assigned to Ethereum addresses. Any provider operation which takes an address
-    // may also take an ENS name.
-    //
-    // ENS also provides the ability for a reverse lookup, which determines the name for an address
-    // if it has been configured.
-
-    /// Returns the address that the `ens_name` resolves to (or None if not configured).
-    ///
-    /// # Panics
-    ///
-    /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
-    /// an address. This should theoretically never happen.
     async fn resolve_name(&self, ens_name: &str) -> Result<Address, ProviderError> {
         self.query_resolver(ParamType::Address, ens_name, ens::ADDR_SELECTOR).await
     }
 
-    /// Returns the ENS name the `address` resolves to (or None if not configured).
-    /// # Panics
-    ///
-    /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
-    /// a string. This should theoretically never happen.
     async fn lookup_address(&self, address: Address) -> Result<String, ProviderError> {
         let ens_name = ens::reverse_address(address);
         let domain: String =
@@ -952,25 +786,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         }
     }
 
-    /// Returns the avatar HTTP link of the avatar that the `ens_name` resolves to (or None
-    /// if not configured)
-    ///
-    /// # Example
-    /// ```no_run
-    /// # use ethers_providers::{Provider, Http as HttpProvider, Middleware};
-    /// # use std::convert::TryFrom;
-    /// # #[tokio::main(flavor = "current_thread")]
-    /// # async fn main() {
-    /// # let provider = Provider::<HttpProvider>::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").unwrap();
-    /// let avatar = provider.resolve_avatar("parishilton.eth").await.unwrap();
-    /// assert_eq!(avatar.to_string(), "https://i.imgur.com/YW3Hzph.jpg");
-    /// # }
-    /// ```
-    ///
-    /// # Panics
-    ///
-    /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
-    /// a string. This should theoretically never happen.
     async fn resolve_avatar(&self, ens_name: &str) -> Result<Url, ProviderError> {
         let (field, owner) =
             try_join!(self.resolve_field(ens_name, "avatar"), self.resolve_name(ens_name))?;
@@ -1030,25 +845,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         }
     }
 
-    /// Returns the URL (not necesserily HTTP) of the image behind a token.
-    ///
-    /// # Example
-    /// ```no_run
-    /// # use ethers_providers::{Provider, Http as HttpProvider, Middleware};
-    /// # use std::{str::FromStr, convert::TryFrom};
-    /// # #[tokio::main(flavor = "current_thread")]
-    /// # async fn main() {
-    /// # let provider = Provider::<HttpProvider>::try_from("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").unwrap();
-    /// let token = ethers_providers::erc::ERCNFT::from_str("erc721:0xc92ceddfb8dd984a89fb494c376f9a48b999aafc/9018").unwrap();
-    /// let token_image = provider.resolve_nft(token).await.unwrap();
-    /// assert_eq!(token_image.to_string(), "https://creature.mypinata.cloud/ipfs/QmNwj3aUzXfG4twV3no7hJRYxLLAWNPk6RrfQaqJ6nVJFa/9018.jpg");
-    /// # }
-    /// ```
-    ///
-    /// # Panics
-    ///
-    /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
-    /// a string. This should theoretically never happen.
     async fn resolve_nft(&self, token: erc::ERCNFT) -> Result<Url, ProviderError> {
         let selector = token.type_.resolution_selector();
         let tx = TransactionRequest {
@@ -1070,12 +866,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         Url::parse(&metadata.image).map_err(|e| ProviderError::CustomError(e.to_string()))
     }
 
-    /// Fetch a field for the `ens_name` (no None if not configured).
-    ///
-    /// # Panics
-    ///
-    /// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
-    /// a string. This should theoretically never happen.
     async fn resolve_field(&self, ens_name: &str, field: &str) -> Result<String, ProviderError> {
         let field: String = self
             .query_resolver_parameters(
@@ -1088,28 +878,18 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         Ok(field)
     }
 
-    /// Returns the details of all transactions currently pending for inclusion in the next
-    /// block(s), as well as the ones that are being scheduled for future execution only.
-    /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content)
     async fn txpool_content(&self) -> Result<TxpoolContent, ProviderError> {
         self.request("txpool_content", ()).await
     }
 
-    /// Returns a summary of all the transactions currently pending for inclusion in the next
-    /// block(s), as well as the ones that are being scheduled for future execution only.
-    /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect)
     async fn txpool_inspect(&self) -> Result<TxpoolInspect, ProviderError> {
         self.request("txpool_inspect", ()).await
     }
 
-    /// Returns the number of transactions currently pending for inclusion in the next block(s), as
-    /// well as the ones that are being scheduled for future execution only.
-    /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status)
     async fn txpool_status(&self) -> Result<TxpoolStatus, ProviderError> {
         self.request("txpool_status", ()).await
     }
 
-    /// Executes the given call and returns a number of possible traces for it
     async fn debug_trace_transaction(
         &self,
         tx_hash: TxHash,
@@ -1120,7 +900,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("debug_traceTransaction", [tx_hash, trace_options]).await
     }
 
-    /// Executes the given call and returns a number of possible traces for it
     async fn debug_trace_call<T: Into<TypedTransaction> + Send + Sync>(
         &self,
         req: T,
@@ -1134,7 +913,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("debug_traceCall", [req, block, trace_options]).await
     }
 
-    /// Executes the given call and returns a number of possible traces for it
     async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
         &self,
         req: T,
@@ -1148,7 +926,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("trace_call", [req, trace_type, block]).await
     }
 
-    /// Executes given calls and returns a number of possible traces for each call
     async fn trace_call_many<T: Into<TypedTransaction> + Send + Sync>(
         &self,
         req: Vec<(T, Vec<TraceType>)>,
@@ -1161,7 +938,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("trace_callMany", [req, block]).await
     }
 
-    /// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces
     async fn trace_raw_transaction(
         &self,
         data: Bytes,
@@ -1172,7 +948,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("trace_rawTransaction", [data, trace_type]).await
     }
 
-    /// Replays a transaction, returning the traces
     async fn trace_replay_transaction(
         &self,
         hash: H256,
@@ -1183,7 +958,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("trace_replayTransaction", [hash, trace_type]).await
     }
 
-    /// Replays all transactions in a block returning the requested traces for each transaction
     async fn trace_replay_block_transactions(
         &self,
         block: BlockNumber,
@@ -1194,19 +968,16 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("trace_replayBlockTransactions", [block, trace_type]).await
     }
 
-    /// Returns traces created at given block
     async fn trace_block(&self, block: BlockNumber) -> Result<Vec<Trace>, ProviderError> {
         let block = utils::serialize(&block);
         self.request("trace_block", [block]).await
     }
 
-    /// Return traces matching the given filter
     async fn trace_filter(&self, filter: TraceFilter) -> Result<Vec<Trace>, ProviderError> {
         let filter = utils::serialize(&filter);
         self.request("trace_filter", vec![filter]).await
     }
 
-    /// Returns trace at the given position
     async fn trace_get<T: Into<U64> + Send + Sync>(
         &self,
         hash: H256,
@@ -1218,7 +989,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self.request("trace_get", vec![hash, index]).await
     }
 
-    /// Returns all traces of a given transaction
     async fn trace_transaction(&self, hash: H256) -> Result<Vec<Trace>, ProviderError> {
         let hash = utils::serialize(&hash);
         self.request("trace_transaction", vec![hash]).await
@@ -1458,6 +1228,14 @@ impl Provider<crate::Ws> {
         Ok(Self::new(ws))
     }
 
+    /// Direct connection to a websocket endpoint
+    #[cfg(target_arch = "wasm32")]
+    pub async fn connect(url: &str) -> Result<Self, ProviderError> {
+        let ws = crate::Ws::connect(url).await?;
+        Ok(Self::new(ws))
+    }
+
+    /// Connect to a WS RPC provider with authentication details
     #[cfg(not(target_arch = "wasm32"))]
     pub async fn connect_with_auth(
         url: impl tokio_tungstenite::tungstenite::client::IntoClientRequest + Unpin,
@@ -1466,13 +1244,6 @@ impl Provider<crate::Ws> {
         let ws = crate::Ws::connect_with_auth(url, auth).await?;
         Ok(Self::new(ws))
     }
-
-    /// Direct connection to a websocket endpoint
-    #[cfg(target_arch = "wasm32")]
-    pub async fn connect(url: &str) -> Result<Self, ProviderError> {
-        let ws = crate::Ws::connect(url).await?;
-        Ok(Self::new(ws))
-    }
 }
 
 #[cfg(all(feature = "ipc", any(unix, windows)))]
@@ -1587,6 +1358,8 @@ impl<'a> TryFrom<&'a String> for Provider<HttpProvider> {
 
 #[cfg(not(target_arch = "wasm32"))]
 impl Provider<RetryClient<HttpProvider>> {
+    /// Create a new [`RetryClient`] by connecting to the provided URL. Errors
+    /// if `src` is not a valid URL
     pub fn new_client(src: &str, max_retry: u32, initial_backoff: u64) -> Result<Self, ParseError> {
         Ok(Provider::new(RetryClient::new(
             HttpProvider::new(Url::parse(src)?),
@@ -1711,182 +1484,6 @@ pub fn is_local_endpoint(url: &str) -> bool {
     url.contains("127.0.0.1") || url.contains("localhost")
 }
 
-/// A middleware supporting development-specific JSON RPC methods
-///
-/// # Example
-///
-///```
-/// use ethers_providers::{Provider, Http, Middleware, DevRpcMiddleware};
-/// use ethers_core::types::TransactionRequest;
-/// use ethers_core::utils::Anvil;
-/// use std::convert::TryFrom;
-///
-/// # #[tokio::main(flavor = "current_thread")]
-/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
-/// let anvil = Anvil::new().spawn();
-/// let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
-/// let client = DevRpcMiddleware::new(provider);
-///
-/// // snapshot the initial state
-/// let block0 = client.get_block_number().await.unwrap();
-/// let snap_id = client.snapshot().await.unwrap();
-///
-/// // send a transaction
-/// let accounts = client.get_accounts().await?;
-/// let from = accounts[0];
-/// let to = accounts[1];
-/// let balance_before = client.get_balance(to, None).await?;
-/// let tx = TransactionRequest::new().to(to).value(1000).from(from);
-/// client.send_transaction(tx, None).await?.await?;
-/// let balance_after = client.get_balance(to, None).await?;
-/// assert_eq!(balance_after, balance_before + 1000);
-///
-/// // revert to snapshot
-/// client.revert_to_snapshot(snap_id).await.unwrap();
-/// let balance_after_revert = client.get_balance(to, None).await?;
-/// assert_eq!(balance_after_revert, balance_before);
-/// # Ok(())
-/// # }
-/// ```
-#[cfg(feature = "dev-rpc")]
-pub mod dev_rpc {
-    use crate::{FromErr, Middleware, ProviderError};
-    use async_trait::async_trait;
-    use ethers_core::types::U256;
-    use thiserror::Error;
-
-    use std::fmt::Debug;
-
-    #[derive(Clone, Debug)]
-    pub struct DevRpcMiddleware<M>(M);
-
-    #[derive(Error, Debug)]
-    pub enum DevRpcMiddlewareError<M: Middleware> {
-        #[error("{0}")]
-        MiddlewareError(M::Error),
-
-        #[error("{0}")]
-        ProviderError(ProviderError),
-
-        #[error("Could not revert to snapshot")]
-        NoSnapshot,
-    }
-
-    #[async_trait]
-    impl<M: Middleware> Middleware for DevRpcMiddleware<M> {
-        type Error = DevRpcMiddlewareError<M>;
-        type Provider = M::Provider;
-        type Inner = M;
-
-        fn inner(&self) -> &M {
-            &self.0
-        }
-    }
-
-    impl<M: Middleware> FromErr<M::Error> for DevRpcMiddlewareError<M> {
-        fn from(src: M::Error) -> DevRpcMiddlewareError<M> {
-            DevRpcMiddlewareError::MiddlewareError(src)
-        }
-    }
-
-    impl<M> From<ProviderError> for DevRpcMiddlewareError<M>
-    where
-        M: Middleware,
-    {
-        fn from(src: ProviderError) -> Self {
-            Self::ProviderError(src)
-        }
-    }
-
-    impl<M: Middleware> DevRpcMiddleware<M> {
-        pub fn new(inner: M) -> Self {
-            Self(inner)
-        }
-
-        // Ganache, Hardhat and Anvil increment snapshot ID even if no state has changed
-        pub async fn snapshot(&self) -> Result<U256, DevRpcMiddlewareError<M>> {
-            self.provider().request::<(), U256>("evm_snapshot", ()).await.map_err(From::from)
-        }
-
-        pub async fn revert_to_snapshot(&self, id: U256) -> Result<(), DevRpcMiddlewareError<M>> {
-            let ok = self
-                .provider()
-                .request::<[U256; 1], bool>("evm_revert", [id])
-                .await
-                .map_err(DevRpcMiddlewareError::ProviderError)?;
-            if ok {
-                Ok(())
-            } else {
-                Err(DevRpcMiddlewareError::NoSnapshot)
-            }
-        }
-    }
-    #[cfg(test)]
-    // Celo blocks can not get parsed when used with Ganache
-    #[cfg(not(feature = "celo"))]
-    mod tests {
-        use super::*;
-        use crate::{Http, Provider};
-        use ethers_core::utils::Anvil;
-        use std::convert::TryFrom;
-
-        #[tokio::test]
-        async fn test_snapshot() {
-            let anvil = Anvil::new().spawn();
-            let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
-            let client = DevRpcMiddleware::new(provider);
-
-            // snapshot initial state
-            let block0 = client.get_block_number().await.unwrap();
-            let time0 = client.get_block(block0).await.unwrap().unwrap().timestamp;
-            let snap_id0 = client.snapshot().await.unwrap();
-
-            // mine a new block
-            client.provider().mine(1).await.unwrap();
-
-            // snapshot state
-            let block1 = client.get_block_number().await.unwrap();
-            let time1 = client.get_block(block1).await.unwrap().unwrap().timestamp;
-            let snap_id1 = client.snapshot().await.unwrap();
-
-            // mine some blocks
-            client.provider().mine(5).await.unwrap();
-
-            // snapshot state
-            let block2 = client.get_block_number().await.unwrap();
-            let time2 = client.get_block(block2).await.unwrap().unwrap().timestamp;
-            let snap_id2 = client.snapshot().await.unwrap();
-
-            // mine some blocks
-            client.provider().mine(5).await.unwrap();
-
-            // revert_to_snapshot should reset state to snap id
-            client.revert_to_snapshot(snap_id2).await.unwrap();
-            let block = client.get_block_number().await.unwrap();
-            let time = client.get_block(block).await.unwrap().unwrap().timestamp;
-            assert_eq!(block, block2);
-            assert_eq!(time, time2);
-
-            client.revert_to_snapshot(snap_id1).await.unwrap();
-            let block = client.get_block_number().await.unwrap();
-            let time = client.get_block(block).await.unwrap().unwrap().timestamp;
-            assert_eq!(block, block1);
-            assert_eq!(time, time1);
-
-            // revert_to_snapshot should throw given non-existent or
-            // previously used snapshot
-            let result = client.revert_to_snapshot(snap_id1).await;
-            assert!(result.is_err());
-
-            client.revert_to_snapshot(snap_id0).await.unwrap();
-            let block = client.get_block_number().await.unwrap();
-            let time = client.get_block(block).await.unwrap().unwrap().timestamp;
-            assert_eq!(block, block0);
-            assert_eq!(time, time0);
-        }
-    }
-}
-
 #[cfg(test)]
 #[cfg(not(target_arch = "wasm32"))]
 mod tests {
diff --git a/ethers-providers/src/pubsub.rs b/ethers-providers/src/rpc/pubsub.rs
similarity index 83%
rename from ethers-providers/src/pubsub.rs
rename to ethers-providers/src/rpc/pubsub.rs
index a31c0dcb..690606d6 100644
--- a/ethers-providers/src/pubsub.rs
+++ b/ethers-providers/src/rpc/pubsub.rs
@@ -1,6 +1,6 @@
-use crate::{JsonRpcClient, Middleware, Provider, TransactionStream};
+use crate::{JsonRpcClient, Middleware, Provider};
 
-use ethers_core::types::{TxHash, U256};
+use ethers_core::types::U256;
 
 use futures_util::stream::Stream;
 use pin_project::{pin_project, pinned_drop};
@@ -35,7 +35,7 @@ pub struct SubscriptionStream<'a, P: PubsubClient, R: DeserializeOwned> {
 
     loaded_elements: VecDeque<R>,
 
-    provider: &'a Provider<P>,
+    pub(crate) provider: &'a Provider<P>,
 
     #[pin]
     rx: P::NotificationStream,
@@ -66,6 +66,13 @@ where
         self.provider.unsubscribe(self.id).await
     }
 
+    /// Set the loaded elements buffer. This buffer contains logs waiting for
+    /// the consumer to read. Setting the buffer can be used to add logs
+    /// without receiving them from the RPC node
+    ///
+    /// ### Warning
+    ///
+    /// Setting the buffer will drop any logs in the current buffer.
     pub fn set_loaded_elements(&mut self, loaded_elements: VecDeque<R>) {
         self.loaded_elements = loaded_elements;
     }
@@ -114,18 +121,3 @@ where
         let _ = (*self.provider).as_ref().unsubscribe(self.id);
     }
 }
-
-impl<'a, P> SubscriptionStream<'a, P, TxHash>
-where
-    P: PubsubClient,
-{
-    /// Returns a stream that yields the `Transaction`s for the transaction hashes this stream
-    /// yields.
-    ///
-    /// This internally calls `Provider::get_transaction` with every new transaction.
-    /// No more than n futures will be buffered at any point in time, and less than n may also be
-    /// buffered depending on the state of each future.
-    pub fn transactions_unordered(self, n: usize) -> TransactionStream<'a, P, Self> {
-        TransactionStream::new(self.provider, self, n)
-    }
-}
diff --git a/ethers-providers/src/transports/common.rs b/ethers-providers/src/rpc/transports/common.rs
similarity index 98%
rename from ethers-providers/src/transports/common.rs
rename to ethers-providers/src/rpc/transports/common.rs
index a23b68d5..02591928 100644
--- a/ethers-providers/src/transports/common.rs
+++ b/ethers-providers/src/rpc/transports/common.rs
@@ -186,11 +186,14 @@ impl<'de: 'a, 'a> Deserialize<'de> for Response<'a> {
 /// Use to inject username and password or an auth token into requests
 #[derive(Clone, Debug)]
 pub enum Authorization {
+    /// HTTP Basic Auth
     Basic(String),
+    /// Bearer Auth
     Bearer(String),
 }
 
 impl Authorization {
+    /// Make a new basic auth
     pub fn basic(username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
         let username = username.as_ref();
         let password = password.as_ref();
@@ -198,6 +201,7 @@ impl Authorization {
         Self::Basic(auth_secret)
     }
 
+    /// Make a new bearer auth
     pub fn bearer(token: impl Into<String>) -> Self {
         Self::Bearer(token.into())
     }
diff --git a/ethers-providers/src/transports/http.rs b/ethers-providers/src/rpc/transports/http.rs
similarity index 89%
rename from ethers-providers/src/transports/http.rs
rename to ethers-providers/src/rpc/transports/http.rs
index b110c66f..6198bf2f 100644
--- a/ethers-providers/src/transports/http.rs
+++ b/ethers-providers/src/rpc/transports/http.rs
@@ -1,7 +1,7 @@
 // Code adapted from: https://github.com/althea-net/guac_rs/tree/master/web3/src/jsonrpc
 
 use super::common::{Authorization, JsonRpcError, Request, Response};
-use crate::{provider::ProviderError, JsonRpcClient};
+use crate::{errors::ProviderError, JsonRpcClient};
 use async_trait::async_trait;
 use reqwest::{header::HeaderValue, Client, Error as ReqwestError};
 use serde::{de::DeserializeOwned, Serialize};
@@ -46,7 +46,12 @@ pub enum ClientError {
 
     #[error("Deserialization Error: {err}. Response: {text}")]
     /// Serde JSON Error
-    SerdeJson { err: serde_json::Error, text: String },
+    SerdeJson {
+        /// Underlying error
+        err: serde_json::Error,
+        /// The contents of the HTTP response that could not be deserialized
+        text: String,
+    },
 }
 
 impl From<ClientError> for ProviderError {
@@ -58,13 +63,28 @@ impl From<ClientError> for ProviderError {
     }
 }
 
+impl crate::RpcError for ClientError {
+    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
+        if let ClientError::JsonRpcError(err) = self {
+            Some(err)
+        } else {
+            None
+        }
+    }
+
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        match self {
+            ClientError::SerdeJson { err, .. } => Some(err),
+            _ => None,
+        }
+    }
+}
+
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 impl JsonRpcClient for Provider {
     type Error = ClientError;
 
-    /// Sends a POST request with the provided method and the params serialized as JSON
-    /// over HTTP
     async fn request<T: Serialize + Send + Sync, R: DeserializeOwned>(
         &self,
         method: &str,
diff --git a/ethers-providers/src/transports/ipc.rs b/ethers-providers/src/rpc/transports/ipc.rs
similarity index 96%
rename from ethers-providers/src/transports/ipc.rs
rename to ethers-providers/src/rpc/transports/ipc.rs
index 8f3c597b..8d892257 100644
--- a/ethers-providers/src/transports/ipc.rs
+++ b/ethers-providers/src/rpc/transports/ipc.rs
@@ -1,9 +1,4 @@
 use super::common::Params;
-use crate::{
-    provider::ProviderError,
-    transports::common::{JsonRpcError, Request, Response},
-    JsonRpcClient, PubsubClient,
-};
 use async_trait::async_trait;
 use bytes::{Buf, BytesMut};
 use ethers_core::types::U256;
@@ -31,6 +26,9 @@ use tokio::{
     sync::oneshot::{self, error::RecvError},
 };
 
+use super::common::{JsonRpcError, Request, Response};
+use crate::{errors::ProviderError, JsonRpcClient, PubsubClient};
+
 type FxHashMap<K, V> = std::collections::HashMap<K, V, BuildHasherDefault<FxHasher64>>;
 
 type Pending = oneshot::Sender<Result<Box<RawValue>, JsonRpcError>>;
@@ -471,16 +469,19 @@ pub enum IpcError {
     #[error(transparent)]
     IoError(#[from] io::Error),
 
+    /// Server responded to the request with a valid JSON-RPC error response
     #[error(transparent)]
-    /// Thrown if the response could not be parsed
     JsonRpcError(#[from] JsonRpcError),
 
+    /// Internal channel failed
     #[error("{0}")]
     ChannelError(String),
 
+    /// Listener for request result is gone
     #[error(transparent)]
     RequestCancelled(#[from] RecvError),
 
+    /// IPC server exited
     #[error("The IPC server has exited")]
     ServerExit,
 }
@@ -491,6 +492,23 @@ impl From<IpcError> for ProviderError {
     }
 }
 
+impl crate::RpcError for IpcError {
+    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
+        if let IpcError::JsonRpcError(err) = self {
+            Some(err)
+        } else {
+            None
+        }
+    }
+
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        match self {
+            IpcError::JsonError(err) => Some(err),
+            _ => None,
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/ethers-providers/src/transports/mock.rs b/ethers-providers/src/rpc/transports/mock.rs
similarity index 91%
rename from ethers-providers/src/transports/mock.rs
rename to ethers-providers/src/rpc/transports/mock.rs
index 0e0cedb1..f32c69ea 100644
--- a/ethers-providers/src/transports/mock.rs
+++ b/ethers-providers/src/rpc/transports/mock.rs
@@ -97,16 +97,32 @@ impl MockProvider {
 #[derive(Error, Debug)]
 /// Errors for the `MockProvider`
 pub enum MockError {
+    /// (De)Serialization error
     #[error(transparent)]
     SerdeJson(#[from] serde_json::Error),
 
-    #[error("empty responses array, please push some requests")]
+    /// Empty requests array
+    #[error("empty requests array, please push some requests")]
     EmptyRequests,
 
+    /// Empty responses array
     #[error("empty responses array, please push some responses")]
     EmptyResponses,
 }
 
+impl crate::RpcError for MockError {
+    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
+        None
+    }
+
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        match self {
+            MockError::SerdeJson(e) => Some(e),
+            _ => None,
+        }
+    }
+}
+
 impl From<MockError> for ProviderError {
     fn from(src: MockError) -> Self {
         ProviderError::JsonRpcClientError(Box::new(src))
diff --git a/ethers-providers/src/transports/mod.rs b/ethers-providers/src/rpc/transports/mod.rs
similarity index 88%
rename from ethers-providers/src/transports/mod.rs
rename to ethers-providers/src/rpc/transports/mod.rs
index feb4ed37..6655cdcc 100644
--- a/ethers-providers/src/transports/mod.rs
+++ b/ethers-providers/src/rpc/transports/mod.rs
@@ -1,5 +1,5 @@
-mod common;
-pub use common::Authorization;
+pub(crate) mod common;
+pub use common::{Authorization, JsonRpcError};
 
 mod http;
 pub use self::http::{ClientError as HttpClientError, Provider as Http};
diff --git a/ethers-providers/src/transports/quorum.rs b/ethers-providers/src/rpc/transports/quorum.rs
similarity index 96%
rename from ethers-providers/src/transports/quorum.rs
rename to ethers-providers/src/rpc/transports/quorum.rs
index 23702374..127419fc 100644
--- a/ethers-providers/src/transports/quorum.rs
+++ b/ethers-providers/src/rpc/transports/quorum.rs
@@ -1,4 +1,4 @@
-use crate::{provider::ProviderError, JsonRpcClient, PubsubClient};
+use crate::{errors::ProviderError, JsonRpcClient, PubsubClient};
 use async_trait::async_trait;
 use ethers_core::types::{U256, U64};
 use futures_core::Stream;
@@ -100,10 +100,13 @@ impl<T> QuorumProvider<T> {
         QuorumProviderBuilder::default()
     }
 
+    /// Instantiate a new `QuorumProvider` from a [`Quorum`] and a set of
+    /// providers
     pub fn new(quorum: Quorum, providers: impl IntoIterator<Item = WeightedProvider<T>>) -> Self {
         Self::builder().add_providers(providers).quorum(quorum).build()
     }
 
+    /// Return a reference to the weighted providers
     pub fn providers(&self) -> &[WeightedProvider<T>] {
         &self.providers
     }
@@ -113,6 +116,7 @@ impl<T> QuorumProvider<T> {
         self.quorum_weight
     }
 
+    /// Add a provider to the set
     pub fn add_provider(&mut self, provider: WeightedProvider<T>) {
         self.providers.push(provider);
         self.quorum_weight = self.quorum.weight(&self.providers)
@@ -342,6 +346,7 @@ impl<T> WeightedProvider<T> {
         Self::with_weight(inner, 1)
     }
 
+    /// Instantiate a `WeightedProvider` with a set weight
     pub fn with_weight(inner: T, weight: u64) -> Self {
         assert!(weight > 0);
         Self { inner, weight }
@@ -352,7 +357,23 @@ impl<T> WeightedProvider<T> {
 /// Error thrown when sending an HTTP request
 pub enum QuorumError {
     #[error("No Quorum reached.")]
-    NoQuorumReached { values: Vec<Value>, errors: Vec<ProviderError> },
+    /// NoQuorumReached
+    NoQuorumReached {
+        /// Returned responses
+        values: Vec<Value>,
+        /// Returned errors
+        errors: Vec<ProviderError>,
+    },
+}
+
+impl crate::RpcError for QuorumError {
+    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
+        None
+    }
+
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        None
+    }
 }
 
 impl From<QuorumError> for ProviderError {
@@ -361,9 +382,12 @@ impl From<QuorumError> for ProviderError {
     }
 }
 
+/// Wrapper trait for [`crate::JsonRpcClient`] that erases generics and is
+/// object-safe. This trait is not intended for outside implementation
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 pub trait JsonRpcClientWrapper: Send + Sync + Debug {
+    /// Make a request, as [`crate::JsonRpcClient`]
     async fn request(&self, method: &str, params: QuorumParams) -> Result<Value, ProviderError>;
 }
 type NotificationStream =
diff --git a/ethers-providers/src/transports/retry.rs b/ethers-providers/src/rpc/transports/retry.rs
similarity index 96%
rename from ethers-providers/src/transports/retry.rs
rename to ethers-providers/src/rpc/transports/retry.rs
index 8b51573c..9af8c7a0 100644
--- a/ethers-providers/src/transports/retry.rs
+++ b/ethers-providers/src/rpc/transports/retry.rs
@@ -2,7 +2,7 @@
 //! with an exponential backoff.
 
 use super::{common::JsonRpcError, http::ClientError};
-use crate::{provider::ProviderError, JsonRpcClient};
+use crate::{errors::ProviderError, JsonRpcClient};
 use async_trait::async_trait;
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use std::{
@@ -50,7 +50,7 @@ pub trait RetryPolicy<E>: Send + Sync + Debug {
 pub struct RetryClient<T>
 where
     T: JsonRpcClient,
-    T::Error: Sync + Send + 'static,
+    T::Error: crate::RpcError + Sync + Send + 'static,
 {
     inner: T,
     requests_enqueued: AtomicU32,
@@ -114,6 +114,7 @@ where
     }
 }
 
+/// Builder for a [`RetryClient`]
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct RetryClientBuilder {
     /// How many connection `TimedOut` should be retried.
@@ -201,14 +202,36 @@ impl Default for RetryClientBuilder {
 /// 3. Request timed out i.e. max retries were already made.
 #[derive(Error, Debug)]
 pub enum RetryClientError {
+    /// Internal provider error
     #[error(transparent)]
     ProviderError(ProviderError),
+    /// Timeout while making requests
     TimeoutError,
+    /// (De)Serialization error
     #[error(transparent)]
     SerdeJson(serde_json::Error),
+    /// TimerError (wasm only)
     TimerError,
 }
 
+impl crate::RpcError for RetryClientError {
+    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
+        if let RetryClientError::ProviderError(err) = self {
+            err.as_error_response()
+        } else {
+            None
+        }
+    }
+
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        match self {
+            RetryClientError::ProviderError(e) => e.as_serde_error(),
+            RetryClientError::SerdeJson(e) => Some(e),
+            _ => None,
+        }
+    }
+}
+
 impl std::fmt::Display for RetryClientError {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "{self:?}")
diff --git a/ethers-providers/src/transports/rw.rs b/ethers-providers/src/rpc/transports/rw.rs
similarity index 79%
rename from ethers-providers/src/transports/rw.rs
rename to ethers-providers/src/rpc/transports/rw.rs
index 945c2c52..ed8fdda3 100644
--- a/ethers-providers/src/transports/rw.rs
+++ b/ethers-providers/src/rpc/transports/rw.rs
@@ -1,7 +1,7 @@
 //! A [JsonRpcClient] implementation that serves as a wrapper around two different [JsonRpcClient]
 //! and uses a dedicated client for read and the other for write operations
 
-use crate::{provider::ProviderError, JsonRpcClient};
+use crate::{errors::ProviderError, JsonRpcClient};
 use async_trait::async_trait;
 use serde::{de::DeserializeOwned, Serialize};
 use thiserror::Error;
@@ -68,9 +68,9 @@ impl<Read, Write> RwClient<Read, Write> {
 pub enum RwClientError<Read, Write>
 where
     Read: JsonRpcClient,
-    <Read as JsonRpcClient>::Error: Sync + Send + 'static,
+    <Read as JsonRpcClient>::Error: crate::RpcError + Sync + Send + 'static,
     Write: JsonRpcClient,
-    <Write as JsonRpcClient>::Error: Sync + Send + 'static,
+    <Write as JsonRpcClient>::Error: crate::RpcError + Sync + Send + 'static,
 {
     /// Thrown if the _read_ request failed
     #[error(transparent)]
@@ -80,6 +80,28 @@ where
     Write(Write::Error),
 }
 
+impl<Read, Write> crate::RpcError for RwClientError<Read, Write>
+where
+    Read: JsonRpcClient,
+    <Read as JsonRpcClient>::Error: crate::RpcError + Sync + Send + 'static,
+    Write: JsonRpcClient,
+    <Write as JsonRpcClient>::Error: crate::RpcError + Sync + Send + 'static,
+{
+    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
+        match self {
+            RwClientError::Read(e) => e.as_error_response(),
+            RwClientError::Write(e) => e.as_error_response(),
+        }
+    }
+
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        match self {
+            RwClientError::Read(e) => e.as_serde_error(),
+            RwClientError::Write(e) => e.as_serde_error(),
+        }
+    }
+}
+
 impl<Read, Write> From<RwClientError<Read, Write>> for ProviderError
 where
     Read: JsonRpcClient + 'static,
diff --git a/ethers-providers/src/transports/ws.rs b/ethers-providers/src/rpc/transports/ws.rs
similarity index 96%
rename from ethers-providers/src/transports/ws.rs
rename to ethers-providers/src/rpc/transports/ws.rs
index a8865a8e..1b59741b 100644
--- a/ethers-providers/src/transports/ws.rs
+++ b/ethers-providers/src/rpc/transports/ws.rs
@@ -1,9 +1,9 @@
-use super::common::{Params, Response};
 use crate::{
-    provider::ProviderError,
-    transports::common::{JsonRpcError, Request},
+    errors::ProviderError,
+    rpc::transports::common::{JsonRpcError, Params, Request, Response},
     JsonRpcClient, PubsubClient,
 };
+
 use async_trait::async_trait;
 use ethers_core::types::U256;
 use futures_channel::{mpsc, oneshot};
@@ -461,9 +461,11 @@ pub enum ClientError {
     TungsteniteError(#[from] WsError),
 
     #[error("{0}")]
+    /// Error in internal mpsc channel
     ChannelError(String),
 
-    #[error(transparent)]
+    #[error("{0}")]
+    /// Error in internal oneshot channel
     Canceled(#[from] oneshot::Canceled),
 
     /// Remote server sent a Close message
@@ -472,7 +474,7 @@ pub enum ClientError {
     WsClosed(CloseFrame<'static>),
 
     /// Remote server sent a Close message
-    #[error("Websocket closed with info")]
+    #[error("Websocket closed")]
     #[cfg(target_arch = "wasm32")]
     WsClosed,
 
@@ -496,6 +498,23 @@ pub enum ClientError {
     RequestError(#[from] http::Error),
 }
 
+impl crate::RpcError for ClientError {
+    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
+        if let ClientError::JsonRpcError(err) = self {
+            Some(err)
+        } else {
+            None
+        }
+    }
+
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        match self {
+            ClientError::JsonError(err) => Some(err),
+            _ => None,
+        }
+    }
+}
+
 impl From<ClientError> for ProviderError {
     fn from(src: ClientError) -> Self {
         ProviderError::JsonRpcClientError(Box::new(src))
diff --git a/ethers-providers/src/stream/mod.rs b/ethers-providers/src/stream/mod.rs
new file mode 100644
index 00000000..6b213097
--- /dev/null
+++ b/ethers-providers/src/stream/mod.rs
@@ -0,0 +1,5 @@
+pub mod tx_stream;
+pub use tx_stream::*;
+
+pub mod watcher;
+pub use watcher::*;
diff --git a/ethers-providers/src/stream.rs b/ethers-providers/src/stream/tx_stream.rs
similarity index 60%
rename from ethers-providers/src/stream.rs
rename to ethers-providers/src/stream/tx_stream.rs
index 1d3a0efe..b4fb723e 100644
--- a/ethers-providers/src/stream.rs
+++ b/ethers-providers/src/stream/tx_stream.rs
@@ -1,143 +1,22 @@
-#![allow(clippy::return_self_not_must_use)]
-
-use crate::{JsonRpcClient, Middleware, PinBoxFut, Provider, ProviderError};
-use ethers_core::types::{Transaction, TxHash, U256};
-use futures_core::{stream::Stream, Future};
-use futures_util::{stream, stream::FuturesUnordered, FutureExt, StreamExt};
-use pin_project::pin_project;
-use serde::{de::DeserializeOwned, Serialize};
 use std::{
     collections::VecDeque,
-    fmt::Debug,
     pin::Pin,
     task::{Context, Poll},
-    time::Duration,
-    vec::IntoIter,
 };
 
-#[cfg(not(target_arch = "wasm32"))]
-use futures_timer::Delay;
-#[cfg(target_arch = "wasm32")]
-use wasm_timer::Delay;
-
-// https://github.com/tomusdrw/rust-web3/blob/befcb2fb8f3ca0a43e3081f68886fa327e64c8e6/src/api/eth_filter.rs#L20
-pub fn interval(duration: Duration) -> impl Stream<Item = ()> + Send + Unpin {
-    stream::unfold((), move |_| Delay::new(duration).map(|_| Some(((), ())))).map(drop)
-}
-
-/// The default polling interval for filters and pending transactions
-pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_millis(7000);
-
-/// The polling interval to use for local endpoints, See [`crate::is_local_endpoint()`]
-pub const DEFAULT_LOCAL_POLL_INTERVAL: Duration = Duration::from_millis(100);
-
-enum FilterWatcherState<'a, R> {
-    WaitForInterval,
-    GetFilterChanges(PinBoxFut<'a, Vec<R>>),
-    NextItem(IntoIter<R>),
-}
-
-#[must_use = "filters do nothing unless you stream them"]
-/// Streams data from an installed filter via `eth_getFilterChanges`
-#[pin_project]
-pub struct FilterWatcher<'a, P, R> {
-    /// The filter's installed id on the ethereum node
-    pub id: U256,
-
-    provider: &'a Provider<P>,
-
-    // The polling interval
-    interval: Box<dyn Stream<Item = ()> + Send + Unpin>,
-    /// statemachine driven by the Stream impl
-    state: FilterWatcherState<'a, R>,
-}
-
-impl<'a, P, R> FilterWatcher<'a, P, R>
-where
-    P: JsonRpcClient,
-    R: Send + Sync + DeserializeOwned,
-{
-    /// Creates a new watcher with the provided factory and filter id.
-    pub fn new<T: Into<U256>>(id: T, provider: &'a Provider<P>) -> Self {
-        Self {
-            id: id.into(),
-            interval: Box::new(interval(DEFAULT_POLL_INTERVAL)),
-            state: FilterWatcherState::WaitForInterval,
-            provider,
-        }
-    }
-
-    /// Sets the stream's polling interval
-    pub fn interval(mut self, duration: Duration) -> Self {
-        self.interval = Box::new(interval(duration));
-        self
-    }
-
-    /// Alias for Box::pin, must be called in order to pin the stream and be able
-    /// to call `next` on it.
-    pub fn stream(self) -> Pin<Box<Self>> {
-        Box::pin(self)
-    }
-}
-
-// Advances the filter's state machine
-impl<'a, P, R> Stream for FilterWatcher<'a, P, R>
-where
-    P: JsonRpcClient,
-    R: Serialize + Send + Sync + DeserializeOwned + Debug + 'a,
-{
-    type Item = R;
-
-    fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
-        let mut this = self.project();
-        let id = *this.id;
+use futures_core::{stream::Stream, Future};
+use futures_util::{
+    self,
+    stream::{FuturesUnordered, StreamExt},
+    FutureExt,
+};
 
-        loop {
-            *this.state = match &mut this.state {
-                FilterWatcherState::WaitForInterval => {
-                    // Wait the polling period
-                    let _ready = futures_util::ready!(this.interval.poll_next_unpin(cx));
-                    let fut = Box::pin(this.provider.get_filter_changes(id));
-                    FilterWatcherState::GetFilterChanges(fut)
-                }
-                FilterWatcherState::GetFilterChanges(fut) => {
-                    // NOTE: If the provider returns an error, this will return an empty
-                    // vector. Should we make this return a Result instead? Ideally if we're
-                    // in a streamed loop we wouldn't want the loop to terminate if an error
-                    // is encountered (since it might be a temporary error).
-                    let items: Vec<R> =
-                        futures_util::ready!(fut.as_mut().poll(cx)).unwrap_or_default();
-                    FilterWatcherState::NextItem(items.into_iter())
-                }
-                // Consume 1 element from the vector. If more elements are in the vector,
-                // the next call will immediately go to this branch instead of trying to get
-                // filter changes again. Once the whole vector is consumed, it will poll again
-                // for new logs
-                FilterWatcherState::NextItem(iter) => {
-                    if let item @ Some(_) = iter.next() {
-                        return Poll::Ready(item)
-                    }
-                    FilterWatcherState::WaitForInterval
-                }
-            };
-        }
-    }
-}
+use ethers_core::types::{Transaction, TxHash};
 
-impl<'a, P> FilterWatcher<'a, P, TxHash>
-where
-    P: JsonRpcClient,
-{
-    /// Returns a stream that yields the `Transaction`s for the transaction hashes this stream
-    /// yields.
-    ///
-    /// This internally calls `Provider::get_transaction` with every new transaction.
-    /// No more than n futures will be buffered at any point in time, and less than n may also be
-    /// buffered depending on the state of each future.
-    pub fn transactions_unordered(self, n: usize) -> TransactionStream<'a, P, Self> {
-        TransactionStream::new(self.provider, self, n)
-    }
-}
+use crate::{
+    FilterWatcher, JsonRpcClient, Middleware, Provider, ProviderError, PubsubClient,
+    SubscriptionStream,
+};
 
 /// Errors `TransactionStream` can throw
 #[derive(Debug, thiserror::Error)]
@@ -159,25 +38,26 @@ impl From<GetTransactionError> for ProviderError {
 }
 
 #[cfg(not(target_arch = "wasm32"))]
-type TransactionFut<'a> = Pin<Box<dyn Future<Output = TransactionResult> + Send + 'a>>;
+pub(crate) type TransactionFut<'a> = Pin<Box<dyn Future<Output = TransactionResult> + Send + 'a>>;
+
 #[cfg(target_arch = "wasm32")]
-type TransactionFut<'a> = Pin<Box<dyn Future<Output = TransactionResult> + 'a>>;
+pub(crate) type TransactionFut<'a> = Pin<Box<dyn Future<Output = TransactionResult> + 'a>>;
 
-type TransactionResult = Result<Transaction, GetTransactionError>;
+pub(crate) type TransactionResult = Result<Transaction, GetTransactionError>;
 
 /// Drains a stream of transaction hashes and yields entire `Transaction`.
 #[must_use = "streams do nothing unless polled"]
 pub struct TransactionStream<'a, P, St> {
     /// Currently running futures pending completion.
-    pending: FuturesUnordered<TransactionFut<'a>>,
+    pub(crate) pending: FuturesUnordered<TransactionFut<'a>>,
     /// Temporary buffered transaction that get started as soon as another future finishes.
-    buffered: VecDeque<TxHash>,
+    pub(crate) buffered: VecDeque<TxHash>,
     /// The provider that gets the transaction
-    provider: &'a Provider<P>,
+    pub(crate) provider: &'a Provider<P>,
     /// A stream of transaction hashes.
-    stream: St,
+    pub(crate) stream: St,
     /// max allowed futures to execute at once.
-    max_concurrent: usize,
+    pub(crate) max_concurrent: usize,
 }
 
 impl<'a, P: JsonRpcClient, St> TransactionStream<'a, P, St> {
@@ -193,7 +73,7 @@ impl<'a, P: JsonRpcClient, St> TransactionStream<'a, P, St> {
     }
 
     /// Push a future into the set
-    fn push_tx(&mut self, tx: TxHash) {
+    pub(crate) fn push_tx(&mut self, tx: TxHash) {
         let fut = self.provider.get_transaction(tx).then(move |res| match res {
             Ok(Some(tx)) => futures_util::future::ok(tx),
             Ok(None) => futures_util::future::err(GetTransactionError::NotFound(tx)),
@@ -254,17 +134,47 @@ where
     }
 }
 
+impl<'a, P> FilterWatcher<'a, P, TxHash>
+where
+    P: JsonRpcClient,
+{
+    /// Returns a stream that yields the `Transaction`s for the transaction hashes this stream
+    /// yields.
+    ///
+    /// This internally calls `Provider::get_transaction` with every new transaction.
+    /// No more than n futures will be buffered at any point in time, and less than n may also be
+    /// buffered depending on the state of each future.
+    pub fn transactions_unordered(self, n: usize) -> TransactionStream<'a, P, Self> {
+        TransactionStream::new(self.provider, self, n)
+    }
+}
+
+impl<'a, P> SubscriptionStream<'a, P, TxHash>
+where
+    P: PubsubClient,
+{
+    /// Returns a stream that yields the `Transaction`s for the transaction hashes this stream
+    /// yields.
+    ///
+    /// This internally calls `Provider::get_transaction` with every new transaction.
+    /// No more than n futures will be buffered at any point in time, and less than n may also be
+    /// buffered depending on the state of each future.
+    pub fn transactions_unordered(self, n: usize) -> TransactionStream<'a, P, Self> {
+        TransactionStream::new(self.provider, self, n)
+    }
+}
+
 #[cfg(test)]
 #[cfg(not(target_arch = "wasm32"))]
 mod tests {
     use super::*;
-    use crate::{Http, Ws};
+    use crate::{stream::tx_stream, Http, Ws};
     use ethers_core::{
-        types::{TransactionReceipt, TransactionRequest},
+        types::{Transaction, TransactionReceipt, TransactionRequest},
         utils::Anvil,
     };
     use futures_util::{FutureExt, StreamExt};
-    use std::{collections::HashSet, convert::TryFrom};
+    use std::{collections::HashSet, convert::TryFrom, time::Duration};
 
     #[tokio::test]
     async fn can_stream_pending_transactions() {
@@ -342,9 +252,9 @@ mod tests {
             }))
             .await;
 
-        let stream = TransactionStream::new(
+        let stream = tx_stream::TransactionStream::new(
             &provider,
-            stream::iter(txs.iter().cloned().map(|tx| tx.unwrap().transaction_hash)),
+            futures_util::stream::iter(txs.iter().cloned().map(|tx| tx.unwrap().transaction_hash)),
             10,
         );
         let res =
diff --git a/ethers-providers/src/stream/watcher.rs b/ethers-providers/src/stream/watcher.rs
new file mode 100644
index 00000000..8c37ec26
--- /dev/null
+++ b/ethers-providers/src/stream/watcher.rs
@@ -0,0 +1,115 @@
+use crate::{
+    utils::{interval, PinBoxFut},
+    JsonRpcClient, Middleware, Provider,
+};
+use ethers_core::types::U256;
+use futures_core::stream::Stream;
+use futures_util::StreamExt;
+use pin_project::pin_project;
+use serde::{de::DeserializeOwned, Serialize};
+use std::{
+    fmt::Debug,
+    pin::Pin,
+    task::{Context, Poll},
+    time::Duration,
+    vec::IntoIter,
+};
+
+/// The default polling interval for filters and pending transactions
+pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_millis(7000);
+
+/// The polling interval to use for local endpoints, See [`crate::is_local_endpoint()`]
+pub const DEFAULT_LOCAL_POLL_INTERVAL: Duration = Duration::from_millis(100);
+
+enum FilterWatcherState<'a, R> {
+    WaitForInterval,
+    GetFilterChanges(PinBoxFut<'a, Vec<R>>),
+    NextItem(IntoIter<R>),
+}
+
+#[must_use = "filters do nothing unless you stream them"]
+/// Streams data from an installed filter via `eth_getFilterChanges`
+#[pin_project]
+pub struct FilterWatcher<'a, P, R> {
+    /// The filter's installed id on the ethereum node
+    pub id: U256,
+
+    pub(crate) provider: &'a Provider<P>,
+
+    // The polling interval
+    interval: Box<dyn Stream<Item = ()> + Send + Unpin>,
+    /// statemachine driven by the Stream impl
+    state: FilterWatcherState<'a, R>,
+}
+
+impl<'a, P, R> FilterWatcher<'a, P, R>
+where
+    P: JsonRpcClient,
+    R: Send + Sync + DeserializeOwned,
+{
+    /// Creates a new watcher with the provided factory and filter id.
+    pub fn new<T: Into<U256>>(id: T, provider: &'a Provider<P>) -> Self {
+        Self {
+            id: id.into(),
+            interval: Box::new(interval(DEFAULT_POLL_INTERVAL)),
+            state: FilterWatcherState::WaitForInterval,
+            provider,
+        }
+    }
+
+    /// Sets the stream's polling interval
+    pub fn interval(mut self, duration: Duration) -> Self {
+        self.interval = Box::new(interval(duration));
+        self
+    }
+
+    /// Alias for Box::pin, must be called in order to pin the stream and be able
+    /// to call `next` on it.
+    pub fn stream(self) -> Pin<Box<Self>> {
+        Box::pin(self)
+    }
+}
+
+// Advances the filter's state machine
+impl<'a, P, R> Stream for FilterWatcher<'a, P, R>
+where
+    P: JsonRpcClient,
+    R: Serialize + Send + Sync + DeserializeOwned + Debug + 'a,
+{
+    type Item = R;
+
+    fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
+        let mut this = self.project();
+        let id = *this.id;
+
+        loop {
+            *this.state = match &mut this.state {
+                FilterWatcherState::WaitForInterval => {
+                    // Wait the polling period
+                    let _ready = futures_util::ready!(this.interval.poll_next_unpin(cx));
+                    let fut = Box::pin(this.provider.get_filter_changes(id));
+                    FilterWatcherState::GetFilterChanges(fut)
+                }
+                FilterWatcherState::GetFilterChanges(fut) => {
+                    // NOTE: If the provider returns an error, this will return an empty
+                    // vector. Should we make this return a Result instead? Ideally if we're
+                    // in a streamed loop we wouldn't want the loop to terminate if an error
+                    // is encountered (since it might be a temporary error).
+                    let items: Vec<R> =
+                        futures_util::ready!(fut.as_mut().poll(cx)).unwrap_or_default();
+                    FilterWatcherState::NextItem(items.into_iter())
+                }
+                // Consume 1 element from the vector. If more elements are in the vector,
+                // the next call will immediately go to this branch instead of trying to get
+                // filter changes again. Once the whole vector is consumed, it will poll again
+                // for new logs
+                FilterWatcherState::NextItem(iter) => {
+                    if let item @ Some(_) = iter.next() {
+                        return Poll::Ready(item)
+                    }
+                    FilterWatcherState::WaitForInterval
+                }
+            };
+        }
+    }
+}
diff --git a/ethers-providers/src/call_raw.rs b/ethers-providers/src/toolbox/call_raw.rs
similarity index 98%
rename from ethers-providers/src/call_raw.rs
rename to ethers-providers/src/toolbox/call_raw.rs
index 24e19b76..a3f0b771 100644
--- a/ethers-providers/src/call_raw.rs
+++ b/ethers-providers/src/toolbox/call_raw.rs
@@ -1,6 +1,6 @@
 //! Overrides for the `eth_call` rpc method
 
-use crate::{JsonRpcClient, PinBoxFut, Provider, ProviderError};
+use crate::{utils::PinBoxFut, JsonRpcClient, Provider, ProviderError};
 use ethers_core::{
     types::{
         transaction::eip2718::TypedTransaction, Address, BlockId, BlockNumber, Bytes, H256, U256,
@@ -59,6 +59,7 @@ impl<P: fmt::Debug> fmt::Debug for CallBuilder<'_, P> {
 }
 
 impl<'a, P> CallBuilder<'a, P> {
+    /// Instantiate a new call builder based on `tx`
     pub fn new(provider: &'a Provider<P>, tx: &'a TypedTransaction) -> Self {
         Self::Build(Caller::new(provider, tx))
     }
@@ -125,6 +126,7 @@ pub struct Caller<'a, P> {
 }
 
 impl<'a, P> Caller<'a, P> {
+    /// Instantiate a new `Caller` based on `tx`
     pub fn new(provider: &'a Provider<P>, tx: &'a TypedTransaction) -> Self {
         Self { provider, input: CallInput::new(tx) }
     }
@@ -192,6 +194,7 @@ impl<T: fmt::Debug, F> fmt::Debug for Map<T, F> {
 }
 
 impl<T, F> Map<T, F> {
+    /// Instantiate a new map
     pub fn new(inner: T, f: F) -> Self {
         Self { inner, f }
     }
@@ -236,12 +239,16 @@ pub mod spoof {
     /// The state elements to override for a particular account.
     #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
     pub struct Account {
+        /// Account nonce
         #[serde(skip_serializing_if = "Option::is_none")]
         pub nonce: Option<U64>,
+        /// Account balance
         #[serde(skip_serializing_if = "Option::is_none")]
         pub balance: Option<U256>,
+        /// Account code
         #[serde(skip_serializing_if = "Option::is_none")]
         pub code: Option<Bytes>,
+        /// Account storage
         #[serde(flatten, skip_serializing_if = "Option::is_none")]
         pub storage: Option<Storage>,
     }
@@ -275,8 +282,10 @@ pub mod spoof {
     /// as a diff on the existing state.
     #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
     pub enum Storage {
+        /// State Diff
         #[serde(rename = "stateDiff")]
         Diff(HashMap<H256, H256>),
+        /// State override
         #[serde(rename = "state")]
         Replace(HashMap<H256, H256>),
     }
diff --git a/ethers-providers/src/log_query.rs b/ethers-providers/src/toolbox/log_query.rs
similarity index 92%
rename from ethers-providers/src/log_query.rs
rename to ethers-providers/src/toolbox/log_query.rs
index 43cf7086..b7b2f799 100644
--- a/ethers-providers/src/log_query.rs
+++ b/ethers-providers/src/toolbox/log_query.rs
@@ -1,4 +1,4 @@
-use super::{JsonRpcClient, Middleware, PinBoxFut, Provider, ProviderError};
+use crate::{utils::PinBoxFut, JsonRpcClient, Middleware, Provider, ProviderError};
 use ethers_core::types::{Filter, Log, U64};
 use futures_core::stream::Stream;
 use std::{
@@ -8,6 +8,9 @@ use std::{
 };
 use thiserror::Error;
 
+/// A log query provides streaming access to historical logs via a paginated
+/// request. For streaming access to future logs, use [`Middleware::watch`] or
+/// [`Middleware::subscribe_logs`]
 pub struct LogQuery<'a, P> {
     provider: &'a Provider<P>,
     filter: Filter,
@@ -29,6 +32,7 @@ impl<'a, P> LogQuery<'a, P>
 where
     P: JsonRpcClient,
 {
+    /// Instantiate a new `LogQuery`
     pub fn new(provider: &'a Provider<P>, filter: &Filter) -> Self {
         Self {
             provider,
@@ -56,10 +60,13 @@ macro_rules! rewake_with_new_state {
     };
 }
 
+/// Errors while querying for logs
 #[derive(Error, Debug)]
 pub enum LogQueryError<E> {
+    /// Error loading latest block
     #[error(transparent)]
     LoadLastBlockError(E),
+    /// Error loading logs from block range
     #[error(transparent)]
     LoadLogsError(E),
 }
diff --git a/ethers-providers/src/toolbox/mod.rs b/ethers-providers/src/toolbox/mod.rs
new file mode 100644
index 00000000..5d31172f
--- /dev/null
+++ b/ethers-providers/src/toolbox/mod.rs
@@ -0,0 +1,11 @@
+mod pending_transaction;
+pub use pending_transaction::PendingTransaction;
+
+mod pending_escalator;
+pub use pending_escalator::EscalatingPending;
+
+mod log_query;
+pub use log_query::{LogQuery, LogQueryError};
+
+pub mod call_raw;
+pub use call_raw::*;
diff --git a/ethers-providers/src/pending_escalator.rs b/ethers-providers/src/toolbox/pending_escalator.rs
similarity index 98%
rename from ethers-providers/src/pending_escalator.rs
rename to ethers-providers/src/toolbox/pending_escalator.rs
index 5b77b60d..77803ba4 100644
--- a/ethers-providers/src/pending_escalator.rs
+++ b/ethers-providers/src/toolbox/pending_escalator.rs
@@ -15,7 +15,9 @@ use futures_timer::Delay;
 #[cfg(target_arch = "wasm32")]
 use wasm_timer::Delay;
 
-use crate::{JsonRpcClient, Middleware, PendingTransaction, PinBoxFut, Provider, ProviderError};
+use crate::{
+    utils::PinBoxFut, JsonRpcClient, Middleware, PendingTransaction, Provider, ProviderError,
+};
 
 /// States for the EscalatingPending future
 enum EscalatorStates<'a, P> {
diff --git a/ethers-providers/src/pending_transaction.rs b/ethers-providers/src/toolbox/pending_transaction.rs
similarity index 99%
rename from ethers-providers/src/pending_transaction.rs
rename to ethers-providers/src/toolbox/pending_transaction.rs
index 92a89be8..74bead5a 100644
--- a/ethers-providers/src/pending_transaction.rs
+++ b/ethers-providers/src/toolbox/pending_transaction.rs
@@ -1,4 +1,7 @@
-use crate::{stream::interval, JsonRpcClient, Middleware, PinBoxFut, Provider, ProviderError};
+use crate::{
+    utils::{interval, PinBoxFut},
+    JsonRpcClient, Middleware, Provider, ProviderError,
+};
 use ethers_core::types::{Transaction, TransactionReceipt, TxHash, U64};
 use futures_core::stream::Stream;
 use futures_util::stream::StreamExt;
diff --git a/ethers-providers/src/utils.rs b/ethers-providers/src/utils.rs
new file mode 100644
index 00000000..ec53ec2a
--- /dev/null
+++ b/ethers-providers/src/utils.rs
@@ -0,0 +1,40 @@
+use ethers_core::types::U256;
+use futures_util::{stream, FutureExt, StreamExt};
+use std::{future::Future, pin::Pin};
+
+#[cfg(not(target_arch = "wasm32"))]
+use futures_timer::Delay;
+#[cfg(target_arch = "wasm32")]
+use wasm_timer::Delay;
+
+use crate::ProviderError;
+
+/// A simple gas escalation policy
+pub type EscalationPolicy = Box<dyn Fn(U256, usize) -> U256 + Send + Sync>;
+
+// Helper type alias
+#[cfg(target_arch = "wasm32")]
+pub(crate) type PinBoxFut<'a, T> = Pin<Box<dyn Future<Output = Result<T, ProviderError>> + 'a>>;
+#[cfg(not(target_arch = "wasm32"))]
+pub(crate) type PinBoxFut<'a, T> =
+    Pin<Box<dyn Future<Output = Result<T, ProviderError>> + Send + 'a>>;
+
+/// Calls the future if `item` is None, otherwise returns a `futures::ok`
+pub async fn maybe<F, T, E>(item: Option<T>, f: F) -> Result<T, E>
+where
+    F: Future<Output = Result<T, E>>,
+{
+    if let Some(item) = item {
+        futures_util::future::ok(item).await
+    } else {
+        f.await
+    }
+}
+
+// https://github.com/tomusdrw/rust-web3/blob/befcb2fb8f3ca0a43e3081f68886fa327e64c8e6/src/api/eth_filter.rs#L20
+/// Create a stream that emits items at a fixed interval. Used for rate control
+pub fn interval(
+    duration: std::time::Duration,
+) -> impl futures_core::stream::Stream<Item = ()> + Send + Unpin {
+    stream::unfold((), move |_| Delay::new(duration).map(|_| Some(((), ())))).map(drop)
+}
diff --git a/examples/middleware/examples/create_custom_middleware.rs b/examples/middleware/examples/create_custom_middleware.rs
index 56a4a21d..170dcfd1 100644
--- a/examples/middleware/examples/create_custom_middleware.rs
+++ b/examples/middleware/examples/create_custom_middleware.rs
@@ -5,7 +5,7 @@ use ethers::{
         utils::{parse_units, Anvil},
     },
     middleware::MiddlewareBuilder,
-    providers::{FromErr, Http, Middleware, PendingTransaction, Provider},
+    providers::{Http, Middleware, MiddlewareError, PendingTransaction, Provider},
     signers::{LocalWallet, Signer},
 };
 use thiserror::Error;
@@ -98,7 +98,7 @@ where
         println!("Raised transaction gas: {raised_gas:?} wei");
 
         // Dispatch the call to the inner layer
-        self.inner().send_transaction(tx, block).await.map_err(FromErr::from)
+        self.inner().send_transaction(tx, block).await.map_err(MiddlewareError::from_err)
     }
 }
 
@@ -122,10 +122,19 @@ pub enum GasMiddlewareError<M: Middleware> {
     NoGasSetForTransaction,
 }
 
-impl<M: Middleware> FromErr<M::Error> for GasMiddlewareError<M> {
-    fn from(src: M::Error) -> Self {
+impl<M: Middleware> MiddlewareError for GasMiddlewareError<M> {
+    type Inner = M::Error;
+
+    fn from_err(src: M::Error) -> Self {
         GasMiddlewareError::MiddlewareError(src)
     }
+
+    fn as_inner(&self) -> Option<&Self::Inner> {
+        match self {
+            GasMiddlewareError::MiddlewareError(e) => Some(e),
+            _ => None,
+        }
+    }
 }
 
 #[tokio::main]
diff --git a/examples/providers/examples/custom.rs b/examples/providers/examples/custom.rs
index 450731e6..f21678d0 100644
--- a/examples/providers/examples/custom.rs
+++ b/examples/providers/examples/custom.rs
@@ -7,12 +7,14 @@ use std::fmt::Debug;
 use thiserror::Error;
 use url::Url;
 
-/// First we must create an error type, and implement [`From`] for [`ProviderError`].
+/// First we must create an error type, and implement [`From`] for
+/// [`ProviderError`].
 ///
-/// Here we are using [`thiserror`](https://docs.rs/thiserror) to wrap [`WsClientError`]
-/// and [`IpcError`].
-/// This also provides a conversion implementation ([`From`]) for both, so we can use
-/// the [question mark operator](https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html)
+/// Here we are using [`thiserror`](https://docs.rs/thiserror) to wrap
+/// [`WsClientError`] and [`IpcError`].
+///
+/// This also provides a conversion implementation ([`From`]) for both, so we
+/// can use the [question mark operator](https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html)
 /// later on in our implementations.
 #[derive(Debug, Error)]
 pub enum WsOrIpcError {
@@ -23,6 +25,36 @@ pub enum WsOrIpcError {
     Ipc(#[from] IpcError),
 }
 
+/// In order to use our `WsOrIpcError` in the RPC client, we have to implement
+/// this trait.
+///
+/// [`RpcError`] helps other parts off the stack get access to common provider
+/// error cases. For example, any RPC connection may have a `serde_json` error,
+/// so we want to make those easily accessible, so we implement
+/// `as_serde_error()`
+///
+/// In addition, RPC requests may return JSON errors from the node, describing
+/// why the request failed. In order to make these accessible, we implement
+/// `as_error_response()`.
+impl RpcError for WsOrIpcError {
+    fn as_error_response(&self) -> Option<&ethers::providers::JsonRpcError> {
+        match self {
+            WsOrIpcError::Ws(e) => e.as_error_response(),
+            WsOrIpcError::Ipc(e) => e.as_error_response(),
+        }
+    }
+
+    fn as_serde_error(&self) -> Option<&serde_json::Error> {
+        match self {
+            WsOrIpcError::Ws(WsClientError::JsonError(e)) => Some(e),
+            WsOrIpcError::Ipc(IpcError::JsonError(e)) => Some(e),
+            _ => None,
+        }
+    }
+}
+
+/// This implementation helps us convert our Error to the library's
+/// [`ProviderError`] so that we can use the `?` operator
 impl From<WsOrIpcError> for ProviderError {
     fn from(value: WsOrIpcError) -> Self {
         Self::JsonRpcClientError(Box::new(value))


Test patch:
diff --git a/ethers-contract/tests/it/contract.rs b/ethers-contract/tests/it/contract.rs
index b5e85b03..4f16d827 100644
--- a/ethers-contract/tests/it/contract.rs
+++ b/ethers-contract/tests/it/contract.rs
@@ -13,7 +13,9 @@ mod eth_tests {
         utils::{keccak256, Anvil},
     };
     use ethers_derive_eip712::*;
-    use ethers_providers::{Http, Middleware, PendingTransaction, Provider, StreamExt};
+    use ethers_providers::{
+        Http, Middleware, MiddlewareError, PendingTransaction, Provider, StreamExt,
+    };
     use ethers_signers::{LocalWallet, Signer};
     use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration};
 
@@ -24,13 +26,20 @@ mod eth_tests {
 
     #[derive(Debug)]
     pub struct MwErr<M: Middleware>(M::Error);
-    impl<M> ethers_providers::FromErr<M::Error> for MwErr<M>
+
+    impl<M> MiddlewareError for MwErr<M>
     where
         M: Middleware,
     {
-        fn from(src: M::Error) -> Self {
+        type Inner = M::Error;
+
+        fn from_err(src: M::Error) -> Self {
             Self(src)
         }
+
+        fn as_inner(&self) -> Option<&Self::Inner> {
+            Some(&self.0)
+        }
     }
 
     impl<M: Middleware> std::fmt::Display for MwErr<M> {
diff --git a/ethers-providers/tests/ws_errors.rs b/ethers-providers/tests/ws_errors.rs
index 6ae85213..170771a8 100644
--- a/ethers-providers/tests/ws_errors.rs
+++ b/ethers-providers/tests/ws_errors.rs
@@ -25,7 +25,7 @@ async fn graceful_disconnect_on_ws_errors() {
     spawn_ws_server().await;
 
     // Connect to the fake server
-    let (ws, _) = connect_async(format!("ws://{}", WS_ENDPOINT)).await.unwrap();
+    let (ws, _) = connect_async(format!("ws://{WS_ENDPOINT}")).await.unwrap();
     let provider = Provider::new(Ws::new(ws));
     let filter = Filter::new().event("Transfer(address,address,uint256)");
     let mut stream = provider.subscribe_logs(&filter).await.unwrap();