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, NetworkWallet};
8use alloy_primitives::{Address, B256, Bytes, Signature, TxKind, U256};
9use alloy_provider::Provider;
10use eyre::Result;
11use foundry_wallets::TempoAccountsWallet;
12#[cfg(feature = "optimism")]
13use op_alloy_network::Optimism;
14#[cfg(feature = "optimism")]
15use op_alloy_rpc_types::OpTransactionRequest;
16use tempo_alloy::TempoNetwork;
17use tempo_primitives::{SignatureType, TempoTxType, transaction::Call};
18
19pub trait FoundryTransactionBuilder<N: Network>: NetworkTransactionBuilder<N> {
47 fn reset_gas_limit(&mut self);
49
50 fn max_fee_per_blob_gas(&self) -> Option<u128> {
52 None
53 }
54
55 fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {}
57
58 fn with_max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
60 self.set_max_fee_per_blob_gas(max_fee_per_blob_gas);
61 self
62 }
63
64 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
69 None
70 }
71
72 fn set_blob_versioned_hashes(&mut self, _hashes: Vec<B256>) {}
74
75 fn with_blob_versioned_hashes(mut self, hashes: Vec<B256>) -> Self {
77 self.set_blob_versioned_hashes(hashes);
78 self
79 }
80
81 fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> {
83 None
84 }
85
86 fn set_blob_sidecar(&mut self, _sidecar: BlobTransactionSidecarVariant) {}
91
92 fn with_blob_sidecar(mut self, sidecar: BlobTransactionSidecarVariant) -> Self {
94 self.set_blob_sidecar(sidecar);
95 self
96 }
97
98 fn blob_sidecar_4844(&self) -> Option<&BlobTransactionSidecar> {
100 self.blob_sidecar().and_then(|s| s.as_eip4844())
101 }
102
103 fn set_blob_sidecar_4844(&mut self, sidecar: BlobTransactionSidecar) {
105 self.set_blob_sidecar(BlobTransactionSidecarVariant::Eip4844(sidecar));
106 }
107
108 fn with_blob_sidecar_4844(mut self, sidecar: BlobTransactionSidecar) -> Self {
110 self.set_blob_sidecar_4844(sidecar);
111 self
112 }
113
114 fn blob_sidecar_7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
116 self.blob_sidecar().and_then(|s| s.as_eip7594())
117 }
118
119 fn set_blob_sidecar_7594(&mut self, sidecar: BlobTransactionSidecarEip7594) {
121 self.set_blob_sidecar(BlobTransactionSidecarVariant::Eip7594(sidecar));
122 }
123
124 fn with_blob_sidecar_7594(mut self, sidecar: BlobTransactionSidecarEip7594) -> Self {
126 self.set_blob_sidecar_7594(sidecar);
127 self
128 }
129
130 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
132 None
133 }
134
135 fn set_authorization_list(&mut self, _authorization_list: Vec<SignedAuthorization>) {}
137
138 fn with_authorization_list(mut self, authorization_list: Vec<SignedAuthorization>) -> Self {
140 self.set_authorization_list(authorization_list);
141 self
142 }
143
144 fn fee_token(&self) -> Option<Address> {
146 None
147 }
148
149 fn tempo_calls(&self) -> Vec<(TxKind, &[u8])> {
151 Vec::new()
152 }
153
154 fn has_tempo_call_list(&self) -> bool {
156 false
157 }
158
159 fn is_tempo_aa(&self) -> bool {
161 false
162 }
163
164 fn set_fee_token(&mut self, _fee_token: Address) {}
166
167 fn with_fee_token(mut self, fee_token: Address) -> Self {
169 self.set_fee_token(fee_token);
170 self
171 }
172
173 fn nonce_key(&self) -> Option<U256> {
175 None
176 }
177
178 fn set_nonce_key(&mut self, _nonce_key: U256) {}
180
181 fn with_nonce_key(mut self, nonce_key: U256) -> Self {
183 self.set_nonce_key(nonce_key);
184 self
185 }
186
187 fn browser_wallet_gas_estimation_request(&self) -> Self
192 where
193 Self: Clone,
194 {
195 self.clone()
196 }
197
198 fn key_id(&self) -> Option<Address> {
200 None
201 }
202
203 fn set_key_id(&mut self, _key_id: Address) {}
208
209 fn with_key_id(mut self, key_id: Address) -> Self {
211 self.set_key_id(key_id);
212 self
213 }
214
215 fn valid_before(&self) -> Option<NonZeroU64> {
217 None
218 }
219
220 fn set_valid_before(&mut self, _valid_before: NonZeroU64) {}
222
223 fn with_valid_before(mut self, valid_before: NonZeroU64) -> Self {
225 self.set_valid_before(valid_before);
226 self
227 }
228
229 fn valid_after(&self) -> Option<NonZeroU64> {
231 None
232 }
233
234 fn set_valid_after(&mut self, _valid_after: NonZeroU64) {}
236
237 fn with_valid_after(mut self, valid_after: NonZeroU64) -> Self {
239 self.set_valid_after(valid_after);
240 self
241 }
242
243 fn fee_payer_signature(&self) -> Option<Signature> {
245 None
246 }
247
248 fn set_fee_payer_signature(&mut self, _signature: Signature) {}
250
251 fn with_fee_payer_signature(mut self, signature: Signature) -> Self {
253 self.set_fee_payer_signature(signature);
254 self
255 }
256
257 fn compute_sponsor_hash(&self, _from: Address) -> Option<B256> {
263 None
264 }
265
266 fn prepare_with_tempo_wallet<'a>(
268 &'a mut self,
269 _provider: &'a impl Provider<N>,
270 _wallet: &'a TempoAccountsWallet,
271 ) -> impl Future<Output = Result<TempoAccountsWallet>> + Send + 'a
272 where
273 Self: Send,
274 {
275 std::future::ready(Err(eyre::eyre!(
276 "Tempo access-key wallets are not supported for this network"
277 )))
278 }
279
280 fn convert_create_to_call(&mut self) {}
286
287 fn clear_batch_to(&mut self) {}
294
295 fn sign_with_tempo_wallet(
297 self,
298 _wallet: &TempoAccountsWallet,
299 ) -> impl Future<Output = Result<Vec<u8>>> + Send {
300 std::future::ready(Err(eyre::eyre!(
301 "Tempo access-key wallets are not supported for this network"
302 )))
303 }
304}
305
306impl FoundryTransactionBuilder<Ethereum> for <Ethereum as Network>::TransactionRequest {
307 fn reset_gas_limit(&mut self) {
308 self.gas = None;
309 }
310
311 fn max_fee_per_blob_gas(&self) -> Option<u128> {
312 self.max_fee_per_blob_gas
313 }
314
315 fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) {
316 self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas);
317 }
318
319 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
320 self.blob_versioned_hashes.as_deref()
321 }
322
323 fn set_blob_versioned_hashes(&mut self, hashes: Vec<B256>) {
324 self.blob_versioned_hashes = Some(hashes);
325 }
326
327 fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> {
328 self.sidecar.as_ref()
329 }
330
331 fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecarVariant) {
332 self.sidecar = Some(sidecar);
333 self.populate_blob_hashes();
334 }
335
336 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
337 self.authorization_list.as_ref()
338 }
339
340 fn set_authorization_list(&mut self, authorization_list: Vec<SignedAuthorization>) {
341 self.authorization_list = Some(authorization_list);
342 }
343}
344
345impl FoundryTransactionBuilder<AnyNetwork> for <AnyNetwork as Network>::TransactionRequest {
346 fn reset_gas_limit(&mut self) {
347 self.gas = None;
348 }
349
350 fn max_fee_per_blob_gas(&self) -> Option<u128> {
351 self.max_fee_per_blob_gas
352 }
353
354 fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) {
355 self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas);
356 }
357
358 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
359 self.blob_versioned_hashes.as_deref()
360 }
361
362 fn set_blob_versioned_hashes(&mut self, hashes: Vec<B256>) {
363 self.blob_versioned_hashes = Some(hashes);
364 }
365
366 fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> {
367 self.sidecar.as_ref()
368 }
369
370 fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecarVariant) {
371 self.sidecar = Some(sidecar);
372 self.populate_blob_hashes();
373 }
374
375 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
376 self.authorization_list.as_ref()
377 }
378
379 fn set_authorization_list(&mut self, authorization_list: Vec<SignedAuthorization>) {
380 self.authorization_list = Some(authorization_list);
381 }
382}
383
384#[cfg(feature = "optimism")]
385impl FoundryTransactionBuilder<Optimism> for OpTransactionRequest {
386 fn reset_gas_limit(&mut self) {
387 self.as_mut().gas = None;
388 }
389
390 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
391 self.as_ref().authorization_list.as_ref()
392 }
393
394 fn set_authorization_list(&mut self, authorization_list: Vec<SignedAuthorization>) {
395 self.as_mut().authorization_list = Some(authorization_list);
396 }
397}
398
399const TEMPO_BROWSER_WEBAUTHN_DATA_SIZE: u16 = 1_400;
404
405impl FoundryTransactionBuilder<TempoNetwork> for <TempoNetwork as Network>::TransactionRequest {
406 fn reset_gas_limit(&mut self) {
407 self.gas = None;
408 }
409
410 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
411 self.authorization_list.as_ref()
412 }
413
414 fn set_authorization_list(&mut self, authorization_list: Vec<SignedAuthorization>) {
415 self.authorization_list = Some(authorization_list);
416 }
417
418 fn fee_token(&self) -> Option<Address> {
419 self.fee_token
420 }
421
422 fn tempo_calls(&self) -> Vec<(TxKind, &[u8])> {
423 self.calls
424 .iter()
425 .map(|call| (call.to, call.input.as_ref()))
426 .chain(self.inner.to.map(|to| {
427 (to, self.inner.input.input().map_or(&[] as &[u8], |input| input.as_ref()))
428 }))
429 .collect()
430 }
431
432 fn has_tempo_call_list(&self) -> bool {
433 !self.calls.is_empty()
434 }
435
436 fn is_tempo_aa(&self) -> bool {
437 NetworkTransactionBuilder::<TempoNetwork>::output_tx_type(self) == TempoTxType::AA
438 }
439
440 fn set_fee_token(&mut self, fee_token: Address) {
441 self.fee_token = Some(fee_token);
442 }
443
444 fn nonce_key(&self) -> Option<U256> {
445 self.nonce_key
446 }
447
448 fn set_nonce_key(&mut self, nonce_key: U256) {
449 self.nonce_key = Some(nonce_key);
450 }
451
452 fn browser_wallet_gas_estimation_request(&self) -> Self {
453 let mut request = self.clone();
454 if request.key_type.is_none() {
455 request.key_type = Some(SignatureType::WebAuthn);
456 request.key_data =
457 Some(Bytes::copy_from_slice(&TEMPO_BROWSER_WEBAUTHN_DATA_SIZE.to_be_bytes()));
458 } else if matches!(request.key_type, Some(SignatureType::WebAuthn))
459 && request.key_data.is_none()
460 {
461 request.key_data =
462 Some(Bytes::copy_from_slice(&TEMPO_BROWSER_WEBAUTHN_DATA_SIZE.to_be_bytes()));
463 }
464 request.convert_create_to_call();
465 request
466 }
467
468 fn key_id(&self) -> Option<Address> {
469 self.key_id
470 }
471
472 fn set_key_id(&mut self, key_id: Address) {
473 self.key_id = Some(key_id);
474 }
475
476 fn valid_before(&self) -> Option<NonZeroU64> {
477 self.valid_before
478 }
479
480 fn set_valid_before(&mut self, valid_before: NonZeroU64) {
481 self.valid_before = Some(valid_before);
482 }
483
484 fn valid_after(&self) -> Option<NonZeroU64> {
485 self.valid_after
486 }
487
488 fn set_valid_after(&mut self, valid_after: NonZeroU64) {
489 self.valid_after = Some(valid_after);
490 }
491
492 fn fee_payer_signature(&self) -> Option<Signature> {
493 self.fee_payer_signature
494 }
495
496 fn set_fee_payer_signature(&mut self, signature: Signature) {
497 self.fee_payer_signature = Some(signature);
498 }
499
500 fn compute_sponsor_hash(&self, from: Address) -> Option<B256> {
501 let tx = self.clone().build_aa().ok()?;
502 Some(tx.fee_payer_signature_hash(from))
503 }
504
505 async fn prepare_with_tempo_wallet<'a>(
506 &'a mut self,
507 provider: &'a impl Provider<TempoNetwork>,
508 wallet: &'a TempoAccountsWallet,
509 ) -> Result<TempoAccountsWallet>
510 where
511 Self: Send,
512 {
513 wallet.prepare_request(provider, self).await.map_err(Into::into)
514 }
515
516 fn convert_create_to_call(&mut self) {
517 if self.calls.is_empty() && self.inner.to.is_some_and(|to| to.is_create()) {
518 let input = self.inner.input.input().cloned().unwrap_or_default();
519 let value = self.inner.value.unwrap_or(U256::ZERO);
520 self.calls.push(Call { to: TxKind::Create, value, input });
521 self.inner.input = Default::default();
522 self.inner.value = None;
523 self.inner.to = None;
524 }
525 }
526
527 fn clear_batch_to(&mut self) {
528 if !self.calls.is_empty() {
529 self.inner.to = None;
530 self.inner.value = None;
531 }
532 }
533
534 async fn sign_with_tempo_wallet(self, wallet: &TempoAccountsWallet) -> Result<Vec<u8>> {
535 wallet.sign_request(self).await.map(|envelope| envelope.encoded_2718()).map_err(Into::into)
536 }
537}