From 21930ed2019219c2ffd57c08c0bf675db467a91f Mon Sep 17 00:00:00 2001
From: PG Herveou <pgherveou@gmail.com>
Date: Tue, 22 Oct 2024 12:27:30 +0200
Subject: [PATCH] [pallet-revive] Eth RPC integration (#5866)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR introduces the necessary changes to pallet-revive for
integrating with our Ethereum JSON-RPC.
The RPC proxy itself will be added in a follow up.

## Changes

- A new pallet::call `Call::eth_transact`. This is used as a wrapper to
accept unsigned Ethereum transaction, valid call will be routed to
`Call::call` or `Call::instantiate_with_code`

- A custom UncheckedExtrinsic struct, that wraps the generic one usually
and add the ability to check eth_transact calls sent from an Ethereum
JSON-RPC proxy.
- Generated types and traits to support implementing a JSON-RPC Ethereum
proxy.

## Flow Overview:
- A user submits a transaction via MetaMask or another
Ethereum-compatible wallet.
- The proxy dry run the transaction and add metadata to the call (gas
limit in Weight, storage deposit limit, and length of bytecode and
constructor input for contract instantiation)
- The raw transaction, along with the additional metadata, is submitted
to the node as an unsigned extrinsic.
- On the runtime, our custom UncheckedExtrinsic define a custom
Checkable implementation that converts the unsigned extrinsics into
checked one
 - It recovers the signer
- validates the payload, and injects signed extensions, allowing the
system to increment the nonce and charge the appropriate fees.
- re-route the call to pallet-revive::Call::call or
pallet-revive::Call::instantiateWithCode

## Dependencies

- https://github.com/koute/polkavm/pull/188

## Follow up PRs
- #5926
- #6147 (previously #5953)
- #5502

---------

Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: Cyrill Leutwiler <cyrill@parity.io>
---
 Cargo.lock                                    | 171 ++++-
 prdoc/pr_5866.prdoc                           |  23 +
 substrate/bin/node/cli/Cargo.toml             |   3 +-
 .../bin/node/cli/benches/block_production.rs  |  10 +-
 substrate/bin/node/cli/src/chain_spec.rs      |  33 +-
 substrate/bin/node/cli/src/service.rs         |  19 +-
 substrate/bin/node/cli/tests/basic.rs         |   8 +-
 substrate/bin/node/cli/tests/fees.rs          |   2 +-
 .../bin/node/cli/tests/submit_transaction.rs  |   9 +-
 substrate/bin/node/runtime/Cargo.toml         |   2 +
 substrate/bin/node/runtime/src/lib.rs         |  79 +-
 substrate/bin/node/testing/Cargo.toml         |   1 +
 substrate/bin/node/testing/src/bench.rs       |  15 +-
 substrate/bin/node/testing/src/keyring.rs     |  15 +-
 substrate/frame/revive/Cargo.toml             |  41 +-
 substrate/frame/revive/fixtures/Cargo.toml    |  11 +-
 substrate/frame/revive/fixtures/build.rs      |   9 +-
 .../frame/revive/fixtures/build/Cargo.toml    |   2 +-
 substrate/frame/revive/fixtures/src/lib.rs    |  23 +-
 .../frame/revive/mock-network/Cargo.toml      |  14 +
 substrate/frame/revive/src/address.rs         |   2 +-
 substrate/frame/revive/src/evm.rs             |  22 +
 substrate/frame/revive/src/evm/api.rs         |  38 +
 substrate/frame/revive/src/evm/api/account.rs |  51 ++
 substrate/frame/revive/src/evm/api/byte.rs    | 154 ++++
 .../frame/revive/src/evm/api/rlp_codec.rs     | 219 ++++++
 .../frame/revive/src/evm/api/rpc_types.rs     |  32 +
 .../frame/revive/src/evm/api/rpc_types_gen.rs | 682 +++++++++++++++++
 .../frame/revive/src/evm/api/signature.rs     |  80 ++
 substrate/frame/revive/src/evm/api/type_id.rs |  95 +++
 substrate/frame/revive/src/evm/runtime.rs     | 685 ++++++++++++++++++
 substrate/frame/revive/src/exec.rs            |  15 +-
 substrate/frame/revive/src/lib.rs             | 271 ++++++-
 substrate/frame/revive/src/primitives.rs      |  21 +-
 .../frame/revive/src/test_utils/builder.rs    |   9 +-
 substrate/frame/revive/src/tests.rs           |  36 +-
 substrate/frame/revive/src/wasm/mod.rs        |   5 +-
 substrate/frame/revive/src/wasm/runtime.rs    |  21 +-
 substrate/frame/revive/uapi/Cargo.toml        |   2 +-
 umbrella/Cargo.toml                           |   2 +-
 40 files changed, 2769 insertions(+), 163 deletions(-)
 create mode 100644 prdoc/pr_5866.prdoc
 create mode 100644 substrate/frame/revive/src/evm.rs
 create mode 100644 substrate/frame/revive/src/evm/api.rs
 create mode 100644 substrate/frame/revive/src/evm/api/account.rs
 create mode 100644 substrate/frame/revive/src/evm/api/byte.rs
 create mode 100644 substrate/frame/revive/src/evm/api/rlp_codec.rs
 create mode 100644 substrate/frame/revive/src/evm/api/rpc_types.rs
 create mode 100644 substrate/frame/revive/src/evm/api/rpc_types_gen.rs
 create mode 100644 substrate/frame/revive/src/evm/api/signature.rs
 create mode 100644 substrate/frame/revive/src/evm/api/type_id.rs
 create mode 100644 substrate/frame/revive/src/evm/runtime.rs

diff --git a/Cargo.lock b/Cargo.lock
index e2cce727856..a42f5baa476 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1619,6 +1619,22 @@ dependencies = [
  "syn 2.0.82",
 ]
 
+[[package]]
+name = "bip32"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa13fae8b6255872fd86f7faf4b41168661d7d78609f7bfe6771b85c6739a15b"
+dependencies = [
+ "bs58",
+ "hmac 0.12.1",
+ "k256",
+ "rand_core 0.6.4",
+ "ripemd",
+ "sha2 0.10.8",
+ "subtle 2.5.0",
+ "zeroize",
+]
+
 [[package]]
 name = "bip39"
 version = "2.0.0"
@@ -2571,6 +2587,7 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
 dependencies = [
+ "sha2 0.10.8",
  "tinyvec",
 ]
 
@@ -6798,6 +6815,10 @@ name = "futures-timer"
 version = "3.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
+dependencies = [
+ "gloo-timers",
+ "send_wrapper 0.4.0",
+]
 
 [[package]]
 name = "futures-util"
@@ -6933,6 +6954,27 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d"
 
+[[package]]
+name = "gloo-net"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-sink",
+ "gloo-utils",
+ "http 1.1.0",
+ "js-sys",
+ "pin-project",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
 [[package]]
 name = "gloo-timers"
 version = "0.2.6"
@@ -6945,6 +6987,19 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "gloo-utils"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
+dependencies = [
+ "js-sys",
+ "serde",
+ "serde_json",
+ "wasm-bindgen",
+ "web-sys",
+]
+
 [[package]]
 name = "glutton-westend-runtime"
 version = "3.0.0"
@@ -8046,11 +8101,13 @@ version = "0.24.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5ec465b607a36dc5dd45d48b7689bc83f679f66a3ac6b6b21cc787a11e0f8685"
 dependencies = [
+ "jsonrpsee-client-transport 0.24.3",
  "jsonrpsee-core 0.24.3",
  "jsonrpsee-http-client 0.24.3",
  "jsonrpsee-proc-macros",
  "jsonrpsee-server",
  "jsonrpsee-types 0.24.3",
+ "jsonrpsee-wasm-client",
  "jsonrpsee-ws-client 0.24.3",
  "tokio",
  "tracing",
@@ -8107,7 +8164,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "90f0977f9c15694371b8024c35ab58ca043dbbf4b51ccb03db8858a021241df1"
 dependencies = [
  "base64 0.22.1",
+ "futures-channel",
  "futures-util",
+ "gloo-net",
  "http 1.1.0",
  "jsonrpsee-core 0.24.3",
  "pin-project",
@@ -8192,6 +8251,7 @@ dependencies = [
  "tokio",
  "tokio-stream",
  "tracing",
+ "wasm-bindgen-futures",
 ]
 
 [[package]]
@@ -8317,6 +8377,17 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "jsonrpsee-wasm-client"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0470d0ae043ffcb0cd323797a631e637fb4b55fe3eaa6002934819458bba62a7"
+dependencies = [
+ "jsonrpsee-client-transport 0.24.3",
+ "jsonrpsee-core 0.24.3",
+ "jsonrpsee-types 0.24.3",
+]
+
 [[package]]
 name = "jsonrpsee-ws-client"
 version = "0.23.2"
@@ -8380,6 +8451,16 @@ dependencies = [
  "cpufeatures",
 ]
 
+[[package]]
+name = "keccak-hash"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b286e6b663fb926e1eeb68528e69cb70ed46c6d65871a21b2215ae8154c6d3c"
+dependencies = [
+ "primitive-types 0.12.2",
+ "tiny-keccak",
+]
+
 [[package]]
 name = "keccak-hasher"
 version = "0.16.0"
@@ -8410,6 +8491,7 @@ dependencies = [
  "primitive-types 0.13.1",
  "scale-info",
  "serde_json",
+ "sp-debug-derive 14.0.0",
  "static_assertions",
  "substrate-wasm-builder",
 ]
@@ -9045,7 +9127,7 @@ dependencies = [
  "futures",
  "js-sys",
  "libp2p-core",
- "send_wrapper",
+ "send_wrapper 0.6.0",
  "wasm-bindgen",
  "wasm-bindgen-futures",
 ]
@@ -10235,6 +10317,7 @@ dependencies = [
  "pallet-asset-conversion-tx-payment",
  "pallet-asset-tx-payment",
  "pallet-assets",
+ "pallet-revive",
  "pallet-skip-feeless-payment",
  "parity-scale-codec",
  "sc-block-builder",
@@ -12340,11 +12423,16 @@ dependencies = [
  "array-bytes",
  "assert_matches",
  "bitflags 1.3.2",
+ "derive_more",
  "environmental",
+ "ethereum-types",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
+ "hex",
+ "hex-literal",
  "impl-trait-for-tuples",
+ "jsonrpsee 0.24.3",
  "log",
  "pallet-assets",
  "pallet-balances",
@@ -12354,25 +12442,30 @@ dependencies = [
  "pallet-revive-proc-macro",
  "pallet-revive-uapi",
  "pallet-timestamp",
+ "pallet-transaction-payment",
  "pallet-utility",
  "parity-scale-codec",
  "paste",
- "polkavm 0.12.0",
- "polkavm-common 0.12.0",
+ "polkavm 0.13.0",
+ "polkavm-common 0.13.0",
  "pretty_assertions",
  "rlp 0.6.1",
  "scale-info",
+ "secp256k1",
  "serde",
+ "serde_json",
  "sp-api 26.0.0",
+ "sp-arithmetic 23.0.0",
  "sp-core 28.0.0",
  "sp-io 30.0.0",
  "sp-keystore 0.34.0",
  "sp-runtime 31.0.1",
  "sp-std 14.0.0",
  "sp-tracing 16.0.0",
+ "sp-weights 27.0.0",
  "staging-xcm",
  "staging-xcm-builder",
- "wat",
+ "subxt-signer",
 ]
 
 [[package]]
@@ -12383,7 +12476,7 @@ dependencies = [
  "frame-system",
  "log",
  "parity-wasm",
- "polkavm-linker 0.12.0",
+ "polkavm-linker 0.13.0",
  "sp-core 28.0.0",
  "sp-io 30.0.0",
  "sp-runtime 31.0.1",
@@ -12443,7 +12536,7 @@ dependencies = [
  "bitflags 1.3.2",
  "parity-scale-codec",
  "paste",
- "polkavm-derive 0.12.0",
+ "polkavm-derive 0.13.0",
  "scale-info",
 ]
 
@@ -16111,15 +16204,15 @@ dependencies = [
 
 [[package]]
 name = "polkavm"
-version = "0.12.0"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f27910c5061e4cea6be6c66684b49d0f42b6a05900c9b0da9e7f3dd2d587a8d4"
+checksum = "57e79a14b15ed38cb5b9a1e38d02e933f19e3d180ae5b325fed606c5e5b9177e"
 dependencies = [
  "libc",
  "log",
- "polkavm-assembler 0.12.0",
- "polkavm-common 0.12.0",
- "polkavm-linux-raw 0.12.0",
+ "polkavm-assembler 0.13.0",
+ "polkavm-common 0.13.0",
+ "polkavm-linux-raw 0.13.0",
 ]
 
 [[package]]
@@ -16133,9 +16226,9 @@ dependencies = [
 
 [[package]]
 name = "polkavm-assembler"
-version = "0.12.0"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82f0e374fa043f31459b30d629d7e866247ac4b6c7662ac72e4e5bf50d052b92"
+checksum = "4e8da55465000feb0a61bbf556ed03024db58f3420eca37721fc726b3b2136bf"
 dependencies = [
  "log",
 ]
@@ -16157,13 +16250,13 @@ dependencies = [
 
 [[package]]
 name = "polkavm-common"
-version = "0.12.0"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4e42e082c3d89da2346555baf4d951fe07dcb9208e42a02c272e6d5d0326f9a"
+checksum = "084b4339aae7dfdaaa5aa7d634110afd95970e0737b6fb2a0cb10db8b56b753c"
 dependencies = [
  "blake3",
  "log",
- "polkavm-assembler 0.12.0",
+ "polkavm-assembler 0.13.0",
 ]
 
 [[package]]
@@ -16186,11 +16279,11 @@ dependencies = [
 
 [[package]]
 name = "polkavm-derive"
-version = "0.12.0"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "540b798393e68a890202d5dc9f86a985b7ea83611e3406d90dc1043e7997b4d1"
+checksum = "f4456b9657b2abd04ac41a61c99e206b7410f93daf0e9b42b49089508d836c40"
 dependencies = [
- "polkavm-derive-impl-macro 0.12.0",
+ "polkavm-derive-impl-macro 0.13.0",
 ]
 
 [[package]]
@@ -16219,11 +16312,11 @@ dependencies = [
 
 [[package]]
 name = "polkavm-derive-impl"
-version = "0.12.0"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d179eddaaef62ce5960faaa2ec9e8f131c81661c8b9365c4d55b275011688534"
+checksum = "5e4f2c19e7ccc53d8e21429e83b6589bd4139d15481e455a90ba4335a4decb5a"
 dependencies = [
- "polkavm-common 0.12.0",
+ "polkavm-common 0.13.0",
  "proc-macro2 1.0.86",
  "quote 1.0.37",
  "syn 2.0.82",
@@ -16251,11 +16344,11 @@ dependencies = [
 
 [[package]]
 name = "polkavm-derive-impl-macro"
-version = "0.12.0"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd35472599d35d90e24afe9eb39ae6ee6cb1b924f0c03b277ef8b5f174a63853"
+checksum = "e6f3ad876ca1855038c21d48cbe35164552208a54b21f8295a7d76bc33ef1e38"
 dependencies = [
- "polkavm-derive-impl 0.12.0",
+ "polkavm-derive-impl 0.13.0",
  "syn 2.0.82",
 ]
 
@@ -16276,15 +16369,15 @@ dependencies = [
 
 [[package]]
 name = "polkavm-linker"
-version = "0.12.0"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f917b16db9ab13819a738a321b48a2d0d20d9e32dedcff75054148676afbec4"
+checksum = "4aa6e5a396abf195289d6d63d70182e59a7c27b9b06d0b7361317df05c07c8a8"
 dependencies = [
  "gimli 0.28.0",
  "hashbrown 0.14.5",
  "log",
  "object 0.36.1",
- "polkavm-common 0.12.0",
+ "polkavm-common 0.13.0",
  "regalloc2 0.9.3",
  "rustc-demangle",
 ]
@@ -16297,9 +16390,9 @@ checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120"
 
 [[package]]
 name = "polkavm-linux-raw"
-version = "0.12.0"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d280301d5b5a321c732173c969058f4b5726f3a0046f6802f396df2599f3753d"
+checksum = "686c4dd9c9c16cc22565b51bdbb269792318d0fd2e6b966b5f6c788534cad0e9"
 
 [[package]]
 name = "polling"
@@ -17568,6 +17661,15 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "ripemd"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
+dependencies = [
+ "digest 0.10.7",
+]
+
 [[package]]
 name = "rle-decode-fast"
 version = "1.0.3"
@@ -20194,6 +20296,12 @@ dependencies = [
  "pest",
 ]
 
+[[package]]
+name = "send_wrapper"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
+
 [[package]]
 name = "send_wrapper"
 version = "0.6.0"
@@ -23293,6 +23401,7 @@ dependencies = [
  "sp-keyring",
  "staging-node-inspect",
  "substrate-cli-test-utils",
+ "subxt-signer",
  "tempfile",
  "tokio",
  "tokio-util",
@@ -24126,10 +24235,12 @@ version = "0.37.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f49888ae6ae90fe01b471193528eea5bd4ed52d8eecd2d13f4a2333b87388850"
 dependencies = [
+ "bip32",
  "bip39",
  "cfg-if",
  "hex",
  "hmac 0.12.1",
+ "keccak-hash",
  "parity-scale-codec",
  "pbkdf2",
  "regex",
diff --git a/prdoc/pr_5866.prdoc b/prdoc/pr_5866.prdoc
new file mode 100644
index 00000000000..44fffe1d212
--- /dev/null
+++ b/prdoc/pr_5866.prdoc
@@ -0,0 +1,23 @@
+title: "[pallet-revive] Ethereum JSON-RPC integration"
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      Related PR: https://github.com/paritytech/revive-ethereum-rpc/pull/5
+
+      Changes Included:
+      -  A new pallet::call  eth_transact.
+      - A custom UncheckedExtrinsic struct to dispatch unsigned eth_transact calls from an Ethereum JSON-RPC proxy.
+      - Generated types and traits to support implementing a JSON-RPC Ethereum proxy.
+crates:
+  - name: pallet-revive
+    bump: major
+  - name: pallet-revive-fixtures
+    bump: patch
+  - name: pallet-revive-mock-network 
+    bump: patch
+  - name: pallet-revive-uapi
+    bump: patch
+  - name: polkadot-sdk
+    bump: patch
+
diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml
index 2e53c18645f..933406670e5 100644
--- a/substrate/bin/node/cli/Cargo.toml
+++ b/substrate/bin/node/cli/Cargo.toml
@@ -46,6 +46,7 @@ futures = { workspace = true }
 log = { workspace = true, default-features = true }
 rand = { workspace = true, default-features = true }
 serde_json = { workspace = true, default-features = true }
+subxt-signer = { workspace = true, features = ["unstable-eth"] }
 
 # The Polkadot-SDK:
 polkadot-sdk = { features = [
@@ -56,8 +57,6 @@ polkadot-sdk = { features = [
 	"generate-bags",
 	"mmr-gadget",
 	"mmr-rpc",
-	"pallet-contracts-mock-network",
-	"pallet-revive-mock-network",
 	"pallet-transaction-payment-rpc",
 	"sc-allocator",
 	"sc-authority-discovery",
diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs
index d8041ed400c..da82729dbec 100644
--- a/substrate/bin/node/cli/benches/block_production.rs
+++ b/substrate/bin/node/cli/benches/block_production.rs
@@ -39,6 +39,7 @@ use sp_blockchain::{ApplyExtrinsicFailed::Validity, Error::ApplyExtrinsicFailed}
 use sp_consensus::BlockOrigin;
 use sp_keyring::Sr25519Keyring;
 use sp_runtime::{
+	generic,
 	transaction_validity::{InvalidTransaction, TransactionValidityError},
 	AccountId32, MultiAddress, OpaqueExtrinsic,
 };
@@ -120,10 +121,11 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
 }
 
 fn extrinsic_set_time(now: u64) -> OpaqueExtrinsic {
-	kitchensink_runtime::UncheckedExtrinsic::new_bare(kitchensink_runtime::RuntimeCall::Timestamp(
-		pallet_timestamp::Call::set { now },
-	))
-	.into()
+	let utx: kitchensink_runtime::UncheckedExtrinsic = generic::UncheckedExtrinsic::new_bare(
+		kitchensink_runtime::RuntimeCall::Timestamp(pallet_timestamp::Call::set { now }),
+	)
+	.into();
+	utx.into()
 }
 
 fn import_block(client: &FullClient, built: BuiltBlock<node_primitives::Block>) {
diff --git a/substrate/bin/node/cli/src/chain_spec.rs b/substrate/bin/node/cli/src/chain_spec.rs
index 6cd9adfd7f2..362deacba5f 100644
--- a/substrate/bin/node/cli/src/chain_spec.rs
+++ b/substrate/bin/node/cli/src/chain_spec.rs
@@ -20,6 +20,7 @@
 
 use polkadot_sdk::*;
 
+use crate::chain_spec::{sc_service::Properties, sp_runtime::AccountId32};
 use kitchensink_runtime::{
 	constants::currency::*, wasm_binary_unwrap, Block, MaxNominations, SessionKeys, StakerStatus,
 };
@@ -291,8 +292,8 @@ fn configure_accounts(
 	usize,
 	Vec<(AccountId, AccountId, Balance, StakerStatus<AccountId>)>,
 ) {
-	let mut endowed_accounts: Vec<AccountId> = endowed_accounts
-		.unwrap_or_else(|| Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect());
+	let mut endowed_accounts: Vec<AccountId> =
+		endowed_accounts.unwrap_or_else(default_endowed_accounts);
 	// endow all authorities and nominators.
 	initial_authorities
 		.iter()
@@ -417,12 +418,40 @@ fn development_config_genesis_json() -> serde_json::Value {
 	)
 }
 
+fn props() -> Properties {
+	let mut properties = Properties::new();
+	properties.insert("tokenDecimals".to_string(), 12.into());
+	properties
+}
+
+fn eth_account(from: subxt_signer::eth::Keypair) -> AccountId32 {
+	let mut account_id = AccountId32::new([0xEE; 32]);
+	<AccountId32 as AsMut<[u8; 32]>>::as_mut(&mut account_id)[..20]
+		.copy_from_slice(&from.account_id().0);
+	account_id
+}
+
+fn default_endowed_accounts() -> Vec<AccountId> {
+	Sr25519Keyring::well_known()
+		.map(|k| k.to_account_id())
+		.chain([
+			eth_account(subxt_signer::eth::dev::alith()),
+			eth_account(subxt_signer::eth::dev::baltathar()),
+			eth_account(subxt_signer::eth::dev::charleth()),
+			eth_account(subxt_signer::eth::dev::dorothy()),
+			eth_account(subxt_signer::eth::dev::ethan()),
+			eth_account(subxt_signer::eth::dev::faith()),
+		])
+		.collect()
+}
+
 /// Development config (single validator Alice).
 pub fn development_config() -> ChainSpec {
 	ChainSpec::builder(wasm_binary_unwrap(), Default::default())
 		.with_name("Development")
 		.with_id("dev")
 		.with_chain_type(ChainType::Development)
+		.with_properties(props())
 		.with_genesis_config_patch(development_config_genesis_json())
 		.build()
 }
diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs
index 00658b361df..d71f1304caf 100644
--- a/substrate/bin/node/cli/src/service.rs
+++ b/substrate/bin/node/cli/src/service.rs
@@ -158,12 +158,13 @@ pub fn create_extrinsic(
 	);
 	let signature = raw_payload.using_encoded(|e| sender.sign(e));
 
-	kitchensink_runtime::UncheckedExtrinsic::new_signed(
+	generic::UncheckedExtrinsic::new_signed(
 		function,
 		sp_runtime::AccountId32::from(sender.public()).into(),
 		kitchensink_runtime::Signature::Sr25519(signature),
 		tx_ext,
 	)
+	.into()
 }
 
 /// Creates a new partial node.
@@ -866,7 +867,7 @@ mod tests {
 	use codec::Encode;
 	use kitchensink_runtime::{
 		constants::{currency::CENTS, time::SLOT_DURATION},
-		Address, BalancesCall, RuntimeCall, TxExtension, UncheckedExtrinsic,
+		Address, BalancesCall, RuntimeCall, TxExtension,
 	};
 	use node_primitives::{Block, DigestItem, Signature};
 	use polkadot_sdk::{sc_transaction_pool_api::MaintainedTransactionPool, *};
@@ -883,7 +884,7 @@ mod tests {
 	use sp_keyring::AccountKeyring;
 	use sp_keystore::KeystorePtr;
 	use sp_runtime::{
-		generic::{Digest, Era, SignedPayload},
+		generic::{self, Digest, Era, SignedPayload},
 		key_types::BABE,
 		traits::{Block as BlockT, Header as HeaderT, IdentifyAccount, Verify},
 		RuntimeAppPublic,
@@ -1099,8 +1100,16 @@ mod tests {
 				let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
 				let (function, tx_ext, _) = raw_payload.deconstruct();
 				index += 1;
-				UncheckedExtrinsic::new_signed(function, from.into(), signature.into(), tx_ext)
-					.into()
+				let utx: kitchensink_runtime::UncheckedExtrinsic =
+					generic::UncheckedExtrinsic::new_signed(
+						function,
+						from.into(),
+						signature.into(),
+						tx_ext,
+					)
+					.into();
+
+				utx.into()
 			},
 		);
 	}
diff --git a/substrate/bin/node/cli/tests/basic.rs b/substrate/bin/node/cli/tests/basic.rs
index 616d813f78e..8f1475fce4f 100644
--- a/substrate/bin/node/cli/tests/basic.rs
+++ b/substrate/bin/node/cli/tests/basic.rs
@@ -61,14 +61,14 @@ pub fn bloaty_code_unwrap() -> &'static [u8] {
 /// correct multiplier.
 fn transfer_fee(extrinsic: &UncheckedExtrinsic) -> Balance {
 	let mut info = default_transfer_call().get_dispatch_info();
-	info.extension_weight = extrinsic.extension_weight();
+	info.extension_weight = extrinsic.0.extension_weight();
 	TransactionPayment::compute_fee(extrinsic.encode().len() as u32, &info, 0)
 }
 
 /// Default transfer fee, same as `transfer_fee`, but with a weight refund factored in.
 fn transfer_fee_with_refund(extrinsic: &UncheckedExtrinsic, weight_refund: Weight) -> Balance {
 	let mut info = default_transfer_call().get_dispatch_info();
-	info.extension_weight = extrinsic.extension_weight();
+	info.extension_weight = extrinsic.0.extension_weight();
 	let post_info = (Some(info.total_weight().saturating_sub(weight_refund)), info.pays_fee).into();
 	TransactionPayment::compute_actual_fee(extrinsic.encode().len() as u32, &info, &post_info, 0)
 }
@@ -324,7 +324,7 @@ fn full_native_block_import_works() {
 
 	let mut alice_last_known_balance: Balance = Default::default();
 	let mut fees = t.execute_with(|| transfer_fee(&xt()));
-	let extension_weight = xt().extension_weight();
+	let extension_weight = xt().0.extension_weight();
 	let weight_refund = Weight::zero();
 	let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));
 
@@ -427,7 +427,7 @@ fn full_native_block_import_works() {
 
 	fees = t.execute_with(|| transfer_fee(&xt()));
 	let pot = t.execute_with(|| Treasury::pot());
-	let extension_weight = xt().extension_weight();
+	let extension_weight = xt().0.extension_weight();
 	let weight_refund = Weight::zero();
 	let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));
 
diff --git a/substrate/bin/node/cli/tests/fees.rs b/substrate/bin/node/cli/tests/fees.rs
index 59ade9b8547..da9d2662408 100644
--- a/substrate/bin/node/cli/tests/fees.rs
+++ b/substrate/bin/node/cli/tests/fees.rs
@@ -175,7 +175,7 @@ fn transaction_fee_is_correct() {
 		balance_alice -= length_fee;
 
 		let mut info = default_transfer_call().get_dispatch_info();
-		info.extension_weight = xt.extension_weight();
+		info.extension_weight = xt.0.extension_weight();
 		let weight = info.total_weight();
 		let weight_fee = IdentityFee::<Balance>::weight_to_fee(&weight);
 
diff --git a/substrate/bin/node/cli/tests/submit_transaction.rs b/substrate/bin/node/cli/tests/submit_transaction.rs
index 3b7d82dcab1..3672432ae34 100644
--- a/substrate/bin/node/cli/tests/submit_transaction.rs
+++ b/substrate/bin/node/cli/tests/submit_transaction.rs
@@ -23,6 +23,7 @@ use sp_application_crypto::AppCrypto;
 use sp_core::offchain::{testing::TestTransactionPoolExt, TransactionPoolExt};
 use sp_keyring::sr25519::Keyring::Alice;
 use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt};
+use sp_runtime::generic;
 
 pub mod common;
 use self::common::*;
@@ -44,7 +45,7 @@ fn should_submit_unsigned_transaction() {
 		};
 
 		let call = pallet_im_online::Call::heartbeat { heartbeat: heartbeat_data, signature };
-		let xt = UncheckedExtrinsic::new_bare(call.into());
+		let xt = generic::UncheckedExtrinsic::new_bare(call.into()).into();
 		SubmitTransaction::<Runtime, pallet_im_online::Call<Runtime>>::submit_transaction(xt)
 			.unwrap();
 
@@ -130,7 +131,7 @@ fn should_submit_signed_twice_from_the_same_account() {
 		// now check that the transaction nonces are not equal
 		let s = state.read();
 		fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce<Runtime> {
-			let extra = tx.preamble.to_signed().unwrap().2;
+			let extra = tx.0.preamble.to_signed().unwrap().2;
 			extra.5
 		}
 		let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap());
@@ -179,7 +180,7 @@ fn should_submit_signed_twice_from_all_accounts() {
 		// now check that the transaction nonces are not equal
 		let s = state.read();
 		fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce<Runtime> {
-			let extra = tx.preamble.to_signed().unwrap().2;
+			let extra = tx.0.preamble.to_signed().unwrap().2;
 			extra.5
 		}
 		let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap());
@@ -236,7 +237,7 @@ fn submitted_transaction_should_be_valid() {
 		let source = TransactionSource::External;
 		let extrinsic = UncheckedExtrinsic::decode(&mut &*tx0).unwrap();
 		// add balance to the account
-		let author = extrinsic.preamble.clone().to_signed().clone().unwrap().0;
+		let author = extrinsic.0.preamble.clone().to_signed().clone().unwrap().0;
 		let address = Indices::lookup(author).unwrap();
 		let data = pallet_balances::AccountData { free: 5_000_000_000_000, ..Default::default() };
 		let account = frame_system::AccountInfo { providers: 1, data, ..Default::default() };
diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml
index 6310e16d5a1..7acf4294c51 100644
--- a/substrate/bin/node/runtime/Cargo.toml
+++ b/substrate/bin/node/runtime/Cargo.toml
@@ -27,6 +27,7 @@ scale-info = { features = ["derive", "serde"], workspace = true }
 static_assertions = { workspace = true, default-features = true }
 log = { workspace = true }
 serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true }
+sp-debug-derive = { workspace = true, features = ["force-debug"] }
 
 # pallet-asset-conversion: turn on "num-traits" feature
 primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true }
@@ -56,6 +57,7 @@ std = [
 	"primitive-types/std",
 	"scale-info/std",
 	"serde_json/std",
+	"sp-debug-derive/std",
 	"substrate-wasm-builder",
 ]
 runtime-benchmarks = [
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index a2112e5977f..d6a17856e47 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -76,6 +76,7 @@ use pallet_identity::legacy::IdentityInfo;
 use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
 use pallet_nfts::PalletFeatures;
 use pallet_nis::WithMaximumOf;
+use pallet_revive::evm::runtime::EthExtra;
 use pallet_session::historical as pallet_session_historical;
 // Can't use `FungibleAdapter` here until Treasury pallet migrates to fungibles
 // <https://github.com/paritytech/polkadot-sdk/issues/226>
@@ -1449,7 +1450,7 @@ where
 	type Extension = TxExtension;
 
 	fn create_transaction(call: RuntimeCall, extension: TxExtension) -> UncheckedExtrinsic {
-		UncheckedExtrinsic::new_transaction(call, extension)
+		generic::UncheckedExtrinsic::new_transaction(call, extension).into()
 	}
 }
 
@@ -1499,7 +1500,8 @@ where
 		let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?;
 		let address = Indices::unlookup(account);
 		let (call, tx_ext, _) = raw_payload.deconstruct();
-		let transaction = UncheckedExtrinsic::new_signed(call, address, signature, tx_ext);
+		let transaction =
+			generic::UncheckedExtrinsic::new_signed(call, address, signature, tx_ext).into();
 		Some(transaction)
 	}
 }
@@ -1509,7 +1511,7 @@ where
 	RuntimeCall: From<LocalCall>,
 {
 	fn create_inherent(call: RuntimeCall) -> UncheckedExtrinsic {
-		UncheckedExtrinsic::new_bare(call)
+		generic::UncheckedExtrinsic::new_bare(call).into()
 	}
 }
 
@@ -2587,6 +2589,16 @@ mod runtime {
 	pub type VerifySignature = pallet_verify_signature::Pallet<Runtime>;
 }
 
+impl TryFrom<RuntimeCall> for pallet_revive::Call<Runtime> {
+	type Error = ();
+
+	fn try_from(value: RuntimeCall) -> Result<Self, Self::Error> {
+		match value {
+			RuntimeCall::Revive(call) => Ok(call),
+			_ => Err(()),
+		}
+	}
+}
 /// The address format for describing accounts.
 pub type Address = sp_runtime::MultiAddress<AccountId, AccountIndex>;
 /// Block header type as expected by this runtime.
@@ -2617,9 +2629,32 @@ pub type TxExtension = (
 	frame_metadata_hash_extension::CheckMetadataHash<Runtime>,
 );
 
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct EthExtraImpl;
+
+impl EthExtra for EthExtraImpl {
+	type Config = Runtime;
+	type Extension = TxExtension;
+
+	fn get_eth_extension(nonce: u32, tip: Balance) -> Self::Extension {
+		(
+			frame_system::CheckNonZeroSender::<Runtime>::new(),
+			frame_system::CheckSpecVersion::<Runtime>::new(),
+			frame_system::CheckTxVersion::<Runtime>::new(),
+			frame_system::CheckGenesis::<Runtime>::new(),
+			frame_system::CheckEra::from(crate::generic::Era::Immortal),
+			frame_system::CheckNonce::<Runtime>::from(nonce),
+			frame_system::CheckWeight::<Runtime>::new(),
+			pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<Runtime>::from(tip, None)
+				.into(),
+			frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(false),
+		)
+	}
+}
+
 /// Unchecked extrinsic type as expected by this runtime.
 pub type UncheckedExtrinsic =
-	generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>;
+	pallet_revive::evm::runtime::UncheckedExtrinsic<Address, Signature, EthExtraImpl>;
 /// Unchecked signature payload type as expected by this runtime.
 pub type UncheckedSignaturePayload =
 	generic::UncheckedSignaturePayload<Address, Signature, TxExtension>;
@@ -3124,6 +3159,38 @@ impl_runtime_apis! {
 
 	impl pallet_revive::ReviveApi<Block, AccountId, Balance, BlockNumber, EventRecord> for Runtime
 	{
+		fn eth_transact(
+			from: H160,
+			dest: Option<H160>,
+			value: Balance,
+			input: Vec<u8>,
+			gas_limit: Option<Weight>,
+			storage_deposit_limit: Option<Balance>,
+		) -> pallet_revive::EthContractResult<Balance>
+		{
+			use pallet_revive::AddressMapper;
+			let blockweights: BlockWeights = <Runtime as frame_system::Config>::BlockWeights::get();
+			let origin = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&from);
+
+			let encoded_size = |pallet_call| {
+				let call = RuntimeCall::Revive(pallet_call);
+				let uxt: UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into();
+				uxt.encoded_size() as u32
+			};
+
+			Revive::bare_eth_transact(
+				origin,
+				dest,
+				value,
+				input,
+				gas_limit.unwrap_or(blockweights.max_block),
+				storage_deposit_limit.unwrap_or(u128::MAX),
+				encoded_size,
+				pallet_revive::DebugInfo::UnsafeDebug,
+				pallet_revive::CollectEvents::UnsafeCollect,
+			)
+		}
+
 		fn call(
 			origin: AccountId,
 			dest: H160,
@@ -3131,7 +3198,7 @@ impl_runtime_apis! {
 			gas_limit: Option<Weight>,
 			storage_deposit_limit: Option<Balance>,
 			input_data: Vec<u8>,
-		) -> pallet_revive::ContractExecResult<Balance, EventRecord> {
+		) -> pallet_revive::ContractResult<pallet_revive::ExecReturnValue, Balance, EventRecord> {
 			Revive::bare_call(
 				RuntimeOrigin::signed(origin),
 				dest,
@@ -3152,7 +3219,7 @@ impl_runtime_apis! {
 			code: pallet_revive::Code,
 			data: Vec<u8>,
 			salt: Option<[u8; 32]>,
-		) -> pallet_revive::ContractInstantiateResult<Balance, EventRecord>
+		) -> pallet_revive::ContractResult<pallet_revive::InstantiateReturnValue, Balance, EventRecord>
 		{
 			Revive::bare_instantiate(
 				RuntimeOrigin::signed(origin),
diff --git a/substrate/bin/node/testing/Cargo.toml b/substrate/bin/node/testing/Cargo.toml
index a5cec856717..16112386ad7 100644
--- a/substrate/bin/node/testing/Cargo.toml
+++ b/substrate/bin/node/testing/Cargo.toml
@@ -28,6 +28,7 @@ node-primitives = { workspace = true, default-features = true }
 kitchensink-runtime = { workspace = true }
 pallet-asset-conversion = { workspace = true, default-features = true }
 pallet-assets = { workspace = true, default-features = true }
+pallet-revive = { workspace = true, default-features = true }
 pallet-asset-conversion-tx-payment = { workspace = true, default-features = true }
 pallet-asset-tx-payment = { workspace = true, default-features = true }
 pallet-skip-feeless-payment = { workspace = true, default-features = true }
diff --git a/substrate/bin/node/testing/src/bench.rs b/substrate/bin/node/testing/src/bench.rs
index cce1627a2ad..3812524f0b1 100644
--- a/substrate/bin/node/testing/src/bench.rs
+++ b/substrate/bin/node/testing/src/bench.rs
@@ -53,7 +53,7 @@ use sp_core::{
 use sp_crypto_hashing::blake2_256;
 use sp_inherents::InherentData;
 use sp_runtime::{
-	generic::{ExtrinsicFormat, Preamble, EXTRINSIC_FORMAT_VERSION},
+	generic::{self, ExtrinsicFormat, Preamble, EXTRINSIC_FORMAT_VERSION},
 	traits::{Block as BlockT, IdentifyAccount, Verify},
 	OpaqueExtrinsic,
 };
@@ -586,7 +586,7 @@ impl BenchKeyring {
 						key.sign(b)
 					}
 				});
-				UncheckedExtrinsic {
+				generic::UncheckedExtrinsic {
 					preamble: Preamble::Signed(
 						sp_runtime::MultiAddress::Id(signed),
 						signature,
@@ -595,15 +595,18 @@ impl BenchKeyring {
 					),
 					function: payload.0,
 				}
+				.into()
 			},
-			ExtrinsicFormat::Bare => UncheckedExtrinsic {
+			ExtrinsicFormat::Bare => generic::UncheckedExtrinsic {
 				preamble: Preamble::Bare(EXTRINSIC_FORMAT_VERSION),
 				function: xt.function,
-			},
-			ExtrinsicFormat::General(tx_ext) => UncheckedExtrinsic {
+			}
+			.into(),
+			ExtrinsicFormat::General(tx_ext) => generic::UncheckedExtrinsic {
 				preamble: sp_runtime::generic::Preamble::General(0, tx_ext),
 				function: xt.function,
-			},
+			}
+			.into(),
 		}
 	}
 
diff --git a/substrate/bin/node/testing/src/keyring.rs b/substrate/bin/node/testing/src/keyring.rs
index 2334cb3c4df..20497e85eab 100644
--- a/substrate/bin/node/testing/src/keyring.rs
+++ b/substrate/bin/node/testing/src/keyring.rs
@@ -24,7 +24,7 @@ use node_primitives::{AccountId, Balance, Nonce};
 use sp_core::{crypto::get_public_from_string_or_panic, ecdsa, ed25519, sr25519};
 use sp_crypto_hashing::blake2_256;
 use sp_keyring::Sr25519Keyring;
-use sp_runtime::generic::{Era, ExtrinsicFormat, EXTRINSIC_FORMAT_VERSION};
+use sp_runtime::generic::{self, Era, ExtrinsicFormat, EXTRINSIC_FORMAT_VERSION};
 
 /// Alice's account id.
 pub fn alice() -> AccountId {
@@ -119,7 +119,7 @@ pub fn sign(
 						}
 					})
 					.into();
-			UncheckedExtrinsic {
+			generic::UncheckedExtrinsic {
 				preamble: sp_runtime::generic::Preamble::Signed(
 					sp_runtime::MultiAddress::Id(signed),
 					signature,
@@ -128,14 +128,17 @@ pub fn sign(
 				),
 				function: payload.0,
 			}
+			.into()
 		},
-		ExtrinsicFormat::Bare => UncheckedExtrinsic {
+		ExtrinsicFormat::Bare => generic::UncheckedExtrinsic {
 			preamble: sp_runtime::generic::Preamble::Bare(EXTRINSIC_FORMAT_VERSION),
 			function: xt.function,
-		},
-		ExtrinsicFormat::General(tx_ext) => UncheckedExtrinsic {
+		}
+		.into(),
+		ExtrinsicFormat::General(tx_ext) => generic::UncheckedExtrinsic {
 			preamble: sp_runtime::generic::Preamble::General(0, tx_ext),
 			function: xt.function,
-		},
+		}
+		.into(),
 	}
 }
diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml
index e896d9e8fa2..8dbad5ffd8b 100644
--- a/substrate/frame/revive/Cargo.toml
+++ b/substrate/frame/revive/Cargo.toml
@@ -19,18 +19,22 @@ targets = ["x86_64-unknown-linux-gnu"]
 [dependencies]
 environmental = { workspace = true }
 paste = { workspace = true }
-polkavm = { version = "0.12.0", default-features = false }
-polkavm-common = { version = "0.12.0", default-features = false }
+polkavm = { version = "0.13.0", default-features = false }
+polkavm-common = { version = "0.13.0", default-features = false }
 bitflags = { workspace = true }
-codec = { features = [
-	"derive",
-	"max-encoded-len",
-], workspace = true }
+codec = { features = ["derive", "max-encoded-len"], workspace = true }
 scale-info = { features = ["derive"], workspace = true }
 log = { workspace = true }
-serde = { optional = true, features = ["derive"], workspace = true, default-features = true }
+serde = { features = [
+	"alloc",
+	"derive",
+], workspace = true, default-features = false }
 impl-trait-for-tuples = { workspace = true }
 rlp = { workspace = true }
+derive_more = { workspace = true }
+hex = { workspace = true }
+jsonrpsee = { workspace = true, features = ["full"], optional = true }
+ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] }
 
 # Polkadot SDK Dependencies
 frame-benchmarking = { optional = true, workspace = true }
@@ -40,20 +44,28 @@ pallet-balances = { optional = true, workspace = true }
 pallet-revive-fixtures = { workspace = true, default-features = false }
 pallet-revive-uapi = { workspace = true, default-features = true }
 pallet-revive-proc-macro = { workspace = true, default-features = true }
+pallet-transaction-payment = { workspace = true }
 sp-api = { workspace = true }
+sp-arithmetic = { workspace = true }
 sp-core = { workspace = true }
 sp-io = { workspace = true }
 sp-runtime = { workspace = true }
 sp-std = { workspace = true }
+sp-weights = { workspace = true }
 xcm = { workspace = true }
 xcm-builder = { workspace = true }
+subxt-signer = { workspace = true, optional = true, features = [
+	"unstable-eth",
+] }
 
 [dev-dependencies]
 array-bytes = { workspace = true, default-features = true }
 assert_matches = { workspace = true }
 pretty_assertions = { workspace = true }
-wat = { workspace = true }
 pallet-revive-fixtures = { workspace = true, default-features = true }
+secp256k1 = { workspace = true, features = ["recovery"] }
+serde_json = { workspace = true }
+hex-literal = { workspace = true }
 
 # Polkadot SDK Dependencies
 pallet-balances = { workspace = true, default-features = true }
@@ -75,26 +87,35 @@ riscv = ["pallet-revive-fixtures/riscv"]
 std = [
 	"codec/std",
 	"environmental/std",
+	"ethereum-types/std",
 	"frame-benchmarking?/std",
 	"frame-support/std",
 	"frame-system/std",
+	"hex/std",
+	"jsonrpsee",
 	"log/std",
 	"pallet-balances?/std",
 	"pallet-proxy/std",
 	"pallet-revive-fixtures/std",
 	"pallet-timestamp/std",
+	"pallet-transaction-payment/std",
 	"pallet-utility/std",
 	"polkavm-common/std",
 	"polkavm/std",
 	"rlp/std",
 	"scale-info/std",
-	"serde",
+	"secp256k1/std",
+	"serde/std",
+	"serde_json/std",
 	"sp-api/std",
+	"sp-arithmetic/std",
 	"sp-core/std",
 	"sp-io/std",
 	"sp-keystore/std",
 	"sp-runtime/std",
 	"sp-std/std",
+	"sp-weights/std",
+	"subxt-signer",
 	"xcm-builder/std",
 	"xcm/std",
 ]
@@ -107,6 +128,7 @@ runtime-benchmarks = [
 	"pallet-message-queue/runtime-benchmarks",
 	"pallet-proxy/runtime-benchmarks",
 	"pallet-timestamp/runtime-benchmarks",
+	"pallet-transaction-payment/runtime-benchmarks",
 	"pallet-utility/runtime-benchmarks",
 	"sp-runtime/runtime-benchmarks",
 	"xcm-builder/runtime-benchmarks",
@@ -119,6 +141,7 @@ try-runtime = [
 	"pallet-message-queue/try-runtime",
 	"pallet-proxy/try-runtime",
 	"pallet-timestamp/try-runtime",
+	"pallet-transaction-payment/try-runtime",
 	"pallet-utility/try-runtime",
 	"sp-runtime/try-runtime",
 ]
diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml
index 75b23fdd44d..1d89db002b7 100644
--- a/substrate/frame/revive/fixtures/Cargo.toml
+++ b/substrate/frame/revive/fixtures/Cargo.toml
@@ -21,7 +21,7 @@ log = { workspace = true }
 parity-wasm = { workspace = true }
 tempfile = { workspace = true }
 toml = { workspace = true }
-polkavm-linker = { version = "0.12.0" }
+polkavm-linker = { version = "0.13.0" }
 anyhow = { workspace = true, default-features = true }
 
 [features]
@@ -31,11 +31,4 @@ default = ["std"]
 # we will remove this once there is an upstream toolchain
 riscv = []
 # only when std is enabled all fixtures are available
-std = [
-	"anyhow",
-	"frame-system",
-	"log/std",
-	"sp-core",
-	"sp-io",
-	"sp-runtime",
-]
+std = ["anyhow", "frame-system", "log/std", "sp-core", "sp-io", "sp-runtime"]
diff --git a/substrate/frame/revive/fixtures/build.rs b/substrate/frame/revive/fixtures/build.rs
index 3178baf6bbe..cb4b7640814 100644
--- a/substrate/frame/revive/fixtures/build.rs
+++ b/substrate/frame/revive/fixtures/build.rs
@@ -65,7 +65,6 @@ mod build {
 	}
 
 	/// Collect all contract entries from the given source directory.
-	/// Contracts that have already been compiled are filtered out.
 	fn collect_entries(contracts_dir: &Path) -> Vec<Entry> {
 		fs::read_dir(contracts_dir)
 			.expect("src dir exists; qed")
@@ -184,7 +183,13 @@ mod build {
 		let fixtures_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")?.into();
 		let contracts_dir = fixtures_dir.join("contracts");
 		let uapi_dir = fixtures_dir.parent().expect("uapi dir exits; qed").join("uapi");
-		let out_dir: PathBuf = env::var("OUT_DIR")?.into();
+		let ws_dir: PathBuf = env::var("CARGO_WORKSPACE_ROOT_DIR")?.into();
+		let out_dir: PathBuf = ws_dir.join("target").join("pallet-revive-fixtures");
+
+		// create out_dir if it does not exist
+		if !out_dir.exists() {
+			fs::create_dir_all(&out_dir)?;
+		}
 
 		// the fixtures have a dependency on the uapi crate
 		println!("cargo::rerun-if-changed={}", fixtures_dir.display());
diff --git a/substrate/frame/revive/fixtures/build/Cargo.toml b/substrate/frame/revive/fixtures/build/Cargo.toml
index 948d7438cf9..c4aaf131148 100644
--- a/substrate/frame/revive/fixtures/build/Cargo.toml
+++ b/substrate/frame/revive/fixtures/build/Cargo.toml
@@ -11,7 +11,7 @@ edition = "2021"
 [dependencies]
 uapi = { package = 'pallet-revive-uapi', path = "", default-features = false }
 common = { package = 'pallet-revive-fixtures-common', path = "" }
-polkavm-derive = { version = "0.12.0" }
+polkavm-derive = { version = "0.13.0" }
 
 [profile.release]
 opt-level = 3
diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs
index 5548dca66d0..eacd63b97e5 100644
--- a/substrate/frame/revive/fixtures/src/lib.rs
+++ b/substrate/frame/revive/fixtures/src/lib.rs
@@ -22,8 +22,11 @@ extern crate alloc;
 /// Load a given wasm module and returns a wasm binary contents along with it's hash.
 #[cfg(feature = "std")]
 pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec<u8>, sp_core::H256)> {
-	let out_dir: std::path::PathBuf = env!("OUT_DIR").into();
-	let fixture_path = out_dir.join(format!("{fixture_name}.polkavm"));
+	let ws_dir: std::path::PathBuf = env!("CARGO_WORKSPACE_ROOT_DIR").into();
+	let fixture_path = ws_dir
+		.join("target")
+		.join("pallet-revive-fixtures")
+		.join(format!("{fixture_name}.polkavm"));
 	log::debug!("Loading fixture from {fixture_path:?}");
 	let binary = std::fs::read(fixture_path)?;
 	let code_hash = sp_io::hashing::keccak_256(&binary);
@@ -40,7 +43,12 @@ pub mod bench {
 	#[cfg(feature = "riscv")]
 	macro_rules! fixture {
 		($name: literal) => {
-			include_bytes!(concat!(env!("OUT_DIR"), "/", $name, ".polkavm"))
+			include_bytes!(concat!(
+				env!("CARGO_WORKSPACE_ROOT_DIR"),
+				"/target/pallet-revive-fixtures/",
+				$name,
+				".polkavm"
+			))
 		};
 	}
 	#[cfg(not(feature = "riscv"))]
@@ -63,12 +71,3 @@ pub mod bench {
 		dummy
 	}
 }
-
-#[cfg(test)]
-mod test {
-	#[test]
-	fn out_dir_should_have_compiled_mocks() {
-		let out_dir: std::path::PathBuf = env!("OUT_DIR").into();
-		assert!(out_dir.join("dummy.polkavm").exists());
-	}
-}
diff --git a/substrate/frame/revive/mock-network/Cargo.toml b/substrate/frame/revive/mock-network/Cargo.toml
index 85656a57b49..12de634b0b4 100644
--- a/substrate/frame/revive/mock-network/Cargo.toml
+++ b/substrate/frame/revive/mock-network/Cargo.toml
@@ -87,3 +87,17 @@ runtime-benchmarks = [
 	"xcm-builder/runtime-benchmarks",
 	"xcm-executor/runtime-benchmarks",
 ]
+try-runtime = [
+	"frame-support/try-runtime",
+	"frame-system/try-runtime",
+	"pallet-assets/try-runtime",
+	"pallet-balances/try-runtime",
+	"pallet-message-queue/try-runtime",
+	"pallet-proxy/try-runtime",
+	"pallet-revive/try-runtime",
+	"pallet-timestamp/try-runtime",
+	"pallet-utility/try-runtime",
+	"pallet-xcm/try-runtime",
+	"polkadot-runtime-parachains/try-runtime",
+	"sp-runtime/try-runtime",
+]
diff --git a/substrate/frame/revive/src/address.rs b/substrate/frame/revive/src/address.rs
index c51940ba771..4633fce1f32 100644
--- a/substrate/frame/revive/src/address.rs
+++ b/substrate/frame/revive/src/address.rs
@@ -34,7 +34,7 @@ use sp_runtime::AccountId32;
 /// case for all existing runtimes as of right now. Reasing is that this will allow
 /// us to reverse an address -> account_id mapping by just stripping the prefix.
 pub trait AddressMapper<T>: private::Sealed {
-	/// Convert an account id to an ethereum adress.
+	/// Convert an account id to an ethereum address.
 	///
 	/// This mapping is **not** required to be reversible.
 	fn to_address(account_id: &T) -> H160;
diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs
new file mode 100644
index 00000000000..c3495fc0559
--- /dev/null
+++ b/substrate/frame/revive/src/evm.rs
@@ -0,0 +1,22 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//!Types, and traits to integrate pallet-revive with EVM.
+#![warn(missing_docs)]
+
+mod api;
+pub use api::*;
+pub mod runtime;
diff --git a/substrate/frame/revive/src/evm/api.rs b/substrate/frame/revive/src/evm/api.rs
new file mode 100644
index 00000000000..fe18c8735be
--- /dev/null
+++ b/substrate/frame/revive/src/evm/api.rs
@@ -0,0 +1,38 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//! JSON-RPC methods and types, for Ethereum.
+
+mod byte;
+pub use byte::*;
+
+mod rlp_codec;
+pub use rlp;
+
+mod type_id;
+pub use type_id::*;
+
+mod rpc_types;
+mod rpc_types_gen;
+pub use rpc_types_gen::*;
+
+#[cfg(feature = "std")]
+mod account;
+
+#[cfg(feature = "std")]
+pub use account::*;
+
+mod signature;
diff --git a/substrate/frame/revive/src/evm/api/account.rs b/substrate/frame/revive/src/evm/api/account.rs
new file mode 100644
index 00000000000..c2217defc31
--- /dev/null
+++ b/substrate/frame/revive/src/evm/api/account.rs
@@ -0,0 +1,51 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//! Utilities for working with Ethereum accounts.
+use crate::{
+	evm::{TransactionLegacySigned, TransactionLegacyUnsigned},
+	H160,
+};
+use rlp::Encodable;
+
+/// A simple account that can sign transactions
+pub struct Account(subxt_signer::eth::Keypair);
+
+impl Default for Account {
+	fn default() -> Self {
+		Self(subxt_signer::eth::dev::alith())
+	}
+}
+
+impl From<subxt_signer::eth::Keypair> for Account {
+	fn from(kp: subxt_signer::eth::Keypair) -> Self {
+		Self(kp)
+	}
+}
+
+impl Account {
+	/// Get the [`H160`] address of the account.
+	pub fn address(&self) -> H160 {
+		H160::from_slice(&self.0.account_id().as_ref())
+	}
+
+	/// Sign a transaction.
+	pub fn sign_transaction(&self, tx: TransactionLegacyUnsigned) -> TransactionLegacySigned {
+		let rlp_encoded = tx.rlp_bytes();
+		let signature = self.0.sign(&rlp_encoded);
+		TransactionLegacySigned::from(tx, signature.as_ref())
+	}
+}
diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/src/evm/api/byte.rs
new file mode 100644
index 00000000000..df4ed1740ec
--- /dev/null
+++ b/substrate/frame/revive/src/evm/api/byte.rs
@@ -0,0 +1,154 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//! Define Byte wrapper types for encoding and decoding hex strings
+use alloc::{vec, vec::Vec};
+use codec::{Decode, Encode};
+use core::{
+	fmt::{Debug, Display, Formatter, Result as FmtResult},
+	str::FromStr,
+};
+use hex_serde::HexCodec;
+use scale_info::TypeInfo;
+use serde::{Deserialize, Serialize};
+
+mod hex_serde {
+	#[cfg(not(feature = "std"))]
+	use alloc::{format, string::String, vec::Vec};
+	use serde::{Deserialize, Deserializer, Serializer};
+
+	pub trait HexCodec: Sized {
+		type Error;
+		fn to_hex(&self) -> String;
+		fn from_hex(s: String) -> Result<Self, Self::Error>;
+	}
+
+	impl HexCodec for u8 {
+		type Error = core::num::ParseIntError;
+		fn to_hex(&self) -> String {
+			format!("0x{:x}", self)
+		}
+		fn from_hex(s: String) -> Result<Self, Self::Error> {
+			u8::from_str_radix(s.trim_start_matches("0x"), 16)
+		}
+	}
+
+	impl<const T: usize> HexCodec for [u8; T] {
+		type Error = hex::FromHexError;
+		fn to_hex(&self) -> String {
+			format!("0x{}", hex::encode(self))
+		}
+		fn from_hex(s: String) -> Result<Self, Self::Error> {
+			let data = hex::decode(s.trim_start_matches("0x"))?;
+			data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength)
+		}
+	}
+
+	impl HexCodec for Vec<u8> {
+		type Error = hex::FromHexError;
+		fn to_hex(&self) -> String {
+			format!("0x{}", hex::encode(self))
+		}
+		fn from_hex(s: String) -> Result<Self, Self::Error> {
+			hex::decode(s.trim_start_matches("0x"))
+		}
+	}
+
+	pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
+	where
+		S: Serializer,
+		T: HexCodec,
+	{
+		let s = value.to_hex();
+		serializer.serialize_str(&s)
+	}
+
+	pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
+	where
+		D: Deserializer<'de>,
+		T: HexCodec,
+		<T as HexCodec>::Error: core::fmt::Debug,
+	{
+		let s = String::deserialize(deserializer)?;
+		let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
+		Ok(value)
+	}
+}
+
+impl FromStr for Bytes {
+	type Err = hex::FromHexError;
+	fn from_str(s: &str) -> Result<Self, Self::Err> {
+		let data = hex::decode(s.trim_start_matches("0x"))?;
+		Ok(Bytes(data))
+	}
+}
+
+macro_rules! impl_hex {
+    ($type:ident, $inner:ty, $default:expr) => {
+        #[derive(Encode, Decode, Eq, PartialEq, TypeInfo, Clone, Serialize, Deserialize)]
+        #[doc = concat!("`", stringify!($inner), "`", " wrapper type for encoding and decoding hex strings")]
+        pub struct $type(#[serde(with = "hex_serde")] pub $inner);
+
+        impl Default for $type {
+            fn default() -> Self {
+                $type($default)
+            }
+        }
+
+        impl From<$inner> for $type {
+            fn from(inner: $inner) -> Self {
+                $type(inner)
+            }
+        }
+
+        impl Debug for $type {
+            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+                write!(f, concat!(stringify!($type), "({})"), self.0.to_hex())
+            }
+        }
+
+        impl Display for $type {
+            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+                write!(f, "{}", self.0.to_hex())
+            }
+        }
+    };
+}
+
+impl_hex!(Byte, u8, 0u8);
+impl_hex!(Bytes, Vec<u8>, vec![]);
+impl_hex!(Bytes8, [u8; 8], [0u8; 8]);
+impl_hex!(Bytes256, [u8; 256], [0u8; 256]);
+
+#[test]
+fn serialize_works() {
+	let a = Byte(42);
+	let s = serde_json::to_string(&a).unwrap();
+	assert_eq!(s, "\"0x2a\"");
+	let b = serde_json::from_str::<Byte>(&s).unwrap();
+	assert_eq!(a, b);
+
+	let a = Bytes(b"bello world".to_vec());
+	let s = serde_json::to_string(&a).unwrap();
+	assert_eq!(s, "\"0x62656c6c6f20776f726c64\"");
+	let b = serde_json::from_str::<Bytes>(&s).unwrap();
+	assert_eq!(a, b);
+
+	let a = Bytes256([42u8; 256]);
+	let s = serde_json::to_string(&a).unwrap();
+	let b = serde_json::from_str::<Bytes256>(&s).unwrap();
+	assert_eq!(a, b);
+}
diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs
new file mode 100644
index 00000000000..e5f24c28a48
--- /dev/null
+++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs
@@ -0,0 +1,219 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//! RLP encoding and decoding for Ethereum transactions.
+//! See <https://eth.wiki/fundamentals/rlp> for more information about RLP encoding.
+
+use super::*;
+use alloc::vec::Vec;
+use rlp::{Decodable, Encodable};
+
+impl TransactionLegacyUnsigned {
+	/// Get the rlp encoded bytes of a signed transaction with a dummy 65 bytes signature.
+	pub fn dummy_signed_payload(&self) -> Vec<u8> {
+		let mut s = rlp::RlpStream::new();
+		s.append(self);
+		const DUMMY_SIGNATURE: [u8; 65] = [0u8; 65];
+		s.append_raw(&DUMMY_SIGNATURE.as_ref(), 1);
+		s.out().to_vec()
+	}
+}
+
+/// See <https://eips.ethereum.org/EIPS/eip-155>
+impl Encodable for TransactionLegacyUnsigned {
+	fn rlp_append(&self, s: &mut rlp::RlpStream) {
+		if let Some(chain_id) = self.chain_id {
+			s.begin_list(9);
+			s.append(&self.nonce);
+			s.append(&self.gas_price);
+			s.append(&self.gas);
+			match self.to {
+				Some(ref to) => s.append(to),
+				None => s.append_empty_data(),
+			};
+			s.append(&self.value);
+			s.append(&self.input.0);
+			s.append(&chain_id);
+			s.append(&0_u8);
+			s.append(&0_u8);
+		} else {
+			s.begin_list(6);
+			s.append(&self.nonce);
+			s.append(&self.gas_price);
+			s.append(&self.gas);
+			match self.to {
+				Some(ref to) => s.append(to),
+				None => s.append_empty_data(),
+			};
+			s.append(&self.value);
+			s.append(&self.input.0);
+		}
+	}
+}
+
+/// See <https://eips.ethereum.org/EIPS/eip-155>
+impl Decodable for TransactionLegacyUnsigned {
+	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
+		Ok(TransactionLegacyUnsigned {
+			nonce: rlp.val_at(0)?,
+			gas_price: rlp.val_at(1)?,
+			gas: rlp.val_at(2)?,
+			to: {
+				let to = rlp.at(3)?;
+				if to.is_empty() {
+					None
+				} else {
+					Some(to.as_val()?)
+				}
+			},
+			value: rlp.val_at(4)?,
+			input: Bytes(rlp.val_at(5)?),
+			chain_id: {
+				if let Ok(chain_id) = rlp.val_at(6) {
+					Some(chain_id)
+				} else {
+					None
+				}
+			},
+			..Default::default()
+		})
+	}
+}
+
+impl Encodable for TransactionLegacySigned {
+	fn rlp_append(&self, s: &mut rlp::RlpStream) {
+		s.begin_list(9);
+		s.append(&self.transaction_legacy_unsigned.nonce);
+		s.append(&self.transaction_legacy_unsigned.gas_price);
+		s.append(&self.transaction_legacy_unsigned.gas);
+		match self.transaction_legacy_unsigned.to {
+			Some(ref to) => s.append(to),
+			None => s.append_empty_data(),
+		};
+		s.append(&self.transaction_legacy_unsigned.value);
+		s.append(&self.transaction_legacy_unsigned.input.0);
+
+		s.append(&self.v);
+		s.append(&self.r);
+		s.append(&self.s);
+	}
+}
+
+/// See <https://eips.ethereum.org/EIPS/eip-155>
+impl Decodable for TransactionLegacySigned {
+	fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
+		let v: U256 = rlp.val_at(6)?;
+
+		let extract_chain_id = |v: U256| {
+			if v.ge(&35u32.into()) {
+				Some((v - 35) / 2)
+			} else {
+				None
+			}
+		};
+
+		Ok(TransactionLegacySigned {
+			transaction_legacy_unsigned: {
+				TransactionLegacyUnsigned {
+					nonce: rlp.val_at(0)?,
+					gas_price: rlp.val_at(1)?,
+					gas: rlp.val_at(2)?,
+					to: {
+						let to = rlp.at(3)?;
+						if to.is_empty() {
+							None
+						} else {
+							Some(to.as_val()?)
+						}
+					},
+					value: rlp.val_at(4)?,
+					input: Bytes(rlp.val_at(5)?),
+					chain_id: extract_chain_id(v).map(|v| v.into()),
+					r#type: Type0 {},
+				}
+			},
+			v,
+			r: rlp.val_at(7)?,
+			s: rlp.val_at(8)?,
+		})
+	}
+}
+
+#[cfg(test)]
+mod test {
+	use super::*;
+
+	#[test]
+	fn encode_decode_legacy_transaction_works() {
+		let tx = TransactionLegacyUnsigned {
+			chain_id: Some(596.into()),
+			gas: U256::from(21000),
+			nonce: U256::from(1),
+			gas_price: U256::from("0x640000006a"),
+			to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()),
+			value: U256::from(123123),
+			input: Bytes(vec![]),
+			r#type: Type0,
+		};
+
+		let rlp_bytes = rlp::encode(&tx);
+		let decoded = rlp::decode::<TransactionLegacyUnsigned>(&rlp_bytes).unwrap();
+		assert_eq!(&tx, &decoded);
+
+		let tx = Account::default().sign_transaction(tx);
+		let rlp_bytes = rlp::encode(&tx);
+		let decoded = rlp::decode::<TransactionLegacySigned>(&rlp_bytes).unwrap();
+		assert_eq!(&tx, &decoded);
+	}
+
+	#[test]
+	fn dummy_signed_payload_works() {
+		let tx = TransactionLegacyUnsigned {
+			chain_id: Some(596.into()),
+			gas: U256::from(21000),
+			nonce: U256::from(1),
+			gas_price: U256::from("0x640000006a"),
+			to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()),
+			value: U256::from(123123),
+			input: Bytes(vec![]),
+			r#type: Type0,
+		};
+
+		let signed_tx = Account::default().sign_transaction(tx.clone());
+		let rlp_bytes = rlp::encode(&signed_tx);
+		assert_eq!(tx.dummy_signed_payload().len(), rlp_bytes.len());
+	}
+
+	#[test]
+	fn recover_address_works() {
+		let account = Account::default();
+
+		let unsigned_tx = TransactionLegacyUnsigned {
+			value: 200_000_000_000_000_000_000u128.into(),
+			gas_price: 100_000_000_200u64.into(),
+			gas: 100_107u32.into(),
+			nonce: 3.into(),
+			to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()),
+			chain_id: Some(596.into()),
+			..Default::default()
+		};
+
+		let tx = account.sign_transaction(unsigned_tx.clone());
+		let recovered_address = tx.recover_eth_address().unwrap();
+
+		assert_eq!(account.address(), recovered_address);
+	}
+}
diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs
new file mode 100644
index 00000000000..b15a0a53cd0
--- /dev/null
+++ b/substrate/frame/revive/src/evm/api/rpc_types.rs
@@ -0,0 +1,32 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//! Utility impl for the RPC types.
+use super::{ReceiptInfo, TransactionInfo, TransactionSigned};
+
+impl TransactionInfo {
+	/// Create a new [`TransactionInfo`] from a receipt and a signed transaction.
+	pub fn new(receipt: ReceiptInfo, transaction_signed: TransactionSigned) -> Self {
+		Self {
+			block_hash: receipt.block_hash,
+			block_number: receipt.block_number,
+			from: receipt.from,
+			hash: receipt.transaction_hash,
+			transaction_index: receipt.transaction_index,
+			transaction_signed,
+		}
+	}
+}
diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs
new file mode 100644
index 00000000000..e4663a82232
--- /dev/null
+++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs
@@ -0,0 +1,682 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//! Generated JSON-RPC types.
+#![allow(missing_docs)]
+
+use super::{byte::*, Type0, Type1, Type2};
+use alloc::vec::Vec;
+use codec::{Decode, Encode};
+use derive_more::{From, TryInto};
+pub use ethereum_types::*;
+use scale_info::TypeInfo;
+use serde::{Deserialize, Serialize};
+
+/// Block object
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct Block {
+	/// Base fee per gas
+	#[serde(rename = "baseFeePerGas", skip_serializing_if = "Option::is_none")]
+	pub base_fee_per_gas: Option<U256>,
+	/// Blob gas used
+	#[serde(rename = "blobGasUsed", skip_serializing_if = "Option::is_none")]
+	pub blob_gas_used: Option<U256>,
+	/// Difficulty
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub difficulty: Option<U256>,
+	/// Excess blob gas
+	#[serde(rename = "excessBlobGas", skip_serializing_if = "Option::is_none")]
+	pub excess_blob_gas: Option<U256>,
+	/// Extra data
+	#[serde(rename = "extraData")]
+	pub extra_data: Bytes,
+	/// Gas limit
+	#[serde(rename = "gasLimit")]
+	pub gas_limit: U256,
+	/// Gas used
+	#[serde(rename = "gasUsed")]
+	pub gas_used: U256,
+	/// Hash
+	pub hash: H256,
+	/// Bloom filter
+	#[serde(rename = "logsBloom")]
+	pub logs_bloom: Bytes256,
+	/// Coinbase
+	pub miner: Address,
+	/// Mix hash
+	#[serde(rename = "mixHash")]
+	pub mix_hash: H256,
+	/// Nonce
+	pub nonce: Bytes8,
+	/// Number
+	pub number: U256,
+	/// Parent Beacon Block Root
+	#[serde(rename = "parentBeaconBlockRoot", skip_serializing_if = "Option::is_none")]
+	pub parent_beacon_block_root: Option<H256>,
+	/// Parent block hash
+	#[serde(rename = "parentHash")]
+	pub parent_hash: H256,
+	/// Receipts root
+	#[serde(rename = "receiptsRoot")]
+	pub receipts_root: H256,
+	/// Ommers hash
+	#[serde(rename = "sha3Uncles")]
+	pub sha_3_uncles: H256,
+	/// Block size
+	pub size: U256,
+	/// State root
+	#[serde(rename = "stateRoot")]
+	pub state_root: H256,
+	/// Timestamp
+	pub timestamp: U256,
+	/// Total difficulty
+	#[serde(rename = "totalDifficulty", skip_serializing_if = "Option::is_none")]
+	pub total_difficulty: Option<U256>,
+	pub transactions: H256OrTransactionInfo,
+	/// Transactions root
+	#[serde(rename = "transactionsRoot")]
+	pub transactions_root: H256,
+	/// Uncles
+	pub uncles: Vec<H256>,
+	/// Withdrawals
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub withdrawals: Option<Vec<Withdrawal>>,
+	/// Withdrawals root
+	#[serde(rename = "withdrawalsRoot", skip_serializing_if = "Option::is_none")]
+	pub withdrawals_root: Option<H256>,
+}
+
+/// Block number or tag
+#[derive(
+	Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq,
+)]
+#[serde(untagged)]
+pub enum BlockNumberOrTag {
+	/// Block number
+	U256(U256),
+	/// Block tag
+	BlockTag(BlockTag),
+}
+impl Default for BlockNumberOrTag {
+	fn default() -> Self {
+		BlockNumberOrTag::U256(Default::default())
+	}
+}
+
+/// Block number, tag, or block hash
+#[derive(
+	Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq,
+)]
+#[serde(untagged)]
+pub enum BlockNumberOrTagOrHash {
+	/// Block number
+	U256(U256),
+	/// Block tag
+	BlockTag(BlockTag),
+	/// Block hash
+	H256(H256),
+}
+impl Default for BlockNumberOrTagOrHash {
+	fn default() -> Self {
+		BlockNumberOrTagOrHash::U256(Default::default())
+	}
+}
+
+/// Transaction object generic to all types
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct GenericTransaction {
+	/// accessList
+	/// EIP-2930 access list
+	#[serde(rename = "accessList", skip_serializing_if = "Option::is_none")]
+	pub access_list: Option<AccessList>,
+	/// blobVersionedHashes
+	/// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs.
+	#[serde(rename = "blobVersionedHashes", skip_serializing_if = "Option::is_none")]
+	pub blob_versioned_hashes: Option<Vec<H256>>,
+	/// blobs
+	/// Raw blob data.
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub blobs: Option<Vec<Bytes>>,
+	/// chainId
+	/// Chain ID that this transaction is valid on.
+	#[serde(rename = "chainId", skip_serializing_if = "Option::is_none")]
+	pub chain_id: Option<U256>,
+	/// from address
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub from: Option<Address>,
+	/// gas limit
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub gas: Option<U256>,
+	/// gas price
+	/// The gas price willing to be paid by the sender in wei
+	#[serde(rename = "gasPrice", skip_serializing_if = "Option::is_none")]
+	pub gas_price: Option<U256>,
+	/// input data
+	#[serde(alias = "data", skip_serializing_if = "Option::is_none")]
+	pub input: Option<Bytes>,
+	/// max fee per blob gas
+	/// The maximum total fee per gas the sender is willing to pay for blob gas in wei
+	#[serde(rename = "maxFeePerBlobGas", skip_serializing_if = "Option::is_none")]
+	pub max_fee_per_blob_gas: Option<U256>,
+	/// max fee per gas
+	/// The maximum total fee per gas the sender is willing to pay (includes the network / base fee
+	/// and miner / priority fee) in wei
+	#[serde(rename = "maxFeePerGas", skip_serializing_if = "Option::is_none")]
+	pub max_fee_per_gas: Option<U256>,
+	/// max priority fee per gas
+	/// Maximum fee per gas the sender is willing to pay to miners in wei
+	#[serde(rename = "maxPriorityFeePerGas", skip_serializing_if = "Option::is_none")]
+	pub max_priority_fee_per_gas: Option<U256>,
+	/// nonce
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub nonce: Option<U256>,
+	/// to address
+	pub to: Option<Address>,
+	/// type
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub r#type: Option<Byte>,
+	/// value
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub value: Option<U256>,
+}
+
+/// Receipt information
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct ReceiptInfo {
+	/// blob gas price
+	/// The actual value per gas deducted from the sender's account for blob gas. Only specified
+	/// for blob transactions as defined by EIP-4844.
+	#[serde(rename = "blobGasPrice", skip_serializing_if = "Option::is_none")]
+	pub blob_gas_price: Option<U256>,
+	/// blob gas used
+	/// The amount of blob gas used for this specific transaction. Only specified for blob
+	/// transactions as defined by EIP-4844.
+	#[serde(rename = "blobGasUsed", skip_serializing_if = "Option::is_none")]
+	pub blob_gas_used: Option<U256>,
+	/// block hash
+	#[serde(rename = "blockHash")]
+	pub block_hash: H256,
+	/// block number
+	#[serde(rename = "blockNumber")]
+	pub block_number: U256,
+	/// contract address
+	/// The contract address created, if the transaction was a contract creation, otherwise null.
+	#[serde(rename = "contractAddress")]
+	pub contract_address: Option<Address>,
+	/// cumulative gas used
+	/// The sum of gas used by this transaction and all preceding transactions in the same block.
+	#[serde(rename = "cumulativeGasUsed")]
+	pub cumulative_gas_used: U256,
+	/// effective gas price
+	/// The actual value per gas deducted from the sender's account. Before EIP-1559, this is equal
+	/// to the transaction's gas price. After, it is equal to baseFeePerGas + min(maxFeePerGas -
+	/// baseFeePerGas, maxPriorityFeePerGas).
+	#[serde(rename = "effectiveGasPrice")]
+	pub effective_gas_price: U256,
+	/// from
+	pub from: Address,
+	/// gas used
+	/// The amount of gas used for this specific transaction alone.
+	#[serde(rename = "gasUsed")]
+	pub gas_used: U256,
+	/// logs
+	pub logs: Vec<Log>,
+	/// logs bloom
+	#[serde(rename = "logsBloom")]
+	pub logs_bloom: Bytes256,
+	/// state root
+	/// The post-transaction state root. Only specified for transactions included before the
+	/// Byzantium upgrade.
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub root: Option<H256>,
+	/// status
+	/// Either 1 (success) or 0 (failure). Only specified for transactions included after the
+	/// Byzantium upgrade.
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub status: Option<U256>,
+	/// to
+	/// Address of the receiver or null in a contract creation transaction.
+	pub to: Option<Address>,
+	/// transaction hash
+	#[serde(rename = "transactionHash")]
+	pub transaction_hash: H256,
+	/// transaction index
+	#[serde(rename = "transactionIndex")]
+	pub transaction_index: U256,
+	/// type
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub r#type: Option<Byte>,
+}
+
+/// Syncing status
+#[derive(
+	Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq,
+)]
+#[serde(untagged)]
+pub enum SyncingStatus {
+	/// Syncing progress
+	SyncingProgress(SyncingProgress),
+	/// Not syncing
+	/// Should always return false if not syncing.
+	Bool(bool),
+}
+impl Default for SyncingStatus {
+	fn default() -> Self {
+		SyncingStatus::SyncingProgress(Default::default())
+	}
+}
+
+/// Transaction information
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct TransactionInfo {
+	/// block hash
+	#[serde(rename = "blockHash")]
+	pub block_hash: H256,
+	/// block number
+	#[serde(rename = "blockNumber")]
+	pub block_number: U256,
+	/// from address
+	pub from: Address,
+	/// transaction hash
+	pub hash: H256,
+	/// transaction index
+	#[serde(rename = "transactionIndex")]
+	pub transaction_index: U256,
+	#[serde(flatten)]
+	pub transaction_signed: TransactionSigned,
+}
+
+#[derive(
+	Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq,
+)]
+#[serde(untagged)]
+pub enum TransactionUnsigned {
+	Transaction4844Unsigned(Transaction4844Unsigned),
+	Transaction1559Unsigned(Transaction1559Unsigned),
+	Transaction2930Unsigned(Transaction2930Unsigned),
+	TransactionLegacyUnsigned(TransactionLegacyUnsigned),
+}
+impl Default for TransactionUnsigned {
+	fn default() -> Self {
+		TransactionUnsigned::Transaction4844Unsigned(Default::default())
+	}
+}
+
+/// Access list
+pub type AccessList = Vec<AccessListEntry>;
+
+/// Block tag
+/// `earliest`: The lowest numbered block the client has available; `finalized`: The most recent
+/// crypto-economically secure block, cannot be re-orged outside of manual intervention driven by
+/// community coordination; `safe`: The most recent block that is safe from re-orgs under honest
+/// majority and certain synchronicity assumptions; `latest`: The most recent block in the canonical
+/// chain observed by the client, this block may be re-orged out of the canonical chain even under
+/// healthy/normal conditions; `pending`: A sample next block built by the client on top of `latest`
+/// and containing the set of transactions usually taken from local mempool. Before the merge
+/// transition is finalized, any call querying for `finalized` or `safe` block MUST be responded to
+/// with `-39001: Unknown block` error
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub enum BlockTag {
+	#[serde(rename = "earliest")]
+	#[default]
+	Earliest,
+	#[serde(rename = "finalized")]
+	Finalized,
+	#[serde(rename = "safe")]
+	Safe,
+	#[serde(rename = "latest")]
+	Latest,
+	#[serde(rename = "pending")]
+	Pending,
+}
+
+#[derive(
+	Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq,
+)]
+#[serde(untagged)]
+pub enum H256OrTransactionInfo {
+	/// Transaction hashes
+	H256s(Vec<H256>),
+	/// Full transactions
+	TransactionInfos(Vec<TransactionInfo>),
+}
+impl Default for H256OrTransactionInfo {
+	fn default() -> Self {
+		H256OrTransactionInfo::H256s(Default::default())
+	}
+}
+
+/// log
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct Log {
+	/// address
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub address: Option<Address>,
+	/// block hash
+	#[serde(rename = "blockHash", skip_serializing_if = "Option::is_none")]
+	pub block_hash: Option<H256>,
+	/// block number
+	#[serde(rename = "blockNumber", skip_serializing_if = "Option::is_none")]
+	pub block_number: Option<U256>,
+	/// data
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub data: Option<Bytes>,
+	/// log index
+	#[serde(rename = "logIndex", skip_serializing_if = "Option::is_none")]
+	pub log_index: Option<U256>,
+	/// removed
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub removed: Option<bool>,
+	/// topics
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub topics: Option<Vec<H256>>,
+	/// transaction hash
+	#[serde(rename = "transactionHash")]
+	pub transaction_hash: H256,
+	/// transaction index
+	#[serde(rename = "transactionIndex", skip_serializing_if = "Option::is_none")]
+	pub transaction_index: Option<U256>,
+}
+
+/// Syncing progress
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct SyncingProgress {
+	/// Current block
+	#[serde(rename = "currentBlock", skip_serializing_if = "Option::is_none")]
+	pub current_block: Option<U256>,
+	/// Highest block
+	#[serde(rename = "highestBlock", skip_serializing_if = "Option::is_none")]
+	pub highest_block: Option<U256>,
+	/// Starting block
+	#[serde(rename = "startingBlock", skip_serializing_if = "Option::is_none")]
+	pub starting_block: Option<U256>,
+}
+
+/// EIP-1559 transaction.
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct Transaction1559Unsigned {
+	/// accessList
+	/// EIP-2930 access list
+	#[serde(rename = "accessList")]
+	pub access_list: AccessList,
+	/// chainId
+	/// Chain ID that this transaction is valid on.
+	#[serde(rename = "chainId")]
+	pub chain_id: U256,
+	/// gas limit
+	pub gas: U256,
+	/// gas price
+	/// The effective gas price paid by the sender in wei. For transactions not yet included in a
+	/// block, this value should be set equal to the max fee per gas. This field is DEPRECATED,
+	/// please transition to using effectiveGasPrice in the receipt object going forward.
+	#[serde(rename = "gasPrice")]
+	pub gas_price: U256,
+	/// input data
+	pub input: Bytes,
+	/// max fee per gas
+	/// The maximum total fee per gas the sender is willing to pay (includes the network / base fee
+	/// and miner / priority fee) in wei
+	#[serde(rename = "maxFeePerGas")]
+	pub max_fee_per_gas: U256,
+	/// max priority fee per gas
+	/// Maximum fee per gas the sender is willing to pay to miners in wei
+	#[serde(rename = "maxPriorityFeePerGas")]
+	pub max_priority_fee_per_gas: U256,
+	/// nonce
+	pub nonce: U256,
+	/// to address
+	pub to: Option<Address>,
+	/// type
+	pub r#type: Type2,
+	/// value
+	pub value: U256,
+}
+
+/// EIP-2930 transaction.
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct Transaction2930Unsigned {
+	/// accessList
+	/// EIP-2930 access list
+	#[serde(rename = "accessList")]
+	pub access_list: AccessList,
+	/// chainId
+	/// Chain ID that this transaction is valid on.
+	#[serde(rename = "chainId")]
+	pub chain_id: U256,
+	/// gas limit
+	pub gas: U256,
+	/// gas price
+	/// The gas price willing to be paid by the sender in wei
+	#[serde(rename = "gasPrice")]
+	pub gas_price: U256,
+	/// input data
+	pub input: Bytes,
+	/// nonce
+	pub nonce: U256,
+	/// to address
+	pub to: Option<Address>,
+	/// type
+	pub r#type: Type1,
+	/// value
+	pub value: U256,
+}
+
+/// EIP-4844 transaction.
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct Transaction4844Unsigned {
+	/// accessList
+	/// EIP-2930 access list
+	#[serde(rename = "accessList")]
+	pub access_list: AccessList,
+	/// blobVersionedHashes
+	/// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs.
+	#[serde(rename = "blobVersionedHashes")]
+	pub blob_versioned_hashes: Vec<H256>,
+	/// chainId
+	/// Chain ID that this transaction is valid on.
+	#[serde(rename = "chainId")]
+	pub chain_id: U256,
+	/// gas limit
+	pub gas: U256,
+	/// input data
+	pub input: Bytes,
+	/// max fee per blob gas
+	/// The maximum total fee per gas the sender is willing to pay for blob gas in wei
+	#[serde(rename = "maxFeePerBlobGas")]
+	pub max_fee_per_blob_gas: U256,
+	/// max fee per gas
+	/// The maximum total fee per gas the sender is willing to pay (includes the network / base fee
+	/// and miner / priority fee) in wei
+	#[serde(rename = "maxFeePerGas")]
+	pub max_fee_per_gas: U256,
+	/// max priority fee per gas
+	/// Maximum fee per gas the sender is willing to pay to miners in wei
+	#[serde(rename = "maxPriorityFeePerGas")]
+	pub max_priority_fee_per_gas: U256,
+	/// nonce
+	pub nonce: U256,
+	/// to address
+	pub to: Address,
+	/// type
+	pub r#type: Byte,
+	/// value
+	pub value: U256,
+}
+
+/// Legacy transaction.
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct TransactionLegacyUnsigned {
+	/// chainId
+	/// Chain ID that this transaction is valid on.
+	#[serde(rename = "chainId", skip_serializing_if = "Option::is_none")]
+	pub chain_id: Option<U256>,
+	/// gas limit
+	pub gas: U256,
+	/// gas price
+	/// The gas price willing to be paid by the sender in wei
+	#[serde(rename = "gasPrice")]
+	pub gas_price: U256,
+	/// input data
+	pub input: Bytes,
+	/// nonce
+	pub nonce: U256,
+	/// to address
+	pub to: Option<Address>,
+	/// type
+	pub r#type: Type0,
+	/// value
+	pub value: U256,
+}
+
+#[derive(
+	Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq,
+)]
+#[serde(untagged)]
+pub enum TransactionSigned {
+	Transaction4844Signed(Transaction4844Signed),
+	Transaction1559Signed(Transaction1559Signed),
+	Transaction2930Signed(Transaction2930Signed),
+	TransactionLegacySigned(TransactionLegacySigned),
+}
+impl Default for TransactionSigned {
+	fn default() -> Self {
+		TransactionSigned::Transaction4844Signed(Default::default())
+	}
+}
+
+/// Validator withdrawal
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct Withdrawal {
+	/// recipient address for withdrawal value
+	pub address: Address,
+	/// value contained in withdrawal
+	pub amount: U256,
+	/// index of withdrawal
+	pub index: U256,
+	/// index of validator that generated withdrawal
+	#[serde(rename = "validatorIndex")]
+	pub validator_index: U256,
+}
+
+/// Access list entry
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct AccessListEntry {
+	pub address: Address,
+	#[serde(rename = "storageKeys")]
+	pub storage_keys: Vec<H256>,
+}
+
+/// Signed 1559 Transaction
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct Transaction1559Signed {
+	#[serde(flatten)]
+	pub transaction_1559_unsigned: Transaction1559Unsigned,
+	/// r
+	pub r: U256,
+	/// s
+	pub s: U256,
+	/// v
+	/// For backwards compatibility, `v` is optionally provided as an alternative to `yParity`.
+	/// This field is DEPRECATED and all use of it should migrate to `yParity`.
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub v: Option<U256>,
+	/// yParity
+	/// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.
+	#[serde(rename = "yParity", skip_serializing_if = "Option::is_none")]
+	pub y_parity: Option<U256>,
+}
+
+/// Signed 2930 Transaction
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct Transaction2930Signed {
+	#[serde(flatten)]
+	pub transaction_2930_unsigned: Transaction2930Unsigned,
+	/// r
+	pub r: U256,
+	/// s
+	pub s: U256,
+	/// v
+	/// For backwards compatibility, `v` is optionally provided as an alternative to `yParity`.
+	/// This field is DEPRECATED and all use of it should migrate to `yParity`.
+	#[serde(skip_serializing_if = "Option::is_none")]
+	pub v: Option<U256>,
+	/// yParity
+	/// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.
+	#[serde(rename = "yParity")]
+	pub y_parity: U256,
+}
+
+/// Signed 4844 Transaction
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct Transaction4844Signed {
+	#[serde(flatten)]
+	pub transaction_4844_unsigned: Transaction4844Unsigned,
+	/// r
+	pub r: U256,
+	/// s
+	pub s: U256,
+	/// yParity
+	/// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.
+	#[serde(rename = "yParity", skip_serializing_if = "Option::is_none")]
+	pub y_parity: Option<U256>,
+}
+
+/// Signed Legacy Transaction
+#[derive(
+	Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq,
+)]
+pub struct TransactionLegacySigned {
+	#[serde(flatten)]
+	pub transaction_legacy_unsigned: TransactionLegacyUnsigned,
+	/// r
+	pub r: U256,
+	/// s
+	pub s: U256,
+	/// v
+	pub v: U256,
+}
diff --git a/substrate/frame/revive/src/evm/api/signature.rs b/substrate/frame/revive/src/evm/api/signature.rs
new file mode 100644
index 00000000000..957d50c8e32
--- /dev/null
+++ b/substrate/frame/revive/src/evm/api/signature.rs
@@ -0,0 +1,80 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//! Ethereum signature utilities
+use super::{TransactionLegacySigned, TransactionLegacyUnsigned};
+use rlp::Encodable;
+use sp_core::{H160, U256};
+use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
+
+impl TransactionLegacyUnsigned {
+	/// Recover the Ethereum address, from an RLP encoded transaction and a 65 bytes signature.
+	pub fn recover_eth_address(rlp_encoded: &[u8], signature: &[u8; 65]) -> Result<H160, ()> {
+		let hash = keccak_256(rlp_encoded);
+		let mut addr = H160::default();
+		let pk = secp256k1_ecdsa_recover(&signature, &hash).map_err(|_| ())?;
+		addr.assign_from_slice(&keccak_256(&pk[..])[12..]);
+
+		Ok(addr)
+	}
+}
+
+impl TransactionLegacySigned {
+	/// Create a signed transaction from an [`TransactionLegacyUnsigned`] and a signature.
+	pub fn from(
+		transaction_legacy_unsigned: TransactionLegacyUnsigned,
+		signature: &[u8; 65],
+	) -> TransactionLegacySigned {
+		let r = U256::from_big_endian(&signature[..32]);
+		let s = U256::from_big_endian(&signature[32..64]);
+		let recovery_id = signature[64] as u32;
+		let v = transaction_legacy_unsigned
+			.chain_id
+			.map(|chain_id| chain_id * 2 + 35 + recovery_id)
+			.unwrap_or_else(|| U256::from(27) + recovery_id);
+
+		TransactionLegacySigned { transaction_legacy_unsigned, r, s, v }
+	}
+
+	/// Get the raw 65 bytes signature from the signed transaction.
+	pub fn raw_signature(&self) -> Result<[u8; 65], ()> {
+		let mut s = [0u8; 65];
+		self.r.write_as_big_endian(s[0..32].as_mut());
+		self.s.write_as_big_endian(s[32..64].as_mut());
+		s[64] = self.extract_recovery_id().ok_or(())?;
+		Ok(s)
+	}
+
+	/// Get the recovery ID from the signed transaction.
+	/// See https://eips.ethereum.org/EIPS/eip-155
+	fn extract_recovery_id(&self) -> Option<u8> {
+		if let Some(chain_id) = self.transaction_legacy_unsigned.chain_id {
+			// self.v - chain_id * 2 - 35
+			let v: u64 = self.v.try_into().ok()?;
+			let chain_id: u64 = chain_id.try_into().ok()?;
+			let r = v.checked_sub(chain_id.checked_mul(2)?)?.checked_sub(35)?;
+			r.try_into().ok()
+		} else {
+			self.v.try_into().ok()
+		}
+	}
+
+	/// Recover the Ethereum address from the signed transaction.
+	pub fn recover_eth_address(&self) -> Result<H160, ()> {
+		let rlp_encoded = self.transaction_legacy_unsigned.rlp_bytes();
+		TransactionLegacyUnsigned::recover_eth_address(&rlp_encoded, &self.raw_signature()?)
+	}
+}
diff --git a/substrate/frame/revive/src/evm/api/type_id.rs b/substrate/frame/revive/src/evm/api/type_id.rs
new file mode 100644
index 00000000000..7d75d53500b
--- /dev/null
+++ b/substrate/frame/revive/src/evm/api/type_id.rs
@@ -0,0 +1,95 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//! Ethereum Typed Transaction types
+use super::Byte;
+use codec::{Decode, Encode};
+use scale_info::TypeInfo;
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+
+/// A macro to generate Transaction type identifiers
+/// See <https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope>
+macro_rules! transaction_type {
+	($name:ident, $value:literal) => {
+		#[doc = concat!("Transaction type identifier: ", $value)]
+		#[derive(Clone, Default, Debug, Eq, PartialEq)]
+		pub struct $name;
+
+		impl $name {
+			/// Convert to Byte
+			pub fn as_byte(&self) -> Byte {
+				Byte::from($value)
+			}
+
+			/// Try to convert from Byte
+			pub fn try_from_byte(byte: Byte) -> Result<Self, Byte> {
+				if byte.0 == $value {
+					Ok(Self {})
+				} else {
+					Err(byte)
+				}
+			}
+		}
+
+		impl Encode for $name {
+			fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
+				f(&[$value])
+			}
+		}
+		impl Decode for $name {
+			fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
+				if $value == input.read_byte()? {
+					Ok(Self {})
+				} else {
+					Err(codec::Error::from(concat!("expected ", $value)))
+				}
+			}
+		}
+
+		impl TypeInfo for $name {
+			type Identity = u8;
+			fn type_info() -> scale_info::Type {
+				<u8 as TypeInfo>::type_info()
+			}
+		}
+
+		impl Serialize for $name {
+			fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+			where
+				S: Serializer,
+			{
+				serializer.serialize_str(concat!("0x", $value))
+			}
+		}
+		impl<'de> Deserialize<'de> for $name {
+			fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+			where
+				D: Deserializer<'de>,
+			{
+				let s: &str = Deserialize::deserialize(deserializer)?;
+				if s == concat!("0x", $value) {
+					Ok($name {})
+				} else {
+					Err(serde::de::Error::custom(concat!("expected ", $value)))
+				}
+			}
+		}
+	};
+}
+
+transaction_type!(Type0, 0);
+transaction_type!(Type1, 1);
+transaction_type!(Type2, 2);
diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs
new file mode 100644
index 00000000000..58110bcf186
--- /dev/null
+++ b/substrate/frame/revive/src/evm/runtime.rs
@@ -0,0 +1,685 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//! Runtime types for integrating `pallet-revive` with the EVM.
+use crate::{
+	evm::api::{TransactionLegacySigned, TransactionLegacyUnsigned},
+	AccountIdOf, AddressMapper, BalanceOf, MomentOf, Weight, LOG_TARGET,
+};
+use codec::{Decode, Encode};
+use frame_support::{
+	dispatch::{DispatchInfo, GetDispatchInfo},
+	traits::{ExtrinsicCall, InherentBuilder, SignedTransactionBuilder},
+};
+use pallet_transaction_payment::OnChargeTransaction;
+use scale_info::TypeInfo;
+use sp_arithmetic::Percent;
+use sp_core::{Get, U256};
+use sp_runtime::{
+	generic::{self, CheckedExtrinsic, ExtrinsicFormat},
+	traits::{
+		self, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, IdentifyAccount, Member,
+		TransactionExtension,
+	},
+	transaction_validity::{InvalidTransaction, TransactionValidityError},
+	OpaqueExtrinsic, RuntimeDebug, Saturating,
+};
+
+use alloc::vec::Vec;
+
+type CallOf<T> = <T as frame_system::Config>::RuntimeCall;
+
+/// The EVM gas price.
+/// This constant is used by the proxy to advertise it via the eth_gas_price RPC.
+///
+/// We use a fixed value for the gas price.
+/// This let us calculate the gas estimate for a transaction with the formula:
+/// `estimate_gas = substrate_fee / gas_price`.
+pub const GAS_PRICE: u32 = 1_000u32;
+
+/// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned
+/// [`crate::Call::eth_transact`] extrinsic.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
+#[scale_info(skip_type_params(E))]
+pub struct UncheckedExtrinsic<Address, Signature, E: EthExtra>(
+	pub generic::UncheckedExtrinsic<Address, CallOf<E::Config>, Signature, E::Extension>,
+);
+
+impl<Address, Signature, E: EthExtra>
+	From<generic::UncheckedExtrinsic<Address, CallOf<E::Config>, Signature, E::Extension>>
+	for UncheckedExtrinsic<Address, Signature, E>
+{
+	fn from(
+		utx: generic::UncheckedExtrinsic<Address, CallOf<E::Config>, Signature, E::Extension>,
+	) -> Self {
+		Self(utx)
+	}
+}
+
+impl<Address: TypeInfo, Signature: TypeInfo, E: EthExtra> ExtrinsicLike
+	for UncheckedExtrinsic<Address, Signature, E>
+{
+	fn is_bare(&self) -> bool {
+		ExtrinsicLike::is_bare(&self.0)
+	}
+}
+
+impl<Address, Signature, E: EthExtra> ExtrinsicMetadata
+	for UncheckedExtrinsic<Address, Signature, E>
+{
+	const VERSION: u8 =
+		generic::UncheckedExtrinsic::<Address, CallOf<E::Config>, Signature, E::Extension>::VERSION;
+	type TransactionExtensions = E::Extension;
+}
+
+impl<Address: TypeInfo, Signature: TypeInfo, E: EthExtra> ExtrinsicCall
+	for UncheckedExtrinsic<Address, Signature, E>
+{
+	type Call = CallOf<E::Config>;
+
+	fn call(&self) -> &Self::Call {
+		self.0.call()
+	}
+}
+
+use sp_runtime::traits::MaybeDisplay;
+type OnChargeTransactionBalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<T>>::Balance;
+
+impl<LookupSource, Signature, E, Lookup> Checkable<Lookup>
+	for UncheckedExtrinsic<LookupSource, Signature, E>
+where
+	E: EthExtra,
+	Self: Encode,
+	<E::Config as frame_system::Config>::Nonce: TryFrom<U256>,
+	<E::Config as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo>,
+	OnChargeTransactionBalanceOf<E::Config>: Into<BalanceOf<E::Config>>,
+	BalanceOf<E::Config>: Into<U256> + TryFrom<U256>,
+	MomentOf<E::Config>: Into<U256>,
+	CallOf<E::Config>: From<crate::Call<E::Config>> + TryInto<crate::Call<E::Config>>,
+
+	// required by Checkable for `generic::UncheckedExtrinsic`
+	LookupSource: Member + MaybeDisplay,
+	CallOf<E::Config>: Encode + Member + Dispatchable,
+	Signature: Member + traits::Verify,
+	<Signature as traits::Verify>::Signer: IdentifyAccount<AccountId = AccountIdOf<E::Config>>,
+	E::Extension: Encode + TransactionExtension<CallOf<E::Config>>,
+	Lookup: traits::Lookup<Source = LookupSource, Target = AccountIdOf<E::Config>>,
+{
+	type Checked = CheckedExtrinsic<AccountIdOf<E::Config>, CallOf<E::Config>, E::Extension>;
+
+	fn check(self, lookup: &Lookup) -> Result<Self::Checked, TransactionValidityError> {
+		if !self.0.is_signed() {
+			if let Ok(call) = self.0.function.clone().try_into() {
+				if let crate::Call::eth_transact { payload, gas_limit, storage_deposit_limit } =
+					call
+				{
+					let checked = E::try_into_checked_extrinsic(
+						payload,
+						gas_limit,
+						storage_deposit_limit,
+						self.encoded_size(),
+					)?;
+					return Ok(checked)
+				};
+			}
+		}
+		self.0.check(lookup)
+	}
+
+	#[cfg(feature = "try-runtime")]
+	fn unchecked_into_checked_i_know_what_i_am_doing(
+		self,
+		lookup: &Lookup,
+	) -> Result<Self::Checked, TransactionValidityError> {
+		self.0.unchecked_into_checked_i_know_what_i_am_doing(lookup)
+	}
+}
+
+impl<Address, Signature, E: EthExtra> GetDispatchInfo for UncheckedExtrinsic<Address, Signature, E>
+where
+	CallOf<E::Config>: GetDispatchInfo + Dispatchable,
+{
+	fn get_dispatch_info(&self) -> DispatchInfo {
+		self.0.get_dispatch_info()
+	}
+}
+
+impl<Address: Encode, Signature: Encode, E: EthExtra> serde::Serialize
+	for UncheckedExtrinsic<Address, Signature, E>
+{
+	fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error>
+	where
+		S: ::serde::Serializer,
+	{
+		self.0.serialize(seq)
+	}
+}
+
+impl<'a, Address: Decode, Signature: Decode, E: EthExtra> serde::Deserialize<'a>
+	for UncheckedExtrinsic<Address, Signature, E>
+{
+	fn deserialize<D>(de: D) -> Result<Self, D::Error>
+	where
+		D: serde::Deserializer<'a>,
+	{
+		let r = sp_core::bytes::deserialize(de)?;
+		Decode::decode(&mut &r[..])
+			.map_err(|e| serde::de::Error::custom(sp_runtime::format!("Decode error: {}", e)))
+	}
+}
+
+impl<Address, Signature, E: EthExtra> SignedTransactionBuilder
+	for UncheckedExtrinsic<Address, Signature, E>
+where
+	Address: TypeInfo,
+	CallOf<E::Config>: TypeInfo,
+	Signature: TypeInfo,
+	E::Extension: TypeInfo,
+{
+	type Address = Address;
+	type Signature = Signature;
+	type Extension = E::Extension;
+
+	fn new_signed_transaction(
+		call: Self::Call,
+		signed: Address,
+		signature: Signature,
+		tx_ext: E::Extension,
+	) -> Self {
+		generic::UncheckedExtrinsic::new_signed(call, signed, signature, tx_ext).into()
+	}
+}
+
+impl<Address, Signature, E: EthExtra> InherentBuilder for UncheckedExtrinsic<Address, Signature, E>
+where
+	Address: TypeInfo,
+	CallOf<E::Config>: TypeInfo,
+	Signature: TypeInfo,
+	E::Extension: TypeInfo,
+{
+	fn new_inherent(call: Self::Call) -> Self {
+		generic::UncheckedExtrinsic::new_bare(call).into()
+	}
+}
+
+impl<Address, Signature, E: EthExtra> From<UncheckedExtrinsic<Address, Signature, E>>
+	for OpaqueExtrinsic
+where
+	Address: Encode,
+	Signature: Encode,
+	CallOf<E::Config>: Encode,
+	E::Extension: Encode,
+{
+	fn from(extrinsic: UncheckedExtrinsic<Address, Signature, E>) -> Self {
+		Self::from_bytes(extrinsic.encode().as_slice()).expect(
+			"both OpaqueExtrinsic and UncheckedExtrinsic have encoding that is compatible with \
+				raw Vec<u8> encoding; qed",
+		)
+	}
+}
+
+/// EthExtra convert an unsigned [`crate::Call::eth_transact`] into a [`CheckedExtrinsic`].
+pub trait EthExtra {
+	/// The Runtime configuration.
+	type Config: crate::Config + pallet_transaction_payment::Config;
+
+	/// The Runtime's transaction extension.
+	/// It should include at least:
+	/// - [`frame_system::CheckNonce`] to ensure that the nonce from the Ethereum transaction is
+	///   correct.
+	type Extension: TransactionExtension<CallOf<Self::Config>>;
+
+	/// Get the transaction extension to apply to an unsigned [`crate::Call::eth_transact`]
+	/// extrinsic.
+	///
+	/// # Parameters
+	/// - `nonce`: The nonce extracted from the Ethereum transaction.
+	/// - `tip`: The transaction tip calculated from the Ethereum transaction.
+	fn get_eth_extension(
+		nonce: <Self::Config as frame_system::Config>::Nonce,
+		tip: BalanceOf<Self::Config>,
+	) -> Self::Extension;
+
+	/// Convert the unsigned [`crate::Call::eth_transact`] into a [`CheckedExtrinsic`].
+	/// and ensure that the fees from the Ethereum transaction correspond to the fees computed from
+	/// the encoded_len, the injected gas_limit and storage_deposit_limit.
+	///
+	/// # Parameters
+	/// - `payload`: The RLP-encoded Ethereum transaction.
+	/// - `gas_limit`: The gas limit for the extrinsic
+	/// - `storage_deposit_limit`: The storage deposit limit for the extrinsic,
+	/// - `encoded_len`: The encoded length of the extrinsic.
+	fn try_into_checked_extrinsic(
+		payload: Vec<u8>,
+		gas_limit: Weight,
+		storage_deposit_limit: BalanceOf<Self::Config>,
+		encoded_len: usize,
+	) -> Result<
+		CheckedExtrinsic<AccountIdOf<Self::Config>, CallOf<Self::Config>, Self::Extension>,
+		InvalidTransaction,
+	>
+	where
+		<Self::Config as frame_system::Config>::Nonce: TryFrom<U256>,
+		BalanceOf<Self::Config>: Into<U256> + TryFrom<U256>,
+		MomentOf<Self::Config>: Into<U256>,
+		<Self::Config as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo>,
+		OnChargeTransactionBalanceOf<Self::Config>: Into<BalanceOf<Self::Config>>,
+		CallOf<Self::Config>: From<crate::Call<Self::Config>>,
+	{
+		let tx = rlp::decode::<TransactionLegacySigned>(&payload).map_err(|err| {
+			log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}");
+			InvalidTransaction::Call
+		})?;
+
+		let signer = tx.recover_eth_address().map_err(|err| {
+			log::debug!(target: LOG_TARGET, "Failed to recover signer: {err:?}");
+			InvalidTransaction::BadProof
+		})?;
+
+		let signer =
+			<Self::Config as crate::Config>::AddressMapper::to_account_id_contract(&signer);
+		let TransactionLegacyUnsigned { nonce, chain_id, to, value, input, gas, gas_price, .. } =
+			tx.transaction_legacy_unsigned;
+
+		if chain_id.unwrap_or_default() != <Self::Config as crate::Config>::ChainId::get().into() {
+			log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}");
+			return Err(InvalidTransaction::Call);
+		}
+
+		let call = if let Some(dest) = to {
+			crate::Call::call::<Self::Config> {
+				dest,
+				value: value.try_into().map_err(|_| InvalidTransaction::Call)?,
+				gas_limit,
+				storage_deposit_limit,
+				data: input.0,
+			}
+		} else {
+			let blob = match polkavm::ProgramBlob::blob_length(&input.0) {
+				Some(blob_len) => blob_len
+					.try_into()
+					.ok()
+					.and_then(|blob_len| (input.0.split_at_checked(blob_len))),
+				_ => None,
+			};
+
+			let Some((code, data)) = blob else {
+				log::debug!(target: LOG_TARGET, "Failed to extract polkavm code & data");
+				return Err(InvalidTransaction::Call);
+			};
+
+			crate::Call::instantiate_with_code::<Self::Config> {
+				value: value.try_into().map_err(|_| InvalidTransaction::Call)?,
+				gas_limit,
+				storage_deposit_limit,
+				code: code.to_vec(),
+				data: data.to_vec(),
+				salt: None,
+			}
+		};
+
+		let nonce = nonce.try_into().map_err(|_| InvalidTransaction::Call)?;
+
+		// Fees calculated with the fixed `GAS_PRICE` that should be used to estimate the gas.
+		let eth_fee_no_tip = U256::from(GAS_PRICE)
+			.saturating_mul(gas)
+			.try_into()
+			.map_err(|_| InvalidTransaction::Call)?;
+
+		// Fees with the actual gas_price from the transaction.
+		let eth_fee: BalanceOf<Self::Config> = U256::from(gas_price)
+			.saturating_mul(gas)
+			.try_into()
+			.map_err(|_| InvalidTransaction::Call)?;
+
+		let info = call.get_dispatch_info();
+		let function: CallOf<Self::Config> = call.into();
+
+		// Fees calculated from the extrinsic, without the tip.
+		let actual_fee: BalanceOf<Self::Config> =
+			pallet_transaction_payment::Pallet::<Self::Config>::compute_fee(
+				encoded_len as u32,
+				&info,
+				Default::default(),
+			)
+			.into();
+
+		log::debug!(target: LOG_TARGET, "Checking Ethereum transaction fees:
+			dispatch_info: {info:?}
+			encoded_len: {encoded_len:?}
+			fees: {actual_fee:?}
+		");
+
+		if eth_fee < actual_fee {
+			log::debug!(target: LOG_TARGET, "fees {eth_fee:?} too low for the extrinsic {actual_fee:?}");
+			return Err(InvalidTransaction::Payment.into())
+		}
+
+		let min = actual_fee.min(eth_fee_no_tip);
+		let max = actual_fee.max(eth_fee_no_tip);
+		let diff = Percent::from_rational(max - min, min);
+		if diff > Percent::from_percent(10) {
+			log::debug!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?} should be no more than 10% got {diff:?}");
+			return Err(InvalidTransaction::Call.into())
+		} else {
+			log::debug!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?}:  {diff:?}");
+		}
+
+		let tip = eth_fee.saturating_sub(eth_fee_no_tip);
+		log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce {nonce:?} and tip: {tip:?}");
+		Ok(CheckedExtrinsic {
+			format: ExtrinsicFormat::Signed(signer.into(), Self::get_eth_extension(nonce, tip)),
+			function,
+		})
+	}
+}
+
+#[cfg(test)]
+mod test {
+	use super::*;
+	use crate::{
+		evm::*,
+		test_utils::*,
+		tests::{ExtBuilder, RuntimeCall, RuntimeOrigin, Test},
+	};
+	use frame_support::{error::LookupError, traits::fungible::Mutate};
+	use pallet_revive_fixtures::compile_module;
+	use rlp::Encodable;
+	use sp_runtime::{
+		traits::{Checkable, DispatchTransaction},
+		MultiAddress, MultiSignature,
+	};
+	type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
+
+	/// A simple account that can sign transactions
+	pub struct Account(subxt_signer::eth::Keypair);
+
+	impl Default for Account {
+		fn default() -> Self {
+			Self(subxt_signer::eth::dev::alith())
+		}
+	}
+
+	impl From<subxt_signer::eth::Keypair> for Account {
+		fn from(kp: subxt_signer::eth::Keypair) -> Self {
+			Self(kp)
+		}
+	}
+
+	impl Account {
+		/// Get the [`AccountId`] of the account.
+		pub fn account_id(&self) -> AccountIdOf<Test> {
+			let address = self.address();
+			<Test as crate::Config>::AddressMapper::to_account_id_contract(&address)
+		}
+
+		/// Get the [`H160`] address of the account.
+		pub fn address(&self) -> H160 {
+			H160::from_slice(&self.0.account_id().as_ref())
+		}
+
+		/// Sign a transaction.
+		pub fn sign_transaction(&self, tx: TransactionLegacyUnsigned) -> TransactionLegacySigned {
+			let rlp_encoded = tx.rlp_bytes();
+			let signature = self.0.sign(&rlp_encoded);
+			TransactionLegacySigned::from(tx, signature.as_ref())
+		}
+	}
+
+	#[derive(Clone, PartialEq, Eq, Debug)]
+	pub struct Extra;
+	type SignedExtra = (frame_system::CheckNonce<Test>, ChargeTransactionPayment<Test>);
+
+	use pallet_transaction_payment::ChargeTransactionPayment;
+	impl EthExtra for Extra {
+		type Config = Test;
+		type Extension = SignedExtra;
+
+		fn get_eth_extension(nonce: u32, tip: BalanceOf<Test>) -> Self::Extension {
+			(frame_system::CheckNonce::from(nonce), ChargeTransactionPayment::from(tip))
+		}
+	}
+
+	type Ex = UncheckedExtrinsic<MultiAddress<AccountId32, u32>, MultiSignature, Extra>;
+	struct TestContext;
+
+	impl traits::Lookup for TestContext {
+		type Source = MultiAddress<AccountId32, u32>;
+		type Target = AccountIdOf<Test>;
+		fn lookup(&self, s: Self::Source) -> Result<Self::Target, LookupError> {
+			match s {
+				MultiAddress::Id(id) => Ok(id),
+				_ => Err(LookupError),
+			}
+		}
+	}
+
+	/// A builder for creating an unchecked extrinsic, and test that the check function works.
+	#[derive(Clone)]
+	struct UncheckedExtrinsicBuilder {
+		tx: TransactionLegacyUnsigned,
+		gas_limit: Weight,
+		storage_deposit_limit: BalanceOf<Test>,
+	}
+
+	impl UncheckedExtrinsicBuilder {
+		/// Create a new builder with default values.
+		fn new() -> Self {
+			Self {
+				tx: TransactionLegacyUnsigned {
+					chain_id: Some(<Test as crate::Config>::ChainId::get().into()),
+					gas_price: U256::from(GAS_PRICE),
+					..Default::default()
+				},
+				gas_limit: Weight::zero(),
+				storage_deposit_limit: 0,
+			}
+		}
+
+		/// Create a new builder with a call to the given address.
+		fn call_with(dest: H160) -> Self {
+			let mut builder = Self::new();
+			builder.tx.to = Some(dest);
+			builder.tx.gas = U256::from(516_708u128);
+			builder
+		}
+
+		/// Create a new builder with an instantiate call.
+		fn instantiate_with(code: Vec<u8>, data: Vec<u8>) -> Self {
+			let mut builder = Self::new();
+			builder.tx.input = Bytes(code.into_iter().chain(data.into_iter()).collect());
+			builder.tx.gas = U256::from(1_035_070u128);
+			builder
+		}
+
+		/// Update the transaction with the given function.
+		fn update(mut self, f: impl FnOnce(&mut TransactionLegacyUnsigned) -> ()) -> Self {
+			f(&mut self.tx);
+			self
+		}
+
+		/// Call `check` on the unchecked extrinsic, and `pre_dispatch` on the signed extension.
+		fn check(&self) -> Result<(RuntimeCall, SignedExtra), TransactionValidityError> {
+			let UncheckedExtrinsicBuilder { tx, gas_limit, storage_deposit_limit } = self.clone();
+
+			// Fund the account.
+			let account = Account::default();
+			let _ = <Test as crate::Config>::Currency::set_balance(
+				&account.account_id(),
+				100_000_000_000_000,
+			);
+
+			let payload = account.sign_transaction(tx).rlp_bytes().to_vec();
+			let call = RuntimeCall::Contracts(crate::Call::eth_transact {
+				payload,
+				gas_limit,
+				storage_deposit_limit,
+			});
+
+			let encoded_len = call.encoded_size();
+			let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into();
+			let result: CheckedExtrinsic<_, _, _> = uxt.check(&TestContext {})?;
+			let (account_id, extra): (AccountId32, SignedExtra) = match result.format {
+				ExtrinsicFormat::Signed(signer, extra) => (signer, extra),
+				_ => unreachable!(),
+			};
+
+			extra.clone().validate_and_prepare(
+				RuntimeOrigin::signed(account_id),
+				&result.function,
+				&result.function.get_dispatch_info(),
+				encoded_len,
+			)?;
+
+			Ok((result.function, extra))
+		}
+	}
+
+	#[test]
+	fn check_eth_transact_call_works() {
+		ExtBuilder::default().build().execute_with(|| {
+			let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20]));
+			assert_eq!(
+				builder.check().unwrap().0,
+				crate::Call::call::<Test> {
+					dest: builder.tx.to.unwrap(),
+					value: builder.tx.value.as_u64(),
+					gas_limit: builder.gas_limit,
+					storage_deposit_limit: builder.storage_deposit_limit,
+					data: builder.tx.input.0
+				}
+				.into()
+			);
+		});
+	}
+
+	#[test]
+	fn check_eth_transact_instantiate_works() {
+		ExtBuilder::default().build().execute_with(|| {
+			let (code, _) = compile_module("dummy").unwrap();
+			let data = vec![];
+			let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone());
+
+			assert_eq!(
+				builder.check().unwrap().0,
+				crate::Call::instantiate_with_code::<Test> {
+					value: builder.tx.value.as_u64(),
+					gas_limit: builder.gas_limit,
+					storage_deposit_limit: builder.storage_deposit_limit,
+					code,
+					data,
+					salt: None
+				}
+				.into()
+			);
+		});
+	}
+
+	#[test]
+	fn check_eth_transact_nonce_works() {
+		ExtBuilder::default().build().execute_with(|| {
+			let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20]))
+				.update(|tx| tx.nonce = 1u32.into());
+
+			assert_eq!(
+				builder.check(),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Future))
+			);
+
+			<crate::System<Test>>::inc_account_nonce(Account::default().account_id());
+
+			let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20]));
+			assert_eq!(
+				builder.check(),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))
+			);
+		});
+	}
+
+	#[test]
+	fn check_eth_transact_chain_id_works() {
+		ExtBuilder::default().build().execute_with(|| {
+			let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20]))
+				.update(|tx| tx.chain_id = Some(42.into()));
+
+			assert_eq!(
+				builder.check(),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Call))
+			);
+		});
+	}
+
+	#[test]
+	fn check_instantiate_data() {
+		ExtBuilder::default().build().execute_with(|| {
+			let code = b"invalid code".to_vec();
+			let data = vec![1];
+			let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone());
+
+			// Fail because the tx input fail to get the blob length
+			assert_eq!(
+				builder.clone().update(|tx| tx.input = Bytes(vec![1, 2, 3])).check(),
+				Err(TransactionValidityError::Invalid(InvalidTransaction::Call))
+			);
+		});
+	}
+
+	#[test]
+	fn check_transaction_fees() {
+		ExtBuilder::default().build().execute_with(|| {
+			let scenarios: [(_, Box<dyn FnOnce(&mut TransactionLegacyUnsigned)>, _); 5] = [
+				("Eth fees too low", Box::new(|tx| tx.gas_price /= 2), InvalidTransaction::Payment),
+				("Gas fees too high", Box::new(|tx| tx.gas *= 2), InvalidTransaction::Call),
+				("Gas fees too low", Box::new(|tx| tx.gas *= 2), InvalidTransaction::Call),
+				(
+					"Diff > 10%",
+					Box::new(|tx| tx.gas = tx.gas * 111 / 100),
+					InvalidTransaction::Call,
+				),
+				(
+					"Diff < 10%",
+					Box::new(|tx| {
+						tx.gas_price *= 2;
+						tx.gas = tx.gas * 89 / 100
+					}),
+					InvalidTransaction::Call,
+				),
+			];
+
+			for (msg, update_tx, err) in scenarios {
+				let builder =
+					UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).update(update_tx);
+
+				assert_eq!(builder.check(), Err(TransactionValidityError::Invalid(err)), "{}", msg);
+			}
+		});
+	}
+
+	#[test]
+	fn check_transaction_tip() {
+		ExtBuilder::default().build().execute_with(|| {
+			let (code, _) = compile_module("dummy").unwrap();
+			let data = vec![];
+			let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone())
+				.update(|tx| tx.gas_price = tx.gas_price * 103 / 100);
+
+			let tx = &builder.tx;
+			let expected_tip = tx.gas_price * tx.gas - U256::from(GAS_PRICE) * tx.gas;
+			let (_, extra) = builder.check().unwrap();
+			assert_eq!(U256::from(extra.1.tip()), expected_tip);
+		});
+	}
+}
diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs
index 07dbd096339..759fba9f1c6 100644
--- a/substrate/frame/revive/src/exec.rs
+++ b/substrate/frame/revive/src/exec.rs
@@ -841,6 +841,7 @@ where
 			storage_meter,
 			BalanceOf::<T>::zero(),
 			false,
