diff --git a/Cargo.lock b/Cargo.lock
index a945d148e051d0411e2527352af446e81d2eb33b..bc2ebb2a057de6fd4b86b4cf470a21ad4f208489 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -14633,6 +14633,7 @@ dependencies = [
  "assert_matches",
  "bitflags 1.3.2",
  "derive_more 0.99.17",
+ "env_logger 0.11.3",
  "environmental",
  "ethereum-types 0.15.1",
  "frame-benchmarking 28.0.0",
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
index f20b6b1fece081e86335dd3334a9436714d6bca7..98d647d868db043221625b51957aa8982cebc761 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
@@ -124,7 +124,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
 	spec_name: alloc::borrow::Cow::Borrowed("westmint"),
 	impl_name: alloc::borrow::Cow::Borrowed("westmint"),
 	authoring_version: 1,
-	spec_version: 1_016_006,
+	spec_version: 1_016_008,
 	impl_version: 0,
 	apis: RUNTIME_API_VERSIONS,
 	transaction_version: 16,
@@ -2081,18 +2081,10 @@ impl_runtime_apis! {
 			let account = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&address);
 			System::account_nonce(account)
 		}
-		fn eth_transact(
-			from: H160,
-			dest: Option<H160>,
-			value: U256,
-			input: Vec<u8>,
-			gas_limit: Option<Weight>,
-			storage_deposit_limit: Option<Balance>,
-		) -> pallet_revive::EthContractResult<Balance>
+
+		fn eth_transact(tx: pallet_revive::evm::GenericTransaction) -> Result<pallet_revive::EthTransactInfo<Balance>, pallet_revive::EthTransactError>
 		{
-			use pallet_revive::AddressMapper;
-			let blockweights = <Runtime as frame_system::Config>::BlockWeights::get();
-			let origin = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&from);
+			let blockweights: BlockWeights = <Runtime as frame_system::Config>::BlockWeights::get();
 
 			let encoded_size = |pallet_call| {
 				let call = RuntimeCall::Revive(pallet_call);
@@ -2101,15 +2093,9 @@ impl_runtime_apis! {
 			};
 
 			Revive::bare_eth_transact(
-				origin,
-				dest,
-				value,
-				input,
-				gas_limit.unwrap_or(blockweights.max_block),
-				storage_deposit_limit.unwrap_or(u128::MAX),
+				tx,
+				blockweights.max_block,
 				encoded_size,
-				pallet_revive::DebugInfo::UnsafeDebug,
-				pallet_revive::CollectEvents::UnsafeCollect,
 			)
 		}
 
@@ -2127,7 +2113,7 @@ impl_runtime_apis! {
 				dest,
 				value,
 				gas_limit.unwrap_or(blockweights.max_block),
-				storage_deposit_limit.unwrap_or(u128::MAX),
+				pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)),
 				input_data,
 				pallet_revive::DebugInfo::UnsafeDebug,
 				pallet_revive::CollectEvents::UnsafeCollect,
@@ -2149,7 +2135,7 @@ impl_runtime_apis! {
 				RuntimeOrigin::signed(origin),
 				value,
 				gas_limit.unwrap_or(blockweights.max_block),
-				storage_deposit_limit.unwrap_or(u128::MAX),
+				pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)),
 				code,
 				data,
 				salt,
diff --git a/prdoc/pr_6608.prdoc b/prdoc/pr_6608.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..b9cd7008de47bcf2294c446183e91514d45ef805
--- /dev/null
+++ b/prdoc/pr_6608.prdoc
@@ -0,0 +1,14 @@
+title: '[pallet-revive] eth-prc fix geth diff'
+doc:
+- audience: Runtime Dev
+  description: |-
+    * Add a bunch of differential tests to ensure that responses from eth-rpc matches the one from `geth`
+    * EVM RPC server will not fail gas_estimation if no gas is specified, I updated pallet-revive to add an extra `skip_transfer` boolean check to replicate this behavior in our pallet
+    * `eth_transact` and `bare_eth_transact` api have been updated to use `GenericTransaction` directly as this is what is used by `eth_estimateGas` and `eth_call`
+crates:
+- name: pallet-revive-eth-rpc
+  bump: minor
+- name: pallet-revive
+  bump: minor
+- name: asset-hub-westend-runtime
+  bump: minor
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index bff263548087359d3edf326b2968fee99606d43f..faffcd23fbcf9682ce7e663805ce404568b3469f 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -3218,18 +3218,9 @@ impl_runtime_apis! {
 			System::account_nonce(account)
 		}
 
-		fn eth_transact(
-			from: H160,
-			dest: Option<H160>,
-			value: U256,
-			input: Vec<u8>,
-			gas_limit: Option<Weight>,
-			storage_deposit_limit: Option<Balance>,
-		) -> pallet_revive::EthContractResult<Balance>
+		fn eth_transact(tx: pallet_revive::evm::GenericTransaction) -> Result<pallet_revive::EthTransactInfo<Balance>, pallet_revive::EthTransactError>
 		{
-			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);
@@ -3238,15 +3229,9 @@ impl_runtime_apis! {
 			};
 
 			Revive::bare_eth_transact(
-				origin,
-				dest,
-				value,
-				input,
-				gas_limit.unwrap_or(blockweights.max_block),
-				storage_deposit_limit.unwrap_or(u128::MAX),
+				tx,
+				blockweights.max_block,
 				encoded_size,
-				pallet_revive::DebugInfo::UnsafeDebug,
-				pallet_revive::CollectEvents::UnsafeCollect,
 			)
 		}
 
@@ -3263,7 +3248,7 @@ impl_runtime_apis! {
 				dest,
 				value,
 				gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block),
-				storage_deposit_limit.unwrap_or(u128::MAX),
+				pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)),
 				input_data,
 				pallet_revive::DebugInfo::UnsafeDebug,
 				pallet_revive::CollectEvents::UnsafeCollect,
@@ -3284,7 +3269,7 @@ impl_runtime_apis! {
 				RuntimeOrigin::signed(origin),
 				value,
 				gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block),
-				storage_deposit_limit.unwrap_or(u128::MAX),
+				pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)),
 				code,
 				data,
 				salt,
diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml
index 677ef0e1367f839e49cf218b4c7e8660853d928e..098a66df8dee6811f2026f112aa58e538667f73d 100644
--- a/substrate/frame/revive/Cargo.toml
+++ b/substrate/frame/revive/Cargo.toml
@@ -65,6 +65,7 @@ pallet-revive-fixtures = { workspace = true, default-features = true }
 secp256k1 = { workspace = true, features = ["recovery"] }
 serde_json = { workspace = true }
 hex-literal = { workspace = true }
+env_logger = { workspace = true }
 
 # Polkadot SDK Dependencies
 pallet-balances = { workspace = true, default-features = true }
diff --git a/substrate/frame/revive/mock-network/src/tests.rs b/substrate/frame/revive/mock-network/src/tests.rs
index bd05726a1a45f505e5105c673a7d0944f0cb26cb..34f797c2b530f44aa67286fcdccf6b2a7d6b1cd7 100644
--- a/substrate/frame/revive/mock-network/src/tests.rs
+++ b/substrate/frame/revive/mock-network/src/tests.rs
@@ -24,7 +24,7 @@ use frame_support::traits::{fungibles::Mutate, Currency};
 use frame_system::RawOrigin;
 use pallet_revive::{
 	test_utils::{self, builder::*},
-	Code,
+	Code, DepositLimit,
 };
 use pallet_revive_fixtures::compile_module;
 use pallet_revive_uapi::ReturnErrorCode;
@@ -52,7 +52,7 @@ fn instantiate_test_contract(name: &str) -> Contract<parachain::Runtime> {
 			RawOrigin::Signed(ALICE).into(),
 			Code::Upload(wasm),
 		)
-		.storage_deposit_limit(1_000_000_000_000)
+		.storage_deposit_limit(DepositLimit::Balance(1_000_000_000_000))
 		.build_and_unwrap_contract()
 	});
 
diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml
index 9f89b74c668f8ac2122c221cce0e54e7cab293d2..fe9cc82dd4d996db7caf9b0d146f761c84820922 100644
--- a/substrate/frame/revive/rpc/Cargo.toml
+++ b/substrate/frame/revive/rpc/Cargo.toml
@@ -67,13 +67,13 @@ hex = { workspace = true }
 hex-literal = { workspace = true, optional = true }
 scale-info = { workspace = true }
 secp256k1 = { workspace = true, optional = true, features = ["recovery"] }
-env_logger = { workspace = true }
 ethabi = { version = "18.0.0" }
 
 [features]
 example = ["hex-literal", "rlp", "secp256k1", "subxt-signer"]
 
 [dev-dependencies]
+env_logger = { workspace = true }
 static_init = { workspace = true }
 hex-literal = { workspace = true }
 pallet-revive-fixtures = { workspace = true }
