Skip to main content

BTC Flow

This guide walks through BTC address confirmation and UTXO transaction signing with UKey Wallet. The fragile parts of a Bitcoin flow are usually mismatched paths, script types, previous transactions, or change outputs, so the focus here is how those fields are reviewed and verified on the device.

Table of contents

  1. How It Works
  2. Setup
  3. Init
  4. Best For
  5. Flow & State
  6. Demo
  7. Final Checks

How It Works

BTC related methods are invoked through the SDK entry, and the device reviews inputs, outputs, change, and fees according to the UTXO model. The app needs to provide enough complete transaction context that the device can confirm that what the user sees is consistent with the final transaction.

  • The HD path must correspond to the script purpose, such as 44, 49, 84, 86.
  • Each input should find the complete preceding transaction in refTxs.
  • The change output must use the correct path and script type, otherwise the device cannot safely determine whether it belongs to the user.
  • After the user confirms, the SDK returns results such as a signature, serialized transaction, or transaction ID.

Setup

npm i @ukeyfe/hardware-core @ukeyfe/hardware-common-connect-sdk

Init

import ukeySdk from "@ukeyfe/hardware-common-connect-sdk";
await ukeySdk.init({ env: "webusb", fetchConfig: true, debug: false });
const [{ connectId }] = await ukeySdk.searchDevices();
const deviceId = (await ukeySdk.getFeatures(connectId)).payload?.device_id;

Best For

Scenario 1: Read address

Read the BTC address under the specified path. When displaying it to users for the first time, saving payment addresses, or configuring change paths, enable device-side confirmation.

const path84 = "m/84'/0'/1'/0/0";
const opResult = await ukeySdk.btcGetAddress(connectId, deviceId, {
path: path84,
coin: "btc",
showOnUKey: true,
});
// Example data: opResult.payload.address, publicKey?, path

Input Fields

  • path / address_n: BIP44-style path, either as a string or number array, aligned with the script purpose.
  • coin: Network selector, for instance btc or test.
  • showOnUKey?: Enable device-side address review before returning the result.

return

Promise<{ success; payload: { address; path; publicKey?; chainCode? } }>;

Scenario 2: Sign transaction

Before signing the transaction, please confirm that input, output, change and handling fees can be calculated independently by the application. The content displayed on the device should be consistent with the transaction ultimately broadcast on the front end.

const { success, payload } = await ukeySdk.btcSignTransaction(
connectId,
deviceId,
{
coin: "btc",
inputs,
outputs,
refTxs,
locktime: 0,
},
);
// 返回结构大致为: { serializedTx, signatures?, txid? }

Input Fields

  • coin: Selects the Bitcoin network rules and address prefixes.
  • inputs: Transaction inputs. Include these fields; amount is mandatory for SegWit/Taproot and still preferred for Legacy:
    • address_n (path, matches script)
    • prev_hash(UTXO txid,hex)
    • prev_index(UTXO vout)
    • amount(String satoshi)
    • script_typeSPENDADDRESS / SPENDP2SHWITNESS / SPENDWITNESS / SPENDTAPROOT
  • outputs: Transaction outputs. Describe both recipient and change outputs:
    • Payment address output: address + amount + script_type (such as PAYTOADDRESS)
    • Change output: address_n + amount + script_type Corresponding script
  • refTxs: Full previous transactions for every input, including hash, version, lock_time, inputs, and bin_outputs or outputs according to the source transaction format.
  • locktime?: Numeric locktime value when the transaction uses one.

return

Promise<{
success;
payload: { serializedTx; signatures?: string[]; txid?: string };
}>;

Flow & State

  • The method returns the result through Promise; interactions such as unlocking, opening BTC App, confirming input/output/change/handling fee are triggered through UI_REQUEST event.
  • Sign serially on the same device to avoid multiple requests waiting for user confirmation at the same time.
  • Available before signing showOnUKey allows the user to confirm the address and path, especially the change path.

Demo

Example: P2SH-P2WPKH transaction (nested SegWit)

import ukeySdk from "@ukeyfe/hardware-common-connect-sdk";

await ukeySdk.init({ env: "webusb", debug: false });

const [{ connectId }] = await ukeySdk.searchDevices();
const deviceId = (await ukeySdk.getFeatures(connectId)).payload?.device_id;

const path49 = [
(49 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
0,
0,
];
await ukeySdk.btcGetAddress(connectId, deviceId, {
path: path49,
coin: "btc",
showOnUKey: true,
});

const inputs = [
{
address_n: path49,
prev_index: 0,
prev_hash:
"d29e7c4b1a0f3d5e6c7b8a99112233445566778899aabbccddeeff0012345678",
amount: "3471185",
script_type: "SPENDWITNESS",
},
];

const outputs = [
{
address_n: [
(49 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
(0 | 0x80000000) >>> 0,
1,
1,
],
amount: "3265985",
script_type: "PAYTOWITNESS", // Note: change
},
{
address: "1FfmbHfnpaZjKFvyi1okTjJJusN455paPH",
amount: "205200",
script_type: "PAYTOADDRESS", // Note: Payee
},
];

const refTxs = [
{
hash: "d29e7c4b1a0f3d5e6c7b8a99112233445566778899aabbccddeeff0012345678",
version: 1,
lock_time: 0,
inputs: [
/* 这里填写前序交易的 inputs */
],
bin_outputs: [
/* 这里填写前序交易的 outputs */
],
},
];

const { success, payload } = await ukeySdk.btcSignTransaction(
connectId,
deviceId,
{
coin: "btc",
inputs,
outputs,
refTxs,
locktime: 0,
},
);

if (success) {
console.info("Signed transaction payload:", payload.serializedTx); // Note: Can be broadcast directly
}

Final Checks

CheckpointWhat to do
Path and script44' maps to P2PKH, 49' to P2SH-P2WPKH, 84' to P2WPKH, and 86' to P2TR.
Previous transactionsFill refTxs completely for every input or the device will reject the request.
Change addressKeep address_n for change inside the same account path so it is not confused with a recipient output.
Screen reviewInput, output, change, and fee summaries should match what the device shows.
Result checkRebuild the transaction with bitcoinjs-lib or @scure/btc-signer, then compare txid and serializedTx.
Usual blockersSplit oversized inputs, tune outputs and dust handling when fees look off, and expose retry/cancel when the user rejects or times out.

Continue with the API pages: btcSignTransaction · btcGetAddress.