Compare commits
54 Commits
fix/live-t
...
frost
Author | SHA1 | Date | |
---|---|---|---|
|
f2a29c5b05 | ||
|
34543311bb | ||
|
323eb08350 | ||
|
7e5897bd1c | ||
|
ffe0c8c1a4 | ||
|
cddd5f25d8 | ||
|
f4366ac49f | ||
|
e2abc3620a | ||
|
c66b48467a | ||
|
33026108a7 | ||
|
92d40a9f46 | ||
|
3b89af5a6e | ||
|
f66f8417cf | ||
|
92aeeab436 | ||
|
a2794f25b0 | ||
|
1a1920e7e3 | ||
|
6642c5808b | ||
|
e97e9b731c | ||
|
94d31ff7ed | ||
|
84f1329e84 | ||
|
efef60082b | ||
|
53afd9c238 | ||
|
b9128b0e6a | ||
|
9d3733389d | ||
|
65702f401b | ||
|
19b4e1159a | ||
|
e5e7aba208 | ||
|
9790d60324 | ||
|
093eb1fc7e | ||
|
5ef2bf8a1e | ||
|
7fc8da5451 | ||
|
5d41984377 | ||
|
89f803a753 | ||
|
1f7c782d49 | ||
|
af9346c4eb | ||
|
5cf25a50c4 | ||
|
c6174199dd | ||
|
9c45254c3e | ||
|
260a0a65b3 | ||
|
72985f14ad | ||
|
5e3e24906f | ||
|
c702894143 | ||
|
ecdd7c239b | ||
|
ca8a3d0471 | ||
|
8f4c80cb98 | ||
|
4aec4b0434 | ||
|
1913c45ef9 | ||
|
815fe5f62d | ||
|
8d30c86076 | ||
|
c88b33473b | ||
|
79e7ab73ea | ||
|
f169b1a52f | ||
|
97d9bb6fbf | ||
|
f27bada9c9 |
2
.github/workflows/live-tests.yaml
vendored
2
.github/workflows/live-tests.yaml
vendored
@ -49,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
- name: "Build Swift package"
|
- name: "Build Swift package"
|
||||||
working-directory: bdk-swift
|
working-directory: bdk-swift
|
||||||
run: bash ./build-local-swift.sh
|
run: bash ./build-xcframework.sh
|
||||||
|
|
||||||
- name: "Run live Swift tests"
|
- name: "Run live Swift tests"
|
||||||
working-directory: bdk-swift
|
working-directory: bdk-swift
|
||||||
|
19
.github/workflows/publish-python.yaml
vendored
19
.github/workflows/publish-python.yaml
vendored
@ -1,22 +1,17 @@
|
|||||||
name: Publish bdkpython to PyPI
|
name: Publish bdkpython to PyPI
|
||||||
on: [workflow_dispatch]
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
# We use manylinux2014 because older CentOS versions used by 2010 and 1 have a very old glibc version, which
|
|
||||||
# makes it very hard to use GitHub's javascript actions (checkout, upload-artifact, etc).
|
|
||||||
# They mount their own nodejs interpreter inside your container, but since that's not statically linked it
|
|
||||||
# tries to load glibc and fails because it requires a more recent version.
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-manylinux2014-x86_64-wheels:
|
build-manylinux_2_28-x86_64-wheels:
|
||||||
name: "Build Manylinux 2014 x86_64 wheel"
|
name: "Build Manylinux 2.28 x86_64 wheel"
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: bdk-python
|
working-directory: bdk-python
|
||||||
container:
|
container:
|
||||||
image: quay.io/pypa/manylinux2014_x86_64
|
image: quay.io/pypa/manylinux_2_28_x86_64
|
||||||
env:
|
env:
|
||||||
PLAT: manylinux2014_x86_64
|
PLAT: manylinux_2_28_x86_64
|
||||||
PYBIN: "/opt/python/${{ matrix.python }}/bin"
|
PYBIN: "/opt/python/${{ matrix.python }}/bin"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@ -43,11 +38,11 @@ jobs:
|
|||||||
- name: "Build wheel"
|
- name: "Build wheel"
|
||||||
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
|
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
|
||||||
# see issue #350 for more information
|
# see issue #350 for more information
|
||||||
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_17_x86_64 --verbose
|
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_28_x86_64 --verbose
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: bdkpython-manylinux2014-x86_64-${{ matrix.python }}
|
name: bdkpython-manylinux_2_28_x86_64-${{ matrix.python }}
|
||||||
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
|
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
|
||||||
|
|
||||||
build-macos-arm64-wheels:
|
build-macos-arm64-wheels:
|
||||||
@ -168,7 +163,7 @@ jobs:
|
|||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: bdk-python
|
working-directory: bdk-python
|
||||||
needs: [build-manylinux2014-x86_64-wheels, build-macos-arm64-wheels, build-macos-x86_64-wheels, build-windows-wheels]
|
needs: [build-manylinux_2_28-x86_64-wheels, build-macos-arm64-wheels, build-macos-x86_64-wheels, build-windows-wheels]
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
17
.github/workflows/test-python.yaml
vendored
17
.github/workflows/test-python.yaml
vendored
@ -10,22 +10,17 @@ on:
|
|||||||
- "bdk-ffi/**"
|
- "bdk-ffi/**"
|
||||||
- "bdk-python/**"
|
- "bdk-python/**"
|
||||||
|
|
||||||
# We use manylinux2014 because older CentOS versions used by 2010 and 1 have a very old glibc version, which
|
|
||||||
# makes it very hard to use GitHub's javascript actions (checkout, upload-artifact, etc).
|
|
||||||
# They mount their own nodejs interpreter inside your container, but since that's not statically linked it
|
|
||||||
# tries to load glibc and fails because it requires a more recent version.
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-manylinux2014-x86_64-wheels:
|
build-manylinux_2_28-x86_64-wheels:
|
||||||
name: "Build and test Manylinux 2014 x86_64 wheels"
|
name: "Build and test Manylinux 2.28 x86_64 wheels"
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: bdk-python
|
working-directory: bdk-python
|
||||||
container:
|
container:
|
||||||
image: quay.io/pypa/manylinux2014_x86_64
|
image: quay.io/pypa/manylinux_2_28_x86_64
|
||||||
env:
|
env:
|
||||||
PLAT: manylinux2014_x86_64
|
PLAT: manylinux_2_28_x86_64
|
||||||
PYBIN: "/opt/python/${{ matrix.python }}/bin"
|
PYBIN: "/opt/python/${{ matrix.python }}/bin"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@ -52,7 +47,7 @@ jobs:
|
|||||||
- name: "Build wheel"
|
- name: "Build wheel"
|
||||||
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
|
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
|
||||||
# see issue #350 for more information
|
# see issue #350 for more information
|
||||||
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_17_x86_64 --verbose
|
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_28_x86_64 --verbose
|
||||||
|
|
||||||
- name: "Install wheel"
|
- name: "Install wheel"
|
||||||
run: ${PYBIN}/pip install ./dist/*.whl
|
run: ${PYBIN}/pip install ./dist/*.whl
|
||||||
@ -63,7 +58,7 @@ jobs:
|
|||||||
- name: "Upload artifact test"
|
- name: "Upload artifact test"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: bdkpython-manylinux2014-x86_64-${{ matrix.python }}
|
name: bdkpython-manylinux_2_28_x86_64-${{ matrix.python }}
|
||||||
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
|
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
|
||||||
|
|
||||||
build-macos-arm64-wheels:
|
build-macos-arm64-wheels:
|
||||||
|
4
.github/workflows/test-swift.yaml
vendored
4
.github/workflows/test-swift.yaml
vendored
@ -20,8 +20,8 @@ jobs:
|
|||||||
|
|
||||||
- name: "Build Swift package"
|
- name: "Build Swift package"
|
||||||
working-directory: bdk-swift
|
working-directory: bdk-swift
|
||||||
run: bash ./build-local-swift.sh
|
run: bash ./build-xcframework.sh
|
||||||
|
|
||||||
- name: "Run Swift tests"
|
- name: "Run Swift tests"
|
||||||
working-directory: bdk-swift
|
working-directory: bdk-swift
|
||||||
run: swift test --skip LiveWalletTests --skip LiveTxBuilderTests
|
run: swift test --skip LiveElectrumClientTests --skip LiveMemoryWalletTests --skip LiveTransactionTests --skip LiveTxBuilderTests --skip LiveWalletTests
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -31,6 +31,8 @@ bdkFFI.h
|
|||||||
BitcoinDevKit.swift
|
BitcoinDevKit.swift
|
||||||
bdk.swift
|
bdk.swift
|
||||||
.build
|
.build
|
||||||
|
*.xcframework/
|
||||||
|
Info.plist
|
||||||
|
|
||||||
# Python related
|
# Python related
|
||||||
__pycache__
|
__pycache__
|
32
CHANGELOG.md
32
CHANGELOG.md
@ -3,10 +3,27 @@ Changelog information can also be found in each release's git tag (which can be
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [1.0.0-alpha.7]
|
## [v1.0.0-alpha.11]
|
||||||
|
This release brings the latest alpha 11 release of the Rust bdk_wallet library, as well as the new Electrum client, the new memory wallet, and a whole lot of new types and APIs across the library. Also of note are the much simpler-to-use full_scan and sync workflows for syncing wallets.
|
||||||
|
|
||||||
|
Added:
|
||||||
|
- `Amount` type [#533]
|
||||||
|
- `TxIn` type [#536]
|
||||||
|
- `Transaction.input()` method [#536]
|
||||||
|
- `Transaction.output()` method [#536]
|
||||||
|
- `Transaction.lock_time()` method [#536]
|
||||||
|
- `Electrum` client [#535]
|
||||||
|
- Memory wallet [#528]
|
||||||
|
|
||||||
|
[#528]: https://github.com/bitcoindevkit/bdk-ffi/pull/528
|
||||||
|
[#533]: https://github.com/bitcoindevkit/bdk-ffi/pull/533
|
||||||
|
[#535]: https://github.com/bitcoindevkit/bdk-ffi/pull/535
|
||||||
|
[#536]: https://github.com/bitcoindevkit/bdk-ffi/pull/536
|
||||||
|
|
||||||
|
## [v1.0.0-alpha.7]
|
||||||
This release brings back into the 1.0 API a number of APIs from the 0.31 release, and adds the new flat file persistence feature, as well as more fine-grain errors.
|
This release brings back into the 1.0 API a number of APIs from the 0.31 release, and adds the new flat file persistence feature, as well as more fine-grain errors.
|
||||||
|
|
||||||
## [1.0.0-alpha.2a]
|
## [v1.0.0-alpha.2a]
|
||||||
This release is the first alpha release of the 1.0 API for the bindings libraries. Here is what is now available:
|
This release is the first alpha release of the 1.0 API for the bindings libraries. Here is what is now available:
|
||||||
- Create and recover wallets using descriptors, including the four descriptor templates
|
- Create and recover wallets using descriptors, including the four descriptor templates
|
||||||
- Sync a wallet using a blocking Esplora client
|
- Sync a wallet using a blocking Esplora client
|
||||||
@ -14,7 +31,7 @@ This release is the first alpha release of the 1.0 API for the bindings librarie
|
|||||||
- Create and sign transactions using the transaction builder
|
- Create and sign transactions using the transaction builder
|
||||||
- Broadcast transactions
|
- Broadcast transactions
|
||||||
|
|
||||||
## [0.31.0]
|
## [v0.31.0]
|
||||||
This release updates the bindings libraries to bdk version 0.29.0, updating rust-bitcoin to version 0.30.2.
|
This release updates the bindings libraries to bdk version 0.29.0, updating rust-bitcoin to version 0.30.2.
|
||||||
|
|
||||||
- APIs Changed:
|
- APIs Changed:
|
||||||
@ -26,7 +43,7 @@ This release updates the bindings libraries to bdk version 0.29.0, updating rust
|
|||||||
|
|
||||||
[#443]: https://github.com/bitcoindevkit/bdk-ffi/pull/443
|
[#443]: https://github.com/bitcoindevkit/bdk-ffi/pull/443
|
||||||
|
|
||||||
## [0.30.0]
|
## [v0.30.0]
|
||||||
This release has a new API and a few internal optimizations and refactorings.
|
This release has a new API and a few internal optimizations and refactorings.
|
||||||
|
|
||||||
- APIs Added
|
- APIs Added
|
||||||
@ -34,7 +51,7 @@ This release has a new API and a few internal optimizations and refactorings.
|
|||||||
|
|
||||||
[#388]: https://github.com/bitcoindevkit/bdk-ffi/pull/388
|
[#388]: https://github.com/bitcoindevkit/bdk-ffi/pull/388
|
||||||
|
|
||||||
## [0.29.0]
|
## [v0.29.0]
|
||||||
This release has a number of new APIs, and adds support for Windows in bdk-jvm.
|
This release has a number of new APIs, and adds support for Windows in bdk-jvm.
|
||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
@ -245,8 +262,9 @@ Changelog
|
|||||||
|
|
||||||
[BIP 0174]:https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#encoding
|
[BIP 0174]:https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#encoding
|
||||||
|
|
||||||
[1.0.0-alpha.7]: https://github.com/bitcoindevkit/bdk-ffi/compare/v1.0.0-alpha.2a...v1.0.0-alpha.7
|
[v1.0.0-alpha.11]: https://github.com/bitcoindevkit/bdk-ffi/compare/v1.0.0-alpha.7...v1.0.0-alpha.11
|
||||||
[1.0.0-alpha.2a]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.31.0...v1.0.0-alpha.2a
|
[v1.0.0-alpha.7]: https://github.com/bitcoindevkit/bdk-ffi/compare/v1.0.0-alpha.2a...v1.0.0-alpha.7
|
||||||
|
[v1.0.0-alpha.2a]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.31.0...v1.0.0-alpha.2a
|
||||||
[v0.31.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.30.0...v0.31.0
|
[v0.31.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.30.0...v0.31.0
|
||||||
[v0.30.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.29.0...v0.30.0
|
[v0.30.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.29.0...v0.30.0
|
||||||
[v0.29.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.28.0...v0.29.0
|
[v0.29.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.28.0...v0.29.0
|
||||||
|
@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx1536m
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
libraryVersion=1.0.0-alpha.10-SNAPSHOT
|
libraryVersion=1.0.0-alpha.12-SNAPSHOT
|
||||||
|
@ -17,4 +17,4 @@ test:
|
|||||||
./gradlew connectedAndroidTest
|
./gradlew connectedAndroidTest
|
||||||
|
|
||||||
test-specific TEST:
|
test-specific TEST:
|
||||||
./gradlew test --tests {{TEST}}
|
./gradlew test --tests {{TEST}}
|
||||||
|
@ -18,7 +18,7 @@ android {
|
|||||||
compileSdk = 34
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 21
|
minSdk = 24
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
|
@ -13,8 +13,9 @@ private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
|||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class LiveTxBuilderTest {
|
class LiveTxBuilderTest {
|
||||||
private val persistenceFilePath = InstrumentationRegistry
|
private val persistenceFilePath = InstrumentationRegistry.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence3.sqlite"
|
||||||
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence.db"
|
private val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
||||||
|
private val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.SIGNET)
|
||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
@ -26,20 +27,20 @@ class LiveTxBuilderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTxBuilder() {
|
fun testTxBuilder() {
|
||||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
|
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||||
val psbt: Psbt = TxBuilder()
|
val psbt: Psbt = TxBuilder()
|
||||||
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
|
||||||
.feeRate(FeeRate.fromSatPerVb(2uL))
|
.feeRate(FeeRate.fromSatPerVb(2uL))
|
||||||
.finish(wallet)
|
.finish(wallet)
|
||||||
|
|
||||||
@ -49,23 +50,23 @@ class LiveTxBuilderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun complexTxBuilder() {
|
fun complexTxBuilder() {
|
||||||
val externalDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.SIGNET)
|
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.TESTNET)
|
|
||||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
|
|
||||||
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||||
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.SIGNET)
|
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.SIGNET)
|
||||||
val allRecipients: List<ScriptAmount> = listOf(
|
val allRecipients: List<ScriptAmount> = listOf(
|
||||||
ScriptAmount(recipient1.scriptPubkey(), 4200uL),
|
ScriptAmount(recipient1.scriptPubkey(), Amount.fromSat(4200uL)),
|
||||||
ScriptAmount(recipient2.scriptPubkey(), 4200uL),
|
ScriptAmount(recipient2.scriptPubkey(), Amount.fromSat(4200uL)),
|
||||||
)
|
)
|
||||||
|
|
||||||
val psbt: Psbt = TxBuilder()
|
val psbt: Psbt = TxBuilder()
|
||||||
|
@ -14,7 +14,9 @@ private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class LiveWalletTest {
|
class LiveWalletTest {
|
||||||
private val persistenceFilePath = InstrumentationRegistry
|
private val persistenceFilePath = InstrumentationRegistry
|
||||||
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence.db"
|
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence2.sqlite"
|
||||||
|
private val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
||||||
|
private val changeDescriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.SIGNET)
|
||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
@ -26,24 +28,24 @@ class LiveWalletTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSyncedBalance() {
|
fun testSyncedBalance() {
|
||||||
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
|
|
||||||
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
val balance: Balance = wallet.balance()
|
||||||
val balance: Balance = wallet.getBalance()
|
|
||||||
println("Balance: $balance")
|
println("Balance: $balance")
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
println("Transactions count: ${wallet.transactions().count()}")
|
println("Transactions count: ${wallet.transactions().count()}")
|
||||||
val transactions = wallet.transactions().take(3)
|
val transactions = wallet.transactions().take(3)
|
||||||
for (tx in transactions) {
|
for (tx in transactions) {
|
||||||
val sentAndReceived = wallet.sentAndReceived(tx.transaction)
|
val sentAndReceived = wallet.sentAndReceived(tx.transaction)
|
||||||
println("Transaction: ${tx.transaction.txid()}")
|
println("Transaction: ${tx.transaction.computeTxid()}")
|
||||||
println("Sent ${sentAndReceived.sent}")
|
println("Sent ${sentAndReceived.sent}")
|
||||||
println("Received ${sentAndReceived.received}")
|
println("Received ${sentAndReceived.received}")
|
||||||
}
|
}
|
||||||
@ -51,24 +53,21 @@ class LiveWalletTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBroadcastTransaction() {
|
fun testBroadcastTransaction() {
|
||||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
|
|
||||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
|
||||||
println("New address: ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address}")
|
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL) {
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
}
|
}
|
||||||
|
|
||||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||||
|
|
||||||
val psbt: Psbt = TxBuilder()
|
val psbt: Psbt = TxBuilder()
|
||||||
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
|
||||||
.feeRate(FeeRate.fromSatPerVb(4uL))
|
.feeRate(FeeRate.fromSatPerVb(4uL))
|
||||||
.finish(wallet)
|
.finish(wallet)
|
||||||
|
|
||||||
@ -79,10 +78,10 @@ class LiveWalletTest {
|
|||||||
assertTrue(walletDidSign)
|
assertTrue(walletDidSign)
|
||||||
|
|
||||||
val tx: Transaction = psbt.extractTx()
|
val tx: Transaction = psbt.extractTx()
|
||||||
println("Txid is: ${tx.txid()}")
|
println("Txid is: ${tx.computeTxid()}")
|
||||||
|
|
||||||
val txFee: ULong = wallet.calculateFee(tx)
|
val txFee: Amount = wallet.calculateFee(tx)
|
||||||
println("Tx fee is: ${txFee}")
|
println("Tx fee is: ${txFee.toSat()}")
|
||||||
|
|
||||||
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
|
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
|
||||||
println("Tx fee rate is: ${feeRate.toSatPerVbCeil()} sat/vB")
|
println("Tx fee rate is: ${feeRate.toSatPerVbCeil()} sat/vB")
|
||||||
|
@ -15,7 +15,7 @@ class OfflineDescriptorTest {
|
|||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
||||||
actual = descriptor.asString()
|
actual = descriptor.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,9 @@ import kotlin.test.AfterTest
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class OfflineWalletTest {
|
class OfflineWalletTest {
|
||||||
private val persistenceFilePath = InstrumentationRegistry
|
private val persistenceFilePath = InstrumentationRegistry
|
||||||
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence.db"
|
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence1.sqlite"
|
||||||
|
private val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||||
|
private val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.TESTNET)
|
||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
@ -29,19 +31,14 @@ class OfflineWalletTest {
|
|||||||
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
|
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
|
||||||
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
|
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
|
||||||
|
|
||||||
assertTrue(descriptor.asString().startsWith("tr"), "Bip86 Descriptor does not start with 'tr'")
|
assertTrue(descriptor.toString().startsWith("tr"), "Bip86 Descriptor does not start with 'tr'")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNewAddress() {
|
fun testNewAddress() {
|
||||||
val descriptor: Descriptor = Descriptor(
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
|
||||||
Network.TESTNET
|
|
||||||
)
|
|
||||||
val wallet: Wallet = Wallet(
|
val wallet: Wallet = Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
null,
|
changeDescriptor,
|
||||||
persistenceFilePath,
|
|
||||||
Network.TESTNET
|
Network.TESTNET
|
||||||
)
|
)
|
||||||
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
||||||
@ -52,27 +49,22 @@ class OfflineWalletTest {
|
|||||||
assertFalse(addressInfo.address.isValidForNetwork(Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
|
assertFalse(addressInfo.address.isValidForNetwork(Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e",
|
expected = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||||
actual = addressInfo.address.asString()
|
actual = addressInfo.address.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBalance() {
|
fun testBalance() {
|
||||||
val descriptor: Descriptor = Descriptor(
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
|
||||||
Network.TESTNET
|
|
||||||
)
|
|
||||||
val wallet: Wallet = Wallet(
|
val wallet: Wallet = Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
null,
|
changeDescriptor,
|
||||||
persistenceFilePath,
|
|
||||||
Network.TESTNET
|
Network.TESTNET
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = 0uL,
|
expected = 0uL,
|
||||||
actual = wallet.getBalance().total
|
actual = wallet.balance().total.toSat()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,10 +34,10 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
|||||||
environment(
|
environment(
|
||||||
// add build toolchain to PATH
|
// add build toolchain to PATH
|
||||||
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
||||||
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=21"),
|
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=24"),
|
||||||
Pair("AR", "llvm-ar"),
|
Pair("AR", "llvm-ar"),
|
||||||
Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android21-clang"),
|
Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android24-clang"),
|
||||||
Pair("CC", "aarch64-linux-android21-clang")
|
Pair("CC", "aarch64-linux-android24-clang")
|
||||||
)
|
)
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
@ -57,10 +57,10 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
|||||||
environment(
|
environment(
|
||||||
// add build toolchain to PATH
|
// add build toolchain to PATH
|
||||||
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
||||||
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=21"),
|
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=24"),
|
||||||
Pair("AR", "llvm-ar"),
|
Pair("AR", "llvm-ar"),
|
||||||
Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android21-clang"),
|
Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android24-clang"),
|
||||||
Pair("CC", "x86_64-linux-android21-clang")
|
Pair("CC", "x86_64-linux-android24-clang")
|
||||||
)
|
)
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
@ -80,10 +80,10 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
|||||||
environment(
|
environment(
|
||||||
// add build toolchain to PATH
|
// add build toolchain to PATH
|
||||||
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
||||||
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=21"),
|
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=24"),
|
||||||
Pair("AR", "llvm-ar"),
|
Pair("AR", "llvm-ar"),
|
||||||
Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER", "armv7a-linux-androideabi21-clang"),
|
Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER", "armv7a-linux-androideabi24-clang"),
|
||||||
Pair("CC", "armv7a-linux-androideabi21-clang")
|
Pair("CC", "armv7a-linux-androideabi24-clang")
|
||||||
)
|
)
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
|
665
bdk-ffi/Cargo.lock
generated
665
bdk-ffi/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk-ffi"
|
name = "bdk-ffi"
|
||||||
version = "1.0.0-alpha.10"
|
version = "1.0.0-alpha.11"
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
repository = "https://github.com/bitcoindevkit/bdk"
|
repository = "https://github.com/bitcoindevkit/bdk"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@ -18,19 +18,24 @@ path = "uniffi-bindgen.rs"
|
|||||||
default = ["uniffi/cli"]
|
default = ["uniffi/cli"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk = { version = "1.0.0-alpha.10", features = ["all-keys", "keys-bip39"] }
|
bdk_wallet = { version = "1.0.0-alpha.13", features = ["all-keys", "keys-bip39"] }
|
||||||
bdk_esplora = { version = "0.12.0", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] }
|
bdk_esplora = { version = "0.15.0", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] }
|
||||||
bdk_file_store = { version = "0.10.0" }
|
# NOTE: This is a temporary workaround to use the electrum-client with the use-rustls-ring feature. It points to a fork
|
||||||
|
# of bdk in which the bdk_electrum library uses the electrum-client with the use-rustls-ring feature.
|
||||||
uniffi = { version = "=0.26.1" }
|
bdk_electrum = { git = "https://github.com/thunderbiscuit/bdk/", package = "bdk_electrum", branch = "feature/electrum-client-ring-ffi-alpha13", default-features = false, features = ["use-rustls-ring"] }
|
||||||
|
# bdk_electrum = { version = "0.15.0" }
|
||||||
|
bdk_sqlite = { version = "0.2.0" }
|
||||||
|
bdk_bitcoind_rpc = { version = "0.12.0" }
|
||||||
bitcoin-internals = { version = "0.2.0", features = ["alloc"] }
|
bitcoin-internals = { version = "0.2.0", features = ["alloc"] }
|
||||||
|
|
||||||
|
uniffi = { version = "=0.28.0" }
|
||||||
thiserror = "1.0.58"
|
thiserror = "1.0.58"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
uniffi = { version = "=0.26.1", features = ["build"] }
|
uniffi = { version = "=0.28.0", features = ["build"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
uniffi = { version = "=0.26.1", features = ["bindgen-tests"] }
|
uniffi = { version = "=0.28.0", features = ["bindgen-tests"] }
|
||||||
assert_matches = "1.5.0"
|
assert_matches = "1.5.0"
|
||||||
|
|
||||||
[profile.release-smaller]
|
[profile.release-smaller]
|
||||||
|
@ -5,16 +5,17 @@ namespace bdk {};
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
interface AddressError {
|
interface AddressParseError {
|
||||||
Base58();
|
Base58();
|
||||||
Bech32();
|
Bech32();
|
||||||
WitnessVersion(string error_message);
|
WitnessVersion(string error_message);
|
||||||
WitnessProgram(string error_message);
|
WitnessProgram(string error_message);
|
||||||
UncompressedPubkey();
|
UnknownHrp();
|
||||||
ExcessiveScriptSize();
|
LegacyAddressTooLong();
|
||||||
UnrecognizedScript();
|
InvalidBase58PayloadLength();
|
||||||
NetworkValidation(Network required, Network found, string address);
|
InvalidLegacyPrefix();
|
||||||
OtherAddressErr();
|
NetworkValidation();
|
||||||
|
OtherAddressParseErr();
|
||||||
};
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
@ -44,7 +45,7 @@ interface Bip39Error {
|
|||||||
[Error]
|
[Error]
|
||||||
interface CalculateFeeError {
|
interface CalculateFeeError {
|
||||||
MissingTxOut(sequence<OutPoint> out_points);
|
MissingTxOut(sequence<OutPoint> out_points);
|
||||||
NegativeFee(i64 fee);
|
NegativeFee(string amount);
|
||||||
};
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
@ -55,7 +56,6 @@ interface CannotConnectError {
|
|||||||
[Error]
|
[Error]
|
||||||
interface CreateTxError {
|
interface CreateTxError {
|
||||||
Descriptor(string error_message);
|
Descriptor(string error_message);
|
||||||
Persist(string error_message);
|
|
||||||
Policy(string error_message);
|
Policy(string error_message);
|
||||||
SpendingPolicyRequired(string kind);
|
SpendingPolicyRequired(string kind);
|
||||||
Version0();
|
Version0();
|
||||||
@ -63,7 +63,7 @@ interface CreateTxError {
|
|||||||
LockTime(string requested, string required);
|
LockTime(string requested, string required);
|
||||||
RbfSequence();
|
RbfSequence();
|
||||||
RbfSequenceCsv(string rbf, string csv);
|
RbfSequenceCsv(string rbf, string csv);
|
||||||
FeeTooLow(u64 required);
|
FeeTooLow(string required);
|
||||||
FeeRateTooLow(string required);
|
FeeRateTooLow(string required);
|
||||||
NoUtxosSelected();
|
NoUtxosSelected();
|
||||||
OutputBelowDustLimit(u64 index);
|
OutputBelowDustLimit(u64 index);
|
||||||
@ -92,6 +92,7 @@ interface DescriptorError {
|
|||||||
Pk(string error_message);
|
Pk(string error_message);
|
||||||
Miniscript(string error_message);
|
Miniscript(string error_message);
|
||||||
Hex(string error_message);
|
Hex(string error_message);
|
||||||
|
ExternalAndInternalAreTheSame();
|
||||||
};
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
@ -101,6 +102,27 @@ interface DescriptorKeyError {
|
|||||||
Bip32(string error_message);
|
Bip32(string error_message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Error]
|
||||||
|
interface ElectrumError {
|
||||||
|
IOError(string error_message);
|
||||||
|
Json(string error_message);
|
||||||
|
Hex(string error_message);
|
||||||
|
Protocol(string error_message);
|
||||||
|
Bitcoin(string error_message);
|
||||||
|
AlreadySubscribed();
|
||||||
|
NotSubscribed();
|
||||||
|
InvalidResponse(string error_message);
|
||||||
|
Message(string error_message);
|
||||||
|
InvalidDNSNameError(string domain);
|
||||||
|
MissingDomain();
|
||||||
|
AllAttemptsErrored();
|
||||||
|
SharedIOError(string error_message);
|
||||||
|
CouldntLockReader();
|
||||||
|
Mpsc();
|
||||||
|
CouldNotCreateConnection(string error_message);
|
||||||
|
RequestAlreadyConsumed();
|
||||||
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
interface EsploraError {
|
interface EsploraError {
|
||||||
Minreq(string error_message);
|
Minreq(string error_message);
|
||||||
@ -131,17 +153,77 @@ enum FeeRateError {
|
|||||||
"ArithmeticOverflow"
|
"ArithmeticOverflow"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Error]
|
||||||
|
interface FromScriptError {
|
||||||
|
UnrecognizedScript();
|
||||||
|
WitnessProgram(string error_message);
|
||||||
|
WitnessVersion(string error_message);
|
||||||
|
OtherFromScriptErr();
|
||||||
|
};
|
||||||
|
|
||||||
|
[Error]
|
||||||
|
interface ParseAmountError {
|
||||||
|
OutOfRange();
|
||||||
|
TooPrecise();
|
||||||
|
MissingDigits();
|
||||||
|
InputTooLarge();
|
||||||
|
InvalidCharacter(string error_message);
|
||||||
|
OtherParseAmountErr();
|
||||||
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
interface PersistenceError {
|
interface PersistenceError {
|
||||||
Write(string error_message);
|
Write(string error_message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Error]
|
||||||
|
interface PsbtError {
|
||||||
|
InvalidMagic();
|
||||||
|
MissingUtxo();
|
||||||
|
InvalidSeparator();
|
||||||
|
PsbtUtxoOutOfBounds();
|
||||||
|
InvalidKey(string key);
|
||||||
|
InvalidProprietaryKey();
|
||||||
|
DuplicateKey(string key);
|
||||||
|
UnsignedTxHasScriptSigs();
|
||||||
|
UnsignedTxHasScriptWitnesses();
|
||||||
|
MustHaveUnsignedTx();
|
||||||
|
NoMorePairs();
|
||||||
|
UnexpectedUnsignedTx();
|
||||||
|
NonStandardSighashType(u32 sighash);
|
||||||
|
InvalidHash(string hash);
|
||||||
|
InvalidPreimageHashPair();
|
||||||
|
CombineInconsistentKeySources(string xpub);
|
||||||
|
ConsensusEncoding(string encoding_error);
|
||||||
|
NegativeFee();
|
||||||
|
FeeOverflow();
|
||||||
|
InvalidPublicKey(string error_message);
|
||||||
|
InvalidSecp256k1PublicKey(string secp256k1_error);
|
||||||
|
InvalidXOnlyPublicKey();
|
||||||
|
InvalidEcdsaSignature(string error_message);
|
||||||
|
InvalidTaprootSignature(string error_message);
|
||||||
|
InvalidControlBlock();
|
||||||
|
InvalidLeafVersion();
|
||||||
|
Taproot();
|
||||||
|
TapTree(string error_message);
|
||||||
|
XPubKey();
|
||||||
|
Version(string error_message);
|
||||||
|
PartialDataConsumption();
|
||||||
|
Io(string error_message);
|
||||||
|
OtherPsbtErr();
|
||||||
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
interface PsbtParseError {
|
interface PsbtParseError {
|
||||||
PsbtEncoding(string error_message);
|
PsbtEncoding(string error_message);
|
||||||
Base64Encoding(string error_message);
|
Base64Encoding(string error_message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Error]
|
||||||
|
interface InspectError {
|
||||||
|
RequestAlreadyConsumed();
|
||||||
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
interface SignerError {
|
interface SignerError {
|
||||||
MissingKey();
|
MissingKey();
|
||||||
@ -155,11 +237,19 @@ interface SignerError {
|
|||||||
MissingHdKeypath();
|
MissingHdKeypath();
|
||||||
NonStandardSighash();
|
NonStandardSighash();
|
||||||
InvalidSighash();
|
InvalidSighash();
|
||||||
SighashError(string error_message);
|
SighashP2wpkh(string error_message);
|
||||||
|
SighashTaproot(string error_message);
|
||||||
|
TxInputsIndexError(string error_message);
|
||||||
MiniscriptPsbt(string error_message);
|
MiniscriptPsbt(string error_message);
|
||||||
External(string error_message);
|
External(string error_message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Error]
|
||||||
|
interface SqliteError {
|
||||||
|
InvalidNetwork(Network expected, Network given);
|
||||||
|
Sqlite(string rusqlite_error);
|
||||||
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
interface TransactionError {
|
interface TransactionError {
|
||||||
Io();
|
Io();
|
||||||
@ -178,17 +268,14 @@ interface TxidParseError {
|
|||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
interface WalletCreationError {
|
interface WalletCreationError {
|
||||||
Io(string error_message);
|
Descriptor(string error_message);
|
||||||
InvalidMagicBytes(sequence<u8> got, sequence<u8> expected);
|
|
||||||
Descriptor();
|
|
||||||
Persist(string error_message);
|
|
||||||
NotInitialized();
|
|
||||||
LoadedGenesisDoesNotMatch(string expected, string got);
|
LoadedGenesisDoesNotMatch(string expected, string got);
|
||||||
LoadedNetworkDoesNotMatch(Network expected, Network? got);
|
LoadedNetworkDoesNotMatch(Network expected, Network? got);
|
||||||
|
LoadedDescriptorDoesNotMatch(string got, KeychainKind keychain);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// bdk crate - types module
|
// bdk_wallet crate - types module
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
enum KeychainKind {
|
enum KeychainKind {
|
||||||
@ -203,17 +290,17 @@ dictionary AddressInfo {
|
|||||||
};
|
};
|
||||||
|
|
||||||
dictionary Balance {
|
dictionary Balance {
|
||||||
u64 immature;
|
Amount immature;
|
||||||
|
|
||||||
u64 trusted_pending;
|
Amount trusted_pending;
|
||||||
|
|
||||||
u64 untrusted_pending;
|
Amount untrusted_pending;
|
||||||
|
|
||||||
u64 confirmed;
|
Amount confirmed;
|
||||||
|
|
||||||
u64 trusted_spendable;
|
Amount trusted_spendable;
|
||||||
|
|
||||||
u64 total;
|
Amount total;
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary LocalOutput {
|
dictionary LocalOutput {
|
||||||
@ -239,12 +326,30 @@ dictionary CanonicalTx {
|
|||||||
ChainPosition chain_position;
|
ChainPosition chain_position;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FullScanRequest {};
|
interface FullScanRequest {
|
||||||
|
[Throws=InspectError]
|
||||||
|
FullScanRequest inspect_spks_for_all_keychains(FullScanScriptInspector inspector);
|
||||||
|
};
|
||||||
|
|
||||||
interface SyncRequest {};
|
interface SyncRequest {
|
||||||
|
[Throws=InspectError]
|
||||||
|
SyncRequest inspect_spks(SyncScriptInspector inspector);
|
||||||
|
};
|
||||||
|
|
||||||
|
[Trait, WithForeign]
|
||||||
|
interface SyncScriptInspector {
|
||||||
|
void inspect(Script script, u64 total);
|
||||||
|
};
|
||||||
|
|
||||||
|
[Trait, WithForeign]
|
||||||
|
interface FullScanScriptInspector {
|
||||||
|
void inspect(KeychainKind keychain, u32 index, Script script);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ChangeSet {};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// bdk crate - wallet module
|
// bdk_wallet crate - wallet module
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
enum ChangeSpendPolicy {
|
enum ChangeSpendPolicy {
|
||||||
@ -255,21 +360,20 @@ enum ChangeSpendPolicy {
|
|||||||
|
|
||||||
interface Wallet {
|
interface Wallet {
|
||||||
[Throws=WalletCreationError]
|
[Throws=WalletCreationError]
|
||||||
constructor(Descriptor descriptor, Descriptor? change_descriptor, string persistence_backend_path, Network network);
|
constructor(Descriptor descriptor, Descriptor change_descriptor, Network network);
|
||||||
|
|
||||||
|
[Name=new_or_load, Throws=WalletCreationError]
|
||||||
|
constructor(Descriptor descriptor, Descriptor change_descriptor, ChangeSet? change_set, Network network);
|
||||||
|
|
||||||
[Throws=PersistenceError]
|
|
||||||
AddressInfo reveal_next_address(KeychainKind keychain);
|
AddressInfo reveal_next_address(KeychainKind keychain);
|
||||||
|
|
||||||
Network network();
|
Network network();
|
||||||
|
|
||||||
Balance get_balance();
|
Balance balance();
|
||||||
|
|
||||||
[Throws=CannotConnectError]
|
[Throws=CannotConnectError]
|
||||||
void apply_update(Update update);
|
void apply_update(Update update);
|
||||||
|
|
||||||
[Throws=PersistenceError]
|
|
||||||
boolean commit();
|
|
||||||
|
|
||||||
boolean is_mine([ByRef] Script script);
|
boolean is_mine([ByRef] Script script);
|
||||||
|
|
||||||
[Throws=SignerError]
|
[Throws=SignerError]
|
||||||
@ -283,7 +387,7 @@ interface Wallet {
|
|||||||
CanonicalTx? get_tx(string txid);
|
CanonicalTx? get_tx(string txid);
|
||||||
|
|
||||||
[Throws=CalculateFeeError]
|
[Throws=CalculateFeeError]
|
||||||
u64 calculate_fee([ByRef] Transaction tx);
|
Amount calculate_fee([ByRef] Transaction tx);
|
||||||
|
|
||||||
[Throws=CalculateFeeError]
|
[Throws=CalculateFeeError]
|
||||||
FeeRate calculate_fee_rate([ByRef] Transaction tx);
|
FeeRate calculate_fee_rate([ByRef] Transaction tx);
|
||||||
@ -295,6 +399,8 @@ interface Wallet {
|
|||||||
FullScanRequest start_full_scan();
|
FullScanRequest start_full_scan();
|
||||||
|
|
||||||
SyncRequest start_sync_with_revealed_spks();
|
SyncRequest start_sync_with_revealed_spks();
|
||||||
|
|
||||||
|
ChangeSet? take_staged();
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Update {};
|
interface Update {};
|
||||||
@ -302,7 +408,7 @@ interface Update {};
|
|||||||
interface TxBuilder {
|
interface TxBuilder {
|
||||||
constructor();
|
constructor();
|
||||||
|
|
||||||
TxBuilder add_recipient([ByRef] Script script, u64 amount);
|
TxBuilder add_recipient([ByRef] Script script, Amount amount);
|
||||||
|
|
||||||
TxBuilder set_recipients(sequence<ScriptAmount> recipients);
|
TxBuilder set_recipients(sequence<ScriptAmount> recipients);
|
||||||
|
|
||||||
@ -322,7 +428,7 @@ interface TxBuilder {
|
|||||||
|
|
||||||
TxBuilder fee_rate([ByRef] FeeRate fee_rate);
|
TxBuilder fee_rate([ByRef] FeeRate fee_rate);
|
||||||
|
|
||||||
TxBuilder fee_absolute(u64 fee);
|
TxBuilder fee_absolute(Amount fee);
|
||||||
|
|
||||||
TxBuilder drain_wallet();
|
TxBuilder drain_wallet();
|
||||||
|
|
||||||
@ -347,10 +453,26 @@ interface BumpFeeTxBuilder {
|
|||||||
Psbt finish([ByRef] Wallet wallet);
|
Psbt finish([ByRef] Wallet wallet);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// bdk_sqlite crate
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface SqliteStore {
|
||||||
|
[Throws=SqliteError]
|
||||||
|
constructor(string path);
|
||||||
|
|
||||||
|
[Throws=SqliteError]
|
||||||
|
void write([ByRef] ChangeSet change_set);
|
||||||
|
|
||||||
|
[Throws=SqliteError]
|
||||||
|
ChangeSet? read();
|
||||||
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// bdk crate - descriptor module
|
// bdk crate - descriptor module
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Traits=(Display)]
|
||||||
interface Mnemonic {
|
interface Mnemonic {
|
||||||
constructor(WordCount word_count);
|
constructor(WordCount word_count);
|
||||||
|
|
||||||
@ -359,8 +481,6 @@ interface Mnemonic {
|
|||||||
|
|
||||||
[Name=from_entropy, Throws=Bip39Error]
|
[Name=from_entropy, Throws=Bip39Error]
|
||||||
constructor(sequence<u8> entropy);
|
constructor(sequence<u8> entropy);
|
||||||
|
|
||||||
string as_string();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DerivationPath {
|
interface DerivationPath {
|
||||||
@ -400,6 +520,7 @@ interface DescriptorPublicKey {
|
|||||||
string as_string();
|
string as_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Traits=(Display)]
|
||||||
interface Descriptor {
|
interface Descriptor {
|
||||||
[Throws=DescriptorError]
|
[Throws=DescriptorError]
|
||||||
constructor(string descriptor, Network network);
|
constructor(string descriptor, Network network);
|
||||||
@ -428,9 +549,7 @@ interface Descriptor {
|
|||||||
[Name=new_bip86_public]
|
[Name=new_bip86_public]
|
||||||
constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||||
|
|
||||||
string as_string();
|
string to_string_with_secret();
|
||||||
|
|
||||||
string as_string_private();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@ -450,22 +569,40 @@ interface EsploraClient {
|
|||||||
void broadcast([ByRef] Transaction transaction);
|
void broadcast([ByRef] Transaction transaction);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// bdk_electrum crate
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface ElectrumClient {
|
||||||
|
[Throws=ElectrumError]
|
||||||
|
constructor(string url);
|
||||||
|
|
||||||
|
[Throws=ElectrumError]
|
||||||
|
Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 batch_size, boolean fetch_prev_txouts);
|
||||||
|
|
||||||
|
[Throws=ElectrumError]
|
||||||
|
Update sync(SyncRequest sync_request, u64 batch_size, boolean fetch_prev_txouts);
|
||||||
|
|
||||||
|
[Throws=ElectrumError]
|
||||||
|
string broadcast([ByRef] Transaction transaction);
|
||||||
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// bdk-ffi-defined types
|
// bdk-ffi-defined types
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
dictionary ScriptAmount {
|
dictionary ScriptAmount {
|
||||||
Script script;
|
Script script;
|
||||||
u64 amount;
|
Amount amount;
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary SentAndReceivedValues {
|
dictionary SentAndReceivedValues {
|
||||||
u64 sent;
|
Amount sent;
|
||||||
u64 received;
|
Amount received;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// bdk crate - bitcoin re-exports
|
// bdk_wallet crate - bitcoin re-exports
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
interface Script {
|
interface Script {
|
||||||
@ -490,18 +627,18 @@ enum WordCount {
|
|||||||
"Words24",
|
"Words24",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Traits=(Display)]
|
||||||
interface Address {
|
interface Address {
|
||||||
[Throws=AddressError]
|
[Throws=AddressParseError]
|
||||||
constructor(string address, Network network);
|
constructor(string address, Network network);
|
||||||
|
|
||||||
Network network();
|
[Name=from_script, Throws=FromScriptError]
|
||||||
|
constructor(Script script, Network network);
|
||||||
|
|
||||||
Script script_pubkey();
|
Script script_pubkey();
|
||||||
|
|
||||||
string to_qr_uri();
|
string to_qr_uri();
|
||||||
|
|
||||||
string as_string();
|
|
||||||
|
|
||||||
boolean is_valid_for_network(Network network);
|
boolean is_valid_for_network(Network network);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -509,7 +646,7 @@ interface Transaction {
|
|||||||
[Throws=TransactionError]
|
[Throws=TransactionError]
|
||||||
constructor(sequence<u8> transaction_bytes);
|
constructor(sequence<u8> transaction_bytes);
|
||||||
|
|
||||||
string txid();
|
string compute_txid();
|
||||||
|
|
||||||
u64 total_size();
|
u64 total_size();
|
||||||
|
|
||||||
@ -526,6 +663,12 @@ interface Transaction {
|
|||||||
sequence<u8> serialize();
|
sequence<u8> serialize();
|
||||||
|
|
||||||
u64 weight();
|
u64 weight();
|
||||||
|
|
||||||
|
sequence<TxIn> input();
|
||||||
|
|
||||||
|
sequence<TxOut> output();
|
||||||
|
|
||||||
|
u32 lock_time();
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Psbt {
|
interface Psbt {
|
||||||
@ -536,6 +679,14 @@ interface Psbt {
|
|||||||
|
|
||||||
[Throws=ExtractTxError]
|
[Throws=ExtractTxError]
|
||||||
Transaction extract_tx();
|
Transaction extract_tx();
|
||||||
|
|
||||||
|
[Throws=PsbtError]
|
||||||
|
u64 fee();
|
||||||
|
|
||||||
|
[Throws=PsbtError]
|
||||||
|
Psbt combine(Psbt other);
|
||||||
|
|
||||||
|
string json_serialize();
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary OutPoint {
|
dictionary OutPoint {
|
||||||
@ -543,6 +694,18 @@ dictionary OutPoint {
|
|||||||
u32 vout;
|
u32 vout;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface Amount {
|
||||||
|
[Name=from_sat]
|
||||||
|
constructor(u64 from_sat);
|
||||||
|
|
||||||
|
[Name=from_btc, Throws=ParseAmountError]
|
||||||
|
constructor(f64 from_btc);
|
||||||
|
|
||||||
|
u64 to_sat();
|
||||||
|
|
||||||
|
f64 to_btc();
|
||||||
|
};
|
||||||
|
|
||||||
interface FeeRate {
|
interface FeeRate {
|
||||||
[Name=from_sat_per_vb, Throws=FeeRateError]
|
[Name=from_sat_per_vb, Throws=FeeRateError]
|
||||||
constructor(u64 sat_per_vb);
|
constructor(u64 sat_per_vb);
|
||||||
@ -556,3 +719,10 @@ interface FeeRate {
|
|||||||
|
|
||||||
u64 to_sat_per_kwu();
|
u64 to_sat_per_kwu();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dictionary TxIn {
|
||||||
|
OutPoint previous_output;
|
||||||
|
Script script_sig;
|
||||||
|
u32 sequence;
|
||||||
|
sequence<sequence<u8>> witness;
|
||||||
|
};
|
||||||
|
@ -1,23 +1,65 @@
|
|||||||
use crate::error::{AddressError, FeeRateError, PsbtParseError, TransactionError};
|
use crate::error::{
|
||||||
|
AddressParseError, FeeRateError, FromScriptError, PsbtError, PsbtParseError, TransactionError,
|
||||||
|
};
|
||||||
|
|
||||||
use bdk::bitcoin::address::{NetworkChecked, NetworkUnchecked};
|
use bdk_bitcoind_rpc::bitcoincore_rpc::jsonrpc::serde_json;
|
||||||
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
|
use bdk_wallet::bitcoin::address::{NetworkChecked, NetworkUnchecked};
|
||||||
use bdk::bitcoin::blockdata::transaction::TxOut as BdkTxOut;
|
use bdk_wallet::bitcoin::amount::ParseAmountError;
|
||||||
use bdk::bitcoin::consensus::encode::serialize;
|
use bdk_wallet::bitcoin::consensus::encode::serialize;
|
||||||
use bdk::bitcoin::consensus::Decodable;
|
use bdk_wallet::bitcoin::consensus::Decodable;
|
||||||
use bdk::bitcoin::psbt::ExtractTxError;
|
use bdk_wallet::bitcoin::io::Cursor;
|
||||||
use bdk::bitcoin::Address as BdkAddress;
|
use bdk_wallet::bitcoin::psbt::ExtractTxError;
|
||||||
use bdk::bitcoin::FeeRate as BdkFeeRate;
|
use bdk_wallet::bitcoin::Address as BdkAddress;
|
||||||
use bdk::bitcoin::Network;
|
use bdk_wallet::bitcoin::Amount as BdkAmount;
|
||||||
use bdk::bitcoin::OutPoint as BdkOutPoint;
|
use bdk_wallet::bitcoin::FeeRate as BdkFeeRate;
|
||||||
use bdk::bitcoin::Psbt as BdkPsbt;
|
use bdk_wallet::bitcoin::Network;
|
||||||
use bdk::bitcoin::Transaction as BdkTransaction;
|
use bdk_wallet::bitcoin::OutPoint as BdkOutPoint;
|
||||||
use bdk::bitcoin::Txid;
|
use bdk_wallet::bitcoin::Psbt as BdkPsbt;
|
||||||
|
use bdk_wallet::bitcoin::ScriptBuf as BdkScriptBuf;
|
||||||
|
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
|
||||||
|
use bdk_wallet::bitcoin::TxIn as BdkTxIn;
|
||||||
|
use bdk_wallet::bitcoin::TxOut as BdkTxOut;
|
||||||
|
use bdk_wallet::bitcoin::Txid;
|
||||||
|
|
||||||
use std::io::Cursor;
|
use std::fmt::Display;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Amount(pub(crate) BdkAmount);
|
||||||
|
|
||||||
|
impl Amount {
|
||||||
|
pub fn from_sat(sat: u64) -> Self {
|
||||||
|
Amount(BdkAmount::from_sat(sat))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_btc(btc: f64) -> Result<Self, ParseAmountError> {
|
||||||
|
let bdk_amount = BdkAmount::from_btc(btc).map_err(ParseAmountError::from)?;
|
||||||
|
Ok(Amount(bdk_amount))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_sat(&self) -> u64 {
|
||||||
|
self.0.to_sat()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_btc(&self) -> f64 {
|
||||||
|
self.0.to_btc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Amount> for BdkAmount {
|
||||||
|
fn from(amount: Amount) -> Self {
|
||||||
|
amount.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BdkAmount> for Amount {
|
||||||
|
fn from(amount: BdkAmount) -> Self {
|
||||||
|
Amount(amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Script(pub(crate) BdkScriptBuf);
|
pub struct Script(pub(crate) BdkScriptBuf);
|
||||||
|
|
||||||
@ -42,15 +84,17 @@ impl From<BdkScriptBuf> for Script {
|
|||||||
pub struct Address(BdkAddress<NetworkChecked>);
|
pub struct Address(BdkAddress<NetworkChecked>);
|
||||||
|
|
||||||
impl Address {
|
impl Address {
|
||||||
pub fn new(address: String, network: Network) -> Result<Self, AddressError> {
|
pub fn new(address: String, network: Network) -> Result<Self, AddressParseError> {
|
||||||
let parsed_address = address.parse::<bdk::bitcoin::Address<NetworkUnchecked>>()?;
|
let parsed_address = address.parse::<bdk_wallet::bitcoin::Address<NetworkUnchecked>>()?;
|
||||||
let network_checked_address = parsed_address.require_network(network)?;
|
let network_checked_address = parsed_address.require_network(network)?;
|
||||||
|
|
||||||
Ok(Address(network_checked_address))
|
Ok(Address(network_checked_address))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn network(&self) -> Network {
|
pub fn from_script(script: Arc<Script>, network: Network) -> Result<Self, FromScriptError> {
|
||||||
*self.0.network()
|
let address = BdkAddress::from_script(&script.0.clone(), network)?;
|
||||||
|
|
||||||
|
Ok(Address(address))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn script_pubkey(&self) -> Arc<Script> {
|
pub fn script_pubkey(&self) -> Arc<Script> {
|
||||||
@ -61,10 +105,6 @@ impl Address {
|
|||||||
self.0.to_qr_uri()
|
self.0.to_qr_uri()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_string(&self) -> String {
|
|
||||||
self.0.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_valid_for_network(&self, network: Network) -> bool {
|
pub fn is_valid_for_network(&self, network: Network) -> bool {
|
||||||
let address_str = self.0.to_string();
|
let address_str = self.0.to_string();
|
||||||
if let Ok(unchecked_address) = address_str.parse::<BdkAddress<NetworkUnchecked>>() {
|
if let Ok(unchecked_address) = address_str.parse::<BdkAddress<NetworkUnchecked>>() {
|
||||||
@ -75,6 +115,12 @@ impl Address {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Address {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Address> for BdkAddress {
|
impl From<Address> for BdkAddress {
|
||||||
fn from(address: Address) -> Self {
|
fn from(address: Address) -> Self {
|
||||||
address.0
|
address.0
|
||||||
@ -97,8 +143,8 @@ impl Transaction {
|
|||||||
Ok(Transaction(tx))
|
Ok(Transaction(tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn txid(&self) -> String {
|
pub fn compute_txid(&self) -> String {
|
||||||
self.0.txid().to_string()
|
self.0.compute_txid().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn weight(&self) -> u64 {
|
pub fn weight(&self) -> u64 {
|
||||||
@ -132,6 +178,18 @@ impl Transaction {
|
|||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
serialize(&self.0)
|
serialize(&self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn input(&self) -> Vec<TxIn> {
|
||||||
|
self.0.input.iter().map(|tx_in| tx_in.into()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output(&self) -> Vec<TxOut> {
|
||||||
|
self.0.output.iter().map(|tx_out| tx_out.into()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock_time(&self) -> u32 {
|
||||||
|
self.0.lock_time.to_consensus_u32()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BdkTransaction> for Transaction {
|
impl From<BdkTransaction> for Transaction {
|
||||||
@ -170,6 +228,27 @@ impl Psbt {
|
|||||||
let transaction: Transaction = tx.into();
|
let transaction: Transaction = tx.into();
|
||||||
Ok(Arc::new(transaction))
|
Ok(Arc::new(transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fee(&self) -> Result<u64, PsbtError> {
|
||||||
|
self.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.fee()
|
||||||
|
.map(|fee| fee.to_sat())
|
||||||
|
.map_err(PsbtError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn combine(&self, other: Arc<Psbt>) -> Result<Arc<Psbt>, PsbtError> {
|
||||||
|
let mut original_psbt = self.0.lock().unwrap().clone();
|
||||||
|
let other_psbt = other.0.lock().unwrap().clone();
|
||||||
|
original_psbt.combine(other_psbt)?;
|
||||||
|
Ok(Arc::new(Psbt(Mutex::new(original_psbt))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn json_serialize(&self) -> String {
|
||||||
|
let psbt = self.0.lock().unwrap();
|
||||||
|
serde_json::to_string(psbt.deref()).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BdkPsbt> for Psbt {
|
impl From<BdkPsbt> for Psbt {
|
||||||
@ -202,6 +281,28 @@ impl From<&BdkOutPoint> for OutPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TxIn {
|
||||||
|
pub previous_output: OutPoint,
|
||||||
|
pub script_sig: Arc<Script>,
|
||||||
|
pub sequence: u32,
|
||||||
|
pub witness: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&BdkTxIn> for TxIn {
|
||||||
|
fn from(tx_in: &BdkTxIn) -> Self {
|
||||||
|
TxIn {
|
||||||
|
previous_output: OutPoint {
|
||||||
|
txid: tx_in.previous_output.txid.to_string(),
|
||||||
|
vout: tx_in.previous_output.vout,
|
||||||
|
},
|
||||||
|
script_sig: Arc::new(Script(tx_in.script_sig.clone())),
|
||||||
|
sequence: tx_in.sequence.0,
|
||||||
|
witness: tx_in.witness.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TxOut {
|
pub struct TxOut {
|
||||||
pub value: u64,
|
pub value: u64,
|
||||||
@ -218,7 +319,7 @@ impl From<&BdkTxOut> for TxOut {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FeeRate(pub BdkFeeRate);
|
pub struct FeeRate(pub(crate) BdkFeeRate);
|
||||||
|
|
||||||
impl FeeRate {
|
impl FeeRate {
|
||||||
pub fn from_sat_per_vb(sat_per_vb: u64) -> Result<Self, FeeRateError> {
|
pub fn from_sat_per_vb(sat_per_vb: u64) -> Result<Self, FeeRateError> {
|
||||||
@ -271,11 +372,6 @@ mod tests {
|
|||||||
docs_address_testnet.is_valid_for_network(Network::Regtest),
|
docs_address_testnet.is_valid_for_network(Network::Regtest),
|
||||||
"Address should be valid for Regtest"
|
"Address should be valid for Regtest"
|
||||||
);
|
);
|
||||||
assert_ne!(
|
|
||||||
docs_address_testnet.network(),
|
|
||||||
Network::Bitcoin,
|
|
||||||
"Address should not be parsed as Bitcoin"
|
|
||||||
);
|
|
||||||
|
|
||||||
let docs_address_mainnet_str = "32iVBEu4dxkUQk9dJbZUiBiQdmypcEyJRf";
|
let docs_address_mainnet_str = "32iVBEu4dxkUQk9dJbZUiBiQdmypcEyJRf";
|
||||||
let docs_address_mainnet =
|
let docs_address_mainnet =
|
||||||
@ -284,21 +380,6 @@ mod tests {
|
|||||||
docs_address_mainnet.is_valid_for_network(Network::Bitcoin),
|
docs_address_mainnet.is_valid_for_network(Network::Bitcoin),
|
||||||
"Address should be valid for Bitcoin"
|
"Address should be valid for Bitcoin"
|
||||||
);
|
);
|
||||||
assert_ne!(
|
|
||||||
docs_address_mainnet.network(),
|
|
||||||
Network::Testnet,
|
|
||||||
"Address should not be valid for Testnet"
|
|
||||||
);
|
|
||||||
assert_ne!(
|
|
||||||
docs_address_mainnet.network(),
|
|
||||||
Network::Signet,
|
|
||||||
"Address should not be valid for Signet"
|
|
||||||
);
|
|
||||||
assert_ne!(
|
|
||||||
docs_address_mainnet.network(),
|
|
||||||
Network::Regtest,
|
|
||||||
"Address should not be valid for Regtest"
|
|
||||||
);
|
|
||||||
|
|
||||||
// ====Bech32====
|
// ====Bech32====
|
||||||
|
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
use crate::error::DescriptorError;
|
use crate::error::DescriptorError;
|
||||||
use crate::keys::DescriptorPublicKey;
|
use crate::keys::DescriptorPublicKey;
|
||||||
use crate::keys::DescriptorSecretKey;
|
use crate::keys::DescriptorSecretKey;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use bdk::bitcoin::bip32::Fingerprint;
|
use bdk_wallet::bitcoin::bip32::Fingerprint;
|
||||||
use bdk::bitcoin::key::Secp256k1;
|
use bdk_wallet::bitcoin::key::Secp256k1;
|
||||||
use bdk::bitcoin::Network;
|
use bdk_wallet::bitcoin::Network;
|
||||||
use bdk::descriptor::{ExtendedDescriptor, IntoWalletDescriptor};
|
use bdk_wallet::descriptor::{ExtendedDescriptor, IntoWalletDescriptor};
|
||||||
use bdk::keys::DescriptorPublicKey as BdkDescriptorPublicKey;
|
use bdk_wallet::keys::DescriptorPublicKey as BdkDescriptorPublicKey;
|
||||||
use bdk::keys::{DescriptorSecretKey as BdkDescriptorSecretKey, KeyMap};
|
use bdk_wallet::keys::{DescriptorSecretKey as BdkDescriptorSecretKey, KeyMap};
|
||||||
use bdk::template::{
|
use bdk_wallet::template::{
|
||||||
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
|
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
|
||||||
DescriptorTemplate,
|
DescriptorTemplate,
|
||||||
};
|
};
|
||||||
use bdk::KeychainKind;
|
use bdk_wallet::KeychainKind;
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@ -260,14 +261,16 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn as_string_private(&self) -> String {
|
pub(crate) fn to_string_with_secret(&self) -> String {
|
||||||
let descriptor = &self.extended_descriptor;
|
let descriptor = &self.extended_descriptor;
|
||||||
let key_map = &self.key_map;
|
let key_map = &self.key_map;
|
||||||
descriptor.to_string_with_secret(key_map)
|
descriptor.to_string_with_secret(key_map)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn as_string(&self) -> String {
|
impl Display for Descriptor {
|
||||||
self.extended_descriptor.to_string()
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.extended_descriptor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,6 +278,8 @@ impl Descriptor {
|
|||||||
mod test {
|
mod test {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
|
use bdk_wallet::bitcoin::Network;
|
||||||
|
use bdk_wallet::KeychainKind;
|
||||||
|
|
||||||
fn get_descriptor_secret_key() -> DescriptorSecretKey {
|
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();
|
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();
|
||||||
@ -319,10 +324,10 @@ mod test {
|
|||||||
let template_private_86 =
|
let template_private_86 =
|
||||||
Descriptor::new_bip86(&master, KeychainKind::External, Network::Testnet);
|
Descriptor::new_bip86(&master, KeychainKind::External, Network::Testnet);
|
||||||
// the extended public keys are the same when creating them manually as they are with the templates
|
// 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 49: {}", template_private_49);
|
||||||
println!("Template 44: {}", template_private_44.as_string());
|
println!("Template 44: {}", template_private_44);
|
||||||
println!("Template 84: {}", template_private_84.as_string());
|
println!("Template 84: {}", template_private_84);
|
||||||
println!("Template 86: {}", template_private_86.as_string());
|
println!("Template 86: {}", template_private_86);
|
||||||
let template_public_44 = Descriptor::new_bip44_public(
|
let template_public_44 = Descriptor::new_bip44_public(
|
||||||
&handmade_public_44,
|
&handmade_public_44,
|
||||||
"d1d04177".to_string(),
|
"d1d04177".to_string(),
|
||||||
@ -347,43 +352,43 @@ mod test {
|
|||||||
KeychainKind::External,
|
KeychainKind::External,
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
);
|
);
|
||||||
println!("Template public 49: {}", template_public_49.as_string());
|
println!("Template public 49: {}", template_public_49);
|
||||||
println!("Template public 44: {}", template_public_44.as_string());
|
println!("Template public 44: {}", template_public_44);
|
||||||
println!("Template public 84: {}", template_public_84.as_string());
|
println!("Template public 84: {}", template_public_84);
|
||||||
println!("Template public 86: {}", template_public_86.as_string());
|
println!("Template public 86: {}", template_public_86);
|
||||||
// when using a public key, both as_string and as_string_private return the same string
|
// when using a public key, both to_string and as_string_private return the same string
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_public_44.as_string_private(),
|
template_public_44.to_string_with_secret(),
|
||||||
template_public_44.as_string()
|
template_public_44.to_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_public_49.as_string_private(),
|
template_public_49.to_string_with_secret(),
|
||||||
template_public_49.as_string()
|
template_public_49.to_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_public_84.as_string_private(),
|
template_public_84.to_string_with_secret(),
|
||||||
template_public_84.as_string()
|
template_public_84.to_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_public_86.as_string_private(),
|
template_public_86.to_string_with_secret(),
|
||||||
template_public_86.as_string()
|
template_public_86.to_string()
|
||||||
);
|
);
|
||||||
// when using as_string on a private key, we get the same result as when using it on a public key
|
// when using to_string on a private key, we get the same result as when using it on a public key
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_private_44.as_string(),
|
template_private_44.to_string(),
|
||||||
template_public_44.as_string()
|
template_public_44.to_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_private_49.as_string(),
|
template_private_49.to_string(),
|
||||||
template_public_49.as_string()
|
template_public_49.to_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_private_84.as_string(),
|
template_private_84.to_string(),
|
||||||
template_public_84.as_string()
|
template_public_84.to_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_private_86.as_string(),
|
template_private_86.to_string(),
|
||||||
template_public_86.as_string()
|
template_public_86.to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
101
bdk-ffi/src/electrum.rs
Normal file
101
bdk-ffi/src/electrum.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
use crate::bitcoin::Transaction;
|
||||||
|
use crate::error::ElectrumError;
|
||||||
|
use crate::types::{FullScanRequest, SyncRequest};
|
||||||
|
use crate::wallet::Update;
|
||||||
|
|
||||||
|
use bdk_electrum::BdkElectrumClient as BdkBdkElectrumClient;
|
||||||
|
use bdk_electrum::{ElectrumFullScanResult, ElectrumSyncResult};
|
||||||
|
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
|
||||||
|
use bdk_wallet::chain::spk_client::FullScanRequest as BdkFullScanRequest;
|
||||||
|
use bdk_wallet::chain::spk_client::FullScanResult as BdkFullScanResult;
|
||||||
|
use bdk_wallet::chain::spk_client::SyncRequest as BdkSyncRequest;
|
||||||
|
use bdk_wallet::chain::spk_client::SyncResult as BdkSyncResult;
|
||||||
|
use bdk_wallet::wallet::Update as BdkUpdate;
|
||||||
|
use bdk_wallet::KeychainKind;
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
// NOTE: We are keeping our naming convention where the alias of the inner type is the Rust type
|
||||||
|
// prefixed with `Bdk`. In this case the inner type is `BdkElectrumClient`, so the alias is
|
||||||
|
// funnily enough named `BdkBdkElectrumClient`.
|
||||||
|
pub struct ElectrumClient(BdkBdkElectrumClient<bdk_electrum::electrum_client::Client>);
|
||||||
|
|
||||||
|
impl ElectrumClient {
|
||||||
|
pub fn new(url: String) -> Result<Self, ElectrumError> {
|
||||||
|
let inner_client: bdk_electrum::electrum_client::Client =
|
||||||
|
bdk_electrum::electrum_client::Client::new(url.as_str())?;
|
||||||
|
let client = BdkBdkElectrumClient::new(inner_client);
|
||||||
|
Ok(Self(client))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn full_scan(
|
||||||
|
&self,
|
||||||
|
request: Arc<FullScanRequest>,
|
||||||
|
stop_gap: u64,
|
||||||
|
batch_size: u64,
|
||||||
|
fetch_prev_txouts: bool,
|
||||||
|
) -> Result<Arc<Update>, ElectrumError> {
|
||||||
|
// using option and take is not ideal but the only way to take full ownership of the request
|
||||||
|
let request: BdkFullScanRequest<KeychainKind> = request
|
||||||
|
.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.take()
|
||||||
|
.ok_or(ElectrumError::RequestAlreadyConsumed)?;
|
||||||
|
|
||||||
|
let electrum_result: ElectrumFullScanResult<KeychainKind> = self.0.full_scan(
|
||||||
|
request,
|
||||||
|
stop_gap as usize,
|
||||||
|
batch_size as usize,
|
||||||
|
fetch_prev_txouts,
|
||||||
|
)?;
|
||||||
|
let full_scan_result: BdkFullScanResult<KeychainKind> =
|
||||||
|
electrum_result.with_confirmation_time_height_anchor(&self.0)?;
|
||||||
|
|
||||||
|
let update = BdkUpdate {
|
||||||
|
last_active_indices: full_scan_result.last_active_indices,
|
||||||
|
graph: full_scan_result.graph_update,
|
||||||
|
chain: Some(full_scan_result.chain_update),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Arc::new(Update(update)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync(
|
||||||
|
&self,
|
||||||
|
request: Arc<SyncRequest>,
|
||||||
|
batch_size: u64,
|
||||||
|
fetch_prev_txouts: bool,
|
||||||
|
) -> Result<Arc<Update>, ElectrumError> {
|
||||||
|
// using option and take is not ideal but the only way to take full ownership of the request
|
||||||
|
let request: BdkSyncRequest = request
|
||||||
|
.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.take()
|
||||||
|
.ok_or(ElectrumError::RequestAlreadyConsumed)?;
|
||||||
|
|
||||||
|
let electrum_result: ElectrumSyncResult =
|
||||||
|
self.0
|
||||||
|
.sync(request, batch_size as usize, fetch_prev_txouts)?;
|
||||||
|
let sync_result: BdkSyncResult =
|
||||||
|
electrum_result.with_confirmation_time_height_anchor(&self.0)?;
|
||||||
|
|
||||||
|
let update = BdkUpdate {
|
||||||
|
last_active_indices: BTreeMap::default(),
|
||||||
|
graph: sync_result.graph_update,
|
||||||
|
chain: Some(sync_result.chain_update),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Arc::new(Update(update)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn broadcast(&self, transaction: &Transaction) -> Result<String, ElectrumError> {
|
||||||
|
let bdk_transaction: BdkTransaction = transaction.into();
|
||||||
|
self.0
|
||||||
|
.transaction_broadcast(&bdk_transaction)
|
||||||
|
.map_err(ElectrumError::from)
|
||||||
|
.map(|txid| txid.to_string())
|
||||||
|
}
|
||||||
|
}
|
1264
bdk-ffi/src/error.rs
1264
bdk-ffi/src/error.rs
File diff suppressed because it is too large
Load Diff
@ -2,17 +2,18 @@ use crate::bitcoin::Transaction;
|
|||||||
use crate::error::EsploraError;
|
use crate::error::EsploraError;
|
||||||
use crate::types::{FullScanRequest, SyncRequest};
|
use crate::types::{FullScanRequest, SyncRequest};
|
||||||
use crate::wallet::Update;
|
use crate::wallet::Update;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use bdk::bitcoin::Transaction as BdkTransaction;
|
|
||||||
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
|
|
||||||
use bdk::chain::spk_client::FullScanResult as BdkFullScanResult;
|
|
||||||
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
|
|
||||||
use bdk::chain::spk_client::SyncResult as BdkSyncResult;
|
|
||||||
use bdk::KeychainKind;
|
|
||||||
use bdk_esplora::esplora_client::{BlockingClient, Builder};
|
use bdk_esplora::esplora_client::{BlockingClient, Builder};
|
||||||
use bdk_esplora::EsploraExt;
|
use bdk_esplora::EsploraExt;
|
||||||
|
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
|
||||||
|
use bdk_wallet::chain::spk_client::FullScanRequest as BdkFullScanRequest;
|
||||||
|
use bdk_wallet::chain::spk_client::FullScanResult as BdkFullScanResult;
|
||||||
|
use bdk_wallet::chain::spk_client::SyncRequest as BdkSyncRequest;
|
||||||
|
use bdk_wallet::chain::spk_client::SyncResult as BdkSyncResult;
|
||||||
|
use bdk_wallet::wallet::Update as BdkUpdate;
|
||||||
|
use bdk_wallet::KeychainKind;
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct EsploraClient(BlockingClient);
|
pub struct EsploraClient(BlockingClient);
|
||||||
@ -41,7 +42,7 @@ impl EsploraClient {
|
|||||||
self.0
|
self.0
|
||||||
.full_scan(request, stop_gap as usize, parallel_requests as usize)?;
|
.full_scan(request, stop_gap as usize, parallel_requests as usize)?;
|
||||||
|
|
||||||
let update = bdk::wallet::Update {
|
let update = BdkUpdate {
|
||||||
last_active_indices: result.last_active_indices,
|
last_active_indices: result.last_active_indices,
|
||||||
graph: result.graph_update,
|
graph: result.graph_update,
|
||||||
chain: Some(result.chain_update),
|
chain: Some(result.chain_update),
|
||||||
@ -65,7 +66,7 @@ impl EsploraClient {
|
|||||||
|
|
||||||
let result: BdkSyncResult = self.0.sync(request, parallel_requests as usize)?;
|
let result: BdkSyncResult = self.0.sync(request, parallel_requests as usize)?;
|
||||||
|
|
||||||
let update = bdk::wallet::Update {
|
let update = BdkUpdate {
|
||||||
last_active_indices: BTreeMap::default(),
|
last_active_indices: BTreeMap::default(),
|
||||||
graph: result.graph_update,
|
graph: result.graph_update,
|
||||||
chain: Some(result.chain_update),
|
chain: Some(result.chain_update),
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
use crate::error::{Bip32Error, Bip39Error, DescriptorKeyError};
|
use crate::error::{Bip32Error, Bip39Error, DescriptorKeyError};
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use bdk::bitcoin::bip32::DerivationPath as BdkDerivationPath;
|
use bdk_wallet::bitcoin::bip32::DerivationPath as BdkDerivationPath;
|
||||||
use bdk::bitcoin::key::Secp256k1;
|
use bdk_wallet::bitcoin::key::Secp256k1;
|
||||||
use bdk::bitcoin::secp256k1::rand;
|
use bdk_wallet::bitcoin::secp256k1::rand;
|
||||||
use bdk::bitcoin::secp256k1::rand::Rng;
|
use bdk_wallet::bitcoin::secp256k1::rand::Rng;
|
||||||
use bdk::bitcoin::Network;
|
use bdk_wallet::bitcoin::Network;
|
||||||
use bdk::keys::bip39::WordCount;
|
use bdk_wallet::keys::bip39::WordCount;
|
||||||
use bdk::keys::bip39::{Language, Mnemonic as BdkMnemonic};
|
use bdk_wallet::keys::bip39::{Language, Mnemonic as BdkMnemonic};
|
||||||
use bdk::keys::{
|
use bdk_wallet::keys::{
|
||||||
DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
|
DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
|
||||||
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
|
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
|
||||||
};
|
};
|
||||||
use bdk::miniscript::descriptor::{DescriptorXKey, Wildcard};
|
use bdk_wallet::miniscript::descriptor::{DescriptorXKey, Wildcard};
|
||||||
use bdk::miniscript::BareCtx;
|
use bdk_wallet::miniscript::BareCtx;
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
pub(crate) struct Mnemonic(pub(crate) BdkMnemonic);
|
pub(crate) struct Mnemonic(BdkMnemonic);
|
||||||
|
|
||||||
impl Mnemonic {
|
impl Mnemonic {
|
||||||
pub(crate) fn new(word_count: WordCount) -> Self {
|
pub(crate) fn new(word_count: WordCount) -> Self {
|
||||||
@ -44,9 +45,11 @@ impl Mnemonic {
|
|||||||
.map(Mnemonic)
|
.map(Mnemonic)
|
||||||
.map_err(Bip39Error::from)
|
.map_err(Bip39Error::from)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn as_string(&self) -> String {
|
impl Display for Mnemonic {
|
||||||
self.0.to_string()
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,10 +227,9 @@ impl DescriptorPublicKey {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::keys::{DerivationPath, DescriptorPublicKey, DescriptorSecretKey, Mnemonic};
|
|
||||||
// use bdk::bitcoin::hashes::hex::ToHex;
|
|
||||||
use crate::error::DescriptorKeyError;
|
use crate::error::DescriptorKeyError;
|
||||||
use bdk::bitcoin::Network;
|
use crate::keys::{DerivationPath, DescriptorPublicKey, DescriptorSecretKey, Mnemonic};
|
||||||
|
use bdk_wallet::bitcoin::Network;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn get_inner() -> DescriptorSecretKey {
|
fn get_inner() -> DescriptorSecretKey {
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
mod bitcoin;
|
mod bitcoin;
|
||||||
mod descriptor;
|
mod descriptor;
|
||||||
|
mod electrum;
|
||||||
mod error;
|
mod error;
|
||||||
mod esplora;
|
mod esplora;
|
||||||
mod keys;
|
mod keys;
|
||||||
|
mod store;
|
||||||
mod types;
|
mod types;
|
||||||
mod wallet;
|
mod wallet;
|
||||||
|
|
||||||
use crate::bitcoin::Address;
|
use crate::bitcoin::Address;
|
||||||
|
use crate::bitcoin::Amount;
|
||||||
use crate::bitcoin::FeeRate;
|
use crate::bitcoin::FeeRate;
|
||||||
use crate::bitcoin::OutPoint;
|
use crate::bitcoin::OutPoint;
|
||||||
use crate::bitcoin::Psbt;
|
use crate::bitcoin::Psbt;
|
||||||
use crate::bitcoin::Script;
|
use crate::bitcoin::Script;
|
||||||
use crate::bitcoin::Transaction;
|
use crate::bitcoin::Transaction;
|
||||||
|
use crate::bitcoin::TxIn;
|
||||||
use crate::bitcoin::TxOut;
|
use crate::bitcoin::TxOut;
|
||||||
use crate::descriptor::Descriptor;
|
use crate::descriptor::Descriptor;
|
||||||
use crate::error::AddressError;
|
use crate::electrum::ElectrumClient;
|
||||||
|
use crate::error::AddressParseError;
|
||||||
use crate::error::Bip32Error;
|
use crate::error::Bip32Error;
|
||||||
use crate::error::Bip39Error;
|
use crate::error::Bip39Error;
|
||||||
use crate::error::CalculateFeeError;
|
use crate::error::CalculateFeeError;
|
||||||
@ -22,12 +27,18 @@ use crate::error::CannotConnectError;
|
|||||||
use crate::error::CreateTxError;
|
use crate::error::CreateTxError;
|
||||||
use crate::error::DescriptorError;
|
use crate::error::DescriptorError;
|
||||||
use crate::error::DescriptorKeyError;
|
use crate::error::DescriptorKeyError;
|
||||||
|
use crate::error::ElectrumError;
|
||||||
use crate::error::EsploraError;
|
use crate::error::EsploraError;
|
||||||
use crate::error::ExtractTxError;
|
use crate::error::ExtractTxError;
|
||||||
use crate::error::FeeRateError;
|
use crate::error::FeeRateError;
|
||||||
|
use crate::error::FromScriptError;
|
||||||
|
use crate::error::InspectError;
|
||||||
|
use crate::error::ParseAmountError;
|
||||||
use crate::error::PersistenceError;
|
use crate::error::PersistenceError;
|
||||||
|
use crate::error::PsbtError;
|
||||||
use crate::error::PsbtParseError;
|
use crate::error::PsbtParseError;
|
||||||
use crate::error::SignerError;
|
use crate::error::SignerError;
|
||||||
|
use crate::error::SqliteError;
|
||||||
use crate::error::TransactionError;
|
use crate::error::TransactionError;
|
||||||
use crate::error::TxidParseError;
|
use crate::error::TxidParseError;
|
||||||
use crate::error::WalletCreationError;
|
use crate::error::WalletCreationError;
|
||||||
@ -36,23 +47,27 @@ use crate::keys::DerivationPath;
|
|||||||
use crate::keys::DescriptorPublicKey;
|
use crate::keys::DescriptorPublicKey;
|
||||||
use crate::keys::DescriptorSecretKey;
|
use crate::keys::DescriptorSecretKey;
|
||||||
use crate::keys::Mnemonic;
|
use crate::keys::Mnemonic;
|
||||||
|
use crate::store::SqliteStore;
|
||||||
use crate::types::AddressInfo;
|
use crate::types::AddressInfo;
|
||||||
use crate::types::Balance;
|
use crate::types::Balance;
|
||||||
use crate::types::CanonicalTx;
|
use crate::types::CanonicalTx;
|
||||||
use crate::types::ChainPosition;
|
use crate::types::ChainPosition;
|
||||||
|
use crate::types::ChangeSet;
|
||||||
use crate::types::FullScanRequest;
|
use crate::types::FullScanRequest;
|
||||||
|
use crate::types::FullScanScriptInspector;
|
||||||
use crate::types::LocalOutput;
|
use crate::types::LocalOutput;
|
||||||
use crate::types::ScriptAmount;
|
use crate::types::ScriptAmount;
|
||||||
use crate::types::SyncRequest;
|
use crate::types::SyncRequest;
|
||||||
|
use crate::types::SyncScriptInspector;
|
||||||
use crate::wallet::BumpFeeTxBuilder;
|
use crate::wallet::BumpFeeTxBuilder;
|
||||||
use crate::wallet::SentAndReceivedValues;
|
use crate::wallet::SentAndReceivedValues;
|
||||||
use crate::wallet::TxBuilder;
|
use crate::wallet::TxBuilder;
|
||||||
use crate::wallet::Update;
|
use crate::wallet::Update;
|
||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
use bdk::bitcoin::Network;
|
use bdk_wallet::bitcoin::Network;
|
||||||
use bdk::keys::bip39::WordCount;
|
use bdk_wallet::keys::bip39::WordCount;
|
||||||
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
use bdk_wallet::wallet::tx_builder::ChangeSpendPolicy;
|
||||||
use bdk::KeychainKind;
|
use bdk_wallet::KeychainKind;
|
||||||
|
|
||||||
uniffi::include_scaffolding!("bdk");
|
uniffi::include_scaffolding!("bdk");
|
||||||
|
39
bdk-ffi/src/store.rs
Normal file
39
bdk-ffi/src/store.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use crate::error::SqliteError;
|
||||||
|
use crate::types::ChangeSet;
|
||||||
|
|
||||||
|
use bdk_sqlite::rusqlite::Connection;
|
||||||
|
use bdk_sqlite::{Store as BdkSqliteStore, Store};
|
||||||
|
use bdk_wallet::chain::ConfirmationTimeHeightAnchor;
|
||||||
|
use bdk_wallet::KeychainKind;
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
|
pub struct SqliteStore(Mutex<BdkSqliteStore<KeychainKind, ConfirmationTimeHeightAnchor>>);
|
||||||
|
|
||||||
|
impl SqliteStore {
|
||||||
|
pub fn new(path: String) -> Result<Self, SqliteError> {
|
||||||
|
let connection = Connection::open(path)?;
|
||||||
|
let db = Store::new(connection)?;
|
||||||
|
Ok(Self(Mutex::new(db)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_store(
|
||||||
|
&self,
|
||||||
|
) -> MutexGuard<BdkSqliteStore<KeychainKind, ConfirmationTimeHeightAnchor>> {
|
||||||
|
self.0.lock().expect("sqlite store")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self, changeset: &ChangeSet) -> Result<(), SqliteError> {
|
||||||
|
self.get_store()
|
||||||
|
.write(&changeset.0)
|
||||||
|
.map_err(SqliteError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self) -> Result<Option<Arc<ChangeSet>>, SqliteError> {
|
||||||
|
self.get_store()
|
||||||
|
.read()
|
||||||
|
.map_err(SqliteError::from)
|
||||||
|
.map(|optional_bdk_change_set| optional_bdk_change_set.map(ChangeSet::from))
|
||||||
|
.map(|optional_change_set| optional_change_set.map(Arc::new))
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,19 @@
|
|||||||
|
use crate::bitcoin::Amount;
|
||||||
use crate::bitcoin::{Address, OutPoint, Script, Transaction, TxOut};
|
use crate::bitcoin::{Address, OutPoint, Script, Transaction, TxOut};
|
||||||
|
use crate::InspectError;
|
||||||
|
|
||||||
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
|
use bdk_wallet::bitcoin::ScriptBuf as BdkScriptBuf;
|
||||||
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
|
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
|
||||||
use bdk::chain::tx_graph::CanonicalTx as BdkCanonicalTx;
|
use bdk_wallet::chain::spk_client::FullScanRequest as BdkFullScanRequest;
|
||||||
use bdk::chain::{ChainPosition as BdkChainPosition, ConfirmationTimeHeightAnchor};
|
use bdk_wallet::chain::spk_client::SyncRequest as BdkSyncRequest;
|
||||||
use bdk::wallet::AddressInfo as BdkAddressInfo;
|
use bdk_wallet::chain::tx_graph::CanonicalTx as BdkCanonicalTx;
|
||||||
use bdk::wallet::Balance as BdkBalance;
|
use bdk_wallet::chain::{ChainPosition as BdkChainPosition, ConfirmationTimeHeightAnchor};
|
||||||
use bdk::KeychainKind;
|
use bdk_wallet::wallet::AddressInfo as BdkAddressInfo;
|
||||||
use bdk::LocalOutput as BdkLocalOutput;
|
use bdk_wallet::wallet::Balance as BdkBalance;
|
||||||
|
use bdk_wallet::KeychainKind;
|
||||||
|
use bdk_wallet::LocalOutput as BdkLocalOutput;
|
||||||
|
|
||||||
|
use bdk_electrum::bdk_chain::CombinedChangeSet;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@ -22,12 +27,8 @@ pub struct CanonicalTx {
|
|||||||
pub chain_position: ChainPosition,
|
pub chain_position: ChainPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BdkCanonicalTx<'_, Arc<bdk::bitcoin::Transaction>, ConfirmationTimeHeightAnchor>>
|
impl From<BdkCanonicalTx<'_, Arc<BdkTransaction>, ConfirmationTimeHeightAnchor>> for CanonicalTx {
|
||||||
for CanonicalTx
|
fn from(tx: BdkCanonicalTx<'_, Arc<BdkTransaction>, ConfirmationTimeHeightAnchor>) -> Self {
|
||||||
{
|
|
||||||
fn from(
|
|
||||||
tx: BdkCanonicalTx<'_, Arc<bdk::bitcoin::Transaction>, ConfirmationTimeHeightAnchor>,
|
|
||||||
) -> Self {
|
|
||||||
let chain_position = match tx.chain_position {
|
let chain_position = match tx.chain_position {
|
||||||
BdkChainPosition::Confirmed(anchor) => ChainPosition::Confirmed {
|
BdkChainPosition::Confirmed(anchor) => ChainPosition::Confirmed {
|
||||||
height: anchor.confirmation_height,
|
height: anchor.confirmation_height,
|
||||||
@ -45,7 +46,7 @@ impl From<BdkCanonicalTx<'_, Arc<bdk::bitcoin::Transaction>, ConfirmationTimeHei
|
|||||||
|
|
||||||
pub struct ScriptAmount {
|
pub struct ScriptAmount {
|
||||||
pub script: Arc<Script>,
|
pub script: Arc<Script>,
|
||||||
pub amount: u64,
|
pub amount: Arc<Amount>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AddressInfo {
|
pub struct AddressInfo {
|
||||||
@ -65,27 +66,35 @@ impl From<BdkAddressInfo> for AddressInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Balance {
|
pub struct Balance {
|
||||||
pub immature: u64,
|
pub immature: Arc<Amount>,
|
||||||
pub trusted_pending: u64,
|
pub trusted_pending: Arc<Amount>,
|
||||||
pub untrusted_pending: u64,
|
pub untrusted_pending: Arc<Amount>,
|
||||||
pub confirmed: u64,
|
pub confirmed: Arc<Amount>,
|
||||||
pub trusted_spendable: u64,
|
pub trusted_spendable: Arc<Amount>,
|
||||||
pub total: u64,
|
pub total: Arc<Amount>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BdkBalance> for Balance {
|
impl From<BdkBalance> for Balance {
|
||||||
fn from(bdk_balance: BdkBalance) -> Self {
|
fn from(bdk_balance: BdkBalance) -> Self {
|
||||||
Balance {
|
Balance {
|
||||||
immature: bdk_balance.immature,
|
immature: Arc::new(bdk_balance.immature.into()),
|
||||||
trusted_pending: bdk_balance.trusted_pending,
|
trusted_pending: Arc::new(bdk_balance.trusted_pending.into()),
|
||||||
untrusted_pending: bdk_balance.untrusted_pending,
|
untrusted_pending: Arc::new(bdk_balance.untrusted_pending.into()),
|
||||||
confirmed: bdk_balance.confirmed,
|
confirmed: Arc::new(bdk_balance.confirmed.into()),
|
||||||
trusted_spendable: bdk_balance.trusted_spendable(),
|
trusted_spendable: Arc::new(bdk_balance.trusted_spendable().into()),
|
||||||
total: bdk_balance.total(),
|
total: Arc::new(bdk_balance.total().into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ChangeSet(pub(crate) CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>);
|
||||||
|
|
||||||
|
impl From<CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>> for ChangeSet {
|
||||||
|
fn from(change_set: CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>) -> Self {
|
||||||
|
ChangeSet(change_set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct LocalOutput {
|
pub struct LocalOutput {
|
||||||
pub outpoint: OutPoint,
|
pub outpoint: OutPoint,
|
||||||
pub txout: TxOut,
|
pub txout: TxOut,
|
||||||
@ -110,6 +119,55 @@ impl From<BdkLocalOutput> for LocalOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Callback for the FullScanRequest
|
||||||
|
pub trait FullScanScriptInspector: Sync + Send {
|
||||||
|
fn inspect(&self, keychain: KeychainKind, index: u32, script: Arc<Script>);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback for the SyncRequest
|
||||||
|
pub trait SyncScriptInspector: Sync + Send {
|
||||||
|
fn inspect(&self, script: Arc<Script>, total: u64);
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FullScanRequest(pub(crate) Mutex<Option<BdkFullScanRequest<KeychainKind>>>);
|
pub struct FullScanRequest(pub(crate) Mutex<Option<BdkFullScanRequest<KeychainKind>>>);
|
||||||
|
|
||||||
pub struct SyncRequest(pub(crate) Mutex<Option<BdkSyncRequest>>);
|
pub struct SyncRequest(pub(crate) Mutex<Option<BdkSyncRequest>>);
|
||||||
|
|
||||||
|
impl SyncRequest {
|
||||||
|
pub fn inspect_spks(
|
||||||
|
&self,
|
||||||
|
inspector: Arc<dyn SyncScriptInspector>,
|
||||||
|
) -> Result<Arc<Self>, InspectError> {
|
||||||
|
let mut guard = self.0.lock().unwrap();
|
||||||
|
if let Some(sync_request) = guard.take() {
|
||||||
|
let total = sync_request.spks.len() as u64;
|
||||||
|
let sync_request = sync_request.inspect_spks(move |spk| {
|
||||||
|
inspector.inspect(Arc::new(BdkScriptBuf::from(spk).into()), total)
|
||||||
|
});
|
||||||
|
Ok(Arc::new(SyncRequest(Mutex::new(Some(sync_request)))))
|
||||||
|
} else {
|
||||||
|
Err(InspectError::RequestAlreadyConsumed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FullScanRequest {
|
||||||
|
pub fn inspect_spks_for_all_keychains(
|
||||||
|
&self,
|
||||||
|
inspector: Arc<dyn FullScanScriptInspector>,
|
||||||
|
) -> Result<Arc<Self>, InspectError> {
|
||||||
|
let mut guard = self.0.lock().unwrap();
|
||||||
|
if let Some(full_scan_request) = guard.take() {
|
||||||
|
let inspector = Arc::new(inspector);
|
||||||
|
let full_scan_request =
|
||||||
|
full_scan_request.inspect_spks_for_all_keychains(move |k, spk_i, script| {
|
||||||
|
inspector.inspect(k, spk_i, Arc::new(BdkScriptBuf::from(script).into()))
|
||||||
|
});
|
||||||
|
Ok(Arc::new(FullScanRequest(Mutex::new(Some(
|
||||||
|
full_scan_request,
|
||||||
|
)))))
|
||||||
|
} else {
|
||||||
|
Err(InspectError::RequestAlreadyConsumed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,29 +1,30 @@
|
|||||||
|
use crate::bitcoin::Amount;
|
||||||
use crate::bitcoin::{FeeRate, OutPoint, Psbt, Script, Transaction};
|
use crate::bitcoin::{FeeRate, OutPoint, Psbt, Script, Transaction};
|
||||||
use crate::descriptor::Descriptor;
|
use crate::descriptor::Descriptor;
|
||||||
use crate::error::{
|
use crate::error::{
|
||||||
CalculateFeeError, CannotConnectError, CreateTxError, PersistenceError, SignerError,
|
CalculateFeeError, CannotConnectError, CreateTxError, SignerError, TxidParseError,
|
||||||
TxidParseError, WalletCreationError,
|
WalletCreationError,
|
||||||
};
|
};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
AddressInfo, Balance, CanonicalTx, FullScanRequest, LocalOutput, ScriptAmount, SyncRequest,
|
AddressInfo, Balance, CanonicalTx, ChangeSet, FullScanRequest, LocalOutput, ScriptAmount,
|
||||||
|
SyncRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
|
use bdk_wallet::bitcoin::amount::Amount as BdkAmount;
|
||||||
use bdk::bitcoin::Network;
|
use bdk_wallet::bitcoin::Network;
|
||||||
use bdk::bitcoin::Psbt as BdkPsbt;
|
use bdk_wallet::bitcoin::Psbt as BdkPsbt;
|
||||||
use bdk::bitcoin::{OutPoint as BdkOutPoint, Sequence, Txid};
|
use bdk_wallet::bitcoin::ScriptBuf as BdkScriptBuf;
|
||||||
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
use bdk_wallet::bitcoin::{OutPoint as BdkOutPoint, Sequence, Txid};
|
||||||
use bdk::wallet::{ChangeSet, Update as BdkUpdate};
|
use bdk_wallet::chain::{CombinedChangeSet, ConfirmationTimeHeightAnchor};
|
||||||
use bdk::Wallet as BdkWallet;
|
use bdk_wallet::wallet::tx_builder::ChangeSpendPolicy;
|
||||||
use bdk::{KeychainKind, SignOptions};
|
use bdk_wallet::wallet::Update as BdkUpdate;
|
||||||
use bdk_file_store::Store;
|
use bdk_wallet::Wallet as BdkWallet;
|
||||||
|
use bdk_wallet::{KeychainKind, SignOptions};
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
const MAGIC_BYTES: &[u8] = "bdkffi".as_bytes();
|
|
||||||
|
|
||||||
pub struct Wallet {
|
pub struct Wallet {
|
||||||
inner_mutex: Mutex<BdkWallet>,
|
inner_mutex: Mutex<BdkWallet>,
|
||||||
}
|
}
|
||||||
@ -31,16 +32,30 @@ pub struct Wallet {
|
|||||||
impl Wallet {
|
impl Wallet {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
descriptor: Arc<Descriptor>,
|
descriptor: Arc<Descriptor>,
|
||||||
change_descriptor: Option<Arc<Descriptor>>,
|
change_descriptor: Arc<Descriptor>,
|
||||||
persistence_backend_path: String,
|
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<Self, WalletCreationError> {
|
) -> Result<Self, WalletCreationError> {
|
||||||
let descriptor = descriptor.as_string_private();
|
let descriptor = descriptor.to_string_with_secret();
|
||||||
let change_descriptor = change_descriptor.map(|d| d.as_string_private());
|
let change_descriptor = change_descriptor.to_string_with_secret();
|
||||||
let db = Store::<ChangeSet>::open_or_create_new(MAGIC_BYTES, persistence_backend_path)?;
|
let wallet: BdkWallet = BdkWallet::new(&descriptor, &change_descriptor, network)?;
|
||||||
|
|
||||||
|
Ok(Wallet {
|
||||||
|
inner_mutex: Mutex::new(wallet),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_or_load(
|
||||||
|
descriptor: Arc<Descriptor>,
|
||||||
|
change_descriptor: Arc<Descriptor>,
|
||||||
|
change_set: Option<Arc<ChangeSet>>,
|
||||||
|
network: Network,
|
||||||
|
) -> Result<Self, WalletCreationError> {
|
||||||
|
let descriptor = descriptor.to_string_with_secret();
|
||||||
|
let change_descriptor = change_descriptor.to_string_with_secret();
|
||||||
|
let change_set: Option<CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>> =
|
||||||
|
change_set.map(|cs| cs.0.clone());
|
||||||
let wallet: BdkWallet =
|
let wallet: BdkWallet =
|
||||||
BdkWallet::new_or_load(&descriptor, change_descriptor.as_ref(), db, network)?;
|
BdkWallet::new_or_load(&descriptor, &change_descriptor, change_set, network)?;
|
||||||
|
|
||||||
Ok(Wallet {
|
Ok(Wallet {
|
||||||
inner_mutex: Mutex::new(wallet),
|
inner_mutex: Mutex::new(wallet),
|
||||||
@ -51,16 +66,8 @@ impl Wallet {
|
|||||||
self.inner_mutex.lock().expect("wallet")
|
self.inner_mutex.lock().expect("wallet")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reveal_next_address(
|
pub fn reveal_next_address(&self, keychain_kind: KeychainKind) -> AddressInfo {
|
||||||
&self,
|
self.get_wallet().reveal_next_address(keychain_kind).into()
|
||||||
keychain_kind: KeychainKind,
|
|
||||||
) -> Result<AddressInfo, PersistenceError> {
|
|
||||||
self.get_wallet()
|
|
||||||
.reveal_next_address(keychain_kind)
|
|
||||||
.map(|address_info| address_info.into())
|
|
||||||
.map_err(|e| PersistenceError::Write {
|
|
||||||
error_message: e.to_string(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_update(&self, update: Arc<Update>) -> Result<(), CannotConnectError> {
|
pub fn apply_update(&self, update: Arc<Update>) -> Result<(), CannotConnectError> {
|
||||||
@ -69,20 +76,12 @@ impl Wallet {
|
|||||||
.map_err(CannotConnectError::from)
|
.map_err(CannotConnectError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit(&self) -> Result<bool, PersistenceError> {
|
|
||||||
self.get_wallet()
|
|
||||||
.commit()
|
|
||||||
.map_err(|e| PersistenceError::Write {
|
|
||||||
error_message: e.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn network(&self) -> Network {
|
pub fn network(&self) -> Network {
|
||||||
self.get_wallet().network()
|
self.get_wallet().network()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_balance(&self) -> Balance {
|
pub fn balance(&self) -> Balance {
|
||||||
let bdk_balance: bdk::wallet::Balance = self.get_wallet().get_balance();
|
let bdk_balance = self.get_wallet().balance();
|
||||||
Balance::from(bdk_balance)
|
Balance::from(bdk_balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,8 +101,11 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn sent_and_received(&self, tx: &Transaction) -> SentAndReceivedValues {
|
pub fn sent_and_received(&self, tx: &Transaction) -> SentAndReceivedValues {
|
||||||
let (sent, received): (u64, u64) = self.get_wallet().sent_and_received(&tx.into());
|
let (sent, received) = self.get_wallet().sent_and_received(&tx.into());
|
||||||
SentAndReceivedValues { sent, received }
|
SentAndReceivedValues {
|
||||||
|
sent: Arc::new(sent.into()),
|
||||||
|
received: Arc::new(received.into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transactions(&self) -> Vec<CanonicalTx> {
|
pub fn transactions(&self) -> Vec<CanonicalTx> {
|
||||||
@ -119,9 +121,11 @@ impl Wallet {
|
|||||||
Ok(self.get_wallet().get_tx(txid).map(|tx| tx.into()))
|
Ok(self.get_wallet().get_tx(txid).map(|tx| tx.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
|
pub fn calculate_fee(&self, tx: &Transaction) -> Result<Arc<Amount>, CalculateFeeError> {
|
||||||
self.get_wallet()
|
self.get_wallet()
|
||||||
.calculate_fee(&tx.into())
|
.calculate_fee(&tx.into())
|
||||||
|
.map(Amount::from)
|
||||||
|
.map(Arc::new)
|
||||||
.map_err(|e| e.into())
|
.map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,24 +153,30 @@ impl Wallet {
|
|||||||
let request = self.get_wallet().start_sync_with_revealed_spks();
|
let request = self.get_wallet().start_sync_with_revealed_spks();
|
||||||
Arc::new(SyncRequest(Mutex::new(Some(request))))
|
Arc::new(SyncRequest(Mutex::new(Some(request))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn take_staged(&self) -> Option<Arc<ChangeSet>> {
|
||||||
|
self.get_wallet()
|
||||||
|
.take_staged()
|
||||||
|
.map(|change_set| Arc::new(change_set.into()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SentAndReceivedValues {
|
pub struct SentAndReceivedValues {
|
||||||
pub sent: u64,
|
pub sent: Arc<Amount>,
|
||||||
pub received: u64,
|
pub received: Arc<Amount>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Update(pub(crate) BdkUpdate);
|
pub struct Update(pub(crate) BdkUpdate);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TxBuilder {
|
pub struct TxBuilder {
|
||||||
pub(crate) recipients: Vec<(BdkScriptBuf, u64)>,
|
pub(crate) recipients: Vec<(BdkScriptBuf, BdkAmount)>,
|
||||||
pub(crate) utxos: Vec<OutPoint>,
|
pub(crate) utxos: Vec<OutPoint>,
|
||||||
pub(crate) unspendable: HashSet<OutPoint>,
|
pub(crate) unspendable: HashSet<OutPoint>,
|
||||||
pub(crate) change_policy: ChangeSpendPolicy,
|
pub(crate) change_policy: ChangeSpendPolicy,
|
||||||
pub(crate) manually_selected_only: bool,
|
pub(crate) manually_selected_only: bool,
|
||||||
pub(crate) fee_rate: Option<FeeRate>,
|
pub(crate) fee_rate: Option<FeeRate>,
|
||||||
pub(crate) fee_absolute: Option<u64>,
|
pub(crate) fee_absolute: Option<Arc<Amount>>,
|
||||||
pub(crate) drain_wallet: bool,
|
pub(crate) drain_wallet: bool,
|
||||||
pub(crate) drain_to: Option<BdkScriptBuf>,
|
pub(crate) drain_to: Option<BdkScriptBuf>,
|
||||||
pub(crate) rbf: Option<RbfValue>,
|
pub(crate) rbf: Option<RbfValue>,
|
||||||
@ -190,9 +200,9 @@ impl TxBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_recipient(&self, script: &Script, amount: u64) -> Arc<Self> {
|
pub(crate) fn add_recipient(&self, script: &Script, amount: Arc<Amount>) -> Arc<Self> {
|
||||||
let mut recipients: Vec<(BdkScriptBuf, u64)> = self.recipients.clone();
|
let mut recipients: Vec<(BdkScriptBuf, BdkAmount)> = self.recipients.clone();
|
||||||
recipients.append(&mut vec![(script.0.clone(), amount)]);
|
recipients.append(&mut vec![(script.0.clone(), amount.0)]);
|
||||||
|
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
recipients,
|
recipients,
|
||||||
@ -203,7 +213,7 @@ impl TxBuilder {
|
|||||||
pub(crate) fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
|
pub(crate) fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
|
||||||
let recipients = recipients
|
let recipients = recipients
|
||||||
.iter()
|
.iter()
|
||||||
.map(|script_amount| (script_amount.script.0.clone(), script_amount.amount))
|
.map(|script_amount| (script_amount.script.0.clone(), script_amount.amount.0)) //;
|
||||||
.collect();
|
.collect();
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
recipients,
|
recipients,
|
||||||
@ -275,7 +285,7 @@ impl TxBuilder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fee_absolute(&self, fee_amount: u64) -> Arc<Self> {
|
pub(crate) fn fee_absolute(&self, fee_amount: Arc<Amount>) -> Arc<Self> {
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
fee_absolute: Some(fee_amount),
|
fee_absolute: Some(fee_amount),
|
||||||
..self.clone()
|
..self.clone()
|
||||||
@ -335,8 +345,8 @@ impl TxBuilder {
|
|||||||
if let Some(fee_rate) = &self.fee_rate {
|
if let Some(fee_rate) = &self.fee_rate {
|
||||||
tx_builder.fee_rate(fee_rate.0);
|
tx_builder.fee_rate(fee_rate.0);
|
||||||
}
|
}
|
||||||
if let Some(fee_amount) = self.fee_absolute {
|
if let Some(fee_amount) = &self.fee_absolute {
|
||||||
tx_builder.fee_absolute(fee_amount);
|
tx_builder.fee_absolute(fee_amount.0);
|
||||||
}
|
}
|
||||||
if self.drain_wallet {
|
if self.drain_wallet {
|
||||||
tx_builder.drain_wallet();
|
tx_builder.drain_wallet();
|
||||||
|
@ -4,6 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import bdk
|
import BitcoinDevKit
|
||||||
|
|
||||||
let network = Network.testnet
|
let network = Network.testnet
|
||||||
|
@ -5,8 +5,6 @@ cdylib_name = "bdkffi"
|
|||||||
[bindings.python]
|
[bindings.python]
|
||||||
cdylib_name = "bdkffi"
|
cdylib_name = "bdkffi"
|
||||||
|
|
||||||
[bindings.ruby]
|
|
||||||
cdylib_name = "bdkffi"
|
|
||||||
|
|
||||||
[bindings.swift]
|
[bindings.swift]
|
||||||
|
module_name = "BitcoinDevKit"
|
||||||
cdylib_name = "bdkffi"
|
cdylib_name = "bdkffi"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
org.gradle.jvmargs=-Xmx1536m
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
libraryVersion=1.0.0-alpha.10-SNAPSHOT
|
libraryVersion=1.0.0-alpha.12-SNAPSHOT
|
||||||
|
@ -24,13 +24,16 @@ java {
|
|||||||
|
|
||||||
// This block ensures that the tests that require access to a blockchain are not
|
// This block ensures that the tests that require access to a blockchain are not
|
||||||
// run if the -P excludeConnectedTests flag is passed to gradle.
|
// run if the -P excludeConnectedTests flag is passed to gradle.
|
||||||
// This ensures our CI runs are not fickle by not requiring access to testnet.
|
// This ensures our CI runs are not fickle by not requiring access to testnet or signet.
|
||||||
// This is a workaround until we have a proper regtest setup for the CI.
|
// This is a workaround until we have a proper regtest setup for the CI.
|
||||||
// Note that the command in the CI is ./gradlew test -P excludeConnectedTests
|
// Note that the command in the CI is ./gradlew test -P excludeConnectedTests
|
||||||
tasks.test {
|
tasks.test {
|
||||||
if (project.hasProperty("excludeConnectedTests")) {
|
if (project.hasProperty("excludeConnectedTests")) {
|
||||||
exclude("**/LiveWalletTest.class")
|
exclude("**/LiveElectrumClientTest.class")
|
||||||
|
exclude("**/LiveMemoryWalletTest.class")
|
||||||
|
exclude("**/LiveTransactionTest.class")
|
||||||
exclude("**/LiveTxBuilderTest.class")
|
exclude("**/LiveTxBuilderTest.class")
|
||||||
|
exclude("**/LiveWalletTest.class")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package org.bitcoindevkit
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
private const val SIGNET_ELECTRUM_URL = "ssl://mempool.space:60602"
|
||||||
|
|
||||||
|
class LiveElectrumClientTest {
|
||||||
|
private val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
private val changeDescriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSyncedBalance() {
|
||||||
|
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
|
val electrumClient: ElectrumClient = ElectrumClient(SIGNET_ELECTRUM_URL)
|
||||||
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
|
val update = electrumClient.fullScan(fullScanRequest, 10uL, 10uL, false)
|
||||||
|
wallet.applyUpdate(update)
|
||||||
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
|
|
||||||
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Transactions count: ${wallet.transactions().count()}")
|
||||||
|
val transactions = wallet.transactions().take(3)
|
||||||
|
for (tx in transactions) {
|
||||||
|
val sentAndReceived = wallet.sentAndReceived(tx.transaction)
|
||||||
|
println("Transaction: ${tx.transaction.computeTxid()}")
|
||||||
|
println("Sent ${sentAndReceived.sent.toSat()}")
|
||||||
|
println("Received ${sentAndReceived.received.toSat()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package org.bitcoindevkit
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||||
|
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||||
|
|
||||||
|
class LiveMemoryWalletTest {
|
||||||
|
private val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
private val changeDescriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSyncedBalance() {
|
||||||
|
val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
|
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
|
wallet.applyUpdate(update)
|
||||||
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
|
|
||||||
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Transactions count: ${wallet.transactions().count()}")
|
||||||
|
val transactions = wallet.transactions().take(3)
|
||||||
|
for (tx in transactions) {
|
||||||
|
val sentAndReceived = wallet.sentAndReceived(tx.transaction)
|
||||||
|
println("Transaction: ${tx.transaction.computeTxid()}")
|
||||||
|
println("Sent ${sentAndReceived.sent.toSat()}")
|
||||||
|
println("Received ${sentAndReceived.received.toSat()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testScriptInspector() {
|
||||||
|
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
|
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
|
|
||||||
|
val scriptInspector: FullScriptInspector = FullScriptInspector()
|
||||||
|
val fullScanRequest: FullScanRequest = wallet.startFullScan().inspectSpksForAllKeychains(scriptInspector)
|
||||||
|
val update = esploraClient.fullScan(fullScanRequest, 21uL, 1uL)
|
||||||
|
|
||||||
|
wallet.applyUpdate(update)
|
||||||
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
|
|
||||||
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FullScriptInspector: FullScanScriptInspector {
|
||||||
|
override fun inspect(keychain: KeychainKind, index: UInt, script: Script){
|
||||||
|
println("Inspecting index $index for keychain $keychain")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package org.bitcoindevkit
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||||
|
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||||
|
|
||||||
|
class LiveTransactionTests {
|
||||||
|
private val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
private val changeDescriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSyncedBalance() {
|
||||||
|
val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
|
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
|
wallet.applyUpdate(update)
|
||||||
|
println("Wallet balance: ${wallet.balance().total.toSat()}")
|
||||||
|
|
||||||
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
|
val transaction: Transaction = wallet.transactions().first().transaction
|
||||||
|
println("First transaction:")
|
||||||
|
println("Txid: ${transaction.computeTxid()}")
|
||||||
|
println("Version: ${transaction.version()}")
|
||||||
|
println("Total size: ${transaction.totalSize()}")
|
||||||
|
println("Vsize: ${transaction.vsize()}")
|
||||||
|
println("Weight: ${transaction.weight()}")
|
||||||
|
println("Coinbase transaction: ${transaction.isCoinbase()}")
|
||||||
|
println("Is explicitly RBF: ${transaction.isExplicitlyRbf()}")
|
||||||
|
println("Inputs: ${transaction.input()}")
|
||||||
|
println("Outputs: ${transaction.output()}")
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,16 @@ private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
|||||||
class LiveTxBuilderTest {
|
class LiveTxBuilderTest {
|
||||||
private val persistenceFilePath = run {
|
private val persistenceFilePath = run {
|
||||||
val currentDirectory = System.getProperty("user.dir")
|
val currentDirectory = System.getProperty("user.dir")
|
||||||
"$currentDirectory/bdk_persistence.db"
|
"$currentDirectory/bdk_persistence.sqlite"
|
||||||
}
|
}
|
||||||
|
private val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
private val changeDescriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
@ -25,19 +33,20 @@ class LiveTxBuilderTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testTxBuilder() {
|
fun testTxBuilder() {
|
||||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||||
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
|
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||||
val psbt: Psbt = TxBuilder()
|
val psbt: Psbt = TxBuilder()
|
||||||
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
|
||||||
.feeRate(FeeRate.fromSatPerVb(2uL))
|
.feeRate(FeeRate.fromSatPerVb(2uL))
|
||||||
.finish(wallet)
|
.finish(wallet)
|
||||||
|
|
||||||
@ -48,23 +57,22 @@ class LiveTxBuilderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun complexTxBuilder() {
|
fun complexTxBuilder() {
|
||||||
val externalDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.TESTNET)
|
|
||||||
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.SIGNET)
|
|
||||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||||
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.SIGNET)
|
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.SIGNET)
|
||||||
val allRecipients: List<ScriptAmount> = listOf(
|
val allRecipients: List<ScriptAmount> = listOf(
|
||||||
ScriptAmount(recipient1.scriptPubkey(), 4200uL),
|
ScriptAmount(recipient1.scriptPubkey(), Amount.fromSat(4200uL)),
|
||||||
ScriptAmount(recipient2.scriptPubkey(), 4200uL),
|
ScriptAmount(recipient2.scriptPubkey(), Amount.fromSat(4200uL)),
|
||||||
)
|
)
|
||||||
|
|
||||||
val psbt: Psbt = TxBuilder()
|
val psbt: Psbt = TxBuilder()
|
||||||
|
@ -11,8 +11,16 @@ private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
|||||||
class LiveWalletTest {
|
class LiveWalletTest {
|
||||||
private val persistenceFilePath = run {
|
private val persistenceFilePath = run {
|
||||||
val currentDirectory = System.getProperty("user.dir")
|
val currentDirectory = System.getProperty("user.dir")
|
||||||
"$currentDirectory/bdk_persistence.db"
|
"$currentDirectory/bdk_persistence.sqlite"
|
||||||
}
|
}
|
||||||
|
private val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
private val changeDescriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
@ -24,22 +32,22 @@ class LiveWalletTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSyncedBalance() {
|
fun testSyncedBalance() {
|
||||||
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
|
|
||||||
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
println("Transactions count: ${wallet.transactions().count()}")
|
println("Transactions count: ${wallet.transactions().count()}")
|
||||||
val transactions = wallet.transactions().take(3)
|
val transactions = wallet.transactions().take(3)
|
||||||
for (tx in transactions) {
|
for (tx in transactions) {
|
||||||
val sentAndReceived = wallet.sentAndReceived(tx.transaction)
|
val sentAndReceived = wallet.sentAndReceived(tx.transaction)
|
||||||
println("Transaction: ${tx.transaction.txid()}")
|
println("Transaction: ${tx.transaction.computeTxid()}")
|
||||||
println("Sent ${sentAndReceived.sent}")
|
println("Sent ${sentAndReceived.sent}")
|
||||||
println("Received ${sentAndReceived.received}")
|
println("Received ${sentAndReceived.received}")
|
||||||
}
|
}
|
||||||
@ -48,23 +56,21 @@ class LiveWalletTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testBroadcastTransaction() {
|
fun testBroadcastTransaction() {
|
||||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
||||||
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
|
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
println("Balance: ${wallet.balance().total.toSat()}")
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
|
||||||
println("New address: ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()}")
|
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL) {
|
assert(wallet.balance().total.toSat() > 0uL) {
|
||||||
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||||
}
|
}
|
||||||
|
|
||||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||||
|
|
||||||
val psbt: Psbt = TxBuilder()
|
val psbt: Psbt = TxBuilder()
|
||||||
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
|
||||||
.feeRate(FeeRate.fromSatPerVb(2uL))
|
.feeRate(FeeRate.fromSatPerVb(2uL))
|
||||||
.finish(wallet)
|
.finish(wallet)
|
||||||
|
|
||||||
@ -75,9 +81,9 @@ class LiveWalletTest {
|
|||||||
assertTrue(walletDidSign)
|
assertTrue(walletDidSign)
|
||||||
|
|
||||||
val tx: Transaction = psbt.extractTx()
|
val tx: Transaction = psbt.extractTx()
|
||||||
println("Txid is: ${tx.txid()}")
|
println("Txid is: ${tx.computeTxid()}")
|
||||||
|
|
||||||
val txFee: ULong = wallet.calculateFee(tx)
|
val txFee: Amount = wallet.calculateFee(tx)
|
||||||
println("Tx fee is: ${txFee}")
|
println("Tx fee is: ${txFee}")
|
||||||
|
|
||||||
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
|
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
|
||||||
|
@ -12,7 +12,7 @@ class OfflineDescriptorTest {
|
|||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
||||||
actual = descriptor.asString()
|
actual = descriptor.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package org.bitcoindevkit
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class OfflinePersistenceTest {
|
||||||
|
private val persistenceFilePath = run {
|
||||||
|
val currentDirectory = System.getProperty("user.dir")
|
||||||
|
val dbFileName = "pre_existing_wallet_persistence_test.sqlite"
|
||||||
|
"$currentDirectory/src/test/resources/$dbFileName"
|
||||||
|
}
|
||||||
|
private val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
private val changeDescriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPersistence() {
|
||||||
|
val sqliteStore: SqliteStore = SqliteStore(persistenceFilePath)
|
||||||
|
val initialChangeSet: ChangeSet? = sqliteStore.read()
|
||||||
|
requireNotNull(initialChangeSet) { "ChangeSet should not be null after loading a valid database" }
|
||||||
|
|
||||||
|
val wallet: Wallet = Wallet.newOrLoad(
|
||||||
|
descriptor,
|
||||||
|
changeDescriptor,
|
||||||
|
initialChangeSet,
|
||||||
|
Network.SIGNET,
|
||||||
|
)
|
||||||
|
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
||||||
|
println("Address: $addressInfo")
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
expected = 7u,
|
||||||
|
actual = addressInfo.index,
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
expected = "tb1qan3lldunh37ma6c0afeywgjyjgnyc8uz975zl2",
|
||||||
|
actual = addressInfo.address.toString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -10,8 +10,16 @@ import kotlin.test.assertFalse
|
|||||||
class OfflineWalletTest {
|
class OfflineWalletTest {
|
||||||
private val persistenceFilePath = run {
|
private val persistenceFilePath = run {
|
||||||
val currentDirectory = System.getProperty("user.dir")
|
val currentDirectory = System.getProperty("user.dir")
|
||||||
"$currentDirectory/bdk_persistence.db"
|
"$currentDirectory/bdk_persistence.sqlite"
|
||||||
}
|
}
|
||||||
|
private val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.TESTNET
|
||||||
|
)
|
||||||
|
private val changeDescriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
Network.TESTNET
|
||||||
|
)
|
||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
@ -27,19 +35,14 @@ class OfflineWalletTest {
|
|||||||
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
|
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
|
||||||
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
|
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
|
||||||
|
|
||||||
assertTrue(descriptor.asString().startsWith("tr"), "Bip86 Descriptor does not start with 'tr'")
|
assertTrue(descriptor.toString().startsWith("tr"), "Bip86 Descriptor does not start with 'tr'")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNewAddress() {
|
fun testNewAddress() {
|
||||||
val descriptor: Descriptor = Descriptor(
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
|
||||||
Network.TESTNET
|
|
||||||
)
|
|
||||||
val wallet: Wallet = Wallet(
|
val wallet: Wallet = Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
null,
|
changeDescriptor,
|
||||||
persistenceFilePath,
|
|
||||||
Network.TESTNET
|
Network.TESTNET
|
||||||
)
|
)
|
||||||
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
||||||
@ -50,27 +53,22 @@ class OfflineWalletTest {
|
|||||||
assertFalse(addressInfo.address.isValidForNetwork(Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
|
assertFalse(addressInfo.address.isValidForNetwork(Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e",
|
expected = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||||
actual = addressInfo.address.asString()
|
actual = addressInfo.address.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBalance() {
|
fun testBalance() {
|
||||||
val descriptor: Descriptor = Descriptor(
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
|
||||||
Network.TESTNET
|
|
||||||
)
|
|
||||||
val wallet: Wallet = Wallet(
|
val wallet: Wallet = Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
null,
|
changeDescriptor,
|
||||||
persistenceFilePath,
|
|
||||||
Network.TESTNET
|
Network.TESTNET
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = 0uL,
|
expected = 0uL,
|
||||||
actual = wallet.getBalance().total
|
actual = wallet.balance().total.toSat()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -18,7 +18,7 @@ import bdkpython as bdk
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="bdkpython",
|
name="bdkpython",
|
||||||
version="1.0.0a10.dev",
|
version="1.0.0a12.dev",
|
||||||
description="The Python language bindings for the Bitcoin Development Kit",
|
description="The Python language bindings for the Bitcoin Development Kit",
|
||||||
long_description=LONG_DESCRIPTION,
|
long_description=LONG_DESCRIPTION,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
|
@ -5,21 +5,29 @@ import os
|
|||||||
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||||
TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||||
|
|
||||||
|
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
bdk.Network.TESTNET
|
||||||
|
)
|
||||||
|
change_descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
bdk.Network.TESTNET
|
||||||
|
)
|
||||||
|
|
||||||
class LiveTxBuilderTest(unittest.TestCase):
|
class LiveTxBuilderTest(unittest.TestCase):
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
if os.path.exists("./bdk_persistence.db"):
|
if os.path.exists("./bdk_persistence.sqlite"):
|
||||||
os.remove("./bdk_persistence.db")
|
os.remove("./bdk_persistence.sqlite")
|
||||||
|
|
||||||
def test_tx_builder(self):
|
def test_tx_builder(self):
|
||||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
bdk.Network.SIGNET
|
bdk.Network.SIGNET
|
||||||
)
|
)
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
wallet: bdk.Wallet = bdk.Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
None,
|
change_descriptor,
|
||||||
"./bdk_persistence.db",
|
|
||||||
bdk.Network.SIGNET
|
bdk.Network.SIGNET
|
||||||
)
|
)
|
||||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
||||||
@ -30,32 +38,26 @@ class LiveTxBuilderTest(unittest.TestCase):
|
|||||||
parallel_requests=1
|
parallel_requests=1
|
||||||
)
|
)
|
||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
wallet.commit()
|
|
||||||
|
|
||||||
self.assertGreater(wallet.get_balance().total, 0)
|
self.assertGreater(
|
||||||
|
wallet.balance().total.to_sat(),
|
||||||
|
0,
|
||||||
|
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
)
|
||||||
|
|
||||||
recipient = bdk.Address(
|
recipient = bdk.Address(
|
||||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||||
network=bdk.Network.SIGNET
|
network=bdk.Network.SIGNET
|
||||||
)
|
)
|
||||||
|
|
||||||
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
|
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=bdk.Amount.from_sat(4200)).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
|
||||||
|
|
||||||
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
||||||
|
|
||||||
def complex_tx_builder(self):
|
def complex_tx_builder(self):
|
||||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
|
||||||
bdk.Network.SIGNET
|
|
||||||
)
|
|
||||||
change_descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)",
|
|
||||||
bdk.Network.SIGNET
|
|
||||||
)
|
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
wallet: bdk.Wallet = bdk.Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
change_descriptor,
|
change_descriptor,
|
||||||
"./bdk_persistence.db",
|
|
||||||
bdk.Network.SIGNET
|
bdk.Network.SIGNET
|
||||||
)
|
)
|
||||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
||||||
@ -66,9 +68,12 @@ class LiveTxBuilderTest(unittest.TestCase):
|
|||||||
parallel_requests=1
|
parallel_requests=1
|
||||||
)
|
)
|
||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
wallet.commit()
|
|
||||||
|
|
||||||
self.assertGreater(wallet.get_balance().total, 0)
|
self.assertGreater(
|
||||||
|
wallet.balance().total.to_sat(),
|
||||||
|
0,
|
||||||
|
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
)
|
||||||
|
|
||||||
recipient1 = bdk.Address(
|
recipient1 = bdk.Address(
|
||||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||||
|
@ -5,21 +5,25 @@ import os
|
|||||||
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||||
TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||||
|
|
||||||
|
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
bdk.Network.TESTNET
|
||||||
|
)
|
||||||
|
change_descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
bdk.Network.TESTNET
|
||||||
|
)
|
||||||
|
|
||||||
class LiveWalletTest(unittest.TestCase):
|
class LiveWalletTest(unittest.TestCase):
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
if os.path.exists("./bdk_persistence.db"):
|
if os.path.exists("./bdk_persistence.sqlite"):
|
||||||
os.remove("./bdk_persistence.db")
|
os.remove("./bdk_persistence.sqlite")
|
||||||
|
|
||||||
def test_synced_balance(self):
|
def test_synced_balance(self):
|
||||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
bdk.Network.SIGNET
|
|
||||||
)
|
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
wallet: bdk.Wallet = bdk.Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
None,
|
change_descriptor,
|
||||||
"./bdk_persistence.db",
|
|
||||||
bdk.Network.SIGNET
|
bdk.Network.SIGNET
|
||||||
)
|
)
|
||||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
||||||
@ -30,28 +34,26 @@ class LiveWalletTest(unittest.TestCase):
|
|||||||
parallel_requests=1
|
parallel_requests=1
|
||||||
)
|
)
|
||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
wallet.commit()
|
|
||||||
|
|
||||||
self.assertGreater(wallet.get_balance().total, 0)
|
self.assertGreater(
|
||||||
|
wallet.balance().total.to_sat(),
|
||||||
|
0,
|
||||||
|
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
)
|
||||||
|
|
||||||
print(f"Transactions count: {len(wallet.transactions())}")
|
print(f"Transactions count: {len(wallet.transactions())}")
|
||||||
transactions = wallet.transactions()[:3]
|
transactions = wallet.transactions()[:3]
|
||||||
for tx in transactions:
|
for tx in transactions:
|
||||||
sent_and_received = wallet.sent_and_received(tx.transaction)
|
sent_and_received = wallet.sent_and_received(tx.transaction)
|
||||||
print(f"Transaction: {tx.transaction.txid()}")
|
print(f"Transaction: {tx.transaction.compute_txid()}")
|
||||||
print(f"Sent {sent_and_received.sent}")
|
print(f"Sent {sent_and_received.sent.to_sat()}")
|
||||||
print(f"Received {sent_and_received.received}")
|
print(f"Received {sent_and_received.received.to_sat()}")
|
||||||
|
|
||||||
|
|
||||||
def test_broadcast_transaction(self):
|
def test_broadcast_transaction(self):
|
||||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
bdk.Network.SIGNET
|
|
||||||
)
|
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
wallet: bdk.Wallet = bdk.Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
None,
|
change_descriptor,
|
||||||
"./bdk_persistence.db",
|
|
||||||
bdk.Network.SIGNET
|
bdk.Network.SIGNET
|
||||||
)
|
)
|
||||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
||||||
@ -62,25 +64,27 @@ class LiveWalletTest(unittest.TestCase):
|
|||||||
parallel_requests=1
|
parallel_requests=1
|
||||||
)
|
)
|
||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
wallet.commit()
|
|
||||||
|
|
||||||
self.assertGreater(wallet.get_balance().total, 0)
|
self.assertGreater(
|
||||||
|
wallet.balance().total.to_sat(),
|
||||||
|
0,
|
||||||
|
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address} and try again."
|
||||||
|
)
|
||||||
|
|
||||||
recipient = bdk.Address(
|
recipient = bdk.Address(
|
||||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||||
network=bdk.Network.SIGNET
|
network=bdk.Network.SIGNET
|
||||||
)
|
)
|
||||||
|
|
||||||
psbt: bdk.Psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
|
psbt: bdk.Psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=bdk.Amount.from_sat(4200)).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
|
||||||
# print(psbt.serialize())
|
|
||||||
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
||||||
|
|
||||||
walletDidSign = wallet.sign(psbt)
|
walletDidSign = wallet.sign(psbt)
|
||||||
self.assertTrue(walletDidSign)
|
self.assertTrue(walletDidSign)
|
||||||
tx = psbt.extract_tx()
|
tx = psbt.extract_tx()
|
||||||
print(f"Transaction Id: {tx.txid()}")
|
print(f"Transaction Id: {tx.compute_txid()}")
|
||||||
fee = wallet.calculate_fee(tx)
|
fee = wallet.calculate_fee(tx)
|
||||||
print(f"Transaction Fee: {fee}")
|
print(f"Transaction Fee: {fee.to_sat()}")
|
||||||
fee_rate = wallet.calculate_fee_rate(tx)
|
fee_rate = wallet.calculate_fee_rate(tx)
|
||||||
print(f"Transaction Fee Rate: {fee_rate.to_sat_per_vb_ceil()} sat/vB")
|
print(f"Transaction Fee Rate: {fee_rate.to_sat_per_vb_ceil()} sat/vB")
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ class OfflineDescriptorTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
"tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
||||||
descriptor.as_string()
|
descriptor.__str__()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,21 +2,25 @@ import bdkpython as bdk
|
|||||||
import unittest
|
import unittest
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
bdk.Network.TESTNET
|
||||||
|
)
|
||||||
|
change_descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
bdk.Network.TESTNET
|
||||||
|
)
|
||||||
|
|
||||||
class OfflineWalletTest(unittest.TestCase):
|
class OfflineWalletTest(unittest.TestCase):
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
if os.path.exists("./bdk_persistence.db"):
|
if os.path.exists("./bdk_persistence.sqlite"):
|
||||||
os.remove("./bdk_persistence.db")
|
os.remove("./bdk_persistence.sqlite")
|
||||||
|
|
||||||
def test_new_address(self):
|
def test_new_address(self):
|
||||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
|
||||||
bdk.Network.TESTNET
|
|
||||||
)
|
|
||||||
wallet: Wallet = bdk.Wallet(
|
wallet: Wallet = bdk.Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
None,
|
change_descriptor,
|
||||||
"./bdk_persistence.db",
|
|
||||||
bdk.Network.TESTNET
|
bdk.Network.TESTNET
|
||||||
)
|
)
|
||||||
address_info: bdk.AddressInfo = wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL)
|
address_info: bdk.AddressInfo = wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL)
|
||||||
@ -26,21 +30,16 @@ class OfflineWalletTest(unittest.TestCase):
|
|||||||
self.assertFalse(address_info.address.is_valid_for_network(bdk.Network.REGTEST), "Address is valid for regtest network, but it shouldn't be")
|
self.assertFalse(address_info.address.is_valid_for_network(bdk.Network.REGTEST), "Address is valid for regtest network, but it shouldn't be")
|
||||||
self.assertFalse(address_info.address.is_valid_for_network(bdk.Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
|
self.assertFalse(address_info.address.is_valid_for_network(bdk.Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
|
||||||
|
|
||||||
self.assertEqual("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address_info.address.as_string())
|
self.assertEqual("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", address_info.address.__str__())
|
||||||
|
|
||||||
def test_balance(self):
|
def test_balance(self):
|
||||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
|
||||||
bdk.Network.TESTNET
|
|
||||||
)
|
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
wallet: bdk.Wallet = bdk.Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
None,
|
change_descriptor,
|
||||||
"./bdk_persistence.db",
|
|
||||||
bdk.Network.TESTNET
|
bdk.Network.TESTNET
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(wallet.get_balance().total, 0)
|
self.assertEqual(wallet.balance().total.to_sat(), 0)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -33,6 +33,10 @@ let package = Package(
|
|||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "BitcoinDevKitTests",
|
name: "BitcoinDevKitTests",
|
||||||
dependencies: ["BitcoinDevKit"]),
|
dependencies: ["BitcoinDevKit"],
|
||||||
|
resources: [
|
||||||
|
.copy("Resources/pre_existing_wallet_persistence_test.sqlite")
|
||||||
|
]
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,7 @@ import BitcoinDevKit
|
|||||||
|
|
||||||
Swift Package Manager releases for `bdk-swift` are published to a separate repository (https://github.com/bitcoindevkit/bdk-swift), and that is where the releases are created for it.
|
Swift Package Manager releases for `bdk-swift` are published to a separate repository (https://github.com/bitcoindevkit/bdk-swift), and that is where the releases are created for it.
|
||||||
|
|
||||||
The `bdk-swift/build-local-swift.sh` script can be used instead to create a version of the project for local testing.
|
The `bdk-swift/build-xcframework.sh` script can be used instead to create a version of the project for local testing.
|
||||||
|
|
||||||
### How to test
|
### How to test
|
||||||
|
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
|
private let SIGNET_ELECTRUM_URL = "ssl://mempool.space:60602"
|
||||||
|
|
||||||
|
final class LiveElectrumClientTests: XCTestCase {
|
||||||
|
private let descriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
private let changeDescriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
|
||||||
|
func testSyncedBalance() throws {
|
||||||
|
let wallet = try Wallet(
|
||||||
|
descriptor: descriptor,
|
||||||
|
changeDescriptor: changeDescriptor,
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
let electrumClient: ElectrumClient = try ElectrumClient(url: SIGNET_ELECTRUM_URL)
|
||||||
|
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
|
let update = try electrumClient.fullScan(
|
||||||
|
fullScanRequest: fullScanRequest,
|
||||||
|
stopGap: 10,
|
||||||
|
batchSize: 10,
|
||||||
|
fetchPrevTxouts: false
|
||||||
|
)
|
||||||
|
try wallet.applyUpdate(update: update)
|
||||||
|
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.balance().total.toSat(),
|
||||||
|
UInt64(0),
|
||||||
|
"Wallet must have positive balance, please send funds to \(address)"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Transactions count: \(wallet.transactions().count)")
|
||||||
|
let transactions = wallet.transactions().prefix(3)
|
||||||
|
for tx in transactions {
|
||||||
|
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
|
||||||
|
print("Transaction: \(tx.transaction.computeTxid())")
|
||||||
|
print("Sent \(sentAndReceived.sent.toSat())")
|
||||||
|
print("Received \(sentAndReceived.received.toSat())")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
|
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||||
|
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||||
|
|
||||||
|
final class LiveMemoryWalletTests: XCTestCase {
|
||||||
|
private let descriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
private let changeDescriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
|
||||||
|
func testSyncedBalance() throws {
|
||||||
|
let wallet = try Wallet(
|
||||||
|
descriptor: descriptor,
|
||||||
|
changeDescriptor: changeDescriptor,
|
||||||
|
network: .signet
|
||||||
|
)
|
||||||
|
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
||||||
|
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
|
let update = try esploraClient.fullScan(
|
||||||
|
fullScanRequest: fullScanRequest,
|
||||||
|
stopGap: 10,
|
||||||
|
parallelRequests: 1
|
||||||
|
)
|
||||||
|
try wallet.applyUpdate(update: update)
|
||||||
|
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.balance().total.toSat(),
|
||||||
|
UInt64(0),
|
||||||
|
"Wallet must have positive balance, please send funds to \(address)"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Transactions count: \(wallet.transactions().count)")
|
||||||
|
let transactions = wallet.transactions().prefix(3)
|
||||||
|
for tx in transactions {
|
||||||
|
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
|
||||||
|
print("Transaction: \(tx.transaction.computeTxid())")
|
||||||
|
print("Sent \(sentAndReceived.sent.toSat())")
|
||||||
|
print("Received \(sentAndReceived.received.toSat())")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testScriptInspector() throws {
|
||||||
|
let wallet = try Wallet(
|
||||||
|
descriptor: descriptor,
|
||||||
|
changeDescriptor: changeDescriptor,
|
||||||
|
network: .signet
|
||||||
|
)
|
||||||
|
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
||||||
|
let scriptInspector = FullScriptInspector()
|
||||||
|
let fullScanRequest = try wallet.startFullScan().inspectSpksForAllKeychains(inspector: scriptInspector)
|
||||||
|
|
||||||
|
let update = try esploraClient.fullScan(
|
||||||
|
fullScanRequest: fullScanRequest,
|
||||||
|
stopGap: 21,
|
||||||
|
parallelRequests: 1
|
||||||
|
)
|
||||||
|
try wallet.applyUpdate(update: update)
|
||||||
|
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.balance().total.toSat(),
|
||||||
|
UInt64(0),
|
||||||
|
"Wallet must have positive balance, please send funds to \(address)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class FullScriptInspector: FullScanScriptInspector {
|
||||||
|
func inspect(keychain: KeychainKind, index: UInt32, script: Script) {
|
||||||
|
print("Inspecting index \(index) for keychain \(keychain)")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
|
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||||
|
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||||
|
|
||||||
|
|
||||||
|
final class LiveTransactionTests: XCTestCase {
|
||||||
|
private let descriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
private let changeDescriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
|
||||||
|
func testSyncedBalance() throws {
|
||||||
|
let wallet = try Wallet(
|
||||||
|
descriptor: descriptor,
|
||||||
|
changeDescriptor: changeDescriptor,
|
||||||
|
network: .signet
|
||||||
|
)
|
||||||
|
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
||||||
|
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
|
let update = try esploraClient.fullScan(
|
||||||
|
fullScanRequest: fullScanRequest,
|
||||||
|
stopGap: 10,
|
||||||
|
parallelRequests: 1
|
||||||
|
)
|
||||||
|
try wallet.applyUpdate(update: update)
|
||||||
|
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.balance().total.toSat(),
|
||||||
|
UInt64(0),
|
||||||
|
"Wallet must have positive balance, please send funds to \(address)"
|
||||||
|
)
|
||||||
|
|
||||||
|
guard let transaction = wallet.transactions().first?.transaction else {
|
||||||
|
print("No transactions found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
print("First transaction:")
|
||||||
|
print("Txid: \(transaction.computeTxid())")
|
||||||
|
print("Version: \(transaction.version())")
|
||||||
|
print("Total size: \(transaction.totalSize())")
|
||||||
|
print("Vsize: \(transaction.vsize())")
|
||||||
|
print("Weight: \(transaction.weight())")
|
||||||
|
print("Coinbase transaction: \(transaction.isCoinbase())")
|
||||||
|
print("Is explicitly RBF: \(transaction.isExplicitlyRbf())")
|
||||||
|
print("Inputs: \(transaction.input())")
|
||||||
|
print("Outputs: \(transaction.output())")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,25 @@
|
|||||||
import XCTest
|
import XCTest
|
||||||
@testable import BitcoinDevKit
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||||
let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||||
|
|
||||||
final class LiveTxBuilderTests: XCTestCase {
|
final class LiveTxBuilderTests: XCTestCase {
|
||||||
|
private let descriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
private let changeDescriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
var dbFilePath: URL!
|
var dbFilePath: URL!
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
let uniqueDbFileName = "bdk_persistence_\(UUID().uuidString).db"
|
let uniqueDbFileName = "bdk_persistence_\(UUID().uuidString).sqlite"
|
||||||
dbFilePath = documentDirectory.appendingPathComponent(uniqueDbFileName)
|
dbFilePath = documentDirectory.appendingPathComponent(uniqueDbFileName)
|
||||||
|
|
||||||
if fileManager.fileExists(atPath: dbFilePath.path) {
|
if fileManager.fileExists(atPath: dbFilePath.path) {
|
||||||
@ -27,14 +35,9 @@ final class LiveTxBuilderTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testTxBuilder() throws {
|
func testTxBuilder() throws {
|
||||||
let descriptor = try Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
let wallet = try Wallet(
|
let wallet = try Wallet(
|
||||||
descriptor: descriptor,
|
descriptor: descriptor,
|
||||||
changeDescriptor: nil,
|
changeDescriptor: changeDescriptor,
|
||||||
persistenceBackendPath: dbFilePath.path,
|
|
||||||
network: .signet
|
network: .signet
|
||||||
)
|
)
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
||||||
@ -45,13 +48,17 @@ final class LiveTxBuilderTests: XCTestCase {
|
|||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
try wallet.commit()
|
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
||||||
|
|
||||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
|
XCTAssertGreaterThan(
|
||||||
|
wallet.balance().total.toSat(),
|
||||||
|
UInt64(0),
|
||||||
|
"Wallet must have positive balance, please send funds to \(address)"
|
||||||
|
)
|
||||||
|
|
||||||
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
||||||
let psbt: Psbt = try TxBuilder()
|
let psbt: Psbt = try TxBuilder()
|
||||||
.addRecipient(script: recipient.scriptPubkey(), amount: 4200)
|
.addRecipient(script: recipient.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200))
|
||||||
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 2))
|
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 2))
|
||||||
.finish(wallet: wallet)
|
.finish(wallet: wallet)
|
||||||
|
|
||||||
@ -71,7 +78,6 @@ final class LiveTxBuilderTests: XCTestCase {
|
|||||||
let wallet = try Wallet(
|
let wallet = try Wallet(
|
||||||
descriptor: descriptor,
|
descriptor: descriptor,
|
||||||
changeDescriptor: changeDescriptor,
|
changeDescriptor: changeDescriptor,
|
||||||
persistenceBackendPath: dbFilePath.path,
|
|
||||||
network: .signet
|
network: .signet
|
||||||
)
|
)
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
||||||
@ -82,15 +88,19 @@ final class LiveTxBuilderTests: XCTestCase {
|
|||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
try wallet.commit()
|
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
||||||
|
|
||||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
|
XCTAssertGreaterThan(
|
||||||
|
wallet.balance().total.toSat(),
|
||||||
|
UInt64(0),
|
||||||
|
"Wallet must have positive balance, please send funds to \(address)"
|
||||||
|
)
|
||||||
|
|
||||||
let recipient1: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
let recipient1: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
||||||
let recipient2: Address = try Address(address: "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", network: .signet)
|
let recipient2: Address = try Address(address: "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", network: .signet)
|
||||||
let allRecipients: [ScriptAmount] = [
|
let allRecipients: [ScriptAmount] = [
|
||||||
ScriptAmount(script: recipient1.scriptPubkey(), amount: 4200),
|
ScriptAmount(script: recipient1.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200)),
|
||||||
ScriptAmount(script: recipient2.scriptPubkey(), amount: 4200)
|
ScriptAmount(script: recipient2.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200))
|
||||||
]
|
]
|
||||||
|
|
||||||
let psbt: Psbt = try TxBuilder()
|
let psbt: Psbt = try TxBuilder()
|
||||||
@ -100,7 +110,7 @@ final class LiveTxBuilderTests: XCTestCase {
|
|||||||
.enableRbf()
|
.enableRbf()
|
||||||
.finish(wallet: wallet)
|
.finish(wallet: wallet)
|
||||||
|
|
||||||
try! wallet.sign(psbt: psbt)
|
let _ = try! wallet.sign(psbt: psbt)
|
||||||
|
|
||||||
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
|
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,25 @@
|
|||||||
import XCTest
|
import XCTest
|
||||||
@testable import BitcoinDevKit
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||||
let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||||
|
|
||||||
final class LiveWalletTests: XCTestCase {
|
final class LiveWalletTests: XCTestCase {
|
||||||
|
private let descriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
private let changeDescriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
var dbFilePath: URL!
|
var dbFilePath: URL!
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
let uniqueDbFileName = "bdk_persistence_\(UUID().uuidString).db"
|
let uniqueDbFileName = "bdk_persistence_\(UUID().uuidString).sqlite"
|
||||||
dbFilePath = documentDirectory.appendingPathComponent(uniqueDbFileName)
|
dbFilePath = documentDirectory.appendingPathComponent(uniqueDbFileName)
|
||||||
|
|
||||||
if fileManager.fileExists(atPath: dbFilePath.path) {
|
if fileManager.fileExists(atPath: dbFilePath.path) {
|
||||||
@ -27,14 +35,9 @@ final class LiveWalletTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testSyncedBalance() throws {
|
func testSyncedBalance() throws {
|
||||||
let descriptor = try Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
let wallet = try Wallet(
|
let wallet = try Wallet(
|
||||||
descriptor: descriptor,
|
descriptor: descriptor,
|
||||||
changeDescriptor: nil,
|
changeDescriptor: changeDescriptor,
|
||||||
persistenceBackendPath: dbFilePath.path,
|
|
||||||
network: .signet
|
network: .signet
|
||||||
)
|
)
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
||||||
@ -45,29 +48,28 @@ final class LiveWalletTests: XCTestCase {
|
|||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
try wallet.commit()
|
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
||||||
|
|
||||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0))
|
XCTAssertGreaterThan(
|
||||||
|
wallet.balance().total.toSat(),
|
||||||
|
UInt64(0),
|
||||||
|
"Wallet must have positive balance, please send funds to \(address)"
|
||||||
|
)
|
||||||
|
|
||||||
print("Transactions count: \(wallet.transactions().count)")
|
print("Transactions count: \(wallet.transactions().count)")
|
||||||
let transactions = wallet.transactions().prefix(3)
|
let transactions = wallet.transactions().prefix(3)
|
||||||
for tx in transactions {
|
for tx in transactions {
|
||||||
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
|
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
|
||||||
print("Transaction: \(tx.transaction.txid())")
|
print("Transaction: \(tx.transaction.computeTxid())")
|
||||||
print("Sent \(sentAndReceived.sent)")
|
print("Sent \(sentAndReceived.sent.toSat())")
|
||||||
print("Received \(sentAndReceived.received)")
|
print("Received \(sentAndReceived.received.toSat())")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBroadcastTransaction() throws {
|
func testBroadcastTransaction() throws {
|
||||||
let descriptor = try Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
let wallet = try Wallet(
|
let wallet = try Wallet(
|
||||||
descriptor: descriptor,
|
descriptor: descriptor,
|
||||||
changeDescriptor: nil,
|
changeDescriptor: changeDescriptor,
|
||||||
persistenceBackendPath: dbFilePath.path,
|
|
||||||
network: .signet
|
network: .signet
|
||||||
)
|
)
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
||||||
@ -78,16 +80,20 @@ final class LiveWalletTests: XCTestCase {
|
|||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
try wallet.commit()
|
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.balance().total.toSat(),
|
||||||
|
UInt64(0),
|
||||||
|
"Wallet must have positive balance, please send funds to \(address)"
|
||||||
|
)
|
||||||
|
|
||||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
|
print("Balance: \(wallet.balance().total)")
|
||||||
|
|
||||||
print("Balance: \(wallet.getBalance().total)")
|
|
||||||
|
|
||||||
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
||||||
let psbt: Psbt = try
|
let psbt: Psbt = try
|
||||||
TxBuilder()
|
TxBuilder()
|
||||||
.addRecipient(script: recipient.scriptPubkey(), amount: 4200)
|
.addRecipient(script: recipient.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200))
|
||||||
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 2))
|
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 2))
|
||||||
.finish(wallet: wallet)
|
.finish(wallet: wallet)
|
||||||
|
|
||||||
@ -98,8 +104,8 @@ final class LiveWalletTests: XCTestCase {
|
|||||||
XCTAssertTrue(walletDidSign, "Wallet did not sign transaction")
|
XCTAssertTrue(walletDidSign, "Wallet did not sign transaction")
|
||||||
|
|
||||||
let tx: Transaction = try! psbt.extractTx()
|
let tx: Transaction = try! psbt.extractTx()
|
||||||
print(tx.txid())
|
print(tx.computeTxid())
|
||||||
let fee: UInt64 = try wallet.calculateFee(tx: tx)
|
let fee: Amount = try wallet.calculateFee(tx: tx)
|
||||||
print("Transaction Fee: \(fee)")
|
print("Transaction Fee: \(fee)")
|
||||||
let feeRate: FeeRate = try wallet.calculateFeeRate(tx: tx)
|
let feeRate: FeeRate = try wallet.calculateFeeRate(tx: tx)
|
||||||
print("Transaction Fee Rate: \(feeRate.toSatPerVbCeil()) sat/vB")
|
print("Transaction Fee Rate: \(feeRate.toSatPerVbCeil()) sat/vB")
|
||||||
|
@ -15,6 +15,6 @@ final class OfflineDescriptorTests: XCTestCase {
|
|||||||
network: Network.testnet
|
network: Network.testnet
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(descriptor.asString(), "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx")
|
XCTAssertEqual(descriptor.description, "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
|
final class OfflinePersistenceTests: XCTestCase {
|
||||||
|
private let descriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
private let changeDescriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
var dbFilePath: URL!
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
super.setUp()
|
||||||
|
|
||||||
|
guard let resourceUrl = Bundle.module.url(
|
||||||
|
forResource: "pre_existing_wallet_persistence_test",
|
||||||
|
withExtension: "sqlite"
|
||||||
|
) else {
|
||||||
|
print("error finding resourceURL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dbFilePath = resourceUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPersistence() throws {
|
||||||
|
let sqliteStore = try! SqliteStore(path: dbFilePath.path)
|
||||||
|
let initialChangeSet = try! sqliteStore.read()
|
||||||
|
let wallet = try Wallet.newOrLoad(
|
||||||
|
descriptor: descriptor,
|
||||||
|
changeDescriptor: changeDescriptor,
|
||||||
|
changeSet: initialChangeSet,
|
||||||
|
network: .signet
|
||||||
|
)
|
||||||
|
let nextAddress: AddressInfo = wallet.revealNextAddress(keychain: KeychainKind.external)
|
||||||
|
print("Address: \(nextAddress)")
|
||||||
|
|
||||||
|
XCTAssertTrue(nextAddress.address.description == "tb1qan3lldunh37ma6c0afeywgjyjgnyc8uz975zl2")
|
||||||
|
XCTAssertTrue(nextAddress.index == 7)
|
||||||
|
}
|
||||||
|
}
|
@ -2,13 +2,21 @@ import XCTest
|
|||||||
@testable import BitcoinDevKit
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
final class OfflineWalletTests: XCTestCase {
|
final class OfflineWalletTests: XCTestCase {
|
||||||
|
private let descriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
private let changeDescriptor = try! Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
var dbFilePath: URL!
|
var dbFilePath: URL!
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
let uniqueDbFileName = "bdk_persistence_\(UUID().uuidString).db"
|
let uniqueDbFileName = "bdk_persistence_\(UUID().uuidString).sqlite"
|
||||||
dbFilePath = documentDirectory.appendingPathComponent(uniqueDbFileName)
|
dbFilePath = documentDirectory.appendingPathComponent(uniqueDbFileName)
|
||||||
|
|
||||||
if fileManager.fileExists(atPath: dbFilePath.path) {
|
if fileManager.fileExists(atPath: dbFilePath.path) {
|
||||||
@ -24,17 +32,12 @@ final class OfflineWalletTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testNewAddress() throws {
|
func testNewAddress() throws {
|
||||||
let descriptor: Descriptor = try Descriptor(
|
|
||||||
descriptor: "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
|
||||||
network: Network.testnet
|
|
||||||
)
|
|
||||||
let wallet = try Wallet(
|
let wallet = try Wallet(
|
||||||
descriptor: descriptor,
|
descriptor: descriptor,
|
||||||
changeDescriptor: nil,
|
changeDescriptor: changeDescriptor,
|
||||||
persistenceBackendPath: dbFilePath.path,
|
|
||||||
network: .testnet
|
network: .testnet
|
||||||
)
|
)
|
||||||
let addressInfo: AddressInfo = try wallet.revealNextAddress(keychain: KeychainKind.external)
|
let addressInfo: AddressInfo = wallet.revealNextAddress(keychain: KeychainKind.external)
|
||||||
|
|
||||||
XCTAssertTrue(addressInfo.address.isValidForNetwork(network: Network.testnet),
|
XCTAssertTrue(addressInfo.address.isValidForNetwork(network: Network.testnet),
|
||||||
"Address is not valid for testnet network")
|
"Address is not valid for testnet network")
|
||||||
@ -45,21 +48,16 @@ final class OfflineWalletTests: XCTestCase {
|
|||||||
XCTAssertFalse(addressInfo.address.isValidForNetwork(network: Network.bitcoin),
|
XCTAssertFalse(addressInfo.address.isValidForNetwork(network: Network.bitcoin),
|
||||||
"Address is valid for bitcoin network, but it shouldn't be")
|
"Address is valid for bitcoin network, but it shouldn't be")
|
||||||
|
|
||||||
XCTAssertEqual(addressInfo.address.asString(), "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e")
|
XCTAssertEqual(addressInfo.address.description, "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBalance() throws {
|
func testBalance() throws {
|
||||||
let descriptor: Descriptor = try Descriptor(
|
|
||||||
descriptor: "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
|
||||||
network: Network.testnet
|
|
||||||
)
|
|
||||||
let wallet = try Wallet(
|
let wallet = try Wallet(
|
||||||
descriptor: descriptor,
|
descriptor: descriptor,
|
||||||
changeDescriptor: nil,
|
changeDescriptor: changeDescriptor,
|
||||||
persistenceBackendPath: dbFilePath.path,
|
|
||||||
network: .testnet
|
network: .testnet
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(wallet.getBalance().total, 0)
|
XCTAssertEqual(wallet.balance().total.toSat(), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -1,59 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>AvailableLibraries</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>macos-arm64_x86_64</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>bdkFFI.framework</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
<string>x86_64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>macos</string>
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
|
||||||
<string>12.0</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64_x86_64-simulator</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>bdkFFI.framework</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
<string>x86_64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
<key>SupportedPlatformVariant</key>
|
|
||||||
<string>simulator</string>
|
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>15.0</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>bdkFFI.framework</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>15.0</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>XFWK</string>
|
|
||||||
<key>XCFrameworkFormatVersion</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,4 +0,0 @@
|
|||||||
// This is the "umbrella header" for our combined Rust code library.
|
|
||||||
// It needs to import all of the individual headers.
|
|
||||||
|
|
||||||
#import "bdkFFI.h"
|
|
@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>com.bitcoindevkit.bdkFFI</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>bdkFFI</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>1.0.0</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0.0</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>bdkFFI</string>
|
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>100</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,6 +0,0 @@
|
|||||||
framework module bdkFFI {
|
|
||||||
umbrella header "bdkFFI-umbrella.h"
|
|
||||||
|
|
||||||
export *
|
|
||||||
module * { export * }
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
// This is the "umbrella header" for our combined Rust code library.
|
|
||||||
// It needs to import all of the individual headers.
|
|
||||||
|
|
||||||
#import "bdkFFI.h"
|
|
@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>com.bitcoindevkit.bdkFFI</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>bdkFFI</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>1.0.0</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0.0</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>bdkFFI</string>
|
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>15.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,6 +0,0 @@
|
|||||||
framework module bdkFFI {
|
|
||||||
umbrella header "bdkFFI-umbrella.h"
|
|
||||||
|
|
||||||
export *
|
|
||||||
module * { export * }
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
// This is the "umbrella header" for our combined Rust code library.
|
|
||||||
// It needs to import all of the individual headers.
|
|
||||||
|
|
||||||
#import "bdkFFI.h"
|
|
@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>com.bitcoindevkit.bdkFFI</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>bdkFFI</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>1.0.0</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0.0</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>bdkFFI</string>
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
|
||||||
<string>12.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
@ -1,6 +0,0 @@
|
|||||||
framework module bdkFFI {
|
|
||||||
umbrella header "bdkFFI-umbrella.h"
|
|
||||||
|
|
||||||
export *
|
|
||||||
module * { export * }
|
|
||||||
}
|
|
@ -3,6 +3,16 @@
|
|||||||
# The results of this script can be used for locally testing your SPM package adding a local package
|
# The results of this script can be used for locally testing your SPM package adding a local package
|
||||||
# to your application pointing at the bdk-swift directory.
|
# to your application pointing at the bdk-swift directory.
|
||||||
|
|
||||||
|
HEADERPATH="Sources/BitcoinDevKit/BitcoinDevKitFFI.h"
|
||||||
|
MODMAPPATH="Sources/BitcoinDevKit/BitcoinDevKitFFI.modulemap"
|
||||||
|
TARGETDIR="../bdk-ffi/target"
|
||||||
|
OUTDIR="."
|
||||||
|
RELDIR="release-smaller"
|
||||||
|
NAME="bdkffi"
|
||||||
|
STATIC_LIB_NAME="lib${NAME}.a"
|
||||||
|
NEW_HEADER_DIR="../bdk-ffi/target/include"
|
||||||
|
|
||||||
|
# set required rust version and install component and targets
|
||||||
rustup default 1.77.1
|
rustup default 1.77.1
|
||||||
rustup component add rust-src
|
rustup component add rust-src
|
||||||
rustup target add aarch64-apple-ios # iOS arm64
|
rustup target add aarch64-apple-ios # iOS arm64
|
||||||
@ -13,26 +23,38 @@ rustup target add x86_64-apple-darwin # mac x86_64
|
|||||||
|
|
||||||
cd ../bdk-ffi/ || exit
|
cd ../bdk-ffi/ || exit
|
||||||
|
|
||||||
|
# build bdk-ffi rust lib for apple targets
|
||||||
cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-darwin
|
cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-darwin
|
||||||
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-darwin
|
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-darwin
|
||||||
cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-ios
|
cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-ios
|
||||||
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-ios
|
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-ios
|
||||||
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-ios-sim
|
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-ios-sim
|
||||||
|
|
||||||
|
# build bdk-ffi Swift bindings and put in bdk-swift Sources
|
||||||
cargo run --bin uniffi-bindgen generate --library ./target/aarch64-apple-ios/release-smaller/libbdkffi.dylib --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit --no-format
|
cargo run --bin uniffi-bindgen generate --library ./target/aarch64-apple-ios/release-smaller/libbdkffi.dylib --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit --no-format
|
||||||
|
|
||||||
|
# combine bdk-ffi static libs for aarch64 and x86_64 targets via lipo tool
|
||||||
mkdir -p target/lipo-ios-sim/release-smaller
|
mkdir -p target/lipo-ios-sim/release-smaller
|
||||||
lipo target/aarch64-apple-ios-sim/release-smaller/libbdkffi.a target/x86_64-apple-ios/release-smaller/libbdkffi.a -create -output target/lipo-ios-sim/release-smaller/libbdkffi.a
|
lipo target/aarch64-apple-ios-sim/release-smaller/libbdkffi.a target/x86_64-apple-ios/release-smaller/libbdkffi.a -create -output target/lipo-ios-sim/release-smaller/libbdkffi.a
|
||||||
mkdir -p target/lipo-macos/release-smaller
|
mkdir -p target/lipo-macos/release-smaller
|
||||||
lipo target/aarch64-apple-darwin/release-smaller/libbdkffi.a target/x86_64-apple-darwin/release-smaller/libbdkffi.a -create -output target/lipo-macos/release-smaller/libbdkffi.a
|
lipo target/aarch64-apple-darwin/release-smaller/libbdkffi.a target/x86_64-apple-darwin/release-smaller/libbdkffi.a -create -output target/lipo-macos/release-smaller/libbdkffi.a
|
||||||
|
|
||||||
cd ../bdk-swift/ || exit
|
cd ../bdk-swift/ || exit
|
||||||
mv Sources/BitcoinDevKit/bdk.swift Sources/BitcoinDevKit/BitcoinDevKit.swift
|
|
||||||
cp Sources/BitcoinDevKit/bdkFFI.h bdkFFI.xcframework/ios-arm64/bdkFFI.framework/Headers
|
# move bdk-ffi static lib header files to temporary directory
|
||||||
cp Sources/BitcoinDevKit/bdkFFI.h bdkFFI.xcframework/ios-arm64_x86_64-simulator/bdkFFI.framework/Headers
|
mkdir -p "${NEW_HEADER_DIR}"
|
||||||
cp Sources/BitcoinDevKit/bdkFFI.h bdkFFI.xcframework/macos-arm64_x86_64/bdkFFI.framework/Headers
|
mv "${HEADERPATH}" "${NEW_HEADER_DIR}"
|
||||||
cp ../bdk-ffi/target/aarch64-apple-ios/release-smaller/libbdkffi.a bdkFFI.xcframework/ios-arm64/bdkFFI.framework/bdkFFI
|
mv "${MODMAPPATH}" "${NEW_HEADER_DIR}/module.modulemap"
|
||||||
cp ../bdk-ffi/target/lipo-ios-sim/release-smaller/libbdkffi.a bdkFFI.xcframework/ios-arm64_x86_64-simulator/bdkFFI.framework/bdkFFI
|
|
||||||
cp ../bdk-ffi/target/lipo-macos/release-smaller/libbdkffi.a bdkFFI.xcframework/macos-arm64_x86_64/bdkFFI.framework/bdkFFI
|
# remove old xcframework directory
|
||||||
rm Sources/BitcoinDevKit/bdkFFI.h
|
rm -rf "${OUTDIR}/${NAME}.xcframework"
|
||||||
rm Sources/BitcoinDevKit/bdkFFI.modulemap
|
|
||||||
|
# create new xcframework directory from bdk-ffi static libs and headers
|
||||||
|
xcodebuild -create-xcframework \
|
||||||
|
-library "${TARGETDIR}/lipo-macos/${RELDIR}/${STATIC_LIB_NAME}" \
|
||||||
|
-headers "${NEW_HEADER_DIR}" \
|
||||||
|
-library "${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}" \
|
||||||
|
-headers "${NEW_HEADER_DIR}" \
|
||||||
|
-library "${TARGETDIR}/lipo-ios-sim/${RELDIR}/${STATIC_LIB_NAME}" \
|
||||||
|
-headers "${NEW_HEADER_DIR}" \
|
||||||
|
-output "${OUTDIR}/${NAME}.xcframework"
|
@ -2,7 +2,7 @@ default:
|
|||||||
just --list
|
just --list
|
||||||
|
|
||||||
build:
|
build:
|
||||||
bash ./build-local-swift.sh
|
bash ./build-xcframework.sh
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf ../bdk-ffi/target/
|
rm -rf ../bdk-ffi/target/
|
||||||
@ -11,4 +11,4 @@ test:
|
|||||||
swift test
|
swift test
|
||||||
|
|
||||||
test-offline:
|
test-offline:
|
||||||
swift test --skip LiveWalletTests --skip LiveTxBuilderTests
|
swift test --skip LiveElectrumClientTests --skip LiveMemoryWalletTests --skip LiveTransactionTests --skip LiveTxBuilderTests --skip LiveWalletTests
|
Loading…
x
Reference in New Issue
Block a user