Repository: gakonst/ethers-rs
Pull request #353 (2021-07-30 09:24:11)

feat: eip2930/1559 response type adjustments (part 1)
* Refactors the tx types to its own subdirectory, to prepare for adding more tx request types / serialization helpers
* Adds `base_fee_per_gas` to `Block`
* Adds `access_list`, `transaction_type`, `max_priority_fee_per_gas`, `max_fee_per_gas` to `Transaction`
* Adds `transaction_type`, `effective_gas_price` to `TransactionReceipt`
* `SignerMiddleware::sign_transaction` now returns an rlp encoded signed transaction instead of a `Transaction`
* `Middleware::send_raw_transaction` now receives a signed rlp transaction instead of a `Transaction`


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index b75ad1da..9d31619f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -66,6 +66,15 @@ dependencies = [
  "backtrace",
 ]
 
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "anyhow"
 version = "1.0.41"
@@ -319,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"
@@ -498,6 +538,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
 [[package]]
 name = "crunchy"
 version = "0.2.2"
@@ -595,6 +644,27 @@ dependencies = [
  "generic-array 0.14.4",
 ]
 
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if 1.0.0",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
 [[package]]
 name = "ecdsa"
 version = "0.12.3"
@@ -769,8 +839,10 @@ version = "0.4.0"
 dependencies = [
  "Inflector",
  "anyhow",
+ "cargo_metadata",
  "ethers-core",
  "hex",
+ "once_cell",
  "proc-macro2",
  "quote",
  "reqwest",
@@ -803,7 +875,6 @@ dependencies = [
  "elliptic-curve",
  "ethabi",
  "ethers",
- "funty",
  "futures-util",
  "generic-array 0.14.4",
  "glob",
@@ -813,6 +884,7 @@ dependencies = [
  "once_cell",
  "rand 0.8.4",
  "rlp",
+ "rlp-derive",
  "serde",
  "serde_json",
  "thiserror",
@@ -888,10 +960,16 @@ dependencies = [
  "futures-util",
  "hex",
  "rand 0.8.4",
+ "rusoto_core",
+ "rusoto_kms",
  "sha2 0.9.5",
+ "spki",
  "tempfile",
  "thiserror",
  "tokio",
+ "tracing",
+ "tracing-futures",
+ "tracing-subscriber",
  "yubihsm",
 ]
 
@@ -1417,12 +1495,32 @@ dependencies = [
  "cfg-if 1.0.0",
 ]
 
+[[package]]
+name = "matchers"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
+dependencies = [
+ "regex-automata",
+]
+
 [[package]]
 name = "matches"
 version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
 
+[[package]]
+name = "md-5"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
+dependencies = [
+ "block-buffer 0.9.0",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
 [[package]]
 name = "memchr"
 version = "2.4.0"
@@ -1906,6 +2004,16 @@ dependencies = [
  "bitflags",
 ]
 
+[[package]]
+name = "redox_users"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
+dependencies = [
+ "getrandom 0.2.3",
+ "redox_syscall",
+]
+
 [[package]]
 name = "regex"
 version = "1.5.4"
@@ -1917,6 +2025,15 @@ dependencies = [
  "regex-syntax",
 ]
 
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax",
+]
+
 [[package]]
 name = "regex-syntax"
 version = "0.6.25"
@@ -2007,6 +2124,17 @@ dependencies = [
  "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"
@@ -2017,6 +2145,89 @@ dependencies = [
  "libusb1-sys",
 ]
 
+[[package]]
+name = "rusoto_core"
+version = "0.47.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b4f000e8934c1b4f70adde180056812e7ea6b1a247952db8ee98c94cd3116cc"
+dependencies = [
+ "async-trait",
+ "base64 0.13.0",
+ "bytes",
+ "crc32fast",
+ "futures",
+ "http",
+ "hyper",
+ "hyper-tls",
+ "lazy_static",
+ "log",
+ "rusoto_credential",
+ "rusoto_signature",
+ "rustc_version",
+ "serde",
+ "serde_json",
+ "tokio",
+ "xml-rs",
+]
+
+[[package]]
+name = "rusoto_credential"
+version = "0.47.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a46b67db7bb66f5541e44db22b0a02fed59c9603e146db3a9e633272d3bac2f"
+dependencies = [
+ "async-trait",
+ "chrono",
+ "dirs-next",
+ "futures",
+ "hyper",
+ "serde",
+ "serde_json",
+ "shlex",
+ "tokio",
+ "zeroize",
+]
+
+[[package]]
+name = "rusoto_kms"
+version = "0.47.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7892cd2cca7644d33bd6fafdb2236efd3659162fd7b73ca68d3877f0528399c"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures",
+ "rusoto_core",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "rusoto_signature"
+version = "0.47.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6264e93384b90a747758bcc82079711eacf2e755c3a8b5091687b5349d870bcc"
+dependencies = [
+ "base64 0.13.0",
+ "bytes",
+ "chrono",
+ "digest 0.9.0",
+ "futures",
+ "hex",
+ "hmac",
+ "http",
+ "hyper",
+ "log",
+ "md-5",
+ "percent-encoding",
+ "pin-project-lite",
+ "rusoto_credential",
+ "rustc_version",
+ "serde",
+ "sha2 0.9.5",
+ "tokio",
+]
+
 [[package]]
 name = "rustc-demangle"
 version = "0.1.20"
@@ -2029,6 +2240,15 @@ version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
 
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
 [[package]]
 name = "rustls"
 version = "0.19.1"
@@ -2126,6 +2346,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "semver"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "serde"
 version = "1.0.126"
@@ -2229,6 +2458,30 @@ dependencies = [
  "opaque-debug 0.3.0",
 ]
 
+[[package]]
+name = "sharded-slab"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "signature"
 version = "1.3.1"
@@ -2258,6 +2511,12 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
 
+[[package]]
+name = "smallvec"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
+
 [[package]]
 name = "socket2"
 version = "0.4.0"
@@ -2358,6 +2617,15 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "thread_local"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
+dependencies = [
+ "once_cell",
+]
+
 [[package]]
 name = "time"
 version = "0.1.43"
@@ -2404,7 +2672,9 @@ dependencies = [
  "memchr",
  "mio",
  "num_cpus",
+ "once_cell",
  "pin-project-lite",
+ "signal-hook-registry",
  "tokio-macros",
  "winapi",
 ]
@@ -2497,9 +2767,21 @@ checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
 dependencies = [
  "cfg-if 1.0.0",
  "pin-project-lite",
+ "tracing-attributes",
  "tracing-core",
 ]
 
+[[package]]
+name = "tracing-attributes"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "tracing-core"
 version = "0.1.18"
@@ -2519,6 +2801,49 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "tracing-log"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-serde"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab69019741fca4d98be3c62d2b75254528b5432233fd8a4d2739fec20278de48"
+dependencies = [
+ "ansi_term",
+ "chrono",
+ "lazy_static",
+ "matchers",
+ "regex",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
+]
+
 [[package]]
 name = "try-lock"
 version = "0.2.3"
@@ -2798,6 +3123,12 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
 
+[[package]]
+name = "xml-rs"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
+
 [[package]]
 name = "yubihsm"
 version = "0.39.0"
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/event.rs b/ethers-contract/src/event.rs
index 38d8bb3d..2d06087c 100644
--- a/ethers-contract/src/event.rs
+++ b/ethers-contract/src/event.rs
@@ -1,8 +1,8 @@
-use crate::{stream::EventStream, ContractError, EthLogDecode};
+use crate::{log::LogMeta, stream::EventStream, ContractError, EthLogDecode};
 
 use ethers_core::{
     abi::{Detokenize, RawLog},
-    types::{Address, BlockNumber, Filter, Log, TxHash, ValueOrArray, H256, U256, U64},
+    types::{BlockNumber, Filter, Log, ValueOrArray, H256},
 };
 use ethers_providers::{FilterWatcher, Middleware, PubsubClient, SubscriptionStream};
 use std::borrow::Cow;
@@ -214,38 +214,3 @@ where
         .map_err(From::from)
     }
 }
-
-/// Metadata inside a log
-#[derive(Clone, Debug, PartialEq)]
-pub struct LogMeta {
-    /// Address from which this log originated
-    pub address: Address,
-
-    /// The block in which the log was emitted
-    pub block_number: U64,
-
-    /// The block hash in which the log was emitted
-    pub block_hash: H256,
-
-    /// The transaction hash in which the log was emitted
-    pub transaction_hash: TxHash,
-
-    /// Transactions index position log was created from
-    pub transaction_index: U64,
-
-    /// Log index position in the block
-    pub log_index: U256,
-}
-
-impl From<&Log> for LogMeta {
-    fn from(src: &Log) -> Self {
-        LogMeta {
-            address: src.address,
-            block_number: src.block_number.expect("should have a block number"),
-            block_hash: src.block_hash.expect("should have a block hash"),
-            transaction_hash: src.transaction_hash.expect("should have a tx hash"),
-            transaction_index: src.transaction_index.expect("should have a tx index"),
-            log_index: src.log_index.expect("should have a log index"),
-        }
-    }
-}
diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs
index 3d82029c..6d2c544d 100644
--- a/ethers-contract/src/lib.rs
+++ b/ethers-contract/src/lib.rs
@@ -26,10 +26,10 @@ mod factory;
 pub use factory::ContractFactory;
 
 mod event;
-pub use event::{EthEvent, LogMeta};
+pub use event::EthEvent;
 
 mod log;
-pub use log::{decode_logs, EthLogDecode};
+pub use log::{decode_logs, EthLogDecode, LogMeta};
 
 mod stream;
 
diff --git a/ethers-contract/src/log.rs b/ethers-contract/src/log.rs
index cb538649..08e81aaa 100644
--- a/ethers-contract/src/log.rs
+++ b/ethers-contract/src/log.rs
@@ -1,6 +1,7 @@
 //! Mod of types for ethereum logs
 use ethers_core::abi::Error;
 use ethers_core::abi::RawLog;
+use ethers_core::types::{Address, Log, TxHash, H256, U256, U64};
 
 /// A trait for types (events) that can be decoded from a `RawLog`
 pub trait EthLogDecode: Send + Sync {
@@ -14,3 +15,38 @@ pub trait EthLogDecode: Send + Sync {
 pub fn decode_logs<T: EthLogDecode>(logs: &[RawLog]) -> Result<Vec<T>, Error> {
     logs.iter().map(T::decode_log).collect()
 }
+
+/// Metadata inside a log
+#[derive(Clone, Debug, PartialEq)]
+pub struct LogMeta {
+    /// Address from which this log originated
+    pub address: Address,
+
+    /// The block in which the log was emitted
+    pub block_number: U64,
+
+    /// The block hash in which the log was emitted
+    pub block_hash: H256,
+
+    /// The transaction hash in which the log was emitted
+    pub transaction_hash: TxHash,
+
+    /// Transactions index position log was created from
+    pub transaction_index: U64,
+
+    /// Log index position in the block
+    pub log_index: U256,
+}
+
+impl From<&Log> for LogMeta {
+    fn from(src: &Log) -> Self {
+        LogMeta {
+            address: src.address,
+            block_number: src.block_number.expect("should have a block number"),
+            block_hash: src.block_hash.expect("should have a block hash"),
+            transaction_hash: src.transaction_hash.expect("should have a tx hash"),
+            transaction_index: src.transaction_index.expect("should have a tx index"),
+            log_index: src.log_index.expect("should have a log index"),
+        }
+    }
+}
diff --git a/ethers-contract/src/stream.rs b/ethers-contract/src/stream.rs
index b0385b0a..79d6bae3 100644
--- a/ethers-contract/src/stream.rs
+++ b/ethers-contract/src/stream.rs
@@ -1,3 +1,4 @@
+use crate::LogMeta;
 use ethers_core::types::{Log, U256};
 use futures_util::stream::{Stream, StreamExt};
 use pin_project::pin_project;
@@ -19,6 +20,12 @@ pub struct EventStream<'a, T, R, E> {
     parse: MapEvent<'a, R, E>,
 }
 
+impl<'a, T, R, E> EventStream<'a, T, R, E> {
+    pub fn with_meta(self) -> EventStreamMeta<'a, T, R, E> {
+        EventStreamMeta(self)
+    }
+}
+
 impl<'a, T, R, E> EventStream<'a, T, R, E> {
     pub fn new(id: U256, stream: T, parse: MapEvent<'a, R, E>) -> Self {
         Self { id, stream, parse }
@@ -39,3 +46,26 @@ where
         }
     }
 }
+
+#[pin_project]
+pub struct EventStreamMeta<'a, T, R, E>(pub EventStream<'a, T, R, E>);
+
+impl<'a, T, R, E> Stream for EventStreamMeta<'a, T, R, E>
+where
+    T: Stream<Item = Log> + Unpin,
+{
+    type Item = Result<(R, LogMeta), E>;
+
+    fn poll_next(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Option<Self::Item>> {
+        let this = self.project();
+        match futures_util::ready!(this.0.stream.poll_next_unpin(ctx)) {
+            Some(item) => {
+                let meta = LogMeta::from(&item);
+                let res = (this.0.parse)(item);
+                let res = res.map(|inner| (inner, meta));
+                Poll::Ready(Some(res))
+            }
+            None => Poll::Pending,
+        }
+    }
+}
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..597b7cb6 100644
--- a/ethers-core/src/types/mod.rs
+++ b/ethers-core/src/types/mod.rs
@@ -8,7 +8,8 @@ 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 use transaction::request::TransactionRequest;
+pub use transaction::response::{Transaction, TransactionReceipt};
 
 mod address_or_bytes;
 pub use address_or_bytes::AddressOrBytes;
diff --git a/ethers-core/src/types/transaction/eip2930.rs b/ethers-core/src/types/transaction/eip2930.rs
new file mode 100644
index 00000000..ae0c7419
--- /dev/null
+++ b/ethers-core/src/types/transaction/eip2930.rs
@@ -0,0 +1,24 @@
+use crate::types::{Address, H256};
+
+use rlp_derive::RlpEncodable;
+use serde::{Deserialize, Serialize};
+
+/// Access list
+#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, RlpEncodable)]
+pub struct AccessList(pub Vec<AccessListItem>);
+
+impl From<Vec<AccessListItem>> for AccessList {
+    fn from(src: Vec<AccessListItem>) -> AccessList {
+        AccessList(src)
+    }
+}
+
+/// Access list item
+#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, RlpEncodable)]
+#[serde(rename_all = "camelCase")]
+pub struct AccessListItem {
+    /// Accessed address
+    pub address: Address,
+    /// Accessed storage keys
+    pub storage_keys: Vec<H256>,
+}
diff --git a/ethers-core/src/types/transaction/mod.rs b/ethers-core/src/types/transaction/mod.rs
new file mode 100644
index 00000000..22f5e8c8
--- /dev/null
+++ b/ethers-core/src/types/transaction/mod.rs
@@ -0,0 +1,21 @@
+pub mod request;
+pub mod response;
+
+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(ref inner) = opt {
+        rlp.append(inner);
+    } else {
+        rlp.append(&"");
+    }
+}
diff --git a/ethers-core/src/types/transaction.rs b/ethers-core/src/types/transaction/request.rs
similarity index 50%
rename from ethers-core/src/types/transaction.rs
rename to ethers-core/src/types/transaction/request.rs
index d32f56a6..7dd1e1e2 100644
--- a/ethers-core/src/types/transaction.rs
+++ b/ethers-core/src/types/transaction/request.rs
@@ -1,21 +1,13 @@
 //! Transaction types
