Skip to content

Commit fee4e67

Browse files
ValuedMammalevanlinjin
authored andcommitted
chore: Add examples directory
- Moved `synopsis.rs` to examples dir - Added `examples/common.rs` to contain the example wallet - Update README to link to the examples
1 parent 62250fe commit fee4e67

File tree

5 files changed

+368
-372
lines changed

5 files changed

+368
-372
lines changed

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,10 @@ bdk_chain = { version = "0.21" }
2323
[features]
2424
default = ["std"]
2525
std = ["miniscript/std"]
26+
27+
[[example]]
28+
name = "synopsis"
29+
30+
[[example]]
31+
name = "common"
32+
crate-type = ["lib"]

README.md

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# `bdk-tx`
1+
# `bdk_tx`
22

33
This is a transaction building library based on `rust-miniscript` that lets you build, update, and finalize PSBTs with minimal dependencies.
44

@@ -7,26 +7,9 @@ Because the project builds upon [miniscript] we support [descriptors] natively.
77

88
Refer to [BIP174], [BIP370], and [BIP371] to learn more about partially signed bitcoin transactions (PSBT).
99

10-
## Example
10+
**Note:**
11+
The library is unstable and API changes should be expected. Check the [examples] directory for detailed usage examples.
1112

12-
To get started see the `DataProvider` trait and the methods for adding inputs and outputs.
13-
14-
```rust
15-
use bdk_tx::Builder;
16-
use bdk_tx::DataProvider;
17-
18-
impl DataProvider for MyType { ... }
19-
20-
let mut builder = Builder::new();
21-
builder.add_input(plan_utxo);
22-
builder.add_output(script_pubkey, amount);
23-
let (mut psbt, finalizer) = builder.build_tx(data_provider)?;
24-
25-
// Your PSBT signing flow...
26-
27-
let result = finalizer.finalize(&mut psbt)?;
28-
assert!(result.is_finalized());
29-
```
3013

3114
## Contributing
3215
Found a bug, have an issue or a feature request? Feel free to open an issue on GitHub. This library is open source licensed under MIT.
@@ -36,3 +19,4 @@ Found a bug, have an issue or a feature request? Feel free to open an issue on G
3619
[BIP174]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
3720
[BIP370]: https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki
3821
[BIP371]: https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki
22+
[examples]: ./examples

