Skip to main content

Hedera (HBAR)

Sign Hedera transactions and messages. The device signs a CryptoTransfer built from balanced transfers[] (native HBAR) or tokenTransfers[] (HTS tokens). A Hedera accountId (0.0.x) is assigned at account creation and is not derivable from the key.

FieldValue
coinType3030
keyPathm/44'/3030'/<accountIdx>'
amount unittinybar (1 HBAR = 1e8)
chainIdhedera:mainnet/slip44:3030

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
getAddress2.19.3 or higher1.0.0 or higher
signTransaction2.19.3 or higher1.0.0 or higher

getAddress — account address

Retrieves the account public key for a chainId + keyPath. Because a Hedera accountId (0.0.x) is assigned at account creation and is not derivable from the key, the device returns the derived public key in parameter — resolve the matching accountId from a mirror node before building a CryptoTransfer. Hedera has no address variants, so addressFormat does not apply.

FieldTypeRequiredSourceDescription
chainIdstringRequiredAppCAIP-19 — hedera:mainnet/slip44:3030
keyPathstringRequiredAppBIP-44, default m/44'/3030'/0'

Request

await dcent.getAddress({
chainId: 'hedera:mainnet/slip44:3030',
keyPath: "m/44'/3030'/0'"
})

Response

FieldTypeDescription
addressstringThe account public key derived from keyPath. Hedera does not return a 0.0.x accountId from the device — use this public key to look up the accountId on a mirror node.
{
"header": {
"version": "1.0",
"status": "success"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "302a30…"
}
}
}
info

The returned value is the account's public key, not a 0.0.x address. Resolve the accountId from a mirror node before populating transfers[].accountId / tokenTransfers[].accountId.

signTransaction <CryptoTransfer>

Variant — native HBAR transfer

FieldTypeRequiredSourceDescription
typestringRequiredAppAlways "CryptoTransfer"
transfers[].accountIdstringRequired-RealWalletReal Hedera account 0.0.x — sender side must be the device account (not key-derivable)
transfers[].amountnumberRequiredApptinybar; negative = debit (sender), positive = credit (recipient); all entries sum to 0
memostringOptionalAppTransaction memo (empty string allowed)
maxTransactionFeenumberRequiredAppFee cap in tinybar
transactionValidDurationnumberOptionalAppValidity window in seconds (e.g. 120)

Request:

await dcent.sign({
method: 'signTransaction',
chainId: 'hedera:mainnet/slip44:3030',
payload: {
keyPath: "m/44'/3030'/0'",
transaction: {
"type": "CryptoTransfer",
"transfers": [
{
"accountId": "0.0.587690",
"amount": -100000000
},
{
"accountId": "0.0.587690",
"amount": 100000000
}
],
"memo": "",
"maxTransactionFee": 100000000,
"transactionValidDuration": 120
}
}
})

Response

FieldTypeDescription
header.statusstring"success" on completion; an error envelope otherwise
body.commandstring"signTransaction"
body.parameter.signaturestringThe broadcast-ready serialized signed Hedera transaction (signed CryptoTransfer protobuf bytes), not a bare signature — submit it directly to a Hedera node
{
"header": {
"status": "success"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "<signed tx bytes>"
}
}
}

Variant — HTS token transfer

info

Token model: form-D descriptor (same pattern as TRON / Havah / Stacks). Always send a compact token descriptor instead of raw tokenTransfers[] (see the variant below) — the bridge builds the CryptoTransfer envelope itself, no node timestamp or registry lookup required. See Core Concepts → Token descriptors.

HTS (Hedera Token Service) transfers carry tokenTransfers[] instead of transfers[]. There is no native HBAR movement (no transfers field). Amounts are in the token's own base units — for SAUCE (6 decimals) 1.5 SAUCE = 1500000, not tinybar.

FieldTypeRequiredSourceDescription
typestringRequiredAppAlways "CryptoTransfer"
tokenTransfers[].tokenIdstringRequiredAppHTS token id 0.0.x — a registered mainnet token resolves display metadata automatically; an unregistered token still signs, but use the token descriptor variant below instead of raw tokenTransfers[]
tokenTransfers[].accountIdstringRequired-RealWalletReal Hedera account 0.0.x; recipient must have associated the token first
tokenTransfers[].amountnumberRequiredAppToken base units; negative/positive pair summing to 0
memostringOptionalAppTransaction memo
maxTransactionFeenumberRequiredAppFee cap in tinybar

Request:

await dcent.sign({
method: 'signTransaction',
chainId: 'hedera:mainnet/slip44:3030',
payload: {
keyPath: "m/44'/3030'/0'",
transaction: {
"type": "CryptoTransfer",
"tokenTransfers": [
{
"tokenId": "0.0.731861",
"accountId": "0.0.587690",
"amount": -1500000
},
{
"tokenId": "0.0.731861",
"accountId": "0.0.587690",
"amount": 1500000
}
],
"memo": "",
"maxTransactionFee": 100000000
}
}
})

