Skip to content

Commit 1b08e6c

Browse files
committed
npm: add btcXpubs()
1 parent 05cdd8a commit 1b08e6c

File tree

5 files changed

+111
-1
lines changed

5 files changed

+111
-1
lines changed

CHANGELOG-npm.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 0.11.0
4+
- Add `btcXpubs()`
5+
36
## 0.10.1
47
- package.json: use "main" instead of "module" to fix compatiblity with vitest
58

NPM_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.10.1
1+
0.11.0

sandbox/src/Bitcoin.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,73 @@ function BtcXPub({ bb02 } : Props) {
7676
);
7777
}
7878

79+
80+
function BtcXPubs({ bb02 } : Props) {
81+
const [coin, setCoin] = useState<bitbox.BtcCoin>('btc');
82+
const [keypaths, setKeypaths] = useState(`["m/49'/0'/0'", "m/84'/0'/0'", "m/86'/0'/0'"]`);
83+
const [xpubType, setXpubType] = useState<bitbox.BtcXPubsType>('xpub');
84+
const [result, setResult] = useState<bitbox.BtcXpubs | undefined>();
85+
const [running, setRunning] = useState(false);
86+
const [err, setErr] = useState<bitbox.Error>();
87+
88+
const btcXPubsTypeOptions = ['tpub', 'xpub'];
89+
90+
const submitForm = async (e: FormEvent) => {
91+
e.preventDefault();
92+
setRunning(true);
93+
setResult(undefined);
94+
setErr(undefined);
95+
try {
96+
setResult(await bb02.btcXpubs(coin, JSON.parse(keypaths), xpubType));
97+
} catch (err) {
98+
throw err;
99+
setErr(bitbox.ensureError(err));
100+
} finally {
101+
setRunning(false);
102+
}
103+
}
104+
105+
106+
return (
107+
<div>
108+
<h4>Multiple XPubs</h4>
109+
<form className="verticalForm"onSubmit={submitForm}>
110+
<label>
111+
Coin
112+
<select value={coin} onChange={e => setCoin(e.target.value as bitbox.BtcCoin)}>
113+
{btcCoinOptions.map(option => <option key={option} value={option}>{option}</option>)}
114+
</select>
115+
</label>
116+
<label>
117+
Keypaths
118+
</label>
119+
<textarea value={keypaths} onChange={e => setKeypaths(e.target.value)} rows={5} cols={80} />
120+
<label>
121+
XPub Type
122+
<select value={xpubType} onChange={e => setXpubType(e.target.value as bitbox.BtcXPubsType)}>
123+
{btcXPubsTypeOptions.map(option => <option key={option} value={option}>{option}</option>)}
124+
</select>
125+
</label>
126+
127+
<button type='submit' disabled={running}>Get XPubs</button>
128+
{result ? <>
129+
<div className="resultContainer">
130+
<label>Result</label>
131+
{
132+
result.map((xpub, i) => (
133+
<code key={i}>
134+
{i}: <b>{xpub}</b><br />
135+
</code>
136+
))
137+
}
138+
</div>
139+
</> : null}
140+
<ShowError err={err} />
141+
</form>
142+
</div>
143+
);
144+
}
145+
79146
function BtcAddressSimple({ bb02 }: Props) {
80147
const [coin, setCoin] = useState<bitbox.BtcCoin>('btc');
81148
const [simpleType, setSimpleType] = useState<bitbox.BtcSimpleType>('p2wpkhP2sh');
@@ -451,6 +518,9 @@ export function Bitcoin({ bb02 } : Props) {
451518
<div className="action">
452519
<BtcXPub bb02={bb02} />
453520
</div>
521+
<div className="action">
522+
<BtcXPubs bb02={bb02} />
523+
</div>
454524
<div className="action">
455525
<BtcAddressSimple bb02={bb02} />
456526
</div>

src/wasm/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,29 @@ impl PairedBitBox {
257257
.await?)
258258
}
259259

260+
/// Query the device for xpubs. The result contains one xpub per requested keypath.
261+
#[wasm_bindgen(js_name = btcXpubs)]
262+
pub async fn btc_xpubs(
263+
&self,
264+
coin: types::TsBtcCoin,
265+
keypaths: Vec<types::TsKeypath>,
266+
xpub_type: types::TsBtcXPubsType,
267+
) -> Result<types::TsBtcXpubs, JavascriptError> {
268+
let xpubs = self
269+
.device
270+
.btc_xpubs(
271+
coin.try_into()?,
272+
keypaths
273+
.into_iter()
274+
.map(|kp| kp.try_into())
275+
.collect::<Result<Vec<crate::Keypath>, _>>()?
276+
.as_slice(),
277+
xpub_type.try_into()?,
278+
)
279+
.await?;
280+
Ok(serde_wasm_bindgen::to_value(&xpubs).unwrap().into())
281+
}
282+
260283
/// Before a multisig or policy script config can be used to display receive addresses or sign
261284
/// transactions, it must be registered on the device. This function checks if the script config
262285
/// was already registered.

src/wasm/types.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type Product = 'unknown' | 'bitbox02-multi' | 'bitbox02-btconly' | 'bitbox02-nov
1515
type BtcCoin = 'btc' | 'tbtc' | 'ltc' | 'tltc' | 'rbtc';
1616
type BtcFormatUnit = 'default' | 'sat';
1717
type XPubType = 'tpub' | 'xpub' | 'ypub' | 'zpub' | 'vpub' | 'upub' | 'Vpub' | 'Zpub' | 'Upub' | 'Ypub';
18+
type BtcXPubsType = 'tpub' | 'xpub';
1819
type Keypath = string | number[];
1920
type XPub = string;
2021
type DeviceInfo = {
@@ -50,6 +51,7 @@ type BtcSignMessageSignature = {
5051
recid: bigint,
5152
electrumSig65: Uint8Array,
5253
};
54+
type BtcXpubs = string[];
5355
// nonce, gasPrice, gasLimit and value must be big-endian encoded, no trailing zeroes.
5456
type EthTransaction = {
5557
nonce: Uint8Array;
@@ -172,6 +174,8 @@ extern "C" {
172174
pub type TsBtcFormatUnit;
173175
#[wasm_bindgen(typescript_type = "XPubType")]
174176
pub type TsXPubType;
177+
#[wasm_bindgen(typescript_type = "BtcXPubsType")]
178+
pub type TsBtcXPubsType;
175179
#[wasm_bindgen(typescript_type = "Keypath")]
176180
pub type TsKeypath;
177181
#[wasm_bindgen(typescript_type = "DeviceInfo")]
@@ -190,6 +194,8 @@ extern "C" {
190194
pub type TsBtcScriptConfigWithKeypath;
191195
#[wasm_bindgen(typescript_type = "BtcSignMessageSignature")]
192196
pub type TsBtcSignMessageSignature;
197+
#[wasm_bindgen(typescript_type = "BtcXpubs")]
198+
pub type TsBtcXpubs;
193199
#[wasm_bindgen(typescript_type = "EthTransaction")]
194200
pub type TsEthTransaction;
195201
#[wasm_bindgen(typescript_type = "Eth1559Transaction")]
@@ -238,6 +244,14 @@ impl TryFrom<TsXPubType> for crate::pb::btc_pub_request::XPubType {
238244
}
239245
}
240246

247+
impl TryFrom<TsBtcXPubsType> for crate::pb::btc_xpubs_request::XPubType {
248+
type Error = JavascriptError;
249+
fn try_from(value: TsBtcXPubsType) -> Result<Self, Self::Error> {
250+
serde_wasm_bindgen::from_value(value.into())
251+
.map_err(|_| JavascriptError::InvalidType("wrong type for BTCXPubsType"))
252+
}
253+
}
254+
241255
impl TryFrom<TsKeypath> for crate::Keypath {
242256
type Error = JavascriptError;
243257
fn try_from(value: TsKeypath) -> Result<Self, Self::Error> {

0 commit comments

Comments
 (0)