1use alloy_consensus::{
4 EthereumTxEnvelope, EthereumTypedTransaction, Signed, TxEip1559, TxEip2930, TxEip4844,
5 TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy, Typed2718,
6};
7use alloy_eips::{eip2718::Encodable2718, eip7702::SignedAuthorization};
8use alloy_network_primitives::TransactionResponse;
9use alloy_primitives::{Address, BlockHash, Bytes, ChainId, TxKind, B256, U256};
10
11use alloy_consensus::transaction::Recovered;
12pub use alloy_consensus::{
13 transaction::TransactionInfo, BlobTransactionSidecar, Receipt, ReceiptEnvelope,
14 ReceiptWithBloom, Transaction as TransactionTrait,
15};
16pub use alloy_consensus_any::AnyReceiptEnvelope;
17pub use alloy_eips::{
18 eip2930::{AccessList, AccessListItem, AccessListResult},
19 eip7702::Authorization,
20};
21
22mod error;
23pub use error::ConversionError;
24
25mod receipt;
26pub use receipt::TransactionReceipt;
27
28pub mod request;
29pub use request::{TransactionInput, TransactionRequest};
30
31#[derive(Clone, Debug, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37#[cfg_attr(all(any(test, feature = "arbitrary"), feature = "k256"), derive(arbitrary::Arbitrary))]
38#[cfg_attr(
39 feature = "serde",
40 serde(
41 into = "tx_serde::TransactionSerdeHelper<T>",
42 try_from = "tx_serde::TransactionSerdeHelper<T>",
43 bound = "T: TransactionTrait + Clone + serde::Serialize + serde::de::DeserializeOwned"
44 )
45)]
46#[doc(alias = "Tx")]
47pub struct Transaction<T = TxEnvelope> {
48 pub inner: Recovered<T>,
50
51 pub block_hash: Option<BlockHash>,
53
54 pub block_number: Option<u64>,
56
57 pub transaction_index: Option<u64>,
59
60 pub effective_gas_price: Option<u128>,
62}
63
64impl<T> Default for Transaction<T>
65where
66 T: Default,
67{
68 fn default() -> Self {
69 Self {
70 inner: Recovered::new_unchecked(Default::default(), Default::default()),
71 block_hash: Default::default(),
72 block_number: Default::default(),
73 transaction_index: Default::default(),
74 effective_gas_price: Default::default(),
75 }
76 }
77}
78
79impl<T> Transaction<T> {
80 pub fn into_inner(self) -> T {
82 self.inner.into_inner()
83 }
84
85 pub fn into_recovered(self) -> Recovered<T> {
87 self.inner
88 }
89
90 pub const fn as_recovered(&self) -> Recovered<&T> {
92 self.inner.as_recovered_ref()
93 }
94
95 pub fn convert<U>(self) -> Transaction<U>
97 where
98 U: From<T>,
99 {
100 self.map(U::from)
101 }
102
103 pub fn try_convert<U>(self) -> Result<Transaction<U>, U::Error>
107 where
108 U: TryFrom<T>,
109 {
110 self.try_map(U::try_from)
111 }
112
113 pub fn map<Tx>(self, f: impl FnOnce(T) -> Tx) -> Transaction<Tx> {
115 let Self { inner, block_hash, block_number, transaction_index, effective_gas_price } = self;
116 Transaction {
117 inner: inner.map(f),
118 block_hash,
119 block_number,
120 transaction_index,
121 effective_gas_price,
122 }
123 }
124
125 pub fn try_map<Tx, E>(self, f: impl FnOnce(T) -> Result<Tx, E>) -> Result<Transaction<Tx>, E> {
127 let Self { inner, block_hash, block_number, transaction_index, effective_gas_price } = self;
128 Ok(Transaction {
129 inner: inner.try_map(f)?,
130 block_hash,
131 block_number,
132 transaction_index,
133 effective_gas_price,
134 })
135 }
136}
137
138impl<T> AsRef<T> for Transaction<T> {
139 fn as_ref(&self) -> &T {
140 &self.inner
141 }
142}
143
144impl<T> Transaction<T>
145where
146 T: TransactionTrait,
147{
148 pub fn is_legacy_gas(&self) -> bool {
150 self.inner.gas_price().is_some()
151 }
152}
153
154impl<T> Transaction<T>
155where
156 T: TransactionTrait + Encodable2718,
157{
158 pub fn info(&self) -> TransactionInfo {
162 TransactionInfo {
163 hash: Some(self.tx_hash()),
164 index: self.transaction_index,
165 block_hash: self.block_hash,
166 block_number: self.block_number,
167 base_fee: None,
170 }
171 }
172}
173
174impl<T> Transaction<T>
175where
176 T: Into<TransactionRequest>,
177{
178 pub fn into_request(self) -> TransactionRequest {
183 self.inner.into_inner().into()
184 }
185}
186
187impl<Eip4844> Transaction<EthereumTxEnvelope<Eip4844>> {
188 pub fn into_signed(self) -> Signed<EthereumTypedTransaction<Eip4844>>
191 where
192 EthereumTypedTransaction<Eip4844>: From<Eip4844>,
193 {
194 self.inner.into_inner().into_signed()
195 }
196
197 pub fn into_signed_recovered(self) -> Recovered<Signed<EthereumTypedTransaction<Eip4844>>>
199 where
200 EthereumTypedTransaction<Eip4844>: From<Eip4844>,
201 {
202 self.inner.map(|tx| tx.into_signed())
203 }
204}
205
206impl<T> From<&Transaction<T>> for TransactionInfo
207where
208 T: TransactionTrait + Encodable2718,
209{
210 fn from(tx: &Transaction<T>) -> Self {
211 tx.info()
212 }
213}
214
215impl<T> From<Transaction<T>> for Recovered<T> {
216 fn from(tx: Transaction<T>) -> Self {
217 tx.into_recovered()
218 }
219}
220
221impl<Eip4844> TryFrom<Transaction<EthereumTxEnvelope<Eip4844>>> for Signed<TxLegacy> {
222 type Error = ConversionError;
223
224 fn try_from(tx: Transaction<EthereumTxEnvelope<Eip4844>>) -> Result<Self, Self::Error> {
225 match tx.inner.into_inner() {
226 EthereumTxEnvelope::Legacy(tx) => Ok(tx),
227 tx => Err(ConversionError::Custom(format!("expected Legacy, got {}", tx.tx_type()))),
228 }
229 }
230}
231
232impl<Eip4844> TryFrom<Transaction<EthereumTxEnvelope<Eip4844>>> for Signed<TxEip1559> {
233 type Error = ConversionError;
234
235 fn try_from(tx: Transaction<EthereumTxEnvelope<Eip4844>>) -> Result<Self, Self::Error> {
236 match tx.inner.into_inner() {
237 EthereumTxEnvelope::Eip1559(tx) => Ok(tx),
238 tx => Err(ConversionError::Custom(format!("expected Eip1559, got {}", tx.tx_type()))),
239 }
240 }
241}
242
243impl<Eip4844> TryFrom<Transaction<EthereumTxEnvelope<Eip4844>>> for Signed<TxEip2930> {
244 type Error = ConversionError;
245
246 fn try_from(tx: Transaction<EthereumTxEnvelope<Eip4844>>) -> Result<Self, Self::Error> {
247 match tx.inner.into_inner() {
248 EthereumTxEnvelope::Eip2930(tx) => Ok(tx),
249 tx => Err(ConversionError::Custom(format!("expected Eip2930, got {}", tx.tx_type()))),
250 }
251 }
252}
253
254impl TryFrom<Transaction> for Signed<TxEip4844> {
255 type Error = ConversionError;
256
257 fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
258 let tx: Signed<TxEip4844Variant> = tx.try_into()?;
259
260 let (tx, sig, hash) = tx.into_parts();
261
262 Ok(Self::new_unchecked(tx.into(), sig, hash))
263 }
264}
265
266impl TryFrom<Transaction> for Signed<TxEip4844Variant> {
267 type Error = ConversionError;
268
269 fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
270 match tx.inner.into_inner() {
271 TxEnvelope::Eip4844(tx) => Ok(tx),
272 tx => Err(ConversionError::Custom(format!(
273 "expected TxEip4844Variant, got {}",
274 tx.tx_type()
275 ))),
276 }
277 }
278}
279
280impl<Eip4844> TryFrom<Transaction<EthereumTxEnvelope<Eip4844>>> for Signed<TxEip7702> {
281 type Error = ConversionError;
282
283 fn try_from(tx: Transaction<EthereumTxEnvelope<Eip4844>>) -> Result<Self, Self::Error> {
284 match tx.inner.into_inner() {
285 EthereumTxEnvelope::Eip7702(tx) => Ok(tx),
286 tx => Err(ConversionError::Custom(format!("expected Eip7702, got {}", tx.tx_type()))),
287 }
288 }
289}
290
291impl<Eip4844> From<Transaction<Self>> for EthereumTxEnvelope<Eip4844> {
292 fn from(tx: Transaction<Self>) -> Self {
293 tx.inner.into_inner()
294 }
295}
296
297impl<Eip4844> From<Transaction<Self>> for EthereumTypedTransaction<Eip4844> {
298 fn from(tx: Transaction<Self>) -> Self {
299 tx.inner.into_inner()
300 }
301}
302
303impl<Eip4844> From<Transaction<EthereumTxEnvelope<Eip4844>>>
304 for Signed<EthereumTypedTransaction<Eip4844>>
305where
306 EthereumTypedTransaction<Eip4844>: From<Eip4844>,
307{
308 fn from(tx: Transaction<EthereumTxEnvelope<Eip4844>>) -> Self {
309 tx.into_signed()
310 }
311}
312
313impl<T: TransactionTrait> TransactionTrait for Transaction<T> {
314 fn chain_id(&self) -> Option<ChainId> {
315 self.inner.chain_id()
316 }
317
318 fn nonce(&self) -> u64 {
319 self.inner.nonce()
320 }
321
322 fn gas_limit(&self) -> u64 {
323 self.inner.gas_limit()
324 }
325
326 fn gas_price(&self) -> Option<u128> {
327 self.inner.gas_price()
328 }
329
330 fn max_fee_per_gas(&self) -> u128 {
331 self.inner.max_fee_per_gas()
332 }
333
334 fn max_priority_fee_per_gas(&self) -> Option<u128> {
335 self.inner.max_priority_fee_per_gas()
336 }
337
338 fn max_fee_per_blob_gas(&self) -> Option<u128> {
339 self.inner.max_fee_per_blob_gas()
340 }
341
342 fn priority_fee_or_price(&self) -> u128 {
343 self.inner.priority_fee_or_price()
344 }
345
346 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
347 self.inner.effective_gas_price(base_fee)
348 }
349
350 fn is_dynamic_fee(&self) -> bool {
351 self.inner.is_dynamic_fee()
352 }
353
354 fn kind(&self) -> TxKind {
355 self.inner.kind()
356 }
357
358 fn is_create(&self) -> bool {
359 self.inner.is_create()
360 }
361
362 fn value(&self) -> U256 {
363 self.inner.value()
364 }
365
366 fn input(&self) -> &Bytes {
367 self.inner.input()
368 }
369
370 fn access_list(&self) -> Option<&AccessList> {
371 self.inner.access_list()
372 }
373
374 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
375 self.inner.blob_versioned_hashes()
376 }
377
378 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
379 self.inner.authorization_list()
380 }
381}
382
383impl<T: TransactionTrait + Encodable2718> TransactionResponse for Transaction<T> {
384 fn tx_hash(&self) -> B256 {
385 self.inner.trie_hash()
386 }
387
388 fn block_hash(&self) -> Option<BlockHash> {
389 self.block_hash
390 }
391
392 fn block_number(&self) -> Option<u64> {
393 self.block_number
394 }
395
396 fn transaction_index(&self) -> Option<u64> {
397 self.transaction_index
398 }
399
400 fn from(&self) -> Address {
401 self.inner.signer()
402 }
403}
404
405impl<T: Typed2718> Typed2718 for Transaction<T> {
406 fn ty(&self) -> u8 {
407 self.inner.ty()
408 }
409}
410
411#[cfg(feature = "serde")]
412mod tx_serde {
413 use super::*;
418 use serde::{Deserialize, Serialize};
419
420 #[derive(Serialize, Deserialize)]
423 struct MaybeGasPrice {
424 #[serde(
425 default,
426 rename = "gasPrice",
427 skip_serializing_if = "Option::is_none",
428 with = "alloy_serde::quantity::opt"
429 )]
430 pub effective_gas_price: Option<u128>,
431 }
432
433 #[derive(Serialize, Deserialize)]
434 #[serde(rename_all = "camelCase")]
435 pub(crate) struct TransactionSerdeHelper<T> {
436 #[serde(flatten)]
437 inner: T,
438 #[serde(default)]
439 block_hash: Option<BlockHash>,
440 #[serde(default, with = "alloy_serde::quantity::opt")]
441 block_number: Option<u64>,
442 #[serde(default, with = "alloy_serde::quantity::opt")]
443 transaction_index: Option<u64>,
444 from: Address,
446
447 #[serde(flatten)]
448 gas_price: MaybeGasPrice,
449 }
450
451 impl<T: TransactionTrait> From<Transaction<T>> for TransactionSerdeHelper<T> {
452 fn from(value: Transaction<T>) -> Self {
453 let Transaction {
454 inner,
455 block_hash,
456 block_number,
457 transaction_index,
458 effective_gas_price,
459 } = value;
460
461 let (inner, from) = inner.into_parts();
462
463 let effective_gas_price = effective_gas_price.filter(|_| inner.gas_price().is_none());
465
466 Self {
467 inner,
468 block_hash,
469 block_number,
470 transaction_index,
471 from,
472 gas_price: MaybeGasPrice { effective_gas_price },
473 }
474 }
475 }
476
477 impl<T: TransactionTrait> TryFrom<TransactionSerdeHelper<T>> for Transaction<T> {
478 type Error = serde_json::Error;
479
480 fn try_from(value: TransactionSerdeHelper<T>) -> Result<Self, Self::Error> {
481 let TransactionSerdeHelper {
482 inner,
483 block_hash,
484 block_number,
485 transaction_index,
486 from,
487 gas_price,
488 } = value;
489
490 let effective_gas_price = inner.gas_price().or(gas_price.effective_gas_price);
493
494 Ok(Self {
495 inner: Recovered::new_unchecked(inner, from),
496 block_hash,
497 block_number,
498 transaction_index,
499 effective_gas_price,
500 })
501 }
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 #[allow(unused)]
510 fn assert_convert_into_envelope(tx: Transaction) -> TxEnvelope {
511 tx.into()
512 }
513
514 #[test]
515 #[cfg(feature = "serde")]
516 fn into_request_legacy() {
517 let rpc_tx = r#"{"blockHash":"0x8e38b4dbf6b11fcc3b9dee84fb7986e29ca0a02cecd8977c161ff7333329681e","blockNumber":"0xf4240","hash":"0xe9e91f1ee4b56c0df2e9f06c2b8c27c6076195a88a7b8537ba8313d80e6f124e","transactionIndex":"0x1","type":"0x0","nonce":"0x43eb","input":"0x","r":"0x3b08715b4403c792b8c7567edea634088bedcd7f60d9352b1f16c69830f3afd5","s":"0x10b9afb67d2ec8b956f0e1dbc07eb79152904f3a7bf789fc869db56320adfe09","chainId":"0x0","v":"0x1c","gas":"0xc350","from":"0x32be343b94f860124dc4fee278fdcbd38c102d88","to":"0xdf190dc7190dfba737d7777a163445b7fff16133","value":"0x6113a84987be800","gasPrice":"0xdf8475800"}"#;
520
521 let tx = serde_json::from_str::<Transaction>(rpc_tx).unwrap();
522 let request = tx.into_request();
523 assert!(request.gas_price.is_some());
524 assert!(request.max_fee_per_gas.is_none());
525 }
526
527 #[test]
528 #[cfg(feature = "serde")]
529 fn into_request_eip1559() {
530 let rpc_tx = r#"{"blockHash":"0x883f974b17ca7b28cb970798d1c80f4d4bb427473dc6d39b2a7fe24edc02902d","blockNumber":"0xe26e6d","hash":"0x0e07d8b53ed3d91314c80e53cf25bcde02084939395845cbb625b029d568135c","accessList":[],"transactionIndex":"0xad","type":"0x2","nonce":"0x16d","input":"0x5ae401dc00000000000000000000000000000000000000000000000000000000628ced5b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e442712a6700000000000000000000000000000000000000000000b3ff1489674e11c40000000000000000000000000000000000000000000000000000004a6ed55bbcc18000000000000000000000000000000000000000000000000000000000000000800000000000000000000000003cf412d970474804623bb4e3a42de13f9bca54360000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003a75941763f31c930b19c041b709742b0b31ebb600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000412210e8a00000000000000000000000000000000000000000000000000000000","r":"0x7f2153019a74025d83a73effdd91503ceecefac7e35dd933adc1901c875539aa","s":"0x334ab2f714796d13c825fddf12aad01438db3a8152b2fe3ef7827707c25ecab3","chainId":"0x1","v":"0x0","gas":"0x46a02","maxPriorityFeePerGas":"0x59682f00","from":"0x3cf412d970474804623bb4e3a42de13f9bca5436","to":"0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45","maxFeePerGas":"0x7fc1a20a8","value":"0x4a6ed55bbcc180","gasPrice":"0x50101df3a"}"#;
533
534 let tx = serde_json::from_str::<Transaction>(rpc_tx).unwrap();
535 let request = tx.into_request();
536 assert!(request.gas_price.is_none());
537 assert!(request.max_fee_per_gas.is_some());
538 }
539
540 #[test]
541 #[cfg(feature = "serde")]
542 fn serde_tx_from_contract_mod() {
543 let rpc_tx = r#"{"hash":"0x018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f","nonce":"0x1","blockHash":"0x6e4e53d1de650d5a5ebed19b38321db369ef1dc357904284ecf4d89b8834969c","blockNumber":"0x2","transactionIndex":"0x0","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","value":"0x0","gasPrice":"0x3a29f0f8","gas":"0x1c9c380","maxFeePerGas":"0xba43b7400","maxPriorityFeePerGas":"0x5f5e100","input":"0xd09de08a","r":"0xd309309a59a49021281cb6bb41d164c96eab4e50f0c1bd24c03ca336e7bc2bb7","s":"0x28a7f089143d0a1355ebeb2a1b9f0e5ad9eca4303021c1400d61bc23c9ac5319","v":"0x0","yParity":"0x0","chainId":"0x7a69","accessList":[],"type":"0x2"}"#;
544
545 let tx = serde_json::from_str::<Transaction>(rpc_tx).unwrap();
546 assert_eq!(tx.block_number, Some(2));
547 }
548
549 #[test]
550 #[cfg(feature = "serde")]
551 fn test_gas_price_present() {
552 let blob_rpc_tx = r#"{"blockHash":"0x1732a5fe86d54098c431fa4fea34387b650e41dbff65ca554370028172fcdb6a","blockNumber":"0x3","from":"0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f","gas":"0x186a0","gasPrice":"0x281d620e","maxFeePerGas":"0x281d620e","maxPriorityFeePerGas":"0x1","maxFeePerBlobGas":"0x20000","hash":"0xb0ebf0d8fca6724d5111d0be9ac61f0e7bf174208e0fafcb653f337c72465b83","input":"0xdc4c8669df128318656d6974","nonce":"0x8","to":"0x7dcd17433742f4c0ca53122ab541d0ba67fc27df","transactionIndex":"0x0","value":"0x3","type":"0x3","accessList":[{"address":"0x7dcd17433742f4c0ca53122ab541d0ba67fc27df","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x462708a3c1cd03b21605715d090136df64e227f7e7792f74bb1bd7a8288f8801"]}],"chainId":"0xc72dd9d5e883e","blobVersionedHashes":["0x015a4cab4911426699ed34483de6640cf55a568afc5c5edffdcbd8bcd4452f68"],"v":"0x0","r":"0x478385a47075dd6ba56300b623038052a6e4bb03f8cfc53f367712f1c1d3e7de","s":"0x2f79ed9b154b0af2c97ddfc1f4f76e6c17725713b6d44ea922ca4c6bbc20775c","yParity":"0x0"}"#;
553 let legacy_rpc_tx = r#"{"blockHash":"0x7e5d03caac4eb2b613ae9c919ef3afcc8ed0e384f31ee746381d3c8739475d2a","blockNumber":"0x4","from":"0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f","gas":"0x5208","gasPrice":"0x23237dee","hash":"0x3f38cdc805c02e152bfed34471a3a13a786fed436b3aec0c3eca35d23e2cdd2c","input":"0x","nonce":"0xc","to":"0x4dde844b71bcdf95512fb4dc94e84fb67b512ed8","transactionIndex":"0x0","value":"0x1","type":"0x0","chainId":"0xc72dd9d5e883e","v":"0x18e5bb3abd10a0","r":"0x3d61f5d7e93eecd0669a31eb640ab3349e9e5868a44c2be1337c90a893b51990","s":"0xc55f44ba123af37d0e73ed75e578647c3f473805349936f64ea902ea9e03bc7"}"#;
554
555 let blob_tx = serde_json::from_str::<Transaction>(blob_rpc_tx).unwrap();
556 assert_eq!(blob_tx.block_number, Some(3));
557 assert_eq!(blob_tx.effective_gas_price, Some(0x281d620e));
558
559 let legacy_tx = serde_json::from_str::<Transaction>(legacy_rpc_tx).unwrap();
560 assert_eq!(legacy_tx.block_number, Some(4));
561 assert_eq!(legacy_tx.effective_gas_price, Some(0x23237dee));
562 }
563
564 #[test]
566 #[cfg(feature = "serde")]
567 fn deserialize_7702_v() {
568 let raw = r#"{"blockHash":"0xb14eac260f0cb7c3bbf4c9ff56034defa4f566780ed3e44b7a79b6365d02887c","blockNumber":"0xb022","from":"0x6d2d4e1c2326a069f36f5d6337470dc26adb7156","gas":"0xf8ac","gasPrice":"0xe07899f","maxFeePerGas":"0xe0789a0","maxPriorityFeePerGas":"0xe078998","hash":"0xadc3f24d05f05f1065debccb1c4b033eaa35917b69b343d88d9062cdf8ecad83","input":"0x","nonce":"0x1a","to":"0x6d2d4e1c2326a069f36f5d6337470dc26adb7156","transactionIndex":"0x0","value":"0x0","type":"0x4","accessList":[],"chainId":"0x1a5ee289c","authorizationList":[{"chainId":"0x1a5ee289c","address":"0x529f773125642b12a44bd543005650989eceaa2a","nonce":"0x1a","v":"0x0","r":"0x9b3de20cf8bd07f3c5c55c38c920c146f081bc5ab4580d0c87786b256cdab3c2","s":"0x74841956f4832bace3c02aed34b8f0a2812450da3728752edbb5b5e1da04497"}],"v":"0x1","r":"0xb3bf7d6877864913bba04d6f93d98009a5af16ee9c12295cd634962a2346b67c","s":"0x31ca4a874afa964ec7643e58c6b56b35b1bcc7698eb1b5e15e61e78b353bd42d","yParity":"0x1"}"#;
569 let tx = serde_json::from_str::<Transaction>(raw).unwrap();
570 assert!(tx.inner.is_eip7702());
571 }
572}