1use alloy_consensus::{Signed, TxEip1559, TxEip2930, TxEip7702, TxLegacy, error::ValueError};
2use alloy_contract::{CallBuilder, CallDecoder};
3use alloy_eips::Typed2718;
4use alloy_primitives::{Address, Bytes, U256};
5use alloy_provider::Provider;
6use alloy_rpc_types_eth::{TransactionRequest, TransactionTrait};
7use serde::{Deserialize, Serialize};
8use tempo_primitives::{
9 AASigned, SignatureType, TempoTransaction, TempoTxEnvelope, TxFeeToken,
10 transaction::{Call, TempoSignedAuthorization, TempoTypedTransaction},
11};
12
13use crate::TempoNetwork;
14
15#[derive(
17 Clone,
18 Debug,
19 Default,
20 PartialEq,
21 Eq,
22 Hash,
23 Serialize,
24 Deserialize,
25 derive_more::Deref,
26 derive_more::DerefMut,
27)]
28#[serde(rename_all = "camelCase")]
29pub struct TempoTransactionRequest {
30 #[serde(flatten)]
32 #[deref]
33 #[deref_mut]
34 pub inner: TransactionRequest,
35
36 pub fee_token: Option<Address>,
38
39 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub nonce_key: Option<U256>,
42
43 #[serde(default)]
45 pub calls: Vec<Call>,
46
47 pub key_type: Option<SignatureType>,
50
51 pub key_data: Option<Bytes>,
54
55 #[serde(
57 default,
58 skip_serializing_if = "Vec::is_empty",
59 rename = "aaAuthorizationList"
60 )]
61 pub tempo_authorization_list: Vec<TempoSignedAuthorization>,
62}
63
64impl TempoTransactionRequest {
65 pub fn with_fee_token(mut self, fee_token: Address) -> Self {
67 self.fee_token = Some(fee_token);
68 self
69 }
70
71 pub fn set_nonce_key(&mut self, nonce_key: U256) {
73 self.nonce_key = Some(nonce_key);
74 }
75
76 pub fn with_nonce_key(mut self, nonce_key: U256) -> Self {
78 self.nonce_key = Some(nonce_key);
79 self
80 }
81
82 pub fn build_fee_token(self) -> Result<TxFeeToken, ValueError<Self>> {
83 let Some(to) = self.inner.to else {
84 return Err(ValueError::new(
85 self,
86 "Missing 'to' field for FeeToken transaction.",
87 ));
88 };
89 let Some(nonce) = self.inner.nonce else {
90 return Err(ValueError::new(
91 self,
92 "Missing 'nonce' field for FeeToken transaction.",
93 ));
94 };
95 let Some(gas_limit) = self.inner.gas else {
96 return Err(ValueError::new(
97 self,
98 "Missing 'gas_limit' field for FeeToken transaction.",
99 ));
100 };
101 let Some(max_fee_per_gas) = self.inner.max_fee_per_gas else {
102 return Err(ValueError::new(
103 self,
104 "Missing 'max_fee_per_gas' field for FeeToken transaction.",
105 ));
106 };
107 let Some(max_priority_fee_per_gas) = self.inner.max_priority_fee_per_gas else {
108 return Err(ValueError::new(
109 self,
110 "Missing 'max_priority_fee_per_gas' field for FeeToken transaction.",
111 ));
112 };
113
114 Ok(TxFeeToken {
115 chain_id: self.inner.chain_id.unwrap_or(1),
116 nonce,
117 gas_limit,
118 max_fee_per_gas,
119 max_priority_fee_per_gas,
120 to,
121 fee_token: self.fee_token,
122 value: self.inner.value.unwrap_or_default(),
123 input: self.inner.input.into_input().unwrap_or_default(),
124 access_list: self.inner.access_list.unwrap_or_default(),
125 authorization_list: self.inner.authorization_list.unwrap_or_default(),
126 fee_payer_signature: None,
127 })
128 }
129
130 pub fn build_aa(self) -> Result<TempoTransaction, ValueError<Self>> {
132 if self.calls.is_empty() && self.inner.to.is_none() {
133 return Err(ValueError::new(
134 self,
135 "Missing 'calls' or 'to' field for Tempo transaction.",
136 ));
137 }
138
139 let Some(nonce) = self.inner.nonce else {
140 return Err(ValueError::new(
141 self,
142 "Missing 'nonce' field for Tempo transaction.",
143 ));
144 };
145 let Some(gas_limit) = self.inner.gas else {
146 return Err(ValueError::new(
147 self,
148 "Missing 'gas_limit' field for Tempo transaction.",
149 ));
150 };
151 let Some(max_fee_per_gas) = self.inner.max_fee_per_gas else {
152 return Err(ValueError::new(
153 self,
154 "Missing 'max_fee_per_gas' field for Tempo transaction.",
155 ));
156 };
157 let Some(max_priority_fee_per_gas) = self.inner.max_priority_fee_per_gas else {
158 return Err(ValueError::new(
159 self,
160 "Missing 'max_priority_fee_per_gas' field for Tempo transaction.",
161 ));
162 };
163
164 let mut calls = self.calls;
165 if let Some(to) = self.inner.to {
166 calls.push(Call {
167 to,
168 value: self.inner.value.unwrap_or_default(),
169 input: self.inner.input.into_input().unwrap_or_default(),
170 });
171 }
172
173 Ok(TempoTransaction {
174 chain_id: self.inner.chain_id.unwrap_or(1),
176 nonce,
177 fee_payer_signature: None,
178 valid_before: None,
179 valid_after: None,
180 gas_limit,
181 max_fee_per_gas,
182 max_priority_fee_per_gas,
183 fee_token: self.fee_token,
184 access_list: self.inner.access_list.unwrap_or_default(),
185 calls,
186 tempo_authorization_list: self.tempo_authorization_list,
187 nonce_key: self.nonce_key.unwrap_or_default(),
188 key_authorization: None,
189 })
190 }
191}
192
193impl AsRef<TransactionRequest> for TempoTransactionRequest {
194 fn as_ref(&self) -> &TransactionRequest {
195 &self.inner
196 }
197}
198
199impl AsMut<TransactionRequest> for TempoTransactionRequest {
200 fn as_mut(&mut self) -> &mut TransactionRequest {
201 &mut self.inner
202 }
203}
204
205impl From<TransactionRequest> for TempoTransactionRequest {
206 fn from(value: TransactionRequest) -> Self {
207 Self {
208 inner: value,
209 fee_token: None,
210 ..Default::default()
211 }
212 }
213}
214
215impl From<TempoTransactionRequest> for TransactionRequest {
216 fn from(value: TempoTransactionRequest) -> Self {
217 value.inner
218 }
219}
220
221impl From<TempoTxEnvelope> for TempoTransactionRequest {
222 fn from(value: TempoTxEnvelope) -> Self {
223 match value {
224 TempoTxEnvelope::Legacy(tx) => tx.into(),
225 TempoTxEnvelope::Eip2930(tx) => tx.into(),
226 TempoTxEnvelope::Eip1559(tx) => tx.into(),
227 TempoTxEnvelope::Eip7702(tx) => tx.into(),
228 TempoTxEnvelope::FeeToken(tx) => tx.into(),
229 TempoTxEnvelope::AA(tx) => tx.into(),
230 }
231 }
232}
233
234pub trait FeeToken {
235 fn fee_token(&self) -> Option<Address>;
236}
237
238impl FeeToken for TxFeeToken {
239 fn fee_token(&self) -> Option<Address> {
240 self.fee_token
241 }
242}
243
244impl FeeToken for TempoTransaction {
245 fn fee_token(&self) -> Option<Address> {
246 self.fee_token
247 }
248}
249
250impl FeeToken for TxEip7702 {
251 fn fee_token(&self) -> Option<Address> {
252 None
253 }
254}
255
256impl FeeToken for TxEip1559 {
257 fn fee_token(&self) -> Option<Address> {
258 None
259 }
260}
261
262impl FeeToken for TxEip2930 {
263 fn fee_token(&self) -> Option<Address> {
264 None
265 }
266}
267
268impl FeeToken for TxLegacy {
269 fn fee_token(&self) -> Option<Address> {
270 None
271 }
272}
273
274impl<T: TransactionTrait + FeeToken> From<Signed<T>> for TempoTransactionRequest {
275 fn from(value: Signed<T>) -> Self {
276 Self {
277 fee_token: value.tx().fee_token(),
278 inner: TransactionRequest::from_transaction(value),
279 ..Default::default()
280 }
281 }
282}
283
284impl From<TempoTransaction> for TempoTransactionRequest {
285 fn from(tx: TempoTransaction) -> Self {
286 Self {
287 fee_token: tx.fee_token,
288 inner: TransactionRequest {
289 from: None,
290 to: Some(tx.kind()),
291 gas: Some(tx.gas_limit()),
292 gas_price: tx.gas_price(),
293 max_fee_per_gas: Some(tx.max_fee_per_gas()),
294 max_priority_fee_per_gas: tx.max_priority_fee_per_gas(),
295 value: Some(tx.value()),
296 input: alloy_rpc_types_eth::TransactionInput::new(tx.input().clone()),
297 nonce: Some(tx.nonce()),
298 chain_id: tx.chain_id(),
299 access_list: tx.access_list().cloned(),
300 max_fee_per_blob_gas: None,
301 blob_versioned_hashes: None,
302 sidecar: None,
303 authorization_list: None,
304 transaction_type: Some(tx.ty()),
305 },
306 calls: tx.calls,
307 tempo_authorization_list: tx.tempo_authorization_list,
308 key_type: None,
309 key_data: None,
310 nonce_key: Some(tx.nonce_key),
311 }
312 }
313}
314
315impl From<AASigned> for TempoTransactionRequest {
316 fn from(value: AASigned) -> Self {
317 value.into_parts().0.into()
318 }
319}
320
321impl From<TempoTypedTransaction> for TempoTransactionRequest {
322 fn from(value: TempoTypedTransaction) -> Self {
323 match value {
324 TempoTypedTransaction::Legacy(tx) => Self {
325 inner: tx.into(),
326 fee_token: None,
327 ..Default::default()
328 },
329 TempoTypedTransaction::Eip2930(tx) => Self {
330 inner: tx.into(),
331 fee_token: None,
332 ..Default::default()
333 },
334 TempoTypedTransaction::Eip1559(tx) => Self {
335 inner: tx.into(),
336 fee_token: None,
337 ..Default::default()
338 },
339 TempoTypedTransaction::Eip7702(tx) => Self {
340 inner: tx.into(),
341 fee_token: None,
342 ..Default::default()
343 },
344 TempoTypedTransaction::FeeToken(tx) => Self {
345 fee_token: tx.fee_token,
346 inner: TransactionRequest::from_transaction(tx),
347 ..Default::default()
348 },
349 TempoTypedTransaction::AA(tx) => tx.into(),
350 }
351 }
352}
353
354pub trait TempoCallBuilderExt {
356 fn fee_token(self, fee_token: Address) -> Self;
358
359 fn nonce_key(self, nonce_key: U256) -> Self;
361}
362
363impl<P: Provider<TempoNetwork>, D: CallDecoder> TempoCallBuilderExt
364 for CallBuilder<P, D, TempoNetwork>
365{
366 fn fee_token(self, fee_token: Address) -> Self {
367 self.map(|request| request.with_fee_token(fee_token))
368 }
369
370 fn nonce_key(self, nonce_key: U256) -> Self {
371 self.map(|request| request.with_nonce_key(nonce_key))
372 }
373}