Skip to main content

Bitcoin (BTC)

UTXO-based signing. Provide the previous transactions for the inputs you spend; the device signs each input by its keyPath.

FieldValue
FamilyBitcoin (UTXO)
coinType0
keyPathm/44'/0'/<accountIdx>'/0/0
addressFormatlegacysegwit-native
chainIdbip122:000000000019d6689c085ae165831e93/slip44:0

Supported Methods

MethodSupported
getAddress
signTransaction
signMessage
signTypedData

Requirements

Minimum firmware for each method. The two models are on different version lines, so DCENT Biometric Wallet and DCENT X are listed separately (the numbers are not comparable across models). Below this the request returns the firmware-update error (5005) and the bridge prompts to update.

MethodDCENT Biometric WalletDCENT X
getAddress1.0.0 or higher1.0.0 or higher
signTransaction1.0.0 or higher1.0.0 or higher
info

Firmware versions below are for the DCENT Biometric Wallet line — DCENT X supports them from 1.0.0. SegWit (bech32) addresses require firmware 1.7.0 or higher.

addressFormat

FormatPathStatus
legacy (P2PKH)m/44'/0'/0'/0/0Supported
segwit-native (P2WPKH, bech32)m/44'/0'/0'/0/0Supported
segwit-wrapped (P2SH-P2WPKH, BIP-49)Not supported
taproot (P2TR, BIP-86)m/86'/0'/0'/0/0Not supported (no registry entry — param_error)
info

Receiving-side Taproot is a different question. This table is about whose key signs — DCENT has no Taproot account, so it cannot spend a bc1p… UTXO. Sending to a Taproot address works: put the bc1p… address in outputs[].addresses with txType: 'p2tr'. So legacy→Taproot and SegWit→Taproot transfers are fine; Taproot→anything is not.

info

DCENT derives every supported Bitcoin format from m/44'. legacy and segwit-native share the same m/44'/0'/0'/0/0 key — addressFormat only changes how that key is encoded (P2PKH Base58 vs. P2WPKH bech32), not the derivation path. Unlike BIP-84 wallets, a m/84' / m/49' keyPath is rejected.

getAddress — account address

Retrieves the Bitcoin address for a keyPath. The returned format follows addressFormat paired with the derivation purpose: legacy (P2PKH, m/44') returns a Base58Check address starting with 1; segwit-native (P2WPKH, m/44') returns a bech32 address starting with bc1. Both formats derive from the same m/44' key — only the encoding differs.

Parameters

