1use crate::{
2 transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, SignableTransaction, TxHashRef},
3 Transaction,
4};
5use alloy_eips::{
6 eip2718::{Eip2718Error, Eip2718Result},
7 eip2930::AccessList,
8 eip7702::SignedAuthorization,
9 Decodable2718, Encodable2718, Typed2718,
10};
11use alloy_primitives::{Bytes, Sealed, Signature, TxKind, B256, U256};
12use alloy_rlp::BufMut;
13use core::hash::{Hash, Hasher};
14#[cfg(not(feature = "std"))]
15use once_cell::race::OnceBox as OnceLock;
16#[cfg(feature = "std")]
17use std::sync::OnceLock;
18
19#[derive(Debug, Clone)]
21pub struct Signed<T, Sig = Signature> {
22 #[doc(alias = "transaction")]
23 tx: T,
24 signature: Sig,
25 #[doc(alias = "tx_hash", alias = "transaction_hash")]
26 hash: OnceLock<B256>,
27}
28
29impl<T, Sig> Signed<T, Sig> {
30 pub fn new_unchecked(tx: T, signature: Sig, hash: B256) -> Self {
32 let value = OnceLock::new();
33 #[allow(clippy::useless_conversion)]
34 value.get_or_init(|| hash.into());
35 Self { tx, signature, hash: value }
36 }
37
38 pub const fn new_unhashed(tx: T, signature: Sig) -> Self {
40 Self { tx, signature, hash: OnceLock::new() }
41 }
42
43 #[doc(alias = "transaction")]
45 pub const fn tx(&self) -> &T {
46 &self.tx
47 }
48
49 pub const fn tx_mut(&mut self) -> &mut T {
51 &mut self.tx
52 }
53
54 pub const fn signature(&self) -> &Sig {
56 &self.signature
57 }
58
59 pub fn strip_signature(self) -> T {
61 self.tx
62 }
63
64 pub fn convert<U>(self) -> Signed<U, Sig>
69 where
70 U: From<T>,
71 {
72 self.map(U::from)
73 }
74
75 pub fn try_convert<U>(self) -> Result<Signed<U, Sig>, U::Error>
82 where
83 U: TryFrom<T>,
84 {
85 self.try_map(U::try_from)
86 }
87
88 pub fn map<Tx>(self, f: impl FnOnce(T) -> Tx) -> Signed<Tx, Sig> {
93 let Self { tx, signature, hash } = self;
94 Signed { tx: f(tx), signature, hash }
95 }
96
97 pub fn try_map<Tx, E>(self, f: impl FnOnce(T) -> Result<Tx, E>) -> Result<Signed<Tx, Sig>, E> {
102 let Self { tx, signature, hash } = self;
103 Ok(Signed { tx: f(tx)?, signature, hash })
104 }
105}
106
107impl<T: SignableTransaction<Sig>, Sig> Signed<T, Sig> {
108 pub fn signature_hash(&self) -> B256 {
110 self.tx.signature_hash()
111 }
112}
113
114impl<T> Signed<T>
115where
116 T: RlpEcdsaEncodableTx,
117{
118 #[doc(alias = "tx_hash", alias = "transaction_hash")]
120 pub fn hash(&self) -> &B256 {
121 #[allow(clippy::useless_conversion)]
122 self.hash.get_or_init(|| self.tx.tx_hash(&self.signature).into())
123 }
124
125 pub fn into_parts(self) -> (T, Signature, B256) {
127 let hash = *self.hash();
128 (self.tx, self.signature, hash)
129 }
130
131 pub fn rlp_encoded_length(&self) -> usize {
133 self.tx.rlp_encoded_length_with_signature(&self.signature)
134 }
135
136 pub fn rlp_encode(&self, out: &mut dyn BufMut) {
138 self.tx.rlp_encode_signed(&self.signature, out);
139 }
140
141 pub fn eip2718_encoded_length(&self) -> usize {
143 self.tx.eip2718_encoded_length(&self.signature)
144 }
145
146 pub fn eip2718_encode_with_type(&self, ty: u8, out: &mut dyn BufMut) {
148 self.tx.eip2718_encode_with_type(&self.signature, ty, out);
149 }
150
151 pub fn eip2718_encode(&self, out: &mut dyn BufMut) {
153 self.tx.eip2718_encode(&self.signature, out);
154 }
155
156 pub fn network_encoded_length(&self) -> usize {
158 self.tx.network_encoded_length(&self.signature)
159 }
160
161 pub fn network_encode_with_type(&self, ty: u8, out: &mut dyn BufMut) {
163 self.tx.network_encode_with_type(&self.signature, ty, out);
164 }
165
166 pub fn network_encode(&self, out: &mut dyn BufMut) {
168 self.tx.network_encode(&self.signature, out);
169 }
170}
171
172impl<T> TxHashRef for Signed<T>
173where
174 T: RlpEcdsaEncodableTx,
175{
176 fn tx_hash(&self) -> &B256 {
177 self.hash()
178 }
179}
180
181impl<T> Signed<T>
182where
183 T: RlpEcdsaDecodableTx,
184{
185 pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
187 T::rlp_decode_signed(buf)
188 }
189
190 pub fn eip2718_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result<Self> {
192 T::eip2718_decode_with_type(buf, ty)
193 }
194
195 pub fn eip2718_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
197 T::eip2718_decode(buf)
198 }
199
200 pub fn network_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result<Self> {
202 T::network_decode_with_type(buf, ty)
203 }
204
205 pub fn network_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
207 T::network_decode(buf)
208 }
209}
210
211impl<T> Hash for Signed<T>
212where
213 T: RlpEcdsaDecodableTx + Hash,
214{
215 fn hash<H: Hasher>(&self, state: &mut H) {
216 self.hash().hash(state);
217 self.tx.hash(state);
218 self.signature.hash(state);
219 }
220}
221
222impl<T: RlpEcdsaEncodableTx + PartialEq> PartialEq for Signed<T> {
223 fn eq(&self, other: &Self) -> bool {
224 self.hash() == other.hash() && self.tx == other.tx && self.signature == other.signature
225 }
226}
227
228impl<T: RlpEcdsaEncodableTx + PartialEq> Eq for Signed<T> {}
229
230#[cfg(feature = "k256")]
231impl<T: SignableTransaction<Signature>> Signed<T, Signature> {
232 pub fn recover_signer(
234 &self,
235 ) -> Result<alloy_primitives::Address, alloy_primitives::SignatureError> {
236 let sighash = self.tx.signature_hash();
237 self.signature.recover_address_from_prehash(&sighash)
238 }
239
240 pub fn try_into_recovered(
242 self,
243 ) -> Result<crate::transaction::Recovered<T>, alloy_primitives::SignatureError> {
244 let signer = self.recover_signer()?;
245 Ok(crate::transaction::Recovered::new_unchecked(self.tx, signer))
246 }
247
248 pub fn try_to_recovered_ref(
251 &self,
252 ) -> Result<crate::transaction::Recovered<&T>, alloy_primitives::SignatureError> {
253 let signer = self.recover_signer()?;
254 Ok(crate::transaction::Recovered::new_unchecked(&self.tx, signer))
255 }
256}
257
258#[cfg(all(any(test, feature = "arbitrary"), feature = "k256"))]
259impl<'a, T: SignableTransaction<Signature> + arbitrary::Arbitrary<'a>> arbitrary::Arbitrary<'a>
260 for Signed<T, Signature>
261{
262 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
263 use k256::{
264 ecdsa::{signature::hazmat::PrehashSigner, SigningKey},
265 NonZeroScalar,
266 };
267 use rand::{rngs::StdRng, SeedableRng};
268
269 let rng_seed = u.arbitrary::<[u8; 32]>()?;
270 let mut rand_gen = StdRng::from_seed(rng_seed);
271 let signing_key: SigningKey = NonZeroScalar::random(&mut rand_gen).into();
272
273 let tx = T::arbitrary(u)?;
274
275 let (recoverable_sig, recovery_id) =
276 signing_key.sign_prehash(tx.signature_hash().as_ref()).unwrap();
277 let signature: Signature = (recoverable_sig, recovery_id).into();
278
279 Ok(tx.into_signed(signature))
280 }
281}
282
283impl<T> Typed2718 for Signed<T>
284where
285 T: Typed2718,
286{
287 fn ty(&self) -> u8 {
288 self.tx().ty()
289 }
290}
291
292impl<T: Transaction> Transaction for Signed<T> {
293 #[inline]
294 fn chain_id(&self) -> Option<u64> {
295 self.tx.chain_id()
296 }
297
298 #[inline]
299 fn nonce(&self) -> u64 {
300 self.tx.nonce()
301 }
302
303 #[inline]
304 fn gas_limit(&self) -> u64 {
305 self.tx.gas_limit()
306 }
307
308 #[inline]
309 fn gas_price(&self) -> Option<u128> {
310 self.tx.gas_price()
311 }
312
313 #[inline]
314 fn max_fee_per_gas(&self) -> u128 {
315 self.tx.max_fee_per_gas()
316 }
317
318 #[inline]
319 fn max_priority_fee_per_gas(&self) -> Option<u128> {
320 self.tx.max_priority_fee_per_gas()
321 }
322
323 #[inline]
324 fn max_fee_per_blob_gas(&self) -> Option<u128> {
325 self.tx.max_fee_per_blob_gas()
326 }
327
328 #[inline]
329 fn priority_fee_or_price(&self) -> u128 {
330 self.tx.priority_fee_or_price()
331 }
332
333 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
334 self.tx.effective_gas_price(base_fee)
335 }
336
337 #[inline]
338 fn is_dynamic_fee(&self) -> bool {
339 self.tx.is_dynamic_fee()
340 }
341
342 #[inline]
343 fn kind(&self) -> TxKind {
344 self.tx.kind()
345 }
346
347 #[inline]
348 fn is_create(&self) -> bool {
349 self.tx.is_create()
350 }
351
352 #[inline]
353 fn value(&self) -> U256 {
354 self.tx.value()
355 }
356
357 #[inline]
358 fn input(&self) -> &Bytes {
359 self.tx.input()
360 }
361
362 #[inline]
363 fn access_list(&self) -> Option<&AccessList> {
364 self.tx.access_list()
365 }
366
367 #[inline]
368 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
369 self.tx.blob_versioned_hashes()
370 }
371
372 #[inline]
373 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
374 self.tx.authorization_list()
375 }
376}
377
378impl<T: Transaction> Transaction for Sealed<T> {
379 #[inline]
380 fn chain_id(&self) -> Option<u64> {
381 self.inner().chain_id()
382 }
383
384 #[inline]
385 fn nonce(&self) -> u64 {
386 self.inner().nonce()
387 }
388
389 #[inline]
390 fn gas_limit(&self) -> u64 {
391 self.inner().gas_limit()
392 }
393
394 #[inline]
395 fn gas_price(&self) -> Option<u128> {
396 self.inner().gas_price()
397 }
398
399 #[inline]
400 fn max_fee_per_gas(&self) -> u128 {
401 self.inner().max_fee_per_gas()
402 }
403
404 #[inline]
405 fn max_priority_fee_per_gas(&self) -> Option<u128> {
406 self.inner().max_priority_fee_per_gas()
407 }
408
409 #[inline]
410 fn max_fee_per_blob_gas(&self) -> Option<u128> {
411 self.inner().max_fee_per_blob_gas()
412 }
413
414 #[inline]
415 fn priority_fee_or_price(&self) -> u128 {
416 self.inner().priority_fee_or_price()
417 }
418
419 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
420 self.inner().effective_gas_price(base_fee)
421 }
422
423 #[inline]
424 fn is_dynamic_fee(&self) -> bool {
425 self.inner().is_dynamic_fee()
426 }
427
428 #[inline]
429 fn kind(&self) -> TxKind {
430 self.inner().kind()
431 }
432
433 #[inline]
434 fn is_create(&self) -> bool {
435 self.inner().is_create()
436 }
437
438 #[inline]
439 fn value(&self) -> U256 {
440 self.inner().value()
441 }
442
443 #[inline]
444 fn input(&self) -> &Bytes {
445 self.inner().input()
446 }
447
448 #[inline]
449 fn access_list(&self) -> Option<&AccessList> {
450 self.inner().access_list()
451 }
452
453 #[inline]
454 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
455 self.inner().blob_versioned_hashes()
456 }
457
458 #[inline]
459 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
460 self.inner().authorization_list()
461 }
462}
463
464#[cfg(any(feature = "secp256k1", feature = "k256"))]
465impl<T> crate::transaction::SignerRecoverable for Signed<T>
466where
467 T: SignableTransaction<Signature>,
468{
469 fn recover_signer(&self) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
470 let signature_hash = self.signature_hash();
471 crate::crypto::secp256k1::recover_signer(self.signature(), signature_hash)
472 }
473
474 fn recover_signer_unchecked(
475 &self,
476 ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
477 let signature_hash = self.signature_hash();
478 crate::crypto::secp256k1::recover_signer_unchecked(self.signature(), signature_hash)
479 }
480
481 fn recover_unchecked_with_buf(
482 &self,
483 buf: &mut alloc::vec::Vec<u8>,
484 ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
485 buf.clear();
486 self.tx.encode_for_signing(buf);
487 let signature_hash = alloy_primitives::keccak256(buf);
488 crate::crypto::secp256k1::recover_signer_unchecked(self.signature(), signature_hash)
489 }
490}
491
492impl<T> Encodable2718 for Signed<T>
493where
494 T: RlpEcdsaEncodableTx + Typed2718 + Send + Sync,
495{
496 fn encode_2718_len(&self) -> usize {
497 self.eip2718_encoded_length()
498 }
499
500 fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
501 self.eip2718_encode(out)
502 }
503
504 fn trie_hash(&self) -> B256 {
505 *self.hash()
506 }
507}
508
509impl<T> Decodable2718 for Signed<T>
510where
511 T: RlpEcdsaDecodableTx + Typed2718 + Send + Sync,
512{
513 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
514 let decoded = T::rlp_decode_signed(buf)?;
515
516 if decoded.ty() != ty {
517 return Err(Eip2718Error::UnexpectedType(ty));
518 }
519
520 Ok(decoded)
521 }
522
523 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
524 T::rlp_decode_signed(buf).map_err(Into::into)
525 }
526}
527
528#[cfg(feature = "serde")]
529mod serde {
530 use crate::transaction::RlpEcdsaEncodableTx;
531 use alloc::borrow::Cow;
532 use alloy_primitives::B256;
533 use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
534
535 #[derive(Serialize, Deserialize)]
536 struct Signed<'a, T: Clone, Sig: Clone> {
537 #[serde(flatten)]
538 tx: Cow<'a, T>,
539 #[serde(flatten)]
540 signature: Cow<'a, Sig>,
541 hash: Cow<'a, B256>,
542 }
543
544 impl<T> Serialize for super::Signed<T>
545 where
546 T: Clone + RlpEcdsaEncodableTx + Serialize,
547 {
548 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
549 where
550 S: Serializer,
551 {
552 Signed {
553 tx: Cow::Borrowed(&self.tx),
554 signature: Cow::Borrowed(&self.signature),
555 hash: Cow::Borrowed(self.hash()),
556 }
557 .serialize(serializer)
558 }
559 }
560
561 impl<'de, T, Sig> Deserialize<'de> for super::Signed<T, Sig>
562 where
563 T: Clone + DeserializeOwned,
564 Sig: Clone + DeserializeOwned,
565 {
566 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
567 where
568 D: Deserializer<'de>,
569 {
570 Signed::<T, Sig>::deserialize(deserializer).map(|value| {
571 Self::new_unchecked(
572 value.tx.into_owned(),
573 value.signature.into_owned(),
574 value.hash.into_owned(),
575 )
576 })
577 }
578 }
579}