Skip to content

Commit

Permalink
feat: expose Wallet::policies method
Browse files Browse the repository at this point in the history
  • Loading branch information
BitcoinZavior authored and thunderbiscuit committed Nov 15, 2024
1 parent 0329ddc commit 5b74617
Show file tree
Hide file tree
Showing 5 changed files with 457 additions and 3 deletions.
59 changes: 59 additions & 0 deletions bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ interface Wallet {
/// Internally calls [`Self::public_descriptor`] to fetch the right descriptor
string descriptor_checksum(KeychainKind keychain);

[Throws=DescriptorError]
Policy? policies(KeychainKind keychain);

/// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
/// values.
Balance balance();
Expand Down Expand Up @@ -569,6 +572,60 @@ interface Wallet {

interface Update {};

interface Policy {
string id();

string as_string();

boolean requires_path();

SatisfiableItem item();

Satisfaction satisfaction();

Satisfaction contribution();
};

[Enum]
interface SatisfiableItem {
EcdsaSignature(PkOrF key);
SchnorrSignature(PkOrF key);
Sha256Preimage(string hash);
Hash256Preimage(string hash);
Ripemd160Preimage(string hash);
Hash160Preimage(string hash);
AbsoluteTimelock(LockTime value);
RelativeTimelock(u32 value);
Multisig(sequence<PkOrF> keys, u64 threshold);
Thresh(sequence<Policy> items, u64 threshold);
};

[Enum]
interface PkOrF {
Pubkey(string value);
XOnlyPubkey(string value);
Fingerprint(string value);
};

[Enum]
interface LockTime {
Blocks(u32 height);
Seconds(u32 consensus_time);
};

[Enum]
interface Satisfaction {
Partial(u64 n, u64 m, sequence<u64> items, boolean? sorted, record<u32, sequence<Condition>> conditions);
PartialComplete(u64 n, u64 m, sequence<u64> items, boolean? sorted, record<sequence<u32>, sequence<Condition>> conditions);
Complete(Condition condition);
None(string msg);
};

dictionary Condition {
u32? csv;
LockTime? timelock;
};

interface TxBuilder {
constructor();

Expand All @@ -584,6 +641,8 @@ interface TxBuilder {

TxBuilder add_utxo(OutPoint outpoint);

TxBuilder policy_path(record<string, sequence<u64>> policy_path, KeychainKind keychain);

TxBuilder change_policy(ChangeSpendPolicy change_policy);

TxBuilder do_not_spend_change();
Expand Down
6 changes: 6 additions & 0 deletions bdk-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,18 @@ use crate::types::Balance;
use crate::types::BlockId;
use crate::types::CanonicalTx;
use crate::types::ChainPosition;
use crate::types::Condition;
use crate::types::ConfirmationBlockTime;
use crate::types::FullScanRequest;
use crate::types::FullScanRequestBuilder;
use crate::types::FullScanScriptInspector;
use crate::types::KeychainAndIndex;
use crate::types::LocalOutput;
use crate::types::LockTime;
use crate::types::PkOrF;
use crate::types::Policy;
use crate::types::Satisfaction;
use crate::types::SatisfiableItem;
use crate::types::ScriptAmount;
use crate::types::SentAndReceivedValues;
use crate::types::SyncRequest;
Expand Down
121 changes: 121 additions & 0 deletions bdk-ffi/src/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::error::CreateTxError;
use crate::types::ScriptAmount;
use crate::wallet::Wallet;

use bdk_wallet::KeychainKind;
use bitcoin_ffi::{Amount, FeeRate, Script};

use bdk_wallet::bitcoin::amount::Amount as BdkAmount;
Expand All @@ -11,6 +12,8 @@ use bdk_wallet::bitcoin::ScriptBuf as BdkScriptBuf;
use bdk_wallet::bitcoin::{OutPoint, Sequence, Txid};
use bdk_wallet::ChangeSpendPolicy;

use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::str::FromStr;
use std::sync::Arc;
Expand All @@ -21,6 +24,8 @@ pub struct TxBuilder {
pub(crate) recipients: Vec<(BdkScriptBuf, BdkAmount)>,
pub(crate) utxos: Vec<OutPoint>,
pub(crate) unspendable: HashSet<OutPoint>,
pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>,
pub(crate) external_policy_path: Option<BTreeMap<String, Vec<usize>>>,
pub(crate) change_policy: ChangeSpendPolicy,
pub(crate) manually_selected_only: bool,
pub(crate) fee_rate: Option<FeeRate>,
Expand All @@ -37,6 +42,8 @@ impl TxBuilder {
recipients: Vec::new(),
utxos: Vec::new(),
unspendable: HashSet::new(),
internal_policy_path: None,
external_policy_path: None,
change_policy: ChangeSpendPolicy::ChangeAllowed,
manually_selected_only: false,
fee_rate: None,
Expand Down Expand Up @@ -104,6 +111,25 @@ impl TxBuilder {
})
}

pub(crate) fn policy_path(
&self,
policy_path: HashMap<String, Vec<u64>>,
keychain: KeychainKind,
) -> Arc<Self> {
let mut updated_self = self.clone();
let to_update = match keychain {
KeychainKind::Internal => &mut updated_self.internal_policy_path,
KeychainKind::External => &mut updated_self.external_policy_path,
};
*to_update = Some(
policy_path
.into_iter()
.map(|(key, value)| (key, value.into_iter().map(|x| x as usize).collect()))
.collect::<BTreeMap<String, Vec<usize>>>(),
);
Arc::new(updated_self)
}

pub(crate) fn change_policy(&self, change_policy: ChangeSpendPolicy) -> Arc<Self> {
Arc::new(TxBuilder {
change_policy,
Expand Down Expand Up @@ -177,6 +203,12 @@ impl TxBuilder {
for (script, amount) in &self.recipients {
tx_builder.add_recipient(script.clone(), *amount);
}
if let Some(policy_path) = &self.external_policy_path {
tx_builder.policy_path(policy_path.clone(), KeychainKind::External);
}
if let Some(policy_path) = &self.internal_policy_path {
tx_builder.policy_path(policy_path.clone(), KeychainKind::Internal);
}
tx_builder.change_policy(self.change_policy);
if !self.utxos.is_empty() {
tx_builder
Expand Down Expand Up @@ -251,3 +283,92 @@ impl BumpFeeTxBuilder {
Ok(Arc::new(psbt.into()))
}
}

#[cfg(test)]
mod tests {
use std::sync::Arc;

use bitcoin_ffi::Network;

use crate::{
descriptor::Descriptor, esplora::EsploraClient, store::Connection,
types::FullScanScriptInspector, wallet::Wallet,
};

struct FullScanInspector;
impl FullScanScriptInspector for FullScanInspector {
fn inspect(&self, _: bdk_wallet::KeychainKind, _: u32, _: Arc<bitcoin_ffi::Script>) {}
}

#[test]
fn test_policy_path() {
let wallet = create_and_sync_wallet();
let address = wallet
.next_unused_address(bdk_wallet::KeychainKind::External)
.address;
println!("Wallet address: {:?}", address);

let ext_policy = wallet.policies(bdk_wallet::KeychainKind::External);
let int_policy = wallet.policies(bdk_wallet::KeychainKind::Internal);

if let (Ok(Some(ext_policy)), Ok(Some(int_policy))) = (ext_policy, int_policy) {
let ext_path = vec![(ext_policy.id().clone(), vec![0, 1])]
.into_iter()
.collect();
println!("External Policy path : {:?}\n", ext_path);
let int_path = vec![(int_policy.id().clone(), vec![0, 1])]
.into_iter()
.collect();
println!("Internal Policy Path: {:?}\n", int_path);

match crate::tx_builder::TxBuilder::new()
.add_recipient(
&(*address.script_pubkey()).to_owned(),
Arc::new(bitcoin_ffi::Amount::from_sat(1000)),
)
.do_not_spend_change()
.policy_path(int_path, bdk_wallet::KeychainKind::Internal)
.policy_path(ext_path, bdk_wallet::KeychainKind::External)
.finish(&Arc::new(wallet))
{
Ok(tx) => println!("Transaction serialized: {}\n", tx.serialize()),
Err(e) => eprintln!("Error: {:?}", e),
}
} else {
println!("Failed to retrieve valid policies for keychains.");
}
}

fn create_and_sync_wallet() -> Wallet {
let external_descriptor = format!(
"wsh(thresh(2,pk({}/0/*),sj:and_v(v:pk({}/0/*),n:older(6)),snj:and_v(v:pk({}/0/*),after(630000))))",
"tpubD6NzVbkrYhZ4XJBfEJ6gt9DiVdfWJijsQTCE3jtXByW3Tk6AVGQ3vL1NNxg3SjB7QkJAuutACCQjrXD8zdZSM1ZmBENszCqy49ECEHmD6rf",
"tpubD6NzVbkrYhZ4YfAr3jCBRk4SpqB9L1Hh442y83njwfMaker7EqZd7fHMqyTWrfRYJ1e5t2ue6BYjW5i5yQnmwqbzY1a3kfqNxog1AFcD1aE",
"tprv8ZgxMBicQKsPeitVUz3s6cfyCECovNP7t82FaKPa4UKqV1kssWcXgLkMDjzDbgG9GWoza4pL7z727QitfzkiwX99E1Has3T3a1MKHvYWmQZ"
);
let internal_descriptor = format!(
"wsh(thresh(2,pk({}/1/*),sj:and_v(v:pk({}/1/*),n:older(6)),snj:and_v(v:pk({}/1/*),after(630000))))",
"tpubD6NzVbkrYhZ4XJBfEJ6gt9DiVdfWJijsQTCE3jtXByW3Tk6AVGQ3vL1NNxg3SjB7QkJAuutACCQjrXD8zdZSM1ZmBENszCqy49ECEHmD6rf",
"tpubD6NzVbkrYhZ4YfAr3jCBRk4SpqB9L1Hh442y83njwfMaker7EqZd7fHMqyTWrfRYJ1e5t2ue6BYjW5i5yQnmwqbzY1a3kfqNxog1AFcD1aE",
"tprv8ZgxMBicQKsPeitVUz3s6cfyCECovNP7t82FaKPa4UKqV1kssWcXgLkMDjzDbgG9GWoza4pL7z727QitfzkiwX99E1Has3T3a1MKHvYWmQZ"
);
let wallet = Wallet::new(
Arc::new(Descriptor::new(external_descriptor, Network::Signet).unwrap()),
Arc::new(Descriptor::new(internal_descriptor, Network::Signet).unwrap()),
Network::Signet,
Arc::new(Connection::new_in_memory().unwrap()),
)
.unwrap();
let client = EsploraClient::new("https://mutinynet.com/api/".to_string());
let full_scan_builder = wallet.start_full_scan();
let full_scan_request = full_scan_builder
.inspect_spks_for_all_keychains(Arc::new(FullScanInspector))
.unwrap()
.build()
.unwrap();
let update = client.full_scan(full_scan_request, 10, 10).unwrap();
wallet.apply_update(update).unwrap();
println!("Wallet balance: {:?}", wallet.balance().total.to_sat());
wallet
}
}
Loading

0 comments on commit 5b74617

Please sign in to comment.