HAVAH (HVH)
Sign HAVAH transactions. HAVAH is an ICON-family chain, so transactions use the ICON-native wire shape (from/to/value/stepLimit/stepPrice/timestamp/nonce). The bridge converts this wire into a HavahTransaction before the device signs.
| Field | Value |
|---|---|
| coinType | 858 |
| keyPath | m/44'/858'/<accountIdx>'/0/0 |
| amount unit | loop |
| chainId | havah:mainnet/slip44:858 |
| chainId · Testnet | havah:testnet/slip44:858 |
Supported Methods
| Method | Supported |
|---|---|
| getAddress | ✓ |
| signTransaction | ✓ |
| signMessage | ✗ |
| signTypedData | ✗ |
| signData | ✗ |
| signAuthEntry | ✗ |
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.
| Method | DCENT Biometric Wallet | DCENT X |
|---|---|---|
getAddress | 2.26.0 or higher | 1.0.0 or higher |
signTransaction | 2.26.0 or higher | 1.0.0 or higher |
getAddress — account address
Retrieves the HAVAH account address for a chainId + keyPath. HAVAH uses the ICON-family hx… address format (there are no address variants). Call this before building a transaction so from matches the device account.
| Field | Type | Required | Source | Description |
|---|---|---|---|---|
chainId | string | Required | App | Full CAIP-19 id — havah:mainnet/slip44:858 |
keyPath | string | Required | App | BIP-44 derivation path — default m/44'/858'/0'/0/0 |
Request
await dcent.getAddress({
chainId: 'havah:mainnet/slip44:858',
keyPath: "m/44'/858'/0'/0/0"
})
Response
| Field | Type | Description |
|---|---|---|
header.status | string | success on a completed request |
body.command | string | Echoes the method — getAddress |
body.parameter.address | string | HAVAH account address in ICON-family hx… format |
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "getAddress"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "hx…"
}
}
}
signTransaction — ICON-native wire
Variant — native HVH transfer
payload.transaction:
{
"from": "hx0000000000000000000000000000000000000000",
"to": "hx0000000000000000000000000000000000000000",
"value": "0xDE0B6B3A7640001",
"stepLimit": "0x186a0",
"stepPrice": "0x2e90edd000",
"timestamp": "<current time in µs, hex>",
"nid": "0x100",
"nonce": "0x1",
"version": "0x3"
}
| Field | Type | Required | Source | Description |
|---|---|---|---|---|
from | string | Required-Real | Wallet | Device HAVAH account (hx…). Not checked against the keyPath account — a different address is signed as sent |
to | string | Required-Real | App | Recipient (hx… for native send) |
value | hex | Required | App | Amount in loop, 0x-prefixed hex (1 HVH = 1e18 loop); a plain decimal string is read as base-10. Send it as a string — values exceed MAX_SAFE_INTEGER |
stepLimit | hex | Required | App | Step (gas) limit, hex. When provided together with stepPrice the bridge builds a self-contained preparedFee |
stepPrice | hex | Required-Real | App | Real, current step price, hex. Missing or zero → -32602; a stale value is not detected. Not part of the signed transaction — it sets the fee sent to the device (stepPrice × stepLimit) |
timestamp | hex | Required-Real | App | ICON microsecond epoch. The wallet never fills this in — the value you send is the one that is signed. Omitting it fails with -32602 |
nid | hex | Optional | App | Ignored — the wallet signs 0x100 on mainnet and 0x101 on testnet, from the chainId |
nonce | hex | Optional | App | Transaction nonce, hex. Defaults to 0x1 if omitted; 0x0 (or 0) is dropped from the signed transaction |
version | hex | Optional | App | Ignored — the wallet always signs version 0x3 |
js — Request:
await dcent.sign({
method: 'signTransaction',
chainId: 'havah:mainnet/slip44:858',
payload: {
keyPath: "m/44'/858'/0'/0/0",
transaction: {
from: "hx0000000000000000000000000000000000000000",
to: "hx0000000000000000000000000000000000000000",
value: "0xDE0B6B3A7640001",
stepLimit: "0x186a0",
stepPrice: "0x2e90edd000",
timestamp: "<current time in µs, hex>",
nid: "0x100",
nonce: "0x1",
version: "0x3"
}
}
}) // → body.parameter.signature
Response
| Field | Type | Description |
|---|---|---|
header.status | string | success on a completed signature |
body.command | string | Signing command echo — signTransaction |
body.parameter.signature | string | The device signature (65 bytes, r‖s‖recovery id, on DCENT X) as base64 — not a serialized transaction. To broadcast, set it as the signature field of the ICON transaction exactly as it was signed — nid from the chainId, version 0x3, the nonce you sent (or 0x1; a 0x0 nonce is left out), and numeric fields as lowercase 0x hex — and call icx_sendTransaction |
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "signTransaction"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "<base64 signature>"
}
}
}
Variant — HSP-20 token transfer
Token model: form-D descriptor. A token on DCENT's supported list is signed with its registered symbol and decimals. For any other token, send a form-D descriptor under transaction.token — contract, to, amount, decimals and optionally symbol (without contract — or its aliases assetId / address — the descriptor is ignored) — the bridge signs with no network access and uses the decimals you supply. See Core Concepts → Token descriptors.
HSP-20 (IRC-2-like) tokens use the ICON call data shape. to is the token contract (cx…), the real recipient is data.params._to, and native value stays 0x0 (no native HVH moves).
payload.transaction:
{
"dataType": "call",
"from": "hx0000000000000000000000000000000000000000",
"to": "cxd7fed2ae1d9b2314087520efe1870c33e5c5baa4",
"value": "0x0",
"stepLimit": "0x186a0",
"stepPrice": "0x2e90edd000",
"timestamp": "<current time in µs, hex>",
"nid": "0x100",
"nonce": "0x1",
"version": "0x3",
"data": {
"method": "transfer",
"params": {
"_to": "hx0000000000000000000000000000000000000000",
"_value": "0xDE0B6B3A7640001"
}
}
}
| Field | Type | Required | Source | Description |
|---|---|---|---|---|
dataType | enum | Required-Real | App | Must be call for a contract call — not checked: without it the request is signed as a native HVH transfer to the cx… address and data is dropped |
to | string | Required-Real | App | Token contract address (cx…), not the recipient |
value | hex | Required | App | Must be present and non-negative; it is not part of the signed call — keep 0x0 |
data.method | string | Required | App | Contract method — transfer |
data.params._to | string | Required-Real | App | Real token recipient (hx…) |
data.params._value | hex | Required | App | Token amount in base units, 0x-prefixed hex (PER has 18 decimals; 0xF4240 = 1e6 for a 6-decimal token) |
timestamp | hex | Required-Real | App | ICON microsecond epoch — the wire path never fills it in; omitting it fails with -32602 |
js — Request:
await dcent.sign({
method: 'signTransaction',
chainId: 'havah:mainnet/slip44:858',
payload: {
keyPath: "m/44'/858'/0'/0/0",
transaction: {
dataType: "call",
from: "hx0000000000000000000000000000000000000000",
to: "cxd7fed2ae1d9b2314087520efe1870c33e5c5baa4",
value: "0x0",
stepLimit: "0x186a0",
stepPrice: "0x2e90edd000",
timestamp: "<current time in µs, hex>",
nid: "0x100",
nonce: "0x1",
version: "0x3",
data: { method: "transfer",
params: { _to: "hx0000000000000000000000000000000000000000",
_value: "0xDE0B6B3A7640001" } }
}
}
}) // → body.parameter.signature
Response
| Field | Type | Description |
|---|---|---|
header.status | string | success on a completed signature |
body.command | string | Signing command echo — signTransaction |
body.parameter.signature | string | The device signature (65 bytes, r‖s‖recovery id, on DCENT X) as base64 — not a serialized transaction. To broadcast, set it as the signature field of the ICON transaction exactly as it was signed — nid from the chainId, version 0x3, the nonce you sent (or 0x1; a 0x0 nonce is left out), no value field for the HSP-20 call, and numeric fields (including _value) as lowercase 0x hex — and call icx_sendTransaction |
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "signTransaction"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "<base64 signature>"
}
}
}
Registered HSP-20 tokens are looked up on the selected network — HAVAH mainnet and testnet each have their own list. A transfer call to an unregistered HSP-20 contract is rejected with -32602 — to sign one, use the form-D descriptor below with token.decimals (see Core Concepts → Token descriptors). A dataType:"call" whose to is not a cx… SCORE address → -32602; a SCORE method other than transfer → -32601.
Variant — HSP-20 form-D descriptor
Send a compact token descriptor instead of a full ICON dataType:"call" envelope. The bridge builds the transfer(_to,_value) SCORE call from { contract, to, amount }, rewrites to to the contract address, and sets value to 0x0. The other envelope fields (from, stepLimit, stepPrice, timestamp, nonce) are handled as in the table below; nid and version are ignored (nid comes from the chainId, version is 0x3). amount is a base-unit string (not rescaled by decimals).
payload.transaction:
{
"token": {
"contract": "cxd8303731bf989a1597a6d38505d7a9483c4d9a38",
"to": "hx0000000000000000000000000000000000000000",
"amount": "1000000",
"decimals": 6,
"symbol": "GODH"
},
"from": "hx0000000000000000000000000000000000000000",
"stepLimit": "0x186a0",
"stepPrice": "0x2e90edd000",
"timestamp": "<current time in µs, hex>",
"nid": "0x100",
"nonce": "0x1",
"version": "0x3"
}
| Field | Type | Required | Notes |
|---|---|---|---|
token.contract | string | Required | SCORE address; must match cx + 40 hex chars. Unregistered tokens allowed. |
token.to | string | Required | Recipient (hx…). Becomes data.params._to. |
token.amount | string | Required | Base-unit integer string; not rescaled by decimals. 0 is allowed. |
token.decimals | number | Optional (Required for an unregistered token) | Base-unit exponent; lets the bridge resolve an unregistered token (the device receives it for display). An unregistered token without token.decimals is rejected with -32602. |
token.symbol | string | Optional | Display metadata only. |
from | string | Required-Real | Device HAVAH account that signs (hx…). Omitting it fails with -32602. |
stepLimit / stepPrice | string | Required | Map to the fee gate (gasLimit / fee). Zero or missing fails the gate. |
nid / nonce / version | string | Optional | nonce is signed as sent (0x1 if omitted; 0x0 is left out); nid and version are ignored (nid from the chainId, version 0x3). |
timestamp | hex | Required-Real | ICON microsecond epoch — the wire path never fills it in; omitting it fails with -32602 |
This descriptor is signed as 1.0 GODH — amount "1000000" at decimals 6. Replace from with your own device HAVAH account (hx…) and token.to with the recipient.
Common Mistakes
Decimal amounts — value / _value are integer loop amounts: 0x-prefixed hex, or a plain decimal string read as base-10. Send them as strings because values exceed MAX_SAFE_INTEGER. Token to vs recipient — for HSP-20, to is the contract (cx…) and the recipient is data.params._to (hx…). Putting the recipient (hx…) in to of a dataType:"call" transaction is rejected with -32602. value on a token transfer — for HSP-20 the native value is ignored (it is not part of the signed call); leave it 0x0. Missing stepLimit — without it the bridge cannot build preparedFee. Missing stepPrice — the app must supply a real, current step price; it is not auto-filled.