1use crate::tx::fill_transaction_gas_fees;
4use alloy_network::{Ethereum, Network, TransactionBuilder};
5use alloy_provider::Provider;
6use alloy_rpc_client::BuiltInConnectionString;
7use alloy_transport::{BoxTransport, TransportConnect, TransportError};
8use eyre::Result;
9use foundry_cli::{
10 json::print_json_success,
11 opts::{EthereumOpts, TempoOpts},
12 utils::{LoadConfig, get_chain},
13};
14use foundry_common::{provider::ProviderBuilder, shell};
15use foundry_config::{Chain, Eip1559FeeEstimatePreset};
16use foundry_wallets::{TempoAccountsWallet, WalletOpts, WalletSigner};
17use serde_json::Value;
18use std::str::FromStr;
19use tempo_alloy::{
20 TempoNetwork,
21 transport::{RelayConnector, SponsorshipMode},
22};
23
24pub use foundry_common::tempo::{TempoSponsor, TempoSponsorPreview, resolve_tempo_sponsor_signer};
25
26pub(crate) fn print_payload<F>(payload: Value, human: F) -> Result<()>
28where
29 F: FnOnce(&Value) -> Result<()>,
30{
31 if shell::is_json() {
32 print_json_success(payload)?;
33 } else {
34 human(&payload)?;
35 }
36 Ok(())
37}
38
39pub(crate) fn print_expires(expires_at: Option<u64>) -> Result<()> {
40 if let Some(ts) = expires_at {
41 sh_status!("Transaction expires at unix timestamp {ts}")?;
42 }
43 Ok(())
44}
45
46pub(crate) async fn resolve_session_or_wallet_signer(
52 tempo: &TempoOpts,
53 wallet: &WalletOpts,
54 chain_id: u64,
55) -> Result<(Option<WalletSigner>, Option<TempoAccountsWallet>)> {
56 match tempo.session_signer_for_wallet(wallet, chain_id)? {
57 Some(session) => Ok((None, Some(session.access_key))),
58 None => wallet.maybe_signer_for_chain(chain_id).await,
59 }
60}
61
62pub(crate) fn ensure_session_not_browser(tempo: &TempoOpts, browser: bool) -> Result<()> {
63 if browser && tempo.session_id()?.is_some() {
64 eyre::bail!("--tempo.session/TEMPO_SESSION_ID cannot be combined with --browser");
65 }
66 Ok(())
67}
68
69#[derive(Clone, Debug)]
74pub(crate) struct ExistingTransportConnector {
75 transport: BoxTransport,
76 is_local: bool,
77}
78
79impl TransportConnect for ExistingTransportConnector {
80 fn is_local(&self) -> bool {
81 self.is_local
82 }
83
84 async fn get_transport(&self) -> Result<BoxTransport, TransportError> {
85 Ok(self.transport.clone())
86 }
87}
88
89pub(crate) fn sponsor_relay_connector<N: Network>(
90 provider: &impl Provider<N>,
91 sponsor_url: &str,
92) -> Result<RelayConnector<ExistingTransportConnector, BuiltInConnectionString>> {
93 let default = ExistingTransportConnector {
94 transport: provider.client().transport().clone(),
95 is_local: provider.client().is_local(),
96 };
97 let relay = BuiltInConnectionString::from_str(sponsor_url)?;
98 Ok(RelayConnector::with_config(default, relay, SponsorshipMode::SignOnly, false))
99}
100
101pub(crate) async fn resolve_transaction_network_and_signer(
108 tempo: &TempoOpts,
109 eth: &EthereumOpts,
110) -> Result<(bool, Option<WalletSigner>, Option<TempoAccountsWallet>)> {
111 let mut explicit_wallet = eth.wallet.clone();
112 explicit_wallet.from = None;
113 let (signer, access_key) = explicit_wallet.maybe_signer().await?;
114
115 if access_key.is_some() {
116 return Ok((true, signer, access_key));
117 }
118
119 if tempo.is_tempo() {
120 if signer.is_some() || eth.wallet.from.is_none() {
121 return Ok((true, signer, None));
122 }
123 let (signer, access_key) = eth.wallet.maybe_signer().await?;
124 return Ok((true, signer, access_key));
125 }
126
127 if signer.is_some() || eth.wallet.from.is_none() {
128 return Ok((false, signer, None));
129 }
130
131 let config = eth.load_config()?;
132 let provider = ProviderBuilder::<Ethereum>::from_config(&config)?.build()?;
133 let chain = get_chain(config.chain, &provider).await?;
134 if !chain.is_tempo() {
135 return Ok((false, None, None));
136 }
137
138 let (signer, access_key) = eth.wallet.maybe_signer_for_chain(chain.id()).await?;
139 Ok((true, signer, access_key))
140}
141
142pub(crate) async fn fill_access_key_transaction<P>(
145 provider: &P,
146 tx: &mut <TempoNetwork as Network>::TransactionRequest,
147 access_key: &TempoAccountsWallet,
148 chain: Chain,
149 eip1559_fee_estimate: Eip1559FeeEstimatePreset,
150) -> Result<TempoAccountsWallet>
151where
152 P: Provider<TempoNetwork>,
153{
154 tx.set_chain_id(chain.id());
155 let prepared = access_key.prepare_request(provider, tx).await?;
156
157 if tx.nonce().is_none() {
158 tx.set_nonce(provider.get_transaction_count(prepared.account()).await?);
159 }
160 fill_transaction_gas_fees(provider, tx, chain.is_legacy(), false, eip1559_fee_estimate).await?;
161 if tx.gas_limit().is_none() {
162 tx.set_gas_limit(provider.estimate_gas(tx.clone()).await?);
163 }
164
165 Ok(prepared)
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use alloy_provider::{ProviderBuilder as AlloyProviderBuilder, mock::Asserter};
172 use alloy_rpc_client::RpcClient;
173
174 #[tokio::test]
175 async fn sponsor_relay_reuses_existing_default_transport() {
176 let asserter = Asserter::new();
177 asserter.push_success(&alloy_primitives::U64::from(42));
178 let provider = AlloyProviderBuilder::new().connect_mocked_client(asserter);
179 let connector =
180 sponsor_relay_connector(&provider, "http://127.0.0.1:1").expect("valid relay");
181 let transport = connector.get_transport().await.expect("relay transport");
182 let client = RpcClient::builder().transport(transport, true);
183 let relayed = AlloyProviderBuilder::new().connect_client(client);
184
185 assert_eq!(relayed.get_block_number().await.unwrap(), 42);
186 }
187}