VeChain (VET)
Sign VeChain Thor transactions. The app builds a Thor transaction (thor-devkit shape) with a single clause; the device signs the transaction hash by its keyPath. A transaction with more than one clause is rejected with -32601.
| Field | Value |
|---|---|
| Family | VeChain |
| coinType | 818 in the chainId (slip44:818); keys are derived on the EVM path m/44'/60' (m/44'/818' selects the separate VeChain Legacy account) |
| keyPath | m/44'/60'/<accountIdx>'/0/0 |
| amount unit | wei |
| chainId | vechain:b1ac3413d346d43539627e6be7ec1b4a/slip44:818 |
Supported Methods
| Method | Supported |
|---|---|
| getAddress | ✓ |
| signTransaction | ✓ |
| signMessage | ✗ |
| signTypedData | ✗ |
| signData | ✗ |
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.23.4 or higher | 1.0.0 or higher |
signTransaction | 2.23.4 or higher | 1.0.0 or higher |
VeChain derives its address on the EVM coinType 60 path (m/44'/60'/0'/0/0) even though its SLIP-44 coinType is 818. The chainId on the wire is always the full vechain:b1ac3413d346d43539627e6be7ec1b4a/slip44:818.
getAddress — account address
Retrieves the VeChain address. Returns a 0x address (same key as the EVM derivation).
| Field | Type | Required | Source | Notes |
|---|---|---|---|---|
chainId | string | Required | App | Full CAIP-19 — vechain:b1ac3413d346d43539627e6be7ec1b4a/slip44:818 |
keyPath | string | Required | App | BIP-32 path; VeChain uses the EVM coinType 60 path (m/44'/60'/0'/0/0) |
Request
await dcent.getAddress({
chainId: 'vechain:b1ac3413d346d43539627e6be7ec1b4a/slip44:818',
keyPath: "m/44'/60'/0'/0/0"
})
Response
| Field | Type | Notes |
|---|---|---|
header.status | string | "success" on success |
body.command | string | "getAddress" |
body.parameter.address | hex addr | The VeChain account address — an EVM-style 0x-prefixed 20-byte hex string (on m/44'/60' the same key as the EVM derivation; m/44'/818' selects the separate VeChain Legacy account). The keyPath alone picks the account — omit addressFormat (legacy is ignored; ledger / standard fail with -32602). |
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "getAddress"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "0x6A5C…"
}
}
}
signTransaction — Thor transaction
Signs a VeChain Thor transaction. Returns the signed raw transaction ready to broadcast.
Payload — native VET transfer
payload.transaction:
{
"chainTag": 74,
"blockRef": "0x00b3c1e4a5f6d7e8",
"expiration": 720,
"clauses": [
{
"to": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
"value": "0x10",
"data": "0x"
}
],
"gasPriceCoef": 0,
"gas": 21000,
"fee": "210000000000000000",
"dependsOn": null,
"nonce": "0x0"
}
| Field | Type | Required | Source | Unit | Notes |
|---|---|---|---|---|---|
chainTag | number | Optional | App | — | Ignored — the bridge always signs chain tag 74 (mainnet) |
blockRef | hex | Required-Real | App | — | Real reference block — 0x + 16 hex characters (8 bytes); the app must supply it, not auto-filled. Missing or malformed → -32602 |
expiration | number | Optional | App | blocks | Block count the tx stays valid after blockRef. Defaults to 18 if omitted |
clauses[].to | hex addr | Required-Real | App | — | Recipient (native) or token contract (VIP-180) |
clauses[].value | hex | Optional | App | wei | VET amount (hex or decimal string); omitted → 0. 0x0 for token transfers (amount lives in data) |
clauses[].data | hex | Optional | App | — | 0x for native; calldata for contracts (opaque pass-through) |
gasPriceCoef | number | Optional | App | — | Gas price coefficient (0–255). Defaults to 0 |
gas | number | Required-Real | App | — | Gas limit; app estimate |
fee | string | Required-Real | App | — | Total VTHO in wei-scale (display only — not part of the signed RLP). Paired with gas by the fee gate; omitting it fails with -32602 |
dependsOn | hex / null | Optional | App | — | Tx id this tx depends on, or null |
nonce | hex | Optional | App | — | Transaction nonce. Defaults to 0 |
Request
await dcent.sign({
method: 'signTransaction',
chainId: 'vechain:b1ac3413d346d43539627e6be7ec1b4a/slip44:818',
payload: {
keyPath: "m/44'/60'/0'/0/0",
transaction: {
chainTag: 74,
blockRef: '0x00b3c1e4a5f6d7e8',
expiration: 720,
clauses: [
{
to: '0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd',
value: '0x10',
data: '0x'
}
],
gasPriceCoef: 0,
gas: 21000,
fee: '210000000000000000',
dependsOn: null,
nonce: '0x0'
}
}
})
Response
| Field | Type | Notes |
|---|---|---|
header.status | string | "success" on success |
body.command | string | "signTransaction" |
body.parameter.signature | hex | The signed raw Thor transaction (RLP-encoded) as hex without 0x, not a bare signature — prepend 0x when posting it as raw to a Thor node; do not re-encode it. |
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "signTransaction"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "f8…"
}
}
} // signed raw tx
Variant — VIP-180 token transfer a9059cbb
Token model: form-D descriptor. For a token not on DCENT's supported list, send a form-D descriptor under transaction.token — contract, to, amount, trustworthy decimals (read from the chain yourself) and optionally symbol — the bridge signs with no network access and never looks symbol / decimals up, so an unregistered token without the descriptor fails closed with -32602 (a registered token such as VTHO resolves without one). See Core Concepts → Token descriptors.
VIP-180 is VeChain's ERC-20-compatible token standard (e.g. VTHO, contract 0x0000000000000000000000000000456E65726779). The token amount lives in data; value stays 0x0.
payload.transaction:
{
"chainTag": 74,
"blockRef": "0x00b3c1e4a5f6d7e8",
"expiration": 720,
"clauses": [
{
"to": "0x0000000000000000000000000000456E65726779",
"value": "0x0",
"data": "0xa9059cbb<recipient 32B><amount 32B>"
}
],
"gasPriceCoef": 0,
"gas": 80000,
"fee": "800000000000000000",
"dependsOn": null,
"nonce": "0x0"
}
clauses[0].to= the token contract (not the recipient). Required-Realclauses[0].value=0x0(token amount is encoded indata).clauses[0].data= selector0xa9059cbb+ recipient (32B, left-padded) + amount (32B, token base units). Example amount0x3e8= 1000 base units.
Single-clause; VIP-180 identified. Only one clause is supported — a multi-clause transaction is rejected with -32601. VIP-180 token transfers are identified when the clause value is 0 or omitted (with a non-zero value the clause is not decoded as a token: it is signed as a native VET clause with value and the original data as sent): the bridge decodes the transfer(recipient,amount) calldata and sends the token symbol and decimals with the transaction, so the device can show the token amount. A registered token (e.g. VTHO, on the default m/44'/60' account) signs without a descriptor; an unregistered token signs only with a form-D descriptor that carries decimals — the bridge signs with no network access and never looks the metadata up for you. If an unregistered token has no descriptor, or the transfer calldata has the wrong length, it fail-closes with -32602, so a token transfer is never blind-signed as a 0-VET native tx. Non-token clause data stays opaque.
Variant — VIP-180 form-D descriptor
Send a compact token descriptor instead of building clauses yourself. The bridge ABI-encodes a9059cbb transfer(address,uint256) from { contract, to, amount } and emits a single clause { to: contract, value: "0", data }. Your envelope fields (blockRef, expiration, gas, gasPriceCoef, fee, nonce, dependsOn) are used as sent; chainTag is ignored (always 74). amount is a base-unit string (not rescaled by decimals).
payload.transaction:
{
"chainTag": 74,
"blockRef": "0x00b3c1e4a5f6d7e8",
"expiration": 720,
"token": {
"contract": "0x4e17357053da4b473e2daa2c65c2c949545724b8",
"to": "0x0000000000000000000000000000000000000000",
"amount": "1000",
"decimals": 6,
"symbol": "VeUSD"
},
"gasPriceCoef": 0,
"gas": 80000,
"fee": "800000000000000000",
"dependsOn": null,
"nonce": "0x0"
}
| Field | Type | Required | Notes |
|---|---|---|---|
token.contract | string | Required | VIP-180 contract; must match 0x + 40 hex chars (checked case-insensitively). Unregistered tokens allowed. |
token.to | string | Required | Recipient; also validated as 0x + 40 hex so a malformed address is rejected instead of silently padded. |
token.amount | string | Required | Base-unit integer string, must fit uint256; 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. |
blockRef | string | Required-Real | 8-byte block reference. The bridge does not refresh it — send a fresh value. |
gas / fee | number / string | Required | Map to the fee gate (gasLimit / fee). fee is the total in wei-scale VTHO. |
expiration / gasPriceCoef / nonce / dependsOn | mixed | Optional | Thor envelope fields; signed as sent, or 18 / 0 / 0 / null if omitted. chainTag is ignored (always 74). |
This descriptor is signed as 0.001 VeUSD — amount "1000" at decimals 6. Note that fee is display-only; it is not part of the signed RLP (VeChain derives the actual cost from gas and gasPriceCoef on-chain).
Common Mistakes
Wrong keyPath — the default VeChain account uses the EVM path m/44'/60'/0'/0/0. m/44'/818'/… selects the separate VeChain Legacy account (a different address), so use it only when you mean that account. Stale blockRef — refresh blockRef immediately before signing — together with expiration it bounds how long the transaction stays valid. Multiple clauses — a transaction with more than one clause is rejected with -32601. VIP-180 on the Legacy account — on m/44'/818'/… every token, including VTHO, is treated as not on DCENT's list: send form-D with decimals. VIP-180 to set to the recipient instead of the token contract, with the amount left out of data.