+use super::{rlp_opt, NUM_TX_FIELDS};
 use crate::{
-    types::{Address, Bloom, Bytes, Log, NameOrAddress, Signature, H256, U256, U64},
+    types::{Address, Bytes, 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 {
@@ -160,7 +152,7 @@ impl TransactionRequest {
         rlp.out().freeze().into()
     }
 
-    fn rlp_base(&self, rlp: &mut RlpStream) {
+    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);
@@ -206,163 +198,6 @@ impl TransactionRequest {
     }
 }
 
-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 {
@@ -385,47 +220,4 @@ mod tests {
         )
         .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/response.rs b/ethers-core/src/types/transaction/response.rs
new file mode 100644
index 00000000..85222d48
--- /dev/null
+++ b/ethers-core/src/types/transaction/response.rs
@@ -0,0 +1,290 @@
+//! 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(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/signer.rs b/ethers-middleware/src/signer.rs
index 7bd5d272..a7333884 100644
--- a/ethers-middleware/src/signer.rs
+++ b/ethers-middleware/src/signer.rs
@@ -1,9 +1,4 @@
-use ethers_core::{
-    types::{
-        Address, BlockId, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest, U256,
-    },
-    utils::keccak256,
-};
+use ethers_core::types::{Address, BlockId, Bytes, NameOrAddress, Signature, TransactionRequest};
 use ethers_providers::{FromErr, Middleware, PendingTransaction};
 use ethers_signers::Signer;
 
@@ -120,64 +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);
-        }
-
+    ) -> 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,
-        })
+        // Return the raw rlp-encoded signed transaction
+        Ok(tx.rlp_signed(&signature))
     }
 
     async fn fill_transaction(
@@ -282,7 +232,7 @@ where
 
         // Submit the raw transaction
         self.inner
-            .send_raw_transaction(&signed_tx)
+            .send_raw_transaction(signed_tx)
             .await
             .map_err(SignerMiddlewareError::MiddlewareError)
     }