Response

FieldTypeDescription
header.statusstring"success" on completion; an error envelope otherwise
body.commandstring"signTransaction"
body.parameter.signaturestringThe broadcast-ready serialized signed Hedera transaction (signed CryptoTransfer protobuf bytes carrying the HTS transfer), not a bare signature — submit it directly to a Hedera node
{
"header": {
"status": "success"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "<signed tx bytes>"
}
}
}

Variant — HTS form-D descriptor

Send a compact token descriptor instead of raw tokenTransfers[]. The bridge builds the balanced two-leg CryptoTransfer (negative sender / positive recipient) from { contract, to, amount } and signs — no HTS registry lookup, no node timestamp. from (or sender / owner_address) is read-only and identifies the debit leg — omitting it fails with -32602. amount is a base-unit number (not rescaled by decimals).

payload.transaction:

{
"token": {
"contract": "0.0.731861",
"to": "0.0.587690",
"amount": 1500000,
"decimals": 6,
"symbol": "SAUCE"
},
"from": "0.0.587690",
"maxTransactionFee": 100000000
}
FieldTypeRequiredDescription
token.contractstringRequiredHTS token id 0.0.x; unregistered tokens allowed
token.tostringRequiredRecipient 0.0.x (must have associated the token)
token.amountnumberRequiredBase-unit number; not rescaled by decimals
token.decimalsnumberOptionalDisplay metadata only
token.symbolstringOptionalDisplay metadata only
fromstringRequired-RealDevice Hedera account that signs (read-only; sender / owner_address also accepted). Omitting it fails with -32602

Variant — unsignedTx (blind-sign passthrough)

Send a pre-built, frozen Hedera Transaction serialized to bytes (hex) under unsignedTx. The device blind-signs the bytes as-is — this is the only fully self-contained signTransaction form for Hedera; it covers any operation (TokenCreate / Mint / Burn / TransferTransaction / …), not just transfers. Missing or malformed bytes return -32602.

FieldTypeRequiredSourceDescription
unsignedTxhex stringRequired-RealAppSerialized, frozen Hedera Transaction bytes (hex) built outside the device

Request

await dcent.sign({
method: 'signTransaction',
chainId: 'hedera:mainnet/slip44:3030',
payload: {
keyPath: "m/44'/3030'/0'",
transaction: {
"unsignedTx": "0a9d012a9a010a95010a160a080880e2cfaa06100012080800100018aaef2318..."
}
}
})

signMessage Not supported — returns -32601

FieldTypeRequiredSourceDescription
keyPathstringRequiredWalletSigning path, default m/44'/3030'/0'
messagestringRequiredAppMessage bytes to sign

Request:

await dcent.sign({
method: 'signMessage',
chainId: 'hedera:mainnet/slip44:3030',
payload: {
keyPath: "m/44'/3030'/0'",
message: '<bytes>'
}
})

Response

FieldTypeDescription
header.statusstring"success" on completion; an error envelope otherwise
body.commandstring"signMessage"
body.parameter.signaturestringThe raw signature over the supplied message bytes (not a serialized transaction)
{
"header": {
"status": "success"
},
"body": {
"command": "signMessage",
"parameter": {
"signature": "<signature>"
}
}
}

Supported transaction types

Hedera has two signing paths:

  • Structured operation — pass a typed object and the bridge builds + signs the bytes: TransferTransaction (HBAR + HTS transfers), TokenAssociate / TokenDissociate.
  • Blind-sign passthrough — build and freeze the transaction yourself (Hedera SDK) and pass the serialized unsignedTx bytes; the device blind-signs any operation (TokenCreate / Mint / Burn / etc.). Matches v1 getHederaSignedTransaction.
info

The structured ops the bridge builds are limited to transfer + associate / dissociate. Everything else (TokenCreate / Mint / Burn / Freeze / …, AccountCreate / Update) goes through blind-sign passthrough — the device shows raw bytes rather than a parsed summary.

Common Mistakes

danger

Using a key-derived address as accountId — a Hedera accountId (0.0.x) is assigned at account creation, not derived from the key. Replace transfers[].accountId / tokenTransfers[].accountId with your real device Hedera account. Unbalanced transfers — the negative (sender) and positive (recipient) amounts must sum to exactly 0. Wrong amount unit for tokens — HTS tokenTransfers[].amount is in token base units (SAUCE = 6 decimals), not tinybar. Recipient has not associated the token — an HTS transfer fails unless the recipient ran TokenAssociate for that token first.