VeChain (VET)
Sign VeChain Thor transactions. The app builds a Thor transaction (thor-devkit shape) with one or more clauses; the device signs the transaction hash by its keyPath. Note: only the first clause is used.
| Field | Value |
|---|---|
| Family | VeChain |
| coinType | 818 |
| keyPath | m/44'/60'/<accountIdx>'/0/0 |
| amount unit | wei |
| chainId | vechain:b1ac3413d346d43539627e6be7ec1b4a/slip44:818 |
Supported Methods
| Method | Supported |
|---|---|
| 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.
| 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 (same key as the EVM derivation). VeChain has no address variants, so no addressFormat is needed. |
{
"header": {
"version": "1.0",
"status": "success"
},
"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": "0x0000000000000000",
"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 | Required | App | — | Network tag (74 = mainnet) — last byte of the genesis block id |
blockRef | hex | Required-Real | App | — | Real reference block — the app must supply it (non-zero); not auto-filled |
expiration | number | Required | App | blocks | Block count the tx stays valid after blockRef |
clauses[].to | hex addr | Required-Real | App | — | Recipient (native) or token contract (VIP-180) |
clauses[].value | hex | Required | App | wei | VET amount; 0x0 for token transfers (amount lives in data) |
clauses[].data | hex | Optional | App | — | 0x for native; calldata for contracts (opaque pass-through) |
gasPriceCoef | number | Required | App | — | Gas price coefficient (0–255) |
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 | Required | App | — | Transaction nonce |
Request
await dcent.sign({
method: 'signTransaction',
chainId: 'vechain:b1ac3413d346d43539627e6be7ec1b4a/slip44:818',
payload: {
keyPath: "m/44'/60'/0'/0/0",
transaction: {
chainTag: 74,
blockRef: '0x0000000000000000',
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 broadcast-ready signed raw Thor transaction (RLP-encoded), not a bare signature. Broadcast this value as-is to a Thor node — do not re-encode it. |
{
"header": {
"version": "1.0",
"status": "success"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "0xf8…"
}
}
} // signed raw tx
Variant — VIP-180 token transfer a9059cbb
Token model: form-D descriptor. Always send the token under transaction.token with its symbol and trustworthy decimals (read from the chain yourself) — the bridge signs with no network access and never looks symbol / decimals up, so without the descriptor it fails closed with -32602. 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": "0x0000000000000000",
"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. The bridge uses only clauses[0] — multi-clause batches are not supported. VIP-180 token transfers are now identified (m02-05-75): the bridge decodes the transfer(recipient,amount) calldata via extractVechainTokenIdentity and synthesizes a TokenAccount, so the device shows the token symbol/amount — no longer opaque. A token still signs, but 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 it can resolve neither (no descriptor) or the calldata is malformed 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. (the bridge)
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 (chainTag, blockRef, expiration, gas, gasPriceCoef, fee, nonce, dependsOn) are preserved. 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. 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 | Required | Base-unit exponent; enables descriptor resolution on the device. |
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. |
chainTag / expiration / gasPriceCoef / nonce / dependsOn | mixed | Required | Thor envelope fields; passed through into the signed RLP. |
Verified on device (2026-07-20): this descriptor displays as VeUSD 0.001 — 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 — VeChain uses the EVM path m/44'/60'/0'/0/0, not m/44'/818'/…. Stale blockRef — refresh blockRef (and nonce) immediately before signing or the tx expires. Multiple clauses — only the first clause is signed; extra clauses are ignored. VIP-180 to set to the recipient instead of the token contract, with the amount left out of data.