Ethereum / EVM (ETH)
Sign transactions, messages, and EIP-712 typed data on Ethereum and the other EVM-compatible networks (mainnets and testnets) listed in the Support Matrix. All EVM chains share one payload shape — only chainId changes (and XDC / XDC Apothem use their own default keyPath).
| Field | Value |
|---|---|
| Family | Ethereum (EVM) |
| coinType | 60 |
| keyPath | m/44'/60'/<accountIdx>'/0/0 |
| chainId · Mainnet | eip155:1/slip44:60 |
Supported Methods
| Method | Supported |
|---|---|
| getAddress | ✓ |
| signTransaction | ✓ |
| signMessage | ✓ |
| signTypedData EIP-712 | ✓ |
| 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 | 1.0.0 or higher | 1.0.0 or higher |
signTransaction | 1.0.0 or higher | 1.0.0 or higher |
signMessage | 1.3.0 or higher | 1.0.0 or higher |
signTypedData | 2.11.1 or higher | 1.0.0 or higher |
Firmware versions above are for the DCENT Biometric Wallet line — DCENT X supports them from 1.0.0. Values are for Ethereum mainnet. Some EVM chains need newer firmware — 64-bit chainIds require 2.19.7, and several chains were added later (e.g. BSC 2.6.1, Polygon 2.8.0).
getAddress — account address
Retrieves the EVM address. Returns a checksummed address — 0x… (EIP-55) on most chains; see the Response table for XDC and RSK.
| Field | Type | Required | Notes |
|---|---|---|---|
chainId | string | Required | Full CAIP-19 — eip155:1/slip44:60 |
keyPath | string | Required | BIP-44 derivation path — default m/44'/60'/0'/0/0 |
Request
await dcent.getAddress({
chainId: 'eip155:1/slip44:60',
keyPath: "m/44'/60'/0'/0/0"
})
Response
| Field | Type | Notes |
|---|---|---|
body.parameter.address | string | Checksummed EIP-55 0x address (40 hex chars). On DCENT X, XDC / XDC Apothem return an xdc… address and RSK / RSK Testnet use the RSKIP-60 checksum. EVM does not return a separate public-key field. |
header.status | string | "success" on completion |
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "getAddress"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "0x6A5C…"
}
}
}
Supported chainIds
| Network | chainId |
|---|---|
| Ethereum | eip155:1/slip44:60 |
| Sepolia (testnet) | eip155:11155111/slip44:60 |
| Polygon | eip155:137/slip44:60 |
| BNB Smart Chain | eip155:56/slip44:60 |
| Arbitrum One | eip155:42161/slip44:60 |
| Optimism | eip155:10/slip44:60 |
| Base | eip155:8453/slip44:60 |
| Avalanche C-Chain | eip155:43114/slip44:60 |
Every EVM chain uses this exact payload shape — only the chainId changes (always the full eip155:N/slip44:60), plus the keyPath on XDC / XDC Apothem. Full list on Supported EVM Chains.
Kaia (Klaytn) has its own page — see Kaia.
keyPaths
| Use | Path |
|---|---|
| Default | m/44'/60'/0'/0/0 |
| Account index N | m/44'/60'/N'/0/0 |
| XDC / XDC Apothem | m/44'/550'/N'/0/0 (default). XDC mainnet also accepts m/44'/60'/N'/0/0, a separate account |
signTransaction
Signs an EVM transaction. Returns an RLP-encoded signed raw transaction ready for eth_sendRawTransaction.
Payload — native transfer (EIP-1559)
payload.transaction:
{
"type": 2,
"to": "0x000000000000000000000000000000000000dEaD",
"value": "0x2386f26fc10000",
"gasLimit": "0x5208",
"maxFeePerGas": "0x77359400",
"maxPriorityFeePerGas": "0x3b9aca00",
"nonce": "0x0",
"data": "0x"
}
| Field | Type | Required | Source | Unit | Notes |
|---|---|---|---|---|---|
type | number | Optional | App | — | 2 = EIP-1559 (recommended), 1 = EIP-2930 (accessList), 0 = legacy. The declared type must match the fields you send (e.g. type: 2 without maxPriorityFeePerGas → -32602). EIP-4844 / EIP-7702 fields are rejected with -32602. These type rules do not apply to Kaia — see its page |
to | hex addr | Required-Real | App | — | Recipient |
value | hex | Optional | App | wei | 0x2386f26fc10000 = 0.01 ETH. Omitted or empty → 0. Must be a non-negative integer no larger than 2^256−1, otherwise -32602 |
gasLimit | hex | Required-Real | App | — | app estimate. gas is accepted as an alias. Missing → -32602 |
gasPrice | hex | Optional | App | wei | Legacy fee for type 0 / 1. One of gasPrice or maxFeePerGas is required (-32602); on a type 0 / 1 transaction without gasPrice, maxFeePerGas is used as the gas price. A non-zero gasPrice always wins — on a type 2 transaction it is signed as maxFeePerGas, so do not send both |
maxFeePerGas | hex | Required-Real for type 2 | App | wei | EIP-1559 fee. Required for type 2 (a type 2 request without it fails with -32602); if a non-zero gasPrice is also sent, that value is signed in its place. On type 0 it may be sent instead of gasPrice and is then used as the gas price |
maxPriorityFeePerGas | hex | Required-Real for type 2 | App | wei | EIP-1559 tip — signed as sent, capped at the signed maxFeePerGas (a token on DCENT's supported list is signed as legacy type 0 — see the form-D note) |
nonce | hex | Required-Real | App | — | Account nonce |
data | hex | Optional | App | — | 0x for native; calldata for contracts |
chainId | number | hex | Optional | App | — | If present it overrides the EIP-155 chainId taken from the request chainId — omit it or keep it identical |
Request
await dcent.sign({
method: 'signTransaction',
chainId: 'eip155:1/slip44:60',
payload: {
keyPath: "m/44'/60'/0'/0/0",
transaction: {
type: 2,
to: '0x000000000000000000000000000000000000dEaD',
value: '0x2386f26fc10000',
gasLimit: '0x5208',
maxFeePerGas: '0x77359400',
maxPriorityFeePerGas: '0x3b9aca00',
nonce: '0x0',
data: '0x'
}
}
})
Response
| Field | Type | Notes |
|---|---|---|
body.parameter.signature | hex | Broadcast-ready RLP-encoded signed raw transaction (not a bare r/s/v signature). Pass directly to eth_sendRawTransaction. |
body.command | string | "signTransaction" |
header.status | string | "success" on completion |
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "signTransaction"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "0x02f8…"
}
}
} // RLP signed raw tx
Variants
ERC-20 transfer a9059cbb
Token model: form-D descriptor. Instead of encoding data yourself (below), you can describe the transfer under transaction.token — contract, to and amount are required, and the signer builds the same 0xa9059cbb calldata from them (see the form-D example below). symbol and decimals are optional. A token on DCENT's supported list is signed as a token transfer with its registered values. A token that is not on the list is signed as a plain contract call — symbol / decimals are not used for it. Do not send an integer decimals from 0 to 255 for an unregistered token on Arbitrum One, Palm, Metadium Testnet, Polygon Amoy, Astar zkEVM, EIOB Chain, VERY Mainnet, XPLA and Basecamp Testnet: there the request currently fails with internal_error (-32603). XDC / XDC Apothem have no token handling — raw data is signed as a plain contract call and the form-D transaction.token descriptor is not supported there. See Core Concepts → Token descriptors.
payload.transaction:
{
"type": 2,
"to": "0x<token-contract>",
"value": "0x0",
"gasLimit": "0x186a0",
"maxFeePerGas": "0x77359400",
"maxPriorityFeePerGas": "0x3b9aca00",
"nonce": "0x0",
"data": "0xa9059cbb<recipient 32B><amount 32B>"
}
| Field | Type | Required | Source | Description |
|---|---|---|---|---|
to | hex addr | Required-Real | App | The token contract address — not the recipient. |
value | hex | Optional | App | 0x0 — no native value is sent; the token amount lives in data. |
data | hex | Required | App | Selector 0xa9059cbb + recipient (32B, left-padded) + amount (32B, token base units). |
import { Interface } from 'ethers'
const data = new Interface(['function transfer(address,uint256)'])
.encodeFunctionData('transfer', [recipient, amount]) // amount in base units
Form-D descriptor — the same transfer, described instead of encoded. payload.transaction:
{
"type": 2,
"gasLimit": "0x186a0",
"maxFeePerGas": "0x77359400",
"maxPriorityFeePerGas": "0x3b9aca00",
"nonce": "0x0",
"token": {
"contract": "0x<token-contract>",
"to": "0x<recipient>",
"amount": "1000000",
"decimals": 6,
"symbol": "USDT"
}
}
| Field | Type | Required | Source | Description |
|---|---|---|---|---|
token.contract | hex addr | Required | App | Token contract address. Without a non-empty string contract (or, when contract is omitted or null, its assetId / address alias), token is ignored. |
token.to | hex addr | Required | App | Recipient. Missing or invalid → -32602. |
token.amount | string | Required | App | Non-negative integer in token base units; not rescaled by decimals. Missing, negative, non-integer or above the uint256 range → -32602. |
token.decimals | number | Optional | App | Not used to encode the amount. A token on DCENT's supported list uses its registered values; for any other token it is not used (and on the networks listed above an integer value from 0 to 255 makes the request fail). |
token.symbol | string | Optional | App | Not used for signing; see token.decimals. |
data | hex | — | App | Omit. A non-null data sent together with token is rejected with -32602. |
The signer sets the transaction's to to token.contract, value to 0x0, and data to the encoded transfer(token.to, token.amount). For a token on DCENT's supported list (form-D descriptor or raw transfer calldata; not on XDC — see above) the device signs a legacy (type 0) transaction: the gas price is gasPrice if sent, otherwise maxFeePerGas, and maxPriorityFeePerGas / accessList are dropped. A token not on the list keeps the declared type.
ERC-20 approve 095ea7b3
data = 0x095ea7b3 + spender (32B) + amount (32B). Prefer an exact amount over unlimited (0xff…).
ERC-721 transfer 42842e0e
to = NFT contract; data = 0x42842e0e + from (32B) + to (32B) + tokenId (32B). 23b872dd (transferFrom) and b88d4fde (safeTransferFrom with data) are sent the same way — NFT calls are signed as raw data; the bridge does not decode them.
Token transfer — three ways. Raw data, a form-D transaction.token descriptor (both above), or the structured transaction.contract = {address, to, value, decimals} field, which the connector forwards unchanged and the bridge turns into transfer calldata. All three sign (not on XDC — raw data only, see above), but only raw data and the form-D descriptor identify a token on DCENT's supported list; transaction.contract is always signed as a plain contract call.
Token labeling (not XDC — see above). A token on DCENT's supported list is signed as a token transfer — whether you describe it with a form-D descriptor or send its raw transfer calldata in data. A token that is not on the list does not error (except with an integer decimals from 0 to 255 on the networks listed under form-D above): it is signed as a plain contract call with the original {to: contract, value: 0x0, data}, the same as the WalletConnect native path. Playground preset: evm-erc20-transfer-unregistered.
signMessage
XDC: supported since wallet-models v0.8.16-rc.2 (2026-07-29). An earlier sweep (2026-07-21) found signMessage returning -32601 on XDC (eip155:50/slip44:60) and XDC Apothem (eip155:51/slip44:60), because the XINFIN family had no slot for it. That release wires XINFIN to the Ethereum family implementation, so both networks now sign messages and EIP-712 typed data. Keep handling -32601 if you target an older bridge deployment.
await dcent.sign({
method: 'signMessage',
chainId: 'eip155:1/slip44:60',
payload: {
keyPath: "m/44'/60'/0'/0/0",
message: '0x48656c6c6f',
meta: {
kind: 'personal'
}
}
})
// → body.parameter.signature
| Field | Type | Required | Notes |
|---|---|---|---|
message | hex string | Required | EIP-191 personal message (hex recommended) |
meta.kind | enum | Optional | 'personal' (default) | 'eip712'. 'eip712' routes to signTypedData: put the JSON typed data in message and set meta.version ('V1' | 'V3' | 'V4'), otherwise -32602. 'raw' is not supported on EVM and fails with internal_error (-32603) |
Response
| Field | Type | Notes |
|---|---|---|
body.parameter.signature | hex | EIP-191 personal-sign signature — 65-byte r/s/v hex (0x…). This is a bare signature, not a serialized transaction. |
header.status | string | "success" on completion |
signTypedData EIP-712
await dcent.sign({
method: 'signTypedData',
chainId: 'eip155:1/slip44:60',
payload: {
keyPath: "m/44'/60'/0'/0/0",
data: JSON.stringify({
types,
primaryType,
domain,
message
}),
version: 'V4'
}
})
// → body.parameter.signature
| Field | Type | Required | Notes |
|---|---|---|---|
data | string | Required | JSON.stringify of the typed data — for V3 / V4 the { types, primaryType, domain, message } object, for V1 the legacy array [{ type, name, value }, …] |
version | enum | Required | 'V1' | 'V3' | 'V4' |
Response
| Field | Type | Notes |
|---|---|---|
body.parameter.signature | hex | EIP-712 typed-data signature — 65-byte r/s/v hex (0x…) over the hashed struct. A bare signature, not a serialized transaction. |
header.status | string | "success" on completion |
Common Mistakes
Wrong chainId — replay protection differs per chain; use the exact eip155:N. Stale nonce / gas — refetch from RPC immediately before signing. ERC-20 to set to the recipient instead of the token contract. Mixed-case token addresses — prefer all-lowercase (on RSK with DCENT X, getAddress returns an RSKIP-60 / EIP-1191 checksummed address, not EIP-55).