}
}
-fn main() -> Result<(), magical_bitcoin_wallet::error::Error> {
+fn main() -> Result<(), magical_bitcoin_wallet::Error> {
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
let mut wallet: OfflineWallet<_> =
Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
use miniscript::Descriptor;
use magical_bitcoin_wallet::database::memory::MemoryDatabase;
-use magical_bitcoin_wallet::types::ScriptType;
-use magical_bitcoin_wallet::{OfflineWallet, Wallet};
+use magical_bitcoin_wallet::{OfflineWallet, Wallet, ScriptType};
fn main() {
env_logger::init_from_env(
use bitcoin::Network;
use magical_bitcoin_wallet::bitcoin;
-use magical_bitcoin_wallet::blockchain::ElectrumBlockchain;
+use magical_bitcoin_wallet::blockchain::compact_filters::*;
use magical_bitcoin_wallet::cli;
use magical_bitcoin_wallet::sled;
-use magical_bitcoin_wallet::{Client, Wallet};
+use magical_bitcoin_wallet::Wallet;
fn prepare_home_dir() -> PathBuf {
let mut dir = PathBuf::new();
.unwrap();
debug!("database opened successfully");
- let client = Client::new(
- matches.value_of("server").unwrap(),
- matches.value_of("proxy"),
- )
- .unwrap();
- let wallet = Wallet::new(
- descriptor,
- change_descriptor,
- network,
- tree,
- ElectrumBlockchain::from(client),
- )
- .unwrap();
+ let num_threads = 1;
+
+ let mempool = Arc::new(Mempool::default());
+ let peers = (0..num_threads)
+ .map(|_| Peer::connect("192.168.1.136:8333", Arc::clone(&mempool), Network::Bitcoin))
+ .collect::<Result<_, _>>()
+ .unwrap();
+ let blockchain =
+ CompactFiltersBlockchain::new(peers, "./wallet-filters", Some(500_000)).unwrap();
+
+ let wallet = Wallet::new(descriptor, change_descriptor, network, tree, blockchain).unwrap();
let wallet = Arc::new(wallet);
if let Some(_sub_matches) = matches.subcommand_matches("repl") {
use bitcoin::consensus::encode::{deserialize, serialize, serialize_hex};
use bitcoin::hashes::hex::FromHex;
use bitcoin::util::psbt::PartiallySignedTransaction;
-use bitcoin::{Address, OutPoint, Txid};
+use bitcoin::{Address, OutPoint, Script, Txid};
use crate::blockchain::log_progress;
use crate::error::Error;
use crate::types::ScriptType;
use crate::{FeeRate, TxBuilder, Wallet};
-fn parse_recipient(s: &str) -> Result<(Address, u64), String> {
+fn parse_recipient(s: &str) -> Result<(Script, u64), String> {
let parts: Vec<_> = s.split(":").collect();
if parts.len() != 2 {
return Err("Invalid format".to_string());
return Err(format!("{:?}", e));
}
- Ok((addr.unwrap(), val.unwrap()))
+ Ok((addr.unwrap().script_pubkey(), val.unwrap()))
}
fn parse_outpoint(s: &str) -> Result<OutPoint, String> {
/// Descriptor spending policy
#[derive(Debug, Clone, Serialize)]
pub struct Policy {
- id: String,
+ /// Identifier for this policy node
+ pub id: String,
+ /// Type of this policy node
#[serde(flatten)]
- item: SatisfiableItem,
- satisfaction: Satisfaction,
- contribution: Satisfaction,
+ pub item: SatisfiableItem,
+ /// How a much given PSBT already satisfies this polcy node **(currently unused)**
+ pub satisfaction: Satisfaction,
+ /// How the wallet's descriptor can satisfy this policy node
+ pub contribution: Satisfaction,
}
/// An extra condition that must be satisfied but that is out of control of the user
let addr = testutils!(@external descriptors, 10);
wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(
+ addr.script_pubkey(),
+ 25_000,
+ )]))
.unwrap();
}
}
//!
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
//! let (psbt, details) = wallet.create_tx(
-//! TxBuilder::with_recipients(vec![(to_address, 50_000)])
+//! TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
//! .coin_selection(AlwaysSpendEverything),
//! )?;
//!
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
+//! Wallet export
+//!
+//! This modules implements the wallet export format used by [FullyNoded](https://github.com/Fonta1n3/FullyNoded/blob/10b7808c8b929b171cca537fb50522d015168ac9/Docs/Wallets/Wallet-Export-Spec.md).
+//!
+//! ## Examples
+//!
+//! ### Import from JSON
+//!
+//! ```
+//! # use std::str::FromStr;
+//! # use bitcoin::*;
+//! # use magical_bitcoin_wallet::database::*;
+//! # use magical_bitcoin_wallet::wallet::export::*;
+//! # use magical_bitcoin_wallet::*;
+//! let import = r#"{
+//! "descriptor": "wpkh([c258d2e4\/84h\/1h\/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe\/0\/*)",
+//! "blockheight":1782088,
+//! "label":"testnet"
+//! }"#;
+//!
+//! let import = WalletExport::from_str(import)?;
+//! let wallet: OfflineWallet<_> = Wallet::new_offline(&import.descriptor(), import.change_descriptor().as_deref(), Network::Testnet, MemoryDatabase::default())?;
+//! # Ok::<_, magical_bitcoin_wallet::Error>(())
+//! ```
+//!
+//! ### Export a `Wallet`
+//! ```
+//! # use bitcoin::*;
+//! # use magical_bitcoin_wallet::database::*;
+//! # use magical_bitcoin_wallet::wallet::export::*;
+//! # use magical_bitcoin_wallet::*;
+//! let wallet: OfflineWallet<_> = Wallet::new_offline(
+//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
+//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
+//! Network::Testnet,
+//! MemoryDatabase::default()
+//! )?;
+//! let export = WalletExport::export_wallet(&wallet, "exported wallet", true)
+//! .map_err(ToString::to_string)
+//! .map_err(magical_bitcoin_wallet::Error::Generic)?;
+//!
+//! println!("Exported: {}", export.to_string());
+//! # Ok::<_, magical_bitcoin_wallet::Error>(())
+//! ```
+
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::database::BatchDatabase;
use crate::wallet::Wallet;
+/// Structure that contains the export of a wallet
+///
+/// For a usage example see [this module](crate::wallet::export)'s documentation.
#[derive(Debug, Serialize, Deserialize)]
pub struct WalletExport {
descriptor: String,
+ /// Earliest block to rescan when looking for the wallet's transactions
pub blockheight: u32,
+ /// Arbitrary label for the wallet
pub label: String,
}
}
impl WalletExport {
+ /// Export a wallet
+ ///
+ /// This function returns an error if it determines that the `wallet`'s descriptor(s) are not
+ /// supported by Bitcoin Core or don't follow the standard derivation paths defined by BIP44
+ /// and others.
+ ///
+ /// If `include_blockheight` is `true`, this function will look into the `wallet`'s database
+ /// for the oldest transaction it knows and use that as the earliest block to rescan.
+ ///
+ /// If the database is empty or `include_blockheight` is false, the `blockheight` field
+ /// returned will be `0`.
pub fn export_wallet<B: Blockchain, D: BatchDatabase>(
wallet: &Wallet<B, D>,
label: &str,
}
}
+ /// Return the external descriptor
pub fn descriptor(&self) -> String {
self.descriptor.clone()
}
+ /// Return the internal descriptor, if present
pub fn change_descriptor(&self) -> Option<String> {
let replaced = self.descriptor.replace("/0/*", "/1/*");
(None, Some(csv)) => csv,
(Some(rbf), Some(csv)) if rbf < csv => return Err(Error::Generic(format!("Cannot enable RBF with nSequence `{}`, since at least `{}` is required to spend with OP_CSV", rbf, csv))),
(None, _) if requirements.timelock.is_some() => 0xFFFFFFFE,
- (Some(rbf), _) if rbf >= 0xFFFFFFFE => return Err(Error::Generic("Cannot enable RBF with anumber >= 0xFFFFFFFE".into())),
+ (Some(rbf), _) if rbf >= 0xFFFFFFFE => return Err(Error::Generic("Cannot enable RBF with a nSequence >= 0xFFFFFFFE".into())),
(Some(rbf), _) => rbf,
(None, _) => 0xFFFFFFFF,
};
let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0;
fee_amount += calc_fee_bytes(tx.get_weight());
- for (index, (address, satoshi)) in builder.recipients.iter().enumerate() {
+ for (index, (script_pubkey, satoshi)) in builder.recipients.iter().enumerate() {
let value = match builder.send_all {
true => 0,
false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)),
false => *satoshi,
};
- // TODO: proper checks for testnet/regtest p2sh/p2pkh
- if address.network != self.network && self.network != Network::Regtest {
- return Err(Error::InvalidAddressNetwork(address.clone()));
- } else if self.is_mine(&address.script_pubkey())? {
+ if self.is_mine(script_pubkey)? {
received += value;
}
let new_out = TxOut {
- script_pubkey: address.script_pubkey(),
+ script_pubkey: script_pubkey.clone(),
value,
};
fee_amount += calc_fee_bytes(serialize(&new_out).len() * 4);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(0))
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(0))
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(1))
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(1))
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(42))
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(42))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.version, 42);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(
+ addr.script_pubkey(),
+ 25_000,
+ )]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 0);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(
+ addr.script_pubkey(),
+ 25_000,
+ )]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 100_000);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(630_000))
+ .create_tx(
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(630_000),
+ )
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(630_000))
+ .create_tx(
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(630_000),
+ )
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(50000))
+ .create_tx(
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(50000),
+ )
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(
+ addr.script_pubkey(),
+ 25_000,
+ )]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf())
+ .create_tx(
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
+ )
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFD);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf_with_sequence(3))
+ .create_tx(
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
+ .enable_rbf_with_sequence(3),
+ )
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(
+ addr.script_pubkey(),
+ 25_000,
+ )]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
}
#[test]
- #[should_panic(expected = "Cannot enable RBF with anumber >= 0xFFFFFFFE")]
+ #[should_panic(expected = "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")]
fn test_create_tx_invalid_rbf_sequence() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr, 25_000)])
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
.enable_rbf_with_sequence(0xFFFFFFFE),
)
.unwrap();
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr, 25_000)])
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
.enable_rbf_with_sequence(0xDEADBEEF),
)
.unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(
+ addr.script_pubkey(),
+ 25_000,
+ )]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]).do_not_spend_change(),
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
+ .do_not_spend_change(),
)
.unwrap();
}
let addr = wallet.get_new_address().unwrap();
wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 25_000), (addr, 10_000)]).send_all(),
+ TxBuilder::with_recipients(vec![
+ (addr.script_pubkey(), 25_000),
+ (addr.script_pubkey(), 10_000),
+ ])
+ .send_all(),
)
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::default(), @add_signature);
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.fee_rate(FeeRate::from_sat_per_vb(5.0))
.send_all(),
)
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 25_000)])
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
.ordering(TxOrdering::Untouched),
)
.unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 49_800)]))
+ .create_tx(TxBuilder::with_recipients(vec![(
+ addr.script_pubkey(),
+ 49_800,
+ )]))
.unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
// very high fee rate, so that the only output would be below dust
wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.send_all()
.fee_rate(crate::FeeRate::from_sat_per_vb(453.0)),
)
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 30_000), (addr.clone(), 10_000)])
- .ordering(super::tx_builder::TxOrdering::BIP69Lexicographic),
+ TxBuilder::with_recipients(vec![
+ (addr.script_pubkey(), 30_000),
+ (addr.script_pubkey(), 10_000),
+ ])
+ .ordering(super::tx_builder::TxOrdering::BIP69Lexicographic),
)
.unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 30_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(
+ addr.script_pubkey(),
+ 30_000,
+ )]))
.unwrap();
assert_eq!(psbt.inputs[0].sighash_type, Some(bitcoin::SigHashType::All));
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 30_000)])
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 30_000)])
.sighash(bitcoin::SigHashType::Single),
)
.unwrap();
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1);
let addr = testutils!(@external descriptors, 5);
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1);
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_eq!(
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert_eq!(psbt.inputs[0].redeem_script, None);
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
let script = Script::from(
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_some());
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_none());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.force_non_witness_utxo()
.send_all(),
)
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(
+ addr.script_pubkey(),
+ 25_000,
+ )]))
.unwrap();
let tx = psbt.extract_tx();
let txid = tx.txid();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+ .create_tx(TxBuilder::with_recipients(vec![(
+ addr.script_pubkey(),
+ 25_000,
+ )]))
.unwrap();
let tx = psbt.extract_tx();
let txid = tx.txid();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf())
+ .create_tx(
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
+ )
.unwrap();
let tx = psbt.extract_tx();
let txid = tx.txid();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]).enable_rbf())
+ .create_tx(
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
+ )
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.send_all()
.enable_rbf(),
)
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.utxos(vec![OutPoint {
txid: incoming_txid,
vout: 0,
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
+ .create_tx(
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
+ )
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
.create_tx(
- TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
.send_all()
.add_utxo(OutPoint {
txid: incoming_txid,
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
+ .create_tx(
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
+ )
.unwrap();
let mut tx = psbt.extract_tx();
assert_eq!(tx.input.len(), 1);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
+ .create_tx(
+ TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
+ )
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap();
let (mut psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+ .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
.unwrap();
psbt.inputs[0].hd_keypaths.clear();
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
+//! Generalized signers
+//!
+//! This module provides the ability to add customized signers to a [`Wallet`](super::Wallet)
+//! through the [`Wallet::add_signer`](super::Wallet::add_signer) function.
+//!
+//! ```
+//! # use std::sync::Arc;
+//! # use std::str::FromStr;
+//! # use bitcoin::*;
+//! # use bitcoin::util::psbt;
+//! # use bitcoin::util::bip32::Fingerprint;
+//! # use magical_bitcoin_wallet::signer::*;
+//! # use magical_bitcoin_wallet::database::*;
+//! # use magical_bitcoin_wallet::*;
+//! # #[derive(Debug)]
+//! # struct CustomHSM;
+//! # impl CustomHSM {
+//! # fn sign_input(&self, _psbt: &mut psbt::PartiallySignedTransaction, _input: usize) -> Result<(), SignerError> {
+//! # Ok(())
+//! # }
+//! # fn connect() -> Self {
+//! # CustomHSM
+//! # }
+//! # }
+//! #[derive(Debug)]
+//! struct CustomSigner {
+//! device: CustomHSM,
+//! }
+//!
+//! impl CustomSigner {
+//! fn connect() -> Self {
+//! CustomSigner { device: CustomHSM::connect() }
+//! }
+//! }
+//!
+//! impl Signer for CustomSigner {
+//! fn sign(
+//! &self,
+//! psbt: &mut psbt::PartiallySignedTransaction,
+//! input_index: Option<usize>,
+//! ) -> Result<(), SignerError> {
+//! let input_index = input_index.ok_or(SignerError::InputIndexOutOfRange)?;
+//! self.device.sign_input(psbt, input_index)?;
+//!
+//! Ok(())
+//! }
+//!
+//! fn sign_whole_tx(&self) -> bool {
+//! false
+//! }
+//! }
+//!
+//! let custom_signer = CustomSigner::connect();
+//!
+//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
+//! let mut wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
+//! wallet.add_signer(
+//! ScriptType::External,
+//! Fingerprint::from_str("e30f11b8").unwrap().into(),
+//! SignerOrdering(200),
+//! Arc::new(Box::new(custom_signer))
+//! );
+//!
+//! # Ok::<_, magical_bitcoin_wallet::Error>(())
+//! ```
+
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt;
use crate::descriptor::XKeyUtils;
/// Identifier of a signer in the `SignersContainers`. Used as a key to find the right signer among
-/// many of them
+/// multiple of them
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SignerId<Pk: MiniscriptKey> {
PkHash(<Pk as MiniscriptKey>::Hash),
impl std::error::Error for SignerError {}
/// Trait for signers
+///
+/// This trait can be implemented to provide customized signers to the wallet. For an example see
+/// [`this module`](crate::wallet::signer)'s documentation.
pub trait Signer: fmt::Debug {
+ /// Sign a PSBT
+ ///
+ /// The `input_index` argument is only provided if the wallet doesn't declare to sign the whole
+ /// transaction in one go (see [`Signer::sign_whole_tx`]). Otherwise its value is `None` and
+ /// can be ignored.
fn sign(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
input_index: Option<usize>,
) -> Result<(), SignerError>;
+ /// Return whether or not the signer signs the whole transaction in one go instead of every
+ /// input individually
fn sign_whole_tx(&self) -> bool;
+ /// Return the secret key for the signer
+ ///
+ /// This is used internally to reconstruct the original descriptor that may contain secrets.
+ /// External signers that are meant to keep key isolated should just return `None` here (which
+ /// is the default for this method, if not overridden).
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
None
}
}
}
+/// Defines the order in which signers are called
+///
+/// The default value is `100`. Signers with an ordering above that will be called later,
+/// and they will thus see the partial signatures added to the transaction once they get to sign
+/// themselves.
#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
pub struct SignerOrdering(pub usize);
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
+//! Cross-platform time
+//!
+//! This module provides a function to get the current timestamp that works on all the platforms
+//! supported by the library.
+//!
+//! It can be useful to compare it with the timestamps found in
+//! [`TransactionDetails`](crate::types::TransactionDetails).
+
use std::time::Duration;
#[cfg(target_arch = "wasm32")]
#[cfg(not(target_arch = "wasm32"))]
use std::time::{Instant as SystemInstant, SystemTime, UNIX_EPOCH};
+/// Return the current timestamp in seconds
#[cfg(not(target_arch = "wasm32"))]
pub fn get_timestamp() -> u64 {
SystemTime::now()
.unwrap()
.as_secs()
}
+/// Return the current timestamp in seconds
#[cfg(target_arch = "wasm32")]
pub fn get_timestamp() -> u64 {
let millis = Date::now();
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
+//! Transaction builder
+//!
+//! ## Example
+//!
+//! ```
+//! # use std::str::FromStr;
+//! # use bitcoin::*;
+//! # use magical_bitcoin_wallet::*;
+//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
+//! // Create a transaction with one output to `to_address` of 50_000 satoshi, with a custom fee rate
+//! // of 5.0 satoshi/vbyte, only spending non-change outputs and with RBF signaling
+//! // enabled
+//! let builder = TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
+//! .fee_rate(FeeRate::from_sat_per_vb(5.0))
+//! .do_not_spend_change()
+//! .enable_rbf();
+//! ```
+
use std::collections::BTreeMap;
use std::default::Default;
-use bitcoin::{Address, OutPoint, SigHashType, Transaction};
+use bitcoin::{OutPoint, Script, SigHashType, Transaction};
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
use crate::types::{FeeRate, UTXO};
+/// A transaction builder
+///
+/// This structure contains the configuration that the wallet must follow to build a transaction.
+///
+/// For an example see [this module](super::tx_builder)'s documentation;
#[derive(Debug, Default)]
pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
- pub(crate) recipients: Vec<(Address, u64)>,
+ pub(crate) recipients: Vec<(Script, u64)>,
pub(crate) send_all: bool,
pub(crate) fee_rate: Option<FeeRate>,
pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
}
impl TxBuilder<DefaultCoinSelectionAlgorithm> {
+ /// Create an empty builder
pub fn new() -> Self {
Self::default()
}
- pub fn with_recipients(recipients: Vec<(Address, u64)>) -> Self {
+ /// Create a builder starting from a list of recipients
+ pub fn with_recipients(recipients: Vec<(Script, u64)>) -> Self {
Self::default().set_recipients(recipients)
}
}
impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
- pub fn set_recipients(mut self, recipients: Vec<(Address, u64)>) -> Self {
+ /// Replace the recipients already added with a new list
+ pub fn set_recipients(mut self, recipients: Vec<(Script, u64)>) -> Self {
self.recipients = recipients;
self
}
- pub fn add_recipient(mut self, address: Address, amount: u64) -> Self {
- self.recipients.push((address, amount));
+ /// Add a recipient to the internal list
+ pub fn add_recipient(mut self, script_pubkey: Script, amount: u64) -> Self {
+ self.recipients.push((script_pubkey, amount));
self
}
+ /// Send all the selected utxos to a single output
+ ///
+ /// Adding more than one recipients with this option enabled will result in an error.
+ ///
+ /// The value associated with the only recipient is irrelevant and will be replaced by the wallet.
pub fn send_all(mut self) -> Self {
self.send_all = true;
self
}
+ /// Set a custom fee rate
pub fn fee_rate(mut self, fee_rate: FeeRate) -> Self {
self.fee_rate = Some(fee_rate);
self
}
+ /// Set the policy path to use while creating the transaction
+ ///
+ /// This method accepts a map where the key is the policy node id (see
+ /// [`Policy::id`](crate::descriptor::Policy::id)) and the value is the list of the indexes of
+ /// the items that are intended to be satisfied from the policy node (see
+ /// [`SatisfiableItem::Thresh::items`](crate::descriptor::policy::SatisfiableItem::Thresh::items)).
pub fn policy_path(mut self, policy_path: BTreeMap<String, Vec<usize>>) -> Self {
self.policy_path = Some(policy_path);
self
}
- /// These have priority over the "unspendable" utxos
+ /// Replace the internal list of utxos that **must** be spent with a new list
+ ///
+ /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
+ /// the "utxos" and the "unspendable" list, it will be spent.
pub fn utxos(mut self, utxos: Vec<OutPoint>) -> Self {
self.utxos = Some(utxos);
self
}
- /// This has priority over the "unspendable" utxos
+ /// Add a utxo to the internal list of utxos that **must** be spent
+ ///
+ /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
+ /// the "utxos" and the "unspendable" list, it will be spent.
pub fn add_utxo(mut self, utxo: OutPoint) -> Self {
self.utxos.get_or_insert(vec![]).push(utxo);
self
}
+ /// Replace the internal list of unspendable utxos with a new list
+ ///
+ /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::utxos`] and
+ /// [`TxBuilder::add_utxo`] have priority over these. See the docs of the two linked methods
+ /// for more details.
pub fn unspendable(mut self, unspendable: Vec<OutPoint>) -> Self {
self.unspendable = Some(unspendable);
self
}
+ /// Add a utxo to the internal list of unspendable utxos
+ ///
+ /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::utxos`] and
+ /// [`TxBuilder::add_utxo`] have priority over this. See the docs of the two linked methods
+ /// for more details.
pub fn add_unspendable(mut self, unspendable: OutPoint) -> Self {
self.unspendable.get_or_insert(vec![]).push(unspendable);
self
}
+ /// Sign with a specific sig hash
+ ///
+ /// **Use this option very carefully**
pub fn sighash(mut self, sighash: SigHashType) -> Self {
self.sighash = Some(sighash);
self
}
+ /// Choose the ordering for inputs and outputs of the transaction
pub fn ordering(mut self, ordering: TxOrdering) -> Self {
self.ordering = ordering;
self
}
+ /// Use a specific nLockTime while creating the transaction
+ ///
+ /// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator.
pub fn nlocktime(mut self, locktime: u32) -> Self {
self.locktime = Some(locktime);
self
}
+ /// Enable signaling RBF
+ ///
+ /// This will use the default nSequence value of `0xFFFFFFFD`.
pub fn enable_rbf(self) -> Self {
self.enable_rbf_with_sequence(0xFFFFFFFD)
}
+ /// Enable signaling RBF with a specific nSequence value
+ ///
+ /// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
+ /// and the given `nsequence` is lower than the CSV value.
+ ///
+ /// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
+ /// be a valid nSequence to signal RBF.
pub fn enable_rbf_with_sequence(mut self, nsequence: u32) -> Self {
self.rbf = Some(nsequence);
self
}
+ /// Build a transaction with a specific version
+ ///
+ /// The `version` should always be greater than `0` and greater than `1` if the wallet's
+ /// descriptors contain an "older" (OP_CSV) operator.
pub fn version(mut self, version: u32) -> Self {
self.version = Some(Version(version));
self
}
+ /// Do not spend change outputs
+ ///
+ /// This effectively adds all the change outputs to the "unspendable" list. See
+ /// [`TxBuilder::unspendable`].
pub fn do_not_spend_change(mut self) -> Self {
self.change_policy = ChangeSpendPolicy::ChangeForbidden;
self
}
+ /// Only spend change outputs
+ ///
+ /// This effectively adds all the non-change outputs to the "unspendable" list. See
+ /// [`TxBuilder::unspendable`].
pub fn only_spend_change(mut self) -> Self {
self.change_policy = ChangeSpendPolicy::OnlyChange;
self
}
+ /// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and
+ /// [`TxBuilder::only_spend_change`] for some shortcuts.
pub fn change_policy(mut self, change_policy: ChangeSpendPolicy) -> Self {
self.change_policy = change_policy;
self
}
+ /// Fill-in the [`psbt::Input::non_witness_utxo`](bitcoin::util::psbt::Input::non_witness_utxo) field even if the wallet only has SegWit
+ /// descriptors.
+ ///
+ /// This is useful for signers which always require it, like Trezor hardware wallets.
pub fn force_non_witness_utxo(mut self) -> Self {
self.force_non_witness_utxo = true;
self
}
+ /// Choose the coin selection algorithm
+ ///
+ /// Overrides the [`DefaultCoinSelectionAlgorithm`](super::coin_selection::DefaultCoinSelectionAlgorithm).
pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
TxBuilder {
recipients: self.recipients,
}
}
+/// Ordering of the transaction's inputs and outputs
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub enum TxOrdering {
+ /// Randomized (default)
Shuffle,
+ /// Unchanged
Untouched,
+ /// BIP69 / Lexicographic
BIP69Lexicographic,
}
}
}
-// Helper type that wraps u32 and has a default value of 1
+/// Transaction version
+///
+/// Has a default value of `1`
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub(crate) struct Version(pub(crate) u32);
}
}
+/// Policy regarding the use of change outputs when creating a transaction
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub enum ChangeSpendPolicy {
+ /// Use both change and non-change outputs (default)
ChangeAllowed,
+ /// Only use change outputs (see [`TxBuilder::only_spend_change`])
OnlyChange,
+ /// Only use non-change outputs (see [`TxBuilder::do_not_spend_change`])
ChangeForbidden,
}
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr, 25_000)])).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey(), 25_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr, 25_000)])).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey(), 25_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
let mut total_sent = 0;
for _ in 0..5 {
- let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 5_000)])).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 5_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 5_000)]).enable_rbf()).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 5_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000);
- let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
+ let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 49_000)]).enable_rbf()).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();