foundry_common/transactions/
builder.rs1use std::num::NonZeroU64;
2
3use alloy_consensus::{
4 BlobTransactionSidecar, BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant,
5};
6use alloy_eips::{Encodable2718, eip7702::SignedAuthorization};
7use alloy_network::{AnyNetwork, Ethereum, Network, NetworkTransactionBuilder};
8use alloy_primitives::{Address, B256, Signature, TxKind, U256};
9use alloy_provider::Provider;
10use alloy_signer::Signer;
11use eyre::Result;
12#[cfg(feature = "optimism")]
13use op_alloy_network::Optimism;
14#[cfg(feature = "optimism")]
15use op_alloy_rpc_types::OpTransactionRequest;
16use tempo_alloy::{TempoNetwork, provider::TempoProviderExt};
17use tempo_primitives::{
18 TempoSignature, TempoTxType,
19 transaction::{Call, KeychainSignature, PrimitiveSignature, SignedKeyAuthorization},
20};
21
22pub trait FoundryTransactionBuilder<N: Network>: NetworkTransactionBuilder<N> {
50 fn reset_gas_limit(&mut self);
52
53 fn max_fee_per_blob_gas(&self) -> Option<u128> {
55 None
56 }
57
58 fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {}
60
61 fn with_max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
63 self.set_max_fee_per_blob_gas(max_fee_per_blob_gas);
64 self
65 }
66
67 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
72 None
73 }
74
75 fn set_blob_versioned_hashes(&mut self, _hashes: Vec<B256>) {}
77
78 fn with_blob_versioned_hashes(mut self, hashes: Vec<B256>) -> Self {
80 self.set_blob_versioned_hashes(hashes);
81 self
82 }
83
84 fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> {
86 None
87 }
88
89 fn set_blob_sidecar(&mut self, _sidecar: BlobTransactionSidecarVariant) {}
94
95 fn with_blob_sidecar(mut self, sidecar: BlobTransactionSidecarVariant) -> Self {
97 self.set_blob_sidecar(sidecar);
98 self
99 }
100
101 fn blob_sidecar_4844(&self) -> Option<&BlobTransactionSidecar> {
103 self.blob_sidecar().and_then(|s| s.as_eip4844())
104 }
105
106 fn set_blob_sidecar_4844(&mut self, sidecar: BlobTransactionSidecar) {
108 self.set_blob_sidecar(BlobTransactionSidecarVariant::Eip4844(sidecar));
109 }
110
111 fn with_blob_sidecar_4844(mut self, sidecar: BlobTransactionSidecar) -> Self {
113 self.set_blob_sidecar_4844(sidecar);
114 self
115 }
116
117 fn blob_sidecar_7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
119 self.blob_sidecar().and_then(|s| s.as_eip7594())
120 }
121
122 fn set_blob_sidecar_7594(&mut self, sidecar: BlobTransactionSidecarEip7594) {
124 self.set_blob_sidecar(BlobTransactionSidecarVariant::Eip7594(sidecar));
125 }
126
127 fn with_blob_sidecar_7594(mut self, sidecar: BlobTransactionSidecarEip7594) -> Self {
129 self.set_blob_sidecar_7594(sidecar);
130 self
131 }
132
133 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
135 None
136 }
137
138 fn set_authorization_list(&mut self, _authorization_list: Vec<SignedAuthorization>) {}
140
141 fn with_authorization_list(mut self, authorization_list: Vec<SignedAuthorization>) -> Self {
143 self.set_authorization_list(authorization_list);
144 self
145 }
146
147 fn fee_token(&self) -> Option<Address> {
149 None
150 }
151
152 fn tempo_calls(&self) -> Vec<(TxKind, &[u8])> {
154 Vec::new()
155 }
156
157 fn has_tempo_call_list(&self) -> bool {
159 false
160 }
161
162 fn is_tempo_aa(&self) -> bool {
164 false
165 }
166
167 fn set_fee_token(&mut self, _fee_token: Address) {}
169
170 fn with_fee_token(mut self, fee_token: Address) -> Self {
172 self.set_fee_token(fee_token);
173 self
174 }
175
176 fn nonce_key(&self) -> Option<U256> {
178 None
179 }
180
181 fn set_nonce_key(&mut self, _nonce_key: U256) {}
183
184 fn with_nonce_key(mut self, nonce_key: U256) -> Self {
186 self.set_nonce_key(nonce_key);
187 self
188 }
189
190 fn key_id(&self) -> Option<Address> {
192 None
193 }
194
195 fn set_key_id(&mut self, _key_id: Address) {}
200
201 fn with_key_id(mut self, key_id: Address) -> Self {
203 self.set_key_id(key_id);
204 self
205 }
206
207 fn valid_before(&self) -> Option<NonZeroU64> {
209 None
210 }
211
212 fn set_valid_before(&mut self, _valid_before: NonZeroU64) {}
214
215 fn with_valid_before(mut self, valid_before: NonZeroU64) -> Self {
217 self.set_valid_before(valid_before);
218 self
219 }
220
221 fn valid_after(&self) -> Option<NonZeroU64> {
223 None
224 }
225
226 fn set_valid_after(&mut self, _valid_after: NonZeroU64) {}
228
229 fn with_valid_after(mut self, valid_after: NonZeroU64) -> Self {
231 self.set_valid_after(valid_after);
232 self
233 }
234
235 fn fee_payer_signature(&self) -> Option<Signature> {
237 None
238 }
239
240 fn set_fee_payer_signature(&mut self, _signature: Signature) {}
242
243 fn with_fee_payer_signature(mut self, signature: Signature) -> Self {
245 self.set_fee_payer_signature(signature);
246 self
247 }
248
249 fn compute_sponsor_hash(&self, _from: Address) -> Option<B256> {
255 None
256 }
257
258 fn set_key_authorization(&mut self, _key_authorization: SignedKeyAuthorization) {}
263
264 fn prepare_access_key_authorization<'a>(
270 &'a mut self,
271 _provider: &'a impl Provider<N>,
272 _wallet_address: Address,
273 _key_address: Address,
274 _key_authorization: Option<&'a SignedKeyAuthorization>,
275 ) -> impl Future<Output = Result<()>> + Send + 'a
276 where
277 Self: Send,
278 {
279 async { Ok(()) }
280 }
281
282 fn convert_create_to_call(&mut self) {}
288
289 fn clear_batch_to(&mut self) {}
296
297 fn sign_with_access_key(
304 self,
305 _provider: &impl Provider<N>,
306 _signer: &(impl Signer + Sync),
307 _wallet_address: Address,
308 _key_address: Address,
309 _key_authorization: Option<&SignedKeyAuthorization>,
310 ) -> impl Future<Output = Result<Vec<u8>>> + Send {
311 async {
312 eyre::bail!("access key signing is not supported for this network");
313 }
314 }
315}
316
317impl FoundryTransactionBuilder<Ethereum> for <Ethereum as Network>::TransactionRequest {
318 fn reset_gas_limit(&mut self) {
319 self.gas = None;
320 }
321
322 fn max_fee_per_blob_gas(&self) -> Option<u128> {
323 self.max_fee_per_blob_gas
324 }
325
326 fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) {
327 self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas);
328 }
329
330 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
331 self.blob_versioned_hashes.as_deref()
332 }
333
334 fn set_blob_versioned_hashes(&mut self, hashes: Vec<B256>) {
335 self.blob_versioned_hashes = Some(hashes);
336 }
337
338 fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> {
339 self.sidecar.as_ref()
340 }
341
342 fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecarVariant) {
343 self.sidecar = Some(sidecar);
344 self.populate_blob_hashes();
345 }
346
347 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
348 self.authorization_list.as_ref()
349 }
350
351 fn set_authorization_list(&mut self, authorization_list: Vec<SignedAuthorization>) {
352 self.authorization_list = Some(authorization_list);
353 }
354}
355
356impl FoundryTransactionBuilder<AnyNetwork> for <AnyNetwork as Network>::TransactionRequest {
357 fn reset_gas_limit(&mut self) {
358 self.gas = None;
359 }
360
361 fn max_fee_per_blob_gas(&self) -> Option<u128> {
362 self.max_fee_per_blob_gas
363 }
364
365 fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) {
366 self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas);
367 }
368
369 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
370 self.blob_versioned_hashes.as_deref()
371 }
372
373 fn set_blob_versioned_hashes(&mut self, hashes: Vec<B256>) {
374 self.blob_versioned_hashes = Some(hashes);
375 }
376
377 fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> {
378 self.sidecar.as_ref()
379 }
380
381 fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecarVariant) {
382 self.sidecar = Some(sidecar);
383 self.populate_blob_hashes();
384 }
385
386 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
387 self.authorization_list.as_ref()
388 }
389
390 fn set_authorization_list(&mut self, authorization_list: Vec<SignedAuthorization>) {
391 self.authorization_list = Some(authorization_list);
392 }
393}
394
395#[cfg(feature = "optimism")]
396impl FoundryTransactionBuilder<Optimism> for OpTransactionRequest {
397 fn reset_gas_limit(&mut self) {
398 self.as_mut().gas = None;
399 }
400
401 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
402 self.as_ref().authorization_list.as_ref()
403 }
404
405 fn set_authorization_list(&mut self, authorization_list: Vec<SignedAuthorization>) {
406 self.as_mut().authorization_list = Some(authorization_list);
407 }
408}
409
410impl FoundryTransactionBuilder<TempoNetwork> for <TempoNetwork as Network>::TransactionRequest {
411 fn reset_gas_limit(&mut self) {
412 self.gas = None;
413 }
414
415 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
416 self.authorization_list.as_ref()
417 }
418
419 fn set_authorization_list(&mut self, authorization_list: Vec<SignedAuthorization>) {
420 self.authorization_list = Some(authorization_list);
421 }
422
423 fn fee_token(&self) -> Option<Address> {
424 self.fee_token
425 }
426
427 fn tempo_calls(&self) -> Vec<(TxKind, &[u8])> {
428 self.calls
429 .iter()
430 .map(|call| (call.to, call.input.as_ref()))
431 .chain(self.inner.to.map(|to| {
432 (to, self.inner.input.input().map_or(&[] as &[u8], |input| input.as_ref()))
433 }))
434 .collect()
435 }
436
437 fn has_tempo_call_list(&self) -> bool {
438 !self.calls.is_empty()
439 }
440
441 fn is_tempo_aa(&self) -> bool {
442 NetworkTransactionBuilder::<TempoNetwork>::output_tx_type(self) == TempoTxType::AA
443 }
444
445 fn set_fee_token(&mut self, fee_token: Address) {
446 self.fee_token = Some(fee_token);
447 }
448
449 fn nonce_key(&self) -> Option<U256> {
450 self.nonce_key
451 }
452
453 fn set_nonce_key(&mut self, nonce_key: U256) {
454 self.nonce_key = Some(nonce_key);
455 }
456
457 fn key_id(&self) -> Option<Address> {
458 self.key_id
459 }
460
461 fn set_key_id(&mut self, key_id: Address) {
462 self.key_id = Some(key_id);
463 }
464
465 fn valid_before(&self) -> Option<NonZeroU64> {
466 self.valid_before
467 }
468
469 fn set_valid_before(&mut self, valid_before: NonZeroU64) {
470 self.valid_before = Some(valid_before);
471 }
472
473 fn valid_after(&self) -> Option<NonZeroU64> {
474 self.valid_after
475 }
476
477 fn set_valid_after(&mut self, valid_after: NonZeroU64) {
478 self.valid_after = Some(valid_after);
479 }
480
481 fn fee_payer_signature(&self) -> Option<Signature> {
482 self.fee_payer_signature
483 }
484
485 fn set_fee_payer_signature(&mut self, signature: Signature) {
486 self.fee_payer_signature = Some(signature);
487 }
488
489 fn compute_sponsor_hash(&self, from: Address) -> Option<B256> {
490 let tx = self.clone().build_aa().ok()?;
491 Some(tx.fee_payer_signature_hash(from))
492 }
493
494 fn set_key_authorization(&mut self, key_authorization: SignedKeyAuthorization) {
495 self.key_authorization = Some(key_authorization);
496 }
497
498 fn prepare_access_key_authorization<'a>(
499 &'a mut self,
500 provider: &'a impl Provider<TempoNetwork>,
501 wallet_address: Address,
502 key_address: Address,
503 key_authorization: Option<&'a SignedKeyAuthorization>,
504 ) -> impl Future<Output = Result<()>> + Send + 'a
505 where
506 Self: Send,
507 {
508 let auth = key_authorization.cloned();
509
510 async move {
511 if let Some(auth) = auth {
512 let is_provisioned = provider
513 .get_keychain_key(wallet_address, key_address)
514 .await
515 .map(|info| info.keyId != Address::ZERO)
516 .unwrap_or(false);
517
518 if !is_provisioned {
519 self.set_key_authorization(auth);
520 }
521 }
522
523 Ok(())
524 }
525 }
526
527 fn convert_create_to_call(&mut self) {
528 if self.calls.is_empty() && self.inner.to.is_some_and(|to| to.is_create()) {
529 let input = self.inner.input.input().cloned().unwrap_or_default();
530 let value = self.inner.value.unwrap_or(U256::ZERO);
531 self.calls.push(Call { to: TxKind::Create, value, input });
532 self.inner.input = Default::default();
533 self.inner.value = None;
534 self.inner.to = None;
535 }
536 }
537
538 fn clear_batch_to(&mut self) {
539 if !self.calls.is_empty() {
540 self.inner.to = None;
541 self.inner.value = None;
542 }
543 }
544
545 fn sign_with_access_key(
546 mut self,
547 provider: &impl Provider<TempoNetwork>,
548 signer: &(impl Signer + Sync),
549 wallet_address: Address,
550 key_address: Address,
551 key_authorization: Option<&SignedKeyAuthorization>,
552 ) -> impl Future<Output = Result<Vec<u8>>> + Send {
553 let auth = key_authorization.cloned();
554 let provisioning_fut = provider.get_keychain_key(wallet_address, key_address);
555
556 async move {
557 if let Some(auth) = auth {
558 let is_provisioned =
559 provisioning_fut.await.map(|info| info.keyId != Address::ZERO).unwrap_or(false);
560
561 if !is_provisioned && self.key_authorization.is_none() {
562 if self.fee_payer_signature.is_some() {
563 eyre::bail!(
564 "cannot add Tempo key authorization after fee payer signature was attached"
565 );
566 }
567 self.set_key_authorization(auth);
568 }
569 }
570
571 let tempo_tx = self
572 .build_aa()
573 .map_err(|e| eyre::eyre!("failed to build Tempo AA transaction: {e}"))?;
574
575 let sig_hash = tempo_tx.signature_hash();
576 let signing_hash = KeychainSignature::signing_hash(sig_hash, wallet_address);
577 let raw_sig = signer.sign_hash(&signing_hash).await?;
578
579 let keychain_sig =
580 KeychainSignature::new(wallet_address, PrimitiveSignature::Secp256k1(raw_sig));
581 let aa_signed = tempo_tx.into_signed(TempoSignature::Keychain(keychain_sig));
582
583 let mut buf = Vec::new();
584 aa_signed.encode_2718(&mut buf);
585 Ok(buf)
586 }
587 }
588}