Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions crates/cast/src/cmd/erc20.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{str::FromStr, time::Duration};

use crate::{
cmd::send::{cast_send, cast_send_with_access_key},
cmd::send::{cast_send, cast_send_with_access_key, cast_send_with_browser_eip712},
format_uint_exp,
tx::{CastTxSender, SendTxOpts, TxParams},
};
Expand Down Expand Up @@ -446,15 +446,27 @@ impl Erc20Subcommand {
if let Some(sponsor) = &tempo_sponsor {
sponsor.attach_and_print::<N>(&mut tx, browser.address()).await?;
}
let tx_hash = browser.send_transaction_via_browser(tx).await?;
CastTxSender::new(&$provider)
.print_tx_result(
tx_hash,
if chain.is_tempo() {
cast_send_with_browser_eip712(
&$provider,
tx,
&browser,
$send_tx.cast_async,
$send_tx.confirmations,
timeout,
)
.await?
} else {
let tx_hash = browser.send_transaction_via_browser(tx).await?;
CastTxSender::new(&$provider)
.print_tx_result(
tx_hash,
$send_tx.cast_async,
$send_tx.confirmations,
timeout,
)
.await?
}
} else {
let signer = pre_resolved_signer.unwrap_or($send_tx.eth.wallet.signer().await?);
let from = signer.address();
Expand Down
42 changes: 38 additions & 4 deletions crates/cast/src/cmd/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use foundry_common::{
provider::ProviderBuilder,
tempo::TEMPO_BROWSER_GAS_BUFFER,
};
use foundry_wallets::{TempoAccessKeyConfig, WalletSigner};
use foundry_wallets::{TempoAccessKeyConfig, WalletSigner, wallet_browser::signer::BrowserSigner};
use tempo_alloy::TempoNetwork;

use crate::{
Expand Down Expand Up @@ -302,10 +302,23 @@ impl SendTxArgs {
sponsor.attach_and_print::<N>(&mut tx_request, browser.address()).await?;
}

let tx_hash = browser.send_transaction_via_browser(tx_request).await?;
if chain.is_tempo() {
cast_send_with_browser_eip712(
&provider,
tx_request,
&browser,
send_tx.cast_async,
send_tx.confirmations,
timeout,
)
.await
} else {
let tx_hash = browser.send_transaction_via_browser(tx_request).await?;

let cast = CastTxSender::new(&provider);
cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await
let cast = CastTxSender::new(&provider);
cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout)
.await
}
// Case 3:
// Tempo access key (keychain) signing. Uses `sign_with_access_key` which
// handles the provisioning check and embeds `key_authorization` when needed.
Expand Down Expand Up @@ -433,3 +446,24 @@ where
let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash();
CastTxSender::new(provider).print_tx_result(tx_hash, cast_async, confirmations, timeout).await
}

/// Signs a Tempo transaction in the browser using EIP-712 typed data and submits it raw.
///
/// This is used for Tempo because browser wallets cannot sign/send the custom `0x76` typed
/// transaction envelope directly.
pub(crate) async fn cast_send_with_browser_eip712<N: Network, P: Provider<N>>(
provider: &P,
tx: N::TransactionRequest,
browser: &BrowserSigner<N>,
cast_async: bool,
confirmations: u64,
timeout: u64,
) -> Result<()>
where
N::TransactionRequest: FoundryTransactionBuilder<N>,
N::ReceiptResponse: UIfmt + UIfmtReceiptExt,
{
let raw_tx = tx.sign_with_browser_eip712(browser).await?;
let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash();
CastTxSender::new(provider).print_tx_result(tx_hash, cast_async, confirmations, timeout).await
}
42 changes: 42 additions & 0 deletions crates/common/src/transactions/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ use std::num::NonZeroU64;

use alloy_consensus::{
BlobTransactionSidecar, BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant,
transaction::SignerRecoverable,
};
use alloy_eips::{Encodable2718, eip7702::SignedAuthorization};
use alloy_network::{AnyNetwork, Ethereum, Network, NetworkTransactionBuilder};
use alloy_primitives::{Address, B256, Signature, TxKind, U256};
use alloy_provider::Provider;
use alloy_signer::Signer;
use eyre::Result;
use foundry_wallets::wallet_browser::signer::BrowserSigner;
#[cfg(feature = "optimism")]
use op_alloy_network::Optimism;
#[cfg(feature = "optimism")]
Expand Down Expand Up @@ -295,6 +297,16 @@ pub trait FoundryTransactionBuilder<N: Network>: NetworkTransactionBuilder<N> {
) -> impl Future<Output = Result<Vec<u8>>> + Send {
async { eyre::bail!("access key signing is not supported for this network") }
}

/// Signs the transaction as a browser-wallet Tempo transaction using EIP-712 typed data.
///
/// The default implementation returns an error. Only `TempoNetwork` supports this.
fn sign_with_browser_eip712(
self,
_browser: &BrowserSigner<N>,
) -> impl Future<Output = Result<Vec<u8>>> + Send {
async { eyre::bail!("browser EIP-712 signing is not supported for this network") }
}
}

impl FoundryTransactionBuilder<Ethereum> for <Ethereum as Network>::TransactionRequest {
Expand Down Expand Up @@ -550,4 +562,34 @@ impl FoundryTransactionBuilder<TempoNetwork> for <TempoNetwork as Network>::Tran
Ok(buf)
}
}

fn sign_with_browser_eip712(
self,
browser: &BrowserSigner<TempoNetwork>,
) -> impl Future<Output = Result<Vec<u8>>> + Send {
async move {
let tempo_tx = self
.build_aa()
.map_err(|e| eyre::eyre!("failed to build Tempo AA transaction: {e}"))?;
let raw_sig = browser.sign_typed_data_v4(tempo_tx.eip712_typed_data()).await?;
let signature = Signature::try_from(raw_sig.as_ref())
.map_err(|e| eyre::eyre!("failed to parse browser EIP-712 signature: {e}"))?;
let aa_signed = tempo_tx.into_signed(TempoSignature::Primitive(
PrimitiveSignature::Eip712Secp256k1(signature),
));
let recovered = aa_signed
.recover_signer()
.map_err(|e| eyre::eyre!("failed to recover browser EIP-712 signer: {e}"))?;
if recovered != browser.address() {
eyre::bail!(
"browser EIP-712 signature recovered {recovered}, expected {}",
browser.address()
);
}

let mut buf = Vec::new();
aa_signed.encode_2718(&mut buf);
Ok(buf)
}
}
}
Loading