+			true,
 		)?
 		else {
 			return Ok(None);
@@ -874,6 +875,7 @@ where
 		storage_meter: &mut storage::meter::GenericMeter<T, S>,
 		deposit_limit: BalanceOf<T>,
 		read_only: bool,
+		origin_is_caller: bool,
 	) -> Result<Option<(Frame<T>, E)>, ExecError> {
 		let (account_id, contract_info, executable, delegate_caller, entry_point) = match frame_args
 		{
@@ -905,7 +907,17 @@ where
 				let address = if let Some(salt) = salt {
 					address::create2(&deployer, executable.code(), input_data, salt)
 				} else {
-					address::create1(&deployer, account_nonce.saturated_into())
+					use sp_runtime::Saturating;
+					address::create1(
+						&deployer,
+						// the Nonce from the origin has been incremented pre-dispatch, so we need
+						// to subtract 1 to get the nonce at the time of the call.
+						if origin_is_caller {
+							account_nonce.saturating_sub(1u32.into()).saturated_into()
+						} else {
+							account_nonce.saturated_into()
+						},
+					)
 				};
 				let contract = ContractInfo::new(
 					&address,
@@ -976,6 +988,7 @@ where
 			nested_storage,
 			deposit_limit,
 			read_only,
+			false,
 		)? {
 			self.frames.try_push(frame).map_err(|_| Error::<T>::MaxCallDepthReached)?;
 			Ok(Some(executable))
diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs
index 9986da472c9..9b0bbb2d6bc 100644
--- a/substrate/frame/revive/src/lib.rs
+++ b/substrate/frame/revive/src/lib.rs
@@ -26,25 +26,23 @@ mod benchmarking;
 mod benchmarking_dummy;
 mod exec;
 mod gas;
-mod primitives;
-use crate::exec::MomentOf;
-use frame_support::traits::IsType;
-pub use primitives::*;
-use sp_core::U256;
-
 mod limits;
+mod primitives;
 mod storage;
 mod transient_storage;
 mod wasm;
 
+#[cfg(test)]
+mod tests;
+
 pub mod chain_extension;
 pub mod debug;
+pub mod evm;
 pub mod test_utils;
 pub mod weights;
 
-#[cfg(test)]
-mod tests;
 use crate::{
+	evm::{runtime::GAS_PRICE, TransactionLegacyUnsigned},
 	exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack},
 	gas::GasMeter,
 	storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager},
@@ -58,9 +56,10 @@ use frame_support::{
 		PostDispatchInfo, RawOrigin,
 	},
 	ensure,
+	pallet_prelude::DispatchClass,
 	traits::{
 		fungible::{Inspect, Mutate, MutateHold},
-		ConstU32, ConstU64, Contains, EnsureOrigin, Get, Time,
+		ConstU32, ConstU64, Contains, EnsureOrigin, Get, IsType, OriginTrait, Time,
 	},
 	weights::{Weight, WeightMeter},
 	BoundedVec, RuntimeDebugNoBound,
@@ -70,18 +69,21 @@ use frame_system::{
 	pallet_prelude::{BlockNumberFor, OriginFor},
 	EventRecord, Pallet as System,
 };
+use pallet_transaction_payment::OnChargeTransaction;
 use scale_info::TypeInfo;
-use sp_core::{H160, H256};
+use sp_core::{H160, H256, U256};
 use sp_runtime::{
 	traits::{BadOrigin, Convert, Dispatchable, Saturating},
 	DispatchError,
 };
 
 pub use crate::{
-	address::{AddressMapper, DefaultAddressMapper},
+	address::{create1, create2, AddressMapper, DefaultAddressMapper},
 	debug::Tracing,
+	exec::MomentOf,
 	pallet::*,
 };
+pub use primitives::*;
 pub use weights::WeightInfo;
 
 #[cfg(doc)]
@@ -90,6 +92,7 @@ pub use crate::wasm::SyscallDoc;
 type TrieId = BoundedVec<u8, ConstU32<128>>;
 type BalanceOf<T> =
 	<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
+type OnChargeTransactionBalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<T>>::Balance;
 type CodeVec = BoundedVec<u8, ConstU32<{ limits::code::BLOB_BYTES }>>;
 type EventRecordOf<T> =
 	EventRecord<<T as frame_system::Config>::RuntimeEvent, <T as frame_system::Config>::Hash>;
@@ -134,7 +137,7 @@ pub mod pallet {
 	use sp_runtime::Perbill;
 
 	/// The in-code storage version.
-	pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
+	pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
 
 	#[pallet::pallet]
 	#[pallet::storage_version(STORAGE_VERSION)]
@@ -160,6 +163,7 @@ pub mod pallet {
 		type RuntimeCall: Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
 			+ GetDispatchInfo
 			+ codec::Decode
+			+ core::fmt::Debug
 			+ IsType<<Self as frame_system::Config>::RuntimeCall>;
 
 		/// Overarching hold reason.
@@ -738,6 +742,33 @@ pub mod pallet {
 		BalanceOf<T>: Into<U256> + TryFrom<U256>,
 		MomentOf<T>: Into<U256>,
 	{
+		/// A raw EVM transaction, typically dispatched by an Ethereum JSON-RPC server.
+		///
+		/// # Parameters
+		///
+		/// * `payload`: The RLP-encoded [`crate::evm::TransactionLegacySigned`].
+		/// * `gas_limit`: The gas limit enforced during contract execution.
+		/// * `storage_deposit_limit`: The maximum balance that can be charged to the caller for
+		///   storage usage.
+		///
+		/// # Note
+		///
+		/// This call cannot be dispatched directly; attempting to do so will result in a failed
+		/// transaction. It serves as a wrapper for an Ethereum transaction. When submitted, the
+		/// runtime converts it into a [`sp_runtime::generic::CheckedExtrinsic`] by recovering the
+		/// signer and validating the transaction.
+		#[allow(unused_variables)]
+		#[pallet::call_index(0)]
+		#[pallet::weight(Weight::MAX)]
+		pub fn eth_transact(
+			origin: OriginFor<T>,
+			payload: Vec<u8>,
+			gas_limit: Weight,
+			#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
+		) -> DispatchResultWithPostInfo {
+			Err(frame_system::Error::CallFiltered::<T>.into())
+		}
+
 		/// Makes a call to an account, optionally transferring some balance.
 		///
 		/// # Parameters
@@ -754,7 +785,7 @@ pub mod pallet {
 		/// * If the account is a regular account, any value will be transferred.
 		/// * If no account exists and the call value is not less than `existential_deposit`,
 		/// a regular account will be created and any value will be transferred.
-		#[pallet::call_index(0)]
+		#[pallet::call_index(1)]
 		#[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))]
 		pub fn call(
 			origin: OriginFor<T>,
@@ -764,6 +795,7 @@ pub mod pallet {
 			#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
 			data: Vec<u8>,
 		) -> DispatchResultWithPostInfo {
+			log::info!(target: LOG_TARGET, "Call: {:?} {:?} {:?}", dest, value, data);
 			let mut output = Self::bare_call(
 				origin,
 				dest,
@@ -787,7 +819,7 @@ pub mod pallet {
 		/// This function is identical to [`Self::instantiate_with_code`] but without the
 		/// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary
 		/// must be supplied.
-		#[pallet::call_index(1)]
+		#[pallet::call_index(2)]
 		#[pallet::weight(
 			T::WeightInfo::instantiate(data.len() as u32).saturating_add(*gas_limit)
 		)]
@@ -851,7 +883,7 @@ pub mod pallet {
 		/// - The smart-contract account is created at the computed address.
 		/// - The `value` is transferred to the new account.
 		/// - The `deploy` function is executed in the context of the newly-created account.
-		#[pallet::call_index(2)]
+		#[pallet::call_index(3)]
 		#[pallet::weight(
 			T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32)
 			.saturating_add(*gas_limit)
@@ -902,7 +934,7 @@ pub mod pallet {
 		/// To avoid this situation a constructor could employ access control so that it can
 		/// only be instantiated by permissioned entities. The same is true when uploading
 		/// through [`Self::instantiate_with_code`].
-		#[pallet::call_index(3)]
+		#[pallet::call_index(4)]
 		#[pallet::weight(T::WeightInfo::upload_code(code.len() as u32))]
 		pub fn upload_code(
 			origin: OriginFor<T>,
@@ -916,7 +948,7 @@ pub mod pallet {
 		///
 		/// A code can only be removed by its original uploader (its owner) and only if it is
 		/// not used by any contract.
-		#[pallet::call_index(4)]
+		#[pallet::call_index(5)]
 		#[pallet::weight(T::WeightInfo::remove_code())]
 		pub fn remove_code(
 			origin: OriginFor<T>,
@@ -938,7 +970,7 @@ pub mod pallet {
 		/// This does **not** change the address of the contract in question. This means
 		/// that the contract address is no longer derived from its code hash after calling
 		/// this dispatchable.
-		#[pallet::call_index(5)]
+		#[pallet::call_index(6)]
 		#[pallet::weight(T::WeightInfo::set_code())]
 		pub fn set_code(
 			origin: OriginFor<T>,
@@ -1002,7 +1034,7 @@ where
 		data: Vec<u8>,
 		debug: DebugInfo,
 		collect_events: CollectEvents,
-	) -> ContractExecResult<BalanceOf<T>, EventRecordOf<T>> {
+	) -> ContractResult<ExecReturnValue, BalanceOf<T>, EventRecordOf<T>> {
 		let mut gas_meter = GasMeter::new(gas_limit);
 		let mut storage_deposit = Default::default();
 		let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) {
@@ -1031,7 +1063,7 @@ where
 		} else {
 			None
 		};
-		ContractExecResult {
+		ContractResult {
 			result: result.map_err(|r| r.error),
 			gas_consumed: gas_meter.gas_consumed(),
 			gas_required: gas_meter.gas_required(),
@@ -1057,7 +1089,7 @@ where
 		salt: Option<[u8; 32]>,
 		debug: DebugInfo,
 		collect_events: CollectEvents,
-	) -> ContractInstantiateResult<BalanceOf<T>, EventRecordOf<T>> {
+	) -> ContractResult<InstantiateReturnValue, BalanceOf<T>, EventRecordOf<T>> {
 		let mut gas_meter = GasMeter::new(gas_limit);
 		let mut storage_deposit = Default::default();
 		let mut debug_message =
@@ -1099,7 +1131,7 @@ where
 		} else {
 			None
 		};
-		ContractInstantiateResult {
+		ContractResult {
 			result: output
 				.map(|(addr, result)| InstantiateReturnValue { result, addr })
 				.map_err(|e| e.error),
@@ -1111,6 +1143,184 @@ where
 		}
 	}
 
+	/// A version of [`Self::eth_transact`] used to dry-run Ethereum calls.
+	///
+	/// # Parameters
+	///
+	/// - `origin`: The origin of the call.
+	/// - `dest`: The destination address of the call.
+	/// - `value`: The value to transfer.
+	/// - `input`: The input data.
+	/// - `gas_limit`: The gas limit enforced during contract execution.
+	/// - `storage_deposit_limit`: The maximum balance that can be charged to the caller for storage
+	///   usage.
+	/// - `utx_encoded_size`: A function that takes a call and returns the encoded size of the
+	///   unchecked extrinsic.
+	/// - `debug`: Debugging configuration.
+	/// - `collect_events`: Event collection configuration.
+	pub fn bare_eth_transact(
+		origin: T::AccountId,
+		dest: Option<H160>,
+		value: BalanceOf<T>,
+		input: Vec<u8>,
+		gas_limit: Weight,
+		storage_deposit_limit: BalanceOf<T>,
+		utx_encoded_size: impl Fn(Call<T>) -> u32,
+		debug: DebugInfo,
+		collect_events: CollectEvents,
+	) -> EthContractResult<BalanceOf<T>>
+	where
+		T: pallet_transaction_payment::Config,
+		<T as frame_system::Config>::RuntimeCall:
+			Dispatchable<Info = frame_support::dispatch::DispatchInfo>,
+		<T as Config>::RuntimeCall: From<crate::Call<T>>,
+		<T as Config>::RuntimeCall: Encode,
+		OnChargeTransactionBalanceOf<T>: Into<BalanceOf<T>>,
+		T::Nonce: Into<U256>,
+	{
+		// Get the nonce to encode in the tx.
+		let nonce: T::Nonce = <System<T>>::account_nonce(&origin);
+
+		// Use a big enough gas price to ensure that the encoded size is large enough.
+		let max_gas_fee: BalanceOf<T> =
+			(pallet_transaction_payment::Pallet::<T>::weight_to_fee(Weight::MAX) /
+				GAS_PRICE.into())
+			.into();
+
+		// A contract call.
+		if let Some(dest) = dest {
+			// Dry run the call.
+			let result = crate::Pallet::<T>::bare_call(
+				T::RuntimeOrigin::signed(origin),
+				dest,
+				value,
+				gas_limit,
+				storage_deposit_limit,
+				input.clone(),
+				debug,
+				collect_events,
+			);
+
+			// Get the encoded size of the transaction.
+			let tx = TransactionLegacyUnsigned {
+				value: value.into(),
+				input: input.into(),
+				nonce: nonce.into(),
+				chain_id: Some(T::ChainId::get().into()),
+				gas_price: GAS_PRICE.into(),
+				gas: max_gas_fee.into(),
+				to: Some(dest),
+				..Default::default()
+			};
+			let eth_dispatch_call = crate::Call::<T>::eth_transact {
+				payload: tx.dummy_signed_payload(),
+				gas_limit: result.gas_required,
+				storage_deposit_limit: result.storage_deposit.charge_or_zero(),
+			};
+			let encoded_len = utx_encoded_size(eth_dispatch_call);
+
+			// Get the dispatch info of the call.
+			let dispatch_call: <T as Config>::RuntimeCall = crate::Call::<T>::call {
+				dest,
+				value,
+				gas_limit: result.gas_required,
+				storage_deposit_limit: result.storage_deposit.charge_or_zero(),
+				data: tx.input.0,
+			}
+			.into();
+			let dispatch_info = dispatch_call.get_dispatch_info();
+
+			// Compute the fee.
+			let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(
+				encoded_len,
+				&dispatch_info,
+				0u32.into(),
+			)
+			.into();
+
+			log::debug!(target: LOG_TARGET, "Call dry run Result: dispatch_info: {dispatch_info:?} len: {encoded_len:?} fee: {fee:?}");
+			EthContractResult {
+				gas_required: result.gas_required,
+				storage_deposit: result.storage_deposit.charge_or_zero(),
+				result: result.result.map(|v| v.data),
+				fee,
+			}
+			// A contract deployment
+		} else {
+			// Extract code and data from the input.
+			let (code, data) = match polkavm::ProgramBlob::blob_length(&input) {
+				Some(blob_len) => blob_len
+					.try_into()
+					.ok()
+					.and_then(|blob_len| (input.split_at_checked(blob_len)))
+					.unwrap_or_else(|| (&input[..], &[][..])),
+				_ => {
+					log::debug!(target: LOG_TARGET, "Failed to extract polkavm blob length");
+					(&input[..], &[][..])
+				},
+			};
+
+			// Dry run the call.
+			let result = crate::Pallet::<T>::bare_instantiate(
+				T::RuntimeOrigin::signed(origin),
+				value,
+				gas_limit,
+				storage_deposit_limit,
+				Code::Upload(code.to_vec()),
+				data.to_vec(),
+				None,
+				debug,
+				collect_events,
+			);
+
+			// Get the encoded size of the transaction.
+			let tx = TransactionLegacyUnsigned {
+				gas: max_gas_fee.into(),
+				nonce: nonce.into(),
+				value: value.into(),
+				input: input.clone().into(),
+				gas_price: GAS_PRICE.into(),
+				chain_id: Some(T::ChainId::get().into()),
+				..Default::default()
+			};
+			let eth_dispatch_call = crate::Call::<T>::eth_transact {
+				payload: tx.dummy_signed_payload(),
+				gas_limit: result.gas_required,
+				storage_deposit_limit: result.storage_deposit.charge_or_zero(),
+			};
+			let encoded_len = utx_encoded_size(eth_dispatch_call);
+
+			// Get the dispatch info of the call.
+			let dispatch_call: <T as Config>::RuntimeCall =
+				crate::Call::<T>::instantiate_with_code {
+					value,
+					gas_limit: result.gas_required,
+					storage_deposit_limit: result.storage_deposit.charge_or_zero(),
+					code: code.to_vec(),
+					data: data.to_vec(),
+					salt: None,
+				}
+				.into();
+			let dispatch_info = dispatch_call.get_dispatch_info();
+
+			// Compute the fee.
+			let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(
+				encoded_len,
+				&dispatch_info,
+				0u32.into(),
+			)
+			.into();
+
+			log::debug!(target: LOG_TARGET, "Call dry run Result: dispatch_info: {dispatch_info:?} len: {encoded_len:?} fee: {fee:?}");
+			EthContractResult {
+				gas_required: result.gas_required,
+				storage_deposit: result.storage_deposit.charge_or_zero(),
+				result: result.result.map(|v| v.result.data),
+				fee,
+			}
+		}
+	}
+
 	/// A generalized version of [`Self::upload_code`].
 	///
 	/// It is identical to [`Self::upload_code`] and only differs in the information it returns.
@@ -1199,7 +1409,7 @@ sp_api::decl_runtime_apis! {
 			gas_limit: Option<Weight>,
 			storage_deposit_limit: Option<Balance>,
 			input_data: Vec<u8>,
-		) -> ContractExecResult<Balance, EventRecord>;
+		) -> ContractResult<ExecReturnValue, Balance, EventRecord>;
 
 		/// Instantiate a new contract.
 		///
@@ -1212,7 +1422,20 @@ sp_api::decl_runtime_apis! {
 			code: Code,
 			data: Vec<u8>,
 			salt: Option<[u8; 32]>,
-		) -> ContractInstantiateResult<Balance, EventRecord>;
+		) -> ContractResult<InstantiateReturnValue, Balance, EventRecord>;
+
+
+		/// Perform an Ethereum call.
+		///
+		/// See [`crate::Pallet::bare_eth_transact`]
+		fn eth_transact(
+			origin: H160,
+			dest: Option<H160>,
+			value: Balance,
+			input: Vec<u8>,
+			gas_limit: Option<Weight>,
+			storage_deposit_limit: Option<Balance>,
+		) -> EthContractResult<Balance>;
 
 		/// Upload new code without instantiating a contract from it.
 		///
diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs
index 67bc144c3dd..af0100d59cb 100644
--- a/substrate/frame/revive/src/primitives.rs
+++ b/substrate/frame/revive/src/primitives.rs
@@ -76,19 +76,24 @@ pub struct ContractResult<R, Balance, EventRecord> {
 	/// RPC calls.
 	pub debug_message: Vec<u8>,
 	/// The execution result of the wasm code.
-	pub result: R,
+	pub result: Result<R, DispatchError>,
 	/// The events that were emitted during execution. It is an option as event collection is
 	/// optional.
 	pub events: Option<Vec<EventRecord>>,
 }
 
-/// Result type of a `bare_call` call as well as `ContractsApi::call`.
-pub type ContractExecResult<Balance, EventRecord> =
-	ContractResult<Result<ExecReturnValue, DispatchError>, Balance, EventRecord>;
-
-/// Result type of a `bare_instantiate` call as well as `ContractsApi::instantiate`.
-pub type ContractInstantiateResult<Balance, EventRecord> =
-	ContractResult<Result<InstantiateReturnValue, DispatchError>, Balance, EventRecord>;
+/// The result of the execution of a `eth_transact` call.
+#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
+pub struct EthContractResult<Balance> {
+	/// The fee charged for the execution.
+	pub fee: Balance,
+	/// The amount of gas that was necessary to execute the transaction.
+	pub gas_required: Weight,
+	/// Storage deposit charged.
+	pub storage_deposit: Balance,
+	/// The execution result.
+	pub result: Result<Vec<u8>, DispatchError>,
+}
 
 /// Result type of a `bare_code_upload` call.
 pub type CodeUploadResult<Balance> = Result<CodeUploadReturnValue<Balance>, DispatchError>;
diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs
index d361590df95..e64f5889443 100644
--- a/substrate/frame/revive/src/test_utils/builder.rs
+++ b/substrate/frame/revive/src/test_utils/builder.rs
@@ -17,9 +17,8 @@
 
 use super::{deposit_limit, GAS_LIMIT};
 use crate::{
-	address::AddressMapper, AccountIdOf, BalanceOf, Code, CollectEvents, Config,
-	ContractExecResult, ContractInstantiateResult, DebugInfo, EventRecordOf, ExecReturnValue,
-	InstantiateReturnValue, OriginFor, Pallet, Weight,
+	address::AddressMapper, AccountIdOf, BalanceOf, Code, CollectEvents, Config, ContractResult,
+	DebugInfo, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight,
 };
 use frame_support::pallet_prelude::DispatchResultWithPostInfo;
 use paste::paste;
@@ -140,7 +139,7 @@ builder!(
 		salt: Option<[u8; 32]>,
 		debug: DebugInfo,
 		collect_events: CollectEvents,
-	) -> ContractInstantiateResult<BalanceOf<T>, EventRecordOf<T>>;
+	) -> ContractResult<InstantiateReturnValue, BalanceOf<T>, EventRecordOf<T>>;
 
 	/// Build the instantiate call and unwrap the result.
 	pub fn build_and_unwrap_result(self) -> InstantiateReturnValue {
@@ -203,7 +202,7 @@ builder!(
 		data: Vec<u8>,
 		debug: DebugInfo,
 		collect_events: CollectEvents,
-	) -> ContractExecResult<BalanceOf<T>, EventRecordOf<T>>;
+	) -> ContractResult<ExecReturnValue, BalanceOf<T>, EventRecordOf<T>>;
 
 	/// Build the call and unwrap the result.
 	pub fn build_and_unwrap_result(self) -> ExecReturnValue {
diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs
index e637c5f991c..94af7dbd04d 100644
--- a/substrate/frame/revive/src/tests.rs
+++ b/substrate/frame/revive/src/tests.rs
@@ -58,16 +58,17 @@ use frame_support::{
 		tokens::Preservation,
 		ConstU32, ConstU64, Contains, OnIdle, OnInitialize, StorageVersion,
 	},
-	weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight, WeightMeter},
+	weights::{constants::WEIGHT_REF_TIME_PER_SECOND, FixedFee, IdentityFee, Weight, WeightMeter},
 };
 use frame_system::{EventRecord, Phase};
 use pallet_revive_fixtures::{bench::dummy_unique, compile_module};
 use pallet_revive_uapi::ReturnErrorCode as RuntimeReturnCode;
+use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier};
 use sp_io::hashing::blake2_256;
 use sp_keystore::{testing::MemoryKeystore, KeystoreExt};
 use sp_runtime::{
 	testing::H256,
-	traits::{BlakeTwo256, Convert, IdentityLookup},
+	traits::{BlakeTwo256, Convert, IdentityLookup, One},
 	AccountId32, BuildStorage, DispatchError, Perbill, TokenError,
 };
 
@@ -82,6 +83,7 @@ frame_support::construct_runtime!(
 		Utility: pallet_utility,
 		Contracts: pallet_revive,
 		Proxy: pallet_proxy,
+		TransactionPayment: pallet_transaction_payment,
 		Dummy: pallet_dummy
 	}
 );
@@ -415,6 +417,18 @@ impl pallet_proxy::Config for Test {
 	type AnnouncementDepositFactor = ConstU64<1>;
 }
 
+parameter_types! {
+	pub FeeMultiplier: Multiplier = Multiplier::one();
+}
+
+#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)]
+impl pallet_transaction_payment::Config for Test {
+	type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter<Balances, ()>;
+	type WeightToFee = IdentityFee<<Self as pallet_balances::Config>::Balance>;
+	type LengthToFee = FixedFee<100, <Self as pallet_balances::Config>::Balance>;
+	type FeeMultiplierUpdate = ConstFeeMultiplier<FeeMultiplier>;
+}
+
 impl pallet_dummy::Config for Test {}
 
 parameter_types! {
@@ -509,6 +523,17 @@ impl Config for Test {
 	type ChainId = ChainId;
 }
 
+impl TryFrom<RuntimeCall> for crate::Call<Test> {
+	type Error = ();
+
+	fn try_from(value: RuntimeCall) -> Result<Self, Self::Error> {
+		match value {
+			RuntimeCall::Contracts(call) => Ok(call),
+			_ => Err(()),
+		}
+	}
+}
+
 pub struct ExtBuilder {
 	existential_deposit: u64,
 	storage_version: Option<StorageVersion>,
@@ -727,15 +752,16 @@ mod run_tests {
 			));
 
 			assert_eq!(System::account_nonce(&ALICE), 0);
+			System::inc_account_nonce(&ALICE);
 
-			for nonce in 0..3 {
+			for nonce in 1..3 {
 				let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash))
 					.salt(None)
 					.build_and_unwrap_contract();
 				assert!(ContractInfoOf::<Test>::contains_key(&addr));
 				assert_eq!(
 					addr,
-					create1(&<Test as Config>::AddressMapper::to_address(&ALICE), nonce)
+					create1(&<Test as Config>::AddressMapper::to_address(&ALICE), nonce - 1)
 				);
 			}
 			assert_eq!(System::account_nonce(&ALICE), 3);
@@ -747,7 +773,7 @@ mod run_tests {
 				assert!(ContractInfoOf::<Test>::contains_key(&addr));
 				assert_eq!(
 					addr,
-					create1(&<Test as Config>::AddressMapper::to_address(&ALICE), nonce)
+					create1(&<Test as Config>::AddressMapper::to_address(&ALICE), nonce - 1)
 				);
 			}
 			assert_eq!(System::account_nonce(&ALICE), 6);
diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs
index e2256d7dcea..2b802290384 100644
--- a/substrate/frame/revive/src/wasm/mod.rs
+++ b/substrate/frame/revive/src/wasm/mod.rs
@@ -200,7 +200,10 @@ where
 						&self.code_info.owner,
 						deposit,
 					)
-					.map_err(|_| <Error<T>>::StorageDepositNotEnoughFunds)?;
+					.map_err(|err| {
+						log::debug!(target: LOG_TARGET, "failed to store code for owner: {:?}: {err:?}", self.code_info.owner);
+						<Error<T>>::StorageDepositNotEnoughFunds
+					})?;
 
 					self.code_info.refcount = 0;
 					<PristineCode<T>>::insert(code_hash, &self.code);
diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs
index 78c8b192965..00be26aeaf8 100644
--- a/substrate/frame/revive/src/wasm/runtime.rs
+++ b/substrate/frame/revive/src/wasm/runtime.rs
@@ -44,11 +44,6 @@ type CallOf<T> = <T as frame_system::Config>::RuntimeCall;
 /// The maximum nesting depth a contract can use when encoding types.
 const MAX_DECODE_NESTING: u32 = 256;
 
-/// Encode a `U256` into a 32 byte buffer.
-fn as_bytes(u: U256) -> [u8; 32] {
-	u.to_little_endian()
-}
-
 #[derive(Clone, Copy)]
 pub enum ApiVersion {
 	/// Expose all APIs even unversioned ones. Only used for testing and benchmarking.
@@ -1545,7 +1540,7 @@ pub mod env {
 		Ok(self.write_fixed_sandbox_output(
 			memory,
 			out_ptr,
-			&as_bytes(self.ext.balance()),
+			&self.ext.balance().to_little_endian(),
 			false,
 			already_charged,
 		)?)
@@ -1566,7 +1561,7 @@ pub mod env {
 		Ok(self.write_fixed_sandbox_output(
 			memory,
 			out_ptr,
-			&as_bytes(self.ext.balance_of(&address)),
+			&self.ext.balance_of(&address).to_little_endian(),
 			false,
 			already_charged,
 		)?)
@@ -1579,7 +1574,7 @@ pub mod env {
 		Ok(self.write_fixed_sandbox_output(
 			memory,
 			out_ptr,
-			&as_bytes(U256::from(<E::T as Config>::ChainId::get())),
+			&U256::from(<E::T as Config>::ChainId::get()).to_little_endian(),
 			false,
 			|_| Some(RuntimeCosts::CopyToContract(32)),
 		)?)
@@ -1593,7 +1588,7 @@ pub mod env {
 		Ok(self.write_fixed_sandbox_output(
 			memory,
 			out_ptr,
-			&as_bytes(self.ext.value_transferred()),
+			&self.ext.value_transferred().to_little_endian(),
 			false,
 			already_charged,
 		)?)
@@ -1607,7 +1602,7 @@ pub mod env {
 		Ok(self.write_fixed_sandbox_output(
 			memory,
 			out_ptr,
-			&as_bytes(self.ext.now()),
+			&self.ext.now().to_little_endian(),
 			false,
 			already_charged,
 		)?)
@@ -1621,7 +1616,7 @@ pub mod env {
 		Ok(self.write_fixed_sandbox_output(
 			memory,
 			out_ptr,
-			&as_bytes(self.ext.minimum_balance()),
+			&self.ext.minimum_balance().to_little_endian(),
 			false,
 			already_charged,
 		)?)
@@ -1675,7 +1670,7 @@ pub mod env {
 		Ok(self.write_fixed_sandbox_output(
 			memory,
 			out_ptr,
-			&as_bytes(self.ext.block_number()),
+			&self.ext.block_number().to_little_endian(),
 			false,
 			already_charged,
 		)?)
@@ -2033,7 +2028,7 @@ pub mod env {
 		Ok(self.write_fixed_sandbox_output(
 			memory,
 			out_ptr,
-			&as_bytes(U256::from(self.ext.last_frame_output().data.len())),
+			&U256::from(self.ext.last_frame_output().data.len()).to_little_endian(),
 			false,
 			|len| Some(RuntimeCosts::CopyToContract(len)),
 		)?)
diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml
index 8705781db00..9eaa1b68ca8 100644
--- a/substrate/frame/revive/uapi/Cargo.toml
+++ b/substrate/frame/revive/uapi/Cargo.toml
@@ -21,7 +21,7 @@ codec = { features = [
 ], optional = true, workspace = true }
 
 [target.'cfg(target_arch = "riscv32")'.dependencies]
-polkavm-derive = { version = "0.12.0" }
+polkavm-derive = { version = "0.13.0" }
 
 [package.metadata.docs.rs]
 default-target = ["wasm32-unknown-unknown"]
diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml
index 7147a11fb9c..370673622b9 100644
--- a/umbrella/Cargo.toml
+++ b/umbrella/Cargo.toml
@@ -442,6 +442,7 @@ try-runtime = [
 	"pallet-recovery?/try-runtime",
 	"pallet-referenda?/try-runtime",
 	"pallet-remark?/try-runtime",
+	"pallet-revive-mock-network?/try-runtime",
 	"pallet-revive?/try-runtime",
 	"pallet-root-offences?/try-runtime",
 	"pallet-root-testing?/try-runtime",
@@ -497,7 +498,6 @@ serde = [
 	"pallet-parameters?/serde",
 	"pallet-referenda?/serde",
 	"pallet-remark?/serde",
-	"pallet-revive?/serde",
 	"pallet-state-trie-migration?/serde",
 	"pallet-tips?/serde",
 	"pallet-transaction-payment?/serde",
-- 
GitLab