1use alloy::consensus::{Signed, Typed2718};
2use alloy::network::eip2718::{Decodable2718, Encodable2718};
3use alloy::rlp::{Encodable, Header};
4use serde::{Deserialize, Serialize};
5
6use super::tx_type::TxType;
7use super::unsigned_tx::eip712::TxEip712;
8#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(
12 into = "serde_from::TaggedTxEnvelope",
13 from = "serde_from::MaybeTaggedTxEnvelope"
14)]
15pub enum TxEnvelope {
16 Native(alloy::consensus::TxEnvelope),
18 Eip712(Signed<TxEip712>),
20}
21
22impl TxEnvelope {
23 #[inline]
25 pub const fn is_legacy(&self) -> bool {
26 match self {
27 Self::Native(inner) => inner.is_legacy(),
28 Self::Eip712(_) => false,
29 }
30 }
31
32 #[inline]
34 pub const fn is_eip2930(&self) -> bool {
35 match self {
36 Self::Native(inner) => inner.is_eip2930(),
37 Self::Eip712(_) => false,
38 }
39 }
40
41 #[inline]
43 pub const fn is_eip1559(&self) -> bool {
44 match self {
45 Self::Native(inner) => inner.is_eip1559(),
46 Self::Eip712(_) => false,
47 }
48 }
49
50 #[inline]
52 pub const fn is_eip4844(&self) -> bool {
53 match self {
54 Self::Native(inner) => inner.is_eip4844(),
55 Self::Eip712(_) => false,
56 }
57 }
58
59 #[inline]
61 pub const fn is_eip7702(&self) -> bool {
62 match self {
63 Self::Native(inner) => inner.is_eip7702(),
64 Self::Eip712(_) => false,
65 }
66 }
67
68 #[inline]
70 pub const fn is_eip712(&self) -> bool {
71 matches!(self, Self::Eip712(_))
72 }
73
74 #[inline]
83 pub const fn is_replay_protected(&self) -> bool {
84 match self {
85 Self::Native(inner) => inner.is_replay_protected(),
86 Self::Eip712(_) => true,
87 }
88 }
89
90 pub const fn as_legacy(&self) -> Option<&Signed<alloy::consensus::TxLegacy>> {
92 match self {
93 Self::Native(inner) => inner.as_legacy(),
94 Self::Eip712(_) => None,
95 }
96 }
97
98 pub const fn as_eip2930(&self) -> Option<&Signed<alloy::consensus::TxEip2930>> {
100 match self {
101 Self::Native(inner) => inner.as_eip2930(),
102 Self::Eip712(_) => None,
103 }
104 }
105
106 pub const fn as_eip1559(&self) -> Option<&Signed<alloy::consensus::TxEip1559>> {
108 match self {
109 Self::Native(inner) => inner.as_eip1559(),
110 Self::Eip712(_) => None,
111 }
112 }
113
114 pub const fn as_eip4844(&self) -> Option<&Signed<alloy::consensus::TxEip4844Variant>> {
116 match self {
117 Self::Native(inner) => inner.as_eip4844(),
118 Self::Eip712(_) => None,
119 }
120 }
121
122 pub const fn as_eip7702(&self) -> Option<&Signed<alloy::consensus::TxEip7702>> {
124 match self {
125 Self::Native(inner) => inner.as_eip7702(),
126 Self::Eip712(_) => None,
127 }
128 }
129
130 pub const fn as_eip712(&self) -> Option<&Signed<TxEip712>> {
132 match self {
133 Self::Native(_) => None,
134 Self::Eip712(inner) => Some(inner),
135 }
136 }
137
138 pub fn signature_hash(&self) -> alloy::primitives::B256 {
140 match self {
141 Self::Native(inner) => inner.signature_hash(),
142 Self::Eip712(inner) => inner.signature_hash(),
143 }
144 }
145
146 pub const fn signature(&self) -> &alloy::primitives::Signature {
148 match self {
149 Self::Native(inner) => inner.signature(),
150 Self::Eip712(inner) => inner.signature(),
151 }
152 }
153
154 #[doc(alias = "transaction_hash")]
156 pub fn tx_hash(&self) -> &alloy::primitives::B256 {
157 match self {
158 Self::Native(inner) => inner.tx_hash(),
159 Self::Eip712(inner) => inner.hash(),
160 }
161 }
162
163 #[doc(alias = "transaction_type")]
165 pub const fn tx_type(&self) -> crate::network::tx_type::TxType {
166 match self {
167 Self::Native(inner) => match inner.tx_type() {
168 alloy::consensus::TxType::Legacy => crate::network::tx_type::TxType::Legacy,
169 alloy::consensus::TxType::Eip2930 => crate::network::tx_type::TxType::Eip2930,
170 alloy::consensus::TxType::Eip1559 => crate::network::tx_type::TxType::Eip1559,
171 alloy::consensus::TxType::Eip4844 => crate::network::tx_type::TxType::Eip4844,
172 alloy::consensus::TxType::Eip7702 => crate::network::tx_type::TxType::Eip7702,
173 },
174 Self::Eip712(_) => crate::network::tx_type::TxType::Eip712,
175 }
176 }
177
178 pub fn eip2718_encoded_length(&self) -> usize {
180 match self {
181 Self::Native(inner) => inner.eip2718_encoded_length(),
182 Self::Eip712(inner) => inner.tx().encoded_length(inner.signature()),
183 }
184 }
185}
186
187impl Typed2718 for TxEnvelope {
188 fn ty(&self) -> u8 {
189 match self {
190 Self::Native(inner) => inner.ty(),
191 Self::Eip712(inner) => inner.tx().tx_type() as u8,
192 }
193 }
194}
195
196impl Encodable2718 for TxEnvelope {
197 fn type_flag(&self) -> Option<u8> {
198 match self {
199 Self::Native(inner) => inner.type_flag(),
200 Self::Eip712(inner) => Some(inner.tx().tx_type() as u8),
201 }
202 }
203
204 fn encode_2718_len(&self) -> usize {
205 match self {
206 Self::Native(inner) => inner.encode_2718_len(),
207 Self::Eip712(inner) => {
208 let payload_length = inner.tx().fields_len()
209 + inner.signature().rlp_rs_len()
210 + inner.signature().v().length();
211 Header {
212 list: true,
213 payload_length,
214 }
215 .length()
216 + payload_length
217 }
218 }
219 }
220
221 fn encode_2718(&self, out: &mut dyn alloy::primitives::bytes::BufMut) {
222 match self {
223 Self::Native(inner) => inner.encode_2718(out),
224 Self::Eip712(tx) => {
225 tx.tx().encode_with_signature(tx.signature(), out);
226 }
227 }
228 }
229}
230
231impl Decodable2718 for TxEnvelope {
232 fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy::network::eip2718::Eip2718Result<Self> {
233 match ty {
234 _ if ty == (TxType::Eip712 as u8) => {
235 let tx = TxEip712::decode_signed_fields(buf)?;
236 Ok(Self::Eip712(tx))
237 }
238 _ => {
239 let inner = alloy::consensus::TxEnvelope::typed_decode(ty, buf)?;
240 Ok(Self::Native(inner))
241 }
242 }
243 }
244
245 fn fallback_decode(buf: &mut &[u8]) -> alloy::network::eip2718::Eip2718Result<Self> {
246 let inner = alloy::consensus::TxEnvelope::fallback_decode(buf)?;
247 Ok(Self::Native(inner))
248 }
249}
250
251impl AsRef<dyn alloy::consensus::Transaction> for TxEnvelope {
252 fn as_ref(&self) -> &dyn alloy::consensus::Transaction {
253 match self {
254 TxEnvelope::Native(inner) => inner,
255 TxEnvelope::Eip712(signed_inner) => signed_inner.tx(),
256 }
257 }
258}
259
260impl alloy::consensus::Transaction for TxEnvelope {
261 fn chain_id(&self) -> Option<alloy::primitives::ChainId> {
262 self.as_ref().chain_id()
263 }
264
265 fn nonce(&self) -> u64 {
266 self.as_ref().nonce()
267 }
268
269 fn gas_limit(&self) -> u64 {
270 self.as_ref().gas_limit()
271 }
272
273 fn gas_price(&self) -> Option<u128> {
274 self.as_ref().gas_price()
275 }
276
277 fn max_fee_per_gas(&self) -> u128 {
278 self.as_ref().max_fee_per_gas()
279 }
280
281 fn max_priority_fee_per_gas(&self) -> Option<u128> {
282 self.as_ref().max_priority_fee_per_gas()
283 }
284
285 fn max_fee_per_blob_gas(&self) -> Option<u128> {
286 self.as_ref().max_fee_per_blob_gas()
287 }
288
289 fn priority_fee_or_price(&self) -> u128 {
290 self.as_ref().priority_fee_or_price()
291 }
292
293 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
294 self.as_ref().effective_gas_price(base_fee)
295 }
296
297 fn is_dynamic_fee(&self) -> bool {
298 self.as_ref().is_dynamic_fee()
299 }
300
301 fn kind(&self) -> alloy::primitives::TxKind {
302 self.as_ref().kind()
303 }
304
305 fn is_create(&self) -> bool {
306 self.as_ref().is_create()
307 }
308
309 fn value(&self) -> alloy::primitives::U256 {
310 self.as_ref().value()
311 }
312
313 fn input(&self) -> &alloy::primitives::Bytes {
314 self.as_ref().input()
315 }
316
317 fn access_list(&self) -> Option<&alloy::rpc::types::AccessList> {
318 self.as_ref().access_list()
319 }
320
321 fn blob_versioned_hashes(&self) -> Option<&[alloy::primitives::B256]> {
322 self.as_ref().blob_versioned_hashes()
323 }
324
325 fn authorization_list(&self) -> Option<&[alloy::eips::eip7702::SignedAuthorization]> {
326 self.as_ref().authorization_list()
327 }
328}
329
330mod serde_from {
331 use crate::network::tx_envelope::TxEnvelope;
347 use crate::network::unsigned_tx::eip712::TxEip712;
348 use alloy::consensus::{Signed, TxEip1559, TxEip2930, TxEip4844Variant, TxEip7702, TxLegacy};
349
350 #[derive(Debug, serde::Deserialize)]
351 #[serde(untagged)]
352 pub(crate) enum MaybeTaggedTxEnvelope {
353 Tagged(TaggedTxEnvelopeDe),
354 Untagged {
355 #[serde(
356 default,
357 rename = "type",
358 deserialize_with = "alloy::serde::reject_if_some"
359 )]
360 _ty: Option<()>,
361 #[serde(flatten, with = "alloy::consensus::transaction::signed_legacy_serde")]
362 tx: Signed<TxLegacy>,
363 },
364 }
365
366 #[derive(Debug, serde::Serialize)]
371 #[serde(tag = "type")]
372 pub(crate) enum TaggedTxEnvelope {
373 #[serde(
374 rename = "0x0",
375 with = "alloy::consensus::transaction::signed_legacy_serde"
376 )]
377 Legacy(Signed<TxLegacy>),
378 #[serde(rename = "0x1")]
379 Eip2930(Signed<TxEip2930>),
380 #[serde(rename = "0x2")]
381 Eip1559(Signed<TxEip1559>),
382 #[serde(rename = "0x3")]
383 Eip4844(Signed<TxEip4844Variant>),
384 #[serde(rename = "0x4")]
385 Eip7702(Signed<TxEip7702>),
386 #[serde(rename = "0x71")]
387 Eip712(Signed<TxEip712>),
388 }
389
390 #[derive(Debug, serde::Deserialize)]
396 #[serde(tag = "type")]
397 pub(crate) enum TaggedTxEnvelopeDe {
398 #[serde(
399 rename = "0x0",
400 alias = "0x00",
401 with = "alloy::consensus::transaction::signed_legacy_serde"
402 )]
403 Legacy(Signed<TxLegacy>),
404 #[serde(rename = "0x1", alias = "0x01")]
405 Eip2930(Signed<TxEip2930>),
406 #[serde(
407 rename = "0x2",
408 alias = "0x02",
409 deserialize_with = "deserialize_eip1559_permissive"
410 )]
411 Eip1559(Signed<TxEip1559>),
412 #[serde(rename = "0x3", alias = "0x03")]
413 Eip4844(Signed<TxEip4844Variant>),
414 #[serde(rename = "0x4", alias = "0x04")]
415 Eip7702(Signed<TxEip7702>),
416 #[serde(rename = "0x71")]
417 Eip712(Signed<TxEip712>),
418 }
419
420 fn deserialize_eip1559_permissive<'de, D>(
425 deserializer: D,
426 ) -> Result<Signed<TxEip1559>, D::Error>
427 where
428 D: serde::Deserializer<'de>,
429 {
430 use alloy::eips::eip2930::AccessList;
431 use alloy::primitives::{B256, Bytes, ChainId, Signature, TxKind, U256};
432 use serde::Deserialize;
433
434 #[derive(Deserialize)]
440 #[serde(rename_all = "camelCase")]
441 struct TxEip1559Permissive {
442 #[serde(with = "alloy::serde::quantity")]
443 chain_id: ChainId,
444 #[serde(with = "alloy::serde::quantity")]
445 nonce: u64,
446 #[serde(with = "alloy::serde::quantity", rename = "gas", alias = "gasLimit")]
447 gas_limit: u64,
448 #[serde(with = "alloy::serde::quantity")]
449 max_fee_per_gas: u128,
450 #[serde(with = "alloy::serde::quantity")]
451 max_priority_fee_per_gas: u128,
452 #[serde(default)]
453 to: TxKind,
454 value: U256,
455 #[serde(default, deserialize_with = "alloy::serde::null_as_default")]
456 access_list: AccessList,
457 input: Bytes,
458 }
459
460 #[derive(Deserialize)]
463 struct SignedHelper {
464 #[serde(flatten)]
465 tx: TxEip1559Permissive,
466 #[serde(flatten)]
467 signature: Signature,
468 hash: B256,
469 }
470
471 let helper = SignedHelper::deserialize(deserializer)?;
472 let tx = TxEip1559 {
473 chain_id: helper.tx.chain_id,
474 nonce: helper.tx.nonce,
475 gas_limit: helper.tx.gas_limit,
476 max_fee_per_gas: helper.tx.max_fee_per_gas,
477 max_priority_fee_per_gas: helper.tx.max_priority_fee_per_gas,
478 to: helper.tx.to,
479 value: helper.tx.value,
480 access_list: helper.tx.access_list,
481 input: helper.tx.input,
482 };
483 Ok(Signed::new_unchecked(tx, helper.signature, helper.hash))
486 }
487
488 impl From<MaybeTaggedTxEnvelope> for TxEnvelope {
489 fn from(value: MaybeTaggedTxEnvelope) -> Self {
490 match value {
491 MaybeTaggedTxEnvelope::Tagged(tagged) => tagged.into(),
492 MaybeTaggedTxEnvelope::Untagged { tx, .. } => {
493 Self::Native(alloy::consensus::TxEnvelope::Legacy(tx))
494 }
495 }
496 }
497 }
498
499 impl From<TaggedTxEnvelopeDe> for TxEnvelope {
500 fn from(value: TaggedTxEnvelopeDe) -> Self {
501 match value {
502 TaggedTxEnvelopeDe::Legacy(signed) => {
503 Self::Native(alloy::consensus::TxEnvelope::Legacy(signed))
504 }
505 TaggedTxEnvelopeDe::Eip2930(signed) => {
506 Self::Native(alloy::consensus::TxEnvelope::Eip2930(signed))
507 }
508 TaggedTxEnvelopeDe::Eip1559(signed) => {
509 Self::Native(alloy::consensus::TxEnvelope::Eip1559(signed))
510 }
511 TaggedTxEnvelopeDe::Eip4844(signed) => {
512 Self::Native(alloy::consensus::TxEnvelope::Eip4844(signed))
513 }
514 TaggedTxEnvelopeDe::Eip7702(signed) => {
515 Self::Native(alloy::consensus::TxEnvelope::Eip7702(signed))
516 }
517 TaggedTxEnvelopeDe::Eip712(signed) => Self::Eip712(signed),
518 }
519 }
520 }
521
522 impl From<TxEnvelope> for TaggedTxEnvelope {
523 fn from(value: TxEnvelope) -> Self {
524 match value {
525 TxEnvelope::Native(alloy::consensus::TxEnvelope::Legacy(signed)) => {
526 Self::Legacy(signed)
527 }
528 TxEnvelope::Native(alloy::consensus::TxEnvelope::Eip2930(signed)) => {
529 Self::Eip2930(signed)
530 }
531 TxEnvelope::Native(alloy::consensus::TxEnvelope::Eip1559(signed)) => {
532 Self::Eip1559(signed)
533 }
534 TxEnvelope::Native(alloy::consensus::TxEnvelope::Eip4844(signed)) => {
535 Self::Eip4844(signed)
536 }
537 TxEnvelope::Native(alloy::consensus::TxEnvelope::Eip7702(signed)) => {
538 Self::Eip7702(signed)
539 }
540 TxEnvelope::Eip712(signed) => Self::Eip712(signed),
541 }
542 }
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549 use crate::network::transaction_response::TransactionResponse;
550 use alloy::consensus::Transaction;
551
552 #[test]
558 fn eip1559_roundtrip_through_permissive_path() {
559 use alloy::consensus::{Signed, TxEip1559};
560 use alloy::eips::eip2930::AccessList;
561 use alloy::primitives::{Address, B256, Bytes, Signature, U256};
562
563 let tx = TxEip1559 {
564 chain_id: 324,
565 nonce: 42,
566 gas_limit: 21_000,
567 max_fee_per_gas: 4_000_000_000,
568 max_priority_fee_per_gas: 1_000_000,
569 to: Address::repeat_byte(0xab).into(),
570 value: U256::from(1_000_000_000_000_000_000u128),
571 access_list: AccessList::default(),
572 input: Bytes::from_static(b"\xde\xad"),
573 };
574
575 let sig = Signature::test_signature();
576 let hash = B256::repeat_byte(0xff);
577 let signed = Signed::new_unchecked(tx.clone(), sig, hash);
578
579 let envelope = TxEnvelope::Native(alloy::consensus::TxEnvelope::Eip1559(signed));
581 let json = serde_json::to_string(&envelope).expect("serialize");
582
583 let roundtripped: TxEnvelope = serde_json::from_str(&json).expect("deserialize");
585 let inner = roundtripped.as_eip1559().expect("should be EIP-1559").tx();
586
587 assert_eq!(inner.chain_id, tx.chain_id);
588 assert_eq!(inner.nonce, tx.nonce);
589 assert_eq!(inner.gas_limit, tx.gas_limit);
590 assert_eq!(inner.max_fee_per_gas, tx.max_fee_per_gas);
591 assert_eq!(inner.max_priority_fee_per_gas, tx.max_priority_fee_per_gas);
592 assert_eq!(inner.to, tx.to);
593 assert_eq!(inner.value, tx.value);
594 assert_eq!(inner.access_list, tx.access_list);
595 assert_eq!(inner.input, tx.input);
596 }
597
598 #[test]
602 fn deserialize_eip1559_without_access_list() {
603 let json = r#"
604 {
605 "type": "0x2",
606 "chainId": "0x144",
607 "nonce": "0x5",
608 "gas": "0x5208",
609 "maxFeePerGas": "0xee6b2800",
610 "maxPriorityFeePerGas": "0x0",
611 "to": "0x1234567890abcdef1234567890abcdef12345678",
612 "value": "0xde0b6b3a7640000",
613 "input": "0x",
614 "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
615 "s": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
616 "v": "0x0",
617 "yParity": "0x0",
618 "hash": "0x09d047b22ceb10d30bd1a36969e45eb9f63b6d01f16439f4fd0b9f0114177cff"
619 }
620 "#;
621
622 let envelope: TxEnvelope = serde_json::from_str(json).unwrap();
623 assert!(envelope.is_eip1559());
624 let access_list = envelope
625 .access_list()
626 .expect("should have defaulted to empty");
627 assert!(access_list.is_empty());
628 }
629
630 #[test]
632 fn deserialize_eip1559_with_access_list() {
633 let json = r#"
634 {
635 "type": "0x2",
636 "chainId": "0x144",
637 "nonce": "0x5",
638 "gas": "0x5208",
639 "maxFeePerGas": "0xee6b2800",
640 "maxPriorityFeePerGas": "0x0",
641 "to": "0x1234567890abcdef1234567890abcdef12345678",
642 "value": "0xde0b6b3a7640000",
643 "input": "0x",
644 "accessList": [],
645 "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
646 "s": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
647 "v": "0x0",
648 "yParity": "0x0",
649 "hash": "0x09d047b22ceb10d30bd1a36969e45eb9f63b6d01f16439f4fd0b9f0114177cff"
650 }
651 "#;
652
653 let envelope: TxEnvelope = serde_json::from_str(json).unwrap();
654 assert!(envelope.is_eip1559());
655 }
656
657 #[test]
660 fn deserialize_eip1559_with_null_access_list() {
661 let json = r#"
662 {
663 "type": "0x2",
664 "chainId": "0x144",
665 "nonce": "0x5",
666 "gas": "0x5208",
667 "maxFeePerGas": "0xee6b2800",
668 "maxPriorityFeePerGas": "0x0",
669 "to": "0x1234567890abcdef1234567890abcdef12345678",
670 "value": "0xde0b6b3a7640000",
671 "input": "0x",
672 "accessList": null,
673 "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
674 "s": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
675 "v": "0x0",
676 "yParity": "0x0",
677 "hash": "0x09d047b22ceb10d30bd1a36969e45eb9f63b6d01f16439f4fd0b9f0114177cff"
678 }
679 "#;
680
681 let envelope: TxEnvelope = serde_json::from_str(json).unwrap();
682 assert!(envelope.is_eip1559());
683 let access_list = envelope
684 .access_list()
685 .expect("should have defaulted null to empty");
686 assert!(access_list.is_empty());
687 }
688
689 #[test]
691 fn deserialize_eip712_without_access_list() {
692 let json = r#"
693 {
694 "type": "0x71",
695 "blockHash": "0x05a9dc0814c1bf7dc96c464663bf24db8e1306e2814c848c87de2ea0684dadb5",
696 "blockNumber": "0x41e64c0",
697 "chainId": "0x144",
698 "from": "0xaede212cda8fc657ac7b4ad5e98fd510624638b3",
699 "gas": "0x388d8",
700 "gasPrice": "0x2b275d0",
701 "hash": "0xfb0fac3fccaaec0a1863e87941ba68752e92f9190f036f1af2b2f485e5829d28",
702 "input": "0x51cff8d9000000000000000000000000aede212cda8fc657ac7b4ad5e98fd510624638b3",
703 "l1BatchNumber": "0x7c0c8",
704 "l1BatchTxIndex": "0x140",
705 "maxFeePerGas": "0x2b275d0",
706 "maxPriorityFeePerGas": "0x2b275d0",
707 "nonce": "0x31db5",
708 "r": "0x3e6d7b9325a8ef7b707a02f287c91d7d07b06bb1ad16d645ba35703bc5804936",
709 "s": "0x6a8a76d975aec0c560233be60e13bfa2233a95ee7d2262d8c119ec6589e1bc26",
710 "to": "0x000000000000000000000000000000000000800a",
711 "transactionIndex": "0x0",
712 "v": "0x0",
713 "value": "0x1"
714 }
715 "#;
716
717 let envelope: TxEnvelope = serde_json::from_str(json).unwrap();
718 assert!(envelope.is_eip712());
719 }
720
721 #[test]
725 fn deserialize_transaction_response_without_access_list() {
726 let json = r#"
727 {
728 "type": "0x2",
729 "blockHash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
730 "blockNumber": "0x41a3a9b",
731 "transactionIndex": "0x0",
732 "hash": "0x09d047b22ceb10d30bd1a36969e45eb9f63b6d01f16439f4fd0b9f0114177cff",
733 "from": "0x1234567890abcdef1234567890abcdef12345678",
734 "to": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
735 "value": "0xde0b6b3a7640000",
736 "nonce": "0x5",
737 "gas": "0x5208",
738 "maxFeePerGas": "0xee6b2800",
739 "maxPriorityFeePerGas": "0x0",
740 "input": "0x",
741 "chainId": "0x144",
742 "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
743 "s": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
744 "v": "0x0",
745 "yParity": "0x0"
746 }
747 "#;
748
749 let response: TransactionResponse = serde_json::from_str(json).unwrap();
750 assert_eq!(response.nonce(), 5);
751 assert_eq!(response.gas_limit(), 21000);
752 assert_eq!(response.max_fee_per_gas(), 0xee6b2800);
753 let access_list = response
754 .access_list()
755 .expect("should have defaulted to empty");
756 assert!(access_list.is_empty());
757 }
758}