@@ -317,6 +267,7 @@ where
 mod tests {
     use super::*;
     use ethers::{providers::Provider, signers::LocalWallet};
+    use ethers_core::utils::{self, keccak256, Ganache};
     use std::convert::TryFrom;
 
     #[tokio::test]
@@ -349,51 +300,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-providers/src/lib.rs b/ethers-providers/src/lib.rs
index 628dfeae..09f4817d 100644
--- a/ethers-providers/src/lib.rs
+++ b/ethers-providers/src/lib.rs
@@ -182,6 +182,10 @@ pub trait Middleware: Sync + Send + Debug {
         self.inner().provider()
     }
 
+    async fn client_version(&self) -> Result<String, Self::Error> {
+        self.inner().client_version().await.map_err(FromErr::from)
+    }
+
     async fn get_block_number(&self) -> Result<U64, Self::Error> {
         self.inner().get_block_number().await.map_err(FromErr::from)
     }
@@ -289,6 +293,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 +313,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)
diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs
index 37ddbc58..956216fd 100644
--- a/ethers-providers/src/provider.rs
+++ b/ethers-providers/src/provider.rs
@@ -50,12 +50,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 +107,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 +130,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)
         }
@@ -183,6 +196,11 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
     //
     // 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 +240,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
@@ -301,7 +330,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         _: Option<BlockId>,
     ) -> Result<PendingTransaction<'_, P>, ProviderError> {
         if tx.from.is_none() {
-            tx.from = self.3;
+            tx.from = self.from;
         }
 
         if tx.gas.is_none() {
@@ -325,9 +354,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 +364,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,
         }
