Repository: gakonst/ethers-rs
Pull request #2082 (2023-01-27 15:41:45)

refactor: make contract abstract over Borrow
## Motivation

Contracts currently hold `Arc<M>` and have bound `where M: Middleware` . This requires managing arcs across a codebase, which imposes unpleasant requirements on binaries

## Solution

- Modify the contract object to be abstract over `B: Borrow<M>` instead of holding an `Arc<M>`. This allows contracts to be used directly with Cows, non-ARCed middleware, Boxes, other smart pointers, and anything else that uses the `Borrow`  trait.
- rename the modified object `ContractInternal` and create a type alias under the old name, for backwards compatibility

Example use case: 
- you can now use a contract directly with a `Lazy<Provider<Http>>` without having to have an arc in the lazy first

## Inference Limitation

While `FunctionCall` and `ContractInstance` may be used with any type is `Borrow<M>`, there are sometimes multiple choices of borrowed type that satisfy the `M: Middleware` constraint. 

because `Middleware` has an `auto_impl(&, Box, Arc)`, references, boxes, and arcs have multiple `Borrow` options that satisfy the constraint. 
E.g. `Arc<M>` has these impls:
- `impl Borrow<Arc<M>> for Arc<M>`
- `impl Borrow<M> for Arc<M>`
- `impl Borrow<&M> for Arc<M>`

as a result, type inference fails when calling `ContractInstance::new(address, arc_mware)` users must instead specify `ContractInstance::<_, ConcreteMiddlewareType>(address, arc_mware)`

Removing the `auto_impl` will solve this. However, we don't know the extent of reliance on that auto_impl. As a result, using the backwards-compatibility aliases `Contract` and `ContractCall` is still recommended. the Followup work section describes a migration route to fix inference and deprecate the backwards-compatibility aliases

## Followup work

- Change the abigen derived Contract objects to use the generic `ContractInternal` 
- in 2.0.0, eliminate the type alias, and have the  generic version use the `Contract` name

Lib-wide refactoring plan:
1. list further abstraction targets
    - EthEvent
    - ContractDeployer
    - ContractFactory
    - Middlewares (should the stack be `MyMiddleware { inner: Borrow<M> } ` ?)
    - ProviderOracle
    - abigen! output code
3. refactor to use borrow
4. provide backwards compatible type aliases like `type Contract = ContractInstance<Arc<M>, M>`
5. remove `auto_impl(&, Box, Arc)` on `Middleware`
6. write migration guide
7. deprecate backwards-compatibility aliases
8. remove aliases

## PR Checklist

-   [x] Added Tests
-   [x] Added Documentation
-   [x] Updated the changelog
-   [x] Breaking changes


prestwich: currently checking out the test breakage. looks like this will affect type inference
prestwich: ```
error[E0283]: type annotations needed
   --> ethers-contract/tests/it/contract.rs:431:14
    |
431 |             .connect(client2.clone())
    |              ^^^^^^^ cannot infer type of the type parameter `N` declared on the associated function `connect`
    |
    = note: multiple `impl`s satisfying `Arc<ethers_providers::Provider<Http>>: Borrow<_>` found in the following crates: `alloc`, `core`:
            - impl<T> Borrow<T> for Arc<T>
              where T: ?Sized;
            - impl<T> Borrow<T> for T
              where T: ?Sized;
note: required by a bound in `ethers_contract::contract::ContractInternal::<B, M>::connect`
   --> /Users/james/devel/ethers-rs/ethers-contract/src/contract.rs:269:12
    |
269 |         C: Borrow<N>,
    |            ^^^^^^^^^ required by this bound in `ethers_contract::contract::ContractInternal::<B, M>::connect`
help: consider specifying the type arguments in the function call
    |
431 |             .connect::<C, N>(client2.clone())
    |                     ++++++++
```

the test failures are all driven by failed inference on `connect`. because `connect` is now generic over `B`, and `B: Borrow<M>` and `M: Borrow<M>`, both B and M are valid args for `connect()`. the compiler doesn't know whether it should be using the `B` or borrowing `M` from it and using `M`

this is only an issue when _switching_ the middleware connected to a contract, and the solution is to write out `connect::<Arc<_>, _>()`. I think this is acceptable (although it is a breaking change)
prestwich: workaround:
- changed params to `connect` to use an `arc` to leave existing code un-broken
- added `connect_with` with the borrow api, which requires more type ascription
prestwich: anyone have a better name than `ContractInternal` ? 🤔 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 214b2531..ce8d0fb7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@
 
 ### Unreleased
 
