-
Notifications
You must be signed in to change notification settings - Fork 452
Add psbt_signer.rs example #744
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
afilini
merged 1 commit into
bitcoindevkit:master
from
notmandatory:example/psbt_signer
Sep 13, 2022
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| // Copyright (c) 2020-2022 Bitcoin Dev Kit Developers | ||
| // | ||
| // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE | ||
| // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option. | ||
| // You may not use this file except in accordance with one or both of these | ||
| // licenses. | ||
|
|
||
| use bdk::blockchain::{Blockchain, ElectrumBlockchain}; | ||
| use bdk::database::MemoryDatabase; | ||
| use bdk::wallet::AddressIndex; | ||
| use bdk::SyncOptions; | ||
| use bdk::{FeeRate, SignOptions, Wallet}; | ||
| use bitcoin::{Address, Network}; | ||
| use electrum_client::Client; | ||
| use std::error::Error; | ||
| use std::str::FromStr; | ||
|
|
||
| fn main() -> Result<(), Box<dyn Error>> { | ||
| // test keys created with `bdk-cli key generate` and `bdk-cli key derive` commands | ||
| let signing_external_descriptor = "wpkh([e9824965/84'/1'/0']tprv8fvem7qWxY3SGCQczQpRpqTKg455wf1zgixn6MZ4ze8gRfHjov5gXBQTadNfDgqs9ERbZZ3Bi1PNYrCCusFLucT39K525MWLpeURjHwUsfX/0/*)"; | ||
| let signing_internal_descriptor = "wpkh([e9824965/84'/1'/0']tprv8fvem7qWxY3SGCQczQpRpqTKg455wf1zgixn6MZ4ze8gRfHjov5gXBQTadNfDgqs9ERbZZ3Bi1PNYrCCusFLucT39K525MWLpeURjHwUsfX/1/*)"; | ||
|
|
||
| let watch_only_external_descriptor = "wpkh([e9824965/84'/1'/0']tpubDCcguXsm6uj79fSQt4V2EF7SF5b26zCuG2ZZNsbNQuw5G9YWSJuGhg2KknQBywRq4VGTu41zYTCh3QeVFyBdbsymgRX9Mrts94SW7obEdqs/0/*)"; | ||
| let watch_only_internal_descriptor = "wpkh([e9824965/84'/1'/0']tpubDCcguXsm6uj79fSQt4V2EF7SF5b26zCuG2ZZNsbNQuw5G9YWSJuGhg2KknQBywRq4VGTu41zYTCh3QeVFyBdbsymgRX9Mrts94SW7obEdqs/1/*)"; | ||
|
|
||
| // create client for Blockstream's testnet electrum server | ||
| let blockchain = | ||
| ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); | ||
|
|
||
| // create watch only wallet | ||
| let watch_only_wallet: Wallet<MemoryDatabase> = Wallet::new( | ||
| watch_only_external_descriptor, | ||
| Some(watch_only_internal_descriptor), | ||
| Network::Testnet, | ||
| MemoryDatabase::default(), | ||
| )?; | ||
|
|
||
| // create signing wallet | ||
| let signing_wallet: Wallet<MemoryDatabase> = Wallet::new( | ||
| signing_external_descriptor, | ||
| Some(signing_internal_descriptor), | ||
| Network::Testnet, | ||
| MemoryDatabase::default(), | ||
| )?; | ||
|
|
||
| println!("Syncing watch only wallet."); | ||
| watch_only_wallet.sync(&blockchain, SyncOptions::default())?; | ||
|
|
||
| // get deposit address | ||
| let deposit_address = watch_only_wallet.get_address(AddressIndex::New)?; | ||
|
|
||
| let balance = watch_only_wallet.get_balance()?; | ||
| println!("Watch only wallet balances in SATs: {}", balance); | ||
|
|
||
| if balance.get_total() < 10000 { | ||
| println!( | ||
| "Send at least 10000 SATs (0.0001 BTC) from the u01.net testnet faucet to address '{addr}'.\nFaucet URL: https://bitcoinfaucet.uo1.net/?to={addr}", | ||
| addr = deposit_address.address | ||
| ); | ||
| } else if balance.get_spendable() < 10000 { | ||
| println!( | ||
| "Wait for at least 10000 SATs of your wallet transactions to be confirmed...\nBe patient, this could take 10 mins or longer depending on how testnet is behaving." | ||
| ); | ||
| for tx_details in watch_only_wallet | ||
| .list_transactions(false)? | ||
| .iter() | ||
| .filter(|txd| txd.received > 0 && txd.confirmation_time.is_none()) | ||
| { | ||
| println!( | ||
| "See unconfirmed tx for {} SATs: https://mempool.space/testnet/tx/{}", | ||
| tx_details.received, tx_details.txid | ||
| ); | ||
| } | ||
| } else { | ||
| println!("Creating a PSBT sending 9800 SATs plus fee to the u01.net testnet faucet return address 'tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt'."); | ||
| let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?; | ||
| let mut builder = watch_only_wallet.build_tx(); | ||
| builder | ||
| .add_recipient(return_address.script_pubkey(), 9_800) | ||
| .enable_rbf() | ||
| .fee_rate(FeeRate::from_sat_per_vb(1.0)); | ||
|
|
||
| let (mut psbt, details) = builder.finish()?; | ||
| println!("Transaction details: {:#?}", details); | ||
| println!("Unsigned PSBT: {}", psbt); | ||
|
|
||
| // Sign and finalize the PSBT with the signing wallet | ||
| let finalized = signing_wallet.sign(&mut psbt, SignOptions::default())?; | ||
| assert!(finalized, "The PSBT was not finalized!"); | ||
| println!("The PSBT has been signed and finalized."); | ||
|
|
||
| // Broadcast the transaction | ||
| let raw_transaction = psbt.extract_tx(); | ||
| let txid = raw_transaction.txid(); | ||
|
|
||
| blockchain.broadcast(&raw_transaction)?; | ||
| println!("Transaction broadcast! TXID: {txid}.\nExplorer URL: https://mempool.space/testnet/tx/{txid}", txid = txid); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use
.drain_wallet().drain_to(return_address.script_pubkey())to make sure a change is not created. It's also a chance to show that BDK can do more than "just send x to this address".There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea but I do like having some coin selection and change going on, which I think will be how most new projects will be doing rather than sweeping all funds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I think about it, the problem of receiving 10_000 and sending 9_800 is that you create a small change (~150 sats?), which will be uneconomical to spend (the fees for spending it are greater than the utxo value) - which BDK's coin selection won't pick, unless it's explicitly instructed to do so.
What this means is: after running this example many times, we might have the wallet full of these rather small utxos which we can't spend, and users will start to see some weird errors: for example, they might see that the balance is 10_000 (~70 utxos, ~150 each), but the coin selection will fail saying that there are not enough funds.
We can prevent this either by sweeping all funds every time, or by asking users to fill the wallet with 10_000 sats, but then spending only a smaller number, like 5_000
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, nevermind, the CS is smart enough that it picks 200 sats as fee, leaving no residue in the wallet :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I guess also in my testing it used the change for fee and left an empty wallet balance. Yea for BDK coin selection! So we think it's ok to do it this way and not use
drain_toto ensure there's no change?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah the end result should be the same because the leftover is lower than the dust threshold. I can go ahead and merge it like this, my suggestion was based on what I would have done personally, but it also looks good like this.