1use std::{str::FromStr, time::Duration};
2
3use crate::{
4 cmd::{
5 call_overrides::CallOverrideOpts,
6 send::{
7 cast_send, cast_send_with_tempo_wallet, cast_send_with_tempo_wallet_via_sponsor,
8 validate_sponsor_url,
9 },
10 },
11 format_uint_exp, tempo,
12 tx::{CastTxSender, SendTxOpts, TxParams, fill_transaction_gas_fees},
13};
14use alloy_consensus::{SignableTransaction, Signed};
15use alloy_eips::BlockId;
16use alloy_ens::NameOrAddress;
17use alloy_network::{Ethereum, EthereumWallet, Network, TransactionBuilder};
18use alloy_primitives::{Address, U256};
19use alloy_provider::{
20 Provider, ProviderBuilder as AlloyProviderBuilder, fillers::RecommendedFillers,
21};
22use alloy_signer::{Signature, Signer};
23use alloy_sol_types::sol;
24use clap::Parser;
25use foundry_cli::{
26 json::{print_json_success, print_scalar},
27 opts::RpcOpts,
28 utils::{LoadConfig, get_chain, get_provider},
29};
30use foundry_common::{
31 FoundryTransactionBuilder,
32 fmt::{UIfmt, UIfmtReceiptExt},
33 provider::{ProviderBuilder, RetryProviderWithSigner},
34 shell,
35 tempo::{maybe_print_fee_token, resolve_and_set_fee_token},
36};
37#[doc(hidden)]
38pub use foundry_config::{Chain, Eip1559FeeEstimatePreset, utils::*};
39use foundry_wallets::{TempoAccountsWallet, WalletSigner};
40use tempo_alloy::TempoNetwork;
41use tempo_primitives::transaction::FEE_PAYER_SIGNATURE_MARKER;
42
43sol! {
44 #[sol(rpc)]
45 interface IERC20 {
46 #[derive(Debug)]
47 function name() external view returns (string);
48 function symbol() external view returns (string);
49 function decimals() external view returns (uint8);
50 function totalSupply() external view returns (uint256);
51 function balanceOf(address owner) external view returns (uint256);
52 function transfer(address to, uint256 amount) external returns (bool);
53 function approve(address spender, uint256 amount) external returns (bool);
54 function allowance(address owner, address spender) external view returns (uint256);
55 function mint(address to, uint256 amount) external;
56 function burn(uint256 amount) external;
57 }
58}
59
60#[derive(Debug, Parser, Clone)]
62pub enum Erc20Subcommand {
63 #[command(visible_alias = "b")]
65 Balance {
66 #[arg(value_parser = NameOrAddress::from_str)]
68 token: NameOrAddress,
69
70 #[arg(value_parser = NameOrAddress::from_str)]
72 owner: NameOrAddress,
73
74 #[arg(long, short = 'B')]
76 block: Option<BlockId>,
77
78 #[command(flatten)]
79 rpc: RpcOpts,
80
81 #[command(flatten)]
82 overrides: CallOverrideOpts,
83 },
84
85 #[command(visible_aliases = ["t", "send"])]
87 Transfer {
88 #[arg(value_parser = NameOrAddress::from_str)]
90 token: NameOrAddress,
91
92 #[arg(value_parser = NameOrAddress::from_str)]
94 to: NameOrAddress,
95
96 amount: String,
98
99 #[command(flatten)]
100 send_tx: SendTxOpts,
101
102 #[command(flatten)]
103 tx: TxParams,
104 },
105
106 #[command(visible_alias = "a")]
108 Approve {
109 #[arg(value_parser = NameOrAddress::from_str)]
111 token: NameOrAddress,
112
113 #[arg(value_parser = NameOrAddress::from_str)]
115 spender: NameOrAddress,
116
117 amount: String,
119
120 #[command(flatten)]
121 send_tx: SendTxOpts,
122
123 #[command(flatten)]
124 tx: TxParams,
125 },
126
127 #[command(visible_alias = "al")]
129 Allowance {
130 #[arg(value_parser = NameOrAddress::from_str)]
132 token: NameOrAddress,
133
134 #[arg(value_parser = NameOrAddress::from_str)]
136 owner: NameOrAddress,
137
138 #[arg(value_parser = NameOrAddress::from_str)]
140 spender: NameOrAddress,
141
142 #[arg(long, short = 'B')]
144 block: Option<BlockId>,
145
146 #[command(flatten)]
147 rpc: RpcOpts,
148 },
149
150 #[command(visible_alias = "n")]
152 Name {
153 #[arg(value_parser = NameOrAddress::from_str)]
155 token: NameOrAddress,
156
157 #[arg(long, short = 'B')]
159 block: Option<BlockId>,
160
161 #[command(flatten)]
162 rpc: RpcOpts,
163 },
164
165 #[command(visible_alias = "s")]
167 Symbol {
168 #[arg(value_parser = NameOrAddress::from_str)]
170 token: NameOrAddress,
171
172 #[arg(long, short = 'B')]
174 block: Option<BlockId>,
175
176 #[command(flatten)]
177 rpc: RpcOpts,
178 },
179
180 #[command(visible_alias = "d")]
182 Decimals {
183 #[arg(value_parser = NameOrAddress::from_str)]
185 token: NameOrAddress,
186
187 #[arg(long, short = 'B')]
189 block: Option<BlockId>,
190
191 #[command(flatten)]
192 rpc: RpcOpts,
193 },
194
195 #[command(visible_alias = "ts")]
197 TotalSupply {
198 #[arg(value_parser = NameOrAddress::from_str)]
200 token: NameOrAddress,
201
202 #[arg(long, short = 'B')]
204 block: Option<BlockId>,
205
206 #[command(flatten)]
207 rpc: RpcOpts,
208 },
209
210 #[command(visible_alias = "m")]
212 Mint {
213 #[arg(value_parser = NameOrAddress::from_str)]
215 token: NameOrAddress,
216
217 #[arg(value_parser = NameOrAddress::from_str)]
219 to: NameOrAddress,
220
221 amount: String,
223
224 #[command(flatten)]
225 send_tx: SendTxOpts,
226
227 #[command(flatten)]
228 tx: TxParams,
229 },
230
231 #[command(visible_alias = "bu")]
233 Burn {
234 #[arg(value_parser = NameOrAddress::from_str)]
236 token: NameOrAddress,
237
238 amount: String,
240
241 #[command(flatten)]
242 send_tx: SendTxOpts,
243
244 #[command(flatten)]
245 tx: TxParams,
246 },
247}
248
249impl Erc20Subcommand {
250 const fn rpc_opts(&self) -> &RpcOpts {
251 match self {
252 Self::Allowance { rpc, .. } => rpc,
253 Self::Approve { send_tx, .. } => &send_tx.eth.rpc,
254 Self::Balance { rpc, .. } => rpc,
255 Self::Transfer { send_tx, .. } => &send_tx.eth.rpc,
256 Self::Name { rpc, .. } => rpc,
257 Self::Symbol { rpc, .. } => rpc,
258 Self::Decimals { rpc, .. } => rpc,
259 Self::TotalSupply { rpc, .. } => rpc,
260 Self::Mint { send_tx, .. } => &send_tx.eth.rpc,
261 Self::Burn { send_tx, .. } => &send_tx.eth.rpc,
262 }
263 }
264
265 const fn erc20_opts(&self) -> Option<&TxParams> {
266 match self {
267 Self::Approve { tx, .. }
268 | Self::Transfer { tx, .. }
269 | Self::Mint { tx, .. }
270 | Self::Burn { tx, .. } => Some(tx),
271 Self::Allowance { .. }
272 | Self::Balance { .. }
273 | Self::Name { .. }
274 | Self::Symbol { .. }
275 | Self::Decimals { .. }
276 | Self::TotalSupply { .. } => None,
277 }
278 }
279
280 const fn uses_browser_send(&self) -> bool {
281 match self {
282 Self::Transfer { send_tx, .. }
283 | Self::Approve { send_tx, .. }
284 | Self::Mint { send_tx, .. }
285 | Self::Burn { send_tx, .. } => send_tx.browser.browser,
286 _ => false,
287 }
288 }
289
290 async fn should_use_tempo_network(
291 &self,
292 tempo_access_key: &Option<TempoAccountsWallet>,
293 has_session: bool,
294 ) -> eyre::Result<bool> {
295 if self.erc20_opts().is_some_and(|erc20| erc20.tempo.is_tempo())
296 || has_session
297 || tempo_access_key.is_some()
298 {
299 return Ok(true);
300 }
301
302 if self.uses_browser_send() {
303 let config = self.rpc_opts().load_config()?;
304 return Ok(get_chain(config.chain, &get_provider(&config)?).await?.is_tempo());
305 }
306
307 Ok(false)
308 }
309
310 fn has_tempo_session(&self) -> eyre::Result<bool> {
311 self.erc20_opts().map_or(Ok(false), |opts| opts.tempo.session_id().map(|id| id.is_some()))
312 }
313
314 pub async fn run(self) -> eyre::Result<()> {
315 let has_session = self.has_tempo_session()?;
316 let (resolved_tempo, signer, tempo_access_key) = match &self {
318 Self::Transfer { send_tx, tx, .. }
319 | Self::Approve { send_tx, tx, .. }
320 | Self::Mint { send_tx, tx, .. }
321 | Self::Burn { send_tx, tx, .. } => {
322 if has_session {
325 (true, None, None)
326 } else {
327 tempo::resolve_transaction_network_and_signer(&tx.tempo, &send_tx.eth).await?
328 }
329 }
330 _ => (false, None, None),
331 };
332
333 let is_tempo =
334 resolved_tempo || self.should_use_tempo_network(&tempo_access_key, has_session).await?;
335
336 if is_tempo {
337 self.run_generic::<TempoNetwork>(signer, tempo_access_key, has_session).await
338 } else {
339 self.run_generic::<Ethereum>(signer, None, has_session).await
340 }
341 }
342
343 #[allow(clippy::large_stack_frames)]
344 pub async fn run_generic<N: Network + RecommendedFillers>(
345 self,
346 pre_resolved_signer: Option<WalletSigner>,
347 tempo_keychain: Option<TempoAccountsWallet>,
348 has_session: bool,
349 ) -> eyre::Result<()>
350 where
351 N::TxEnvelope: From<Signed<N::UnsignedTx>>,
352 N::UnsignedTx: SignableTransaction<Signature>,
353 N::TransactionRequest: FoundryTransactionBuilder<N>,
354 N::ReceiptResponse: UIfmt + UIfmtReceiptExt,
355 {
356 let config = self.rpc_opts().load_config()?;
357
358 macro_rules! erc20_send {
361 (
362 $token:expr,
363 $send_tx:expr,
364 $tx_opts:expr, |
365 $erc20:ident,
366 $provider:ident |
367 $build_tx:expr
368 ) => {{
369 let mut tx_opts = $tx_opts;
370 tempo::ensure_session_not_browser(&tx_opts.tempo, $send_tx.browser.browser)?;
371 let (pre_resolved_signer, tempo_keychain) =
372 if has_session || tempo_keychain.is_some() {
373 let $provider =
374 ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
375 let chain = get_chain(config.chain, &$provider).await?;
376 tempo::resolve_session_or_wallet_signer(
377 &tx_opts.tempo,
378 &$send_tx.eth.wallet,
379 chain.id(),
380 )
381 .await?
382 } else {
383 (pre_resolved_signer, tempo_keychain)
384 };
385 let print_sponsor_hash = tx_opts.tempo.print_sponsor_hash;
386 let sponsor_url = tx_opts.tempo.sponsor_url.clone();
387 let sponsor_fee_payer = tx_opts.tempo.sponsor;
388 let expires_at = tx_opts.tempo.resolve_expires();
389 let tempo_sponsor = if print_sponsor_hash || sponsor_url.is_some() {
390 None
391 } else {
392 tx_opts.tempo.sponsor_config().await?
393 };
394 let needs_sponsor_payload =
395 print_sponsor_hash || tempo_sponsor.is_some() || sponsor_url.is_some();
396 if let Some(ref url) = sponsor_url {
397 validate_sponsor_url(url)?;
398 if $send_tx.browser.browser {
399 eyre::bail!("--sponsor-url cannot be combined with --browser");
400 }
401 }
402 if let Some(ts) = expires_at {
403 sh_status!("Transaction expires at unix timestamp {ts}")?;
404 }
405
406 let timeout = $send_tx.timeout.unwrap_or(config.transaction_timeout);
407 if let Some(ref access_key) = tempo_keychain {
408 let $provider =
409 ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
410 let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider);
411 let mut tx = { $build_tx }.into_transaction_request();
412 let chain = get_chain(config.chain, &$provider).await?;
413 tx_opts.apply::<TempoNetwork>(&mut tx, chain.is_legacy());
414 let prepared_access_key = tempo::fill_access_key_transaction(
415 &$provider,
416 &mut tx,
417 access_key,
418 chain,
419 config.eip1559_fee_estimate,
420 )
421 .await?;
422 if needs_sponsor_payload {
423 if print_sponsor_hash {
424 if let Some(fee_payer) = sponsor_fee_payer {
425 resolve_and_set_fee_token(
426 (!config.eth_rpc_curl).then_some(&$provider),
427 Some(chain),
428 &mut tx,
429 Some(fee_payer),
430 )
431 .await?;
432 }
433 let hash = tx
434 .compute_sponsor_hash(prepared_access_key.account())
435 .ok_or_else(|| {
436 eyre::eyre!(
437 "This network does not support sponsored transactions"
438 )
439 })?;
440 sh_println!("{hash:?}")?;
441 return Ok(());
442 }
443 if let Some(sponsor) = &tempo_sponsor {
444 sponsor
445 .resolve_and_set_fee_token(
446 (!config.eth_rpc_curl).then_some(&$provider),
447 Some(chain),
448 &mut tx,
449 )
450 .await?;
451 sponsor
452 .attach_and_print::<TempoNetwork>(
453 &mut tx,
454 prepared_access_key.account(),
455 )
456 .await?;
457 }
458 }
459 if let Some(sponsor_url) = sponsor_url.as_deref() {
460 cast_send_with_tempo_wallet_via_sponsor(
461 &$provider,
462 tx,
463 &prepared_access_key,
464 sponsor_url,
465 $send_tx.cast_async,
466 $send_tx.sync,
467 $send_tx.confirmations,
468 timeout,
469 )
470 .await?;
471 } else {
472 cast_send_with_tempo_wallet(
473 &$provider,
474 tx,
475 &prepared_access_key,
476 tempo_sponsor.is_none().then_some(chain),
477 None,
478 $send_tx.cast_async,
479 $send_tx.sync,
480 $send_tx.confirmations,
481 timeout,
482 tempo_sponsor.is_none() && !config.eth_rpc_curl,
483 )
484 .await?;
485 }
486 } else if let Some(browser) = $send_tx.browser.run::<N>().await? {
487 let $provider = ProviderBuilder::<N>::from_config(&config)?.build()?;
488 if let Some(interval) = $send_tx.poll_interval {
489 $provider.client().set_poll_interval(Duration::from_secs(interval));
490 }
491 let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider);
492 let mut tx = { $build_tx }.into_transaction_request();
493 let chain = get_chain(config.chain, &$provider).await?;
494 tx_opts.apply::<N>(&mut tx, chain.is_legacy());
495 fill_tx(
496 &$provider,
497 &mut tx,
498 browser.address(),
499 chain,
500 true,
501 config.eip1559_fee_estimate,
502 )
503 .await?;
504 if print_sponsor_hash {
505 if let Some(fee_payer) = sponsor_fee_payer {
506 resolve_and_set_fee_token(
507 (!config.eth_rpc_curl).then_some(&$provider),
508 Some(chain),
509 &mut tx,
510 Some(fee_payer),
511 )
512 .await?;
513 }
514 let hash = tx.compute_sponsor_hash(browser.address()).ok_or_else(|| {
515 eyre::eyre!("This network does not support sponsored transactions")
516 })?;
517 sh_println!("{hash:?}")?;
518 return Ok(());
519 }
520 if let Some(sponsor) = &tempo_sponsor {
521 sponsor
522 .resolve_and_set_fee_token(
523 (!config.eth_rpc_curl).then_some(&$provider),
524 Some(chain),
525 &mut tx,
526 )
527 .await?;
528 sponsor.attach_and_print::<N>(&mut tx, browser.address()).await?;
529 } else {
530 let fee_token = resolve_and_set_fee_token(
531 (!config.eth_rpc_curl).then_some(&$provider),
532 Some(chain),
533 &mut tx,
534 Some(browser.address()),
535 )
536 .await?;
537 maybe_print_fee_token(
538 (!config.eth_rpc_curl).then_some(&$provider),
539 fee_token,
540 )
541 .await?;
542 }
543 let tx_hash = browser.send_transaction_via_browser(tx).await?;
544 CastTxSender::new(&$provider)
545 .print_tx_result(
546 tx_hash,
547 $send_tx.cast_async,
548 $send_tx.confirmations,
549 timeout,
550 )
551 .await?
552 } else {
553 let signer = pre_resolved_signer.unwrap_or($send_tx.eth.wallet.signer().await?);
554 let from = signer.address();
555 let wallet = EthereumWallet::from(signer);
556 let $provider = ProviderBuilder::<N>::from_config(&config)?
557 .build_with_wallet(wallet.clone())?;
558 if let Some(interval) = $send_tx.poll_interval {
559 $provider.client().set_poll_interval(Duration::from_secs(interval));
560 }
561 let $erc20 = IERC20::new($token.resolve(&$provider).await?, &$provider);
562 let mut tx = { $build_tx }.into_transaction_request();
563 let chain = get_chain(config.chain, &$provider).await?;
564 tx_opts.apply::<N>(&mut tx, chain.is_legacy());
565 if needs_sponsor_payload {
566 fill_tx(
567 &$provider,
568 &mut tx,
569 from,
570 chain,
571 false,
572 config.eip1559_fee_estimate,
573 )
574 .await?;
575 if print_sponsor_hash {
576 if let Some(fee_payer) = sponsor_fee_payer {
577 resolve_and_set_fee_token(
578 (!config.eth_rpc_curl).then_some(&$provider),
579 Some(chain),
580 &mut tx,
581 Some(fee_payer),
582 )
583 .await?;
584 }
585 let hash = tx.compute_sponsor_hash(from).ok_or_else(|| {
586 eyre::eyre!("This network does not support sponsored transactions")
587 })?;
588 sh_println!("{hash:?}")?;
589 return Ok(());
590 }
591 if let Some(sponsor) = &tempo_sponsor {
592 sponsor
593 .resolve_and_set_fee_token(
594 (!config.eth_rpc_curl).then_some(&$provider),
595 Some(chain),
596 &mut tx,
597 )
598 .await?;
599 sponsor.attach_and_print::<N>(&mut tx, from).await?;
600 }
601 } else {
602 fill_transaction_gas_fees(
604 &$provider,
605 &mut tx,
606 chain.is_legacy(),
607 false,
608 config.eip1559_fee_estimate,
609 )
610 .await?;
611 }
612 if let Some(sponsor_url) = sponsor_url {
613 tx.set_fee_payer_signature(FEE_PAYER_SIGNATURE_MARKER);
614 let connector = tempo::sponsor_relay_connector(&$provider, &sponsor_url)?;
615 let provider = AlloyProviderBuilder::<_, _, N>::default()
616 .wallet(wallet)
617 .connect_with(&connector)
618 .await?;
619 cast_send(
620 provider,
621 tx,
622 None,
623 None,
624 $send_tx.cast_async,
625 $send_tx.sync,
626 $send_tx.confirmations,
627 timeout,
628 false,
629 )
630 .await?;
631 } else {
632 cast_send(
633 $provider,
634 tx,
635 tempo_sponsor.is_none().then_some(chain),
636 None,
637 $send_tx.cast_async,
638 $send_tx.sync,
639 $send_tx.confirmations,
640 timeout,
641 tempo_sponsor.is_none() && !config.eth_rpc_curl,
642 )
643 .await?;
644 }
645 }
646 }};
647 }
648
649 match self {
650 Self::Allowance { token, owner, spender, block, .. } => {
652 let provider = get_provider(&config)?;
653 let token = token.resolve(&provider).await?;
654 let owner = owner.resolve(&provider).await?;
655 let spender = spender.resolve(&provider).await?;
656
657 let allowance = IERC20::new(token, &provider)
658 .allowance(owner, spender)
659 .block(block.unwrap_or_default())
660 .call()
661 .await?;
662
663 if shell::is_json() {
664 print_json_success(allowance.to_string())?;
665 } else {
666 sh_println!("{}", format_uint_exp(allowance))?;
667 }
668 }
669 Self::Balance { token, owner, block, overrides, .. } => {
670 let provider = get_provider(&config)?;
671 let token = token.resolve(&provider).await?;
672 let owner = owner.resolve(&provider).await?;
673
674 let token = IERC20::new(token, &provider);
675 let balance_call = token.balanceOf(owner).block(block.unwrap_or_default());
676 let call = balance_call.call();
677 let balance = overrides.apply(call)?.await?;
678
679 if shell::is_json() {
680 print_json_success(balance.to_string())?;
681 } else {
682 sh_println!("{balance}")?;
683 }
684 }
685 Self::Name { token, block, .. } => {
686 let provider = get_provider(&config)?;
687 let token = token.resolve(&provider).await?;
688
689 let name = IERC20::new(token, &provider)
690 .name()
691 .block(block.unwrap_or_default())
692 .call()
693 .await?;
694
695 print_scalar(name)?;
696 }
697 Self::Symbol { token, block, .. } => {
698 let provider = get_provider(&config)?;
699 let token = token.resolve(&provider).await?;
700
701 let symbol = IERC20::new(token, &provider)
702 .symbol()
703 .block(block.unwrap_or_default())
704 .call()
705 .await?;
706
707 print_scalar(symbol)?;
708 }
709 Self::Decimals { token, block, .. } => {
710 let provider = get_provider(&config)?;
711 let token = token.resolve(&provider).await?;
712
713 let decimals = IERC20::new(token, &provider)
714 .decimals()
715 .block(block.unwrap_or_default())
716 .call()
717 .await?;
718 print_scalar(decimals)?;
719 }
720 Self::TotalSupply { token, block, .. } => {
721 let provider = get_provider(&config)?;
722 let token = token.resolve(&provider).await?;
723
724 let total_supply = IERC20::new(token, &provider)
725 .totalSupply()
726 .block(block.unwrap_or_default())
727 .call()
728 .await?;
729
730 if shell::is_json() {
731 print_json_success(total_supply.to_string())?;
732 } else {
733 sh_println!("{}", format_uint_exp(total_supply))?
734 }
735 }
736 Self::Transfer { token, to, amount, send_tx, tx: tx_opts, .. } => {
738 erc20_send!(token, send_tx, tx_opts, |erc20, provider| {
739 erc20.transfer(to.resolve(&provider).await?, U256::from_str(&amount)?)
740 })
741 }
742 Self::Approve { token, spender, amount, send_tx, tx: tx_opts, .. } => {
743 erc20_send!(token, send_tx, tx_opts, |erc20, provider| {
744 erc20.approve(spender.resolve(&provider).await?, U256::from_str(&amount)?)
745 })
746 }
747 Self::Mint { token, to, amount, send_tx, tx: tx_opts, .. } => {
748 erc20_send!(token, send_tx, tx_opts, |erc20, provider| {
749 erc20.mint(to.resolve(&provider).await?, U256::from_str(&amount)?)
750 })
751 }
752 Self::Burn { token, amount, send_tx, tx: tx_opts, .. } => {
753 erc20_send!(token, send_tx, tx_opts, |erc20, provider| {
754 erc20.burn(U256::from_str(&amount)?)
755 })
756 }
757 };
758 Ok(())
759 }
760}
761
762pub(crate) fn build_provider_with_signer<N: Network + RecommendedFillers>(
764 tx_opts: &SendTxOpts,
765 signer: WalletSigner,
766) -> eyre::Result<RetryProviderWithSigner<N>>
767where
768 N::TxEnvelope: From<Signed<N::UnsignedTx>>,
769 N::UnsignedTx: SignableTransaction<Signature>,
770{
771 let config = tx_opts.eth.load_config()?;
772 let wallet = EthereumWallet::from(signer);
773 let provider = ProviderBuilder::<N>::from_config(&config)?.build_with_wallet(wallet)?;
774 if let Some(interval) = tx_opts.poll_interval {
775 provider.client().set_poll_interval(Duration::from_secs(interval))
776 }
777 Ok(provider)
778}
779
780async fn fill_tx<N: Network, P: Provider<N>>(
785 provider: &P,
786 tx: &mut N::TransactionRequest,
787 from: Address,
788 chain: Chain,
789 browser: bool,
790 eip1559_fee_estimate: Eip1559FeeEstimatePreset,
791) -> eyre::Result<()>
792where
793 N::TransactionRequest: FoundryTransactionBuilder<N>,
794{
795 tx.set_from(from);
796 tx.set_chain_id(chain.id());
797
798 if tx.nonce().is_none() {
799 tx.set_nonce(provider.get_transaction_count(from).await?);
800 }
801
802 let legacy = chain.is_legacy();
803
804 fill_transaction_gas_fees(provider, tx, legacy, browser, eip1559_fee_estimate).await?;
805
806 if tx.gas_limit().is_none() {
807 let request = if browser && chain.is_tempo() {
808 tx.browser_wallet_gas_estimation_request()
809 } else {
810 tx.clone()
811 };
812 let estimated = provider.estimate_gas(request).await?;
813 tx.set_gas_limit(estimated);
814 }
815
816 Ok(())
817}