+- Add `abi::encode_packed` [#2104](https://github.com/gakonst/ethers-rs/pull/2104)
+- Add support for custom JavaScript tracer to `debug_traceCall` and `debug_traceTransaction` [#2064](https://github.com/gakonst/ethers-rs/pull/2064)
 - Add a `Send` bound to the `IntoFuture` implementation of `ContractCall` [#2083](https://github.com/gakonst/ethers-rs/pull/2083)
 - Bump [`svm-rs`](https://github.com/roynalnaruto/svm-rs) dependency to fix conflicts with Rust Crytpo packages [#2051](https://github.com/gakonst/ethers-rs/pull/2051)
 - Avoid unnecessary allocations in `utils` [#2046](https://github.com/gakonst/ethers-rs/pull/2046)
@@ -295,6 +297,8 @@
 
 ### Unreleased
 
+- Make `Contract` objects generic over the borrow trait, to allow non-arc mware
+  [#2082](https://github.com/gakonst/ethers-rs/pull/2082)
 - Return pending transaction from `Multicall::send`
   [#2044](https://github.com/gakonst/ethers-rs/pull/2044)
 - Add abigen to default features
diff --git a/Cargo.lock b/Cargo.lock
index 57421658..064fa836 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -381,9 +381,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
 [[package]]
 name = "bytes"
-version = "1.3.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
 dependencies = [
  "serde",
 ]
@@ -429,9 +429,9 @@ dependencies = [
 
 [[package]]
 name = "cargo_metadata"
-version = "0.15.2"
+version = "0.15.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "982a0cf6a99c350d7246035613882e376d58cebe571785abc5da4f648d53ac0a"
+checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07"
 dependencies = [
  "camino",
  "cargo-platform",
@@ -1675,12 +1675,15 @@ dependencies = [
 name = "examples-providers"
 version = "1.0.2"
 dependencies = [
+ "async-trait",
  "ethers",
  "eyre",
  "reqwest",
  "serde",
  "serde_json",
+ "thiserror",
  "tokio",
+ "url",
 ]
 
 [[package]]
@@ -1859,9 +1862,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1869,15 +1872,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
+checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
+checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -1886,9 +1889,9 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
+checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
 
 [[package]]
 name = "futures-locks"
@@ -1902,9 +1905,9 @@ dependencies = [
 
 [[package]]
 name = "futures-macro"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
+checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1913,15 +1916,15 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
 
 [[package]]
 name = "futures-task"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
+checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
 
 [[package]]
 name = "futures-timer"
@@ -1931,9 +1934,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
 
 [[package]]
 name = "futures-util"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
+checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -2400,9 +2403,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.60"
+version = "0.3.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -3775,9 +3778,9 @@ dependencies = [
 
 [[package]]
 name = "send_wrapper"
-version = "0.5.0"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "930c0acf610d3fdb5e2ab6213019aaa04e227ebe9547b0649ba599b16d788bd7"
+checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
 
 [[package]]
 name = "serde"
@@ -4106,9 +4109,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
 
 [[package]]
 name = "svm-rs"
-version = "0.2.19"
+version = "0.2.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e18bbb2b229a2cc0d8ba58603adb0e460ad49a3451b1540fd6f7a5d37fd03b80"
+checksum = "b1b8e811a6443e8d93665a5e532efa8429ea8e2052a234a82e2cd69478913310"
 dependencies = [
  "anyhow",
  "cfg-if 1.0.0",
@@ -4137,9 +4140,9 @@ dependencies = [
 
 [[package]]
 name = "svm-rs-builds"
-version = "0.1.11"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebf4cc09b0dd1cd78bc8ca82c5e22bbcd0cca126ad0d584e2fc47d4dc0a3dd73"
+checksum = "a61ba26a7b971b10d5211b945f6d3661c883bf3e67ffb95ebb4051f14ef14b99"
 dependencies = [
  "build_const",
  "hex",
@@ -4689,9 +4692,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.83"
+version = "0.2.84"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
 dependencies = [
  "cfg-if 1.0.0",
  "serde",
@@ -4701,9 +4704,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.83"
+version = "0.2.84"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
 dependencies = [
  "bumpalo",
  "log",
@@ -4716,9 +4719,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.33"
+version = "0.4.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
+checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
 dependencies = [
  "cfg-if 1.0.0",
  "js-sys",
@@ -4728,9 +4731,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.83"
+version = "0.2.84"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -4738,9 +4741,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.83"
+version = "0.2.84"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4751,15 +4754,15 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.83"
+version = "0.2.84"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
 
 [[package]]
 name = "wasm-bindgen-test"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09d2fff962180c3fadf677438054b1db62bee4aa32af26a45388af07d1287e1d"
+checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b"
 dependencies = [
  "console_error_panic_hook",
  "js-sys",
@@ -4771,9 +4774,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-test-macro"
-version = "0.3.33"
+version = "0.3.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4683da3dfc016f704c9f82cf401520c4f1cb3ee440f7f52b3d6ac29506a49ca7"
+checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4796,9 +4799,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.60"
+version = "0.3.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
+checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -4934,13 +4937,14 @@ dependencies = [
 
 [[package]]
 name = "ws_stream_wasm"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47ca1ab42f5afed7fc332b22b6e932ca5414b209465412c8cdf0ad23bc0de645"
+checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5"
 dependencies = [
  "async_io_stream",
  "futures",
  "js-sys",
+ "log",
  "pharos",
  "rustc_version",
  "send_wrapper",
diff --git a/Cargo.toml b/Cargo.toml
index 6c5fdb04..6c2794ac 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -123,7 +123,7 @@ serde = { version = "1.0.124", features = ["derive"] }
 serde_json = "1.0.64"
 tokio = { version = "1.18", features = ["macros", "rt-multi-thread"] }
 hex = "0.4.3"
-bytes = "1.3.0"
+bytes = "1.4.0"
 
 # profile for the wasm example
 [profile.release.package.ethers-wasm]
diff --git a/book/README.md b/book/README.md
new file mode 100644
index 00000000..16970565
--- /dev/null
+++ b/book/README.md
@@ -0,0 +1,21 @@
+# The ethers-rs book
+
+Everything about `ethers-rs`. Work-in-progress. View online here: <https://www.gakonst.com/ethers-rs>
+
+## Contributing
+
+The book is built with [mdbook](https://github.com/rust-lang/mdBook), which you can install by running `cargo install mdbook`.
+
+To view changes live, run:
+
+```sh
+mdbook serve
+```
+
+Or with docker:
+
+```sh
+docker run -p 3000:3000 -v `pwd`:/book peaceiris/mdbook serve
+```
+
+To add a new section (file) to the book, add it to [`SUMMARY.md`](./SUMMARY.md).
diff --git a/book/SUMMARY.md b/book/SUMMARY.md
index a3de1adb..1998ee06 100644
--- a/book/SUMMARY.md
+++ b/book/SUMMARY.md
@@ -14,7 +14,7 @@
   - [Quorum](./providers/quorum.md)
   - [Retry](./providers/retry.md)
   - [RW](./providers/rw.md)
-  - [WebSocket](./providers/ws.md)
+  - [Custom](./providers/custom.md)
   - [Advanced Usage](./providers/advanced_usage.md)
 - [Middleware](./middleware/middleware.md)
   - [Builder](./middleware/builder.md)
@@ -88,4 +88,3 @@
     - [Deploy contracts]()
     - [Fork]() 
     - [Testing]()
-
diff --git a/book/getting-started/connect_to_an_ethereum_node.md b/book/getting-started/connect_to_an_ethereum_node.md
index 5a19d9c7..dec2e334 100644
--- a/book/getting-started/connect_to_an_ethereum_node.md
+++ b/book/getting-started/connect_to_an_ethereum_node.md
@@ -1,4 +1,5 @@
 # Connect to an Ethereum node
+
 Ethers-rs allows application to connect the blockchain using web3 providers. Providers act as an interface between applications and an Ethereum node, allowing you to send requests and receive responses via JSON-RPC messages.
 
 Some common actions you can perform using a provider include:
@@ -13,11 +14,12 @@ Some common actions you can perform using a provider include:
 Providers are an important part of web3 libraries because they allow you to easily interact with the Ethereum blockchain without having to manage the underlying connection to the node yourself.
 
 Code below shows a basic setup to connect a provider to a node:
+
 ```rust
-/// The `prelude` module provides a convenient way to import a number 
-/// of common dependencies at once. This can be useful if you are working 
-/// with multiple parts of the library and want to avoid having 
-/// to import each dependency individually.
+// The `prelude` module provides a convenient way to import a number 
+// of common dependencies at once. This can be useful if you are working 
+// with multiple parts of the library and want to avoid having 
+// to import each dependency individually.
 use ethers::prelude::*;
 
 const RPC_URL: &str = "https://mainnet.infura.io/v3/your-project-id";
@@ -30,4 +32,4 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 
     Ok(())
 }
-```
\ No newline at end of file
+```
diff --git a/book/getting-started/intro.md b/book/getting-started/intro.md
index 9d40f99a..321aa76f 100644
--- a/book/getting-started/intro.md
+++ b/book/getting-started/intro.md
@@ -1,9 +1,10 @@
 # Intro
+
 Welcome to the hands-on guide for the ethers-rs library!
 
 This documentation contains a collection of examples demonstrating how to use the library to build Ethereum-based applications in Rust. The examples cover a range of topics, from basic smart contract interactions to more advanced usage of ethers-rs.
 
-```admonish info 
+```admonish info
 You can find the official ethers-rs documentation on docs.rs - [here](https://docs.rs/ethers/0.5.0/ethers/).
 ```
 
@@ -11,12 +12,12 @@ Each example includes a detailed description of the functionality being demonstr
 
 We hope that these docs will help you get started with ethers-rs and give you a better understanding of how to use the library to build your own web3 applications in Rust. If you have any questions or need further assistance, please don't hesitate to reach out to the ethers-rs community.
 
-The following is a brief overview diagram of the  topis covered in this guide.
+The following is a brief overview diagram of the topics covered in this guide.
 
 ```mermaid
 {{#include ../mermaid-style.txt}}
 
-graph LR  
+graph LR
   A[Ethers-rs <br> Manual] --> A1[Providers]
 	A --> A2[Middleware]
   A --> A3[Contracts]
@@ -28,6 +29,7 @@ graph LR
   A --> A9[Big numbers]
   A --> A10[Anvil]
 ```
-```admonish bug 
+
+```admonish bug
 This diagram is incomplete and will undergo continuous changes.
 ```
diff --git a/book/getting-started/start_a_new_project.md b/book/getting-started/start_a_new_project.md
index ad97b339..a1e58178 100644
--- a/book/getting-started/start_a_new_project.md
+++ b/book/getting-started/start_a_new_project.md
@@ -1,25 +1,29 @@
 # Start a new project
-To set up a new project with ethers-rs, you will need to install the Rust programming language and the cargo package manager on your system.
+
+To set up a new project with ethers-rs, you will need to install the Rust programming language toolchain and the Cargo package manager on your system.
 
 1. Install Rust by following the instructions at [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install).
 2. Once Rust is installed, create a new Rust project by running the following command:
+
     ```bash
     cargo new my-project
     ```
-   This will create a new directory called my-project with the necessary files for a new Rust project.
+
+    This will create a new directory called my-project with the necessary files for a new Rust project.
 
 3. Navigate to the project directory and add ethers-rs as a dependency in your `Cargo.toml` file:
-   ```toml
-   [dependencies]
-   ethers = "1.0.0" 
 
-   # Most of ethers-rs features rely upon an async Rust runtime.
-   # Since Rust doesn't provide an async runtime itself, you can
-   # include the excellent tokio library
-   tokio = { version = "1.23.0", features = ["macros"] }
-   ```
+    ```toml
+    [dependencies]
+    ethers = "1.0.0"
+
+    # Most of ethers-rs features rely upon an async Rust runtime.
+    # Since Rust doesn't provide an async runtime itself, you can
+    # include the excellent tokio library
+    tokio = { version = "1.23.0", features = ["macros"] }
+    ```
 
-   If you want to make experiments and/or play around with early ethers-rs features link our GitHub repo in the `Cargo.toml`.
+    If you want to make experiments and/or play around with early ethers-rs features link our GitHub repo in the `Cargo.toml`.
 
     ```toml
     [dependencies]
@@ -33,33 +37,34 @@ To set up a new project with ethers-rs, you will need to install the Rust progra
     [dependencies]
     ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "1.0.2" }
     ```
-   > **Note:** using a Git repository as a dependency is generally not recommended 
-   > for production projects, as it can make it difficult to ensure that you are using 
-   > a specific and stable version of the dependency. 
-   > It is usually better to specify a version number or range to ensure that your project 
-   > is reproducible.
+
+    > **Note:** using a Git repository as a dependency is generally not recommended
+    > for production projects, as it can make it difficult to ensure that you are using
+    > a specific and stable version of the dependency.
+    > It is usually better to specify a version number or range to ensure that your project
+    > is reproducible.
 
 ## Enable transports
+
 Ethers-rs enables interactions with Ethereum nodes through different "transport" types, or communication protocols.
 The following transport types are currently supported by ethers.rs:
 
-* **HTTP(S):** The HTTP(S) transport is used to communicate with Ethereum nodes over the HTTP or HTTPS protocols. This is the most common way to interact with Ethereum nodes. If you are looking to connect to a HTTPS endpoint, then you need to enable the `rustls` or `openssl` features:
+- **HTTP(S):** The HTTP(S) transport is used to communicate with Ethereum nodes over the HTTP or HTTPS protocols. This is the most common way to interact with Ethereum nodes. If you are looking to connect to a HTTPS endpoint, then you need to enable the `rustls` or `openssl` features:
+
     ```toml
     [dependencies]
     ethers = { version = "1.0.0", features = ["rustls"] }
     ```
 
-* **WebSocket:** The WebSocket transport is used to communicate with Ethereum nodes over the WebSocket protocol, which is a widely-supported standard for establishing a bi-directional communication channel between a client and a server. This can be used for a variety of purposes, including receiving real-time updates from an Ethereum node, or submitting transactions to the Ethereum network. Websockets support is turned on via the feature-flag ws:
+- **WebSocket:** The WebSocket transport is used to communicate with Ethereum nodes over the WebSocket protocol, which is a widely-supported standard for establishing a bi-directional communication channel between a client and a server. This can be used for a variety of purposes, including receiving real-time updates from an Ethereum node, or submitting transactions to the Ethereum network. Websockets support is turned on via the feature-flag ws:
+
     ```toml
     [dependencies]
     ethers = { version = "1.0.0", features = ["ws"] }
     ```
 
-* **IPC (Interprocess Communication):** The IPC transport is used to communicate with a local Ethereum node using the IPC protocol, which is a way for processes to communicate with each other on a single computer. This is commonly used in Ethereum development to allow applications to communicate with a local Ethereum node, such as geth or parity. IPC support is turned on via the feature-flag `ipc`:
+- **IPC (Interprocess Communication):** The IPC transport is used to communicate with a local Ethereum node using the IPC protocol, which is a way for processes to communicate with each other on a single computer. This is commonly used in Ethereum development to allow applications to communicate with a local Ethereum node, such as geth or parity. IPC support is turned on via the feature-flag `ipc`:
     ```toml
     [dependencies]
     ethers = { version = "1.0.0", features = ["ipc"] }
     ```
-
-
-
diff --git a/book/providers/advanced_usage.md b/book/providers/advanced_usage.md
index d777dbcd..28916a6a 100644
--- a/book/providers/advanced_usage.md
+++ b/book/providers/advanced_usage.md
@@ -1,9 +1,8 @@
 # Advanced Usage
 
-
 ## `CallBuilder`
 
-The `CallBuilder` is an enum to help create complex calls. `CallBuilder` implements `[RawCall](https://docs.rs/ethers/latest/ethers/providers/call_raw/trait.RawCall.html)`  methods for overriding parameters to the `eth_call`rpc method.
+The `CallBuilder` is an enum to help create complex calls. `CallBuilder` implements [`RawCall`](https://docs.rs/ethers/latest/ethers/providers/call_raw/trait.RawCall.html) methods for overriding parameters to the `eth_call` rpc method.
 
 Lets take a quick look at how to use the `CallBuilder`.
 
@@ -73,7 +72,6 @@ async fn main() -> eyre::Result<()> {
 
 Let's look at how to use the state override set. In short, the state override set is an optional address-to-state mapping, where each entry specifies some state to be ephemerally overridden prior to executing the call. The state override set allows you to override an account's balance, an account's nonce, the code at a given address, the entire state of an account's storage or an individual slot in an account's storage. Note that the state override set is not a default feature and is not available on every node.
 
-
 ```rust
 use ethers::{
     providers::{
@@ -113,4 +111,4 @@ async fn main() -> eyre::Result<()> {
 }
 ```
 
-In this example, the account balance and nonce for the `from_adr` is overridden. The state override set is a very powerful tool that you can use to simulate complicated transactions without undergoing any actual state changes.
\ No newline at end of file
+In this example, the account balance and nonce for the `from_adr` is overridden. The state override set is a very powerful tool that you can use to simulate complicated transactions without undergoing any actual state changes.
diff --git a/book/providers/custom.md b/book/providers/custom.md
new file mode 100644
index 00000000..7baa5769
--- /dev/null
+++ b/book/providers/custom.md
@@ -0,0 +1,9 @@
+# Custom data transport
+
+As [we've previously seen](./providers.md#data-transports), a transport must implement [`JsonRpcClient`](https://docs.rs/ethers/latest/ethers/providers/trait.JsonRpcClient.html), and can also optionally implement [`PubsubClient`](https://docs.rs/ethers/latest/ethers/providers/trait.PubsubClient.html).
+
+Let's see how we can create a custom data transport by implementing one that stores either a `Ws` or an `Ipc` transport:
+
+```rust
+{{#include ../../examples/providers/examples/custom.rs}}
+```
diff --git a/book/providers/http.md b/book/providers/http.md
index c8c0da9f..1c119577 100644
--- a/book/providers/http.md
+++ b/book/providers/http.md
@@ -1,9 +1,10 @@
 # Http
 
-The `Http` provider establishes an Http connection with a node, allowing you to send RPC requests to the node to fetch data, simulate calls, send transactions and much more. 
+The `Http` provider establishes an HTTP connection with a node, allowing you to send RPC requests to the node to fetch data, simulate calls, send transactions and much more.
 
 ## Initializing an Http Provider
-Lets take a quick look at few ways to create a new `Http` provider. Since the `Http` provider implements the [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) one of the easiest ways to initialize a new provider is by using the `from_str()` method. 
+
+Lets take a quick look at few ways to create a new `Http` provider. One of the easiest ways to initialize a new `Provider<Http>` is by using the [`TryFrom`](https://doc.rust-lang.org/stable/std/convert/trait.TryFrom.html) trait's `try_from` method.
 
 ```rust
 use ethers::providers::{Http, Middleware, Provider};
@@ -21,18 +22,20 @@ async fn main() -> eyre::Result<()> {
 The `Http` provider also supplies a way to initialize a new authorized connection.
 
 ```rust
-// Initializes a new HTTP Client with authentication
 use ethers::providers::{Authorization, Http};
 use url::Url;
+
 #[tokio::main]
 async fn main() -> eyre::Result<()> {
+    // Initialize a new HTTP Client with authentication
     let url = Url::parse("http://localhost:8545")?;
     let provider = Http::new_with_auth(url, Authorization::basic("admin", "good_password"));
+
     Ok(())
 }
 ```
 
-Additionally, you can initialize a new provider with your own custom `reqwest::Client`. 
+Additionally, you can initialize a new provider with your own custom `reqwest::Client`.
 
 ```rust
 use ethers::providers::Http;
@@ -43,6 +46,7 @@ async fn main() -> eyre::Result<()> {
     let url = Url::parse("http://localhost:8545")?;
     let client = reqwest::Client::builder().build()?;
     let provider = Http::new_with_client(url, client);
+
     Ok(())
 }
 ```
@@ -67,16 +71,15 @@ async fn main() -> eyre::Result<()> {
 }
 ```
 
-You can also use the provider to interact with smart contracts. The snippet below uses the provider to establish a new instance of a UniswapV2Pool and uses the `get_reserves()` method from the smart contract to fetch the current state of the pool's reserves. 
+You can also use the provider to interact with smart contracts. The snippet below uses the provider to establish a new instance of a UniswapV2Pool and uses the `get_reserves()` method from the smart contract to fetch the current state of the pool's reserves.
 
 ```rust
-use std::{str::FromStr, sync::Arc};
-
 use ethers::{
     prelude::abigen,
     providers::{Http, Provider},
-    types::H160,
+    types::Address,
 };
+use std::sync::Arc;
 
 abigen!(
     IUniswapV2Pair,
@@ -89,7 +92,7 @@ async fn main() -> eyre::Result<()> {
     let provider = Arc::new(Provider::try_from(rpc_url)?);
 
     // Initialize a new instance of the Weth/Dai Uniswap V2 pair contract
-    let pair_address = H160::from_str("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11")?;
+    let pair_address: Address = "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".parse()?;
     let uniswap_v2_pair = IUniswapV2Pair::new(pair_address, provider);
 
     // Use the get_reserves() function to fetch the pool reserves
@@ -100,9 +103,9 @@ async fn main() -> eyre::Result<()> {
 }
 ```
 
-This example is a little more complicated, so lets walk through what is going on. The `IUniswapV2Pair` is a struct that is generated from the `abigen!()` macro. The `IUniswapV2Pair::new()` function is used to create a new instance of the contract, taking in an `Address` and an `Arc<M>` as arguments, where `M` is any type that implements the `Middleware` trait. Note that the provider is wrapped in an [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) when being passed into the `new()` function. 
+This example is a little more complicated, so let's walk through what is going on. The `IUniswapV2Pair` is a struct that is generated from the `abigen!()` macro. The `IUniswapV2Pair::new()` function is used to create a new instance of the contract, taking in an `Address` and an `Arc<M>` as arguments, where `M` is any type that implements the `Middleware` trait. Note that the provider is wrapped in an [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) when being passed into the `new()` function.
 
-It is very common to wrap a provider in an `Arc` to share the provider across threads. Lets look at another example where the provider is used asynchronously across two tokio threads. In the next example, a new provider is initialized and used to asynchronously fetch the number of Ommer blocks from the most recent block, as well as the previous block.
+It is very common to wrap a provider in an `Arc` to share the provider across threads. Let's look at another example where the provider is used asynchronously across two tokio threads. In the next example, a new provider is initialized and used to asynchronously fetch the number of Ommer blocks from the most recent block, as well as the previous block.
 
 ```rust
 use std::sync::Arc;
@@ -138,4 +141,4 @@ async fn main() -> eyre::Result<()> {
 
 <br>
 
-Before heading to the next chapter, feel free to check out the docs for the [`Http` provider](https://docs.rs/ethers/latest/ethers/providers/struct.Http.html). Keep in mind that we will cover advanced usage of providers at the end of this chapter. Now that we have the basics covered, lets move on to the next Provider, Websockets! 
\ No newline at end of file
+Before heading to the next chapter, feel free to check out the docs for the [`Http` provider](https://docs.rs/ethers/latest/ethers/providers/struct.Http.html). Keep in mind that we will cover advanced usage of providers at the end of this chapter. Now that we have the basics covered, let's move on to the next provider, Websockets!
diff --git a/book/providers/ipc.md b/book/providers/ipc.md
index f4abb323..3e4fd920 100644
--- a/book/providers/ipc.md
+++ b/book/providers/ipc.md
@@ -1,80 +1,36 @@
 # IPC provider
-The IPC (Inter-Process Communication) transport is a way for a process to communicate with a running Ethereum client over a local Unix domain socket. If you are new to IPC, you can [follow this link to learn more](https://en.wikipedia.org/wiki/Inter-process_communication). Using the IPC transport allows the ethers library to send JSON-RPC requests to the Ethereum client and receive responses, without the need for a network connection or HTTP server. This can be useful for interacting with a local Ethereum node that is running on the same machine. Using Ipc [is faster than RPC](https://github.com/0xKitsune/geth-ipc-rpc-bench), however you will need to have a local node that you can connect to.
 
-## Initializing an Ipc Provider
-Below is an example of how to initialize a new Ipc provider. 
-
-```rust
-#[tokio::main]
-async fn main() -> eyre::Result<()> {
-
-    // We instantiate the provider using the path of a local Unix domain socket
-    // --------------------------------------------------------------------------------
-    // NOTE: The IPC transport supports push notifications, but we still need to specify a polling
-    // interval because only subscribe RPC calls (e.g., transactions, blocks, events) support push
-    // notifications in Ethereum's RPC API. For other calls we must use repeated polling for many
-    // operations even with the IPC transport.
-    let provider = Provider::connect_ipc("~/.ethereum/geth.ipc").await?;
+The [IPC (Inter-Process Communication)](https://en.wikipedia.org/wiki/Inter-process_communication) transport allows our program to communicate with a node over a local [Unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket) or [Windows named pipe](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes).
 
-    Ok(())
-}
-```
-
-Note that if you are using Windows, you must use [Windows Ipc (Named pipes)](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes). Instead of passing the provider the path to the `.ipc` file, you must pass a named pipe (`\\<machine_address>\pipe\<pipe_name>`).  For a local geth connection, the named pipe will look something like this: `\\.\pipe\geth`
-
-## Usage
+Using the IPC transport allows the ethers library to send JSON-RPC requests to the Ethereum client and receive responses, without the need for a network connection or HTTP server. This can be useful for interacting with a local Ethereum node that is running on the same network. Using IPC [is faster than RPC](https://github.com/0xKitsune/geth-ipc-rpc-bench), however you will need to have a local node that you can connect to.
 
-The `Ipc` provider has the same methods as the `Ws` provider, allowing it to subscribe and unsubscribe via a `NotificationStream`.
+## Initializing an Ipc Provider
 
+Below is an example of how to initialize a new Ipc provider.
 
 ```rust
-use ethers::providers::{Middleware, Provider, StreamExt, Ws};
+use ethers::providers::Provider;
 
 #[tokio::main]
 async fn main() -> eyre::Result<()> {
+    // Using a UNIX domain socket: `/path/to/ipc`
+    #[cfg(unix)]
     let provider = Provider::connect_ipc("~/.ethereum/geth.ipc").await?;
-        
-    // Create a new stream yielding pending transactions from the mempool
-    let mut tx_pool_stream = provider.subscribe_pending_txs().await?;
 
-    while let Some(tx_hash) = tx_pool_stream.next().await {
-        println!("Pending tx: {:?}", tx_hash);
-    }
+    // Using a Windows named pipe: `\\<machine_address>\pipe\<pipe_name>`
+    #[cfg(windows)]
+    let provider = Provider::connect_ipc(r"\\.\pipe\geth").await?;
 
     Ok(())
 }
 ```
 
+## Usage
 
-Note that the `Ipc` provider, like all providers, has access to the methods defined by the `Middleware` trait. With this in mind, we can use the `Ipc` provider just like the `Http` provider as well, with the only difference being that we are connected to the node via a Unix socket now!
+The `Ipc` provider implements both `JsonRpcClient` and `PubsubClient`, just like `Ws`.
 
+In this example, we monitor the [`WETH/USDC`](https://etherscan.io/address/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc) [UniswapV2](https://docs.uniswap.org/) pair reserves and print when they have changed.
 
 ```rust
-use std::{str::FromStr, sync::Arc};
-
-use ethers::{
-    prelude::abigen,
-    providers::{Http, Provider},
-    types::H160,
-};
-
-abigen!(
-    IUniswapV2Pair,
-    r#"[function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)]"#
-);
-
-#[tokio::main]
-async fn main() -> eyre::Result<()> {
-    let provider = Provider::connect_ipc("~/.ethereum/geth.ipc").await?;
-
-    // Initialize a new instance of the Weth/Dai Uniswap V2 pair contract
-    let pair_address = H160::from_str("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11")?;
-    let uniswap_v2_pair = IUniswapV2Pair::new(pair_address, provider);
-
-    // Use the get_reserves() function to fetch the pool reserves
-    let (reserve_0, reserve_1, block_timestamp_last) =
-        uniswap_v2_pair.get_reserves().call().await?;
-
-    Ok(())
-}
+{{#include ../../examples/providers/examples/ipc.rs}}
 ```
diff --git a/book/providers/mock.md b/book/providers/mock.md
index 2363bf71..a4a1024a 100644
--- a/book/providers/mock.md
+++ b/book/providers/mock.md
@@ -2,4 +2,4 @@
 
 ```rust
 {{#include ../../examples/providers/examples/mock.rs}}
-```
\ No newline at end of file
+```
diff --git a/book/providers/providers.md b/book/providers/providers.md
index fa81fc04..a6968829 100644
--- a/book/providers/providers.md
+++ b/book/providers/providers.md
@@ -1,9 +1,22 @@
 # Providers
 
-Providers play a central role in `ethers-rs`, enabling you to establish asynchronous [Ethereum JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) compliant clients.
+A Provider is an abstraction of a connection to the Ethereum network, providing a concise, consistent interface to standard Ethereum node functionality.
 
-Providers let your program connect to a node to get data, interact with smart contracts, listen to the mempool and much more. There are a few different types of default providers that are built into the library. The default providers are `Http`,`WS`,`Ipc`,`RWClient`,`Quorum`,`Mock` and `RetryClient`. In addition to all of these options, you can also create your own custom provider, which we will walk through later in this chapter. For now let take a look at what the `Provider` actually looks like.
+This is achieved through the [`Middleware` trait][middleware], which provides the interface for the [Ethereum JSON-RPC API](https://ethereum.github.io/execution-apis/api-documentation) and other helpful methods, explained in more detail in [the Middleware chapter](../middleware/middleware.md), and the [`Provider`][provider] struct, which implements `Middleware`.
 
+## Data transports
+
+A [`Provider`][provider] wraps a generic data transport `P`, through which all JSON-RPC API calls are routed.
+
+Ethers provides concrete transport implementations for [HTTP](./http.md), [WebSockets](./ws.md), and [IPC](./ipc.md), as well as higher level transports which wrap a single or multiple transports. Of course, it is also possible to [define custom data transports](./custom.md).
+
+Transports implement the [`JsonRpcClient`](https://docs.rs/ethers/latest/ethers/providers/trait.JsonRpcClient.html) trait, which defines a `request` method, used for sending data to the underlying Ethereum node using [JSON-RPC](https://www.jsonrpc.org/specification).
+
+Transports can optionally implement the [`PubsubClient`](https://docs.rs/ethers/latest/ethers/providers/trait.PubsubClient.html) trait, if they support the [Publish-subscribe pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern), like `Websockets` and `IPC`. This is a [supertrait](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-supertraits-to-require-one-traits-functionality-within-another-trait) of `JsonRpcClient`. It defines the `subscribe` and `unsubscribe` methods.
+
+## The Provider type
+
+This is the definition of the [`Provider`][provider] type:
 
 ```rust
 #[derive(Clone, Debug)]
@@ -12,18 +25,17 @@ pub struct Provider<P> {
     ens: Option<Address>,
     interval: Option<Duration>,
     from: Option<Address>,
-    /// Node client hasn't been checked yet= `None`
-    /// Unsupported node client = `Some(None)`
-    /// Supported node client = `Some(Some(NodeClient))`
-    _node_client: Arc<Mutex<Option<NodeClient>>>,
+    node_client: Arc<Mutex<Option<NodeClient>>>,
 }
 ```
 
-
-The `Provider` struct defines a generic type `P` that can be any type that implements the [`JsonRpcClient` trait](https://docs.rs/ethers/latest/ethers/providers/trait.JsonRpcClient.html). The `inner` field stores the type that implements the `JsonRpcClient` type, allowing the Provider to make RPC calls to the node. The `ens` field is an optional value that specifies the ENS address for the provider's default sender. The `interval` field is an optional value that defines the polling interval when for streams (subscribing to logs, block headers, etc.). The `from` field is an optional type that allows you to set a default "from" address when constructing transactions and making calls. Lastly, the `_node_client` field is another optional value that allows the user to specify the node they are using to access node specific API calls. 
-
-
-Note that all providers implement the [`Middleware` trait](https://docs.rs/ethers/latest/ethers/providers/trait.Middleware.html), which gives every provider access to [commonly used methods](https://docs.rs/ethers/latest/ethers/providers/struct.Provider.html#impl-Middleware-for-Provider%3CP%3E) to interact with the node. Later in this chapter, we will go over these methods and examples for how to use them in detail. Additionally, `Middleware` will be covered extensively in a later chapter.
+-   `inner`: stores the generic data transport, which sends the requests;
+-   `ens`: optional override for the default ENS registry address;
+-   `interval`: optional value that defines the polling interval for `watch_*` streams;
+-   `from`: optional address that sets a default `from` address when constructing calls and transactions;
+-   `node_client`: the type of node the provider is connected to, like Geth, Erigon, etc.
 
 Now that you have a basis for what the `Provider` type actually is, the next few sections will walk through each implementation of the `Provider`, starting with the HTTP provider.
 
+[middleware]: https://docs.rs/ethers/latest/ethers/providers/trait.Middleware.html
+[provider]: https://docs.rs/ethers/latest/ethers/providers/struct.Provider.html
diff --git a/book/providers/retry.md b/book/providers/retry.md
index 1f9fd3eb..4d448c88 100644
--- a/book/providers/retry.md
+++ b/book/providers/retry.md
@@ -2,4 +2,4 @@
 
 ```rust
 {{#include ../../examples/providers/examples/retry.rs}}
-```
\ No newline at end of file
+```
diff --git a/book/providers/ws.md b/book/providers/ws.md
index 261485c8..e5acc046 100644
--- a/book/providers/ws.md
+++ b/book/providers/ws.md
@@ -1,17 +1,17 @@
 # WebSocket provider
-The Ws provider allows you to send JSON-RPC requests and receive responses over WebSocket connections. The WS provider can be used with any Ethereum node that supports WebSocket connections. This allows programs interact with the network in real-time without the need for HTTP polling for things like new block headers and filter logs. Ethers-rs has support for WebSockets via Tokio. Make sure that you have the “ws” and “rustls” / “openssl” features enabled in your project's toml file if you wish to use WebSockets.
-
 
+The Ws provider allows you to send JSON-RPC requests and receive responses over WebSocket connections. The WS provider can be used with any Ethereum node that supports WebSocket connections. This allows programs interact with the network in real-time without the need for HTTP polling for things like new block headers and filter logs. Ethers-rs has support for WebSockets via Tokio. Make sure that you have the “ws” and “rustls” / “openssl” features enabled in your project's toml file if you wish to use WebSockets.
 
 ## Initializing a WS Provider
-Lets look at a few ways to create a new `WS` provider. Below is the most straightforward way to initialize a new `Ws` provider.
 
+Lets look at a few ways to create a new `WS` provider. Below is the most straightforward way to initialize a new `Ws` provider.
 
 ```rust
+use ethers::providers::{Provider, Ws};
+
 #[tokio::main]
 async fn main() -> eyre::Result<()> {
-    let ws_endpoint = "";
-    let provider = Provider::<Ws>::connect(ws_endpoint).await?;
+    let provider = Provider::<Ws>::connect("wss://...").await?;
     Ok(())
 }
 ```
@@ -19,66 +19,23 @@ async fn main() -> eyre::Result<()> {
 Similar to the other providers, you can also establish an authorized connection with a node via websockets.
 
 ```rust
+use ethers::providers::{Authorization, Provider, Ws};
+
 #[tokio::main]
 async fn main() -> eyre::Result<()> {
-    let ws_endpoint = "";
+    let url = "wss://...";
     let auth = Authorization::basic("username", "password");
-    
-    if let Ok(_provider) = Provider::<Ws>::connect_with_auth(url, auth).await {
-        println!("Create Ws provider with auth");
-    }
-    
+    let provider = Provider::<Ws>::connect_with_auth(url, auth).await?;
     Ok(())
 }
 ```
 
 ## Usage
 
-The `Ws` provider allows a user to send requests to the node just like the other providers. In addition to these methods, the `Ws` provider can also subscribe to new logs and events, watch transactions in the mempool and other types of data streams from the node. The default polling interval for the `Ws` provider is `7 seconds`. You can update the polling interval, by using the `provider.interval()` method.
+The `Ws` provider allows a user to send requests to the node just like the other providers. In addition to these methods, the `Ws` provider can also subscribe to new logs and events, watch transactions in the mempool and other types of data streams from the node.
 
-In the snippet below, a new `Ws` provider is used to watch pending transactions in the mempool as well as new block headers in two separate threads.
+In the snippet below, a new `Ws` provider is used to subscribe to new pending transactions in the mempool as well as new block headers in two separate threads.
 
 ```rust
-use ethers::providers::{Middleware, Provider, StreamExt, Ws};
-use std::{sync::Arc, time::Duration};
-#[tokio::main]
-async fn main() -> eyre::Result<()> {
-    let ws_endpoint = "";
-    let mut provider = Provider::<Ws>::connect(ws_endpoint).await?;
-
-    // Update the polling interval
-    provider.set_interval(Duration::new(3, 0));
-
-    // Clone the providers to use in separate threads
-    let provider = Arc::new(provider);
-    let provider_0 = provider.clone();
-    let provider_1 = provider.clone();
-
-    let mut handles = vec![];
-
-    let pending_tx_handle = tokio::spawn(async move {
-        let mut tx_pool_stream = provider_0.watch_pending_transactions().await?;
-        while let Some(tx_hash) = tx_pool_stream.next().await {
-            println!("Pending tx: {:?}", tx_hash);
-        }
-    });
-
-    let new_block_headers_handle = tokio::spawn(async move {
-        let mut new_block_headers_stream = provider_1.watch_blocks().await?;
-        while let Some(block_hash) = new_block_headers_stream.next().await {
-            println!("New block: {:?}", block_hash);
-        }
-    });
-
-    // Add the JoinHandles to a vec and wait for the handles to complete
-    handles.push(pending_tx_handle);
-    handles.push(new_block_headers_handle);
-    for handle in handles {
-        if let Err(err) = handle.await {
-            panic!("{}", err);
-        }
-    }
-
-    Ok(())
-}
-```
\ No newline at end of file
+{{#include ../../examples/providers/examples/ws.rs}}
+```
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
index 5868f19d..c48e306d 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
@@ -1,11 +1,9 @@
-use super::{util, Context};
-
-use crate::contract::types;
+use super::{types, util, Context};
 use ethers_core::{
     abi::{Param, ParamType},
     macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
 };
-use proc_macro2::{Ident, TokenStream};
+use proc_macro2::{Ident, Literal, TokenStream};
 use quote::quote;
 
 /// Expands to the `name : type` pairs for the params
@@ -64,7 +62,7 @@ where
 }
 
 pub(crate) fn imports(name: &str) -> TokenStream {
-    let doc = util::expand_doc(&format!("{name} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs"));
+    let doc_str = format!("{name} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs");
 
     let ethers_core = ethers_core_crate();
     let ethers_providers = ethers_providers_crate();
@@ -75,7 +73,7 @@ pub(crate) fn imports(name: &str) -> TokenStream {
         #![allow(dead_code)]
         #![allow(clippy::type_complexity)]
         #![allow(unused_imports)]
-        #doc
+        #![doc = #doc_str]
 
         use std::sync::Arc;
         use #ethers_core::{
@@ -87,49 +85,56 @@ pub(crate) fn imports(name: &str) -> TokenStream {
     }
 }
 
-/// Generates the static `Abi` constants and the contract struct
+/// Generates the token stream for the contract's ABI, bytecode and struct declarations.
 pub(crate) fn struct_declaration(cx: &Context) -> TokenStream {
     let name = &cx.contract_ident;
-    let abi = &cx.abi_str;
-
-    let abi_name = cx.inline_abi_ident();
 
     let ethers_core = ethers_core_crate();
     let ethers_contract = ethers_contract_crate();
 
-    let abi_parse = if !cx.human_readable {
+    let abi = {
+        let abi_name = cx.inline_abi_ident();
+        let abi = &cx.abi_str;
+        let (doc_str, parse) = if cx.human_readable {
+            // Human readable: use abi::parse_abi_str
+            let doc_str = "The parsed human-readable ABI of the contract.";
+            let parse = quote!(#ethers_core::abi::parse_abi_str(__ABI));
+            (doc_str, parse)
+        } else {
+            // JSON ABI: use serde_json::from_str
+            let doc_str = "The parsed JSON ABI of the contract.";
+            let parse = quote!(#ethers_core::utils::__serde_json::from_str(__ABI));
+            (doc_str, parse)
+        };
+
         quote! {
             #[rustfmt::skip]
             const __ABI: &str = #abi;
 
-            /// The parsed JSON-ABI of the contract.
-            pub static #abi_name: #ethers_contract::Lazy<#ethers_core::abi::Abi> = #ethers_contract::Lazy::new(|| #ethers_core::utils::__serde_json::from_str(__ABI)
-                                              .expect("invalid abi"));
-        }
-    } else {
-        quote! {
-            /// The parsed human readable ABI of the contract.
-            pub static #abi_name: #ethers_contract::Lazy<#ethers_core::abi::Abi> = #ethers_contract::Lazy::new(|| #ethers_core::abi::parse_abi_str(#abi)
-                                                .expect("invalid abi"));
+            // This never fails as we are parsing the ABI in this macro
+            #[doc = #doc_str]
+            pub static #abi_name: #ethers_contract::Lazy<#ethers_core::abi::Abi> =
+                #ethers_contract::Lazy::new(|| #parse.expect("ABI is always valid"));
         }
     };
 
-    let bytecode = if let Some(ref bytecode) = cx.contract_bytecode {
+    let bytecode = cx.contract_bytecode.as_ref().map(|bytecode| {
+        let bytecode = bytecode.iter().copied().map(Literal::u8_unsuffixed);
         let bytecode_name = cx.inline_bytecode_ident();
-        let hex_bytecode = format!("{bytecode}");
         quote! {
-            /// Bytecode of the #name contract
-            pub static #bytecode_name: #ethers_contract::Lazy<#ethers_core::types::Bytes> = #ethers_contract::Lazy::new(|| #hex_bytecode.parse()
-                                                .expect("invalid bytecode"));
+            #[rustfmt::skip]
+            const __BYTECODE: &[u8] = &[ #( #bytecode ),* ];
+
+            #[doc = "The bytecode of the contract."]
+            pub static #bytecode_name: #ethers_core::types::Bytes = #ethers_core::types::Bytes::from_static(__BYTECODE);
         }
-    } else {
-        quote! {}
-    };
+    });
 
     quote! {
-        // Inline ABI declaration
-        #abi_parse
+        // The `Lazy` ABI
+        #abi
 
+        // The static Bytecode, if present
         #bytecode
 
         // Struct declaration
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/errors.rs b/ethers-contract/ethers-contract-abigen/src/contract/errors.rs
index e8c3d3fb..4462386e 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/errors.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/errors.rs
@@ -59,13 +59,12 @@ impl Context {
             expand_data_struct(&error_name, &fields)
         };
 
-        let doc = format!(
+        let doc_str = format!(
             "Custom Error type `{}` with signature `{}` and selector `0x{}`",
             error.name,
             abi_signature,
             hex::encode(&error.selector()[..])
         );
-        let abi_signature_doc = util::expand_doc(&doc);
         let ethers_contract = ethers_contract_crate();
         // use the same derives as for events
         let derives = util::expand_derives(&self.event_derives);
@@ -73,9 +72,9 @@ impl Context {
         let error_name = &error.name;
 
         Ok(quote! {
-             #abi_signature_doc
+            #[doc = #doc_str]
             #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthError, #ethers_contract::EthDisplay, #derives)]
-            #[etherror( name = #error_name, abi = #abi_signature )]
+            #[etherror(name = #error_name, abi = #abi_signature)]
             pub #data_type_definition
         })
     }
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs
index bae8ca66..f0e1d778 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs
@@ -217,24 +217,25 @@ impl Context {
 
     /// Expands into a single method for contracting an event stream.
     fn expand_filter(&self, event: &Event) -> TokenStream {
-        let ethers_contract = ethers_contract_crate();
+        let name = &event.name;
         let alias = self.event_aliases.get(&event.abi_signature()).cloned();
 
-        let name = if let Some(id) = alias.clone() {
+        // append `filter` to disambiguate with potentially conflicting
+        // function names
+        let function_name = if let Some(id) = alias.clone() {
             util::safe_ident(&format!("{}_filter", id.to_string().to_snake_case()))
         } else {
             util::safe_ident(&format!("{}_filter", event.name.to_snake_case()))
         };
+        let struct_name = event_struct_name(name, alias);
 
-        // append `filter` to disambiguate with potentially conflicting
-        // function names
+        let doc_str = format!("Gets the contract's `{name}` event");
 
-        let result = event_struct_name(&event.name, alias);
+        let ethers_contract = ethers_contract_crate();
 
-        let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
         quote! {
-            #doc
-            pub fn #name(&self) -> #ethers_contract::builders::Event<M, #result> {
+            #[doc = #doc_str]
+            pub fn #function_name(&self) -> #ethers_contract::builders::Event<M, #struct_name> {
                 self.0.event()
             }
         }
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index e2d048ad..b4d1ed21 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -124,13 +124,11 @@ impl Context {
         };
         let function_name = &function.name;
         let abi_signature = function.abi_signature();
-        let doc = format!(
-            "Container type for all input parameters for the `{}` function with signature `{}` and selector `0x{}`",
-            function.name,
-            abi_signature,
+        let doc_str = format!(
+            "Container type for all input parameters for the `{function_name}` function with signature `{abi_signature}` and selector `0x{}`",
             hex::encode(&function.selector()[..])
         );
-        let abi_signature_doc = util::expand_doc(&doc);
+
         let ethers_contract = ethers_contract_crate();
         // use the same derives as for events
         let derives = util::expand_derives(&self.event_derives);
@@ -146,7 +144,7 @@ impl Context {
         };
 
         Ok(quote! {
-            #abi_signature_doc
+            #[doc = #doc_str]
             #[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthCall, #ethers_contract::EthDisplay, #derives)]
             #derive_default
             #[ethcall( name = #function_name, abi = #abi_signature )]
@@ -160,6 +158,7 @@ impl Context {
         function: &Function,
         alias: Option<&MethodAlias>,
     ) -> Result<TokenStream> {
+        let name = &function.name;
         let struct_name = expand_return_struct_name(function, alias);
         let fields = self.expand_output_params(function)?;
         // no point in having structs when there is no data returned
@@ -176,13 +175,11 @@ impl Context {
             expand_data_struct(&struct_name, &fields)
         };
         let abi_signature = function.abi_signature();
-        let doc = format!(
-            "Container type for all return fields from the `{}` function with signature `{}` and selector `0x{}`",
-            function.name,
-            abi_signature,
+        let doc_str = format!(
+            "Container type for all return fields from the `{name}` function with signature `{abi_signature}` and selector `0x{}`",
             hex::encode(&function.selector()[..])
         );
-        let abi_signature_doc = util::expand_doc(&doc);
+
         let ethers_contract = ethers_contract_crate();
         // use the same derives as for events
         let derives = util::expand_derives(&self.event_derives);
@@ -198,9 +195,9 @@ impl Context {
         };
 
         Ok(quote! {
-            #abi_signature_doc
+            #[doc = #doc_str]
             #[derive(Clone, Debug,Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
-             #derive_default
+            #derive_default
             pub #return_type_definition
         })
     }
@@ -436,10 +433,11 @@ impl Context {
         function: &Function,
         alias: Option<MethodAlias>,
     ) -> Result<TokenStream> {
-        let ethers_contract = ethers_contract_crate();
+        let name = &function.name;
+        let function_name = expand_function_name(function, alias.as_ref());
+        let selector = function.selector();
 
-        let name = expand_function_name(function, alias.as_ref());
-        let selector = expand_selector(function.selector());
+        let selector_tokens = expand_selector(selector);
 
         let contract_args = self.expand_contract_call_args(function)?;
         let function_params =
@@ -448,18 +446,15 @@ impl Context {
 
         let outputs = self.expand_outputs(function)?;
 
-        let result = quote! { #ethers_contract::builders::ContractCall<M, #outputs> };
+        let doc_str =
+            format!("Calls the contract's `{name}` (0x{}) function", hex::encode(selector));
 
-        let doc = util::expand_doc(&format!(
-            "Calls the contract's `{}` (0x{}) function",
-            function.name,
-            hex::encode(function.selector())
-        ));
-        Ok(quote! {
+        let ethers_contract = ethers_contract_crate();
 
-            #doc
-            pub fn #name(&self #function_params) -> #result {
-                self.0.method_hash(#selector, #contract_args)
+        Ok(quote! {
+            #[doc = #doc_str]
+            pub fn #function_name(&self #function_params) -> #ethers_contract::builders::ContractCall<M, #outputs> {
+                self.0.method_hash(#selector_tokens, #contract_args)
                     .expect("method not found (this should never happen)")
             }
         })
@@ -470,9 +465,10 @@ impl Context {
     ///
     /// In case of overloaded functions we would follow rust's general
     /// 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))*
+    ///
+    /// 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))*
     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/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
index 661e9e37..4002e7b6 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
@@ -123,22 +123,18 @@ impl Context {
             }
         };
 
-        let sig = if let ParamType::Tuple(ref tokens) = tuple {
-            tokens.iter().map(|kind| kind.to_string()).collect::<Vec<_>>().join(",")
-        } else {
-            "".to_string()
+        let sig = match tuple {
+            ParamType::Tuple(ref types) if !types.is_empty() => util::abi_signature_types(types),
+            _ => String::new(),
         };
-
-        let abi_signature = format!("{name}({sig})",);
-
-        let abi_signature_doc = util::expand_doc(&format!("`{abi_signature}`"));
+        let doc_str = format!("`{name}({sig})`");
 
         // use the same derives as for events
         let derives = util::expand_derives(&self.event_derives);
 
         let ethers_contract = ethers_contract_crate();
         Ok(quote! {
-            #abi_signature_doc
+            #[doc = #doc_str]
             #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
             #struct_def
         })
@@ -178,13 +174,7 @@ impl Context {
             }
         }
 
-        let abi_signature = format!(
-            "{}({})",
-            name,
-            param_types.iter().map(|kind| kind.to_string()).collect::<Vec<_>>().join(","),
-        );
-
-        let abi_signature_doc = util::expand_doc(&format!("`{abi_signature}`"));
+        let abi_signature = util::abi_signature(name, &param_types);
 
         let name = util::ident(name);
 
@@ -195,7 +185,7 @@ impl Context {
         let ethers_contract = ethers_contract_crate();
 
         Ok(quote! {
-            #abi_signature_doc
+            #[doc = #abi_signature]
             #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
             pub struct #name {
                 #( #fields ),*
diff --git a/ethers-contract/ethers-contract-abigen/src/util.rs b/ethers-contract/ethers-contract-abigen/src/util.rs
index 92c4c4dc..48f9c6d5 100644
--- a/ethers-contract/ethers-contract-abigen/src/util.rs
+++ b/ethers-contract/ethers-contract-abigen/src/util.rs
@@ -1,7 +1,7 @@
 use ethers_core::abi::{Param, ParamType};
 use eyre::Result;
 use inflector::Inflector;
-use proc_macro2::{Ident, Literal, Span, TokenStream};
+use proc_macro2::{Ident, Span, TokenStream};
 use quote::quote;
 use std::path::PathBuf;
 use syn::{Ident as SynIdent, Path};
@@ -81,14 +81,6 @@ pub fn expand_input_name(index: usize, name: &str) -> TokenStream {
     quote! { #name }
 }
 
-/// Expands a doc string into an attribute token stream.
-pub fn expand_doc(s: &str) -> TokenStream {
-    let doc = Literal::string(s);
-    quote! {
-        #[doc = #doc]
-    }
-}
-
 pub fn expand_derives(derives: &[Path]) -> TokenStream {
     quote! {#(#derives),*}
 }
@@ -167,13 +159,16 @@ pub fn json_files(root: impl AsRef<std::path::Path>) -> Vec<PathBuf> {
         .collect()
 }
 
-/// rust-std derives `Default` automatically only for arrays len <= 32
+/// Returns whether all the given parameters can derive [`Default`].
 ///
-/// Returns whether the corresponding struct can derive `Default`
-pub fn can_derive_defaults(params: &[Param]) -> bool {
-    params.iter().map(|param| &param.kind).all(can_derive_default)
+/// rust-std derives `Default` automatically only for arrays len <= 32
+pub fn can_derive_defaults<'a>(params: impl IntoIterator<Item = &'a Param>) -> bool {
+    params.into_iter().map(|param| &param.kind).all(can_derive_default)
 }
 
+/// Returns whether the given type can derive [`Default`].
+///
+/// rust-std derives `Default` automatically only for arrays len <= 32
 pub fn can_derive_default(param: &ParamType) -> bool {
     const MAX_SUPPORTED_LEN: usize = 32;
     match param {
@@ -190,6 +185,21 @@ pub fn can_derive_default(param: &ParamType) -> bool {
     }
 }
 
+/// Returns the formatted Solidity ABI signature.
+pub fn abi_signature<'a, N, T>(name: N, types: T) -> String
+where
+    N: std::fmt::Display,
+    T: IntoIterator<Item = &'a ParamType>,
+{
+    let types = abi_signature_types(types);
+    format!("`{name}({types})`")
+}
+
+/// Returns the Solidity stringified ABI types joined by a single comma.
+pub fn abi_signature_types<'a, T: IntoIterator<Item = &'a ParamType>>(types: T) -> String {
+    types.into_iter().map(ToString::to_string).collect::<Vec<_>>().join(",")
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/ethers-contract/src/base.rs b/ethers-contract/src/base.rs
index 56792137..6f0c4395 100644
--- a/ethers-contract/src/base.rs
+++ b/ethers-contract/src/base.rs
@@ -1,4 +1,4 @@
-use crate::Contract;
+use crate::contract::ContractInstance;
 
 pub use ethers_core::abi::AbiError;
 use ethers_core::{
@@ -8,10 +8,10 @@ use ethers_core::{
 use ethers_providers::Middleware;
 
 use std::{
+    borrow::Borrow,
     collections::{BTreeMap, HashMap},
     fmt::Debug,
     hash::Hash,
-    sync::Arc,
 };
 
 /// A reduced form of `Contract` which just takes the `abi` and produces
@@ -195,12 +195,12 @@ impl BaseContract {
     }
 
     /// Upgrades a `BaseContract` into a full fledged contract with an address and middleware.
-    pub fn into_contract<M: Middleware>(
-        self,
-        address: Address,
-        client: impl Into<Arc<M>>,
-    ) -> Contract<M> {
-        Contract::new(address, self, client)
+    pub fn into_contract<B, M>(self, address: Address, client: B) -> ContractInstance<B, M>
+    where
+        B: Borrow<M>,
+        M: Middleware,
+    {
+        ContractInstance::new(address, self, client)
     }
 }
 
diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs
index 0c5a4985..97923ebd 100644
--- a/ethers-contract/src/call.rs
+++ b/ethers-contract/src/call.rs
@@ -15,12 +15,11 @@ use ethers_providers::{
 };
 
 use std::{
-    borrow::Cow,
+    borrow::{Borrow, Cow},
     fmt::Debug,
     future::{Future, IntoFuture},
     marker::PhantomData,
     pin::Pin,
-    sync::Arc,
 };
 
 use thiserror::Error as ThisError;
@@ -73,33 +72,49 @@ pub enum ContractError<M: Middleware> {
     ContractNotDeployed,
 }
 
+/// `ContractCall` is a [`FunctionCall`] object with an [`std::sync::Arc`] middleware.
+/// This type alias exists to preserve backwards compatibility with
+/// less-abstract Contracts.
+///
+/// For full usage docs, see [`FunctionCall`].
+pub type ContractCall<M, D> = FunctionCall<std::sync::Arc<M>, M, D>;
+
 #[derive(Debug)]
 #[must_use = "contract calls do nothing unless you `send` or `call` them"]
 /// Helper for managing a transaction before submitting it to a node
-pub struct ContractCall<M, D> {
+pub struct FunctionCall<B, M, D> {
     /// The raw transaction object
     pub tx: TypedTransaction,
     /// The ABI of the function being called
     pub function: Function,
     /// Optional block number to be used when calculating the transaction's gas and nonce
     pub block: Option<BlockId>,
-    pub(crate) client: Arc<M>,
+    pub(crate) client: B,
     pub(crate) datatype: PhantomData<D>,
+    pub(crate) _m: PhantomData<M>,
 }
 
-impl<M, D> Clone for ContractCall<M, D> {
+impl<B, M, D> Clone for FunctionCall<B, M, D>
+where
+    B: Clone,
+{
     fn clone(&self) -> Self {
-        ContractCall {
+        FunctionCall {
             tx: self.tx.clone(),
             function: self.function.clone(),
             block: self.block,
             client: self.client.clone(),
             datatype: self.datatype,
+            _m: self._m,
         }
     }
 }
 
-impl<M, D: Detokenize> ContractCall<M, D> {
+impl<B, M, D> FunctionCall<B, M, D>
+where
+    B: Borrow<M>,
+    D: Detokenize,
+{
     /// Sets the `from` field in the transaction to the provided value
     pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
         self.tx.set_from(from.into());
@@ -145,8 +160,9 @@ impl<M, D: Detokenize> ContractCall<M, D> {
     }
 }
 
-impl<M, D> ContractCall<M, D>
+impl<B, M, D> FunctionCall<B, M, D>
 where
+    B: Borrow<M>,
     M: Middleware,
     D: Detokenize,
 {
@@ -157,7 +173,11 @@ where
 
     /// Returns the estimated gas cost for the underlying transaction to be executed
     pub async fn estimate_gas(&self) -> Result<U256, ContractError<M>> {
-        self.client.estimate_gas(&self.tx, self.block).await.map_err(ContractError::MiddlewareError)
+        self.client
+            .borrow()
+            .estimate_gas(&self.tx, self.block)
+            .await
+            .map_err(ContractError::MiddlewareError)
     }
 
     /// Queries the blockchain via an `eth_call` for the provided transaction.
@@ -170,8 +190,12 @@ where
     ///
     /// Note: this function _does not_ send a transaction from your account
     pub async fn call(&self) -> Result<D, ContractError<M>> {
-        let bytes =
-            self.client.call(&self.tx, self.block).await.map_err(ContractError::MiddlewareError)?;
+        let bytes = self
+            .client
+            .borrow()
+            .call(&self.tx, self.block)
+            .await
+            .map_err(ContractError::MiddlewareError)?;
 
         // decode output
         let data = decode_function_data(&self.function, &bytes, false)?;
@@ -202,7 +226,7 @@ where
     ///
     /// Note: this function _does not_ send a transaction from your account
     pub fn call_raw_bytes(&self) -> CallBuilder<'_, M::Provider> {
-        let call = self.client.provider().call_raw(&self.tx);
+        let call = self.client.borrow().provider().call_raw(&self.tx);
         if let Some(block) = self.block {
             call.block(block)
         } else {
@@ -213,21 +237,28 @@ where
     /// Signs and broadcasts the provided transaction
     pub async fn send(&self) -> Result<PendingTransaction<'_, M::Provider>, ContractError<M>> {
         self.client
+            .borrow()
             .send_transaction(self.tx.clone(), self.block)
             .await
             .map_err(ContractError::MiddlewareError)
     }
 }
 
-/// [`ContractCall`] can be turned into [`Future`] automatically with `.await`.
-/// Defaults to calling [`ContractCall::call`].
-impl<M, D> IntoFuture for ContractCall<M, D>
+/// [`FunctionCall`] can be turned into [`Future`] automatically with `.await`.
+/// Defaults to calling [`FunctionCall::call`].
+impl<B, M, D> IntoFuture for FunctionCall<B, M, D>
 where
     Self: 'static,
+    B: Borrow<M> + Send + Sync,
     M: Middleware,
     D: Detokenize + Send + Sync,
 {
     type Output = Result<D, ContractError<M>>;
+
+    #[cfg(target_arch = "wasm32")]
+    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
+
+    #[cfg(not(target_arch = "wasm32"))]
     type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
 
     fn into_future(self) -> Self::IntoFuture {
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index 09a2d713..c54418a9 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -1,6 +1,6 @@
 use crate::{
     base::{encode_function_data, AbiError, BaseContract},
-    call::ContractCall,
+    call::FunctionCall,
     event::{EthEvent, Event},
     EthLogDecode,
 };
@@ -9,13 +9,20 @@ use ethers_core::{
     types::{Address, Filter, Selector, ValueOrArray},
 };
 use ethers_providers::Middleware;
-use std::{marker::PhantomData, sync::Arc};
+use std::{borrow::Borrow, fmt::Debug, marker::PhantomData, sync::Arc};
 
 #[cfg(not(feature = "legacy"))]
 use ethers_core::types::Eip1559TransactionRequest;
 #[cfg(feature = "legacy")]
 use ethers_core::types::TransactionRequest;
 
+/// `Contract` is a [`ContractInstance`] object with an `Arc` middleware.
+/// This type alias exists to preserve backwards compatibility with
+/// less-abstract Contracts.
+///
+/// For full usage docs, see [`ContractInstance`].
+pub type Contract<M> = ContractInstance<std::sync::Arc<M>, M>;
+
 /// A Contract is an abstraction of an executable program on the Ethereum Blockchain.
 /// It has code (called byte code) as well as allocated long-term memory
 /// (called storage). Every deployed Contract has an address, which is used to connect
@@ -69,7 +76,7 @@ use ethers_core::types::TransactionRequest;
 /// use ethers_contract::Contract;
 /// use ethers_providers::{Provider, Http};
 /// use ethers_signers::Wallet;
-/// use std::convert::TryFrom;
+/// use std::{convert::TryFrom, sync::Arc};
 ///
 /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
 /// // this is a fake address used just for this example
@@ -82,7 +89,7 @@ use ethers_core::types::TransactionRequest;
 /// let client = Provider::<Http>::try_from("http://localhost:8545").unwrap();
 ///
 /// // create the contract object at the address
-/// let contract = Contract::new(address, abi, client);
+/// let contract = Contract::new(address, abi, Arc::new(client));
 ///
 /// // Calling constant methods is done by calling `call()` on the method builder.
 /// // (if the function takes no arguments, then you must use `()` as the argument)
@@ -116,13 +123,13 @@ use ethers_core::types::TransactionRequest;
 /// use ethers_contract::{Contract, EthEvent};
 /// use ethers_providers::{Provider, Http, Middleware};
 /// use ethers_signers::Wallet;
-/// use std::convert::TryFrom;
+/// use std::{convert::TryFrom, sync::Arc};
 /// use ethers_core::abi::{Detokenize, Token, InvalidOutputType};
 /// # // this is a fake address used just for this example
 /// # let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
 /// # let abi: Abi = serde_json::from_str(r#"[]"#)?;
 /// # let client = Provider::<Http>::try_from("http://localhost:8545").unwrap();
-/// # let contract = Contract::new(address, abi, client);
+/// # let contract = Contract::new(address, abi, Arc::new(client));
 ///
 /// #[derive(Clone, Debug, EthEvent)]
 /// struct ValueChanged {
@@ -145,18 +152,40 @@ use ethers_core::types::TransactionRequest;
 ///
 /// _Disclaimer: these above docs have been adapted from the corresponding [ethers.js page](https://docs.ethers.io/ethers.js/html/api-contract.html)_
 ///
+/// # Usage Note
+///
+/// `ContractInternal` accepts any client that implements `B: Borrow<M>` where
+/// `M :Middleware`. Previous `Contract` versions used only arcs, and relied
+/// heavily on [`Arc`]. Due to constraints on the [`FunctionCall`] type,
+/// calling contracts requires a `B: Borrow<M> + Clone`. This is fine for most
+/// middlware. However, when `B` is an owned middleware that is not Clone, we
+/// cannot issue contract calls. Some notable exceptions:
+///
+/// - `NonceManagerMiddleware`
+/// - `SignerMiddleware` (when using a non-Clone Signer)
+///
+/// When using non-Clone middlewares, instead of instantiating a contract that
+/// OWNS the middlware, pass the contract a REFERENCE to the middleware. This
+/// will fix the trait bounds issue (as `&M` is always `Clone`).
+///
+/// We expect to fix this fully in a future version
+///
 /// [`abigen`]: macro.abigen.html
 /// [`Abigen` builder]: struct.Abigen.html
-/// [`event`]: method@crate::Contract::event
-/// [`method`]: method@crate::Contract::method
+/// [`event`]: method@crate::ContractInstance::event
+/// [`method`]: method@crate::ContractInstance::method
 #[derive(Debug)]
-pub struct Contract<M> {
+pub struct ContractInstance<B, M> {
     address: Address,
     base_contract: BaseContract,
-    client: Arc<M>,
+    client: B,
+    _m: PhantomData<M>,
 }
 
-impl<M> std::ops::Deref for Contract<M> {
+impl<B, M> std::ops::Deref for ContractInstance<B, M>
+where
+    B: Borrow<M>,
+{
     type Target = BaseContract;
 
     fn deref(&self) -> &Self::Target {
@@ -164,18 +193,25 @@ impl<M> std::ops::Deref for Contract<M> {
     }
 }
 
-impl<M> Clone for Contract<M> {
+impl<B, M> Clone for ContractInstance<B, M>
+where
+    B: Clone + Borrow<M>,
+{
     fn clone(&self) -> Self {
-        Contract {
+        ContractInstance {
             base_contract: self.base_contract.clone(),
             client: self.client.clone(),
             address: self.address,
+            _m: self._m,
         }
     }
 }
 
-impl<M> Contract<M> {
-    /// Returns the contract's address.
+impl<B, M> ContractInstance<B, M>
+where
+    B: Borrow<M>,
+{
+    /// Returns the contract's address
     pub fn address(&self) -> Address {
         self.address
     }
@@ -186,17 +222,24 @@ impl<M> Contract<M> {
     }
 
     /// Returns a pointer to the contract's client.
-    pub fn client(&self) -> Arc<M> {
-        Arc::clone(&self.client)
+    pub fn client(&self) -> B
+    where
+        B: Clone,
+    {
+        self.client.clone()
     }
 
     /// Returns a reference to the contract's client.
     pub fn client_ref(&self) -> &M {
-        Arc::as_ref(&self.client)
+        self.client.borrow()
     }
 }
 
-impl<M: Middleware> Contract<M> {
+impl<B, M> ContractInstance<B, M>
+where
+    B: Borrow<M>,
+    M: Middleware,
+{
     /// Returns an [`Event`](crate::builders::Event) builder for the provided event.
     /// This function operates in a static context, then it does not require a `self`
     /// to reference to instantiate an [`Event`](crate::builders::Event) builder.
@@ -209,14 +252,14 @@ impl<M: Middleware> Contract<M> {
     }
 }
 
-impl<M: Middleware> Contract<M> {
+impl<B, M> ContractInstance<B, M>
+where
+    B: Borrow<M>,
+    M: Middleware,
+{
     /// Creates a new contract from the provided client, abi and address
-    pub fn new(
-        address: impl Into<Address>,
-        abi: impl Into<BaseContract>,
-        client: impl Into<Arc<M>>,
-    ) -> Self {
-        Self { base_contract: abi.into(), client: client.into(), address: address.into() }
+    pub fn new(address: impl Into<Address>, abi: impl Into<BaseContract>, client: B) -> Self {
+        Self { base_contract: abi.into(), client, address: address.into(), _m: PhantomData }
     }
 
     /// Returns an [`Event`](crate::builders::Event) builder for the provided event.
@@ -227,7 +270,7 @@ impl<M: Middleware> Contract<M> {
     /// Returns an [`Event`](crate::builders::Event) builder with the provided filter.
     pub fn event_with_filter<D: EthLogDecode>(&self, filter: Filter) -> Event<M, D> {
         Event {
-            provider: &self.client,
+            provider: self.client.borrow(),
             filter: filter.address(ValueOrArray::Value(self.address)),
             datatype: PhantomData,
         }
@@ -240,40 +283,49 @@ impl<M: Middleware> Contract<M> {
         Ok(self.event_with_filter(Filter::new().event(&event.abi_signature())))
     }
 
-    /// Returns a transaction builder for the provided function name. If there are
-    /// multiple functions with the same name due to overloading, consider using
-    /// the `method_hash` method instead, since this will use the first match.
-    pub fn method<T: Tokenize, D: Detokenize>(
-        &self,
-        name: &str,
-        args: T,
-    ) -> Result<ContractCall<M, D>, AbiError> {
-        // get the function
-        let function = self.base_contract.abi.function(name)?;
-        self.method_func(function, args)
+    /// Returns a new contract instance using the provided client
+    ///
+    /// Clones `self` internally
+    #[must_use]
+    pub fn connect<N>(&self, client: Arc<N>) -> ContractInstance<Arc<N>, N>
+    where
+        N: Middleware,
+    {
+        ContractInstance {
+            base_contract: self.base_contract.clone(),
+            client,
+            address: self.address,
+            _m: PhantomData,
+        }
     }
 
-    /// Returns a transaction builder for the selected function signature. This should be
-    /// preferred if there are overloaded functions in your smart contract
-    pub fn method_hash<T: Tokenize, D: Detokenize>(
-        &self,
-        signature: Selector,
-        args: T,
-    ) -> Result<ContractCall<M, D>, AbiError> {
-        let function = self
-            .base_contract
-            .methods
-            .get(&signature)
-            .map(|(name, index)| &self.base_contract.abi.functions[name][*index])
-            .ok_or_else(|| Error::InvalidName(hex::encode(signature)))?;
-        self.method_func(function, args)
+    /// Returns a new contract instance using the provided client
+    ///
+    /// Clones `self` internally
+    #[must_use]
+    pub fn connect_with<C, N>(&self, client: C) -> ContractInstance<C, N>
+    where
+        C: Borrow<N>,
+    {
+        ContractInstance {
+            base_contract: self.base_contract.clone(),
+            client,
+            address: self.address,
+            _m: PhantomData,
+        }
     }
+}
 
+impl<B, M> ContractInstance<B, M>
+where
+    B: Clone + Borrow<M>,
+    M: Middleware,
+{
     fn method_func<T: Tokenize, D: Detokenize>(
         &self,
         function: &Function,
         args: T,
-    ) -> Result<ContractCall<M, D>, AbiError> {
+    ) -> Result<FunctionCall<B, M, D>, AbiError> {
         let data = encode_function_data(function, args)?;
 
         #[cfg(feature = "legacy")]
@@ -291,15 +343,45 @@ impl<M: Middleware> Contract<M> {
 
         let tx = tx.into();
 
-        Ok(ContractCall {
+        Ok(FunctionCall {
             tx,
-            client: Arc::clone(&self.client), // cheap clone behind the Arc
+            client: self.client.clone(),
             block: None,
             function: function.to_owned(),
             datatype: PhantomData,
+            _m: self._m,
         })
     }
 
+    /// Returns a transaction builder for the selected function signature. This should be
+    /// preferred if there are overloaded functions in your smart contract
+    pub fn method_hash<T: Tokenize, D: Detokenize>(
+        &self,
+        signature: Selector,
+        args: T,
+    ) -> Result<FunctionCall<B, M, D>, AbiError> {
+        let function = self
+            .base_contract
+            .methods
+            .get(&signature)
+            .map(|(name, index)| &self.base_contract.abi.functions[name][*index])
+            .ok_or_else(|| Error::InvalidName(hex::encode(signature)))?;
+        self.method_func(function, args)
+    }
+
+    /// Returns a transaction builder for the provided function name. If there are
+    /// multiple functions with the same name due to overloading, consider using
+    /// the `method_hash` method instead, since this will use the first match.
+    pub fn method<T: Tokenize, D: Detokenize>(
+        &self,
+        name: &str,
+        args: T,
+    ) -> Result<FunctionCall<B, M, D>, AbiError> {
+        // get the function
+        let function = self.base_contract.abi.function(name)?;
+        self.method_func(function, args)
+    }
+
     /// Returns a new contract instance at `address`.
     ///
     /// Clones `self` internally
@@ -309,12 +391,4 @@ impl<M: Middleware> Contract<M> {
         this.address = address.into();
         this
     }
-
-    /// Returns a new contract instance using the provided client
-    ///
-    /// Clones `self` internally
-    #[must_use]
-    pub fn connect<N>(&self, client: Arc<N>) -> Contract<N> {
-        Contract { base_contract: self.base_contract.clone(), client, address: self.address }
-    }
 }
diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs
index f764616a..2d4fc645 100644
--- a/ethers-contract/src/lib.rs
+++ b/ethers-contract/src/lib.rs
@@ -3,13 +3,13 @@
 #![deny(unsafe_code)]
 
 mod contract;
-pub use contract::Contract;
+pub use contract::{Contract, ContractInstance};
 
 mod base;
 pub use base::{decode_function_data, encode_function_data, AbiError, BaseContract};
 
 mod call;
-pub use call::{ContractError, EthCall};
+pub use call::{ContractCall, ContractError, EthCall, FunctionCall};
 
 mod error;
 pub use error::EthError;
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index f03ae067..38aaf4ff 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -29,14 +29,14 @@ chrono = { version = "0.4", default-features = false }
 serde = { version = "1.0.124", default-features = false, features = ["derive"] }
 serde_json = { version = "1.0.64", default-features = false, features = ["arbitrary_precision"] }
 thiserror = { version = "1.0", default-features = false }
-bytes = { version = "1.3.0", features = ["serde"] }
+bytes = { version = "1.4.0", features = ["serde"] }
 hex = { version = "0.4.3", default-features = false, features = ["std"] }
 once_cell = { version = "1.17.0", optional = true }
 unicode-xid = "0.2.4"
 strum = { version = "0.24", features = ["derive"] }
 
 # macros feature enabled dependencies
-cargo_metadata = { version = "0.15.2", optional = true }
+cargo_metadata = { version = "0.15.3", optional = true }
 
 # eip712 feature enabled dependencies
 convert_case = { version = "0.6.0", optional = true }
diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs
index e9cdbf55..5fd5c3b0 100644
--- a/ethers-core/src/abi/mod.rs
+++ b/ethers-core/src/abi/mod.rs
@@ -26,6 +26,9 @@ pub use human_readable::{
 mod raw;
 pub use raw::{AbiObject, Component, Item, JsonAbi, RawAbi};
 
+mod packed;
+pub use packed::{encode_packed, EncodePackedError};
+
 mod sealed {
     use ethabi::{Event, Function};
 
diff --git a/ethers-core/src/abi/packed.rs b/ethers-core/src/abi/packed.rs
new file mode 100644
index 00000000..e491f8dd
--- /dev/null
+++ b/ethers-core/src/abi/packed.rs
@@ -0,0 +1,424 @@
+use ethabi::Token;
+use thiserror::Error;
+use Token::*;
+
+/// An error thrown by [`encode_packed`].
+#[derive(Debug, Error)]
+pub enum EncodePackedError {
+    #[error("This token cannot be encoded in packed mode: {0:?}")]
+    InvalidToken(Token),
+
+    #[error("FixedBytes token length > 32")]
+    InvalidBytesLength,
+}
+
+/// Encodes the given tokens into an ABI compliant vector of bytes.
+///
+/// This function uses [non-standard packed mode][ref], where:
+/// - types shorter than 32 bytes are concatenated directly, without padding or sign extension;
+/// - dynamic types are encoded in-place and without the length;
+/// - array elements are padded, but still encoded in-place.
+///
+/// Since this encoding is ambiguous, there is no decoding function.
+///
+/// Note that this function has the same behaviour as its [Solidity counterpart][ref], and
+/// thus structs as well as nested arrays are not supported.
+///
+/// `Uint` and `Int` tokens will be encoded using the least number of bits, so no padding will be
+/// added by default.
+///
+/// [ref]: https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode
+///
+/// # Examples
+///
+/// Calculate the UniswapV2 pair address for two ERC20 tokens:
+///
+/// ```
+/// # use ethers_core::abi::{self, Token};
+/// # use ethers_core::types::{Address, H256};
+/// # use ethers_core::utils;
+/// let factory: Address = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f".parse()?;
+///
+/// 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>>(())
+/// ```
+pub fn encode_packed(tokens: &[Token]) -> Result<Vec<u8>, EncodePackedError> {
+    // Get vec capacity and find invalid tokens
+    let mut max = 0;
+    for token in tokens {
+        check(token)?;
+        max += max_encoded_length(token);
+    }
+
+    // Encode the tokens
+    let mut bytes = Vec::with_capacity(max);
+    for token in tokens {
+        encode_token(token, &mut bytes, false);
+    }
+    Ok(bytes)
+}
+
+/// The maximum byte length of the token encoded using packed mode.
+fn max_encoded_length(token: &Token) -> usize {
+    match token {
+        Int(_) | Uint(_) | FixedBytes(_) => 32,
+        Address(_) => 20,
+        Bool(_) => 1,
+        // account for padding
+        Array(vec) | FixedArray(vec) | Tuple(vec) => {
+            vec.iter().map(|token| max_encoded_length(token).max(32)).sum()
+        }
+        Bytes(b) => b.len(),
+        String(s) => s.len(),
+    }
+}
+
+/// Tuples and nested arrays are invalid in packed encoding.
+fn check(token: &Token) -> Result<(), EncodePackedError> {
+    match token {
+        FixedBytes(vec) if vec.len() > 32 => Err(EncodePackedError::InvalidBytesLength),
+
+        Tuple(_) => Err(EncodePackedError::InvalidToken(token.clone())),
+        Array(vec) | FixedArray(vec) => {
+            for t in vec.iter() {
+                if t.is_dynamic() || matches!(t, Array(_)) {
+                    return Err(EncodePackedError::InvalidToken(token.clone()))
+                }
+                check(t)?;
+            }
+            Ok(())
+        }
+
+        _ => Ok(()),
+    }
+}
+
+/// Encodes `token` as bytes into `out`.
+fn encode_token(token: &Token, out: &mut Vec<u8>, in_array: bool) {
+    match token {
+        // Padded to 32 bytes if in_array
+        Address(addr) => {
+            if in_array {
+                out.extend_from_slice(&[0; 12]);
+            }
+            out.extend_from_slice(&addr.0)
+        }
+        Int(n) | Uint(n) => {
+            let mut buf = [0; 32];
+            n.to_big_endian(&mut buf);
+            let start = if in_array { 0 } else { 32 - ((n.bits() + 7) / 8) };
+            out.extend_from_slice(&buf[start..32]);
+        }
+        Bool(b) => {
+            if in_array {
+                out.extend_from_slice(&[0; 31]);
+            }
+            out.push((*b) as u8);
+        }
+        FixedBytes(bytes) => {
+            out.extend_from_slice(bytes);
+            if in_array {
+                let mut remaining = vec![0; 32 - bytes.len()];
+                out.append(&mut remaining);
+            }
+        }
+
+        // Encode dynamic types in-place, without their length
+        Bytes(bytes) => out.extend_from_slice(bytes),
+        String(s) => out.extend_from_slice(s.as_bytes()),
+        Array(vec) | FixedArray(vec) => {
+            for token in vec {
+                encode_token(token, out, true);
+            }
+        }
+
+        // Should never happen
+        token => unreachable!("Uncaught invalid token: {token:?}"),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use hex_literal::hex;
+
+    fn encode(tokens: &[Token]) -> Vec<u8> {
+        encode_packed(tokens).unwrap()
+    }
+
+    fn string(s: &str) -> Token {
+        Token::String(s.into())
+    }
+
+    fn bytes(b: &[u8]) -> Token {
+        Token::Bytes(b.into())
+    }
+
+    #[test]
+    fn encode_bytes0() {
+        let expected = b"hello world";
+        assert_eq!(encode_packed(&[string("hello world")]).unwrap(), expected);
+        assert_eq!(encode_packed(&[bytes(b"hello world")]).unwrap(), expected);
+        assert_eq!(
+            encode_packed(&[string("hello"), string(" "), string("world")]).unwrap(),
+            expected
+        );
+        assert_eq!(
+            encode_packed(&[bytes(b"hello"), bytes(b" "), bytes(b"world")]).unwrap(),
+            expected
+        );
+    }
+
+    // modified from: ethabi::encoder::tests
+    // https://github.com/rust-ethereum/ethabi/blob/4e14ff83bf27de56555cc2ae0ece9ed2fe28fa0f/ethabi/src/encoder.rs#L181
+
+    #[test]
+    fn encode_address() {
+        let address = Token::Address([0x11u8; 20].into());
+        let encoded = encode(&[address]);
+        let expected = hex!("1111111111111111111111111111111111111111");
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_dynamic_array_of_addresses() {
+        let address1 = Token::Address([0x11u8; 20].into());
+        let address2 = Token::Address([0x22u8; 20].into());
+        let addresses = Token::Array(vec![address1, address2]);
+        let encoded = encode(&[addresses]);
+        let expected = hex!(
+            "
+			0000000000000000000000001111111111111111111111111111111111111111
+			0000000000000000000000002222222222222222222222222222222222222222
+		"
+        )
+        .to_vec();
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_fixed_array_of_addresses() {
+        let address1 = Token::Address([0x11u8; 20].into());
+        let address2 = Token::Address([0x22u8; 20].into());
+        let addresses = Token::FixedArray(vec![address1, address2]);
+        let encoded = encode(&[addresses]);
+        let expected = hex!(
+            "
+			0000000000000000000000001111111111111111111111111111111111111111
+			0000000000000000000000002222222222222222222222222222222222222222
+		"
+        )
+        .to_vec();
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_two_addresses() {
+        let address1 = Token::Address([0x11u8; 20].into());
+        let address2 = Token::Address([0x22u8; 20].into());
+        let encoded = encode(&[address1, address2]);
+        let expected = hex!(
+            "
+			1111111111111111111111111111111111111111
+			2222222222222222222222222222222222222222
+		"
+        )
+        .to_vec();
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_empty_array() {
+        let encoded = encode(&[Token::Array(vec![]), Token::Array(vec![])]);
+        assert_eq!(encoded, b"");
+    }
+
+    #[test]
+    fn encode_bytes() {
+        let bytes = Token::Bytes(hex!("1234").to_vec());
+        let encoded = encode(&[bytes]);
+        let expected = hex!("1234");
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_bytes2() {
+        let bytes = Token::Bytes(
+            hex!("10000000000000000000000000000000000000000000000000000000000002").to_vec(),
+        );
+        let encoded = encode(&[bytes]);
+        let expected =
+            hex!("10000000000000000000000000000000000000000000000000000000000002").to_vec();
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_bytes3() {
+        let bytes = Token::Bytes(
+            hex!(
+                "
+                    1000000000000000000000000000000000000000000000000000000000000000
+                    1000000000000000000000000000000000000000000000000000000000000000
+		        "
+            )
+            .to_vec(),
+        );
+        let encoded = encode(&[bytes]);
+        let expected = hex!(
+            "
+            1000000000000000000000000000000000000000000000000000000000000000
+            1000000000000000000000000000000000000000000000000000000000000000
+		    "
+        )
+        .to_vec();
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_string() {
+        let s = Token::String("gavofyork".to_owned());
+        let encoded = encode(&[s]);
+        assert_eq!(encoded, b"gavofyork");
+    }
+
+    #[test]
+    fn encode_two_bytes() {
+        let bytes1 = Token::Bytes(
+            hex!("10000000000000000000000000000000000000000000000000000000000002").to_vec(),
+        );
+        let bytes2 = Token::Bytes(
+            hex!("0010000000000000000000000000000000000000000000000000000000000002").to_vec(),
+        );
+        let encoded = encode(&[bytes1, bytes2]);
+        let expected = hex!(
+            "
+			10000000000000000000000000000000000000000000000000000000000002
+			0010000000000000000000000000000000000000000000000000000000000002
+		"
+        )
+        .to_vec();
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_fixed_bytes() {
+        let bytes = Token::FixedBytes(vec![0x12, 0x34]);
+        let encoded = encode(&[bytes]);
+        let expected = hex!("1234");
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_array_of_fixed_bytes() {
+        let array = Token::FixedArray(vec![
+            Token::FixedBytes(vec![0x12, 0x34]),
+            Token::FixedBytes(vec![0x56, 0x78]),
+        ]);
+        let encoded = encode(&[array]);
+        let expected = hex!(
+            "
+            1234000000000000000000000000000000000000000000000000000000000000
+            5678000000000000000000000000000000000000000000000000000000000000
+        "
+        );
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_uint() {
+        let mut uint = [0u8; 32];
+        uint[31] = 4;
+        let encoded = encode(&[Token::Uint(uint.into())]);
+        let expected = hex!("04");
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_int() {
+        let mut int = [0u8; 32];
+        int[31] = 4;
+        let encoded = encode(&[Token::Int(int.into())]);
+        let expected = hex!("04");
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_bool() {
+        let encoded = encode(&[Token::Bool(true)]);
+        let expected = hex!("01");
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn encode_bool2() {
+        let encoded = encode(&[Token::Bool(false)]);
+        let expected = hex!("00");
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn comprehensive_test() {
+        let bytes = hex!(
+            "
+			131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b
+			131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b
+		"
+        )
+        .to_vec();
+        let encoded = encode(&[
+            Token::Int(5.into()),
+            Token::Bytes(bytes.clone()),
+            Token::Int(3.into()),
+            Token::Bytes(bytes),
+        ]);
+
+        let expected = hex!(
+            "
+			05
+            131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b
+			131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b
+			03
+            131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b
+			131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b
+		"
+        )
+        .to_vec();
+        assert_eq!(encoded, expected);
+    }
+
+    #[test]
+    fn comprehensive_test2() {
+        let encoded = encode(&vec![
+            Token::Int(1.into()),
+            Token::String("gavofyork".to_owned()),
+            Token::Int(2.into()),
+            Token::Int(3.into()),
+            Token::Int(4.into()),
+            Token::Array(vec![Token::Int(5.into()), Token::Int(6.into()), Token::Int(7.into())]),
+        ]);
+
+        let expected = hex!(
+            "
+			01
+			6761766f66796f726b
+			02
+			03
+			04
+			0000000000000000000000000000000000000000000000000000000000000005
+			0000000000000000000000000000000000000000000000000000000000000006
+			0000000000000000000000000000000000000000000000000000000000000007
+		"
+        )
+        .to_vec();
+        assert_eq!(encoded, expected);
+    }
+}
diff --git a/ethers-core/src/types/block.rs b/ethers-core/src/types/block.rs
index 89b1dd69..0359c4b8 100644
--- a/ethers-core/src/types/block.rs
+++ b/ethers-core/src/types/block.rs
@@ -1,9 +1,7 @@
-// Taken from <https://github.com/tomusdrw/rust-web3/blob/master/src/types/block.rs>
+// Modified from <https://github.com/tomusdrw/rust-web3/blob/master/src/types/block.rs>
+
 use crate::types::{Address, Bloom, Bytes, Transaction, TxHash, H256, U256, U64};
 use chrono::{DateTime, TimeZone, Utc};
-#[cfg(not(feature = "celo"))]
-use core::cmp::Ordering;
-
 use serde::{
     de::{MapAccess, Visitor},
     ser::SerializeStruct,
@@ -13,6 +11,7 @@ use std::{fmt, fmt::Formatter, str::FromStr};
 use thiserror::Error;
 
 /// The block type returned from RPC calls.
+///
 /// This is generic over a `TX` type which will be either the hash or the full transaction,
 /// i.e. `Block<TxHash>` or `Block<Transaction>`.
 #[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
@@ -142,6 +141,8 @@ impl<TX> Block<TX> {
     /// Reference: <https://eips.ethereum.org/EIPS/eip-1559>
     #[cfg(not(feature = "celo"))]
     pub fn next_block_base_fee(&self) -> Option<U256> {
+        use core::cmp::Ordering;
+
         let target_usage = self.gas_target();
         let base_fee_per_gas = self.base_fee_per_gas?;
 
@@ -397,10 +398,10 @@ impl From<Block<Transaction>> for Block<TxHash> {
     }
 }
 
-#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
-#[cfg(feature = "celo")]
 /// Commit-reveal data for generating randomness in the
 /// [Celo protocol](https://docs.celo.org/celo-codebase/protocol/identity/randomness)
+#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
+#[cfg(feature = "celo")]
 pub struct Randomness {
     /// The committed randomness for that block
     pub committed: Bytes,
@@ -408,9 +409,9 @@ pub struct Randomness {
     pub revealed: Bytes,
 }
 
+/// SNARK-friendly epoch block signature and bitmap
 #[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
 #[cfg(feature = "celo")]
-/// SNARK-friendly epoch block signature and bitmap
 pub struct EpochSnarkData {
     /// The bitmap showing which validators signed on the epoch block
     pub bitmap: Bytes,
@@ -418,8 +419,8 @@ pub struct EpochSnarkData {
     pub signature: Bytes,
 }
 
+/// A [block hash](H256) or [block number](BlockNumber).
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
-/// A Block Hash or Block Number
 pub enum BlockId {
     // TODO: May want to expand this to include the requireCanonical field
     // <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md>
@@ -530,7 +531,21 @@ impl<'de> Deserialize<'de> for BlockId {
     }
 }
 
-/// A block Number (or tag - "latest", "earliest", "pending")
+impl FromStr for BlockId {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let s = s.strip_prefix("0x").unwrap_or(s);
+        if s.len() == 64 {
+            let hash = s.parse::<H256>().map_err(|e| e.to_string());
+            hash.map(Self::Hash)
+        } else {
+            s.parse().map(Self::Number)
+        }
+    }
+}
+
+/// A block number or tag.
 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
 pub enum BlockNumber {
     /// Latest block
@@ -624,15 +639,22 @@ impl FromStr for BlockNumber {
     type Err = String;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let block = match s {
-            "latest" => Self::Latest,
-            "finalized" => Self::Finalized,
-            "safe" => Self::Safe,
-            "earliest" => Self::Earliest,
-            "pending" => Self::Pending,
-            n => BlockNumber::Number(n.parse::<U64>().map_err(|err| err.to_string())?),
-        };
-        Ok(block)
+        match s {
+            "latest" => Ok(Self::Latest),
+            "finalized" => Ok(Self::Finalized),
+            "safe" => Ok(Self::Safe),
+            "earliest" => Ok(Self::Earliest),
+            "pending" => Ok(Self::Pending),
+            n => {
+                if let Ok(n) = n.parse::<U64>() {
+                    Ok(Self::Number(n))
+                } else if let Ok(n) = n.parse::<u64>() {
+                    Ok(Self::Number(n.into()))
+                } else {
+                    Err("Invalid block number".into())
+                }
+            }
+        }
     }
 }
 
diff --git a/ethers-core/src/types/bytes.rs b/ethers-core/src/types/bytes.rs
index b6d8833c..f09c9170 100644
--- a/ethers-core/src/types/bytes.rs
+++ b/ethers-core/src/types/bytes.rs
@@ -16,31 +16,62 @@ pub struct Bytes(
     pub  bytes::Bytes,
 );
 
-fn bytes_to_hex(b: &Bytes) -> String {
-    hex::encode(b.0.as_ref())
+impl Bytes {
+    /// Creates a new empty `Bytes`.
+    ///
+    /// This will not allocate and the returned `Bytes` handle will be empty.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use ethers_core::types::Bytes;
+    ///
+    /// let b = Bytes::new();
+    /// assert_eq!(&b[..], b"");
+    /// ```
+    #[inline]
+    pub const fn new() -> Self {
+        Self(bytes::Bytes::new())
+    }
+
+    /// Creates a new `Bytes` from a static slice.
+    ///
+    /// The returned `Bytes` will point directly to the static slice. There is
+    /// no allocating or copying.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use ethers_core::types::Bytes;
+    ///
+    /// let b = Bytes::from_static(b"hello");
+    /// assert_eq!(&b[..], b"hello");
+    /// ```
+    #[inline]
+    pub const fn from_static(bytes: &'static [u8]) -> Self {
+        Self(bytes::Bytes::from_static(bytes))
+    }
+
+    fn hex_encode(&self) -> String {
+        hex::encode(self.0.as_ref())
+    }
 }
 
 impl Debug for Bytes {
     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
-        write!(f, "Bytes(0x{})", bytes_to_hex(self))
+        write!(f, "Bytes(0x{})", self.hex_encode())
     }
 }
 
 impl Display for Bytes {
     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
-        write!(f, "0x{}", bytes_to_hex(self))
+        write!(f, "0x{}", self.hex_encode())
     }
 }
 
 impl LowerHex for Bytes {
     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
-        write!(f, "0x{}", bytes_to_hex(self))
-    }
-}
-
-impl Bytes {
-    pub fn to_vec(&self) -> Vec<u8> {
-        self.as_ref().to_vec()
+        write!(f, "0x{}", self.hex_encode())
     }
 }
 
diff --git a/ethers-core/src/types/ens.rs b/ethers-core/src/types/ens.rs
index af8faaab..baa2bebe 100644
--- a/ethers-core/src/types/ens.rs
+++ b/ethers-core/src/types/ens.rs
@@ -1,7 +1,7 @@
 use crate::types::Address;
 use rlp::{Decodable, Encodable, RlpStream};
 use serde::{ser::Error as SerializationError, Deserialize, Deserializer, Serialize, Serializer};
-use std::{cmp::Ordering, convert::Infallible, str::FromStr};
+use std::{cmp::Ordering, str::FromStr};
 
 /// ENS name or Ethereum Address. Not RLP encoded/serialized if it's a name.
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -100,10 +100,14 @@ impl From<Address> for NameOrAddress {
 }
 
 impl FromStr for NameOrAddress {
-    type Err = Infallible;
+    type Err = <Address as FromStr>::Err;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        Ok(Self::Name(s.to_string()))
+        if s.starts_with("0x") {
+            s.parse().map(Self::Address)
+        } else {
+            Ok(Self::Name(s.to_string()))
+        }
     }
 }
 
diff --git a/ethers-core/src/types/log.rs b/ethers-core/src/types/log.rs
index 3d055d56..03281f50 100644
--- a/ethers-core/src/types/log.rs
+++ b/ethers-core/src/types/log.rs
@@ -1,3 +1,4 @@
+use ethabi::RawLog;
 // Adapted from https://github.com/tomusdrw/rust-web3/blob/master/src/types/log.rs
 use crate::types::{Address, Bytes, H256, U256, U64};
 use serde::{Deserialize, Serialize};
@@ -68,4 +69,10 @@ impl rlp::Encodable for Log {
     }
 }
 
+impl From<Log> for RawLog {
+    fn from(val: Log) -> Self {
+        (val.topics, val.data.to_vec()).into()
+    }
+}
+
 // TODO: Implement more common types - or adjust this to work with all Tokenizable items
diff --git a/ethers-core/src/types/other.rs b/ethers-core/src/types/other.rs
index abb570d3..72874235 100644
--- a/ethers-core/src/types/other.rs
+++ b/ethers-core/src/types/other.rs
@@ -1,7 +1,10 @@
 //! Support for capturing other fields
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use serde_json::Map;
-use std::{collections::BTreeMap, ops::Deref};
+use std::{
+    collections::BTreeMap,
+    ops::{Deref, DerefMut},
+};
 
 /// A type that is supposed to capture additional fields that are not native to ethereum but included in ethereum adjacent networks, for example fields the [optimism `eth_getTransactionByHash` request](https://docs.alchemy.com/alchemy/apis/optimism/eth-gettransactionbyhash) returns additional fields that this type will capture
 ///
@@ -138,6 +141,12 @@ impl Deref for OtherFields {
     }
 }
 
+impl DerefMut for OtherFields {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.inner
+    }
+}
+
 impl AsRef<BTreeMap<String, serde_json::Value>> for OtherFields {
     fn as_ref(&self) -> &BTreeMap<String, serde_json::Value> {
         &self.inner
diff --git a/ethers-core/src/types/trace/geth.rs b/ethers-core/src/types/trace/geth.rs
index c346a299..6c4ca3ac 100644
--- a/ethers-core/src/types/trace/geth.rs
+++ b/ethers-core/src/types/trace/geth.rs
@@ -1,17 +1,18 @@
 use crate::{
-    types::{Bytes, H256, U256},
+    types::{Address, Bytes, NameOrAddress, H256, U256},
     utils::from_int_or_hex,
 };
-use serde::{Deserialize, Serialize, Serializer};
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
 use std::collections::BTreeMap;
 
 // https://github.com/ethereum/go-ethereum/blob/a9ef135e2dd53682d106c6a2aede9187026cc1de/eth/tracers/logger/logger.go#L406-L411
 #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
-pub struct GethTrace {
+pub struct DefaultFrame {
     pub failed: bool,
     #[serde(deserialize_with = "from_int_or_hex")]
     pub gas: U256,
-    #[serde(serialize_with = "serialize_bytes", rename = "returnValue")]
+    #[serde(rename = "returnValue")]
     pub return_value: Bytes,
     #[serde(rename = "structLogs")]
     pub struct_logs: Vec<StructLog>,
@@ -21,34 +22,107 @@ pub struct GethTrace {
 #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
 pub struct StructLog {
     pub depth: u64,
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
     pub error: Option<String>,
     pub gas: u64,
     #[serde(rename = "gasCost")]
     pub gas_cost: u64,
     /// ref <https://github.com/ethereum/go-ethereum/blob/366d2169fbc0e0f803b68c042b77b6b480836dbc/eth/tracers/logger/logger.go#L450-L452>
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
     pub memory: Option<Vec<String>>,
     pub op: String,
     pub pc: u64,
-    #[serde(rename = "refund", skip_serializing_if = "Option::is_none")]
+    #[serde(default, rename = "refund", skip_serializing_if = "Option::is_none")]
     pub refund_counter: Option<u64>,
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
     pub stack: Option<Vec<U256>>,
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
     pub storage: Option<BTreeMap<H256, H256>>,
 }
 
+// https://github.com/ethereum/go-ethereum/blob/a9ef135e2dd53682d106c6a2aede9187026cc1de/eth/tracers/native/call.go#L37
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct CallFrame {
+    #[serde(rename = "type")]
+    pub typ: String,
+    pub from: Address,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub to: Option<NameOrAddress>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub value: Option<U256>,
+    #[serde(deserialize_with = "from_int_or_hex")]
+    pub gas: U256,
+    #[serde(deserialize_with = "from_int_or_hex", rename = "gasUsed")]
+    pub gas_used: U256,
+    pub input: Bytes,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub output: Option<Bytes>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub error: Option<String>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub calls: Option<Vec<CallFrame>>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+#[serde(untagged)]
+pub enum GethTraceFrame {
+    Default(DefaultFrame),
+    CallTracer(CallFrame),
+}
+
+impl From<DefaultFrame> for GethTraceFrame {
+    fn from(value: DefaultFrame) -> Self {
+        GethTraceFrame::Default(value)
+    }
+}
+
+impl From<CallFrame> for GethTraceFrame {
+    fn from(value: CallFrame) -> Self {
+        GethTraceFrame::CallTracer(value)
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+#[serde(untagged)]
+pub enum GethTrace {
+    Known(GethTraceFrame),
+    Unknown(Value),
+}
+
+impl From<GethTraceFrame> for GethTrace {
+    fn from(value: GethTraceFrame) -> Self {
+        GethTrace::Known(value)
+    }
+}
+
+impl From<Value> for GethTrace {
+    fn from(value: Value) -> Self {
+        GethTrace::Unknown(value)
+    }
+}
+
 /// Available built-in tracers
 ///
 /// See <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>
-pub enum GethDebugTracerType {
-    /// callTracer (native)
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+pub enum GethDebugBuiltInTracerType {
     #[serde(rename = "callTracer")]
     CallTracer,
 }
 
+/// Available tracers
+///
+/// See <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers> and <https://geth.ethereum.org/docs/developers/evm-tracing/custom-tracer>
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+#[serde(untagged)]
+pub enum GethDebugTracerType {
+    /// built-in tracer
+    BuiltInTracer(GethDebugBuiltInTracerType),
+
+    /// custom JS tracer
+    JsTracer(String),
+}
+
 /// Bindings for additional `debug_traceTransaction` options
 ///
 /// See <https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracetransaction>
@@ -79,11 +153,3 @@ pub struct GethDebugTracingCallOptions {
     pub tracing_options: GethDebugTracingOptions,
     // TODO: Add stateoverrides and blockoverrides options
 }
-
-fn serialize_bytes<S, T>(x: T, s: S) -> Result<S::Ok, S::Error>
-where
-    S: Serializer,
-    T: AsRef<[u8]>,
-{
-    s.serialize_str(&hex::encode(x.as_ref()))
-}
diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs
index a266169a..1e6dbe42 100644
--- a/ethers-core/src/utils/geth.rs
+++ b/ethers-core/src/utils/geth.rs
@@ -48,6 +48,8 @@ pub struct GethInstance {
     ipc: Option<PathBuf>,
     data_dir: Option<PathBuf>,
     p2p_port: Option<u16>,
+    genesis: Option<Genesis>,
+    clique_private_key: Option<SigningKey>,
 }
 
 impl GethInstance {
@@ -81,6 +83,16 @@ impl GethInstance {
         &self.data_dir
     }
 
+    /// Returns the genesis configuration used to conifugre this instance
+    pub fn genesis(&self) -> &Option<Genesis> {
+        &self.genesis
+    }
+
+    /// Returns the private key used to configure clique on this instance
+    pub fn clique_private_key(&self) -> &Option<SigningKey> {
+        &self.clique_private_key
+    }
+
     /// Takes the stderr contained in the child process.
     ///
     /// This leaves a `None` in its place, so calling methods that require a stderr to be present
@@ -186,9 +198,9 @@ pub struct Geth {
     data_dir: Option<PathBuf>,
     chain_id: Option<u64>,
     insecure_unlock: bool,
-    pub genesis: Option<Genesis>,
+    genesis: Option<Genesis>,
     mode: GethMode,
-    pub clique_private_key: Option<SigningKey>,
+    clique_private_key: Option<SigningKey>,
 }
 
 impl Geth {
@@ -515,7 +527,15 @@ impl Geth {
 
         child.stderr = Some(reader.into_inner());
 
-        GethInstance { pid: child, port, ipc: self.ipc_path, data_dir: self.data_dir, p2p_port }
+        GethInstance {
+            pid: child,
+            port,
+            ipc: self.ipc_path,
+            data_dir: self.data_dir,
+            p2p_port,
+            genesis: self.genesis,
+            clique_private_key: self.clique_private_key,
+        }
     }
 }
 
@@ -569,4 +589,64 @@ mod tests {
 
         assert!(p2p_port.is_none());
     }
+
+    #[test]
+    fn clique_private_key_configured() {
+        let temp_dir = tempfile::tempdir().unwrap();
+        let temp_dir_path = temp_dir.path().to_path_buf();
+
+        let private_key = SigningKey::random(&mut rand::thread_rng());
+        let geth = Geth::new()
+            .set_clique_private_key(private_key)
+            .chain_id(1337u64)
+            .data_dir(temp_dir_path)
+            .spawn();
+
+        let clique_private_key = geth.clique_private_key().clone();
+
+        drop(geth);
+        temp_dir.close().unwrap();
+
+        assert!(clique_private_key.is_some());
+    }
+
+    #[test]
+    fn clique_genesis_configured() {
+        let temp_dir = tempfile::tempdir().unwrap();
+        let temp_dir_path = temp_dir.path().to_path_buf();
+
+        let private_key = SigningKey::random(&mut rand::thread_rng());
+        let geth = Geth::new()
+            .set_clique_private_key(private_key)
+            .chain_id(1337u64)
+            .data_dir(temp_dir_path)
+            .spawn();
+
+        let genesis = geth.genesis().clone();
+
+        drop(geth);
+        temp_dir.close().unwrap();
+
+        assert!(genesis.is_some());
+    }
+
+    #[test]
+    fn clique_p2p_configured() {
+        let temp_dir = tempfile::tempdir().unwrap();
+        let temp_dir_path = temp_dir.path().to_path_buf();
+
+        let private_key = SigningKey::random(&mut rand::thread_rng());
+        let geth = Geth::new()
+            .set_clique_private_key(private_key)
+            .chain_id(1337u64)
+            .data_dir(temp_dir_path)
+            .spawn();
+
+        let p2p_port = geth.p2p_port();
+
+        drop(geth);
+        temp_dir.close().unwrap();
+
+        assert!(p2p_port.is_some());
+    }
 }
diff --git a/ethers-etherscan/src/errors.rs b/ethers-etherscan/src/errors.rs
index f840cc57..f10920ba 100644
--- a/ethers-etherscan/src/errors.rs
+++ b/ethers-etherscan/src/errors.rs
@@ -39,6 +39,8 @@ pub enum EtherscanError {
     InvalidApiKey,
     #[error("Sorry, you have been blocked by Cloudflare, See also https://community.cloudflare.com/t/sorry-you-have-been-blocked/110790")]
     BlockedByCloudflare,
+    #[error("The Requested prompted a cloudflare captcha security challenge to review the security of your connection before proceeding.")]
+    CloudFlareSecurityChallenge,
 }
 
 /// etherscan/polyscan is protected by cloudflare, which can lead to html responses like `Sorry, you have been blocked` See also <https://community.cloudflare.com/t/sorry-you-have-been-blocked/110790>
@@ -48,10 +50,23 @@ pub(crate) fn is_blocked_by_cloudflare_response(txt: &str) -> bool {
     txt.to_lowercase().contains("sorry, you have been blocked")
 }
 
+/// etherscan/polyscan is protected by cloudflare, which can require captchas to "review the
+/// security of your connection before proceeding"
+pub(crate) fn is_cloudflare_security_challenge(txt: &str) -> bool {
+    txt.contains("https://www.cloudflare.com?utm_source=challenge") ||
+        txt.to_lowercase().contains("checking if the site connection is secure")
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
 
+    #[test]
+    fn test_is_cloudflare_security_challenge() {
+        let res = "<!DOCTYPE html><html lang=\"en-US\"><head>    <title>Just a moment...</title>    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge\">    <meta name=\"robots\" content=\"noindex,nofollow\">    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">    <link href=\"/cdn-cgi/styles/challenges.css\" rel=\"stylesheet\">    </head><body class=\"no-js\">    <div class=\"main-wrapper\" role=\"main\">    <div class=\"main-content\">        <h1 class=\"zone-name-title h1\">            <img class=\"heading-favicon\" src=\"/favicon.ico\" alt=\"Icon for api-goerli.etherscan.io\"                 onerror=\"this.onerror=null;this.parentNode.removeChild(this)\">            api-goerli.etherscan.io        </h1>        <h2 class=\"h2\" id=\"challenge-running\">            Checking if the site connection is secure        </h2>        <noscript>            <div id=\"challenge-error-title\">                <div class=\"h2\">                    <span class=\"icon-wrapper\">                        <div class=\"heading-icon warning-icon\"></div>                    </span>                    <span id=\"challenge-error-text\">                        Enable JavaScript and cookies to continue                    </span>                </div>            </div>        </noscript>        <div id=\"trk_jschal_js\" style=\"display:none;background-image:url('/cdn-cgi/images/trace/captcha/nojs/transparent.gif?ray=794294b0ff122cc8')\"></div>        <div id=\"challenge-body-text\" class=\"core-msg spacer\">            api-goerli.etherscan.io needs to review the security of your connection before proceeding.        </div>        <form id=\"challenge-form\" action=\"/api/?__cf_chl_f_tk=3e8YnNWEoJpt7yhj9ZB_z7nPP6BpgWkrEO9fxS_FHfs-1675505052-0-gaNycGzNCv0\" method=\"POST\" enctype=\"application/x-www-form-urlencoded\">            <input type=\"hidden\" name=\"md\" value=\"kphchgOl8CtVKUgr8zjngomaIt8zm7QzUkLvDaiTi.Q-1675505052-0-Ae-rscWjz835ha6epVnom-tK6T9VtNzQrwuMr1t3Ajth5BX5PQNhXdiKh7SzcaqcQ1sNxb_BVXo_zQsvD9DKNXvBQXaSAWho5s2SvMaYxzNolLg01kyTNP0b9YiAKirGukD524hsIbyLgZRR3H8VDEfVwpDADGKb3MhA5rwiHE8oGZcLgjcVjj4nXGYWGwKOjo6LyPWuoRLSHrMRYzsrizm-GQ1fafuos6afDCPTV6B05TLLodI4z2wNbeirPKaZGL3rScvPR_F-CGS1vWFJvLxlqJ0dulyMP_gehm_1AJm-FPevnqJoCgyU51wBgCkkVfOmwj31xAFhk1SaWUIrjxhN-PRH2RUMtnEdWHRffX20JCcc4OJ2HbTZ-J7u9U713PG-A3SPE2Az_nUoVOeJ90_aytPlYLppspJCYFceac4VUUldRVlGyHGkZBafp9xA0wOJzAhDy8K9PiVfRkso-dyM1t5tNr4qCtg3HutQ5u2E\">            <input type=\"hidden\" name=\"r\" value=\"qppUKLcDpkximVAwuJGymQi_4Iem1Vkg1AKAEuUoy4o-1675505052-0-AUjKqnZvkX/rH2NF9nWOKnBioJS6gAqsecZ9eBAReDbXOyH5oBbk82apd8JE6SBpwnsH8HSZB4WR3SmHm3Xcjx/DW+BzBEgXa3JTofbKCg0kf1Nel3ZsDHnbr5ytKZtayqWpgePUifTkyHTwRN1x/5U8FUZI5WsBl6HWWlMLwfJBpDdB0ivDdyXkivgB+56Dv3QXznJysMig3umWQDLDCD+ywJkeW7xct4l6wyCvLYUqs2MmQ4dV1FQidhJHqmO+zy4YGl1Db5TZH3MkL9ut2kYeMLj794tjA4P26y2qSf2Y7Tzj1C4dP9ZrfiAeaTK76HrHhpjZYnf8FcMjYhp7/C2xPnI/kXpesffJosE0t+L6zrwDrq9SUe1HviAgbETcWSI6uNOtw0RFQ4u5QMRR2u+eJEXhbqupVAkDF6XdSL2DppU+/ndpoQ7NVrtn4FwfW3gj4+4i95A2R5g+lisi97znSUdazE3YBLof0oZCdlL2WkBX2g6XxM3fMk7vk/i0LUDxsgZdPn6hndGPpEk06ilQ8ZGyNoqOOqbFa3/u1jTDmQ60B37Qp58bItB46XScO9DuVbtyEJ1VsWHyN1Q+hJE2TLaFuYmXkJMxDpP6dmqQ+GlXuIueRC/hompkJpDnzfhMg22G3X4EFw+I8vvMeHGcQvvSL1DcAr1V0g4egwpaZgtUQEnHT6V3/Os7j6OGdlioDLnIeowBM4M2mPEQ+ikcz9unZ47025a3VFJy4SuPgJpaAqCntlqedrDwKGXEFJFnsJmj/lTwb38rgGX2irzSxcOXNC4MWlDkwae3KJz/rUhdgDQknTkw6xvNmPhkCIwgIcagEAgNzQfvih72/cXQPiAAauB7+p/vS5VH6052cbNgvYZ74C2BEkPvw6qj3ASWorsiT8/BJfn/CcArpL0AYP/jL6QbubPqFLfO0Zs9e55lT0cMkko2+jT66HWgIUashrjW1dMxoCAZ97TSVRm4tZ/pCWEQym6tSr6EnkUA7GdbfAwCnxIvD5DH/0J81WJjMkW6C6+NC5GFC3iWoN48MAHXTtvpm7d3g6aQpGydNiP9QVckwCkoF0eFbSV8OE6oZsbJfKo0TzYi2Eg2gEZJYmZOqDHI2E3KdODxdd3vUeL73bZkyNqsDK/u9AC8kiqHna4oaGZ9ABeym6sGqHl9cwTsf5h2EUexMklhGu0QChVyOXhPTv0I3O1+/YVkrWX2wX40XC+KqCqc00lNyzf3zEdgM65WMC3OTrljansYl81eUXfoNpcPL/K+37kFG+f8UQxIiZu+q1/qMPgGA4bpQTe0bki3IFSkV/nKwCjfzy0j4b8YWhGyCY7yuaSTsLViVeYrRK6pOl9WNhEmsYqdeoKmp/SA7G2WsPCkth/OUYxmzjYKDikWzXv3sp/0w+kjh4vmPqTgoY5rK/cfueHuy9F2ojOxETD44yGn9ulogLILxgquGkuSTU60YBQY8MKtk9m/PMnt81SxMXeo29zNAkuyASqzRaAT9RZGguZuXEDmvIgb2LQ6doPLOo/RqA5+FJnLMfMoTIXYNERPAHin4SMa0CHia+pS0VqOgitj/g5wADnJwHgrwatNxmqOsho6vSx/H15XswadKEgVMk2dHGEG1Tdh0LSekt3+IOmjTRP7eJiS5Tdx0+vnP7R4QjWBgpYgc1Xg9JKDhSZypsdln47cluQA0qaLRgz+yrXAxMpTKwZp6r6HCecoBU0IXRfCudAsh5ko0dlYCibOO25wUSPkgvKSCEuPJm2aEBVQ48N3T/qDdBuSbcFZ+4S47mcAAlZCGMmCF3tyMChMWAEcYMfiaOYJjYC/5GDiPE7DGbfqGverrVKhYHfw7XBtPyhEmBxvdVrS2Hvjh/hK/af3oKMAJa88r2N185lHBETBbALK81HUAPVRNrKASS1Ejth0iMN98+LV8ozTduO5ok46P7qiRKrzjpYugzzs6c//yvLBRaY2xoiUoTO/OwAEZZlgYysoZHzg3m5GuZvnKg3Jb3/chQniF2R1lmz3bnDu001nHFdp+W9INk7CDIMnmYG1BMFRQf1Q00bSyUJyFqhB17E8UxIABOkxSgaBWGanU0+WjU6kXMdumvsBnkkV2FBkaiRHgkYXN/bUYfUEE2b1b7A/ezXRNRATfs2Nj3GE1alRoUAvOqDRrXHcwtXuwLCN2NJytLL6LZWhq8RlH3wiTiujIHgTSb3LxR9lOupK4nQkL02DuNIydAb9mwu3GEKpA6kYoM3q3Em+yOJcY3IdA2qOEH9iKnPy7CUaQVtDQYAGF+KglnYa6UrYfX3nv4BSXR1ZB0Yzu0DIXP1ktQituRgY+lYSh9wpaRHE18gi86zwNAm1spa1bHCzPkL0DDacw9hlz0S22aL/5c05FUz6rE11DLOaKtKwL18XDd1xlSZj1o9sw3T5aw57OE2pVN1BC0EKdscWiCYewxneKkvZNqU/7DMUke5tdXTkTC6mssdb0JqyXgOzKIMvLh66lrBBETUAq5nWWO/NMiCAfZ5NWJgFjhWJ0wojXVxD1Px81YR5LcZecEoAW73xkEJZjppRiDdpiGYU2tPs1GJPsraPdkBl4aROw0+3lZldKMaDhA7UCeGX6yhqBUZBbIQ/Ly3Bwkf0LG/slbqGJW3bfFPBs5TZ3fWC1lP+C0LdvrXZ54c2SJywKN/aDUuGzDDWoYspc2kOgK1u2AX8cZTp7qgc1rZBkLkLnMC/zSjtyv1f8py+9aHpjpnheQVbfuzgow==\">        </form>    </div></div><script>    (function(){        window._cf_chl_opt={            cvId: '2',            cZone: 'api-goerli.etherscan.io',            cType: 'interactive',            cNounce: '15579',            cRay: '794294b0ff122cc8',            cHash: '8512e1389fe72aa',            cUPMDTk: \"\\/api\\/?__cf_chl_tk=3e8YnNWEoJpt7yhj9ZB_z7nPP6BpgWkrEO9fxS_FHfs-1675505052-0-gaNycGzNCv0\",            cFPWv: 'b',            cTTimeMs: '1000',            cMTimeMs: '0',            cTplV: 4,            cTplB: 'cf',            cRq: {                ru: 'aHR0cHM6Ly9hcGktZ29lcmxpLmV0aGVyc2Nhbi5pby9hcGkv',                ra: 'Tk9fVUE=',                rm: 'UE9TVA==',                d: 'iVIFhJNH894P6YjM/iKMo5udA3+52aRoalezmALjhknKSbsB4BNUHq4xo08NpSCwVCpWCaVZhDDFDyjuk/b8Qc7g0gq6r2Nvql1jbmMxMYtueXQZm3u2y81oNhVmL0b0KzAx055+4tUbIe8wMboAsOqvpV8HaPpnIEcjO41rPHALM1ut6PKgqUidMU2DTeQbXhyZHbsrIqMsPbNpBQ4G9nepnyEEiOKZVICjhh2ic0LlLh9dcVTFH7r/ROe+A0rslaTcS+rrPg02VaiykvjlAHsbI+LNk6L/VBwZiX2BmQzErGaj3BF04hqKLou63qO55g6mwwiNfV/clJ8CivVcpJiZqEVpSpINLP3WCuEoYoxUzDhhSDQ1YQ2pCVVpc452gocFRa9HjyV0FD8UoZ6OvJsCglBzHa8OnzVsxd8GrVnmM0lrb5NuhoNo+0SQwmu58AcvXeB4thDjbbWAjQ0bMw4xFX7VUyzFtXWw7bb2pXdccyTeknMFlj2G9LdIuWyKPD+oYiuMGZjM64rc+LwEFOTZoXtGcyYLCIY8tkuKpzv8BVhndkDe6oEPNK5K5dqTWXa6lVeOE/Y057XOafCx7Nk6WBqL8yKp9o/QTv5DL3+/cfVHo1BDAD7cPupkBxB8VERZymbMhOt+LaDbw5/9F58hhWIXKaA0H2QAWnhLeoQ=',                t: 'MTY3NTUwNTA1Mi4zMTYwMDA=',                m: 'huwkp03FUUZ92f+uj1z1ri+z/UDtSxTEi2kfQAe8UXc=',                i1: 'AOVDmRSzY4mEqw31LLK8pg==',                i2: 'iqndipsrr9TKcGG+p6I0sQ==',                zh: 'qb4aFuGlbJn/rUOkKXjUqElKDKE10jDqu5PE014OTwk=',                uh: 'DV4j3Tmrbi5Rs1q3ahwVS6SgbPbI7np5884QO1u1Cgg=',                hh: 'Ax949TKiHbaXasTISC7ryL1/i3VsF1So3LziNEbpSQM=',            }        };        var trkjs = document.createElement('img');        trkjs.setAttribute('src', '/cdn-cgi/images/trace/captcha/js/transparent.gif?ray=794294b0ff122cc8');        trkjs.setAttribute('alt', '');        trkjs.setAttribute('style', 'display: none');        document.body.appendChild(trkjs);        var cpo = document.createElement('script');        cpo.src = '/cdn-cgi/challenge-platform/h/b/orchestrate/captcha/v1?ray=794294b0ff122cc8';        window._cf_chl_opt.cOgUHash = location.hash === '' && location.href.indexOf('#') !== -1 ? '#' : location.hash;        window._cf_chl_opt.cOgUQuery = location.search === '' && location.href.slice(0, location.href.length - window._cf_chl_opt.cOgUHash.length).indexOf('?') !== -1 ? '?' : location.search;        if (window.history && window.history.replaceState) {            var ogU = location.pathname + window._cf_chl_opt.cOgUQuery + window._cf_chl_opt.cOgUHash;            history.replaceState(null, null, \"\\/api\\/?__cf_chl_rt_tk=3e8YnNWEoJpt7yhj9ZB_z7nPP6BpgWkrEO9fxS_FHfs-1675505052-0-gaNycGzNCv0\" + window._cf_chl_opt.cOgUHash);            cpo.onload = function() {                history.replaceState(null, null, ogU);            };        }        document.getElementsByTagName('head')[0].appendChild(cpo);    }());</script>    <div class=\"footer\" role=\"contentinfo\">        <div class=\"footer-inner\">            <div class=\"clearfix diagnostic-wrapper\">                <div class=\"ray-id\">Ray ID: <code>794294b0ff122cc8</code></div>            </div>            <div class=\"text-center\" id=\"footer-text\">Performance & security by <a rel=\"noopener noreferrer\" href=\"https://www.cloudflare.com?utm_source=challenge&utm_campaign=l\" target=\"_blank\">Cloudflare</a></div>        </div>    </div></body></html>";
+        assert!(is_cloudflare_security_challenge(res));
+    }
+
     #[test]
     fn test_cloudflare_response() {
         let resp = "<!DOCTYPE html>\n<!--[if lt IE 7]> <html class=\"no-js ie6 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if IE 7]>    <html class=\"no-js ie7 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if IE 8]>    <html class=\"no-js ie8 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if gt IE 8]><!--> <html class=\"no-js\" lang=\"en-US\"> <!--<![endif]-->\n<head>\n<title>Attention Required! | Cloudflare</title>\n<meta charset=\"UTF-8\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge\" />\n<meta name=\"robots\" content=\"noindex, nofollow\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<link rel=\"stylesheet\" id=\"cf_styles-css\" href=\"/cdn-cgi/styles/cf.errors.css\" />\n<!--[if lt IE 9]><link rel=\"stylesheet\" id='cf_styles-ie-css' href=\"/cdn-cgi/styles/cf.errors.ie.css\" /><![endif]-->\n<style>body{margin:0;padding:0}</style>\n\n\n<!--[if gte IE 10]><!-->\n<script>\n  if (!navigator.cookieEnabled) {\n    window.addEventListener('DOMContentLoaded', function () {\n      var cookieEl = document.getElementById('cookie-alert');\n      cookieEl.style.display = 'block';\n    })\n  }\n</script>\n<!--<![endif]-->\n\n\n</head>\n<body>\n  <div id=\"cf-wrapper\">\n    <div class=\"cf-alert cf-alert-error cf-cookie-error\" id=\"cookie-alert\" data-translate=\"enable_cookies\">Please enable cookies.</div>\n    <div id=\"cf-error-details\" class=\"cf-error-details-wrapper\">\n      <div class=\"cf-wrapper cf-header cf-error-overview\">\n        <h1 data-translate=\"block_headline\">Sorry, you have been blocked</h1>\n        <h2 class=\"cf-subheadline\"><span data-translate=\"unable_to_access\">You are unable to access</span> polygonscan.com</h2>\n      </div><!-- /.header -->\n\n      <div class=\"cf-section cf-highlight\">\n        <div class=\"cf-wrapper\">\n          <div class=\"cf-screenshot-container cf-screenshot-full\">\n            \n              <span class=\"cf-no-screenshot error\"></span>\n            \n          </div>\n        </div>\n      </div><!-- /.captcha-container -->\n\n      <div class=\"cf-section cf-wrapper\">\n        <div class=\"cf-columns two\">\n          <div class=\"cf-column\">\n            <h2 data-translate=\"blocked_why_headline\">Why have I been blocked?</h2>\n\n            <p data-translate=\"blocked_why_detail\">This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data.</p>\n          </div>\n\n          <div class=\"cf-column\">\n            <h2 data-translate=\"blocked_resolve_headline\">What can I do to resolve this?</h2>\n\n            <p data-translate=\"blocked_resolve_detail\">You can email the site owner to let them know you were blocked. Please include what you were doing when this page came up and the Cloudflare Ray ID found at the bottom of this page.</p>\n          </div>\n        </div>\n      </div><!-- /.section -->\n\n      <div class=\"cf-error-footer cf-wrapper w-240 lg:w-full py-10 sm:py-4 sm:px-8 mx-auto text-center sm:text-left border-solid border-0 border-t border-gray-300\">\n  <p class=\"text-13\">\n    <span class=\"cf-footer-item sm:block sm:mb-1\">Cloudflare Ray ID: <strong class=\"font-semibold\">74d2aa5ed9e27367</strong></span>\n    <span class=\"cf-footer-separator sm:hidden\">&bull;</span>\n    <span id=\"cf-footer-item-ip\" class=\"cf-footer-item hidden sm:block sm:mb-1\">\n      Your IP:\n      <button type=\"button\" id=\"cf-footer-ip-reveal\" class=\"cf-footer-ip-reveal-btn\">Click to reveal</button>\n      <span class=\"hidden\" id=\"cf-footer-ip\">62.96.232.178</span>\n      <span class=\"cf-footer-separator sm:hidden\">&bull;</span>\n    </span>\n    <span class=\"cf-footer-item sm:block sm:mb-1\"><span>Performance &amp; security by</span> <a rel=\"noopener noreferrer\" href=\"https://www.cloudflare.com/5xx-error-landing\" id=\"brand_link\" target=\"_blank\">Cloudflare</a></span>\n    \n  </p>\n  <script>(function(){function d(){var b=a.getElementById(\"cf-footer-item-ip\"),c=a.getElementById(\"cf-footer-ip-reveal\");b&&\"classList\"in b&&(b.classList.remove(\"hidden\"),c.addEventListener(\"click\",function(){c.classList.add(\"hidden\");a.getElementById(\"cf-footer-ip\").classList.remove(\"hidden\")}))}var a=document;document.addEventListener&&a.addEventListener(\"DOMContentLoaded\",d)})();</script>\n</div><!-- /.error-footer -->\n\n\n    </div><!-- /#cf-error-details -->\n  </div><!-- /#cf-wrapper -->\n\n  <script>\n  window._cf_translation = {};\n  \n  \n</script>\n\n</body>\n</html>\n";
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index a77128f3..b3fa1c6e 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -1,6 +1,6 @@
 //! Bindings for [etherscan.io web api](https://docs.etherscan.io/)
 
-use crate::errors::is_blocked_by_cloudflare_response;
+use crate::errors::{is_blocked_by_cloudflare_response, is_cloudflare_security_challenge};
 use contract::ContractMetadata;
 use errors::EtherscanError;
 use ethers_core::{
@@ -209,6 +209,8 @@ impl Client {
             error!(target: "etherscan", ?res, "Failed to deserialize response: {}", err);
             if is_blocked_by_cloudflare_response(res) {
                 EtherscanError::BlockedByCloudflare
+            } else if is_cloudflare_security_challenge(res) {
+                EtherscanError::CloudFlareSecurityChallenge
             } else {
                 EtherscanError::Serde(err)
             }
diff --git a/ethers-providers/Cargo.toml b/ethers-providers/Cargo.toml
index cb76aa32..5f4102d0 100644
--- a/ethers-providers/Cargo.toml
+++ b/ethers-providers/Cargo.toml
@@ -42,7 +42,7 @@ enr = { version = "0.7.0", default-features = false, features = ["k256", "serde"
 tracing = { version = "0.1.37", default-features = false }
 tracing-futures = { version = "0.2.5", default-features = false, features = ["std-future"] }
 
-bytes = { version = "1.3.0", default-features = false, optional = true }
+bytes = { version = "1.4.0", default-features = false, optional = true }
 once_cell = "1.17.0"
 hashers = "1.0.1"
 
diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs
index d5c34da7..ad7ce8c1 100644
--- a/ethers-providers/src/lib.rs
+++ b/ethers-providers/src/lib.rs
@@ -513,7 +513,7 @@ pub trait Middleware: Sync + Send + Debug {
         &self,
         private_key: Bytes,
         passphrase: String,
-    ) -> Result<Address, ProviderError> {
+    ) -> Result<Address, Self::Error> {
         self.inner().import_raw_key(private_key, passphrase).await.map_err(FromErr::from)
     }
 
@@ -522,7 +522,7 @@ pub trait Middleware: Sync + Send + Debug {
         account: T,
         passphrase: String,
         duration: Option<u64>,
-    ) -> Result<bool, ProviderError> {
+    ) -> Result<bool, Self::Error> {
         self.inner().unlock_account(account, passphrase, duration).await.map_err(FromErr::from)
     }
 
@@ -590,7 +590,7 @@ pub trait Middleware: Sync + Send + Debug {
         &self,
         tx_hash: TxHash,
         trace_options: GethDebugTracingOptions,
-    ) -> Result<GethTrace, ProviderError> {
+    ) -> Result<GethTrace, Self::Error> {
         self.inner().debug_trace_transaction(tx_hash, trace_options).await.map_err(FromErr::from)
     }
 
@@ -600,7 +600,7 @@ pub trait Middleware: Sync + Send + Debug {
         req: T,
         block: Option<BlockId>,
         trace_options: GethDebugTracingCallOptions,
-    ) -> Result<GethTrace, ProviderError> {
+    ) -> Result<GethTrace, Self::Error> {
         self.inner().debug_trace_call(req, block, trace_options).await.map_err(FromErr::from)
     }
 
@@ -778,14 +778,7 @@ pub mod test_provider {
     use std::{convert::TryFrom, iter::Cycle, slice::Iter, sync::Mutex};
 
     // List of infura keys to rotate through so we don't get rate limited
-    const INFURA_KEYS: &[&str] = &[
-        "6770454bc6ea42c58aac12978531b93f",
-        "7a8769b798b642f6933f2ed52042bd70",
-        "631fd9a6539644088297dc605d35fff3",
-        "16a8be88795540b9b3903d8de0f7baa5",
-        "f4a0bdad42674adab5fc0ac077ffab2b",
-        "5c812e02193c4ba793f8c214317582bd",
-    ];
+    const INFURA_KEYS: &[&str] = &["15e8aaed6f894d63a0f6a0206c006cdd"];
 
     pub static MAINNET: Lazy<TestProvider> =
         Lazy::new(|| TestProvider::new(INFURA_KEYS, "mainnet"));
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index 2c2098dd..96e95159 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -45,10 +45,10 @@ cfg-if = "1.0.0"
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
 home = "0.5.4"
-svm = { package = "svm-rs", version = "0.2.19", default-features = false, optional = true, features = [
+svm = { package = "svm-rs", version = "0.2.20", default-features = false, optional = true, features = [
     "blocking",
 ] }
-svm-builds = { package = "svm-rs-builds", version = "0.1.11", optional = true }
+svm-builds = { package = "svm-rs-builds", version = "0.1.12", optional = true }
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
 # NOTE: this enables wasm compatibility for getrandom indirectly
diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs
index b6a356e6..a00123c9 100644
--- a/ethers-solc/src/compile/mod.rs
+++ b/ethers-solc/src/compile/mod.rs
@@ -833,7 +833,7 @@ mod tests {
             // update this test whenever there's a new sol
             // version. that's ok! good reminder to check the
             // patch notes.
-            (">=0.5.0", "0.8.17"),
+            (">=0.5.0", "0.8.18"),
             // range
             (">=0.4.0 <0.5.0", "0.4.26"),
         ]
diff --git a/ethers-solc/src/remappings.rs b/ethers-solc/src/remappings.rs
index 0006d603..b3eaee2a 100644
--- a/ethers-solc/src/remappings.rs
+++ b/ethers-solc/src/remappings.rs
@@ -423,7 +423,7 @@ impl Candidate {
     /// Which should be resolved to the top level dir `@openzeppelin`
     ///
     /// We also treat candidates with a `node_modules` parent directory differently and consider
-    /// them to `hardhat` style. In which case the trailing library barrier `contracts` will be
+    /// them to be `hardhat` style. In which case the trailing library barrier `contracts` will be
     /// stripped from the remapping path. This differs from dapptools style which does not include
     /// the library barrier path `src` in the solidity import statements. For example, for
     /// dapptools you could have
@@ -472,8 +472,23 @@ impl Candidate {
         window_start: PathBuf,
         is_inside_node_modules: bool,
     ) {
-        if let Some(pos) =
-            candidates.iter().position(|c| c.source_dir.ends_with(DAPPTOOLS_CONTRACTS_DIR))
+        // if there's only a single source dir candidate then we use this
+        if let Some(pos) = candidates
+            .iter()
+            .enumerate()
+            .fold((0, None), |(mut contracts_dir_count, mut pos), (idx, c)| {
+                if c.source_dir.ends_with(DAPPTOOLS_CONTRACTS_DIR) {
+                    contracts_dir_count += 1;
+                    if contracts_dir_count == 1 {
+                        pos = Some(idx)
+                    } else {
+                        pos = None;
+                    }
+                }
+
+                (contracts_dir_count, pos)
+            })
+            .1
         {
             let c = candidates.remove(pos);
             *candidates = vec![c];
@@ -630,6 +645,7 @@ fn find_remapping_candidates(
             }
         }
     }
+
     // need to find the actual next window in the event `open` is a lib dir
     let window_start = next_nested_window(open, current_dir);
     // finally, we need to merge, adjust candidates from the same level and opening window
@@ -1227,4 +1243,37 @@ mod tests {
             "/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/lib.Z6ODLZJQeJQa/repo1/lib/ds-test"
         ));
     }
+
+    #[test]
+    fn find_openzeppelin_remapping() {
+        let tmp_dir = tempdir("lib").unwrap();
+        let tmp_dir_path = tmp_dir.path();
+        let paths = [
+            "lib/ds-test/src/test.sol",
+            "lib/forge-std/src/test.sol",
+            "openzeppelin/contracts/interfaces/c.sol",
+        ];
+        mkdir_or_touch(tmp_dir_path, &paths[..]);
+
+        let path = tmp_dir_path.display().to_string();
+        let mut remappings = Remapping::find_many(path);
+        remappings.sort_unstable();
+
+        let mut expected = vec![
+            Remapping {
+                name: "ds-test/".to_string(),
+                path: to_str(tmp_dir_path.join("lib/ds-test/src")),
+            },
+            Remapping {
+                name: "openzeppelin/".to_string(),
+                path: to_str(tmp_dir_path.join("openzeppelin/contracts")),
+            },
+            Remapping {
+                name: "forge-std/".to_string(),
+                path: to_str(tmp_dir_path.join("lib/forge-std/src")),
+            },
+        ];
+        expected.sort_unstable();
+        pretty_assertions::assert_eq!(remappings, expected);
+    }
 }
diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs
index aba0a883..24e45fa4 100644
--- a/ethers-solc/src/utils.rs
+++ b/ethers-solc/src/utils.rs
@@ -473,7 +473,7 @@ pub fn create_parent_dir_all(file: impl AsRef<Path>) -> Result<(), SolcError> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::resolver::Node;
+
     use solang_parser::pt::SourceUnitPart;
     use std::{
         collections::HashSet,
@@ -516,7 +516,7 @@ contract A {}
 
         assert!(!non_existing.exists());
 
-        let found = Node::read(&non_existing).unwrap_err();
+        let found = crate::resolver::Node::read(&non_existing).unwrap_err();
         matches!(found, SolcError::ResolveCaseSensitiveFileName { .. });
     }
 
diff --git a/examples/ethers-wasm/Cargo.toml b/examples/ethers-wasm/Cargo.toml
index 6ca4f575..53aa8de8 100644
--- a/examples/ethers-wasm/Cargo.toml
+++ b/examples/ethers-wasm/Cargo.toml
@@ -20,7 +20,7 @@ default = ["console_error_panic_hook"]
 [dependencies]
 ethers = { path = "../..", version = "1.0.0", features = ["abigen", "legacy", "ws"] }
 
-wasm-bindgen-futures = "0.4.33"
+wasm-bindgen-futures = "0.4.34"
 wasm-bindgen = { version = "0.2.83", features = ["serde-serialize"] }
 
 serde = { version = "1.0.144", features = ["derive"] }
@@ -40,9 +40,9 @@ console_error_panic_hook = { version = "0.1.6", optional = true }
 # Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
 wee_alloc = { version = "0.4.5", optional = true }
 
-web-sys = { version = "0.3.60", features = ["console"] }
+web-sys = { version = "0.3.61", features = ["console"] }
 
 hex = "0.4.3"
 
 [dev-dependencies]
-wasm-bindgen-test = "0.3.33"
+wasm-bindgen-test = "0.3.34"
diff --git a/examples/providers/Cargo.toml b/examples/providers/Cargo.toml
index f8c0db21..c3d80d7b 100644
--- a/examples/providers/Cargo.toml
+++ b/examples/providers/Cargo.toml
@@ -4,10 +4,6 @@ version = "1.0.2"
 authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
 edition = "2021"
 
-[features]
-default = ["ipc"]
-ipc = []
-
 [dev-dependencies]
 ethers = { path = "../..", version = "1.0.0", features = ["abigen", "ipc", "rustls", "ws"] }
 
@@ -16,3 +12,7 @@ reqwest = { version = "0.11.14", default-features = false }
 serde = { version = "1.0.144", features = ["derive"] }
 serde_json = "1.0.64"
 tokio = { version = "1.18", features = ["macros"] }
+
+async-trait = "0.1"
+url = "2.3"
+thiserror = "1.0"
diff --git a/examples/providers/examples/custom.rs b/examples/providers/examples/custom.rs
new file mode 100644
index 00000000..450731e6
--- /dev/null
+++ b/examples/providers/examples/custom.rs
@@ -0,0 +1,125 @@
+//! Create a custom data transport to use with a Provider.
+
+use async_trait::async_trait;
+use ethers::{core::utils::Anvil, prelude::*};
+use serde::{de::DeserializeOwned, Serialize};
+use std::fmt::Debug;
+use thiserror::Error;
+use url::Url;
+
+/// First we must create an error type, and implement [`From`] for [`ProviderError`].
+///
+/// Here we are using [`thiserror`](https://docs.rs/thiserror) to wrap [`WsClientError`]
+/// and [`IpcError`].
+/// This also provides a conversion implementation ([`From`]) for both, so we can use
+/// the [question mark operator](https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html)
+/// later on in our implementations.
+#[derive(Debug, Error)]
+pub enum WsOrIpcError {
+    #[error(transparent)]
+    Ws(#[from] WsClientError),
+
+    #[error(transparent)]
+    Ipc(#[from] IpcError),
+}
+
+impl From<WsOrIpcError> for ProviderError {
+    fn from(value: WsOrIpcError) -> Self {
+        Self::JsonRpcClientError(Box::new(value))
+    }
+}
+
+/// Next, we create our transport type, which in this case will be an enum that contains
+/// either [`Ws`] or [`Ipc`].
+#[derive(Clone, Debug)]
+enum WsOrIpc {
+    Ws(Ws),
+    Ipc(Ipc),
+}
+
+// We implement a convenience "constructor" method, to easily initialize the transport.
+// This will connect to [`Ws`] if it's a valid [URL](url::Url), otherwise it'll
+// default to [`Ipc`].
+impl WsOrIpc {
+    pub async fn connect(s: &str) -> Result<Self, WsOrIpcError> {
+        let this = match Url::parse(s) {
+            Ok(url) => Self::Ws(Ws::connect(url).await?),
+            Err(_) => Self::Ipc(Ipc::connect(s).await?),
+        };
+        Ok(this)
+    }
+}
+
+// Next, the most important step: implement [`JsonRpcClient`].
+//
+// For this implementation, we simply delegate to the wrapped transport and return the
+// result.
+//
+// Note that we are using [`async-trait`](https://docs.rs/async-trait) for asynchronous
+// functions in traits, as this is not yet supported in stable Rust; see:
+// <https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html>
+#[async_trait]
+impl JsonRpcClient for WsOrIpc {
+    type Error = WsOrIpcError;
+
+    async fn request<T, R>(&self, method: &str, params: T) -> Result<R, Self::Error>
+    where
+        T: Debug + Serialize + Send + Sync,
+        R: DeserializeOwned + Send,
+    {
+        let res = match self {
+            Self::Ws(ws) => JsonRpcClient::request(ws, method, params).await?,
+            Self::Ipc(ipc) => JsonRpcClient::request(ipc, method, params).await?,
+        };
+        Ok(res)
+    }
+}
+
+// We can also implement [`PubsubClient`], since both `Ws` and `Ipc` implement it, by
+// doing the same as in the `JsonRpcClient` implementation above.
+impl PubsubClient for WsOrIpc {
+    // Since both `Ws` and `Ipc`'s `NotificationStream` associated type is the same,
+    // we can simply return one of them.
+    // In case they differed, we would have to create a `WsOrIpcNotificationStream`,
+    // similar to the error type.
+    type NotificationStream = <Ws as PubsubClient>::NotificationStream;
+
+    fn subscribe<T: Into<U256>>(&self, id: T) -> Result<Self::NotificationStream, Self::Error> {
+        let stream = match self {
+            Self::Ws(ws) => PubsubClient::subscribe(ws, id)?,
+            Self::Ipc(ipc) => PubsubClient::subscribe(ipc, id)?,
+        };
+        Ok(stream)
+    }
+
+    fn unsubscribe<T: Into<U256>>(&self, id: T) -> Result<(), Self::Error> {
+        match self {
+            Self::Ws(ws) => PubsubClient::unsubscribe(ws, id)?,
+            Self::Ipc(ipc) => PubsubClient::unsubscribe(ipc, id)?,
+        };
+        Ok(())
+    }
+}
+
+#[tokio::main]
+async fn main() -> eyre::Result<()> {
+    // Spawn Anvil
+    let anvil = Anvil::new().block_time(1u64).spawn();
+
+    // Connect to our transport
+    let transport = WsOrIpc::connect(&anvil.ws_endpoint()).await?;
+
+    // Wrap the transport in a provider
+    let provider = Provider::new(transport);
+
+    // Now we can use our custom transport provider like normal
+    let block_number = provider.get_block_number().await?;
+    println!("Current block: {block_number}");
+
+    let mut subscription = provider.subscribe_blocks().await?.take(3);
+    while let Some(block) = subscription.next().await {
+        println!("New block: {:?}", block.number);
+    }
+
+    Ok(())
+}
diff --git a/examples/providers/examples/http.rs b/examples/providers/examples/http.rs
index ff45119f..f6bce87e 100644
--- a/examples/providers/examples/http.rs
+++ b/examples/providers/examples/http.rs
@@ -1,13 +1,12 @@
+//! The Http transport is used to send JSON-RPC requests over HTTP to an Ethereum node.
+//! This is the most basic connection to a node.
+
 use ethers::prelude::*;
 use reqwest::header::{HeaderMap, HeaderValue};
 use std::sync::Arc;
 
 const RPC_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
 
-/// The Http transport is used to send JSON-RPC requests over Http to an
-/// Ethereum node. It allows you to perform various actions on the Ethereum blockchain, such as
-/// reading and writing data, sending transactions, and more. To use the Http transport, you will
-/// need to create a new `Provider` instance as described in this example.
 #[tokio::main]
 async fn main() -> eyre::Result<()> {
     create_instance().await?;
diff --git a/examples/providers/examples/ipc.rs b/examples/providers/examples/ipc.rs
index 89fe05f1..aeb2dda0 100644
--- a/examples/providers/examples/ipc.rs
+++ b/examples/providers/examples/ipc.rs
@@ -1,32 +1,41 @@
-/// The IPC (Inter-Process Communication) transport is a way for a process to communicate with a
-/// running Ethereum client over a local Unix domain socket. Using the IPC transport allows the
-/// ethers library to send JSON-RPC requests to the Ethereum client and receive responses, without
-/// the need for a network connection or HTTP server. This can be useful for interacting with a
-/// local Ethereum node that is running on the same machine.
+//! The IPC (Inter-Process Communication) transport allows our program to communicate
+//! with a node over a local [Unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket)
+//! or [Windows named pipe](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes).
+//!
+//! It functions much the same as a Ws connection.
+
+use ethers::prelude::*;
+use std::sync::Arc;
+
+abigen!(
+    IUniswapV2Pair,
+    "[function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)]"
+);
+
 #[tokio::main]
-#[cfg(feature = "ipc")]
 async fn main() -> eyre::Result<()> {
-    use ethers::prelude::*;
-
-    // We instantiate the provider using the path of a local Unix domain socket
-    // --------------------------------------------------------------------------------
-    // NOTE: The IPC transport supports push notifications, but we still need to specify a polling
-    // interval because only subscribe RPC calls (e.g., transactions, blocks, events) support push
-    // notifications in Ethereum's RPC API. For other calls we must use repeated polling for many
-    // operations even with the IPC transport.
-    let provider = Provider::connect_ipc("~/.ethereum/geth.ipc")
-        .await?
-        .interval(std::time::Duration::from_millis(2000));
+    let provider = Provider::connect_ipc("~/.ethereum/geth.ipc").await?;
+    let provider = Arc::new(provider);
+
+    let pair_address: Address = "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc".parse()?;
+    let weth_usdc = IUniswapV2Pair::new(pair_address, provider.clone());
 
     let block = provider.get_block_number().await?;
     println!("Current block: {block}");
-    let mut stream = provider.watch_blocks().await?.stream();
+
+    let mut initial_reserves = weth_usdc.get_reserves().call().await?;
+    println!("Initial reserves: {initial_reserves:?}");
+
+    let mut stream = provider.subscribe_blocks().await?;
     while let Some(block) = stream.next().await {
-        dbg!(block);
+        println!("New block: {:?}", block.number);
+
+        let reserves = weth_usdc.get_reserves().call().await?;
+        if reserves != initial_reserves {
+            println!("Reserves changed: old {initial_reserves:?} - new {reserves:?}");
+            initial_reserves = reserves;
+        }
     }
 
     Ok(())
 }
-
-#[cfg(not(feature = "ipc"))]
-fn main() {}
diff --git a/examples/providers/examples/mock.rs b/examples/providers/examples/mock.rs
index 4d80fe57..e2d89dae 100644
--- a/examples/providers/examples/mock.rs
+++ b/examples/providers/examples/mock.rs
@@ -1,15 +1,16 @@
+//! `MockProvider` is a mock Ethereum provider that can be used for testing purposes.
+//! It allows to simulate Ethereum state and behavior, by explicitly instructing
+//! provider's responses on client requests.
+//!
+//! This can be useful for testing code that relies on providers without the need to
+//! connect to a real network or spend real Ether. It also allows to test code in a
+//! deterministic manner, as you can control the state and behavior of the provider.
+//!
+//! In these examples we use the common Arrange, Act, Assert (AAA) test approach.
+//! It is a useful pattern for well-structured, understandable and maintainable tests.
+
 use ethers::prelude::*;
 
-/// `MockProvider` is a mock Ethereum provider that can be used for testing purposes.
-/// It allows to simulate Ethereum state and behavior, by explicitly instructing
-/// provider's responses on client requests.
-///
-/// This can be useful for testing code that relies on providers without the need to
-/// connect to a real network or spend real Ether. It also allows to test code in a
-/// deterministic manner, as you can control the state and behavior of the provider.
-///
-/// In these examples we use the common Arrange, Act, Assert (AAA) test approach.
-/// It is a useful pattern for well-structured, understandable and maintainable tests.
 #[tokio::main]
 async fn main() -> eyre::Result<()> {
     mocked_block_number().await?;
diff --git a/examples/providers/examples/quorum.rs b/examples/providers/examples/quorum.rs
index a1d50c8c..30e852fa 100644
--- a/examples/providers/examples/quorum.rs
+++ b/examples/providers/examples/quorum.rs
@@ -1,5 +1,5 @@
-//! Example usage for the `QuorumProvider` that requests multiple backends and only returns
-//! a value if the configured `Quorum` was reached.
+//! The `QuorumProvider` sends a request to multiple backends and only returns a value
+//! if the configured `Quorum` was reached.
 
 use ethers::{
     core::utils::Anvil,
diff --git a/examples/providers/examples/retry.rs b/examples/providers/examples/retry.rs
index 8f78b689..4df2a605 100644
--- a/examples/providers/examples/retry.rs
+++ b/examples/providers/examples/retry.rs
@@ -1,16 +1,17 @@
+//! The RetryClient is a type that wraps around a JsonRpcClient and automatically retries failed
+//! requests using an exponential backoff and filtering based on a RetryPolicy. It presents as a
+//! JsonRpcClient, but with additional functionality for retrying requests.
+//!
+//! The RetryPolicy can be customized for specific applications and endpoints, mainly to handle
+//! rate-limiting errors. In addition to the RetryPolicy, errors caused by connectivity issues such
+//! as timed out connections or responses in the 5xx range can also be retried separately.
+
 use ethers::prelude::*;
 use reqwest::Url;
 use std::time::Duration;
 
 const RPC_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
 
-/// The RetryClient is a type that wraps around a JsonRpcClient and automatically retries failed
-/// requests using an exponential backoff and filtering based on a RetryPolicy. It presents as a
-/// JsonRpcClient, but with additional functionality for retrying requests.
-///
-/// The RetryPolicy can be customized for specific applications and endpoints, mainly to handle
-/// rate-limiting errors. In addition to the RetryPolicy, errors caused by connectivity issues such
-/// as timed out connections or responses in the 5xx range can also be retried separately.
 #[tokio::main]
 async fn main() -> eyre::Result<()> {
     let provider = Http::new(Url::parse(RPC_URL)?);
diff --git a/examples/providers/examples/rw.rs b/examples/providers/examples/rw.rs
index 89f1f439..517dcf1a 100644
--- a/examples/providers/examples/rw.rs
+++ b/examples/providers/examples/rw.rs
@@ -1,23 +1,19 @@
-//! Example usage for the `RwClient` that uses a didicated client to send transaction and nother one
-//! for read ops
+//! The RwClient wraps two data transports: the first is used for read operations, and the second
+//! one is used for write operations, that consume gas like sending transactions.
 
-use ethers::{
-    core::utils::Anvil,
-    providers::{Http, Middleware, Provider, Ws},
-};
-use eyre::Result;
-use std::{str::FromStr, time::Duration};
+use ethers::{prelude::*, utils::Anvil};
+use url::Url;
 
 #[tokio::main]
-async fn main() -> Result<()> {
+async fn main() -> eyre::Result<()> {
     let anvil = Anvil::new().spawn();
 
-    let http = Http::from_str(&anvil.endpoint())?;
-    let ws = Ws::connect(anvil.ws_endpoint()).await?;
+    let http_url = Url::parse(&anvil.endpoint())?;
+    let http = Http::new(http_url);
 
-    let provider = Provider::rw(http, ws).interval(Duration::from_millis(10u64));
+    let ws = Ws::connect(anvil.ws_endpoint()).await?;
 
-    dbg!(provider.get_accounts().await?);
+    let _provider = Provider::rw(http, ws);
 
     Ok(())
 }
diff --git a/examples/providers/examples/ws.rs b/examples/providers/examples/ws.rs
index e4aded8a..d1c339dc 100644
--- a/examples/providers/examples/ws.rs
+++ b/examples/providers/examples/ws.rs
@@ -1,48 +1,23 @@
-use std::time::Duration;
+//! The Ws transport allows you to send JSON-RPC requests and receive responses over
+//! [WebSocket](https://en.wikipedia.org/wiki/WebSocket).
+//!
+//! This allows to interact with the network in real-time without the need for HTTP
+//! polling.
 
 use ethers::prelude::*;
 
 const WSS_URL: &str = "wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27";
 
-/// The Ws transport allows you to send JSON-RPC requests and receive responses over WebSocket
-/// connections. It is useful for connecting to Ethereum nodes that support WebSockets.
-/// This allows to interact with the Ethereum network in real-time without the need for HTTP
-/// polling.
 #[tokio::main]
 async fn main() -> eyre::Result<()> {
-    create_instance().await?;
-    watch_blocks().await?;
-    Ok(())
-}
-
-async fn create_instance() -> eyre::Result<()> {
-    // An Ws provider can be created from an ws(s) URI.
+    // A Ws provider can be created from a ws(s) URI.
     // In case of wss you must add the "rustls" or "openssl" feature
     // to the ethers library dependency in `Cargo.toml`.
-    //------------------------------------------------------------------------------------------
-    // NOTE: The Ws transport supports push notifications, but we still need to specify a polling
-    // interval because only subscribe RPC calls (e.g., transactions, blocks, events) support push
-    // notifications in Ethereum's RPC API. For other calls we must use repeated polling for many
-    // operations even with the Ws transport.
-    let _provider = Provider::<Ws>::connect(WSS_URL).await?.interval(Duration::from_millis(500));
-
-    // Instantiate with auth to send basic authorization headers on connection.
-    let url = reqwest::Url::parse(WSS_URL)?;
-    let auth = Authorization::basic("username", "password");
-    if let Ok(_provider) = Provider::<Ws>::connect_with_auth(url, auth).await {
-        println!("Create Ws provider with auth");
-    }
-
-    Ok(())
-}
-
-/// Let's show how the Ws connection enables listening for blocks using a persistent TCP connection
-async fn watch_blocks() -> eyre::Result<()> {
     let provider = Provider::<Ws>::connect(WSS_URL).await?;
-    let mut stream = provider.watch_blocks().await?.take(1);
 
-    while let Some(block_hash) = stream.next().await {
-        println!("{block_hash:?}");
+    let mut stream = provider.subscribe_blocks().await?.take(1);
+    while let Some(block) = stream.next().await {
+        println!("{:?}", block.hash);
     }
 
     Ok(())
diff --git a/examples/transactions/examples/trace.rs b/examples/transactions/examples/trace.rs
deleted file mode 100644
index 35ea370d..00000000
--- a/examples/transactions/examples/trace.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-use ethers::{
-    core::types::{GethDebugTracingOptions, H256},
-    providers::{Http, Middleware, Provider},
-};
-use eyre::Result;
-use std::str::FromStr;
-
-/// use `debug_traceTransaction` to fetch traces
-/// requires, a valid endpoint in `RPC_URL` env var that supports `debug_traceTransaction`
-#[tokio::main]
-async fn main() -> Result<()> {
-    if let Ok(url) = std::env::var("RPC_URL") {
-        let client = Provider::<Http>::try_from(url)?;
-        let tx_hash = "0x97a02abf405d36939e5b232a5d4ef5206980c5a6661845436058f30600c52df7";
-        let h: H256 = H256::from_str(tx_hash)?;
-        let options: GethDebugTracingOptions = GethDebugTracingOptions::default();
-        let traces = client.debug_trace_transaction(h, options).await?;
-        println!("{traces:?}");
-    }
-
-    Ok(())
-}
diff --git a/examples/geth_trace_call.rs b/examples/transactions/examples/trace_call.rs
similarity index 74%
rename from examples/geth_trace_call.rs
rename to examples/transactions/examples/trace_call.rs
index 00851951..67551d6e 100644
--- a/examples/geth_trace_call.rs
+++ b/examples/transactions/examples/trace_call.rs
@@ -1,4 +1,11 @@
-use ethers::prelude::*;
+use ethers::{
+    core::types::GethDebugTracingOptions,
+    providers::{Http, Middleware, Provider},
+    types::{
+        Address, BlockId, Bytes, GethDebugBuiltInTracerType, GethDebugTracerType,
+        GethDebugTracingCallOptions, TransactionRequest,
+    },
+};
 use eyre::Result;
 use std::str::FromStr;
 
@@ -14,7 +21,9 @@ async fn main() -> Result<()> {
             tracing_options: GethDebugTracingOptions {
                 disable_storage: Some(true),
                 enable_memory: Some(false),
-                tracer: Some(GethDebugTracerType::CallTracer),
+                tracer: Some(GethDebugTracerType::BuiltInTracer(
+                    GethDebugBuiltInTracerType::CallTracer,
+                )),
                 ..Default::default()
             },
         };
diff --git a/examples/transactions/examples/trace_transaction.rs b/examples/transactions/examples/trace_transaction.rs
new file mode 100644
index 00000000..028e4162
--- /dev/null
+++ b/examples/transactions/examples/trace_transaction.rs
@@ -0,0 +1,47 @@
+use ethers::{
+    core::types::{GethDebugTracingOptions, H256},
+    providers::{Http, Middleware, Provider},
+    types::{GethDebugBuiltInTracerType, GethDebugTracerType},
+};
+use eyre::Result;
+use std::str::FromStr;
+
+/// use `debug_traceTransaction` to fetch traces
+/// requires, a valid endpoint in `RPC_URL` env var that supports `debug_traceTransaction`
+#[tokio::main]
+async fn main() -> Result<()> {
+    if let Ok(url) = std::env::var("RPC_URL") {
+        let client = Provider::<Http>::try_from(url)?;
+        let tx_hash = "0x97a02abf405d36939e5b232a5d4ef5206980c5a6661845436058f30600c52df7";
+        let h: H256 = H256::from_str(tx_hash)?;
+
+        // default tracer
+        let options = GethDebugTracingOptions::default();
+        let traces = client.debug_trace_transaction(h, options).await?;
+        println!("{traces:?}");
+
+        // call tracer
+        let options = GethDebugTracingOptions {
+            disable_storage: Some(true),
+            enable_memory: Some(false),
+            tracer: Some(GethDebugTracerType::BuiltInTracer(
+                GethDebugBuiltInTracerType::CallTracer,
+            )),
+            ..Default::default()
+        };
+        let traces = client.debug_trace_transaction(h, options).await?;
+        println!("{traces:?}");
+
+        // js tracer
+        let options = GethDebugTracingOptions {
+                disable_storage: Some(true),
+                enable_memory: Some(false),
+                tracer: Some(GethDebugTracerType::JsTracer(String::from("{data: [], fault: function(log) {}, step: function(log) { if(log.op.toString() == \"DELEGATECALL\") this.data.push(log.stack.peek(0)); }, result: function() { return this.data; }}"))),
+                ..Default::default()
+            };
+        let traces = client.debug_trace_transaction(h, options).await?;
+        println!("{traces:?}");
+    }
+
+    Ok(())
+}


Test patch:
diff --git a/ethers-contract/tests/it/contract.rs b/ethers-contract/tests/it/contract.rs
index c4f58fba..0d3b7a64 100644
--- a/ethers-contract/tests/it/contract.rs
+++ b/ethers-contract/tests/it/contract.rs
@@ -6,10 +6,10 @@ use ethers_core::types::{Filter, ValueOrArray, H256};
 #[cfg(not(feature = "celo"))]
 mod eth_tests {
     use super::*;
-    use ethers_contract::{EthEvent, LogMeta, Multicall, MulticallVersion};
+    use ethers_contract::{ContractInstance, EthEvent, LogMeta, Multicall, MulticallVersion};
     use ethers_core::{
         abi::{encode, Detokenize, Token, Tokenizable},
-        types::{transaction::eip712::Eip712, Address, BlockId, Bytes, I256, U256},
+        types::{transaction::eip712::Eip712, Address, BlockId, Bytes, H160, I256, U256},
         utils::{keccak256, Anvil},
     };
     use ethers_derive_eip712::*;
@@ -17,6 +17,81 @@ mod eth_tests {
     use ethers_signers::{LocalWallet, Signer};
     use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration};
 
+    #[derive(Debug)]
+    pub struct NonClone<M> {
+        m: M,
+    }
+
+    #[derive(Debug)]
+    pub struct MwErr<M: Middleware>(M::Error);
+    impl<M> ethers_providers::FromErr<M::Error> for MwErr<M>
+    where
+        M: Middleware,
+    {
+        fn from(src: M::Error) -> Self {
+            Self(src)
+        }
+    }
+
+    impl<M: Middleware> std::fmt::Display for MwErr<M> {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            Ok(())
+        }
+    }
+    impl<M: Middleware> std::error::Error for MwErr<M> {}
+
+    impl<M: Middleware> Middleware for NonClone<M> {
+        type Error = MwErr<M>;
+
+        type Provider = M::Provider;
+
+        type Inner = M;
+
+        fn inner(&self) -> &Self::Inner {
+            &self.m
+        }
+    }
+
+    // this is not a test. It is a compile check. :)
+    // It exists to ensure that trait bounds on contract internal behave as
+    // expected. It should not be run
+    fn it_compiles() {
+        let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
+
+        // launch anvil
+        let anvil = Anvil::new().spawn();
+
+        let client = Provider::<Http>::try_from(anvil.endpoint())
+            .unwrap()
+            .interval(Duration::from_millis(10u64));
+
+        // Works (B == M, M: Clone)
+        let c: ContractInstance<&Provider<Http>, Provider<Http>> =
+            ContractInstance::new(H160::default(), abi.clone(), &client);
+
+        let _ = c.method::<(), ()>("notARealMethod", ());
+
+        // Works (B == &M, M: Clone)
+        let c: ContractInstance<Provider<Http>, Provider<Http>> =
+            ContractInstance::new(H160::default(), abi.clone(), client.clone());
+
+        let _ = c.method::<(), ()>("notARealMethod", ());
+
+        let non_clone_mware = NonClone { m: client };
+
+        // Works (B == &M, M: !Clone)
+        let c: ContractInstance<&NonClone<Provider<Http>>, NonClone<Provider<Http>>> =
+            ContractInstance::new(H160::default(), abi, &non_clone_mware);
+
+        let _ = c.method::<(), ()>("notARealMethod", ());
+
+        // // Fails (B == M, M: !Clone)
+        // let c: ContractInternal<NonClone<Provider<Http>>, NonClone<Provider<Http>>> =
+        //     ContractInternal::new(H160::default(), abi, non_clone_mware);
+
+        // let _ = c.method::<(), ()>("notARealMethod", ());
+    }
+
     #[tokio::test]
     async fn deploy_and_call_contract() {
         let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
@@ -265,7 +340,7 @@ mod eth_tests {
 
         // Also set up a subscription for the same thing
         let ws = Provider::connect(anvil.ws_endpoint()).await.unwrap();
-        let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws);
+        let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws.into());
         let event2 = contract2.event::<ValueChanged>();
         let mut subscription = event2.subscribe().await.unwrap();