From da5e8723ce112e35f54d31e632004169225518d9 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Thu, 11 Sep 2025 22:40:18 +0200 Subject: [PATCH 1/3] btc: adjust tests to simulator v9.24+ simulating Nova Since v9.24, the simulator simulates a BitBox02 Nova, as it is a superset of BitBox02. --- tests/test_device.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_device.rs b/tests/test_device.rs index c78f327..3487364 100644 --- a/tests/test_device.rs +++ b/tests/test_device.rs @@ -14,8 +14,20 @@ async fn test_device_info() { test_simulators_after_pairing(async |paired_bitbox| { let device_info = paired_bitbox.device_info().await.unwrap(); - assert_eq!(device_info.name, "My BitBox"); - assert_eq!(paired_bitbox.product(), bitbox_api::Product::BitBox02Multi); + // Since v9.24.0, the simulator simulates a Nova device. + if semver::VersionReq::parse(">=9.24.0") + .unwrap() + .matches(paired_bitbox.version()) + { + assert_eq!( + paired_bitbox.product(), + bitbox_api::Product::BitBox02NovaMulti + ); + assert_eq!(device_info.name, "BitBox HCXT") + } else { + assert_eq!(paired_bitbox.product(), bitbox_api::Product::BitBox02Multi); + assert_eq!(device_info.name, "My BitBox") + } }) .await } From e5c9858a03b13067c20c52f277b4fa39c4ea6f72 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Thu, 11 Sep 2025 22:47:02 +0200 Subject: [PATCH 2/3] btc: add btc_xpubs() To fetch multiple xpubs at once, introduced in v9.24.0. --- CHANGELOG-rust.md | 3 + Cargo.lock | 2 +- Cargo.toml | 2 +- README-rust.md | 2 +- messages/bitbox02_system.proto | 10 ++ messages/bluetooth.proto | 50 ++++++++++ messages/btc.proto | 13 +++ messages/common.proto | 4 + messages/hww.proto | 3 + src/btc.rs | 37 ++++++++ src/shiftcrypto.bitbox02.rs | 169 ++++++++++++++++++++++++++++++++- tests/test_btc.rs | 27 ++++++ 12 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 messages/bluetooth.proto diff --git a/CHANGELOG-rust.md b/CHANGELOG-rust.md index 5a85bfb..4988f50 100644 --- a/CHANGELOG-rust.md +++ b/CHANGELOG-rust.md @@ -1,5 +1,8 @@ # Changelog +## 0.10.0 +- Add `btc_xpubs()` + ## 0.9.0 - Add support for BitBox02 Nova diff --git a/Cargo.lock b/Cargo.lock index ad7a2d8..2fb1b52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,7 +162,7 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "bitbox-api" -version = "0.9.0" +version = "0.10.0" dependencies = [ "async-trait", "base32", diff --git a/Cargo.toml b/Cargo.toml index 5efab3e..4cc44c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bitbox-api" authors = ["Marko Bencun "] -version = "0.9.0" +version = "0.10.0" homepage = "https://bitbox.swiss/" repository = "https://github.com/BitBoxSwiss/bitbox-api-rs/" readme = "README-rust.md" diff --git a/README-rust.md b/README-rust.md index 4dd62c4..fbdc025 100644 --- a/README-rust.md +++ b/README-rust.md @@ -27,7 +27,7 @@ Use `--nocapture` to also see some useful simulator output. If you want to test against a custom simulator build (e.g. when developing new firmware features), you can run: - SIMULATOR=/path/to/simulator cargo test --features=simulator,tokio + SIMULATOR=/path/to/simulator cargo test --features=simulator,tokio -- --test-threads 1 In this case, only the given simulator will be used, and the ones defined in simulators.json will be ignored. diff --git a/messages/bitbox02_system.proto b/messages/bitbox02_system.proto index a51acee..a5e8e55 100644 --- a/messages/bitbox02_system.proto +++ b/messages/bitbox02_system.proto @@ -26,6 +26,14 @@ message DeviceInfoRequest { } message DeviceInfoResponse { + message Bluetooth { + // Hash of the currently active Bluetooth firmware on the device. + bytes firmware_hash = 1; + // Firmware version, formated as an unsigned integer "1", "2", etc. + string firmware_version = 2; + // True if Bluetooth is enabled + bool enabled = 3; + } string name = 1; bool initialized = 2; string version = 3; @@ -33,6 +41,8 @@ message DeviceInfoResponse { uint32 monotonic_increments_remaining = 5; // From v9.6.0: "ATECC608A" or "ATECC608B". string securechip_model = 6; + // Only present in Bluetooth-enabled devices. + optional Bluetooth bluetooth = 7; } message InsertRemoveSDCardRequest { diff --git a/messages/bluetooth.proto b/messages/bluetooth.proto new file mode 100644 index 0000000..db98275 --- /dev/null +++ b/messages/bluetooth.proto @@ -0,0 +1,50 @@ +// Copyright 2025 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; +package shiftcrypto.bitbox02; + +message BluetoothToggleEnabledRequest { +} + +message BluetoothUpgradeInitRequest { + uint32 firmware_length = 1; +} + +message BluetoothChunkRequest { + bytes data = 1; +} + +message BluetoothSuccess { +} + +message BluetoothRequestChunkResponse { + uint32 offset = 1; + uint32 length = 2; +} + +message BluetoothRequest { + oneof request { + BluetoothUpgradeInitRequest upgrade_init = 1; + BluetoothChunkRequest chunk = 2; + BluetoothToggleEnabledRequest toggle_enabled = 3; + } +} + +message BluetoothResponse { + oneof response { + BluetoothSuccess success = 1; + BluetoothRequestChunkResponse request_chunk = 2; + } +} diff --git a/messages/btc.proto b/messages/btc.proto index 7a49fe4..24669ff 100644 --- a/messages/btc.proto +++ b/messages/btc.proto @@ -93,6 +93,17 @@ message BTCPubRequest { bool display = 5; } +message BTCXpubsRequest{ + enum XPubType { + UNKNOWN = 0; + XPUB = 1; + TPUB = 2; + } + BTCCoin coin = 1; + XPubType xpub_type = 2; + repeated Keypath keypaths = 3; +} + message BTCScriptConfigWithKeypath { BTCScriptConfig script_config = 2; repeated uint32 keypath = 3; @@ -281,6 +292,7 @@ message BTCRequest { BTCSignMessageRequest sign_message = 6; AntiKleptoSignatureRequest antiklepto_signature = 7; BTCPaymentRequestRequest payment_request = 8; + BTCXpubsRequest xpubs = 9; } } @@ -291,5 +303,6 @@ message BTCResponse { BTCSignNextResponse sign_next = 3; BTCSignMessageResponse sign_message = 4; AntiKleptoSignerCommitment antiklepto_signer_commitment = 5; + PubsResponse pubs = 6; } } diff --git a/messages/common.proto b/messages/common.proto index 0b80c6f..0fed08c 100644 --- a/messages/common.proto +++ b/messages/common.proto @@ -19,6 +19,10 @@ message PubResponse { string pub = 1; } +message PubsResponse { + repeated string pubs = 1; +} + message RootFingerprintRequest { } diff --git a/messages/hww.proto b/messages/hww.proto index 54c34f6..4088ae3 100644 --- a/messages/hww.proto +++ b/messages/hww.proto @@ -19,6 +19,7 @@ import "common.proto"; import "backup_commands.proto"; import "bitbox02_system.proto"; +import "bluetooth.proto"; import "btc.proto"; import "cardano.proto"; import "eth.proto"; @@ -67,6 +68,7 @@ message Request { ElectrumEncryptionKeyRequest electrum_encryption_key = 26; CardanoRequest cardano = 27; BIP85Request bip85 = 28; + BluetoothRequest bluetooth = 29; } } @@ -89,5 +91,6 @@ message Response { ElectrumEncryptionKeyResponse electrum_encryption_key = 14; CardanoResponse cardano = 15; BIP85Response bip85 = 16; + BluetoothResponse bluetooth = 17; } } diff --git a/src/btc.rs b/src/btc.rs index b16088c..1be5968 100644 --- a/src/btc.rs +++ b/src/btc.rs @@ -643,6 +643,43 @@ impl PairedBitBox { } } + /// Retrieves multiple xpubs at once. Only standard keypaths are allowed. + /// On firmware Result, Error> { + if self.validate_version(">=9.24.0").is_err() { + // Fallback to fetching them one-by-one on older firmware. + let mut xpubs = Vec::::with_capacity(keypaths.len()); + for keypath in keypaths { + let converted_xpub_type = match xpub_type { + pb::btc_xpubs_request::XPubType::Unknown => return Err(Error::Unknown), + pb::btc_xpubs_request::XPubType::Tpub => pb::btc_pub_request::XPubType::Tpub, + pb::btc_xpubs_request::XPubType::Xpub => pb::btc_pub_request::XPubType::Xpub, + }; + let xpub = self + .btc_xpub(coin, keypath, converted_xpub_type, false) + .await?; + xpubs.push(xpub); + } + return Ok(xpubs); + } + match self + .query_proto_btc(pb::btc_request::Request::Xpubs(pb::BtcXpubsRequest { + coin: coin as _, + xpub_type: xpub_type as _, + keypaths: keypaths.iter().map(|kp| kp.into()).collect(), + })) + .await? + { + pb::btc_response::Response::Pubs(pb::PubsResponse { pubs }) => Ok(pubs), + _ => Err(Error::UnexpectedResponse), + } + } + /// Retrieves a Bitcoin address at the provided keypath. /// /// For the simple script configs (single-sig), the keypath must follow the diff --git a/src/shiftcrypto.bitbox02.rs b/src/shiftcrypto.bitbox02.rs index 8eedd15..a0f729d 100644 --- a/src/shiftcrypto.bitbox02.rs +++ b/src/shiftcrypto.bitbox02.rs @@ -8,6 +8,13 @@ pub struct PubResponse { } #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PubsResponse { + #[prost(string, repeated, tag = "1")] + pub pubs: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RootFingerprintRequest {} #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] @@ -153,6 +160,26 @@ pub struct DeviceInfoResponse { /// From v9.6.0: "ATECC608A" or "ATECC608B". #[prost(string, tag = "6")] pub securechip_model: ::prost::alloc::string::String, + /// Only present in Bluetooth-enabled devices. + #[prost(message, optional, tag = "7")] + pub bluetooth: ::core::option::Option, +} +/// Nested message and enum types in `DeviceInfoResponse`. +pub mod device_info_response { + #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Bluetooth { + /// Hash of the currently active Bluetooth firmware on the device. + #[prost(bytes = "vec", tag = "1")] + pub firmware_hash: ::prost::alloc::vec::Vec, + /// Firmware version, formated as an unsigned integer "1", "2", etc. + #[prost(string, tag = "2")] + pub firmware_version: ::prost::alloc::string::String, + /// True if Bluetooth is enabled + #[prost(bool, tag = "3")] + pub enabled: bool, + } } #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] @@ -229,6 +256,77 @@ pub struct SetPasswordRequest { } #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct BluetoothToggleEnabledRequest {} +#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct BluetoothUpgradeInitRequest { + #[prost(uint32, tag = "1")] + pub firmware_length: u32, +} +#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BluetoothChunkRequest { + #[prost(bytes = "vec", tag = "1")] + pub data: ::prost::alloc::vec::Vec, +} +#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct BluetoothSuccess {} +#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct BluetoothRequestChunkResponse { + #[prost(uint32, tag = "1")] + pub offset: u32, + #[prost(uint32, tag = "2")] + pub length: u32, +} +#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BluetoothRequest { + #[prost(oneof = "bluetooth_request::Request", tags = "1, 2, 3")] + pub request: ::core::option::Option, +} +/// Nested message and enum types in `BluetoothRequest`. +pub mod bluetooth_request { + #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Request { + #[prost(message, tag = "1")] + UpgradeInit(super::BluetoothUpgradeInitRequest), + #[prost(message, tag = "2")] + Chunk(super::BluetoothChunkRequest), + #[prost(message, tag = "3")] + ToggleEnabled(super::BluetoothToggleEnabledRequest), + } +} +#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct BluetoothResponse { + #[prost(oneof = "bluetooth_response::Response", tags = "1, 2")] + pub response: ::core::option::Option, +} +/// Nested message and enum types in `BluetoothResponse`. +pub mod bluetooth_response { + #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] + #[derive(Clone, Copy, PartialEq, ::prost::Oneof)] + pub enum Response { + #[prost(message, tag = "1")] + Success(super::BluetoothSuccess), + #[prost(message, tag = "2")] + RequestChunk(super::BluetoothRequestChunkResponse), + } +} +#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AntiKleptoHostNonceCommitment { #[prost(bytes = "vec", tag = "1")] @@ -495,6 +593,61 @@ pub mod btc_pub_request { #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct BtcXpubsRequest { + #[prost(enumeration = "BtcCoin", tag = "1")] + pub coin: i32, + #[prost(enumeration = "btc_xpubs_request::XPubType", tag = "2")] + pub xpub_type: i32, + #[prost(message, repeated, tag = "3")] + pub keypaths: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `BTCXpubsRequest`. +pub mod btc_xpubs_request { + #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum XPubType { + Unknown = 0, + Xpub = 1, + Tpub = 2, + } + impl XPubType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unknown => "UNKNOWN", + Self::Xpub => "XPUB", + Self::Tpub => "TPUB", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "UNKNOWN" => Some(Self::Unknown), + "XPUB" => Some(Self::Xpub), + "TPUB" => Some(Self::Tpub), + _ => None, + } + } + } +} +#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct BtcScriptConfigWithKeypath { #[prost(message, optional, tag = "2")] pub script_config: ::core::option::Option, @@ -932,7 +1085,7 @@ pub struct BtcSignMessageResponse { #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] #[derive(Clone, PartialEq, ::prost::Message)] pub struct BtcRequest { - #[prost(oneof = "btc_request::Request", tags = "1, 2, 3, 4, 5, 6, 7, 8")] + #[prost(oneof = "btc_request::Request", tags = "1, 2, 3, 4, 5, 6, 7, 8, 9")] pub request: ::core::option::Option, } /// Nested message and enum types in `BTCRequest`. @@ -957,13 +1110,15 @@ pub mod btc_request { AntikleptoSignature(super::AntiKleptoSignatureRequest), #[prost(message, tag = "8")] PaymentRequest(super::BtcPaymentRequestRequest), + #[prost(message, tag = "9")] + Xpubs(super::BtcXpubsRequest), } } #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] #[derive(Clone, PartialEq, ::prost::Message)] pub struct BtcResponse { - #[prost(oneof = "btc_response::Response", tags = "1, 2, 3, 4, 5")] + #[prost(oneof = "btc_response::Response", tags = "1, 2, 3, 4, 5, 6")] pub response: ::core::option::Option, } /// Nested message and enum types in `BTCResponse`. @@ -982,6 +1137,8 @@ pub mod btc_response { SignMessage(super::BtcSignMessageResponse), #[prost(message, tag = "5")] AntikleptoSignerCommitment(super::AntiKleptoSignerCommitment), + #[prost(message, tag = "6")] + Pubs(super::PubsResponse), } } #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] @@ -2076,7 +2233,7 @@ pub struct Success {} pub struct Request { #[prost( oneof = "request::Request", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29" )] pub request: ::core::option::Option, } @@ -2140,6 +2297,8 @@ pub mod request { Cardano(super::CardanoRequest), #[prost(message, tag = "28")] Bip85(super::Bip85Request), + #[prost(message, tag = "29")] + Bluetooth(super::BluetoothRequest), } } #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] @@ -2148,7 +2307,7 @@ pub mod request { pub struct Response { #[prost( oneof = "response::Response", - tags = "1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16" + tags = "1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17" )] pub response: ::core::option::Option, } @@ -2189,5 +2348,7 @@ pub mod response { Cardano(super::CardanoResponse), #[prost(message, tag = "16")] Bip85(super::Bip85Response), + #[prost(message, tag = "17")] + Bluetooth(super::BluetoothResponse), } } diff --git a/tests/test_btc.rs b/tests/test_btc.rs index 16d2619..ab0643f 100644 --- a/tests/test_btc.rs +++ b/tests/test_btc.rs @@ -34,6 +34,33 @@ async fn test_btc_xpub() { .await } +#[tokio::test] +async fn test_btc_xpubs() { + test_initialized_simulators(async |paired_bitbox| { + let result = paired_bitbox + .btc_xpubs( + pb::BtcCoin::Tbtc, + &[ + "m/49'/1'/0'".try_into().unwrap(), + "m/84'/1'/0'".try_into().unwrap(), + "m/86'/1'/0'".try_into().unwrap(), + ], + pb::btc_xpubs_request::XPubType::Tpub, + ) + .await; + + assert_eq!( + result.unwrap(), + vec![ + "tpubDCNtvuCS9oj3psPNfXZXuGjcQ5rSBi3MzigjBqqwQohWWetoRdLzT5v2uJq6KBTwxj1FYvuPTr7RoWkN4cmubDy5wW8SU3q9xYnDRpQepiT", + "tpubDCYNsKenq7Cuuf4fHsu2fsWA7Wb5cTD2qRUrw6uHbNNYQoNkEoJk4hgNhxbnGss5gnEe2MpqN2qbRVqWJGmuofAWmwFFi4CZ9Tg1LHKJDhF", + "tpubDDc6eecoyYxL4g3WKYpbbinyUmnfVikQCzHTPd6rJQivaPqGKBFiueQqWoAYonB8hAEXGM1ak7LqrnwczH24EbW7jbG5bNK5rncmRXtv7nG", + ], + ); + }) + .await +} + #[tokio::test] async fn test_btc_address() { test_initialized_simulators(async |paired_bitbox| { From a1b992d3379d58e4986c28a2f21953aea925f4a1 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Mon, 15 Sep 2025 11:04:12 +0200 Subject: [PATCH 3/3] npm: add btcXpubs() --- CHANGELOG-npm.md | 3 ++ NPM_VERSION | 2 +- sandbox/src/Bitcoin.tsx | 70 +++++++++++++++++++++++++++++++++++++++++ src/wasm/mod.rs | 23 ++++++++++++++ src/wasm/types.rs | 14 +++++++++ 5 files changed, 111 insertions(+), 1 deletion(-) diff --git a/CHANGELOG-npm.md b/CHANGELOG-npm.md index 8424b2d..81042aa 100644 --- a/CHANGELOG-npm.md +++ b/CHANGELOG-npm.md @@ -1,5 +1,8 @@ # Changelog +## 0.11.0 +- Add `btcXpubs()` + ## 0.10.1 - package.json: use "main" instead of "module" to fix compatiblity with vitest diff --git a/NPM_VERSION b/NPM_VERSION index 71172b4..142464b 100644 --- a/NPM_VERSION +++ b/NPM_VERSION @@ -1 +1 @@ -0.10.1 \ No newline at end of file +0.11.0 \ No newline at end of file diff --git a/sandbox/src/Bitcoin.tsx b/sandbox/src/Bitcoin.tsx index e6c8636..9438448 100644 --- a/sandbox/src/Bitcoin.tsx +++ b/sandbox/src/Bitcoin.tsx @@ -76,6 +76,73 @@ function BtcXPub({ bb02 } : Props) { ); } + +function BtcXPubs({ bb02 } : Props) { + const [coin, setCoin] = useState('btc'); + const [keypaths, setKeypaths] = useState(`["m/49'/0'/0'", "m/84'/0'/0'", "m/86'/0'/0'"]`); + const [xpubType, setXpubType] = useState('xpub'); + const [result, setResult] = useState(); + const [running, setRunning] = useState(false); + const [err, setErr] = useState(); + + const btcXPubsTypeOptions = ['tpub', 'xpub']; + + const submitForm = async (e: FormEvent) => { + e.preventDefault(); + setRunning(true); + setResult(undefined); + setErr(undefined); + try { + setResult(await bb02.btcXpubs(coin, JSON.parse(keypaths), xpubType)); + } catch (err) { + throw err; + setErr(bitbox.ensureError(err)); + } finally { + setRunning(false); + } + } + + + return ( +
+

Multiple XPubs

+
+ + +