#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
use std::collections::BTreeMap;
use std::collections::HashSet;
pub use bdk_wallet::Update;
use bdk_wallet::chain::{keychain_txout::KeychainTxOutIndex, local_chain, IndexedTxGraph};
use bdk_wallet::chain::{ConfirmationBlockTime, TxUpdate};
use bdk_wallet::KeychainKind;
pub extern crate kyoto;
pub use kyoto::builder::NodeDefault;
#[doc(inline)]
pub use kyoto::{
ClientError, FeeRate, Info, NodeState, RejectPayload, RejectReason, Requester, ScriptBuf,
SyncUpdate, TrustedPeer, TxBroadcast, TxBroadcastPolicy, Txid, Warning,
};
#[doc(inline)]
pub use kyoto::Receiver;
#[doc(inline)]
pub use kyoto::UnboundedReceiver;
use kyoto::{BlockHash, Event, IndexedBlock};
#[doc(inline)]
pub use builder::NodeBuilderExt;
pub mod builder;
#[derive(Debug)]
pub struct LightClient {
pub requester: Requester,
pub log_subscriber: Receiver<String>,
pub info_subscriber: Receiver<Info>,
pub warning_subscriber: UnboundedReceiver<Warning>,
pub update_subscriber: UpdateSubscriber,
pub node: NodeDefault,
}
#[derive(Debug)]
pub struct UpdateSubscriber {
receiver: UnboundedReceiver<Event>,
chain: local_chain::LocalChain,
graph: IndexedTxGraph<ConfirmationBlockTime, KeychainTxOutIndex<KeychainKind>>,
chain_changeset: BTreeMap<u32, Option<BlockHash>>,
}
impl UpdateSubscriber {
pub async fn update(&mut self) -> Update {
while let Some(message) = self.receiver.recv().await {
match message {
Event::Block(IndexedBlock { height, block }) => {
let hash = block.header.block_hash();
self.chain_changeset.insert(height, Some(hash));
let _ = self.graph.apply_block_relevant(&block, height);
}
Event::BlocksDisconnected(headers) => {
for header in headers {
let height = header.height;
self.chain_changeset.insert(height, None);
}
}
Event::Synced(SyncUpdate {
tip: _,
recent_history,
}) => {
recent_history.into_iter().for_each(|(height, header)| {
self.chain_changeset
.insert(height, Some(header.block_hash()));
});
break;
}
}
}
self.get_scan_response()
}
fn get_scan_response(&mut self) -> Update {
let chain_changeset = core::mem::take(&mut self.chain_changeset);
self.chain
.apply_changeset(&local_chain::ChangeSet::from(chain_changeset))
.expect("chain was initialized with genesis");
let tx_update = TxUpdate::from(self.graph.graph().clone());
let graph = core::mem::take(&mut self.graph);
let last_active_indices = graph.index.last_used_indices();
self.graph = IndexedTxGraph::new(graph.index);
Update {
tx_update,
last_active_indices,
chain: Some(self.chain.tip()),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub enum ScanType {
New,
#[default]
Sync,
Recovery {
from_height: u32,
},
}
pub trait WalletExt {
fn peek_revealed_plus_lookahead(&self) -> Box<dyn Iterator<Item = ScriptBuf>>;
}
impl WalletExt for bdk_wallet::Wallet {
fn peek_revealed_plus_lookahead(&self) -> Box<dyn Iterator<Item = ScriptBuf>> {
let mut spks: HashSet<ScriptBuf> = HashSet::new();
for keychain in [KeychainKind::External, KeychainKind::Internal] {
let last_revealed = self.spk_index().last_revealed_index(keychain).unwrap_or(0);
let lookahead_index = last_revealed + self.spk_index().lookahead();
for index in 0..=lookahead_index {
spks.insert(self.peek_address(keychain, index).script_pubkey());
}
}
Box::new(spks.into_iter())
}
}
pub trait RequesterExt {
fn add_revealed_scripts<'a>(
&'a self,
wallet: &'a bdk_wallet::Wallet,
) -> Result<(), ClientError>;
}
impl RequesterExt for Requester {
fn add_revealed_scripts<'a>(
&'a self,
wallet: &'a bdk_wallet::Wallet,
) -> Result<(), ClientError> {
for keychain in [KeychainKind::External, KeychainKind::Internal] {
let scripts = wallet.spk_index().revealed_keychain_spks(keychain);
for (_, script) in scripts {
self.add_script(script)?;
}
}
Ok(())
}
}