Skip to main content

foundry_wallets/wallet_browser/
signer.rs

1use 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    /// Send a transaction through the browser wallet.
58    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}