Repository: gakonst/ethers-rs
Pull request #357 (2021-08-01 18:58:00)

feat: typed txs provider / middleware changes (part 3)
* Introduces new functions for manipulating the contents of `TypedTransaction`
* Makes the gas price and gas escalator middlewares error with eip1559 txs (primarily because they are deprecated by eip1559)
* Adds support for `eth_feeHistory` and `eth_createAccessList`
* Adjust `send_transaction` to take `T: Into<TypedTransaction>`
* Adjust `estimate_gas`, `call`, `trace_call` to take `TypedTransaction` (from `TransactionRequest`). You'll need to add a `.into()` in your calls to convert `TransactionRequest`s to `TypedTransaction`s.
* Adds helper function at the base of the middleware stack for filling a transaction's information, to avoid leaking typed tx internals to other packages


Patch:
diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs
index b8a4c76b..d9f18f30 100644
--- a/ethers-contract/src/call.rs
+++ b/ethers-contract/src/call.rs
@@ -102,7 +102,7 @@ where
     /// Returns the estimated gas cost for the underlying transaction to be executed
     pub async fn estimate_gas(&self) -> Result<U256, ContractError<M>> {
         self.client
-            .estimate_gas(&self.tx)
+            .estimate_gas(&self.tx.clone().into())
             .await
             .map_err(ContractError::MiddlewareError)
     }
