nakamoto_wallet/
lib.rs

1//! A TUI Bitcoin wallet.
2#![allow(clippy::too_many_arguments)]
3pub mod error;
4pub mod input;
5pub mod logger;
6pub mod wallet;
7
8use std::path::Path;
9use std::{io, net, thread};
10
11use termion::raw::IntoRawMode;
12
13use nakamoto_client::chan;
14use nakamoto_client::handle::Handle;
15use nakamoto_client::Network;
16use nakamoto_client::{Client, Config};
17use nakamoto_common::bitcoin::util::bip32::DerivationPath;
18use nakamoto_common::block::Height;
19
20use crate::error::Error;
21use crate::wallet::Db;
22use crate::wallet::Hw;
23use crate::wallet::Wallet;
24
25/// The network reactor we're going to use.
26type Reactor = nakamoto_net_poll::Reactor<net::TcpStream>;
27
28/// Entry point for running the wallet.
29pub fn run(
30    wallet: &Path,
31    birth: Height,
32    hd_path: DerivationPath,
33    network: Network,
34    connect: Vec<net::SocketAddr>,
35    offline: bool,
36) -> Result<(), Error> {
37    let cfg = Config {
38        network,
39        connect,
40        listen: vec![], // Don't listen for incoming connections.
41        ..Config::default()
42    };
43
44    // Create a new client using `Reactor` for networking.
45    let client = Client::<Reactor>::new()?;
46    let handle = client.handle();
47    let client_recv = handle.events();
48    let (loading_send, loading_recv) = chan::unbounded();
49
50    log::info!("Opening wallet file `{}`..", wallet.display());
51
52    let db = Db::open(wallet)?;
53    let hw = Hw::new(hd_path);
54
55    let (inputs_tx, inputs_rx) = crossbeam_channel::unbounded();
56    let (exit_tx, exit_rx) = crossbeam_channel::bounded(1);
57    let (signals_tx, signals_rx) = crossbeam_channel::unbounded();
58
59    log::info!("Spawning client threads..");
60
61    // Start the UI loop in the background.
62    let t1 = thread::spawn(|| input::run(inputs_tx, exit_rx));
63    // Start the signal handler thread.
64    let t2 = thread::spawn(|| input::signals(signals_tx));
65    // Start the network client in the background.
66    let t3 = thread::spawn(move || {
67        if offline {
68            Ok(())
69        } else {
70            client.load(cfg, loading_send)?.run()
71        }
72    });
73
74    log::info!("Switching to alternative screen..");
75
76    let stdout = io::stdout().into_raw_mode()?;
77    let term = termion::screen::AlternateScreen::from(termion::cursor::HideCursor::from(
78        termion::input::MouseTerminal::from(stdout),
79    ));
80
81    // Run the main wallet loop. This will block until the wallet exits.
82    log::info!("Running main wallet loop..");
83    Wallet::new(handle.clone(), network, db, hw).run(
84        birth,
85        inputs_rx,
86        signals_rx,
87        loading_recv,
88        client_recv,
89        offline,
90        term,
91    )?;
92
93    // Tell other threads that they should exit.
94    log::info!("Exiting..");
95    exit_tx.send(()).unwrap();
96
97    // Shutdown the client, since the main loop exited.
98    log::info!("Shutting down client..");
99    handle.shutdown()?;
100
101    t1.join().unwrap()?;
102    t2.join().unwrap()?;
103    t3.join().unwrap()?;
104
105    Ok(())
106}