examples/common.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#![allow(unused)]
2+
3+
use std::sync::Arc;
4+
5+
use bdk_bitcoind_rpc::Emitter;
6+
use bdk_chain::{bdk_core, Anchor, Balance, ChainPosition, ConfirmationBlockTime};
7+
use bdk_testenv::{bitcoincore_rpc::RpcApi, TestEnv};
8+
use bdk_tx::{CanonicalUnspents, Input, InputCandidates, InputStatus, RbfParams, TxWithStatus};
9+
use bitcoin::{absolute, Address, BlockHash, OutPoint, Transaction, Txid};
10+
use miniscript::{
11+
plan::{Assets, Plan},
12+
Descriptor, DescriptorPublicKey, ForEachKey,
13+
};
14+
15+
const EXTERNAL: &str = "external";
16+
const INTERNAL: &str = "internal";
17+
18+
pub struct Wallet {
19+
pub chain: bdk_chain::local_chain::LocalChain,
20+
pub graph: bdk_chain::IndexedTxGraph<
21+
bdk_core::ConfirmationBlockTime,
22+
bdk_chain::keychain_txout::KeychainTxOutIndex<&'static str>,
23+
>,
24+
}
25+
26+
impl Wallet {
27+
pub fn new(
28+
genesis_hash: BlockHash,
29+
external: Descriptor<DescriptorPublicKey>,
30+
internal: Descriptor<DescriptorPublicKey>,
31+
) -> anyhow::Result<Self> {
32+
let mut indexer = bdk_chain::keychain_txout::KeychainTxOutIndex::default();
33+
indexer.insert_descriptor(EXTERNAL, external)?;
34+
indexer.insert_descriptor(INTERNAL, internal)?;
35+
let graph = bdk_chain::IndexedTxGraph::new(indexer);
36+
let (chain, _) = bdk_chain::local_chain::LocalChain::from_genesis_hash(genesis_hash);
37+
Ok(Self { chain, graph })
38+
}
39+
40+
pub fn sync(&mut self, env: &TestEnv) -> anyhow::Result<()> {
41+
let client = env.rpc_client();
42+
let last_cp = self.chain.tip();
43+
let mut emitter = Emitter::new(client, last_cp, 0);
44+
while let Some(event) = emitter.next_block()? {
45+
let _ = self
46+
.graph
47+
.apply_block_relevant(&event.block, event.block_height());
48+
let _ = self.chain.apply_update(event.checkpoint);
49+
}
50+
let mempool = emitter.mempool()?;
51+
let _ = self.graph.batch_insert_relevant_unconfirmed(mempool);
52+
Ok(())
53+
}
54+
55+
pub fn next_address(&mut self) -> Option<Address> {
56+
let ((_, spk), _) = self.graph.index.next_unused_spk(EXTERNAL)?;
57+
Address::from_script(&spk, bitcoin::consensus::Params::REGTEST).ok()
58+
}
59+
60+
pub fn balance(&self) -> Balance {
61+
let outpoints = self.graph.index.outpoints().clone();
62+
self.graph.graph().balance(
63+
&self.chain,
64+
self.chain.tip().block_id(),
65+
outpoints,
66+
|_, _| true,
67+
)
68+
}
69+
70+
/// TODO: Add to chain sources.
71+
pub fn tip_info(
72+
&self,
73+
client: &impl RpcApi,
74+
) -> anyhow::Result<(absolute::Height, absolute::Time)> {
75+
let tip = self.chain.tip().block_id();
76+
let tip_info = client.get_block_header_info(&tip.hash)?;
77+
let tip_height = absolute::Height::from_consensus(tip.height)?;
78+
let tip_time =
79+
absolute::Time::from_consensus(tip_info.median_time.unwrap_or(tip_info.time) as _)?;
80+
Ok((tip_height, tip_time))
81+
}
82+
83+
// TODO: Maybe create an `AssetsBuilder` or `AssetsExt` that makes it easier to add
84+
// assets from descriptors, etc.
85+
pub fn assets(&self) -> Assets {
86+
let index = &self.graph.index;
87+
let tip = self.chain.tip().block_id();
88+
Assets::new()
89+
.after(absolute::LockTime::from_height(tip.height).expect("must be valid height"))
90+
.add({
91+
let mut pks = vec![];
92+
for (_, desc) in index.keychains() {
93+
desc.for_each_key(|k| {
94+
pks.extend(k.clone().into_single_keys());
95+
true
96+
});
97+
}
98+
pks
99+
})
100+
}
101+
102+
pub fn plan_of_output(&self, outpoint: OutPoint, assets: &Assets) -> Option<Plan> {
103+
let index = &self.graph.index;
104+
let ((k, i), _txout) = index.txout(outpoint)?;
105+
let desc = index.get_descriptor(k)?.at_derivation_index(i).ok()?;
106+
let plan = desc.plan(assets).ok()?;
107+
Some(plan)
108+
}
109+
110+
pub fn canonical_txs(&self) -> impl Iterator<Item = TxWithStatus<Arc<Transaction>>> + '_ {
111+
pub fn status_from_position(
112+
pos: ChainPosition<ConfirmationBlockTime>,
113+
) -> Option<InputStatus> {
114+
match pos {
115+
bdk_chain::ChainPosition::Confirmed { anchor, .. } => Some(InputStatus {
116+
height: absolute::Height::from_consensus(
117+
anchor.confirmation_height_upper_bound(),
118+
)
119+
.expect("must convert to height"),
120+
time: absolute::Time::from_consensus(anchor.confirmation_time as _)
121+
.expect("must convert from time"),
122+
}),
123+
bdk_chain::ChainPosition::Unconfirmed { .. } => None,
124+
}
125+
}
126+
self.graph
127+
.graph()
128+
.list_canonical_txs(&self.chain, self.chain.tip().block_id())
129+
.map(|c_tx| (c_tx.tx_node.tx, status_from_position(c_tx.chain_position)))
130+
}
131+
132+
pub fn all_candidates(&self) -> bdk_tx::InputCandidates {
133+
let index = &self.graph.index;
134+
let assets = self.assets();
135+
let canon_utxos = CanonicalUnspents::new(self.canonical_txs());
136+
let can_select = canon_utxos.try_get_unspents(
137+
index
138+
.outpoints()
139+
.iter()
140+
.filter_map(|(_, op)| Some((*op, self.plan_of_output(*op, &assets)?))),
141+
);
142+
InputCandidates::new([], can_select)
143+
}
144+
145+
pub fn rbf_candidates(
146+
&self,
147+
replace: impl IntoIterator<Item = Txid>,
148+
tip_height: absolute::Height,
149+
) -> anyhow::Result<(bdk_tx::InputCandidates, RbfParams)> {
150+
let index = &self.graph.index;
151+
let assets = self.assets();
152+
let mut canon_utxos = CanonicalUnspents::new(self.canonical_txs());
153+
154+
// Exclude txs that reside-in `rbf_set`.
155+
let rbf_set = canon_utxos
156+
.extract_replacements(replace)
157+
.ok_or(anyhow::anyhow!("cannot replace given txs"))?;
158+
// TODO: We should really be returning an error if we fail to select an input of a tx we
159+
// are intending to replace.
160+
let must_select = rbf_set
161+
.must_select_largest_input_of_each_original_tx(&canon_utxos)?
162+
.into_iter()
163+
.map(|op| canon_utxos.try_get_unspent(op, self.plan_of_output(op, &assets)?))
164+
.collect::<Option<Vec<Input>>>()
165+
.ok_or(anyhow::anyhow!(
166+
"failed to find input of tx we are intending to replace"
167+
))?;
168+
169+
let can_select = index.outpoints().iter().filter_map(|(_, op)| {
170+
canon_utxos.try_get_unspent(*op, self.plan_of_output(*op, &assets)?)
171+
});
172+
Ok((
173+
InputCandidates::new(must_select, can_select)
174+
.filter(rbf_set.candidate_filter(tip_height)),
175+
rbf_set.selector_rbf_params(),
176+
))
177+
}
178+
}

0 commit comments

Comments
 (0)