foundry_wallets/wallet_browser/
signer.rs1use std::{
2 sync::Arc,
3 time::{Duration, Instant},
4};
5
6use alloy_network::{Network, TransactionBuilder};
7use alloy_primitives::{Address, B256, ChainId};
8use alloy_signer::Result;
9use tokio::sync::Mutex;
10use uuid::Uuid;
11
12use crate::wallet_browser::{
13 server::BrowserWalletServer,
14 types::{BrowserTransactionRequest, Connection},
15};
16
17#[derive(Clone, Debug)]
18pub struct BrowserSigner<N: Network> {
19 server: Arc<Mutex<BrowserWalletServer<N>>>,
20 address: Address,
21 chain_id: ChainId,
22}
23
24impl<N: Network> BrowserSigner<N> {
25 pub async fn new(
26 port: u16,
27 open_browser: bool,
28 timeout: Duration,
29 development: bool,
30 ) -> Result<Self> {
31 let mut server = BrowserWalletServer::new(port, open_browser, timeout, development);
32
33 server.start().await.map_err(alloy_signer::Error::other)?;
34
35 let _ = sh_warn!("Browser wallet is still in early development. Use with caution!");
36 let _ = sh_println!("Opening browser for wallet connection...");
37 let _ = sh_println!("Waiting for wallet connection...");
38
39 let start = Instant::now();
40
41 loop {
42 if let Some(Connection { address, chain_id }) = server.get_connection().await {
43 let _ = sh_println!("Wallet connected: {}", address);
44 let _ = sh_println!("Chain ID: {}", chain_id);
45
46 return Ok(Self { server: Arc::new(Mutex::new(server)), address, chain_id });
47 }
48
49 if start.elapsed() > timeout {
50 return Err(alloy_signer::Error::other("Wallet connection timeout"));
51 }
52
53 tokio::time::sleep(Duration::from_secs(1)).await;
54 }
55 }
56
57 pub async fn send_transaction_via_browser(
59 &self,
60 tx_request: N::TransactionRequest,
61 ) -> Result<B256> {
62 if let Some(from) = tx_request.from()
63 && from != self.address
64 {
65 return Err(alloy_signer::Error::other(
66 "Transaction `from` address does not match connected wallet address",
67 ));
68 }
69
70 if let Some(chain_id) = tx_request.chain_id()
71 && chain_id != self.chain_id
72 {
73 return Err(alloy_signer::Error::other(
74 "Transaction `chainId` does not match connected wallet chain ID",
75 ));
76 }
77
78 let request = BrowserTransactionRequest { id: Uuid::new_v4(), request: tx_request };
79
80 let server = self.server.lock().await;
81 let tx_hash =
82 server.request_transaction(request).await.map_err(alloy_signer::Error::other)?;
83
84 tokio::time::sleep(Duration::from_millis(500)).await;
85
86 Ok(tx_hash)
87 }
88
89 pub fn address(&self) -> Address {
90 self.address
91 }
92}
93
94impl<N: Network> Drop for BrowserSigner<N> {
95 fn drop(&mut self) {
96 let server = self.server.clone();
97
98 tokio::spawn(async move {
99 let mut server = server.lock().await;
100 let _ = server.stop().await;
101 });
102 }
103}