diff --git a/crates/eips/Cargo.toml b/crates/eips/Cargo.toml
index 6c55582a7d8..900af7c4762 100644
--- a/crates/eips/Cargo.toml
+++ b/crates/eips/Cargo.toml
@@ -29,6 +29,7 @@ alloy-rlp = { workspace = true, features = ["derive"] }
# serde
alloy-serde = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
+serde_with = { workspace = true, optional = true }
# kzg
c-kzg = { workspace = true, optional = true }
@@ -64,7 +65,7 @@ rand.workspace = true
default = ["std", "kzg-sidecar"]
std = ["alloy-primitives/std", "alloy-rlp/std",
"serde?/std", "c-kzg?/std", "once_cell?/std"]
-serde = ["dep:alloy-serde", "dep:serde", "alloy-primitives/serde",
+serde = ["dep:alloy-serde", "dep:serde", "dep:serde_with", "alloy-primitives/serde",
"c-kzg?/serde", "alloy-eip2930/serde", "alloy-eip7702/serde"]
serde-bincode-compat = ["alloy-eip7702/serde-bincode-compat"]
kzg = ["kzg-sidecar", "sha2", "dep:c-kzg", "dep:once_cell"]
diff --git a/crates/eips/src/eip6110.rs b/crates/eips/src/eip6110.rs
index 7dee0fa09c4..3f25c19489e 100644
--- a/crates/eips/src/eip6110.rs
+++ b/crates/eips/src/eip6110.rs
@@ -1,8 +1,10 @@
-//! Contains Deposit request constants, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md).
+//! Contains Deposit request types, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md).
//!
//! See also [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110): Supply validator deposits on chain
-use alloy_primitives::{address, Address};
+use alloy_primitives::{address, Address, FixedBytes, B256};
+use alloy_rlp::{RlpDecodable, RlpEncodable};
+use serde_with::{serde_as, DisplayFromStr};
/// Mainnet deposit contract address.
pub const MAINNET_DEPOSIT_CONTRACT_ADDRESS: Address =
@@ -10,3 +12,127 @@ pub const MAINNET_DEPOSIT_CONTRACT_ADDRESS: Address =
/// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for deposit requests.
pub const DEPOSIT_REQUEST_TYPE: u8 = 0x00;
+
+/// This structure maps onto the deposit object from [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110).
+#[cfg_attr(feature = "serde", serde_as)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable, Default)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
+#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
+pub struct DepositRequest {
+ /// Validator public key
+ pub pubkey: FixedBytes<48>,
+ /// Withdrawal credentials
+ pub withdrawal_credentials: B256,
+ /// Amount of ether deposited in gwei
+ #[serde_as(as = "DisplayFromStr")]
+ pub amount: u64,
+ /// Deposit signature
+ pub signature: FixedBytes<96>,
+ /// Deposit index
+ #[serde_as(as = "DisplayFromStr")]
+ pub index: u64,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use alloy_primitives::hex;
+ use alloy_rlp::{Decodable, Encodable};
+
+ #[test]
+ fn test_encode_decode_request_roundtrip() {
+ // Define multiple test cases as tuples containing the test data
+ let test_cases = vec![
+ (
+ // https://etherscan.io/tx/0xab9e0b47767c6172f49f691e5fd96cb257c17f2d39cf64742d71e5435308403c#eventlog
+ FixedBytes::<48>::from(hex!("8E01A8F21BDC38991ADA53CA86D6C78D874675A450A38431CC6AA0F12D5661E344784C56C8A211F7025224D1303EE801")),
+ B256::from(hex!("010000000000000000000000AF6DF504F08DDF582D604D2F0A593BC153C25DBD")),
+ 0x0040597307000000u64,
+ FixedBytes::<96>::from(hex!("B65F3DB79405544528D6D92040282F29171F4FF6E5ABB2D59F9EE1F1254ACED2A7000F87BC2684F543E913A7CC1007EA0E97289B349C553EECDF253CD3EF5814088BA3D4AC286F2634DAC3D026D9A01E4C166DC75E249D626A0F1C180DAB75CE")),
+ 0xB92E1A0000000000u64,
+ ),
+ // https://etherscan.io/tx/0x756a8aba9f8df9fba33519bc1ec1ad2251507f66ef65cb15eb0a80ddfd0bcbef#eventlog
+ (
+ FixedBytes::<48>::from(hex!("85BA6057EA5100DCB0B347D545BE0B688B2AD10A029C24A9D653F18BA3AC743B3CF8E022AE487AD8C5670D9C20D101D8")),
+ B256::from(hex!("010000000000000000000000E839A3E9EFB32C6A56AB7128E51056585275506C")),
+ 0x0040597307000000u64,
+ FixedBytes::<96>::from(hex!("8F685E17FC36B8DE5EF6E81523995139EF59280F7D25D0C422C7BDF573217F8127B2425B87E414443430B4EE05EE5ABE19EEDB9B239BA1354FBB8133C5B068FA7B278296856F7C7592F1AF9332762AB4389B9FC224D32E209077368AE3CED710")),
+ 0xDD2A1A0000000000u64,
+ ),
+ (
+ // https://etherscan.io/tx/0xa5759378809a22bf8072de41c155e176a74a08323e94e4167ee2692887e83859#eventlog
+ FixedBytes::<48>::from(hex!("A3151E4E6BE6A4002249331B60EF426F6CDE5C33B27C9F14FC6639E6888A10F54C4A44AEE7AB0690BF09A89BDC00237C")),
+ B256::from(hex!("01000000000000000000000006676E8584342CC8B6052CFDF381C3A281F00AC8")),
+ 0x0040597307000000u64,
+ FixedBytes::<96>::from(hex!("A3C85DF60DF11200166D49A055E96C4659D37AC630CDC6E5C3EE039478E5B558F50F390D306249CEC66ACAA09011B85300434FFB566FE599E3E1596162BC3BBCE7BEE9122DAEBF7D1F42124C0FF00BE6EE8B79E0F436044337148EB061E0B163")),
+ 0x85A6130000000000u64,
+ ),
+ (
+ // https://etherscan.io/tx/0x132bb7c90069d9699a84cdd041ddbe7a5cc42b2d26b604a8ac282aa5c17ed218#eventlog
+ FixedBytes::<48>::from(hex!("AAE673FEE94E4552CFEC432DCDDF46D1F613DD48E3DFB36179C973B73BCFDE5C463C5B719DD916DA8DA3981BFD0BAE29")),
+ B256::from(hex!("010000000000000000000000A8C62111E4652B07110A0FC81816303C42632F64")),
+ 0x0040597307000000u64,
+ FixedBytes::<96>::from(hex!("B6BA4C2CA28E46BA0DD8757EBDD52BC3609DA3BCF17BCCEEDD181630797F2A51CA2D8B4BFBA8639074276ABA6A4B7316106AB9F1642BAB0D9A8058211F366BBEB6E42CB1C56D17155A84B2C32F61F772C41749134665CBD7EC43122691527050")),
+ 0xC9A8130000000000u64,
+ ),
+ (
+ // https://etherscan.io/tx/0xc97358e047333933278b8ab08ec4cfff8f6cf4028e2c3d877a5a89bd9f7303c9#eventlog
+ FixedBytes::<48>::from(hex!("A3CF35BEC7827E666C591BE49336B19FDE0F6ADA5F7139CFEEB719F372B200EDAC11FE73EDE7BE5B4A17E43BF055C58A")),
+ B256::from(hex!("0100000000000000000000008B7F4F725AE240D9B28D8129D35E6580D1251852")),
+ 0x0040597307000000u64,
+ FixedBytes::<96>::from(hex!("B9844B35D0831E22DF5E8374FF6C405F98DED278E813EBF9F1A61CFC08F6019D99E3C4B975BFDE999DC2AAC8EA99C540112FFAB1557CE0ADD9D80E0F91EB2D370F3220DA04D96F6256A07288CEFDB2F27C2C3DDE26B49EBAF5801E99FDCA095D")),
+ 0x62301A0000000000u64,
+ ),
+ ];
+
+ // Iterate over each test case
+ for (pubkey, withdrawal_credentials, amount, signature, index) in test_cases {
+ let original_request =
+ DepositRequest { pubkey, withdrawal_credentials, amount, signature, index };
+
+ // Encode the request
+ let mut buf = Vec::new();
+ original_request.encode(&mut buf);
+
+ // Decode the request
+ let decoded_request =
+ DepositRequest::decode(&mut &buf[..]).expect("Failed to decode request");
+
+ // Ensure the encoded and then decoded request matches the original
+ assert_eq!(original_request, decoded_request);
+ }
+ }
+
+ #[test]
+ fn test_serde_deposit_request() {
+ // Sample JSON input representing a deposit request
+ let json_data = r#"{"pubkey":"0x8e01a8f21bdc38991ada53ca86d6c78d874675a450a38431cc6aa0f12d5661e344784c56c8a211f7025224d1303ee801","withdrawalCredentials":"0x010000000000000000000000af6df504f08ddf582d604d2f0a593bc153c25dbd","amount":"0x40597307000000","signature":"0xb65f3db79405544528d6d92040282f29171f4ff6e5abb2d59f9ee1f1254aced2a7000f87bc2684f543e913a7cc1007ea0e97289b349c553eecdf253cd3ef5814088ba3d4ac286f2634dac3d026d9a01e4c166dc75e249d626a0f1c180dab75ce","index":"0xb92e1a0000000000"}"#;
+
+ // Deserialize the JSON into a DepositRequest struct
+ let deposit_request: DepositRequest =
+ serde_json::from_str(json_data).expect("Failed to deserialize");
+
+ // Verify the deserialized content
+ assert_eq!(
+ deposit_request.pubkey,
+ FixedBytes::<48>::from(hex!("8E01A8F21BDC38991ADA53CA86D6C78D874675A450A38431CC6AA0F12D5661E344784C56C8A211F7025224D1303EE801"))
+ );
+ assert_eq!(
+ deposit_request.withdrawal_credentials,
+ B256::from(hex!("010000000000000000000000AF6DF504F08DDF582D604D2F0A593BC153C25DBD"))
+ );
+ assert_eq!(deposit_request.amount, 0x0040597307000000u64);
+ assert_eq!(
+ deposit_request.signature,
+ FixedBytes::<96>::from(hex!("B65F3DB79405544528D6D92040282F29171F4FF6E5ABB2D59F9EE1F1254ACED2A7000F87BC2684F543E913A7CC1007EA0E97289B349C553EECDF253CD3EF5814088BA3D4AC286F2634DAC3D026D9A01E4C166DC75E249D626A0F1C180DAB75CE"))
+ );
+ assert_eq!(deposit_request.index, 0xB92E1A0000000000u64);
+
+ // Serialize the struct back into JSON
+ let serialized_json = serde_json::to_string(&deposit_request).expect("Failed to serialize");
+
+ // Check if the serialized JSON matches the expected JSON structure
+ assert_eq!(serialized_json, json_data);
+ }
+}
diff --git a/crates/eips/src/eip7002.rs b/crates/eips/src/eip7002.rs
index b8dfef97e53..ce421602497 100644
--- a/crates/eips/src/eip7002.rs
+++ b/crates/eips/src/eip7002.rs
@@ -1,8 +1,10 @@
-//! Contains the system contract, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md).
+//! Contains the system contract and [WithdrawalRequest] types, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md).
//!
//! See also [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002): Execution layer triggerable withdrawals
-use alloy_primitives::{address, bytes, Address, Bytes};
+use alloy_primitives::{address, bytes, Address, Bytes, FixedBytes};
+use alloy_rlp::{RlpDecodable, RlpEncodable};
+use serde_with::{serde_as, DisplayFromStr};
/// The caller to be used when calling the EIP-7002 withdrawal requests contract at the end of the
/// block.
@@ -17,3 +19,107 @@ pub static WITHDRAWAL_REQUEST_PREDEPLOY_CODE: Bytes = bytes!(" 3373fffffffffff
/// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for withdrawal requests.
pub const WITHDRAWAL_REQUEST_TYPE: u8 = 0x01;
+
+/// Represents an execution layer triggerable withdrawal request.
+///
+/// See [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002).
+#[cfg_attr(feature = "serde", serde_as)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable, Default)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
+#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
+pub struct WithdrawalRequest {
+ /// Address of the source of the exit.
+ pub source_address: Address,
+ /// Validator public key.
+ pub validator_pubkey: FixedBytes<48>,
+ /// Amount of withdrawn ether in gwei.
+ #[serde_as(as = "DisplayFromStr")]
+ pub amount: u64,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use alloy_primitives::hex;
+ use alloy_rlp::{Decodable, Encodable};
+ use core::str::FromStr;
+
+ #[test]
+ fn test_encode_decode_request_roundtrip() {
+ // Define multiple test cases as tuples containing the test data.
+ //
+ // Examples are randomly generated using some random validators found on Etherscan.
+ let test_cases = vec![
+ (
+ Address::from_str("0xaE0E8770147AaA6828a0D6f642504663F10F7d1E").unwrap(),
+ FixedBytes::<48>::from(hex!("8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b")),
+ 10
+ ),
+ (
+ Address::from_str("0xf86f8D6A7d2AF439245c1145d88B04dAf2d7e509").unwrap(),
+ FixedBytes::<48>::from(hex!("a85d7a6aa90eedebe103b8d4d3dc86003aea8b6c8159d9d50f7685828bc97d211b2c512b1dcbb8d63b60a56c91dda8ea")),
+ 354
+ ),
+ (
+ Address::from_str("0xf86f8D6A7d2AF439245c1145d88B04dAf2d7e509").unwrap(),
+ FixedBytes::<48>::from(hex!("a77eec36b046fbbf088e9253aa8c6800863d882c56fc6fa04800bbed742820f1bc7eb837601322840a18bbe0d24893b2")),
+ 19
+ ),
+ (
+ Address::from_str("0xAFedF06777839D59eED3163cC3e0A5057b514399").unwrap(),
+ FixedBytes::<48>::from(hex!("a3ecb9359401bb22d00cefddf6f6879d14a2ee74d3325cc8cdff0796bd0b3b47c5f5b4d02e5a865d7b639eb8124286a5")),
+ 9
+ ),
+ ];
+
+ // Iterate over each test case
+ for (source_address, validator_pubkey, amount) in test_cases {
+ let original_request = WithdrawalRequest { source_address, validator_pubkey, amount };
+
+ // Encode the request
+ let mut buf = Vec::new();
+ original_request.encode(&mut buf);
+
+ // Decode the request
+ let decoded_request =
+ WithdrawalRequest::decode(&mut &buf[..]).expect("Failed to decode request");
+
+ // Ensure the encoded and then decoded request matches the original
+ assert_eq!(original_request, decoded_request);
+ }
+ }
+
+ #[test]
+ fn test_serde_withdrawal_request() {
+ // Sample JSON input representing a withdrawal request
+ let json_data = r#"{
+ "sourceAddress":"0xAE0E8770147AaA6828a0D6f642504663F10F7d1E",
+ "validatorPubkey":"0x8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b",
+ "amount":"0x1"
+ }"#;
+
+ // Deserialize the JSON into a WithdrawalRequest struct
+ let withdrawal_request: WithdrawalRequest =
+ serde_json::from_str(json_data).expect("Failed to deserialize");
+
+ // Verify the deserialized content
+ assert_eq!(
+ withdrawal_request.source_address,
+ Address::from_str("0xAE0E8770147AaA6828a0D6f642504663F10F7d1E").unwrap()
+ );
+ assert_eq!(
+ withdrawal_request.validator_pubkey,
+ FixedBytes::<48>::from(hex!("8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b"))
+ );
+ assert_eq!(withdrawal_request.amount, 1);
+
+ // Serialize the struct back into JSON
+ let serialized_json =
+ serde_json::to_string(&withdrawal_request).expect("Failed to serialize");
+
+ // Check if the serialized JSON matches the expected JSON structure
+ let expected_json = r#"{"sourceAddress":"0xae0e8770147aaa6828a0d6f642504663f10f7d1e","validatorPubkey":"0x8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b","amount":"0x1"}"#;
+ assert_eq!(serialized_json, expected_json);
+ }
+}
diff --git a/crates/eips/src/eip7251.rs b/crates/eips/src/eip7251.rs
index ead24920a23..c786d0c3ae3 100644
--- a/crates/eips/src/eip7251.rs
+++ b/crates/eips/src/eip7251.rs
@@ -1,8 +1,9 @@
-//! Contains consolidation code, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md).
+//! Contains consolidation types, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md).
//!
//! See also [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251): Increase the MAX_EFFECTIVE_BALANCE
-use alloy_primitives::{address, bytes, Address, Bytes};
+use alloy_primitives::{address, bytes, Address, Bytes, FixedBytes};
+use alloy_rlp::{RlpDecodable, RlpEncodable};
/// The address for the EIP-7251 consolidation requests contract:
/// `0x00b42dbF2194e931E80326D950320f7d9Dbeac02`
@@ -14,3 +15,82 @@ pub static CONSOLIDATION_REQUEST_PREDEPLOY_CODE: Bytes = bytes!("3373fffffffffff
/// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for consolidation requests.
pub const CONSOLIDATION_REQUEST_TYPE: u8 = 0x02;
+
+/// This structure maps onto the consolidation request object from [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251).
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable, Default)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
+#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
+pub struct ConsolidationRequest {
+ /// Source address
+ pub source_address: Address,
+ /// Source public key
+ pub source_pubkey: FixedBytes<48>,
+ /// Target public key
+ pub target_pubkey: FixedBytes<48>,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use alloy_primitives::hex;
+ use alloy_rlp::{Decodable, Encodable};
+ use core::str::FromStr;
+
+ #[test]
+ fn test_encode_decode_consolidation_request_roundtrip() {
+ // Generate and test multiple cases in a single pass using an iterator.
+ (0..4).for_each(|_| {
+ let original_request = ConsolidationRequest {
+ source_address: Address::random(),
+ source_pubkey: FixedBytes::<48>::random(),
+ target_pubkey: FixedBytes::<48>::random(),
+ };
+
+ // Encode the request
+ let mut buf = Vec::new();
+ original_request.encode(&mut buf);
+
+ // Decode the request and assert equality
+ let decoded_request =
+ ConsolidationRequest::decode(&mut &buf[..]).expect("Failed to decode request");
+ assert_eq!(original_request, decoded_request);
+ });
+ }
+
+ #[test]
+ fn test_serde_consolidation_request() {
+ // Sample JSON input representing a consolidation request
+ let json_data = r#"{
+ "sourceAddress":"0x007eABCA654E67103dF02f49EbdC5f6Cd9387a07",
+ "sourcePubkey":"0xb13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7",
+ "targetPubkey":"0xd0e5be6b709f2dc02a49f6e37e0d03b7d832b79b0db1c8bbfd5b81b8e57b79a1282fb99a671b4629a0e0bfffa7cf6d4f"
+ }"#;
+
+ // Deserialize the JSON into a ConsolidationRequest struct
+ let consolidation_request: ConsolidationRequest =
+ serde_json::from_str(json_data).expect("Failed to deserialize");
+
+ // Verify the deserialized content
+ assert_eq!(
+ consolidation_request.source_address,
+ Address::from_str("0x007eABCA654E67103dF02f49EbdC5f6Cd9387a07").unwrap()
+ );
+ assert_eq!(
+ consolidation_request.source_pubkey,
+ FixedBytes::<48>::from(hex!("b13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7"))
+ );
+ assert_eq!(
+ consolidation_request.target_pubkey,
+ FixedBytes::<48>::from(hex!("d0e5be6b709f2dc02a49f6e37e0d03b7d832b79b0db1c8bbfd5b81b8e57b79a1282fb99a671b4629a0e0bfffa7cf6d4f"))
+ );
+
+ // Serialize the struct back into JSON
+ let serialized_json =
+ serde_json::to_string(&consolidation_request).expect("Failed to serialize");
+
+ // Check if the serialized JSON matches the expected JSON structure
+ let expected_json = r#"{"sourceAddress":"0x007eabca654e67103df02f49ebdc5f6cd9387a07","sourcePubkey":"0xb13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7","targetPubkey":"0xd0e5be6b709f2dc02a49f6e37e0d03b7d832b79b0db1c8bbfd5b81b8e57b79a1282fb99a671b4629a0e0bfffa7cf6d4f"}"#;
+ assert_eq!(serialized_json, expected_json);
+ }
+}
diff --git a/crates/rpc-types-beacon/Cargo.toml b/crates/rpc-types-beacon/Cargo.toml
index 85eec6b6f02..9e9c60549f6 100644
--- a/crates/rpc-types-beacon/Cargo.toml
+++ b/crates/rpc-types-beacon/Cargo.toml
@@ -22,7 +22,6 @@ workspace = true
alloy-eips = { workspace = true, features = ["serde"] }
alloy-rpc-types-engine = { workspace = true, features = ["serde"] }
alloy-primitives.workspace = true
-alloy-serde.workspace = true
# ssz
ethereum_ssz_derive = { workspace = true, optional = true }
diff --git a/crates/rpc-types-beacon/src/examples/relay_builder_block_validation_request_v4.json b/crates/rpc-types-beacon/src/examples/relay_builder_block_validation_request_v4.json
new file mode 100644
index 00000000000..e61d8277280
--- /dev/null
+++ b/crates/rpc-types-beacon/src/examples/relay_builder_block_validation_request_v4.json
@@ -0,0 +1,164 @@
+{
+ "message": {
+ "slot": "6",
+ "parent_hash": "0x139b559e427d726a8cf7eda9c11a2586ed827a7619ffaeed3033845dc06b88e8",
+ "block_hash": "0x2982946c9fb44951fd9f65f73446a49d4fd6e0140b3f3ea857ff50eac7be69d7",
+ "builder_pubkey": "0xa1885d66bef164889a2e35845c3b626545d7b0e513efe335e97c3a45e534013fa3bc38c3b7e6143695aecc4872ac52c4",
+ "proposer_pubkey": "0x903e2989e7442ee0a8958d020507a8bd985d3974f5e8273093be00db3935f0500e141b252bd09e3728892c7a8443863c",
+ "proposer_fee_recipient": "0x690b9a9e9aa1c9db991c7721a92d351db4fac990",
+ "gas_limit": "30000000",
+ "gas_used": "42000",
+ "value": "999990575298322000"
+ },
+ "execution_payload": {
+ "parent_hash": "0x139b559e427d726a8cf7eda9c11a2586ed827a7619ffaeed3033845dc06b88e8",
+ "fee_recipient": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
+ "state_root": "0x0f1109a777d62875f91b9457eb41122e27cf0d5eb478e475c46e57c6ee53ebcf",
+ "receipts_root": "0x75308898d571eafb5cd8cde8278bf5b3d13c5f6ec074926de3bb895b519264e1",
+ "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "prev_randao": "0x790a18ca85bf4a2e1f14679abda509fcdda1e322b6a2adbcabee1727793960d7",
+ "block_number": "6",
+ "gas_limit": "30000000",
+ "gas_used": "42000",
+ "timestamp": "1733949274",
+ "extra_data": "0xe29aa1f09fa496",
+ "base_fee_per_gas": "448795319",
+ "block_hash": "0x2982946c9fb44951fd9f65f73446a49d4fd6e0140b3f3ea857ff50eac7be69d7",
+ "transactions": [
+ "0x02f87082053901018445e0d81382520894f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c001a0650262f2686338f44fc10d7da58e1aea564840d0c98ff11c44e0032143a52738a03d5bf4978d9d3429da0be7a5f0f82d1408a1a4c97ae2c72b7625c778d08190bd",
+ "0x02f8708205390180841ac012b782520894690b9a9e9aa1c9db991c7721a92d351db4fac990880de0ae214b651e5080c001a03ef471558fe8f14fbfc1e094e0e37da8f6fe16272e388fd00c47e438e4f7e68ea011a49e0f3fe3833c7629f809612c22dbba3361902721885bb0e2ce15e3efc16b"
+ ],
+ "withdrawals": [
+ {
+ "index": "33",
+ "validator_index": "84",
+ "address": "0x8794388915e86e4988363cdd4289ad19182209c8",
+ "amount": "4578"
+ },
+ {
+ "index": "34",
+ "validator_index": "85",
+ "address": "0xa3862121db5914d7272b0b705e6e3c5336b79e31",
+ "amount": "5886"
+ },
+ {
+ "index": "35",
+ "validator_index": "86",
+ "address": "0x96ef954b331a534199f4f113d993a50ec7a781fc",
+ "amount": "5232"
+ },
+ {
+ "index": "36",
+ "validator_index": "87",
+ "address": "0x96c8d3dd08724624017f178393d176b425dab9df",
+ "amount": "4578"
+ },
+ {
+ "index": "37",
+ "validator_index": "88",
+ "address": "0x92bd81b8e9099b9ca87a2033fdd84475752dc34a",
+ "amount": "2616"
+ },
+ {
+ "index": "38",
+ "validator_index": "89",
+ "address": "0x83802cd575a3cea7e3e38fc1a73d94a9e4fdb999",
+ "amount": "654"
+ },
+ {
+ "index": "39",
+ "validator_index": "90",
+ "address": "0xb451eb0ff4990917aba6e3d80c34aee91ea1ce49",
+ "amount": "5886"
+ },
+ {
+ "index": "40",
+ "validator_index": "91",
+ "address": "0xa7f711233af57440e9ea700113fc4dbaef97e7da",
+ "amount": "1962"
+ },
+ {
+ "index": "41",
+ "validator_index": "92",
+ "address": "0xaca5e4979f281b5ab0ea0f549d6dcc34989607c3",
+ "amount": "4578"
+ },
+ {
+ "index": "42",
+ "validator_index": "93",
+ "address": "0x984620db3658a19769475080998db9e7f5bcd425",
+ "amount": "5886"
+ },
+ {
+ "index": "43",
+ "validator_index": "94",
+ "address": "0x8f1ef3639aea57fef705847e251b785bb608a848",
+ "amount": "2616"
+ },
+ {
+ "index": "44",
+ "validator_index": "95",
+ "address": "0x8967da3c8071ba2bf632cd40ae08fbbf0a203c47",
+ "amount": "7194"
+ },
+ {
+ "index": "45",
+ "validator_index": "96",
+ "address": "0x8d58f7e2e58471b46d20a66a61f4cde3c78ab6c0",
+ "amount": "7194"
+ },
+ {
+ "index": "46",
+ "validator_index": "97",
+ "address": "0x8db9f236d3483af79703244c7034b5267a0546c3",
+ "amount": "3270"
+ },
+ {
+ "index": "47",
+ "validator_index": "98",
+ "address": "0xb7721412ae5a793f34ac8866698b221c67ef8272",
+ "amount": "3270"
+ },
+ {
+ "index": "48",
+ "validator_index": "99",
+ "address": "0x99f6e5b80dc52407f0436d3474bd5da5ff23a19c",
+ "amount": "2616"
+ }
+ ],
+ "blob_gas_used": "0",
+ "excess_blob_gas": "0"
+ },
+ "blobs_bundle": {
+ "commitments": [],
+ "proofs": [],
+ "blobs": []
+ },
+ "execution_requests": {
+ "deposits": [
+ {
+ "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
+ "withdrawal_credentials": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
+ "amount": "1",
+ "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
+ "index": "1"
+ }
+ ],
+ "withdrawals": [
+ {
+ "source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
+ "validator_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
+ "amount": "1"
+ }
+ ],
+ "consolidations": [
+ {
+ "source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
+ "source_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
+ "target_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
+ }
+ ]
+ },
+ "target_blobs_per_block": "6",
+ "signature": "0xa37afc405ef69e4f331e79f9de77e0df870609898c0e10a1cfcd8162e8771c4e4fefa7258059d83f72fb599b12f1bb73068476ebfaedc65e5a068425693ba272f277d83e11334e87a7d1425a2fbd369ed9351f0eb14fdc8bd93115543f6a4c67"
+}
\ No newline at end of file
diff --git a/crates/rpc-types-beacon/src/lib.rs b/crates/rpc-types-beacon/src/lib.rs
index 9097056cc37..897cea89a2b 100644
--- a/crates/rpc-types-beacon/src/lib.rs
+++ b/crates/rpc-types-beacon/src/lib.rs
@@ -24,6 +24,9 @@ pub mod payload;
/// Types and functions related to the relay mechanism.
pub mod relay;
+/// Types and functions related to execution requests.
+pub mod requests;
+
/// Types and functions related to the sidecar.
pub mod sidecar;
diff --git a/crates/rpc-types-beacon/src/relay.rs b/crates/rpc-types-beacon/src/relay.rs
index 2940256c47e..2a4fe0e55dc 100644
--- a/crates/rpc-types-beacon/src/relay.rs
+++ b/crates/rpc-types-beacon/src/relay.rs
@@ -2,8 +2,8 @@
//!
//! See also
-use crate::{BlsPublicKey, BlsSignature};
-use alloy_primitives::{Address, Bytes, B256, U256};
+use crate::{requests::ExecutionRequestsV4, BlsPublicKey, BlsSignature};
+use alloy_primitives::{Address, B256, U256};
use alloy_rpc_types_engine::{
BlobsBundleV1, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3,
};
@@ -146,6 +146,7 @@ pub struct SignedBidSubmissionV3 {
}
/// Submission for the `/relay/v1/builder/blocks` endpoint (Electra).
+#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))]
@@ -158,9 +159,9 @@ pub struct SignedBidSubmissionV4 {
/// The Electra block bundle for this bid.
pub blobs_bundle: BlobsBundleV1,
/// The Pectra execution requests for this bid.
- pub execution_requests: Vec,
+ pub execution_requests: ExecutionRequestsV4,
/// The EIP-7742 blobs per block for this bid.
- #[serde(with = "alloy_serde::quantity")]
+ #[serde_as(as = "DisplayFromStr")]
pub target_blobs_per_block: u64,
/// The signature associated with the submission.
pub signature: BlsSignature,
@@ -479,6 +480,15 @@ mod tests {
assert_eq!(json, serde_json::to_value(bid).unwrap());
}
+ #[test]
+ fn electra_bid_submission() {
+ let s = include_str!("examples/relay_builder_block_validation_request_v4.json");
+
+ let bid = serde_json::from_str::(s).unwrap();
+ let json: serde_json::Value = serde_json::from_str(s).unwrap();
+ assert_eq!(json, serde_json::to_value(bid).unwrap());
+ }
+
#[cfg(feature = "ssz")]
#[test]
fn capella_bid_submission_ssz() {
diff --git a/crates/rpc-types-beacon/src/requests.rs b/crates/rpc-types-beacon/src/requests.rs
new file mode 100644
index 00000000000..31092fb23d1
--- /dev/null
+++ b/crates/rpc-types-beacon/src/requests.rs
@@ -0,0 +1,18 @@
+use alloy_eips::{
+ eip6110::DepositRequest, eip7002::WithdrawalRequest, eip7251::ConsolidationRequest,
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+#[serde(rename_all = "snake_case")]
+#[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))]
+/// An Electra-compatible execution requests payload.
+pub struct ExecutionRequestsV4 {
+ /// The requested deposits.
+ pub deposits: Vec,
+ /// The requested withdrawals.
+ pub withdrawals: Vec,
+ /// The requested consolidations.
+ pub consolidations: Vec,
+}