@@ -675,7 +704,7 @@ 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
@@ -700,7 +729,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 +739,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 +820,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)?)))
     }
 }
 
diff --git a/ethers-signers/Cargo.toml b/ethers-signers/Cargo.toml
index d450727d..775a19e8 100644
--- a/ethers-signers/Cargo.toml
+++ b/ethers-signers/Cargo.toml
@@ -29,6 +29,13 @@ yubihsm = { version = "0.39.0", features = ["secp256k1", "http", "usb"], optiona
 futures-util = "0.3.16"
 futures-executor = "0.3.16"
 
+# aws
+rusoto_core = { version = "0.47.0", optional = true }
+rusoto_kms = { version = "0.47.0", optional = true }
+tracing = { version = "0.1.26", optional = true }
+tracing-futures = { version = "0.2.5", optional = true }
+spki = { version = "0.4.0", optional = true }
+
 [dev-dependencies]
 ethers = { version = "0.4.0", path = "../ethers" }
 yubihsm = { version = "0.39.0", features = ["secp256k1", "usb", "mockhsm"] }
@@ -36,7 +43,10 @@ yubihsm = { version = "0.39.0", features = ["secp256k1", "usb", "mockhsm"] }
 tempfile = "3.2.0"
 tokio = { version = "1.5", default-features = false, features = ["macros"] }
 
+tracing-subscriber = "0.2.19"
+
 [features]
 celo = ["ethers-core/celo"]
 ledger = ["coins-ledger"]
 yubi = ["yubihsm"]
+aws = ["rusoto_core", "rusoto_kms", "tracing", "tracing-futures", "spki"]
diff --git a/ethers-signers/src/aws/mod.rs b/ethers-signers/src/aws/mod.rs
new file mode 100644
index 00000000..a58cce2a
--- /dev/null
+++ b/ethers-signers/src/aws/mod.rs
@@ -0,0 +1,314 @@
+//! AWS KMS-based Signer
+
+use ethers_core::{
+    k256::ecdsa::{Error as K256Error, Signature as KSig, VerifyingKey},
+    types::{Address, Signature as EthSig, TransactionRequest, H256},
+    utils::hash_message,
+};
+use rusoto_core::RusotoError;
+use rusoto_kms::{
+    GetPublicKeyError, GetPublicKeyRequest, Kms, KmsClient, SignError, SignRequest, SignResponse,
+};
+use tracing::{debug, instrument, trace};
+
+mod utils;
+use utils::{apply_eip155, rsig_to_ethsig, verifying_key_to_address};
+
+/// An ethers Signer that uses keys held in Amazon AWS KMS.
+///
+/// The AWS Signer passes signing requests to the cloud service. AWS KMS keys
+/// are identified by a UUID, the `key_id`.
+///
+/// Because the public key is unknwon, we retrieve it on instantiation of the
+/// signer. This means that the new function is `async` and must be called
+/// within some runtime.
+///
+/// ```compile_fail
+/// use rusoto_core::Client;
+/// use rusoto_kms::{Kms, KmsClient};
+///
+/// user ethers_signers::Signer;
+///
+/// let client = Client::new_with(
+///     EnvironmentProvider::default(),
+///     HttpClient::new().unwrap()
+/// );
+/// let kms_client = KmsClient::new_with_client(client, Region::UsWest1);
+/// let key_id = "...";
+/// let chain_id = 1;
+///
+/// let signer = AwsSigner::new(kms_client, key_id, chain_id).await?;
+/// let sig = signer.sign_message(H256::zero()).await?;
+/// ```
+#[derive(Clone)]
+pub struct AwsSigner<'a> {
+    kms: &'a rusoto_kms::KmsClient,
+    chain_id: u64,
+    key_id: String,
+    pubkey: VerifyingKey,
+    address: Address,
+}
+
+impl<'a> std::fmt::Debug for AwsSigner<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("AwsSigner")
+            .field("key_id", &self.key_id)
+            .field("chain_id", &self.chain_id)
+            .field("pubkey", &hex::encode(self.pubkey.to_bytes()))
+            .field("address", &self.address)
+            .finish()
+    }
+}
+
+impl<'a> std::fmt::Display for AwsSigner<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "AwsSigner {{ address: {}, chain_id: {}, key_id: {} }}",
+            self.address, self.chain_id, self.key_id
+        )
+    }
+}
+
+/// Errors produced by the AwsSigner
+#[derive(thiserror::Error, Debug)]
+pub enum AwsSignerError {
+    #[error("{0}")]
+    SignError(#[from] RusotoError<SignError>),
+    #[error("{0}")]
+    GetPublicKeyError(#[from] RusotoError<GetPublicKeyError>),
+    #[error("{0}")]
+    K256(#[from] K256Error),
+    #[error("{0}")]
+    Spki(spki::der::Error),
+    #[error("{0}")]
+    Other(String),
+}
+
+impl From<String> for AwsSignerError {
+    fn from(s: String) -> Self {
+        Self::Other(s)
+    }
+}
+
+impl From<spki::der::Error> for AwsSignerError {
+    fn from(e: spki::der::Error) -> Self {
+        Self::Spki(e)
+    }
+}
+
+#[instrument(err, skip(kms, key_id), fields(key_id = %key_id.as_ref()))]
+async fn request_get_pubkey<T>(
+    kms: &KmsClient,
+    key_id: T,
+) -> Result<rusoto_kms::GetPublicKeyResponse, RusotoError<GetPublicKeyError>>
+where
+    T: AsRef<str>,
+{
+    debug!("Dispatching get_public_key");
+
+    let req = GetPublicKeyRequest {
+        grant_tokens: None,
+        key_id: key_id.as_ref().to_owned(),
+    };
+    trace!("{:?}", &req);
+    let resp = kms.get_public_key(req).await;
+    trace!("{:?}", &resp);
+    resp
+}
+
+#[instrument(err, skip(kms, digest, key_id), fields(digest = %hex::encode(&digest), key_id = %key_id.as_ref()))]
+async fn request_sign_digest<T>(
+    kms: &KmsClient,
+    key_id: T,
+    digest: [u8; 32],
+) -> Result<SignResponse, RusotoError<SignError>>
+where
+    T: AsRef<str>,
+{
+    debug!("Dispatching sign");
+    let req = SignRequest {
+        grant_tokens: None,
+        key_id: key_id.as_ref().to_owned(),
+        message: digest.to_vec().into(),
+        message_type: Some("DIGEST".to_owned()),
+        signing_algorithm: "ECDSA_SHA_256".to_owned(),
+    };
+    trace!("{:?}", &req);
+    let resp = kms.sign(req).await;
+    trace!("{:?}", &resp);
+    resp
+}
+
+impl<'a> AwsSigner<'a> {
+    /// Instantiate a new signer from an existing `KmsClient` and Key ID.
+    ///
+    /// This function retrieves the public key from AWS and calculates the
+    /// Etheruem address. It is therefore `async`.
+    #[instrument(err, skip(kms, key_id, chain_id), fields(key_id = %key_id.as_ref()))]
+    pub async fn new<T>(
+        kms: &'a KmsClient,
+        key_id: T,
+        chain_id: u64,
+    ) -> Result<AwsSigner<'a>, AwsSignerError>
+    where
+        T: AsRef<str>,
+    {
+        let pubkey = request_get_pubkey(kms, &key_id)
+            .await
+            .map(utils::decode_pubkey)??;
+        let address = verifying_key_to_address(&pubkey);
+
+        debug!(
+            "Instantiated AWS signer with pubkey 0x{} and address 0x{}",
+            hex::encode(&pubkey.to_bytes()),
+            hex::encode(&address)
+        );
+
+        Ok(Self {
+            kms,
+            chain_id,
+            key_id: key_id.as_ref().to_owned(),
+            pubkey,
+            address,
+        })
+    }
+
+    /// Fetch the pubkey associated with a key id
+    pub async fn get_pubkey_for_key<T>(&self, key_id: T) -> Result<VerifyingKey, AwsSignerError>
+    where
+        T: AsRef<str>,
+    {
+        Ok(request_get_pubkey(&self.kms, key_id)
+            .await
+            .map(utils::decode_pubkey)??)
+    }
+
+    /// Fetch the pubkey associated with this signer's key ID
+    pub async fn get_pubkey(&self) -> Result<VerifyingKey, AwsSignerError> {
+        self.get_pubkey_for_key(&self.key_id).await
+    }
+
+    /// Sign a digest with the key associated with a key id
+    pub async fn sign_digest_with_key<T>(
+        &self,
+        key_id: T,
+        digest: [u8; 32],
+    ) -> Result<KSig, AwsSignerError>
+    where
+        T: AsRef<str>,
+    {
+        Ok(request_sign_digest(&self.kms, key_id, digest)
+            .await
+            .map(utils::decode_signature)??)
+    }
+
+    /// Sign a digest with this signer's key
+    pub async fn sign_digest(&self, digest: [u8; 32]) -> Result<KSig, AwsSignerError> {
+        self.sign_digest_with_key(self.key_id.clone(), digest).await
+    }
+
+    /// Sign a digest with this signer's key and add the eip155 `v` value
+    /// corresponding to this signer's chain_id
+    #[instrument(err, skip(digest), fields(digest = %hex::encode(&digest)))]
+    async fn sign_digest_with_eip155(&self, digest: H256) -> Result<EthSig, AwsSignerError> {
+        let sig = self.sign_digest(digest.into()).await?;
+
+        let sig = utils::rsig_from_digest_bytes_trial_recovery(&sig, digest.into(), &self.pubkey);
+
+        let mut sig = rsig_to_ethsig(&sig);
+        apply_eip155(&mut sig, self.chain_id);
+        Ok(sig)
+    }
+}
+
+#[async_trait::async_trait]
+impl<'a> super::Signer for AwsSigner<'a> {
+    type Error = AwsSignerError;
+
+    #[instrument(err, skip(message))]
+    async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
+        &self,
+        message: S,
+    ) -> Result<EthSig, Self::Error> {
+        let message = message.as_ref();
+        let message_hash = hash_message(message);
+        trace!("{:?}", message_hash);
+        trace!("{:?}", message);
+
+        self.sign_digest_with_eip155(message_hash).await
+    }
+
+    #[instrument(err)]
+    async fn sign_transaction(&self, tx: &TransactionRequest) -> Result<EthSig, Self::Error> {
+        let sighash = tx.sighash(self.chain_id);
+        self.sign_digest_with_eip155(sighash).await
+    }
+
+    fn address(&self) -> Address {
+        self.address
+    }
+
+    /// Returns the signer's chain id
+    fn chain_id(&self) -> u64 {
+        self.chain_id
+    }
+
+    /// Sets the signer's chain id
+    fn with_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
+        self.chain_id = chain_id.into();
+        self
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use rusoto_core::{
+        credential::{EnvironmentProvider, StaticProvider},
+        Client, HttpClient, Region,
+    };
+    use tracing::metadata::LevelFilter;
+
+    use super::*;
+    use crate::Signer;
+
+    #[allow(dead_code)]
+    fn setup_tracing() {
+        tracing_subscriber::fmt()
+            .with_max_level(LevelFilter::DEBUG)
+            .try_init()
+            .unwrap();
+    }
+
+    #[allow(dead_code)]
+    fn static_client() -> KmsClient {
+        let access_key = "".to_owned();
+        let secret_access_key = "".to_owned();
+
+        let client = Client::new_with(
+            StaticProvider::new(access_key, secret_access_key, None, None),
+            HttpClient::new().unwrap(),
+        );
+        KmsClient::new_with_client(client, Region::UsWest1)
+    }
+
+    #[allow(dead_code)]
+    fn env_client() -> KmsClient {
+        let client = Client::new_with(EnvironmentProvider::default(), HttpClient::new().unwrap());
+        KmsClient::new_with_client(client, Region::UsWest1)
+    }
+
+    #[tokio::test]
+    async fn it_signs_messages() {
+        let chain_id = 1;
+        let key_id = std::env::var("AWS_KEY_ID").expect("no key id");
+        setup_tracing();
+        let client = env_client();
+        let signer = AwsSigner::new(&client, key_id, chain_id).await.unwrap();
+
+        let message = vec![0, 1, 2, 3];
+
+        let sig = signer.sign_message(&message).await.unwrap();
+        sig.verify(message, signer.address).expect("valid sig");
+    }
+}
diff --git a/ethers-signers/src/aws/utils.rs b/ethers-signers/src/aws/utils.rs
new file mode 100644
index 00000000..149b66d7
--- /dev/null
+++ b/ethers-signers/src/aws/utils.rs
@@ -0,0 +1,99 @@
+//! These utils are NOT meant for general usage. They are ONLY meant for use
+//! within this module. They DO NOT perform basic safety checks and may panic
+//! if used incorrectly.
+
+use std::convert::TryFrom;
+
+use ethers_core::{
+    k256::{
+        ecdsa::{
+            recoverable::{Id, Signature as RSig},
+            Signature as KSig, VerifyingKey,
+        },
+        elliptic_curve::sec1::ToEncodedPoint,
+        FieldBytes,
+    },
+    types::{Address, Signature as EthSig, H256},
+    utils::keccak256,
+};
+use rusoto_kms::{GetPublicKeyResponse, SignResponse};
+
+use crate::aws::AwsSignerError;
+
+/// Converts a recoverable signature to an ethers signature
+pub(super) fn rsig_to_ethsig(sig: &RSig) -> EthSig {
+    let v: u8 = sig.recovery_id().into();
+    let v = (v + 27) as u64;
+    let r_bytes: FieldBytes = sig.r().into();
+    let s_bytes: FieldBytes = sig.s().into();
+    let r = H256::from_slice(&r_bytes.as_slice());
+    let s = H256::from_slice(&s_bytes.as_slice());
+    EthSig { r, s, v }
+}
+
+/// Makes a trial recovery to check whether an RSig corresponds to a known
+/// `VerifyingKey`
+fn check_candidate(sig: &RSig, digest: [u8; 32], vk: &VerifyingKey) -> bool {
+    if let Ok(key) = sig.recover_verify_key_from_digest_bytes(digest.as_ref().into()) {
+        key == *vk
+    } else {
+        false
+    }
+}
+
+/// Recover an rsig from a signature under a known key by trial/error
+pub(super) fn rsig_from_digest_bytes_trial_recovery(
+    sig: &KSig,
+    digest: [u8; 32],
+    vk: &VerifyingKey,
+) -> RSig {
+    let sig_0 = RSig::new(sig, Id::new(0).unwrap()).unwrap();
+    let sig_1 = RSig::new(sig, Id::new(1).unwrap()).unwrap();
+
+    if check_candidate(&sig_0, digest, vk) {
+        sig_0
+    } else if check_candidate(&sig_1, digest, vk) {
+        sig_1
+    } else {
+        panic!("bad sig");
+    }
+}
+
+/// Modify the v value of a signature to conform to eip155
+pub(super) fn apply_eip155(sig: &mut EthSig, chain_id: u64) {
+    let v = (chain_id * 2 + 35) + ((sig.v - 1) % 2);
+    sig.v = v;
+}
+
+/// Convert a verifying key to an ethereum address
+pub(super) fn verifying_key_to_address(key: &VerifyingKey) -> Address {
+    // false for uncompressed
+    let uncompressed_pub_key = key.to_encoded_point(false);
+    let public_key = uncompressed_pub_key.to_bytes();
+    debug_assert_eq!(public_key[0], 0x04);
+    let hash = keccak256(&public_key[1..]);
+    Address::from_slice(&hash[12..])
+}
+
+/// Decode an AWS KMS Pubkey response
+pub(super) fn decode_pubkey(resp: GetPublicKeyResponse) -> Result<VerifyingKey, AwsSignerError> {
+    let raw = resp
+        .public_key
+        .ok_or_else(|| AwsSignerError::from("Pubkey not found in response".to_owned()))?;
+
+    let spk = spki::SubjectPublicKeyInfo::try_from(raw.as_ref())?;
+    let key = VerifyingKey::from_sec1_bytes(&spk.subject_public_key)?;
+
+    Ok(key)
+}
+
+/// Decode an AWS KMS Signature response
+pub(super) fn decode_signature(resp: SignResponse) -> Result<KSig, AwsSignerError> {
+    let raw = resp
+        .signature
+        .ok_or_else(|| AwsSignerError::from("Signature not found in response".to_owned()))?;
+
+    let mut sig = KSig::from_der(&raw)?;
+    sig.normalize_s()?;
+    Ok(sig)
+}
diff --git a/ethers-signers/src/lib.rs b/ethers-signers/src/lib.rs
index c54c4f38..b39619da 100644
--- a/ethers-signers/src/lib.rs
+++ b/ethers-signers/src/lib.rs
@@ -10,6 +10,7 @@
 //! - [Private key](crate::LocalWallet)
 //! - [Ledger](crate::Ledger)
 //! - [YubiHSM2](crate::YubiWallet)
+//! - [AWS KMS](crate::AwsSigner)
 //!
 //! ```no_run
 //! # use ethers::{
@@ -62,6 +63,12 @@ pub use ledger::{
 #[cfg(feature = "yubi")]
 pub use yubihsm;
 
+#[cfg(feature = "aws")]
+mod aws;
+
+#[cfg(feature = "aws")]
+pub use aws::{AwsSigner, AwsSignerError};
+
 use async_trait::async_trait;
 use ethers_core::types::{Address, Signature, TransactionRequest};
 use std::error::Error;


Test patch:
diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs
index baa335fd..773bb868 100644
--- a/ethers-contract/tests/contract.rs
+++ b/ethers-contract/tests/contract.rs
@@ -278,7 +278,7 @@ mod eth_tests {
         let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
         let ganache = Ganache::new().spawn();
         let client = connect(&ganache, 0);
-        let contract = deploy(client, abi.clone(), bytecode).await;
+        let contract = deploy(client.clone(), abi.clone(), bytecode).await;
 
         // We spawn the event listener:
         let event = contract.event::<ValueChanged>();
@@ -292,9 +292,13 @@ mod eth_tests {
         let mut subscription = event2.subscribe().await.unwrap();
         assert_eq!(subscription.id, 2.into());
 
+        let mut subscription_meta = event2.subscribe().await.unwrap().with_meta();
+        assert_eq!(subscription_meta.0.id, 3.into());
+
         let num_calls = 3u64;
 
         // and we make a few calls
+        let num = client.get_block_number().await.unwrap();
         for i in 0..num_calls {
             let call = contract
                 .method::<_, H256>("setValue", i.to_string())
@@ -307,8 +311,19 @@ mod eth_tests {
             // unwrap the option of the stream, then unwrap the decoding result
             let log = stream.next().await.unwrap().unwrap();
             let log2 = subscription.next().await.unwrap().unwrap();
+            let (log3, meta) = subscription_meta.next().await.unwrap().unwrap();
+            assert_eq!(log.new_value, log3.new_value);
             assert_eq!(log.new_value, log2.new_value);
             assert_eq!(log.new_value, i.to_string());
+            assert_eq!(meta.block_number, num + i + 1);
+            let hash = client
+                .get_block(num + i + 1)
+                .await
+                .unwrap()
+                .unwrap()
+                .hash
+                .unwrap();
+            assert_eq!(meta.block_hash, hash);
         }
     }
 
diff --git a/ethers-providers/tests/provider.rs b/ethers-providers/tests/provider.rs
index 08a586e0..ba35d2a6 100644
--- a/ethers-providers/tests/provider.rs
+++ b/ethers-providers/tests/provider.rs
@@ -40,6 +40,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() {