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