Skip to content

Commit 2627944

Browse files
committed
feat: add python tab to language bindings starter example
1 parent 4581dcb commit 2627944

File tree

3 files changed

+153
-2
lines changed

3 files changed

+153
-2
lines changed

docs/cookbook/bindings/starter-example.md

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ So you want to build a bitcoin wallet using BDK. Great! Here is the rough outlin
1616

1717
This page provides a starter example showcasing how BDK can be used to create, sync, and manage a wallet using an Esplora client as a blockchain data source. Familiarity with this example will help you work through the more advanced pages in this section.
1818

19-
You can find [working code examples](https://github.com/bitcoindevkit/book-of-bdk/tree/master/examples) of this example in two programming languages: [Swift](https://github.com/bitcoindevkit/book-of-bdk/tree/master/examples/swift) and [Kotlin](https://github.com/bitcoindevkit/book-of-bdk/tree/master/examples/kotlin). (Note: some additional language bindings are available for BDK, see [3rd Party Bindings](../getting-started/3rd-party-bindings.md)).
19+
You can find [working code examples](https://github.com/bitcoindevkit/book-of-bdk/tree/master/examples) of this example in three programming languages: [Swift](https://github.com/bitcoindevkit/book-of-bdk/tree/master/examples/swift), [Kotlin](https://github.com/bitcoindevkit/book-of-bdk/tree/master/examples/kotlin) and [Python](https://github.com/bitcoindevkit/book-of-bdk/tree/master/examples/python). (Note: some additional language bindings are available for BDK, see [3rd Party Bindings](../getting-started/3rd-party-bindings.md)).
2020

2121
!!!tip
2222
To complete this example from top to bottom, you'll need to create new descriptors and replace the ones provided. Once you do so, you'll run the example twice; on first run the wallet will not have any balance and will exit with an address to send funds to. Once that's done, you can run the example again and the wallet will be able to perform the later steps, namely creating and broadcasting a new transaction.
@@ -35,12 +35,21 @@ You can find [working code examples](https://github.com/bitcoindevkit/book-of-bd
3535
gradle init
3636
```
3737

38+
=== "Python"
39+
40+
```shell
41+
python3 -m venv venv
42+
source venv/bin/activate
43+
mkdir src
44+
touch src/app.py requirements.txt
45+
```
46+
3847
## Add required dependencies
3948

4049
=== "Swift"
4150

4251
```toml title="Package.swift"
43-
--8<-- "examples/swift/quickstart/Package.swift"
52+
--8<-- "examples/swift/starter-example/Package.swift"
4453
```
4554
Or, if you're building an iOS app:
4655

@@ -63,6 +72,12 @@ You can find [working code examples](https://github.com/bitcoindevkit/book-of-bd
6372
}
6473
```
6574

75+
=== "Python"
76+
77+
```text title="requirements.txt"
78+
bdkpython==2.2.0
79+
```
80+
6681
## Use descriptors
6782

6883
To create a wallet using BDK, we need some <a href="https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md" target="_blank">descriptors</a> for our wallet. This example uses public descriptors (meaning they cannot be used to sign transactions) on Signet. Step 7 and below will fail unless you replace those public descriptors with private ones of your own and fund them using Signet coins through a faucet. Refer to the [Creating Descriptors](./keys-descriptors/descriptors.md) page for information on how to generate your own private descriptors.
@@ -82,6 +97,12 @@ To create a wallet using BDK, we need some <a href="https://github.com/bitcoin/b
8297
--8<-- "examples/kotlin/starter-example/app/src/main/kotlin/org/starterexample/App.kt:descriptors"
8398
```
8499

100+
=== "Python"
101+
102+
```python
103+
--8<-- "examples/python/starter-example/src/app.py:descriptors"
104+
```
105+
85106
These are taproot descriptors (`tr()`) using public keys on Signet (`tpub`) as described in <a href="https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki" target="_blank">BIP86</a>. The first descriptor is an HD wallet with a path for generating addresses to give out externally for payments. The second one is used by the wallet to generate addresses to pay ourselves change when sending payments (remember that <a href="https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch06_transactions.adoc#outpoint" target="_blank">UTXOs</a> must be spent in full, so you often need to make change).
86107

87108
## Create or load a wallet
@@ -100,6 +121,12 @@ Next let's load up our wallet.
100121
--8<-- "examples/kotlin/starter-example/app/src/main/kotlin/org/starterexample/App.kt:wallet"
101122
```
102123

124+
=== "Python"
125+
126+
```python
127+
--8<-- "examples/python/starter-example/src/app.py:wallet"
128+
```
129+
103130
## Sync the wallet
104131

105132
Now let's build an Esplora client and use it to request transaction history for the wallet.
@@ -116,6 +143,12 @@ Now let's build an Esplora client and use it to request transaction history for
116143
--8<-- "examples/kotlin/starter-example/app/src/main/kotlin/org/starterexample/App.kt:client"
117144
```
118145

146+
=== "Python"
147+
148+
```python
149+
--8<-- "examples/python/starter-example/src/app.py:client"
150+
```
151+
119152
In cases where you are using new descriptors that do not have a balance yet, the example will request a new address from the wallet and print it out so you can fund the wallet. Remember that this example uses Signet coins!
120153

121154
=== "Swift"
@@ -130,6 +163,12 @@ In cases where you are using new descriptors that do not have a balance yet, the
130163
--8<-- "examples/kotlin/starter-example/app/src/main/kotlin/org/starterexample/App.kt:address"
131164
```
132165

166+
=== "Python"
167+
168+
```python
169+
--8<-- "examples/python/starter-example/src/app.py:address"
170+
```
171+
133172
## Send a transaction
134173

135174
For this step you'll need a wallet built with private keys, funded with some Signet satoshis. You can find a faucet [here](https://signet25.bublina.eu.org/) to get some coins.
@@ -148,6 +187,12 @@ Let's prepare to send a transaction. The two core choices here are where to send
148187
--8<-- "examples/kotlin/starter-example/app/src/main/kotlin/org/starterexample/App.kt:client"
149188
```
150189

190+
=== "Python"
191+
192+
```python
193+
--8<-- "examples/python/starter-example/src/app.py:recipient"
194+
```
195+
151196
Here we are sending 5000 sats back to the faucet (make sure the wallet has at least this much balance, or change this value).
152197

153198
Finally we are ready to build, sign, and broadcast the transaction:
@@ -164,4 +209,10 @@ Finally we are ready to build, sign, and broadcast the transaction:
164209
--8<-- "examples/kotlin/starter-example/app/src/main/kotlin/org/starterexample/App.kt:transaction"
165210
```
166211

212+
=== "Python"
213+
214+
```python
215+
--8<-- "examples/python/starter-example/src/app.py:transaction"
216+
```
217+
167218
We can view our transaction on the [mempool.space Signet explorer](https://mempool.space/signet).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bdkpython==2.2.0
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import sys
2+
from pathlib import Path
3+
from bdkpython import (
4+
Descriptor,
5+
Network,
6+
Persister,
7+
Wallet,
8+
EsploraClient,
9+
KeychainKind,
10+
Address,
11+
Amount,
12+
TxBuilder,
13+
FeeRate,
14+
)
15+
16+
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
17+
PERSISTENCE_FILE_PATH = str(Path.cwd() / "starter.sqlite")
18+
19+
20+
def main():
21+
# --8<-- [start:descriptors]
22+
descriptor = Descriptor(
23+
"tr([12071a7c/86'/1'/0']tpubDCaLkqfh67Qr7ZuRrUNrCYQ54sMjHfsJ4yQSGb3aBr1yqt3yXpamRBUwnGSnyNnxQYu7rqeBiPfw3mjBcFNX4ky2vhjj9bDrGstkfUbLB9T/0/*)#z3x5097m",
24+
Network.SIGNET,
25+
)
26+
change_descriptor = Descriptor(
27+
"tr([12071a7c/86'/1'/0']tpubDCaLkqfh67Qr7ZuRrUNrCYQ54sMjHfsJ4yQSGb3aBr1yqt3yXpamRBUwnGSnyNnxQYu7rqeBiPfw3mjBcFNX4ky2vhjj9bDrGstkfUbLB9T/1/*)#n9r4jswr",
28+
Network.SIGNET,
29+
)
30+
# --8<-- [end:descriptors]
31+
32+
# --8<-- [start:wallet]
33+
db_path = Path(PERSISTENCE_FILE_PATH)
34+
persistence_exists = db_path.exists()
35+
persister = Persister.new_sqlite(PERSISTENCE_FILE_PATH)
36+
37+
if persistence_exists:
38+
print("Loading up existing wallet")
39+
wallet = Wallet.load(
40+
descriptor=descriptor,
41+
change_descriptor=change_descriptor,
42+
persister=persister,
43+
)
44+
else:
45+
print("Creating new wallet")
46+
wallet = Wallet(
47+
descriptor=descriptor,
48+
change_descriptor=change_descriptor,
49+
network=Network.SIGNET,
50+
persister=persister,
51+
)
52+
# --8<-- [end:wallet]
53+
54+
# --8<-- [start:client]
55+
esplora_client = EsploraClient(SIGNET_ESPLORA_URL)
56+
full_scan_request = wallet.start_full_scan().build()
57+
update = esplora_client.full_scan(
58+
request=full_scan_request, stop_gap=10, parallel_requests=1
59+
)
60+
wallet.apply_update(update)
61+
balance = wallet.balance().total.to_sat()
62+
print(f"Balance: {balance}")
63+
# --8<-- [end:client]
64+
65+
# --8<-- [start:address]
66+
if balance < 5000:
67+
print("Your wallet does not have sufficient balance for the following steps!")
68+
address_info = wallet.reveal_next_address(KeychainKind.EXTERNAL)
69+
print(
70+
f"Send Signet coins to address {address_info.address} (address generated at index {address_info.index})"
71+
)
72+
wallet.persist(persister)
73+
sys.exit(0)
74+
# --8<-- [end:address]
75+
76+
# --8<-- [start:recipient]
77+
faucet_address = Address(
78+
"tb1p4tp4l6glyr2gs94neqcpr5gha7344nfyznfkc8szkreflscsdkgqsdent4", Network.SIGNET
79+
)
80+
amount = Amount.from_sat(4000)
81+
# --8<-- [end:recipient]
82+
83+
# --8<-- [start:transaction]
84+
psbt = (
85+
TxBuilder()
86+
.add_recipient(faucet_address.script_pubkey(), amount)
87+
.fee_rate(FeeRate.from_sat_per_vb(7))
88+
.finish(wallet)
89+
)
90+
91+
wallet.sign(psbt)
92+
tx = psbt.extract_tx()
93+
esplora_client.broadcast(tx)
94+
print(f"Transaction broadcast successfully! Txid: {tx.compute_txid()}")
95+
# --8<-- [end:transaction]
96+
97+
98+
if __name__ == "__main__":
99+
main()

0 commit comments

Comments
 (0)