use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::{self, Display, Formatter};
use std::io::{Read, Write};
use std::ops::{Deref, RangeInclusive};
use amplify::Wrapper;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::SECP256K1;
use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
use bitcoin::{
Address, BlockHash, LockTime, Network, PublicKey, Script, Sequence, Transaction, TxOut, Txid,
};
use bitcoin_scripts::address::AddressCompat;
use bitcoin_scripts::PubkeyScript;
use chrono::{DateTime, Utc};
#[cfg(feature = "electrum")]
use electrum_client::HeaderNotification;
use miniscript::descriptor::{DescriptorType, Sh, Wsh};
use miniscript::policy::compiler::CompilerError;
use miniscript::policy::concrete::{Policy, PolicyError};
use miniscript::{Descriptor, Legacy, Segwitv0, Tap};
use strict_encoding::{StrictDecode, StrictEncode};
use wallet::descriptors::derive::DeriveDescriptor;
use wallet::descriptors::{DescrVariants, DescriptorClass};
use wallet::hd::standards::DerivationBlockchain;
use wallet::hd::{
Bip43, DerivationAccount, DerivationStandard, DerivationSubpath, HardenedIndex,
HardenedIndexExpected, IndexRange, IndexRangeList, SegmentIndexes, TerminalStep,
UnhardenedIndex, UnsatisfiableKey, XpubkeyCore,
};
use wallet::onchain::{PublicNetwork, ResolveTx, TxResolverError};
use wallet::slip132::KeyApplication;
use crate::onchain::Comment;
use crate::{
AddressSource, AddressSummary, AddressValue, ElectrumServer, HistoryEntry, Prevout, Signer,
SigsReq, TimelockReq, TimelockedSigs, ToTapTree, TxidMeta, UtxoTxid,
};
#[derive(Getters, Clone, Debug)]
#[derive(StrictEncode, StrictDecode)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct Wallet {
#[getter(skip)]
settings: WalletSettings,
last_indexes: BTreeMap<UnhardenedIndex, UnhardenedIndex>,
#[getter(as_copy)]
last_block: BlockHash,
#[getter(as_copy)]
height: u32,
#[getter(as_copy)]
state: WalletState,
ephemerals: WalletEphemerals,
utxos: BTreeSet<UtxoTxid>,
history: BTreeSet<HistoryEntry>,
}
impl From<WalletSettings> for Wallet {
fn from(settings: WalletSettings) -> Self {
Wallet {
settings,
last_indexes: empty!(),
last_block: BlockHash::all_zeros(),
height: 0,
state: zero!(),
ephemerals: zero!(),
utxos: bset![],
history: bset![],
}
}
}
impl Wallet {
pub fn as_settings(&self) -> &WalletSettings { &self.settings }
pub fn to_settings(&self) -> WalletSettings { self.settings.clone() }
pub fn into_settings(self) -> WalletSettings { self.settings }
pub fn tx_count(&self) -> usize { self.history.len() }
pub fn next_default_index(&self) -> UnhardenedIndex {
self.last_indexes
.get(&UnhardenedIndex::zero())
.and_then(UnhardenedIndex::checked_inc)
.unwrap_or_else(UnhardenedIndex::zero)
}
pub fn next_change_index(&self) -> UnhardenedIndex {
self.last_indexes
.get(&UnhardenedIndex::one())
.and_then(|index| index.checked_inc())
.unwrap_or_else(UnhardenedIndex::zero)
}
pub fn update_next_change_index(&mut self, new_index: UnhardenedIndex) -> bool {
let index = self.last_indexes.entry(UnhardenedIndex::one()).or_default();
let prev_index = *index;
*index = new_index;
prev_index != new_index
}
pub fn indexed_address(&self, index: UnhardenedIndex) -> Address {
let (descriptor, _) = self
.as_settings()
.descriptors_all()
.expect("invalid wallet descriptor");
let d = DeriveDescriptor::<PublicKey>::derive_descriptor(&descriptor, SECP256K1, [
UnhardenedIndex::zero(),
index,
])
.expect("unable to derive address for the wallet descriptor");
d.address(self.settings.network.into())
.expect("unable to derive address for the wallet descriptor")
}
pub fn next_address(&self) -> Address { self.indexed_address(self.next_default_index()) }
pub fn coinselect(&self, value: u64) -> Option<(BTreeSet<Prevout>, u64)> {
let mut prevouts = self.utxos.iter().map(Prevout::from).collect::<Vec<_>>();
prevouts.sort_by_key(|p| p.amount);
let mut acc = 0u64;
let mut take_next = true;
#[allow(clippy::needless_collect)]
let prevouts = prevouts
.into_iter()
.take_while(|p| {
let take_this = take_next;
acc += p.amount;
take_next = acc < value;
take_this
})
.collect::<Vec<_>>();
let mut acc = 0u64;
let mut take_next = true;
let prevouts = prevouts
.into_iter()
.rev()
.take_while(|p| {
let take_this = take_next;
acc += p.amount;
take_next = acc < value;
take_this
})
.collect();
if acc < value {
None
} else {
Some((prevouts, acc))
}
}
pub fn address_info(&self, include_empty: bool) -> Vec<AddressSummary> {
let mut addresses = self
.history
.iter()
.flat_map(|item| item.address_summaries())
.fold(
BTreeMap::<AddressCompat, AddressSummary>::new(),
|mut list, info| {
match list.entry(info.addr_src.address) {
Entry::Vacant(entry) => {
entry.insert(info);
}
Entry::Occupied(entry) => {
entry.into_mut().merge(info);
}
};
list
},
);
for utxo in &self.utxos {
match addresses.entry(utxo.addr_src.address) {
Entry::Vacant(entry) => {
entry.insert(AddressSummary {
addr_src: utxo.addr_src,
balance: utxo.value,
volume: 0,
tx_count: 1,
});
}
Entry::Occupied(mut entry) => {
let item = entry.get_mut();
item.balance = utxo.value;
}
}
}
let max_index = addresses
.values()
.filter(|info| info.addr_src.change == UnhardenedIndex::zero())
.map(|info| info.addr_src.index.first_index() as u16)
.max()
.unwrap_or_default()
+ 20;
if include_empty {
for (index, address) in self
.settings
.addresses(false, 0..=max_index)
.expect("bad descriptor")
{
addresses.entry(address).or_insert(AddressSummary {
addr_src: AddressSource {
address,
change: UnhardenedIndex::zero(),
index,
},
balance: 0,
volume: 0,
tx_count: 0,
});
}
}
addresses.into_values().collect()
}
pub fn update_signers(
&mut self,
signers: impl IntoIterator<Item = Signer>,
) -> Result<u16, DescriptorError> {
self.settings.update_signers(signers)
}
pub fn add_descriptor_class(&mut self, descriptor_class: DescriptorClass) -> bool {
self.settings.add_descriptor_class(descriptor_class)
}
#[cfg(feature = "electrum")]
pub fn update_last_block(&mut self, last_block: &HeaderNotification) {
self.last_block = last_block.header.block_hash();
self.height = last_block.height as u32;
}
pub fn update_fees(&mut self, f0: f64, f1: f64, f2: f64) {
self.ephemerals.fees = (
f0 as f32 * 100_000.0,
f1 as f32 * 100_000.0,
f2 as f32 * 100_000.0,
);
}
pub fn clear_utxos(&mut self) { self.utxos = bset![]; }
pub fn update_utxos(&mut self, batch: BTreeSet<UtxoTxid>) { self.utxos.extend(batch); }
pub fn update_complete(
&mut self,
addr_buffer: &BTreeMap<AddressSource, BTreeSet<TxidMeta>>,
tx_buffer: &[Transaction],
) {
self.state.volume = 0;
self.state.balance = self.utxos.iter().map(|utxo| utxo.value).sum::<u64>();
self.last_indexes = zero!();
for (addr_src, set) in addr_buffer {
if set.is_empty() {
continue;
}
let idx = self
.last_indexes
.entry(addr_src.change_index())
.or_default();
*idx = *idx.deref().max(&addr_src.index);
}
let txid2tx = tx_buffer
.iter()
.map(|tx| (tx.txid(), tx))
.collect::<BTreeMap<_, _>>();
let txid2meta = addr_buffer
.values()
.flat_map(BTreeSet::iter)
.map(|meta| (meta.onchain.txid, meta))
.collect::<BTreeMap<_, _>>();
let script2src = addr_buffer
.keys()
.map(|src| (src.address.script_pubkey().into_inner(), *src))
.collect::<BTreeMap<Script, AddressSource>>();
let txout2addr = |(no, txout): (usize, &TxOut)| -> Option<(u32, AddressValue)> {
script2src
.get(&txout.script_pubkey)
.map(|src| AddressValue {
addr_src: *src,
value: txout.value,
})
.map(|addr| (no as u32, addr))
};
for tx in tx_buffer {
let debit = tx
.output
.iter()
.enumerate()
.filter_map(txout2addr)
.map(|(no, a)| (no, a.addr_src))
.collect();
let credit = tx
.input
.iter()
.enumerate()
.filter_map(|(vin, txin)| {
txid2tx
.get(&txin.previous_output.txid)
.and_then(|tx| tx.output.get(txin.previous_output.vout as usize))
.map(|txout| (vin, txout))
})
.filter_map(txout2addr)
.collect();
let meta = txid2meta[&tx.txid()];
match self
.history
.iter()
.find(|entry| entry.tx.txid() == tx.txid())
{
Some(entry) if entry.onchain != meta.onchain => {
let mut entry = entry.clone();
self.history.remove(&entry);
entry.onchain = meta.onchain;
self.state.volume += entry.value_credited();
self.history.insert(entry);
}
None => {
let entry = HistoryEntry {
onchain: meta.onchain,
tx: tx.clone(),
credit,
debit,
payers: empty!(),
beneficiaries: empty!(),
fee: meta.fee,
comment: None,
};
self.state.volume += entry.value_credited();
self.history.insert(entry);
}
Some(entry) => {
self.state.volume += entry.value_credited();
}
}
}
}
pub fn update_electrum(&mut self, electrum: ElectrumServer) -> bool {
self.settings.update_electrum(electrum)
}
#[allow(clippy::result_unit_err)]
pub fn set_comment(&mut self, txid: Txid, label: String) -> Result<Option<Comment>, ()> {
let mut entry = self
.history
.iter()
.find(|entry| entry.tx.txid() == txid)
.ok_or(())?
.clone();
let comment = entry.comment.clone();
self.history.remove(&entry);
entry.set_comment(label);
self.history.insert(entry);
Ok(comment)
}
}
impl ResolveTx for Wallet {
fn resolve_tx(&self, txid: Txid) -> Result<Transaction, TxResolverError> {
self.history
.iter()
.find(|item| item.onchain.txid == txid)
.map(|meta| meta.tx.clone())
.ok_or_else(|| TxResolverError::with(txid))
}
}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error)]
#[display(doc_comments)]
pub enum DescriptorError {
UnknownSigner(Fingerprint),
UnknownConditionSigner(SpendingCondition),
NoSigners,
NoConditions,
NoDescriptorClasses,
MultipleDescriptorsNotAllowed,
DuplicateCondition(u8, SpendingCondition),
DuplicateSigner(String, Fingerprint),
InsufficientSignerCount(usize, SpendingCondition),
}
#[derive(Getters, Clone, PartialEq, Eq, Hash, Debug)]
#[derive(StrictEncode, StrictDecode)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct WalletSettings {
#[getter(as_copy)]
network: PublicNetwork,
core: WalletDescriptor,
signers: Vec<Signer>,
electrum: ElectrumServer,
}
impl Deref for WalletSettings {
type Target = WalletDescriptor;
fn deref(&self) -> &Self::Target { &self.core }
}
#[derive(Getters, Clone, PartialEq, Eq, Hash, Debug)]
#[derive(StrictEncode, StrictDecode)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct WalletDescriptor {
pub(self) testnet: bool,
pub(self) descriptor_classes: BTreeSet<DescriptorClass>,
pub(self) terminal: DerivationSubpath<TerminalStep>,
pub(self) signing_keys: Vec<XpubkeyCore>,
pub(self) spending_conditions: BTreeSet<(u8, SpendingCondition)>,
}
impl Display for WalletDescriptor {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if self.descriptor_classes.len() == 1 {
let s = match self
.descriptor_classes
.first()
.expect("length checked above")
{
DescriptorClass::PreSegwit => "Legacy ",
DescriptorClass::SegwitV0 => "SegWit ",
DescriptorClass::NestedV0 => "SegWit-compatible ",
DescriptorClass::TaprootC0 => "Taproot ",
};
f.write_str(s)?;
if self.signing_keys.len() == 1 {
f.write_str("single-sig")?;
} else if let Some((_, SpendingCondition::Sigs(TimelockedSigs { sigs, timelock }))) =
self.spending_conditions.first()
{
if timelock != &TimelockReq::Anytime || self.spending_conditions.len() > 1 {
f.write_str("time-locked multi-sig")?;
} else {
let n = self.signing_keys.len() as u16;
write!(
f,
"{}-of-{} multi-sig",
sigs.required_sigs_count().unwrap_or(n),
n
)?;
}
} else {
unreachable!("empty spending conditions");
}
} else {
f.write_str("Multi-descriptor")?;
}
if self.testnet {
f.write_str(" (testnet)")?;
}
Ok(())
}
}
impl WalletSettings {
pub fn new_btc(
signers: impl IntoIterator<Item = Signer>,
spending_conditions: impl IntoIterator<Item = (u8, SpendingCondition)>,
descriptor_class: DescriptorClass,
network: PublicNetwork,
electrum: ElectrumServer,
) -> Result<WalletSettings, DescriptorError> {
let terminal = vec![TerminalStep::range(0u8, 1u8), TerminalStep::Wildcard];
Self::with_unchecked(
signers,
spending_conditions,
[descriptor_class],
terminal.into(),
network,
electrum,
)
}
pub fn new_rgb(
signers: impl IntoIterator<Item = Signer>,
spending_conditions: impl IntoIterator<Item = (u8, SpendingCondition)>,
descriptor_class: DescriptorClass,
network: PublicNetwork,
electrum: ElectrumServer,
) -> Result<WalletSettings, DescriptorError> {
let terminal = vec![
TerminalStep::Range(
IndexRangeList::with([IndexRange::with(0u8, 1u8), IndexRange::new(9u8)])
.expect("hardcoded range"),
),
TerminalStep::Wildcard,
];
Self::with_unchecked(
signers,
spending_conditions,
[descriptor_class],
terminal.into(),
network,
electrum,
)
}
pub fn with_unchecked(
signers: impl IntoIterator<Item = Signer>,
spending_conditions: impl IntoIterator<Item = (u8, SpendingCondition)>,
descriptor_classes: impl IntoIterator<Item = DescriptorClass>,
terminal: DerivationSubpath<TerminalStep>,
network: PublicNetwork,
electrum: ElectrumServer,
) -> Result<WalletSettings, DescriptorError> {
let mut descriptor = WalletSettings {
signers: empty!(),
network,
electrum,
core: WalletDescriptor {
testnet: network.is_testnet(),
descriptor_classes: empty!(),
terminal,
signing_keys: empty!(),
spending_conditions: empty!(),
},
};
for signer in signers {
descriptor.add_signer(signer)?;
}
for (depth, condition) in spending_conditions {
descriptor.add_condition(depth, condition)?;
}
for class in descriptor_classes {
descriptor.add_descriptor_class(class);
}
if descriptor.signers.is_empty() {
return Err(DescriptorError::NoSigners);
}
if descriptor.core.spending_conditions.is_empty() {
return Err(DescriptorError::NoConditions);
}
if descriptor.core.descriptor_classes.is_empty() {
return Err(DescriptorError::NoDescriptorClasses);
}
Ok(descriptor)
}
fn add_descriptor_class(&mut self, class: DescriptorClass) -> bool {
self.core.descriptor_classes.insert(class)
}
fn add_condition(
&mut self,
depth: u8,
condition: impl Into<SpendingCondition>,
) -> Result<(), DescriptorError> {
let condition = condition.into();
if self.signers.is_empty() {
return Err(DescriptorError::NoSigners);
}
if self
.core
.spending_conditions
.contains(&(depth, condition.clone()))
{
return Err(DescriptorError::DuplicateCondition(depth, condition));
}
let signer_count = self.signers.len();
match &condition {
SpendingCondition::Sigs(ts) => match &ts.sigs {
SigsReq::AtLeast(n) if (*n as usize) > signer_count => Err(
DescriptorError::InsufficientSignerCount(signer_count, condition),
),
SigsReq::Specific(_, signers)
if !self
.signers
.iter()
.map(|signer| signer.xpub.fingerprint())
.collect::<BTreeSet<_>>()
.intersection(&signers.iter().copied().collect::<BTreeSet<_>>())
.count()
== signers.len() =>
{
Err(DescriptorError::UnknownConditionSigner(condition))
}
SigsReq::Specific(count, signers) if signers.len() < *count as usize => Err(
DescriptorError::InsufficientSignerCount(signers.len(), condition),
),
SigsReq::AccountBased(at_least, account_no) => {
let count = self
.signers
.iter()
.filter_map(|signer| signer.account)
.filter(|acc_no| acc_no == account_no)
.count();
if count < *at_least as usize {
Err(DescriptorError::InsufficientSignerCount(count, condition))
} else {
self.core.spending_conditions.insert((depth, condition));
Ok(())
}
}
_ => {
self.core.spending_conditions.insert((depth, condition));
Ok(())
}
},
}
}
fn add_signer(&mut self, signer: Signer) -> Result<(), DescriptorError> {
let xpub = signer.xpub.into();
if self.core.signing_keys.contains(&xpub) {
return Err(DescriptorError::DuplicateSigner(
signer.name.clone(),
signer.fingerprint(),
));
}
self.core.signing_keys.push(xpub);
self.signers.push(signer);
Ok(())
}
fn update_signers(
&mut self,
signers: impl IntoIterator<Item = Signer>,
) -> Result<u16, DescriptorError> {
let mut count = 0u16;
for signer in signers {
let fingerprint = signer.fingerprint();
if !self.update_signer(signer) {
return Err(DescriptorError::UnknownSigner(fingerprint));
}
count += 1;
}
Ok(count)
}
fn update_signer(&mut self, signer: Signer) -> bool {
if let Some(index) = self.signers.iter().position(|s| s == &signer) {
self.signers[index] = signer;
true
} else {
false
}
}
pub fn update_electrum(&mut self, electrum: ElectrumServer) -> bool {
if self.electrum != electrum {
self.electrum = electrum;
true
} else {
false
}
}
pub fn descriptors_all(
&self,
) -> Result<
(
Descriptor<DerivationAccount>,
Vec<Descriptor<DerivationAccount>>,
),
miniscript::Error,
> {
let mut descriptors = self
.descriptor_classes
.iter()
.map(|class| self.descriptor_for_class(*class));
Ok((
descriptors
.next()
.expect("wallet core without descriptor class")?,
descriptors.collect::<Result<_, _>>()?,
))
}
pub fn descriptor_for_class(
&self,
class: DescriptorClass,
) -> Result<Descriptor<DerivationAccount>, miniscript::Error> {
if self.signers.len() <= 1 {
let first_key = self
.signers
.first()
.ok_or_else(|| {
miniscript::Error::Unexpected(s!("wallet core does not contain any signers"))
})?
.to_tracking_account(self.terminal.clone());
return Ok(match class {
DescriptorClass::PreSegwit => Descriptor::new_pk(first_key),
DescriptorClass::SegwitV0 => Descriptor::new_wpkh(first_key)?,
DescriptorClass::NestedV0 => Descriptor::new_sh_wpkh(first_key)?,
DescriptorClass::TaprootC0 => Descriptor::new_tr(first_key, None)?,
});
}
let mut dfs_tree = self
.spending_conditions
.iter()
.map(|(depth, cond)| (depth, cond.policy(&self.signers, &self.terminal)));
if class == DescriptorClass::TaprootC0 {
let tree = dfs_tree.try_fold::<_, _, Result<_, miniscript::Error>>(
Vec::new(),
|mut acc, (depth, policy)| {
acc.push((*depth, policy.compile::<Tap>()?));
Ok(acc)
},
)?;
return Descriptor::new_tr(
DerivationAccount::unsatisfiable_key((
self.network.is_testnet(),
self.terminal.clone(),
)),
Some(tree.to_tap_tree()?),
);
}
let (policy, remnant) = dfs_tree.rfold(
(None, None)
as (
Option<Policy<DerivationAccount>>,
Option<Policy<DerivationAccount>>,
),
|(acc, prev), (index, pol)| match (acc, prev) {
(None, None) if index % 2 == 1 => (None, Some(pol)),
(None, None) => (Some(pol), None),
(None, Some(prev)) => (
Some(Policy::Or(vec![
(*index as usize, pol),
(*index as usize + 1, prev),
])),
None,
),
(Some(acc), None) => (
Some(Policy::Or(vec![
(*index as usize, pol),
(*index as usize + 1, acc),
])),
None,
),
_ => unreachable!(),
},
);
let policy = policy.or(remnant).ok_or_else(|| {
miniscript::Error::Unexpected(s!("zero signing accounts must be filtered"))
})?;
let err_mapper = |err| match err {
CompilerError::PolicyError(PolicyError::DuplicatePubKeys) => {
miniscript::Error::Unexpected(s!(
"Multiple spending conditions re-using the same keys require Taproot"
))
}
err => miniscript::Error::CompilerError(err),
};
if class.is_segwit_v0() {
let mut min_sigs = 0usize;
let mut sorted_multi = vec![];
if let Policy::Threshold(k, ref thresh) = policy {
min_sigs = k;
let sigs =
thresh
.iter()
.filter_map(|pol| {
if let Policy::Key(key) = pol {
Some(key.clone())
} else {
None
}
})
.collect::<Vec<_>>();
if sigs.len() == thresh.len() {
sorted_multi = sigs
}
};
let descr = if !sorted_multi.is_empty() {
Wsh::new_sortedmulti(min_sigs, sorted_multi)?
} else {
let ms_witscript = policy.compile::<Segwitv0>().map_err(err_mapper)?;
Wsh::new(ms_witscript)?
};
return Ok(match class {
DescriptorClass::SegwitV0 => Descriptor::Wsh(descr),
DescriptorClass::NestedV0 => Descriptor::Sh(Sh::new_with_wsh(descr)),
_ => unreachable!(),
});
}
let ms = policy.compile::<Legacy>().map_err(err_mapper)?;
Ok(Descriptor::Sh(Sh::new(ms)?))
}
pub fn script_pubkeys(
&self,
change: bool,
range: RangeInclusive<u16>,
) -> Result<BTreeMap<UnhardenedIndex, PubkeyScript>, miniscript::Error> {
let (descriptor, _) = self.descriptors_all()?;
let len = 2; let mut pat = vec![UnhardenedIndex::zero(); len];
pat[len - 2] = if change { UnhardenedIndex::one() } else { UnhardenedIndex::zero() };
range
.map(UnhardenedIndex::from)
.map(|index| -> Result<_, _> {
pat[len - 1] = index;
let d =
DeriveDescriptor::<PublicKey>::derive_descriptor(&descriptor, SECP256K1, &pat)
.map_err(|_| {
miniscript::Error::BadDescriptor(s!("unable to derive script pubkey"))
})?;
Ok((index, d.script_pubkey().into()))
})
.collect()
}
pub fn addresses(
&self,
change: bool,
range: RangeInclusive<u16>,
) -> Result<BTreeMap<UnhardenedIndex, AddressCompat>, miniscript::Error> {
let network = bitcoin::Network::from(self.network);
self.script_pubkeys(change, range)?
.into_iter()
.map(|(index, spk)| -> Result<_, _> {
Ok((
index,
AddressCompat::from_script(&spk, network.into()).ok_or_else(|| {
miniscript::Error::BadDescriptor(s!("address can't be generated"))
})?,
))
})
.collect()
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display, From)]
#[derive(StrictEncode, StrictDecode)]
#[display(inner)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub enum SpendingCondition {
#[from]
Sigs(TimelockedSigs),
}
impl Default for SpendingCondition {
fn default() -> Self { SpendingCondition::Sigs(default!()) }
}
impl SpendingCondition {
pub fn all() -> SpendingCondition {
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::All,
timelock: TimelockReq::Anytime,
})
}
pub fn at_least(sigs: u16) -> SpendingCondition {
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::AtLeast(sigs),
timelock: TimelockReq::Anytime,
})
}
pub fn anybody_after_date(date: DateTime<Utc>) -> SpendingCondition {
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::Any,
timelock: TimelockReq::AfterDate(date),
})
}
pub fn after_date(sigs: SigsReq, date: DateTime<Utc>) -> SpendingCondition {
SpendingCondition::Sigs(TimelockedSigs {
sigs,
timelock: TimelockReq::AfterDate(date),
})
}
pub fn policy(
&self,
signers: &[Signer],
terminal: &DerivationSubpath<TerminalStep>,
) -> Policy<DerivationAccount> {
let accounts: BTreeMap<Fingerprint, DerivationAccount> = signers
.iter()
.map(|signer| {
(
signer.fingerprint(),
signer.to_tracking_account(terminal.clone()),
)
})
.collect::<BTreeMap<Fingerprint, DerivationAccount>>();
let count = accounts.len();
let key_policies = accounts.values().cloned().map(Policy::Key).collect();
let sigs = match self {
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::All, ..
}) => Policy::Threshold(count, key_policies),
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::Any, ..
}) => Policy::Threshold(1, key_policies),
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::AtLeast(k),
..
}) => Policy::Threshold(*k as usize, key_policies),
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::Specific(at_least, signers),
..
}) => Policy::Threshold(
*at_least as usize,
signers
.iter()
.map(|fp| {
Policy::Key(
accounts
.get(fp)
.expect("fingerprint is absent from the accounts")
.clone(),
)
})
.collect(),
),
SpendingCondition::Sigs(TimelockedSigs {
sigs: SigsReq::AccountBased(at_least, account_no),
..
}) => Policy::Threshold(
*at_least as usize,
signers
.iter()
.filter(|signer| {
signer
.account
.map(|acc_no| acc_no == *account_no)
.unwrap_or_default()
})
.map(|signer| {
Policy::Key(
accounts
.get(&signer.fingerprint())
.expect("fingerprint is absent from the accounts")
.clone(),
)
})
.collect(),
),
};
let timelock = match self {
SpendingCondition::Sigs(TimelockedSigs {
timelock: TimelockReq::Anytime,
..
}) => None,
SpendingCondition::Sigs(TimelockedSigs {
timelock: TimelockReq::AfterDate(datetime),
..
}) => Some(Policy::After(
LockTime::from_time(datetime.timestamp() as u32)
.unwrap()
.into(),
)),
SpendingCondition::Sigs(TimelockedSigs {
timelock: TimelockReq::AfterHeight(block),
..
}) => Some(Policy::After(LockTime::from_height(*block).unwrap().into())),
SpendingCondition::Sigs(TimelockedSigs {
timelock: TimelockReq::AfterPeriod(duration),
..
}) => Some(Policy::Older(Sequence::from_512_second_intervals(
duration.intervals(),
))),
SpendingCondition::Sigs(TimelockedSigs {
timelock: TimelockReq::AfterBlock(block),
..
}) => Some(Policy::Older(Sequence::from_height(*block))),
};
timelock
.map(|timelock| Policy::And(vec![sigs.clone(), timelock]))
.unwrap_or(sigs)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, From)]
#[derive(StrictEncode, StrictDecode)]
#[display(inner)]
pub enum DerivationType {
#[from]
LnpBp(DescrVariants),
#[from]
Bip43(Bip43),
}
impl Default for DerivationType {
fn default() -> Self { DerivationType::Bip43(Bip43::Bip48Native) }
}
impl DerivationType {
pub fn bip43(self) -> Option<Bip43> {
match self {
DerivationType::LnpBp(_) => None,
DerivationType::Bip43(bip43) => Some(bip43),
}
}
}
impl DerivationStandard for DerivationType {
fn deduce(derivation: &DerivationPath) -> Option<Self>
where Self: Sized {
Bip43::deduce(derivation).map(DerivationType::Bip43)
}
fn matching(slip: KeyApplication) -> Option<Self>
where Self: Sized {
Bip43::matching(slip).map(DerivationType::Bip43)
}
fn purpose(&self) -> Option<HardenedIndex> {
match self {
DerivationType::LnpBp(_) => None,
DerivationType::Bip43(bip43) => bip43.purpose(),
}
}
fn account_depth(&self) -> Option<u8> {
match self {
DerivationType::LnpBp(_) => None,
DerivationType::Bip43(bip43) => bip43.account_depth(),
}
}
fn coin_type_depth(&self) -> Option<u8> {
match self {
DerivationType::LnpBp(_) => None,
DerivationType::Bip43(bip43) => bip43.coin_type_depth(),
}
}
fn is_account_last_hardened(&self) -> Option<bool> {
match self {
DerivationType::LnpBp(_) => None,
DerivationType::Bip43(bip43) => bip43.is_account_last_hardened(),
}
}
fn network(&self, path: &DerivationPath) -> Option<Result<Network, HardenedIndexExpected>> {
match self {
DerivationType::LnpBp(_) => None,
DerivationType::Bip43(bip43) => bip43.network(path),
}
}
fn account_template_string(&self, blockchain: DerivationBlockchain) -> String {
match self {
DerivationType::LnpBp(_) => s!("m/"),
DerivationType::Bip43(bip43) => bip43.account_template_string(blockchain),
}
}
fn to_origin_derivation(&self, blockchain: DerivationBlockchain) -> DerivationPath {
match self {
DerivationType::LnpBp(_) => empty!(),
DerivationType::Bip43(bip43) => bip43.to_origin_derivation(blockchain),
}
}
fn to_account_derivation(
&self,
account_index: ChildNumber,
blockchain: DerivationBlockchain,
) -> DerivationPath {
match self {
DerivationType::LnpBp(_) => empty!(),
DerivationType::Bip43(bip43) => bip43.to_account_derivation(account_index, blockchain),
}
}
fn to_key_derivation(
&self,
account_index: ChildNumber,
blockchain: DerivationBlockchain,
index: UnhardenedIndex,
case: Option<UnhardenedIndex>,
) -> DerivationPath {
match self {
DerivationType::LnpBp(_) => empty!(),
DerivationType::Bip43(bip43) => {
bip43.to_key_derivation(account_index, blockchain, index, case)
}
}
}
fn descriptor_types(&self) -> &'static [DescriptorType] {
match self {
DerivationType::LnpBp(_) => &[],
DerivationType::Bip43(bip43) => bip43.descriptor_types(),
}
}
fn slip_application(&self) -> Option<KeyApplication> {
match self {
DerivationType::LnpBp(_) => None,
DerivationType::Bip43(bip43) => bip43.slip_application(),
}
}
}
pub trait DerivationStandardExt: DerivationStandard {
fn descriptor_class(&self) -> Option<DescriptorClass>;
}
impl DerivationStandardExt for Bip43 {
fn descriptor_class(&self) -> Option<DescriptorClass> {
Some(match self {
Bip43::Bip44 => DescriptorClass::PreSegwit,
Bip43::Bip45 => DescriptorClass::PreSegwit,
Bip43::Bip48Nested => DescriptorClass::NestedV0,
Bip43::Bip48Native => DescriptorClass::SegwitV0,
Bip43::Bip49 => DescriptorClass::NestedV0,
Bip43::Bip84 => DescriptorClass::SegwitV0,
Bip43::Bip86 => DescriptorClass::TaprootC0,
Bip43::Bip87 => return None,
Bip43::Bip43 { .. } => return None,
_ => return None,
})
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[derive(StrictEncode, StrictDecode)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct WalletState {
pub balance: u64,
pub volume: u64,
}
impl WalletState {
pub fn balance_btc(self) -> f64 { self.balance as f64 / 100_000_000.0 }
pub fn volume_btc(self) -> f64 { self.volume as f64 / 100_000_000.0 }
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)]
#[derive(StrictEncode, StrictDecode)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct Sats(u64);
#[derive(Clone, PartialEq, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct WalletEphemerals {
pub fees: (f32, f32, f32),
pub fiat: String,
pub exchange_rate: f64,
}
impl StrictEncode for WalletEphemerals {
fn strict_encode<E: Write>(&self, mut e: E) -> Result<usize, strict_encoding::Error> {
Ok(
strict_encode_list!(e; self.fees.0, self.fees.1, self.fees.2, self.fiat, self.exchange_rate),
)
}
}
impl StrictDecode for WalletEphemerals {
fn strict_decode<D: Read>(mut d: D) -> Result<Self, strict_encoding::Error> {
Ok(WalletEphemerals {
fees: (
f32::strict_decode(&mut d)?,
f32::strict_decode(&mut d)?,
f32::strict_decode(&mut d)?,
),
fiat: String::strict_decode(&mut d)?,
exchange_rate: f64::strict_decode(&mut d)?,
})
}
}