diff --git a/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts
new file mode 100644
index 0000000000000000000000000000000000000000..93daf34e02b67462aaaa08976d0456a569c7ebae
--- /dev/null
+++ b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts
@@ -0,0 +1,106 @@
+export const abi = [
+	{
+		inputs: [
+			{
+				internalType: 'string',
+				name: 'message',
+				type: 'string',
+			},
+		],
+		name: 'CustomError',
+		type: 'error',
+	},
+	{
+		inputs: [
+			{
+				internalType: 'bool',
+				name: 'newState',
+				type: 'bool',
+			},
+		],
+		name: 'setState',
+		outputs: [],
+		stateMutability: 'nonpayable',
+		type: 'function',
+	},
+	{
+		inputs: [],
+		name: 'state',
+		outputs: [
+			{
+				internalType: 'bool',
+				name: '',
+				type: 'bool',
+			},
+		],
+		stateMutability: 'view',
+		type: 'function',
+	},
+	{
+		inputs: [],
+		name: 'triggerAssertError',
+		outputs: [],
+		stateMutability: 'pure',
+		type: 'function',
+	},
+	{
+		inputs: [],
+		name: 'triggerCustomError',
+		outputs: [],
+		stateMutability: 'pure',
+		type: 'function',
+	},
+	{
+		inputs: [],
+		name: 'triggerDivisionByZero',
+		outputs: [
+			{
+				internalType: 'uint256',
+				name: '',
+				type: 'uint256',
+			},
+		],
+		stateMutability: 'pure',
+		type: 'function',
+	},
+	{
+		inputs: [],
+		name: 'triggerOutOfBoundsError',
+		outputs: [
+			{
+				internalType: 'uint256',
+				name: '',
+				type: 'uint256',
+			},
+		],
+		stateMutability: 'pure',
+		type: 'function',
+	},
+	{
+		inputs: [],
+		name: 'triggerRequireError',
+		outputs: [],
+		stateMutability: 'pure',
+		type: 'function',
+	},
+	{
+		inputs: [],
+		name: 'triggerRevertError',
+		outputs: [],
+		stateMutability: 'pure',
+		type: 'function',
+	},
+	{
+		inputs: [
+			{
+				internalType: 'uint256',
+				name: 'value',
+				type: 'uint256',
+			},
+		],
+		name: 'valueMatch',
+		outputs: [],
+		stateMutability: 'payable',
+		type: 'function',
+	},
+] as const
diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.json b/substrate/frame/revive/rpc/examples/js/abi/event.json
deleted file mode 100644
index d36089fbc84ea25fc4cfcc757222526aaa14f1fb..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/abi/event.json
+++ /dev/null
@@ -1,34 +0,0 @@
-[
-	{
-		"anonymous": false,
-		"inputs": [
-			{
-				"indexed": true,
-				"internalType": "address",
-				"name": "sender",
-				"type": "address"
-			},
-			{
-				"indexed": false,
-				"internalType": "uint256",
-				"name": "value",
-				"type": "uint256"
-			},
-			{
-				"indexed": false,
-				"internalType": "string",
-				"name": "message",
-				"type": "string"
-			}
-		],
-		"name": "ExampleEvent",
-		"type": "event"
-	},
-	{
-		"inputs": [],
-		"name": "triggerEvent",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	}
-]
diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.ts b/substrate/frame/revive/rpc/examples/js/abi/event.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c389e2daf1daebbf50d9f9b0119b8e8281a94211
--- /dev/null
+++ b/substrate/frame/revive/rpc/examples/js/abi/event.ts
@@ -0,0 +1,34 @@
+export const abi = [
+	{
+		anonymous: false,
+		inputs: [
+			{
+				indexed: true,
+				internalType: 'address',
+				name: 'sender',
+				type: 'address',
+			},
+			{
+				indexed: false,
+				internalType: 'uint256',
+				name: 'value',
+				type: 'uint256',
+			},
+			{
+				indexed: false,
+				internalType: 'string',
+				name: 'message',
+				type: 'string',
+			},
+		],
+		name: 'ExampleEvent',
+		type: 'event',
+	},
+	{
+		inputs: [],
+		name: 'triggerEvent',
+		outputs: [],
+		stateMutability: 'nonpayable',
+		type: 'function',
+	},
+] as const
diff --git a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json
deleted file mode 100644
index 2c2cfd5f753377371f4fb6d111fda23aabf863e7..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json
+++ /dev/null
@@ -1,65 +0,0 @@
-[
-	{
-		"inputs": [],
-		"stateMutability": "nonpayable",
-		"type": "constructor"
-	},
-	{
-		"inputs": [],
-		"name": "deposit",
-		"outputs": [
-			{
-				"internalType": "uint256",
-				"name": "",
-				"type": "uint256"
-			}
-		],
-		"stateMutability": "payable",
-		"type": "function"
-	},
-	{
-		"inputs": [],
-		"name": "getDeposit",
-		"outputs": [
-			{
-				"internalType": "uint256",
-				"name": "",
-				"type": "uint256"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [],
-		"name": "owner",
-		"outputs": [
-			{
-				"internalType": "address",
-				"name": "",
-				"type": "address"
-			}
-		],
-		"stateMutability": "view",
-		"type": "function"
-	},
-	{
-		"inputs": [
-			{
-				"internalType": "uint256",
-				"name": "withdrawAmount",
-				"type": "uint256"
-			}
-		],
-		"name": "withdraw",
-		"outputs": [
-			{
-				"internalType": "uint256",
-				"name": "remainingBal",
-				"type": "uint256"
-			}
-		],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	}
-]
diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts
similarity index 62%
rename from substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts
rename to substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts
index 0efea80ed2dc88c637bacd0eb9dda91858426a23..3d44cd998ad16e1b54119477a889a76bab21de1c 100644
--- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts
+++ b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts
@@ -1,11 +1,4 @@
-/* Autogenerated file. Do not edit manually. */
-/* tslint:disable */
-/* eslint-disable */
-
-import { Contract, Interface, type ContractRunner } from 'ethers'
-import type { PiggyBank, PiggyBankInterface } from '../PiggyBank'
-
-const _abi = [
+export const abi = [
 	{
 		inputs: [],
 		stateMutability: 'nonpayable',
@@ -70,13 +63,3 @@ const _abi = [
 		type: 'function',
 	},
 ] as const
-
-export class PiggyBank__factory {
-	static readonly abi = _abi
-	static createInterface(): PiggyBankInterface {
-		return new Interface(_abi) as PiggyBankInterface
-	}
-	static connect(address: string, runner?: ContractRunner | null): PiggyBank {
-		return new Contract(address, _abi, runner) as unknown as PiggyBank
-	}
-}
diff --git a/substrate/frame/revive/rpc/examples/js/abi/revert.json b/substrate/frame/revive/rpc/examples/js/abi/revert.json
deleted file mode 100644
index be2945fcc0a59e28ea15ce24bb25badad71cd993..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/abi/revert.json
+++ /dev/null
@@ -1,14 +0,0 @@
-[
-	{
-		"inputs": [],
-		"stateMutability": "nonpayable",
-		"type": "constructor"
-	},
-	{
-		"inputs": [],
-		"name": "doRevert",
-		"outputs": [],
-		"stateMutability": "nonpayable",
-		"type": "function"
-	}
-]
diff --git a/substrate/frame/revive/rpc/examples/js/bun.lockb b/substrate/frame/revive/rpc/examples/js/bun.lockb
index 700dca51da2ad3f843e890258b59c16fd4df6457..0ff3d54157db21a636e30076634343a24c812dca 100755
Binary files a/substrate/frame/revive/rpc/examples/js/bun.lockb and b/substrate/frame/revive/rpc/examples/js/bun.lockb differ
diff --git a/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json b/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json
new file mode 100644
index 0000000000000000000000000000000000000000..ce2220e0b7560eedc6b32ab9f629dbf077c9944c
--- /dev/null
+++ b/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json
@@ -0,0 +1,3 @@
+{
+  "extends": "solhint:recommended"
+}
diff --git a/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol b/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol
new file mode 100644
index 0000000000000000000000000000000000000000..f1fdd219624ab9cd7d367922908e10f1a10cf213
--- /dev/null
+++ b/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+contract ErrorTester {
+	bool public state;
+
+	// Payable function that can be used to test insufficient funds errors
+    function valueMatch(uint256 value) public payable {
+		require(msg.value == value , "msg.value does not match value");
+    }
+
+    function setState(bool newState) public {
+        state = newState;
+    }
+
+    // Trigger a require statement failure with a custom error message
+    function triggerRequireError() public pure {
+        require(false, "This is a require error");
+    }
+
+    // Trigger an assert statement failure
+    function triggerAssertError() public pure {
+        assert(false);
+    }
+
+    // Trigger a revert statement with a custom error message
+    function triggerRevertError() public pure {
+        revert("This is a revert error");
+    }
+
+    // Trigger a division by zero error
+    function triggerDivisionByZero() public pure returns (uint256) {
+        uint256 a = 1;
+        uint256 b = 0;
+        return a / b;
+    }
+
+    // Trigger an out-of-bounds array access
+    function triggerOutOfBoundsError() public pure returns (uint256) {
+        uint256[] memory arr = new uint256[](1);
+        return arr[2];
+    }
+
+    // Trigger a custom error
+    error CustomError(string message);
+
+    function triggerCustomError() public pure {
+        revert CustomError("This is a custom error");
+    }
+}
+
diff --git a/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol b/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol
index 1906c46588895567b7edb8cd48a459b39cd2dd22..0c8a4d26f4dc224219ac3507ff7a541b16676241 100644
--- a/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol
+++ b/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol
@@ -3,7 +3,7 @@ pragma solidity ^0.8.0;
 
 contract PiggyBank {
 
-    uint private balance;
+    uint256 private balance;
     address public owner;
 
     constructor() {
@@ -11,16 +11,16 @@ contract PiggyBank {
         balance = 0;
     }
 
-    function deposit() public payable returns (uint) {
+    function deposit() public payable returns (uint256) {
         balance += msg.value;
         return balance;
     }
 
-    function getDeposit() public view returns (uint) {
+    function getDeposit() public view returns (uint256) {
         return balance;
     }
 
-    function withdraw(uint withdrawAmount) public returns (uint remainingBal) {
+    function withdraw(uint256 withdrawAmount) public returns (uint256 remainingBal) {
         require(msg.sender == owner);
         balance -= withdrawAmount;
         (bool success, ) = payable(msg.sender).call{value: withdrawAmount}("");
diff --git a/substrate/frame/revive/rpc/examples/js/package.json b/substrate/frame/revive/rpc/examples/js/package.json
index 3ae1f0fbd799a9e6be03292c0599e47b8e0f061a..6d8d00fd4214721e3f42418c78e440aaa3309f4e 100644
--- a/substrate/frame/revive/rpc/examples/js/package.json
+++ b/substrate/frame/revive/rpc/examples/js/package.json
@@ -1,22 +1,23 @@
 {
-  "name": "demo",
-  "private": true,
-  "version": "0.0.0",
-  "type": "module",
-  "scripts": {
-    "dev": "vite",
-    "build": "tsc && vite build",
-    "preview": "vite preview",
-	"generate-types": "typechain --target=ethers-v6 'abi/*.json'"
-  },
-  "dependencies": {
-    "@typechain/ethers-v6": "^0.5.1",
-    "ethers": "^6.13.4",
-    "solc": "^0.8.28",
-    "typechain": "^8.3.2"
-  },
-  "devDependencies": {
-    "typescript": "^5.5.3",
-    "vite": "^5.4.8"
-  }
+	"name": "demo",
+	"private": true,
+	"version": "0.0.0",
+	"type": "module",
+	"scripts": {
+		"dev": "vite",
+		"build": "tsc && vite build",
+		"preview": "vite preview"
+	},
+	"dependencies": {
+		"ethers": "^6.13.4",
+		"solc": "^0.8.28",
+		"viem": "^2.21.47",
+		"@parity/revive": "^0.0.5"
+	},
+	"devDependencies": {
+		"prettier": "^3.3.3",
+		"@types/bun": "^1.1.13",
+		"typescript": "^5.5.3",
+		"vite": "^5.4.8"
+	}
 }
diff --git a/substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm
new file mode 100644
index 0000000000000000000000000000000000000000..aebe24c4c0f597fb3d0171a9f527bc86d2d77b93
Binary files /dev/null and b/substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm differ
diff --git a/substrate/frame/revive/rpc/examples/js/src/balance.ts b/substrate/frame/revive/rpc/examples/js/src/balance.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1261dcab7812f9248c2cce86afb2874c15174507
--- /dev/null
+++ b/substrate/frame/revive/rpc/examples/js/src/balance.ts
@@ -0,0 +1,8 @@
+import { walletClient } from './lib.ts'
+
+const recipient = '0x8D97689C9818892B700e27F316cc3E41e17fBeb9'
+try {
+	console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`)
+} catch (err) {
+	console.error(err)
+}
diff --git a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts
index c6b7700d1ccf44a4e7f38e609abab71749559cbb..b25b5a7f2199e28838dd373cceac7032d6f95261 100644
--- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts
+++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts
@@ -1,11 +1,23 @@
 import { compile } from '@parity/revive'
+import { format } from 'prettier'
+import { parseArgs } from 'node:util'
 import solc from 'solc'
 import { readFileSync, writeFileSync } from 'fs'
 import { join } from 'path'
 
 type CompileInput = Parameters<typeof compile>[0]
-type CompileOutput = Awaited<ReturnType<typeof compile>>
-type Abi = CompileOutput['contracts'][string][string]['abi']
+
+const {
+	values: { filter },
+} = parseArgs({
+	args: process.argv.slice(2),
+	options: {
+		filter: {
+			type: 'string',
+			short: 'f',
+		},
+	},
+})
 
 function evmCompile(sources: CompileInput) {
 	const input = {
@@ -27,9 +39,9 @@ console.log('Compiling contracts...')
 
 const input = [
 	{ file: 'Event.sol', contract: 'EventExample', keypath: 'event' },
-	{ file: 'Revert.sol', contract: 'RevertExample', keypath: 'revert' },
 	{ file: 'PiggyBank.sol', contract: 'PiggyBank', keypath: 'piggyBank' },
-]
+	{ file: 'ErrorTester.sol', contract: 'ErrorTester', keypath: 'errorTester' },
+].filter(({ keypath }) => !filter || keypath.includes(filter))
 
 for (const { keypath, contract, file } of input) {
 	const input = {
@@ -41,7 +53,12 @@ for (const { keypath, contract, file } of input) {
 		const out = JSON.parse(evmCompile(input))
 		const entry = out.contracts[file][contract]
 		writeFileSync(join('evm', `${keypath}.bin`), Buffer.from(entry.evm.bytecode.object, 'hex'))
-		writeFileSync(join('abi', `${keypath}.json`), JSON.stringify(entry.abi, null, 2))
+		writeFileSync(
+			join('abi', `${keypath}.ts`),
+			await format(`export const abi = ${JSON.stringify(entry.abi, null, 2)} as const`, {
+				parser: 'typescript',
+			})
+		)
 	}
 
 	{
diff --git a/substrate/frame/revive/rpc/examples/js/src/event.ts b/substrate/frame/revive/rpc/examples/js/src/event.ts
index 94cc2560272e6813a17880d6fec2eee3027c9f25..2e672a9772ff412ae6f8e8c5bd36c41f5c9105a5 100644
--- a/substrate/frame/revive/rpc/examples/js/src/event.ts
+++ b/substrate/frame/revive/rpc/examples/js/src/event.ts
@@ -1,15 +1,29 @@
 //! Run with bun run script-event.ts
-import { call, getContract, deploy } from './lib.ts'
-
-try {
-	const { abi, bytecode } = getContract('event')
-	const contract = await deploy(bytecode, abi)
-	const receipt = await call('triggerEvent', await contract.getAddress(), abi)
-	if (receipt) {
-		for (const log of receipt.logs) {
-			console.log('Event log:', JSON.stringify(log, null, 2))
-		}
-	}
-} catch (err) {
-	console.error(err)
+
+import { abi } from '../abi/event.ts'
+import { assert, getByteCode, walletClient } from './lib.ts'
+
+const deployHash = await walletClient.deployContract({
+	abi,
+	bytecode: getByteCode('event'),
+})
+const deployReceipt = await walletClient.waitForTransactionReceipt({ hash: deployHash })
+const contractAddress = deployReceipt.contractAddress
+console.log('Contract deployed:', contractAddress)
+assert(contractAddress, 'Contract address should be set')
+
+const { request } = await walletClient.simulateContract({
+	account: walletClient.account,
+	address: contractAddress,
+	abi,
+	functionName: 'triggerEvent',
+})
+
+const hash = await walletClient.writeContract(request)
+const receipt = await walletClient.waitForTransactionReceipt({ hash })
+console.log(`Receipt: ${receipt.status}`)
+console.log(`Logs receipt: ${receipt.status}`)
+
+for (const log of receipt.logs) {
+	console.log('Event log:', log)
 }
diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..92b20473d165a2cfba8767e50f1671f372d7ce08
--- /dev/null
+++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts
@@ -0,0 +1,162 @@
+import { spawn, spawnSync, Subprocess } from 'bun'
+import { join, resolve } from 'path'
+import { readFileSync } from 'fs'
+import { createWalletClient, defineChain, Hex, http, publicActions } from 'viem'
+import { privateKeyToAccount } from 'viem/accounts'
+
+export function getByteCode(name: string, evm: boolean): Hex {
+	const bytecode = evm ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`)
+	return `0x${Buffer.from(bytecode).toString('hex')}`
+}
+
+export type JsonRpcError = {
+	code: number
+	message: string
+	data: Hex
+}
+
+export function killProcessOnPort(port: number) {
+	// Check which process is using the specified port
+	const result = spawnSync(['lsof', '-ti', `:${port}`])
+	const output = result.stdout.toString().trim()
+
+	if (output) {
+		console.log(`Port ${port} is in use. Killing process...`)
+		const pids = output.split('\n')
+
+		// Kill each process using the port
+		for (const pid of pids) {
+			spawnSync(['kill', '-9', pid])
+			console.log(`Killed process with PID: ${pid}`)
+		}
+	}
+}
+
+export let jsonRpcErrors: JsonRpcError[] = []
+export async function createEnv(name: 'geth' | 'kitchensink') {
+	const gethPort = process.env.GETH_PORT || '8546'
+	const kitchensinkPort = process.env.KITCHENSINK_PORT || '8545'
+	const url = `http://localhost:${name == 'geth' ? gethPort : kitchensinkPort}`
+	const chain = defineChain({
+		id: name == 'geth' ? 1337 : 420420420,
+		name,
+		nativeCurrency: {
+			name: 'Westie',
+			symbol: 'WST',
+			decimals: 18,
+		},
+		rpcUrls: {
+			default: {
+				http: [url],
+			},
+		},
+		testnet: true,
+	})
+
+	const transport = http(url, {
+		onFetchResponse: async (response) => {
+			const raw = await response.clone().json()
+			if (raw.error) {
+				jsonRpcErrors.push(raw.error as JsonRpcError)
+			}
+		},
+	})
+
+	const wallet = createWalletClient({
+		transport,
+		chain,
+	})
+
+	const [account] = await wallet.getAddresses()
+	const serverWallet = createWalletClient({
+		account,
+		transport,
+		chain,
+	}).extend(publicActions)
+
+	const accountWallet = createWalletClient({
+		account: privateKeyToAccount(
+			'0xa872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f'
+		),
+		transport,
+		chain,
+	}).extend(publicActions)
+
+	return { serverWallet, accountWallet, evm: name == 'geth' }
+}
+
+// wait for http request to return 200
+export function waitForHealth(url: string) {
+	return new Promise<void>((resolve, reject) => {
+		const start = Date.now()
+		const interval = setInterval(() => {
+			fetch(url)
+				.then((res) => {
+					if (res.status === 200) {
+						clearInterval(interval)
+						resolve()
+					}
+				})
+				.catch(() => {
+					const elapsed = Date.now() - start
+					if (elapsed > 30_000) {
+						clearInterval(interval)
+						reject(new Error('hit timeout'))
+					}
+				})
+		}, 1000)
+	})
+}
+
+export const procs: Subprocess[] = []
+const polkadotSdkPath = resolve(__dirname, '../../../../../../..')
+if (!process.env.USE_LIVE_SERVERS) {
+	procs.push(
+		// Run geth on port 8546
+		//
+		(() => {
+			killProcessOnPort(8546)
+			return spawn(
+				'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split(
+					' '
+				),
+				{ stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') }
+			)
+		})(),
+		//Run the substate node
+		(() => {
+			killProcessOnPort(9944)
+			return spawn(
+				[
+					'./target/debug/substrate-node',
+					'--dev',
+					'-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug',
+				],
+				{
+					stdout: Bun.file('/tmp/kitchensink.out.log'),
+					stderr: Bun.file('/tmp/kitchensink.err.log'),
+					cwd: polkadotSdkPath,
+				}
+			)
+		})(),
+		// Run eth-rpc on 8545
+		await (async () => {
+			killProcessOnPort(8545)
+			const proc = spawn(
+				[
+					'./target/debug/eth-rpc',
+					'--dev',
+					'--node-rpc-url=ws://localhost:9944',
+					'-l=rpc-metrics=debug,eth-rpc=debug',
+				],
+				{
+					stdout: Bun.file('/tmp/eth-rpc.out.log'),
+					stderr: Bun.file('/tmp/eth-rpc.err.log'),
+					cwd: polkadotSdkPath,
+				}
+			)
+			await waitForHealth('http://localhost:8545/health').catch()
+			return proc
+		})()
+	)
+}
diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..468e7860bb9aa1348b6d7d8c022b041ffdbe9ace
--- /dev/null
+++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts
@@ -0,0 +1,245 @@
+import { jsonRpcErrors, procs, createEnv, getByteCode } from './geth-diff-setup.ts'
+import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test'
+import { encodeFunctionData, Hex, parseEther } from 'viem'
+import { abi } from '../abi/errorTester'
+
+afterEach(() => {
+	jsonRpcErrors.length = 0
+})
+
+afterAll(async () => {
+	procs.forEach((proc) => proc.kill())
+})
+
+const envs = await Promise.all([createEnv('geth'), createEnv('kitchensink')])
+
+for (const env of envs) {
+	describe(env.serverWallet.chain.name, () => {
+		let errorTesterAddr: Hex = '0x'
+		beforeAll(async () => {
+			const hash = await env.serverWallet.deployContract({
+				abi,
+				bytecode: getByteCode('errorTester', env.evm),
+			})
+			const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash })
+			if (!deployReceipt.contractAddress) throw new Error('Contract address should be set')
+			errorTesterAddr = deployReceipt.contractAddress
+		})
+
+		test('triggerAssertError', async () => {
+			expect.assertions(3)
+			try {
+				await env.accountWallet.readContract({
+					address: errorTesterAddr,
+					abi,
+					functionName: 'triggerAssertError',
+				})
+			} catch (err) {
+				const lastJsonRpcError = jsonRpcErrors.pop()
+				expect(lastJsonRpcError?.code).toBe(3)
+				expect(lastJsonRpcError?.data).toBe(
+					'0x4e487b710000000000000000000000000000000000000000000000000000000000000001'
+				)
+				expect(lastJsonRpcError?.message).toBe('execution reverted: assert(false)')
+			}
+		})
+
+		test('triggerRevertError', async () => {
+			expect.assertions(3)
+			try {
+				await env.accountWallet.readContract({
+					address: errorTesterAddr,
+					abi,
+					functionName: 'triggerRevertError',
+				})
+			} catch (err) {
+				const lastJsonRpcError = jsonRpcErrors.pop()
+				expect(lastJsonRpcError?.code).toBe(3)
+				expect(lastJsonRpcError?.message).toBe('execution reverted: This is a revert error')
+				expect(lastJsonRpcError?.data).toBe(
+					'0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001654686973206973206120726576657274206572726f7200000000000000000000'
+				)
+			}
+		})
+
+		test('triggerDivisionByZero', async () => {
+			expect.assertions(3)
+			try {
+				await env.accountWallet.readContract({
+					address: errorTesterAddr,
+					abi,
+					functionName: 'triggerDivisionByZero',
+				})
+			} catch (err) {
+				const lastJsonRpcError = jsonRpcErrors.pop()
+				expect(lastJsonRpcError?.code).toBe(3)
+				expect(lastJsonRpcError?.data).toBe(
+					'0x4e487b710000000000000000000000000000000000000000000000000000000000000012'
+				)
+				expect(lastJsonRpcError?.message).toBe(
+					'execution reverted: division or modulo by zero'
+				)
+			}
+		})
+
+		test('triggerOutOfBoundsError', async () => {
+			expect.assertions(3)
+			try {
+				await env.accountWallet.readContract({
+					address: errorTesterAddr,
+					abi,
+					functionName: 'triggerOutOfBoundsError',
+				})
+			} catch (err) {
+				const lastJsonRpcError = jsonRpcErrors.pop()
+				expect(lastJsonRpcError?.code).toBe(3)
+				expect(lastJsonRpcError?.data).toBe(
+					'0x4e487b710000000000000000000000000000000000000000000000000000000000000032'
+				)
+				expect(lastJsonRpcError?.message).toBe(
+					'execution reverted: out-of-bounds access of an array or bytesN'
+				)
+			}
+		})
+
+		test('triggerCustomError', async () => {
+			expect.assertions(3)
+			try {
+				await env.accountWallet.readContract({
+					address: errorTesterAddr,
+					abi,
+					functionName: 'triggerCustomError',
+				})
+			} catch (err) {
+				const lastJsonRpcError = jsonRpcErrors.pop()
+				expect(lastJsonRpcError?.code).toBe(3)
+				expect(lastJsonRpcError?.data).toBe(
+					'0x8d6ea8be0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001654686973206973206120637573746f6d206572726f7200000000000000000000'
+				)
+				expect(lastJsonRpcError?.message).toBe('execution reverted')
+			}
+		})
+
+		test('eth_call (not enough funds)', async () => {
+			expect.assertions(3)
+			try {
+				await env.accountWallet.simulateContract({
+					address: errorTesterAddr,
+					abi,
+					functionName: 'valueMatch',
+					value: parseEther('10'),
+					args: [parseEther('10')],
+				})
+			} catch (err) {
+				const lastJsonRpcError = jsonRpcErrors.pop()
+				expect(lastJsonRpcError?.code).toBe(-32000)
+				expect(lastJsonRpcError?.message).toInclude('insufficient funds')
+				expect(lastJsonRpcError?.data).toBeUndefined()
+			}
+		})
+
+		test('eth_call transfer (not enough funds)', async () => {
+			expect.assertions(3)
+			try {
+				await env.accountWallet.sendTransaction({
+					to: '0x75E480dB528101a381Ce68544611C169Ad7EB342',
+					value: parseEther('10'),
+				})
+			} catch (err) {
+				const lastJsonRpcError = jsonRpcErrors.pop()
+				expect(lastJsonRpcError?.code).toBe(-32000)
+				expect(lastJsonRpcError?.message).toInclude('insufficient funds')
+				expect(lastJsonRpcError?.data).toBeUndefined()
+			}
+		})
+
+		test('eth_estimate (not enough funds)', async () => {
+			expect.assertions(3)
+			try {
+				await env.accountWallet.estimateContractGas({
+					address: errorTesterAddr,
+					abi,
+					functionName: 'valueMatch',
+					value: parseEther('10'),
+					args: [parseEther('10')],
+				})
+			} catch (err) {
+				const lastJsonRpcError = jsonRpcErrors.pop()
+				expect(lastJsonRpcError?.code).toBe(-32000)
+				expect(lastJsonRpcError?.message).toInclude('insufficient funds')
+				expect(lastJsonRpcError?.data).toBeUndefined()
+			}
+		})
+
+		test('eth_estimate (revert)', async () => {
+			expect.assertions(3)
+			try {
+				await env.serverWallet.estimateContractGas({
+					address: errorTesterAddr,
+					abi,
+					functionName: 'valueMatch',
+					value: parseEther('11'),
+					args: [parseEther('10')],
+				})
+			} catch (err) {
+				const lastJsonRpcError = jsonRpcErrors.pop()
+				expect(lastJsonRpcError?.code).toBe(3)
+				expect(lastJsonRpcError?.message).toBe(
+					'execution reverted: msg.value does not match value'
+				)
+				expect(lastJsonRpcError?.data).toBe(
+					'0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e6d73672e76616c756520646f6573206e6f74206d617463682076616c75650000'
+				)
+			}
+		})
+
+		test('eth_get_balance (no account)', async () => {
+			const balance = await env.serverWallet.getBalance({
+				address: '0x0000000000000000000000000000000000000123',
+			})
+			expect(balance).toBe(0n)
+		})
+
+		test('eth_estimate (not enough funds to cover gas specified)', async () => {
+			expect.assertions(4)
+			try {
+				let balance = await env.serverWallet.getBalance(env.accountWallet.account)
+				expect(balance).toBe(0n)
+
+				await env.accountWallet.estimateContractGas({
+					address: errorTesterAddr,
+					abi,
+					functionName: 'setState',
+					args: [true],
+				})
+			} catch (err) {
+				const lastJsonRpcError = jsonRpcErrors.pop()
+				expect(lastJsonRpcError?.code).toBe(-32000)
+				expect(lastJsonRpcError?.message).toInclude('insufficient funds')
+				expect(lastJsonRpcError?.data).toBeUndefined()
+			}
+		})
+
+		test('eth_estimate (no gas specified)', async () => {
+			let balance = await env.serverWallet.getBalance(env.accountWallet.account)
+			expect(balance).toBe(0n)
+
+			const data = encodeFunctionData({
+				abi,
+				functionName: 'setState',
+				args: [true],
+			})
+
+			await env.accountWallet.request({
+				method: 'eth_estimateGas',
+				params: [
+					{
+						data,
+						from: env.accountWallet.account.address,
+						to: errorTesterAddr,
+					},
+				],
+			})
+		})
+	})
+}
diff --git a/substrate/frame/revive/rpc/examples/js/src/lib.ts b/substrate/frame/revive/rpc/examples/js/src/lib.ts
index 975d8faf15b31327783fb4aecc63f57895dc92fb..e1f0e780d95b42c4fc8e2086ffda00dfbeff74ad 100644
--- a/substrate/frame/revive/rpc/examples/js/src/lib.ts
+++ b/substrate/frame/revive/rpc/examples/js/src/lib.ts
@@ -1,22 +1,11 @@
-import {
-	Contract,
-	ContractFactory,
-	JsonRpcProvider,
-	TransactionReceipt,
-	TransactionResponse,
-	Wallet,
-} from 'ethers'
 import { readFileSync } from 'node:fs'
-import type { compile } from '@parity/revive'
 import { spawn } from 'node:child_process'
 import { parseArgs } from 'node:util'
-import { BaseContract } from 'ethers'
-
-type CompileOutput = Awaited<ReturnType<typeof compile>>
-type Abi = CompileOutput['contracts'][string][string]['abi']
+import { createWalletClient, defineChain, Hex, http, parseEther, publicActions } from 'viem'
+import { privateKeyToAccount } from 'viem/accounts'
 
 const {
-	values: { geth, westend, ['private-key']: privateKey },
+	values: { geth, proxy, westend, endowment, ['private-key']: privateKey },
 } = parseArgs({
 	args: process.argv.slice(2),
 	options: {
@@ -24,6 +13,13 @@ const {
 			type: 'string',
 			short: 'k',
 		},
+		endowment: {
+			type: 'string',
+			short: 'e',
+		},
+		proxy: {
+			type: 'boolean',
+		},
 		geth: {
 			type: 'boolean',
 		},
@@ -42,7 +38,7 @@ if (geth) {
 			'--http.api',
 			'web3,eth,debug,personal,net',
 			'--http.port',
-			'8546',
+			process.env.GETH_PORT ?? '8546',
 			'--dev',
 			'--verbosity',
 			'0',
@@ -55,56 +51,78 @@ if (geth) {
 	await new Promise((resolve) => setTimeout(resolve, 500))
 }
 
-export const provider = new JsonRpcProvider(
-	westend
+const rpcUrl = proxy
+	? 'http://localhost:8080'
+	: westend
 		? 'https://westend-asset-hub-eth-rpc.polkadot.io'
 		: geth
 			? 'http://localhost:8546'
 			: 'http://localhost:8545'
-)
 
-export const signer = privateKey ? new Wallet(privateKey, provider) : await provider.getSigner()
-console.log(`Signer address: ${await signer.getAddress()}, Nonce: ${await signer.getNonce()}`)
+export const chain = defineChain({
+	id: geth ? 1337 : 420420420,
+	name: 'Asset Hub Westend',
+	network: 'asset-hub',
+	nativeCurrency: {
+		name: 'Westie',
+		symbol: 'WST',
+		decimals: 18,
+	},
+	rpcUrls: {
+		default: {
+			http: [rpcUrl],
+		},
+	},
+	testnet: true,
+})
+
+const wallet = createWalletClient({
+	transport: http(),
+	chain,
+})
+const [account] = await wallet.getAddresses()
+export const serverWalletClient = createWalletClient({
+	account,
+	transport: http(),
+	chain,
+})
+
+export const walletClient = await (async () => {
+	if (privateKey) {
+		const account = privateKeyToAccount(`0x${privateKey}`)
+		console.log(`Wallet address ${account.address}`)
+
+		const wallet = createWalletClient({
+			account,
+			transport: http(),
+			chain,
+		})
+
+		if (endowment) {
+			await serverWalletClient.sendTransaction({
+				to: account.address,
+				value: parseEther(endowment),
+			})
+			console.log(`Endowed address ${account.address} with: ${endowment}`)
+		}
+
+		return wallet.extend(publicActions)
+	} else {
+		return serverWalletClient.extend(publicActions)
+	}
+})()
 
 /**
  * Get one of the pre-built contracts
  * @param name - the contract name
  */
-export function getContract(name: string): { abi: Abi; bytecode: string } {
+export function getByteCode(name: string): Hex {
 	const bytecode = geth ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`)
-	const abi = JSON.parse(readFileSync(`abi/${name}.json`, 'utf8')) as Abi
-	return { abi, bytecode: Buffer.from(bytecode).toString('hex') }
+	return `0x${Buffer.from(bytecode).toString('hex')}`
 }
 
-/**
- * Deploy a contract
- * @returns the contract address
- **/
-export async function deploy(bytecode: string, abi: Abi, args: any[] = []): Promise<BaseContract> {
-	console.log('Deploying contract with', args)
-	const contractFactory = new ContractFactory(abi, bytecode, signer)
-
-	const contract = await contractFactory.deploy(args)
-	await contract.waitForDeployment()
-	const address = await contract.getAddress()
-	console.log(`Contract deployed: ${address}`)
-
-	return contract
-}
-
-/**
- * Call a contract
- **/
-export async function call(
-	method: string,
-	address: string,
-	abi: Abi,
-	args: any[] = [],
-	opts: { value?: bigint } = {}
-): Promise<null | TransactionReceipt> {
-	console.log(`Calling ${method} at ${address} with`, args, opts)
-	const contract = new Contract(address, abi, signer)
-	const tx = (await contract[method](...args, opts)) as TransactionResponse
-	console.log('Call transaction hash:', tx.hash)
-	return tx.wait()
+export function assert(condition: any, message: string): asserts condition {
+	if (!condition) {
+		throw new Error(message)
+	}
 }
diff --git a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts
index 7a8edbde36626f097b7e87ef0ec0e5d5812d20f8..0040b0c78dc47f66521aeef559d8d2797201ea70 100644
--- a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts
+++ b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts
@@ -1,24 +1,69 @@
-import { provider, call, getContract, deploy } from './lib.ts'
-import { parseEther } from 'ethers'
-import { PiggyBank } from '../types/ethers-contracts/PiggyBank'
+import { assert, getByteCode, walletClient } from './lib.ts'
+import { abi } from '../abi/piggyBank.ts'
+import { parseEther } from 'viem'
 
-try {
-	const { abi, bytecode } = getContract('piggyBank')
-	const contract = (await deploy(bytecode, abi)) as PiggyBank
-	const address = await contract.getAddress()
+const hash = await walletClient.deployContract({
+	abi,
+	bytecode: getByteCode('piggyBank'),
+})
+const deployReceipt = await walletClient.waitForTransactionReceipt({ hash })
+const contractAddress = deployReceipt.contractAddress
+console.log('Contract deployed:', contractAddress)
+assert(contractAddress, 'Contract address should be set')
 
-	let receipt = await call('deposit', address, abi, [], {
-		value: parseEther('10.0'),
+// Deposit 10 WST
+{
+	const result = await walletClient.estimateContractGas({
+		account: walletClient.account,
+		address: contractAddress,
+		abi,
+		functionName: 'deposit',
+		value: parseEther('10'),
 	})
-	console.log('Deposit receipt:', receipt?.status)
-	console.log(`Contract balance: ${await provider.getBalance(address)}`)
 
-	console.log('deposit: ', await contract.getDeposit())
+	console.log(`Gas estimate: ${result}`)
 
-	receipt = await call('withdraw', address, abi, [parseEther('5.0')])
-	console.log('Withdraw receipt:', receipt?.status)
-	console.log(`Contract balance: ${await provider.getBalance(address)}`)
-	console.log('deposit: ', await contract.getDeposit())
-} catch (err) {
-	console.error(err)
+	const { request } = await walletClient.simulateContract({
+		account: walletClient.account,
+		address: contractAddress,
+		abi,
+		functionName: 'deposit',
+		value: parseEther('10'),
+	})
+
+	request.nonce = 0
+	const hash = await walletClient.writeContract(request)
+
+	const receipt = await walletClient.waitForTransactionReceipt({ hash })
+	console.log(`Deposit receipt: ${receipt.status}`)
+	if (process.env.STOP) {
+		process.exit(0)
+	}
+}
+
+// Withdraw 5 WST
+{
+	const { request } = await walletClient.simulateContract({
+		account: walletClient.account,
+		address: contractAddress,
+		abi,
+		functionName: 'withdraw',
+		args: [parseEther('5')],
+	})
+
+	const hash = await walletClient.writeContract(request)
+	const receipt = await walletClient.waitForTransactionReceipt({ hash })
+	console.log(`Withdraw receipt: ${receipt.status}`)
+
+	// Check remaining balance
+	const balance = await walletClient.readContract({
+		address: contractAddress,
+		abi,
+		functionName: 'getDeposit',
+	})
+
+	console.log(`Get deposit: ${balance}`)
+	console.log(
+		`Get contract balance: ${await walletClient.getBalance({ address: contractAddress })}`
+	)
 }
diff --git a/substrate/frame/revive/rpc/examples/js/src/revert.ts b/substrate/frame/revive/rpc/examples/js/src/revert.ts
deleted file mode 100644
index ea1bf4eceeb9c3991412b0d9de9c9c0513b45815..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/src/revert.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-//! Run with bun run script-revert.ts
-import { call, getContract, deploy } from './lib.ts'
-
-try {
-	const { abi, bytecode } = getContract('revert')
-	const contract = await deploy(bytecode, abi)
-	await call('doRevert', await contract.getAddress(), abi)
-} catch (err) {
-	console.error(err)
-}
diff --git a/substrate/frame/revive/rpc/examples/js/src/transfer.ts b/substrate/frame/revive/rpc/examples/js/src/transfer.ts
index ae2dd50f2af868b84b5b29ad70b6da5df78ebb60..aef9a487b0c014c72b2d1b8f6c44796268ec1b00 100644
--- a/substrate/frame/revive/rpc/examples/js/src/transfer.ts
+++ b/substrate/frame/revive/rpc/examples/js/src/transfer.ts
@@ -1,17 +1,18 @@
-import { parseEther } from 'ethers'
-import { provider, signer } from './lib.ts'
+import { parseEther } from 'viem'
+import { walletClient } from './lib.ts'
 
 const recipient = '0x75E480dB528101a381Ce68544611C169Ad7EB342'
 try {
-	console.log(`Signer balance:    ${await provider.getBalance(signer.address)}`)
-	console.log(`Recipient balance: ${await provider.getBalance(recipient)}`)
-	await signer.sendTransaction({
+	console.log(`Signer balance:    ${await walletClient.getBalance(walletClient.account)}`)
+	console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`)
+
+	await walletClient.sendTransaction({
 		to: recipient,
 		value: parseEther('1.0'),
 	})
 	console.log(`Sent:              ${parseEther('1.0')}`)
-	console.log(`Signer balance:    ${await provider.getBalance(signer.address)}`)
-	console.log(`Recipient balance: ${await provider.getBalance(recipient)}`)
+	console.log(`Signer balance:    ${await walletClient.getBalance(walletClient.account)}`)
+	console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`)
 } catch (err) {
 	console.error(err)
 }
diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts
deleted file mode 100644
index d65f953969f0c3158fb527103021f59dd960598c..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-/* Autogenerated file. Do not edit manually. */
-/* tslint:disable */
-/* eslint-disable */
-import type {
-	BaseContract,
-	BigNumberish,
-	BytesLike,
-	FunctionFragment,
-	Result,
-	Interface,
-	EventFragment,
-	AddressLike,
-	ContractRunner,
-	ContractMethod,
-	Listener,
-} from 'ethers'
-import type {
-	TypedContractEvent,
-	TypedDeferredTopicFilter,
-	TypedEventLog,
-	TypedLogDescription,
-	TypedListener,
-	TypedContractMethod,
-} from './common'
-
-export interface EventInterface extends Interface {
-	getFunction(nameOrSignature: 'triggerEvent'): FunctionFragment
-
-	getEvent(nameOrSignatureOrTopic: 'ExampleEvent'): EventFragment
-
-	encodeFunctionData(functionFragment: 'triggerEvent', values?: undefined): string
-
-	decodeFunctionResult(functionFragment: 'triggerEvent', data: BytesLike): Result
-}
-
-export namespace ExampleEventEvent {
-	export type InputTuple = [sender: AddressLike, value: BigNumberish, message: string]
-	export type OutputTuple = [sender: string, value: bigint, message: string]
-	export interface OutputObject {
-		sender: string
-		value: bigint
-		message: string
-	}
-	export type Event = TypedContractEvent<InputTuple, OutputTuple, OutputObject>
-	export type Filter = TypedDeferredTopicFilter<Event>
-	export type Log = TypedEventLog<Event>
-	export type LogDescription = TypedLogDescription<Event>
-}
-
-export interface Event extends BaseContract {
-	connect(runner?: ContractRunner | null): Event
-	waitForDeployment(): Promise<this>
-
-	interface: EventInterface
-
-	queryFilter<TCEvent extends TypedContractEvent>(
-		event: TCEvent,
-		fromBlockOrBlockhash?: string | number | undefined,
-		toBlock?: string | number | undefined
-	): Promise<Array<TypedEventLog<TCEvent>>>
-	queryFilter<TCEvent extends TypedContractEvent>(
-		filter: TypedDeferredTopicFilter<TCEvent>,
-		fromBlockOrBlockhash?: string | number | undefined,
-		toBlock?: string | number | undefined
-	): Promise<Array<TypedEventLog<TCEvent>>>
-
-	on<TCEvent extends TypedContractEvent>(
-		event: TCEvent,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-	on<TCEvent extends TypedContractEvent>(
-		filter: TypedDeferredTopicFilter<TCEvent>,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-
-	once<TCEvent extends TypedContractEvent>(
-		event: TCEvent,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-	once<TCEvent extends TypedContractEvent>(
-		filter: TypedDeferredTopicFilter<TCEvent>,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-
-	listeners<TCEvent extends TypedContractEvent>(
-		event: TCEvent
-	): Promise<Array<TypedListener<TCEvent>>>
-	listeners(eventName?: string): Promise<Array<Listener>>
-	removeAllListeners<TCEvent extends TypedContractEvent>(event?: TCEvent): Promise<this>
-
-	triggerEvent: TypedContractMethod<[], [void], 'nonpayable'>
-
-	getFunction<T extends ContractMethod = ContractMethod>(key: string | FunctionFragment): T
-
-	getFunction(nameOrSignature: 'triggerEvent'): TypedContractMethod<[], [void], 'nonpayable'>
-
-	getEvent(
-		key: 'ExampleEvent'
-	): TypedContractEvent<
-		ExampleEventEvent.InputTuple,
-		ExampleEventEvent.OutputTuple,
-		ExampleEventEvent.OutputObject
-	>
-
-	filters: {
-		'ExampleEvent(address,uint256,string)': TypedContractEvent<
-			ExampleEventEvent.InputTuple,
-			ExampleEventEvent.OutputTuple,
-			ExampleEventEvent.OutputObject
-		>
-		ExampleEvent: TypedContractEvent<
-			ExampleEventEvent.InputTuple,
-			ExampleEventEvent.OutputTuple,
-			ExampleEventEvent.OutputObject
-		>
-	}
-}
diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts
deleted file mode 100644
index ca137fcc8b30aec6ebf9fda80248f890ed5668d7..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-/* Autogenerated file. Do not edit manually. */
-/* tslint:disable */
-/* eslint-disable */
-import type {
-	BaseContract,
-	BigNumberish,
-	BytesLike,
-	FunctionFragment,
-	Result,
-	Interface,
-	ContractRunner,
-	ContractMethod,
-	Listener,
-} from 'ethers'
-import type {
-	TypedContractEvent,
-	TypedDeferredTopicFilter,
-	TypedEventLog,
-	TypedListener,
-	TypedContractMethod,
-} from './common'
-
-export interface PiggyBankInterface extends Interface {
-	getFunction(nameOrSignature: 'deposit' | 'getDeposit' | 'owner' | 'withdraw'): FunctionFragment
-
-	encodeFunctionData(functionFragment: 'deposit', values?: undefined): string
-	encodeFunctionData(functionFragment: 'getDeposit', values?: undefined): string
-	encodeFunctionData(functionFragment: 'owner', values?: undefined): string
-	encodeFunctionData(functionFragment: 'withdraw', values: [BigNumberish]): string
-
-	decodeFunctionResult(functionFragment: 'deposit', data: BytesLike): Result
-	decodeFunctionResult(functionFragment: 'getDeposit', data: BytesLike): Result
-	decodeFunctionResult(functionFragment: 'owner', data: BytesLike): Result
-	decodeFunctionResult(functionFragment: 'withdraw', data: BytesLike): Result
-}
-
-export interface PiggyBank extends BaseContract {
-	connect(runner?: ContractRunner | null): PiggyBank
-	waitForDeployment(): Promise<this>
-
-	interface: PiggyBankInterface
-
-	queryFilter<TCEvent extends TypedContractEvent>(
-		event: TCEvent,
-		fromBlockOrBlockhash?: string | number | undefined,
-		toBlock?: string | number | undefined
-	): Promise<Array<TypedEventLog<TCEvent>>>
-	queryFilter<TCEvent extends TypedContractEvent>(
-		filter: TypedDeferredTopicFilter<TCEvent>,
-		fromBlockOrBlockhash?: string | number | undefined,
-		toBlock?: string | number | undefined
-	): Promise<Array<TypedEventLog<TCEvent>>>
-
-	on<TCEvent extends TypedContractEvent>(
-		event: TCEvent,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-	on<TCEvent extends TypedContractEvent>(
-		filter: TypedDeferredTopicFilter<TCEvent>,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-
-	once<TCEvent extends TypedContractEvent>(
-		event: TCEvent,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-	once<TCEvent extends TypedContractEvent>(
-		filter: TypedDeferredTopicFilter<TCEvent>,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-
-	listeners<TCEvent extends TypedContractEvent>(
-		event: TCEvent
-	): Promise<Array<TypedListener<TCEvent>>>
-	listeners(eventName?: string): Promise<Array<Listener>>
-	removeAllListeners<TCEvent extends TypedContractEvent>(event?: TCEvent): Promise<this>
-
-	deposit: TypedContractMethod<[], [bigint], 'payable'>
-
-	getDeposit: TypedContractMethod<[], [bigint], 'view'>
-
-	owner: TypedContractMethod<[], [string], 'view'>
-
-	withdraw: TypedContractMethod<[withdrawAmount: BigNumberish], [bigint], 'nonpayable'>
-
-	getFunction<T extends ContractMethod = ContractMethod>(key: string | FunctionFragment): T
-
-	getFunction(nameOrSignature: 'deposit'): TypedContractMethod<[], [bigint], 'payable'>
-	getFunction(nameOrSignature: 'getDeposit'): TypedContractMethod<[], [bigint], 'view'>
-	getFunction(nameOrSignature: 'owner'): TypedContractMethod<[], [string], 'view'>
-	getFunction(
-		nameOrSignature: 'withdraw'
-	): TypedContractMethod<[withdrawAmount: BigNumberish], [bigint], 'nonpayable'>
-
-	filters: {}
-}
diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts
deleted file mode 100644
index ad6e23b38a65dbd4859d821c00e90f8e08efe1e0..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/* Autogenerated file. Do not edit manually. */
-/* tslint:disable */
-/* eslint-disable */
-import type {
-	BaseContract,
-	BytesLike,
-	FunctionFragment,
-	Result,
-	Interface,
-	ContractRunner,
-	ContractMethod,
-	Listener,
-} from 'ethers'
-import type {
-	TypedContractEvent,
-	TypedDeferredTopicFilter,
-	TypedEventLog,
-	TypedListener,
-	TypedContractMethod,
-} from './common'
-
-export interface RevertInterface extends Interface {
-	getFunction(nameOrSignature: 'doRevert'): FunctionFragment
-
-	encodeFunctionData(functionFragment: 'doRevert', values?: undefined): string
-
-	decodeFunctionResult(functionFragment: 'doRevert', data: BytesLike): Result
-}
-
-export interface Revert extends BaseContract {
-	connect(runner?: ContractRunner | null): Revert
-	waitForDeployment(): Promise<this>
-
-	interface: RevertInterface
-
-	queryFilter<TCEvent extends TypedContractEvent>(
-		event: TCEvent,
-		fromBlockOrBlockhash?: string | number | undefined,
-		toBlock?: string | number | undefined
-	): Promise<Array<TypedEventLog<TCEvent>>>
-	queryFilter<TCEvent extends TypedContractEvent>(
-		filter: TypedDeferredTopicFilter<TCEvent>,
-		fromBlockOrBlockhash?: string | number | undefined,
-		toBlock?: string | number | undefined
-	): Promise<Array<TypedEventLog<TCEvent>>>
-
-	on<TCEvent extends TypedContractEvent>(
-		event: TCEvent,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-	on<TCEvent extends TypedContractEvent>(
-		filter: TypedDeferredTopicFilter<TCEvent>,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-
-	once<TCEvent extends TypedContractEvent>(
-		event: TCEvent,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-	once<TCEvent extends TypedContractEvent>(
-		filter: TypedDeferredTopicFilter<TCEvent>,
-		listener: TypedListener<TCEvent>
-	): Promise<this>
-
-	listeners<TCEvent extends TypedContractEvent>(
-		event: TCEvent
-	): Promise<Array<TypedListener<TCEvent>>>
-	listeners(eventName?: string): Promise<Array<Listener>>
-	removeAllListeners<TCEvent extends TypedContractEvent>(event?: TCEvent): Promise<this>
-
-	doRevert: TypedContractMethod<[], [void], 'nonpayable'>
-
-	getFunction<T extends ContractMethod = ContractMethod>(key: string | FunctionFragment): T
-
-	getFunction(nameOrSignature: 'doRevert'): TypedContractMethod<[], [void], 'nonpayable'>
-
-	filters: {}
-}
diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts
deleted file mode 100644
index 247b9468ece25709be8181295f8a322d7e754fa4..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/* Autogenerated file. Do not edit manually. */
-/* tslint:disable */
-/* eslint-disable */
-import type {
-	FunctionFragment,
-	Typed,
-	EventFragment,
-	ContractTransaction,
-	ContractTransactionResponse,
-	DeferredTopicFilter,
-	EventLog,
-	TransactionRequest,
-	LogDescription,
-} from 'ethers'
-
-export interface TypedDeferredTopicFilter<_TCEvent extends TypedContractEvent>
-	extends DeferredTopicFilter {}
-
-export interface TypedContractEvent<
-	InputTuple extends Array<any> = any,
-	OutputTuple extends Array<any> = any,
-	OutputObject = any,
-> {
-	(
-		...args: Partial<InputTuple>
-	): TypedDeferredTopicFilter<TypedContractEvent<InputTuple, OutputTuple, OutputObject>>
-	name: string
-	fragment: EventFragment
-	getFragment(...args: Partial<InputTuple>): EventFragment
-}
-
-type __TypechainAOutputTuple<T> = T extends TypedContractEvent<infer _U, infer W> ? W : never
-type __TypechainOutputObject<T> =
-	T extends TypedContractEvent<infer _U, infer _W, infer V> ? V : never
-
-export interface TypedEventLog<TCEvent extends TypedContractEvent> extends Omit<EventLog, 'args'> {
-	args: __TypechainAOutputTuple<TCEvent> & __TypechainOutputObject<TCEvent>
-}
-
-export interface TypedLogDescription<TCEvent extends TypedContractEvent>
-	extends Omit<LogDescription, 'args'> {
-	args: __TypechainAOutputTuple<TCEvent> & __TypechainOutputObject<TCEvent>
-}
-
-export type TypedListener<TCEvent extends TypedContractEvent> = (
-	...listenerArg: [...__TypechainAOutputTuple<TCEvent>, TypedEventLog<TCEvent>, ...undefined[]]
-) => void
-
-export type MinEthersFactory<C, ARGS> = {
-	deploy(...a: ARGS[]): Promise<C>
-}
-
-export type GetContractTypeFromFactory<F> = F extends MinEthersFactory<infer C, any> ? C : never
-export type GetARGsTypeFromFactory<F> =
-	F extends MinEthersFactory<any, any> ? Parameters<F['deploy']> : never
-
-export type StateMutability = 'nonpayable' | 'payable' | 'view'
-
-export type BaseOverrides = Omit<TransactionRequest, 'to' | 'data'>
-export type NonPayableOverrides = Omit<BaseOverrides, 'value' | 'blockTag' | 'enableCcipRead'>
-export type PayableOverrides = Omit<BaseOverrides, 'blockTag' | 'enableCcipRead'>
-export type ViewOverrides = Omit<TransactionRequest, 'to' | 'data'>
-export type Overrides<S extends StateMutability> = S extends 'nonpayable'
-	? NonPayableOverrides
-	: S extends 'payable'
-		? PayableOverrides
-		: ViewOverrides
-
-export type PostfixOverrides<A extends Array<any>, S extends StateMutability> =
-	| A
-	| [...A, Overrides<S>]
-export type ContractMethodArgs<A extends Array<any>, S extends StateMutability> = PostfixOverrides<
-	{ [I in keyof A]-?: A[I] | Typed },
-	S
->
-
-export type DefaultReturnType<R> = R extends Array<any> ? R[0] : R
-
-// export interface ContractMethod<A extends Array<any> = Array<any>, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse> {
-export interface TypedContractMethod<
-	A extends Array<any> = Array<any>,
-	R = any,
-	S extends StateMutability = 'payable',
-> {
-	(
-		...args: ContractMethodArgs<A, S>
-	): S extends 'view' ? Promise<DefaultReturnType<R>> : Promise<ContractTransactionResponse>
-
-	name: string
-
-	fragment: FunctionFragment
-
-	getFragment(...args: ContractMethodArgs<A, S>): FunctionFragment
-
-	populateTransaction(...args: ContractMethodArgs<A, S>): Promise<ContractTransaction>
-	staticCall(...args: ContractMethodArgs<A, 'view'>): Promise<DefaultReturnType<R>>
-	send(...args: ContractMethodArgs<A, S>): Promise<ContractTransactionResponse>
-	estimateGas(...args: ContractMethodArgs<A, S>): Promise<bigint>
-	staticCallResult(...args: ContractMethodArgs<A, 'view'>): Promise<R>
-}
diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts
deleted file mode 100644
index 2e16b18a7ed8c5e4d37abe614ed118388ffb1be3..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/* Autogenerated file. Do not edit manually. */
-/* tslint:disable */
-/* eslint-disable */
-
-import { Contract, Interface, type ContractRunner } from 'ethers'
-import type { Event, EventInterface } from '../Event'
-
-const _abi = [
-	{
-		anonymous: false,
-		inputs: [
-			{
-				indexed: true,
-				internalType: 'address',
-				name: 'sender',
-				type: 'address',
-			},
-			{
-				indexed: false,
-				internalType: 'uint256',
-				name: 'value',
-				type: 'uint256',
-			},
-			{
-				indexed: false,
-				internalType: 'string',
-				name: 'message',
-				type: 'string',
-			},
-		],
-		name: 'ExampleEvent',
-		type: 'event',
-	},
-	{
-		inputs: [],
-		name: 'triggerEvent',
-		outputs: [],
-		stateMutability: 'nonpayable',
-		type: 'function',
-	},
-] as const
-
-export class Event__factory {
-	static readonly abi = _abi
-	static createInterface(): EventInterface {
-		return new Interface(_abi) as EventInterface
-	}
-	static connect(address: string, runner?: ContractRunner | null): Event {
-		return new Contract(address, _abi, runner) as unknown as Event
-	}
-}
diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts
deleted file mode 100644
index ece1c6b5426ef7e31547d3cd736b06248d603746..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Autogenerated file. Do not edit manually. */
-/* tslint:disable */
-/* eslint-disable */
-
-import { Contract, Interface, type ContractRunner } from 'ethers'
-import type { Revert, RevertInterface } from '../Revert'
-
-const _abi = [
-	{
-		inputs: [],
-		stateMutability: 'nonpayable',
-		type: 'constructor',
-	},
-	{
-		inputs: [],
-		name: 'doRevert',
-		outputs: [],
-		stateMutability: 'nonpayable',
-		type: 'function',
-	},
-] as const
-
-export class Revert__factory {
-	static readonly abi = _abi
-	static createInterface(): RevertInterface {
-		return new Interface(_abi) as RevertInterface
-	}
-	static connect(address: string, runner?: ContractRunner | null): Revert {
-		return new Contract(address, _abi, runner) as unknown as Revert
-	}
-}
diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts
deleted file mode 100644
index 67370dba411c3d9e5908cccdbee1a858944258f5..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-/* Autogenerated file. Do not edit manually. */
-/* tslint:disable */
-/* eslint-disable */
-export { Event__factory } from './Event__factory'
-export { PiggyBank__factory } from './PiggyBank__factory'
-export { Revert__factory } from './Revert__factory'
diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts
deleted file mode 100644
index 3e324e80dcb1c04a0a34c2abaaad2afbec620278..0000000000000000000000000000000000000000
--- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/* Autogenerated file. Do not edit manually. */
-/* tslint:disable */
-/* eslint-disable */
-export type { Event } from './Event'
-export type { PiggyBank } from './PiggyBank'
-export type { Revert } from './Revert'
-export * as factories from './factories'
-export { Event__factory } from './factories/Event__factory'
-export { PiggyBank__factory } from './factories/PiggyBank__factory'
-export { Revert__factory } from './factories/Revert__factory'
diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata
index 3560b3b90407acce7f602ce91ac089843be8dea8..64b1f2014dd06815fcea6a87bc96306eb00eda8b 100644
Binary files a/substrate/frame/revive/rpc/revive_chain.metadata and b/substrate/frame/revive/rpc/revive_chain.metadata differ
diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs
index d37f1d760065314afe2a96ffe166109b77d572a7..901c15e9756b65b6059602c59b2bc190d927d7b0 100644
--- a/substrate/frame/revive/rpc/src/client.rs
+++ b/substrate/frame/revive/rpc/src/client.rs
@@ -32,7 +32,7 @@ use pallet_revive::{
 		Block, BlockNumberOrTag, BlockNumberOrTagOrHash, Bytes256, GenericTransaction, Log,
 		ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256,
 	},
-	EthContractResult,
+	EthTransactError, EthTransactInfo,
 };
 use sp_core::keccak_256;
 use sp_weights::Weight;
@@ -116,18 +116,42 @@ fn unwrap_call_err(err: &subxt::error::RpcError) -> Option<ErrorObjectOwned> {
 
 /// Extract the revert message from a revert("msg") solidity statement.
 fn extract_revert_message(exec_data: &[u8]) -> Option<String> {
-	let function_selector = exec_data.get(0..4)?;
-
-	// keccak256("Error(string)")
-	let expected_selector = [0x08, 0xC3, 0x79, 0xA0];
-	if function_selector != expected_selector {
-		return None;
-	}
+	let error_selector = exec_data.get(0..4)?;
+
+	match error_selector {
+		// assert(false)
+		[0x4E, 0x48, 0x7B, 0x71] => {
+			let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?;
+
+			// See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
+			let msg = match panic_code {
+				0x00 => "generic panic",
+				0x01 => "assert(false)",
+				0x11 => "arithmetic underflow or overflow",
+				0x12 => "division or modulo by zero",
+				0x21 => "enum overflow",
+				0x22 => "invalid encoded storage byte array accessed",
+				0x31 => "out-of-bounds array access; popping on an empty array",
+				0x32 => "out-of-bounds access of an array or bytesN",
+				0x41 => "out of memory",
+				0x51 => "uninitialized function",
+				code => return Some(format!("execution reverted: unknown panic code: {code:#x}")),
+			};
 
-	let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?;
-	match decoded.first()? {
-		ethabi::Token::String(msg) => Some(msg.to_string()),
-		_ => None,
+			Some(format!("execution reverted: {msg}"))
+		},
+		// revert(string)
+		[0x08, 0xC3, 0x79, 0xA0] => {
+			let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?;
+			if let Some(ethabi::Token::String(msg)) = decoded.first() {
+				return Some(format!("execution reverted: {msg}"))
+			}
+			Some("execution reverted".to_string())
+		},
+		_ => {
+			log::debug!(target: LOG_TARGET, "Unknown revert function selector: {error_selector:?}");
+			Some("execution reverted".to_string())
+		},
 	}
 }
 
@@ -146,42 +170,46 @@ pub enum ClientError {
 	/// A [`codec::Error`] wrapper error.
 	#[error(transparent)]
 	CodecError(#[from] codec::Error),
-	/// The dry run failed.
-	#[error("Dry run failed: {0}")]
-	DryRunFailed(String),
 	/// Contract reverted
-	#[error("Execution reverted: {}", extract_revert_message(.0).unwrap_or_default())]
-	Reverted(Vec<u8>),
+	#[error("contract reverted")]
+	Reverted(EthTransactError),
 	/// A decimal conversion failed.
-	#[error("Conversion failed")]
+	#[error("conversion failed")]
 	ConversionFailed,
 	/// The block hash was not found.
-	#[error("Hash not found")]
+	#[error("hash not found")]
 	BlockNotFound,
 	/// The transaction fee could not be found
-	#[error("TransactionFeePaid event not found")]
+	#[error("transactionFeePaid event not found")]
 	TxFeeNotFound,
 	/// The cache is empty.
-	#[error("Cache is empty")]
+	#[error("cache is empty")]
 	CacheEmpty,
 }
 
-// TODO convert error code to https://eips.ethereum.org/EIPS/eip-1474#error-codes
+const REVERT_CODE: i32 = 3;
 impl From<ClientError> for ErrorObjectOwned {
 	fn from(err: ClientError) -> Self {
-		let msg = err.to_string();
 		match err {
 			ClientError::SubxtError(subxt::Error::Rpc(err)) | ClientError::RpcError(err) => {
 				if let Some(err) = unwrap_call_err(&err) {
 					return err;
 				}
-				ErrorObjectOwned::owned::<Vec<u8>>(CALL_EXECUTION_FAILED_CODE, msg, None)
+				ErrorObjectOwned::owned::<Vec<u8>>(
+					CALL_EXECUTION_FAILED_CODE,
+					err.to_string(),
+					None,
+				)
 			},
-			ClientError::Reverted(data) => {
+			ClientError::Reverted(EthTransactError::Data(data)) => {
+				let msg = extract_revert_message(&data).unwrap_or_default();
 				let data = format!("0x{}", hex::encode(data));
-				ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, Some(data))
+				ErrorObjectOwned::owned::<String>(REVERT_CODE, msg, Some(data))
 			},
-			_ => ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, None),
+			ClientError::Reverted(EthTransactError::Message(msg)) =>
+				ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, None),
+			_ =>
+				ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, err.to_string(), None),
 		}
 	}
 }
@@ -634,54 +662,25 @@ impl Client {
 		Ok(result)
 	}
 
-	/// Dry run a transaction and returns the [`EthContractResult`] for the transaction.
+	/// Dry run a transaction and returns the [`EthTransactInfo`] for the transaction.
 	pub async fn dry_run(
 		&self,
-		tx: &GenericTransaction,
+		tx: GenericTransaction,
 		block: BlockNumberOrTagOrHash,
-	) -> Result<EthContractResult<Balance, Vec<u8>>, ClientError> {
+	) -> Result<EthTransactInfo<Balance>, ClientError> {
 		let runtime_api = self.runtime_api(&block).await?;
+		let payload = subxt_client::apis().revive_api().eth_transact(tx.into());
 
-		// TODO: remove once subxt is updated
-		let value = subxt::utils::Static(tx.value.unwrap_or_default());
-		let from = tx.from.map(|v| v.0.into());
-		let to = tx.to.map(|v| v.0.into());
-
-		let payload = subxt_client::apis().revive_api().eth_transact(
-			from.unwrap_or_default(),
-			to,
-			value,
-			tx.input.clone().unwrap_or_default().0,
-			None,
-			None,
-		);
-
-		let EthContractResult { fee, gas_required, storage_deposit, result } =
-			runtime_api.call(payload).await?.0;
+		let result = runtime_api.call(payload).await?;
 		match result {
 			Err(err) => {
 				log::debug!(target: LOG_TARGET, "Dry run failed {err:?}");
-				Err(ClientError::DryRunFailed(format!("{err:?}")))
+				Err(ClientError::Reverted(err.0))
 			},
-			Ok(result) if result.did_revert() => {
-				log::debug!(target: LOG_TARGET, "Dry run reverted");
-				Err(ClientError::Reverted(result.0.data))
-			},
-			Ok(result) =>
-				Ok(EthContractResult { fee, gas_required, storage_deposit, result: result.0.data }),
+			Ok(result) => Ok(result.0),
 		}
 	}
 
-	/// Dry run a transaction and returns the gas estimate for the transaction.
-	pub async fn estimate_gas(
-		&self,
-		tx: &GenericTransaction,
-		block: BlockNumberOrTagOrHash,
-	) -> Result<U256, ClientError> {
-		let dry_run = self.dry_run(tx, block).await?;
-		Ok(U256::from(dry_run.fee / GAS_PRICE as u128) + GAS_PRICE)
-	}
-
 	/// Get the nonce of the given address.
 	pub async fn nonce(
 		&self,
diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs
index 6a324e63a8573eda22dec3f4c865fd7576568def..ccd8bb043e90ec77008eed81a87c83bd5baa35df 100644
--- a/substrate/frame/revive/rpc/src/lib.rs
+++ b/substrate/frame/revive/rpc/src/lib.rs
@@ -23,7 +23,7 @@ use jsonrpsee::{
 	core::{async_trait, RpcResult},
 	types::{ErrorCode, ErrorObjectOwned},
 };
-use pallet_revive::{evm::*, EthContractResult};
+use pallet_revive::evm::*;
 use sp_core::{keccak_256, H160, H256, U256};
 use thiserror::Error;
 
@@ -128,10 +128,22 @@ impl EthRpcServer for EthRpcServerImpl {
 	async fn estimate_gas(
 		&self,
 		transaction: GenericTransaction,
-		_block: Option<BlockNumberOrTag>,
+		block: Option<BlockNumberOrTag>,
 	) -> RpcResult<U256> {
-		let result = self.client.estimate_gas(&transaction, BlockTag::Latest.into()).await?;
-		Ok(result)
+		let dry_run = self.client.dry_run(transaction, block.unwrap_or_default().into()).await?;
+		Ok(dry_run.eth_gas)
+	}
+
+	async fn call(
+		&self,
+		transaction: GenericTransaction,
+		block: Option<BlockNumberOrTagOrHash>,
+	) -> RpcResult<Bytes> {
+		let dry_run = self
+			.client
+			.dry_run(transaction, block.unwrap_or_else(|| BlockTag::Latest.into()))
+			.await?;
+		Ok(dry_run.data.into())
 	}
 
 	async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult<H256> {
@@ -150,15 +162,17 @@ impl EthRpcServer for EthRpcServerImpl {
 		let tx = GenericTransaction::from_signed(tx, Some(eth_addr));
 
 		// Dry run the transaction to get the weight limit and storage deposit limit
-		let dry_run = self.client.dry_run(&tx, BlockTag::Latest.into()).await?;
+		let dry_run = self.client.dry_run(tx, BlockTag::Latest.into()).await?;
 
-		let EthContractResult { gas_required, storage_deposit, .. } = dry_run;
 		let call = subxt_client::tx().revive().eth_transact(
 			transaction.0,
-			gas_required.into(),
-			storage_deposit,
+			dry_run.gas_required.into(),
+			dry_run.storage_deposit,
 		);
-		self.client.submit(call).await?;
+		self.client.submit(call).await.map_err(|err| {
+			log::debug!(target: LOG_TARGET, "submit call failed: {err:?}");
+			err
+		})?;
 		log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}");
 		Ok(hash)
 	}
@@ -234,18 +248,6 @@ impl EthRpcServer for EthRpcServerImpl {
 		Ok(self.accounts.iter().map(|account| account.address()).collect())
 	}
 
-	async fn call(
-		&self,
-		transaction: GenericTransaction,
-		block: Option<BlockNumberOrTagOrHash>,
-	) -> RpcResult<Bytes> {
-		let dry_run = self
-			.client
-			.dry_run(&transaction, block.unwrap_or_else(|| BlockTag::Latest.into()))
-			.await?;
-		Ok(dry_run.result.into())
-	}
-
 	async fn get_block_by_number(
 		&self,
 		block: BlockNumberOrTag,
diff --git a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs
index 33908036896989593caec66f32db80c4df8ed017..ad34dbfdfb4918bbfce7eec88c6ebf92311fbea2 100644
--- a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs
+++ b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs
@@ -14,6 +14,7 @@
 // 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 methods.
 #![allow(missing_docs)]
 
diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs
index a232b231bc7c0337c71e7e45de462573ffc63e25..1e1c395028a457461987056528d837dbdf87a057 100644
--- a/substrate/frame/revive/rpc/src/subxt_client.rs
+++ b/substrate/frame/revive/rpc/src/subxt_client.rs
@@ -27,8 +27,16 @@ use subxt::config::{signed_extensions, Config, PolkadotConfig};
 		with = "::subxt::utils::Static<::sp_core::U256>"
 	),
 	substitute_type(
-		path = "pallet_revive::primitives::EthContractResult<A, B>",
-		with = "::subxt::utils::Static<::pallet_revive::EthContractResult<A, B>>"
+		path = "pallet_revive::evm::api::rpc_types_gen::GenericTransaction",
+		with = "::subxt::utils::Static<::pallet_revive::evm::GenericTransaction>"
+	),
+	substitute_type(
+		path = "pallet_revive::primitives::EthTransactInfo<B>",
+		with = "::subxt::utils::Static<::pallet_revive::EthTransactInfo<B>>"
+	),
+	substitute_type(
+		path = "pallet_revive::primitives::EthTransactError",
+		with = "::subxt::utils::Static<::pallet_revive::EthTransactError>"
 	),
 	substitute_type(
 		path = "pallet_revive::primitives::ExecReturnValue",
diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs
index 920318b26f713b861273b954cb4c99974c2dad42..7f2d4e683c31d5879f3954966ae1447f7a42e306 100644
--- a/substrate/frame/revive/rpc/src/tests.rs
+++ b/substrate/frame/revive/rpc/src/tests.rs
@@ -238,7 +238,8 @@ async fn revert_call() -> anyhow::Result<()> {
 		.unwrap_err();
 
 	let call_err = unwrap_call_err!(err.source().unwrap());
-	assert_eq!(call_err.message(), "Execution reverted: revert message");
+	assert_eq!(call_err.message(), "execution reverted: revert message");
+	assert_eq!(call_err.code(), 3);
 	Ok(())
 }
 
diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs
index 9c4d817a07dee527f1377ac7786e4d82957e76f6..b73815bfb9ea9ae0032e495bfe4e8c08c3855d6b 100644
--- a/substrate/frame/revive/src/benchmarking/mod.rs
+++ b/substrate/frame/revive/src/benchmarking/mod.rs
@@ -103,7 +103,7 @@ where
 			origin,
 			0u32.into(),
 			Weight::MAX,
-			default_deposit_limit::<T>(),
+			DepositLimit::Balance(default_deposit_limit::<T>()),
 			Code::Upload(module.code),
 			data,
 			salt,
diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs
index 3442ed73accab7be5caff8f884a336c4be03b252..9b61cd042ec5433655c1002bcab62b6d67bd2fd7 100644
--- a/substrate/frame/revive/src/evm/api/rlp_codec.rs
+++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs
@@ -88,14 +88,14 @@ impl TransactionSigned {
 	}
 }
 
-impl TransactionLegacyUnsigned {
-	/// Get the rlp encoded bytes of a signed transaction with a dummy 65 bytes signature.
+impl TransactionUnsigned {
+	/// Get a signed transaction payload 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()
+		self.unsigned_payload()
+			.into_iter()
+			.chain(DUMMY_SIGNATURE.iter().copied())
+			.collect::<Vec<_>>()
 	}
 }
 
@@ -567,7 +567,7 @@ mod test {
 
 	#[test]
 	fn dummy_signed_payload_works() {
-		let tx = TransactionLegacyUnsigned {
+		let tx: TransactionUnsigned = TransactionLegacyUnsigned {
 			chain_id: Some(596.into()),
 			gas: U256::from(21000),
 			nonce: U256::from(1),
@@ -576,10 +576,10 @@ mod test {
 			value: U256::from(123123),
 			input: Bytes(vec![]),
 			r#type: TypeLegacy,
-		};
+		}
+		.into();
 
 		let dummy_signed_payload = tx.dummy_signed_payload();
-		let tx: TransactionUnsigned = tx.into();
 		let payload = Account::default().sign_transaction(tx).signed_payload();
 		assert_eq!(dummy_signed_payload.len(), payload.len());
 	}
diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs
index 1cf8d984b68b847d512fba8dd9483745ecb507e8..ed046cb4da4456b3bcec1ed1fb5ae428b08193a3 100644
--- a/substrate/frame/revive/src/evm/api/rpc_types.rs
+++ b/substrate/frame/revive/src/evm/api/rpc_types.rs
@@ -19,6 +19,27 @@ use super::*;
 use alloc::vec::Vec;
 use sp_core::{H160, U256};
 
+impl From<BlockNumberOrTag> for BlockNumberOrTagOrHash {
+	fn from(b: BlockNumberOrTag) -> Self {
+		match b {
+			BlockNumberOrTag::U256(n) => BlockNumberOrTagOrHash::U256(n),
+			BlockNumberOrTag::BlockTag(t) => BlockNumberOrTagOrHash::BlockTag(t),
+		}
+	}
+}
+
+impl From<TransactionSigned> for TransactionUnsigned {
+	fn from(tx: TransactionSigned) -> Self {
+		use TransactionSigned::*;
+		match tx {
+			Transaction4844Signed(tx) => tx.transaction_4844_unsigned.into(),
+			Transaction1559Signed(tx) => tx.transaction_1559_unsigned.into(),
+			Transaction2930Signed(tx) => tx.transaction_2930_unsigned.into(),
+			TransactionLegacySigned(tx) => tx.transaction_legacy_unsigned.into(),
+		}
+	}
+}
+
 impl TransactionInfo {
 	/// Create a new [`TransactionInfo`] from a receipt and a signed transaction.
 	pub fn new(receipt: ReceiptInfo, transaction_signed: TransactionSigned) -> Self {
@@ -143,76 +164,69 @@ fn logs_bloom_works() {
 impl GenericTransaction {
 	/// Create a new [`GenericTransaction`] from a signed transaction.
 	pub fn from_signed(tx: TransactionSigned, from: Option<H160>) -> Self {
-		use TransactionSigned::*;
+		Self::from_unsigned(tx.into(), from)
+	}
+
+	/// Create a new [`GenericTransaction`] from a unsigned transaction.
+	pub fn from_unsigned(tx: TransactionUnsigned, from: Option<H160>) -> Self {
+		use TransactionUnsigned::*;
 		match tx {
-			TransactionLegacySigned(tx) => {
-				let tx = tx.transaction_legacy_unsigned;
-				GenericTransaction {
-					from,
-					r#type: Some(tx.r#type.as_byte()),
-					chain_id: tx.chain_id,
-					input: Some(tx.input),
-					nonce: Some(tx.nonce),
-					value: Some(tx.value),
-					to: tx.to,
-					gas: Some(tx.gas),
-					gas_price: Some(tx.gas_price),
-					..Default::default()
-				}
+			TransactionLegacyUnsigned(tx) => GenericTransaction {
+				from,
+				r#type: Some(tx.r#type.as_byte()),
+				chain_id: tx.chain_id,
+				input: Some(tx.input),
+				nonce: Some(tx.nonce),
+				value: Some(tx.value),
+				to: tx.to,
+				gas: Some(tx.gas),
+				gas_price: Some(tx.gas_price),
+				..Default::default()
 			},
-			Transaction4844Signed(tx) => {
-				let tx = tx.transaction_4844_unsigned;
-				GenericTransaction {
-					from,
-					r#type: Some(tx.r#type.as_byte()),
-					chain_id: Some(tx.chain_id),
-					input: Some(tx.input),
-					nonce: Some(tx.nonce),
-					value: Some(tx.value),
-					to: Some(tx.to),
-					gas: Some(tx.gas),
-					gas_price: Some(tx.max_fee_per_blob_gas),
-					access_list: Some(tx.access_list),
-					blob_versioned_hashes: Some(tx.blob_versioned_hashes),
-					max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas),
-					max_fee_per_gas: Some(tx.max_fee_per_gas),
-					max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
-					..Default::default()
-				}
+			Transaction4844Unsigned(tx) => GenericTransaction {
+				from,
+				r#type: Some(tx.r#type.as_byte()),
+				chain_id: Some(tx.chain_id),
+				input: Some(tx.input),
+				nonce: Some(tx.nonce),
+				value: Some(tx.value),
+				to: Some(tx.to),
+				gas: Some(tx.gas),
+				gas_price: Some(tx.max_fee_per_blob_gas),
+				access_list: Some(tx.access_list),
+				blob_versioned_hashes: tx.blob_versioned_hashes,
+				max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas),
+				max_fee_per_gas: Some(tx.max_fee_per_gas),
+				max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
+				..Default::default()
 			},
-			Transaction1559Signed(tx) => {
-				let tx = tx.transaction_1559_unsigned;
-				GenericTransaction {
-					from,
-					r#type: Some(tx.r#type.as_byte()),
-					chain_id: Some(tx.chain_id),
-					input: Some(tx.input),
-					nonce: Some(tx.nonce),
-					value: Some(tx.value),
-					to: tx.to,
-					gas: Some(tx.gas),
-					gas_price: Some(tx.gas_price),
-					access_list: Some(tx.access_list),
-					max_fee_per_gas: Some(tx.max_fee_per_gas),
-					max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
-					..Default::default()
-				}
+			Transaction1559Unsigned(tx) => GenericTransaction {
+				from,
+				r#type: Some(tx.r#type.as_byte()),
+				chain_id: Some(tx.chain_id),
+				input: Some(tx.input),
+				nonce: Some(tx.nonce),
+				value: Some(tx.value),
+				to: tx.to,
+				gas: Some(tx.gas),
+				gas_price: Some(tx.gas_price),
+				access_list: Some(tx.access_list),
+				max_fee_per_gas: Some(tx.max_fee_per_gas),
+				max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
+				..Default::default()
 			},
-			Transaction2930Signed(tx) => {
-				let tx = tx.transaction_2930_unsigned;
-				GenericTransaction {
-					from,
-					r#type: Some(tx.r#type.as_byte()),
-					chain_id: Some(tx.chain_id),
-					input: Some(tx.input),
-					nonce: Some(tx.nonce),
-					value: Some(tx.value),
-					to: tx.to,
-					gas: Some(tx.gas),
-					gas_price: Some(tx.gas_price),
-					access_list: Some(tx.access_list),
-					..Default::default()
-				}
+			Transaction2930Unsigned(tx) => GenericTransaction {
+				from,
+				r#type: Some(tx.r#type.as_byte()),
+				chain_id: Some(tx.chain_id),
+				input: Some(tx.input),
+				nonce: Some(tx.nonce),
+				value: Some(tx.value),
+				to: tx.to,
+				gas: Some(tx.gas),
+				gas_price: Some(tx.gas_price),
+				access_list: Some(tx.access_list),
+				..Default::default()
 			},
 		}
 	}
@@ -269,7 +283,7 @@ impl GenericTransaction {
 				max_fee_per_blob_gas: self.max_fee_per_blob_gas.unwrap_or_default(),
 				max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
 				access_list: self.access_list.unwrap_or_default(),
-				blob_versioned_hashes: self.blob_versioned_hashes.unwrap_or_default(),
+				blob_versioned_hashes: self.blob_versioned_hashes,
 			}
 			.into()),
 			_ => Err(()),
diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs
index 5037ec05d881c94f33cc8e0b798b9c2ef7d4c1c9..1d65fdefdde685d1349a34de2c8792230f920495 100644
--- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs
+++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs
@@ -94,8 +94,8 @@ pub struct Block {
 	/// Uncles
 	pub uncles: Vec<H256>,
 	/// Withdrawals
-	#[serde(skip_serializing_if = "Option::is_none")]
-	pub withdrawals: Option<Vec<Withdrawal>>,
+	#[serde(default, skip_serializing_if = "Vec::is_empty")]
+	pub withdrawals: Vec<Withdrawal>,
 	/// Withdrawals root
 	#[serde(rename = "withdrawalsRoot", skip_serializing_if = "Option::is_none")]
 	pub withdrawals_root: Option<H256>,
@@ -114,7 +114,7 @@ pub enum BlockNumberOrTag {
 }
 impl Default for BlockNumberOrTag {
 	fn default() -> Self {
-		BlockNumberOrTag::U256(Default::default())
+		BlockNumberOrTag::BlockTag(Default::default())
 	}
 }
 
@@ -133,7 +133,7 @@ pub enum BlockNumberOrTagOrHash {
 }
 impl Default for BlockNumberOrTagOrHash {
 	fn default() -> Self {
-		BlockNumberOrTagOrHash::U256(Default::default())
+		BlockNumberOrTagOrHash::BlockTag(Default::default())
 	}
 }
 
@@ -148,12 +148,12 @@ pub struct GenericTransaction {
 	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>>,
+	#[serde(rename = "blobVersionedHashes", default, skip_serializing_if = "Vec::is_empty")]
+	pub blob_versioned_hashes: Vec<H256>,
 	/// blobs
 	/// Raw blob data.
-	#[serde(skip_serializing_if = "Option::is_none")]
-	pub blobs: Option<Vec<Bytes>>,
+	#[serde(default, skip_serializing_if = "Vec::is_empty")]
+	pub blobs: Vec<Bytes>,
 	/// chainId
 	/// Chain ID that this transaction is valid on.
 	#[serde(rename = "chainId", skip_serializing_if = "Option::is_none")]
@@ -319,7 +319,7 @@ pub enum TransactionUnsigned {
 }
 impl Default for TransactionUnsigned {
 	fn default() -> Self {
-		TransactionUnsigned::Transaction4844Unsigned(Default::default())
+		TransactionUnsigned::TransactionLegacyUnsigned(Default::default())
 	}
 }
 
@@ -341,13 +341,13 @@ pub type AccessList = Vec<AccessListEntry>;
 )]
 pub enum BlockTag {
 	#[serde(rename = "earliest")]
-	#[default]
 	Earliest,
 	#[serde(rename = "finalized")]
 	Finalized,
 	#[serde(rename = "safe")]
 	Safe,
 	#[serde(rename = "latest")]
+	#[default]
 	Latest,
 	#[serde(rename = "pending")]
 	Pending,
@@ -392,7 +392,7 @@ pub struct Log {
 	#[serde(skip_serializing_if = "Option::is_none")]
 	pub removed: Option<bool>,
 	/// topics
-	#[serde(skip_serializing_if = "Vec::is_empty")]
+	#[serde(default, skip_serializing_if = "Vec::is_empty")]
 	pub topics: Vec<H256>,
 	/// transaction hash
 	#[serde(rename = "transactionHash")]
@@ -574,7 +574,7 @@ pub enum TransactionSigned {
 }
 impl Default for TransactionSigned {
 	fn default() -> Self {
-		TransactionSigned::Transaction4844Signed(Default::default())
+		TransactionSigned::TransactionLegacySigned(Default::default())
 	}
 }
 
diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs
index b5dc9a36065bf47a8c6811e5c8a3d3e2ab4b60f9..24b75de835698697311d191a812df36f6fd81390 100644
--- a/substrate/frame/revive/src/evm/runtime.rs
+++ b/substrate/frame/revive/src/evm/runtime.rs
@@ -455,236 +455,265 @@ mod test {
 	/// A builder for creating an unchecked extrinsic, and test that the check function works.
 	#[derive(Clone)]
 	struct UncheckedExtrinsicBuilder {
-		tx: TransactionLegacyUnsigned,
+		tx: GenericTransaction,
 		gas_limit: Weight,
 		storage_deposit_limit: BalanceOf<Test>,
+		before_validate: Option<std::sync::Arc<dyn Fn() + Send + Sync>>,
 	}
 
 	impl UncheckedExtrinsicBuilder {
 		/// Create a new builder with default values.
 		fn new() -> Self {
 			Self {
-				tx: TransactionLegacyUnsigned {
+				tx: GenericTransaction {
+					from: Some(Account::default().address()),
 					chain_id: Some(<Test as crate::Config>::ChainId::get().into()),
-					gas_price: U256::from(GAS_PRICE),
+					gas_price: Some(U256::from(GAS_PRICE)),
 					..Default::default()
 				},
 				gas_limit: Weight::zero(),
 				storage_deposit_limit: 0,
+				before_validate: None,
 			}
 		}
 
 		fn estimate_gas(&mut self) {
-			let dry_run = crate::Pallet::<Test>::bare_eth_transact(
-				Account::default().substrate_account(),
-				self.tx.to,
-				self.tx.value.try_into().unwrap(),
-				self.tx.input.clone().0,
-				Weight::MAX,
-				u64::MAX,
-				|call| {
+			let dry_run =
+				crate::Pallet::<Test>::bare_eth_transact(self.tx.clone(), Weight::MAX, |call| {
 					let call = RuntimeCall::Contracts(call);
 					let uxt: Ex = sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into();
 					uxt.encoded_size() as u32
+				});
+
+			match dry_run {
+				Ok(dry_run) => {
+					log::debug!(target: LOG_TARGET, "Estimated gas: {:?}", dry_run.eth_gas);
+					self.tx.gas = Some(dry_run.eth_gas);
+				},
+				Err(err) => {
+					log::debug!(target: LOG_TARGET, "Failed to estimate gas: {:?}", err);
 				},
-				crate::DebugInfo::Skip,
-				crate::CollectEvents::Skip,
-			);
-			self.tx.gas = ((dry_run.fee + GAS_PRICE as u64) / (GAS_PRICE as u64)).into();
+			}
 		}
 
 		/// 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.estimate_gas();
+			ExtBuilder::default().build().execute_with(|| builder.estimate_gas());
 			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.estimate_gas();
+			builder.tx.input = Some(Bytes(code.into_iter().chain(data.into_iter()).collect()));
+			ExtBuilder::default().build().execute_with(|| builder.estimate_gas());
 			builder
 		}
 
 		/// Update the transaction with the given function.
-		fn update(mut self, f: impl FnOnce(&mut TransactionLegacyUnsigned) -> ()) -> Self {
+		fn update(mut self, f: impl FnOnce(&mut GenericTransaction) -> ()) -> Self {
 			f(&mut self.tx);
 			self
 		}
+		/// Set before_validate function.
+		fn before_validate(mut self, f: impl Fn() + Send + Sync + 'static) -> Self {
+			self.before_validate = Some(std::sync::Arc::new(f));
+			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.substrate_account(),
-				100_000_000_000_000,
-			);
-
-			let payload = account.sign_transaction(tx.into()).signed_payload();
-			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,
-				0,
-			)?;
+			ExtBuilder::default().build().execute_with(|| {
+				let UncheckedExtrinsicBuilder {
+					tx,
+					gas_limit,
+					storage_deposit_limit,
+					before_validate,
+				} = self.clone();
+
+				// Fund the account.
+				let account = Account::default();
+				let _ = <Test as crate::Config>::Currency::set_balance(
+					&account.substrate_account(),
+					100_000_000_000_000,
+				);
+
+				let payload =
+					account.sign_transaction(tx.try_into_unsigned().unwrap()).signed_payload();
+				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!(),
+				};
 
-			Ok((result.function, extra))
+				before_validate.map(|f| f());
+				extra.clone().validate_and_prepare(
+					RuntimeOrigin::signed(account_id),
+					&result.function,
+					&result.function.get_dispatch_info(),
+					encoded_len,
+					0,
+				)?;
+
+				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()
-			);
-		});
+		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.unwrap_or_default().as_u64(),
+				gas_limit: builder.gas_limit,
+				storage_deposit_limit: builder.storage_deposit_limit,
+				data: builder.tx.input.unwrap_or_default().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()
-			);
-		});
+		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.unwrap_or_default().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().substrate_account());
-
-			let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20]));
-			assert_eq!(
-				builder.check(),
-				Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))
-			);
-		});
+		let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20]))
+			.update(|tx| tx.nonce = Some(1u32.into()));
+
+		assert_eq!(
+			builder.check(),
+			Err(TransactionValidityError::Invalid(InvalidTransaction::Future))
+		);
+
+		let builder =
+			UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).before_validate(|| {
+				<crate::System<Test>>::inc_account_nonce(Account::default().substrate_account());
+			});
+
+		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))
-			);
-		});
+		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))
-			);
-		});
+		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 = Some(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);
-			}
-		});
+		let scenarios: [(_, Box<dyn FnOnce(&mut GenericTransaction)>, _); 5] = [
+			(
+				"Eth fees too low",
+				Box::new(|tx| {
+					tx.gas_price = Some(tx.gas_price.unwrap() / 2);
+				}),
+				InvalidTransaction::Payment,
+			),
+			(
+				"Gas fees too high",
+				Box::new(|tx| {
+					tx.gas = Some(tx.gas.unwrap() * 2);
+				}),
+				InvalidTransaction::Call,
+			),
+			(
+				"Gas fees too low",
+				Box::new(|tx| {
+					tx.gas = Some(tx.gas.unwrap() * 2);
+				}),
+				InvalidTransaction::Call,
+			),
+			(
+				"Diff > 10%",
+				Box::new(|tx| {
+					tx.gas = Some(tx.gas.unwrap() * 111 / 100);
+				}),
+				InvalidTransaction::Call,
+			),
+			(
+				"Diff < 10%",
+				Box::new(|tx| {
+					tx.gas_price = Some(tx.gas_price.unwrap() * 2);
+					tx.gas = Some(tx.gas.unwrap() * 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);
-		});
+		let (code, _) = compile_module("dummy").unwrap();
+		let data = vec![];
+		let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone())
+			.update(|tx| {
+				tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100);
+				log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price);
+			});
+
+		let tx = &builder.tx;
+		let expected_tip =
+			tx.gas_price.unwrap() * tx.gas.unwrap() - U256::from(GAS_PRICE) * tx.gas.unwrap();
+		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 49c08166483e50508b8be6b385959edf663b958a..b23d7e4e60efe26b3dcc2555d5d95ba75ee9e43f 100644
--- a/substrate/frame/revive/src/exec.rs
+++ b/substrate/frame/revive/src/exec.rs
@@ -562,6 +562,9 @@ pub struct Stack<'a, T: Config, E> {
 	debug_message: Option<&'a mut DebugBuffer>,
 	/// Transient storage used to store data, which is kept for the duration of a transaction.
 	transient_storage: TransientStorage<T>,
+	/// Whether or not actual transfer of funds should be performed.
+	/// This is set to `true` exclusively when we simulate a call through eth_transact.
+	skip_transfer: bool,
 	/// No executable is held by the struct but influences its behaviour.
 	_phantom: PhantomData<E>,
 }
@@ -777,6 +780,7 @@ where
 		storage_meter: &'a mut storage::meter::Meter<T>,
 		value: U256,
 		input_data: Vec<u8>,
+		skip_transfer: bool,
 		debug_message: Option<&'a mut DebugBuffer>,
 	) -> ExecResult {
 		let dest = T::AddressMapper::to_account_id(&dest);
@@ -786,6 +790,7 @@ where
 			gas_meter,
 			storage_meter,
 			value,
+			skip_transfer,
 			debug_message,
 		)? {
 			stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output)
@@ -812,6 +817,7 @@ where
 		value: U256,
 		input_data: Vec<u8>,
 		salt: Option<&[u8; 32]>,
+		skip_transfer: bool,
 		debug_message: Option<&'a mut DebugBuffer>,
 	) -> Result<(H160, ExecReturnValue), ExecError> {
 		let (mut stack, executable) = Self::new(
@@ -825,6 +831,7 @@ where
 			gas_meter,
 			storage_meter,
 			value,
+			skip_transfer,
 			debug_message,
 		)?
 		.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE);
@@ -853,6 +860,7 @@ where
 			gas_meter,
 			storage_meter,
 			value.into(),
+			false,
 			debug_message,
 		)
 		.unwrap()
@@ -869,6 +877,7 @@ where
 		gas_meter: &'a mut GasMeter<T>,
 		storage_meter: &'a mut storage::meter::Meter<T>,
 		value: U256,
+		skip_transfer: bool,
 		debug_message: Option<&'a mut DebugBuffer>,
 	) -> Result<Option<(Self, E)>, ExecError> {
 		origin.ensure_mapped()?;
@@ -896,6 +905,7 @@ where
 			frames: Default::default(),
 			debug_message,
 			transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES),
+			skip_transfer,
 			_phantom: Default::default(),
 		};
 
@@ -1073,6 +1083,7 @@ where
 					&frame.account_id,
 					frame.contract_info.get(&frame.account_id),
 					executable.code_info(),
+					self.skip_transfer,
 				)?;
 				// Needs to be incremented before calling into the code so that it is visible
 				// in case of recursion.
@@ -2101,6 +2112,7 @@ mod tests {
 					&mut storage_meter,
 					value.into(),
 					vec![],
+					false,
 					None,
 				),
 				Ok(_)
@@ -2193,6 +2205,7 @@ mod tests {
 				&mut storage_meter,
 				value.into(),
 				vec![],
+				false,
 				None,
 			)
 			.unwrap();
@@ -2233,6 +2246,7 @@ mod tests {
 				&mut storage_meter,
 				value.into(),
 				vec![],
+				false,
 				None,
 			));
 
@@ -2269,6 +2283,7 @@ mod tests {
 					&mut storage_meter,
 					U256::zero(),
 					vec![],
+					false,
 					None,
 				),
 				ExecError {
@@ -2286,6 +2301,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			));
 		});
@@ -2314,6 +2330,7 @@ mod tests {
 				&mut storage_meter,
 				55u64.into(),
 				vec![],
+				false,
 				None,
 			)
 			.unwrap();
@@ -2363,6 +2380,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			);
 
@@ -2392,6 +2410,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			);
 
@@ -2421,6 +2440,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![1, 2, 3, 4],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -2457,6 +2477,7 @@ mod tests {
 					min_balance.into(),
 					vec![1, 2, 3, 4],
 					Some(&[0; 32]),
+					false,
 					None,
 				);
 				assert_matches!(result, Ok(_));
@@ -2511,6 +2532,7 @@ mod tests {
 				&mut storage_meter,
 				value.into(),
 				vec![],
+				false,
 				None,
 			);
 
@@ -2575,6 +2597,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			);
 
@@ -2640,6 +2663,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			);
 
@@ -2672,6 +2696,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -2709,6 +2734,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![0],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -2735,6 +2761,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![0],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -2779,6 +2806,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![0],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -2805,6 +2833,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![0],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -2831,6 +2860,7 @@ mod tests {
 				&mut storage_meter,
 				1u64.into(),
 				vec![0],
+				false,
 				None,
 			);
 			assert_matches!(result, Err(_));
@@ -2875,6 +2905,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![0],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -2920,6 +2951,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			);
 
@@ -2946,6 +2978,7 @@ mod tests {
 					U256::zero(), // <- zero value
 					vec![],
 					Some(&[0; 32]),
+					false,
 					None,
 				),
 				Err(_)
@@ -2981,6 +3014,7 @@ mod tests {
 						min_balance.into(),
 						vec![],
 						Some(&[0 ;32]),
+						false,
 						None,
 					),
 					Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address
@@ -3032,10 +3066,10 @@ mod tests {
 						executable,
 						&mut gas_meter,
 						&mut storage_meter,
-
 						min_balance.into(),
 						vec![],
 						Some(&[0; 32]),
+						false,
 						None,
 					),
 					Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address
@@ -3100,6 +3134,7 @@ mod tests {
 						&mut storage_meter,
 						(min_balance * 10).into(),
 						vec![],
+						false,
 						None,
 					),
 					Ok(_)
@@ -3180,6 +3215,7 @@ mod tests {
 						&mut storage_meter,
 						U256::zero(),
 						vec![],
+						false,
 						None,
 					),
 					Ok(_)
@@ -3223,6 +3259,7 @@ mod tests {
 						100u64.into(),
 						vec![],
 						Some(&[0; 32]),
+						false,
 						None,
 					),
 					Err(Error::<Test>::TerminatedInConstructor.into())
@@ -3287,6 +3324,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![0],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -3349,6 +3387,7 @@ mod tests {
 					10u64.into(),
 					vec![],
 					Some(&[0; 32]),
+					false,
 					None,
 				);
 				assert_matches!(result, Ok(_));
@@ -3395,6 +3434,7 @@ mod tests {
 					&mut storage_meter,
 					U256::zero(),
 					vec![],
+					false,
 					None,
 				)
 				.unwrap();
@@ -3426,6 +3466,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				Some(&mut debug_buffer),
 			)
 			.unwrap();
@@ -3459,6 +3500,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				Some(&mut debug_buffer),
 			);
 			assert!(result.is_err());
@@ -3492,6 +3534,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				Some(&mut debug_buf_after),
 			)
 			.unwrap();
@@ -3525,6 +3568,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				CHARLIE_ADDR.as_bytes().to_vec(),
+				false,
 				None,
 			));
 
@@ -3537,6 +3581,7 @@ mod tests {
 					&mut storage_meter,
 					U256::zero(),
 					BOB_ADDR.as_bytes().to_vec(),
+					false,
 					None,
 				)
 				.map_err(|e| e.error),
@@ -3587,6 +3632,7 @@ mod tests {
 					&mut storage_meter,
 					U256::zero(),
 					vec![0],
+					false,
 					None,
 				)
 				.map_err(|e| e.error),
@@ -3621,6 +3667,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			)
 			.unwrap();
@@ -3705,6 +3752,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			)
 			.unwrap();
@@ -3831,6 +3879,7 @@ mod tests {
 					(min_balance * 100).into(),
 					vec![],
 					Some(&[0; 32]),
+					false,
 					None,
 				)
 				.ok();
@@ -3844,6 +3893,7 @@ mod tests {
 					(min_balance * 100).into(),
 					vec![],
 					Some(&[0; 32]),
+					false,
 					None,
 				));
 				assert_eq!(System::account_nonce(&ALICE), 1);
@@ -3856,6 +3906,7 @@ mod tests {
 					(min_balance * 200).into(),
 					vec![],
 					Some(&[0; 32]),
+					false,
 					None,
 				));
 				assert_eq!(System::account_nonce(&ALICE), 2);
@@ -3868,6 +3919,7 @@ mod tests {
 					(min_balance * 200).into(),
 					vec![],
 					Some(&[0; 32]),
+					false,
 					None,
 				));
 				assert_eq!(System::account_nonce(&ALICE), 3);
@@ -3936,6 +3988,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			));
 		});
@@ -4047,6 +4100,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			));
 		});
@@ -4086,6 +4140,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			));
 		});
@@ -4125,6 +4180,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			));
 		});
@@ -4178,6 +4234,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			));
 		});
@@ -4234,6 +4291,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			));
 		});
@@ -4309,6 +4367,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			));
 		});
@@ -4379,6 +4438,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![0],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -4417,6 +4477,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			));
 		});
@@ -4479,6 +4540,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![0],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -4512,6 +4574,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -4595,6 +4658,7 @@ mod tests {
 					&mut storage_meter,
 					U256::zero(),
 					vec![],
+					false,
 					None,
 				)
 				.unwrap()
@@ -4663,6 +4727,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![0],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -4734,6 +4799,7 @@ mod tests {
 				&mut storage_meter,
 				U256::zero(),
 				vec![],
+				false,
 				None,
 			);
 			assert_matches!(result, Ok(_));
@@ -4785,6 +4851,7 @@ mod tests {
 					&mut storage_meter,
 					U256::zero(),
 					vec![],
+					false,
 					None,
 				)
 				.unwrap()
@@ -4854,6 +4921,7 @@ mod tests {
 					&mut storage_meter,
 					U256::zero(),
 					vec![],
+					false,
 					None,
 				)
 				.unwrap()
@@ -4900,6 +4968,7 @@ mod tests {
 					&mut storage_meter,
 					U256::zero(),
 					vec![],
+					false,
 					None,
 				)
 				.unwrap()
@@ -4944,6 +5013,7 @@ mod tests {
 					&mut storage_meter,
 					U256::zero(),
 					vec![],
+					false,
 					None,
 				)
 				.unwrap()
@@ -4999,6 +5069,7 @@ mod tests {
 					&mut storage_meter,
 					U256::zero(),
 					vec![0],
+					false,
 					None,
 				),
 				Ok(_)
diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs
index b55854e2eec583ef954ac8a9df7c796cc69f4c4d..1dee1da03bc4791f90efb8e38ae23fa815f45242 100644
--- a/substrate/frame/revive/src/lib.rs
+++ b/substrate/frame/revive/src/lib.rs
@@ -41,13 +41,13 @@ pub mod test_utils;
 pub mod weights;
 
 use crate::{
-	evm::{runtime::GAS_PRICE, TransactionLegacyUnsigned},
+	evm::{runtime::GAS_PRICE, GenericTransaction},
 	exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack},
 	gas::GasMeter,
 	storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager},
 	wasm::{CodeInfo, RuntimeCosts, WasmBlob},
 };
-use alloc::boxed::Box;
+use alloc::{boxed::Box, format, vec};
 use codec::{Codec, Decode, Encode};
 use environmental::*;
 use frame_support::{
@@ -74,7 +74,7 @@ use pallet_transaction_payment::OnChargeTransaction;
 use scale_info::TypeInfo;
 use sp_core::{H160, H256, U256};
 use sp_runtime::{
-	traits::{BadOrigin, Convert, Dispatchable, Saturating, Zero},
+	traits::{BadOrigin, Bounded, Convert, Dispatchable, Saturating, Zero},
 	DispatchError,
 };
 
@@ -823,7 +823,7 @@ pub mod pallet {
 				dest,
 				value,
 				gas_limit,
-				storage_deposit_limit,
+				DepositLimit::Balance(storage_deposit_limit),
 				data,
 				DebugInfo::Skip,
 				CollectEvents::Skip,
@@ -859,7 +859,7 @@ pub mod pallet {
 				origin,
 				value,
 				gas_limit,
-				storage_deposit_limit,
+				DepositLimit::Balance(storage_deposit_limit),
 				Code::Existing(code_hash),
 				data,
 				salt,
@@ -925,7 +925,7 @@ pub mod pallet {
 				origin,
 				value,
 				gas_limit,
-				storage_deposit_limit,
+				DepositLimit::Balance(storage_deposit_limit),
 				Code::Upload(code),
 				data,
 				salt,
@@ -1083,7 +1083,7 @@ fn dispatch_result<R>(
 
 impl<T: Config> Pallet<T>
 where
-	BalanceOf<T>: Into<U256> + TryFrom<U256>,
+	BalanceOf<T>: Into<U256> + TryFrom<U256> + Bounded,
 	MomentOf<T>: Into<U256>,
 	T::Hash: frame_support::traits::IsType<H256>,
 {
@@ -1098,7 +1098,7 @@ where
 		dest: H160,
 		value: BalanceOf<T>,
 		gas_limit: Weight,
-		storage_deposit_limit: BalanceOf<T>,
+		storage_deposit_limit: DepositLimit<BalanceOf<T>>,
 		data: Vec<u8>,
 		debug: DebugInfo,
 		collect_events: CollectEvents,
@@ -1112,7 +1112,10 @@ where
 		};
 		let try_call = || {
 			let origin = Origin::from_runtime_origin(origin)?;
-			let mut storage_meter = StorageMeter::new(&origin, storage_deposit_limit, value)?;
+			let mut storage_meter = match storage_deposit_limit {
+				DepositLimit::Balance(limit) => StorageMeter::new(&origin, limit, value)?,
+				DepositLimit::Unchecked => StorageMeter::new_unchecked(BalanceOf::<T>::max_value()),
+			};
 			let result = ExecStack::<T, WasmBlob<T>>::run_call(
 				origin.clone(),
 				dest,
@@ -1120,9 +1123,14 @@ where
 				&mut storage_meter,
 				Self::convert_native_to_evm(value),
 				data,
+				storage_deposit_limit.is_unchecked(),
 				debug_message.as_mut(),
 			)?;
-			storage_deposit = storage_meter.try_into_deposit(&origin)?;
+			storage_deposit = storage_meter
+				.try_into_deposit(&origin, storage_deposit_limit.is_unchecked())
+				.inspect_err(|err| {
+					log::error!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}");
+				})?;
 			Ok(result)
 		};
 		let result = Self::run_guarded(try_call);
@@ -1151,7 +1159,7 @@ where
 		origin: OriginFor<T>,
 		value: BalanceOf<T>,
 		gas_limit: Weight,
-		mut storage_deposit_limit: BalanceOf<T>,
+		storage_deposit_limit: DepositLimit<BalanceOf<T>>,
 		code: Code,
 		data: Vec<u8>,
 		salt: Option<[u8; 32]>,
@@ -1162,13 +1170,24 @@ where
 		let mut storage_deposit = Default::default();
 		let mut debug_message =
 			if debug == DebugInfo::UnsafeDebug { Some(DebugBuffer::default()) } else { None };
+
+		let unchecked_deposit_limit = storage_deposit_limit.is_unchecked();
+		let mut storage_deposit_limit = match storage_deposit_limit {
+			DepositLimit::Balance(limit) => limit,
+			DepositLimit::Unchecked => BalanceOf::<T>::max_value(),
+		};
+
 		let try_instantiate = || {
 			let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?;
 			let (executable, upload_deposit) = match code {
 				Code::Upload(code) => {
 					let upload_account = T::UploadOrigin::ensure_origin(origin)?;
-					let (executable, upload_deposit) =
-						Self::try_upload_code(upload_account, code, storage_deposit_limit)?;
+					let (executable, upload_deposit) = Self::try_upload_code(
+						upload_account,
+						code,
+						storage_deposit_limit,
+						unchecked_deposit_limit,
+					)?;
 					storage_deposit_limit.saturating_reduce(upload_deposit);
 					(executable, upload_deposit)
 				},
@@ -1176,8 +1195,12 @@ where
 					(WasmBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()),
 			};
 			let instantiate_origin = Origin::from_account_id(instantiate_account.clone());
-			let mut storage_meter =
-				StorageMeter::new(&instantiate_origin, storage_deposit_limit, value)?;
+			let mut storage_meter = if unchecked_deposit_limit {
+				StorageMeter::new_unchecked(storage_deposit_limit)
+			} else {
+				StorageMeter::new(&instantiate_origin, storage_deposit_limit, value)?
+			};
+
 			let result = ExecStack::<T, WasmBlob<T>>::run_instantiate(
 				instantiate_account,
 				executable,
@@ -1186,10 +1209,11 @@ where
 				Self::convert_native_to_evm(value),
 				data,
 				salt.as_ref(),
+				unchecked_deposit_limit,
 				debug_message.as_mut(),
 			);
 			storage_deposit = storage_meter
-				.try_into_deposit(&instantiate_origin)?
+				.try_into_deposit(&instantiate_origin, unchecked_deposit_limit)?
 				.saturating_add(&StorageDeposit::Charge(upload_deposit));
 			result
 		};
@@ -1215,28 +1239,15 @@ where
 	///
 	/// # Parameters
 	///
-	/// - `origin`: The origin of the call.
-	/// - `dest`: The destination address of the call.
-	/// - `value`: The EVM value to transfer.
-	/// - `input`: The input data.
+	/// - `tx`: The Ethereum transaction to simulate.
 	/// - `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: U256,
-		input: Vec<u8>,
+		mut tx: GenericTransaction,
 		gas_limit: Weight,
-		storage_deposit_limit: BalanceOf<T>,
 		utx_encoded_size: impl Fn(Call<T>) -> u32,
-		debug: DebugInfo,
-		collect_events: CollectEvents,
-	) -> EthContractResult<BalanceOf<T>>
+	) -> Result<EthTransactInfo<BalanceOf<T>>, EthTransactError>
 	where
 		T: pallet_transaction_payment::Config,
 		<T as frame_system::Config>::RuntimeCall:
@@ -1247,26 +1258,58 @@ where
 		T::Nonce: Into<U256>,
 		T::Hash: frame_support::traits::IsType<H256>,
 	{
-		log::debug!(target: LOG_TARGET, "bare_eth_transact: dest: {dest:?} value: {value:?}
-		 gas_limit: {gas_limit:?} storage_deposit_limit: {storage_deposit_limit:?}");
+		log::debug!(target: LOG_TARGET, "bare_eth_transact: tx: {tx:?} gas_limit: {gas_limit:?}");
+
+		let from = tx.from.unwrap_or_default();
+		let origin = T::AddressMapper::to_account_id(&from);
 
-		// Get the nonce to encode in the tx.
-		let nonce: T::Nonce = <System<T>>::account_nonce(&origin);
+		let storage_deposit_limit = if tx.gas.is_some() {
+			DepositLimit::Balance(BalanceOf::<T>::max_value())
+		} else {
+			DepositLimit::Unchecked
+		};
+
+		// TODO remove once we have revisited how we encode the gas limit.
+		if tx.nonce.is_none() {
+			tx.nonce = Some(<System<T>>::account_nonce(&origin).into());
+		}
+		if tx.gas_price.is_none() {
+			tx.gas_price = Some(GAS_PRICE.into());
+		}
+		if tx.chain_id.is_none() {
+			tx.chain_id = Some(T::ChainId::get().into());
+		}
 
 		// Convert the value to the native balance type.
-		let native_value = match Self::convert_evm_to_native(value) {
+		let evm_value = tx.value.unwrap_or_default();
+		let native_value = match Self::convert_evm_to_native(evm_value) {
 			Ok(v) => v,
-			Err(err) =>
-				return EthContractResult {
-					gas_required: Default::default(),
-					storage_deposit: Default::default(),
-					fee: Default::default(),
-					result: Err(err.into()),
-				},
+			Err(_) => return Err(EthTransactError::Message("Failed to convert value".into())),
+		};
+
+		let input = tx.input.clone().unwrap_or_default().0;
+		let debug = DebugInfo::Skip;
+		let collect_events = CollectEvents::Skip;
+
+		let extract_error = |err| {
+			if err == Error::<T>::TransferFailed.into() ||
+				err == Error::<T>::StorageDepositNotEnoughFunds.into() ||
+				err == Error::<T>::StorageDepositLimitExhausted.into()
+			{
+				let balance = Self::evm_balance(&from);
+				return Err(EthTransactError::Message(
+						format!("insufficient funds for gas * price + value: address {from:?} have {balance} (supplied gas {})",
+							tx.gas.unwrap_or_default()))
+					);
+			}
+
+			return Err(EthTransactError::Message(format!(
+				"Failed to instantiate contract: {err:?}"
+			)));
 		};
 
 		// Dry run the call
-		let (mut result, dispatch_info) = match dest {
+		let (mut result, dispatch_info) = match tx.to {
 			// A contract call.
 			Some(dest) => {
 				// Dry run the call.
@@ -1281,11 +1324,24 @@ where
 					collect_events,
 				);
 
-				let result = EthContractResult {
+				let data = match result.result {
+					Ok(return_value) => {
+						if return_value.did_revert() {
+							return Err(EthTransactError::Data(return_value.data));
+						}
+						return_value.data
+					},
+					Err(err) => {
+						log::debug!(target: LOG_TARGET, "Failed to execute call: {err:?}");
+						return extract_error(err)
+					},
+				};
+
+				let result = EthTransactInfo {
 					gas_required: result.gas_required,
 					storage_deposit: result.storage_deposit.charge_or_zero(),
-					result: result.result,
-					fee: Default::default(),
+					data,
+					eth_gas: Default::default(),
 				};
 				// Get the dispatch info of the call.
 				let dispatch_call: <T as Config>::RuntimeCall = crate::Call::<T>::call {
@@ -1326,11 +1382,24 @@ where
 					collect_events,
 				);
 
-				let result = EthContractResult {
+				let returned_data = match result.result {
+					Ok(return_value) => {
+						if return_value.result.did_revert() {
+							return Err(EthTransactError::Data(return_value.result.data));
+						}
+						return_value.result.data
+					},
+					Err(err) => {
+						log::debug!(target: LOG_TARGET, "Failed to instantiate: {err:?}");
+						return extract_error(err)
+					},
+				};
+
+				let result = EthTransactInfo {
 					gas_required: result.gas_required,
 					storage_deposit: result.storage_deposit.charge_or_zero(),
-					result: result.result.map(|v| v.result),
-					fee: Default::default(),
+					data: returned_data,
+					eth_gas: Default::default(),
 				};
 
 				// Get the dispatch info of the call.
@@ -1348,23 +1417,18 @@ where
 			},
 		};
 
-		let mut tx = TransactionLegacyUnsigned {
-			value,
-			input: input.into(),
-			nonce: nonce.into(),
-			chain_id: Some(T::ChainId::get().into()),
-			gas_price: GAS_PRICE.into(),
-			to: dest,
-			..Default::default()
-		};
-
 		// The transaction fees depend on the extrinsic's length, which in turn is influenced by
 		// the encoded length of the gas limit specified in the transaction (tx.gas).
 		// We iteratively compute the fee by adjusting tx.gas until the fee stabilizes.
 		// with a maximum of 3 iterations to avoid an infinite loop.
 		for _ in 0..3 {
+			let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else {
+				log::debug!(target: LOG_TARGET, "Failed to convert to unsigned");
+				return Err(EthTransactError::Message("Invalid transaction".into()));
+			};
+
 			let eth_dispatch_call = crate::Call::<T>::eth_transact {
-				payload: tx.dummy_signed_payload(),
+				payload: unsigned_tx.dummy_signed_payload(),
 				gas_limit: result.gas_required,
 				storage_deposit_limit: result.storage_deposit,
 			};
@@ -1375,17 +1439,18 @@ where
 				0u32.into(),
 			)
 			.into();
+			let eth_gas: U256 = (fee / GAS_PRICE.into()).into();
 
-			if fee == result.fee {
-				log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} fee: {fee:?}");
+			if eth_gas == result.eth_gas {
+				log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}");
 				break;
 			}
-			result.fee = fee;
-			tx.gas = (fee / GAS_PRICE.into()).into();
-			log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {:?}", tx.gas);
+			result.eth_gas = eth_gas;
+			tx.gas = Some(eth_gas.into());
+			log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {eth_gas:?}");
 		}
 
-		result
+		Ok(result)
 	}
 
 	/// Get the balance with EVM decimals of the given `address`.
@@ -1403,7 +1468,7 @@ where
 		storage_deposit_limit: BalanceOf<T>,
 	) -> CodeUploadResult<BalanceOf<T>> {
 		let origin = T::UploadOrigin::ensure_origin(origin)?;
-		let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit)?;
+		let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit, false)?;
 		Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit })
 	}
 
@@ -1421,9 +1486,10 @@ where
 		origin: T::AccountId,
 		code: Vec<u8>,
 		storage_deposit_limit: BalanceOf<T>,
+		skip_transfer: bool,
 	) -> Result<(WasmBlob<T>, BalanceOf<T>), DispatchError> {
 		let mut module = WasmBlob::from_code(code, origin)?;
-		let deposit = module.store_code()?;
+		let deposit = module.store_code(skip_transfer)?;
 		ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
 		Ok((module, deposit))
 	}
@@ -1527,14 +1593,7 @@ sp_api::decl_runtime_apis! {
 		/// Perform an Ethereum call.
 		///
 		/// See [`crate::Pallet::bare_eth_transact`]
-		fn eth_transact(
-			origin: H160,
-			dest: Option<H160>,
-			value: U256,
-			input: Vec<u8>,
-			gas_limit: Option<Weight>,
-			storage_deposit_limit: Option<Balance>,
-		) -> EthContractResult<Balance>;
+		fn eth_transact(tx: GenericTransaction) -> Result<EthTransactInfo<Balance>, EthTransactError>;
 
 		/// 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 024b1f3448e12802f22a08ca66a72c971e496178..a7127f812b4b9726c4b7b191956c4fb1975b8a83 100644
--- a/substrate/frame/revive/src/primitives.rs
+++ b/substrate/frame/revive/src/primitives.rs
@@ -17,8 +17,8 @@
 
 //! A crate that hosts a common definitions that are relevant for the pallet-revive.
 
-use crate::H160;
-use alloc::vec::Vec;
+use crate::{H160, U256};
+use alloc::{string::String, vec::Vec};
 use codec::{Decode, Encode, MaxEncodedLen};
 use frame_support::weights::Weight;
 use pallet_revive_uapi::ReturnFlags;
@@ -28,6 +28,30 @@ use sp_runtime::{
 	DispatchError, RuntimeDebug,
 };
 
+#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
+pub enum DepositLimit<Balance> {
+	/// Allows bypassing all balance transfer checks.
+	Unchecked,
+
+	/// Specifies a maximum allowable balance for a deposit.
+	Balance(Balance),
+}
+
+impl<T> DepositLimit<T> {
+	pub fn is_unchecked(&self) -> bool {
+		match self {
+			Self::Unchecked => true,
+			_ => false,
+		}
+	}
+}
+
+impl<T> From<T> for DepositLimit<T> {
+	fn from(value: T) -> Self {
+		Self::Balance(value)
+	}
+}
+
 /// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and
 /// `ContractsApi::instantiate`.
 ///
@@ -84,15 +108,22 @@ pub struct ContractResult<R, Balance, EventRecord> {
 
 /// The result of the execution of a `eth_transact` call.
 #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
-pub struct EthContractResult<Balance, R = Result<ExecReturnValue, DispatchError>> {
-	/// The fee charged for the execution.
-	pub fee: Balance,
+pub struct EthTransactInfo<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: R,
+	/// The weight and deposit equivalent in EVM Gas.
+	pub eth_gas: U256,
+	/// The execution return value.
+	pub data: Vec<u8>,
+}
+
+/// Error type of a `eth_transact` call.
+#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
+pub enum EthTransactError {
+	Data(Vec<u8>),
+	Message(String),
 }
 
 /// Result type of a `bare_code_upload` call.
diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs
index 712010bc8257454defa0ad81f466fbddc5822a9a..6eddf048be98fc0470650fdb1c90197ea03db91e 100644
--- a/substrate/frame/revive/src/storage/meter.rs
+++ b/substrate/frame/revive/src/storage/meter.rs
@@ -373,24 +373,36 @@ where
 		}
 	}
 
+	/// Create new storage meter without checking the limit.
+	pub fn new_unchecked(limit: BalanceOf<T>) -> Self {
+		return Self { limit, ..Default::default() }
+	}
+
 	/// The total amount of deposit that should change hands as result of the execution
 	/// that this meter was passed into. This will also perform all the charges accumulated
 	/// in the whole contract stack.
 	///
 	/// This drops the root meter in order to make sure it is only called when the whole
 	/// execution did finish.
-	pub fn try_into_deposit(self, origin: &Origin<T>) -> Result<DepositOf<T>, DispatchError> {
-		// Only refund or charge deposit if the origin is not root.
-		let origin = match origin {
-			Origin::Root => return Ok(Deposit::Charge(Zero::zero())),
-			Origin::Signed(o) => o,
-		};
-		for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) {
-			E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
-		}
-		for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) {
-			E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
+	pub fn try_into_deposit(
+		self,
+		origin: &Origin<T>,
+		skip_transfer: bool,
+	) -> Result<DepositOf<T>, DispatchError> {
+		if !skip_transfer {
+			// Only refund or charge deposit if the origin is not root.
+			let origin = match origin {
+				Origin::Root => return Ok(Deposit::Charge(Zero::zero())),
+				Origin::Signed(o) => o,
+			};
+			for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) {
+				E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
+			}
+			for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) {
+				E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
+			}
 		}
+
 		Ok(self.total_deposit)
 	}
 }
@@ -425,13 +437,18 @@ impl<T: Config, E: Ext<T>> RawMeter<T, E, Nested> {
 		contract: &T::AccountId,
 		contract_info: &mut ContractInfo<T>,
 		code_info: &CodeInfo<T>,
+		skip_transfer: bool,
 	) -> Result<(), DispatchError> {
 		debug_assert!(matches!(self.contract_state(), ContractState::Alive));
 
 		// We need to make sure that the contract's account exists.
 		let ed = Pallet::<T>::min_balance();
 		self.total_deposit = Deposit::Charge(ed);
-		T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?;
+		if skip_transfer {
+			T::Currency::set_balance(contract, ed);
+		} else {
+			T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?;
+		}
 
 		// A consumer is added at account creation and removed it on termination, otherwise the
 		// runtime could remove the account. As long as a contract exists its account must exist.
@@ -479,6 +496,7 @@ impl<T: Config, E: Ext<T>> RawMeter<T, E, Nested> {
 		}
 		if let Deposit::Charge(amount) = total_deposit {
 			if amount > self.limit {
+				log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit);
 				return Err(<Error<T>>::StorageDepositLimitExhausted.into())
 			}
 		}
@@ -811,7 +829,10 @@ mod tests {
 			nested0.enforce_limit(Some(&mut nested0_info)).unwrap();
 			meter.absorb(nested0, &BOB, Some(&mut nested0_info));
 
-			assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
+			assert_eq!(
+				meter.try_into_deposit(&test_case.origin, false).unwrap(),
+				test_case.deposit
+			);
 
 			assert_eq!(nested0_info.extra_deposit(), 112);
 			assert_eq!(nested1_info.extra_deposit(), 110);
@@ -882,7 +903,10 @@ mod tests {
 			nested0.absorb(nested1, &CHARLIE, None);
 
 			meter.absorb(nested0, &BOB, None);
-			assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
+			assert_eq!(
+				meter.try_into_deposit(&test_case.origin, false).unwrap(),
+				test_case.deposit
+			);
 			assert_eq!(TestExtTestValue::get(), test_case.expected)
 		}
 	}
diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs
index e64f588944326720af153920c86baa1e59961790..8ba5e7384070289617c667c8e95b0608a2846a6d 100644
--- a/substrate/frame/revive/src/test_utils/builder.rs
+++ b/substrate/frame/revive/src/test_utils/builder.rs
@@ -18,7 +18,8 @@
 use super::{deposit_limit, GAS_LIMIT};
 use crate::{
 	address::AddressMapper, AccountIdOf, BalanceOf, Code, CollectEvents, Config, ContractResult,
-	DebugInfo, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight,
+	DebugInfo, DepositLimit, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor,
+	Pallet, Weight,
 };
 use frame_support::pallet_prelude::DispatchResultWithPostInfo;
 use paste::paste;
@@ -133,7 +134,7 @@ builder!(
 		origin: OriginFor<T>,
 		value: BalanceOf<T>,
 		gas_limit: Weight,
-		storage_deposit_limit: BalanceOf<T>,
+		storage_deposit_limit: DepositLimit<BalanceOf<T>>,
 		code: Code,
 		data: Vec<u8>,
 		salt: Option<[u8; 32]>,
@@ -159,7 +160,7 @@ builder!(
 			origin,
 			value: 0u32.into(),
 			gas_limit: GAS_LIMIT,
-			storage_deposit_limit: deposit_limit::<T>(),
+			storage_deposit_limit: DepositLimit::Balance(deposit_limit::<T>()),
 			code,
 			data: vec![],
 			salt: Some([0; 32]),
@@ -198,7 +199,7 @@ builder!(
 		dest: H160,
 		value: BalanceOf<T>,
 		gas_limit: Weight,
-		storage_deposit_limit: BalanceOf<T>,
+		storage_deposit_limit: DepositLimit<BalanceOf<T>>,
 		data: Vec<u8>,
 		debug: DebugInfo,
 		collect_events: CollectEvents,
@@ -216,7 +217,7 @@ builder!(
 			dest,
 			value: 0u32.into(),
 			gas_limit: GAS_LIMIT,
-			storage_deposit_limit: deposit_limit::<T>(),
+			storage_deposit_limit: DepositLimit::Balance(deposit_limit::<T>()),
 			data: vec![],
 			debug: DebugInfo::UnsafeDebug,
 			collect_events: CollectEvents::Skip,
diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs
index 34afe8aabfe6f7aee9f3aed510687d2e36a2a8f0..1df300f031a763424418221e03e619884914382f 100644
--- a/substrate/frame/revive/src/tests.rs
+++ b/substrate/frame/revive/src/tests.rs
@@ -1249,7 +1249,7 @@ fn transfer_expendable_cannot_kill_account() {
 			test_utils::contract_info_storage_deposit(&addr)
 		);
 
-		// Some ot the total balance is held, so it can't be transferred.
+		// Some or the total balance is held, so it can't be transferred.
 		assert_err!(
 			<<Test as Config>::Currency as Mutate<AccountId32>>::transfer(
 				&account,
@@ -2290,7 +2290,7 @@ fn gas_estimation_for_subcalls() {
 				// Make the same call using the estimated gas. Should succeed.
 				let result = builder::bare_call(addr_caller)
 					.gas_limit(result_orig.gas_required)
-					.storage_deposit_limit(result_orig.storage_deposit.charge_or_zero())
+					.storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into())
 					.data(input.clone())
 					.build();
 				assert_ok!(&result.result);
@@ -2298,7 +2298,7 @@ fn gas_estimation_for_subcalls() {
 				// Check that it fails with too little ref_time
 				let result = builder::bare_call(addr_caller)
 					.gas_limit(result_orig.gas_required.sub_ref_time(1))
-					.storage_deposit_limit(result_orig.storage_deposit.charge_or_zero())
+					.storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into())
 					.data(input.clone())
 					.build();
 				assert_err!(result.result, error);
@@ -2306,7 +2306,7 @@ fn gas_estimation_for_subcalls() {
 				// Check that it fails with too little proof_size
 				let result = builder::bare_call(addr_caller)
 					.gas_limit(result_orig.gas_required.sub_proof_size(1))
-					.storage_deposit_limit(result_orig.storage_deposit.charge_or_zero())
+					.storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into())
 					.data(input.clone())
 					.build();
 				assert_err!(result.result, error);
@@ -3592,7 +3592,7 @@ fn deposit_limit_in_nested_instantiate() {
 		// Set enough deposit limit for the child instantiate. This should succeed.
 		let result = builder::bare_call(addr_caller)
 			.origin(RuntimeOrigin::signed(BOB))
-			.storage_deposit_limit(callee_info_len + 2 + ED + 4 + 2)
+			.storage_deposit_limit((callee_info_len + 2 + ED + 4 + 2).into())
 			.data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 3 + 2)).encode())
 			.build();
 
@@ -3879,7 +3879,7 @@ fn locking_delegate_dependency_works() {
 		// Locking a dependency with a storage limit too low should fail.
 		assert_err!(
 			builder::bare_call(addr_caller)
-				.storage_deposit_limit(dependency_deposit - 1)
+				.storage_deposit_limit((dependency_deposit - 1).into())
 				.data((1u32, hash2addr(&callee_hashes[0]), callee_hashes[0]).encode())
 				.build()
 				.result,
diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs
index 7c4fbba71f656616ab82bdb860dd22f54c6c0db9..c9e19e52ace1372aecf6129c22f03b11f845f66c 100644
--- a/substrate/frame/revive/src/tests/test_debug.rs
+++ b/substrate/frame/revive/src/tests/test_debug.rs
@@ -21,6 +21,7 @@ use crate::{
 	debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing},
 	primitives::ExecReturnValue,
 	test_utils::*,
+	DepositLimit,
 };
 use frame_support::traits::Currency;
 use pretty_assertions::assert_eq;
@@ -114,7 +115,7 @@ fn debugging_works() {
 			RuntimeOrigin::signed(ALICE),
 			0,
 			GAS_LIMIT,
-			deposit_limit::<Test>(),
+			DepositLimit::Balance(deposit_limit::<Test>()),
 			Code::Upload(wasm),
 			vec![],
 			Some([0u8; 32]),
@@ -198,7 +199,7 @@ fn call_interception_works() {
 			RuntimeOrigin::signed(ALICE),
 			0,
 			GAS_LIMIT,
-			deposit_limit::<Test>(),
+			deposit_limit::<Test>().into(),
 			Code::Upload(wasm),
 			vec![],
 			// some salt to ensure that the address of this contract is unique among all tests
diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs
index d87ec711228684d38cab02669bcdb647a1d344fe..54fb02c866e1045e33958d472ceea8b5e77405bd 100644
--- a/substrate/frame/revive/src/wasm/mod.rs
+++ b/substrate/frame/revive/src/wasm/mod.rs
@@ -183,7 +183,7 @@ where
 	}
 
 	/// Puts the module blob into storage, and returns the deposit collected for the storage.
-	pub fn store_code(&mut self) -> Result<BalanceOf<T>, Error<T>> {
+	pub fn store_code(&mut self, skip_transfer: bool) -> Result<BalanceOf<T>, Error<T>> {
 		let code_hash = *self.code_hash();
 		<CodeInfoOf<T>>::mutate(code_hash, |stored_code_info| {
 			match stored_code_info {
@@ -195,15 +195,16 @@ where
 				// the `owner` is always the origin of the current transaction.
 				None => {
 					let deposit = self.code_info.deposit;
-					T::Currency::hold(
+
+					if !skip_transfer {
+						T::Currency::hold(
 						&HoldReason::CodeUploadDepositReserve.into(),
 						&self.code_info.owner,
 						deposit,
-					)
-					.map_err(|err| {
-						log::debug!(target: LOG_TARGET, "failed to store code for owner: {:?}: {err:?}", self.code_info.owner);
+					) .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);