Compare commits
19 Commits
fix/live-t
...
v1.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5cf483223 | ||
|
|
c6174199dd | ||
|
|
9c45254c3e | ||
|
|
260a0a65b3 | ||
|
|
72985f14ad | ||
|
|
5e3e24906f | ||
|
|
c702894143 | ||
|
|
ecdd7c239b | ||
|
|
ca8a3d0471 | ||
|
|
8f4c80cb98 | ||
|
|
4aec4b0434 | ||
|
|
1913c45ef9 | ||
|
|
815fe5f62d | ||
|
|
8d30c86076 | ||
|
|
c88b33473b | ||
|
|
79e7ab73ea | ||
|
|
f169b1a52f | ||
|
|
97d9bb6fbf | ||
|
|
f27bada9c9 |
@@ -3,6 +3,9 @@ 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.11]
|
||||||
|
This release adds the new `Amount` type, as well as more fine-grain errors.
|
||||||
|
|
||||||
## [1.0.0-alpha.7]
|
## [1.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.
|
||||||
|
|
||||||
|
|||||||
@@ -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.11
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ 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_persistence.db"
|
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence3.db"
|
||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
@@ -28,18 +28,20 @@ class LiveTxBuilderTest {
|
|||||||
fun testTxBuilder() {
|
fun testTxBuilder() {
|
||||||
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(descriptor, null, persistenceFilePath, Network.SIGNET)
|
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
|
||||||
val 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()
|
wallet.commit()
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.getBalance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} 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)
|
||||||
|
|
||||||
@@ -51,21 +53,23 @@ class LiveTxBuilderTest {
|
|||||||
fun complexTxBuilder() {
|
fun complexTxBuilder() {
|
||||||
val externalDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
val externalDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
||||||
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.SIGNET)
|
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.SIGNET)
|
||||||
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.TESTNET)
|
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.SIGNET)
|
||||||
val 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()
|
wallet.commit()
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.getBalance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} 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,7 @@ 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.db"
|
||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
@@ -33,11 +33,13 @@ class LiveWalletTest {
|
|||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
val balance: Balance = wallet.getBalance()
|
val balance: Balance = wallet.getBalance()
|
||||||
println("Balance: $balance")
|
println("Balance: $balance")
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.getBalance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} 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)
|
||||||
@@ -58,17 +60,16 @@ class LiveWalletTest {
|
|||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
println("New address: ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address}")
|
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL) {
|
assert(wallet.getBalance().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)
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ class LiveWalletTest {
|
|||||||
println("Txid is: ${tx.txid()}")
|
println("Txid is: ${tx.txid()}")
|
||||||
|
|
||||||
val txFee: ULong = wallet.calculateFee(tx)
|
val txFee: ULong = 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)
|
||||||
println("Tx fee rate is: ${feeRate.toSatPerVbCeil()} sat/vB")
|
println("Tx fee rate is: ${feeRate.toSatPerVbCeil()} sat/vB")
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ 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.db"
|
||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
@@ -72,7 +72,7 @@ class OfflineWalletTest {
|
|||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = 0uL,
|
expected = 0uL,
|
||||||
actual = wallet.getBalance().total
|
actual = wallet.getBalance().total.toSat()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
80
bdk-ffi/Cargo.lock
generated
80
bdk-ffi/Cargo.lock
generated
@@ -138,9 +138,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bdk"
|
name = "bdk"
|
||||||
version = "1.0.0-alpha.10"
|
version = "1.0.0-alpha.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "66fc0ebc2a63463f709cfdfbb7e7877b9975bcaea9d2d4f02f97ad012de37e3b"
|
checksum = "65c23f2903ac5dbb7b35934ae319aadc946201e4fa51b652440bd1c8fa3080ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bdk_chain",
|
"bdk_chain",
|
||||||
@@ -157,10 +157,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bdk-ffi"
|
name = "bdk-ffi"
|
||||||
version = "1.0.0-alpha.10"
|
version = "1.0.0-alpha.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
"bdk",
|
"bdk",
|
||||||
|
"bdk_electrum",
|
||||||
"bdk_esplora",
|
"bdk_esplora",
|
||||||
"bdk_file_store",
|
"bdk_file_store",
|
||||||
"bitcoin-internals",
|
"bitcoin-internals",
|
||||||
@@ -170,9 +171,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bdk_chain"
|
name = "bdk_chain"
|
||||||
version = "0.13.0"
|
version = "0.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e879c03ebf3a64643295152a19a8b0e0a3af22e25539d2bc56ce07d07b059c33"
|
checksum = "440ec5b1c8911f126b540e05c98493b699b497a3cb90c5e9c5eee21cdd8d1e01"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
"miniscript",
|
"miniscript",
|
||||||
@@ -180,10 +181,20 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bdk_esplora"
|
name = "bdk_electrum"
|
||||||
version = "0.12.0"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0aad9d99b103cd9c67ce1f4702720f2813db7aeba72abc9628ae9b00462a492"
|
checksum = "44bbf3b0031651a37a48bdfab0c1d96a305b587f616593d34df9b1ff63efc4ff"
|
||||||
|
dependencies = [
|
||||||
|
"bdk_chain",
|
||||||
|
"electrum-client",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bdk_esplora"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fb5b46f8c256bc083640342bd0d35ec1963971f18800c3fee1a9189eda60ecd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bdk_chain",
|
"bdk_chain",
|
||||||
"esplora-client",
|
"esplora-client",
|
||||||
@@ -191,9 +202,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bdk_file_store"
|
name = "bdk_file_store"
|
||||||
version = "0.10.0"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "492a011ee853773bce14f2d899fa34fe3ac3b5f39eeb1504d0d2b28de448bd73"
|
checksum = "5dfd7e9a5edb8d384ea1836b0bcd4febdd3211815acc058d64c7e284776d69ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bdk_chain",
|
"bdk_chain",
|
||||||
@@ -204,9 +215,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bdk_persist"
|
name = "bdk_persist"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6f7d6b38071ee828329434f86799e0bb6aaa5a4256e225480c2c53b7b2df295"
|
checksum = "aba103c2108dd0f0b452650043d21c449ae07ce866dbaea29a9c59899a5964f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bdk_chain",
|
"bdk_chain",
|
||||||
@@ -286,6 +297,12 @@ version = "3.16.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@@ -382,6 +399,23 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "electrum-client"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89008f106be6f303695522f2f4c1f28b40c3e8367ed8b3bb227f1f882cb52cc2"
|
||||||
|
dependencies = [
|
||||||
|
"bitcoin",
|
||||||
|
"byteorder",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"rustls",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"webpki-roots",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "esplora-client"
|
name = "esplora-client"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -1129,6 +1163,28 @@ dependencies = [
|
|||||||
"nom",
|
"nom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|||||||
@@ -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,9 +18,10 @@ 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 = { version = "1.0.0-alpha.11", features = ["all-keys", "keys-bip39"] }
|
||||||
bdk_esplora = { version = "0.12.0", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] }
|
bdk_esplora = { version = "0.13.0", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] }
|
||||||
bdk_file_store = { version = "0.10.0" }
|
bdk_electrum = { version = "0.13.0" }
|
||||||
|
bdk_file_store = { version = "0.11.0" }
|
||||||
|
|
||||||
uniffi = { version = "=0.26.1" }
|
uniffi = { version = "=0.26.1" }
|
||||||
bitcoin-internals = { version = "0.2.0", features = ["alloc"] }
|
bitcoin-internals = { version = "0.2.0", features = ["alloc"] }
|
||||||
|
|||||||
@@ -101,6 +101,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,6 +152,19 @@ enum FeeRateError {
|
|||||||
"ArithmeticOverflow"
|
"ArithmeticOverflow"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Error]
|
||||||
|
interface ParseAmountError {
|
||||||
|
Negative();
|
||||||
|
TooBig();
|
||||||
|
TooPrecise();
|
||||||
|
InvalidFormat();
|
||||||
|
InputTooLarge();
|
||||||
|
InvalidCharacter(string error_message);
|
||||||
|
UnknownDenomination(string error_message);
|
||||||
|
PossiblyConfusingDenomination(string error_message);
|
||||||
|
OtherParseAmountErr();
|
||||||
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
interface PersistenceError {
|
interface PersistenceError {
|
||||||
Write(string error_message);
|
Write(string error_message);
|
||||||
@@ -185,6 +219,7 @@ interface WalletCreationError {
|
|||||||
NotInitialized();
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@@ -203,17 +238,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 {
|
||||||
@@ -257,6 +292,9 @@ interface Wallet {
|
|||||||
[Throws=WalletCreationError]
|
[Throws=WalletCreationError]
|
||||||
constructor(Descriptor descriptor, Descriptor? change_descriptor, string persistence_backend_path, Network network);
|
constructor(Descriptor descriptor, Descriptor? change_descriptor, string persistence_backend_path, Network network);
|
||||||
|
|
||||||
|
[Name=new_no_persist, Throws=DescriptorError]
|
||||||
|
constructor(Descriptor descriptor, Descriptor? change_descriptor, Network network);
|
||||||
|
|
||||||
[Throws=PersistenceError]
|
[Throws=PersistenceError]
|
||||||
AddressInfo reveal_next_address(KeychainKind keychain);
|
AddressInfo reveal_next_address(KeychainKind keychain);
|
||||||
|
|
||||||
@@ -302,7 +340,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);
|
||||||
|
|
||||||
@@ -450,18 +488,36 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@@ -526,6 +582,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 {
|
||||||
@@ -543,6 +605,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 +630,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,60 @@
|
|||||||
use crate::error::{AddressError, FeeRateError, PsbtParseError, TransactionError};
|
use crate::error::{AddressError, FeeRateError, PsbtParseError, TransactionError};
|
||||||
|
|
||||||
use bdk::bitcoin::address::{NetworkChecked, NetworkUnchecked};
|
use bdk::bitcoin::address::{NetworkChecked, NetworkUnchecked};
|
||||||
|
use bdk::bitcoin::amount::ParseAmountError;
|
||||||
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
|
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
|
||||||
use bdk::bitcoin::blockdata::transaction::TxOut as BdkTxOut;
|
use bdk::bitcoin::blockdata::transaction::TxOut as BdkTxOut;
|
||||||
use bdk::bitcoin::consensus::encode::serialize;
|
use bdk::bitcoin::consensus::encode::serialize;
|
||||||
use bdk::bitcoin::consensus::Decodable;
|
use bdk::bitcoin::consensus::Decodable;
|
||||||
use bdk::bitcoin::psbt::ExtractTxError;
|
use bdk::bitcoin::psbt::ExtractTxError;
|
||||||
use bdk::bitcoin::Address as BdkAddress;
|
use bdk::bitcoin::Address as BdkAddress;
|
||||||
|
use bdk::bitcoin::Amount as BdkAmount;
|
||||||
use bdk::bitcoin::FeeRate as BdkFeeRate;
|
use bdk::bitcoin::FeeRate as BdkFeeRate;
|
||||||
use bdk::bitcoin::Network;
|
use bdk::bitcoin::Network;
|
||||||
use bdk::bitcoin::OutPoint as BdkOutPoint;
|
use bdk::bitcoin::OutPoint as BdkOutPoint;
|
||||||
use bdk::bitcoin::Psbt as BdkPsbt;
|
use bdk::bitcoin::Psbt as BdkPsbt;
|
||||||
use bdk::bitcoin::Transaction as BdkTransaction;
|
use bdk::bitcoin::Transaction as BdkTransaction;
|
||||||
|
use bdk::bitcoin::TxIn as BdkTxIn;
|
||||||
use bdk::bitcoin::Txid;
|
use bdk::bitcoin::Txid;
|
||||||
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
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);
|
||||||
|
|
||||||
@@ -132,6 +169,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 {
|
||||||
@@ -202,6 +251,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,
|
||||||
|
|||||||
95
bdk-ffi/src/electrum.rs
Normal file
95
bdk-ffi/src/electrum.rs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
use crate::bitcoin::Transaction;
|
||||||
|
use crate::error::ElectrumError;
|
||||||
|
use crate::types::{FullScanRequest, SyncRequest};
|
||||||
|
use crate::wallet::Update;
|
||||||
|
|
||||||
|
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_electrum::electrum_client::{Client as BdkBlockingClient, ElectrumApi};
|
||||||
|
use bdk_electrum::{ElectrumExt, ElectrumFullScanResult, ElectrumSyncResult};
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct ElectrumClient(BdkBlockingClient);
|
||||||
|
|
||||||
|
impl ElectrumClient {
|
||||||
|
pub fn new(url: String) -> Result<Self, ElectrumError> {
|
||||||
|
let client = BdkBlockingClient::new(url.as_str())?;
|
||||||
|
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 = bdk::wallet::Update {
|
||||||
|
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 = bdk::wallet::Update {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,16 +13,20 @@ use bdk::wallet::error::CreateTxError as BdkCreateTxError;
|
|||||||
use bdk::wallet::signer::SignerError as BdkSignerError;
|
use bdk::wallet::signer::SignerError as BdkSignerError;
|
||||||
use bdk::wallet::tx_builder::AddUtxoError;
|
use bdk::wallet::tx_builder::AddUtxoError;
|
||||||
use bdk::wallet::NewOrLoadError;
|
use bdk::wallet::NewOrLoadError;
|
||||||
|
use bdk_electrum::electrum_client::Error as BdkElectrumError;
|
||||||
use bdk_esplora::esplora_client::{Error as BdkEsploraError, Error};
|
use bdk_esplora::esplora_client::{Error as BdkEsploraError, Error};
|
||||||
use bdk_file_store::FileError as BdkFileError;
|
use bdk_file_store::FileError as BdkFileError;
|
||||||
use bitcoin_internals::hex::display::DisplayHex;
|
use bitcoin_internals::hex::display::DisplayHex;
|
||||||
|
|
||||||
|
use bdk::bitcoin::amount::ParseAmountError as BdkParseAmountError;
|
||||||
|
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use bdk::bitcoin::address::Error as BdkAddressError;
|
use bdk::bitcoin::address::Error as BdkAddressError;
|
||||||
use bdk::bitcoin::consensus::encode::Error as BdkEncodeError;
|
use bdk::bitcoin::consensus::encode::Error as BdkEncodeError;
|
||||||
use bdk::bitcoin::psbt::ExtractTxError as BdkExtractTxError;
|
use bdk::bitcoin::psbt::ExtractTxError as BdkExtractTxError;
|
||||||
use bdk::chain::local_chain::CannotConnectError as BdkCannotConnectError;
|
use bdk::chain::local_chain::CannotConnectError as BdkCannotConnectError;
|
||||||
|
use bdk::KeychainKind;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// error definitions
|
// error definitions
|
||||||
@@ -65,37 +69,37 @@ pub enum AddressError {
|
|||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Bip32Error {
|
pub enum Bip32Error {
|
||||||
#[error("Cannot derive from a hardened key")]
|
#[error("cannot derive from a hardened key")]
|
||||||
CannotDeriveFromHardenedKey,
|
CannotDeriveFromHardenedKey,
|
||||||
|
|
||||||
#[error("Secp256k1 error: {error_message}")]
|
#[error("secp256k1 error: {error_message}")]
|
||||||
Secp256k1 { error_message: String },
|
Secp256k1 { error_message: String },
|
||||||
|
|
||||||
#[error("Invalid child number: {child_number}")]
|
#[error("invalid child number: {child_number}")]
|
||||||
InvalidChildNumber { child_number: u32 },
|
InvalidChildNumber { child_number: u32 },
|
||||||
|
|
||||||
#[error("Invalid format for child number")]
|
#[error("invalid format for child number")]
|
||||||
InvalidChildNumberFormat,
|
InvalidChildNumberFormat,
|
||||||
|
|
||||||
#[error("Invalid derivation path format")]
|
#[error("invalid derivation path format")]
|
||||||
InvalidDerivationPathFormat,
|
InvalidDerivationPathFormat,
|
||||||
|
|
||||||
#[error("Unknown version: {version}")]
|
#[error("unknown version: {version}")]
|
||||||
UnknownVersion { version: String },
|
UnknownVersion { version: String },
|
||||||
|
|
||||||
#[error("Wrong extended key length: {length}")]
|
#[error("wrong extended key length: {length}")]
|
||||||
WrongExtendedKeyLength { length: u32 },
|
WrongExtendedKeyLength { length: u32 },
|
||||||
|
|
||||||
#[error("Base58 error: {error_message}")]
|
#[error("base58 error: {error_message}")]
|
||||||
Base58 { error_message: String },
|
Base58 { error_message: String },
|
||||||
|
|
||||||
#[error("Hexadecimal conversion error: {error_message}")]
|
#[error("hexadecimal conversion error: {error_message}")]
|
||||||
Hex { error_message: String },
|
Hex { error_message: String },
|
||||||
|
|
||||||
#[error("Invalid public key hex length: {length}")]
|
#[error("invalid public key hex length: {length}")]
|
||||||
InvalidPublicKeyHexLength { length: u32 },
|
InvalidPublicKeyHexLength { length: u32 },
|
||||||
|
|
||||||
#[error("Unknown error: {error_message}")]
|
#[error("unknown error: {error_message}")]
|
||||||
UnknownError { error_message: String },
|
UnknownError { error_message: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,70 +138,70 @@ pub enum CannotConnectError {
|
|||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum CreateTxError {
|
pub enum CreateTxError {
|
||||||
#[error("Descriptor error: {error_message}")]
|
#[error("descriptor error: {error_message}")]
|
||||||
Descriptor { error_message: String },
|
Descriptor { error_message: String },
|
||||||
|
|
||||||
#[error("Persistence failure: {error_message}")]
|
#[error("persistence failure: {error_message}")]
|
||||||
Persist { error_message: String },
|
Persist { error_message: String },
|
||||||
|
|
||||||
#[error("Policy error: {error_message}")]
|
#[error("policy error: {error_message}")]
|
||||||
Policy { error_message: String },
|
Policy { error_message: String },
|
||||||
|
|
||||||
#[error("Spending policy required for {kind}")]
|
#[error("spending policy required for {kind}")]
|
||||||
SpendingPolicyRequired { kind: String },
|
SpendingPolicyRequired { kind: String },
|
||||||
|
|
||||||
#[error("Unsupported version 0")]
|
#[error("unsupported version 0")]
|
||||||
Version0,
|
Version0,
|
||||||
|
|
||||||
#[error("Unsupported version 1 with CSV")]
|
#[error("unsupported version 1 with csv")]
|
||||||
Version1Csv,
|
Version1Csv,
|
||||||
|
|
||||||
#[error("Lock time conflict: requested {requested}, but required {required}")]
|
#[error("lock time conflict: requested {requested}, but required {required}")]
|
||||||
LockTime { requested: String, required: String },
|
LockTime { requested: String, required: String },
|
||||||
|
|
||||||
#[error("Transaction requires RBF sequence number")]
|
#[error("transaction requires rbf sequence number")]
|
||||||
RbfSequence,
|
RbfSequence,
|
||||||
|
|
||||||
#[error("RBF sequence: {rbf}, CSV sequence: {csv}")]
|
#[error("rbf sequence: {rbf}, csv sequence: {csv}")]
|
||||||
RbfSequenceCsv { rbf: String, csv: String },
|
RbfSequenceCsv { rbf: String, csv: String },
|
||||||
|
|
||||||
#[error("Fee too low: {required} sat required")]
|
#[error("fee too low: {required} sat required")]
|
||||||
FeeTooLow { required: u64 },
|
FeeTooLow { required: u64 },
|
||||||
|
|
||||||
#[error("Fee rate too low: {required}")]
|
#[error("fee rate too low: {required}")]
|
||||||
FeeRateTooLow { required: String },
|
FeeRateTooLow { required: String },
|
||||||
|
|
||||||
#[error("No UTXOs selected for the transaction")]
|
#[error("no utxos selected for the transaction")]
|
||||||
NoUtxosSelected,
|
NoUtxosSelected,
|
||||||
|
|
||||||
#[error("Output value below dust limit at index {index}")]
|
#[error("output value below dust limit at index {index}")]
|
||||||
OutputBelowDustLimit { index: u64 },
|
OutputBelowDustLimit { index: u64 },
|
||||||
|
|
||||||
#[error("Change policy descriptor error")]
|
#[error("change policy descriptor error")]
|
||||||
ChangePolicyDescriptor,
|
ChangePolicyDescriptor,
|
||||||
|
|
||||||
#[error("Coin selection failed: {error_message}")]
|
#[error("coin selection failed: {error_message}")]
|
||||||
CoinSelection { error_message: String },
|
CoinSelection { error_message: String },
|
||||||
|
|
||||||
#[error("Insufficient funds: needed {needed} sat, available {available} sat")]
|
#[error("insufficient funds: needed {needed} sat, available {available} sat")]
|
||||||
InsufficientFunds { needed: u64, available: u64 },
|
InsufficientFunds { needed: u64, available: u64 },
|
||||||
|
|
||||||
#[error("Transaction has no recipients")]
|
#[error("transaction has no recipients")]
|
||||||
NoRecipients,
|
NoRecipients,
|
||||||
|
|
||||||
#[error("PSBT creation error: {error_message}")]
|
#[error("psbt creation error: {error_message}")]
|
||||||
Psbt { error_message: String },
|
Psbt { error_message: String },
|
||||||
|
|
||||||
#[error("Missing key origin for: {key}")]
|
#[error("missing key origin for: {key}")]
|
||||||
MissingKeyOrigin { key: String },
|
MissingKeyOrigin { key: String },
|
||||||
|
|
||||||
#[error("Reference to an unknown UTXO: {outpoint}")]
|
#[error("reference to an unknown utxo: {outpoint}")]
|
||||||
UnknownUtxo { outpoint: String },
|
UnknownUtxo { outpoint: String },
|
||||||
|
|
||||||
#[error("Missing non-witness UTXO for outpoint: {outpoint}")]
|
#[error("missing non-witness utxo for outpoint: {outpoint}")]
|
||||||
MissingNonWitnessUtxo { outpoint: String },
|
MissingNonWitnessUtxo { outpoint: String },
|
||||||
|
|
||||||
#[error("Miniscript PSBT error: {error_message}")]
|
#[error("miniscript psbt error: {error_message}")]
|
||||||
MiniscriptPsbt { error_message: String },
|
MiniscriptPsbt { error_message: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,19 +228,19 @@ pub enum DescriptorError {
|
|||||||
#[error("invalid descriptor character: {char}")]
|
#[error("invalid descriptor character: {char}")]
|
||||||
InvalidDescriptorCharacter { char: String },
|
InvalidDescriptorCharacter { char: String },
|
||||||
|
|
||||||
#[error("BIP32 error: {error_message}")]
|
#[error("bip32 error: {error_message}")]
|
||||||
Bip32 { error_message: String },
|
Bip32 { error_message: String },
|
||||||
|
|
||||||
#[error("Base58 error: {error_message}")]
|
#[error("base58 error: {error_message}")]
|
||||||
Base58 { error_message: String },
|
Base58 { error_message: String },
|
||||||
|
|
||||||
#[error("Key-related error: {error_message}")]
|
#[error("key-related error: {error_message}")]
|
||||||
Pk { error_message: String },
|
Pk { error_message: String },
|
||||||
|
|
||||||
#[error("Miniscript error: {error_message}")]
|
#[error("miniscript error: {error_message}")]
|
||||||
Miniscript { error_message: String },
|
Miniscript { error_message: String },
|
||||||
|
|
||||||
#[error("Hex decoding error: {error_message}")]
|
#[error("hex decoding error: {error_message}")]
|
||||||
Hex { error_message: String },
|
Hex { error_message: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +256,60 @@ pub enum DescriptorKeyError {
|
|||||||
Bip32 { error_message: String },
|
Bip32 { error_message: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ElectrumError {
|
||||||
|
#[error("{error_message}")]
|
||||||
|
IOError { error_message: String },
|
||||||
|
|
||||||
|
#[error("{error_message}")]
|
||||||
|
Json { error_message: String },
|
||||||
|
|
||||||
|
#[error("{error_message}")]
|
||||||
|
Hex { error_message: String },
|
||||||
|
|
||||||
|
#[error("electrum server error: {error_message}")]
|
||||||
|
Protocol { error_message: String },
|
||||||
|
|
||||||
|
#[error("{error_message}")]
|
||||||
|
Bitcoin { error_message: String },
|
||||||
|
|
||||||
|
#[error("already subscribed to the notifications of an address")]
|
||||||
|
AlreadySubscribed,
|
||||||
|
|
||||||
|
#[error("not subscribed to the notifications of an address")]
|
||||||
|
NotSubscribed,
|
||||||
|
|
||||||
|
#[error("error during the deserialization of a response from the server: {error_message}")]
|
||||||
|
InvalidResponse { error_message: String },
|
||||||
|
|
||||||
|
#[error("{error_message}")]
|
||||||
|
Message { error_message: String },
|
||||||
|
|
||||||
|
#[error("invalid domain name {domain} not matching SSL certificate")]
|
||||||
|
InvalidDNSNameError { domain: String },
|
||||||
|
|
||||||
|
#[error("missing domain while it was explicitly asked to validate it")]
|
||||||
|
MissingDomain,
|
||||||
|
|
||||||
|
#[error("made one or multiple attempts, all errored")]
|
||||||
|
AllAttemptsErrored,
|
||||||
|
|
||||||
|
#[error("{error_message}")]
|
||||||
|
SharedIOError { error_message: String },
|
||||||
|
|
||||||
|
#[error("couldn't take a lock on the reader mutex. This means that there's already another reader thread is running")]
|
||||||
|
CouldntLockReader,
|
||||||
|
|
||||||
|
#[error("broken IPC communication channel: the other thread probably has exited")]
|
||||||
|
Mpsc,
|
||||||
|
|
||||||
|
#[error("{error_message}")]
|
||||||
|
CouldNotCreateConnection { error_message: String },
|
||||||
|
|
||||||
|
#[error("the request has already been consumed")]
|
||||||
|
RequestAlreadyConsumed,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum EsploraError {
|
pub enum EsploraError {
|
||||||
#[error("minreq error: {error_message}")]
|
#[error("minreq error: {error_message}")]
|
||||||
@@ -263,7 +321,7 @@ pub enum EsploraError {
|
|||||||
#[error("parsing error: {error_message}")]
|
#[error("parsing error: {error_message}")]
|
||||||
Parsing { error_message: String },
|
Parsing { error_message: String },
|
||||||
|
|
||||||
#[error("Invalid status code, unable to convert to u16: {error_message}")]
|
#[error("invalid status code, unable to convert to u16: {error_message}")]
|
||||||
StatusCode { error_message: String },
|
StatusCode { error_message: String },
|
||||||
|
|
||||||
#[error("bitcoin encoding error: {error_message}")]
|
#[error("bitcoin encoding error: {error_message}")]
|
||||||
@@ -284,10 +342,10 @@ pub enum EsploraError {
|
|||||||
#[error("header hash not found")]
|
#[error("header hash not found")]
|
||||||
HeaderHashNotFound,
|
HeaderHashNotFound,
|
||||||
|
|
||||||
#[error("invalid HTTP header name: {name}")]
|
#[error("invalid http header name: {name}")]
|
||||||
InvalidHttpHeaderName { name: String },
|
InvalidHttpHeaderName { name: String },
|
||||||
|
|
||||||
#[error("invalid HTTP header value: {value}")]
|
#[error("invalid http header value: {value}")]
|
||||||
InvalidHttpHeaderValue { value: String },
|
InvalidHttpHeaderValue { value: String },
|
||||||
|
|
||||||
#[error("the request has already been consumed")]
|
#[error("the request has already been consumed")]
|
||||||
@@ -317,6 +375,37 @@ pub enum FeeRateError {
|
|||||||
ArithmeticOverflow,
|
ArithmeticOverflow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ParseAmountError {
|
||||||
|
#[error("amount is negative")]
|
||||||
|
Negative,
|
||||||
|
|
||||||
|
#[error("amount is too large")]
|
||||||
|
TooBig,
|
||||||
|
|
||||||
|
#[error("amount is too precise")]
|
||||||
|
TooPrecise,
|
||||||
|
|
||||||
|
#[error("invalid amount format")]
|
||||||
|
InvalidFormat,
|
||||||
|
|
||||||
|
#[error("input is too large")]
|
||||||
|
InputTooLarge,
|
||||||
|
|
||||||
|
#[error("invalid character: {error_message}")]
|
||||||
|
InvalidCharacter { error_message: String },
|
||||||
|
|
||||||
|
#[error("unknown denomination: {error_message}")]
|
||||||
|
UnknownDenomination { error_message: String },
|
||||||
|
|
||||||
|
#[error("possibly confusing denomination: {error_message}")]
|
||||||
|
PossiblyConfusingDenomination { error_message: String },
|
||||||
|
|
||||||
|
// Has to handle non-exhaustive
|
||||||
|
#[error("unknown parse amount error")]
|
||||||
|
OtherParseAmountErr,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum PersistenceError {
|
pub enum PersistenceError {
|
||||||
#[error("writing to persistence error: {error_message}")]
|
#[error("writing to persistence error: {error_message}")]
|
||||||
@@ -325,61 +414,61 @@ pub enum PersistenceError {
|
|||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum PsbtParseError {
|
pub enum PsbtParseError {
|
||||||
#[error("error in internal PSBT data structure: {error_message}")]
|
#[error("error in internal psbt data structure: {error_message}")]
|
||||||
PsbtEncoding { error_message: String },
|
PsbtEncoding { error_message: String },
|
||||||
|
|
||||||
#[error("error in PSBT base64 encoding: {error_message}")]
|
#[error("error in psbt base64 encoding: {error_message}")]
|
||||||
Base64Encoding { error_message: String },
|
Base64Encoding { error_message: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum SignerError {
|
pub enum SignerError {
|
||||||
#[error("Missing key for signing")]
|
#[error("missing key for signing")]
|
||||||
MissingKey,
|
MissingKey,
|
||||||
|
|
||||||
#[error("Invalid key provided")]
|
#[error("invalid key provided")]
|
||||||
InvalidKey,
|
InvalidKey,
|
||||||
|
|
||||||
#[error("User canceled operation")]
|
#[error("user canceled operation")]
|
||||||
UserCanceled,
|
UserCanceled,
|
||||||
|
|
||||||
#[error("Input index out of range")]
|
#[error("input index out of range")]
|
||||||
InputIndexOutOfRange,
|
InputIndexOutOfRange,
|
||||||
|
|
||||||
#[error("Missing non-witness UTXO information")]
|
#[error("missing non-witness utxo information")]
|
||||||
MissingNonWitnessUtxo,
|
MissingNonWitnessUtxo,
|
||||||
|
|
||||||
#[error("Invalid non-witness UTXO information provided")]
|
#[error("invalid non-witness utxo information provided")]
|
||||||
InvalidNonWitnessUtxo,
|
InvalidNonWitnessUtxo,
|
||||||
|
|
||||||
#[error("Missing witness UTXO")]
|
#[error("missing witness utxo")]
|
||||||
MissingWitnessUtxo,
|
MissingWitnessUtxo,
|
||||||
|
|
||||||
#[error("Missing witness script")]
|
#[error("missing witness script")]
|
||||||
MissingWitnessScript,
|
MissingWitnessScript,
|
||||||
|
|
||||||
#[error("Missing HD keypath")]
|
#[error("missing hd keypath")]
|
||||||
MissingHdKeypath,
|
MissingHdKeypath,
|
||||||
|
|
||||||
#[error("Non-standard sighash type used")]
|
#[error("non-standard sighash type used")]
|
||||||
NonStandardSighash,
|
NonStandardSighash,
|
||||||
|
|
||||||
#[error("Invalid sighash type provided")]
|
#[error("invalid sighash type provided")]
|
||||||
InvalidSighash,
|
InvalidSighash,
|
||||||
|
|
||||||
#[error("Error with sighash computation: {error_message}")]
|
#[error("error with sighash computation: {error_message}")]
|
||||||
SighashError { error_message: String },
|
SighashError { error_message: String },
|
||||||
|
|
||||||
#[error("Miniscript Psbt error: {error_message}")]
|
#[error("miniscript psbt error: {error_message}")]
|
||||||
MiniscriptPsbt { error_message: String },
|
MiniscriptPsbt { error_message: String },
|
||||||
|
|
||||||
#[error("External error: {error_message}")]
|
#[error("external error: {error_message}")]
|
||||||
External { error_message: String },
|
External { error_message: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum TransactionError {
|
pub enum TransactionError {
|
||||||
#[error("IO error")]
|
#[error("io error")]
|
||||||
Io,
|
Io,
|
||||||
|
|
||||||
#[error("allocation of oversized vector")]
|
#[error("allocation of oversized vector")]
|
||||||
@@ -388,7 +477,7 @@ pub enum TransactionError {
|
|||||||
#[error("invalid checksum: expected={expected} actual={actual}")]
|
#[error("invalid checksum: expected={expected} actual={actual}")]
|
||||||
InvalidChecksum { expected: String, actual: String },
|
InvalidChecksum { expected: String, actual: String },
|
||||||
|
|
||||||
#[error("non-minimal varint")]
|
#[error("non-minimal var int")]
|
||||||
NonMinimalVarInt,
|
NonMinimalVarInt,
|
||||||
|
|
||||||
#[error("parse failed")]
|
#[error("parse failed")]
|
||||||
@@ -434,6 +523,9 @@ pub enum WalletCreationError {
|
|||||||
expected: Network,
|
expected: Network,
|
||||||
got: Option<Network>,
|
got: Option<Network>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("loaded descriptor '{got}' does not match what was provided '{keychain:?}'")]
|
||||||
|
LoadedDescriptorDoesNotMatch { got: String, keychain: KeychainKind },
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@@ -466,6 +558,51 @@ impl From<BdkAddressError> for AddressError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<BdkElectrumError> for ElectrumError {
|
||||||
|
fn from(error: BdkElectrumError) -> Self {
|
||||||
|
match error {
|
||||||
|
BdkElectrumError::IOError(e) => ElectrumError::IOError {
|
||||||
|
error_message: e.to_string(),
|
||||||
|
},
|
||||||
|
BdkElectrumError::JSON(e) => ElectrumError::Json {
|
||||||
|
error_message: e.to_string(),
|
||||||
|
},
|
||||||
|
BdkElectrumError::Hex(e) => ElectrumError::Hex {
|
||||||
|
error_message: e.to_string(),
|
||||||
|
},
|
||||||
|
BdkElectrumError::Protocol(e) => ElectrumError::Protocol {
|
||||||
|
error_message: e.to_string(),
|
||||||
|
},
|
||||||
|
BdkElectrumError::Bitcoin(e) => ElectrumError::Bitcoin {
|
||||||
|
error_message: e.to_string(),
|
||||||
|
},
|
||||||
|
BdkElectrumError::AlreadySubscribed(_) => ElectrumError::AlreadySubscribed,
|
||||||
|
BdkElectrumError::NotSubscribed(_) => ElectrumError::NotSubscribed,
|
||||||
|
BdkElectrumError::InvalidResponse(e) => ElectrumError::InvalidResponse {
|
||||||
|
error_message: e.to_string(),
|
||||||
|
},
|
||||||
|
BdkElectrumError::Message(e) => ElectrumError::Message {
|
||||||
|
error_message: e.to_string(),
|
||||||
|
},
|
||||||
|
BdkElectrumError::InvalidDNSNameError(domain) => {
|
||||||
|
ElectrumError::InvalidDNSNameError { domain }
|
||||||
|
}
|
||||||
|
BdkElectrumError::MissingDomain => ElectrumError::MissingDomain,
|
||||||
|
BdkElectrumError::AllAttemptsErrored(_) => ElectrumError::AllAttemptsErrored,
|
||||||
|
BdkElectrumError::SharedIOError(e) => ElectrumError::SharedIOError {
|
||||||
|
error_message: e.to_string(),
|
||||||
|
},
|
||||||
|
BdkElectrumError::CouldntLockReader => ElectrumError::CouldntLockReader,
|
||||||
|
BdkElectrumError::Mpsc => ElectrumError::Mpsc,
|
||||||
|
BdkElectrumError::CouldNotCreateConnection(error_message) => {
|
||||||
|
ElectrumError::CouldNotCreateConnection {
|
||||||
|
error_message: error_message.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<ParseError> for AddressError {
|
impl From<ParseError> for AddressError {
|
||||||
fn from(error: ParseError) -> Self {
|
fn from(error: ParseError) -> Self {
|
||||||
match error {
|
match error {
|
||||||
@@ -800,6 +937,28 @@ impl From<BdkExtractTxError> for ExtractTxError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<BdkParseAmountError> for ParseAmountError {
|
||||||
|
fn from(error: BdkParseAmountError) -> Self {
|
||||||
|
match error {
|
||||||
|
BdkParseAmountError::Negative => ParseAmountError::Negative,
|
||||||
|
BdkParseAmountError::TooBig => ParseAmountError::TooBig,
|
||||||
|
BdkParseAmountError::InvalidFormat => ParseAmountError::InvalidFormat,
|
||||||
|
BdkParseAmountError::TooPrecise => ParseAmountError::TooPrecise,
|
||||||
|
BdkParseAmountError::InputTooLarge => ParseAmountError::InputTooLarge,
|
||||||
|
BdkParseAmountError::InvalidCharacter(c) => ParseAmountError::InvalidCharacter {
|
||||||
|
error_message: c.to_string(),
|
||||||
|
},
|
||||||
|
BdkParseAmountError::UnknownDenomination(s) => {
|
||||||
|
ParseAmountError::UnknownDenomination { error_message: s }
|
||||||
|
}
|
||||||
|
BdkParseAmountError::PossiblyConfusingDenomination(s) => {
|
||||||
|
ParseAmountError::PossiblyConfusingDenomination { error_message: s }
|
||||||
|
}
|
||||||
|
_ => ParseAmountError::OtherParseAmountErr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for PersistenceError {
|
impl From<std::io::Error> for PersistenceError {
|
||||||
fn from(error: std::io::Error) -> Self {
|
fn from(error: std::io::Error) -> Self {
|
||||||
PersistenceError::Write {
|
PersistenceError::Write {
|
||||||
@@ -902,6 +1061,12 @@ impl From<NewOrLoadError> for WalletCreationError {
|
|||||||
NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => {
|
NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => {
|
||||||
WalletCreationError::LoadedNetworkDoesNotMatch { expected, got }
|
WalletCreationError::LoadedNetworkDoesNotMatch { expected, got }
|
||||||
}
|
}
|
||||||
|
NewOrLoadError::LoadedDescriptorDoesNotMatch { got, keychain } => {
|
||||||
|
WalletCreationError::LoadedDescriptorDoesNotMatch {
|
||||||
|
got: format!("{:?}", got),
|
||||||
|
keychain,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -914,13 +1079,15 @@ impl From<NewOrLoadError> for WalletCreationError {
|
|||||||
mod test {
|
mod test {
|
||||||
use crate::error::{
|
use crate::error::{
|
||||||
AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError,
|
AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError,
|
||||||
DescriptorKeyError, EsploraError, ExtractTxError, FeeRateError, PersistenceError,
|
DescriptorKeyError, ElectrumError, EsploraError, ExtractTxError, FeeRateError,
|
||||||
PsbtParseError, TransactionError, TxidParseError, WalletCreationError,
|
ParseAmountError, PersistenceError, PsbtParseError, TransactionError, TxidParseError,
|
||||||
|
WalletCreationError,
|
||||||
};
|
};
|
||||||
use crate::CalculateFeeError;
|
use crate::CalculateFeeError;
|
||||||
use crate::OutPoint;
|
use crate::OutPoint;
|
||||||
use crate::SignerError;
|
use crate::SignerError;
|
||||||
use bdk::bitcoin::Network;
|
use bdk::bitcoin::Network;
|
||||||
|
use bdk::KeychainKind;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_error_address() {
|
fn test_error_address() {
|
||||||
@@ -972,57 +1139,57 @@ mod test {
|
|||||||
let cases = vec![
|
let cases = vec![
|
||||||
(
|
(
|
||||||
Bip32Error::CannotDeriveFromHardenedKey,
|
Bip32Error::CannotDeriveFromHardenedKey,
|
||||||
"Cannot derive from a hardened key",
|
"cannot derive from a hardened key",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Bip32Error::Secp256k1 {
|
Bip32Error::Secp256k1 {
|
||||||
error_message: "failure".to_string(),
|
error_message: "failure".to_string(),
|
||||||
},
|
},
|
||||||
"Secp256k1 error: failure",
|
"secp256k1 error: failure",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Bip32Error::InvalidChildNumber { child_number: 123 },
|
Bip32Error::InvalidChildNumber { child_number: 123 },
|
||||||
"Invalid child number: 123",
|
"invalid child number: 123",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Bip32Error::InvalidChildNumberFormat,
|
Bip32Error::InvalidChildNumberFormat,
|
||||||
"Invalid format for child number",
|
"invalid format for child number",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Bip32Error::InvalidDerivationPathFormat,
|
Bip32Error::InvalidDerivationPathFormat,
|
||||||
"Invalid derivation path format",
|
"invalid derivation path format",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Bip32Error::UnknownVersion {
|
Bip32Error::UnknownVersion {
|
||||||
version: "0x123".to_string(),
|
version: "0x123".to_string(),
|
||||||
},
|
},
|
||||||
"Unknown version: 0x123",
|
"unknown version: 0x123",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Bip32Error::WrongExtendedKeyLength { length: 512 },
|
Bip32Error::WrongExtendedKeyLength { length: 512 },
|
||||||
"Wrong extended key length: 512",
|
"wrong extended key length: 512",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Bip32Error::Base58 {
|
Bip32Error::Base58 {
|
||||||
error_message: "error".to_string(),
|
error_message: "error".to_string(),
|
||||||
},
|
},
|
||||||
"Base58 error: error",
|
"base58 error: error",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Bip32Error::Hex {
|
Bip32Error::Hex {
|
||||||
error_message: "error".to_string(),
|
error_message: "error".to_string(),
|
||||||
},
|
},
|
||||||
"Hexadecimal conversion error: error",
|
"hexadecimal conversion error: error",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Bip32Error::InvalidPublicKeyHexLength { length: 66 },
|
Bip32Error::InvalidPublicKeyHexLength { length: 66 },
|
||||||
"Invalid public key hex length: 66",
|
"invalid public key hex length: 66",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Bip32Error::UnknownError {
|
Bip32Error::UnknownError {
|
||||||
error_message: "mystery".to_string(),
|
error_message: "mystery".to_string(),
|
||||||
},
|
},
|
||||||
"Unknown error: mystery",
|
"unknown error: mystery",
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1110,111 +1277,111 @@ mod test {
|
|||||||
CreateTxError::Descriptor {
|
CreateTxError::Descriptor {
|
||||||
error_message: "Descriptor failure".to_string(),
|
error_message: "Descriptor failure".to_string(),
|
||||||
},
|
},
|
||||||
"Descriptor error: Descriptor failure",
|
"descriptor error: Descriptor failure",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::Persist {
|
CreateTxError::Persist {
|
||||||
error_message: "Persistence error".to_string(),
|
error_message: "Persistence error".to_string(),
|
||||||
},
|
},
|
||||||
"Persistence failure: Persistence error",
|
"persistence failure: Persistence error",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::Policy {
|
CreateTxError::Policy {
|
||||||
error_message: "Policy violation".to_string(),
|
error_message: "Policy violation".to_string(),
|
||||||
},
|
},
|
||||||
"Policy error: Policy violation",
|
"policy error: Policy violation",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::SpendingPolicyRequired {
|
CreateTxError::SpendingPolicyRequired {
|
||||||
kind: "multisig".to_string(),
|
kind: "multisig".to_string(),
|
||||||
},
|
},
|
||||||
"Spending policy required for multisig",
|
"spending policy required for multisig",
|
||||||
),
|
),
|
||||||
(CreateTxError::Version0, "Unsupported version 0"),
|
(CreateTxError::Version0, "unsupported version 0"),
|
||||||
(CreateTxError::Version1Csv, "Unsupported version 1 with CSV"),
|
(CreateTxError::Version1Csv, "unsupported version 1 with csv"),
|
||||||
(
|
(
|
||||||
CreateTxError::LockTime {
|
CreateTxError::LockTime {
|
||||||
requested: "today".to_string(),
|
requested: "today".to_string(),
|
||||||
required: "tomorrow".to_string(),
|
required: "tomorrow".to_string(),
|
||||||
},
|
},
|
||||||
"Lock time conflict: requested today, but required tomorrow",
|
"lock time conflict: requested today, but required tomorrow",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::RbfSequence,
|
CreateTxError::RbfSequence,
|
||||||
"Transaction requires RBF sequence number",
|
"transaction requires rbf sequence number",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::RbfSequenceCsv {
|
CreateTxError::RbfSequenceCsv {
|
||||||
rbf: "123".to_string(),
|
rbf: "123".to_string(),
|
||||||
csv: "456".to_string(),
|
csv: "456".to_string(),
|
||||||
},
|
},
|
||||||
"RBF sequence: 123, CSV sequence: 456",
|
"rbf sequence: 123, csv sequence: 456",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::FeeTooLow { required: 1000 },
|
CreateTxError::FeeTooLow { required: 1000 },
|
||||||
"Fee too low: 1000 sat required",
|
"fee too low: 1000 sat required",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::FeeRateTooLow {
|
CreateTxError::FeeRateTooLow {
|
||||||
required: "5 sat/vB".to_string(),
|
required: "5 sat/vB".to_string(),
|
||||||
},
|
},
|
||||||
"Fee rate too low: 5 sat/vB",
|
"fee rate too low: 5 sat/vB",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::NoUtxosSelected,
|
CreateTxError::NoUtxosSelected,
|
||||||
"No UTXOs selected for the transaction",
|
"no utxos selected for the transaction",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::OutputBelowDustLimit { index: 2 },
|
CreateTxError::OutputBelowDustLimit { index: 2 },
|
||||||
"Output value below dust limit at index 2",
|
"output value below dust limit at index 2",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::ChangePolicyDescriptor,
|
CreateTxError::ChangePolicyDescriptor,
|
||||||
"Change policy descriptor error",
|
"change policy descriptor error",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::CoinSelection {
|
CreateTxError::CoinSelection {
|
||||||
error_message: "No suitable outputs".to_string(),
|
error_message: "No suitable outputs".to_string(),
|
||||||
},
|
},
|
||||||
"Coin selection failed: No suitable outputs",
|
"coin selection failed: No suitable outputs",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::InsufficientFunds {
|
CreateTxError::InsufficientFunds {
|
||||||
needed: 5000,
|
needed: 5000,
|
||||||
available: 3000,
|
available: 3000,
|
||||||
},
|
},
|
||||||
"Insufficient funds: needed 5000 sat, available 3000 sat",
|
"insufficient funds: needed 5000 sat, available 3000 sat",
|
||||||
),
|
),
|
||||||
(CreateTxError::NoRecipients, "Transaction has no recipients"),
|
(CreateTxError::NoRecipients, "transaction has no recipients"),
|
||||||
(
|
(
|
||||||
CreateTxError::Psbt {
|
CreateTxError::Psbt {
|
||||||
error_message: "PSBT creation failed".to_string(),
|
error_message: "PSBT creation failed".to_string(),
|
||||||
},
|
},
|
||||||
"PSBT creation error: PSBT creation failed",
|
"psbt creation error: PSBT creation failed",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::MissingKeyOrigin {
|
CreateTxError::MissingKeyOrigin {
|
||||||
key: "xpub...".to_string(),
|
key: "xpub...".to_string(),
|
||||||
},
|
},
|
||||||
"Missing key origin for: xpub...",
|
"missing key origin for: xpub...",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::UnknownUtxo {
|
CreateTxError::UnknownUtxo {
|
||||||
outpoint: "outpoint123".to_string(),
|
outpoint: "outpoint123".to_string(),
|
||||||
},
|
},
|
||||||
"Reference to an unknown UTXO: outpoint123",
|
"reference to an unknown utxo: outpoint123",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::MissingNonWitnessUtxo {
|
CreateTxError::MissingNonWitnessUtxo {
|
||||||
outpoint: "outpoint456".to_string(),
|
outpoint: "outpoint456".to_string(),
|
||||||
},
|
},
|
||||||
"Missing non-witness UTXO for outpoint: outpoint456",
|
"missing non-witness utxo for outpoint: outpoint456",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
CreateTxError::MiniscriptPsbt {
|
CreateTxError::MiniscriptPsbt {
|
||||||
error_message: "Miniscript error".to_string(),
|
error_message: "Miniscript error".to_string(),
|
||||||
},
|
},
|
||||||
"Miniscript PSBT error: Miniscript error",
|
"miniscript psbt error: Miniscript error",
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1261,31 +1428,31 @@ mod test {
|
|||||||
DescriptorError::Bip32 {
|
DescriptorError::Bip32 {
|
||||||
error_message: "Bip32 error".to_string(),
|
error_message: "Bip32 error".to_string(),
|
||||||
},
|
},
|
||||||
"BIP32 error: Bip32 error",
|
"bip32 error: Bip32 error",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
DescriptorError::Base58 {
|
DescriptorError::Base58 {
|
||||||
error_message: "Base58 decode error".to_string(),
|
error_message: "Base58 decode error".to_string(),
|
||||||
},
|
},
|
||||||
"Base58 error: Base58 decode error",
|
"base58 error: Base58 decode error",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
DescriptorError::Pk {
|
DescriptorError::Pk {
|
||||||
error_message: "Public key error".to_string(),
|
error_message: "Public key error".to_string(),
|
||||||
},
|
},
|
||||||
"Key-related error: Public key error",
|
"key-related error: Public key error",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
DescriptorError::Miniscript {
|
DescriptorError::Miniscript {
|
||||||
error_message: "Miniscript evaluation error".to_string(),
|
error_message: "Miniscript evaluation error".to_string(),
|
||||||
},
|
},
|
||||||
"Miniscript error: Miniscript evaluation error",
|
"miniscript error: Miniscript evaluation error",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
DescriptorError::Hex {
|
DescriptorError::Hex {
|
||||||
error_message: "Hexadecimal decoding error".to_string(),
|
error_message: "Hexadecimal decoding error".to_string(),
|
||||||
},
|
},
|
||||||
"Hex decoding error: Hexadecimal decoding error",
|
"hex decoding error: Hexadecimal decoding error",
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1317,6 +1484,92 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_electrum_client() {
|
||||||
|
let cases = vec![
|
||||||
|
(
|
||||||
|
ElectrumError::IOError { error_message: "message".to_string(), },
|
||||||
|
"message",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::Json { error_message: "message".to_string(), },
|
||||||
|
"message",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::Hex { error_message: "message".to_string(), },
|
||||||
|
"message",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::Protocol { error_message: "message".to_string(), },
|
||||||
|
"electrum server error: message",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::Bitcoin {
|
||||||
|
error_message: "message".to_string(),
|
||||||
|
},
|
||||||
|
"message",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::AlreadySubscribed,
|
||||||
|
"already subscribed to the notifications of an address",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::NotSubscribed,
|
||||||
|
"not subscribed to the notifications of an address",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::InvalidResponse {
|
||||||
|
error_message: "message".to_string(),
|
||||||
|
},
|
||||||
|
"error during the deserialization of a response from the server: message",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::Message {
|
||||||
|
error_message: "message".to_string(),
|
||||||
|
},
|
||||||
|
"message",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::InvalidDNSNameError {
|
||||||
|
domain: "domain".to_string(),
|
||||||
|
},
|
||||||
|
"invalid domain name domain not matching SSL certificate",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::MissingDomain,
|
||||||
|
"missing domain while it was explicitly asked to validate it",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::AllAttemptsErrored,
|
||||||
|
"made one or multiple attempts, all errored",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::SharedIOError {
|
||||||
|
error_message: "message".to_string(),
|
||||||
|
},
|
||||||
|
"message",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::CouldntLockReader,
|
||||||
|
"couldn't take a lock on the reader mutex. This means that there's already another reader thread is running"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::Mpsc,
|
||||||
|
"broken IPC communication channel: the other thread probably has exited",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ElectrumError::CouldNotCreateConnection {
|
||||||
|
error_message: "message".to_string(),
|
||||||
|
},
|
||||||
|
"message",
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
for (error, expected_message) in cases {
|
||||||
|
assert_eq!(error.to_string(), expected_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_error_esplora() {
|
fn test_error_esplora() {
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
@@ -1337,7 +1590,7 @@ mod test {
|
|||||||
EsploraError::StatusCode {
|
EsploraError::StatusCode {
|
||||||
error_message: "code 1234567".to_string(),
|
error_message: "code 1234567".to_string(),
|
||||||
},
|
},
|
||||||
"Invalid status code, unable to convert to u16: code 1234567",
|
"invalid status code, unable to convert to u16: code 1234567",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
EsploraError::Parsing {
|
EsploraError::Parsing {
|
||||||
@@ -1418,6 +1671,43 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_parse_amount() {
|
||||||
|
let cases = vec![
|
||||||
|
(ParseAmountError::Negative, "amount is negative"),
|
||||||
|
(ParseAmountError::TooBig, "amount is too large"),
|
||||||
|
(ParseAmountError::TooPrecise, "amount is too precise"),
|
||||||
|
(ParseAmountError::InvalidFormat, "invalid amount format"),
|
||||||
|
(ParseAmountError::InputTooLarge, "input is too large"),
|
||||||
|
(
|
||||||
|
ParseAmountError::InvalidCharacter {
|
||||||
|
error_message: "invalid char".to_string(),
|
||||||
|
},
|
||||||
|
"invalid character: invalid char",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ParseAmountError::UnknownDenomination {
|
||||||
|
error_message: "unknown denom".to_string(),
|
||||||
|
},
|
||||||
|
"unknown denomination: unknown denom",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ParseAmountError::PossiblyConfusingDenomination {
|
||||||
|
error_message: "confusing denom".to_string(),
|
||||||
|
},
|
||||||
|
"possibly confusing denomination: confusing denom",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ParseAmountError::OtherParseAmountErr,
|
||||||
|
"unknown parse amount error",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (error, expected_message) in cases {
|
||||||
|
assert_eq!(error.to_string(), expected_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_persistence_error() {
|
fn test_persistence_error() {
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
@@ -1449,13 +1739,13 @@ mod test {
|
|||||||
PsbtParseError::PsbtEncoding {
|
PsbtParseError::PsbtEncoding {
|
||||||
error_message: "invalid PSBT structure".to_string(),
|
error_message: "invalid PSBT structure".to_string(),
|
||||||
},
|
},
|
||||||
"error in internal PSBT data structure: invalid PSBT structure",
|
"error in internal psbt data structure: invalid PSBT structure",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
PsbtParseError::Base64Encoding {
|
PsbtParseError::Base64Encoding {
|
||||||
error_message: "base64 decode error".to_string(),
|
error_message: "base64 decode error".to_string(),
|
||||||
},
|
},
|
||||||
"error in PSBT base64 encoding: base64 decode error",
|
"error in psbt base64 encoding: base64 decode error",
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1467,46 +1757,46 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_signer_errors() {
|
fn test_signer_errors() {
|
||||||
let errors = vec![
|
let errors = vec![
|
||||||
(SignerError::MissingKey, "Missing key for signing"),
|
(SignerError::MissingKey, "missing key for signing"),
|
||||||
(SignerError::InvalidKey, "Invalid key provided"),
|
(SignerError::InvalidKey, "invalid key provided"),
|
||||||
(SignerError::UserCanceled, "User canceled operation"),
|
(SignerError::UserCanceled, "user canceled operation"),
|
||||||
(
|
(
|
||||||
SignerError::InputIndexOutOfRange,
|
SignerError::InputIndexOutOfRange,
|
||||||
"Input index out of range",
|
"input index out of range",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
SignerError::MissingNonWitnessUtxo,
|
SignerError::MissingNonWitnessUtxo,
|
||||||
"Missing non-witness UTXO information",
|
"missing non-witness utxo information",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
SignerError::InvalidNonWitnessUtxo,
|
SignerError::InvalidNonWitnessUtxo,
|
||||||
"Invalid non-witness UTXO information provided",
|
"invalid non-witness utxo information provided",
|
||||||
),
|
),
|
||||||
(SignerError::MissingWitnessUtxo, "Missing witness UTXO"),
|
(SignerError::MissingWitnessUtxo, "missing witness utxo"),
|
||||||
(SignerError::MissingWitnessScript, "Missing witness script"),
|
(SignerError::MissingWitnessScript, "missing witness script"),
|
||||||
(SignerError::MissingHdKeypath, "Missing HD keypath"),
|
(SignerError::MissingHdKeypath, "missing hd keypath"),
|
||||||
(
|
(
|
||||||
SignerError::NonStandardSighash,
|
SignerError::NonStandardSighash,
|
||||||
"Non-standard sighash type used",
|
"non-standard sighash type used",
|
||||||
),
|
),
|
||||||
(SignerError::InvalidSighash, "Invalid sighash type provided"),
|
(SignerError::InvalidSighash, "invalid sighash type provided"),
|
||||||
(
|
(
|
||||||
SignerError::SighashError {
|
SignerError::SighashError {
|
||||||
error_message: "dummy error".into(),
|
error_message: "dummy error".into(),
|
||||||
},
|
},
|
||||||
"Error with sighash computation: dummy error",
|
"error with sighash computation: dummy error",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
SignerError::MiniscriptPsbt {
|
SignerError::MiniscriptPsbt {
|
||||||
error_message: "psbt issue".into(),
|
error_message: "psbt issue".into(),
|
||||||
},
|
},
|
||||||
"Miniscript Psbt error: psbt issue",
|
"miniscript psbt error: psbt issue",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
SignerError::External {
|
SignerError::External {
|
||||||
error_message: "external error".into(),
|
error_message: "external error".into(),
|
||||||
},
|
},
|
||||||
"External error: external error",
|
"external error: external error",
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1518,7 +1808,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_error_transaction() {
|
fn test_error_transaction() {
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
(TransactionError::Io, "IO error"),
|
(TransactionError::Io, "io error"),
|
||||||
(
|
(
|
||||||
TransactionError::OversizedVectorAllocation,
|
TransactionError::OversizedVectorAllocation,
|
||||||
"allocation of oversized vector",
|
"allocation of oversized vector",
|
||||||
@@ -1530,7 +1820,7 @@ mod test {
|
|||||||
},
|
},
|
||||||
"invalid checksum: expected=deadbeef actual=beadbeef",
|
"invalid checksum: expected=deadbeef actual=beadbeef",
|
||||||
),
|
),
|
||||||
(TransactionError::NonMinimalVarInt, "non-minimal varint"),
|
(TransactionError::NonMinimalVarInt, "non-minimal var int"),
|
||||||
(TransactionError::ParseFailed, "parse failed"),
|
(TransactionError::ParseFailed, "parse failed"),
|
||||||
(
|
(
|
||||||
TransactionError::UnsupportedSegwitFlag { flag: 1 },
|
TransactionError::UnsupportedSegwitFlag { flag: 1 },
|
||||||
@@ -1605,6 +1895,13 @@ mod test {
|
|||||||
},
|
},
|
||||||
"loaded network type is not bitcoin, got Some(Testnet)".to_string(),
|
"loaded network type is not bitcoin, got Some(Testnet)".to_string(),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
WalletCreationError::LoadedDescriptorDoesNotMatch {
|
||||||
|
got: "def".to_string(),
|
||||||
|
keychain: KeychainKind::External,
|
||||||
|
},
|
||||||
|
"loaded descriptor 'def' does not match what was provided 'External'".to_string(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (error, expected) in errors {
|
for (error, expected) in errors {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod bitcoin;
|
mod bitcoin;
|
||||||
mod descriptor;
|
mod descriptor;
|
||||||
|
mod electrum;
|
||||||
mod error;
|
mod error;
|
||||||
mod esplora;
|
mod esplora;
|
||||||
mod keys;
|
mod keys;
|
||||||
@@ -7,13 +8,16 @@ 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::electrum::ElectrumClient;
|
||||||
use crate::error::AddressError;
|
use crate::error::AddressError;
|
||||||
use crate::error::Bip32Error;
|
use crate::error::Bip32Error;
|
||||||
use crate::error::Bip39Error;
|
use crate::error::Bip39Error;
|
||||||
@@ -22,9 +26,11 @@ 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::ParseAmountError;
|
||||||
use crate::error::PersistenceError;
|
use crate::error::PersistenceError;
|
||||||
use crate::error::PsbtParseError;
|
use crate::error::PsbtParseError;
|
||||||
use crate::error::SignerError;
|
use crate::error::SignerError;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ use bdk::LocalOutput as BdkLocalOutput;
|
|||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::bitcoin::Amount;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ChainPosition {
|
pub enum ChainPosition {
|
||||||
Confirmed { height: u32, timestamp: u64 },
|
Confirmed { height: u32, timestamp: u64 },
|
||||||
@@ -45,7 +47,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,23 +67,23 @@ 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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
|
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, DescriptorError, PersistenceError,
|
||||||
TxidParseError, WalletCreationError,
|
SignerError, TxidParseError, WalletCreationError,
|
||||||
};
|
};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
AddressInfo, Balance, CanonicalTx, FullScanRequest, LocalOutput, ScriptAmount, SyncRequest,
|
AddressInfo, Balance, CanonicalTx, FullScanRequest, LocalOutput, ScriptAmount, SyncRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use bdk::bitcoin::amount::Amount as BdkAmount;
|
||||||
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
|
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
|
||||||
use bdk::bitcoin::Network;
|
use bdk::bitcoin::Network;
|
||||||
use bdk::bitcoin::Psbt as BdkPsbt;
|
use bdk::bitcoin::Psbt as BdkPsbt;
|
||||||
@@ -47,6 +49,22 @@ impl Wallet {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_no_persist(
|
||||||
|
descriptor: Arc<Descriptor>,
|
||||||
|
change_descriptor: Option<Arc<Descriptor>>,
|
||||||
|
network: Network,
|
||||||
|
) -> Result<Self, DescriptorError> {
|
||||||
|
let descriptor = descriptor.as_string_private();
|
||||||
|
let change_descriptor = change_descriptor.map(|d| d.as_string_private());
|
||||||
|
|
||||||
|
let wallet: BdkWallet =
|
||||||
|
BdkWallet::new_no_persist(&descriptor, change_descriptor.as_ref(), network)?;
|
||||||
|
|
||||||
|
Ok(Wallet {
|
||||||
|
inner_mutex: Mutex::new(wallet),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn get_wallet(&self) -> MutexGuard<BdkWallet> {
|
pub(crate) fn get_wallet(&self) -> MutexGuard<BdkWallet> {
|
||||||
self.inner_mutex.lock().expect("wallet")
|
self.inner_mutex.lock().expect("wallet")
|
||||||
}
|
}
|
||||||
@@ -82,7 +100,7 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_balance(&self) -> Balance {
|
pub fn get_balance(&self) -> Balance {
|
||||||
let bdk_balance: bdk::wallet::Balance = self.get_wallet().get_balance();
|
let bdk_balance = self.get_wallet().get_balance();
|
||||||
Balance::from(bdk_balance)
|
Balance::from(bdk_balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,8 +120,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> {
|
||||||
@@ -152,15 +173,15 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@@ -190,9 +211,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 +224,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,
|
||||||
|
|||||||
@@ -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.11
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.bitcoindevkit
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
private const val SIGNET_ELECTRUM_URL = "ssl://mempool.space:60602"
|
||||||
|
|
||||||
|
class LiveElectrumClientTest {
|
||||||
|
@Test
|
||||||
|
fun testSyncedBalance() {
|
||||||
|
val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
val wallet: Wallet = Wallet.newNoPersist(descriptor, null, 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)
|
||||||
|
wallet.commit()
|
||||||
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
|
|
||||||
|
assert(wallet.getBalance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} 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.txid()}")
|
||||||
|
println("Sent ${sentAndReceived.sent.toSat()}")
|
||||||
|
println("Received ${sentAndReceived.received.toSat()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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 {
|
||||||
|
@Test
|
||||||
|
fun testSyncedBalance() {
|
||||||
|
val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
val wallet: Wallet = Wallet.newNoPersist(descriptor, null, Network.SIGNET)
|
||||||
|
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
|
wallet.applyUpdate(update)
|
||||||
|
wallet.commit()
|
||||||
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
|
|
||||||
|
assert(wallet.getBalance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} 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.txid()}")
|
||||||
|
println("Sent ${sentAndReceived.sent.toSat()}")
|
||||||
|
println("Received ${sentAndReceived.received.toSat()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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 {
|
||||||
|
@Test
|
||||||
|
fun testSyncedBalance() {
|
||||||
|
val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
val wallet: Wallet = Wallet.newNoPersist(descriptor, null, Network.SIGNET)
|
||||||
|
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
|
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||||
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
|
wallet.applyUpdate(update)
|
||||||
|
wallet.commit()
|
||||||
|
println("Wallet balance: ${wallet.getBalance().total.toSat()}")
|
||||||
|
|
||||||
|
assert(wallet.getBalance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
|
val transaction: Transaction = wallet.transactions().first().transaction
|
||||||
|
println("First transaction:")
|
||||||
|
println("Txid: ${transaction.txid()}")
|
||||||
|
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()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,13 +31,15 @@ class LiveTxBuilderTest {
|
|||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.getBalance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} 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)
|
||||||
|
|
||||||
@@ -56,15 +58,17 @@ class LiveTxBuilderTest {
|
|||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.getBalance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} 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()
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ class LiveWalletTest {
|
|||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL)
|
assert(wallet.getBalance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} 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)
|
||||||
@@ -54,17 +56,16 @@ class LiveWalletTest {
|
|||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
println("Balance: ${wallet.getBalance().total}")
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
println("New address: ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()}")
|
|
||||||
|
|
||||||
assert(wallet.getBalance().total > 0uL) {
|
assert(wallet.getBalance().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.asString()} 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)
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class OfflineWalletTest {
|
|||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = 0uL,
|
expected = 0uL,
|
||||||
actual = wallet.getBalance().total
|
actual = wallet.getBalance().total.toSat()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import bdkpython as bdk
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="bdkpython",
|
name="bdkpython",
|
||||||
version="1.0.0a10.dev",
|
version="1.0.0a11",
|
||||||
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",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class LiveTxBuilderTest(unittest.TestCase):
|
|||||||
|
|
||||||
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(
|
||||||
@@ -32,29 +32,29 @@ class LiveTxBuilderTest(unittest.TestCase):
|
|||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
|
|
||||||
self.assertGreater(wallet.get_balance().total, 0)
|
self.assertGreater(
|
||||||
|
wallet.get_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.as_string()} 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(
|
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
bdk.Network.SIGNET
|
|
||||||
)
|
|
||||||
change_descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)",
|
|
||||||
bdk.Network.SIGNET
|
bdk.Network.SIGNET
|
||||||
)
|
)
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
wallet: bdk.Wallet = bdk.Wallet(
|
||||||
descriptor,
|
descriptor,
|
||||||
change_descriptor,
|
None,
|
||||||
"./bdk_persistence.db",
|
"./bdk_persistence.db",
|
||||||
bdk.Network.SIGNET
|
bdk.Network.SIGNET
|
||||||
)
|
)
|
||||||
@@ -68,7 +68,11 @@ class LiveTxBuilderTest(unittest.TestCase):
|
|||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
|
|
||||||
self.assertGreater(wallet.get_balance().total, 0)
|
self.assertGreater(
|
||||||
|
wallet.get_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.as_string()} and try again."
|
||||||
|
)
|
||||||
|
|
||||||
recipient1 = bdk.Address(
|
recipient1 = bdk.Address(
|
||||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||||
|
|||||||
@@ -32,15 +32,19 @@ class LiveWalletTest(unittest.TestCase):
|
|||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
|
|
||||||
self.assertGreater(wallet.get_balance().total, 0)
|
self.assertGreater(
|
||||||
|
wallet.get_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.as_string()} 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.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):
|
||||||
@@ -64,15 +68,18 @@ class LiveWalletTest(unittest.TestCase):
|
|||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
wallet.commit()
|
wallet.commit()
|
||||||
|
|
||||||
self.assertGreater(wallet.get_balance().total, 0)
|
self.assertGreater(
|
||||||
|
wallet.get_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.as_string()} 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)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class OfflineWalletTest(unittest.TestCase):
|
|||||||
bdk.Network.TESTNET
|
bdk.Network.TESTNET
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(wallet.get_balance().total, 0)
|
self.assertEqual(wallet.get_balance().total.to_sat(), 0)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
|
private let SIGNET_ELECTRUM_URL = "ssl://mempool.space:60602"
|
||||||
|
|
||||||
|
final class LiveElectrumClientTests: XCTestCase {
|
||||||
|
func testSyncedBalance() throws {
|
||||||
|
let descriptor = try Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
let wallet = try Wallet.newNoPersist(
|
||||||
|
descriptor: descriptor,
|
||||||
|
changeDescriptor: nil,
|
||||||
|
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 _ = try wallet.commit()
|
||||||
|
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.getBalance().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.txid())")
|
||||||
|
print("Sent \(sentAndReceived.sent.toSat())")
|
||||||
|
print("Received \(sentAndReceived.received.toSat())")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
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 {
|
||||||
|
func testSyncedBalance() throws {
|
||||||
|
let descriptor = try Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
let wallet = try Wallet.newNoPersist(
|
||||||
|
descriptor: descriptor,
|
||||||
|
changeDescriptor: nil,
|
||||||
|
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 _ = try wallet.commit()
|
||||||
|
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.getBalance().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.txid())")
|
||||||
|
print("Sent \(sentAndReceived.sent.toSat())")
|
||||||
|
print("Received \(sentAndReceived.received.toSat())")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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 {
|
||||||
|
func testSyncedBalance() throws {
|
||||||
|
let descriptor = try Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
let wallet = try Wallet.newNoPersist(
|
||||||
|
descriptor: descriptor,
|
||||||
|
changeDescriptor: nil,
|
||||||
|
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 _ = try wallet.commit()
|
||||||
|
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.getBalance().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.txid())")
|
||||||
|
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,8 +1,8 @@
|
|||||||
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 {
|
||||||
var dbFilePath: URL!
|
var dbFilePath: URL!
|
||||||
@@ -45,13 +45,18 @@ final class LiveTxBuilderTests: XCTestCase {
|
|||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
try wallet.commit()
|
let _ = try wallet.commit()
|
||||||
|
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
|
||||||
|
|
||||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
|
XCTAssertGreaterThan(
|
||||||
|
wallet.getBalance().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)
|
||||||
|
|
||||||
@@ -82,15 +87,20 @@ final class LiveTxBuilderTests: XCTestCase {
|
|||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
try wallet.commit()
|
let _ = try wallet.commit()
|
||||||
|
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
|
||||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.getBalance().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,8 +1,8 @@
|
|||||||
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 {
|
||||||
var dbFilePath: URL!
|
var dbFilePath: URL!
|
||||||
@@ -45,17 +45,22 @@ final class LiveWalletTests: XCTestCase {
|
|||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
try wallet.commit()
|
let _ = try wallet.commit()
|
||||||
|
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
|
||||||
|
|
||||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0))
|
XCTAssertGreaterThan(
|
||||||
|
wallet.getBalance().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.txid())")
|
||||||
print("Sent \(sentAndReceived.sent)")
|
print("Sent \(sentAndReceived.sent.toSat())")
|
||||||
print("Received \(sentAndReceived.received)")
|
print("Received \(sentAndReceived.received.toSat())")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,16 +83,21 @@ final class LiveWalletTests: XCTestCase {
|
|||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
try wallet.commit()
|
let _ = try wallet.commit()
|
||||||
|
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
|
||||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.getBalance().total.toSat(),
|
||||||
|
UInt64(0),
|
||||||
|
"Wallet must have positive balance, please send funds to \(address)"
|
||||||
|
)
|
||||||
|
|
||||||
print("Balance: \(wallet.getBalance().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)
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,6 @@ final class OfflineWalletTests: XCTestCase {
|
|||||||
network: .testnet
|
network: .testnet
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(wallet.getBalance().total, 0)
|
XCTAssertEqual(wallet.getBalance().total.toSat(), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user