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, Signature, TxKind, U256};
7use alloy_provider::Provider;
8use alloy_rpc_types::{TransactionRequest, serde_helpers::WithOtherFields};
9use eyre::Result;
10use foundry_wallets::TempoAccountsWallet;
11use std::num::NonZeroU64;
12use tempo_alloy::{TempoNetwork, rpc::TempoTransactionRequest};
13use tempo_primitives::{SignatureType, TempoTxType, transaction::Call};
14
15#[cfg(feature = "base")]
16use base_common_network::Base;
17#[cfg(feature = "base")]
18use base_common_rpc_types::BaseTransactionRequest;
19
20#[cfg(feature = "optimism")]
21use op_alloy_network::Optimism;
22#[cfg(feature = "optimism")]
23use op_alloy_rpc_types::OpTransactionRequest;
24
25pub trait FoundryTransactionBuilder<N: Network>: NetworkTransactionBuilder<N> {
53 fn reset_gas_limit(&mut self);
55
56 fn max_fee_per_blob_gas(&self) -> Option<u128> {
58 None
59 }
60
61 fn set_max_fee_per_blob_gas(&mut self, _max_fee_per_blob_gas: u128) {}
63
64 fn with_max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
66 self.set_max_fee_per_blob_gas(max_fee_per_blob_gas);
67 self
68 }
69
70 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
75 None
76 }
77
78 fn set_blob_versioned_hashes(&mut self, _hashes: Vec<B256>) {}
80
81 fn with_blob_versioned_hashes(mut self, hashes: Vec<B256>) -> Self {
83 self.set_blob_versioned_hashes(hashes);
84 self
85 }
86
87 fn blob_sidecar(&self) -> Option<&BlobTransactionSidecarVariant> {
89 None
90 }
91
92 fn set_blob_sidecar(&mut self, _sidecar: BlobTransactionSidecarVariant) {}
97
98 fn with_blob_sidecar(mut self, sidecar: BlobTransactionSidecarVariant) -> Self {
100 self.set_blob_sidecar(sidecar);
101 self
102 }
103
104 fn blob_sidecar_4844(&self) -> Option<&BlobTransactionSidecar> {
106 self.blob_sidecar().and_then(|s| s.as_eip4844())
107 }
108
109 fn set_blob_sidecar_4844(&mut self, sidecar: BlobTransactionSidecar) {
111 self.set_blob_sidecar(BlobTransactionSidecarVariant::Eip4844(sidecar));
112 }
113
114 fn with_blob_sidecar_4844(mut self, sidecar: BlobTransactionSidecar) -> Self {
116 self.set_blob_sidecar_4844(sidecar);
117 self
118 }
119
120 fn blob_sidecar_7594(&self) -> Option<&BlobTransactionSidecarEip7594> {
122 self.blob_sidecar().and_then(|s| s.as_eip7594())
123 }
124
125 fn set_blob_sidecar_7594(&mut self, sidecar: BlobTransactionSidecarEip7594) {
127 self.set_blob_sidecar(BlobTransactionSidecarVariant::Eip7594(sidecar));
128 }
129
130 fn with_blob_sidecar_7594(mut self, sidecar: BlobTransactionSidecarEip7594) -> Self {
132 self.set_blob_sidecar_7594(sidecar);
133 self
134 }
135
136 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
138 None
139 }
140
141 fn set_authorization_list(&mut self, _authorization_list: Vec<SignedAuthorization>) {}
143
144 fn with_authorization_list(mut self, authorization_list: Vec<SignedAuthorization>) -> Self {
146 self.set_authorization_list(authorization_list);
147 self
148 }
149
150 fn fee_token(&self) -> Option<Address> {
152 None
153 }
154
155 fn tempo_calls(&self) -> Vec<(TxKind, &[u8])> {
157 Vec::new()
158 }
159
160 fn has_tempo_call_list(&self) -> bool {
162 false
163 }
164
165 fn is_tempo_aa(&self) -> bool {
167 false
168 }
169
170 fn supports_fee_token(&self) -> bool {
172 false
173 }
174
175 fn set_fee_token(&mut self, _fee_token: Address) {}
177
178 fn with_fee_token(mut self, fee_token: Address) -> Self {
180 self.set_fee_token(fee_token);
181 self
182 }
183
184 fn nonce_key(&self) -> Option<U256> {
186 None
187 }
188
189 fn set_nonce_key(&mut self, _nonce_key: U256) {}
191
192 fn with_nonce_key(mut self, nonce_key: U256) -> Self {
194 self.set_nonce_key(nonce_key);
195 self
196 }
197
198 fn browser_wallet_gas_estimation_request(&self) -> Self
203 where
204 Self: Clone,
205 {
206 self.clone()
207 }
208
209 fn key_id(&self) -> Option<Address> {
211 None
212 }
213
214 fn set_key_id(&mut self, _key_id: Address) {}
219
220 fn with_key_id(mut self, key_id: Address) -> Self {
222 self.set_key_id(key_id);
223 self
224 }
225
226 fn valid_before(&self) -> Option<NonZeroU64> {
228 None
229 }
230
231 fn set_valid_before(&mut self, _valid_before: NonZeroU64) {}
233
234 fn with_valid_before(mut self, valid_before: NonZeroU64) -> Self {
236 self.set_valid_before(valid_before);
237 self
238 }
239
240 fn valid_after(&self) -> Option<NonZeroU64> {
242 None
243 }
244
245 fn set_valid_after(&mut self, _valid_after: NonZeroU64) {}
247
248 fn with_valid_after(mut self, valid_after: NonZeroU64) -> Self {
250 self.set_valid_after(valid_after);
251 self
252 }
253
254 fn fee_payer_signature(&self) -> Option<Signature> {
256 None
257 }
258
259 fn set_fee_payer_signature(&mut self, _signature: Signature) {}
261
262 fn with_fee_payer_signature(mut self, signature: Signature) -> Self {
264 self.set_fee_payer_signature(signature);
265 self
266 }
267
268 fn compute_sponsor_hash(&self, _from: Address) -> Option<B256> {
274 None
275 }
276
277 fn prepare_with_tempo_wallet<'a>(
279 &'a mut self,
280 _provider: &'a impl Provider<N>,
281 _wallet: &'a TempoAccountsWallet,
282 ) -> impl Future<Output = Result<TempoAccountsWallet>> + Send + 'a
283 where
284 Self: Send,
285 {
286 std::future::ready(Err(eyre::eyre!(
287 "Tempo access-key wallets are not supported for this network"
288 )))
289 }
290
291 fn convert_create_to_call(&mut self) {}
297
298 fn clear_batch_to(&mut self) {}
305
306 fn sign_with_tempo_wallet(
308 self,
309 _wallet: &TempoAccountsWallet,
310 ) -> impl Future<Output = Result<Vec<u8>>> + Send {
311 std::future::ready(Err(eyre::eyre!(
312 "Tempo access-key wallets are not supported for this network"
313 )))
314 }
315}
316
317impl FoundryTransactionBuilder<Ethereum> for 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 WithOtherFields<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 = "base")]
396impl FoundryTransactionBuilder<Base> for BaseTransactionRequest {
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
410#[cfg(feature = "optimism")]
411impl FoundryTransactionBuilder<Optimism> for OpTransactionRequest {
412 fn reset_gas_limit(&mut self) {
413 self.as_mut().gas = None;
414 }
415
416 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
417 self.as_ref().authorization_list.as_ref()
418 }
419
420 fn set_authorization_list(&mut self, authorization_list: Vec<SignedAuthorization>) {
421 self.as_mut().authorization_list = Some(authorization_list);
422 }
423}
424
425const TEMPO_BROWSER_WEBAUTHN_DATA_SIZE: u16 = 1_400;
430
431impl FoundryTransactionBuilder<TempoNetwork> for TempoTransactionRequest {
432 fn reset_gas_limit(&mut self) {
433 self.gas = None;
434 }
435
436 fn authorization_list(&self) -> Option<&Vec<SignedAuthorization>> {
437 self.authorization_list.as_ref()
438 }
439
440 fn set_authorization_list(&mut self, authorization_list: Vec<SignedAuthorization>) {
441 self.authorization_list = Some(authorization_list);
442 }
443
444 fn fee_token(&self) -> Option<Address> {
445 self.fee_token
446 }
447
448 fn tempo_calls(&self) -> Vec<(TxKind, &[u8])> {
449 self.calls
450 .iter()
451 .map(|call| (call.to, call.input.as_ref()))
452 .chain(self.inner.to.map(|to| {
453 (to, self.inner.input.input().map_or(&[] as &[u8], |input| input.as_ref()))
454 }))
455 .collect()
456 }
457
458 fn has_tempo_call_list(&self) -> bool {
459 !self.calls.is_empty()
460 }
461
462 fn is_tempo_aa(&self) -> bool {
463 NetworkTransactionBuilder::<TempoNetwork>::output_tx_type(self) == TempoTxType::AA
464 }
465
466 fn supports_fee_token(&self) -> bool {
467 true
468 }
469
470 fn set_fee_token(&mut self, fee_token: Address) {
471 self.fee_token = Some(fee_token);
472 }
473
474 fn nonce_key(&self) -> Option<U256> {
475 self.nonce_key
476 }
477
478 fn set_nonce_key(&mut self, nonce_key: U256) {
479 self.nonce_key = Some(nonce_key);
480 }
481
482 fn browser_wallet_gas_estimation_request(&self) -> Self {
483 let mut request = self.clone();
484 if request.key_type.is_none() {
485 request.key_type = Some(SignatureType::WebAuthn);
486 request.key_data = Some(TEMPO_BROWSER_WEBAUTHN_DATA_SIZE.to_be_bytes().into());
487 } else if matches!(request.key_type, Some(SignatureType::WebAuthn))
488 && request.key_data.is_none()
489 {
490 request.key_data = Some(TEMPO_BROWSER_WEBAUTHN_DATA_SIZE.to_be_bytes().into());
491 }
492 request.convert_create_to_call();
493 request
494 }
495
496 fn key_id(&self) -> Option<Address> {
497 self.key_id
498 }
499
500 fn set_key_id(&mut self, key_id: Address) {
501 self.key_id = Some(key_id);
502 }
503
504 fn valid_before(&self) -> Option<NonZeroU64> {
505 self.valid_before
506 }
507
508 fn set_valid_before(&mut self, valid_before: NonZeroU64) {
509 self.valid_before = Some(valid_before);
510 }
511
512 fn valid_after(&self) -> Option<NonZeroU64> {
513 self.valid_after
514 }
515
516 fn set_valid_after(&mut self, valid_after: NonZeroU64) {
517 self.valid_after = Some(valid_after);
518 }
519
520 fn fee_payer_signature(&self) -> Option<Signature> {
521 self.fee_payer_signature
522 }
523
524 fn set_fee_payer_signature(&mut self, signature: Signature) {
525 self.fee_payer_signature = Some(signature);
526 }
527
528 fn compute_sponsor_hash(&self, from: Address) -> Option<B256> {
529 let tx = self.clone().build_aa().ok()?;
530 Some(tx.fee_payer_signature_hash(from))
531 }
532
533 async fn prepare_with_tempo_wallet<'a>(
534 &'a mut self,
535 provider: &'a impl Provider<TempoNetwork>,
536 wallet: &'a TempoAccountsWallet,
537 ) -> Result<TempoAccountsWallet>
538 where
539 Self: Send,
540 {
541 wallet.prepare_request(provider, self).await.map_err(Into::into)
542 }
543
544 fn convert_create_to_call(&mut self) {
545 if self.calls.is_empty() && self.inner.to.is_some_and(|to| to.is_create()) {
546 let input = self.inner.input.input().cloned().unwrap_or_default();
547 let value = self.inner.value.unwrap_or(U256::ZERO);
548 self.calls.push(Call { to: TxKind::Create, value, input });
549 self.inner.input = Default::default();
550 self.inner.value = None;
551 self.inner.to = None;
552 }
553 }
554
555 fn clear_batch_to(&mut self) {
556 if !self.calls.is_empty() {
557 self.inner.to = None;
558 self.inner.value = None;
559 }
560 }
561
562 async fn sign_with_tempo_wallet(self, wallet: &TempoAccountsWallet) -> Result<Vec<u8>> {
563 wallet.sign_request(self).await.map(|envelope| envelope.encoded_2718()).map_err(Into::into)
564 }
565}