Repository: gakonst/ethers-rs
Pull request #2195 (2023-02-25 09:07:17)

feat(solc): add is_empty fn for structs of Options
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation
add `OptimizerDetails::is_empty` 
<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

-   [ ] Added Tests
-   [ ] Added Documentation
-   [ ] Updated the changelog
-   [ ] Breaking changes



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 255d604d..7534ccd9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -284,6 +284,8 @@
 
 ### Unreleased
 
+- fix: `LedgerSigner` has improved tracing and a ledger app bug mitigation
+  [#2192](https://github.com/gakonst/ethers-rs/pull/2192)
 - `eth-keystore-rs` crate updated. Allow an optional name for the to-be-generated
   keystore file [#910](https://github.com/gakonst/ethers-rs/pull/910)
 - [1983](https://github.com/gakonst/ethers-rs/pull/1983) Added a `from_bytes` function for the `Wallet` type.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7c0c387f..24f5e4ca 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -22,7 +22,7 @@ us!
 The ethers-rs project adheres to the [Rust Code of Conduct][coc]. This describes
 the _minimum_ behavior expected from all contributors. Instances of violations of the Code of Conduct can be reported by contacting the project team at [me@gakonst.com](mailto:me@gakonst.com).
 
-[coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md
+[coc]: https://www.rust-lang.org/policies/code-of-conduct
 
 ## Contributing in Issues
 
diff --git a/Cargo.lock b/Cargo.lock
index 811c9c81..06d397c3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -141,18 +141,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "auto_impl"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7862e21c893d65a1650125d157eaeec691439379a1cee17ee49031b79236ada4"
-dependencies = [
- "proc-macro-error",
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "auto_impl"
 version = "1.0.1"
@@ -1471,7 +1459,7 @@ name = "ethers-middleware"
 version = "1.0.2"
 dependencies = [
  "async-trait",
- "auto_impl 0.5.0",
+ "auto_impl",
  "ethers-contract",
  "ethers-core",
  "ethers-etherscan",
@@ -1500,7 +1488,7 @@ name = "ethers-providers"
 version = "1.0.2"
 dependencies = [
  "async-trait",
- "auto_impl 1.0.1",
+ "auto_impl",
  "base64 0.21.0",
  "bytes",
  "enr",
@@ -2744,7 +2732,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce"
 dependencies = [
  "arrayvec 0.7.2",
- "auto_impl 1.0.1",
+ "auto_impl",
  "bytes",
  "ethereum-types",
  "open-fastrlp-derive",
@@ -3370,15 +3358,6 @@ version = "0.6.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
 
-[[package]]
-name = "remove_dir_all"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
-dependencies = [
- "winapi",
-]
-
 [[package]]
 name = "reqwest"
 version = "0.11.14"
@@ -4158,9 +4137,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.108"
+version = "1.0.109"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d56e159d99e6c2b93995d171050271edb50ecc5288fbc7cc17de8fdce4e58c14"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4187,16 +4166,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
 
 [[package]]
 name = "tempfile"
-version = "3.3.0"
+version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
 dependencies = [
  "cfg-if 1.0.0",
  "fastrand",
- "libc",
  "redox_syscall",
- "remove_dir_all",
- "winapi",
+ "rustix",
+ "windows-sys",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 6c2794ac..abfb4738 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,7 @@ readme = "README.md"
 documentation = "https://docs.rs/ethers"
 repository = "https://github.com/gakonst/ethers-rs"
 homepage = "https://docs.rs/ethers"
-description = "Complete Ethereum library and wallet implementation in Rust."
+description = "A complete Ethereum and Celo Rust library"
 
 [workspace]
 members = [
@@ -110,7 +110,7 @@ ethers-contract = { version = "^1.0.0", default-features = false, path = "./ethe
 ethers-providers = { version = "^1.0.0", default-features = false, path = "./ethers-providers", features = [
     "ws",
 ] }
-tempfile = "3.3.0"
+tempfile = "3.4.0"
 
 [target.'cfg(target_family = "unix")'.dev-dependencies]
 ethers-providers = { version = "^1.0.0", default-features = false, path = "./ethers-providers", features = [
diff --git a/README.md b/README.md
index c85e9f9e..97cea2aa 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# <h1 align="center"> ethers.rs </h1>
+# <h1 align="center"> ethers-rs </h1>
 
-**Complete Ethereum and Celo wallet implementation and utilities in Rust**
+**A complete Ethereum and Celo Rust library**
 
 ![Github Actions](https://github.com/gakonst/ethers-rs/workflows/Tests/badge.svg)
 [![Telegram Chat](https://img.shields.io/endpoint?color=neon&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fethers_rs)](https://t.me/ethers_rs)
@@ -9,42 +9,34 @@
 [crates-badge]: https://img.shields.io/crates/v/ethers.svg
 [crates-url]: https://crates.io/crates/ethers
 
-## Documentation
-
-Extensive documentation and examples are available [here](https://docs.rs/ethers).
-
-Alternatively, you may clone the repository and run `cd ethers/ && cargo doc --open`
-
-Examples are organized into individual crates under the `/examples` folder.
-You can run any of the examples by executing: 
-```bash
-# cargo run -p <example-crate-name> --example <name>
-cargo run -p examples-big-numbers --example math_operations
-```
+## Quickstart
 
-## Add ethers-rs to your repository
+Add this to your Cargo.toml:
 
 ```toml
 [dependencies]
-
-ethers = "1.0.0"
+ethers = "1.0.2"
 ```
 
-</details>
+And this to your code:
 
-## Running the tests
+```rust
+use ethers::prelude::*;
+```
 
-Tests require the following installed:
+## Documentation
 
-1. [`solc`](https://solidity.readthedocs.io/en/latest/installing-solidity.html) (>=0.8.10). We also recommend using [solc-select](https://github.com/crytic/solc-select) for more flexibility.
-2. [`anvil`](https://github.com/foundry-rs/foundry/blob/master/anvil/README.md)
-3. [`geth`](https://github.com/ethereum/go-ethereum)
+View the API reference [here](https://docs.rs/ethers) or the online book [here](https://gakonst.com/ethers-rs).
 
-In addition, it is recommended that you set the `ETHERSCAN_API_KEY` environment variable
-for [the abigen via Etherscan](https://github.com/gakonst/ethers-rs/blob/master/ethers-contract/tests/it/abigen.rs) tests.
-You can get one [here](https://etherscan.io/apis).
+Examples are organized into individual crates under the `/examples` folder.
+You can run any of the examples by executing:
+
+```bash
+# cargo run -p <example-crate-name> --example <name>
+cargo run -p examples-big-numbers --example math_operations
+```
 
-### EVM-compatible chains support
+## EVM-compatible chains support
 
 There are many chains live which are Ethereum JSON-RPC & EVM compatible, but do not yet have
 support for [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) Typed Transactions. This means
@@ -53,8 +45,7 @@ address that, you must use the `legacy` feature flag:
 
 ```toml
 [dependencies]
-
-ethers = { version = "1.0.0", features = ["legacy"] }
+ethers = { version = "1.0.2", features = ["legacy"] }
 ```
 
 ### Polygon support
@@ -69,12 +60,11 @@ You can get one [here](https://snowtrace.io/apis).
 
 ### Celo Support
 
-[Celo](http://celo.org/) support is turned on via the feature-flag `celo`:
+[Celo](https://celo.org) support is turned on via the feature-flag `celo`:
 
 ```toml
 [dependencies]
-
-ethers = { version = "1.0.0", features = ["celo"] }
+ethers = { version = "1.0.2", features = ["celo"] }
 ```
 
 Celo's transactions differ from Ethereum transactions by including 3 new fields:
@@ -111,8 +101,7 @@ Websockets support is turned on via the feature-flag `ws`:
 
 ```toml
 [dependencies]
-
-ethers = { version = "1.0.0", features = ["ws"] }
+ethers = { version = "1.0.2", features = ["ws"] }
 ```
 
 ### Interprocess Communication (IPC)
@@ -121,8 +110,7 @@ IPC support is turned on via the feature-flag `ipc`:
 
 ```toml
 [dependencies]
-
-ethers = { version = "1.0.0", features = ["ipc"] }
+ethers = { version = "1.0.2", features = ["ipc"] }
 ```
 
 ### HTTP Secure (HTTPS)
@@ -134,16 +122,14 @@ To enable `rustls`:
 
 ```toml
 [dependencies]
-
-ethers = { version = "1.0.0", features = ["rustls"] }
+ethers = { version = "1.0.2", features = ["rustls"] }
 ```
 
 To enable `openssl`:
 
 ```toml
 [dependencies]
-
-ethers = { version = "1.0.0", features = ["openssl"] }
+ethers = { version = "1.0.2", features = ["openssl"] }
 ```
 
 ## Note on WASM and FFI bindings
@@ -171,24 +157,22 @@ Join the [ethers-rs telegram](https://t.me/ethers_rs) to chat with the community
 ## Contributing
 
 Thanks for your help improving the project! We are so happy to have you! We have
-[a contributing guide](https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md) to
-help you get involved in the ethers-rs project.
+[a contributing guide](./CONTRIBUTING.md) to help you get involved in the ethers-rs project.
 
-If you make a Pull Request, do not forget to add your changes in the [CHANGELOG](CHANGELOG.md) and ensure your code is
-properly formatted with `cargo +nightly fmt` and clippy is happy `cargo clippy`, you can even try to let clippy fix simple
-issues itself: `cargo +nightly clippy --fix -Z unstable-options`
+If you open a Pull Request, do not forget to add your changes in the [CHANGELOG](./CHANGELOG.md), ensure your code is
+properly formatted with `cargo +nightly fmt` and that Clippy is happy `cargo clippy`; you can even try to let clippy fix simple
+issues itself: `cargo +nightly clippy --fix`
 
-## Related Projects
+### Running the tests
 
-This library would not have been possible without the great work done in:
+Tests require the following installed:
 
--   [`ethers.js`](https://github.com/ethers-io/ethers.js/)
--   [`rust-web3`](https://github.com/tomusdrw/rust-web3/)
--   [`ethcontract-rs`](https://github.com/gnosis/ethcontract-rs/)
--   [`guac_rs`](https://github.com/althea-net/guac_rs/tree/master/web3/src/jsonrpc)
+1. [`solc`](https://docs.soliditylang.org/en/latest/installing-solidity.html) (>=0.8.0). We also recommend using [svm](https://github.com/roynalnaruto/svm-rs) for more flexibility.
+2. [`anvil`](https://github.com/foundry-rs/foundry/blob/master/anvil/README.md)
+3. [`geth`](https://github.com/ethereum/go-ethereum)
 
-A lot of the code was inspired and adapted from them, to a unified and opinionated interface,
-built with async/await and std futures from the ground up.
+Additionally, the `ETHERSCAN_API_KEY` environment variable has to be set to run [`ethers-etherscan`](./ethers-etherscan) tests.
+You can get one [here](https://etherscan.io/apis).
 
 ## Projects using ethers-rs
 
@@ -199,3 +183,15 @@ built with async/await and std futures from the ground up.
 -   [Celo Threshold BLS DKG](https://github.com/celo-org/celo-threshold-bls-rs/): CLI for using Celo as a data availability network for the Joint-Feldman BLS DKG
 -   [Celo Plumo Prover](https://github.com/celo-org/plumo-prover): Creates Celo's ultralight client proof from on-chain data
 -   [Celo SNARK Setup Coordinator](https://github.com/celo-org/snark-setup-operator): Coordinator for executing a pipelined Groth16 SNARK setup
+
+## Credits
+
+This library would not have been possible without the great work done in:
+
+-   [`ethers.js`](https://github.com/ethers-io/ethers.js/)
+-   [`rust-web3`](https://github.com/tomusdrw/rust-web3/)
+-   [`ethcontract-rs`](https://github.com/gnosis/ethcontract-rs/)
+-   [`guac_rs`](https://github.com/althea-net/guac_rs/)
+
+A lot of the code was inspired and adapted from them, to a unified and opinionated interface,
+built with async/await and std futures from the ground up.
diff --git a/assets/CONTRACT_README.md b/assets/CONTRACT_README.md
deleted file mode 120000
index c28ebb7e..00000000
--- a/assets/CONTRACT_README.md
+++ /dev/null
@@ -1,1 +0,0 @@
-../ethers-contract/README.md
\ No newline at end of file
diff --git a/assets/CORE_README.md b/assets/CORE_README.md
deleted file mode 120000
index b61206a8..00000000
--- a/assets/CORE_README.md
+++ /dev/null
@@ -1,1 +0,0 @@
-../ethers-core/README.md
\ No newline at end of file
diff --git a/assets/MIDDLEWARE_README.md b/assets/MIDDLEWARE_README.md
deleted file mode 120000
index 31026c1a..00000000
--- a/assets/MIDDLEWARE_README.md
+++ /dev/null
@@ -1,1 +0,0 @@
-../ethers-middleware/README.md
\ No newline at end of file
diff --git a/assets/PROVIDERS_README.md b/assets/PROVIDERS_README.md
deleted file mode 120000
index 149647fb..00000000
--- a/assets/PROVIDERS_README.md
+++ /dev/null
@@ -1,1 +0,0 @@
-../ethers-providers/README.md
\ No newline at end of file
diff --git a/assets/SIGNERS_README.md b/assets/SIGNERS_README.md
deleted file mode 120000
index ea8f7fac..00000000
--- a/assets/SIGNERS_README.md
+++ /dev/null
@@ -1,1 +0,0 @@
-../ethers-signers/README.md
\ No newline at end of file
diff --git a/assets/SOLC_README.md b/assets/SOLC_README.md
deleted file mode 120000
index bb5c3c0f..00000000
--- a/assets/SOLC_README.md
+++ /dev/null
@@ -1,1 +0,0 @@
-../ethers-solc/README.md
\ No newline at end of file
diff --git a/ethers-addressbook/Cargo.toml b/ethers-addressbook/Cargo.toml
index 3dd8ee1b..f13e7792 100644
--- a/ethers-addressbook/Cargo.toml
+++ b/ethers-addressbook/Cargo.toml
@@ -11,8 +11,8 @@ repository = "https://github.com/gakonst/ethers-rs"
 keywords = ["ethereum", "web3", "celo", "ethers"]
 
 [dependencies]
+ethers-core = { version = "^1.0.0", path = "../ethers-core", default-features = false }
+
 once_cell = "1.17.1"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
-
-ethers-core = { path = "../ethers-core", version = "^1.0.0" }
diff --git a/ethers-addressbook/README.md b/ethers-addressbook/README.md
new file mode 100644
index 00000000..434ce7b5
--- /dev/null
+++ b/ethers-addressbook/README.md
@@ -0,0 +1,15 @@
+# ethers-addressbook
+
+A collection of commonly used smart contract addresses.
+
+For more information, please refer to the [book](https://gakonst.com/ethers-rs).
+
+## Examples
+
+```rust
+use ethers_addressbook::{contract, Chain};
+
+let weth = contract("weth").unwrap();
+let mainnet_address = weth.address(Chain::Mainnet).unwrap();
+assert_eq!(mainnet_address, "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse().unwrap());
+```
diff --git a/ethers-addressbook/src/lib.rs b/ethers-addressbook/src/lib.rs
index 2acbfc42..c2acc2f6 100644
--- a/ethers-addressbook/src/lib.rs
+++ b/ethers-addressbook/src/lib.rs
@@ -1,7 +1,10 @@
-use ethers_core::types::{Address, Chain};
+#![doc = include_str!("../README.md")]
+#![deny(unsafe_code, rustdoc::broken_intra_doc_links)]
+
+pub use ethers_core::types::{Address, Chain};
+
 use once_cell::sync::Lazy;
 use serde::Deserialize;
-
 use std::collections::HashMap;
 
 const CONTRACTS_JSON: &str = include_str!("./contracts/contracts.json");
@@ -9,7 +12,7 @@ const CONTRACTS_JSON: &str = include_str!("./contracts/contracts.json");
 static ADDRESSBOOK: Lazy<HashMap<String, Contract>> =
     Lazy::new(|| serde_json::from_str(CONTRACTS_JSON).unwrap());
 
-/// Wrapper around a hash map that maps a [chain](https://github.com/gakonst/ethers-rs/blob/master/ethers-core/src/types/chain.rs) to the contract's deployed address on that chain.
+/// Wrapper around a hash map that maps a [Chain] to the contract's deployed address on that chain.
 #[derive(Clone, Debug, Deserialize)]
 pub struct Contract {
     addresses: HashMap<Chain, Address>,
diff --git a/ethers-contract/README.md b/ethers-contract/README.md
index d5658613..b91644f2 100644
--- a/ethers-contract/README.md
+++ b/ethers-contract/README.md
@@ -1,4 +1,6 @@
-Type-safe abstractions for interacting with Ethereum smart contracts
+# ethers-core
+
+Type-safe abstractions for interacting with Ethereum smart contracts.
 
 Interacting with a smart contract requires broadcasting carefully crafted
 [transactions](ethers_core::types::TransactionRequest) where the `data` field
@@ -10,6 +12,8 @@ This module provides the [`Contract`] and [`ContractFactory`] abstractions so
 that you do not have to worry about that. It also provides typesafe bindings via
 the [`abigen`] macro and the [`Abigen` builder].
 
+For more information, please refer to the [book](https://gakonst.com/ethers-rs).
+
 [`contractfactory`]: ./struct.ContractFactory.html
 [`contract`]: ./struct.Contract.html
 [`abigen`]: ./macro.abigen.html
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index 246e716e..de11b029 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -371,8 +371,8 @@ impl Context {
     /// convention of suffixing the function name with _with
     ///
     /// The first function or the function with the least amount of arguments should
-    /// be named as in the ABI, the following functions suffixed with _with_ +
-    /// additional_params[0].name + (_and_(additional_params[1+i].name))*
+    /// be named as in the ABI, the following functions suffixed with:
+    /// `_with_ + additional_params[0].name + (_and_(additional_params[1+i].name))*`
     fn get_method_aliases(&self) -> Result<BTreeMap<String, MethodAlias>> {
         let mut aliases = self.method_aliases.clone();
 
diff --git a/ethers-contract/ethers-contract-abigen/src/source/online.rs b/ethers-contract/ethers-contract-abigen/src/source/online.rs
index 3a6f04c0..3c3c70d8 100644
--- a/ethers-contract/ethers-contract-abigen/src/source/online.rs
+++ b/ethers-contract/ethers-contract-abigen/src/source/online.rs
@@ -67,7 +67,8 @@ impl Explorer {
         let chain = self.chain();
         let client = match api_key {
             Some(api_key) => Client::new(chain, api_key),
-            None => Client::new_from_env(chain),
+            None => Client::new_from_env(chain)
+                .or_else(|_| Client::builder().chain(chain).and_then(|b| b.build())),
         }?;
         Ok(client)
     }
@@ -119,7 +120,7 @@ impl Source {
     /// Parse `s` as an explorer ("etherscan"), explorer domain ("etherscan.io") or a chain that has
     /// an explorer ("mainnet").
     ///
-    /// The URL can be either <explorer>:<address> or <explorer_url>/.../<address>
+    /// The URL can be either `<explorer>:<address>` or `<explorer_url>/.../<address>`
     fn from_explorer(s: &str, url: &Url) -> Result<Self> {
         let explorer: Explorer = s.parse().or_else(|_| Explorer::from_chain(s.parse()?))?;
         let address = last_segment_address(url).ok_or_else(|| eyre::eyre!("Invalid URL: {url}"))?;
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index 3e340807..8be4b4d8 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -40,12 +40,12 @@ cargo_metadata = { version = "0.15.3", optional = true }
 
 # eip712 feature enabled dependencies
 convert_case = { version = "0.6.0", optional = true }
-syn = { version = "1.0.108", optional = true }
+syn = { version = "1.0.109", optional = true }
 proc-macro2 = { version = "1.0.51", optional = true }
 num_enum = "0.5.11"
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-tempfile = { version = "3.3.0", default-features = false }
+tempfile = { version = "3.4.0", default-features = false }
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
 # NOTE: this enables wasm compatibility for getrandom indirectly
diff --git a/ethers-core/README.md b/ethers-core/README.md
index 15cba6ce..d4131855 100644
--- a/ethers-core/README.md
+++ b/ethers-core/README.md
@@ -1,4 +1,6 @@
-# Ethereum types, cryptography and utilities.
+# ethers-core
+
+Ethereum data types, cryptography and utilities.
 
 It is recommended to use the `utils`, `types` and `abi` re-exports instead of
 the `core` module to simplify your imports.
@@ -6,43 +8,43 @@ the `core` module to simplify your imports.
 This library provides type definitions for Ethereum's main datatypes along with
 other utilities for interacting with the Ethereum ecosystem
 
-## Signing an ethereum-prefixed message
-
-Signing in Ethereum is done by first prefixing the message with
-`"\x19Ethereum Signed Message:\n" + message.length`, and then signing the hash
-of the result.
+For more information, please refer to the [book](https://gakonst.com/ethers-rs).
 
-```rust,ignore
-# async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-use ethers::signers::{Signer, LocalWallet};
+## Feature flags
 
-let message = "Some data";
-let wallet = LocalWallet::new(&mut rand::thread_rng());
+-   `eip712`: Provides the `Eip712` trait and derive procedural macro for EIP-712 encoding of typed data.
 
-// Sign the message
-let signature = wallet.sign_message(message).await?;
+## ABI
 
-// Recover the signer from the message
-let recovered = signature.recover(message)?;
-
-assert_eq!(recovered, wallet.address());
-# Ok(())
-# }
-```
+This crate re-exports the [`ethabi`](https://docs.rs/ethabi) crate's functions
+under the `abi` module, as well as the
+[`secp256k1`](https://docs.rs/libsecp256k1) and [`rand`](https://docs.rs/rand)
+crates for convenience.
 
 ## Utilities
 
 The crate provides utilities for launching local Ethereum testnets by using
 `ganache-cli` via the `GanacheBuilder` struct.
 
-# Features
+## Examples
 
--   ["eip712"] | Provides Eip712 trait for EIP-712 encoding of typed data for
-    derived structs
+Calculate the UniswapV2 pair address for two ERC20 tokens:
 
-# ABI Encoding and Decoding
+```rust
+# use ethers_core::abi::{self, Token};
+# use ethers_core::types::{Address, H256};
+# use ethers_core::utils;
+let factory: Address = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f".parse()?;
 
-This crate re-exports the [`ethabi`](https://docs.rs/ethabi) crate's functions
-under the `abi` module, as well as the
-[`secp256k1`](https://docs.rs/libsecp256k1) and [`rand`](https://docs.rs/rand)
-crates for convenience.
+let token_a: Address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".parse()?;
+let token_b: Address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse()?;
+let encoded = abi::encode_packed(&[Token::Address(token_a), Token::Address(token_b)])?;
+let salt = utils::keccak256(encoded);
+
+let init_code_hash: H256 = "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f".parse()?;
+
+let pair = utils::get_create2_address_from_hash(factory, salt, init_code_hash);
+let weth_usdc = "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".parse()?;
+assert_eq!(pair, weth_usdc);
+# Ok::<(), Box<dyn std::error::Error>>(())
+```
diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs
index 7cc733a5..f777d89a 100644
--- a/ethers-core/src/abi/mod.rs
+++ b/ethers-core/src/abi/mod.rs
@@ -1,5 +1,7 @@
-//! This module implements extensions to the [`ethabi`](https://docs.rs/ethabi) API.
-// Adapted from [Gnosis' ethcontract](https://github.com/gnosis/ethcontract-rs/blob/master/common/src/abiext.rs)
+//! Extensions to the [`ethabi`](https://docs.rs/ethabi) API.
+//!
+//! Adapted from [Gnosis' `ethcontract-rs`](https://github.com/gnosis/ethcontract-rs).
+
 use crate::{
     types::{Bytes, Selector, Uint8, H256, H512, I256, U128, U256, U64},
     utils::id,
diff --git a/ethers-core/src/types/chain.rs b/ethers-core/src/types/chain.rs
index 072daac3..05d8140d 100644
--- a/ethers-core/src/types/chain.rs
+++ b/ethers-core/src/types/chain.rs
@@ -20,6 +20,11 @@ pub type ParseChainError = TryFromPrimitiveError<Chain>;
 //      "main" must be present and will be used in `Display`, `Serialize` and `FromStr`,
 //      while the aliases will be added only to `FromStr`.
 
+// We don't derive Serialize because it is manually implemented using AsRef<str> and it would
+// break a lot of things since Serialize is `kebab-case` vs Deserialize `snake_case`.
+// This means that the Chain type is not "round-trippable", because the Serialize and Deserialize
+// implementations do not use the same case style.
+
 /// An Ethereum EIP-155 chain.
 #[derive(
     Clone,
@@ -30,11 +35,11 @@ pub type ParseChainError = TryFromPrimitiveError<Chain>;
     PartialOrd,
     Ord,
     Hash,
-    AsRefStr,         // also for fmt::Display and serde::Serialize
-    EnumVariantNames, // Self::VARIANTS
+    AsRefStr,         // AsRef<str>, fmt::Display and serde::Serialize
+    EnumVariantNames, // Chain::VARIANTS
     EnumString,       // FromStr, TryFrom<&str>
-    EnumIter,
-    EnumCount,
+    EnumIter,         // Chain::iter
+    EnumCount,        // Chain::COUNT
     TryFromPrimitive, // TryFrom<u64>
     Deserialize,
 )]
@@ -212,12 +217,25 @@ impl Chain {
     /// **Note:** this is not an accurate average, but is rather a sensible default derived from
     /// blocktime charts such as [Etherscan's](https://etherscan.com/chart/blocktime)
     /// or [Polygonscan's](https://polygonscan.com/chart/blocktime).
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use ethers_core::types::Chain;
+    /// use std::time::Duration;
+    ///
+    /// assert_eq!(
+    ///     Chain::Mainnet.average_blocktime_hint(),
+    ///     Some(Duration::from_millis(12_000)),
+    /// );
+    /// assert_eq!(Chain::Optimism.average_blocktime_hint(), None);
+    /// ```
     pub const fn average_blocktime_hint(&self) -> Option<Duration> {
         use Chain::*;
 
         let ms = match self {
+            Mainnet => 12_000,
             Arbitrum | ArbitrumTestnet | ArbitrumGoerli | ArbitrumNova => 1_300,
-            Mainnet | Optimism => 13_000,
             Polygon | PolygonMumbai => 2_100,
             Moonbeam | Moonriver => 12_500,
             BinanceSmartChain | BinanceSmartChainTestnet => 3_000,
@@ -230,20 +248,82 @@ impl Chain {
             Emerald => 6_000,
             Dev | AnvilHardhat => 200,
             Celo | CeloAlfajores | CeloBaklava => 5_000,
-            // Explictly handle all network to make it easier not to forget this match when new
-            // networks are added.
+
+            // Explicitly exhaustive. See NB above.
             Morden | Ropsten | Rinkeby | Goerli | Kovan | XDai | Chiado | Sepolia | Moonbase |
-            MoonbeamDev | OptimismGoerli | OptimismKovan | Poa | Sokol | Rsk | EmeraldTestnet => {
-                return None
-            }
+            MoonbeamDev | Optimism | OptimismGoerli | OptimismKovan | Poa | Sokol | Rsk |
+            EmeraldTestnet => return None,
         };
 
         Some(Duration::from_millis(ms))
     }
 
+    /// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use ethers_core::types::Chain;
+    ///
+    /// assert!(!Chain::Mainnet.is_legacy());
+    /// assert!(Chain::Celo.is_legacy());
+    /// ```
+    #[allow(clippy::match_like_matches_macro)]
+    pub const fn is_legacy(&self) -> bool {
+        use Chain::*;
+
+        match self {
+            // Known legacy chains / non EIP-1559 compliant
+            Optimism |
+            OptimismGoerli |
+            OptimismKovan |
+            Fantom |
+            FantomTestnet |
+            BinanceSmartChain |
+            BinanceSmartChainTestnet |
+            Arbitrum |
+            ArbitrumTestnet |
+            ArbitrumGoerli |
+            ArbitrumNova |
+            Rsk |
+            Oasis |
+            Emerald |
+            EmeraldTestnet |
+            Celo |
+            CeloAlfajores |
+            CeloBaklava => true,
+
+            // Known EIP-1559 chains
+            Mainnet | Goerli | Sepolia | Polygon | PolygonMumbai | Avalanche | AvalancheFuji => {
+                false
+            }
+
+            // Unknown / not applicable, default to false for backwards compatibility
+            Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan |
+            Sokol | Poa | XDai | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos |
+            EvmosTestnet | Chiado | Aurora | AuroraTestnet | Canto | CantoTestnet => false,
+        }
+    }
+
     /// Returns the chain's blockchain explorer and its API (Etherscan and Etherscan-like) URLs.
     ///
-    /// Returns `(API URL, BASE_URL)`, like `("https://api(-chain).etherscan.io/api", "https://etherscan.io")`
+    /// Returns `(API_URL, BASE_URL)`
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use ethers_core::types::Chain;
+    ///
+    /// assert_eq!(
+    ///     Chain::Mainnet.etherscan_urls(),
+    ///     Some(("https://api.etherscan.io/api", "https://etherscan.io"))
+    /// );
+    /// assert_eq!(
+    ///     Chain::Avalanche.etherscan_urls(),
+    ///     Some(("https://api.snowtrace.io/api", "https://snowtrace.io"))
+    /// );
+    /// assert_eq!(Chain::AnvilHardhat.etherscan_urls(), None);
+    /// ```
     pub const fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> {
         use Chain::*;
 
@@ -328,51 +408,83 @@ impl Chain {
                 "https://testnet-explorer.canto.neobase.one/",
                 "https://testnet-explorer.canto.neobase.one/api",
             ),
-            AnvilHardhat | Dev | Morden | MoonbeamDev => {
-                // this is explicitly exhaustive so we don't forget to add new urls when adding a
-                // new chain
-                return None
-            }
+
+            // Explicitly exhaustive. See NB above.
+            AnvilHardhat | Dev | Morden | MoonbeamDev => return None,
         };
 
         Some(urls)
     }
 
-    /// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
-    pub const fn is_legacy(&self) -> bool {
+    /// Returns the chain's blockchain explorer's API key environment variable's default name.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use ethers_core::types::Chain;
+    ///
+    /// assert_eq!(Chain::Mainnet.etherscan_api_key_name(), Some("ETHERSCAN_API_KEY"));
+    /// assert_eq!(Chain::AnvilHardhat.etherscan_api_key_name(), None);
+    /// ```
+    pub const fn etherscan_api_key_name(&self) -> Option<&'static str> {
         use Chain::*;
 
-        match self {
-            // Known legacy chains / non EIP-1559 compliant
+        let api_key_name = match self {
+            Mainnet |
+            Morden |
+            Ropsten |
+            Kovan |
+            Rinkeby |
+            Goerli |
             Optimism |
             OptimismGoerli |
             OptimismKovan |
-            Fantom |
-            FantomTestnet |
             BinanceSmartChain |
             BinanceSmartChainTestnet |
             Arbitrum |
             ArbitrumTestnet |
             ArbitrumGoerli |
             ArbitrumNova |
-            Rsk |
-            Oasis |
-            Emerald |
-            EmeraldTestnet |
+            Cronos |
+            CronosTestnet |
+            Aurora |
+            AuroraTestnet |
             Celo |
             CeloAlfajores |
-            CeloBaklava => true,
+            CeloBaklava => "ETHERSCAN_API_KEY",
 
-            // Known EIP-1559 chains
-            Mainnet | Goerli | Sepolia | Polygon | PolygonMumbai | Avalanche | AvalancheFuji => {
-                false
-            }
+            Avalanche | AvalancheFuji => "SNOWTRACE_API_KEY",
 
-            // Unknown / not applicable, default to false for backwards compatibility
-            Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan |
-            Sokol | Poa | XDai | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos |
-            EvmosTestnet | Chiado | Aurora | AuroraTestnet | Canto | CantoTestnet => false,
-        }
+            Polygon | PolygonMumbai => "POLYGONSCAN_API_KEY",
+
+            Fantom | FantomTestnet => "FTMSCAN_API_KEY",
+
+            Moonbeam | Moonbase | MoonbeamDev | Moonriver => "MOONSCAN_API_KEY",
+
+            Canto | CantoTestnet => "BLOCKSCOUT_API_KEY",
+
+            // Explicitly exhaustive. See NB above.
+            XDai | Chiado | Sepolia | Rsk | Sokol | Poa | Oasis | Emerald | EmeraldTestnet |
+            Evmos | EvmosTestnet | AnvilHardhat | Dev => return None,
+        };
+
+        Some(api_key_name)
+    }
+
+    /// Returns the chain's blockchain explorer's API key, from the environment variable with the
+    /// name specified in [`etherscan_api_key_name`](Chain::etherscan_api_key_name).
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use ethers_core::types::Chain;
+    ///
+    /// let chain = Chain::Mainnet;
+    /// std::env::set_var(chain.etherscan_api_key_name().unwrap(), "KEY");
+    /// assert_eq!(chain.etherscan_api_key().as_deref(), Some("KEY"));
+    /// ```
+    pub fn etherscan_api_key(&self) -> Option<String> {
+        self.etherscan_api_key_name().and_then(|name| std::env::var(name).ok())
     }
 }
 
diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs
index 99eeadb5..ce00d1c8 100644
--- a/ethers-core/src/types/mod.rs
+++ b/ethers-core/src/types/mod.rs
@@ -1,3 +1,5 @@
+//! Ethereum data types.
+
 pub type Selector = [u8; 4];
 
 // Re-export common ethereum datatypes with more specific names
diff --git a/ethers-etherscan/Cargo.toml b/ethers-etherscan/Cargo.toml
index 1c5b373d..5e6c724e 100644
--- a/ethers-etherscan/Cargo.toml
+++ b/ethers-etherscan/Cargo.toml
@@ -33,7 +33,7 @@ semver = "1.0.16"
 getrandom = { version = "0.2", features = ["js"] }
 
 [dev-dependencies]
-tempfile = "3.3.0"
+tempfile = "3.4.0"
 tokio = { version = "1.18", features = ["macros", "rt-multi-thread", "time"] }
 serial_test = "0.10.0"
 tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] }
diff --git a/ethers-etherscan/README.md b/ethers-etherscan/README.md
new file mode 100644
index 00000000..d9e3dce4
--- /dev/null
+++ b/ethers-etherscan/README.md
@@ -0,0 +1,22 @@
+# ethers-etherscan
+
+Bindings for the [etherscan.io web API](https://docs.etherscan.io).
+
+For more information, please refer to the [book](https://gakonst.com/ethers-rs).
+
+## Examples
+
+```rust,no_run
+# use ethers_core::types::Chain;
+# use ethers_etherscan::Client;
+# async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+let client = Client::new(Chain::Mainnet, "<your_api_key>")?;
+// Or using environment variables
+let client = Client::new_from_env(Chain::Mainnet)?;
+
+let address = "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse()?;
+let metadata = client.contract_source_code(address).await?;
+assert_eq!(metadata.items[0].contract_name, "DAO");
+# Ok(())
+# }
+```
diff --git a/ethers-etherscan/src/contract.rs b/ethers-etherscan/src/contract.rs
index 2b5adb23..4e80bbf8 100644
--- a/ethers-etherscan/src/contract.rs
+++ b/ethers-etherscan/src/contract.rs
@@ -363,14 +363,12 @@ impl Client {
     /// ```no_run
     /// # use ethers_etherscan::Client;
     /// # use ethers_core::types::Chain;
-    ///
-    /// # #[tokio::main]
-    /// # async fn main() {
-    ///     let client = Client::new(Chain::Mainnet, "API_KEY").unwrap();
-    ///     let meta = client
-    ///         .contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap())
-    ///         .await.unwrap();
-    ///     let code = meta.source_code();
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let client = Client::new(Chain::Mainnet, "<your_api_key>")?;
+    /// let address = "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse()?;
+    /// let metadata = client.contract_source_code(address).await?;
+    /// assert_eq!(metadata.items[0].contract_name, "DAO");
+    /// # Ok(())
     /// # }
     /// ```
     pub async fn contract_source_code(&self, address: Address) -> Result<ContractMetadata> {
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index 27682909..d6fd450c 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -1,4 +1,5 @@
-//! Bindings for [etherscan.io web api](https://docs.etherscan.io/)
+#![doc = include_str!("../README.md")]
+#![deny(unsafe_code, rustdoc::broken_intra_doc_links)]
 
 use crate::errors::{is_blocked_by_cloudflare_response, is_cloudflare_security_challenge};
 use contract::ContractMetadata;
@@ -45,6 +46,7 @@ pub struct Client {
 
 impl Client {
     /// Creates a `ClientBuilder` to configure a `Client`.
+    ///
     /// This is the same as `ClientBuilder::default()`.
     ///
     /// # Example
@@ -76,36 +78,15 @@ impl Client {
     }
 
     /// Create a new client with the correct endpoints based on the chain and API key
-    /// from ETHERSCAN_API_KEY environment variable
+    /// from the default environment variable defined in [`Chain`].
     pub fn new_from_env(chain: Chain) -> Result<Self> {
         let api_key = match chain {
-            Chain::Avalanche | Chain::AvalancheFuji => std::env::var("SNOWTRACE_API_KEY")?,
-            Chain::Polygon | Chain::PolygonMumbai => std::env::var("POLYGONSCAN_API_KEY")?,
-            Chain::Mainnet |
-            Chain::Morden |
-            Chain::Ropsten |
-            Chain::Kovan |
-            Chain::Rinkeby |
-            Chain::Goerli |
-            Chain::Optimism |
-            Chain::OptimismGoerli |
-            Chain::OptimismKovan |
-            Chain::BinanceSmartChain |
-            Chain::BinanceSmartChainTestnet |
-            Chain::Arbitrum |
-            Chain::ArbitrumTestnet |
-            Chain::ArbitrumGoerli |
-            Chain::ArbitrumNova |
-            Chain::Cronos |
-            Chain::CronosTestnet |
-            Chain::Aurora |
-            Chain::AuroraTestnet |
-            Chain::Celo |
-            Chain::CeloAlfajores |
-            Chain::CeloBaklava => std::env::var("ETHERSCAN_API_KEY")?,
-            Chain::Fantom | Chain::FantomTestnet => {
-                std::env::var("FTMSCAN_API_KEY").or_else(|_| std::env::var("FANTOMSCAN_API_KEY"))?
-            }
+            // Extra aliases
+            Chain::Fantom | Chain::FantomTestnet => std::env::var("FMTSCAN_API_KEY")
+                .or_else(|_| std::env::var("FANTOMSCAN_API_KEY"))
+                .map_err(Into::into),
+
+            // Backwards compatibility, ideally these should return an error.
             Chain::XDai |
             Chain::Chiado |
             Chain::Sepolia |
@@ -116,15 +97,14 @@ impl Client {
             Chain::Emerald |
             Chain::EmeraldTestnet |
             Chain::Evmos |
-            Chain::EvmosTestnet => String::default(),
-            Chain::Moonbeam | Chain::Moonbase | Chain::MoonbeamDev | Chain::Moonriver => {
-                std::env::var("MOONSCAN_API_KEY")?
-            }
-            Chain::Canto | Chain::CantoTestnet => std::env::var("BLOCKSCOUT_API_KEY")?,
-            Chain::AnvilHardhat | Chain::Dev => {
-                return Err(EtherscanError::LocalNetworksNotSupported)
-            }
-        };
+            Chain::EvmosTestnet => Ok(String::new()),
+            Chain::AnvilHardhat | Chain::Dev => Err(EtherscanError::LocalNetworksNotSupported),
+
+            _ => chain
+                .etherscan_api_key_name()
+                .ok_or_else(|| EtherscanError::ChainNotSupported(chain))
+                .and_then(|key_name| std::env::var(key_name).map_err(Into::into)),
+        }?;
         Self::new(chain, api_key)
     }
 
diff --git a/ethers-etherscan/src/utils.rs b/ethers-etherscan/src/utils.rs
index 5549b1a9..a587fcd7 100644
--- a/ethers-etherscan/src/utils.rs
+++ b/ethers-etherscan/src/utils.rs
@@ -3,8 +3,7 @@ use ethers_core::types::Address;
 use semver::Version;
 use serde::{Deserialize, Deserializer};
 
-static SOLC_BIN_LIST_URL: &str =
-    "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.txt";
+static SOLC_BIN_LIST_URL: &str = "https://binaries.soliditylang.org/bin/list.txt";
 
 /// Given a Solc [Version], lookup the build metadata and return the full SemVer.
 /// e.g. `0.8.13` -> `0.8.13+commit.abaa5c0e`
diff --git a/ethers-middleware/Cargo.toml b/ethers-middleware/Cargo.toml
index 714e01ff..6cb746a5 100644
--- a/ethers-middleware/Cargo.toml
+++ b/ethers-middleware/Cargo.toml
@@ -22,7 +22,7 @@ ethers-providers = { version = "^1.0.0", path = "../ethers-providers", default-f
 ethers-signers = { version = "^1.0.0", path = "../ethers-signers", default-features = false }
 
 async-trait = { version = "0.1.50", default-features = false }
-auto_impl = { version = "0.5.0", default-features = false }
+auto_impl = { version = "1.0.1", default-features = false }
 serde = { version = "1.0.124", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0", default-features = false }
 futures-util = { version = "^0.3" }
diff --git a/ethers-middleware/README.md b/ethers-middleware/README.md
index 75a9b328..c66835e0 100644
--- a/ethers-middleware/README.md
+++ b/ethers-middleware/README.md
@@ -1,93 +1,83 @@
+# ethers-middleware
+
 Your ethers application interacts with the blockchain through a [`Provider`](ethers_providers::Provider) abstraction. [`Provider`](ethers_providers::Provider) is a special type of [`Middleware`](ethers_providers::Middleware) that can be composed with others to obtain a layered architecture. This approach promotes "Open Closed Principle", "Single Responsibility" and composable patterns. The building process happens in a wrapping fashion, and starts from a [`Provider`](ethers_providers::Provider) being the first element in the stack. This process continues having new middlewares being pushed on top of a layered data structure.
 
+For more information, please refer to the [book](https://gakonst.com/ethers-rs).
 
 ## Available Middleware
 
-- [`Signer`](./signer/struct.SignerMiddleware.html): Signs transactions locally, with a private key or a hardware wallet.
-- [`Nonce Manager`](./nonce_manager/struct.NonceManagerMiddleware.html): Manages nonces locally. Allows to sign multiple consecutive transactions without waiting for them to hit the mempool.
-- [`Gas Escalator`](./gas_escalator/struct.GasEscalatorMiddleware.html): Bumps transactions gas price in the background to avoid getting them stuck in the memory pool. A [`GasEscalatorMiddleware`](crate::gas_escalator::GasEscalatorMiddleware) supports different escalation strategies (see [GasEscalator](crate::gas_escalator::GasEscalator)) and bump frequencies (see [Frequency](crate::gas_escalator::Frequency)).
-- [`Gas Oracle`](./gas_oracle/struct.GasOracleMiddleware.html): Allows getting
-  your gas price estimates from places other than `eth_gasPrice`, including REST based gas stations (i.e. Etherscan, ETH Gas Station etc.).
-- [`Transformer`](./transformer/trait.Transformer.html): Allows intercepting and
-  transforming a transaction to be broadcasted via a proxy wallet, e.g.
-  [`DSProxy`](./transformer/struct.DsProxy.html).
+-   [`Signer`](./signer/struct.SignerMiddleware.html): Signs transactions locally, with a private key or a hardware wallet.
+-   [`Nonce Manager`](./nonce_manager/struct.NonceManagerMiddleware.html): Manages nonces locally. Allows to sign multiple consecutive transactions without waiting for them to hit the mempool.
+-   [`Gas Escalator`](./gas_escalator/struct.GasEscalatorMiddleware.html): Bumps transactions gas price in the background to avoid getting them stuck in the memory pool. A [`GasEscalatorMiddleware`](crate::gas_escalator::GasEscalatorMiddleware) supports different escalation strategies (see [GasEscalator](crate::gas_escalator::GasEscalator)) and bump frequencies (see [Frequency](crate::gas_escalator::Frequency)).
+-   [`Gas Oracle`](./gas_oracle/struct.GasOracleMiddleware.html): Allows getting
+    your gas price estimates from places other than `eth_gasPrice`, including REST based gas stations (i.e. Etherscan, ETH Gas Station etc.).
+-   [`Transformer`](./transformer/trait.Transformer.html): Allows intercepting and
+    transforming a transaction to be broadcasted via a proxy wallet, e.g.
+    [`DSProxy`](./transformer/struct.DsProxy.html).
 
-## Stacking middlewares using a builder
+## Examples
 
 Each [`Middleware`](ethers_providers::Middleware) implements the trait [MiddlewareBuilder](crate::MiddlewareBuilder). This trait helps a developer to compose a custom [`Middleware`](ethers_providers::Middleware) stack.
 
 The following example shows how to build a composed [`Middleware`](ethers_providers::Middleware) starting from a [`Provider`](ethers_providers::Provider):
 
 ```rust
-use ethers_providers::{Middleware, Provider, Http};
-use std::sync::Arc;
-use std::convert::TryFrom;
-use ethers_signers::{LocalWallet, Signer};
-use ethers_middleware::{gas_oracle::{GasOracle, GasNow}, MiddlewareBuilder};
-
-fn builder_example() {
-    let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
-    let signer = key.parse::<LocalWallet>().unwrap();
-    let address = signer.address();
-    let gas_oracle = GasNow::new();
-
-    let provider = Provider::<Http>::try_from("http://localhost:8545")
-        .unwrap()
-        .gas_oracle(gas_oracle)
-        .with_signer(signer)
-        .nonce_manager(address); // Outermost layer
-}
+# use ethers_providers::{Middleware, Provider, Http};
+# use ethers_signers::{LocalWallet, Signer};
+# use ethers_middleware::{gas_oracle::GasNow, MiddlewareBuilder};
+let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
+let signer = key.parse::<LocalWallet>()?;
+let address = signer.address();
+let gas_oracle = GasNow::new();
+
+let provider = Provider::<Http>::try_from("http://localhost:8545")?
+    .gas_oracle(gas_oracle)
+    .with_signer(signer)
+    .nonce_manager(address); // Outermost layer
+# Ok::<_, Box<dyn std::error::Error>>(())
 ```
 
 The [wrap_into](crate::MiddlewareBuilder::wrap_into) function can be used to wrap [`Middleware`](ethers_providers::Middleware) layers explicitly. This is useful when pushing [`Middleware`](ethers_providers::Middleware)s not directly handled by the builder interface.
 
-```rust
-use ethers_providers::{Middleware, Provider, Http};
-use std::sync::Arc;
-use std::convert::TryFrom;
-use ethers_signers::{LocalWallet, Signer};
-use ethers_middleware::{*,gas_escalator::*,gas_oracle::*};
-
-fn builder_example_wrap_into() {
-    let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
-    let signer = key.parse::<LocalWallet>().unwrap();
-    let address = signer.address();
-    let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
-
-    let provider = Provider::<Http>::try_from("http://localhost:8545")
-        .unwrap()
-        .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
-        .wrap_into(|p| SignerMiddleware::new(p, signer))
-        .wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
-        .wrap_into(|p| NonceManagerMiddleware::new(p, address)); // Outermost layer
-}
+```rust,no_run
+# use ethers_providers::{Middleware, Provider, Http};
+# use std::convert::TryFrom;
+# use ethers_signers::{LocalWallet, Signer};
+# use ethers_middleware::{*,gas_escalator::*,gas_oracle::*};
+let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
+let signer = key.parse::<LocalWallet>()?;
+let address = signer.address();
+let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
+
+let provider = Provider::<Http>::try_from("http://localhost:8545")?
+    .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
+    .wrap_into(|p| SignerMiddleware::new(p, signer))
+    .wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
+    .wrap_into(|p| NonceManagerMiddleware::new(p, address)); // Outermost layer
+# Ok::<_, Box<dyn std::error::Error>>(())
 ```
 
-
-## Stacking middlewares manually
 A [`Middleware`](ethers_providers::Middleware) stack can be also constructed manually. This is achieved by explicitly wrapping layers.
 
-```rust no_run
-use ethers_providers::{Provider, Http};
-use ethers_signers::{LocalWallet, Signer};
-use ethers_middleware::{
-    gas_escalator::{GasEscalatorMiddleware, GeometricGasPrice, Frequency},
-    gas_oracle::{GasOracleMiddleware, GasCategory, GasNow},
-    signer::SignerMiddleware,
-    nonce_manager::NonceManagerMiddleware,
-};
-use ethers_core::rand;
-use std::convert::TryFrom;
-
+```rust,no_run
+# use ethers_providers::{Provider, Http};
+# use ethers_signers::{LocalWallet, Signer};
+# use ethers_middleware::{
+#     gas_escalator::{GasEscalatorMiddleware, GeometricGasPrice, Frequency},
+#     gas_oracle::{GasOracleMiddleware, GasCategory, GasNow},
+#     signer::SignerMiddleware,
+#     nonce_manager::NonceManagerMiddleware,
+# };
 // Start the stack
-let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
+let provider = Provider::<Http>::try_from("http://localhost:8545")?;
 
 // Escalate gas prices
 let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
 let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
 
 // Sign transactions with a private key
-let signer = LocalWallet::new(&mut rand::thread_rng());
+let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
+let signer = key.parse::<LocalWallet>()?;
 let address = signer.address();
 let provider = SignerMiddleware::new(provider, signer);
 
@@ -97,6 +87,5 @@ let provider = GasOracleMiddleware::new(provider, gas_oracle);
 
 // Manage nonces locally
 let provider = NonceManagerMiddleware::new(provider, address);
-
-// ... do something with the provider
+# Ok::<_, Box<dyn std::error::Error>>(())
 ```
diff --git a/ethers-middleware/src/gas_oracle/median.rs b/ethers-middleware/src/gas_oracle/median.rs
index c6dd6c7e..def9c55f 100644
--- a/ethers-middleware/src/gas_oracle/median.rs
+++ b/ethers-middleware/src/gas_oracle/median.rs
@@ -75,21 +75,20 @@ impl GasOracle for Median {
     }
 }
 
-/// Weighted fractile by key
+/// Weighted fractile by key.
 ///
-/// Sort the values in place by key and return the weighted fractile value such
-/// that `fractile` fraction of the values by weight are less than or equal to
-/// the value.
+/// Sort the values in place by key and return the weighted fractile value such that `fractile`
+/// fraction of the values by weight are less than or equal to the value.
 ///
-/// Returns None if the values are empty.
+/// Returns `None` if the values are empty.
 ///
 /// Note: it doesn't handle NaNs or other special float values.
 ///
-/// See <https://en.wikipedia.org/wiki/Percentile#The_weighted_percentile_method>
+/// See: <https://en.wikipedia.org/wiki/Percentile#The_weighted_percentile_method>
 ///
 /// # Panics
 ///
-/// Panics if [`fractile`] is not in the range $[0, 1]$.
+/// Panics if `fractile` is not in the range `0.0..=1.0`.
 fn weighted_fractile_by_key<'a, T, F, K>(
     fractile: f32,
     values: &'a mut [(f32, T)],
diff --git a/ethers-middleware/src/lib.rs b/ethers-middleware/src/lib.rs
index 8593a10b..88aa042f 100644
--- a/ethers-middleware/src/lib.rs
+++ b/ethers-middleware/src/lib.rs
@@ -1,6 +1,5 @@
 #![doc = include_str!("../README.md")]
-#![deny(unsafe_code)]
-#![deny(rustdoc::broken_intra_doc_links)]
+#![deny(unsafe_code, rustdoc::broken_intra_doc_links)]
 
 /// The [Gas Escalator middleware](crate::gas_escalator::GasEscalatorMiddleware)
 /// is used to re-broadcast transactions with an increasing gas price to guarantee
diff --git a/ethers-providers/Cargo.toml b/ethers-providers/Cargo.toml
index 99f06670..ed529c59 100644
--- a/ethers-providers/Cargo.toml
+++ b/ethers-providers/Cargo.toml
@@ -69,7 +69,7 @@ getrandom = { version = "0.2", features = ["js"] }
 
 [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
 tokio = { version = "1.18", default-features = false, features = ["rt", "macros", "time"] }
-tempfile = "3.3.0"
+tempfile = "3.4.0"
 
 [features]
 default = ["ws", "rustls"]
diff --git a/ethers-providers/README.md b/ethers-providers/README.md
index 7e0a38fe..820eeb83 100644
--- a/ethers-providers/README.md
+++ b/ethers-providers/README.md
@@ -1,24 +1,58 @@
-# Clients for interacting with Ethereum nodes
+# ethers-providers
+
+Clients for interacting with Ethereum nodes.
 
 This crate provides asynchronous
 [Ethereum JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) compliant
 clients.
 
-For more documentation on the available calls, refer to the
-[`Provider`](./struct.Provider.html) struct.
+For more information, please refer to the [book](https://gakonst.com/ethers-rs).
+
+## Websockets
+
+This crate supports for WebSockets via `tokio-tungstenite`.
+Please ensure that you have the `ws` feature enabled if you wish to use WebSockets:
+
+```toml
+[dependencies]
+ethers-providers = { version = "1.0.2", features = ["ws"] }
+```
+
+## Interprocess Communication (IPC)
+
+This crate supports for Interprocess Communication via Unix sockets and Windows named pipes.
+Please ensure that you have the `ipc` feature enabled if you wish to use IPC:
+
+```toml
+[dependencies]
+ethers-providers = { version = "1.0.2", features = ["ipc"] }
+```
+
+## Ethereum Name Service
+
+The provider may also be used to resolve [Ethereum Name Service](https://ens.domains) (ENS) names
+to addresses (and vice versa).
+The default ENS address is [`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`][ens]
+and can be overriden with the [`ens`](./struct.Provider.html#method.ens) method on the provider.
 
-# Examples
+[ens]: https://etherscan.io/address/0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e
 
-```no_run
-use ethers_core::types::Address;
-use ethers_providers::{Provider, Http, Middleware};
-use std::convert::TryFrom;
+## Examples
 
+```rust,no_run
+# use ethers_core::types::Address;
+# use ethers_providers::{Provider, Http, Middleware, Ws};
 # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+// HTTP Provider
 let provider = Provider::<Http>::try_from(
     "https://mainnet.infura.io/v3/YOUR_API_KEY"
 )?;
 
+// Websocket Provider
+let provider = Provider::<Ws>::connect(
+    "wss://mainnet.infura.io/v3/YOUR_API_KEY"
+).await?;
+
 let block = provider.get_block(100u64).await?;
 println!("Got block: {}", serde_json::to_string(&block)?);
 
@@ -29,30 +63,10 @@ println!("Got code: {}", serde_json::to_string(&code)?);
 # }
 ```
 
-# Websockets
-
-The crate has support for WebSockets via Tokio. Please ensure that you have the "ws" and "rustls" / "openssl" features enabled if you wish to use WebSockets.
-
-```
-# async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-# use ethers_providers::Ws;
-let ws = Ws::connect("ws://localhost:8545").await?;
-# Ok(())
-# }
-```
-
-# Ethereum Name Service
-
-The provider may also be used to resolve
-[Ethereum Name Service](https://ens.domains) (ENS) names to addresses (and vice
-versa). The default ENS address is
-[mainnet](https://etherscan.io/address/0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e)
-and can be overriden by calling the [`ens`](./struct.Provider.html#method.ens)
-method on the provider.
+Using ENS:
 
-```no_run
+```rust,no_run
 # use ethers_providers::{Provider, Http, Middleware};
-# use std::convert::TryFrom;
 # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
 # let provider = Provider::<Http>::try_from(
 #     "https://mainnet.infura.io/v3/YOUR_API_KEY"
diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs
index 50c64bd2..a49a78d6 100644
--- a/ethers-providers/src/lib.rs
+++ b/ethers-providers/src/lib.rs
@@ -1,9 +1,8 @@
+#![doc = include_str!("../README.md")]
 #![cfg_attr(docsrs, feature(doc_cfg))]
-#![deny(unsafe_code)]
-#![deny(rustdoc::broken_intra_doc_links)]
 #![allow(clippy::type_complexity)]
-#![doc = include_str!("../README.md")]
 #![warn(missing_docs)]
+#![deny(unsafe_code, rustdoc::broken_intra_doc_links)]
 
 mod ext;
 pub use ext::*;
diff --git a/ethers-providers/src/rpc/transports/common.rs b/ethers-providers/src/rpc/transports/common.rs
index 9caab6b1..072cf7d6 100644
--- a/ethers-providers/src/rpc/transports/common.rs
+++ b/ethers-providers/src/rpc/transports/common.rs
@@ -24,9 +24,10 @@ pub struct JsonRpcError {
     pub data: Option<Value>,
 }
 
-/// Recursively traverses the value, looking for hex data that it can extract
+/// Recursively traverses the value, looking for hex data that it can extract.
+///
 /// Inspired by ethers-js logic:
-/// https://github.com/ethers-io/ethers.js/blob/9f990c57f0486728902d4b8e049536f2bb3487ee/packages/providers/src.ts/json-rpc-provider.ts#L25-L53
+/// <https://github.com/ethers-io/ethers.js/blob/9f990c57f0486728902d4b8e049536f2bb3487ee/packages/providers/src.ts/json-rpc-provider.ts#L25-L53>
 fn spelunk_revert(value: &Value) -> Option<Bytes> {
     match value {
         Value::String(s) => s.parse().ok(),
diff --git a/ethers-signers/Cargo.toml b/ethers-signers/Cargo.toml
index 57a4d25e..505db6a7 100644
--- a/ethers-signers/Cargo.toml
+++ b/ethers-signers/Cargo.toml
@@ -29,6 +29,7 @@ yubihsm = { version = "0.41.0", features = ["secp256k1", "http", "usb"], optiona
 futures-util = { version = "^0.3", optional = true }
 futures-executor = { version = "^0.3", optional = true }
 semver = { version = "1.0.16", optional = true }
+tracing = { version = "0.1.37" }
 trezor-client = { version = "0.0.7", optional = true, default-features = false, features = [
     "f_ethereum",
 ] }
@@ -36,7 +37,6 @@ trezor-client = { version = "0.0.7", optional = true, default-features = false,
 # aws
 rusoto_core = { version = "0.48.0", default-features = false, optional = true }
 rusoto_kms = { version = "0.48.0", default-features = false, optional = true }
-tracing = { version = "0.1.37", optional = true }
 spki = { version = "0.6.0", optional = true }
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
@@ -53,12 +53,12 @@ yubihsm = { version = "0.41.0", features = ["secp256k1", "usb", "mockhsm"] }
 [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
 yubihsm = { version = "0.41.0", features = ["secp256k1", "usb", "mockhsm"] }
 tokio = { version = "1.18", default-features = false, features = ["macros", "rt"] }
-tempfile = "3.3.0"
+tempfile = "3.4.0"
 
 [features]
 futures = ["futures-util", "futures-executor"]
 celo = ["ethers-core/celo"]
 ledger = ["coins-ledger", "futures", "semver"]
 yubi = ["yubihsm"]
-aws = ["rusoto_core/rustls", "rusoto_kms/rustls", "tracing", "spki"]
+aws = ["rusoto_core/rustls", "rusoto_kms/rustls", "spki"]
 trezor = ["trezor-client", "futures", "semver", "home"]
diff --git a/ethers-signers/README.md b/ethers-signers/README.md
index 227d2cb4..a3c45cbd 100644
--- a/ethers-signers/README.md
+++ b/ethers-signers/README.md
@@ -1,3 +1,7 @@
+# ethers-signers
+
+A unified interface for locally signing transactions.
+
 You can implement the `Signer` trait to extend functionality to other signers
 such as Hardware Security Modules, KMS etc.
 
@@ -13,10 +17,16 @@ Supported signers:
 -   [YubiHSM2](./src/wallet/yubi.rs)
 -   [AWS KMS](./src/aws)
 
-```no_run
+For more information, please refer to the [book](https://gakonst.com/ethers-rs).
+
+[`transaction`]: ethers_core::types::Transaction
+[`transactionrequest`]: ethers_core::types::TransactionRequest
+
+## Examples
+
+```rust,no_run
 # use ethers_signers::{LocalWallet, Signer};
 # use ethers_core::{k256::ecdsa::SigningKey, types::TransactionRequest};
-
 # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
 // instantiate the wallet
 let wallet = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7"
@@ -37,5 +47,21 @@ signature.verify("hello world", wallet.address()).unwrap();
 # }
 ```
 
-[`transaction`]: ethers_core::types::Transaction
-[`transactionrequest`]: ethers_core::types::TransactionRequest
+Sign an Ethereum prefixed message ([eip-712](https://eips.ethereum.org/EIPS/eip-712)):
+
+```rust,no_run
+# use ethers_signers::{Signer, LocalWallet};
+# async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+let message = "Some data";
+let wallet = LocalWallet::new(&mut rand::thread_rng());
+
+// Sign the message
+let signature = wallet.sign_message(message).await?;
+
+// Recover the signer from the message
+let recovered = signature.recover(message)?;
+
+assert_eq!(recovered, wallet.address());
+# Ok(())
+# }
+```
diff --git a/ethers-signers/src/ledger/app.rs b/ethers-signers/src/ledger/app.rs
index 5932f0b8..05adbc2f 100644
--- a/ethers-signers/src/ledger/app.rs
+++ b/ethers-signers/src/ledger/app.rs
@@ -29,6 +29,16 @@ pub struct LedgerEthereum {
     pub(crate) address: Address,
 }
 
+impl std::fmt::Display for LedgerEthereum {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "LedgerApp. Key at index {} with address {:?} on chain_id {}",
+            self.derivation, self.address, self.chain_id
+        )
+    }
+}
+
 const EIP712_MIN_VERSION: &str = ">=1.6.0";
 
 impl LedgerEthereum {
@@ -68,6 +78,7 @@ impl LedgerEthereum {
         Self::get_address_with_path_transport(&transport, derivation).await
     }
 
+    #[tracing::instrument(skip(transport))]
     async fn get_address_with_path_transport(
         transport: &Ledger,
         derivation: &DerivationType,
@@ -82,6 +93,7 @@ impl LedgerEthereum {
             response_len: None,
         };
 
+        tracing::debug!("Dispatching get_address request to ethereum app");
         let answer = block_on(transport.exchange(&command))?;
         let result = answer.data().ok_or(LedgerError::UnexpectedNullResponse)?;
 
@@ -93,7 +105,7 @@ impl LedgerEthereum {
             address.copy_from_slice(&hex::decode(address_str)?);
             Address::from(address)
         };
-
+        tracing::debug!(?address, "Received address from device");
         Ok(address)
     }
 
@@ -109,10 +121,15 @@ impl LedgerEthereum {
             response_len: None,
         };
 
+        tracing::debug!("Dispatching get_version");
         let answer = block_on(transport.exchange(&command))?;
         let result = answer.data().ok_or(LedgerError::UnexpectedNullResponse)?;
-
-        Ok(format!("{}.{}.{}", result[1], result[2], result[3]))
+        if result.len() < 4 {
+            return Err(LedgerError::ShortResponse { got: result.len(), at_least: 4 })
+        }
+        let version = format!("{}.{}.{}", result[1], result[2], result[3]);
+        tracing::debug!(version, "Retrieved version from device");
+        Ok(version)
     }
 
     /// Signs an Ethereum transaction (requires confirmation on the ledger)
@@ -125,7 +142,7 @@ impl LedgerEthereum {
         let mut payload = Self::path_to_bytes(&self.derivation);
         payload.extend_from_slice(tx_with_chain.rlp().as_ref());
 
-        let mut signature = self.sign_payload(INS::SIGN, payload).await?;
+        let mut signature = self.sign_payload(INS::SIGN, &payload).await?;
 
         // modify `v` value of signature to match EIP-155 for chains with large chain ID
         // The logic is derived from Ledger's library
@@ -158,7 +175,7 @@ impl LedgerEthereum {
         payload.extend_from_slice(&(message.len() as u32).to_be_bytes());
         payload.extend_from_slice(message);
 
-        self.sign_payload(INS::SIGN_PERSONAL_MESSAGE, payload).await
+        self.sign_payload(INS::SIGN_PERSONAL_MESSAGE, &payload).await
     }
 
     /// Signs an EIP712 encoded domain separator and message
@@ -185,16 +202,20 @@ impl LedgerEthereum {
         payload.extend_from_slice(&domain_separator);
         payload.extend_from_slice(&struct_hash);
 
-        self.sign_payload(INS::SIGN_ETH_EIP_712, payload).await
+        self.sign_payload(INS::SIGN_ETH_EIP_712, &payload).await
     }
 
+    #[tracing::instrument(err, skip_all, fields(command = %command, payload = hex::encode(payload)))]
     // Helper function for signing either transaction data, personal messages or EIP712 derived
     // structs
     pub async fn sign_payload(
         &self,
         command: INS,
-        mut payload: Vec<u8>,
+        payload: &Vec<u8>,
     ) -> Result<Signature, LedgerError> {
+        if payload.is_empty() {
+            return Err(LedgerError::EmptyPayload)
+        }
         let transport = self.transport.lock().await;
         let mut command = APDUCommand {
             ins: command as u8,
@@ -204,25 +225,47 @@ impl LedgerEthereum {
             response_len: None,
         };
 
-        let mut result = Vec::new();
+        let mut answer = None;
+        // workaround for https://github.com/LedgerHQ/app-ethereum/issues/409
+        // TODO: remove in future version
+        let chunk_size =
+            (0..=255).rev().find(|i| payload.len() % i != 3).expect("true for any length");
 
         // Iterate in 255 byte chunks
-        while !payload.is_empty() {
-            let chunk_size = std::cmp::min(payload.len(), 255);
-            let data = payload.drain(0..chunk_size).collect::<Vec<_>>();
-            command.data = APDUData::new(&data);
-
-            let answer = block_on(transport.exchange(&command))?;
-            result = answer.data().ok_or(LedgerError::UnexpectedNullResponse)?.to_vec();
+        let span = tracing::debug_span!("send_loop", index = 0, chunk = "");
+        let guard = span.entered();
+        for (index, chunk) in payload.chunks(chunk_size).enumerate() {
+            guard.record("index", index);
+            guard.record("chunk", hex::encode(chunk));
+            command.data = APDUData::new(chunk);
+
+            tracing::debug!("Dispatching packet to device");
+            answer = Some(block_on(transport.exchange(&command))?);
+
+            let data = answer.as_ref().expect("just assigned").data();
+            if data.is_none() {
+                return Err(LedgerError::UnexpectedNullResponse)
+            }
+            tracing::debug!(
+                response = hex::encode(data.expect("just checked")),
+                "Received response from device"
+            );
 
             // We need more data
             command.p1 = P1::MORE as u8;
         }
-
+        drop(guard);
+        let answer = answer.expect("payload is non-empty, therefore loop ran");
+        let result = answer.data().expect("check in loop");
+        if result.len() < 65 {
+            return Err(LedgerError::ShortResponse { got: result.len(), at_least: 65 })
+        }
         let v = result[0] as u64;
         let r = U256::from_big_endian(&result[1..33]);
         let s = U256::from_big_endian(&result[33..]);
-        Ok(Signature { r, s, v })
+        let sig = Signature { r, s, v };
+        tracing::debug!(sig = %sig, "Received signature from device");
+        Ok(sig)
     }
 
     // helper which converts a derivation path to bytes
diff --git a/ethers-signers/src/ledger/types.rs b/ethers-signers/src/ledger/types.rs
index 73ed6663..b33f6d5c 100644
--- a/ethers-signers/src/ledger/types.rs
+++ b/ethers-signers/src/ledger/types.rs
@@ -38,7 +38,6 @@ pub enum LedgerError {
     /// Device response was unexpectedly none
     #[error("Received unexpected response from device. Expected data in response, found none.")]
     UnexpectedNullResponse,
-
     #[error(transparent)]
     /// Error when converting from a hex string
     HexError(#[from] hex::FromHexError),
@@ -51,6 +50,12 @@ pub enum LedgerError {
     /// Error when signing EIP712 struct with not compatible Ledger ETH app
     #[error("Ledger ethereum app requires at least version: {0:?}")]
     UnsupportedAppVersion(String),
+    /// Got a response, but it didn't contain as much data as expected
+    #[error("Cannot deserialize ledger response, insufficient bytes. Got {got} expected at least {at_least}")]
+    ShortResponse { got: usize, at_least: usize },
+    /// Payload is empty
+    #[error("Payload must not be empty")]
+    EmptyPayload,
 }
 
 pub const P1_FIRST: u8 = 0x00;
@@ -66,6 +71,18 @@ pub enum INS {
     SIGN_ETH_EIP_712 = 0x0C,
 }
 
+impl std::fmt::Display for INS {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            INS::GET_PUBLIC_KEY => write!(f, "GET_PUBLIC_KEY"),
+            INS::SIGN => write!(f, "SIGN"),
+            INS::GET_APP_CONFIGURATION => write!(f, "GET_APP_CONFIGURATION"),
+            INS::SIGN_PERSONAL_MESSAGE => write!(f, "SIGN_PERSONAL_MESSAGE"),
+            INS::SIGN_ETH_EIP_712 => write!(f, "SIGN_ETH_EIP_712"),
+        }
+    }
+}
+
 #[repr(u8)]
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 #[allow(non_camel_case_types)]
diff --git a/ethers-signers/src/lib.rs b/ethers-signers/src/lib.rs
index 5287999f..ed9594be 100644
--- a/ethers-signers/src/lib.rs
+++ b/ethers-signers/src/lib.rs
@@ -1,6 +1,5 @@
-//! Provides a unified interface for locally signing transactions.
-#![deny(unsafe_code)]
-#![deny(rustdoc::broken_intra_doc_links)]
+#![doc = include_str!("../README.md")]
+#![deny(unsafe_code, rustdoc::broken_intra_doc_links)]
 
 mod wallet;
 pub use wallet::{MnemonicBuilder, Wallet, WalletError};
diff --git a/ethers-signers/src/wallet/private_key.rs b/ethers-signers/src/wallet/private_key.rs
index f4b83ef0..1d20cabe 100644
--- a/ethers-signers/src/wallet/private_key.rs
+++ b/ethers-signers/src/wallet/private_key.rs
@@ -161,6 +161,12 @@ mod tests {
     use ethers_core::types::Address;
     use tempfile::tempdir;
 
+    #[test]
+    fn parse_pk() {
+        let s = "6f142508b4eea641e33cb2a0161221105086a84584c74245ca463a49effea30b";
+        let pk: Wallet<SigningKey> = s.parse().unwrap();
+    }
+
     #[tokio::test]
     async fn encrypted_json_keystore() {
         // create and store a random encrypted JSON keystore in this directory
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index 5ecb2615..12c3f38a 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -33,7 +33,7 @@ glob = "0.3.1"
 tracing = "0.1.37"
 num_cpus = "1.15.0"
 tiny-keccak = { version = "2.0.2", default-features = false }
-tempfile = { version = "3.3.0", optional = true }
+tempfile = { version = "3.4.0", optional = true }
 fs_extra = { version = "1.3.0", optional = true }
 sha2 = { version = "0.10.6", default-features = false, optional = true }
 dunce = "1.0.3"
@@ -60,7 +60,7 @@ env_logger = "0.10.0"
 tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] }
 rand = "0.8.5"
 pretty_assertions = "1.3.0"
-tempfile = "3.3.0"
+tempfile = "3.4.0"
 tokio = { version = "1.18", features = ["full"] }
 serde_path_to_error = "0.1.9"
 
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index ddd1f47a..37eb52b1 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -683,6 +683,23 @@ pub struct OptimizerDetails {
     pub yul_details: Option<YulDetails>,
 }
 
+// === impl OptimizerDetails ===
+
+impl OptimizerDetails {
+    /// Returns true if no settings are set.
+    pub fn is_empty(&self) -> bool {
+        self.peephole.is_none() &&
+            self.inliner.is_none() &&
+            self.jumpdest_remover.is_none() &&
+            self.order_literals.is_none() &&
+            self.deduplicate.is_none() &&
+            self.cse.is_none() &&
+            self.constant_optimizer.is_none() &&
+            self.yul.is_none() &&
+            self.yul_details.as_ref().map(|yul| yul.is_empty()).unwrap_or(true)
+    }
+}
+
 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
 #[serde(rename_all = "camelCase")]
 pub struct YulDetails {
@@ -696,6 +713,15 @@ pub struct YulDetails {
     pub optimizer_steps: Option<String>,
 }
 
+// === impl YulDetails ===
+
+impl YulDetails {
+    /// Returns true if no settings are set.
+    pub fn is_empty(&self) -> bool {
+        self.stack_allocation.is_none() && self.optimizer_steps.is_none()
+    }
+}
+
 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
 pub enum EvmVersion {
     Homestead,
diff --git a/ethers-solc/src/cache.rs b/ethers-solc/src/cache.rs
index 0a335fa9..08fba251 100644
--- a/ethers-solc/src/cache.rs
+++ b/ethers-solc/src/cache.rs
@@ -463,7 +463,7 @@ impl CacheEntry {
 
     /// Reads all artifact files associated with the `CacheEntry`
     ///
-    /// **Note:** all artifact file paths should be absolute, see [`Self::join`]
+    /// **Note:** all artifact file paths should be absolute.
     fn read_artifact_files<Artifact: DeserializeOwned>(
         &self,
     ) -> Result<BTreeMap<String, Vec<ArtifactFile<Artifact>>>> {
@@ -579,27 +579,32 @@ impl CacheEntry {
 /// and which `Artifacts` can be reused.
 #[derive(Debug)]
 pub(crate) struct ArtifactsCacheInner<'a, T: ArtifactOutput> {
-    /// preexisting cache file
+    /// The preexisting cache file.
     pub cache: SolFilesCache,
-    /// all already existing artifacts
+
+    /// All already existing artifacts.
     pub cached_artifacts: Artifacts<T::Artifact>,
-    /// relationship between all the files
+
+    /// Relationship between all the files.
     pub edges: GraphEdges,
-    /// the project
+
+    /// The project.
     pub project: &'a Project<T>,
-    /// all files that were filtered because they haven't changed
+
+    /// All the files that were filtered because they haven't changed.
     pub filtered: HashMap<PathBuf, (Source, HashSet<Version>)>,
-    /// the corresponding cache entries for all sources that were deemed to be dirty
+
+    /// The corresponding cache entries for all sources that were deemed to be dirty.
     ///
-    /// `CacheEntry` are grouped by their solidity file.
+    /// `CacheEntry` are grouped by their Solidity file.
     /// During preprocessing the `artifacts` field of a new `CacheEntry` is left blank, because in
     /// order to determine the artifacts of the solidity file, the file needs to be compiled first.
     /// Only after the `CompilerOutput` is received and all compiled contracts are handled, see
-    /// [`crate::ArtifactOutput::on_output()`] all artifacts, their disk paths, are determined and
-    /// can be populated before the updated [`crate::SolFilesCache`] is finally written to disk,
-    /// see [`Cache::finish()`]
+    /// [`crate::ArtifactOutput::on_output`] all artifacts, their disk paths, are determined and
+    /// can be populated before the updated [`crate::SolFilesCache`] is finally written to disk.
     pub dirty_source_files: HashMap<PathBuf, (CacheEntry, HashSet<Version>)>,
-    /// the file hashes
+
+    /// The file hashes.
     pub content_hashes: HashMap<PathBuf, String>,
 }
 
@@ -652,7 +657,7 @@ impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
         }
     }
 
-    /// Returns the set of [Source]s that need to be included in the [CompilerOutput] in order to
+    /// Returns the set of [Source]s that need to be included in the `CompilerOutput` in order to
     /// recompile the project.
     ///
     /// We define _dirty_ sources as files that:
@@ -661,12 +666,12 @@ impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
     ///   - their imports were changed
     ///   - their artifact is missing
     ///
-    /// A _dirty_ file is always included in the [CompilerInput].
+    /// A _dirty_ file is always included in the `CompilerInput`.
     /// A _dirty_ file can also include clean files - files that do not match any of the above
     /// criteria - which solc also requires in order to compile a dirty file.
     ///
     /// Therefore, these files will also be included in the filtered output but not marked as dirty,
-    /// so that their [OutputSelection] can be optimized in the [CompilerOutput] and their (empty)
+    /// so that their `OutputSelection` can be optimized in the `CompilerOutput` and their (empty)
     /// artifacts ignored.
     fn filter(&mut self, sources: Sources, version: &Version) -> FilteredSources {
         // all files that are not dirty themselves, but are pulled from a dirty file
@@ -906,7 +911,7 @@ impl<'a, T: ArtifactOutput> ArtifactsCache<'a, T> {
         }
     }
 
-    /// Consumes the `Cache`, rebuilds the [`SolFileCache`] by merging all artifacts that were
+    /// Consumes the `Cache`, rebuilds the `SolFileCache` by merging all artifacts that were
     /// filtered out in the previous step (`Cache::filtered`) and the artifacts that were just
     /// compiled and written to disk `written_artifacts`.
     ///
diff --git a/ethers-solc/src/compile/project.rs b/ethers-solc/src/compile/project.rs
index 1233ba75..e5f503a4 100644
--- a/ethers-solc/src/compile/project.rs
+++ b/ethers-solc/src/compile/project.rs
@@ -80,11 +80,10 @@
 //! If caching is enabled in the [Project](crate::Project) a cache file will be created upon a
 //! successful solc build. The [cache file](crate::cache::SolFilesCache) stores metadata for all the
 //! files that were provided to solc.
-//! For every file the cache file contains a dedicated [cache
-//! entry](crate::cache::CacheEntry), which represents the state of the file. A solidity file can
-//! contain several contracts, for every contract a separate [artifact](crate::Artifact) is emitted.
-//! Therefor the entry also tracks all artifacts emitted by a file. A solidity file can also be
-//! compiled with several solc versions.
+//! For every file the cache file contains a dedicated [cache entry](crate::cache::CacheEntry),
+//! which represents the state of the file. A solidity file can contain several contracts, for every
+//! contract a separate [artifact](crate::Artifact) is emitted. Therefor the entry also tracks all
+//! artifacts emitted by a file. A solidity file can also be compiled with several solc versions.
 //!
 //! For example in `A(<=0.8.10) imports C(>0.4.0)` and
 //! `B(0.8.11) imports C(>0.4.0)`, both `A` and `B` import `C` but there's no solc version that's
@@ -252,10 +251,12 @@ impl<'a, T: ArtifactOutput> ProjectCompiler<'a, T> {
 /// The main reason is to debug all states individually
 #[derive(Debug)]
 struct PreprocessedState<'a, T: ArtifactOutput> {
-    /// contains all sources to compile
+    /// Contains all the sources to compile.
     sources: FilteredCompilerSources,
-    /// cache that holds [CacheEntry] object if caching is enabled and the project is recompiled
+
+    /// Cache that holds `CacheEntry` objects if caching is enabled and the project is recompiled
     cache: ArtifactsCache<'a, T>,
+
     sparse_output: SparseOutputFilter,
 }
 
diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs
index 84eb4fb1..b224a871 100644
--- a/ethers-solc/src/config.rs
+++ b/ethers-solc/src/config.rs
@@ -76,8 +76,9 @@ impl ProjectPathsConfig {
         }
     }
 
-    /// Same as [Self::paths()] but strips the `root` form all paths,
-    /// [ProjectPaths::strip_prefix_all()]
+    /// Same as [`paths`][ProjectPathsConfig::paths] but strips the `root` form all paths.
+    ///
+    /// See: [`ProjectPaths::strip_prefix_all`]
     pub fn paths_relative(&self) -> ProjectPaths {
         let mut paths = self.paths();
         paths.strip_prefix_all(&self.root);
@@ -790,7 +791,8 @@ impl SolcConfigBuilder {
     }
 }
 
-/// Container for all `--include-path` arguments for Solc, se also [Solc docs](https://docs.soliditylang.org/en/v0.8.9/using-the-compiler.html#base-path-and-import-remapping
+/// Container for all `--include-path` arguments for Solc, see also
+/// [Solc docs](https://docs.soliditylang.org/en/v0.8.9/using-the-compiler.html#base-path-and-import-remapping).
 ///
 /// The `--include--path` flag:
 /// > Makes an additional source directory available to the default import callback. Use this option
diff --git a/ethers-solc/src/filter.rs b/ethers-solc/src/filter.rs
index 604414bb..549c8464 100644
--- a/ethers-solc/src/filter.rs
+++ b/ethers-solc/src/filter.rs
@@ -287,14 +287,17 @@ impl FilteredSource {
 /// Helper type that determines the state of a source file
 #[derive(Debug)]
 pub struct FilteredSourceInfo {
-    /// path to the source file
+    /// Path to the source file.
     pub file: PathBuf,
-    /// contents of the file
+
+    /// Contents of the file.
     pub source: Source,
-    /// idx in the [GraphEdges]
+
+    /// Index in the [GraphEdges].
     pub idx: usize,
-    /// whether this file is actually dirty
+
+    /// Whether this file is actually dirty.
     ///
-    /// See also [ArtifactsCacheInner::is_dirty()]
+    /// See also `ArtifactsCacheInner::is_dirty`
     pub dirty: bool,
 }
diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs
index 9d2947f7..6390e80e 100644
--- a/ethers-solc/src/lib.rs
+++ b/ethers-solc/src/lib.rs
@@ -1,5 +1,5 @@
+#![doc = include_str!("../README.md")]
 #![deny(rustdoc::broken_intra_doc_links)]
-#![allow(rustdoc::private_intra_doc_links)]
 
 pub mod artifacts;
 pub mod sourcemap;
@@ -24,7 +24,7 @@ pub use compile::{
 };
 
 mod config;
-pub use config::{AllowedLibPaths, PathStyle, ProjectPathsConfig, SolcConfig};
+pub use config::{AllowedLibPaths, PathStyle, ProjectPaths, ProjectPathsConfig, SolcConfig};
 
 pub mod remappings;
 use crate::artifacts::{Source, SourceFile, StandardJsonCompilerInput};
@@ -280,7 +280,7 @@ impl<T: ArtifactOutput> Project<T> {
     ///
     /// This will autodetect the appropriate `Solc` version(s) to use when compiling the provided
     /// `Sources`. Solc auto-detection follows semver rules, see also
-    /// [`crate::resolver::Graph::get_input_node_versions()`]
+    /// `Graph::get_input_node_versions`
     ///
     /// # Errors
     ///
diff --git a/ethers-solc/src/resolver/mod.rs b/ethers-solc/src/resolver/mod.rs
index 08b1b0f1..0610b71d 100644
--- a/ethers-solc/src/resolver/mod.rs
+++ b/ethers-solc/src/resolver/mod.rs
@@ -179,7 +179,9 @@ impl GraphEdges {
     ///
     /// This is a preprocess function that attempts to resolve those libraries that will the
     /// solidity `file` will be required to link. And further restrict this list to libraries
-    /// that won't be inlined See also [SolLibrary](parse::SolLibrary)
+    /// that won't be inlined.
+    ///
+    /// See also `parse::SolLibrary`.
     pub fn get_link_references(&self, file: impl AsRef<Path>) -> HashSet<&PathBuf> {
         let mut link_references = HashSet::new();
         for import in self.all_imported_nodes(self.node_id(file)) {
diff --git a/src/lib.rs b/src/lib.rs
index 151575b4..5fd61c2b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,56 +1,50 @@
-#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms, unreachable_pub)]
-#![deny(rustdoc::broken_intra_doc_links)]
-#![doc(test(
-    no_crate_inject,
-    attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
-))]
-
-//! # Complete Ethereum & Celo library and wallet implementation.
+//! # ethers-rs
 //!
-//! > ethers-rs is a port of [ethers-js](https://github.com/ethers-io/ethers.js) in Rust.
+//! A complete Ethereum and Celo Rust library.
 //!
 //! ## Quickstart: `prelude`
 //!
 //! A prelude is provided which imports all the important data types and traits for you. Use this
 //! when you want to quickly bootstrap a new project.
 //!
-//! ```no_run
-//! # #[allow(unused)]
+//! ```rust
 //! use ethers::prelude::*;
 //! ```
 //!
-//! Examples on how you can use the types imported by the prelude can be found in
-//! the [`examples` directory of the repository](https://github.com/gakonst/ethers-rs/tree/master/examples)
+//! Examples on how you can use the types imported by the prelude can be found in the
+//! [`examples` directory of the repository](https://github.com/gakonst/ethers-rs/tree/master/examples)
 //! and in the `tests/` directories of each crate.
 //!
-//! # Quick explanation of each module in ascending order of abstraction
+//! ## Modules
+//!
+//! The following paragraphs are a quick explanation of each module in ascending order of
+//! abstraction. More details can be found in [the book](https://gakonst.com/ethers-rs).
 //!
-//! ## `core`
+//! ### `core`
 //!
-//! Contains all the [necessary data structures](core::types) for interacting
-//! with Ethereum, along with cryptographic utilities for signing and verifying
-//! ECDSA signatures on `secp256k1`. Bindings to the Solidity compiler, Anvil and `ganache-cli`
-//! are also provided as helpers. To simplify your imports, consider using the re-exported
-//! modules described in the next subsection.
+//! Contains all the [necessary data structures](core::types) for interacting with Ethereum, along
+//! with cryptographic utilities for signing and verifying ECDSA signatures on `secp256k1`. Bindings
+//! to the Solidity compiler, Anvil and Ganace are also provided as helpers.
+//! To simplify your imports, consider using the re-exported modules described in the next
+//! subsection.
 //!
-//! ## `utils`, `types`, `abi`
+//! ### `utils`, `types`, `abi`
 //!
-//! These are re-exports of the [`utils`], [`types`] and [`abi`] modules from the `core` crate
+//! These are re-exports of the [`utils`], [`types`] and [`abi`] modules from the [`core`] crate.
 //!
-//! ## `providers`
+//! ### `providers`
 //!
-//! Ethereum nodes expose RPC endpoints (by default at `localhost:8545`). You can connect
-//! to them by using the [`Provider`]. The provider instance
-//! allows you to issue requests to the node which involve querying the state of Ethereum or
-//! broadcasting transactions with unlocked accounts on the node.
+//! Contains the [`Provider`] struct, an abstraction of a connection to the Ethereum network, which
+//! alongside the [`Middleware`] trait provides a concise, consistent interface to standard Ethereum
+//! node functionality,
 //!
-//! ## `signers`
+//! ### `signers`
 //!
-//! This module provides a [`Signer`] trait which can be used for signing messages
-//! or transactions. A [`Wallet`] type is implemented which can be used with a
-//! raw private key, or a YubiHSM2. We also provide Ledger support.
+//! Provides a [`Signer`] trait which can be used for signing messages or transactions. A [`Wallet`]
+//! type is implemented which can be used with a raw private key or a YubiHSM2. Ledger and Trezor
+//! support are also provided.
 //!
-//! ## `contract`
+//! ### `contract`
 //!
 //! Interacting with Ethereum is not restricted to sending or receiving funds. It also involves
 //! using smart contracts, which can be thought of as programs with persistent storage.
@@ -61,9 +55,10 @@
 //! selector](https://ethereum.stackexchange.com/questions/72363/what-is-a-function-selector)
 //! along with the arguments of the called function. This module provides the
 //! [`Contract`] and [`ContractFactory`] abstractions so that you do not have to worry about that.
-//! It also provides typesafe bindings via the [`abigen`] macro and the [`Abigen` builder].
 //!
-//! ## `middleware`
+//! It also provides typesafe bindings via the [`abigen`] macro and the [`Abigen`] builder.
+//!
+//! ### `middleware`
 //!
 //! In order to keep the ethers architecture as modular as possible, providers define a
 //! [`Middleware`] trait which defines all the methods to interact with an Ethereum node. By
@@ -78,56 +73,38 @@
 //! [`Signer`]: signers::Signer
 //! [`ContractFactory`]: contract::ContractFactory
 //! [`Contract`]: contract::Contract
-//! [`abigen`]: ./contract/macro.abigen.html
-//! [`Abigen` builder]: contract::Abigen
+//! [`abigen`]: contract::abigen
+//! [`Abigen`]: contract::Abigen
 //! [`utils`]: core::utils
 //! [`abi`]: core::abi
 //! [`types`]: core::types
 
-/// Address book consisting of frequently used contracts
-pub mod addressbook {
-    pub use ethers_addressbook::*;
-}
-
-#[doc = include_str!("../assets/CONTRACT_README.md")]
-pub mod contract {
-    pub use ethers_contract::*;
-}
-
-#[doc = include_str!("../assets/CORE_README.md")]
-pub mod core {
-    pub use ethers_core::*;
-}
-
-#[doc = include_str!("../assets/PROVIDERS_README.md")]
-pub mod providers {
-    pub use ethers_providers::*;
-}
-
-#[doc = include_str!("../assets/MIDDLEWARE_README.md")]
-pub mod middleware {
-    pub use ethers_middleware::*;
-}
-
-#[doc = include_str!("../assets/SIGNERS_README.md")]
-pub mod signers {
-    pub use ethers_signers::*;
-}
-
+#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms, unreachable_pub)]
+#![deny(rustdoc::broken_intra_doc_links)]
+#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms), allow(dead_code, unused_variables))))]
+
+#[doc(inline)]
+pub use ethers_addressbook as addressbook;
+#[doc(inline)]
+pub use ethers_contract as contract;
+#[doc(inline)]
+pub use ethers_core as core;
+#[doc(inline)]
+pub use ethers_etherscan as etherscan;
+#[doc(inline)]
+pub use ethers_middleware as middleware;
+#[doc(inline)]
+pub use ethers_providers as providers;
+#[doc(inline)]
+pub use ethers_signers as signers;
+#[doc(inline)]
 #[cfg(feature = "ethers-solc")]
-#[doc = include_str!("../assets/SOLC_README.md")]
-pub mod solc {
-    pub use ethers_solc::*;
-}
+pub use ethers_solc as solc;
 
-/// Etherscan bindings
-pub mod etherscan {
-    pub use ethers_etherscan::*;
-}
-
-pub use crate::core::{abi, types, utils};
+#[doc(inline)]
+pub use ethers_core::{abi, types, utils};
 
-/// Easy imports of frequently used type definitions and traits
+/// Easy imports of frequently used type definitions and traits.
 #[doc(hidden)]
 pub mod prelude {
     pub use super::addressbook::*;
@@ -136,6 +113,8 @@ pub mod prelude {
 
     pub use super::core::{types::*, *};
 
+    pub use super::etherscan::*;
+
     pub use super::middleware::*;
 
     pub use super::providers::*;
@@ -144,6 +123,4 @@ pub mod prelude {
 
     #[cfg(feature = "ethers-solc")]
     pub use super::solc::*;
-
-    pub use super::etherscan::*;
 }


Test patch:
diff --git a/ethers-middleware/tests/signer.rs b/ethers-middleware/tests/signer.rs
index f496f114..0129deca 100644
--- a/ethers-middleware/tests/signer.rs
+++ b/ethers-middleware/tests/signer.rs
@@ -90,86 +90,89 @@ async fn send_with_chain_id_anvil() {
     let _err = res.unwrap_err();
 }
 
-#[tokio::test]
-#[cfg(not(feature = "celo"))]
-async fn pending_txs_with_confirmations_testnet() {
-    let provider = GOERLI.provider().interval(Duration::from_millis(3000));
-    let chain_id = provider.get_chainid().await.unwrap();
-    let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
-    let address = wallet.address();
-    let provider = SignerMiddleware::new(provider, wallet);
-    generic_pending_txs_test(provider, address).await;
-}
-
-// different keys to avoid nonce errors
-#[tokio::test]
-#[cfg(not(feature = "celo"))]
-async fn websocket_pending_txs_with_confirmations_testnet() {
-    let provider = GOERLI.ws().await.interval(Duration::from_millis(3000));
-    let chain_id = provider.get_chainid().await.unwrap();
-    let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
-    let address = wallet.address();
-    let provider = SignerMiddleware::new(provider, wallet);
-    generic_pending_txs_test(provider, address).await;
-}
-
-#[cfg(not(feature = "celo"))]
-async fn generic_pending_txs_test<M: Middleware>(provider: M, who: Address) {
-    let tx = TransactionRequest::new().to(who).from(who);
-    let pending_tx = provider.send_transaction(tx, None).await.unwrap();
-    let tx_hash = *pending_tx;
-    let receipt = pending_tx.confirmations(1).await.unwrap().unwrap();
-    // got the correct receipt
-    assert_eq!(receipt.transaction_hash, tx_hash);
-}
-
-#[tokio::test]
-#[cfg(not(feature = "celo"))]
-async fn typed_txs() {
-    let provider = GOERLI.provider();
-
-    let chain_id = provider.get_chainid().await.unwrap();
-    let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
-    let address = wallet.address();
-    // our wallet
-    let provider = SignerMiddleware::new(provider, wallet);
-
-    // Uncomment the below and run this test to re-fund the wallets if they get drained.
-    // Would be ideal if we'd have a way to do this automatically, but this should be
-    // happening rarely enough that it doesn't matter.
-    // WALLETS.fund(provider.provider(), 10u32).await;
-
-    async fn check_tx<P: JsonRpcClient + Clone>(
-        pending_tx: ethers_providers::PendingTransaction<'_, P>,
-        expected: u64,
-    ) {
-        let provider = pending_tx.provider();
-        let receipt = pending_tx.await.unwrap().unwrap();
-        let tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
-        assert_eq!(receipt.transaction_type, Some(expected.into()));
-        assert_eq!(tx.transaction_type, Some(expected.into()));
-    }
-
-    let nonce = provider.get_transaction_count(address, None).await.unwrap();
-    let bn = Some(BlockNumber::Pending.into());
-    let gas_price = provider.get_gas_price().await.unwrap() * 125 / 100;
-
-    let tx = TransactionRequest::new().from(address).to(address).nonce(nonce).gas_price(gas_price);
-    let tx1 = provider.send_transaction(tx.clone(), bn).await.unwrap();
-
-    let tx = tx.clone().from(address).to(address).nonce(nonce + 1).with_access_list(vec![]);
-    let tx2 = provider.send_transaction(tx, bn).await.unwrap();
-
-    let tx = Eip1559TransactionRequest::new()
-        .from(address)
-        .to(address)
-        .nonce(nonce + 2)
-        .max_fee_per_gas(gas_price)
-        .max_priority_fee_per_gas(gas_price);
-    let tx3 = provider.send_transaction(tx, bn).await.unwrap();
-
-    futures_util::join!(check_tx(tx1, 0), check_tx(tx2, 1), check_tx(tx3, 2),);
-}
+// #[tokio::test]
+// #[cfg(not(feature = "celo"))]
+// async fn pending_txs_with_confirmations_testnet() {
+//     let provider = GOERLI.provider().interval(Duration::from_millis(3000));
+//     let chain_id = provider.get_chainid().await.unwrap();
+//     let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
+//     let address = wallet.address();
+//     let provider = SignerMiddleware::new(provider, wallet);
+//     generic_pending_txs_test(provider, address).await;
+// }
+//
+// // different keys to avoid nonce errors
+// #[tokio::test]
+// #[cfg(not(feature = "celo"))]
+// async fn websocket_pending_txs_with_confirmations_testnet() {
+//     let provider = GOERLI.ws().await.interval(Duration::from_millis(3000));
+//     let chain_id = provider.get_chainid().await.unwrap();
+//     let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
+//     let address = wallet.address();
+//     let provider = SignerMiddleware::new(provider, wallet);
+//     generic_pending_txs_test(provider, address).await;
+// }
+
+// #[cfg(not(feature = "celo"))]
+// async fn generic_pending_txs_test<M: Middleware>(provider: M, who: Address) {
+//     let tx = TransactionRequest::new().to(who).from(who);
+//     let pending_tx = provider.send_transaction(tx, None).await.unwrap();
+//     let tx_hash = *pending_tx;
+//     let receipt = pending_tx.confirmations(1).await.unwrap().unwrap();
+//     // got the correct receipt
+//     assert_eq!(receipt.transaction_hash, tx_hash);
+// }
+//
+// #[tokio::test]
+// #[cfg(not(feature = "celo"))]
+// async fn typed_txs() {
+//     let provider = GOERLI.provider();
+//
+//     let chain_id = provider.get_chainid().await.unwrap();
+//     let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
+//     let address = wallet.address();
+//
+//     dbg!(&wallet, &address);
+//     // our wallet
+//     let provider = SignerMiddleware::new(provider, wallet);
+//
+//     // Uncomment the below and run this test to re-fund the wallets if they get drained.
+//     // Would be ideal if we'd have a way to do this automatically, but this should be
+//     // happening rarely enough that it doesn't matter.
+//     // WALLETS.fund(provider.provider(), 10u32).await;
+//
+//     async fn check_tx<P: JsonRpcClient + Clone>(
+//         pending_tx: ethers_providers::PendingTransaction<'_, P>,
+//         expected: u64,
+//     ) {
+//         let provider = pending_tx.provider();
+//         let receipt = pending_tx.await.unwrap().unwrap();
+//         let tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
+//         assert_eq!(receipt.transaction_type, Some(expected.into()));
+//         assert_eq!(tx.transaction_type, Some(expected.into()));
+//     }
+//
+//     let nonce = provider.get_transaction_count(address, None).await.unwrap();
+//     let bn = Some(BlockNumber::Pending.into());
+//     let gas_price = provider.get_gas_price().await.unwrap() * 125 / 100;
+//
+//     let tx =
+// TransactionRequest::new().from(address).to(address).nonce(nonce).gas_price(gas_price);
+//     let tx1 = provider.send_transaction(tx.clone(), bn).await.unwrap();
+//
+//     let tx = tx.clone().from(address).to(address).nonce(nonce + 1).with_access_list(vec![]);
+//     let tx2 = provider.send_transaction(tx, bn).await.unwrap();
+//
+//     let tx = Eip1559TransactionRequest::new()
+//         .from(address)
+//         .to(address)
+//         .nonce(nonce + 2)
+//         .max_fee_per_gas(gas_price)
+//         .max_priority_fee_per_gas(gas_price);
+//     let tx3 = provider.send_transaction(tx, bn).await.unwrap();
+//
+//     futures_util::join!(check_tx(tx1, 0), check_tx(tx2, 1), check_tx(tx3, 2),);
+// }
 
 #[tokio::test]
 #[cfg(feature = "celo")]