Repository: gakonst/ethers-rs
Pull request #2279 (2023-03-18 18:57:37)

feat: syn 2.0, merge eip712 crate into ethers-contract-derive
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation

https://github.com/dtolnay/syn/releases/tag/2.0.0

Blocked by:
- ~prettyplease (abigen formatting)~

Haven't updated yet (doesn't block):
- async-trait
- auto_impl
- clap_derive (from criterion and svm-rs)
- ctor (dev; from pretty_assertions)
- derive_more (from ethereum-types)
- futures-macro
- impl-trait-for-tuples (from ethbloom)
- num_enum_derive
- open-fastrlp-derive
- parity-scale-codec-derive
- phf_macros (from phf <- solang-parser)
- pin-project-internal (from pin-project)
- proc-macro-error (from auto_impl, clap_derive; seen above)
- rlp-derive
- serde_derive
- serial_test_derive (dev)
- signature_derive
- strum_macros
- synstructure
- thiserror-impl
- tokio-macros
- tracing-attributes
- tracing-test-macro
- wasm-bindgen-backend
- wasm-bindgen-macro-support
- zeroize_derive

<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution

Fix breaking changes, mainly:
- `input.parse_terminated` from turbofish to argument
- attribute parsing

Additionally, moves `ethers-derive-eip712` to `ethers-contract-derive` due to duplicated code and useless duplicated dependencies on ethers-core. Also ~removes~ deprecates the feature since it doesn't gate dependencies anymore.

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## TODO

- [x] revert temp prettyplease fix
- [x] fix eip712 test: `ethers_contract::it::contract::test_derive_eip712`

## PR Checklist

-   [x] Added Tests
-   [x] Added Documentation
-   [x] Breaking changes



Patch:
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index aebad59b..2ae1cc14 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -8,5 +8,8 @@
 #
 # $ git config blame.ignoreRevsFile .git-blame-ignore-revs
 
+# chore: add rustfmt.toml (#537)
+dcf20022c66921779059bbfc58f5d378d20b84be
+
 # fmt: all (#1751)
 5c762c44d760ed9dad3900be079062174ada0245
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e63f25d2..aea50aa1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,19 +31,19 @@ jobs:
                     - name: all features
                       flags: --workspace --all-features
                 include:
-                    # not workspace because compiling examples fails for no reason in CI,
+                    # not workspace because compiling examples fails for no reason in CI
                     - os: windows-latest
                       flags:
                           name: no default features
-                          flags: -p ethers --no-default-features
+                          flags: --no-default-features
                     - os: windows-latest
                       flags:
                           name: default features
-                          flags: -p ethers
+                          flags: ""
                     - os: windows-latest
                       flags:
                           name: all features
-                          flags: -p ethers --all-features
+                          flags: --all-features
         steps:
             - uses: actions/checkout@v3
             - uses: dtolnay/rust-toolchain@stable
@@ -85,16 +85,17 @@ jobs:
             - name: live tests
               run: cargo test -p ethers-etherscan --test it
 
-    live-tests:
-        name: live tests
-        runs-on: ubuntu-latest
-        concurrency: live-tests-${{ github.head_ref || github.run_id }}
-        steps:
-            - uses: actions/checkout@v3
-            - uses: dtolnay/rust-toolchain@stable
-            - uses: Swatinem/rust-cache@v2
-            - name: live tests
-              run: cargo test -p ethers --all-features
+    # TODO: Create a new `ethers-tests` crate in the workspace for live tests.
+    # live-tests:
+    #     name: live tests
+    #     runs-on: ubuntu-latest
+    #     concurrency: live-tests-${{ github.head_ref || github.run_id }}
+    #     steps:
+    #         - uses: actions/checkout@v3
+    #         - uses: dtolnay/rust-toolchain@stable
+    #         - uses: Swatinem/rust-cache@v2
+    #         - name: live tests
+    #           run: cargo test -p ethers-tests --all-features
 
     # TODO: [#2191](https://github.com/gakonst/ethers-rs/issues/2191)
     # feature-checks:
