Compare commits
43 Commits
v0.26.0
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
016bba93d1 | ||
|
|
3128fad690 | ||
|
|
a1b112cbbb | ||
|
|
553c337241 | ||
|
|
90d12a96c5 | ||
|
|
5ca1d17adb | ||
|
|
f121372c73 | ||
|
|
44a78cc459 | ||
|
|
2dbad2ddd5 | ||
|
|
ec71ef58be | ||
|
|
4ca7919ca9 | ||
|
|
07aa1f8950 | ||
|
|
d42789db9b | ||
|
|
10f893a4b3 | ||
|
|
13043065b6 | ||
|
|
5fa0b916a2 | ||
|
|
7611a65620 | ||
|
|
458a162c2a | ||
|
|
d15783dba0 | ||
|
|
2723577a84 | ||
|
|
7fefb8a7b9 | ||
|
|
aa842c3844 | ||
|
|
df019c11ec | ||
|
|
11d7d6b80f | ||
|
|
85f8a8a526 | ||
|
|
5e75c856c5 | ||
|
|
b854c78dde | ||
|
|
f5d4750ae4 | ||
|
|
f75ead02ff | ||
|
|
8e54ada436 | ||
|
|
302ad8dea8 | ||
|
|
561e93f6dd | ||
|
|
b8230799cf | ||
|
|
d2bec60046 | ||
|
|
aadc622006 | ||
|
|
202dcfa2b5 | ||
|
|
46bd9a1f15 | ||
|
|
6fcb8985f1 | ||
|
|
dd58a9d548 | ||
|
|
d2a4e2adba | ||
|
|
cbc740c407 | ||
|
|
f50ecdb7e7 | ||
|
|
f01e0e30f3 |
4
.github/workflows/build-python-wheels.yaml
vendored
4
.github/workflows/build-python-wheels.yaml
vendored
@@ -90,7 +90,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: bdkpython-macos-${{ matrix.python }}
|
||||
path: dist/*.whl
|
||||
path: /Users/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
|
||||
|
||||
build-windows-wheel:
|
||||
name: 'Build windows wheel'
|
||||
@@ -123,4 +123,4 @@ jobs:
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: bdkpython-win-${{ matrix.python }}
|
||||
path: dist/*.whl
|
||||
path: D:\a\bdk-ffi\bdk-ffi\bdk-python\dist\*.whl
|
||||
|
||||
4
.github/workflows/publish-python.yaml
vendored
4
.github/workflows/publish-python.yaml
vendored
@@ -82,7 +82,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: bdkpython-macos-${{ matrix.python }}
|
||||
path: dist/*.whl
|
||||
path: /Users/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
|
||||
|
||||
build-windows-wheel:
|
||||
name: 'Build windows wheel'
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: bdkpython-win-${{ matrix.python }}
|
||||
path: dist/*.whl
|
||||
path: D:\a\bdk-ffi\bdk-ffi\bdk-python\dist\*.whl
|
||||
|
||||
publish-pypi:
|
||||
name: 'Publish on PyPI'
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -31,3 +31,6 @@ bdkFFI.h
|
||||
BitcoinDevKit.swift
|
||||
bdk.swift
|
||||
.build
|
||||
|
||||
# Python related
|
||||
__pycache__
|
||||
177
Cargo.lock
generated
177
Cargo.lock
generated
@@ -30,9 +30,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.68"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
|
||||
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||
|
||||
[[package]]
|
||||
name = "askama"
|
||||
@@ -87,9 +87,9 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.60"
|
||||
version = "0.1.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3"
|
||||
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -130,9 +130,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bdk"
|
||||
version = "0.26.0"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b238a07736baee43ba9663933e44b1c6c27b43ef871b9486e175902335d83880"
|
||||
checksum = "51c878ac60a45c41523ff790df555ccb1fbfd634a667220104dcae3e64d7ed9f"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"async-trait",
|
||||
@@ -156,7 +156,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bdk-ffi"
|
||||
version = "0.26.0"
|
||||
version = "0.27.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"bdk",
|
||||
@@ -273,9 +273,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.1"
|
||||
version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
||||
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@@ -285,15 +285,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
|
||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e"
|
||||
checksum = "c77df041dc383319cc661b428b6961a005db4d6808d5e12536931b1ca9556055"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -322,9 +322,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -370,7 +370,7 @@ version = "3.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -527,26 +527,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.7.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
|
||||
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -560,9 +554,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
@@ -591,7 +585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -611,9 +605,9 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
version = "0.3.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -644,9 +638,9 @@ checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.24.2"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14"
|
||||
checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
@@ -674,9 +668,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "maybe-uninit"
|
||||
@@ -723,9 +717,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniscript"
|
||||
version = "8.0.0"
|
||||
version = "9.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f4975078076f0b7b914a3044ad7432d2a7fcec38edb855afdc672e24ca35b69"
|
||||
checksum = "123a10aae81d0712ecc09b780f6f0ae0b0f506a5c4c912974725760d59ba073e"
|
||||
dependencies = [
|
||||
"bitcoin",
|
||||
"serde",
|
||||
@@ -742,9 +736,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.2"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
@@ -752,9 +746,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.0"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
@@ -849,9 +843,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.49"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
|
||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -927,24 +921,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.27.0"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a"
|
||||
checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"memchr",
|
||||
"smallvec 1.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.7"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
|
||||
checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
@@ -996,9 +989,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1"
|
||||
version = "0.24.2"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9512ffd81e3a3503ed401f79c33168b9148c75038956039166cd750eaa037c3"
|
||||
checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62"
|
||||
dependencies = [
|
||||
"bitcoin_hashes 0.11.0",
|
||||
"rand",
|
||||
@@ -1046,9 +1039,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.91"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@@ -1164,9 +1157,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
@@ -1188,9 +1181,9 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.23.0"
|
||||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
|
||||
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"pin-project-lite",
|
||||
@@ -1211,9 +1204,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.10"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -1229,9 +1222,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
@@ -1250,9 +1243,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
@@ -1291,7 +1284,7 @@ dependencies = [
|
||||
"clap 3.2.23",
|
||||
"fs-err",
|
||||
"goblin",
|
||||
"heck 0.4.0",
|
||||
"heck 0.4.1",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"serde",
|
||||
@@ -1360,9 +1353,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.6.1"
|
||||
version = "2.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733b5ad78377302af52c0dbcb2623d78fe50e4b3bf215948ff29e9ee031d8566"
|
||||
checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
@@ -1414,9 +1407,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.83"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
@@ -1424,9 +1417,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.83"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@@ -1439,9 +1432,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.83"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1449,9 +1442,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.83"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1462,15 +1455,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.83"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.60"
|
||||
version = "0.3.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
|
||||
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -1552,42 +1545,42 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
|
||||
@@ -35,21 +35,41 @@ data class AddressInfo (
|
||||
/**
|
||||
* The address index selection strategy to use to derive an address from the wallet’s external descriptor.
|
||||
*
|
||||
* If you’re unsure which one to use, use `AddressIndex.NEW`.
|
||||
* If you’re unsure which one to use, use `AddressIndex.New`.
|
||||
*
|
||||
* @sample org.bitcoindevkit.addressIndexSample
|
||||
*/
|
||||
enum class AddressIndex {
|
||||
sealed class AddressIndex {
|
||||
/** Return a new address after incrementing the current descriptor index. */
|
||||
NEW,
|
||||
object New: AddressIndex(),
|
||||
|
||||
/** Return the address for the current descriptor index if it has not been used in a received transaction.
|
||||
/**
|
||||
* Return the address for the current descriptor index if it has not been used in a received transaction.
|
||||
* Otherwise return a new address as with `AddressIndex.NEW`. Use with caution, if the wallet
|
||||
* has not yet detected an address has been used it could return an already used address.
|
||||
* This function is primarily meant for situations where the caller is untrusted;
|
||||
* for example when deriving donation addresses on-demand for a public web page.
|
||||
*/
|
||||
LAST_UNUSED,
|
||||
object LastUnused: AddressIndex(),
|
||||
|
||||
/**
|
||||
* Return the address for a specific descriptor index. Does not change the current descriptor
|
||||
* index used by [AddressIndex.New] and [AddressIndex.LastUsed].
|
||||
* Use with caution, if an index is given that is less than the current descriptor index
|
||||
* then the returned address may have already been used.
|
||||
*/
|
||||
data class Peek(val index: UInt): AddressIndex(),
|
||||
|
||||
/**
|
||||
* Return the address for a specific descriptor index and reset the current descriptor index
|
||||
* used by [AddressIndex.New] and [AddressIndex.LastUsed] to this value.
|
||||
* Use with caution, if an index is given that is less than the current descriptor index
|
||||
* then the returned address and subsequent addresses returned by calls to [AddressIndex.New]
|
||||
* and [AddressIndex.LastUsed] may have already been used. Also if the index is reset to a
|
||||
* value earlier than the [Blockchain] stopGap (default is 20) then a
|
||||
* larger stopGap should be used to monitor for all possibly used addresses.
|
||||
*/
|
||||
data class Reset(val index: UInt): AddressIndex()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,7 +268,10 @@ class Blockchain(
|
||||
config: BlockchainConfig
|
||||
) {
|
||||
/** Broadcast a transaction. */
|
||||
fun broadcast(psbt: PartiallySignedBitcoinTransaction) {}
|
||||
fun broadcast(transaction: Transaction) {}
|
||||
|
||||
/** Estimate the fee rate required to confirm a transaction in a given target of blocks. */
|
||||
fun estimateFee(target: ULong): FeeRate {}
|
||||
|
||||
/** Get the current height of the blockchain. */
|
||||
fun getHeight(): UInt {}
|
||||
@@ -257,6 +280,18 @@ class Blockchain(
|
||||
fun getBlockHash(height: UInt): String {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A bitcoin transaction.
|
||||
*
|
||||
* @constructor Build a new Bitcoin Transaction.
|
||||
*
|
||||
* @param transactionBytes The transaction bytes, bitcoin consensus encoded.
|
||||
*/
|
||||
class Transaction(transactionBytes: List<UByte>) {
|
||||
/** Return the transaction bytes, bitcoin consensus encoded. */
|
||||
fun serialize(): List<UByte> {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A partially signed bitcoin transaction.
|
||||
*
|
||||
@@ -264,21 +299,21 @@ class Blockchain(
|
||||
*
|
||||
* @param psbtBase64 The PSBT in base64 format.
|
||||
*/
|
||||
class PartiallySignedBitcoinTransaction(psbtBase64: String) {
|
||||
class PartiallySignedTransaction(psbtBase64: String) {
|
||||
/** Return the PSBT in string format, using a base64 encoding. */
|
||||
fun serialize(): String {}
|
||||
|
||||
/** Get the txid of the PSBT. */
|
||||
fun txid(): String {}
|
||||
|
||||
/** Return the transaction as bytes. */
|
||||
fun extractTx(): List<UByte>
|
||||
/** Extract the transaction. */
|
||||
fun extractTx(): Transaction {}
|
||||
|
||||
/**
|
||||
* Combines this PartiallySignedTransaction with another PSBT as described by BIP 174.
|
||||
* In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
|
||||
*/
|
||||
fun combine(other: PartiallySignedBitcoinTransaction): PartiallySignedBitcoinTransaction
|
||||
fun combine(other: PartiallySignedTransaction): PartiallySignedTransaction
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,11 +404,20 @@ class Wallet(
|
||||
*/
|
||||
fun getAddress(addressIndex: AddressIndex): AddressInfo {}
|
||||
|
||||
/**
|
||||
* Return a derived address using the internal (change) descriptor.
|
||||
* If the wallet doesn't have an internal descriptor it will use the external descriptor.
|
||||
* See [AddressIndex] for available address index selection strategies. If none of the keys
|
||||
* in the descriptor are derivable (i.e. does not end with /\*) then the same address will always
|
||||
* be returned for any [AddressIndex].
|
||||
*/
|
||||
fun getInternalAddress(addressIndex: AddressIndex): AddressInfo {}
|
||||
|
||||
/** Return the wallet's balance, across different categories. See [Balance] for the categories. Note that this method only operates on the internal database, which first needs to be [Wallet.sync] manually. */
|
||||
fun getBalance(): Balance {}
|
||||
|
||||
/** Sign a transaction with all the wallet’s signers. */
|
||||
fun sign(psbt: PartiallySignedBitcoinTransaction): Boolean {}
|
||||
fun sign(psbt: PartiallySignedTransaction): Boolean {}
|
||||
|
||||
/** Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually. */
|
||||
fun listTransactions(): List<TransactionDetails> {}
|
||||
@@ -664,7 +708,7 @@ enum class WordCount {
|
||||
* @sample org.bitcoindevkit.txBuilderResultSample2
|
||||
*/
|
||||
data class TxBuilderResult (
|
||||
var psbt: PartiallySignedBitcoinTransaction,
|
||||
var psbt: PartiallySignedTransaction,
|
||||
var transactionDetails: TransactionDetails
|
||||
)
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ fun addressIndexSample() {
|
||||
databaseConfig = DatabaseConfig.Memory
|
||||
)
|
||||
|
||||
fun getLastUnusedAddress(): AddressInfo {
|
||||
return wallet.getAddress(AddressIndex.LAST_UNUSED)
|
||||
fun peekAddress100(): AddressInfo {
|
||||
return wallet.getAddress(AddressIndex.Peek(100u))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,11 +85,7 @@ fun addressInfoSample() {
|
||||
databaseConfig = DatabaseConfig.Memory
|
||||
)
|
||||
|
||||
fun getLastUnusedAddress(): AddressInfo {
|
||||
return wallet.getAddress(AddressIndex.NEW)
|
||||
}
|
||||
|
||||
val newAddress: AddressInfo = getLastUnusedAddress()
|
||||
val newAddress: AddressInfo = wallet.getAddress(AddressIndex.New)
|
||||
|
||||
println("New address at index ${newAddress.index} is ${newAddress.address}")
|
||||
}
|
||||
@@ -107,7 +103,16 @@ fun blockchainSample() {
|
||||
|
||||
val blockchain: Blockchain = Blockchain(blockchainConfig)
|
||||
|
||||
blockchain.broadcast(signedPsbt)
|
||||
val feeRate: FeeRate = blockchain.estimateFee(3u)
|
||||
|
||||
val (psbt, txDetails) = TxBuilder()
|
||||
.addRecipient(faucetAddress.scriptPubkey(), 1000uL)
|
||||
.feeRate(feeRate.asSatPerVb())
|
||||
.finish(wallet)
|
||||
|
||||
wallet.sign(psbt)
|
||||
|
||||
blockchain.broadcast(psbt)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ val blockchainConfig = BlockchainConfig.Electrum(
|
||||
ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u, true)
|
||||
)
|
||||
val wallet = Wallet(externalDescriptor, internalDescriptor, Network.TESTNET, databaseConfig, blockchainConfig)
|
||||
val newAddress = wallet.getAddress(AddressIndex.LAST_UNUSED)
|
||||
val newAddress = wallet.getAddress(AddressIndex.LastUnused)
|
||||
```
|
||||
|
||||
### Snapshot releases
|
||||
@@ -96,6 +96,7 @@ and use the `publishToMavenLocal` task without excluding the signing task:
|
||||
```
|
||||
|
||||
## Known issues
|
||||
### JNA dependency
|
||||
Depending on the JVM version you use, you might not have the JNA dependency on your classpath. The exception thrown will be
|
||||
```shell
|
||||
class file for com.sun.jna.Pointer not found
|
||||
@@ -108,5 +109,8 @@ dependencies {
|
||||
}
|
||||
```
|
||||
|
||||
### x86 emulators
|
||||
For some older versions of macOS, Android Studio will recommend users install the x86 version of the emulator by default. This will not work with the bdk-android library, as we do not support 32-bit architectures. Make sure you install an x86_64 emulator to work with bdk-android.
|
||||
|
||||
[`bdk`]: https://github.com/bitcoindevkit/bdk
|
||||
[`bdk-ffi`]: https://github.com/bitcoindevkit/bdk-ffi
|
||||
|
||||
@@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx1536m
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
kotlin.code.style=official
|
||||
libraryVersion=0.26.0-SNAPSHOT
|
||||
libraryVersion=0.27.1
|
||||
|
||||
@@ -54,7 +54,7 @@ class AndroidLibTest {
|
||||
@Test
|
||||
fun memoryWalletNewAddress() {
|
||||
val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
|
||||
val address = wallet.getAddress(AddressIndex.NEW).address
|
||||
val address = wallet.getAddress(AddressIndex.New).address
|
||||
assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bdk-ffi"
|
||||
version = "0.26.0"
|
||||
version = "0.27.0"
|
||||
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -10,7 +10,7 @@ crate-type = ["staticlib", "cdylib"]
|
||||
name = "bdkffi"
|
||||
|
||||
[dependencies]
|
||||
bdk = { version = "0.26", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled", "rpc"] }
|
||||
bdk = { version = "0.27.1", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled", "rpc"] }
|
||||
uniffi_macros = { version = "0.21.0", features = ["builtin-bindgen"] }
|
||||
uniffi = { version = "0.21.0", features = ["builtin-bindgen"] }
|
||||
|
||||
|
||||
@@ -53,9 +53,12 @@ dictionary AddressInfo {
|
||||
string address;
|
||||
};
|
||||
|
||||
enum AddressIndex {
|
||||
"New",
|
||||
"LastUnused",
|
||||
[Enum]
|
||||
interface AddressIndex {
|
||||
New();
|
||||
LastUnused();
|
||||
Peek(u32 index);
|
||||
Reset(u32 index);
|
||||
};
|
||||
|
||||
enum Network {
|
||||
@@ -162,7 +165,10 @@ interface Blockchain {
|
||||
constructor(BlockchainConfig config);
|
||||
|
||||
[Throws=BdkError]
|
||||
void broadcast([ByRef] PartiallySignedTransaction psbt);
|
||||
void broadcast([ByRef] Transaction transaction);
|
||||
|
||||
[Throws=BdkError]
|
||||
FeeRate estimate_fee(u64 target);
|
||||
|
||||
[Throws=BdkError]
|
||||
u32 get_height();
|
||||
@@ -209,6 +215,9 @@ interface Wallet {
|
||||
[Throws=BdkError]
|
||||
AddressInfo get_address(AddressIndex address_index);
|
||||
|
||||
[Throws=BdkError]
|
||||
AddressInfo get_internal_address(AddressIndex address_index);
|
||||
|
||||
[Throws=BdkError]
|
||||
Balance get_balance();
|
||||
|
||||
@@ -234,6 +243,13 @@ interface FeeRate {
|
||||
float as_sat_per_vb();
|
||||
};
|
||||
|
||||
interface Transaction {
|
||||
[Throws=BdkError]
|
||||
constructor(sequence<u8> transaction_bytes);
|
||||
|
||||
sequence<u8> serialize();
|
||||
};
|
||||
|
||||
interface PartiallySignedTransaction {
|
||||
[Throws=BdkError]
|
||||
constructor(string psbt_base64);
|
||||
@@ -242,7 +258,7 @@ interface PartiallySignedTransaction {
|
||||
|
||||
string txid();
|
||||
|
||||
sequence<u8> extract_tx();
|
||||
Transaction extract_tx();
|
||||
|
||||
[Throws=BdkError]
|
||||
PartiallySignedTransaction combine(PartiallySignedTransaction other);
|
||||
|
||||
201
bdk-ffi/src/blockchain.rs
Normal file
201
bdk-ffi/src/blockchain.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
// use crate::BlockchainConfig;
|
||||
use crate::{BdkError, Transaction};
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
|
||||
use bdk::blockchain::rpc::Auth as BdkAuth;
|
||||
use bdk::blockchain::rpc::RpcSyncParams as BdkRpcSyncParams;
|
||||
use bdk::blockchain::Blockchain as BdkBlockchain;
|
||||
use bdk::blockchain::GetBlockHash;
|
||||
use bdk::blockchain::GetHeight;
|
||||
use bdk::blockchain::{
|
||||
electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig,
|
||||
rpc::RpcConfig as BdkRpcConfig, ConfigurableBlockchain,
|
||||
};
|
||||
use bdk::FeeRate;
|
||||
use std::convert::{From, TryFrom};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
pub(crate) struct Blockchain {
|
||||
blockchain_mutex: Mutex<AnyBlockchain>,
|
||||
}
|
||||
|
||||
impl Blockchain {
|
||||
pub(crate) fn new(blockchain_config: BlockchainConfig) -> Result<Self, BdkError> {
|
||||
let any_blockchain_config = match blockchain_config {
|
||||
BlockchainConfig::Electrum { config } => {
|
||||
AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
|
||||
retry: config.retry,
|
||||
socks5: config.socks5,
|
||||
timeout: config.timeout,
|
||||
url: config.url,
|
||||
stop_gap: usize::try_from(config.stop_gap).unwrap(),
|
||||
validate_domain: config.validate_domain,
|
||||
})
|
||||
}
|
||||
BlockchainConfig::Esplora { config } => {
|
||||
AnyBlockchainConfig::Esplora(EsploraBlockchainConfig {
|
||||
base_url: config.base_url,
|
||||
proxy: config.proxy,
|
||||
concurrency: config.concurrency,
|
||||
stop_gap: usize::try_from(config.stop_gap).unwrap(),
|
||||
timeout: config.timeout,
|
||||
})
|
||||
}
|
||||
BlockchainConfig::Rpc { config } => AnyBlockchainConfig::Rpc(BdkRpcConfig {
|
||||
url: config.url,
|
||||
auth: config.auth.into(),
|
||||
network: config.network,
|
||||
wallet_name: config.wallet_name,
|
||||
sync_params: config.sync_params.map(|p| p.into()),
|
||||
}),
|
||||
};
|
||||
let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?;
|
||||
Ok(Self {
|
||||
blockchain_mutex: Mutex::new(blockchain),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_blockchain(&self) -> MutexGuard<AnyBlockchain> {
|
||||
self.blockchain_mutex.lock().expect("blockchain")
|
||||
}
|
||||
|
||||
pub(crate) fn broadcast(&self, transaction: &Transaction) -> Result<(), BdkError> {
|
||||
let tx = &transaction.internal;
|
||||
self.get_blockchain().broadcast(tx)
|
||||
}
|
||||
|
||||
pub(crate) fn estimate_fee(&self, target: u64) -> Result<Arc<FeeRate>, BdkError> {
|
||||
let result: Result<FeeRate, bdk::Error> =
|
||||
self.get_blockchain().estimate_fee(target as usize);
|
||||
result.map(Arc::new)
|
||||
}
|
||||
|
||||
pub(crate) fn get_height(&self) -> Result<u32, BdkError> {
|
||||
self.get_blockchain().get_height()
|
||||
}
|
||||
|
||||
pub(crate) fn get_block_hash(&self, height: u32) -> Result<String, BdkError> {
|
||||
self.get_blockchain()
|
||||
.get_block_hash(u64::from(height))
|
||||
.map(|hash| hash.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for an ElectrumBlockchain
|
||||
pub struct ElectrumConfig {
|
||||
/// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with ssl:// or tcp:// and include a port
|
||||
/// e.g. ssl://electrum.blockstream.info:60002
|
||||
pub url: String,
|
||||
/// URL of the socks5 proxy server or a Tor service
|
||||
pub socks5: Option<String>,
|
||||
/// Request retry count
|
||||
pub retry: u8,
|
||||
/// Request timeout (seconds)
|
||||
pub timeout: Option<u8>,
|
||||
/// Stop searching addresses for transactions after finding an unused gap of this length
|
||||
pub stop_gap: u64,
|
||||
/// Validate the domain when using SSL
|
||||
pub validate_domain: bool,
|
||||
}
|
||||
|
||||
/// Configuration for an EsploraBlockchain
|
||||
pub struct EsploraConfig {
|
||||
/// Base URL of the esplora service
|
||||
/// e.g. https://blockstream.info/api/
|
||||
pub base_url: String,
|
||||
/// Optional URL of the proxy to use to make requests to the Esplora server
|
||||
/// The string should be formatted as: <protocol>://<user>:<password>@host:<port>.
|
||||
/// Note that the format of this value and the supported protocols change slightly between the
|
||||
/// sync version of esplora (using ureq) and the async version (using reqwest). For more
|
||||
/// details check with the documentation of the two crates. Both of them are compiled with
|
||||
/// the socks feature enabled.
|
||||
/// The proxy is ignored when targeting wasm32.
|
||||
pub proxy: Option<String>,
|
||||
/// Number of parallel requests sent to the esplora service (default: 4)
|
||||
pub concurrency: Option<u8>,
|
||||
/// Stop searching addresses for transactions after finding an unused gap of this length.
|
||||
pub stop_gap: u64,
|
||||
/// Socket timeout.
|
||||
pub timeout: Option<u64>,
|
||||
}
|
||||
|
||||
pub enum Auth {
|
||||
/// No authentication
|
||||
None,
|
||||
/// Authentication with username and password, usually [Auth::Cookie] should be preferred
|
||||
UserPass {
|
||||
/// Username
|
||||
username: String,
|
||||
/// Password
|
||||
password: String,
|
||||
},
|
||||
/// Authentication with a cookie file
|
||||
Cookie {
|
||||
/// Cookie file
|
||||
file: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Auth> for BdkAuth {
|
||||
fn from(auth: Auth) -> Self {
|
||||
match auth {
|
||||
Auth::None => BdkAuth::None,
|
||||
Auth::UserPass { username, password } => BdkAuth::UserPass { username, password },
|
||||
Auth::Cookie { file } => BdkAuth::Cookie {
|
||||
file: PathBuf::from(file),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync parameters for Bitcoin Core RPC.
|
||||
///
|
||||
/// In general, BDK tries to sync `scriptPubKey`s cached in `Database` with
|
||||
/// `scriptPubKey`s imported in the Bitcoin Core Wallet. These parameters are used for determining
|
||||
/// how the `importdescriptors` RPC calls are to be made.
|
||||
pub struct RpcSyncParams {
|
||||
/// The minimum number of scripts to scan for on initial sync.
|
||||
pub start_script_count: u64,
|
||||
/// Time in unix seconds in which initial sync will start scanning from (0 to start from genesis).
|
||||
pub start_time: u64,
|
||||
/// Forces every sync to use `start_time` as import timestamp.
|
||||
pub force_start_time: bool,
|
||||
/// RPC poll rate (in seconds) to get state updates.
|
||||
pub poll_rate_sec: u64,
|
||||
}
|
||||
|
||||
impl From<RpcSyncParams> for BdkRpcSyncParams {
|
||||
fn from(params: RpcSyncParams) -> Self {
|
||||
BdkRpcSyncParams {
|
||||
start_script_count: params.start_script_count as usize,
|
||||
start_time: params.start_time,
|
||||
force_start_time: params.force_start_time,
|
||||
poll_rate_sec: params.poll_rate_sec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// RpcBlockchain configuration options
|
||||
pub struct RpcConfig {
|
||||
/// The bitcoin node url
|
||||
pub url: String,
|
||||
/// The bitcoin node authentication mechanism
|
||||
pub auth: Auth,
|
||||
/// The network we are using (it will be checked the bitcoin node network matches this)
|
||||
pub network: Network,
|
||||
/// The wallet name in the bitcoin node, consider using [crate::wallet::wallet_name_from_descriptor] for this
|
||||
pub wallet_name: String,
|
||||
/// Sync parameters
|
||||
pub sync_params: Option<RpcSyncParams>,
|
||||
}
|
||||
|
||||
/// Type that can contain any of the blockchain configurations defined by the library.
|
||||
pub enum BlockchainConfig {
|
||||
/// Electrum client
|
||||
Electrum { config: ElectrumConfig },
|
||||
/// Esplora client
|
||||
Esplora { config: EsploraConfig },
|
||||
/// Bitcoin Core RPC client
|
||||
Rpc { config: RpcConfig },
|
||||
}
|
||||
14
bdk-ffi/src/database.rs
Normal file
14
bdk-ffi/src/database.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use bdk::database::any::{SledDbConfiguration, SqliteDbConfiguration};
|
||||
|
||||
/// Type that can contain any of the database configurations defined by the library
|
||||
/// This allows storing a single configuration that can be loaded into an AnyDatabaseConfig
|
||||
/// instance. Wallets that plan to offer users the ability to switch blockchain backend at runtime
|
||||
/// will find this particularly useful.
|
||||
pub enum DatabaseConfig {
|
||||
/// Memory database has no config
|
||||
Memory,
|
||||
/// Simple key-value embedded database based on sled
|
||||
Sled { config: SledDbConfiguration },
|
||||
/// Sqlite embedded database using rusqlite
|
||||
Sqlite { config: SqliteDbConfiguration },
|
||||
}
|
||||
334
bdk-ffi/src/descriptor.rs
Normal file
334
bdk-ffi/src/descriptor.rs
Normal file
@@ -0,0 +1,334 @@
|
||||
use crate::{BdkError, DescriptorPublicKey, DescriptorSecretKey};
|
||||
use bdk::bitcoin::secp256k1::Secp256k1;
|
||||
use bdk::bitcoin::util::bip32::Fingerprint;
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::descriptor::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap};
|
||||
use bdk::keys::{
|
||||
DescriptorPublicKey as BdkDescriptorPublicKey, DescriptorSecretKey as BdkDescriptorSecretKey,
|
||||
};
|
||||
use bdk::template::{
|
||||
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, DescriptorTemplate,
|
||||
};
|
||||
use bdk::KeychainKind;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Descriptor {
|
||||
pub(crate) extended_descriptor: ExtendedDescriptor,
|
||||
pub(crate) key_map: KeyMap,
|
||||
}
|
||||
|
||||
impl Descriptor {
|
||||
pub(crate) fn new(descriptor: String, network: Network) -> Result<Self, BdkError> {
|
||||
let secp = Secp256k1::new();
|
||||
let (extended_descriptor, key_map) = descriptor.into_wallet_descriptor(&secp, network)?;
|
||||
Ok(Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip44(
|
||||
secret_key: Arc<DescriptorSecretKey>,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let derivable_key = secret_key.descriptor_secret_key_mutex.lock().unwrap();
|
||||
|
||||
match derivable_key.deref() {
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip44(derivable_key, keychain_kind).build(network).unwrap();
|
||||
Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
}
|
||||
}
|
||||
BdkDescriptorSecretKey::Single(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip44_public(
|
||||
public_key: Arc<DescriptorPublicKey>,
|
||||
fingerprint: String,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||
let derivable_key = public_key.descriptor_public_key_mutex.lock().unwrap();
|
||||
|
||||
match derivable_key.deref() {
|
||||
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip44Public(derivable_key, fingerprint, keychain_kind)
|
||||
.build(network)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
}
|
||||
}
|
||||
BdkDescriptorPublicKey::Single(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip49(
|
||||
secret_key: Arc<DescriptorSecretKey>,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let derivable_key = secret_key.descriptor_secret_key_mutex.lock().unwrap();
|
||||
|
||||
match derivable_key.deref() {
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip49(derivable_key, keychain_kind).build(network).unwrap();
|
||||
Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
}
|
||||
}
|
||||
BdkDescriptorSecretKey::Single(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip49_public(
|
||||
public_key: Arc<DescriptorPublicKey>,
|
||||
fingerprint: String,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||
let derivable_key = public_key.descriptor_public_key_mutex.lock().unwrap();
|
||||
|
||||
match derivable_key.deref() {
|
||||
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip49Public(derivable_key, fingerprint, keychain_kind)
|
||||
.build(network)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
}
|
||||
}
|
||||
BdkDescriptorPublicKey::Single(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip84(
|
||||
secret_key: Arc<DescriptorSecretKey>,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let derivable_key = secret_key.descriptor_secret_key_mutex.lock().unwrap();
|
||||
|
||||
match derivable_key.deref() {
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip84(derivable_key, keychain_kind).build(network).unwrap();
|
||||
Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
}
|
||||
}
|
||||
BdkDescriptorSecretKey::Single(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip84_public(
|
||||
public_key: Arc<DescriptorPublicKey>,
|
||||
fingerprint: String,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||
let derivable_key = public_key.descriptor_public_key_mutex.lock().unwrap();
|
||||
|
||||
match derivable_key.deref() {
|
||||
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip84Public(derivable_key, fingerprint, keychain_kind)
|
||||
.build(network)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
}
|
||||
}
|
||||
BdkDescriptorPublicKey::Single(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_string_private(&self) -> String {
|
||||
let descriptor = &self.extended_descriptor;
|
||||
let key_map = &self.key_map;
|
||||
descriptor.to_string_with_secret(key_map)
|
||||
}
|
||||
|
||||
pub(crate) fn as_string(&self) -> String {
|
||||
self.extended_descriptor.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
|
||||
// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk`
|
||||
// crate.
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::database::DatabaseConfig;
|
||||
use crate::*;
|
||||
use assert_matches::assert_matches;
|
||||
use bdk::descriptor::DescriptorError::Key;
|
||||
use bdk::keys::KeyError::InvalidNetwork;
|
||||
|
||||
fn get_descriptor_secret_key() -> DescriptorSecretKey {
|
||||
let mnemonic = Mnemonic::from_string("chaos fabric time speed sponsor all flat solution wisdom trophy crack object robot pave observe combine where aware bench orient secret primary cable detect".to_string()).unwrap();
|
||||
DescriptorSecretKey::new(Network::Testnet, Arc::new(mnemonic), None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_descriptor_templates() {
|
||||
let master: Arc<DescriptorSecretKey> = Arc::new(get_descriptor_secret_key());
|
||||
println!("Master: {:?}", master.as_string());
|
||||
// tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h
|
||||
let handmade_public_44 = master
|
||||
.derive(Arc::new(
|
||||
DerivationPath::new("m/44h/1h/0h".to_string()).unwrap(),
|
||||
))
|
||||
.unwrap()
|
||||
.as_public();
|
||||
println!("Public 44: {}", handmade_public_44.as_string());
|
||||
// Public 44: [d1d04177/44'/1'/0']tpubDCoPjomfTqh1e7o1WgGpQtARWtkueXQAepTeNpWiitS3Sdv8RKJ1yvTrGHcwjDXp2SKyMrTEca4LoN7gEUiGCWboyWe2rz99Kf4jK4m2Zmx/*
|
||||
let handmade_public_49 = master
|
||||
.derive(Arc::new(
|
||||
DerivationPath::new("m/49h/1h/0h".to_string()).unwrap(),
|
||||
))
|
||||
.unwrap()
|
||||
.as_public();
|
||||
println!("Public 49: {}", handmade_public_49.as_string());
|
||||
// Public 49: [d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/*
|
||||
let handmade_public_84 = master
|
||||
.derive(Arc::new(
|
||||
DerivationPath::new("m/84h/1h/0h".to_string()).unwrap(),
|
||||
))
|
||||
.unwrap()
|
||||
.as_public();
|
||||
println!("Public 84: {}", handmade_public_84.as_string());
|
||||
// Public 84: [d1d04177/84'/1'/0']tpubDDNxbq17egjFk2edjv8oLnzxk52zny9aAYNv9CMqTzA4mQDiQq818sEkNe9Gzmd4QU8558zftqbfoVBDQorG3E4Wq26tB2JeE4KUoahLkx6/*
|
||||
let template_private_44 =
|
||||
Descriptor::new_bip44(master.clone(), KeychainKind::External, Network::Testnet);
|
||||
let template_private_49 =
|
||||
Descriptor::new_bip49(master.clone(), KeychainKind::External, Network::Testnet);
|
||||
let template_private_84 =
|
||||
Descriptor::new_bip84(master, KeychainKind::External, Network::Testnet);
|
||||
// the extended public keys are the same when creating them manually as they are with the templates
|
||||
println!("Template 49: {}", template_private_49.as_string());
|
||||
println!("Template 44: {}", template_private_44.as_string());
|
||||
println!("Template 84: {}", template_private_84.as_string());
|
||||
// for the public versions of the templates these are incorrect, bug report and fix in bitcoindevkit/bdk#817 and bitcoindevkit/bdk#818
|
||||
let template_public_44 = Descriptor::new_bip44_public(
|
||||
handmade_public_44,
|
||||
"d1d04177".to_string(),
|
||||
KeychainKind::External,
|
||||
Network::Testnet,
|
||||
);
|
||||
let template_public_49 = Descriptor::new_bip49_public(
|
||||
handmade_public_49,
|
||||
"d1d04177".to_string(),
|
||||
KeychainKind::External,
|
||||
Network::Testnet,
|
||||
);
|
||||
let template_public_84 = Descriptor::new_bip84_public(
|
||||
handmade_public_84,
|
||||
"d1d04177".to_string(),
|
||||
KeychainKind::External,
|
||||
Network::Testnet,
|
||||
);
|
||||
println!("Template public 49: {}", template_public_49.as_string());
|
||||
println!("Template public 44: {}", template_public_44.as_string());
|
||||
println!("Template public 84: {}", template_public_84.as_string());
|
||||
// when using a public key, both as_string and as_string_private return the same string
|
||||
assert_eq!(
|
||||
template_public_44.as_string_private(),
|
||||
template_public_44.as_string()
|
||||
);
|
||||
assert_eq!(
|
||||
template_public_49.as_string_private(),
|
||||
template_public_49.as_string()
|
||||
);
|
||||
assert_eq!(
|
||||
template_public_84.as_string_private(),
|
||||
template_public_84.as_string()
|
||||
);
|
||||
// when using as_string on a private key, we get the same result as when using it on a public key
|
||||
assert_eq!(
|
||||
template_private_44.as_string(),
|
||||
template_public_44.as_string()
|
||||
);
|
||||
assert_eq!(
|
||||
template_private_49.as_string(),
|
||||
template_public_49.as_string()
|
||||
);
|
||||
assert_eq!(
|
||||
template_private_84.as_string(),
|
||||
template_public_84.as_string()
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_descriptor_from_string() {
|
||||
let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet);
|
||||
let descriptor2 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Bitcoin);
|
||||
// Creating a Descriptor using an extended key that doesn't match the network provided will throw and InvalidNetwork Error
|
||||
assert!(descriptor1.is_ok());
|
||||
assert_matches!(
|
||||
descriptor2.unwrap_err(),
|
||||
bdk::Error::Descriptor(Key(InvalidNetwork))
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn test_wallet_from_descriptor() {
|
||||
let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet).unwrap();
|
||||
let wallet1 = Wallet::new(
|
||||
Arc::new(Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet).unwrap()),
|
||||
None,
|
||||
Network::Testnet,
|
||||
DatabaseConfig::Memory
|
||||
);
|
||||
let wallet2 = Wallet::new(
|
||||
Arc::new(descriptor1),
|
||||
None,
|
||||
Network::Bitcoin,
|
||||
DatabaseConfig::Memory,
|
||||
);
|
||||
// Creating a wallet using a Descriptor with an extended key that doesn't match the network provided in the wallet constructor will throw and InvalidNetwork Error
|
||||
assert!(wallet1.is_ok());
|
||||
assert_matches!(
|
||||
wallet2.unwrap_err(),
|
||||
bdk::Error::Descriptor(Key(InvalidNetwork))
|
||||
)
|
||||
}
|
||||
}
|
||||
376
bdk-ffi/src/keys.rs
Normal file
376
bdk-ffi/src/keys.rs
Normal file
@@ -0,0 +1,376 @@
|
||||
use crate::BdkError;
|
||||
|
||||
use bdk::bitcoin::secp256k1::Secp256k1;
|
||||
use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::descriptor::DescriptorXKey;
|
||||
use bdk::keys::bip39::{Language, Mnemonic as BdkMnemonic, WordCount};
|
||||
use bdk::keys::{
|
||||
DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
|
||||
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
|
||||
};
|
||||
use bdk::miniscript::BareCtx;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Mnemonic phrases are a human-readable version of the private keys.
|
||||
/// Supported number of words are 12, 15, 18, 21 and 24.
|
||||
pub(crate) struct Mnemonic {
|
||||
internal: BdkMnemonic,
|
||||
}
|
||||
|
||||
impl Mnemonic {
|
||||
/// Generates Mnemonic with a random entropy
|
||||
pub(crate) fn new(word_count: WordCount) -> Self {
|
||||
let generated_key: GeneratedKey<_, BareCtx> =
|
||||
BdkMnemonic::generate((word_count, Language::English)).unwrap();
|
||||
let mnemonic = BdkMnemonic::parse_in(Language::English, generated_key.to_string()).unwrap();
|
||||
Mnemonic { internal: mnemonic }
|
||||
}
|
||||
|
||||
/// Parse a Mnemonic with given string
|
||||
pub(crate) fn from_string(mnemonic: String) -> Result<Self, BdkError> {
|
||||
BdkMnemonic::from_str(&mnemonic)
|
||||
.map(|m| Mnemonic { internal: m })
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||
}
|
||||
|
||||
/// Create a new Mnemonic in the specified language from the given entropy.
|
||||
/// Entropy must be a multiple of 32 bits (4 bytes) and 128-256 bits in length.
|
||||
pub(crate) fn from_entropy(entropy: Vec<u8>) -> Result<Self, BdkError> {
|
||||
BdkMnemonic::from_entropy(entropy.as_slice())
|
||||
.map(|m| Mnemonic { internal: m })
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||
}
|
||||
|
||||
/// Returns Mnemonic as string
|
||||
pub(crate) fn as_string(&self) -> String {
|
||||
self.internal.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DerivationPath {
|
||||
derivation_path_mutex: Mutex<BdkDerivationPath>,
|
||||
}
|
||||
|
||||
impl DerivationPath {
|
||||
pub(crate) fn new(path: String) -> Result<Self, BdkError> {
|
||||
BdkDerivationPath::from_str(&path)
|
||||
.map(|x| DerivationPath {
|
||||
derivation_path_mutex: Mutex::new(x),
|
||||
})
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DescriptorSecretKey {
|
||||
pub(crate) descriptor_secret_key_mutex: Mutex<BdkDescriptorSecretKey>,
|
||||
}
|
||||
|
||||
impl DescriptorSecretKey {
|
||||
pub(crate) fn new(network: Network, mnemonic: Arc<Mnemonic>, password: Option<String>) -> Self {
|
||||
let mnemonic = mnemonic.internal.clone();
|
||||
let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap();
|
||||
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
||||
origin: None,
|
||||
xkey: xkey.into_xprv(network).unwrap(),
|
||||
derivation_path: BdkDerivationPath::master(),
|
||||
wildcard: bdk::descriptor::Wildcard::Unhardened,
|
||||
});
|
||||
Self {
|
||||
descriptor_secret_key_mutex: Mutex::new(descriptor_secret_key),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_string(private_key: String) -> Result<Self, BdkError> {
|
||||
let descriptor_secret_key = BdkDescriptorSecretKey::from_str(private_key.as_str())
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||
Ok(Self {
|
||||
descriptor_secret_key_mutex: Mutex::new(descriptor_secret_key),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||
let secp = Secp256k1::new();
|
||||
let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap();
|
||||
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
|
||||
match descriptor_secret_key.deref() {
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let derived_xprv = descriptor_x_key.xkey.derive_priv(&secp, &path)?;
|
||||
let key_source = match descriptor_x_key.origin.clone() {
|
||||
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
|
||||
None => (descriptor_x_key.xkey.fingerprint(&secp), path),
|
||||
};
|
||||
let derived_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
||||
origin: Some(key_source),
|
||||
xkey: derived_xprv,
|
||||
derivation_path: BdkDerivationPath::default(),
|
||||
wildcard: descriptor_x_key.wildcard,
|
||||
});
|
||||
Ok(Arc::new(Self {
|
||||
descriptor_secret_key_mutex: Mutex::new(derived_descriptor_secret_key),
|
||||
}))
|
||||
}
|
||||
BdkDescriptorSecretKey::Single(_) => Err(BdkError::Generic(
|
||||
"Cannot derive from a single key".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extend(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||
let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap();
|
||||
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
|
||||
match descriptor_secret_key.deref() {
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let extended_path = descriptor_x_key.derivation_path.extend(path);
|
||||
let extended_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
||||
origin: descriptor_x_key.origin.clone(),
|
||||
xkey: descriptor_x_key.xkey,
|
||||
derivation_path: extended_path,
|
||||
wildcard: descriptor_x_key.wildcard,
|
||||
});
|
||||
Ok(Arc::new(Self {
|
||||
descriptor_secret_key_mutex: Mutex::new(extended_descriptor_secret_key),
|
||||
}))
|
||||
}
|
||||
BdkDescriptorSecretKey::Single(_) => Err(BdkError::Generic(
|
||||
"Cannot extend from a single key".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_public(&self) -> Arc<DescriptorPublicKey> {
|
||||
let secp = Secp256k1::new();
|
||||
let descriptor_public_key = self
|
||||
.descriptor_secret_key_mutex
|
||||
.lock()
|
||||
.unwrap()
|
||||
.to_public(&secp)
|
||||
.unwrap();
|
||||
Arc::new(DescriptorPublicKey {
|
||||
descriptor_public_key_mutex: Mutex::new(descriptor_public_key),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the private key as bytes.
|
||||
pub(crate) fn secret_bytes(&self) -> Vec<u8> {
|
||||
let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap();
|
||||
let secret_bytes: Vec<u8> = match descriptor_secret_key.deref() {
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
descriptor_x_key.xkey.private_key.secret_bytes().to_vec()
|
||||
}
|
||||
BdkDescriptorSecretKey::Single(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
secret_bytes
|
||||
}
|
||||
|
||||
pub(crate) fn as_string(&self) -> String {
|
||||
self.descriptor_secret_key_mutex.lock().unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DescriptorPublicKey {
|
||||
pub(crate) descriptor_public_key_mutex: Mutex<BdkDescriptorPublicKey>,
|
||||
}
|
||||
|
||||
impl DescriptorPublicKey {
|
||||
pub(crate) fn from_string(public_key: String) -> Result<Self, BdkError> {
|
||||
let descriptor_public_key = BdkDescriptorPublicKey::from_str(public_key.as_str())
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||
Ok(Self {
|
||||
descriptor_public_key_mutex: Mutex::new(descriptor_public_key),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||
let secp = Secp256k1::new();
|
||||
let descriptor_public_key = self.descriptor_public_key_mutex.lock().unwrap();
|
||||
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
|
||||
|
||||
match descriptor_public_key.deref() {
|
||||
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
||||
let derived_xpub = descriptor_x_key.xkey.derive_pub(&secp, &path)?;
|
||||
let key_source = match descriptor_x_key.origin.clone() {
|
||||
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
|
||||
None => (descriptor_x_key.xkey.fingerprint(), path),
|
||||
};
|
||||
let derived_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
|
||||
origin: Some(key_source),
|
||||
xkey: derived_xpub,
|
||||
derivation_path: BdkDerivationPath::default(),
|
||||
wildcard: descriptor_x_key.wildcard,
|
||||
});
|
||||
Ok(Arc::new(Self {
|
||||
descriptor_public_key_mutex: Mutex::new(derived_descriptor_public_key),
|
||||
}))
|
||||
}
|
||||
BdkDescriptorPublicKey::Single(_) => Err(BdkError::Generic(
|
||||
"Cannot derive from a single key".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extend(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||
let descriptor_public_key = self.descriptor_public_key_mutex.lock().unwrap();
|
||||
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
|
||||
match descriptor_public_key.deref() {
|
||||
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
||||
let extended_path = descriptor_x_key.derivation_path.extend(path);
|
||||
let extended_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
|
||||
origin: descriptor_x_key.origin.clone(),
|
||||
xkey: descriptor_x_key.xkey,
|
||||
derivation_path: extended_path,
|
||||
wildcard: descriptor_x_key.wildcard,
|
||||
});
|
||||
Ok(Arc::new(Self {
|
||||
descriptor_public_key_mutex: Mutex::new(extended_descriptor_public_key),
|
||||
}))
|
||||
}
|
||||
BdkDescriptorPublicKey::Single(_) => Err(BdkError::Generic(
|
||||
"Cannot extend from a single key".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_string(&self) -> String {
|
||||
self.descriptor_public_key_mutex.lock().unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
|
||||
// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk`
|
||||
// crate.
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::keys::{DerivationPath, DescriptorPublicKey, DescriptorSecretKey, Mnemonic};
|
||||
use crate::BdkError;
|
||||
use bdk::bitcoin::hashes::hex::ToHex;
|
||||
use bdk::bitcoin::Network;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn get_descriptor_secret_key() -> DescriptorSecretKey {
|
||||
let mnemonic = Mnemonic::from_string("chaos fabric time speed sponsor all flat solution wisdom trophy crack object robot pave observe combine where aware bench orient secret primary cable detect".to_string()).unwrap();
|
||||
DescriptorSecretKey::new(Network::Testnet, Arc::new(mnemonic), None)
|
||||
}
|
||||
|
||||
fn derive_dsk(
|
||||
key: &DescriptorSecretKey,
|
||||
path: &str,
|
||||
) -> Result<Arc<DescriptorSecretKey>, BdkError> {
|
||||
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||
key.derive(path)
|
||||
}
|
||||
|
||||
fn extend_dsk(
|
||||
key: &DescriptorSecretKey,
|
||||
path: &str,
|
||||
) -> Result<Arc<DescriptorSecretKey>, BdkError> {
|
||||
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||
key.extend(path)
|
||||
}
|
||||
|
||||
fn derive_dpk(
|
||||
key: &DescriptorPublicKey,
|
||||
path: &str,
|
||||
) -> Result<Arc<DescriptorPublicKey>, BdkError> {
|
||||
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||
key.derive(path)
|
||||
}
|
||||
|
||||
fn extend_dpk(
|
||||
key: &DescriptorPublicKey,
|
||||
path: &str,
|
||||
) -> Result<Arc<DescriptorPublicKey>, BdkError> {
|
||||
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||
key.extend(path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_descriptor_secret_key() {
|
||||
let master_dsk = get_descriptor_secret_key();
|
||||
assert_eq!(master_dsk.as_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/*");
|
||||
assert_eq!(master_dsk.as_public().as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_self() {
|
||||
let master_dsk = get_descriptor_secret_key();
|
||||
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m").unwrap();
|
||||
assert_eq!(derived_dsk.as_string(), "[d1d04177]tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/*");
|
||||
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
|
||||
let derived_dpk: &DescriptorPublicKey = &derive_dpk(master_dpk, "m").unwrap();
|
||||
assert_eq!(derived_dpk.as_string(), "[d1d04177]tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_descriptors_keys() {
|
||||
let master_dsk = get_descriptor_secret_key();
|
||||
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
|
||||
assert_eq!(derived_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
|
||||
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
|
||||
let derived_dpk: &DescriptorPublicKey = &derive_dpk(master_dpk, "m/0").unwrap();
|
||||
assert_eq!(derived_dpk.as_string(), "[d1d04177/0]tpubD9oaCiP1MPmQdndm7DCD3D3QU34pWd6BbKSRedoZF1UJcNhEk3PJwkALNYkhxeTKL29oGNR7psqvT1KZydCGqUDEKXN6dVQJY2R8ooLPy8m/*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extend_descriptor_keys() {
|
||||
let master_dsk = get_descriptor_secret_key();
|
||||
let extended_dsk: &DescriptorSecretKey = &extend_dsk(&master_dsk, "m/0").unwrap();
|
||||
assert_eq!(extended_dsk.as_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/0/*");
|
||||
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
|
||||
let extended_dpk: &DescriptorPublicKey = &extend_dpk(master_dpk, "m/0").unwrap();
|
||||
assert_eq!(extended_dpk.as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0/*");
|
||||
let wif = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
|
||||
let extended_key = DescriptorSecretKey::from_string(wif.to_string()).unwrap();
|
||||
let result = extended_key.derive(Arc::new(DerivationPath::new("m/0".to_string()).unwrap()));
|
||||
dbg!(&result);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str_descriptor_secret_key() {
|
||||
let key1 = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
|
||||
let key2 = "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/1/1/*";
|
||||
let private_descriptor_key1 = DescriptorSecretKey::from_string(key1.to_string()).unwrap();
|
||||
let private_descriptor_key2 = DescriptorSecretKey::from_string(key2.to_string()).unwrap();
|
||||
dbg!(private_descriptor_key1);
|
||||
dbg!(private_descriptor_key2);
|
||||
// Should error out because you can't produce a DescriptorSecretKey from an xpub
|
||||
let key0 = "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi";
|
||||
assert!(DescriptorSecretKey::from_string(key0.to_string()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_and_extend_descriptor_secret_key() {
|
||||
let master_dsk = get_descriptor_secret_key();
|
||||
// derive DescriptorSecretKey with path "m/0" from master
|
||||
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
|
||||
assert_eq!(derived_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
|
||||
// extend derived_dsk with path "m/0"
|
||||
let extended_dsk: &DescriptorSecretKey = &extend_dsk(derived_dsk, "m/0").unwrap();
|
||||
assert_eq!(extended_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/0/*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_hardened_path_using_public() {
|
||||
let master_dpk = get_descriptor_secret_key().as_public();
|
||||
let derived_dpk = &derive_dpk(&master_dpk, "m/84h/1h/0h");
|
||||
assert!(derived_dpk.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_retrieve_master_secret_key() {
|
||||
let master_dpk = get_descriptor_secret_key();
|
||||
let master_private_key = master_dpk.secret_bytes().to_hex();
|
||||
assert_eq!(
|
||||
master_private_key,
|
||||
"e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936"
|
||||
)
|
||||
}
|
||||
}
|
||||
1552
bdk-ffi/src/lib.rs
1552
bdk-ffi/src/lib.rs
File diff suppressed because it is too large
Load Diff
111
bdk-ffi/src/psbt.rs
Normal file
111
bdk-ffi/src/psbt.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use bdk::bitcoin::hashes::hex::ToHex;
|
||||
use bdk::bitcoin::util::psbt::PartiallySignedTransaction as BdkPartiallySignedTransaction;
|
||||
use bdk::psbt::PsbtUtils;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::{BdkError, FeeRate, Transaction};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PartiallySignedTransaction {
|
||||
pub(crate) internal: Mutex<BdkPartiallySignedTransaction>,
|
||||
}
|
||||
|
||||
impl PartiallySignedTransaction {
|
||||
pub(crate) fn new(psbt_base64: String) -> Result<Self, BdkError> {
|
||||
let psbt: BdkPartiallySignedTransaction =
|
||||
BdkPartiallySignedTransaction::from_str(&psbt_base64)?;
|
||||
Ok(PartiallySignedTransaction {
|
||||
internal: Mutex::new(psbt),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn serialize(&self) -> String {
|
||||
let psbt = self.internal.lock().unwrap().clone();
|
||||
psbt.to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn txid(&self) -> String {
|
||||
let tx = self.internal.lock().unwrap().clone().extract_tx();
|
||||
let txid = tx.txid();
|
||||
txid.to_hex()
|
||||
}
|
||||
|
||||
/// Return the transaction.
|
||||
pub(crate) fn extract_tx(&self) -> Arc<Transaction> {
|
||||
let tx = self.internal.lock().unwrap().clone().extract_tx();
|
||||
Arc::new(Transaction { internal: tx })
|
||||
}
|
||||
|
||||
/// Combines this PartiallySignedTransaction with other PSBT as described by BIP 174.
|
||||
///
|
||||
/// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
|
||||
pub(crate) fn combine(
|
||||
&self,
|
||||
other: Arc<PartiallySignedTransaction>,
|
||||
) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
|
||||
let other_psbt = other.internal.lock().unwrap().clone();
|
||||
let mut original_psbt = self.internal.lock().unwrap().clone();
|
||||
|
||||
original_psbt.combine(other_psbt)?;
|
||||
Ok(Arc::new(PartiallySignedTransaction {
|
||||
internal: Mutex::new(original_psbt),
|
||||
}))
|
||||
}
|
||||
|
||||
/// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
|
||||
/// If the PSBT is missing a TxOut for an input returns None.
|
||||
pub(crate) fn fee_amount(&self) -> Option<u64> {
|
||||
self.internal.lock().unwrap().fee_amount()
|
||||
}
|
||||
|
||||
/// The transaction's fee rate. This value will only be accurate if calculated AFTER the
|
||||
/// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the
|
||||
/// transaction.
|
||||
/// If the PSBT is missing a TxOut for an input returns None.
|
||||
pub(crate) fn fee_rate(&self) -> Option<Arc<FeeRate>> {
|
||||
self.internal.lock().unwrap().fee_rate().map(Arc::new)
|
||||
}
|
||||
}
|
||||
|
||||
// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
|
||||
// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk`
|
||||
// crate.
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::wallet::{TxBuilder, Wallet};
|
||||
use bdk::wallet::get_funded_wallet;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[test]
|
||||
fn test_psbt_fee() {
|
||||
let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
|
||||
let (funded_wallet, _, _) = get_funded_wallet(test_wpkh);
|
||||
let test_wallet = Wallet {
|
||||
wallet_mutex: Mutex::new(funded_wallet),
|
||||
};
|
||||
let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string();
|
||||
let drain_to_script = crate::Address::new(drain_to_address)
|
||||
.unwrap()
|
||||
.script_pubkey();
|
||||
|
||||
let tx_builder = TxBuilder::new()
|
||||
.fee_rate(2.0)
|
||||
.drain_wallet()
|
||||
.drain_to(drain_to_script.clone());
|
||||
//dbg!(&tx_builder);
|
||||
assert!(tx_builder.drain_wallet);
|
||||
assert_eq!(tx_builder.drain_to, Some(drain_to_script.script.clone()));
|
||||
|
||||
let tx_builder_result = tx_builder.finish(&test_wallet).unwrap();
|
||||
|
||||
assert!(tx_builder_result.psbt.fee_rate().is_some());
|
||||
assert_eq!(
|
||||
tx_builder_result.psbt.fee_rate().unwrap().as_sat_per_vb(),
|
||||
2.682927
|
||||
);
|
||||
|
||||
assert!(tx_builder_result.psbt.fee_amount().is_some());
|
||||
assert_eq!(tx_builder_result.psbt.fee_amount().unwrap(), 220);
|
||||
}
|
||||
}
|
||||
696
bdk-ffi/src/wallet.rs
Normal file
696
bdk-ffi/src/wallet.rs
Normal file
@@ -0,0 +1,696 @@
|
||||
use bdk::bitcoin::blockdata::script::Script as BdkScript;
|
||||
use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Sequence, Txid};
|
||||
use bdk::database::any::AnyDatabase;
|
||||
use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase};
|
||||
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
||||
use bdk::{FeeRate, SignOptions, SyncOptions as BdkSyncOptions, Wallet as BdkWallet};
|
||||
use std::collections::HashSet;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use crate::blockchain::Blockchain;
|
||||
use crate::database::DatabaseConfig;
|
||||
use crate::descriptor::Descriptor;
|
||||
use crate::psbt::PartiallySignedTransaction;
|
||||
use crate::{
|
||||
AddressIndex, AddressInfo, Balance, BdkError, LocalUtxo, NetworkLocalUtxo, OutPoint, Progress,
|
||||
ProgressHolder, RbfValue, Script, ScriptAmount, TransactionDetails, TxBuilderResult,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Wallet {
|
||||
pub(crate) wallet_mutex: Mutex<BdkWallet<AnyDatabase>>,
|
||||
}
|
||||
|
||||
/// A Bitcoin wallet.
|
||||
/// The Wallet acts as a way of coherently interfacing with output descriptors and related transactions. Its main components are:
|
||||
/// 1. Output descriptors from which it can derive addresses.
|
||||
/// 2. A Database where it tracks transactions and utxos related to the descriptors.
|
||||
/// 3. Signers that can contribute signatures to addresses instantiated from the descriptors.
|
||||
impl Wallet {
|
||||
pub(crate) fn new(
|
||||
descriptor: Arc<Descriptor>,
|
||||
change_descriptor: Option<Arc<Descriptor>>,
|
||||
network: Network,
|
||||
database_config: DatabaseConfig,
|
||||
) -> Result<Self, BdkError> {
|
||||
let any_database_config = match database_config {
|
||||
DatabaseConfig::Memory => AnyDatabaseConfig::Memory(()),
|
||||
DatabaseConfig::Sled { config } => AnyDatabaseConfig::Sled(config),
|
||||
DatabaseConfig::Sqlite { config } => AnyDatabaseConfig::Sqlite(config),
|
||||
};
|
||||
let database = AnyDatabase::from_config(&any_database_config)?;
|
||||
let descriptor: String = descriptor.as_string_private();
|
||||
let change_descriptor: Option<String> = change_descriptor.map(|d| d.as_string_private());
|
||||
|
||||
let wallet_mutex = Mutex::new(BdkWallet::new(
|
||||
&descriptor,
|
||||
change_descriptor.as_ref(),
|
||||
network,
|
||||
database,
|
||||
)?);
|
||||
Ok(Wallet { wallet_mutex })
|
||||
}
|
||||
|
||||
pub(crate) fn get_wallet(&self) -> MutexGuard<BdkWallet<AnyDatabase>> {
|
||||
self.wallet_mutex.lock().expect("wallet")
|
||||
}
|
||||
|
||||
/// Get the Bitcoin network the wallet is using.
|
||||
pub(crate) fn network(&self) -> Network {
|
||||
self.get_wallet().network()
|
||||
}
|
||||
|
||||
/// Sync the internal database with the blockchain.
|
||||
pub(crate) fn sync(
|
||||
&self,
|
||||
blockchain: &Blockchain,
|
||||
progress: Option<Box<dyn Progress>>,
|
||||
) -> Result<(), BdkError> {
|
||||
let bdk_sync_opts = BdkSyncOptions {
|
||||
progress: progress.map(|p| {
|
||||
Box::new(ProgressHolder { progress: p })
|
||||
as Box<(dyn bdk::blockchain::Progress + 'static)>
|
||||
}),
|
||||
};
|
||||
|
||||
let blockchain = blockchain.get_blockchain();
|
||||
self.get_wallet().sync(blockchain.deref(), bdk_sync_opts)
|
||||
}
|
||||
|
||||
/// Return a derived address using the external descriptor, see AddressIndex for available address index selection
|
||||
/// strategies. If none of the keys in the descriptor are derivable (i.e. the descriptor does not end with a * character)
|
||||
/// then the same address will always be returned for any AddressIndex.
|
||||
pub(crate) fn get_address(&self, address_index: AddressIndex) -> Result<AddressInfo, BdkError> {
|
||||
self.get_wallet()
|
||||
.get_address(address_index.into())
|
||||
.map(AddressInfo::from)
|
||||
}
|
||||
|
||||
/// Return a derived address using the internal (change) descriptor.
|
||||
///
|
||||
/// If the wallet doesn't have an internal descriptor it will use the external descriptor.
|
||||
///
|
||||
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
|
||||
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
|
||||
/// be returned for any [`AddressIndex`].
|
||||
pub(crate) fn get_internal_address(
|
||||
&self,
|
||||
address_index: AddressIndex,
|
||||
) -> Result<AddressInfo, BdkError> {
|
||||
self.get_wallet()
|
||||
.get_internal_address(address_index.into())
|
||||
.map(AddressInfo::from)
|
||||
}
|
||||
|
||||
/// Return the balance, meaning the sum of this wallet’s unspent outputs’ values. Note that this method only operates
|
||||
/// on the internal database, which first needs to be Wallet.sync manually.
|
||||
pub(crate) fn get_balance(&self) -> Result<Balance, BdkError> {
|
||||
self.get_wallet().get_balance().map(|b| b.into())
|
||||
}
|
||||
|
||||
/// Sign a transaction with all the wallet’s signers.
|
||||
pub(crate) fn sign(&self, psbt: &PartiallySignedTransaction) -> Result<bool, BdkError> {
|
||||
let mut psbt = psbt.internal.lock().unwrap();
|
||||
self.get_wallet().sign(&mut psbt, SignOptions::default())
|
||||
}
|
||||
|
||||
/// Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually.
|
||||
pub(crate) fn list_transactions(&self) -> Result<Vec<TransactionDetails>, BdkError> {
|
||||
let transaction_details = self.get_wallet().list_transactions(true)?;
|
||||
Ok(transaction_details
|
||||
.iter()
|
||||
.map(TransactionDetails::from)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Return the list of unspent outputs of this wallet. Note that this method only operates on the internal database,
|
||||
/// which first needs to be Wallet.sync manually.
|
||||
pub(crate) fn list_unspent(&self) -> Result<Vec<LocalUtxo>, BdkError> {
|
||||
let unspents = self.get_wallet().list_unspent()?;
|
||||
Ok(unspents
|
||||
.iter()
|
||||
.map(|u| LocalUtxo::from_utxo(u, self.network()))
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// A transaction builder.
|
||||
/// After creating the TxBuilder, you set options on it until finally calling finish to consume the builder and generate the transaction.
|
||||
/// Each method on the TxBuilder returns an instance of a new TxBuilder with the option set/added.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct TxBuilder {
|
||||
pub(crate) recipients: Vec<(BdkScript, u64)>,
|
||||
pub(crate) utxos: Vec<OutPoint>,
|
||||
pub(crate) unspendable: HashSet<OutPoint>,
|
||||
pub(crate) change_policy: ChangeSpendPolicy,
|
||||
pub(crate) manually_selected_only: bool,
|
||||
pub(crate) fee_rate: Option<f32>,
|
||||
pub(crate) fee_absolute: Option<u64>,
|
||||
pub(crate) drain_wallet: bool,
|
||||
pub(crate) drain_to: Option<BdkScript>,
|
||||
pub(crate) rbf: Option<RbfValue>,
|
||||
pub(crate) data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TxBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
TxBuilder {
|
||||
recipients: Vec::new(),
|
||||
utxos: Vec::new(),
|
||||
unspendable: HashSet::new(),
|
||||
change_policy: ChangeSpendPolicy::ChangeAllowed,
|
||||
manually_selected_only: false,
|
||||
fee_rate: None,
|
||||
fee_absolute: None,
|
||||
drain_wallet: false,
|
||||
drain_to: None,
|
||||
rbf: None,
|
||||
data: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a recipient to the internal list.
|
||||
pub(crate) fn add_recipient(&self, script: Arc<Script>, amount: u64) -> Arc<Self> {
|
||||
let mut recipients: Vec<(BdkScript, u64)> = self.recipients.clone();
|
||||
recipients.append(&mut vec![(script.script.clone(), amount)]);
|
||||
Arc::new(TxBuilder {
|
||||
recipients,
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
|
||||
let recipients = recipients
|
||||
.iter()
|
||||
.map(|script_amount| (script_amount.script.script.clone(), script_amount.amount))
|
||||
.collect();
|
||||
Arc::new(TxBuilder {
|
||||
recipients,
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Add a utxo to the internal list of unspendable utxos. It’s important to note that the "must-be-spent"
|
||||
/// utxos added with [TxBuilder.addUtxo] have priority over this. See the Rust docs of the two linked methods for more details.
|
||||
pub(crate) fn add_unspendable(&self, unspendable: OutPoint) -> Arc<Self> {
|
||||
let mut unspendable_hash_set = self.unspendable.clone();
|
||||
unspendable_hash_set.insert(unspendable);
|
||||
Arc::new(TxBuilder {
|
||||
unspendable: unspendable_hash_set,
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Add an outpoint 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(crate) fn add_utxo(&self, outpoint: OutPoint) -> Arc<Self> {
|
||||
self.add_utxos(vec![outpoint])
|
||||
}
|
||||
|
||||
/// Add the list of outpoints to the internal list of UTXOs that must be spent. If an error occurs while adding
|
||||
/// any of the UTXOs then none of them are added and the error is returned. 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(crate) fn add_utxos(&self, mut outpoints: Vec<OutPoint>) -> Arc<Self> {
|
||||
let mut utxos = self.utxos.to_vec();
|
||||
utxos.append(&mut outpoints);
|
||||
Arc::new(TxBuilder {
|
||||
utxos,
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Do not spend change outputs. This effectively adds all the change outputs to the "unspendable" list. See TxBuilder.unspendable.
|
||||
pub(crate) fn do_not_spend_change(&self) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
change_policy: ChangeSpendPolicy::ChangeForbidden,
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Only spend utxos added by [add_utxo]. The wallet will not add additional utxos to the transaction even if they are
|
||||
/// needed to make the transaction valid.
|
||||
pub(crate) fn manually_selected_only(&self) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
manually_selected_only: true,
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Only spend change outputs. This effectively adds all the non-change outputs to the "unspendable" list. See TxBuilder.unspendable.
|
||||
pub(crate) fn only_spend_change(&self) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
change_policy: ChangeSpendPolicy::OnlyChange,
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.addUtxo have priority over these. See the Rust docs of the two linked methods for more details.
|
||||
pub(crate) fn unspendable(&self, unspendable: Vec<OutPoint>) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
unspendable: unspendable.into_iter().collect(),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Set a custom fee rate.
|
||||
pub(crate) fn fee_rate(&self, sat_per_vb: f32) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
fee_rate: Some(sat_per_vb),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Set an absolute fee.
|
||||
pub(crate) fn fee_absolute(&self, fee_amount: u64) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
fee_absolute: Some(fee_amount),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Spend all the available inputs. This respects filters like TxBuilder.unspendable and the change policy.
|
||||
pub(crate) fn drain_wallet(&self) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
drain_wallet: true,
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the address to drain excess coins to. Usually, when there are excess coins they are sent to a change address
|
||||
/// generated by the wallet. This option replaces the usual change address with an arbitrary ScriptPubKey of your choosing.
|
||||
/// Just as with a change output, if the drain output is not needed (the excess coins are too small) it will not be included
|
||||
/// in the resulting transaction. The only difference is that it is valid to use drain_to without setting any ordinary recipients
|
||||
/// with add_recipient (but it is perfectly fine to add recipients as well). If you choose not to set any recipients, you should
|
||||
/// either provide the utxos that the transaction should spend via add_utxos, or set drain_wallet to spend all of them.
|
||||
/// When bumping the fees of a transaction made with this option, you probably want to use BumpFeeTxBuilder.allow_shrinking
|
||||
/// to allow this output to be reduced to pay for the extra fees.
|
||||
pub(crate) fn drain_to(&self, script: Arc<Script>) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
drain_to: Some(script.script.clone()),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`.
|
||||
pub(crate) fn enable_rbf(&self) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
rbf: Some(RbfValue::Default),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// 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(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
rbf: Some(RbfValue::Value(nsequence)),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Add data as an output using OP_RETURN.
|
||||
pub(crate) fn add_data(&self, data: Vec<u8>) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
data,
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Finish building the transaction. Returns the BIP174 PSBT.
|
||||
pub(crate) fn finish(&self, wallet: &Wallet) -> Result<TxBuilderResult, BdkError> {
|
||||
let wallet = wallet.get_wallet();
|
||||
let mut tx_builder = wallet.build_tx();
|
||||
for (script, amount) in &self.recipients {
|
||||
tx_builder.add_recipient(script.clone(), *amount);
|
||||
}
|
||||
tx_builder.change_policy(self.change_policy);
|
||||
if !self.utxos.is_empty() {
|
||||
let bdk_utxos: Vec<BdkOutPoint> = self.utxos.iter().map(BdkOutPoint::from).collect();
|
||||
let utxos: &[BdkOutPoint] = &bdk_utxos;
|
||||
tx_builder.add_utxos(utxos)?;
|
||||
}
|
||||
if !self.unspendable.is_empty() {
|
||||
let bdk_unspendable: Vec<BdkOutPoint> =
|
||||
self.unspendable.iter().map(BdkOutPoint::from).collect();
|
||||
tx_builder.unspendable(bdk_unspendable);
|
||||
}
|
||||
if self.manually_selected_only {
|
||||
tx_builder.manually_selected_only();
|
||||
}
|
||||
if let Some(sat_per_vb) = self.fee_rate {
|
||||
tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb));
|
||||
}
|
||||
if let Some(fee_amount) = self.fee_absolute {
|
||||
tx_builder.fee_absolute(fee_amount);
|
||||
}
|
||||
if self.drain_wallet {
|
||||
tx_builder.drain_wallet();
|
||||
}
|
||||
if let Some(script) = &self.drain_to {
|
||||
tx_builder.drain_to(script.clone());
|
||||
}
|
||||
if let Some(rbf) = &self.rbf {
|
||||
match *rbf {
|
||||
RbfValue::Default => {
|
||||
tx_builder.enable_rbf();
|
||||
}
|
||||
RbfValue::Value(nsequence) => {
|
||||
tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
if !&self.data.is_empty() {
|
||||
tx_builder.add_data(self.data.as_slice());
|
||||
}
|
||||
|
||||
tx_builder
|
||||
.finish()
|
||||
.map(|(psbt, tx_details)| TxBuilderResult {
|
||||
psbt: Arc::new(PartiallySignedTransaction {
|
||||
internal: Mutex::new(psbt),
|
||||
}),
|
||||
transaction_details: TransactionDetails::from(&tx_details),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The BumpFeeTxBuilder is used to bump the fee on a transaction that has been broadcast and has its RBF flag set to true.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BumpFeeTxBuilder {
|
||||
pub(crate) txid: String,
|
||||
pub(crate) fee_rate: f32,
|
||||
pub(crate) allow_shrinking: Option<String>,
|
||||
pub(crate) rbf: Option<RbfValue>,
|
||||
}
|
||||
|
||||
impl BumpFeeTxBuilder {
|
||||
pub(crate) fn new(txid: String, fee_rate: f32) -> Self {
|
||||
Self {
|
||||
txid,
|
||||
fee_rate,
|
||||
allow_shrinking: None,
|
||||
rbf: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this script_pubkey
|
||||
/// in order to bump the transaction fee. Without specifying this the wallet will attempt to find a change output to
|
||||
/// shrink instead. Note that the output may shrink to below the dust limit and therefore be removed. If it is preserved
|
||||
/// then it is currently not guaranteed to be in the same position as it was originally. Returns an error if script_pubkey
|
||||
/// can’t be found among the recipients of the transaction we are bumping.
|
||||
pub(crate) fn allow_shrinking(&self, address: String) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
allow_shrinking: Some(address),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`.
|
||||
pub(crate) fn enable_rbf(&self) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
rbf: Some(RbfValue::Default),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// 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(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
rbf: Some(RbfValue::Value(nsequence)),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Finish building the transaction. Returns the BIP174 PSBT.
|
||||
pub(crate) fn finish(
|
||||
&self,
|
||||
wallet: &Wallet,
|
||||
) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
|
||||
let wallet = wallet.get_wallet();
|
||||
let txid = Txid::from_str(self.txid.as_str())?;
|
||||
let mut tx_builder = wallet.build_fee_bump(txid)?;
|
||||
tx_builder.fee_rate(FeeRate::from_sat_per_vb(self.fee_rate));
|
||||
if let Some(allow_shrinking) = &self.allow_shrinking {
|
||||
let address = BdkAddress::from_str(allow_shrinking)
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||
let script = address.script_pubkey();
|
||||
tx_builder.allow_shrinking(script)?;
|
||||
}
|
||||
if let Some(rbf) = &self.rbf {
|
||||
match *rbf {
|
||||
RbfValue::Default => {
|
||||
tx_builder.enable_rbf();
|
||||
}
|
||||
RbfValue::Value(nsequence) => {
|
||||
tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
tx_builder
|
||||
.finish()
|
||||
.map(|(psbt, _)| PartiallySignedTransaction {
|
||||
internal: Mutex::new(psbt),
|
||||
})
|
||||
.map(Arc::new)
|
||||
}
|
||||
}
|
||||
|
||||
// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
|
||||
// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk`
|
||||
// crate.
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::database::DatabaseConfig;
|
||||
use crate::descriptor::Descriptor;
|
||||
use crate::wallet::{AddressIndex, TxBuilder, Wallet};
|
||||
use bdk::bitcoin::{Address, Network};
|
||||
use bdk::wallet::get_funded_wallet;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[test]
|
||||
fn test_drain_wallet() {
|
||||
let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
|
||||
let (funded_wallet, _, _) = get_funded_wallet(test_wpkh);
|
||||
let test_wallet = Wallet {
|
||||
wallet_mutex: Mutex::new(funded_wallet),
|
||||
};
|
||||
let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string();
|
||||
let drain_to_script = crate::Address::new(drain_to_address)
|
||||
.unwrap()
|
||||
.script_pubkey();
|
||||
let tx_builder = TxBuilder::new()
|
||||
.drain_wallet()
|
||||
.drain_to(drain_to_script.clone());
|
||||
assert!(tx_builder.drain_wallet);
|
||||
assert_eq!(tx_builder.drain_to, Some(drain_to_script.script.clone()));
|
||||
|
||||
let tx_builder_result = tx_builder.finish(&test_wallet).unwrap();
|
||||
let psbt = tx_builder_result.psbt.internal.lock().unwrap().clone();
|
||||
let tx_details = tx_builder_result.transaction_details;
|
||||
|
||||
// confirm one input with 50,000 sats
|
||||
assert_eq!(psbt.inputs.len(), 1);
|
||||
let input_value = psbt
|
||||
.inputs
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.non_witness_utxo
|
||||
.unwrap()
|
||||
.output
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.value;
|
||||
assert_eq!(input_value, 50_000_u64);
|
||||
|
||||
// confirm one output to correct address with all sats - fee
|
||||
assert_eq!(psbt.outputs.len(), 1);
|
||||
let output_address = Address::from_script(
|
||||
&psbt
|
||||
.unsigned_tx
|
||||
.output
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.script_pubkey,
|
||||
Network::Testnet,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
output_address,
|
||||
Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt").unwrap()
|
||||
);
|
||||
let output_value = psbt.unsigned_tx.output.get(0).cloned().unwrap().value;
|
||||
assert_eq!(output_value, 49_890_u64); // input - fee
|
||||
|
||||
assert_eq!(
|
||||
tx_details.txid,
|
||||
"312f1733badab22dc26b8dcbc83ba5629fb7b493af802e8abe07d865e49629c5"
|
||||
);
|
||||
assert_eq!(tx_details.received, 0);
|
||||
assert_eq!(tx_details.sent, 50000);
|
||||
assert!(tx_details.fee.is_some());
|
||||
assert_eq!(tx_details.fee.unwrap(), 110);
|
||||
assert!(tx_details.confirmation_time.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peek_reset_address() {
|
||||
let test_wpkh = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)";
|
||||
let descriptor = Descriptor::new(test_wpkh.to_string(), Network::Regtest).unwrap();
|
||||
let change_descriptor = Descriptor::new(
|
||||
test_wpkh.to_string().replace("/0/*", "/1/*"),
|
||||
Network::Regtest,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let wallet = Wallet::new(
|
||||
Arc::new(descriptor),
|
||||
Some(Arc::new(change_descriptor)),
|
||||
Network::Regtest,
|
||||
DatabaseConfig::Memory,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_address(AddressIndex::Peek { index: 2 })
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1q5g0mq6dkmwzvxscqwgc932jhgcxuqqkjv09tkj"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_address(AddressIndex::Peek { index: 1 })
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
|
||||
);
|
||||
|
||||
// new index still 0
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_address(crate::AddressIndex::New)
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
|
||||
);
|
||||
|
||||
// new index now 1
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_address(crate::AddressIndex::New)
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
|
||||
);
|
||||
|
||||
// new index now 2
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_address(crate::AddressIndex::New)
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1q5g0mq6dkmwzvxscqwgc932jhgcxuqqkjv09tkj"
|
||||
);
|
||||
|
||||
// peek index 1
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_address(AddressIndex::Peek { index: 1 })
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
|
||||
);
|
||||
|
||||
// reset to index 0
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_address(AddressIndex::Reset { index: 0 })
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
|
||||
);
|
||||
|
||||
// new index 1 again
|
||||
assert_eq!(
|
||||
wallet.get_address(AddressIndex::New).unwrap().address,
|
||||
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_address() {
|
||||
let test_wpkh = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)";
|
||||
let descriptor = Descriptor::new(test_wpkh.to_string(), Network::Regtest).unwrap();
|
||||
let change_descriptor = Descriptor::new(
|
||||
test_wpkh.to_string().replace("/0/*", "/1/*"),
|
||||
Network::Regtest,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let wallet = Wallet::new(
|
||||
Arc::new(descriptor),
|
||||
Some(Arc::new(change_descriptor)),
|
||||
Network::Regtest,
|
||||
DatabaseConfig::Memory,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_address(crate::AddressIndex::New)
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_address(crate::AddressIndex::New)
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_address(crate::AddressIndex::LastUnused)
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_internal_address(crate::AddressIndex::New)
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1qpmz73cyx00r4a5dea469j40ax6d6kqyd67nnpj"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_internal_address(crate::AddressIndex::New)
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1qaux734vuhykww9632v8cmdnk7z2mw5lsf74v6k"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_internal_address(crate::AddressIndex::LastUnused)
|
||||
.unwrap()
|
||||
.address,
|
||||
"bcrt1qaux734vuhykww9632v8cmdnk7z2mw5lsf74v6k"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ val blockchainConfig = BlockchainConfig.Electrum(
|
||||
ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u, true)
|
||||
)
|
||||
val wallet = Wallet(externalDescriptor, internalDescriptor, Network.TESTNET, databaseConfig, blockchainConfig)
|
||||
val newAddress = wallet.getAddress(AddressIndex.LAST_UNUSED)
|
||||
val newAddress = wallet.getAddress(AddressIndex.LastUnused)
|
||||
```
|
||||
|
||||
### Snapshot releases
|
||||
@@ -83,6 +83,7 @@ and use the `publishToMavenLocal` task without excluding the signing task:
|
||||
```
|
||||
|
||||
## Known issues
|
||||
## JNA dependency
|
||||
Depending on the JVM version you use, you might not have the JNA dependency on your classpath. The exception thrown will be
|
||||
```shell
|
||||
class file for com.sun.jna.Pointer not found
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
android.enableJetifier=true
|
||||
kotlin.code.style=official
|
||||
libraryVersion=0.26.0-SNAPSHOT
|
||||
libraryVersion=0.27.1
|
||||
|
||||
@@ -46,7 +46,7 @@ class JvmLibTest {
|
||||
@Test
|
||||
fun memoryWalletNewAddress() {
|
||||
val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
|
||||
val address = wallet.getAddress(AddressIndex.NEW).address
|
||||
val address = wallet.getAddress(AddressIndex.New).address
|
||||
assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
This software is licensed under [Apache 2.0](LICENSE-APACHE) or
|
||||
[MIT](LICENSE-MIT), at your option.
|
||||
|
||||
Some files retain their own copyright notice, however, for full authorship
|
||||
information, see version control history.
|
||||
|
||||
Except as otherwise noted in individual files, all files in this repository are
|
||||
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, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of this software or any files in this repository except in
|
||||
accordance with one or both of these licenses.
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -40,7 +40,7 @@ wallet = bdk.Wallet(
|
||||
)
|
||||
|
||||
# print new receive address
|
||||
address_info = wallet.get_address(bdk.AddressIndex.LAST_UNUSED)
|
||||
address_info = wallet.get_address(bdk.AddressIndex.LAST_UNUSED())
|
||||
address = address_info.address
|
||||
index = address_info.index
|
||||
print(f"New BIP84 testnet address: {address} at index {index}")
|
||||
@@ -60,7 +60,7 @@ rust_ext = RustExtension(
|
||||
|
||||
setup(
|
||||
name='bdkpython',
|
||||
version='0.26.0.dev0',
|
||||
version='0.27.1',
|
||||
description="The Python language bindings for the Bitcoin Development Kit",
|
||||
long_description=LONG_DESCRIPTION,
|
||||
long_description_content_type='text/markdown',
|
||||
@@ -68,7 +68,7 @@ setup(
|
||||
zip_safe=False,
|
||||
packages=['bdkpython'],
|
||||
package_dir={'bdkpython': './src/bdkpython'},
|
||||
url="https://github.com/bitcoindevkit/bdk-python",
|
||||
url="https://github.com/bitcoindevkit/bdk-ffi",
|
||||
author="Alekos Filini <alekos.filini@gmail.com>, Steve Myers <steve@notmandatory.org>",
|
||||
license="MIT or Apache 2.0",
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ class TestSimpleBip84Wallet(unittest.TestCase):
|
||||
network=bdk.Network.TESTNET,
|
||||
database_config=db_config
|
||||
)
|
||||
address_info = wallet.get_address(bdk.AddressIndex.LAST_UNUSED)
|
||||
address_info = wallet.get_address(bdk.AddressIndex.LAST_UNUSED())
|
||||
address = address_info.address
|
||||
# print(f"New address is {address}")
|
||||
assert address == "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", f"Wrong address {address}, should be tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e"
|
||||
|
||||
Reference in New Issue
Block a user