FieldTypeRequiredSourceNotes
chainIdstringRequiredAppFull CAIP-19 — bip122:000000000019d6689c085ae165831e93/slip44:0
keyPathstringRequiredAppBIP-44 — always m/44' (both legacy and segwit-native); addressFormat selects the encoding
addressFormatenumOptionalApplegacy / segwit-native only (both m/44', encoding differs).
segwit-wrapped — not supported
taprootnot supported (no registry entry; rejected with param_error)

Request

await dcent.getAddress({
chainId: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
keyPath: "m/44'/0'/0'/0/0",
addressFormat: 'segwit-native'
})

Response

FieldTypeNotes
body.parameter.addressstringThe Bitcoin address — the encoding is chosen by addressFormat (keyPath is always m/44'). Bitcoin does not return a separate public key.
legacy → Base58Check P2PKH (1…)
segwit-native → bech32 P2WPKH (bc1…)
{
"header": {
"version": "1.0",
"status": "success"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "bc1q…"
}
}
}

signTransaction

Payload

FieldTypeRequiredSourceNotes
keyPathstringRequiredAppBIP-44 — always m/44'
addressFormatenumOptionalAppSame enum as getAddress. Selects which Bitcoin variant signs. Omit it and the variant is inferred from inputs[].txType (see below).
legacy / segwit-native — supported
segwit-wrapped / taproot — rejected with param_error (no registry entry yet)
transactionobject | stringRequiredAppThe UTXO payload below, or a PSBT base64 string
info

Pass the addressFormat you got from getAccountInfo. Bitcoin's legacy and SegWit accounts share one chainId and one m/44' keyPath, so neither of those two fields identifies which account is signing. Without addressFormat the signer falls back to reading inputs[].txType, and that signal is missing from PSBT payloads. getAccountInfo already returns meta.addressFormat for each account — send it straight back on signTransaction.

If you send both and they disagree (for example addressFormat: 'legacy' with txType: 'p2wpkh' inputs), the request is rejected with -32602 rather than one of them silently winning.

payload.transaction:

{
"inputs": [
{
"rawTransaction": "<prev tx hex>",
"index": 0,
"txType": "p2pkh",
"keyPath": "m/44'/0'/0'/0/0",
"sequence": 4294967295
}
],
"outputs": [
{
"txType": "p2pkh",
"amount": "990000",
"addresses": [
"<recipient>"
]
}
]
}
FieldTypeRequiredSourceUnitNotes
inputs[].rawTransactionhexRequired-RealWalletFull prev-tx hex of the UTXO; must be device-owned
inputs[].indexnumberRequiredAppUTXO output index
inputs[].txTypeenumRequiredAppThe script type of the UTXO being spent. When payload.addressFormat is omitted this doubles as the fallback signal for legacy vs SegWit (chainId and keyPath are identical across both variants). Prefer the explicit addressFormat — this fallback cannot express every case: p2sh is ambiguous (legacy multisig vs wrapped SegWit) and PSBT payloads carry no txType at all.
p2pkh = legacy scriptSig → legacy
p2wpkh / p2wsh = native SegWit witness → segwit-native
p2sh = accepted, but it maps to no format — the signer falls back to the legacy account, so send an explicit addressFormat
p2tr = Taproot — not supported: the currency registry has no BIP-86 variant, so this value is rejected with param_error (a firmware update does not change it)
All inputs must share one txType; mixed script types are rejected with -32602.
inputs[].keyPathstringRequired-RealWalletPath that signs this input
inputs[].sequencenumberOptionalAppNot defaulted to 0xffffffff — when omitted the bridge uses 0xfffffffd for RBF-enabled coins and leaves it unset otherwise. Also note the first input's value applies to the whole transaction.
outputs[].txTypeenumRequiredAppThe output script type — a different set from inputs[].txType. p2pkh / p2sh / p2wpkh / p2wsh, plus p2tr for a Taproot recipient and change for the change output.
p2trsupported: you can send to a bc1p… address. Use it whenever addresses[0] is Taproot; labelling a bc1p… address as anything else is rejected with param_error.
change — must not be a bc1p… address: DCENT cannot spend a Taproot UTXO, so that change would be stranded forever.
outputs[].amountstringRequiredAppsatoshiOutput value
outputs[].addressesstring[]Required-RealAppRecipient. Only addresses[0] is used, and exactly one non-change output is allowed — more than one destination is rejected with param_error. Use a second output with txType: 'change' for change.
optionobject | hexRequiredAppZcash only — every other Bitcoin-family currency omits this field. (Horizen also used it for the BIP-115 replay-protection tail, but the native ZEN chain was shut down by the 2025-07-23 migration to a Base ERC-20, and signTransaction for it is no longer supported.) Zcash: a 16-hex-char string (GroupId + consensus branch ID). The signer has no network access to derive either value itself; omitting it (or, for Zcash, supplying a branch ID that a network upgrade has made stale) is rejected with -32602.

Response

{
"header": {
"status": "success"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "<signed raw tx hex>"
}
}
}
FieldTypeNotes
header.statusstring"success" when the device completes signing
body.commandstring"signTransaction"
body.parameter.signaturehexThe broadcast-ready signed raw transaction hex for Bitcoin — not a bare signature. Submit it with sendrawtransaction.

Variants — multi-step builder

As an alternative to the single-call form, a stateful builder is available:

const tx = dcent.getBitcoinTransactionObject()
dcent.addBitcoinTransactionInput(tx, prevTxHex, utxoIndex, 'p2pkh', "m/44'/0'/0'/0/0")
dcent.addBitcoinTransactionOutput(tx, 'p2pkh', 990000, recipient)
// then sign

Common Mistakes

danger

Assuming BIP-84 m/84' for native segwit — on DCENT both legacy and native segwit derive from m/44'; addressFormat alone selects the encoding. A m/84' keyPath is rejected. Dropping addressFormat on signTransactionchainId and keyPath are the same for the legacy and SegWit accounts, so a signing request without addressFormat falls back to inputs[].txType; send back the meta.addressFormat from getAccountInfo instead. Missing change output — unspent input value becomes miner fee. Amounts not in satoshi. Missing option for Zcash — the signer cannot fetch the consensus branch ID itself; the app must supply it (a 16-hex-char string). Horizen is discontinued and signTransaction is rejected for it before any of this applies. Omitting it is rejected with -32602.