@@ -119,7 +119,7 @@ where
     pub async fn call(&self) -> Result<D, ContractError<M>> {
         let bytes = self
             .client
-            .call(&self.tx, self.block)
+            .call(&self.tx.clone().into(), self.block)
             .await
             .map_err(ContractError::MiddlewareError)?;
 
diff --git a/ethers-core/src/types/transaction/eip1559.rs b/ethers-core/src/types/transaction/eip1559.rs
index 29d39da2..d8c2adda 100644
--- a/ethers-core/src/types/transaction/eip1559.rs
+++ b/ethers-core/src/types/transaction/eip1559.rs
@@ -1,4 +1,4 @@
-use super::{eip2930::AccessList, rlp_opt};
+use super::{eip2930::AccessList, normalize_v, rlp_opt};
 use crate::{
     types::{Address, Bytes, NameOrAddress, Signature, H256, U256, U64},
     utils::keccak256,
@@ -37,12 +37,8 @@ pub struct Eip1559TransactionRequest {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub nonce: Option<U256>,
 
-    #[serde(
-        rename = "accessList",
-        default,
-        skip_serializing_if = "Option::is_none"
-    )]
-    pub access_list: Option<AccessList>,
+    #[serde(rename = "accessList", default)]
+    pub access_list: AccessList,
 
     #[serde(
         rename = "maxPriorityFeePerGas",
@@ -121,7 +117,7 @@ impl Eip1559TransactionRequest {
 
     /// Sets the `access_list` field in the transaction to the provided value
     pub fn access_list<T: Into<AccessList>>(mut self, access_list: T) -> Self {
-        self.access_list = Some(access_list.into());
+        self.access_list = access_list.into();
         self
     }
 
@@ -148,10 +144,12 @@ impl Eip1559TransactionRequest {
     pub fn rlp_signed<T: Into<U64>>(&self, chain_id: T, signature: &Signature) -> Bytes {
         let mut rlp = RlpStream::new();
         rlp.begin_unbounded_list();
+        let chain_id = chain_id.into();
         self.rlp_base(chain_id, &mut rlp);
 
         // append the signature
-        rlp.append(&signature.v);
+        let v = normalize_v(signature.v, chain_id);
+        rlp.append(&v);
         rlp.append(&signature.r);
         rlp.append(&signature.s);
         rlp.finalize_unbounded_list();
@@ -167,6 +165,6 @@ impl Eip1559TransactionRequest {
         rlp_opt(rlp, &self.to.as_ref());
         rlp_opt(rlp, &self.value);
         rlp_opt(rlp, &self.data.as_ref().map(|d| d.as_ref()));
-        rlp_opt(rlp, &self.access_list);
+        rlp.append(&self.access_list);
     }
 }
diff --git a/ethers-core/src/types/transaction/eip2718.rs b/ethers-core/src/types/transaction/eip2718.rs
index fd23e178..1ae93f25 100644
--- a/ethers-core/src/types/transaction/eip2718.rs
+++ b/ethers-core/src/types/transaction/eip2718.rs
@@ -19,9 +19,10 @@ pub enum TypedTransaction {
     Eip1559(Eip1559TransactionRequest),
 }
 
+use TypedTransaction::*;
+
 impl TypedTransaction {
     pub fn from(&self) -> Option<&Address> {
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.from.as_ref(),
             Eip2930(inner) => inner.tx.from.as_ref(),
@@ -30,7 +31,6 @@ impl TypedTransaction {
     }
 
     pub fn set_from(&mut self, from: Address) {
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.from = Some(from),
             Eip2930(inner) => inner.tx.from = Some(from),
@@ -39,7 +39,6 @@ impl TypedTransaction {
     }
 
     pub fn to(&self) -> Option<&NameOrAddress> {
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.to.as_ref(),
             Eip2930(inner) => inner.tx.to.as_ref(),
@@ -49,7 +48,6 @@ impl TypedTransaction {
 
     pub fn set_to<T: Into<NameOrAddress>>(&mut self, to: T) {
         let to = to.into();
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.to = Some(to),
             Eip2930(inner) => inner.tx.to = Some(to),
@@ -58,7 +56,6 @@ impl TypedTransaction {
     }
 
     pub fn nonce(&self) -> Option<&U256> {
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.nonce.as_ref(),
             Eip2930(inner) => inner.tx.nonce.as_ref(),
@@ -68,7 +65,6 @@ impl TypedTransaction {
 
     pub fn set_nonce<T: Into<U256>>(&mut self, nonce: T) {
         let nonce = nonce.into();
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.nonce = Some(nonce),
             Eip2930(inner) => inner.tx.nonce = Some(nonce),
@@ -77,7 +73,6 @@ impl TypedTransaction {
     }
 
     pub fn value(&self) -> Option<&U256> {
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.value.as_ref(),
             Eip2930(inner) => inner.tx.value.as_ref(),
@@ -87,7 +82,6 @@ impl TypedTransaction {
 
     pub fn set_value<T: Into<U256>>(&mut self, value: T) {
         let value = value.into();
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.value = Some(value),
             Eip2930(inner) => inner.tx.value = Some(value),
@@ -96,7 +90,6 @@ impl TypedTransaction {
     }
 
     pub fn gas(&self) -> Option<&U256> {
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.gas.as_ref(),
             Eip2930(inner) => inner.tx.gas.as_ref(),
@@ -106,7 +99,6 @@ impl TypedTransaction {
 
     pub fn set_gas<T: Into<U256>>(&mut self, gas: T) {
         let gas = gas.into();
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.gas = Some(gas),
             Eip2930(inner) => inner.tx.gas = Some(gas),
@@ -116,7 +108,6 @@ impl TypedTransaction {
 
     pub fn set_gas_price<T: Into<U256>>(&mut self, gas_price: T) {
         let gas_price = gas_price.into();
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.gas_price = Some(gas_price),
             Eip2930(inner) => inner.tx.gas_price = Some(gas_price),
@@ -128,7 +119,6 @@ impl TypedTransaction {
     }
 
     pub fn data(&self) -> Option<&Bytes> {
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.data.as_ref(),
             Eip2930(inner) => inner.tx.data.as_ref(),
@@ -137,7 +127,6 @@ impl TypedTransaction {
     }
 
     pub fn set_data(&mut self, data: Bytes) {
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => inner.data = Some(data),
             Eip2930(inner) => inner.tx.data = Some(data),
@@ -146,12 +135,10 @@ impl TypedTransaction {
     }
 
     pub fn rlp_signed<T: Into<U64>>(&self, chain_id: T, signature: &Signature) -> Bytes {
-        use TypedTransaction::*;
         let mut encoded = vec![];
         match self {
-            Legacy(inner) => {
-                encoded.extend_from_slice(&[0x0]);
-                encoded.extend_from_slice(inner.rlp_signed(signature).as_ref());
+            Legacy(ref tx) => {
+                encoded.extend_from_slice(tx.rlp_signed(signature).as_ref());
             }
             Eip2930(inner) => {
                 encoded.extend_from_slice(&[0x1]);
@@ -162,17 +149,14 @@ impl TypedTransaction {
                 encoded.extend_from_slice(inner.rlp_signed(chain_id, signature).as_ref());
             }
         };
-
-        rlp::encode(&encoded).freeze().into()
+        encoded.into()
     }
 
     pub fn rlp<T: Into<U64>>(&self, chain_id: T) -> Bytes {
         let chain_id = chain_id.into();
         let mut encoded = vec![];
-        use TypedTransaction::*;
         match self {
             Legacy(inner) => {
-                encoded.extend_from_slice(&[0x0]);
                 encoded.extend_from_slice(inner.rlp(chain_id).as_ref());
             }
             Eip2930(inner) => {
diff --git a/ethers-core/src/types/transaction/eip2930.rs b/ethers-core/src/types/transaction/eip2930.rs
index b8577d69..f78730ba 100644
--- a/ethers-core/src/types/transaction/eip2930.rs
+++ b/ethers-core/src/types/transaction/eip2930.rs
@@ -1,5 +1,5 @@
-use super::request::TransactionRequest;
-use crate::types::{Address, Bytes, Signature, H256, U64};
+use super::{normalize_v, request::TransactionRequest};
+use crate::types::{Address, Bytes, Signature, H256, U256, U64};
 
 use rlp::RlpStream;
 use rlp_derive::{RlpEncodable, RlpEncodableWrapper};
@@ -13,6 +13,13 @@ const NUM_EIP2930_FIELDS: usize = 8;
 #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, RlpEncodableWrapper)]
 pub struct AccessList(pub Vec<AccessListItem>);
 
+#[derive(Serialize, Deserialize, Clone, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct AccessListWithGasUsed {
+    pub access_list: AccessList,
+    pub gas_used: U256,
+}
+
 impl From<Vec<AccessListItem>> for AccessList {
     fn from(src: Vec<AccessListItem>) -> AccessList {
         AccessList(src)
@@ -69,13 +76,15 @@ impl Eip2930TransactionRequest {
         let mut rlp = RlpStream::new();
         rlp.begin_list(NUM_EIP2930_FIELDS + 3);
 
-        rlp.append(&chain_id.into());
+        let chain_id = chain_id.into();
+        rlp.append(&chain_id);
         self.tx.rlp_base(&mut rlp);
         // append the access list in addition to the base rlp encoding
         rlp.append(&self.access_list);
 
         // append the signature
-        rlp.append(&signature.v);
+        let v = normalize_v(signature.v, chain_id);
+        rlp.append(&v);
         rlp.append(&signature.r);
         rlp.append(&signature.s);
         rlp.out().freeze().into()
@@ -104,8 +113,6 @@ mod tests {
 
         let hash = tx.sighash(1);
         let sig: Signature = "c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101".parse().unwrap();
-        let enc = tx.rlp_signed(1, &sig);
-
         assert_eq!(
             hash,
             "49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3"
@@ -113,6 +120,7 @@ mod tests {
                 .unwrap()
         );
 
+        let enc = rlp::encode(&tx.rlp_signed(1, &sig).as_ref());
         let expected = "b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521";
         assert_eq!(hex::encode(enc.to_vec()), expected);
     }
diff --git a/ethers-core/src/types/transaction/mod.rs b/ethers-core/src/types/transaction/mod.rs
index 5e95d089..51c5f9c9 100644
--- a/ethers-core/src/types/transaction/mod.rs
+++ b/ethers-core/src/types/transaction/mod.rs
@@ -21,3 +21,12 @@ pub(super) fn rlp_opt<T: rlp::Encodable>(rlp: &mut rlp::RlpStream, opt: &Option<
         rlp.append(&"");
     }
 }
+
+/// normalizes the signature back to 0/1
+pub(crate) fn normalize_v(v: u64, chain_id: crate::types::U64) -> u64 {
+    if v > 1 {
+        v - chain_id.as_u64() * 2 - 35
+    } else {
+        v
+    }
+}
diff --git a/ethers-middleware/src/gas_escalator/mod.rs b/ethers-middleware/src/gas_escalator/mod.rs
index 5eff862f..f8301f10 100644
--- a/ethers-middleware/src/gas_escalator/mod.rs
+++ b/ethers-middleware/src/gas_escalator/mod.rs
@@ -1,4 +1,5 @@
 mod geometric;
+use ethers_core::types::transaction::eip2718::TypedTransaction;
 pub use geometric::GeometricGasPrice;
 
 mod linear;
@@ -82,17 +83,25 @@ where
         &self.inner
     }
 
-    async fn send_transaction(
+    async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
         &self,
-        tx: TransactionRequest,
+        tx: T,
         block: Option<BlockId>,
     ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
+        let tx = tx.into();
+
         let pending_tx = self
             .inner()
             .send_transaction(tx.clone(), block)
             .await
             .map_err(GasEscalatorError::MiddlewareError)?;
 
+        let tx = match tx {
+            TypedTransaction::Legacy(inner) => inner,
+            TypedTransaction::Eip2930(inner) => inner.tx,
+            _ => return Err(GasEscalatorError::UnsupportedTxType),
+        };
+
         // insert the tx in the pending txs
         let mut lock = self.txs.lock().await;
         lock.push((*pending_tx, tx, Instant::now(), block));
@@ -231,4 +240,7 @@ pub enum GasEscalatorError<M: Middleware> {
     #[error("{0}")]
     /// Thrown when an internal middleware errors
     MiddlewareError(M::Error),
+
+    #[error("Gas escalation is only supported for EIP2930 or Legacy transactions")]
+    UnsupportedTxType,
 }
diff --git a/ethers-middleware/src/gas_oracle/eth_gas_station.rs b/ethers-middleware/src/gas_oracle/eth_gas_station.rs
index 6c7bc230..f5c37d09 100644
--- a/ethers-middleware/src/gas_oracle/eth_gas_station.rs
+++ b/ethers-middleware/src/gas_oracle/eth_gas_station.rs
@@ -21,7 +21,7 @@ pub struct EthGasStation {
 #[derive(Deserialize)]
 struct EthGasStationResponse {
     #[serde(rename = "safeLow")]
-    safe_low: u64,
+    safe_low: f64,
     average: u64,
     fast: u64,
     fastest: u64,
@@ -63,7 +63,7 @@ impl GasOracle for EthGasStation {
             .await?;
 
         let gas_price = match self.gas_category {
-            GasCategory::SafeLow => U256::from((res.safe_low * GWEI_TO_WEI) / 10),
+            GasCategory::SafeLow => U256::from((res.safe_low.ceil() as u64 * GWEI_TO_WEI) / 10),
             GasCategory::Standard => U256::from((res.average * GWEI_TO_WEI) / 10),
             GasCategory::Fast => U256::from((res.fast * GWEI_TO_WEI) / 10),
             GasCategory::Fastest => U256::from((res.fastest * GWEI_TO_WEI) / 10),
diff --git a/ethers-middleware/src/gas_oracle/middleware.rs b/ethers-middleware/src/gas_oracle/middleware.rs
index ac8ee97b..d77f3262 100644
--- a/ethers-middleware/src/gas_oracle/middleware.rs
+++ b/ethers-middleware/src/gas_oracle/middleware.rs
@@ -1,6 +1,6 @@
 use super::{GasOracle, GasOracleError};
 use async_trait::async_trait;
-use ethers_core::types::*;
+use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
 use ethers_providers::{FromErr, Middleware, PendingTransaction};
 use thiserror::Error;
 
@@ -28,6 +28,9 @@ pub enum MiddlewareError<M: Middleware> {
 
     #[error("{0}")]
     MiddlewareError(M::Error),
+
+    #[error("This gas price oracle only works with Legacy and EIP2930 transactions.")]
+    UnsupportedTxType,
 }
 
 impl<M: Middleware> FromErr<M::Error> for MiddlewareError<M> {
@@ -56,14 +59,28 @@ where
         Ok(self.gas_oracle.fetch().await?)
     }
 
-    async fn send_transaction(
+    async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
         &self,
-        mut tx: TransactionRequest,
+        tx: T,
         block: Option<BlockId>,
     ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
-        if tx.gas_price.is_none() {
-            tx.gas_price = Some(self.get_gas_price().await?);
-        }
+        let mut tx = tx.into();
+
+        match tx {
+            TypedTransaction::Legacy(ref mut tx) => {
+                if tx.gas_price.is_none() {
+                    tx.gas_price = Some(self.get_gas_price().await?);
+                }
+            }
+            TypedTransaction::Eip2930(ref mut inner) => {
+                if inner.tx.gas_price.is_none() {
+                    inner.tx.gas_price = Some(self.get_gas_price().await?);
+                }
+            }
+            TypedTransaction::Eip1559(_) => {
+                return Err(MiddlewareError::UnsupportedTxType);
+            }
+        };
         self.inner
             .send_transaction(tx, block)
             .await
diff --git a/ethers-middleware/src/nonce_manager.rs b/ethers-middleware/src/nonce_manager.rs
index b7f22cb1..94c82f28 100644
--- a/ethers-middleware/src/nonce_manager.rs
+++ b/ethers-middleware/src/nonce_manager.rs
@@ -1,4 +1,5 @@
 use async_trait::async_trait;
+use ethers_core::types::transaction::eip2718::TypedTransaction;
 use ethers_core::types::*;
 use ethers_providers::{FromErr, Middleware, PendingTransaction};
 use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
@@ -84,17 +85,18 @@ where
     /// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that
     /// gas cost and nonce calculations take it into account. For simple transactions this can be
     /// left to `None`.
-    async fn send_transaction(
+    async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
         &self,
-        mut tx: TransactionRequest,
+        tx: T,
         block: Option<BlockId>,
     ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
-        if tx.nonce.is_none() {
-            tx.nonce = Some(self.get_transaction_count_with_manager(block).await?);
+        let mut tx = tx.into();
+
+        if tx.nonce().is_none() {
+            tx.set_nonce(self.get_transaction_count_with_manager(block).await?);
         }
 
-        let mut tx_clone = tx.clone();
-        match self.inner.send_transaction(tx, block).await {
+        match self.inner.send_transaction(tx.clone(), block).await {
             Ok(tx_hash) => Ok(tx_hash),
             Err(err) => {
                 let nonce = self.get_transaction_count(self.address, block).await?;
@@ -102,9 +104,9 @@ where
                     // try re-submitting the transaction with the correct nonce if there
                     // was a nonce mismatch
                     self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
-                    tx_clone.nonce = Some(nonce);
+                    tx.set_nonce(nonce);
                     self.inner
-                        .send_transaction(tx_clone, block)
+                        .send_transaction(tx, block)
                         .await
                         .map_err(FromErr::from)
                 } else {
diff --git a/ethers-middleware/src/signer.rs b/ethers-middleware/src/signer.rs
index a7333884..1dd5f088 100644
--- a/ethers-middleware/src/signer.rs
+++ b/ethers-middleware/src/signer.rs
@@ -1,10 +1,10 @@
-use ethers_core::types::{Address, BlockId, Bytes, NameOrAddress, Signature, TransactionRequest};
-use ethers_providers::{FromErr, Middleware, PendingTransaction};
+use ethers_core::types::{
+    transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Signature,
+};
+use ethers_providers::{maybe, FromErr, Middleware, PendingTransaction};
 use ethers_signers::Signer;
 
 use async_trait::async_trait;
-use futures_util::{future::ok, join};
-use std::future::Future;
 use thiserror::Error;
 
 #[derive(Clone, Debug)]
@@ -118,7 +118,7 @@ where
     /// Signs and returns the RLP encoding of the signed transaction
     async fn sign_transaction(
         &self,
-        tx: TransactionRequest,
+        tx: TypedTransaction,
     ) -> Result<Bytes, SignerMiddlewareError<M, S>> {
         let signature = self
             .signer
@@ -127,33 +127,7 @@ where
             .map_err(SignerMiddlewareError::SignerError)?;
 
         // Return the raw rlp-encoded signed transaction
-        Ok(tx.rlp_signed(&signature))
-    }
-
-    async fn fill_transaction(
-        &self,
-        tx: &mut TransactionRequest,
-        block: Option<BlockId>,
-    ) -> Result<(), SignerMiddlewareError<M, S>> {
-        // set the `from` field
-        if tx.from.is_none() {
-            tx.from = Some(self.address());
-        }
-
-        // will poll and await the futures concurrently
-        let (gas_price, gas, nonce) = join!(
-            maybe(tx.gas_price, self.inner.get_gas_price()),
-            maybe(tx.gas, self.inner.estimate_gas(tx)),
-            maybe(
-                tx.nonce,
-                self.inner.get_transaction_count(self.address(), block)
-            ),
-        );
-        tx.gas_price = Some(gas_price.map_err(SignerMiddlewareError::MiddlewareError)?);
-        tx.gas = Some(gas.map_err(SignerMiddlewareError::MiddlewareError)?);
-        tx.nonce = Some(nonce.map_err(SignerMiddlewareError::MiddlewareError)?);
-
-        Ok(())
+        Ok(tx.rlp_signed(self.signer.chain_id(), &signature))
     }
 
     /// Returns the client's address
@@ -192,21 +166,54 @@ where
         &self.inner
     }
 
+    /// Returns the client's address
+    fn default_sender(&self) -> Option<Address> {
+        Some(self.address)
+    }
+
     /// `SignerMiddleware` is instantiated with a signer.
     async fn is_signer(&self) -> bool {
         true
     }
 
+    /// Helper for filling a transaction's nonce using the wallet
+    async fn fill_transaction(
+        &self,
+        tx: &mut TypedTransaction,
+        block: Option<BlockId>,
+    ) -> Result<(), Self::Error> {
+        // get the `from` field's nonce if it's set, else get the signer's nonce
+        let from = if tx.from().is_some() && tx.from() != Some(&self.address()) {
+            *tx.from().unwrap()
+        } else {
+            self.address
+        };
+        tx.set_from(from);
+
+        let nonce = maybe(tx.nonce().cloned(), self.get_transaction_count(from, block)).await?;
+        tx.set_nonce(nonce);
+        self.inner()
+            .fill_transaction(tx, block)
+            .await
+            .map_err(SignerMiddlewareError::MiddlewareError)?;
+        Ok(())
+    }
+
     /// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that
     /// gas cost and nonce calculations take it into account. For simple transactions this can be
     /// left to `None`.
-    async fn send_transaction(
+    async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
         &self,
-        mut tx: TransactionRequest,
+        tx: T,
         block: Option<BlockId>,
     ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
+        let mut tx = tx.into();
+
+        // fill any missing fields
+        self.fill_transaction(&mut tx, block).await?;
+
         // If the from address is set and is not our signer, delegate to inner
-        if tx.from.is_some() && tx.from != Some(self.address()) {
+        if tx.from().is_some() && tx.from() != Some(&self.address()) {
             return self
                 .inner
                 .send_transaction(tx, block)
@@ -214,18 +221,6 @@ where
                 .map_err(SignerMiddlewareError::MiddlewareError);
         }
 
-        if let Some(NameOrAddress::Name(ens_name)) = tx.to {
-            let addr = self
-                .inner
-                .resolve_name(&ens_name)
-                .await
-                .map_err(SignerMiddlewareError::MiddlewareError)?;
-            tx.to = Some(addr.into())
-        }
-
-        // fill any missing fields
-        self.fill_transaction(&mut tx, block).await?;
-
         // if we have a nonce manager set, we should try handling the result in
         // case there was a nonce mismatch
         let signed_tx = self.sign_transaction(tx).await?;
@@ -251,23 +246,14 @@ where
     }
 }
 
-/// Calls the future if `item` is None, otherwise returns a `futures::ok`
-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 {
-        ok(item).await
-    } else {
-        f.await
-    }
-}
-
 #[cfg(all(test, not(feature = "celo")))]
 mod tests {
     use super::*;
     use ethers::{providers::Provider, signers::LocalWallet};
-    use ethers_core::utils::{self, keccak256, Ganache};
+    use ethers_core::{
+        types::TransactionRequest,
+        utils::{self, keccak256, Ganache},
+    };
     use std::convert::TryFrom;
 
     #[tokio::test]
@@ -287,7 +273,8 @@ mod tests {
             nonce: Some(0.into()),
             gas_price: Some(21_000_000_000u128.into()),
             data: None,
-        };
+        }
+        .into();
         let chain_id = 1u64;
 
         let provider = Provider::try_from("http://localhost:8545").unwrap();
diff --git a/ethers-middleware/src/transformer/ds_proxy/mod.rs b/ethers-middleware/src/transformer/ds_proxy/mod.rs
index df75aa0c..e55e4535 100644
--- a/ethers-middleware/src/transformer/ds_proxy/mod.rs
+++ b/ethers-middleware/src/transformer/ds_proxy/mod.rs
@@ -3,7 +3,9 @@ use factory::{CreatedFilter, DsProxyFactory, ADDRESS_BOOK};
 
 use super::{Transformer, TransformerError};
 use ethers_contract::{builders::ContractCall, BaseContract, ContractError};
-use ethers_core::{abi::parse_abi, types::*, utils::id};
+use ethers_core::{
+    abi::parse_abi, types::transaction::eip2718::TypedTransaction, types::*, utils::id,
+};
 use ethers_providers::Middleware;
 use std::sync::Arc;
 
@@ -178,29 +180,26 @@ impl DsProxy {
 }
 
 impl Transformer for DsProxy {
-    fn transform(&self, tx: TransactionRequest) -> Result<TransactionRequest, TransformerError> {
-        // clone the tx into a new proxy tx.
-        let mut proxy_tx = tx.clone();
-
+    fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError> {
         // the target address cannot be None.
-        let target = match tx.to {
-            Some(NameOrAddress::Address(addr)) => Ok(addr),
-            _ => Err(TransformerError::MissingField("to".into())),
-        }?;
+        let target = match tx.to() {
+            Some(NameOrAddress::Address(addr)) => addr,
+            _ => return Err(TransformerError::MissingField("to".into())),
+        };
 
         // fetch the data field.
-        let data = tx.data.unwrap_or_else(|| vec![].into());
+        let data = tx.data().cloned().unwrap_or_else(|| vec![].into());
 
         // encode data as the ABI encoded data for DSProxy's execute method.
         let selector = id("execute(address,bytes)");
         let encoded_data = self
             .contract
-            .encode_with_selector(selector, (target, data))?;
+            .encode_with_selector(selector, (*target, data))?;
 
         // update appropriate fields of the proxy tx.
-        proxy_tx.data = Some(encoded_data);
-        proxy_tx.to = Some(NameOrAddress::Address(self.address));
+        tx.set_data(encoded_data);
+        tx.set_to(self.address);
 
-        Ok(proxy_tx)
+        Ok(())
     }
 }
diff --git a/ethers-middleware/src/transformer/middleware.rs b/ethers-middleware/src/transformer/middleware.rs
index 65919c6f..5b8a9985 100644
--- a/ethers-middleware/src/transformer/middleware.rs
+++ b/ethers-middleware/src/transformer/middleware.rs
@@ -1,6 +1,6 @@
 use super::{Transformer, TransformerError};
 use async_trait::async_trait;
-use ethers_core::types::*;
+use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
 use ethers_providers::{FromErr, Middleware, PendingTransaction};
 use thiserror::Error;
 
@@ -53,27 +53,20 @@ where
         &self.inner
     }
 
-    async fn send_transaction(
+    async fn send_transaction<Tx: Into<TypedTransaction> + Send + Sync>(
         &self,
-        mut tx: TransactionRequest,
+        tx: Tx,
         block: Option<BlockId>,
     ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
-        // resolve the to field if that's an ENS name.
-        if let Some(NameOrAddress::Name(ens_name)) = tx.to {
-            let addr = self
-                .inner
-                .resolve_name(&ens_name)
-                .await
-                .map_err(TransformerMiddlewareError::MiddlewareError)?;
-            tx.to = Some(addr.into())
-        }
+        let mut tx = tx.into();
 
         // construct the appropriate proxy tx.
-        let proxy_tx = self.transformer.transform(tx)?;
+        self.transformer.transform(&mut tx)?;
 
+        self.fill_transaction(&mut tx, block).await?;
         // send the proxy tx.
         self.inner
-            .send_transaction(proxy_tx, block)
+            .send_transaction(tx, block)
             .await
             .map_err(TransformerMiddlewareError::MiddlewareError)
     }
diff --git a/ethers-middleware/src/transformer/mod.rs b/ethers-middleware/src/transformer/mod.rs
index 7cf123aa..ab31b5ee 100644
--- a/ethers-middleware/src/transformer/mod.rs
+++ b/ethers-middleware/src/transformer/mod.rs
@@ -5,7 +5,7 @@ mod middleware;
 pub use middleware::TransformerMiddleware;
 
 use ethers_contract::AbiError;
-use ethers_core::{abi::ParseError, types::*};
+use ethers_core::{abi::ParseError, types::transaction::eip2718::TypedTransaction};
 use thiserror::Error;
 
 #[derive(Error, Debug)]
@@ -31,5 +31,5 @@ pub trait Transformer: Send + Sync + std::fmt::Debug {
     /// proxy contract.
     ///
     /// [`transaction request`]: struct@ethers_core::types::TransactionRequest
-    fn transform(&self, tx: TransactionRequest) -> Result<TransactionRequest, TransformerError>;
+    fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError>;
 }
diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs
index 09f4817d..2b115c9b 100644
--- a/ethers-providers/src/lib.rs
+++ b/ethers-providers/src/lib.rs
@@ -83,7 +83,8 @@ pub use pubsub::{PubsubClient, SubscriptionStream};
 
 use async_trait::async_trait;
 use auto_impl::auto_impl;
-use serde::{de::DeserializeOwned, Serialize};
+use ethers_core::types::transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use std::{error::Error, fmt::Debug, future::Future, pin::Pin};
 
 pub use provider::{FilterKind, Provider, ProviderError};
@@ -112,6 +113,18 @@ 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
+    }
+}
+
 #[async_trait]
 #[auto_impl(&, Box, Arc)]
 /// A middleware allows customizing requests send and received from an ethereum node.
@@ -122,7 +135,7 @@ pub trait FromErr<T> {
 /// 3. implementing any of the methods you want to override
 ///
 /// ```rust
-/// use ethers::{providers::{Middleware, FromErr}, types::{U64, TransactionRequest, U256}};
+/// use ethers::{providers::{Middleware, FromErr}, types::{U64, TransactionRequest, U256, transaction::eip2718::TypedTransaction}};
 /// use thiserror::Error;
 /// use async_trait::async_trait;
 ///
@@ -163,7 +176,7 @@ pub trait FromErr<T> {
 ///
 ///     /// 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: &TransactionRequest) -> Result<U256, Self::Error> {
+///     async fn estimate_gas(&self, tx: &TypedTransaction) -> Result<U256, Self::Error> {
 ///         println!("Estimating gas...");
 ///         self.inner().estimate_gas(tx).await.map_err(FromErr::from)
 ///     }
@@ -182,17 +195,90 @@ pub trait Middleware: Sync + Send + Debug {
         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)
     }
 
+    /// Helper for filling a transaction
+    async fn fill_transaction(
+        &self,
+        tx: &mut TypedTransaction,
+        block: Option<BlockId>,
+    ) -> Result<(), Self::Error> {
+        let tx_clone = tx.clone();
+
+        // TODO: Maybe deduplicate the code in a nice way
+        match tx {
+            TypedTransaction::Legacy(ref mut inner) => {
+                if let Some(NameOrAddress::Name(ref ens_name)) = inner.to {
+                    let addr = self.resolve_name(ens_name).await?;
+                    inner.to = Some(addr.into());
+                };
+
+                if inner.from.is_none() {
+                    inner.from = self.default_sender();
+                }
+
+                let (gas_price, gas) = futures_util::try_join!(
+                    maybe(inner.gas_price, self.get_gas_price()),
+                    maybe(inner.gas, self.estimate_gas(&tx_clone)),
+                )?;
+                inner.gas = Some(gas);
+                inner.gas_price = Some(gas_price);
+            }
+            TypedTransaction::Eip2930(inner) => {
+                if let Ok(lst) = self.create_access_list(&tx_clone, block).await {
+                    inner.access_list = lst.access_list;
+                }
+
+                if let Some(NameOrAddress::Name(ref ens_name)) = inner.tx.to {
+                    let addr = self.resolve_name(ens_name).await?;
+                    inner.tx.to = Some(addr.into());
+                };
+
+                let (gas_price, gas) = futures_util::try_join!(
+                    maybe(inner.tx.gas_price, self.get_gas_price()),
+                    maybe(inner.tx.gas, self.estimate_gas(&tx_clone)),
+                )?;
+                inner.tx.gas = Some(gas);
+                inner.tx.gas_price = Some(gas_price);
+            }
+            TypedTransaction::Eip1559(inner) => {
+                if let Ok(lst) = self.create_access_list(&tx_clone, block).await {
+                    inner.access_list = lst.access_list;
+                }
+
+                if let Some(NameOrAddress::Name(ref ens_name)) = inner.to {
+                    let addr = self.resolve_name(ens_name).await?;
+                    inner.to = Some(addr.into());
+                };
+
+                let (max_priority_fee_per_gas, max_fee_per_gas, gas) = futures_util::try_join!(
+                    // TODO: Replace with algorithms using eth_feeHistory
+                    maybe(inner.max_priority_fee_per_gas, self.get_gas_price()),
+                    maybe(inner.max_fee_per_gas, self.get_gas_price()),
+                    maybe(inner.gas, self.estimate_gas(&tx_clone)),
+                )?;
+                inner.gas = Some(gas);
+                inner.max_fee_per_gas = Some(max_fee_per_gas);
+                inner.max_priority_fee_per_gas = Some(max_priority_fee_per_gas);
+            }
+        };
+
+        Ok(())
+    }
+
     async fn get_block_number(&self) -> Result<U64, Self::Error> {
         self.inner().get_block_number().await.map_err(FromErr::from)
     }
 
-    async fn send_transaction(
+    async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
         &self,
-        tx: TransactionRequest,
+        tx: T,
         block: Option<BlockId>,
     ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
         self.inner()
@@ -246,13 +332,13 @@ pub trait Middleware: Sync + Send + Debug {
             .map_err(FromErr::from)
     }
 
-    async fn estimate_gas(&self, tx: &TransactionRequest) -> Result<U256, Self::Error> {
+    async fn estimate_gas(&self, tx: &TypedTransaction) -> Result<U256, Self::Error> {
         self.inner().estimate_gas(tx).await.map_err(FromErr::from)
     }
 
     async fn call(
         &self,
-        tx: &TransactionRequest,
+        tx: &TypedTransaction,
         block: Option<BlockId>,
     ) -> Result<Bytes, Self::Error> {
         self.inner().call(tx, block).await.map_err(FromErr::from)
@@ -427,9 +513,9 @@ pub trait Middleware: Sync + Send + Debug {
     // Parity `trace` support
 
     /// Executes the given call and returns a number of possible traces for it
-    async fn trace_call(
+    async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
         &self,
-        req: TransactionRequest,
+        req: T,
         trace_type: Vec<TraceType>,
         block: Option<BlockNumber>,
     ) -> Result<BlockTrace, Self::Error> {
@@ -574,6 +660,38 @@ pub trait Middleware: Sync + Send + Debug {
             .await
             .map_err(FromErr::from)
     }
+
+    async fn fee_history(
+        &self,
+        block_count: u64,
+        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)
+    }
+}
+
+#[derive(Deserialize, Serialize, Debug, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct FeeHistory {
+    pub base_fee_per_gas: Vec<U256>,
+    pub gas_used_ratio: Vec<f64>,
+    pub oldest_block: u64,
+    pub reward: Vec<Vec<U256>>,
 }
 
 #[cfg(feature = "celo")]
diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs
index 956216fd..8e61b3ab 100644
--- a/ethers-providers/src/provider.rs
+++ b/ethers-providers/src/provider.rs
@@ -2,15 +2,16 @@ use crate::{
     ens,
     pubsub::{PubsubClient, SubscriptionStream},
     stream::{FilterWatcher, DEFAULT_POLL_INTERVAL},
-    FromErr, Http as HttpProvider, JsonRpcClient, MockProvider, PendingTransaction,
+    FeeHistory, FromErr, Http as HttpProvider, JsonRpcClient, MockProvider, PendingTransaction,
 };
 
 use ethers_core::{
     abi::{self, Detokenize, ParamType},
     types::{
+        transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
         Address, Block, BlockId, BlockNumber, BlockTrace, Bytes, Filter, Log, NameOrAddress,
         Selector, Signature, Trace, TraceFilter, TraceType, Transaction, TransactionReceipt,
-        TransactionRequest, TxHash, TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64,
+        TxHash, TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64,
     },
     utils,
 };
@@ -192,6 +193,10 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         self
     }
 
+    fn default_sender(&self) -> Option<Address> {
+        self.from
+    }
+
     ////// Blockchain Status
     //
     // Functions for querying the state of the blockchain
@@ -307,7 +312,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
     /// This is free, since it does not change any state on the blockchain.
     async fn call(
         &self,
-        tx: &TransactionRequest,
+        tx: &TypedTransaction,
         block: Option<BlockId>,
     ) -> Result<Bytes, ProviderError> {
         let tx = utils::serialize(tx);
@@ -318,33 +323,29 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
     /// 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: &TransactionRequest) -> Result<U256, ProviderError> {
+    async fn estimate_gas(&self, tx: &TypedTransaction) -> Result<U256, ProviderError> {
         self.request("eth_estimateGas", [tx]).await
     }
 
+    async fn create_access_list(
+        &self,
+        tx: &TypedTransaction,
+        block: Option<BlockId>,
+    ) -> Result<AccessListWithGasUsed, ProviderError> {
+        let tx = utils::serialize(tx);
+        let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into()));
+        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(
+    async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
         &self,
-        mut tx: TransactionRequest,
-        _: Option<BlockId>,
+        tx: T,
+        block: Option<BlockId>,
     ) -> Result<PendingTransaction<'_, P>, ProviderError> {
-        if tx.from.is_none() {
-            tx.from = self.from;
-        }
-
-        if tx.gas.is_none() {
-            tx.gas = Some(self.estimate_gas(&tx).await?);
-        }
-
-        if let Some(NameOrAddress::Name(ref ens_name)) = tx.to {
-            // resolve to an address
-            let addr = self.resolve_name(ens_name).await?;
-
-            // set the value
-            tx.to = Some(addr.into())
-        }
-
+        let mut tx = tx.into();
+        self.fill_transaction(&mut tx, block).await?;
         let tx_hash = self.request("eth_sendTransaction", [tx]).await?;
 
         Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval()))
@@ -557,12 +558,13 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
     }
 
     /// Executes the given call and returns a number of possible traces for it
-    async fn trace_call(
+    async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
         &self,
-        req: TransactionRequest,
+        req: T,
         trace_type: Vec<TraceType>,
         block: Option<BlockNumber>,
     ) -> Result<BlockTrace, ProviderError> {
+        let req = req.into();
         let req = utils::serialize(&req);
         let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
         let trace_type = utils::serialize(&trace_type);
@@ -694,6 +696,22 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         let filter = utils::serialize(filter);
         self.subscribe([logs, filter]).await
     }
+
+    async fn fee_history(
+        &self,
+        block_count: u64,
+        last_block: BlockNumber,
+        reward_percentiles: &[f64],
+    ) -> Result<FeeHistory, Self::Error> {
+        let block_count = utils::serialize(&block_count);
+        let last_block = utils::serialize(&last_block);
+        let reward_percentiles = utils::serialize(&reward_percentiles);
+        self.request(
+            "eth_feeHistory",
+            [block_count, last_block, reward_percentiles],
+        )
+        .await
+    }
 }
 
 impl<P: JsonRpcClient> Provider<P> {
@@ -709,7 +727,7 @@ impl<P: JsonRpcClient> Provider<P> {
         // first get the resolver responsible for this name
         // the call will return a Bytes array which we convert to an address
         let data = self
-            .call(&ens::get_resolver(ens_addr, ens_name), None)
+            .call(&ens::get_resolver(ens_addr, ens_name).into(), None)
             .await?;
 
         let resolver_address: Address = decode_bytes(ParamType::Address, data);
@@ -719,7 +737,10 @@ impl<P: JsonRpcClient> Provider<P> {
 
         // resolve
         let data = self
-            .call(&ens::resolve(resolver_address, selector, ens_name), None)
+            .call(
+                &ens::resolve(resolver_address, selector, ens_name).into(),
+                None,
+            )
             .await?;
 
         Ok(decode_bytes(param, data))
@@ -885,7 +906,7 @@ mod ens_tests {
 mod tests {
     use super::*;
     use crate::Http;
-    use ethers_core::types::H256;
+    use ethers_core::types::{TransactionRequest, H256};
     use ethers_core::utils::Geth;
     use futures_util::StreamExt;
 
@@ -1020,4 +1041,19 @@ mod tests {
             .await;
         assert_eq!(blocks, vec![1, 2, 3]);
     }
+
+    #[tokio::test]
+    #[cfg_attr(feature = "celo", ignore)]
+    async fn fee_history() {
+        let provider = Provider::<Http>::try_from(
+            "https://goerli.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778",
+        )
+        .unwrap();
+
+        let history = provider
+            .fee_history(10, BlockNumber::Latest, &[10.0, 40.0])
+            .await
+            .unwrap();
+        dbg!(&history);
+    }
 }
diff --git a/ethers-signers/src/aws/mod.rs b/ethers-signers/src/aws/mod.rs
index a58cce2a..e99ee1b5 100644
--- a/ethers-signers/src/aws/mod.rs
+++ b/ethers-signers/src/aws/mod.rs
@@ -2,7 +2,7 @@
 
 use ethers_core::{
     k256::ecdsa::{Error as K256Error, Signature as KSig, VerifyingKey},
-    types::{Address, Signature as EthSig, TransactionRequest, H256},
+    types::{Address, Signature as EthSig, H256, transaction::eip2718::TypedTransaction},
     utils::hash_message,
 };
 use rusoto_core::RusotoError;
@@ -240,7 +240,7 @@ impl<'a> super::Signer for AwsSigner<'a> {
     }
 
     #[instrument(err)]
-    async fn sign_transaction(&self, tx: &TransactionRequest) -> Result<EthSig, Self::Error> {
+    async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<EthSig, Self::Error> {
         let sighash = tx.sighash(self.chain_id);
         self.sign_digest_with_eip155(sighash).await
     }
diff --git a/ethers-signers/src/ledger/app.rs b/ethers-signers/src/ledger/app.rs
index 5d44b133..2579b3df 100644
--- a/ethers-signers/src/ledger/app.rs
+++ b/ethers-signers/src/ledger/app.rs
@@ -8,7 +8,8 @@ use futures_util::lock::Mutex;
 
 use ethers_core::{
     types::{
-        Address, NameOrAddress, Signature, Transaction, TransactionRequest, TxHash, H256, U256,
+        transaction::eip2718::TypedTransaction, Address, NameOrAddress, Signature, Transaction,
+        TransactionRequest, TxHash, H256, U256,
     },
     utils::keccak256,
 };
@@ -118,7 +119,7 @@ impl LedgerEthereum {
     }
 
     /// Signs an Ethereum transaction (requires confirmation on the ledger)
-    pub async fn sign_tx(&self, tx: &TransactionRequest) -> Result<Signature, LedgerError> {
+    pub async fn sign_tx(&self, tx: &TypedTransaction) -> Result<Signature, LedgerError> {
         let mut payload = Self::path_to_bytes(&self.derivation);
         payload.extend_from_slice(tx.rlp(self.chain_id).as_ref());
         self.sign_payload(INS::SIGN, payload).await
@@ -241,7 +242,8 @@ mod tests {
             .gas_price(400e9 as u64)
             .nonce(5)
             .data(data)
-            .value(ethers_core::utils::parse_ether(100).unwrap());
+            .value(ethers_core::utils::parse_ether(100).unwrap())
+            .into();
         let tx = ledger.sign_transaction(&tx_req).await.unwrap();
     }
 
diff --git a/ethers-signers/src/ledger/mod.rs b/ethers-signers/src/ledger/mod.rs
index b22466e2..91a54344 100644
--- a/ethers-signers/src/ledger/mod.rs
+++ b/ethers-signers/src/ledger/mod.rs
@@ -4,7 +4,7 @@ pub mod types;
 use crate::Signer;
 use app::LedgerEthereum;
 use async_trait::async_trait;
-use ethers_core::types::{Address, Signature, TransactionRequest};
+use ethers_core::types::{transaction::eip2718::TypedTransaction, Address, Signature};
 use types::LedgerError;
 
 #[async_trait]
@@ -20,10 +20,7 @@ impl Signer for LedgerEthereum {
     }
 
     /// Signs the transaction
-    async fn sign_transaction(
-        &self,
-        message: &TransactionRequest,
-    ) -> Result<Signature, Self::Error> {
+    async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error> {
         self.sign_tx(message).await
     }
 
diff --git a/ethers-signers/src/lib.rs b/ethers-signers/src/lib.rs
index b39619da..e2ee6aff 100644
--- a/ethers-signers/src/lib.rs
+++ b/ethers-signers/src/lib.rs
@@ -25,7 +25,7 @@
 //! // create a transaction
 //! let tx = TransactionRequest::new()
 //!     .to("vitalik.eth") // this will use ENS
-//!     .value(10000);
+//!     .value(10000).into();
 //!
 //! // sign it
 //! let signature = wallet.sign_transaction(&tx).await?;
@@ -70,7 +70,7 @@ mod aws;
 pub use aws::{AwsSigner, AwsSignerError};
 
 use async_trait::async_trait;
-use ethers_core::types::{Address, Signature, TransactionRequest};
+use ethers_core::types::{transaction::eip2718::TypedTransaction, Address, Signature};
 use std::error::Error;
 
 /// Applies [EIP155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
@@ -91,10 +91,7 @@ pub trait Signer: std::fmt::Debug + Send + Sync {
     ) -> Result<Signature, Self::Error>;
 
     /// Signs the transaction
-    async fn sign_transaction(
-        &self,
-        message: &TransactionRequest,
-    ) -> Result<Signature, Self::Error>;
+    async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error>;
 
     /// Returns the signer's Ethereum Address
     fn address(&self) -> Address;
diff --git a/ethers-signers/src/wallet/mod.rs b/ethers-signers/src/wallet/mod.rs
index ab864fef..53d3a294 100644
--- a/ethers-signers/src/wallet/mod.rs
+++ b/ethers-signers/src/wallet/mod.rs
@@ -16,7 +16,7 @@ use ethers_core::{
         elliptic_curve::FieldBytes,
         Secp256k1,
     },
-    types::{Address, Signature, TransactionRequest, H256},
+    types::{transaction::eip2718::TypedTransaction, Address, Signature, H256},
     utils::hash_message,
 };
 use hash::Sha256Proxy;
@@ -78,7 +78,7 @@ impl<D: Sync + Send + DigestSigner<Sha256Proxy, RecoverableSignature>> Signer fo
         Ok(self.sign_hash(message_hash, false))
     }
 
-    async fn sign_transaction(&self, tx: &TransactionRequest) -> Result<Signature, Self::Error> {
+    async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<Signature, Self::Error> {
         let sighash = tx.sighash(self.chain_id);
         Ok(self.sign_hash(sighash, true))
     }
diff --git a/ethers-signers/src/wallet/private_key.rs b/ethers-signers/src/wallet/private_key.rs
index cf2289a8..f14f1a0f 100644
--- a/ethers-signers/src/wallet/private_key.rs
+++ b/ethers-signers/src/wallet/private_key.rs
@@ -217,7 +217,8 @@ mod tests {
             nonce: Some(0.into()),
             gas_price: Some(21_000_000_000u128.into()),
             data: None,
-        };
+        }
+        .into();
         let chain_id = 1u64;
 
         let wallet: Wallet<SigningKey> =


Test patch:
diff --git a/ethers-middleware/tests/gas_oracle.rs b/ethers-middleware/tests/gas_oracle.rs
index ae79f461..9974d9ad 100644
--- a/ethers-middleware/tests/gas_oracle.rs
+++ b/ethers-middleware/tests/gas_oracle.rs
@@ -33,6 +33,7 @@ async fn using_gas_oracle() {
 
 #[tokio::test]
 #[ignore]
+// TODO: Re-enable, EthGasStation changed its response api @ https://ethgasstation.info/api/ethgasAPI.json
 async fn eth_gas_station() {
     // initialize and fetch gas estimates from EthGasStation
     let eth_gas_station_oracle = EthGasStation::new(None);
@@ -60,6 +61,8 @@ async fn etherscan() {
 
 #[tokio::test]
 #[ignore]
+// TODO: Etherchain has Cloudflare DDOS protection which makes the request fail
+// https://twitter.com/gakonst/status/1421796226316578816
 async fn etherchain() {
     // initialize and fetch gas estimates from Etherchain
     let etherchain_oracle = Etherchain::new().category(GasCategory::Fast);
diff --git a/ethers-providers/tests/provider.rs b/ethers-providers/tests/provider.rs
index ba35d2a6..18a2426b 100644
--- a/ethers-providers/tests/provider.rs
+++ b/ethers-providers/tests/provider.rs
@@ -6,6 +6,7 @@ mod eth_tests {
     use super::*;
     use ethers::{
         middleware::SignerMiddleware,
+        prelude::transaction::eip2718::TypedTransaction,
         signers::{LocalWallet, Signer},
         types::{BlockId, TransactionRequest, H256},
         utils::Ganache,
@@ -148,6 +149,56 @@ mod eth_tests {
         // got the correct receipt
         assert_eq!(receipt.transaction_hash, tx_hash);
     }
+
+    #[tokio::test]
+    async fn typed_txs() {
+        use ethers_core::types::Eip1559TransactionRequest;
+        let provider = Provider::<Http>::try_from(
+            "https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
+        )
+        .unwrap();
+
+        let chain_id = provider.get_chainid().await.unwrap();
+        let wallet = "59c37cb6b16fa2de30675f034c8008f890f4b2696c729d6267946d29736d73e4"
+            .parse::<LocalWallet>()
+            .unwrap()
+            .with_chain_id(chain_id.as_u64());
+        let address = wallet.address();
+        let provider = SignerMiddleware::new(provider, wallet);
+
+        async fn check_tx<M: Middleware>(provider: &M, tx: TypedTransaction, expected: u64) {
+            let receipt = provider
+                .send_transaction(tx, None)
+                .await
+                .unwrap()
+                .await
+                .unwrap()
+                .unwrap();
+            let tx = provider
+                .get_transaction(receipt.transaction_hash)
+                .await
+                .unwrap()
+                .unwrap();
+            assert_eq!(receipt.transaction_type, Some(expected.into()));
+            assert_eq!(tx.transaction_type, Some(expected.into()));
+        }
+
+        let tx: TypedTransaction = TransactionRequest::new().from(address).to(address).into();
+        check_tx(&provider, tx, 0).await;
+
+        let tx: TypedTransaction = TransactionRequest::new()
+            .from(address)
+            .to(address)
+            .with_access_list(vec![])
+            .into();
+        check_tx(&provider, tx, 1).await;
+
+        let tx: TypedTransaction = Eip1559TransactionRequest::new()
+            .from(address)
+            .to(address)
+            .into();
+        check_tx(&provider, tx, 2).await;
+    }
 }
 
 #[cfg(feature = "celo")]