Repository: gakonst/ethers-rs
Pull request #334 (2021-07-07 03:09:41)

chore(deps): bump coins-ledger from 0.1.0 to 0.3.0
Bumps [coins-ledger](https://github.com/summa-tx/bitcoins-rs) from 0.1.0 to 0.3.0.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a href="https://github.com/summa-tx/bitcoins-rs/commits/v0.3.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=coins-ledger&package-manager=cargo&previous-version=0.1.0&new-version=0.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0796f5df..658cfbcc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -328,6 +328,37 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "camino"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c297bd3135f558552f99a0daa180876984ea2c4ffa7470314540dff8c654109a"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "cc"
 version = "1.0.68"
@@ -448,9 +479,9 @@ dependencies = [
 
 [[package]]
 name = "coins-ledger"
-version = "0.1.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d8652edc472a0727097656a9a48225927bfae586a225bb97bdcd930b847a50"
+checksum = "ca01c92b1270aac4a5552349502daa7c05a466e8ce5a8bbf318775a151e93fb8"
 dependencies = [
  "async-trait",
  "blake2b_simd",
@@ -808,8 +839,10 @@ version = "0.4.0"
 dependencies = [
  "Inflector",
  "anyhow",
+ "cargo_metadata",
  "ethers-core",
  "hex",
+ "once_cell",
  "proc-macro2",
  "quote",
  "reqwest",
@@ -842,7 +875,6 @@ dependencies = [
  "elliptic-curve",
  "ethabi",
  "ethers",
- "funty",
  "futures-util",
  "generic-array 0.14.4",
  "glob",
@@ -852,6 +884,7 @@ dependencies = [
  "once_cell",
  "rand 0.8.4",
  "rlp",
+ "rlp-derive",
  "serde",
  "serde_json",
  "thiserror",
@@ -2083,14 +2116,25 @@ dependencies = [
 
 [[package]]
 name = "rlp"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e54369147e3e7796c9b885c7304db87ca3d09a0a98f72843d532868675bbfba8"
+checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5"
 dependencies = [
  "bytes",
  "rustc-hex",
 ]
 
+[[package]]
+name = "rlp-derive"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "rusb"
 version = "0.8.1"
@@ -2307,6 +2351,9 @@ name = "semver"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "serde"
diff --git a/ethers-contract/Cargo.toml b/ethers-contract/Cargo.toml
index 05f4fe78..e72a81ad 100644
--- a/ethers-contract/Cargo.toml
+++ b/ethers-contract/Cargo.toml
@@ -34,7 +34,8 @@ ethers-middleware = { version = "0.4.0", path = "../ethers-middleware" }
 
 [features]
 abigen = ["ethers-contract-abigen", "ethers-contract-derive"]
-celo = ["ethers-core/celo", "ethers-core/celo", "ethers-providers/celo"]
+celo = ["legacy", "ethers-core/celo", "ethers-core/celo", "ethers-providers/celo"]
+legacy = []
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/ethers-contract/ethers-contract-abigen/Cargo.toml b/ethers-contract/ethers-contract-abigen/Cargo.toml
index 2a24d68a..9da44463 100644
--- a/ethers-contract/ethers-contract-abigen/Cargo.toml
+++ b/ethers-contract/ethers-contract-abigen/Cargo.toml
@@ -21,6 +21,8 @@ url = "2.1"
 serde_json = "1.0.61"
 hex = { version = "0.4.2", default-features = false, features = ["std"] }
 reqwest = { version = "0.11.3", features = ["blocking"] }
+once_cell = { version = "1.8.0", default-features = false }
+cargo_metadata = "0.14.0"
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
index ed19b867..4529b1b3 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
@@ -8,6 +8,7 @@ pub(crate) fn imports(name: &str) -> TokenStream {
     let doc = util::expand_doc(&format!("{} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs", name));
 
     quote! {
+        #![allow(clippy::enum_variant_names)]
         #![allow(dead_code)]
         #![allow(unused_imports)]
         #doc
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index a33329cd..ec8100a3 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -1,5 +1,6 @@
 use super::{types, util, Context};
 use anyhow::{anyhow, Context as _, Result};
+use ethers_core::abi::ParamType;
 use ethers_core::{
     abi::{Function, FunctionExt, Param, StateMutability},
     types::Selector,
@@ -76,7 +77,16 @@ pub(crate) fn expand_inputs_call_arg(inputs: &[Param]) -> TokenStream {
     let names = inputs
         .iter()
         .enumerate()
-        .map(|(i, param)| util::expand_input_name(i, &param.name))
+        .map(|(i, param)| {
+            let name = util::expand_input_name(i, &param.name);
+            match param.kind {
+                ParamType::Tuple(_) => {
+                    // make sure the tuple gets converted to `Token::Tuple`
+                    quote! {(#name,)}
+                }
+                _ => name,
+            }
+        })
         .collect::<Vec<TokenStream>>();
     match names.len() {
         0 => quote! { () },
diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs
index e3f69fd4..716ac85b 100644
--- a/ethers-contract/ethers-contract-abigen/src/lib.rs
+++ b/ethers-contract/ethers-contract-abigen/src/lib.rs
@@ -21,7 +21,7 @@ mod util;
 
 pub use ethers_core::types::Address;
 pub use source::Source;
-pub use util::parse_address;
+pub use util::{ethers_contract_crate, ethers_core_crate, parse_address};
 
 use anyhow::Result;
 use proc_macro2::TokenStream;
diff --git a/ethers-contract/ethers-contract-abigen/src/util.rs b/ethers-contract/ethers-contract-abigen/src/util.rs
index ffc835c8..4952f7cd 100644
--- a/ethers-contract/ethers-contract-abigen/src/util.rs
+++ b/ethers-contract/ethers-contract-abigen/src/util.rs
@@ -1,11 +1,59 @@
 use ethers_core::types::Address;
 
 use anyhow::{anyhow, Result};
+use cargo_metadata::{CargoOpt, DependencyKind, Metadata, MetadataCommand};
 use inflector::Inflector;
+use once_cell::sync::Lazy;
 use proc_macro2::{Ident, Literal, Span, TokenStream};
 use quote::quote;
 use reqwest::Client;
-use syn::Ident as SynIdent;
+use syn::{Ident as SynIdent, Path};
+
+/// See `determine_ethers_crates`
+///
+/// This ensures that the `MetadataCommand` is only run once
+static ETHERS_CRATES: Lazy<(&'static str, &'static str)> = Lazy::new(determine_ethers_crates);
+
+/// Convenience function to turn the `ethers_core` name in `ETHERS_CRATE` into a `Path`
+pub fn ethers_core_crate() -> Path {
+    syn::parse_str(ETHERS_CRATES.0).expect("valid path; qed")
+}
+/// Convenience function to turn the `ethers_contract` name in `ETHERS_CRATE` into an `Path`
+pub fn ethers_contract_crate() -> Path {
+    syn::parse_str(ETHERS_CRATES.1).expect("valid path; qed")
+}
+
+/// The crates name to use when deriving macros: (`core`, `contract`)
+///
+/// We try to determine which crate ident to use based on the dependencies of
+/// the project in which the macro is used. This is useful because the macros,
+/// like `EthEvent` are provided by the `ethers-contract` crate which depends on
+/// `ethers_core`. Most commonly `ethers` will be used as dependency which
+/// reexports all the different crates, essentially `ethers::core` is
+/// `ethers_core` So depending on the dependency used `ethers` ors `ethers_core
+/// | ethers_contract`, we need to use the fitting crate ident when expand the
+/// macros This will attempt to parse the current `Cargo.toml` and check the
+/// ethers related dependencies.
+pub fn determine_ethers_crates() -> (&'static str, &'static str) {
+    MetadataCommand::new()
+        .manifest_path(&format!(
+            "{}/Cargo.toml",
+            std::env::var("CARGO_MANIFEST_DIR").expect("No Manifest found")
+        ))
+        .exec()
+        .ok()
+        .and_then(|metadata| {
+            metadata.root_package().and_then(|pkg| {
+                pkg.dependencies
+                    .iter()
+                    .filter(|dep| dep.kind == DependencyKind::Normal)
+                    .find_map(|dep| {
+                        (dep.name == "ethers").then(|| ("ethers::core", "ethers::contract"))
+                    })
+            })
+        })
+        .unwrap_or(("ethers_core", "ethers_contract"))
+}
 
 /// Expands a identifier string into an token.
 pub fn ident(name: &str) -> Ident {
diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs
index 50ccd5c3..ec112741 100644
--- a/ethers-contract/ethers-contract-derive/src/lib.rs
+++ b/ethers-contract/ethers-contract-derive/src/lib.rs
@@ -2,7 +2,7 @@
 //! ethereum smart contract.
 #![deny(missing_docs, unsafe_code)]
 
-use ethers_contract_abigen::Source;
+use ethers_contract_abigen::{ethers_contract_crate, ethers_core_crate, Source};
 use proc_macro::TokenStream;
 use proc_macro2::{Literal, Span};
 use quote::{quote, quote_spanned};
@@ -117,6 +117,11 @@ pub fn abigen(input: TokenStream) -> TokenStream {
 #[proc_macro_derive(EthEvent, attributes(ethevent))]
 pub fn derive_abi_event(input: TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as DeriveInput);
+
+    // the ethers crates to use
+    let core_crate = ethers_core_crate();
+    let contract_crate = ethers_contract_crate();
+
     let name = &input.ident;
     let attributes = match parse_attributes(&input) {
         Ok(attributes) => attributes,
@@ -206,13 +211,13 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream {
     let anon = attributes.anonymous.map(|(b, _)| b).unwrap_or_default();
 
     let ethevent_impl = quote! {
-        impl ethers_contract::EthEvent for #name {
+        impl #contract_crate::EthEvent for #name {
 
             fn name() -> ::std::borrow::Cow<'static, str> {
                 #event_name.into()
             }
 
-            fn signature() -> ethers_core::types::H256 {
+            fn signature() -> #core_crate::types::H256 {
                 #signature
             }
 
@@ -220,7 +225,7 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream {
                 #abi.into()
             }
 
-            fn decode_log(log: &ethers_core::abi::RawLog) -> Result<Self, ethers_core::abi::Error> where Self: Sized {
+            fn decode_log(log: &#core_crate::abi::RawLog) -> Result<Self, #core_crate::abi::Error> where Self: Sized {
                 #decode_log_impl
             }
 
@@ -256,55 +261,57 @@ impl EventField {
 // these indexed param types according to
 // https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters
 fn topic_param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream {
+    let core_crate = ethers_core_crate();
     match kind {
         ParamType::String
         | ParamType::Bytes
         | ParamType::Array(_)
         | ParamType::FixedArray(_, _)
-        | ParamType::Tuple(_) => quote! {ethers_core::abi::ParamType::FixedBytes(32)},
+        | ParamType::Tuple(_) => quote! {#core_crate::abi::ParamType::FixedBytes(32)},
         ty => param_type_quote(ty),
     }
 }
 
 fn param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream {
+    let core_crate = ethers_core_crate();
     match kind {
         ParamType::Address => {
-            quote! {ethers_core::abi::ParamType::Address}
+            quote! {#core_crate::abi::ParamType::Address}
         }
         ParamType::Bytes => {
-            quote! {ethers_core::abi::ParamType::Bytes}
+            quote! {#core_crate::abi::ParamType::Bytes}
         }
         ParamType::Int(size) => {
             let size = Literal::usize_suffixed(*size);
-            quote! {ethers_core::abi::ParamType::Int(#size)}
+            quote! {#core_crate::abi::ParamType::Int(#size)}
         }
         ParamType::Uint(size) => {
             let size = Literal::usize_suffixed(*size);
-            quote! {ethers_core::abi::ParamType::Uint(#size)}
+            quote! {#core_crate::abi::ParamType::Uint(#size)}
         }
         ParamType::Bool => {
-            quote! {ethers_core::abi::ParamType::Bool}
+            quote! {#core_crate::abi::ParamType::Bool}
         }
         ParamType::String => {
-            quote! {ethers_core::abi::ParamType::String}
+            quote! {#core_crate::abi::ParamType::String}
         }
         ParamType::Array(ty) => {
             let ty = param_type_quote(&*ty);
-            quote! {ethers_core::abi::ParamType::Array(Box::new(#ty))}
+            quote! {#core_crate::abi::ParamType::Array(Box::new(#ty))}
         }
         ParamType::FixedBytes(size) => {
             let size = Literal::usize_suffixed(*size);
-            quote! {ethers_core::abi::ParamType::FixedBytes(#size)}
+            quote! {#core_crate::abi::ParamType::FixedBytes(#size)}
         }
         ParamType::FixedArray(ty, size) => {
             let ty = param_type_quote(&*ty);
             let size = Literal::usize_suffixed(*size);
-            quote! {ethers_core::abi::ParamType::FixedArray(Box::new(#ty),#size)}
+            quote! {#core_crate::abi::ParamType::FixedArray(Box::new(#ty),#size)}
         }
         ParamType::Tuple(tuple) => {
             let elements = tuple.iter().map(param_type_quote);
             quote! {
-                ethers_core::abi::ParamType::Tuple(
+                #core_crate::abi::ParamType::Tuple(
                     ::std::vec![
                         #( #elements ),*
                     ]
@@ -318,6 +325,8 @@ fn derive_decode_from_log_impl(
     input: &DeriveInput,
     event: &Event,
 ) -> Result<proc_macro2::TokenStream, Error> {
+    let core_crate = ethers_core_crate();
+
     let fields: Vec<_> = match input.data {
         Data::Struct(ref data) => match data.fields {
             Fields::Named(ref fields) => {
@@ -416,16 +425,16 @@ fn derive_decode_from_log_impl(
             },
             quote! {
                 if topic_tokens.len() != topics.len() {
-                    return Err(ethers_core::abi::Error::InvalidData);
+                    return Err(#core_crate::abi::Error::InvalidData);
                 }
             },
         )
     } else {
         (
             quote! {
-                let event_signature = topics.get(0).ok_or(ethers_core::abi::Error::InvalidData)?;
+                let event_signature = topics.get(0).ok_or(#core_crate::abi::Error::InvalidData)?;
                 if event_signature != &Self::signature() {
-                    return Err(ethers_core::abi::Error::InvalidData);
+                    return Err(#core_crate::abi::Error::InvalidData);
                 }
             },
             quote! {
@@ -433,7 +442,7 @@ fn derive_decode_from_log_impl(
             },
             quote! {
                 if topic_tokens.len() != topics.len() - 1 {
-                    return Err(ethers_core::abi::Error::InvalidData);
+                    return Err(#core_crate::abi::Error::InvalidData);
                 }
             },
         )
@@ -447,9 +456,9 @@ fn derive_decode_from_log_impl(
         .all(|(idx, f)| f.index == idx)
     {
         quote! {
-            let topic_tokens = ethers_core::abi::decode(&topic_types, &flat_topics)?;
+            let topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?;
             #topic_tokens_len_check
-            let data_tokens = ethers_core::abi::decode(&data_types, data)?;
+            let data_tokens = #core_crate::abi::decode(&data_types, data)?;
             let tokens:Vec<_> = topic_tokens.into_iter().chain(data_tokens.into_iter()).collect();
         }
     } else {
@@ -462,9 +471,9 @@ fn derive_decode_from_log_impl(
         });
 
         quote! {
-            let mut topic_tokens = ethers_core::abi::decode(&topic_types, &flat_topics)?;
+            let mut topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?;
             #topic_tokens_len_check
-            let mut data_tokens = ethers_core::abi::decode(&data_types, &data)?;
+            let mut data_tokens = #core_crate::abi::decode(&data_types, &data)?;
             let mut tokens = Vec::with_capacity(topics.len() + data_tokens.len());
             #( tokens.push(#swap_tokens); )*
         }
@@ -472,7 +481,7 @@ fn derive_decode_from_log_impl(
 
     Ok(quote! {
 
-        let ethers_core::abi::RawLog {data, topics} = log;
+        let #core_crate::abi::RawLog {data, topics} = log;
 
         #signature_check
 
@@ -483,7 +492,7 @@ fn derive_decode_from_log_impl(
 
         #tokens_init
 
-        ethers_core::abi::Detokenize::from_tokens(tokens).map_err(|_|ethers_core::abi::Error::InvalidData)
+        #core_crate::abi::Detokenize::from_tokens(tokens).map_err(|_|#core_crate::abi::Error::InvalidData)
     })
 }
 
@@ -669,8 +678,9 @@ fn parse_int_param_type(s: &str) -> Option<ParamType> {
 }
 
 fn signature(hash: &[u8]) -> proc_macro2::TokenStream {
+    let core_crate = ethers_core_crate();
     let bytes = hash.iter().copied().map(Literal::u8_unsuffixed);
-    quote! {ethers_core::types::H256([#( #bytes ),*])}
+    quote! {#core_crate::types::H256([#( #bytes ),*])}
 }
 
 fn parse_event(abi: &str) -> Result<Event, String> {
@@ -695,6 +705,7 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
 }
 
 fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
+    let core_crate = ethers_core_crate();
     let name = &input.ident;
     let generic_params = input.generics.params.iter().map(|p| quote! { #p });
     let generic_params = quote! { #(#generic_params,)* };
@@ -719,13 +730,13 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
             Fields::Named(ref fields) => {
                 let tokenize_predicates = fields.named.iter().map(|f| {
                     let ty = &f.ty;
-                    quote_spanned! { f.span() => #ty: ethers_core::abi::Tokenize }
+                    quote_spanned! { f.span() => #ty: #core_crate::abi::Tokenize }
                 });
                 let tokenize_predicates = quote! { #(#tokenize_predicates,)* };
 
                 let assignments = fields.named.iter().map(|f| {
                     let name = f.ident.as_ref().expect("Named fields have names");
-                    quote_spanned! { f.span() => #name: ethers_core::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? }
+                    quote_spanned! { f.span() => #name: #core_crate::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? }
                 });
                 let init_struct_impl = quote! { Self { #(#assignments,)* } };
 
@@ -745,12 +756,12 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
             Fields::Unnamed(ref fields) => {
                 let tokenize_predicates = fields.unnamed.iter().map(|f| {
                     let ty = &f.ty;
-                    quote_spanned! { f.span() => #ty: ethers_core::abi::Tokenize }
+                    quote_spanned! { f.span() => #ty: #core_crate::abi::Tokenize }
                 });
                 let tokenize_predicates = quote! { #(#tokenize_predicates,)* };
 
                 let assignments = fields.unnamed.iter().map(|f| {
-                    quote_spanned! { f.span() => ethers_core::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? }
+                    quote_spanned! { f.span() => #core_crate::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? }
                 });
                 let init_struct_impl = quote! { Self(#(#assignments,)* ) };
 
@@ -794,7 +805,7 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
             // can't encode an empty struct
             // TODO: panic instead?
             quote! {
-                ethers_core::abi::Token::Tuple(Vec::new())
+                #core_crate::abi::Token::Tuple(Vec::new())
             },
         ),
         1 => {
@@ -819,9 +830,9 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
         }
         _ => {
             let from_token = quote! {
-                if let ethers_core::abi::Token::Tuple(tokens) = token {
+                if let #core_crate::abi::Token::Tuple(tokens) = token {
                     if tokens.len() != #params_len {
-                        return Err(ethers_core::abi::InvalidOutputType(::std::format!(
+                        return Err(#core_crate::abi::InvalidOutputType(::std::format!(
                             "Expected {} tokens, got {}: {:?}",
                             #params_len,
                             tokens.len(),
@@ -833,7 +844,7 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
 
                     Ok(#init_struct_impl)
                 } else {
-                    Err(ethers_core::abi::InvalidOutputType(::std::format!(
+                    Err(#core_crate::abi::InvalidOutputType(::std::format!(
                         "Expected Tuple, got {:?}",
                         token
                     )))
@@ -841,7 +852,7 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
             };
 
             let into_token = quote! {
-                ethers_core::abi::Token::Tuple(
+                #core_crate::abi::Token::Tuple(
                     ::std::vec![
                         #into_token_impl
                     ]
@@ -852,23 +863,23 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
     };
 
     quote! {
-         impl<#generic_params> ethers_core::abi::Tokenizable for #name<#generic_args>
+         impl<#generic_params> #core_crate::abi::Tokenizable for #name<#generic_args>
          where
              #generic_predicates
              #tokenize_predicates
          {
 
-             fn from_token(token: ethers_core::abi::Token) -> Result<Self, ethers_core::abi::InvalidOutputType> where
+             fn from_token(token: #core_crate::abi::Token) -> Result<Self, #core_crate::abi::InvalidOutputType> where
                  Self: Sized {
                 #from_token_impl
              }
 
-             fn into_token(self) -> ethers_core::abi::Token {
+             fn into_token(self) -> #core_crate::abi::Token {
                 #into_token_impl
              }
          }
 
-        impl<#generic_params> ethers_core::abi::TokenizableItem for #name<#generic_args>
+        impl<#generic_params> #core_crate::abi::TokenizableItem for #name<#generic_args>
          where
              #generic_predicates
              #tokenize_predicates
diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs
index b8a4c76b..b6f7edda 100644
--- a/ethers-contract/src/call.rs
+++ b/ethers-contract/src/call.rs
@@ -1,7 +1,9 @@
 use super::base::{decode_function_data, AbiError};
 use ethers_core::{
     abi::{Detokenize, Function, InvalidOutputType},
-    types::{Address, BlockId, Bytes, TransactionRequest, U256},
+    types::{
+        transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, TransactionRequest, U256,
+    },
 };
 use ethers_providers::{Middleware, PendingTransaction, ProviderError};
 
@@ -48,7 +50,7 @@ pub enum ContractError<M: Middleware> {
 /// Helper for managing a transaction before submitting it to a node
 pub struct ContractCall<M, D> {
     /// The raw transaction object
-    pub tx: TransactionRequest,
+    pub tx: TypedTransaction,
     /// The ABI of the function being called
     pub function: Function,
     /// Optional block number to be used when calculating the transaction's gas and nonce
@@ -60,25 +62,39 @@ pub struct ContractCall<M, D> {
 impl<M, D: Detokenize> ContractCall<M, D> {
     /// Sets the `from` field in the transaction to the provided value
     pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
-        self.tx.from = Some(from.into());
+        self.tx.set_from(from.into());
+        self
+    }
+
+    /// Uses a Legacy transaction instead of an EIP-1559 one to execute the call
+    pub fn legacy(mut self) -> Self {
+        self.tx = match self.tx {
+            TypedTransaction::Eip1559(inner) => {
+                let tx: TransactionRequest = inner.into();
+                TypedTransaction::Legacy(tx)
+            }
+            other => other,
+        };
         self
     }
 
     /// Sets the `gas` field in the transaction to the provided value
     pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
-        self.tx.gas = Some(gas.into());
+        self.tx.set_gas(gas);
         self
     }
 
     /// Sets the `gas_price` field in the transaction to the provided value
+    /// If the internal transaction is an EIP-1559 one, then it sets both
+    /// `max_fee_per_gas` and `max_priority_fee_per_gas` to the same value
     pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
-        self.tx.gas_price = Some(gas_price.into());
+        self.tx.set_gas_price(gas_price);
         self
     }
 
     /// Sets the `value` field in the transaction to the provided value
     pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
-        self.tx.value = Some(value.into());
+        self.tx.set_value(value);
         self
     }
 
@@ -96,7 +112,7 @@ where
 {
     /// Returns the underlying transaction's ABI encoded data
     pub fn calldata(&self) -> Option<Bytes> {
-        self.tx.data.clone()
+        self.tx.data().cloned()
     }
 
     /// Returns the estimated gas cost for the underlying transaction to be executed
@@ -119,7 +135,7 @@ where
     pub async fn call(&self) -> Result<D, ContractError<M>> {
         let bytes = self
             .client
-            .call(&self.tx, self.block)
+            .call(&self.tx.clone(), self.block)
             .await
             .map_err(ContractError::MiddlewareError)?;
 
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index c4d4e8c0..6ede8aa2 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -7,8 +7,14 @@ use crate::{
 
 use ethers_core::{
     abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize},
-    types::{Address, Filter, NameOrAddress, Selector, TransactionRequest},
+    types::{Address, Filter, NameOrAddress, Selector},
 };
+
+#[cfg(not(feature = "legacy"))]
+use ethers_core::types::Eip1559TransactionRequest;
+#[cfg(feature = "legacy")]
+use ethers_core::types::TransactionRequest;
+
 use ethers_providers::Middleware;
 
 use std::{fmt::Debug, marker::PhantomData, sync::Arc};
@@ -220,12 +226,20 @@ impl<M: Middleware> Contract<M> {
     ) -> Result<ContractCall<M, D>, AbiError> {
         let data = encode_function_data(function, args)?;
 
-        // create the tx object
+        #[cfg(feature = "legacy")]
         let tx = TransactionRequest {
             to: Some(NameOrAddress::Address(self.address)),
             data: Some(data),
             ..Default::default()
         };
+        #[cfg(not(feature = "legacy"))]
+        let tx = Eip1559TransactionRequest {
+            to: Some(NameOrAddress::Address(self.address)),
+            data: Some(data),
+            ..Default::default()
+        };
+
+        let tx = tx.into();
 
         Ok(ContractCall {
             tx,
diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs
index d0405ec8..ae818f06 100644
--- a/ethers-contract/src/factory.rs
+++ b/ethers-contract/src/factory.rs
@@ -2,17 +2,20 @@ use crate::{Contract, ContractError};
 
 use ethers_core::{
     abi::{Abi, Tokenize},
-    types::{BlockNumber, Bytes, TransactionRequest},
+    types::{transaction::eip2718::TypedTransaction, BlockNumber, Bytes, TransactionRequest},
 };
 use ethers_providers::Middleware;
 
+#[cfg(not(feature = "legacy"))]
+use ethers_core::types::Eip1559TransactionRequest;
+
 use std::sync::Arc;
 
 #[derive(Debug, Clone)]
 /// Helper which manages the deployment transaction of a smart contract
 pub struct Deployer<M> {
     /// The deployer's transaction, exposed for overriding the defaults
-    pub tx: TransactionRequest,
+    pub tx: TypedTransaction,
     abi: Abi,
     client: Arc<M>,
     confs: usize,
@@ -31,6 +34,18 @@ impl<M: Middleware> Deployer<M> {
         self
     }
 
+    /// Uses a Legacy transaction instead of an EIP-1559 one to do the deployment
+    pub fn legacy(mut self) -> Self {
+        self.tx = match self.tx {
+            TypedTransaction::Eip1559(inner) => {
+                let tx: TransactionRequest = inner.into();
+                TypedTransaction::Legacy(tx)
+            }
+            other => other,
+        };
+        self
+    }
+
     /// Broadcasts the contract deployment transaction and after waiting for it to
     /// be sufficiently confirmed (default: 1), it returns a [`Contract`](crate::Contract)
     /// struct at the deployed contract's address.
@@ -150,11 +165,21 @@ impl<M: Middleware> ContractFactory<M> {
         };
 
         // create the tx object. Since we're deploying a contract, `to` is `None`
+        // We default to EIP-1559 transactions, but the sender can convert it back
+        // to a legacy one
+        #[cfg(feature = "legacy")]
         let tx = TransactionRequest {
             to: None,
             data: Some(data),
             ..Default::default()
         };
+        #[cfg(not(feature = "legacy"))]
+        let tx = Eip1559TransactionRequest {
+            to: None,
+            data: Some(data),
+            ..Default::default()
+        };
+        let tx = tx.into();
 
         Ok(Deployer {
             client: Arc::clone(&self.client), // cheap clone behind the arc
diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs
index 1b644327..11d4a108 100644
--- a/ethers-contract/src/multicall/mod.rs
+++ b/ethers-contract/src/multicall/mod.rs
@@ -137,6 +137,7 @@ pub struct Multicall<M> {
     calls: Vec<Call>,
     block: Option<BlockNumber>,
     contract: MulticallContract<M>,
+    legacy: bool,
 }
 
 #[derive(Clone)]
@@ -188,9 +189,16 @@ impl<M: Middleware> Multicall<M> {
             calls: vec![],
             block: None,
             contract,
+            legacy: false,
         })
     }
 
+    /// Makes a legacy transaction instead of an EIP-1559 one
+    pub fn legacy(mut self) -> Self {
+        self.legacy = true;
+        self
+    }
+
     /// Sets the `block` field for the multicall aggregate call
     pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
         self.block = Some(block.into());
@@ -208,11 +216,11 @@ impl<M: Middleware> Multicall<M> {
             panic!("Cannot support more than {} calls", 16);
         }
 
-        match (call.tx.to, call.tx.data) {
+        match (call.tx.to(), call.tx.data()) {
             (Some(NameOrAddress::Address(target)), Some(data)) => {
                 let call = Call {
-                    target,
-                    data,
+                    target: *target,
+                    data: data.clone(),
                     function: call.function,
                 };
                 self.calls.push(call);
@@ -366,11 +374,15 @@ impl<M: Middleware> Multicall<M> {
             .collect();
 
         // Construct the ContractCall for `aggregate` function to broadcast the transaction
-        let contract_call = self.contract.aggregate(calls);
+        let mut contract_call = self.contract.aggregate(calls);
         if let Some(block) = self.block {
-            contract_call.block(block)
-        } else {
-            contract_call
-        }
+            contract_call = contract_call.block(block)
+        };
+
+        if self.legacy {
+            contract_call = contract_call.legacy();
+        };
+
+        contract_call
     }
 }
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index 26151cc7..66919fec 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -13,6 +13,7 @@ keywords = ["ethereum", "web3", "celo", "ethers"]
 rlp = { version = "0.5.0", default-features = false }
 ethabi = { version = "14.1.0", default-features = false }
 arrayvec = { version = "0.7.1", default-features = false }
+rlp-derive = { version = "0.1.0", default-features = false }
 
 # crypto
 ecdsa = { version = "0.12.3", default-features = false, features = ["std"] }
@@ -30,10 +31,6 @@ glob = { version = "0.3.0", default-features = false }
 bytes = { version = "1.0.1", features = ["serde"] }
 hex = { version = "0.4.3", default-features = false, features = ["std"] }
 
-# bitvec compilation issue
-# https://github.com/bitvecto-rs/bitvec/issues/105#issuecomment-778570981
-funty = "=1.1.0"
-
 # async
 tokio = { version = "1.5", default-features = false, optional = true}
 futures-util = { version = "0.3.16", default-features = false, optional = true}
diff --git a/ethers-core/src/types/block.rs b/ethers-core/src/types/block.rs
index 33f558a3..fcfd0c3a 100644
--- a/ethers-core/src/types/block.rs
+++ b/ethers-core/src/types/block.rs
@@ -71,6 +71,9 @@ pub struct Block<TX> {
     /// Nonce
     #[cfg(not(feature = "celo"))]
     pub nonce: Option<U64>,
+    /// Base fee per unit of gas (if past London)
+    #[serde(rename = "baseFeePerGas")]
+    pub base_fee_per_gas: Option<U256>,
 
     #[cfg(feature = "celo")]
     #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
@@ -206,6 +209,44 @@ mod tests {
         let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#;
         let _block: Block<Transaction> = serde_json::from_str(block).unwrap();
     }
+
+    #[test]
+    // https://github.com/tomusdrw/rust-web3/commit/3a32ee962c0f2f8d50a5e25be9f2dfec7ae0750d
+    fn post_london_block() {
+        let json = serde_json::json!(
+        {
+            "baseFeePerGas": "0x7",
+            "miner": "0x0000000000000000000000000000000000000001",
+            "number": "0x1b4",
+            "hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
+            "parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5",
+            "mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010",
+            "nonce": "0x0000000000000000",
+            "sealFields": [
+              "0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2",
+              "0x0000000000000042"
+            ],
+            "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+            "logsBloom":  "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
+            "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+            "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+            "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff",
+            "difficulty": "0x27f07",
+            "totalDifficulty": "0x27f07",
+            "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
+            "size": "0x27f07",
+            "gasLimit": "0x9f759",
+            "minGasPrice": "0x9f759",
+            "gasUsed": "0x9f759",
+            "timestamp": "0x54e34e8e",
+            "transactions": [],
+            "uncles": []
+          }
+        );
+
+        let block: Block<()> = serde_json::from_value(json).unwrap();
+        assert_eq!(block.base_fee_per_gas, Some(U256::from(7)));
+    }
 }
 
 #[cfg(test)]
diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs
index 402dd22b..2cf9d5fc 100644
--- a/ethers-core/src/types/mod.rs
+++ b/ethers-core/src/types/mod.rs
@@ -7,8 +7,13 @@ pub use ethabi::ethereum_types::H256 as TxHash;
 
 pub use ethabi::ethereum_types::{Address, Bloom, H160, H256, U128, U256, U64};
 
-mod transaction;
-pub use transaction::{Transaction, TransactionReceipt, TransactionRequest};
+pub mod transaction;
+pub use transaction::{
+    eip1559::Eip1559TransactionRequest,
+    eip2930::Eip2930TransactionRequest,
+    request::TransactionRequest,
+    response::{Transaction, TransactionReceipt},
+};
 
 mod address_or_bytes;
 pub use address_or_bytes::AddressOrBytes;
diff --git a/ethers-core/src/types/transaction.rs b/ethers-core/src/types/transaction.rs
deleted file mode 100644
index d32f56a6..00000000
--- a/ethers-core/src/types/transaction.rs
+++ /dev/null
@@ -1,431 +0,0 @@
-//! Transaction types
-use crate::{
-    types::{Address, Bloom, Bytes, Log, NameOrAddress, Signature, H256, U256, U64},
-    utils::keccak256,
-};
-
-use rlp::RlpStream;
-use serde::{Deserialize, Serialize};
-
-const BASE_NUM_TX_FIELDS: usize = 9;
-
-// Number of tx fields before signing
-#[cfg(not(feature = "celo"))]
-const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS;
-// Celo has 3 additional fields
-#[cfg(feature = "celo")]
-const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS + 3;
-
-/// Parameters for sending a transaction
-#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
-pub struct TransactionRequest {
-    /// Sender address or ENS name
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub from: Option<Address>,
-
-    /// Recipient address (None for contract creation)
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub to: Option<NameOrAddress>,
-
-    /// Supplied gas (None for sensible default)
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub gas: Option<U256>,
-
-    /// Gas price (None for sensible default)
-    #[serde(rename = "gasPrice")]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub gas_price: Option<U256>,
-
-    /// Transfered value (None for no transfer)
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub value: Option<U256>,
-
-    /// The compiled code of a contract OR the first 4 bytes of the hash of the
-    /// invoked method signature and encoded parameters. For details see Ethereum Contract ABI
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub data: Option<Bytes>,
-
-    /// Transaction nonce (None for next available nonce)
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub nonce: Option<U256>,
-
-    /////////////////  Celo-specific transaction fields /////////////////
-    /// The currency fees are paid in (None for native currency)
-    #[cfg(feature = "celo")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub fee_currency: Option<Address>,
-
-    /// Gateway fee recipient (None for no gateway fee paid)
-    #[cfg(feature = "celo")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub gateway_fee_recipient: Option<Address>,
-
-    /// Gateway fee amount (None for no gateway fee paid)
-    #[cfg(feature = "celo")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub gateway_fee: Option<U256>,
-}
-
-impl TransactionRequest {
-    /// Creates an empty transaction request with all fields left empty
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    /// Convenience function for sending a new payment transaction to the receiver.
-    pub fn pay<T: Into<NameOrAddress>, V: Into<U256>>(to: T, value: V) -> Self {
-        TransactionRequest {
-            to: Some(to.into()),
-            value: Some(value.into()),
-            ..Default::default()
-        }
-    }
-
-    // Builder pattern helpers
-
-    /// Sets the `from` field in the transaction to the provided value
-    pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
-        self.from = Some(from.into());
-        self
-    }
-
-    /// Sets the `to` field in the transaction to the provided value
-    pub fn to<T: Into<NameOrAddress>>(mut self, to: T) -> Self {
-        self.to = Some(to.into());
-        self
-    }
-
-    /// Sets the `gas` field in the transaction to the provided value
-    pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
-        self.gas = Some(gas.into());
-        self
-    }
-
-    /// Sets the `gas_price` field in the transaction to the provided value
-    pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
-        self.gas_price = Some(gas_price.into());
-        self
-    }
-
-    /// Sets the `value` field in the transaction to the provided value
-    pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
-        self.value = Some(value.into());
-        self
-    }
-
-    /// Sets the `data` field in the transaction to the provided value
-    pub fn data<T: Into<Bytes>>(mut self, data: T) -> Self {
-        self.data = Some(data.into());
-        self
-    }
-
-    /// Sets the `nonce` field in the transaction to the provided value
-    pub fn nonce<T: Into<U256>>(mut self, nonce: T) -> Self {
-        self.nonce = Some(nonce.into());
-        self
-    }
-
-    /// Hashes the transaction's data with the provided chain id
-    pub fn sighash<T: Into<U64>>(&self, chain_id: T) -> H256 {
-        keccak256(self.rlp(chain_id).as_ref()).into()
-    }
-
-    /// Gets the unsigned transaction's RLP encoding
-    pub fn rlp<T: Into<U64>>(&self, chain_id: T) -> Bytes {
-        let mut rlp = RlpStream::new();
-        rlp.begin_list(NUM_TX_FIELDS);
-        self.rlp_base(&mut rlp);
-
-        // Only hash the 3 extra fields when preparing the
-        // data to sign if chain_id is present
-        rlp.append(&chain_id.into());
-        rlp.append(&0u8);
-        rlp.append(&0u8);
-        rlp.out().freeze().into()
-    }
-
-    /// Produces the RLP encoding of the transaction with the provided signature
-    pub fn rlp_signed(&self, signature: &Signature) -> Bytes {
-        let mut rlp = RlpStream::new();
-        rlp.begin_list(NUM_TX_FIELDS);
-        self.rlp_base(&mut rlp);
-
-        // append the signature
-        rlp.append(&signature.v);
-        rlp.append(&signature.r);
-        rlp.append(&signature.s);
-        rlp.out().freeze().into()
-    }
-
-    fn rlp_base(&self, rlp: &mut RlpStream) {
-        rlp_opt(rlp, self.nonce);
-        rlp_opt(rlp, self.gas_price);
-        rlp_opt(rlp, self.gas);
-
-        #[cfg(feature = "celo")]
-        self.inject_celo_metadata(rlp);
-
-        rlp_opt(rlp, self.to.as_ref());
-        rlp_opt(rlp, self.value);
-        rlp_opt(rlp, self.data.as_ref().map(|d| d.as_ref()));
-    }
-}
-
-// Separate impl block for the celo-specific fields
-#[cfg(feature = "celo")]
-impl TransactionRequest {
-    // modifies the RLP stream with the Celo-specific information
-    fn inject_celo_metadata(&self, rlp: &mut RlpStream) {
-        rlp_opt(rlp, self.fee_currency);
-        rlp_opt(rlp, self.gateway_fee_recipient);
-        rlp_opt(rlp, self.gateway_fee);
-    }
-
-    /// Sets the `fee_currency` field in the transaction to the provided value
-    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
-    pub fn fee_currency<T: Into<Address>>(mut self, fee_currency: T) -> Self {
-        self.fee_currency = Some(fee_currency.into());
-        self
-    }
-
-    /// Sets the `gateway_fee` field in the transaction to the provided value
-    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
-    pub fn gateway_fee<T: Into<U256>>(mut self, gateway_fee: T) -> Self {
-        self.gateway_fee = Some(gateway_fee.into());
-        self
-    }
-
-    /// Sets the `gateway_fee_recipient` field in the transaction to the provided value
-    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
-    pub fn gateway_fee_recipient<T: Into<Address>>(mut self, gateway_fee_recipient: T) -> Self {
-        self.gateway_fee_recipient = Some(gateway_fee_recipient.into());
-        self
-    }
-}
-
-fn rlp_opt<T: rlp::Encodable>(rlp: &mut RlpStream, opt: Option<T>) {
-    if let Some(ref inner) = opt {
-        rlp.append(inner);
-    } else {
-        rlp.append(&"");
-    }
-}
-
-/// Details of a signed transaction
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
-pub struct Transaction {
-    /// The transaction's hash
-    pub hash: H256,
-
-    /// The transaction's nonce
-    pub nonce: U256,
-
-    /// Block hash. None when pending.
-    #[serde(rename = "blockHash")]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub block_hash: Option<H256>,
-
-    /// Block number. None when pending.
-    #[serde(rename = "blockNumber")]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub block_number: Option<U64>,
-
-    /// Transaction Index. None when pending.
-    #[serde(rename = "transactionIndex")]
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub transaction_index: Option<U64>,
-
-    /// Sender
-    pub from: Address,
-
-    /// Recipient (None when contract creation)
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub to: Option<Address>,
-
-    /// Transfered value
-    pub value: U256,
-
-    /// Gas Price
-    #[serde(rename = "gasPrice")]
-    pub gas_price: U256,
-
-    /// Gas amount
-    pub gas: U256,
-
-    /// Input data
-    pub input: Bytes,
-
-    /// ECDSA recovery id
-    pub v: U64,
-
-    /// ECDSA signature r
-    pub r: U256,
-
-    /// ECDSA signature s
-    pub s: U256,
-
-    /////////////////  Celo-specific transaction fields /////////////////
-    /// The currency fees are paid in (None for native currency)
-    #[cfg(feature = "celo")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
-    #[serde(skip_serializing_if = "Option::is_none", rename = "feeCurrency")]
-    pub fee_currency: Option<Address>,
-
-    /// Gateway fee recipient (None for no gateway fee paid)
-    #[cfg(feature = "celo")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
-    #[serde(
-        skip_serializing_if = "Option::is_none",
-        rename = "gatewayFeeRecipient"
-    )]
-    pub gateway_fee_recipient: Option<Address>,
-
-    /// Gateway fee amount (None for no gateway fee paid)
-    #[cfg(feature = "celo")]
-    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
-    #[serde(skip_serializing_if = "Option::is_none", rename = "gatewayFee")]
-    pub gateway_fee: Option<U256>,
-}
-
-impl Transaction {
-    // modifies the RLP stream with the Celo-specific information
-    // This is duplicated from TransactionRequest. Is there a good way to get rid
-    // of this code duplication?
-    #[cfg(feature = "celo")]
-    fn inject_celo_metadata(&self, rlp: &mut RlpStream) {
-        rlp_opt(rlp, self.fee_currency);
-        rlp_opt(rlp, self.gateway_fee_recipient);
-        rlp_opt(rlp, self.gateway_fee);
-    }
-
-    pub fn hash(&self) -> H256 {
-        keccak256(&self.rlp().as_ref()).into()
-    }
-
-    pub fn rlp(&self) -> Bytes {
-        let mut rlp = RlpStream::new();
-        rlp.begin_list(NUM_TX_FIELDS);
-        rlp.append(&self.nonce);
-        rlp.append(&self.gas_price);
-        rlp.append(&self.gas);
-
-        #[cfg(feature = "celo")]
-        self.inject_celo_metadata(&mut rlp);
-
-        rlp_opt(&mut rlp, self.to);
-        rlp.append(&self.value);
-        rlp.append(&self.input.as_ref());
-        rlp.append(&self.v);
-        rlp.append(&self.r);
-        rlp.append(&self.s);
-
-        rlp.out().freeze().into()
-    }
-}
-
-/// "Receipt" of an executed transaction: details of its execution.
-#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
-pub struct TransactionReceipt {
-    /// Transaction hash.
-    #[serde(rename = "transactionHash")]
-    pub transaction_hash: H256,
-    /// Index within the block.
-    #[serde(rename = "transactionIndex")]
-    pub transaction_index: U64,
-    /// Hash of the block this transaction was included within.
-    #[serde(rename = "blockHash")]
-    pub block_hash: Option<H256>,
-    /// Number of the block this transaction was included within.
-    #[serde(rename = "blockNumber")]
-    pub block_number: Option<U64>,
-    /// Cumulative gas used within the block after this was executed.
-    #[serde(rename = "cumulativeGasUsed")]
-    pub cumulative_gas_used: U256,
-    /// Gas used by this transaction alone.
-    ///
-    /// Gas used is `None` if the the client is running in light client mode.
-    #[serde(rename = "gasUsed")]
-    pub gas_used: Option<U256>,
-    /// Contract address created, or `None` if not a deployment.
-    #[serde(rename = "contractAddress")]
-    pub contract_address: Option<Address>,
-    /// Logs generated within this transaction.
-    pub logs: Vec<Log>,
-    /// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
-    pub status: Option<U64>,
-    /// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
-    pub root: Option<H256>,
-    /// Logs bloom
-    #[serde(rename = "logsBloom")]
-    pub logs_bloom: Bloom,
-}
-
-#[cfg(test)]
-#[cfg(not(feature = "celo"))]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn decode_unsigned_transaction() {
-        let _res: TransactionRequest = serde_json::from_str(
-            r#"{
-    "gas":"0xc350",
-    "gasPrice":"0x4a817c800",
-    "hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
-    "input":"0x68656c6c6f21",
-    "nonce":"0x15",
-    "to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
-    "transactionIndex":"0x41",
-    "value":"0xf3dbb76162000",
-    "chain_id": "0x1"
-  }"#,
-        )
-        .unwrap();
-    }
-
-    #[test]
-    fn decode_transaction_response() {
-        let _res: Transaction = serde_json::from_str(
-            r#"{
-    "blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2",
-    "blockNumber":"0x5daf3b",
-    "from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d",
-    "gas":"0xc350",
-    "gasPrice":"0x4a817c800",
-    "hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
-    "input":"0x68656c6c6f21",
-    "nonce":"0x15",
-    "to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
-    "transactionIndex":"0x41",
-    "value":"0xf3dbb76162000",
-    "v":"0x25",
-    "r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea",
-    "s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c"
-  }"#,
-        )
-        .unwrap();
-
-        let _res: Transaction = serde_json::from_str(
-            r#"{
-            "hash":"0xdd79ab0f996150aa3c9f135bbb9272cf0dedb830fafcbbf0c06020503565c44f",
-            "nonce":"0xe",
-            "blockHash":"0xef3fe1f532c3d8783a6257619bc123e9453aa8d6614e4cdb4cc8b9e1ed861404",
-            "blockNumber":"0xf",
-            "transactionIndex":"0x0",
-            "from":"0x1b67b03cdccfae10a2d80e52d3d026dbe2960ad0",
-            "to":"0x986ee0c8b91a58e490ee59718cca41056cf55f24",
-            "value":"0x2710",
-            "gas":"0x5208",
-            "gasPrice":"0x186a0",
-            "input":"0x",
-            "v":"0x25",
-            "r":"0x75188beb2f601bb8cf52ef89f92a6ba2bb7edcf8e3ccde90548cc99cbea30b1e",
-            "s":"0xc0559a540f16d031f3404d5df2bb258084eee56ed1193d8b534bb6affdb3c2c"
-    }"#,
-        )
-        .unwrap();
-    }
-}
diff --git a/ethers-core/src/types/transaction/eip1559.rs b/ethers-core/src/types/transaction/eip1559.rs
new file mode 100644
index 00000000..0c80fe8a
--- /dev/null
+++ b/ethers-core/src/types/transaction/eip1559.rs
@@ -0,0 +1,190 @@
+use super::{eip2930::AccessList, normalize_v, rlp_opt};
+use crate::{
+    types::{Address, Bytes, NameOrAddress, Signature, H256, U256, U64},
+    utils::keccak256,
+};
+use rlp::RlpStream;
+
+/// EIP-1559 transactions have 9 fields
+const NUM_TX_FIELDS: usize = 9;
+
+use serde::{Deserialize, Serialize};
+/// Parameters for sending a transaction
+#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
+pub struct Eip1559TransactionRequest {
+    /// Sender address or ENS name
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub from: Option<Address>,
+
+    /// Recipient address (None for contract creation)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub to: Option<NameOrAddress>,
+
+    /// Supplied gas (None for sensible default)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub gas: Option<U256>,
+
+    /// Transfered value (None for no transfer)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub value: Option<U256>,
+
+    /// The compiled code of a contract OR the first 4 bytes of the hash of the
+    /// invoked method signature and encoded parameters. For details see Ethereum Contract ABI
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub data: Option<Bytes>,
+
+    /// Transaction nonce (None for next available nonce)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub nonce: Option<U256>,
+
+    #[serde(rename = "accessList", default)]
+    pub access_list: AccessList,
+
+    #[serde(
+        rename = "maxPriorityFeePerGas",
+        default,
+        skip_serializing_if = "Option::is_none"
+    )]
+    /// Represents the maximum tx fee that will go to the miner as part of the user's
+    /// fee payment. It serves 3 purposes:
+    /// 1. Compensates miners for the uncle/ommer risk + fixed costs of including transaction in a block;
+    /// 2. Allows users with high opportunity costs to pay a premium to miners;
+    /// 3. In times where demand exceeds the available block space (i.e. 100% full, 30mm gas),
+    /// this component allows first price auctions (i.e. the pre-1559 fee model) to happen on the priority fee.
+    ///
+    /// More context [here](https://hackmd.io/@q8X_WM2nTfu6nuvAzqXiTQ/1559-wallets)
+    pub max_priority_fee_per_gas: Option<U256>,
+
+    #[serde(
+        rename = "maxFeePerGas",
+        default,
+        skip_serializing_if = "Option::is_none"
+    )]
+    /// Represents the maximum amount that a user is willing to pay for their tx (inclusive of baseFeePerGas and maxPriorityFeePerGas).
+    /// The difference between maxFeePerGas and baseFeePerGas + maxPriorityFeePerGas is “refunded” to the user.
+    pub max_fee_per_gas: Option<U256>,
+}
+
+impl Eip1559TransactionRequest {
+    /// Creates an empty transaction request with all fields left empty
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    // Builder pattern helpers
+
+    /// Sets the `from` field in the transaction to the provided value
+    pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
+        self.from = Some(from.into());
+        self
+    }
+
+    /// Sets the `to` field in the transaction to the provided value
+    pub fn to<T: Into<NameOrAddress>>(mut self, to: T) -> Self {
+        self.to = Some(to.into());
+        self
+    }
+
+    /// Sets the `gas` field in the transaction to the provided value
+    pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
+        self.gas = Some(gas.into());
+        self
+    }
+
+    /// Sets the `max_priority_fee_per_gas` field in the transaction to the provided value
+    pub fn max_priority_fee_per_gas<T: Into<U256>>(mut self, max_priority_fee_per_gas: T) -> Self {
+        self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas.into());
+        self
+    }
+
+    /// Sets the `max_fee_per_gas` field in the transaction to the provided value
+    pub fn max_fee_per_gas<T: Into<U256>>(mut self, max_fee_per_gas: T) -> Self {
+        self.max_fee_per_gas = Some(max_fee_per_gas.into());
+        self
+    }
+
+    /// Sets the `value` field in the transaction to the provided value
+    pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
+        self.value = Some(value.into());
+        self
+    }
+
+    /// Sets the `data` field in the transaction to the provided value
+    pub fn data<T: Into<Bytes>>(mut self, data: T) -> Self {
+        self.data = Some(data.into());
+        self
+    }
+
+    /// 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 = access_list.into();
+        self
+    }
+
+    /// Sets the `nonce` field in the transaction to the provided value
+    pub fn nonce<T: Into<U256>>(mut self, nonce: T) -> Self {
+        self.nonce = Some(nonce.into());
+        self
+    }
+
+    /// Hashes the transaction's data with the provided chain id
+    pub fn sighash<T: Into<U64>>(&self, chain_id: T) -> H256 {
+        keccak256(self.rlp(chain_id).as_ref()).into()
+    }
+
+    /// Gets the unsigned transaction's RLP encoding
+    pub fn rlp<T: Into<U64>>(&self, chain_id: T) -> Bytes {
+        let mut rlp = RlpStream::new();
+        rlp.begin_list(NUM_TX_FIELDS);
+        self.rlp_base(chain_id, &mut rlp);
+        rlp.out().freeze().into()
+    }
+
+    /// Produces the RLP encoding of the transaction with the provided signature
+    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
+        let v = normalize_v(signature.v, chain_id);
+        rlp.append(&v);
+        rlp.append(&signature.r);
+        rlp.append(&signature.s);
+        rlp.finalize_unbounded_list();
+        rlp.out().freeze().into()
+    }
+
+    pub(crate) fn rlp_base<T: Into<U64>>(&self, chain_id: T, rlp: &mut RlpStream) {
+        rlp.append(&chain_id.into());
+        rlp_opt(rlp, &self.nonce);
+        rlp_opt(rlp, &self.max_priority_fee_per_gas);
+        rlp_opt(rlp, &self.max_fee_per_gas);
+        rlp_opt(rlp, &self.gas);
+        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.append(&self.access_list);
+    }
+}
+
+impl From<Eip1559TransactionRequest> for super::request::TransactionRequest {
+    fn from(tx: Eip1559TransactionRequest) -> Self {
+        Self {
+            from: tx.from,
+            to: tx.to,
+            gas: tx.gas,
+            gas_price: tx.max_fee_per_gas,
+            value: tx.value,
+            data: tx.data,
+            nonce: tx.nonce,
+            #[cfg(feature = "celo")]
+            fee_currency: None,
+            #[cfg(feature = "celo")]
+            gateway_fee_recipient: None,
+            #[cfg(feature = "celo")]
+            gateway_fee: None,
+        }
+    }
+}
diff --git a/ethers-core/src/types/transaction/eip2718.rs b/ethers-core/src/types/transaction/eip2718.rs
new file mode 100644
index 00000000..1ae93f25
--- /dev/null
+++ b/ethers-core/src/types/transaction/eip2718.rs
@@ -0,0 +1,221 @@
+use super::{eip1559::Eip1559TransactionRequest, eip2930::Eip2930TransactionRequest};
+use crate::{
+    types::{Address, Bytes, NameOrAddress, Signature, TransactionRequest, H256, U256, U64},
+    utils::keccak256,
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
+#[serde(tag = "type")]
+pub enum TypedTransaction {
+    // 0x00
+    #[serde(rename = "0x00")]
+    Legacy(TransactionRequest),
+    // 0x01
+    #[serde(rename = "0x01")]
+    Eip2930(Eip2930TransactionRequest),
+    // 0x02
+    #[serde(rename = "0x02")]
+    Eip1559(Eip1559TransactionRequest),
+}
+
+use TypedTransaction::*;
+
+impl TypedTransaction {
+    pub fn from(&self) -> Option<&Address> {
+        match self {
+            Legacy(inner) => inner.from.as_ref(),
+            Eip2930(inner) => inner.tx.from.as_ref(),
+            Eip1559(inner) => inner.from.as_ref(),
+        }
+    }
+
+    pub fn set_from(&mut self, from: Address) {
+        match self {
+            Legacy(inner) => inner.from = Some(from),
+            Eip2930(inner) => inner.tx.from = Some(from),
+            Eip1559(inner) => inner.from = Some(from),
+        };
+    }
+
+    pub fn to(&self) -> Option<&NameOrAddress> {
+        match self {
+            Legacy(inner) => inner.to.as_ref(),
+            Eip2930(inner) => inner.tx.to.as_ref(),
+            Eip1559(inner) => inner.to.as_ref(),
+        }
+    }
+
+    pub fn set_to<T: Into<NameOrAddress>>(&mut self, to: T) {
+        let to = to.into();
+        match self {
+            Legacy(inner) => inner.to = Some(to),
+            Eip2930(inner) => inner.tx.to = Some(to),
+            Eip1559(inner) => inner.to = Some(to),
+        };
+    }
+
+    pub fn nonce(&self) -> Option<&U256> {
+        match self {
+            Legacy(inner) => inner.nonce.as_ref(),
+            Eip2930(inner) => inner.tx.nonce.as_ref(),
+            Eip1559(inner) => inner.nonce.as_ref(),
+        }
+    }
+
+    pub fn set_nonce<T: Into<U256>>(&mut self, nonce: T) {
+        let nonce = nonce.into();
+        match self {
+            Legacy(inner) => inner.nonce = Some(nonce),
+            Eip2930(inner) => inner.tx.nonce = Some(nonce),
+            Eip1559(inner) => inner.nonce = Some(nonce),
+        };
+    }
+
+    pub fn value(&self) -> Option<&U256> {
+        match self {
+            Legacy(inner) => inner.value.as_ref(),
+            Eip2930(inner) => inner.tx.value.as_ref(),
+            Eip1559(inner) => inner.value.as_ref(),
+        }
+    }
+
+    pub fn set_value<T: Into<U256>>(&mut self, value: T) {
+        let value = value.into();
+        match self {
+            Legacy(inner) => inner.value = Some(value),
+            Eip2930(inner) => inner.tx.value = Some(value),
+            Eip1559(inner) => inner.value = Some(value),
+        };
+    }
+
+    pub fn gas(&self) -> Option<&U256> {
+        match self {
+            Legacy(inner) => inner.gas.as_ref(),
+            Eip2930(inner) => inner.tx.gas.as_ref(),
+            Eip1559(inner) => inner.gas.as_ref(),
+        }
+    }
+
+    pub fn set_gas<T: Into<U256>>(&mut self, gas: T) {
+        let gas = gas.into();
+        match self {
+            Legacy(inner) => inner.gas = Some(gas),
+            Eip2930(inner) => inner.tx.gas = Some(gas),
+            Eip1559(inner) => inner.gas = Some(gas),
+        };
+    }
+
+    pub fn set_gas_price<T: Into<U256>>(&mut self, gas_price: T) {
+        let gas_price = gas_price.into();
+        match self {
+            Legacy(inner) => inner.gas_price = Some(gas_price),
+            Eip2930(inner) => inner.tx.gas_price = Some(gas_price),
+            Eip1559(inner) => {
+                inner.max_fee_per_gas = Some(gas_price);
+                inner.max_priority_fee_per_gas = Some(gas_price);
+            }
+        };
+    }
+
+    pub fn data(&self) -> Option<&Bytes> {
+        match self {
+            Legacy(inner) => inner.data.as_ref(),
+            Eip2930(inner) => inner.tx.data.as_ref(),
+            Eip1559(inner) => inner.data.as_ref(),
+        }
+    }
+
+    pub fn set_data(&mut self, data: Bytes) {
+        match self {
+            Legacy(inner) => inner.data = Some(data),
+            Eip2930(inner) => inner.tx.data = Some(data),
+            Eip1559(inner) => inner.data = Some(data),
+        };
+    }
+
+    pub fn rlp_signed<T: Into<U64>>(&self, chain_id: T, signature: &Signature) -> Bytes {
+        let mut encoded = vec![];
+        match self {
+            Legacy(ref tx) => {
+                encoded.extend_from_slice(tx.rlp_signed(signature).as_ref());
+            }
+            Eip2930(inner) => {
+                encoded.extend_from_slice(&[0x1]);
+                encoded.extend_from_slice(inner.rlp_signed(chain_id, signature).as_ref());
+            }
+            Eip1559(inner) => {
+                encoded.extend_from_slice(&[0x2]);
+                encoded.extend_from_slice(inner.rlp_signed(chain_id, signature).as_ref());
+            }
+        };
+        encoded.into()
+    }
+
+    pub fn rlp<T: Into<U64>>(&self, chain_id: T) -> Bytes {
+        let chain_id = chain_id.into();
+        let mut encoded = vec![];
+        match self {
+            Legacy(inner) => {
+                encoded.extend_from_slice(inner.rlp(chain_id).as_ref());
+            }
+            Eip2930(inner) => {
+                encoded.extend_from_slice(&[0x1]);
+                encoded.extend_from_slice(inner.rlp(chain_id).as_ref());
+            }
+            Eip1559(inner) => {
+                encoded.extend_from_slice(&[0x2]);
+                encoded.extend_from_slice(inner.rlp(chain_id).as_ref());
+            }
+        };
+
+        encoded.into()
+    }
+
+    /// Hashes the transaction's data with the provided chain id
+    /// Does not double-RLP encode
+    pub fn sighash<T: Into<U64>>(&self, chain_id: T) -> H256 {
+        let encoded = self.rlp(chain_id);
+        keccak256(encoded).into()
+    }
+}
+
+impl From<TransactionRequest> for TypedTransaction {
+    fn from(src: TransactionRequest) -> TypedTransaction {
+        TypedTransaction::Legacy(src)
+    }
+}
+
+impl From<Eip2930TransactionRequest> for TypedTransaction {
+    fn from(src: Eip2930TransactionRequest) -> TypedTransaction {
+        TypedTransaction::Eip2930(src)
+    }
+}
+
+impl From<Eip1559TransactionRequest> for TypedTransaction {
+    fn from(src: Eip1559TransactionRequest) -> TypedTransaction {
+        TypedTransaction::Eip1559(src)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::types::{Address, U256};
+
+    #[test]
+    fn serde_legacy_tx() {
+        let tx = TransactionRequest::new()
+            .to(Address::zero())
+            .value(U256::from(100));
+        let tx: TypedTransaction = tx.into();
+        let serialized = serde_json::to_string(&tx).unwrap();
+
+        // deserializes to either the envelope type or the inner type
+        let de: TypedTransaction = serde_json::from_str(&serialized).unwrap();
+        assert_eq!(tx, de);
+
+        let de: TransactionRequest = serde_json::from_str(&serialized).unwrap();
+        assert_eq!(tx, TypedTransaction::Legacy(de));
+    }
+}
diff --git a/ethers-core/src/types/transaction/eip2930.rs b/ethers-core/src/types/transaction/eip2930.rs
new file mode 100644
index 00000000..cb1cdcdf
--- /dev/null
+++ b/ethers-core/src/types/transaction/eip2930.rs
@@ -0,0 +1,150 @@
+use super::{normalize_v, request::TransactionRequest};
+use crate::types::{Address, Bytes, Signature, H256, U256, U64};
+
+use rlp::RlpStream;
+use rlp_derive::{RlpEncodable, RlpEncodableWrapper};
+use serde::{Deserialize, Serialize};
+
+const NUM_EIP2930_FIELDS: usize = 8;
+
+/// Access list
+// NB: Need to use `RlpEncodableWrapper` else we get an extra [] in the output
+// https://github.com/gakonst/ethers-rs/pull/353#discussion_r680683869
+#[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)
+    }
+}
+
+impl TransactionRequest {
+    /// Sets the `access_list` field in the transaction (converts the [`TransactionRequest`] to
+    /// an [`Eip2930TransactionRequest`])
+    pub fn with_access_list<T: Into<AccessList>>(
+        self,
+        access_list: T,
+    ) -> Eip2930TransactionRequest {
+        Eip2930TransactionRequest::new(self, access_list.into())
+    }
+}
+
+/// Access list item
+#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, RlpEncodable)]
+#[serde(rename_all = "camelCase")]
+pub struct AccessListItem {
+    /// Accessed address
+    pub address: Address,
+    /// Accessed storage keys
+    pub storage_keys: Vec<H256>,
+}
+
+/// An EIP-2930 transaction is a legacy transaction including an [`AccessList`].
+#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
+pub struct Eip2930TransactionRequest {
+    #[serde(flatten)]
+    pub tx: TransactionRequest,
+    pub access_list: AccessList,
+}
+
+impl Eip2930TransactionRequest {
+    pub fn new(tx: TransactionRequest, access_list: AccessList) -> Self {
+        Self { tx, access_list }
+    }
+
+    pub fn rlp<T: Into<U64>>(&self, chain_id: T) -> Bytes {
+        let mut rlp = RlpStream::new();
+        rlp.begin_list(NUM_EIP2930_FIELDS);
+        rlp.append(&chain_id.into());
+        self.tx.rlp_base(&mut rlp);
+        // append the access list in addition to the base rlp encoding
+        rlp.append(&self.access_list);
+
+        rlp.out().freeze().into()
+    }
+
+    /// Produces the RLP encoding of the transaction with the provided signature
+    pub fn rlp_signed<T: Into<U64>>(&self, chain_id: T, signature: &Signature) -> Bytes {
+        let mut rlp = RlpStream::new();
+        rlp.begin_list(NUM_EIP2930_FIELDS + 3);
+
+        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
+        let v = normalize_v(signature.v, chain_id);
+        rlp.append(&v);
+        rlp.append(&signature.r);
+        rlp.append(&signature.s);
+        rlp.out().freeze().into()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::types::{transaction::eip2718::TypedTransaction, U256};
+
+    #[test]
+    #[cfg_attr(feature = "celo", ignore)]
+    // https://github.com/ethereum/go-ethereum/blob/c503f98f6d5e80e079c1d8a3601d188af2a899da/core/types/transaction_test.go#L59-L67
+    fn rlp() {
+        let tx: TypedTransaction = TransactionRequest::new()
+            .nonce(3)
+            .gas_price(1)
+            .gas(25000)
+            .to("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"
+                .parse::<Address>()
+                .unwrap())
+            .value(10)
+            .data(vec![0x55, 0x44])
+            .with_access_list(vec![])
+            .into();
+
+        let hash = tx.sighash(1);
+        let sig: Signature = "c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101".parse().unwrap();
+        assert_eq!(
+            hash,
+            "49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3"
+                .parse()
+                .unwrap()
+        );
+
+        let enc = rlp::encode(&tx.rlp_signed(1, &sig).as_ref());
+        let expected = "b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521";
+        assert_eq!(hex::encode(enc.to_vec()), expected);
+    }
+
+    #[test]
+    fn serde_eip2930_tx() {
+        let access_list = vec![AccessListItem {
+            address: Address::zero(),
+            storage_keys: vec![H256::zero()],
+        }];
+        let tx = TransactionRequest::new()
+            .to(Address::zero())
+            .value(U256::from(100))
+            .with_access_list(access_list);
+        let tx: TypedTransaction = tx.into();
+        let serialized = serde_json::to_string(&tx).unwrap();
+        dbg!(&serialized);
+
+        // deserializes to either the envelope type or the inner type
+        let de: TypedTransaction = serde_json::from_str(&serialized).unwrap();
+        assert_eq!(tx, de);
+
+        let de: Eip2930TransactionRequest = serde_json::from_str(&serialized).unwrap();
+        assert_eq!(tx, TypedTransaction::Eip2930(de));
+    }
+}
diff --git a/ethers-core/src/types/transaction/mod.rs b/ethers-core/src/types/transaction/mod.rs
new file mode 100644
index 00000000..51c5f9c9
--- /dev/null
+++ b/ethers-core/src/types/transaction/mod.rs
@@ -0,0 +1,32 @@
+pub mod request;
+pub mod response;
+
+pub mod eip1559;
+pub mod eip2718;
+pub mod eip2930;
+
+pub(crate) const BASE_NUM_TX_FIELDS: usize = 9;
+
+// Number of tx fields before signing
+#[cfg(not(feature = "celo"))]
+pub(crate) const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS;
+// Celo has 3 additional fields
+#[cfg(feature = "celo")]
+pub(crate) const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS + 3;
+
+pub(super) fn rlp_opt<T: rlp::Encodable>(rlp: &mut rlp::RlpStream, opt: &Option<T>) {
+    if let Some(inner) = opt {
+        rlp.append(inner);
+    } else {
+        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-core/src/types/transaction/request.rs b/ethers-core/src/types/transaction/request.rs
new file mode 100644
index 00000000..e71f5fc9
--- /dev/null
+++ b/ethers-core/src/types/transaction/request.rs
@@ -0,0 +1,223 @@
+//! Transaction types
+use super::{rlp_opt, NUM_TX_FIELDS};
+use crate::{
+    types::{Address, Bytes, NameOrAddress, Signature, H256, U256, U64},
+    utils::keccak256,
+};
+
+use rlp::RlpStream;
+use serde::{Deserialize, Serialize};
+
+/// Parameters for sending a transaction
+#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
+pub struct TransactionRequest {
+    /// Sender address or ENS name
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub from: Option<Address>,
+
+    /// Recipient address (None for contract creation)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub to: Option<NameOrAddress>,
+
+    /// Supplied gas (None for sensible default)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub gas: Option<U256>,
+
+    /// Gas price (None for sensible default)
+    #[serde(rename = "gasPrice")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub gas_price: Option<U256>,
+
+    /// Transfered value (None for no transfer)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub value: Option<U256>,
+
+    /// The compiled code of a contract OR the first 4 bytes of the hash of the
+    /// invoked method signature and encoded parameters. For details see Ethereum Contract ABI
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub data: Option<Bytes>,
+
+    /// Transaction nonce (None for next available nonce)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub nonce: Option<U256>,
+
+    /////////////////  Celo-specific transaction fields /////////////////
+    /// The currency fees are paid in (None for native currency)
+    #[cfg(feature = "celo")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub fee_currency: Option<Address>,
+
+    /// Gateway fee recipient (None for no gateway fee paid)
+    #[cfg(feature = "celo")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub gateway_fee_recipient: Option<Address>,
+
+    /// Gateway fee amount (None for no gateway fee paid)
+    #[cfg(feature = "celo")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub gateway_fee: Option<U256>,
+}
+
+impl TransactionRequest {
+    /// Creates an empty transaction request with all fields left empty
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Convenience function for sending a new payment transaction to the receiver.
+    pub fn pay<T: Into<NameOrAddress>, V: Into<U256>>(to: T, value: V) -> Self {
+        TransactionRequest {
+            to: Some(to.into()),
+            value: Some(value.into()),
+            ..Default::default()
+        }
+    }
+
+    // Builder pattern helpers
+
+    /// Sets the `from` field in the transaction to the provided value
+    pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
+        self.from = Some(from.into());
+        self
+    }
+
+    /// Sets the `to` field in the transaction to the provided value
+    pub fn to<T: Into<NameOrAddress>>(mut self, to: T) -> Self {
+        self.to = Some(to.into());
+        self
+    }
+
+    /// Sets the `gas` field in the transaction to the provided value
+    pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
+        self.gas = Some(gas.into());
+        self
+    }
+
+    /// Sets the `gas_price` field in the transaction to the provided value
+    pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
+        self.gas_price = Some(gas_price.into());
+        self
+    }
+
+    /// Sets the `value` field in the transaction to the provided value
+    pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
+        self.value = Some(value.into());
+        self
+    }
+
+    /// Sets the `data` field in the transaction to the provided value
+    pub fn data<T: Into<Bytes>>(mut self, data: T) -> Self {
+        self.data = Some(data.into());
+        self
+    }
+
+    /// Sets the `nonce` field in the transaction to the provided value
+    pub fn nonce<T: Into<U256>>(mut self, nonce: T) -> Self {
+        self.nonce = Some(nonce.into());
+        self
+    }
+
+    /// Hashes the transaction's data with the provided chain id
+    pub fn sighash<T: Into<U64>>(&self, chain_id: T) -> H256 {
+        keccak256(self.rlp(chain_id).as_ref()).into()
+    }
+
+    /// Gets the unsigned transaction's RLP encoding
+    pub fn rlp<T: Into<U64>>(&self, chain_id: T) -> Bytes {
+        let mut rlp = RlpStream::new();
+        rlp.begin_list(NUM_TX_FIELDS);
+        self.rlp_base(&mut rlp);
+
+        // Only hash the 3 extra fields when preparing the
+        // data to sign if chain_id is present
+        rlp.append(&chain_id.into());
+        rlp.append(&0u8);
+        rlp.append(&0u8);
+        rlp.out().freeze().into()
+    }
+
+    /// Produces the RLP encoding of the transaction with the provided signature
+    pub fn rlp_signed(&self, signature: &Signature) -> Bytes {
+        let mut rlp = RlpStream::new();
+        rlp.begin_list(NUM_TX_FIELDS);
+        self.rlp_base(&mut rlp);
+
+        // append the signature
+        rlp.append(&signature.v);
+        rlp.append(&signature.r);
+        rlp.append(&signature.s);
+        rlp.out().freeze().into()
+    }
+
+    pub(crate) fn rlp_base(&self, rlp: &mut RlpStream) {
+        rlp_opt(rlp, &self.nonce);
+        rlp_opt(rlp, &self.gas_price);
+        rlp_opt(rlp, &self.gas);
+
+        #[cfg(feature = "celo")]
+        self.inject_celo_metadata(rlp);
+
+        rlp_opt(rlp, &self.to.as_ref());
+        rlp_opt(rlp, &self.value);
+        rlp_opt(rlp, &self.data.as_ref().map(|d| d.as_ref()));
+    }
+}
+
+// Separate impl block for the celo-specific fields
+#[cfg(feature = "celo")]
+impl TransactionRequest {
+    // modifies the RLP stream with the Celo-specific information
+    fn inject_celo_metadata(&self, rlp: &mut RlpStream) {
+        rlp_opt(rlp, &self.fee_currency);
+        rlp_opt(rlp, &self.gateway_fee_recipient);
+        rlp_opt(rlp, &self.gateway_fee);
+    }
+
+    /// Sets the `fee_currency` field in the transaction to the provided value
+    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    pub fn fee_currency<T: Into<Address>>(mut self, fee_currency: T) -> Self {
+        self.fee_currency = Some(fee_currency.into());
+        self
+    }
+
+    /// Sets the `gateway_fee` field in the transaction to the provided value
+    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    pub fn gateway_fee<T: Into<U256>>(mut self, gateway_fee: T) -> Self {
+        self.gateway_fee = Some(gateway_fee.into());
+        self
+    }
+
+    /// Sets the `gateway_fee_recipient` field in the transaction to the provided value
+    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    pub fn gateway_fee_recipient<T: Into<Address>>(mut self, gateway_fee_recipient: T) -> Self {
+        self.gateway_fee_recipient = Some(gateway_fee_recipient.into());
+        self
+    }
+}
+
+#[cfg(test)]
+#[cfg(not(feature = "celo"))]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn decode_unsigned_transaction() {
+        let _res: TransactionRequest = serde_json::from_str(
+            r#"{
+    "gas":"0xc350",
+    "gasPrice":"0x4a817c800",
+    "hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
+    "input":"0x68656c6c6f21",
+    "nonce":"0x15",
+    "to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
+    "transactionIndex":"0x41",
+    "value":"0xf3dbb76162000",
+    "chain_id": "0x1"
+  }"#,
+        )
+        .unwrap();
+    }
+}
diff --git a/ethers-core/src/types/transaction/response.rs b/ethers-core/src/types/transaction/response.rs
new file mode 100644
index 00000000..9c427acd
--- /dev/null
+++ b/ethers-core/src/types/transaction/response.rs
@@ -0,0 +1,291 @@
+//! Transaction types
+use super::{eip2930::AccessList, rlp_opt, NUM_TX_FIELDS};
+use crate::{
+    types::{Address, Bloom, Bytes, Log, H256, U256, U64},
+    utils::keccak256,
+};
+use rlp::RlpStream;
+use serde::{Deserialize, Serialize};
+
+/// Details of a signed transaction
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
+pub struct Transaction {
+    /// The transaction's hash
+    pub hash: H256,
+
+    /// The transaction's nonce
+    pub nonce: U256,
+
+    /// Block hash. None when pending.
+    #[serde(rename = "blockHash")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub block_hash: Option<H256>,
+
+    /// Block number. None when pending.
+    #[serde(rename = "blockNumber")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub block_number: Option<U64>,
+
+    /// Transaction Index. None when pending.
+    #[serde(rename = "transactionIndex")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub transaction_index: Option<U64>,
+
+    /// Sender
+    pub from: Address,
+
+    /// Recipient (None when contract creation)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub to: Option<Address>,
+
+    /// Transfered value
+    pub value: U256,
+
+    /// Gas Price
+    #[serde(rename = "gasPrice")]
+    // TODO: Should this be deprecated?
+    // https://twitter.com/TimBeiko/status/1413536062429794304
+    // If yes, how will we handle pre-calculating the transaction's hash keccak256(rlp(tx)),
+    // given that it contains the gas price?
+    pub gas_price: U256,
+
+    /// Gas amount
+    pub gas: U256,
+
+    /// Input data
+    pub input: Bytes,
+
+    /// ECDSA recovery id
+    pub v: U64,
+
+    /// ECDSA signature r
+    pub r: U256,
+
+    /// ECDSA signature s
+    pub s: U256,
+
+    /////////////////  Celo-specific transaction fields /////////////////
+    /// The currency fees are paid in (None for native currency)
+    #[cfg(feature = "celo")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    #[serde(skip_serializing_if = "Option::is_none", rename = "feeCurrency")]
+    pub fee_currency: Option<Address>,
+
+    /// Gateway fee recipient (None for no gateway fee paid)
+    #[cfg(feature = "celo")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    #[serde(
+        skip_serializing_if = "Option::is_none",
+        rename = "gatewayFeeRecipient"
+    )]
+    pub gateway_fee_recipient: Option<Address>,
+
+    /// Gateway fee amount (None for no gateway fee paid)
+    #[cfg(feature = "celo")]
+    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    #[serde(skip_serializing_if = "Option::is_none", rename = "gatewayFee")]
+    pub gateway_fee: Option<U256>,
+
+    // EIP2718
+    /// Transaction type, Some(2) for EIP-1559 transaction,
+    /// Some(1) for AccessList transaction, None for Legacy
+    #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
+    pub transaction_type: Option<U64>,
+
+    // EIP2930
+    #[serde(
+        rename = "accessList",
+        default,
+        skip_serializing_if = "Option::is_none"
+    )]
+    pub access_list: Option<AccessList>,
+
+    #[serde(
+        rename = "maxPriorityFeePerGas",
+        default,
+        skip_serializing_if = "Option::is_none"
+    )]
+    /// Represents the maximum tx fee that will go to the miner as part of the user's
+    /// fee payment. It serves 3 purposes:
+    /// 1. Compensates miners for the uncle/ommer risk + fixed costs of including transaction in a block;
+    /// 2. Allows users with high opportunity costs to pay a premium to miners;
+    /// 3. In times where demand exceeds the available block space (i.e. 100% full, 30mm gas),
+    /// this component allows first price auctions (i.e. the pre-1559 fee model) to happen on the priority fee.
+    ///
+    /// More context [here](https://hackmd.io/@q8X_WM2nTfu6nuvAzqXiTQ/1559-wallets)
+    pub max_priority_fee_per_gas: Option<U256>,
+
+    #[serde(
+        rename = "maxFeePerGas",
+        default,
+        skip_serializing_if = "Option::is_none"
+    )]
+    /// Represents the maximum amount that a user is willing to pay for their tx (inclusive of baseFeePerGas and maxPriorityFeePerGas).
+    /// The difference between maxFeePerGas and baseFeePerGas + maxPriorityFeePerGas is “refunded” to the user.
+    pub max_fee_per_gas: Option<U256>,
+}
+
+impl Transaction {
+    // modifies the RLP stream with the Celo-specific information
+    // This is duplicated from TransactionRequest. Is there a good way to get rid
+    // of this code duplication?
+    #[cfg(feature = "celo")]
+    fn inject_celo_metadata(&self, rlp: &mut RlpStream) {
+        rlp_opt(rlp, &self.fee_currency);
+        rlp_opt(rlp, &self.gateway_fee_recipient);
+        rlp_opt(rlp, &self.gateway_fee);
+    }
+
+    pub fn hash(&self) -> H256 {
+        keccak256(&self.rlp().as_ref()).into()
+    }
+
+    pub fn rlp(&self) -> Bytes {
+        let mut rlp = RlpStream::new();
+        rlp.begin_list(NUM_TX_FIELDS);
+        rlp.append(&self.nonce);
+        rlp.append(&self.gas_price);
+        rlp.append(&self.gas);
+
+        #[cfg(feature = "celo")]
+        self.inject_celo_metadata(&mut rlp);
+
+        rlp_opt(&mut rlp, &self.to);
+        rlp.append(&self.value);
+        rlp.append(&self.input.as_ref());
+        rlp.append(&self.v);
+        rlp.append(&self.r);
+        rlp.append(&self.s);
+
+        rlp.out().freeze().into()
+    }
+}
+
+/// "Receipt" of an executed transaction: details of its execution.
+#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
+pub struct TransactionReceipt {
+    /// Transaction hash.
+    #[serde(rename = "transactionHash")]
+    pub transaction_hash: H256,
+    /// Index within the block.
+    #[serde(rename = "transactionIndex")]
+    pub transaction_index: U64,
+    /// Hash of the block this transaction was included within.
+    #[serde(rename = "blockHash")]
+    pub block_hash: Option<H256>,
+    /// Number of the block this transaction was included within.
+    #[serde(rename = "blockNumber")]
+    pub block_number: Option<U64>,
+    /// Cumulative gas used within the block after this was executed.
+    #[serde(rename = "cumulativeGasUsed")]
+    pub cumulative_gas_used: U256,
+    /// Gas used by this transaction alone.
+    ///
+    /// Gas used is `None` if the the client is running in light client mode.
+    #[serde(rename = "gasUsed")]
+    pub gas_used: Option<U256>,
+    /// Contract address created, or `None` if not a deployment.
+    #[serde(rename = "contractAddress")]
+    pub contract_address: Option<Address>,
+    /// Logs generated within this transaction.
+    pub logs: Vec<Log>,
+    /// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
+    pub status: Option<U64>,
+    /// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
+    pub root: Option<H256>,
+    /// Logs bloom
+    #[serde(rename = "logsBloom")]
+    pub logs_bloom: Bloom,
+    /// Transaction type, Some(1) for AccessList transaction, None for Legacy
+    #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
+    pub transaction_type: Option<U64>,
+    /// The price paid post-execution by the transaction (i.e. base fee + priority fee).
+    /// Both fields in 1559-style transactions are *maximums* (max fee + max priority fee), the amount
+    /// that's actually paid by users can only be determined post-execution
+    #[serde(
+        rename = "effectiveGasPrice",
+        default,
+        skip_serializing_if = "Option::is_none"
+    )]
+    pub effective_gas_price: Option<U256>,
+}
+
+#[cfg(test)]
+#[cfg(not(feature = "celo"))]
+mod tests {
+    use crate::types::transaction::eip2930::AccessListItem;
+
+    use super::*;
+
+    #[test]
+    fn decode_transaction_response() {
+        let _res: Transaction = serde_json::from_str(
+            r#"{
+    "blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2",
+    "blockNumber":"0x5daf3b",
+    "from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d",
+    "gas":"0xc350",
+    "gasPrice":"0x4a817c800",
+    "hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
+    "input":"0x68656c6c6f21",
+    "nonce":"0x15",
+    "to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
+    "transactionIndex":"0x41",
+    "value":"0xf3dbb76162000",
+    "v":"0x25",
+    "r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea",
+    "s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c"
+  }"#,
+        )
+        .unwrap();
+
+        let _res: Transaction = serde_json::from_str(
+            r#"{
+            "hash":"0xdd79ab0f996150aa3c9f135bbb9272cf0dedb830fafcbbf0c06020503565c44f",
+            "nonce":"0xe",
+            "blockHash":"0xef3fe1f532c3d8783a6257619bc123e9453aa8d6614e4cdb4cc8b9e1ed861404",
+            "blockNumber":"0xf",
+            "transactionIndex":"0x0",
+            "from":"0x1b67b03cdccfae10a2d80e52d3d026dbe2960ad0",
+            "to":"0x986ee0c8b91a58e490ee59718cca41056cf55f24",
+            "value":"0x2710",
+            "gas":"0x5208",
+            "gasPrice":"0x186a0",
+            "input":"0x",
+            "v":"0x25",
+            "r":"0x75188beb2f601bb8cf52ef89f92a6ba2bb7edcf8e3ccde90548cc99cbea30b1e",
+            "s":"0xc0559a540f16d031f3404d5df2bb258084eee56ed1193d8b534bb6affdb3c2c"
+    }"#,
+        )
+        .unwrap();
+    }
+
+    #[test]
+    fn decode_london_receipt() {
+        let receipt: TransactionReceipt = serde_json::from_value(serde_json::json!({"blockHash":"0x55ae43d3511e327dc532855510d110676d340aa1bbba369b4b98896d86559586","blockNumber":"0xa3d322","contractAddress":null,"cumulativeGasUsed":"0x207a5b","effectiveGasPrice":"0x3b9aca07","from":"0x541d6a0e9ca9e7a083e41e2e178eef9f22d7492e","gasUsed":"0x6a40","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x8210357f377e901f18e45294e86a2a32215cc3c9","transactionHash":"0x824384376c5972498c6fcafe71fd8cad1689f64e7d5e270d025a898638c0c34d","transactionIndex":"0xd","type":"0x2"})).unwrap();
+        assert_eq!(receipt.transaction_type.unwrap().as_u64(), 2);
+        assert_eq!(receipt.effective_gas_price.unwrap().as_u64(), 0x3b9aca07);
+    }
+
+    #[test]
+    fn decode_london_tx() {
+        let tx: Transaction = serde_json::from_value(serde_json::json!({"accessList":[{"address":"0x8ba1f109551bd432803012645ac136ddd64dba72","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000042"]}],"blockHash":"0x55ae43d3511e327dc532855510d110676d340aa1bbba369b4b98896d86559586","blockNumber":"0xa3d322","chainId":"0x3","from":"0x541d6a0e9ca9e7a083e41e2e178eef9f22d7492e","gas":"0x6a40","gasPrice":"0x3b9aca07","hash":"0x824384376c5972498c6fcafe71fd8cad1689f64e7d5e270d025a898638c0c34d","input":"0x","maxFeePerGas":"0x3b9aca0e","maxPriorityFeePerGas":"0x3b9aca00","nonce":"0x2","r":"0xf13b5088108f783f4b6048d4be456971118aabfb88be96bb541d734b6c2b20dc","s":"0x13fb7eb25a7d5df42a176cd4c6a086e19163ed7cd8ffba015f939d24f66bc17a","to":"0x8210357f377e901f18e45294e86a2a32215cc3c9","transactionIndex":"0xd","type":"0x2","v":"0x1","value":"0x7b"})).unwrap();
+        assert_eq!(tx.transaction_type.unwrap().as_u64(), 2);
+        let lst = AccessList(vec![AccessListItem {
+            address: "0x8ba1f109551bd432803012645ac136ddd64dba72"
+                .parse()
+                .unwrap(),
+            storage_keys: vec![
+                "0x0000000000000000000000000000000000000000000000000000000000000000"
+                    .parse()
+                    .unwrap(),
+                "0x0000000000000000000000000000000000000000000000000000000000000042"
+                    .parse()
+                    .unwrap(),
+            ],
+        }]);
+        assert_eq!(tx.access_list.unwrap(), lst);
+        assert_eq!(tx.max_fee_per_gas.unwrap().as_u64(), 0x3b9aca0e);
+        assert_eq!(tx.max_priority_fee_per_gas.unwrap().as_u64(), 0x3b9aca00);
+    }
+}
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 7bd5d272..1dd5f088 100644
--- a/ethers-middleware/src/signer.rs
+++ b/ethers-middleware/src/signer.rs
@@ -1,15 +1,10 @@
-use ethers_core::{
-    types::{
-        Address, BlockId, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest, U256,
-    },
-    utils::keccak256,
+use ethers_core::types::{
+    transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Signature,
 };
-use ethers_providers::{FromErr, Middleware, PendingTransaction};
+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)]
@@ -120,90 +115,19 @@ where
         }
     }
 
+    /// Signs and returns the RLP encoding of the signed transaction
     async fn sign_transaction(
         &self,
-        tx: TransactionRequest,
-    ) -> Result<Transaction, SignerMiddlewareError<M, S>> {
-        // The nonce, gas and gasprice fields must already be populated
-        let nonce = tx.nonce.ok_or(SignerMiddlewareError::NonceMissing)?;
-        let gas_price = tx.gas_price.ok_or(SignerMiddlewareError::GasPriceMissing)?;
-        let gas = tx.gas.ok_or(SignerMiddlewareError::GasMissing)?;
-
-        // Can't sign a transaction from a different address
-        if tx.from.is_some() && tx.from != Some(self.address()) {
-            return Err(SignerMiddlewareError::WrongSigner);
-        }
-
+        tx: TypedTransaction,
+    ) -> Result<Bytes, SignerMiddlewareError<M, S>> {
         let signature = self
             .signer
             .sign_transaction(&tx)
             .await
             .map_err(SignerMiddlewareError::SignerError)?;
 
-        // Get the actual transaction hash
-        let rlp = tx.rlp_signed(&signature);
-        let hash = keccak256(&rlp.as_ref());
-
-        // This function should not be called with ENS names
-        let to = tx.to.map(|to| match to {
-            NameOrAddress::Address(inner) => inner,
-            NameOrAddress::Name(_) => {
-                panic!("Expected `to` to be an Ethereum Address, not an ENS name")
-            }
-        });
-
-        Ok(Transaction {
-            hash: hash.into(),
-            nonce,
-            from: self.address(),
-            to,
-            value: tx.value.unwrap_or_default(),
-            gas_price,
-            gas,
-            input: tx.data.unwrap_or_default(),
-            v: signature.v.into(),
-            r: U256::from_big_endian(signature.r.as_bytes()),
-            s: U256::from_big_endian(signature.s.as_bytes()),
-
-            // Leave these empty as they're only used for included transactions
-            block_hash: None,
-            block_number: None,
-            transaction_index: None,
-
-            // Celo support
-            #[cfg(feature = "celo")]
-            fee_currency: tx.fee_currency,
-            #[cfg(feature = "celo")]
-            gateway_fee: tx.gateway_fee,
-            #[cfg(feature = "celo")]
-            gateway_fee_recipient: tx.gateway_fee_recipient,
-        })
-    }
-
-    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(())
+        // Return the raw rlp-encoded signed transaction
+        Ok(tx.rlp_signed(self.signer.chain_id(), &signature))
     }
 
     /// Returns the client's address
@@ -242,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)
@@ -264,25 +221,13 @@ 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?;
 
         // Submit the raw transaction
         self.inner
-            .send_raw_transaction(&signed_tx)
+            .send_raw_transaction(signed_tx)
             .await
             .map_err(SignerMiddlewareError::MiddlewareError)
     }
@@ -301,22 +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::{
+        types::TransactionRequest,
+        utils::{self, keccak256, Ganache},
+    };
     use std::convert::TryFrom;
 
     #[tokio::test]
@@ -336,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();
@@ -349,51 +287,60 @@ mod tests {
         let tx = client.sign_transaction(tx).await.unwrap();
 
         assert_eq!(
-            tx.hash,
-            "de8db924885b0803d2edc335f745b2b8750c8848744905684c20b987443a9593"
-                .parse()
+            keccak256(&tx)[..],
+            hex::decode("de8db924885b0803d2edc335f745b2b8750c8848744905684c20b987443a9593")
                 .unwrap()
         );
 
         let expected_rlp = Bytes::from(hex::decode("f869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68").unwrap());
-        assert_eq!(tx.rlp(), expected_rlp);
+        assert_eq!(tx, expected_rlp);
     }
 
     #[tokio::test]
     async fn handles_tx_from_field() {
-        use ethers_core::types::Address;
-
-        // new SignerMiddleware
-        let provider = Provider::try_from("http://localhost:8545").unwrap();
+        let ganache = Ganache::new().spawn();
+        let acc = ganache.addresses()[0];
+        let provider = Provider::try_from(ganache.endpoint()).unwrap();
         let key = LocalWallet::new(&mut rand::thread_rng()).with_chain_id(1u32);
-        let client = SignerMiddleware::new(provider, key);
-
-        // an address that is not the signer address
-        let other = "0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4"
-            .parse::<Address>()
+        provider
+            .send_transaction(
+                TransactionRequest::pay(key.address(), utils::parse_ether(1u64).unwrap()).from(acc),
+                None,
+            )
+            .await
             .unwrap();
+        let client = SignerMiddleware::new(provider, key);
 
-        let request = TransactionRequest::new().nonce(0).gas_price(0).gas(0);
+        let request = TransactionRequest::new();
 
         // signing a TransactionRequest with a from field of None should yield
         // a signed transaction from the signer address
         let request_from_none = request.clone();
-        let signing_result = client.sign_transaction(request_from_none).await;
-
-        assert_eq!(signing_result.unwrap().from, client.address());
+        let hash = *client
+            .send_transaction(request_from_none, None)
+            .await
+            .unwrap();
+        let tx = client.get_transaction(hash).await.unwrap().unwrap();
+        assert_eq!(tx.from, client.address());
 
         // signing a TransactionRequest with the signer as the from address
         // should yield a signed transaction from the signer
         let request_from_signer = request.clone().from(client.address());
-        let signing_result = client.sign_transaction(request_from_signer.clone()).await;
-
-        assert_eq!(signing_result.unwrap().from, client.address());
+        let hash = *client
+            .send_transaction(request_from_signer, None)
+            .await
+            .unwrap();
+        let tx = client.get_transaction(hash).await.unwrap().unwrap();
+        assert_eq!(tx.from, client.address());
 
         // signing a TransactionRequest with a from address that is not the
-        // signer should result in a WrongSigner error
-        let request_from_other = request.from(other);
-        let signing_result = client.sign_transaction(request_from_other.clone()).await;
-
-        assert!(signing_result.is_err());
+        // signer should result in the default ganache account being used
+        let request_from_other = request.from(acc);
+        let hash = *client
+            .send_transaction(request_from_other, None)
+            .await
+            .unwrap();
+        let tx = client.get_transaction(hash).await.unwrap().unwrap();
+        assert_eq!(tx.from, acc);
     }
 }
diff --git a/ethers-middleware/src/transformer/ds_proxy/mod.rs b/ethers-middleware/src/transformer/ds_proxy/mod.rs
index df75aa0c..3e1dfa10 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;
 
@@ -107,6 +109,7 @@ impl DsProxy {
         let ds_proxy_factory = DsProxyFactory::new(factory, client);
         let tx_receipt = ds_proxy_factory
             .build(owner)
+            .legacy()
             .send()
             .await?
             .await
@@ -178,29 +181,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 628dfeae..c09d494f 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,13 +195,98 @@ 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());
+                };
+
+                if inner.tx.from.is_none() {
+                    inner.tx.from = self.default_sender();
+                }
+
+                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());
+                };
+
+                if inner.from.is_none() {
+                    inner.from = self.default_sender();
+                }
+
+                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()
@@ -242,13 +340,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)
@@ -289,6 +387,16 @@ pub trait Middleware: Sync + Send + Debug {
             .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)
     }
@@ -299,7 +407,7 @@ pub trait Middleware: Sync + Send + Debug {
 
     async fn send_raw_transaction<'a>(
         &'a self,
-        tx: &Transaction,
+        tx: Bytes,
     ) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> {
         self.inner()
             .send_raw_transaction(tx)
@@ -413,9 +521,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> {
@@ -560,6 +668,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 37ddbc58..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,
 };
@@ -50,12 +51,16 @@ use tracing_futures::Instrument;
 /// # }
 /// ```
 #[derive(Clone, Debug)]
-// TODO: Convert to proper struct
-pub struct Provider<P>(P, Option<Address>, Option<Duration>, Option<Address>);
+pub struct Provider<P> {
+    inner: P,
+    ens: Option<Address>,
+    interval: Option<Duration>,
+    from: Option<Address>,
+}
 
 impl<P> AsRef<P> for Provider<P> {
     fn as_ref(&self) -> &P {
-        &self.0
+        &self.inner
     }
 }
 
@@ -103,11 +108,16 @@ pub enum FilterKind<'a> {
 impl<P: JsonRpcClient> Provider<P> {
     /// Instantiate a new provider with a backend.
     pub fn new(provider: P) -> Self {
-        Self(provider, None, None, None)
+        Self {
+            inner: provider,
+            ens: None,
+            interval: None,
+            from: None,
+        }
     }
 
     pub fn with_sender(mut self, address: impl Into<Address>) -> Self {
-        self.3 = Some(address.into());
+        self.from = Some(address.into());
         self
     }
 
@@ -121,7 +131,11 @@ impl<P: JsonRpcClient> Provider<P> {
         // https://docs.rs/tracing/0.1.22/tracing/span/struct.Span.html#in-asynchronous-code
         let res = async move {
             trace!("tx");
-            let res: R = self.0.request(method, params).await.map_err(Into::into)?;
+            let res: R = self
+                .inner
+                .request(method, params)
+                .await
+                .map_err(Into::into)?;
             trace!(rx = ?serde_json::to_string(&res)?);
             Ok::<_, ProviderError>(res)
         }
@@ -179,10 +193,19 @@ 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
 
+    /// Returns the current client version using the `web3_clientVersion` RPC.
+    async fn client_version(&self) -> Result<String, Self::Error> {
+        self.request("web3_clientVersion", ()).await
+    }
+
     /// Gets the latest block number via the `eth_BlockNumber` API
     async fn get_block_number(&self) -> Result<U64, ProviderError> {
         self.request("eth_blockNumber", ()).await
@@ -222,6 +245,17 @@ 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,
+    ) -> Result<Vec<TransactionReceipt>, Self::Error> {
+        self.request("eth_getBlockReceipts", [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
@@ -278,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);
@@ -289,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.3;
-        }
-
-        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()))
@@ -325,9 +355,9 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
     /// This will consume gas from the account that signed the transaction.
     async fn send_raw_transaction<'a>(
         &'a self,
-        tx: &Transaction,
+        tx: Bytes,
     ) -> Result<PendingTransaction<'a, P>, ProviderError> {
-        let rlp = utils::serialize(&tx.rlp());
+        let rlp = utils::serialize(&tx);
         let tx_hash = self.request("eth_sendRawTransaction", [rlp]).await?;
         Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval()))
     }
@@ -335,7 +365,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
     /// 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.3 {
+        match self.from {
             Some(sender) => self.sign(vec![], &sender).await.is_ok(),
             None => false,
         }
@@ -528,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);
@@ -665,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> {
@@ -675,12 +722,12 @@ impl<P: JsonRpcClient> Provider<P> {
         selector: Selector,
     ) -> Result<T, ProviderError> {
         // Get the ENS address, prioritize the local override variable
-        let ens_addr = self.1.unwrap_or(ens::ENS_ADDRESS);
+        let ens_addr = self.ens.unwrap_or(ens::ENS_ADDRESS);
 
         // 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);
@@ -690,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))
@@ -700,7 +750,7 @@ impl<P: JsonRpcClient> Provider<P> {
     /// ganache-only function for mining empty blocks
     pub async fn mine(&self, num_blocks: usize) -> Result<(), ProviderError> {
         for _ in 0..num_blocks {
-            self.0
+            self.inner
                 .request::<_, U256>("evm_mine", None::<()>)
                 .await
                 .map_err(Into::into)?;
@@ -710,21 +760,21 @@ impl<P: JsonRpcClient> Provider<P> {
 
     /// Sets the ENS Address (default: mainnet)
     pub fn ens<T: Into<Address>>(mut self, ens: T) -> Self {
-        self.1 = Some(ens.into());
+        self.ens = Some(ens.into());
         self
     }
 
     /// Sets the default polling interval for event filters and pending transactions
     /// (default: 7 seconds)
     pub fn interval<T: Into<Duration>>(mut self, interval: T) -> Self {
-        self.2 = Some(interval.into());
+        self.interval = Some(interval.into());
         self
     }
 
     /// Gets the polling interval which the provider currently uses for event filters
     /// and pending transactions (default: 7 seconds)
     pub fn get_interval(&self) -> Duration {
-        self.2.unwrap_or(DEFAULT_POLL_INTERVAL)
+        self.interval.unwrap_or(DEFAULT_POLL_INTERVAL)
     }
 }
 
@@ -791,12 +841,7 @@ impl TryFrom<&str> for Provider<HttpProvider> {
     type Error = ParseError;
 
     fn try_from(src: &str) -> Result<Self, Self::Error> {
-        Ok(Provider(
-            HttpProvider::new(Url::parse(src)?),
-            None,
-            None,
-            None,
-        ))
+        Ok(Provider::new(HttpProvider::new(Url::parse(src)?)))
     }
 }
 
@@ -861,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;
 
@@ -996,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/Cargo.toml b/ethers-signers/Cargo.toml
index 775a19e8..420155ea 100644
--- a/ethers-signers/Cargo.toml
+++ b/ethers-signers/Cargo.toml
@@ -18,7 +18,7 @@ ethers-core = { version = "0.4.0", path = "../ethers-core" }
 thiserror = { version = "1.0.24", default-features = false }
 coins-bip32 = "0.3.0"
 coins-bip39 = "0.3.0"
-coins-ledger = { version = "0.1.0", default-features = false, optional = true }
+coins-ledger = { version = "0.3.0", default-features = false, optional = true }
 eth-keystore = { version = "0.3.0" }
 hex = { version = "0.4.3", default-features = false, features = ["std"] }
 async-trait = { version = "0.1.50", default-features = false }
diff --git a/ethers-signers/src/aws/mod.rs b/ethers-signers/src/aws/mod.rs
index a58cce2a..8cc668d7 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::{transaction::eip2718::TypedTransaction, Address, Signature as EthSig, H256},
     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 5252e3f7..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?;
@@ -67,10 +67,10 @@ pub use yubihsm;
 mod aws;
 
 #[cfg(feature = "aws")]
-pub use aws::AwsSigner;
+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-contract/tests/common/mod.rs b/ethers-contract/tests/common/mod.rs
index 8404d038..83386852 100644
--- a/ethers-contract/tests/common/mod.rs
+++ b/ethers-contract/tests/common/mod.rs
@@ -50,6 +50,7 @@ pub async fn deploy<M: Middleware>(client: Arc<M>, abi: Abi, bytecode: Bytes) ->
     factory
         .deploy("initial value".to_string())
         .unwrap()
+        .legacy()
         .send()
         .await
         .unwrap()
diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs
index 773bb868..67f04835 100644
--- a/ethers-contract/tests/contract.rs
+++ b/ethers-contract/tests/contract.rs
@@ -32,7 +32,10 @@ mod eth_tests {
         // `send` consumes the deployer so it must be cloned for later re-use
         // (practically it's not expected that you'll need to deploy multiple instances of
         // the _same_ deployer, so it's fine to clone here from a dev UX vs perf tradeoff)
-        let deployer = factory.deploy("initial value".to_string()).unwrap();
+        let deployer = factory
+            .deploy("initial value".to_string())
+            .unwrap()
+            .legacy();
         let contract = deployer.clone().send().await.unwrap();
 
         let get_value = contract.method::<_, String>("getValue", ()).unwrap();
@@ -51,6 +54,7 @@ mod eth_tests {
             .unwrap();
         let calldata = contract_call.calldata().unwrap();
         let gas_estimate = contract_call.estimate_gas().await.unwrap();
+        let contract_call = contract_call.legacy();
         let pending_tx = contract_call.send().await.unwrap();
         let tx = client.get_transaction(*pending_tx).await.unwrap().unwrap();
         let tx_receipt = pending_tx.await.unwrap().unwrap();
@@ -82,6 +86,7 @@ mod eth_tests {
         let _tx_hash = contract
             .method::<_, H256>("setValues", ("hi".to_owned(), "bye".to_owned()))
             .unwrap()
+            .legacy()
             .send()
             .await
             .unwrap()
@@ -99,7 +104,8 @@ mod eth_tests {
         // make a call with `client`
         let func = contract
             .method::<_, H256>("setValue", "hi".to_owned())
-            .unwrap();
+            .unwrap()
+            .legacy();
         let tx = func.send().await.unwrap();
         let _receipt = tx.await.unwrap();
 
@@ -171,6 +177,7 @@ mod eth_tests {
         let value = contract
             .method::<_, String>("getValue", ())
             .unwrap()
+            .legacy()
             .call()
             .await
             .unwrap();
@@ -180,6 +187,7 @@ mod eth_tests {
         let _tx_hash = *contract
             .method::<_, H256>("setValue", "hi".to_owned())
             .unwrap()
+            .legacy()
             .send()
             .await
             .unwrap();
@@ -188,6 +196,7 @@ mod eth_tests {
         let value = contract
             .method::<_, String>("getValue", ())
             .unwrap()
+            .legacy()
             .call()
             .await
             .unwrap();
@@ -197,6 +206,7 @@ mod eth_tests {
         let value = contract
             .method::<_, String>("getValue", ())
             .unwrap()
+            .legacy()
             .block(BlockId::Number(deployed_block.into()))
             .call()
             .await
@@ -302,7 +312,8 @@ mod eth_tests {
         for i in 0..num_calls {
             let call = contract
                 .method::<_, H256>("setValue", i.to_string())
-                .unwrap();
+                .unwrap()
+                .legacy();
             let pending_tx = call.send().await.unwrap();
             let _receipt = pending_tx.await.unwrap();
         }
@@ -341,7 +352,6 @@ mod eth_tests {
         // get the first account
         let deployer = provider.get_accounts().await.unwrap()[0];
         let client = Arc::new(provider.with_sender(deployer));
-        dbg!(deployer);
 
         let contract = deploy(client, abi, bytecode).await;
 
@@ -396,18 +406,26 @@ mod eth_tests {
         let not_so_simple_factory =
             ContractFactory::new(not_so_simple_abi, not_so_simple_bytecode, client3.clone());
 
-        let multicall_contract = multicall_factory.deploy(()).unwrap().send().await.unwrap();
+        let multicall_contract = multicall_factory
+            .deploy(())
+            .unwrap()
+            .legacy()
+            .send()
+            .await
+            .unwrap();
         let addr = multicall_contract.address();
 
         let simple_contract = simple_factory
             .deploy("the first one".to_string())
             .unwrap()
+            .legacy()
             .send()
             .await
             .unwrap();
         let not_so_simple_contract = not_so_simple_factory
             .deploy("the second one".to_string())
             .unwrap()
+            .legacy()
             .send()
             .await
             .unwrap();
@@ -417,6 +435,7 @@ mod eth_tests {
             .connect(client2.clone())
             .method::<_, H256>("setValue", "reset first".to_owned())
             .unwrap()
+            .legacy()
             .send()
             .await
             .unwrap();
@@ -424,6 +443,7 @@ mod eth_tests {
             .connect(client3.clone())
             .method::<_, H256>("setValue", "reset second".to_owned())
             .unwrap()
+            .legacy()
             .send()
             .await
             .unwrap();
@@ -479,7 +499,7 @@ mod eth_tests {
             .add_call(broadcast2);
 
         // broadcast the transaction and wait for it to be mined
-        let tx_hash = multicall_send.send().await.unwrap();
+        let tx_hash = multicall_send.legacy().send().await.unwrap();
         let _tx_receipt = PendingTransaction::new(tx_hash, client.provider())
             .await
             .unwrap();
@@ -544,7 +564,10 @@ mod celo_tests {
         let client = Arc::new(client);
 
         let factory = ContractFactory::new(abi, bytecode, client);
-        let deployer = factory.deploy("initial value".to_string()).unwrap();
+        let deployer = factory
+            .deploy("initial value".to_string())
+            .unwrap()
+            .legacy();
         let contract = deployer.block(BlockNumber::Pending).send().await.unwrap();
 
         let value: String = contract
diff --git a/ethers-middleware/tests/gas_oracle.rs b/ethers-middleware/tests/gas_oracle.rs
index 8366abfd..9974d9ad 100644
--- a/ethers-middleware/tests/gas_oracle.rs
+++ b/ethers-middleware/tests/gas_oracle.rs
@@ -15,7 +15,7 @@ async fn using_gas_oracle() {
     let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap();
 
     // assign a gas oracle to use
-    let gas_oracle = Etherchain::new().category(GasCategory::Fastest);
+    let gas_oracle = GasNow::new().category(GasCategory::Fastest);
     let expected_gas_price = gas_oracle.fetch().await.unwrap();
 
     let provider = GasOracleMiddleware::new(provider, gas_oracle);
@@ -32,6 +32,8 @@ 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);
@@ -58,6 +60,9 @@ 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-middleware/tests/transformer.rs b/ethers-middleware/tests/transformer.rs
index 630447e5..1a7addf2 100644
--- a/ethers-middleware/tests/transformer.rs
+++ b/ethers-middleware/tests/transformer.rs
@@ -44,7 +44,8 @@ async fn ds_proxy_transformer() {
         contract.bytecode.clone(),
         Arc::clone(&provider),
     );
-    let ds_proxy_factory = factory.deploy(()).unwrap().send().await.unwrap();
+    let ds_proxy_factory = factory.deploy(()).unwrap().legacy();
+    let ds_proxy_factory = ds_proxy_factory.send().await.unwrap();
 
     // deploy a new DsProxy contract.
     let ds_proxy = DsProxy::build::<HttpWallet, Arc<HttpWallet>>(
@@ -68,7 +69,8 @@ async fn ds_proxy_transformer() {
         contract.bytecode.clone(),
         Arc::clone(&provider),
     );
-    let simple_storage = factory.deploy(()).unwrap().send().await.unwrap();
+    let deployer = factory.deploy(()).unwrap().legacy();
+    let simple_storage = deployer.send().await.unwrap();
 
     // instantiate a new transformer middleware.
     let provider = TransformerMiddleware::new(signer_middleware, ds_proxy.clone());
@@ -131,7 +133,8 @@ async fn ds_proxy_code() {
         contract.bytecode.clone(),
         Arc::clone(&provider),
     );
-    let ds_proxy_factory = factory.deploy(()).unwrap().send().await.unwrap();
+    let ds_proxy_factory = factory.deploy(()).unwrap().legacy();
+    let ds_proxy_factory = ds_proxy_factory.send().await.unwrap();
 
     // deploy a new DsProxy contract.
     let ds_proxy = DsProxy::build::<HttpWallet, Arc<HttpWallet>>(
@@ -164,6 +167,7 @@ async fn ds_proxy_code() {
             calldata,
         )
         .expect("could not construct DSProxy contract call")
+        .legacy()
         .send()
         .await
         .unwrap();
diff --git a/ethers-providers/tests/provider.rs b/ethers-providers/tests/provider.rs
index 08a586e0..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,
@@ -40,6 +41,21 @@ mod eth_tests {
             .is_none());
     }
 
+    #[tokio::test]
+    async fn client_version() {
+        let provider = Provider::<Http>::try_from(
+            "https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
+        )
+        .unwrap();
+
+        // e.g., Geth/v1.10.6-omnibus-1af33248/linux-amd64/go1.16.6
+        assert!(provider
+            .client_version()
+            .await
+            .expect("Could not make web3_clientVersion call to provider")
+            .starts_with("Geth/v"));
+    }
+
     // Without TLS this would error with "TLS Support not compiled in"
     #[tokio::test]
     async fn ssl_websocket() {
@@ -133,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")]