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