Skip to content

Commit 099309a

Browse files
committed
Initial implementation of wallet functionality
1 parent b646a57 commit 099309a

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed

src/wallet.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use crate::logger::{
2+
log_error, log_given_level, log_internal, log_trace, FilesystemLogger, Logger,
3+
};
4+
use crate::Error;
5+
6+
use lightning::chain::chaininterface::{
7+
BroadcasterInterface, ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
8+
};
9+
10+
use bdk::blockchain::{Blockchain, EsploraBlockchain};
11+
use bdk::database::BatchDatabase;
12+
use bdk::wallet::AddressIndex;
13+
use bdk::{SignOptions, SyncOptions};
14+
15+
use bitcoin::{Script, Transaction};
16+
17+
use std::sync::{Arc, Mutex};
18+
19+
pub struct Wallet<D>
20+
where
21+
D: BatchDatabase,
22+
{
23+
// A BDK blockchain used for wallet sync.
24+
blockchain: EsploraBlockchain,
25+
// A BDK on-chain wallet.
26+
wallet: Mutex<bdk::Wallet<D>>,
27+
logger: Arc<FilesystemLogger>,
28+
}
29+
30+
impl<D> Wallet<D>
31+
where
32+
D: BatchDatabase,
33+
{
34+
pub(crate) fn new(
35+
blockchain: EsploraBlockchain, wallet: bdk::Wallet<D>, logger: Arc<FilesystemLogger>,
36+
) -> Self {
37+
let wallet = Mutex::new(wallet);
38+
Self {
39+
blockchain,
40+
wallet,
41+
logger,
42+
}
43+
}
44+
45+
pub(crate) async fn sync(&self) -> Result<(), Error> {
46+
let sync_options = SyncOptions { progress: None };
47+
48+
self.wallet.lock().unwrap().sync(&self.blockchain, sync_options)?;
49+
50+
Ok(())
51+
}
52+
53+
pub(crate) fn create_funding_transaction(
54+
&self, output_script: &Script, value_sats: u64, confirmation_target: ConfirmationTarget,
55+
) -> Result<Transaction, Error> {
56+
let num_blocks = num_blocks_from_conf_target(confirmation_target);
57+
let fee_rate = self.blockchain.estimate_fee(num_blocks)?;
58+
59+
let locked_wallet = self.wallet.lock().unwrap();
60+
let mut tx_builder = locked_wallet.build_tx();
61+
62+
tx_builder.add_recipient(output_script.clone(), value_sats).fee_rate(fee_rate).enable_rbf();
63+
64+
let (mut psbt, _) = tx_builder.finish()?;
65+
log_trace!(self.logger, "Created funding PSBT: {:?}", psbt);
66+
67+
// We double-check that no inputs try to spend non-witness outputs. As we use a SegWit
68+
// wallet descriptor this technically shouldn't ever happen, but better safe than sorry.
69+
for input in &psbt.inputs {
70+
if input.witness_utxo.is_none() {
71+
log_error!(self.logger, "Tried to spend a non-witness funding output. This must not ever happen. Panicking!");
72+
panic!("Tried to spend a non-witness funding output. This must not ever happen.");
73+
}
74+
}
75+
76+
if !locked_wallet.sign(&mut psbt, SignOptions::default())? {
77+
return Err(Error::FundingTxCreationFailed);
78+
}
79+
80+
Ok(psbt.extract_tx())
81+
}
82+
83+
pub(crate) fn get_new_address(&self) -> Result<bitcoin::Address, Error> {
84+
let address_info = self.wallet.lock().unwrap().get_address(AddressIndex::New)?;
85+
Ok(address_info.address)
86+
}
87+
}
88+
89+
impl<D> FeeEstimator for Wallet<D>
90+
where
91+
D: BatchDatabase,
92+
{
93+
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
94+
let num_blocks = num_blocks_from_conf_target(confirmation_target);
95+
let fallback_fee = fallback_fee_from_conf_target(confirmation_target);
96+
self.blockchain.estimate_fee(num_blocks).map_or(fallback_fee, |fee_rate| {
97+
(fee_rate.fee_wu(1000) as u32).max(FEERATE_FLOOR_SATS_PER_KW)
98+
}) as u32
99+
}
100+
}
101+
102+
impl<D> BroadcasterInterface for Wallet<D>
103+
where
104+
D: BatchDatabase,
105+
{
106+
fn broadcast_transaction(&self, tx: &Transaction) {
107+
match self.blockchain.broadcast(tx) {
108+
Ok(_) => {}
109+
Err(err) => {
110+
log_error!(self.logger, "Failed to broadcast transaction: {}", err);
111+
panic!("Failed to broadcast transaction: {}", err);
112+
}
113+
}
114+
}
115+
}
116+
117+
fn num_blocks_from_conf_target(confirmation_target: ConfirmationTarget) -> usize {
118+
match confirmation_target {
119+
ConfirmationTarget::Background => 12,
120+
ConfirmationTarget::Normal => 6,
121+
ConfirmationTarget::HighPriority => 3,
122+
}
123+
}
124+
125+
fn fallback_fee_from_conf_target(confirmation_target: ConfirmationTarget) -> u32 {
126+
match confirmation_target {
127+
ConfirmationTarget::Background => FEERATE_FLOOR_SATS_PER_KW,
128+
ConfirmationTarget::Normal => 2000,
129+
ConfirmationTarget::HighPriority => 5000,
130+
}
131+
}

0 commit comments

Comments
 (0)