diff --git a/Cargo.lock b/Cargo.lock
index dfcced99..7611919b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -20,9 +20,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
 [[package]]
 name = "aead"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
 dependencies = [
  "crypto-common",
  "generic-array",
@@ -77,9 +77,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "anyhow"
-version = "1.0.69"
+version = "1.0.70"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
+checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
 
 [[package]]
 name = "arrayvec"
@@ -98,13 +98,13 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.66"
+version = "0.1.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
+checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -138,7 +138,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -209,9 +209,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.0.1"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5dd14596c0e5b954530d0e6f1fd99b89c03e313aa2086e8da4303701a09e1cf"
+checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
 
 [[package]]
 name = "bitvec"
@@ -255,9 +255,9 @@ dependencies = [
 
 [[package]]
 name = "block-padding"
-version = "0.3.2"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78"
+checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
 dependencies = [
  "generic-array",
 ]
@@ -345,9 +345,9 @@ dependencies = [
 
 [[package]]
 name = "cargo_metadata"
-version = "0.15.3"
+version = "0.15.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07"
+checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a"
 dependencies = [
  "camino",
  "cargo-platform",
@@ -485,7 +485,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -539,9 +539,9 @@ dependencies = [
 
 [[package]]
 name = "coins-bip39"
-version = "0.8.3"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efb68f3b6c3fee83828ecd8d463f360a397c32aaeb35bd931c01e5ddf5631c69"
+checksum = "ad2a68a46b9d8cc90484f0689adc0e4c890eb215bf698ae52e5235bb88f40be7"
 dependencies = [
  "bitvec 0.17.4",
  "coins-bip32",
@@ -651,15 +651,6 @@ version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
 
-[[package]]
-name = "convert_case"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
-dependencies = [
- "unicode-segmentation",
-]
-
 [[package]]
 name = "core-foundation"
 version = "0.9.3"
@@ -672,15 +663,15 @@ dependencies = [
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.8.3"
+version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.5"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181"
 dependencies = [
  "libc",
 ]
@@ -821,7 +812,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
 dependencies = [
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -850,9 +841,9 @@ dependencies = [
 
 [[package]]
 name = "cxx"
-version = "1.0.92"
+version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72"
+checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
 dependencies = [
  "cc",
  "cxxbridge-flags",
@@ -862,9 +853,9 @@ dependencies = [
 
 [[package]]
 name = "cxx-build"
-version = "1.0.92"
+version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613"
+checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
 dependencies = [
  "cc",
  "codespan-reporting",
@@ -872,24 +863,24 @@ dependencies = [
  "proc-macro2",
  "quote",
  "scratch",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
 name = "cxxbridge-flags"
-version = "1.0.92"
+version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97"
+checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
 
 [[package]]
 name = "cxxbridge-macro"
-version = "1.0.92"
+version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56"
+checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -916,9 +907,9 @@ dependencies = [
 
 [[package]]
 name = "der"
-version = "0.7.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc906908ea6458456e5eaa160a9c08543ec3d1e6f71e2235cedd660cb65f9df0"
+checksum = "19c5cb402c5c958281c7c0702edea7b780d03b86b606497ca3a10fcd3fc393ac"
 dependencies = [
  "const-oid",
  "zeroize",
@@ -932,7 +923,7 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -969,6 +960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
 dependencies = [
  "block-buffer 0.10.4",
+ "const-oid",
  "crypto-common",
  "subtle",
 ]
@@ -1002,11 +994,12 @@ checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
 
 [[package]]
 name = "ecdsa"
-version = "0.16.1"
+version = "0.16.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1b0a1222f8072619e8a6b667a854020a03d363738303203c09468b3424a420a"
+checksum = "106401dadc137d05cb0d4ab4d42be089746aefdfe8992df4d0edcf351c16ddca"
 dependencies = [
  "der",
+ "digest 0.10.6",
  "elliptic-curve",
  "rfc6979",
  "signature",
@@ -1014,9 +1007,9 @@ dependencies = [
 
 [[package]]
 name = "ed25519"
-version = "2.2.0"
+version = "2.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be522bee13fa6d8059f4903a4084aa3bd50725e18150202f0238deb615cd6371"
+checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963"
 dependencies = [
  "pkcs8",
  "signature",
@@ -1044,9 +1037,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
 
 [[package]]
 name = "elliptic-curve"
-version = "0.13.2"
+version = "0.13.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea5a92946e8614bb585254898bb7dd1ddad241ace60c52149e3765e34cc039d"
+checksum = "22cdacd4d6ed3f9b98680b679c0e52a823b8a2c7a97358d508fe247f2180c282"
 dependencies = [
  "base16ct",
  "crypto-bigint",
@@ -1063,9 +1056,9 @@ dependencies = [
 
 [[package]]
 name = "ena"
-version = "0.14.1"
+version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2e5d13ca2353ab7d0230988629def93914a8c4015f621f9b13ed2955614731d"
+checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
 dependencies = [
  "log",
 ]
@@ -1118,13 +1111,13 @@ dependencies = [
 
 [[package]]
 name = "errno"
-version = "0.2.8"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
 dependencies = [
  "errno-dragonfly",
  "libc",
- "winapi",
+ "windows-sys 0.45.0",
 ]
 
 [[package]]
@@ -1240,8 +1233,8 @@ dependencies = [
  "ethers-contract-abigen",
  "ethers-contract-derive",
  "ethers-core",
- "ethers-derive-eip712",
  "ethers-providers",
+ "ethers-signers",
  "ethers-solc",
  "futures-util",
  "hex",
@@ -1272,7 +1265,7 @@ dependencies = [
  "reqwest",
  "serde",
  "serde_json",
- "syn",
+ "syn 2.0.13",
  "tempfile",
  "tokio",
  "toml",
@@ -1284,12 +1277,14 @@ dependencies = [
 name = "ethers-contract-derive"
 version = "2.0.2"
 dependencies = [
+ "Inflector",
  "ethers-contract-abigen",
  "ethers-core",
  "hex",
  "proc-macro2",
  "quote",
- "syn",
+ "serde_json",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -1301,7 +1296,6 @@ dependencies = [
  "bytes",
  "cargo_metadata",
  "chrono",
- "convert_case",
  "elliptic-curve",
  "ethabi",
  "generic-array",
@@ -1312,31 +1306,18 @@ dependencies = [
  "num_enum",
  "once_cell",
  "open-fastrlp",
- "proc-macro2",
  "rand",
  "rlp",
  "serde",
  "serde_json",
  "strum",
- "syn",
+ "syn 2.0.13",
  "tempfile",
  "thiserror",
  "tiny-keccak",
  "unicode-xid",
 ]
 
-[[package]]
-name = "ethers-derive-eip712"
-version = "2.0.2"
-dependencies = [
- "ethers-contract-derive",
- "ethers-core",
- "hex",
- "quote",
- "serde_json",
- "syn",
-]
-
 [[package]]
 name = "ethers-etherscan"
 version = "2.0.2"
@@ -1436,7 +1417,6 @@ dependencies = [
  "eth-keystore",
  "ethers-contract-derive",
  "ethers-core",
- "ethers-derive-eip712",
  "futures-executor",
  "futures-util",
  "hex",
@@ -1667,9 +1647,9 @@ dependencies = [
 
 [[package]]
 name = "fiat-crypto"
-version = "0.1.19"
+version = "0.1.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93ace6ec7cc19c8ed33a32eaa9ea692d7faea05006b5356b9e2b668ec4bc3955"
+checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77"
 
 [[package]]
 name = "fixed-hash"
@@ -1753,9 +1733,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
 
 [[package]]
 name = "futures"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549"
+checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1768,9 +1748,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac"
+checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1778,15 +1758,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd"
+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83"
+checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -1795,9 +1775,9 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91"
+checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
 
 [[package]]
 name = "futures-locks"
@@ -1811,26 +1791,26 @@ dependencies = [
 
 [[package]]
 name = "futures-macro"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
+checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
 name = "futures-sink"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2"
+checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
 
 [[package]]
 name = "futures-task"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879"
+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
 
 [[package]]
 name = "futures-timer"
@@ -1844,9 +1824,9 @@ dependencies = [
 
 [[package]]
 name = "futures-util"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab"
+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -2000,9 +1980,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
 [[package]]
 name = "hex-literal"
-version = "0.3.4"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
+checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
 
 [[package]]
 name = "hidapi-rusb"
@@ -2138,16 +2118,16 @@ dependencies = [
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.53"
+version = "0.1.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
  "iana-time-zone-haiku",
  "js-sys",
  "wasm-bindgen",
- "winapi",
+ "windows",
 ]
 
 [[package]]
@@ -2205,7 +2185,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -2216,9 +2196,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
 
 [[package]]
 name = "indexmap"
-version = "1.9.2"
+version = "1.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
 dependencies = [
  "autocfg",
  "hashbrown",
@@ -2257,25 +2237,26 @@ dependencies = [
 
 [[package]]
 name = "io-lifetimes"
-version = "1.0.6"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
+checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
 dependencies = [
+ "hermit-abi 0.3.1",
  "libc",
- "windows-sys 0.45.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "ipnet"
-version = "2.7.1"
+version = "2.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
+checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
 
 [[package]]
 name = "is-terminal"
-version = "0.4.4"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
+checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8"
 dependencies = [
  "hermit-abi 0.3.1",
  "io-lifetimes",
@@ -2341,15 +2322,15 @@ dependencies = [
 
 [[package]]
 name = "lalrpop"
-version = "0.19.8"
+version = "0.19.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b30455341b0e18f276fa64540aff54deafb54c589de6aca68659c63dd2d5d823"
+checksum = "f34313ec00c2eb5c3c87ca6732ea02dcf3af99c3ff7a8fb622ffb99c9d860a87"
 dependencies = [
  "ascii-canvas",
- "atty",
  "bit-set",
  "diff",
  "ena",
+ "is-terminal",
  "itertools",
  "lalrpop-util",
  "petgraph",
@@ -2364,9 +2345,9 @@ dependencies = [
 
 [[package]]
 name = "lalrpop-util"
-version = "0.19.8"
+version = "0.19.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcf796c978e9b4d983414f4caedc9273aa33ee214c5b887bd55fde84c85d2dc4"
+checksum = "e5c1f7869c94d214466c5fd432dfed12c379fd87786768d36455892d46b18edd"
 dependencies = [
  "regex",
 ]
@@ -2379,9 +2360,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.140"
+version = "0.2.141"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
+checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
 
 [[package]]
 name = "libm"
@@ -2412,9 +2393,9 @@ dependencies = [
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.1.4"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
 
 [[package]]
 name = "lock_api"
@@ -2496,9 +2477,9 @@ dependencies = [
 
 [[package]]
 name = "mime"
-version = "0.3.16"
+version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
 
 [[package]]
 name = "miniz_oxide"
@@ -2600,23 +2581,23 @@ dependencies = [
 
 [[package]]
 name = "num_enum"
-version = "0.5.11"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
+checksum = "a0fa9d8a04aa0af7b5845b514a828f829ae3f0ec3f60d9842e1dfaeb49a0e68b"
 dependencies = [
  "num_enum_derive",
 ]
 
 [[package]]
 name = "num_enum_derive"
-version = "0.5.11"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
+checksum = "3e51dcc6bafb7f3ac88b65d2ad21f4b53d878e496712060e23011862ebd2d2d1"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -2665,14 +2646,14 @@ dependencies = [
  "bytes",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
 name = "openssl"
-version = "0.10.46"
+version = "0.10.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd2523381e46256e40930512c7fd25562b9eae4812cb52078f155e87217c9d1e"
+checksum = "4d2f106ab837a24e03672c59b1239669a0596406ff657c3c0835b6b7f0f35a33"
 dependencies = [
  "bitflags 1.3.2",
  "cfg-if",
@@ -2685,13 +2666,13 @@ dependencies = [
 
 [[package]]
 name = "openssl-macros"
-version = "0.1.0"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -2702,11 +2683,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.81"
+version = "0.9.84"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "176be2629957c157240f68f61f2d0053ad3a4ecfdd9ebf1e6521d18d9635cf67"
+checksum = "3a20eace9dc2d82904039cb76dcf50fb1a0bba071cfd1629720b5d6f1ddba0fa"
 dependencies = [
- "autocfg",
  "cc",
  "libc",
  "pkg-config",
@@ -2715,9 +2695,9 @@ dependencies = [
 
 [[package]]
 name = "os_str_bytes"
-version = "6.4.1"
+version = "6.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
+checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
 
 [[package]]
 name = "output_vt100"
@@ -2791,7 +2771,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -2812,7 +2792,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
 dependencies = [
  "cfg-if",
  "libc",
- "redox_syscall",
+ "redox_syscall 0.2.16",
  "smallvec",
  "windows-sys 0.45.0",
 ]
@@ -2912,7 +2892,7 @@ dependencies = [
  "phf_shared 0.11.1",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -2956,7 +2936,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -2973,9 +2953,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
 name = "pkcs8"
-version = "0.10.1"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d2820d87d2b008616e5c27212dd9e0e694fb4c6b522de06094106813328cb49"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
 dependencies = [
  "der",
  "spki",
@@ -3047,12 +3027,12 @@ dependencies = [
 
 [[package]]
 name = "prettyplease"
-version = "0.1.25"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
+checksum = "1ceca8aaf45b5c46ec7ed39fff75f57290368c1846d33d24a122ca81416ab058"
 dependencies = [
  "proc-macro2",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -3097,7 +3077,7 @@ dependencies = [
  "proc-macro-error-attr",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
  "version_check",
 ]
 
@@ -3114,9 +3094,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.52"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
 dependencies = [
  "unicode-ident",
 ]
@@ -3209,6 +3189,15 @@ dependencies = [
  "bitflags 1.3.2",
 ]
 
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
 [[package]]
 name = "redox_users"
 version = "0.4.3"
@@ -3216,15 +3205,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
 dependencies = [
  "getrandom",
- "redox_syscall",
+ "redox_syscall 0.2.16",
  "thiserror",
 ]
 
 [[package]]
 name = "regex"
-version = "1.7.1"
+version = "1.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
+checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -3242,15 +3231,15 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.6.28"
+version = "0.6.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
 
 [[package]]
 name = "reqwest"
-version = "0.11.14"
+version = "0.11.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
+checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
 dependencies = [
  "base64 0.21.0",
  "bytes",
@@ -3341,14 +3330,14 @@ checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
 name = "rusb"
-version = "0.9.1"
+version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "703aa035c21c589b34fb5136b12e68fc8dcf7ea46486861381361dd8ebf5cee0"
+checksum = "44a8c36914f9b1a3be712c1dfa48c9b397131f9a75707e570a391735f785c5d1"
 dependencies = [
  "libc",
  "libusb1-sys",
@@ -3454,9 +3443,9 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.36.9"
+version = "0.37.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
+checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d"
 dependencies = [
  "bitflags 1.3.2",
  "errno",
@@ -3531,9 +3520,9 @@ dependencies = [
 
 [[package]]
 name = "scale-info"
-version = "2.3.1"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608"
+checksum = "0cfdffd972d76b22f3d7f81c8be34b2296afd3a25e0a547bd9abe340a4dbbe97"
 dependencies = [
  "cfg-if",
  "derive_more",
@@ -3543,14 +3532,14 @@ dependencies = [
 
 [[package]]
 name = "scale-info-derive"
-version = "2.3.1"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c"
+checksum = "61fa974aea2d63dd18a4ec3a49d59af9f34178c73a4f56d2f18205628d00681e"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -3662,9 +3651,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
 
 [[package]]
 name = "serde"
-version = "1.0.156"
+version = "1.0.159"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4"
+checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
 dependencies = [
  "serde_derive",
 ]
@@ -3682,20 +3671,20 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.156"
+version = "1.0.159"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
+checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.94"
+version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
+checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
 dependencies = [
  "itoa",
  "ryu",
@@ -3704,9 +3693,9 @@ dependencies = [
 
 [[package]]
 name = "serde_path_to_error"
-version = "0.1.10"
+version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db0969fff533976baadd92e08b1d102c5a3d8a8049eadfd69d4d1e3c5b2ed189"
+checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0"
 dependencies = [
  "serde",
 ]
@@ -3734,9 +3723,9 @@ dependencies = [
 
 [[package]]
 name = "serial_test"
-version = "1.0.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "538c30747ae860d6fb88330addbbd3e0ddbe46d662d032855596d8a8ca260611"
+checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d"
 dependencies = [
  "dashmap",
  "futures",
@@ -3748,13 +3737,13 @@ dependencies = [
 
 [[package]]
 name = "serial_test_derive"
-version = "1.0.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "079a83df15f85d89a68d64ae1238f142f172b1fa915d0d76b26a7cba1b659a69"
+checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -3828,9 +3817,9 @@ dependencies = [
 
 [[package]]
 name = "signature"
-version = "2.0.0"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d"
+checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500"
 dependencies = [
  "digest 0.10.6",
  "rand_core",
@@ -3845,7 +3834,7 @@ checksum = "ede930749cca4e3a3df7e37b5f0934a55693e01d028d7a4e506b44cbc059d95a"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -3900,9 +3889,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 
 [[package]]
 name = "spki"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0445c905640145c7ea8c1993555957f65e7c46d0535b91ba501bc9bfc85522f"
+checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e"
 dependencies = [
  "base64ct",
  "der",
@@ -3952,7 +3941,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -4017,15 +4006,14 @@ dependencies = [
 ]
 
 [[package]]
-name = "synstructure"
-version = "0.12.6"
+name = "syn"
+version = "2.0.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
+checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
- "unicode-xid",
+ "unicode-ident",
 ]
 
 [[package]]
@@ -4036,15 +4024,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
 
 [[package]]
 name = "tempfile"
-version = "3.4.0"
+version = "3.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
+checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
 dependencies = [
  "cfg-if",
  "fastrand",
- "redox_syscall",
+ "redox_syscall 0.3.5",
  "rustix",
- "windows-sys 0.42.0",
+ "windows-sys 0.45.0",
 ]
 
 [[package]]
@@ -4085,22 +4073,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
 
 [[package]]
 name = "thiserror"
-version = "1.0.39"
+version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.39"
+version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -4175,14 +4163,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.26.0"
+version = "1.27.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
+checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
 dependencies = [
  "autocfg",
  "bytes",
  "libc",
- "memchr",
  "mio",
  "num_cpus",
  "parking_lot",
@@ -4195,13 +4182,13 @@ dependencies = [
 
 [[package]]
 name = "tokio-macros"
-version = "1.8.2"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
+checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -4280,9 +4267,9 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.19.7"
+version = "0.19.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274"
+checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
 dependencies = [
  "indexmap",
  "serde",
@@ -4317,7 +4304,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -4389,7 +4376,7 @@ checksum = "258bc1c4f8e2e73a977812ab339d503e6feeb92700f6d07a6de4d321522d5c08"
 dependencies = [
  "lazy_static",
  "quote",
- "syn",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -4455,9 +4442,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-bidi"
-version = "0.3.11"
+version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
 
 [[package]]
 name = "unicode-ident"
@@ -4474,12 +4461,6 @@ dependencies = [
  "tinyvec",
 ]
 
-[[package]]
-name = "unicode-segmentation"
-version = "1.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
-
 [[package]]
 name = "unicode-width"
 version = "0.1.10"
@@ -4601,7 +4582,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
  "wasm-bindgen-shared",
 ]
 
@@ -4635,7 +4616,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 1.0.109",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -4730,19 +4711,28 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "windows"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+dependencies = [
+ "windows-targets 0.48.0",
+]
+
 [[package]]
 name = "windows-sys"
 version = "0.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
 ]
 
 [[package]]
@@ -4751,7 +4741,16 @@ version = "0.45.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.0",
 ]
 
 [[package]]
@@ -4760,13 +4759,28 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
 ]
 
 [[package]]
@@ -4775,47 +4789,89 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
 
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
 [[package]]
 name = "winnow"
-version = "0.3.6"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966"
+checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
 dependencies = [
  "memchr",
 ]
@@ -4876,7 +4932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f6a89568917376ff46a3de7bd0abdac47e9cc8ded4e1018e4a36d071d43a54ad"
 dependencies = [
  "aes 0.8.2",
- "bitflags 2.0.1",
+ "bitflags 2.1.0",
  "cbc",
  "ccm",
  "cmac",
@@ -4905,23 +4961,22 @@ dependencies = [
 
 [[package]]
 name = "zeroize"
-version = "1.5.7"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
+checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
 dependencies = [
  "zeroize_derive",
 ]
 
 [[package]]
 name = "zeroize_derive"
-version = "1.3.3"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
- "synstructure",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -4965,9 +5020,9 @@ dependencies = [
 
 [[package]]
 name = "zstd-sys"
-version = "2.0.7+zstd.1.5.4"
+version = "2.0.8+zstd.1.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5"
+checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c"
 dependencies = [
  "cc",
  "libc",
diff --git a/Cargo.toml b/Cargo.toml
index aedc4933..cb839738 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -76,29 +76,28 @@ ethers-solc = { version = "2.0.2", path = "ethers-solc", default-features = fals
 
 ethers-contract-abigen = { version = "2.0.2", path = "ethers-contract/ethers-contract-abigen", default-features = false }
 ethers-contract-derive = { version = "2.0.2", path = "ethers-contract/ethers-contract-derive", default-features = false }
-ethers-derive-eip712 = { version = "2.0.2", path = "ethers-core/ethers-derive-eip712", default-features = false }
 
 # async / async utils
-tokio = "1.26"
+tokio = "1.27"
 tokio-tungstenite = { version = "0.18.0", default-features = false }
-futures = { version = "0.3.27", default-features = false, features = ["std"] }
-futures-core = "0.3.27"
-futures-util = "0.3.27"
-futures-executor = "0.3.27"
-futures-channel = "0.3.27"
+futures = { version = "0.3.28", default-features = false, features = ["std"] }
+futures-core = "0.3.28"
+futures-util = "0.3.28"
+futures-executor = "0.3.28"
+futures-channel = "0.3.28"
 futures-locks = { version = "0.7.1", default-features = false }
 futures-timer = { version = "3.0.2", default-features = false, features = ["wasm-bindgen"] }
 pin-project = "1.0"
-reqwest = { version = "0.11.14", default-features = false }
+reqwest = { version = "0.11.16", default-features = false }
 url = { version = "2.3", default-features = false }
 
 # crypto
-elliptic-curve = { version = "0.13.2", default-features = false }
-generic-array = { version = "0.14.6", default-features = false }
+elliptic-curve = { version = "0.13.3", default-features = false }
+generic-array = { version = "0.14.7", default-features = false }
 k256 = { version = "0.13.0", default-features = false, features = ["ecdsa", "std"] }
 sha2 = { version = "0.10.6", default-features = false }
 tiny-keccak = { version = "2.0.2", default-features = false }
-spki = { version = "0.7.0", default-features = false }
+spki = { version = "0.7.1", default-features = false }
 
 # serde
 serde = "1.0"
@@ -107,21 +106,22 @@ serde_json = "1.0"
 # macros
 proc-macro2 = "1.0"
 quote = "1.0"
-syn = { version = "1.0", features = ["extra-traits"] }
-async-trait = "0.1.66"
+syn = { version = "2.0", features = ["extra-traits"] }
+async-trait = "0.1.68"
 auto_impl = "1.0"
 
 # misc
+Inflector = "0.11"
 thiserror = "1.0"
 once_cell = "1.17"
 hex = "0.4"
 eyre = "0.6"
-tempfile = "3.4"
+tempfile = "3.5"
 dunce = "1.0"
 walkdir = "2.3"
 regex = "1.7"
 toml = "0.7"
-hex-literal = "0.3"
+hex-literal = "0.4"
 rand = "0.8"
 bytes = "1.4"
 semver = "1.0"
diff --git a/ethers-contract/Cargo.toml b/ethers-contract/Cargo.toml
index dc0d5483..601fe1cb 100644
--- a/ethers-contract/Cargo.toml
+++ b/ethers-contract/Cargo.toml
@@ -39,10 +39,8 @@ hex.workspace = true
 ethers-contract-abigen = { workspace = true, optional = true }
 ethers-contract-derive = { workspace = true, optional = true }
 
-# eip712
-ethers-derive-eip712 = { workspace = true, optional = true }
-
 [dev-dependencies]
+ethers-signers.workspace = true
 ethers-solc.workspace = true
 ethers-providers = { workspace = true, features = ["ws"] }
 
@@ -52,8 +50,6 @@ tokio = { workspace = true, features = ["macros"] }
 [features]
 default = ["abigen"]
 
-eip712 = ["ethers-derive-eip712", "ethers-core/eip712"]
-
 abigen-offline = ["ethers-contract-abigen", "ethers-contract-derive"]
 abigen = ["abigen-offline", "ethers-contract-abigen/online"]
 
@@ -62,3 +58,6 @@ legacy = []
 
 rustls = ["ethers-contract-abigen/rustls"]
 openssl = ["ethers-contract-abigen/openssl"]
+
+# Deprecated
+eip712 = []
diff --git a/ethers-contract/ethers-contract-abigen/Cargo.toml b/ethers-contract/ethers-contract-abigen/Cargo.toml
index fdbb5a47..072b7a61 100644
--- a/ethers-contract/ethers-contract-abigen/Cargo.toml
+++ b/ethers-contract/ethers-contract-abigen/Cargo.toml
@@ -30,10 +30,10 @@ ethers-core = { workspace = true, features = ["macros"] }
 
 proc-macro2.workspace = true
 quote.workspace = true
-syn.workspace = true
-prettyplease = "0.1.25"
+syn = { workspace = true, features = ["full"] }
+prettyplease = "0.2.4"
 
-Inflector = "0.11"
+Inflector.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 hex.workspace = true
diff --git a/ethers-contract/ethers-contract-abigen/src/multi.rs b/ethers-contract/ethers-contract-abigen/src/multi.rs
index b51bc3a0..0f9127b6 100644
--- a/ethers-contract/ethers-contract-abigen/src/multi.rs
+++ b/ethers-contract/ethers-contract-abigen/src/multi.rs
@@ -13,7 +13,8 @@ use std::{
 use toml::Value;
 
 /// The default ethers dependency to generate.
-const DEFAULT_ETHERS_DEP: &str = "ethers = { git = \"https://github.com/gakonst/ethers-rs\", default-features = false, features = [\"abigen\"] }";
+const DEFAULT_ETHERS_DEP: &str =
+    "ethers = { version = \"2\", default-features = false, features = [\"abigen\"] }";
 
 /// Collects Abigen structs for a series of contracts, pending generation of
 /// the contract bindings.
diff --git a/ethers-contract/ethers-contract-derive/Cargo.toml b/ethers-contract/ethers-contract-derive/Cargo.toml
index 65348d4b..86226b07 100644
--- a/ethers-contract/ethers-contract-derive/Cargo.toml
+++ b/ethers-contract/ethers-contract-derive/Cargo.toml
@@ -28,4 +28,6 @@ proc-macro2.workspace = true
 quote.workspace = true
 syn.workspace = true
 
+Inflector.workspace = true
 hex.workspace = true
+serde_json.workspace = true
diff --git a/ethers-contract/ethers-contract-derive/src/abi_ty.rs b/ethers-contract/ethers-contract-derive/src/abi_ty.rs
index f25fd4b8..8cf6b538 100644
--- a/ethers-contract/ethers-contract-derive/src/abi_ty.rs
+++ b/ethers-contract/ethers-contract-derive/src/abi_ty.rs
@@ -83,16 +83,16 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> Result<TokenStream, Erro
             // can't encode an empty struct
             // TODO: panic instead?
             quote! {
-                #ethers_core::abi::Token::Tuple(Vec::new())
+                #ethers_core::abi::Token::Tuple(::std::vec![])
             },
         ),
         _ => {
+            let err_format_string = format!("Expected {params_len} tokens, got {{}}: {{:?}}");
             let from_token = quote! {
                 if let #ethers_core::abi::Token::Tuple(tokens) = token {
                     if tokens.len() != #params_len {
                         return Err(#ethers_core::abi::InvalidOutputType(::std::format!(
-                            "Expected {} tokens, got {}: {:?}",
-                            #params_len,
+                            #err_format_string,
                             tokens.len(),
                             tokens
                         )));
@@ -136,9 +136,7 @@ pub fn derive_tokenizeable_impl(input: &DeriveInput) -> Result<TokenStream, Erro
             #generic_predicates
             #tokenize_predicates
         {
-            fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result<Self, #ethers_core::abi::InvalidOutputType>
-            where
-                Self: Sized,
+            fn from_token(token: #ethers_core::abi::Token) -> ::core::result::Result<Self, #ethers_core::abi::InvalidOutputType>
             {
                 #from_token_impl
             }
@@ -161,9 +159,7 @@ fn tokenize_unit_type(name: &Ident) -> TokenStream {
 
     quote! {
         impl #ethers_core::abi::Tokenizable for #name {
-            fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result<Self, #ethers_core::abi::InvalidOutputType>
-            where
-                Self: Sized,
+            fn from_token(token: #ethers_core::abi::Token) -> ::core::result::Result<Self, #ethers_core::abi::InvalidOutputType>
             {
                 if let #ethers_core::abi::Token::Tuple(tokens) = token {
                     if !tokens.is_empty() {
@@ -205,45 +201,44 @@ fn tokenize_enum<'a>(
 
     let mut into_tokens = TokenStream::new();
     let mut from_tokens = TokenStream::new();
-    for (idx, variant) in variants.into_iter().enumerate() {
+    let last = variants.size_hint().1.unwrap().saturating_sub(1);
+    for (idx, variant) in variants.enumerate() {
         let var_ident = &variant.ident;
-        if variant.fields.len() > 1 {
-            return Err(Error::new(
-                variant.span(),
-                "EthAbiType cannot be derived for enum variants with multiple fields",
-            ))
-        } else if variant.fields.is_empty() {
-            let value = Literal::u8_unsuffixed(idx as u8);
-            from_tokens.extend(quote! {
-                if let Ok(#value) = u8::from_token(token.clone()) {
-                    return Ok(#enum_name::#var_ident)
-                }
-            });
-            into_tokens.extend(quote! {
-                #enum_name::#var_ident => #value.into_token(),
-            });
-        } else if let Some(field) = variant.fields.iter().next() {
-            let ty = &field.ty;
-            from_tokens.extend(quote! {
-                if let Ok(decoded) = #ty::from_token(token.clone()) {
-                    return Ok(#enum_name::#var_ident(decoded))
-                }
-            });
-            into_tokens.extend(quote! {
-                #enum_name::#var_ident(element) => element.into_token(),
-            });
-        } else {
-            into_tokens.extend(quote! {
-                #enum_name::#var_ident(element) => # ethers_core::abi::Token::Tuple(::std::vec::Vec::new()),
-            });
+        let clone = if idx < last { Some(quote!(.clone())) } else { None };
+        match variant.fields.len() {
+            0 => {
+                let value = Literal::u8_suffixed(idx as u8);
+                from_tokens.extend(quote! {
+                    if let Ok(#value) = <u8 as #ethers_core::abi::Tokenizable>::from_token(token #clone) {
+                        return Ok(#enum_name::#var_ident)
+                    }
+                });
+                into_tokens.extend(quote! {
+                    #enum_name::#var_ident => <u8 as #ethers_core::abi::Tokenizable>::into_token(#value),
+                });
+            }
+            1 => {
+                from_tokens.extend(quote! {
+                    if let Ok(decoded) = #ethers_core::abi::Tokenizable::from_token(token #clone) {
+                        return Ok(#enum_name::#var_ident(decoded))
+                    }
+                });
+                into_tokens.extend(quote! {
+                    #enum_name::#var_ident(element) => element.into_token(),
+                });
+            }
+            _ => {
+                return Err(Error::new(
+                    variant.span(),
+                    "EthAbiType cannot be derived for enum variants with multiple fields",
+                ))
+            }
         }
     }
 
     Ok(quote! {
         impl #ethers_core::abi::Tokenizable for #enum_name {
-            fn from_token(token: #ethers_core::abi::Token) -> ::std::result::Result<Self, #ethers_core::abi::InvalidOutputType>
-            where
-                Self: Sized,
+            fn from_token(token: #ethers_core::abi::Token) -> ::core::result::Result<Self, #ethers_core::abi::InvalidOutputType>
             {
                 #from_tokens
                 Err(#ethers_core::abi::InvalidOutputType("Failed to decode all type variants".to_string()))
diff --git a/ethers-contract/ethers-contract-derive/src/abigen.rs b/ethers-contract/ethers-contract-derive/src/abigen.rs
index 18cb471c..a0106321 100644
--- a/ethers-contract/ethers-contract-derive/src/abigen.rs
+++ b/ethers-contract/ethers-contract-derive/src/abigen.rs
@@ -40,8 +40,7 @@ impl Contracts {
 
 impl Parse for Contracts {
     fn parse(input: ParseStream) -> Result<Self> {
-        let inner =
-            input.parse_terminated::<_, Token![;]>(ContractArgs::parse)?.into_iter().collect();
+        let inner = input.parse_terminated(ContractArgs::parse, Token![;])?.into_iter().collect();
         Ok(Self { inner })
     }
 }
@@ -122,7 +121,7 @@ impl Parse for Parameter {
             "methods" => {
                 let content;
                 braced!(content in input);
-                let parsed = content.parse_terminated::<_, Token![;]>(Spanned::<Method>::parse)?;
+                let parsed = content.parse_terminated(Spanned::<Method>::parse, Token![;])?;
 
                 let mut methods = Vec::with_capacity(parsed.len());
                 let mut signatures = HashSet::new();
@@ -141,7 +140,7 @@ impl Parse for Parameter {
             "derives" | "event_derives" => {
                 let content;
                 parenthesized!(content in input);
-                let derives = content.parse_terminated::<_, Token![,]>(Path::parse)?;
+                let derives = content.parse_terminated(Path::parse, Token![,])?;
                 Ok(Parameter::Derives(derives))
             }
             _ => Err(Error::new(name.span(), "unexpected named parameter")),
@@ -168,7 +167,7 @@ impl Parse for Method {
         // function params
         let content;
         parenthesized!(content in input);
-        let params = content.parse_terminated::<_, Token![,]>(Ident::parse)?;
+        let params = content.parse_terminated(Ident::parse, Token![,])?;
         let last_i = params.len().saturating_sub(1);
 
         signature.push('(');
diff --git a/ethers-contract/ethers-contract-derive/src/call.rs b/ethers-contract/ethers-contract-derive/src/call.rs
index f0ff15a0..fb941c59 100644
--- a/ethers-contract/ethers-contract-derive/src/call.rs
+++ b/ethers-contract/ethers-contract-derive/src/call.rs
@@ -11,12 +11,10 @@ use syn::{parse::Error, DeriveInput};
 
 /// Generates the `ethcall` trait support
 pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> Result<TokenStream, Error> {
-    let attributes = parse_calllike_attributes(&input, "ethcall")?;
+    let attributes = parse_calllike_attributes!(input, "ethcall");
 
-    let function_call_name =
-        attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
-
-    let mut function = if let Some((abi, span)) = attributes.abi {
+    let function_call_name = attributes.name(&input.ident);
+    let mut function = if let Some((abi, span)) = attributes.abi() {
         let sig = abi.trim_start_matches("function ").trim_start();
         // try to parse as solidity function
         match HumanReadableParser::parse_function(&abi) {
diff --git a/ethers-contract/ethers-contract-derive/src/calllike.rs b/ethers-contract/ethers-contract-derive/src/calllike.rs
index 6cac69e3..4cbb28c8 100644
--- a/ethers-contract/ethers-contract-derive/src/calllike.rs
+++ b/ethers-contract/ethers-contract-derive/src/calllike.rs
@@ -7,97 +7,50 @@ use ethers_core::{
 };
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::quote;
-use syn::{parse::Error, spanned::Spanned, AttrStyle, DeriveInput, Lit, Meta, NestedMeta};
+use syn::{DeriveInput, LitStr, Result};
 
 /// All the attributes the `EthCall`/`EthError` macro supports
 #[derive(Default)]
 pub struct EthCalllikeAttributes {
-    pub name: Option<(String, Span)>,
-    pub abi: Option<(String, Span)>,
+    pub name: Option<LitStr>,
+    pub abi: Option<LitStr>,
 }
 
-/// extracts the attributes from the struct annotated with the given attribute
-pub fn parse_calllike_attributes(
-    input: &DeriveInput,
-    attr_name: &str,
-) -> Result<EthCalllikeAttributes, Error> {
-    let mut result = EthCalllikeAttributes::default();
-    for a in input.attrs.iter() {
-        if let AttrStyle::Outer = a.style {
-            if let Ok(Meta::List(meta)) = a.parse_meta() {
-                if meta.path.is_ident(attr_name) {
-                    for n in meta.nested.iter() {
-                        if let NestedMeta::Meta(meta) = n {
-                            match meta {
-                                Meta::Path(path) => {
-                                    return Err(Error::new(
-                                        path.span(),
-                                        format!("unrecognized {attr_name} parameter"),
-                                    ))
-                                }
-                                Meta::List(meta) => {
-                                    return Err(Error::new(
-                                        meta.path.span(),
-                                        format!("unrecognized {attr_name} parameter"),
-                                    ))
-                                }
-                                Meta::NameValue(meta) => {
-                                    if meta.path.is_ident("name") {
-                                        if let Lit::Str(ref lit_str) = meta.lit {
-                                            if result.name.is_none() {
-                                                result.name =
-                                                    Some((lit_str.value(), lit_str.span()));
-                                            } else {
-                                                return Err(Error::new(
-                                                    meta.span(),
-                                                    "name already specified",
-                                                ))
-                                            }
-                                        } else {
-                                            return Err(Error::new(
-                                                meta.span(),
-                                                "name must be a string",
-                                            ))
-                                        }
-                                    } else if meta.path.is_ident("abi") {
-                                        if let Lit::Str(ref lit_str) = meta.lit {
-                                            if result.abi.is_none() {
-                                                result.abi =
-                                                    Some((lit_str.value(), lit_str.span()));
-                                            } else {
-                                                return Err(Error::new(
-                                                    meta.span(),
-                                                    "abi already specified",
-                                                ))
-                                            }
-                                        } else {
-                                            return Err(Error::new(
-                                                meta.span(),
-                                                "abi must be a string",
-                                            ))
-                                        }
-                                    } else {
-                                        return Err(Error::new(
-                                            meta.span(),
-                                            format!("unrecognized {attr_name} parameter"),
-                                        ))
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
+impl EthCalllikeAttributes {
+    pub fn name(&self, fallback: &Ident) -> String {
+        self.name.as_ref().map(|s| s.value()).unwrap_or_else(|| fallback.to_string())
+    }
+
+    pub fn abi(&self) -> Option<(String, Span)> {
+        self.abi.as_ref().map(|s| (s.value(), s.span()))
     }
-    Ok(result)
 }
 
+macro_rules! parse_calllike_attributes {
+    ($input:ident, $attr_ident:literal) => {{
+        let mut result = EthCalllikeAttributes::default();
+        $crate::utils::parse_attributes!($input.attrs.iter(), $attr_ident, meta,
+            "name", result.name => {
+                meta.input.parse::<::syn::Token![=]>()?;
+                let litstr: ::syn::LitStr = meta.input.parse()?;
+                result.name = Some(litstr);
+            }
+            "abi", result.abi => {
+                meta.input.parse::<::syn::Token![=]>()?;
+                let litstr: ::syn::LitStr = meta.input.parse()?;
+                result.abi = Some(litstr);
+            }
+        );
+        result
+    }};
+}
+pub(crate) use parse_calllike_attributes;
+
 /// Generates the decode implementation based on the type's runtime `AbiType` impl
 pub fn derive_decode_impl_with_abi_type(
     input: &DeriveInput,
     trait_ident: Ident,
-) -> Result<TokenStream, Error> {
+) -> Result<TokenStream> {
     let datatypes_array = utils::abi_parameters_array(input, &trait_ident.to_string())?;
     Ok(derive_decode_impl(datatypes_array, trait_ident))
 }
@@ -130,7 +83,7 @@ pub fn derive_codec_impls(
     input: &DeriveInput,
     decode_impl: TokenStream,
     trait_ident: Ident,
-) -> Result<TokenStream, Error> {
+) -> Result<TokenStream> {
     // the ethers crates to use
     let ethers_core = ethers_core_crate();
     let ethers_contract = ethers_contract_crate();
@@ -138,7 +91,7 @@ pub fn derive_codec_impls(
 
     let codec_impl = quote! {
         impl #ethers_core::abi::AbiDecode for #struct_name {
-            fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result<Self, #ethers_core::abi::AbiError> {
+            fn decode(bytes: impl AsRef<[u8]>) -> ::core::result::Result<Self, #ethers_core::abi::AbiError> {
                 #decode_impl
             }
         }
diff --git a/ethers-contract/ethers-contract-derive/src/codec.rs b/ethers-contract/ethers-contract-derive/src/codec.rs
index 35348db8..ba911e5c 100644
--- a/ethers-contract/ethers-contract-derive/src/codec.rs
+++ b/ethers-contract/ethers-contract-derive/src/codec.rs
@@ -11,15 +11,22 @@ pub fn derive_codec_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
 
     quote! {
         impl #ethers_core::abi::AbiDecode for #name {
-            fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result<Self, #ethers_core::abi::AbiError> {
-                if let #ethers_core::abi::ParamType::Tuple(params) = <Self as #ethers_core::abi::AbiType>::param_type() {
-                    let tokens = #ethers_core::abi::decode(&params, bytes.as_ref())?;
-                    Ok(<Self as #ethers_core::abi::Tokenizable>::from_token(#ethers_core::abi::Token::Tuple(tokens))?)
-                } else {
-                    Err(
-                        #ethers_core::abi::InvalidOutputType("Expected tuple".to_string()).into()
-                    )
+            fn decode(bytes: impl AsRef<[u8]>) -> ::core::result::Result<Self, #ethers_core::abi::AbiError> {
+                fn _decode(bytes: &[u8]) -> ::core::result::Result<#name, #ethers_core::abi::AbiError> {
+                    let #ethers_core::abi::ParamType::Tuple(params) =
+                        <#name as #ethers_core::abi::AbiType>::param_type() else { unreachable!() };
+                    let min_len = params.iter().map(#ethers_core::abi::minimum_size).sum();
+                    if bytes.len() < min_len {
+                        Err(#ethers_core::abi::AbiError::DecodingError(#ethers_core::abi::ethabi::Error::InvalidData))
+                    } else {
+                        let tokens = #ethers_core::abi::decode(&params, bytes)?;
+                        let tuple = #ethers_core::abi::Token::Tuple(tokens);
+                        let this = <#name as #ethers_core::abi::Tokenizable>::from_token(tuple)?;
+                        Ok(this)
+                    }
                 }
+
+                _decode(bytes.as_ref())
             }
         }
 
diff --git a/ethers-contract/ethers-contract-derive/src/eip712.rs b/ethers-contract/ethers-contract-derive/src/eip712.rs
new file mode 100644
index 00000000..ece0524e
--- /dev/null
+++ b/ethers-contract/ethers-contract-derive/src/eip712.rs
@@ -0,0 +1,181 @@
+use crate::utils;
+use ethers_core::{
+    abi::{Address, ParamType},
+    macros::ethers_core_crate,
+    types::transaction::eip712::EIP712Domain,
+    utils::keccak256,
+};
+use inflector::Inflector;
+use proc_macro2::{Literal, TokenStream};
+use quote::quote;
+use syn::{spanned::Spanned, Data, DeriveInput, Error, Fields, LitInt, LitStr, Result, Token};
+
+pub(crate) fn impl_derive_eip712(input: &DeriveInput) -> Result<TokenStream> {
+    // Primary type should match the type in the ethereum verifying contract;
+    let primary_type = &input.ident;
+
+    // Instantiate domain from parsed attributes
+    let domain = parse_attributes(input)?;
+
+    let domain_separator = into_tokens(domain.separator());
+
+    let domain_str = serde_json::to_string(&domain).unwrap();
+
+    // Must parse the AST at compile time.
+    let parsed_fields = parse_fields(input)?;
+
+    // Compute the type hash for the derived struct using the parsed fields from above.
+    let type_hash = into_tokens(make_type_hash(primary_type.to_string(), &parsed_fields));
+
+    // Use reference to ethers_core instead of directly using the crate itself.
+    let ethers_core = ethers_core_crate();
+
+    let tokens = quote! {
+        impl #ethers_core::types::transaction::eip712::Eip712 for #primary_type {
+            type Error = #ethers_core::types::transaction::eip712::Eip712Error;
+
+            #[inline]
+            fn type_hash() -> ::core::result::Result<[u8; 32], Self::Error> {
+                Ok([#(#type_hash),*])
+            }
+
+            #[inline]
+            fn domain_separator(&self) -> ::core::result::Result<[u8; 32], Self::Error> {
+                Ok([#(#domain_separator),*])
+            }
+
+            fn domain(&self) -> ::core::result::Result<#ethers_core::types::transaction::eip712::EIP712Domain, Self::Error> {
+                #ethers_core::utils::__serde_json::from_str(#domain_str).map_err(::core::convert::Into::into)
+            }
+
+            fn struct_hash(&self) -> ::core::result::Result<[u8; 32], Self::Error> {
+                let mut items = vec![#ethers_core::abi::Token::Uint(
+                    #ethers_core::types::U256::from(&Self::type_hash()?[..]),
+                )];
+
+                if let #ethers_core::abi::Token::Tuple(tokens) =
+                    #ethers_core::abi::Tokenizable::into_token(::core::clone::Clone::clone(self))
+                {
+                    items.reserve(tokens.len());
+                    for token in tokens {
+                        match &token {
+                            #ethers_core::abi::Token::Tuple(t) => {
+                                // TODO: check for nested Eip712 Type;
+                                // Challenge is determining the type hash
+                                return Err(Self::Error::NestedEip712StructNotImplemented);
+                            },
+                            _ => {
+                                items.push(#ethers_core::types::transaction::eip712::encode_eip712_type(token));
+                            }
+                        }
+                    }
+                }
+
+                let struct_hash = #ethers_core::utils::keccak256(#ethers_core::abi::encode(
+                    &items,
+                ));
+
+                Ok(struct_hash)
+            }
+        }
+    };
+
+    Ok(tokens)
+}
+
+fn parse_attributes(input: &DeriveInput) -> Result<EIP712Domain> {
+    let mut domain = EIP712Domain::default();
+    utils::parse_attributes!(input.attrs.iter(), "eip712", meta,
+        "name", domain.name => {
+            meta.input.parse::<Token![=]>()?;
+            let litstr: LitStr = meta.input.parse()?;
+            domain.name = Some(litstr.value());
+        }
+        "version", domain.version => {
+            meta.input.parse::<Token![=]>()?;
+            let litstr: LitStr = meta.input.parse()?;
+            domain.version = Some(litstr.value());
+        }
+        "chain_id", domain.chain_id => {
+            meta.input.parse::<Token![=]>()?;
+            let litint: LitInt = meta.input.parse()?;
+            let n: u64 = litint.base10_parse()?;
+            domain.chain_id = Some(n.into());
+        }
+        "verifying_contract", domain.verifying_contract => {
+            meta.input.parse::<Token![=]>()?;
+            let litstr: LitStr = meta.input.parse()?;
+            let addr: Address =
+                litstr.value().parse().map_err(|e| Error::new(litstr.span(), e))?;
+            domain.verifying_contract = Some(addr);
+        }
+        "salt", domain.salt => {
+            meta.input.parse::<Token![=]>()?;
+            let litstr: LitStr = meta.input.parse()?;
+            let hash = keccak256(litstr.value());
+            domain.salt = Some(hash);
+        }
+    );
+    Ok(domain)
+}
+
+/// Returns a Vec of `(name, param_type)`
+fn parse_fields(input: &DeriveInput) -> Result<Vec<(String, ParamType)>> {
+    let data = match &input.data {
+        Data::Struct(s) => s,
+        Data::Enum(e) => {
+            return Err(Error::new(e.enum_token.span, "Eip712 is not derivable for enums"))
+        }
+        Data::Union(u) => {
+            return Err(Error::new(u.union_token.span, "Eip712 is not derivable for unions"))
+        }
+    };
+
+    let named_fields = match &data.fields {
+        Fields::Named(fields) => fields,
+        _ => return Err(Error::new(input.span(), "unnamed fields are not supported")),
+    };
+
+    let mut fields = Vec::with_capacity(named_fields.named.len());
+    for f in named_fields.named.iter() {
+        let field_name = f.ident.as_ref().unwrap().to_string().to_camel_case();
+        let field_type =
+            match f.attrs.iter().find(|a| a.path().segments.iter().any(|s| s.ident == "eip712")) {
+                // Found nested Eip712 Struct
+                // TODO: Implement custom
+                Some(a) => {
+                    return Err(Error::new(a.span(), "nested Eip712 struct are not yet supported"))
+                }
+                // Not a nested eip712 struct, return the field param type;
+                None => crate::utils::find_parameter_type(&f.ty)?,
+            };
+
+        fields.push((field_name, field_type));
+    }
+
+    Ok(fields)
+}
+
+/// Convert hash map of field names and types into a type hash corresponding to enc types;
+fn make_type_hash(primary_type: String, fields: &[(String, ParamType)]) -> [u8; 32] {
+    let mut sig = String::with_capacity(256);
+
+    sig.push_str(&primary_type);
+
+    sig.push('(');
+    for (i, (name, ty)) in fields.iter().enumerate() {
+        sig.push_str(&ty.to_string());
+        sig.push(' ');
+        sig.push_str(name);
+        if i < fields.len() - 1 {
+            sig.push(',');
+        }
+    }
+    sig.push(')');
+
+    keccak256(sig)
+}
+
+fn into_tokens(bytes: [u8; 32]) -> impl Iterator<Item = Literal> {
+    bytes.into_iter().map(Literal::u8_suffixed)
+}
diff --git a/ethers-contract/ethers-contract-derive/src/error.rs b/ethers-contract/ethers-contract-derive/src/error.rs
index b6f988ff..832b64e4 100644
--- a/ethers-contract/ethers-contract-derive/src/error.rs
+++ b/ethers-contract/ethers-contract-derive/src/error.rs
@@ -11,11 +11,10 @@ use syn::{parse::Error, DeriveInput};
 
 /// Generates the `EthError` trait support
 pub(crate) fn derive_eth_error_impl(input: DeriveInput) -> Result<TokenStream, Error> {
-    let attributes = parse_calllike_attributes(&input, "etherror")?;
+    let attributes = parse_calllike_attributes!(input, "etherror");
 
-    let error_name = attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
-
-    let mut error = if let Some((src, span)) = attributes.abi {
+    let error_name = attributes.name(&input.ident);
+    let mut error = if let Some((src, span)) = attributes.abi() {
         let raw_function_sig = src.trim_start_matches("error ").trim_start();
         // try to parse as solidity error
         match HumanReadableParser::parse_error(&src) {
diff --git a/ethers-contract/ethers-contract-derive/src/event.rs b/ethers-contract/ethers-contract-derive/src/event.rs
index f7876e65..7beac097 100644
--- a/ethers-contract/ethers-contract-derive/src/event.rs
+++ b/ethers-contract/ethers-contract-derive/src/event.rs
@@ -6,16 +6,12 @@ use ethers_core::{
     abi::{Event, EventExt, EventParam, HumanReadableParser},
     macros::{ethers_contract_crate, ethers_core_crate},
 };
-use hex::FromHex;
 use proc_macro2::{Span, TokenStream};
 use quote::quote;
-use syn::{
-    parse::Error, spanned::Spanned, AttrStyle, Data, DeriveInput, Field, Fields, Lit, Meta,
-    NestedMeta,
-};
+use syn::{spanned::Spanned, Data, DeriveInput, Error, Field, Fields, LitStr, Result, Token};
 
 /// Generates the `EthEvent` trait support
-pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream, Error> {
+pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream> {
     let name = &input.ident;
     let attributes = parse_event_attributes(&input)?;
 
@@ -64,7 +60,7 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream, E
 
     let (abi, event_sig) = (event.abi_signature(), event.signature());
 
-    let signature = if let Some((hash, _)) = attributes.signature_hash {
+    let signature = if let Some((hash, _)) = attributes.signature {
         utils::signature(&hash)
     } else {
         utils::signature(event_sig.as_bytes())
@@ -91,7 +87,7 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream, E
                 #abi.into()
             }
 
-            fn decode_log(log: &#ethers_core::abi::RawLog) -> ::std::result::Result<Self, #ethers_core::abi::Error> where Self: Sized {
+            fn decode_log(log: &#ethers_core::abi::RawLog) -> ::core::result::Result<Self, #ethers_core::abi::Error> where Self: Sized {
                 #decode_log_impl
             }
 
@@ -122,7 +118,7 @@ impl EventField {
     }
 }
 
-fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result<TokenStream, Error> {
+fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result<TokenStream> {
     let ethers_core = ethers_core_crate();
 
     let fields: Vec<_> = match input.data {
@@ -291,7 +287,7 @@ fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result<Tok
 }
 
 /// Determine the event's ABI by parsing the AST
-fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> {
+fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event> {
     let event = Event {
         name: input.ident.to_string(),
         inputs: utils::derive_abi_inputs_from_fields(input, "EthEvent")?
@@ -303,53 +299,18 @@ fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> {
     Ok(event)
 }
 
-fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool), Error> {
-    let mut indexed = false;
-    let mut topic_name = None;
-    for a in field.attrs.iter() {
-        if let AttrStyle::Outer = a.style {
-            if let Ok(Meta::List(meta)) = a.parse_meta() {
-                if meta.path.is_ident("ethevent") {
-                    for n in meta.nested.iter() {
-                        if let NestedMeta::Meta(meta) = n {
-                            match meta {
-                                Meta::Path(path) => {
-                                    if path.is_ident("indexed") {
-                                        indexed = true;
-                                    } else {
-                                        return Err(Error::new(
-                                            path.span(),
-                                            "unrecognized ethevent parameter",
-                                        ))
-                                    }
-                                }
-                                Meta::List(meta) => {
-                                    return Err(Error::new(
-                                        meta.path.span(),
-                                        "unrecognized ethevent parameter",
-                                    ))
-                                }
-                                Meta::NameValue(meta) => {
-                                    if meta.path.is_ident("name") {
-                                        if let Lit::Str(ref lit_str) = meta.lit {
-                                            topic_name = Some(lit_str.value());
-                                        } else {
-                                            return Err(Error::new(
-                                                meta.span(),
-                                                "name attribute must be a string",
-                                            ))
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
+fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool)> {
+    let mut indexed = None::<bool>;
+    let mut topic_name = None::<String>;
+    utils::parse_attributes!(field.attrs.iter(), "ethevent", meta,
+        "indexed", indexed => { indexed = Some(true) }
+        "name", topic_name => {
+            meta.input.parse::<Token![=]>()?;
+            let litstr: LitStr = meta.input.parse()?;
+            topic_name = Some(litstr.value());
         }
-    }
-
-    Ok((topic_name, indexed))
+    );
+    Ok((topic_name, indexed.unwrap_or_default()))
 }
 
 /// All the attributes the `EthEvent` macro supports
@@ -357,139 +318,32 @@ fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool), Error
 struct EthEventAttributes {
     name: Option<(String, Span)>,
     abi: Option<(String, Span)>,
-    signature_hash: Option<(Vec<u8>, Span)>,
+    signature: Option<(Vec<u8>, Span)>,
     anonymous: Option<(bool, Span)>,
 }
 
 /// extracts the attributes from the struct annotated with `EthEvent`
-fn parse_event_attributes(input: &DeriveInput) -> Result<EthEventAttributes, Error> {
+fn parse_event_attributes(input: &DeriveInput) -> Result<EthEventAttributes> {
     let mut result = EthEventAttributes::default();
-    for a in input.attrs.iter() {
-        if let AttrStyle::Outer = a.style {
-            if let Ok(Meta::List(meta)) = a.parse_meta() {
-                if meta.path.is_ident("ethevent") {
-                    for n in meta.nested.iter() {
-                        if let NestedMeta::Meta(meta) = n {
-                            match meta {
-                                Meta::Path(path) => {
-                                    if let Some(name) = path.get_ident() {
-                                        if &*name.to_string() == "anonymous" {
-                                            if result.anonymous.is_none() {
-                                                result.anonymous = Some((true, name.span()));
-                                                continue
-                                            } else {
-                                                return Err(Error::new(
-                                                    name.span(),
-                                                    "anonymous already specified",
-                                                ))
-                                            }
-                                        }
-                                    }
-                                    return Err(Error::new(
-                                        path.span(),
-                                        "unrecognized ethevent parameter",
-                                    ))
-                                }
-                                Meta::List(meta) => {
-                                    return Err(Error::new(
-                                        meta.path.span(),
-                                        "unrecognized ethevent parameter",
-                                    ))
-                                }
-                                Meta::NameValue(meta) => {
-                                    if meta.path.is_ident("anonymous") {
-                                        if let Lit::Bool(ref bool_lit) = meta.lit {
-                                            if result.anonymous.is_none() {
-                                                result.anonymous =
-                                                    Some((bool_lit.value, bool_lit.span()));
-                                            } else {
-                                                return Err(Error::new(
-                                                    meta.span(),
-                                                    "anonymous already specified",
-                                                ))
-                                            }
-                                        } else {
-                                            return Err(Error::new(
-                                                meta.span(),
-                                                "name must be a string",
-                                            ))
-                                        }
-                                    } else if meta.path.is_ident("name") {
-                                        if let Lit::Str(ref lit_str) = meta.lit {
-                                            if result.name.is_none() {
-                                                result.name =
-                                                    Some((lit_str.value(), lit_str.span()));
-                                            } else {
-                                                return Err(Error::new(
-                                                    meta.span(),
-                                                    "name already specified",
-                                                ))
-                                            }
-                                        } else {
-                                            return Err(Error::new(
-                                                meta.span(),
-                                                "name must be a string",
-                                            ))
-                                        }
-                                    } else if meta.path.is_ident("abi") {
-                                        if let Lit::Str(ref lit_str) = meta.lit {
-                                            if result.abi.is_none() {
-                                                result.abi =
-                                                    Some((lit_str.value(), lit_str.span()));
-                                            } else {
-                                                return Err(Error::new(
-                                                    meta.span(),
-                                                    "abi already specified",
-                                                ))
-                                            }
-                                        } else {
-                                            return Err(Error::new(
-                                                meta.span(),
-                                                "abi must be a string",
-                                            ))
-                                        }
-                                    } else if meta.path.is_ident("signature") {
-                                        if let Lit::Str(ref lit_str) = meta.lit {
-                                            if result.signature_hash.is_none() {
-                                                match Vec::from_hex(lit_str.value()) {
-                                                    Ok(sig) => {
-                                                        result.signature_hash =
-                                                            Some((sig, lit_str.span()))
-                                                    }
-                                                    Err(err) => {
-                                                        return Err(Error::new(
-                                                            meta.span(),
-                                                            format!(
-                                                                "Expected hex signature: {err:?}"
-                                                            ),
-                                                        ))
-                                                    }
-                                                }
-                                            } else {
-                                                return Err(Error::new(
-                                                    meta.span(),
-                                                    "signature already specified",
-                                                ))
-                                            }
-                                        } else {
-                                            return Err(Error::new(
-                                                meta.span(),
-                                                "signature must be a hex string",
-                                            ))
-                                        }
-                                    } else {
-                                        return Err(Error::new(
-                                            meta.span(),
-                                            "unrecognized ethevent parameter",
-                                        ))
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
+    utils::parse_attributes!(input.attrs.iter(), "ethevent", meta,
+        "name", result.name => {
+            meta.input.parse::<Token![=]>()?;
+            let litstr: LitStr = meta.input.parse()?;
+            result.name = Some((litstr.value(), litstr.span()));
         }
-    }
+        "abi", result.abi => {
+            meta.input.parse::<Token![=]>()?;
+            let litstr: LitStr = meta.input.parse()?;
+            result.abi = Some((litstr.value(), litstr.span()));
+        }
+        "signature", result.signature => {
+            meta.input.parse::<Token![=]>()?;
+            let litstr: LitStr = meta.input.parse()?;
+            let s = litstr.value();
+            let b = hex::decode(s.strip_prefix("0x").unwrap_or(&s)).map_err(|e| meta.error(e))?;
+            result.signature = Some((b, litstr.span()));
+        }
+        "anonymous", result.anonymous => { result.anonymous = Some((true, meta.path.span())); }
+    );
     Ok(result)
 }
diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs
index 3013f9d3..d259c12f 100644
--- a/ethers-contract/ethers-contract-derive/src/lib.rs
+++ b/ethers-contract/ethers-contract-derive/src/lib.rs
@@ -14,6 +14,7 @@ mod call;
 pub(crate) mod calllike;
 mod codec;
 mod display;
+mod eip712;
 mod error;
 mod event;
 mod spanned;
@@ -141,6 +142,10 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
 /// generalized codec traits used for types, calls, etc. However, encoding/decoding a call differs
 /// from the basic encoding/decoding, (`[selector + encode(self)]`)
 ///
+/// Note that this macro requires the `EthAbiType` macro to be derived or for the type to implement
+/// `AbiType` and `Tokenizable`. The type returned by the `AbiType` implementation must be a
+/// `Token::Tuple`, otherwise this macro's implementation of `AbiDecode` will panic at runtime.
+///
 /// # Example
 ///
 /// ```ignore
@@ -358,3 +363,75 @@ pub fn derive_abi_error(input: TokenStream) -> TokenStream {
     }
     .into()
 }
+
+/// EIP-712 derive macro.
+///
+/// This crate provides a derive macro `Eip712` that is used to encode a rust struct
+/// into a payload hash, according to <https://eips.ethereum.org/EIPS/eip-712>
+///
+/// The trait used to derive the macro is found in `ethers_core::transaction::eip712::Eip712`
+/// Both the derive macro and the trait must be in context when using
+///
+/// This derive macro requires the `#[eip712]` attributes to be included
+/// for specifying the domain separator used in encoding the hash.
+///
+/// NOTE: In addition to deriving `Eip712` trait, the `EthAbiType` trait must also be derived.
+/// This allows the struct to be parsed into `ethers_core::abi::Token` for encoding.
+///
+/// # Optional Eip712 Parameters
+///
+/// The only optional parameter is `salt`, which accepts a string
+/// that is hashed using keccak256 and stored as bytes.
+///
+/// # Example Usage
+///
+/// ```ignore
+/// use ethers_contract::EthAbiType;
+/// use ethers_derive_eip712::*;
+/// use ethers_core::types::{transaction::eip712::Eip712, H160};
+///
+/// #[derive(Debug, Eip712, EthAbiType)]
+/// #[eip712(
+///     name = "Radicle",
+///     version = "1",
+///     chain_id = 1,
+///     verifying_contract = "0x0000000000000000000000000000000000000000"
+///     // salt is an optional parameter
+///     salt = "my-unique-spice"
+/// )]
+/// pub struct Puzzle {
+///     pub organization: H160,
+///     pub contributor: H160,
+///     pub commit: String,
+///     pub project: String,
+/// }
+///
+/// let puzzle = Puzzle {
+///     organization: "0000000000000000000000000000000000000000"
+///         .parse::<H160>()
+///         .expect("failed to parse address"),
+///     contributor: "0000000000000000000000000000000000000000"
+///         .parse::<H160>()
+///         .expect("failed to parse address"),
+///     commit: "5693b7019eb3e4487a81273c6f5e1832d77acb53".to_string(),
+///     project: "radicle-reward".to_string(),
+/// };
+///
+/// let hash = puzzle.encode_eip712().unwrap();
+/// ```
+///
+/// # Limitations
+///
+/// At the moment, the derive macro does not recursively encode nested Eip712 structs.
+///
+/// There is an Inner helper attribute `#[eip712]` for fields that will eventually be used to
+/// determine if there is a nested eip712 struct. However, this work is not yet complete.
+#[proc_macro_derive(Eip712, attributes(eip712))]
+pub fn derive_eip712(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    match eip712::impl_derive_eip712(&input) {
+        Ok(tokens) => tokens,
+        Err(e) => e.to_compile_error(),
+    }
+    .into()
+}
diff --git a/ethers-contract/ethers-contract-derive/src/utils.rs b/ethers-contract/ethers-contract-derive/src/utils.rs
index 33bb2a40..dfb49513 100644
--- a/ethers-contract/ethers-contract-derive/src/utils.rs
+++ b/ethers-contract/ethers-contract-derive/src/utils.rs
@@ -6,6 +6,35 @@ use syn::{
     PathArguments, Type,
 };
 
+/// Parses the specified attributes from a `syn::Attribute` iterator.
+macro_rules! parse_attributes {
+    ($attrs:expr, $attr_ident:literal, $meta:ident, $($field:pat, $opt:expr => $block:block)*) => {
+        const ERROR: &str = concat!("unrecognized ", $attr_ident, " attribute");
+        const ALREADY_SPECIFIED: &str = concat!($attr_ident, " attribute already specified");
+
+        for attr in $attrs {
+            if !attr.path().is_ident($attr_ident) {
+                continue;
+            }
+
+            attr.parse_nested_meta(|$meta| {
+                let ident = $meta.path.get_ident().ok_or_else(|| $meta.error(ERROR))?.to_string();
+                match ident.as_str() {
+                    $(
+                        $field if $opt.is_none() => $block,
+                        $field => return Err($meta.error(ALREADY_SPECIFIED)),
+                    )*
+
+                    _ => return Err($meta.error(ERROR)),
+                }
+
+                Ok(())
+            })?;
+        }
+    };
+}
+pub(crate) use parse_attributes;
+
 pub fn ident(name: &str) -> Ident {
     Ident::new(name, Span::call_site())
 }
@@ -104,17 +133,22 @@ pub fn param_type_quote(kind: &ParamType) -> TokenStream {
 /// Tries to find the corresponding `ParamType` used for tokenization for the
 /// given type
 pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
+    const ERROR: &str = "Failed to derive proper ABI from array field";
+
     match ty {
         Type::Array(arr) => {
             let ty = find_parameter_type(&arr.elem)?;
             if let Expr::Lit(ref expr) = arr.len {
                 if let Lit::Int(ref len) = expr.lit {
-                    if let Ok(size) = len.base10_parse::<usize>() {
-                        return Ok(ParamType::FixedArray(Box::new(ty), size))
+                    if let Ok(len) = len.base10_parse::<usize>() {
+                        return match (ty, len) {
+                            (ParamType::Uint(8), 32) => Ok(ParamType::FixedBytes(32)),
+                            (ty, len) => Ok(ParamType::FixedArray(Box::new(ty), len)),
+                        }
                     }
                 }
             }
-            Err(Error::new(arr.span(), "Failed to derive proper ABI from array field"))
+            Err(Error::new(arr.span(), ERROR))
         }
 
         Type::Path(ty) => {
@@ -151,7 +185,7 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
                         s => parse_param_type(s),
                     }
                 })
-                .ok_or_else(|| Error::new(ty.span(), "Failed to derive proper ABI from fields"))
+                .ok_or_else(|| Error::new(ty.span(), ERROR))
         }
 
         Type::Tuple(ty) => ty
@@ -161,7 +195,7 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
             .collect::<Result<Vec<_>, _>>()
             .map(ParamType::Tuple),
 
-        _ => Err(Error::new(ty.span(), "Failed to derive proper ABI from fields")),
+        _ => Err(Error::new(ty.span(), ERROR)),
     }
 }
 
diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs
index 88f8df43..1d8b6213 100644
--- a/ethers-contract/src/lib.rs
+++ b/ethers-contract/src/lib.rs
@@ -1,4 +1,3 @@
-#![cfg_attr(docsrs, feature(doc_cfg))]
 #![doc = include_str!("../README.md")]
 #![deny(unsafe_code)]
 #![warn(missing_docs)]
@@ -60,16 +59,13 @@ pub use ethers_contract_abigen::{
 #[cfg(any(test, feature = "abigen"))]
 #[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
 pub use ethers_contract_derive::{
-    abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent,
+    abigen, Eip712, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent,
 };
 
 // Hide the Lazy re-export, it's just for convenience
 #[doc(hidden)]
 pub use once_cell::sync::Lazy;
 
-#[cfg(feature = "eip712")]
-pub use ethers_derive_eip712::*;
-
 // For macro expansions only, not public API.
 // See: [#2235](https://github.com/gakonst/ethers-rs/pull/2235)
 
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index fd5211ec..03cc9d41 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -47,15 +47,11 @@ hex.workspace = true
 once_cell = { workspace = true, optional = true }
 unicode-xid = "0.2"
 strum = { version = "0.24", features = ["derive"] }
-num_enum = "0.5"
+num_enum = "0.6"
 
 # macros feature enabled dependencies
-cargo_metadata = { version = "0.15.3", optional = true }
-
-# eip712 feature enabled dependencies
-convert_case = { version = "0.6.0", optional = true }
+cargo_metadata = { version = "0.15.4", optional = true }
 syn = { workspace = true, optional = true }
-proc-macro2 = { workspace = true, optional = true }
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
 tempfile.workspace = true
@@ -73,5 +69,7 @@ rand.workspace = true
 [features]
 celo = ["legacy"] # celo support extends the transaction format with extra fields
 legacy = []
-eip712 = ["convert_case", "syn", "proc-macro2"]
 macros = ["syn", "cargo_metadata", "once_cell"]
+
+# Deprecated
+eip712 = []
diff --git a/ethers-core/ethers-derive-eip712/Cargo.toml b/ethers-core/ethers-derive-eip712/Cargo.toml
deleted file mode 100644
index 5f2e320a..00000000
--- a/ethers-core/ethers-derive-eip712/Cargo.toml
+++ /dev/null
@@ -1,29 +0,0 @@
-[package]
-name = "ethers-derive-eip712"
-authors = ["Ryan Tate <ryan.michael.tate@gmail.com>"]
-description = "Derive procedural macro for EIP-712 typed data"
-
-version.workspace = true
-edition.workspace = true
-rust-version.workspace = true
-license.workspace = true
-documentation.workspace = true
-repository.workspace = true
-homepage.workspace = true
-categories.workspace = true
-keywords.workspace = true
-
-[lib]
-proc-macro = true
-
-[dependencies]
-ethers-core = { workspace = true, features = ["eip712", "macros"] }
-
-quote.workspace = true
-syn.workspace = true
-
-hex.workspace = true
-serde_json.workspace = true
-
-[dev-dependencies]
-ethers-contract-derive.workspace = true
diff --git a/ethers-core/ethers-derive-eip712/src/lib.rs b/ethers-core/ethers-derive-eip712/src/lib.rs
deleted file mode 100644
index e1c1272a..00000000
--- a/ethers-core/ethers-derive-eip712/src/lib.rs
+++ /dev/null
@@ -1,172 +0,0 @@
-//! # EIP-712 Derive Macro
-//!
-//! This crate provides a derive macro `Eip712` that is used to encode a rust struct
-//! into a payload hash, according to [https://eips.ethereum.org/EIPS/eip-712](https://eips.ethereum.org/EIPS/eip-712)
-//!
-//! The trait used to derive the macro is found in `ethers_core::transaction::eip712::Eip712`
-//! Both the derive macro and the trait must be in context when using
-//!
-//! This derive macro requires the `#[eip712]` attributes to be included
-//! for specifying the domain separator used in encoding the hash.
-//!
-//! NOTE: In addition to deriving `Eip712` trait, the `EthAbiType` trait must also be derived.
-//! This allows the struct to be parsed into `ethers_core::abi::Token` for encoding.
-//!
-//! # Optional Eip712 Parameters
-//!
-//! The only optional parameter is `salt`, which accepts a string
-//! that is hashed using keccak256 and stored as bytes.
-//!
-//! # Example Usage
-//!
-//! ```ignore
-//! use ethers_contract::EthAbiType;
-//! use ethers_derive_eip712::*;
-//! use ethers_core::types::{transaction::eip712::Eip712, H160};
-//!
-//! #[derive(Debug, Eip712, EthAbiType)]
-//! #[eip712(
-//!     name = "Radicle",
-//!     version = "1",
-//!     chain_id = 1,
-//!     verifying_contract = "0x0000000000000000000000000000000000000000"
-//!     // salt is an optional parameter
-//!     salt = "my-unique-spice"
-//! )]
-//! pub struct Puzzle {
-//!     pub organization: H160,
-//!     pub contributor: H160,
-//!     pub commit: String,
-//!     pub project: String,
-//! }
-//!
-//! let puzzle = Puzzle {
-//!     organization: "0000000000000000000000000000000000000000"
-//!         .parse::<H160>()
-//!         .expect("failed to parse address"),
-//!     contributor: "0000000000000000000000000000000000000000"
-//!         .parse::<H160>()
-//!         .expect("failed to parse address"),
-//!     commit: "5693b7019eb3e4487a81273c6f5e1832d77acb53".to_string(),
-//!     project: "radicle-reward".to_string(),
-//! };
-//!
-//! let hash = puzzle.encode_eip712().unwrap();
-//! ```
-//!
-//! # Limitations
-//!
-//! At the moment, the derive macro does not recursively encode nested Eip712 structs.
-//!
-//! There is an Inner helper attribute `#[eip712]` for fields that will eventually be used to
-//! determine if there is a nested eip712 struct. However, this work is not yet complete.
-
-#![deny(missing_docs, unsafe_code, rustdoc::broken_intra_doc_links)]
-use ethers_core::{macros::ethers_core_crate, types::transaction::eip712};
-use proc_macro::TokenStream;
-use quote::quote;
-use std::convert::TryFrom;
-use syn::parse_macro_input;
-
-/// Derive macro for `Eip712`
-#[proc_macro_derive(Eip712, attributes(eip712))]
-pub fn eip_712_derive(input: TokenStream) -> TokenStream {
-    let ast = parse_macro_input!(input);
-
-    impl_eip_712_macro(&ast)
-}
-
-// Main implementation macro, used to compute static values and define
-// method for encoding the final eip712 payload;
-fn impl_eip_712_macro(ast: &syn::DeriveInput) -> TokenStream {
-    // Primary type should match the type in the ethereum verifying contract;
-    let primary_type = &ast.ident;
-
-    // Instantiate domain from parsed attributes
-    let domain = match eip712::EIP712Domain::try_from(ast) {
-        Ok(attributes) => attributes,
-        Err(e) => return TokenStream::from(e),
-    };
-
-    let domain_separator = hex::encode(domain.separator());
-
-    //
-    let domain_str = match serde_json::to_string(&domain) {
-        Ok(s) => s,
-        Err(e) => {
-            return TokenStream::from(
-                syn::Error::new(ast.ident.span(), e.to_string()).to_compile_error(),
-            )
-        }
-    };
-
-    // Must parse the AST at compile time.
-    let parsed_fields = match eip712::parse_fields(ast) {
-        Ok(fields) => fields,
-        Err(e) => return TokenStream::from(e),
-    };
-
-    // Compute the type hash for the derived struct using the parsed fields from above.
-    let type_hash =
-        hex::encode(eip712::make_type_hash(primary_type.clone().to_string(), &parsed_fields));
-
-    // Use reference to ethers_core instead of directly using the crate itself.
-    let ethers_core = ethers_core_crate();
-
-    let implementation = quote! {
-        impl Eip712 for #primary_type {
-            type Error = #ethers_core::types::transaction::eip712::Eip712Error;
-
-            fn type_hash() -> Result<[u8; 32], Self::Error> {
-                use std::convert::TryFrom;
-                let decoded = #ethers_core::utils::hex::decode(#type_hash)?;
-                let byte_array: [u8; 32] = <[u8; 32]>::try_from(&decoded[..])?;
-                Ok(byte_array)
-            }
-
-            // Return the pre-computed domain separator from compile time;
-            fn domain_separator(&self) -> Result<[u8; 32], Self::Error> {
-                use std::convert::TryFrom;
-                let decoded = #ethers_core::utils::hex::decode(#domain_separator)?;
-                let byte_array: [u8; 32] = <[u8; 32]>::try_from(&decoded[..])?;
-                Ok(byte_array)
-            }
-
-            fn domain(&self) -> Result<#ethers_core::types::transaction::eip712::EIP712Domain, Self::Error> {
-                let domain: #ethers_core::types::transaction::eip712::EIP712Domain = # ethers_core::utils::__serde_json::from_str(#domain_str)?;
-
-                Ok(domain)
-            }
-
-            fn struct_hash(&self) -> Result<[u8; 32], Self::Error> {
-                use #ethers_core::abi::Tokenizable;
-                let mut items = vec![#ethers_core::abi::Token::Uint(
-                    #ethers_core::types::U256::from(&Self::type_hash()?[..]),
-                )];
-
-                if let #ethers_core::abi::Token::Tuple(tokens) = self.clone().into_token() {
-                    for token in tokens {
-                        match &token {
-                            #ethers_core::abi::Token::Tuple(t) => {
-                                // TODO: check for nested Eip712 Type;
-                                // Challenge is determining the type hash
-                                return Err(Self::Error::NestedEip712StructNotImplemented);
-                            },
-                            _ => {
-                                items.push(#ethers_core::types::transaction::eip712::encode_eip712_type(token));
-                            }
-                        }
-                    }
-                }
-
-                let struct_hash = #ethers_core::utils::keccak256(#ethers_core::abi::encode(
-                    &items,
-                ));
-
-                Ok(struct_hash)
-            }
-        }
-    };
-
-    implementation.into()
-}
diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs
index f777d89a..dfb6c512 100644
--- a/ethers-core/src/abi/mod.rs
+++ b/ethers-core/src/abi/mod.rs
@@ -108,10 +108,32 @@ impl ErrorExt for ethabi::AbiError {
     }
 }
 
-/// A trait for types that can be represented in the ethereum ABI.
+/// A trait for types that can be represented in the Ethereum ABI.
 pub trait AbiType {
     /// The native ABI type this type represents.
     fn param_type() -> ParamType;
+
+    /// A hint of the minimum number of bytes this type takes up in the ABI.
+    fn minimum_size() -> usize {
+        minimum_size(&Self::param_type())
+    }
+}
+
+/// Returns the minimum number of bytes that `ty` takes up in the ABI.
+pub fn minimum_size(ty: &ParamType) -> usize {
+    match ty {
+        // 1 word
+        ParamType::Uint(_) |
+        ParamType::Int(_) |
+        ParamType::Bool |
+        ParamType::Address |
+        ParamType::FixedBytes(_) => 32,
+        // min 2 words (offset, length)
+        ParamType::Bytes | ParamType::String | ParamType::Array(_) => 64,
+        // sum of all elements
+        ParamType::FixedArray(ty, len) => minimum_size(ty) * len,
+        ParamType::Tuple(tys) => tys.iter().map(minimum_size).sum(),
+    }
 }
 
 impl AbiType for u8 {
diff --git a/ethers-core/src/abi/tokens.rs b/ethers-core/src/abi/tokens.rs
index 38d1a284..33b0034d 100644
--- a/ethers-core/src/abi/tokens.rs
+++ b/ethers-core/src/abi/tokens.rs
@@ -84,15 +84,21 @@ macro_rules! impl_tuples {
         {
             fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
                 match token {
-                    Token::Tuple(mut tokens) => {
-                        let mut it = tokens.drain(..);
-                        Ok(($(
-                            $ty::from_token(it.next().expect("All elements are in vector; qed"))?,
-                        )+))
+                    Token::Tuple(tokens) if tokens.len() == $num => {
+                        let mut it = tokens.into_iter();
+                        // SAFETY: length checked above
+                        unsafe {
+                            Ok(($(
+                                <$ty as Tokenizable>::from_token(it.next().unwrap_unchecked())?,
+                            )+))
+                        }
                     },
                     other => Err(InvalidOutputType(format!(
-                        "Expected `Tuple` of length {}, got {:?}",
-                        $num,
+                        concat!(
+                            "Expected `Tuple` of length ",
+                            stringify!($num),
+                            ", got {:?}",
+                        ),
                         other,
                     ))),
                 }
diff --git a/ethers-core/src/lib.rs b/ethers-core/src/lib.rs
index 4a087fa7..f99afc66 100644
--- a/ethers-core/src/lib.rs
+++ b/ethers-core/src/lib.rs
@@ -1,4 +1,3 @@
-#![cfg_attr(docsrs, feature(doc_cfg))]
 #![doc = include_str!("../README.md")]
 #![deny(rustdoc::broken_intra_doc_links)]
 #![cfg_attr(not(target_arch = "wasm32"), deny(unused_crate_dependencies))]
diff --git a/ethers-core/src/macros/ethers_crate.rs b/ethers-core/src/macros/ethers_crate.rs
index 83b20b5c..f30d2fe5 100644
--- a/ethers-core/src/macros/ethers_crate.rs
+++ b/ethers-core/src/macros/ethers_crate.rs
@@ -220,7 +220,6 @@ pub enum EthersCrate {
     EthersContractAbigen,
     EthersContractDerive,
     EthersCore,
-    EthersDeriveEip712,
     EthersEtherscan,
     EthersMiddleware,
     EthersProviders,
@@ -250,7 +249,6 @@ impl EthersCrate {
             Self::EthersContractAbigen => "ethers-contract-abigen",
             Self::EthersContractDerive => "ethers-contract-derive",
             Self::EthersCore => "ethers-core",
-            Self::EthersDeriveEip712 => "ethers-derive-eip712",
             Self::EthersEtherscan => "ethers-etherscan",
             Self::EthersMiddleware => "ethers-middleware",
             Self::EthersProviders => "ethers-providers",
@@ -268,7 +266,6 @@ impl EthersCrate {
             Self::EthersContractAbigen => "::ethers_contract_abigen",
             Self::EthersContractDerive => "::ethers_contract_derive",
             Self::EthersCore => "::ethers_core",
-            Self::EthersDeriveEip712 => "::ethers_derive_eip712",
             Self::EthersEtherscan => "::ethers_etherscan",
             Self::EthersMiddleware => "::ethers_middleware",
             Self::EthersProviders => "::ethers_providers",
@@ -284,7 +281,6 @@ impl EthersCrate {
             // re-exported in ethers::contract
             Self::EthersContractAbigen => "::ethers::contract", // partially
             Self::EthersContractDerive => "::ethers::contract",
-            Self::EthersDeriveEip712 => "::ethers::contract",
 
             Self::EthersAddressbook => "::ethers::addressbook",
             Self::EthersContract => "::ethers::contract",
@@ -303,7 +299,6 @@ impl EthersCrate {
         match self {
             Self::EthersContractAbigen => "ethers-contract/ethers-contract-abigen",
             Self::EthersContractDerive => "ethers-contract/ethers-contract-derive",
-            Self::EthersDeriveEip712 => "ethers-core/ethers-derive-eip712",
             _ => self.crate_name(),
         }
     }
diff --git a/ethers-core/src/types/chain.rs b/ethers-core/src/types/chain.rs
index 76bc50f3..60138ae8 100644
--- a/ethers-core/src/types/chain.rs
+++ b/ethers-core/src/types/chain.rs
@@ -321,13 +321,13 @@ impl Chain {
             PolygonMumbai |
             Avalanche |
             AvalancheFuji |
+            FilecoinMainnet |
             FilecoinHyperspaceTestnet => false,
 
             // Unknown / not applicable, default to false for backwards compatibility
             Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan |
             Sokol | Poa | XDai | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos |
-            EvmosTestnet | Chiado | Aurora | AuroraTestnet | Canto | CantoTestnet |
-            FilecoinMainnet => false,
+            EvmosTestnet | Chiado | Aurora | AuroraTestnet | Canto | CantoTestnet => false,
         }
     }
 
diff --git a/ethers-core/src/types/transaction/eip712.rs b/ethers-core/src/types/transaction/eip712.rs
index b608652a..d3df08a8 100644
--- a/ethers-core/src/types/transaction/eip712.rs
+++ b/ethers-core/src/types/transaction/eip712.rs
@@ -4,43 +4,35 @@ use crate::{
     types::{serde_helpers::StringifiedNumeric, Address, Bytes, U256},
     utils::keccak256,
 };
-use convert_case::{Case, Casing};
-use core::convert::TryFrom;
 use ethabi::encode;
-use proc_macro2::TokenStream;
 use serde::{Deserialize, Deserializer, Serialize};
 use std::{
     collections::{BTreeMap, HashSet},
-    convert::TryInto,
     iter::FromIterator,
 };
-use syn::{
-    parse::Error, spanned::Spanned as _, AttrStyle, Data, DeriveInput, Expr, Fields,
-    GenericArgument, Lit, NestedMeta, PathArguments, Type,
-};
 
 /// Custom types for `TypedData`
 pub type Types = BTreeMap<String, Vec<Eip712DomainType>>;
 
-/// Pre-computed value of the following statement:
+/// Pre-computed value of the following expression:
 ///
-/// `ethers_core::utils::keccak256("EIP712Domain(string name,string version,uint256 chainId,address
+/// `keccak256("EIP712Domain(string name,string version,uint256 chainId,address
 /// verifyingContract)")`
 pub const EIP712_DOMAIN_TYPE_HASH: [u8; 32] = [
     139, 115, 195, 198, 155, 184, 254, 61, 81, 46, 204, 76, 247, 89, 204, 121, 35, 159, 123, 23,
     155, 15, 250, 202, 169, 167, 93, 82, 43, 57, 64, 15,
 ];
 
-/// Pre-computed value of the following statement:
+/// Pre-computed value of the following expression:
 ///
-/// `ethers_core::utils::keccak256("EIP712Domain(string name,string version,uint256 chainId,address
+/// `keccak256("EIP712Domain(string name,string version,uint256 chainId,address
 /// verifyingContract,bytes32 salt)")`
 pub const EIP712_DOMAIN_TYPE_HASH_WITH_SALT: [u8; 32] = [
     216, 124, 214, 239, 121, 212, 226, 185, 94, 21, 206, 138, 191, 115, 45, 181, 30, 199, 113, 241,
     202, 46, 220, 207, 34, 164, 108, 114, 154, 197, 100, 114,
 ];
 
-/// Error typed used by Eip712 derive macro
+/// An EIP-712 error.
 #[derive(Debug, thiserror::Error)]
 pub enum Eip712Error {
     #[error("Failed to serialize serde JSON object")]
@@ -57,8 +49,7 @@ pub enum Eip712Error {
     Message(String),
 }
 
-/// The Eip712 trait provides helper methods for computing
-/// the typed data hash used in `eth_signTypedData`.
+/// Helper methods for computing the typed data hash used in `eth_signTypedData`.
 ///
 /// The ethers-rs `derive_eip712` crate provides a derive macro to
 /// implement the trait for a given struct. See documentation
@@ -245,233 +236,40 @@ impl<T: Eip712 + Clone> Eip712 for EIP712WithDomain<T> {
     }
 }
 
-// Parse the AST of the struct to determine the domain attributes
-impl TryFrom<&syn::DeriveInput> for EIP712Domain {
-    type Error = TokenStream;
-    fn try_from(input: &syn::DeriveInput) -> Result<EIP712Domain, Self::Error> {
-        let mut domain = EIP712Domain::default();
-
-        let mut found_eip712_attribute = false;
-
-        'attribute_search: for attribute in input.attrs.iter() {
-            if let AttrStyle::Outer = attribute.style {
-                if let Ok(syn::Meta::List(meta)) = attribute.parse_meta() {
-                    if meta.path.is_ident("eip712") {
-                        found_eip712_attribute = true;
-
-                        for n in meta.nested.iter() {
-                            if let NestedMeta::Meta(meta) = n {
-                                match meta {
-                                    syn::Meta::NameValue(meta) => {
-                                        let ident = meta.path.get_ident().ok_or_else(|| {
-                                            Error::new(
-                                                meta.path.span(),
-                                                "unrecognized eip712 parameter",
-                                            )
-                                            .to_compile_error()
-                                        })?;
-
-                                        match ident.to_string().as_ref() {
-                                            "name" => match meta.lit {
-                                                syn::Lit::Str(ref lit_str) => {
-                                                    if domain.name.is_some() {
-                                                        return Err(Error::new(
-                                                            meta.path.span(),
-                                                            "domain name already specified",
-                                                        )
-                                                        .to_compile_error())
-                                                    }
-
-                                                    domain.name = Some(lit_str.value());
-                                                }
-                                                _ => {
-                                                    return Err(Error::new(
-                                                        meta.path.span(),
-                                                        "domain name must be a string",
-                                                    )
-                                                    .to_compile_error())
-                                                }
-                                            },
-                                            "version" => match meta.lit {
-                                                syn::Lit::Str(ref lit_str) => {
-                                                    if domain.version.is_some() {
-                                                        return Err(Error::new(
-                                                            meta.path.span(),
-                                                            "domain version already specified",
-                                                        )
-                                                        .to_compile_error())
-                                                    }
-
-                                                    domain.version = Some(lit_str.value());
-                                                }
-                                                _ => {
-                                                    return Err(Error::new(
-                                                        meta.path.span(),
-                                                        "domain version must be a string",
-                                                    )
-                                                    .to_compile_error())
-                                                }
-                                            },
-                                            "chain_id" => match meta.lit {
-                                                syn::Lit::Int(ref lit_int) => {
-                                                    if domain.chain_id.is_some() {
-                                                        return Err(Error::new(
-                                                            meta.path.span(),
-                                                            "domain chain_id already specified",
-                                                        )
-                                                        .to_compile_error())
-                                                    }
-
-                                                    domain.chain_id = Some(U256::from(
-                                                        lit_int.base10_parse::<u64>().map_err(
-                                                            |_| {
-                                                                Error::new(
-                                                                    meta.path.span(),
-                                                                    "failed to parse chain id",
-                                                                )
-                                                                .to_compile_error()
-                                                            },
-                                                        )?,
-                                                    ));
-                                                }
-                                                _ => {
-                                                    return Err(Error::new(
-                                                        meta.path.span(),
-                                                        "domain chain_id must be a positive integer",
-                                                    )
-                                                    .to_compile_error());
-                                                }
-                                            },
-                                            "verifying_contract" => match meta.lit {
-                                                syn::Lit::Str(ref lit_str) => {
-                                                    if domain.verifying_contract.is_some() {
-                                                        return Err(Error::new(
-                                                            meta.path.span(),
-                                                            "domain verifying_contract already specified",
-                                                        )
-                                                        .to_compile_error());
-                                                    }
-
-                                                    domain.verifying_contract = Some(lit_str.value().parse().map_err(|_| {
-                                                            Error::new(
-                                                                meta.path.span(),
-                                                                "failed to parse verifying contract into Address",
-                                                            )
-                                                            .to_compile_error()
-                                                        })?);
-                                                }
-                                                _ => {
-                                                    return Err(Error::new(
-                                                        meta.path.span(),
-                                                        "domain verifying_contract must be a string",
-                                                    )
-                                                    .to_compile_error());
-                                                }
-                                            },
-                                            "salt" => match meta.lit {
-                                                syn::Lit::Str(ref lit_str) => {
-                                                    if domain.salt.is_some() {
-                                                        return Err(Error::new(
-                                                            meta.path.span(),
-                                                            "domain salt already specified",
-                                                        )
-                                                        .to_compile_error())
-                                                    }
-
-                                                    // keccak256(<string>) to compute bytes32
-                                                    // encoded domain salt
-                                                    let salt = keccak256(lit_str.value());
-
-                                                    domain.salt = Some(salt);
-                                                }
-                                                _ => {
-                                                    return Err(Error::new(
-                                                        meta.path.span(),
-                                                        "domain salt must be a string",
-                                                    )
-                                                    .to_compile_error())
-                                                }
-                                            },
-                                            _ => {
-                                                return Err(Error::new(
-                                                    meta.path.span(),
-                                                    "unrecognized eip712 parameter; must be one of 'name', 'version', 'chain_id', or 'verifying_contract'",
-                                                )
-                                                .to_compile_error());
-                                            }
-                                        }
-                                    }
-                                    syn::Meta::Path(path) => {
-                                        return Err(Error::new(
-                                            path.span(),
-                                            "unrecognized eip712 parameter",
-                                        )
-                                        .to_compile_error())
-                                    }
-                                    syn::Meta::List(meta) => {
-                                        return Err(Error::new(
-                                            meta.path.span(),
-                                            "unrecognized eip712 parameter",
-                                        )
-                                        .to_compile_error())
-                                    }
-                                }
-                            }
-                        }
-                    }
-
-                    break 'attribute_search
-                }
-            }
-        }
-
-        if !found_eip712_attribute {
-            return Err(Error::new_spanned(
-                input,
-                "missing required derive attribute: '#[eip712( ... )]'".to_string(),
-            )
-            .to_compile_error())
-        }
-
-        Ok(domain)
-    }
-}
-
 /// Represents the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data object.
 ///
 /// Typed data is a JSON object containing type information, domain separator parameters and the
 /// message object which has the following schema
 ///
-/// ```js
+/// ```json
 /// {
-//   type: 'object',
-//   properties: {
-//     types: {
-//       type: 'object',
-//       properties: {
-//         EIP712Domain: {type: 'array'},
-//       },
-//       additionalProperties: {
-//         type: 'array',
-//         items: {
-//           type: 'object',
-//           properties: {
-//             name: {type: 'string'},
-//             type: {type: 'string'}
-//           },
-//           required: ['name', 'type']
-//         }
-//       },
-//       required: ['EIP712Domain']
-//     },
-//     primaryType: {type: 'string'},
-//     domain: {type: 'object'},
-//     message: {type: 'object'}
-//   },
-//   required: ['types', 'primaryType', 'domain', 'message']
-// }
+///     "type": "object",
+///     "properties": {
+///         "types": {
+///             "type": "object",
+///             "properties": {
+///                 "EIP712Domain": { "type": "array" }
+///             },
+///             "additionalProperties": {
+///                 "type": "array",
+///                 "items": {
+///                     "type": "object",
+///                     "properties": {
+///                         "name": { "type": "string" },
+///                         "type": { "type": "string" }
+///                     },
+///                     "required": ["name", "type"]
+///                 }
+///             },
+///             "required": ["EIP712Domain"]
+///         },
+///         "primaryType": { "type": "string" },
+///         "domain": { "type": "object" },
+///         "message": { "type": "object" }
+///     },
+///     "required": ["types", "primaryType", "domain", "message"]
+/// }
 /// ```
-///
 #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
 #[serde(deny_unknown_fields)]
 pub struct TypedData {
@@ -772,137 +570,6 @@ pub fn encode_field(
     Ok(token)
 }
 
-/// Parse the eth abi parameter type based on the syntax type;
-/// this method is copied from <https://github.com/gakonst/ethers-rs/blob/master/ethers-contract/ethers-contract-derive/src/lib.rs#L600>
-/// with additional modifications for finding byte arrays
-pub fn find_parameter_type(ty: &Type) -> Result<ParamType, TokenStream> {
-    match ty {
-        Type::Array(ty) => {
-            let param = find_parameter_type(ty.elem.as_ref())?;
-            if let Expr::Lit(ref expr) = ty.len {
-                if let Lit::Int(ref len) = expr.lit {
-                    if let Ok(size) = len.base10_parse::<usize>() {
-                        if let ParamType::Uint(_) = param {
-                            return Ok(ParamType::FixedBytes(size))
-                        }
-
-                        return Ok(ParamType::FixedArray(Box::new(param), size))
-                    }
-                }
-            }
-            Err(Error::new(ty.span(), "Failed to derive proper ABI from array field")
-                .to_compile_error())
-        }
-        Type::Path(ty) => {
-            if let Some(ident) = ty.path.get_ident() {
-                let ident = ident.to_string().to_lowercase();
-                return match ident.as_str() {
-                    "address" => Ok(ParamType::Address),
-                    "string" => Ok(ParamType::String),
-                    "bool" => Ok(ParamType::Bool),
-                    "int256" | "int" | "uint" | "uint256" => Ok(ParamType::Uint(256)),
-                    "h160" => Ok(ParamType::FixedBytes(20)),
-                    "h256" | "secret" | "hash" => Ok(ParamType::FixedBytes(32)),
-                    "h512" | "public" => Ok(ParamType::FixedBytes(64)),
-                    "bytes" => Ok(ParamType::Bytes),
-                    s => parse_int_param_type(s).ok_or_else(|| {
-                        Error::new(
-                            ty.span(),
-                            format!("Failed to derive proper ABI from field: {s})"),
-                        )
-                        .to_compile_error()
-                    }),
-                }
-            }
-            // check for `Vec`
-            if ty.path.segments.len() == 1 && ty.path.segments[0].ident == "Vec" {
-                if let PathArguments::AngleBracketed(ref args) = ty.path.segments[0].arguments {
-                    if args.args.len() == 1 {
-                        if let GenericArgument::Type(ref ty) = args.args.iter().next().unwrap() {
-                            let kind = find_parameter_type(ty)?;
-
-                            // Check if byte array is found
-                            if let ParamType::Uint(size) = kind {
-                                if size == 8 {
-                                    return Ok(ParamType::Bytes)
-                                }
-                            }
-
-                            return Ok(ParamType::Array(Box::new(kind)))
-                        }
-                    }
-                }
-            }
-
-            Err(Error::new(ty.span(), "Failed to derive proper ABI from fields").to_compile_error())
-        }
-        Type::Tuple(ty) => {
-            let params = ty.elems.iter().map(find_parameter_type).collect::<Result<Vec<_>, _>>()?;
-            Ok(ParamType::Tuple(params))
-        }
-        _ => {
-            Err(Error::new(ty.span(), "Failed to derive proper ABI from fields").to_compile_error())
-        }
-    }
-}
-
-fn parse_int_param_type(s: &str) -> Option<ParamType> {
-    let size = s.chars().skip(1).collect::<String>().parse::<usize>().ok()?;
-    if s.starts_with('u') {
-        Some(ParamType::Uint(size))
-    } else if s.starts_with('i') {
-        Some(ParamType::Int(size))
-    } else {
-        None
-    }
-}
-
-/// Return HashMap of the field name and the field type;
-pub fn parse_fields(ast: &DeriveInput) -> Result<Vec<(String, ParamType)>, TokenStream> {
-    let mut fields = Vec::new();
-
-    let data = match &ast.data {
-        Data::Struct(s) => s,
-        _ => {
-            return Err(Error::new(
-                ast.span(),
-                "invalid data type. can only derive Eip712 for a struct",
-            )
-            .to_compile_error())
-        }
-    };
-
-    let named_fields = match &data.fields {
-        Fields::Named(name) => name,
-        _ => {
-            return Err(Error::new(ast.span(), "unnamed fields are not supported").to_compile_error())
-        }
-    };
-
-    for f in named_fields.named.iter() {
-        let field_name =
-            f.ident.clone().map(|i| i.to_string().to_case(Case::Camel)).ok_or_else(|| {
-                Error::new(named_fields.span(), "fields must be named").to_compile_error()
-            })?;
-
-        let field_type =
-            match f.attrs.iter().find(|a| a.path.segments.iter().any(|s| s.ident == "eip712")) {
-                // Found nested Eip712 Struct
-                // TODO: Implement custom
-                Some(a) => {
-                    return Err(Error::new(a.span(), "nested Eip712 struct are not yet supported")
-                        .to_compile_error())
-                }
-                // Not a nested eip712 struct, return the field param type;
-                None => find_parameter_type(&f.ty)?,
-            };
-
-        fields.push((field_name, field_type));
-    }
-
-    Ok(fields)
-}
-
 /// Convert hash map of field names and types into a type hash corresponding to enc types;
 pub fn make_type_hash(primary_type: String, fields: &[(String, ParamType)]) -> [u8; 32] {
     let parameters =
diff --git a/ethers-core/src/types/transaction/mod.rs b/ethers-core/src/types/transaction/mod.rs
index 816f4539..41c277cb 100644
--- a/ethers-core/src/types/transaction/mod.rs
+++ b/ethers-core/src/types/transaction/mod.rs
@@ -5,7 +5,6 @@ pub mod eip1559;
 pub mod eip2718;
 pub mod eip2930;
 
-#[cfg(feature = "eip712")]
 pub mod eip712;
 
 pub(crate) const BASE_NUM_TX_FIELDS: usize = 9;
diff --git a/ethers-core/src/types/transaction/response.rs b/ethers-core/src/types/transaction/response.rs
index 29852425..5ad0c93c 100644
--- a/ethers-core/src/types/transaction/response.rs
+++ b/ethers-core/src/types/transaction/response.rs
@@ -335,7 +335,7 @@ impl Transaction {
 /// Get a Transaction directly from a rlp encoded byte stream
 impl Decodable for Transaction {
     fn decode(rlp: &rlp::Rlp) -> Result<Self, DecoderError> {
-        let mut txn = Self::default();
+        let mut txn = Self { hash: H256(keccak256(rlp.as_raw())), ..Default::default() };
         // we can get the type from the first value
         let mut offset = 0;
 
@@ -881,6 +881,19 @@ mod tests {
         );
     }
 
+    // Reference tx hash on Ethereum mainnet:
+    // 0x938913ef1df8cd17e0893a85586ade463014559fb1bd2d536ac282f3b1bdea53
+    #[test]
+    fn decode_tx_assert_hash() {
+        let raw_tx = hex::decode("02f874018201bb8405f5e10085096a1d45b782520894d696a5c568160bbbf5a1356f8ac56ee81a190588871550f7dca7000080c080a07df2299b0181d6d5b817795a7d2eff5897d0d3914ff5f602e17d5b75d32ec25fa051833973e8a8c222e682d2dcea02ad7bf3ec5bc3a86bfbcdbbaa3b853e52ad08").unwrap();
+        let tx: Transaction = Transaction::decode(&Rlp::new(&raw_tx)).unwrap();
+        assert_eq!(
+            tx.hash,
+            H256::from_str("938913ef1df8cd17e0893a85586ade463014559fb1bd2d536ac282f3b1bdea53")
+                .unwrap()
+        )
+    }
+
     #[test]
     fn recover_from() {
         let tx = Transaction {
diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs
index bda3e97e..7ec7e9ba 100644
--- a/ethers-core/src/utils/mod.rs
+++ b/ethers-core/src/utils/mod.rs
@@ -52,11 +52,9 @@ const OVERFLOW_I256_UNITS: usize = 77;
 /// U256 overflows for numbers wider than 78 units.
 const OVERFLOW_U256_UNITS: usize = 78;
 
-/// Re-export of serde-json
+// Re-export serde-json for macro usage
 #[doc(hidden)]
-pub mod __serde_json {
-    pub use serde_json::*;
-}
+pub use serde_json as __serde_json;
 
 #[derive(Error, Debug)]
 pub enum ConversionError {
diff --git a/ethers-etherscan/Cargo.toml b/ethers-etherscan/Cargo.toml
index b402810b..6e503985 100644
--- a/ethers-etherscan/Cargo.toml
+++ b/ethers-etherscan/Cargo.toml
@@ -47,7 +47,7 @@ ethers-solc.workspace = true
 
 tempfile.workspace = true
 tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
-serial_test = "1.0.0"
+serial_test = "2.0.0"
 tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
 
 [features]
diff --git a/ethers-etherscan/src/gas.rs b/ethers-etherscan/src/gas.rs
index e58fc2f3..e8eacdd4 100644
--- a/ethers-etherscan/src/gas.rs
+++ b/ethers-etherscan/src/gas.rs
@@ -7,11 +7,11 @@ use std::{collections::HashMap, str::FromStr};
 #[serde(rename_all = "PascalCase")]
 pub struct GasOracle {
     #[serde(deserialize_with = "deserialize_number_from_string")]
-    pub safe_gas_price: u64,
+    pub safe_gas_price: f64,
     #[serde(deserialize_with = "deserialize_number_from_string")]
-    pub propose_gas_price: u64,
+    pub propose_gas_price: f64,
     #[serde(deserialize_with = "deserialize_number_from_string")]
-    pub fast_gas_price: u64,
+    pub fast_gas_price: f64,
     #[serde(deserialize_with = "deserialize_number_from_string")]
     pub last_block: u64,
     #[serde(deserialize_with = "deserialize_number_from_string")]
@@ -82,3 +82,28 @@ impl Client {
         Ok(response.result)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn response_works() {
+        // Response from Polygon mainnet at 2023-04-05
+        let v = r#"{
+            "status": "1",
+            "message": "OK",
+            "result": {
+                "LastBlock": "41171167",
+                "SafeGasPrice": "119.9",
+                "ProposeGasPrice": "141.9",
+                "FastGasPrice": "142.9",
+                "suggestBaseFee": "89.82627877",
+                "gasUsedRatio": "0.399191166666667,0.4847166,0.997667533333333,0.538075133333333,0.343416033333333",
+                "UsdPrice": "1.15"
+            }
+        }"#;
+        let gas_oracle: Response<GasOracle> = serde_json::from_str(v).unwrap();
+        assert_eq!(gas_oracle.message, "OK");
+    }
+}
diff --git a/ethers-middleware/src/gas_oracle/etherscan.rs b/ethers-middleware/src/gas_oracle/etherscan.rs
index 9d8da4c0..39645eb9 100644
--- a/ethers-middleware/src/gas_oracle/etherscan.rs
+++ b/ethers-middleware/src/gas_oracle/etherscan.rs
@@ -1,4 +1,4 @@
-use super::{GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
+use super::{GasCategory, GasOracle, GasOracleError, Result};
 use async_trait::async_trait;
 use ethers_core::types::U256;
 use ethers_etherscan::Client;
@@ -44,7 +44,9 @@ impl GasOracle for Etherscan {
             GasCategory::Fast => result.fast_gas_price,
             _ => unreachable!(),
         };
-        Ok(U256::from(gas_price) * GWEI_TO_WEI_U256)
+        // returned gas prices are f64 value in gwei
+        let gas_price = super::from_gwei_f64(gas_price);
+        Ok(gas_price)
     }
 
     async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
diff --git a/ethers-middleware/src/gas_oracle/mod.rs b/ethers-middleware/src/gas_oracle/mod.rs
index 40c1bf42..0c32de41 100644
--- a/ethers-middleware/src/gas_oracle/mod.rs
+++ b/ethers-middleware/src/gas_oracle/mod.rs
@@ -89,6 +89,8 @@ pub enum GasOracleError {
     /// Error thrown when the provider failed.
     #[error("Provider error: {0}")]
     ProviderError(#[from] Box<dyn Error + Send + Sync>),
+    #[error("Failed to parse gas values: {0}")]
+    ConversionError(#[from] ethers_core::utils::ConversionError),
 }
 
 /// An Ethereum gas price oracle.
diff --git a/ethers-providers/Cargo.toml b/ethers-providers/Cargo.toml
index 210ffedd..82f7b344 100644
--- a/ethers-providers/Cargo.toml
+++ b/ethers-providers/Cargo.toml
@@ -75,7 +75,7 @@ getrandom.workspace = true
 
 [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
 tokio = { workspace = true, features = ["rt", "macros", "time"] }
-tempfile = "3.4.0"
+tempfile = "3.5.0"
 
 [features]
 default = ["ws", "rustls"]
diff --git a/ethers-signers/Cargo.toml b/ethers-signers/Cargo.toml
index cd23c776..e6a77af1 100644
--- a/ethers-signers/Cargo.toml
+++ b/ethers-signers/Cargo.toml
@@ -24,11 +24,11 @@ rustdoc-args = ["--cfg", "docsrs"]
 all-features = true
 
 [dependencies]
-ethers-core = { workspace = true, features = ["eip712"] }
+ethers-core.workspace = true
 
 # crypto
 coins-bip32 = "0.8.3"
-coins-bip39 = "0.8.3"
+coins-bip39 = "0.8.5"
 elliptic-curve.workspace = true
 sha2.workspace = true
 rand.workspace = true
@@ -66,7 +66,6 @@ yubihsm = { version = "0.42.0-pre.0", features = ["secp256k1", "http", "usb"], o
 
 [dev-dependencies]
 ethers-contract-derive.workspace = true
-ethers-derive-eip712.workspace = true
 
 serde_json.workspace = true
 tempfile.workspace = true
diff --git a/ethers-signers/src/ledger/app.rs b/ethers-signers/src/ledger/app.rs
index 05adbc2f..bed0741c 100644
--- a/ethers-signers/src/ledger/app.rs
+++ b/ethers-signers/src/ledger/app.rs
@@ -293,11 +293,10 @@ impl LedgerEthereum {
 mod tests {
     use super::*;
     use crate::Signer;
-    use ethers_contract_derive::EthAbiType;
+    use ethers_contract_derive::{Eip712, EthAbiType};
     use ethers_core::types::{
         transaction::eip712::Eip712, Address, TransactionRequest, I256, U256,
     };
-    use ethers_derive_eip712::*;
     use std::str::FromStr;
 
     #[derive(Debug, Clone, Eip712, EthAbiType)]
diff --git a/ethers-signers/src/trezor/app.rs b/ethers-signers/src/trezor/app.rs
index a9e7b555..471e4e58 100644
--- a/ethers-signers/src/trezor/app.rs
+++ b/ethers-signers/src/trezor/app.rs
@@ -233,7 +233,7 @@ impl TrezorEthereum {
 mod tests {
     use super::*;
     use crate::Signer;
-    use ethers_contract_derive::EthAbiType;
+    use ethers_contract_derive::{Eip712, EthAbiType};
     use ethers_core::types::{
         transaction::{
             eip2930::{AccessList, AccessListItem},
@@ -241,7 +241,6 @@ mod tests {
         },
         Address, Eip1559TransactionRequest, TransactionRequest, I256, U256,
     };
-    use ethers_derive_eip712::*;
     use std::str::FromStr;
 
     #[derive(Debug, Clone, Eip712, EthAbiType)]
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index d4102ceb..7dfc1792 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -53,7 +53,7 @@ rayon.workspace = true
 path-slash = "0.2.1"
 cfg-if = "1.0.0"
 
-tempfile = { version = "3.4.0", optional = true }
+tempfile = { version = "3.5.0", optional = true }
 fs_extra = { version = "1.3.0", optional = true }
 rand = { workspace = true, optional = true }
 futures-util = { workspace = true, optional = true }
@@ -77,7 +77,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
 rand.workspace = true
 pretty_assertions = "1.3.0"
 tempfile.workspace = true
-serde_path_to_error = "0.1.10"
+serde_path_to_error = "0.1.11"
 
 [[bench]]
 name = "compile_many"
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index 37eb52b1..4ef62da8 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -103,67 +103,13 @@ impl CompilerInput {
 
     /// This will remove/adjust values in the `CompilerInput` that are not compatible with this
     /// version
-    pub fn sanitized(mut self, version: &Version) -> Self {
-        static PRE_V0_6_0: once_cell::sync::Lazy<VersionReq> =
-            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.6.0").unwrap());
-        static PRE_V0_7_5: once_cell::sync::Lazy<VersionReq> =
-            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.7.5").unwrap());
-        static PRE_V0_8_7: once_cell::sync::Lazy<VersionReq> =
-            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.8.7").unwrap());
-        static PRE_V0_8_10: once_cell::sync::Lazy<VersionReq> =
-            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.8.10").unwrap());
-        static PRE_V0_8_18: once_cell::sync::Lazy<VersionReq> =
-            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.8.18").unwrap());
-
-        if PRE_V0_6_0.matches(version) {
-            if let Some(ref mut meta) = self.settings.metadata {
-                // introduced in <https://docs.soliditylang.org/en/v0.6.0/using-the-compiler.html#compiler-api>
-                // missing in <https://docs.soliditylang.org/en/v0.5.17/using-the-compiler.html#compiler-api>
-                meta.bytecode_hash.take();
-            }
-            // introduced in <https://docs.soliditylang.org/en/v0.6.0/using-the-compiler.html#compiler-api>
-            let _ = self.settings.debug.take();
-        }
-
-        if PRE_V0_7_5.matches(version) {
-            // introduced in 0.7.5 <https://github.com/ethereum/solidity/releases/tag/v0.7.5>
-            self.settings.via_ir.take();
-        }
-
-        if PRE_V0_8_7.matches(version) {
-            // lower the disable version from 0.8.10 to 0.8.7, due to `divModNoSlacks`,
-            // `showUnproved` and `solvers` are implemented
-            // introduced in <https://github.com/ethereum/solidity/releases/tag/v0.8.7>
-            self.settings.model_checker = None;
-        }
-
-        if PRE_V0_8_10.matches(version) {
-            if let Some(ref mut debug) = self.settings.debug {
-                // introduced in <https://docs.soliditylang.org/en/v0.8.10/using-the-compiler.html#compiler-api>
-                // <https://github.com/ethereum/solidity/releases/tag/v0.8.10>
-                debug.debug_info.clear();
-            }
-
-            if let Some(ref mut model_checker) = self.settings.model_checker {
-                // introduced in <https://github.com/ethereum/solidity/releases/tag/v0.8.10>
-                model_checker.invariants = None;
-            }
-        }
-
-        if PRE_V0_8_18.matches(version) {
-            // introduced in 0.8.18 <https://github.com/ethereum/solidity/releases/tag/v0.8.18>
-            if let Some(ref mut meta) = self.settings.metadata {
-                meta.cbor_metadata = None;
-            }
-
-            if let Some(ref mut model_checker) = self.settings.model_checker {
-                if let Some(ref mut solvers) = model_checker.solvers {
-                    // elf solver introduced in 0.8.18 <https://github.com/ethereum/solidity/releases/tag/v0.8.18>
-                    solvers.retain(|solver| *solver != ModelCheckerSolver::Eld);
-                }
-            }
-        }
+    pub fn sanitize(&mut self, version: &Version) {
+        self.settings.sanitize(version)
+    }
 
+    /// Consumes the type and returns a [CompilerInput::sanitized] version
+    pub fn sanitized(mut self, version: &Version) -> Self {
+        self.settings.sanitize(version);
         self
     }
 
@@ -353,6 +299,76 @@ impl Settings {
         Self { output_selection: output_selection.into(), ..Default::default() }
     }
 
+    /// Consumes the type and returns a [Settings::sanitize] version
+    pub fn sanitized(mut self, version: &Version) -> Self {
+        self.sanitize(version);
+        self
+    }
+
+    /// This will remove/adjust values in the settings that are not compatible with this
+    /// version
+    pub fn sanitize(&mut self, version: &Version) {
+        static PRE_V0_6_0: once_cell::sync::Lazy<VersionReq> =
+            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.6.0").unwrap());
+        static PRE_V0_7_5: once_cell::sync::Lazy<VersionReq> =
+            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.7.5").unwrap());
+        static PRE_V0_8_7: once_cell::sync::Lazy<VersionReq> =
+            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.8.7").unwrap());
+        static PRE_V0_8_10: once_cell::sync::Lazy<VersionReq> =
+            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.8.10").unwrap());
+        static PRE_V0_8_18: once_cell::sync::Lazy<VersionReq> =
+            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.8.18").unwrap());
+
+        if PRE_V0_6_0.matches(version) {
+            if let Some(ref mut meta) = self.metadata {
+                // introduced in <https://docs.soliditylang.org/en/v0.6.0/using-the-compiler.html#compiler-api>
+                // missing in <https://docs.soliditylang.org/en/v0.5.17/using-the-compiler.html#compiler-api>
+                meta.bytecode_hash.take();
+            }
+            // introduced in <https://docs.soliditylang.org/en/v0.6.0/using-the-compiler.html#compiler-api>
+            let _ = self.debug.take();
+        }
+
+        if PRE_V0_7_5.matches(version) {
+            // introduced in 0.7.5 <https://github.com/ethereum/solidity/releases/tag/v0.7.5>
+            self.via_ir.take();
+        }
+
+        if PRE_V0_8_7.matches(version) {
+            // lower the disable version from 0.8.10 to 0.8.7, due to `divModNoSlacks`,
+            // `showUnproved` and `solvers` are implemented
+            // introduced in <https://github.com/ethereum/solidity/releases/tag/v0.8.7>
+            self.model_checker = None;
+        }
+
+        if PRE_V0_8_10.matches(version) {
+            if let Some(ref mut debug) = self.debug {
+                // introduced in <https://docs.soliditylang.org/en/v0.8.10/using-the-compiler.html#compiler-api>
+                // <https://github.com/ethereum/solidity/releases/tag/v0.8.10>
+                debug.debug_info.clear();
+            }
+
+            if let Some(ref mut model_checker) = self.model_checker {
+                // introduced in <https://github.com/ethereum/solidity/releases/tag/v0.8.10>
+                model_checker.invariants = None;
+            }
+        }
+
+        if PRE_V0_8_18.matches(version) {
+            // introduced in 0.8.18 <https://github.com/ethereum/solidity/releases/tag/v0.8.18>
+            if let Some(ref mut meta) = self.metadata {
+                meta.cbor_metadata = None;
+            }
+
+            if let Some(ref mut model_checker) = self.model_checker {
+                if let Some(ref mut solvers) = model_checker.solvers {
+                    // elf solver introduced in 0.8.18 <https://github.com/ethereum/solidity/releases/tag/v0.8.18>
+                    solvers.retain(|solver| *solver != ModelCheckerSolver::Eld);
+                }
+            }
+        }
+    }
+
     /// Inserts a set of `ContractOutputSelection`
     pub fn push_all(&mut self, settings: impl IntoIterator<Item = ContractOutputSelection>) {
         for value in settings {
diff --git a/ethers/Cargo.toml b/ethers/Cargo.toml
index 9db13a3e..c25379da 100644
--- a/ethers/Cargo.toml
+++ b/ethers/Cargo.toml
@@ -26,18 +26,36 @@ all-features = true
 [features]
 default = ["abigen", "rustls"]
 
-celo = ["ethers-core/celo", "ethers-providers/celo", "ethers-signers/celo", "ethers-contract/celo", "ethers-middleware/celo", "legacy"]
+celo = [
+    "ethers-core/celo",
+    "ethers-providers/celo",
+    "ethers-signers/celo",
+    "ethers-contract/celo",
+    "ethers-middleware/celo",
+    "legacy",
+]
 
 legacy = ["ethers-core/legacy", "ethers-contract/legacy"]
 
 # individual features per sub-crate
-## core
-eip712 = ["ethers-contract/eip712", "ethers-core/eip712"]
 ## providers
 ws = ["ethers-providers/ws"]
+legacy-ws = ["ethers-providers/legacy-ws"]
 ipc = ["ethers-providers/ipc"]
-rustls = ["ethers-middleware/rustls", "ethers-providers/rustls", "ethers-etherscan/rustls", "ethers-contract/rustls", "ethers-solc/rustls"]
-openssl = ["ethers-middleware/openssl", "ethers-providers/openssl", "ethers-etherscan/openssl", "ethers-contract/openssl", "ethers-solc/openssl"]
+rustls = [
+    "ethers-middleware/rustls",
+    "ethers-providers/rustls",
+    "ethers-etherscan/rustls",
+    "ethers-contract/rustls",
+    "ethers-solc/rustls",
+]
+openssl = [
+    "ethers-middleware/openssl",
+    "ethers-providers/openssl",
+    "ethers-etherscan/openssl",
+    "ethers-contract/openssl",
+    "ethers-solc/openssl",
+]
 dev-rpc = ["ethers-providers/dev-rpc"]
 ## signers
 ledger = ["ethers-signers/ledger"]
@@ -53,6 +71,7 @@ solc-tests = ["ethers-solc/tests"]
 
 # Deprecated
 solc-sha2-asm = []
+eip712 = []
 
 [dependencies]
 ethers-addressbook.workspace = true
@@ -67,6 +86,6 @@ ethers-solc.workspace = true
 [dev-dependencies]
 serde.workspace = true
 tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
-ethers-contract = { workspace = true, features = ["eip712"] }
+ethers-contract.workspace = true
 ethers-providers = { workspace = true, features = ["rustls"] } # allow https connections
 ethers-solc = { workspace = true, features = ["svm-solc"] }
diff --git a/examples/README.md b/examples/README.md
index c37836b0..6b5682ff 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -71,6 +71,7 @@
   - [ ] Transaction receipt
   - [ ] Transaction status
   - [x] Transfer ETH
+  - [X] Transfer ERC20 token
 - [x] Wallets
   - [x] Mnemonic
   - [x] Ledger
diff --git a/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/README.md b/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/README.md
new file mode 100644
index 00000000..05805d3e
--- /dev/null
+++ b/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/README.md
@@ -0,0 +1,12 @@
+# OpenZeppelin Contracts
+
+The files in this directory were sourced unmodified from OpenZeppelin Contracts v4.7.3.
+
+They are not meant to be edited.
+
+The originals can be found on [GitHub] and [npm].
+
+[GitHub]: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v4.7.3
+[npm]: https://www.npmjs.com/package/@openzeppelin/contracts/v/4.7.3
+
+Generated with OpenZeppelin Contracts Wizard (https://zpl.in/wizard).
diff --git a/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/token/ERC20/ERC20.sol b/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/token/ERC20/ERC20.sol
new file mode 100644
index 00000000..ed2cd5de
--- /dev/null
+++ b/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/token/ERC20/ERC20.sol
@@ -0,0 +1,383 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol)
+
+pragma solidity ^0.8.0;
+
+import "./IERC20.sol";
+import "./extensions/IERC20Metadata.sol";
+import "../../utils/Context.sol";
+
+/**
+ * @dev Implementation of the {IERC20} interface.
+ *
+ * This implementation is agnostic to the way tokens are created. This means
+ * that a supply mechanism has to be added in a derived contract using {_mint}.
+ * For a generic mechanism see {ERC20PresetMinterPauser}.
+ *
+ * TIP: For a detailed writeup see our guide
+ * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
+ * to implement supply mechanisms].
+ *
+ * We have followed general OpenZeppelin Contracts guidelines: functions revert
+ * instead returning `false` on failure. This behavior is nonetheless
+ * conventional and does not conflict with the expectations of ERC20
+ * applications.
+ *
+ * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
+ * This allows applications to reconstruct the allowance for all accounts just
+ * by listening to said events. Other implementations of the EIP may not emit
+ * these events, as it isn't required by the specification.
+ *
+ * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
+ * functions have been added to mitigate the well-known issues around setting
+ * allowances. See {IERC20-approve}.
+ */
+contract ERC20 is Context, IERC20, IERC20Metadata {
+    mapping(address => uint256) private _balances;
+
+    mapping(address => mapping(address => uint256)) private _allowances;
+
+    uint256 private _totalSupply;
+
+    string private _name;
+    string private _symbol;
+
+    /**
+     * @dev Sets the values for {name} and {symbol}.
+     *
+     * The default value of {decimals} is 18. To select a different value for
+     * {decimals} you should overload it.
+     *
+     * All two of these values are immutable: they can only be set once during
+     * construction.
+     */
+    constructor(string memory name_, string memory symbol_) {
+        _name = name_;
+        _symbol = symbol_;
+    }
+
+    /**
+     * @dev Returns the name of the token.
+     */
+    function name() public view virtual override returns (string memory) {
+        return _name;
+    }
+
+    /**
+     * @dev Returns the symbol of the token, usually a shorter version of the
+     * name.
+     */
+    function symbol() public view virtual override returns (string memory) {
+        return _symbol;
+    }
+
+    /**
+     * @dev Returns the number of decimals used to get its user representation.
+     * For example, if `decimals` equals `2`, a balance of `505` tokens should
+     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
+     *
+     * Tokens usually opt for a value of 18, imitating the relationship between
+     * Ether and Wei. This is the value {ERC20} uses, unless this function is
+     * overridden;
+     *
+     * NOTE: This information is only used for _display_ purposes: it in
+     * no way affects any of the arithmetic of the contract, including
+     * {IERC20-balanceOf} and {IERC20-transfer}.
+     */
+    function decimals() public view virtual override returns (uint8) {
+        return 18;
+    }
+
+    /**
+     * @dev See {IERC20-totalSupply}.
+     */
+    function totalSupply() public view virtual override returns (uint256) {
+        return _totalSupply;
+    }
+
+    /**
+     * @dev See {IERC20-balanceOf}.
+     */
+    function balanceOf(address account) public view virtual override returns (uint256) {
+        return _balances[account];
+    }
+
+    /**
+     * @dev See {IERC20-transfer}.
+     *
+     * Requirements:
+     *
+     * - `to` cannot be the zero address.
+     * - the caller must have a balance of at least `amount`.
+     */
+    function transfer(address to, uint256 amount) public virtual override returns (bool) {
+        address owner = _msgSender();
+        _transfer(owner, to, amount);
+        return true;
+    }
+
+    /**
+     * @dev See {IERC20-allowance}.
+     */
+    function allowance(address owner, address spender) public view virtual override returns (uint256) {
+        return _allowances[owner][spender];
+    }
+
+    /**
+     * @dev See {IERC20-approve}.
+     *
+     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
+     * `transferFrom`. This is semantically equivalent to an infinite approval.
+     *
+     * Requirements:
+     *
+     * - `spender` cannot be the zero address.
+     */
+    function approve(address spender, uint256 amount) public virtual override returns (bool) {
+        address owner = _msgSender();
+        _approve(owner, spender, amount);
+        return true;
+    }
+
+    /**
+     * @dev See {IERC20-transferFrom}.
+     *
+     * Emits an {Approval} event indicating the updated allowance. This is not
+     * required by the EIP. See the note at the beginning of {ERC20}.
+     *
+     * NOTE: Does not update the allowance if the current allowance
+     * is the maximum `uint256`.
+     *
+     * Requirements:
+     *
+     * - `from` and `to` cannot be the zero address.
+     * - `from` must have a balance of at least `amount`.
+     * - the caller must have allowance for ``from``'s tokens of at least
+     * `amount`.
+     */
+    function transferFrom(
+        address from,
+        address to,
+        uint256 amount
+    ) public virtual override returns (bool) {
+        address spender = _msgSender();
+        _spendAllowance(from, spender, amount);
+        _transfer(from, to, amount);
+        return true;
+    }
+
+    /**
+     * @dev Atomically increases the allowance granted to `spender` by the caller.
+     *
+     * This is an alternative to {approve} that can be used as a mitigation for
+     * problems described in {IERC20-approve}.
+     *
+     * Emits an {Approval} event indicating the updated allowance.
+     *
+     * Requirements:
+     *
+     * - `spender` cannot be the zero address.
+     */
+    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
+        address owner = _msgSender();
+        _approve(owner, spender, allowance(owner, spender) + addedValue);
+        return true;
+    }
+
+    /**
+     * @dev Atomically decreases the allowance granted to `spender` by the caller.
+     *
+     * This is an alternative to {approve} that can be used as a mitigation for
+     * problems described in {IERC20-approve}.
+     *
+     * Emits an {Approval} event indicating the updated allowance.
+     *
+     * Requirements:
+     *
+     * - `spender` cannot be the zero address.
+     * - `spender` must have allowance for the caller of at least
+     * `subtractedValue`.
+     */
+    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
+        address owner = _msgSender();
+        uint256 currentAllowance = allowance(owner, spender);
+        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
+        unchecked {
+            _approve(owner, spender, currentAllowance - subtractedValue);
+        }
+
+        return true;
+    }
+
+    /**
+     * @dev Moves `amount` of tokens from `from` to `to`.
+     *
+     * This internal function is equivalent to {transfer}, and can be used to
+     * e.g. implement automatic token fees, slashing mechanisms, etc.
+     *
+     * Emits a {Transfer} event.
+     *
+     * Requirements:
+     *
+     * - `from` cannot be the zero address.
+     * - `to` cannot be the zero address.
+     * - `from` must have a balance of at least `amount`.
+     */
+    function _transfer(
+        address from,
+        address to,
+        uint256 amount
+    ) internal virtual {
+        require(from != address(0), "ERC20: transfer from the zero address");
+        require(to != address(0), "ERC20: transfer to the zero address");
+
+        _beforeTokenTransfer(from, to, amount);
+
+        uint256 fromBalance = _balances[from];
+        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
+        unchecked {
+            _balances[from] = fromBalance - amount;
+        }
+        _balances[to] += amount;
+
+        emit Transfer(from, to, amount);
+
+        _afterTokenTransfer(from, to, amount);
+    }
+
+    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
+     * the total supply.
+     *
+     * Emits a {Transfer} event with `from` set to the zero address.
+     *
+     * Requirements:
+     *
+     * - `account` cannot be the zero address.
+     */
+    function _mint(address account, uint256 amount) internal virtual {
+        require(account != address(0), "ERC20: mint to the zero address");
+
+        _beforeTokenTransfer(address(0), account, amount);
+
+        _totalSupply += amount;
+        _balances[account] += amount;
+        emit Transfer(address(0), account, amount);
+
+        _afterTokenTransfer(address(0), account, amount);
+    }
+
+    /**
+     * @dev Destroys `amount` tokens from `account`, reducing the
+     * total supply.
+     *
+     * Emits a {Transfer} event with `to` set to the zero address.
+     *
+     * Requirements:
+     *
+     * - `account` cannot be the zero address.
+     * - `account` must have at least `amount` tokens.
+     */
+    function _burn(address account, uint256 amount) internal virtual {
+        require(account != address(0), "ERC20: burn from the zero address");
+
+        _beforeTokenTransfer(account, address(0), amount);
+
+        uint256 accountBalance = _balances[account];
+        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
+        unchecked {
+            _balances[account] = accountBalance - amount;
+        }
+        _totalSupply -= amount;
+
+        emit Transfer(account, address(0), amount);
+
+        _afterTokenTransfer(account, address(0), amount);
+    }
+
+    /**
+     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
+     *
+     * This internal function is equivalent to `approve`, and can be used to
+     * e.g. set automatic allowances for certain subsystems, etc.
+     *
+     * Emits an {Approval} event.
+     *
+     * Requirements:
+     *
+     * - `owner` cannot be the zero address.
+     * - `spender` cannot be the zero address.
+     */
+    function _approve(
+        address owner,
+        address spender,
+        uint256 amount
+    ) internal virtual {
+        require(owner != address(0), "ERC20: approve from the zero address");
+        require(spender != address(0), "ERC20: approve to the zero address");
+
+        _allowances[owner][spender] = amount;
+        emit Approval(owner, spender, amount);
+    }
+
+    /**
+     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
+     *
+     * Does not update the allowance amount in case of infinite allowance.
+     * Revert if not enough allowance is available.
+     *
+     * Might emit an {Approval} event.
+     */
+    function _spendAllowance(
+        address owner,
+        address spender,
+        uint256 amount
+    ) internal virtual {
+        uint256 currentAllowance = allowance(owner, spender);
+        if (currentAllowance != type(uint256).max) {
+            require(currentAllowance >= amount, "ERC20: insufficient allowance");
+            unchecked {
+                _approve(owner, spender, currentAllowance - amount);
+            }
+        }
+    }
+
+    /**
+     * @dev Hook that is called before any transfer of tokens. This includes
+     * minting and burning.
+     *
+     * Calling conditions:
+     *
+     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
+     * will be transferred to `to`.
+     * - when `from` is zero, `amount` tokens will be minted for `to`.
+     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
+     * - `from` and `to` are never both zero.
+     *
+     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
+     */
+    function _beforeTokenTransfer(
+        address from,
+        address to,
+        uint256 amount
+    ) internal virtual {}
+
+    /**
+     * @dev Hook that is called after any transfer of tokens. This includes
+     * minting and burning.
+     *
+     * Calling conditions:
+     *
+     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
+     * has been transferred to `to`.
+     * - when `from` is zero, `amount` tokens have been minted for `to`.
+     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
+     * - `from` and `to` are never both zero.
+     *
+     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
+     */
+    function _afterTokenTransfer(
+        address from,
+        address to,
+        uint256 amount
+    ) internal virtual {}
+}
diff --git a/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/token/ERC20/IERC20.sol b/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/token/ERC20/IERC20.sol
new file mode 100644
index 00000000..b816bfed
--- /dev/null
+++ b/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/token/ERC20/IERC20.sol
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
+
+pragma solidity ^0.8.0;
+
+/**
+ * @dev Interface of the ERC20 standard as defined in the EIP.
+ */
+interface IERC20 {
+    /**
+     * @dev Emitted when `value` tokens are moved from one account (`from`) to
+     * another (`to`).
+     *
+     * Note that `value` may be zero.
+     */
+    event Transfer(address indexed from, address indexed to, uint256 value);
+
+    /**
+     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
+     * a call to {approve}. `value` is the new allowance.
+     */
+    event Approval(address indexed owner, address indexed spender, uint256 value);
+
+    /**
+     * @dev Returns the amount of tokens in existence.
+     */
+    function totalSupply() external view returns (uint256);
+
+    /**
+     * @dev Returns the amount of tokens owned by `account`.
+     */
+    function balanceOf(address account) external view returns (uint256);
+
+    /**
+     * @dev Moves `amount` tokens from the caller's account to `to`.
+     *
+     * Returns a boolean value indicating whether the operation succeeded.
+     *
+     * Emits a {Transfer} event.
+     */
+    function transfer(address to, uint256 amount) external returns (bool);
+
+    /**
+     * @dev Returns the remaining number of tokens that `spender` will be
+     * allowed to spend on behalf of `owner` through {transferFrom}. This is
+     * zero by default.
+     *
+     * This value changes when {approve} or {transferFrom} are called.
+     */
+    function allowance(address owner, address spender) external view returns (uint256);
+
+    /**
+     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
+     *
+     * Returns a boolean value indicating whether the operation succeeded.
+     *
+     * IMPORTANT: Beware that changing an allowance with this method brings the risk
+     * that someone may use both the old and the new allowance by unfortunate
+     * transaction ordering. One possible solution to mitigate this race
+     * condition is to first reduce the spender's allowance to 0 and set the
+     * desired value afterwards:
+     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
+     *
+     * Emits an {Approval} event.
+     */
+    function approve(address spender, uint256 amount) external returns (bool);
+
+    /**
+     * @dev Moves `amount` tokens from `from` to `to` using the
+     * allowance mechanism. `amount` is then deducted from the caller's
+     * allowance.
+     *
+     * Returns a boolean value indicating whether the operation succeeded.
+     *
+     * Emits a {Transfer} event.
+     */
+    function transferFrom(
+        address from,
+        address to,
+        uint256 amount
+    ) external returns (bool);
+}
diff --git a/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol b/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol
new file mode 100644
index 00000000..83ba6ac5
--- /dev/null
+++ b/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
+
+pragma solidity ^0.8.0;
+
+import "../IERC20.sol";
+
+/**
+ * @dev Interface for the optional metadata functions from the ERC20 standard.
+ *
+ * _Available since v4.1._
+ */
+interface IERC20Metadata is IERC20 {
+    /**
+     * @dev Returns the name of the token.
+     */
+    function name() external view returns (string memory);
+
+    /**
+     * @dev Returns the symbol of the token.
+     */
+    function symbol() external view returns (string memory);
+
+    /**
+     * @dev Returns the decimals places of the token.
+     */
+    function decimals() external view returns (uint8);
+}
diff --git a/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/utils/Context.sol b/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/utils/Context.sol
new file mode 100644
index 00000000..f304065b
--- /dev/null
+++ b/examples/transactions/examples/contracts/erc20_example/@openzeppelin/contracts/utils/Context.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
+
+pragma solidity ^0.8.0;
+
+/**
+ * @dev Provides information about the current execution context, including the
+ * sender of the transaction and its data. While these are generally available
+ * via msg.sender and msg.data, they should not be accessed in such a direct
+ * manner, since when dealing with meta-transactions the account sending and
+ * paying for execution may not be the actual sender (as far as an application
+ * is concerned).
+ *
+ * This contract is only required for intermediate, library-like contracts.
+ */
+abstract contract Context {
+    function _msgSender() internal view virtual returns (address) {
+        return msg.sender;
+    }
+
+    function _msgData() internal view virtual returns (bytes calldata) {
+        return msg.data;
+    }
+}
diff --git a/examples/transactions/examples/contracts/erc20_example/ERC20Example.sol b/examples/transactions/examples/contracts/erc20_example/ERC20Example.sol
new file mode 100644
index 00000000..e2660ac3
--- /dev/null
+++ b/examples/transactions/examples/contracts/erc20_example/ERC20Example.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.4;
+
+import "./@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract ERC20Example is ERC20 {
+    constructor() ERC20("ERC20Example", "XYZ") {
+        _mint(msg.sender, 1000 * 10 ** decimals());
+    }
+}
diff --git a/examples/transactions/examples/transfer_erc20.rs b/examples/transactions/examples/transfer_erc20.rs
new file mode 100644
index 00000000..0c38f353
--- /dev/null
+++ b/examples/transactions/examples/transfer_erc20.rs
@@ -0,0 +1,88 @@
+use ethers::{
+    contract::{abigen, ContractFactory},
+    core::utils::Anvil,
+    middleware::SignerMiddleware,
+    providers::{Http, Provider},
+    signers::{LocalWallet, Signer},
+    solc::Solc,
+    types::{Address, U256},
+    utils::AnvilInstance,
+};
+use eyre::Result;
+use std::{convert::TryFrom, path::Path, sync::Arc, time::Duration};
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    // Start a fresh local Anvil node to execute the transfer on.
+    let anvil = Anvil::new().spawn();
+    // Use the first wallet autogenerated by the Anvil node to send the transaction.
+    let from_wallet: LocalWallet = anvil.keys()[0].clone().into();
+    let to_address = anvil.addresses()[2].clone();
+
+    // Deploy an ERC20 token contract on the freshly started local Anvil node that doesn't have any
+    // tokens deployed on it. This will also allocate tokens to the `from_wallet` address.
+    // You don't need to do this to transfer an already deployed ERC20 token, in that case you only
+    // need the token address.
+    let token_address = deploy_token_contract(&anvil, from_wallet.clone()).await?;
+
+    // 1. Generate the ABI for the ERC20 contract. This is will define an `ERC20Contract` struct in
+    // this scope that will let us call the methods of the contract.
+    abigen!(
+        ERC20Contract,
+        r#"[
+            function balanceOf(address account) external view returns (uint256)
+            function decimals() external view returns (uint8)
+            function symbol() external view returns (string memory)
+            function transfer(address to, uint256 amount) external returns (bool)
+            event Transfer(address indexed from, address indexed to, uint256 value)
+        ]"#,
+    );
+
+    // 2. Create the contract instance to let us call methods of the contract and let it sign
+    // transactions with the sender wallet.
+    let provider =
+        Provider::<Http>::try_from(anvil.endpoint())?.interval(Duration::from_millis(10u64));
+    let signer =
+        Arc::new(SignerMiddleware::new(provider, from_wallet.with_chain_id(anvil.chain_id())));
+    let contract = ERC20Contract::new(token_address, signer);
+
+    // 3. Fetch the decimals used by the contract so we can compute the decimal amount to send.
+    let whole_amount: u64 = 1000;
+    let decimals = contract.decimals().call().await?;
+    let decimal_amount = U256::from(whole_amount) * U256::exp10(decimals as usize);
+
+    // 4. Transfer the desired amount of tokens to the `to_address`
+    let tx = contract.transfer(to_address, decimal_amount);
+    let pending_tx = tx.send().await?;
+    let _mined_tx = pending_tx.await?;
+
+    // 5. Fetch the balance of the recipient.
+    let balance = contract.balance_of(to_address).call().await?;
+    assert_eq!(balance, decimal_amount);
+
+    Ok(())
+}
+
+/// Helper function to deploy a contract on a local anvil node. See
+/// `examples/contracts/examples/deploy_from_solidity.rs` for detailed explanation on how to deploy
+/// a contract.
+/// Allocates tokens to the deployer address and returns the ERC20 contract address.
+async fn deploy_token_contract(
+    anvil: &AnvilInstance,
+    deployer_wallet: LocalWallet,
+) -> Result<Address> {
+    let source = Path::new(&env!("CARGO_MANIFEST_DIR"))
+        .join("examples/contracts/erc20_example/ERC20Example.sol");
+    let compiled = Solc::default().compile_source(source).expect("Could not compile contract");
+    let (abi, bytecode, _runtime_bytecode) =
+        compiled.find("ERC20Example").expect("could not find contract").into_parts_or_default();
+
+    let provider =
+        Provider::<Http>::try_from(anvil.endpoint())?.interval(Duration::from_millis(10u64));
+    let client = SignerMiddleware::new(provider, deployer_wallet.with_chain_id(anvil.chain_id()));
+    let client = Arc::new(client);
+    let factory = ContractFactory::new(abi, bytecode, client.clone());
+    let contract = factory.deploy(())?.send().await?;
+
+    Ok(contract.address())
+}
diff --git a/examples/wallets/Cargo.toml b/examples/wallets/Cargo.toml
index 483c46d8..8c82e345 100644
--- a/examples/wallets/Cargo.toml
+++ b/examples/wallets/Cargo.toml
@@ -15,7 +15,7 @@ trezor = ["ethers/trezor"]
 yubi = ["ethers/yubi"]
 
 [dev-dependencies]
-ethers = { workspace = true, features = ["abigen", "eip712", "ws", "rustls"] }
+ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
 
 tokio = { workspace = true, features = ["macros"] }
 


Test patch:
diff --git a/ethers-contract/tests/it/contract.rs b/ethers-contract/tests/it/contract.rs
index 88f04ed5..eb5b0c8b 100644
--- a/ethers-contract/tests/it/contract.rs
+++ b/ethers-contract/tests/it/contract.rs
@@ -1,14 +1,18 @@
 use crate::common::*;
 use ethers_contract::{
-    abigen, ContractFactory, ContractInstance, EthEvent, LogMeta, Multicall, MulticallError,
-    MulticallVersion,
+    abigen, ContractFactory, ContractInstance, Eip712, EthAbiType, EthEvent, LogMeta, Multicall,
+    MulticallError, MulticallVersion,
 };
 use ethers_core::{
     abi::{encode, AbiEncode, Token, Tokenizable},
-    types::{Address, BlockId, Bytes, Filter, ValueOrArray, H160, H256, U256},
+    types::{
+        transaction::eip712::*, Address, BlockId, Bytes, Filter, ValueOrArray, H160, H256, I256,
+        U256,
+    },
     utils::{keccak256, Anvil},
 };
-use ethers_providers::{Http, Middleware, MiddlewareError, Provider, StreamExt};
+use ethers_providers::{Http, Middleware, MiddlewareError, Provider, StreamExt, Ws};
+use ethers_signers::{LocalWallet, Signer};
 use std::{sync::Arc, time::Duration};
 
 #[derive(Debug)]
@@ -337,7 +341,7 @@ async fn watch_events() {
     let mut stream = event.stream().await.unwrap();
 
     // Also set up a subscription for the same thing
-    let ws = Provider::connect(anvil.ws_endpoint()).await.unwrap();
+    let ws = Provider::<Ws>::connect(anvil.ws_endpoint()).await.unwrap();
     let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws.into());
     let event2 = contract2.event::<ValueChanged>();
     let mut subscription = event2.subscribe().await.unwrap();
@@ -376,7 +380,7 @@ async fn watch_subscription_events_multiple_addresses() {
     let contract_1 = deploy(client.clone(), abi.clone(), bytecode.clone()).await;
     let contract_2 = deploy(client.clone(), abi.clone(), bytecode).await;
 
-    let ws = Provider::connect(anvil.ws_endpoint()).await.unwrap();
+    let ws = Provider::<Ws>::connect(anvil.ws_endpoint()).await.unwrap();
     let filter = Filter::new()
         .address(ValueOrArray::Array(vec![contract_1.address(), contract_2.address()]));
     let mut stream = ws.subscribe_logs(&filter).await.unwrap();
@@ -779,3 +783,129 @@ async fn multicall_aggregate() {
     assert_eq!(bytes[..4], keccak256("CustomErrorWithData(string)")[..4]);
     assert_eq!(bytes[4..], encode(&[Token::String("Data".to_string())]));
 }
+
+#[tokio::test]
+async fn test_derive_eip712() {
+    // Generate Contract ABI Bindings
+    mod contract {
+        ethers_contract::abigen!(
+            DeriveEip712Test,
+            "./ethers-contract/tests/solidity-contracts/DeriveEip712Test.json",
+            derives(serde::Deserialize, serde::Serialize)
+        );
+    }
+
+    // Create derived structs
+
+    #[derive(Debug, Clone, Eip712, EthAbiType)]
+    #[eip712(
+        name = "Eip712Test",
+        version = "1",
+        chain_id = 1,
+        verifying_contract = "0x0000000000000000000000000000000000000001",
+        salt = "eip712-test-75F0CCte"
+    )]
+    struct FooBar {
+        foo: I256,
+        bar: U256,
+        fizz: Bytes,
+        buzz: [u8; 32],
+        far: String,
+        out: Address,
+    }
+
+    // launch the network & connect to it
+    let anvil = Anvil::new().spawn();
+    let wallet: LocalWallet = anvil.keys()[0].clone().into();
+    let provider = Provider::try_from(anvil.endpoint())
+        .unwrap()
+        .with_sender(wallet.address())
+        .interval(std::time::Duration::from_millis(10));
+    let client = Arc::new(provider);
+
+    let contract: contract::DeriveEip712Test<_> =
+        contract::DeriveEip712Test::deploy(client.clone(), ()).unwrap().send().await.unwrap();
+
+    let foo_bar = FooBar {
+        foo: I256::from(10u64),
+        bar: U256::from(20u64),
+        fizz: b"fizz".into(),
+        buzz: keccak256("buzz"),
+        far: String::from("space"),
+        out: Address::zero(),
+    };
+
+    let derived_foo_bar = contract::FooBar {
+        foo: foo_bar.foo,
+        bar: foo_bar.bar,
+        fizz: foo_bar.fizz.clone(),
+        buzz: foo_bar.buzz,
+        far: foo_bar.far.clone(),
+        out: foo_bar.out,
+    };
+
+    let sig = wallet.sign_typed_data(&foo_bar).await.expect("failed to sign typed data");
+
+    let mut r = [0; 32];
+    sig.r.to_big_endian(&mut r);
+    let mut s = [0; 32];
+    sig.s.to_big_endian(&mut s);
+    let v = sig.v as u8;
+
+    let domain_separator = contract
+        .domain_separator()
+        .call()
+        .await
+        .expect("failed to retrieve domain_separator from contract");
+    let type_hash =
+        contract.type_hash().call().await.expect("failed to retrieve type_hash from contract");
+    let struct_hash = contract
+        .struct_hash(derived_foo_bar.clone())
+        .call()
+        .await
+        .expect("failed to retrieve struct_hash from contract");
+    let encoded = contract
+        .encode_eip_712(derived_foo_bar.clone())
+        .call()
+        .await
+        .expect("failed to retrieve eip712 encoded hash from contract");
+    let verify = contract
+        .verify_foo_bar(wallet.address(), derived_foo_bar, r, s, v)
+        .call()
+        .await
+        .expect("failed to verify signed typed data eip712 payload");
+
+    assert_eq!(
+        domain_separator,
+        foo_bar
+            .domain()
+            .expect("failed to return domain_separator from Eip712 implemented struct")
+            .separator(),
+        "domain separator does not match contract domain separator!"
+    );
+
+    assert_eq!(
+        type_hash,
+        FooBar::type_hash().expect("failed to return type_hash from Eip712 implemented struct"),
+        "type hash does not match contract struct type hash!"
+    );
+
+    assert_eq!(
+        struct_hash,
+        foo_bar
+            .clone()
+            .struct_hash()
+            .expect("failed to return struct_hash from Eip712 implemented struct"),
+        "struct hash does not match contract struct hash!"
+    );
+
+    assert_eq!(
+        encoded,
+        foo_bar
+            .encode_eip712()
+            .expect("failed to return domain_separator from Eip712 implemented struct"),
+        "Encoded value does not match!"
+    );
+
+    assert!(verify, "typed data signature failed!");
+}
diff --git a/ethers-core/ethers-derive-eip712/tests/derive_eip712.rs b/ethers-contract/tests/it/eip712.rs
similarity index 97%
rename from ethers-core/ethers-derive-eip712/tests/derive_eip712.rs
rename to ethers-contract/tests/it/eip712.rs
index 7018f1dc..82ded36a 100644
--- a/ethers-core/ethers-derive-eip712/tests/derive_eip712.rs
+++ b/ethers-contract/tests/it/eip712.rs
@@ -1,6 +1,4 @@
-#![allow(clippy::extra_unused_type_parameters)]
-
-use ethers_contract_derive::EthAbiType;
+use ethers_contract_derive::{Eip712, EthAbiType};
 use ethers_core::{
     types::{
         transaction::eip712::{
@@ -11,7 +9,6 @@ use ethers_core::{
     },
     utils::{keccak256, parse_ether},
 };
-use ethers_derive_eip712::*;
 
 #[test]
 fn test_derive_eip712() {
diff --git a/ethers-contract/tests/it/main.rs b/ethers-contract/tests/it/main.rs
index 135d053a..9e1f2888 100644
--- a/ethers-contract/tests/it/main.rs
+++ b/ethers-contract/tests/it/main.rs
@@ -7,6 +7,8 @@ mod derive;
 
 mod contract_call;
 
+mod eip712;
+
 #[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
 mod common;
 
diff --git a/ethers-contract/tests/solidity-contracts/DeriveEip712Test.json b/ethers-contract/tests/solidity-contracts/DeriveEip712Test.json
new file mode 100644
index 00000000..c9865358
--- /dev/null
+++ b/ethers-contract/tests/solidity-contracts/DeriveEip712Test.json
@@ -0,0 +1,1 @@
+{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"int256","name":"foo","type":"int256"},{"internalType":"uint256","name":"bar","type":"uint256"},{"internalType":"bytes","name":"fizz","type":"bytes"},{"internalType":"bytes32","name":"buzz","type":"bytes32"},{"internalType":"string","name":"far","type":"string"},{"internalType":"address","name":"out","type":"address"}],"internalType":"struct DeriveEip712Test.FooBar","name":"fooBar","type":"tuple"}],"name":"encodeEip712","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"int256","name":"foo","type":"int256"},{"internalType":"uint256","name":"bar","type":"uint256"},{"internalType":"bytes","name":"fizz","type":"bytes"},{"internalType":"bytes32","name":"buzz","type":"bytes32"},{"internalType":"string","name":"far","type":"string"},{"internalType":"address","name":"out","type":"address"}],"internalType":"struct DeriveEip712Test.FooBar","name":"fooBar","type":"tuple"}],"name":"structHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"typeHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"components":[{"internalType":"int256","name":"foo","type":"int256"},{"internalType":"uint256","name":"bar","type":"uint256"},{"internalType":"bytes","name":"fizz","type":"bytes"},{"internalType":"bytes32","name":"buzz","type":"bytes32"},{"internalType":"string","name":"far","type":"string"},{"internalType":"address","name":"out","type":"address"}],"internalType":"struct DeriveEip712Test.FooBar","name":"fooBar","type":"tuple"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"name":"verifyFooBar","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"}],"bytecode":{"object":"0x608060405234801561001057600080fd5b50610588806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630e81d1ea1461005c5780631a82ff96146100825780634992602814610095578063d8ef409b146100bb578063f698da25146100de575b600080fd5b61006f61006a366004610499565b6100e6565b6040519081526020015b60405180910390f35b61006f610090366004610499565b610135565b7f444a4f3bd4b9709dad515de69ec13a80dfaf37cdaff8705e0b7ad3b050f8a05d61006f565b6100ce6100c93660046104d6565b6101c2565b6040519015158152602001610079565b61006f610246565b60006100f0610246565b6100f983610135565b60405161190160f01b6020820152602281019290925260428201526062015b604051602081830303815290604052805190602001209050919050565b60007f444a4f3bd4b9709dad515de69ec13a80dfaf37cdaff8705e0b7ad3b050f8a05d82516020808501516040808701518051908401206060808901516080808b015180519088012060a0808d01518751998a019b909b52958801989098529186019490945284015282015260c08101919091526001600160a01b0390911660e082015261010001610118565b600060016101cf866100e6565b6040805160008152602081018083529290925260ff851690820152606081018690526080810185905260a0016020604051602081039080840390855afa15801561021d573d6000803e3d6000fd5b505050602060405103516001600160a01b0316866001600160a01b031614905095945050505050565b604080517fd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac5647260208201527f980de4e67a47e184738cefeb37eafc9ae849b00443ea6d49487ca4488633f4fd918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6606082015260016080820181905260a08201527f6f6f42aa4c3c5871ad1af387553372290fd350e98f6d38559f840f21b79da02e60c082015260009060e00160405160208183030381529060405280519060200120905090565b634e487b7160e01b600052604160045260246000fd5b60405160c0810167ffffffffffffffff8111828210171561034c5761034c610313565b60405290565b600082601f83011261036357600080fd5b813567ffffffffffffffff8082111561037e5761037e610313565b604051601f8301601f19908116603f011681019082821181831017156103a6576103a6610313565b816040528381528660208588010111156103bf57600080fd5b836020870160208301376000602085830101528094505050505092915050565b80356001600160a01b03811681146103f657600080fd5b919050565b600060c0828403121561040d57600080fd5b610415610329565b90508135815260208201356020820152604082013567ffffffffffffffff8082111561044057600080fd5b61044c85838601610352565b604084015260608401356060840152608084013591508082111561046f57600080fd5b5061047c84828501610352565b60808301525061048e60a083016103df565b60a082015292915050565b6000602082840312156104ab57600080fd5b813567ffffffffffffffff8111156104c257600080fd5b6104ce848285016103fb565b949350505050565b600080600080600060a086880312156104ee57600080fd5b6104f7866103df565b9450602086013567ffffffffffffffff81111561051357600080fd5b61051f888289016103fb565b9450506040860135925060608601359150608086013560ff8116811461054457600080fd5b80915050929550929590935056fea2646970667358221220cf29bf472d8a14fac807f7e952af4f1e27c3ec66e38093f54930dfc99f473b3264736f6c63430008130033","sourceMap":"101:2177:0:-:0;;;737:23;;;;;;;;;;101:2177;;;;;;","linkReferences":{}}}
diff --git a/ethers/tests/DeriveEip712Test.sol b/ethers-contract/tests/solidity-contracts/DeriveEip712Test.sol
similarity index 97%
rename from ethers/tests/DeriveEip712Test.sol
rename to ethers-contract/tests/solidity-contracts/DeriveEip712Test.sol
index 0aced88b..311c8e38 100644
--- a/ethers/tests/DeriveEip712Test.sol
+++ b/ethers-contract/tests/solidity-contracts/DeriveEip712Test.sol
@@ -2,6 +2,7 @@
 pragma solidity >=0.6.0;
 pragma experimental ABIEncoderV2;
 
+// note that this file is not synced with DeriveEip712Test.json
 contract DeriveEip712Test {
     uint256 constant chainId = 1;
     bytes32 constant salt = keccak256("eip712-test-75F0CCte");
diff --git a/ethers-contract/tests/solidity-contracts/derive_eip712_abi.json b/ethers-contract/tests/solidity-contracts/derive_eip712_abi.json
deleted file mode 100644
index a978024e..00000000
--- a/ethers-contract/tests/solidity-contracts/derive_eip712_abi.json
+++ /dev/null
@@ -1,206 +0,0 @@
-[
-	{
-		"inputs": [],
-		"stateMutability": "nonpayable",
-		"type": "constructor"
-	},
-	{
-		"inputs": [],
-		"name": "domainSeparator",
-		"outputs": [
-			{
-				"internalType": "bytes32",
-				"name": "",
-				"type": "bytes32"
-			}
-		],
-		"stateMutability": "pure",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"components": [
-					{
-						"internalType": "int256",
-						"name": "foo",
-						"type": "int256"
-					},
-					{
-						"internalType": "uint256",
-						"name": "bar",
-						"type": "uint256"
-					},
-					{
-						"internalType": "bytes",
-						"name": "fizz",
-						"type": "bytes"
-					},
-					{
-						"internalType": "bytes32",
-						"name": "buzz",
-						"type": "bytes32"
-					},
-					{
-						"internalType": "string",
-						"name": "far",
-						"type": "string"
-					},
-					{
-						"internalType": "address",
-						"name": "out",
-						"type": "address"
-					}
-				],
-				"internalType": "struct DeriveEip712Test.FooBar",
-				"name": "fooBar",
-				"type": "tuple"
-			}
-		],
-		"name": "encodeEip712",
-		"outputs": [
-			{
-				"internalType": "bytes32",
-				"name": "",
-				"type": "bytes32"
-			}
-		],
-		"stateMutability": "pure",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"components": [
-					{
-						"internalType": "int256",
-						"name": "foo",
-						"type": "int256"
-					},
-					{
-						"internalType": "uint256",
-						"name": "bar",
-						"type": "uint256"
-					},
-					{
-						"internalType": "bytes",
-						"name": "fizz",
-						"type": "bytes"
-					},
-					{
-						"internalType": "bytes32",
-						"name": "buzz",
-						"type": "bytes32"
-					},
-					{
-						"internalType": "string",
-						"name": "far",
-						"type": "string"
-					},
-					{
-						"internalType": "address",
-						"name": "out",
-						"type": "address"
-					}
-				],
-				"internalType": "struct DeriveEip712Test.FooBar",
-				"name": "fooBar",
-				"type": "tuple"
-			}
-		],
-		"name": "structHash",
-		"outputs": [
-			{
-				"internalType": "bytes32",
-				"name": "",
-				"type": "bytes32"
-			}
-		],
-		"stateMutability": "pure",
-		"type": "function"
-	},
-	{
-		"inputs": [],
-		"name": "typeHash",
-		"outputs": [
-			{
-				"internalType": "bytes32",
-				"name": "",
-				"type": "bytes32"
-			}
-		],
-		"stateMutability": "pure",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "address",
-				"name": "signer",
-				"type": "address"
-			},
-			{
-				"components": [
-					{
-						"internalType": "int256",
-						"name": "foo",
-						"type": "int256"
-					},
-					{
-						"internalType": "uint256",
-						"name": "bar",
-						"type": "uint256"
-					},
-					{
-						"internalType": "bytes",
-						"name": "fizz",
-						"type": "bytes"
-					},
-					{
-						"internalType": "bytes32",
-						"name": "buzz",
-						"type": "bytes32"
-					},
-					{
-						"internalType": "string",
-						"name": "far",
-						"type": "string"
-					},
-					{
-						"internalType": "address",
-						"name": "out",
-						"type": "address"
-					}
-				],
-				"internalType": "struct DeriveEip712Test.FooBar",
-				"name": "fooBar",
-				"type": "tuple"
-			},
-			{
-				"internalType": "bytes32",
-				"name": "r",
-				"type": "bytes32"
-			},
-			{
-				"internalType": "bytes32",
-				"name": "s",
-				"type": "bytes32"
-			},
-			{
-				"internalType": "uint8",
-				"name": "v",
-				"type": "uint8"
-			}
-		],
-		"name": "verifyFooBar",
-		"outputs": [
-			{
-				"internalType": "bool",
-				"name": "",
-				"type": "bool"
-			}
-		],
-		"stateMutability": "pure",
-		"type": "function"
-	}
-]
\ No newline at end of file
diff --git a/ethers-etherscan/tests/it/gas.rs b/ethers-etherscan/tests/it/gas.rs
index a8754ee5..7c8e548e 100644
--- a/ethers-etherscan/tests/it/gas.rs
+++ b/ethers-etherscan/tests/it/gas.rs
@@ -34,9 +34,9 @@ async fn gas_oracle_success() {
 
         let oracle = result.unwrap();
 
-        assert!(oracle.safe_gas_price > 0);
-        assert!(oracle.propose_gas_price > 0);
-        assert!(oracle.fast_gas_price > 0);
+        assert!(oracle.safe_gas_price > 0f64);
+        assert!(oracle.propose_gas_price > 0f64);
+        assert!(oracle.fast_gas_price > 0f64);
         assert!(oracle.last_block > 0);
         assert!(oracle.suggested_base_fee > 0.0);
         assert!(!oracle.gas_used_ratio.is_empty());
diff --git a/ethers-middleware/tests/it/gas_oracle.rs b/ethers-middleware/tests/it/gas_oracle.rs
index 14aee0a3..5abb5ca9 100644
--- a/ethers-middleware/tests/it/gas_oracle.rs
+++ b/ethers-middleware/tests/it/gas_oracle.rs
@@ -1,5 +1,8 @@
 use async_trait::async_trait;
-use ethers_core::{types::*, utils::Anvil};
+use ethers_core::{
+    types::*,
+    utils::{parse_ether, Anvil},
+};
 use ethers_etherscan::Client;
 use ethers_middleware::gas_oracle::{
     BlockNative, Etherchain, Etherscan, GasCategory, GasNow, GasOracle, GasOracleError,
@@ -102,6 +105,8 @@ async fn etherscan() {
 
     let gas_price = etherscan_oracle.fetch().await.unwrap();
     assert!(gas_price > U256::zero());
+    let ten_ethers = parse_ether(10).unwrap();
+    assert!(gas_price < ten_ethers, "gas calculation is wrong (too high)");
 }
 
 #[tokio::test]
diff --git a/ethers/tests/eip712.rs b/ethers/tests/eip712.rs
deleted file mode 100644
index 869bb635..00000000
--- a/ethers/tests/eip712.rs
+++ /dev/null
@@ -1,158 +0,0 @@
-use ethers::{
-    contract::{abigen, ContractFactory, Eip712, EthAbiType},
-    core::{
-        types::{transaction::eip712::Eip712, Address, Bytes, I256, U256},
-        utils::{keccak256, Anvil},
-    },
-    providers::Provider,
-    signers::LocalWallet,
-    solc::Solc,
-};
-use std::{path::PathBuf, sync::Arc};
-
-#[tokio::test(flavor = "multi_thread")]
-async fn test_derive_eip712() {
-    // Generate Contract ABI Bindings
-    abigen!(
-        DeriveEip712Test,
-        "./ethers-contract/tests/solidity-contracts/derive_eip712_abi.json",
-        event_derives(serde::Deserialize, serde::Serialize)
-    );
-
-    // Create derived structs
-
-    #[derive(Debug, Clone, Eip712, EthAbiType)]
-    #[eip712(
-        name = "Eip712Test",
-        version = "1",
-        chain_id = 1,
-        verifying_contract = "0x0000000000000000000000000000000000000001",
-        salt = "eip712-test-75F0CCte"
-    )]
-    struct FooBar {
-        foo: I256,
-        bar: U256,
-        fizz: Bytes,
-        buzz: [u8; 32],
-        far: String,
-        out: Address,
-    }
-
-    // get ABI and bytecode for the DeriveEip712Test contract
-    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests");
-    Solc::find_or_install_svm_version("0.6.0").unwrap(); // install solc
-    let result = Solc::default().compile_source(path).unwrap();
-    let (abi, bytecode, _) = result
-        .find("DeriveEip712Test")
-        .expect("failed to get DeriveEip712Test contract")
-        .into_parts_or_default();
-
-    // launch the network & connect to it
-    let anvil = Anvil::new().spawn();
-    let from = anvil.addresses()[0];
-    let provider = Provider::try_from(anvil.endpoint())
-        .unwrap()
-        .with_sender(from)
-        .interval(std::time::Duration::from_millis(10));
-    let client = Arc::new(provider);
-
-    let factory = ContractFactory::new(abi.clone(), bytecode.clone(), client.clone());
-
-    let contract = factory
-        .deploy(())
-        .expect("failed to deploy DeriveEip712Test contract")
-        .legacy()
-        .send()
-        .await
-        .expect("failed to instantiate factory for DeriveEip712 contract");
-
-    let addr = contract.address();
-
-    let contract = DeriveEip712Test::new(addr, client.clone());
-
-    let foo_bar = FooBar {
-        foo: I256::from(10u64),
-        bar: U256::from(20u64),
-        fizz: b"fizz".into(),
-        buzz: keccak256("buzz"),
-        far: String::from("space"),
-        out: Address::from([0; 20]),
-    };
-
-    let derived_foo_bar = derive_eip_712_test::FooBar {
-        foo: foo_bar.foo,
-        bar: foo_bar.bar,
-        fizz: foo_bar.fizz.clone(),
-        buzz: foo_bar.buzz,
-        far: foo_bar.far.clone(),
-        out: foo_bar.out,
-    };
-
-    use ethers::signers::Signer;
-
-    let wallet: LocalWallet = anvil.keys()[0].clone().into();
-    let sig = wallet.sign_typed_data(&foo_bar).await.expect("failed to sign typed data");
-
-    let r = <[u8; 32]>::try_from(sig.r)
-        .expect("failed to parse 'r' value from signature into [u8; 32]");
-    let s = <[u8; 32]>::try_from(sig.s)
-        .expect("failed to parse 's' value from signature into [u8; 32]");
-    let v = u8::try_from(sig.v).expect("failed to parse 'v' value from signature into u8");
-
-    let domain_separator = contract
-        .domain_separator()
-        .call()
-        .await
-        .expect("failed to retrieve domain_separator from contract");
-    let type_hash =
-        contract.type_hash().call().await.expect("failed to retrieve type_hash from contract");
-    let struct_hash = contract
-        .struct_hash(derived_foo_bar.clone())
-        .call()
-        .await
-        .expect("failed to retrieve struct_hash from contract");
-    let encoded = contract
-        .encode_eip_712(derived_foo_bar.clone())
-        .call()
-        .await
-        .expect("failed to retrieve eip712 encoded hash from contract");
-    let verify = contract
-        .verify_foo_bar(wallet.address(), derived_foo_bar, r, s, v)
-        .call()
-        .await
-        .expect("failed to verify signed typed data eip712 payload");
-
-    assert_eq!(
-        domain_separator,
-        foo_bar
-            .domain()
-            .expect("failed to return domain_separator from Eip712 implemented struct")
-            .separator(),
-        "domain separator does not match contract domain separator!"
-    );
-
-    assert_eq!(
-        type_hash,
-        FooBar::type_hash().expect("failed to return type_hash from Eip712 implemented struct"),
-        "type hash does not match contract struct type hash!"
-    );
-
-    assert_eq!(
-        struct_hash,
-        foo_bar
-            .clone()
-            .struct_hash()
-            .expect("failed to return struct_hash from Eip712 implemented struct"),
-        "struct hash does not match contract struct hash!"
-    );
-
-    assert_eq!(
-        encoded,
-        foo_bar
-            .encode_eip712()
-            .expect("failed to return domain_separator from Eip712 implemented struct"),
-        "Encoded value does not match!"
-    );
-
-    assert!(verify, "typed data signature failed!");
-}
diff --git a/ethers/tests/main.rs b/ethers/tests/main.rs
deleted file mode 100644
index d4845162..00000000
--- a/ethers/tests/main.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-//! Ethers integration tests.
-#![cfg(not(target_arch = "wasm32"))]
-
-mod eip712;