Skip to main content

Polkadot (DOT)

Sign Polkadot extrinsics and raw messages. You describe the extrinsic with a method and args; the app supplies the live runtime context (nonce, fee, era, spec version) — the bridge never fetches them.

FieldValue
FamilyPolkadot (Substrate)
coinType354
keyPathm/44'/354'/<accountIdx>'/0/0
amount unitPlanck (1 DOT = 1e10)
chainIdpolkadot:91b171bb158e2d3848fa23a9f1c25182/slip44:354

Supported Methods

MethodSupported
getAddress
signTransaction
signMessage✗ relay · ✓ parachains
signTypedData
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.

MethodDCENT Biometric WalletDCENT X
getAddress2.19.1 or higher1.0.0 or higher
signTransaction2.19.1 or higher1.0.0 or higher
signMessage (parachains only)2.30.2 or higher1.0.0 or higher
info

Firmware versions below are for the DCENT Biometric Wallet line — DCENT X supports them from 1.0.0. Astar and other Substrate parachains are a separate device family (Parachain), added in firmware 2.30.0 — distinct from Polkadot (DOT).

getAddress — account address

Retrieves the Polkadot account address for a chainId + keyPath. The returned address is an SS58-encoded string using the Polkadot network prefix (0). Call this before building an extrinsic so you can confirm the sender. Polkadot has a single address format, so no addressFormat is needed.

FieldTypeRequiredSourceDescription
chainIdstringRequiredAppCAIP-19 — polkadot:91b171bb158e2d3848fa23a9f1c25182/slip44:354
keyPathstringRequiredAppBIP-44 path that derives the account

Request

await dcent.getAddress({
chainId: 'polkadot:91b171bb158e2d3848fa23a9f1c25182/slip44:354',
keyPath: "m/44'/354'/0'/0/0"
})

Response

FieldTypeDescription
body.parameter.addressstringSS58-encoded Polkadot address (network prefix 0), e.g. 111111111111111111111111111111111HC1
header.statusstring"success" on completion
{
"header": {
"version": "1.0",
"status": "success"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "111111111111111111111111111111111HC1"
}
}
}

signTransaction <extrinsic>

The transaction is a Substrate extrinsic: method (pallet call, e.g. balances.transferAllowDeath) plus args. For balances.transferAllowDeath, args[0] is the recipient SS58 address and args[1] is the amount in Planck. The sender is derived from keyPath on the device — the bridge does not look the account up on a node.

FieldTypeRequiredSourceDescription
methodstringRequiredAppPallet call, e.g. balances.transferAllowDeath
args[0]stringRequired-RealAppRecipient SS58 address (Polkadot prefix 0)
args[1]stringRequiredAppAmount in Planck (1 DOT = 1e10)
erastringRequired-RealAppReal value from a live node — the playground preset's placeholder (all-zero / 0) is rejected by the network if sent as-is
noncenumberRequired-RealAppReal value from a live node — the playground preset's placeholder (all-zero / 0) is rejected by the network if sent as-is
tipnumberRequiredAppPriority tip in Planck — send 0 for none. Omitting the field fails with -32602
specVersionnumberRequired-RealAppReal value from a live node — the playground preset's placeholder (all-zero / 0) is rejected by the network if sent as-is
transactionVersionnumberRequired-RealAppReal value from a live node — the playground preset's placeholder (all-zero / 0) is rejected by the network if sent as-is
blockHashhexRequired-RealAppReal value from a live node — the playground preset's placeholder (all-zero / 0) is rejected by the network if sent as-is
genesisHashhexRequired-RealAppReal value from a live node — the playground preset's placeholder (all-zero / 0) is rejected by the network if sent as-is
feestringRequired-RealAppThe fee your app estimated from a live node — this is what the device displays. Must be present and a non-negative, finite number; omitting it or sending a negative / non-numeric value fails with -32602. 0 passes the gate but makes the device show Fee 0

Request

await dcent.sign({
method: 'signTransaction',
chainId: 'polkadot:91b171bb158e2d3848fa23a9f1c25182/slip44:354',
payload: {
keyPath: "m/44'/354'/0'/0/0",
transaction: {
"method": "balances.transferAllowDeath",
"args": [ "111111111111111111111111111111111HC1", "1000000000000" ],
"era": "0x00",
"nonce": 0,
"tip": 0,
"specVersion": 9430,
"transactionVersion": 24,
"blockHash": "0x91b1…",
"genesisHash": "0x91b1…",
"fee": "158000000"
}
}
})

Response

FieldTypeDescription
body.parameter.signaturehexBroadcast-ready signed extrinsic for Polkadot (SCALE-encoded), ready to submit via author_submitExtrinsic — this is the full serialized signed transaction, not a bare signature
body.commandstring"signTransaction"
header.statusstring"success" on completion
{
"header": {
"status": "success"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "0x…"
}
}
}
info

Only method, args[0] and args[1] are consumed when building the extrinsic. era / nonce / specVersion / transactionVersion / blockHash / genesisHash must be real, currently-valid values fetched from a live node — the playground preset's all-zero placeholder is rejected by the network if sent as-is. Omitting any of them does not make the wallet self-derive a value — the request fails with -32602 before anything is signed.

Polkadot is the one exception to the no-RPC rule: the bridge reads runtime metadata for display safety, because a Polkadot payload hash cannot be constructed without it. It does not fetch block headers, and it never re-derives the consensus fields — era, nonce, blockHash and the rest still come from your app.

Variant — extra.scaleHex (SCALE-encoded unsigned extrinsic)

Send the pre-encoded SCALE unsigned call/extrinsic body under extra.scaleHex instead of method/args. extra.scaleHex is the byte-authoritative field the device signs; the top-level payload fields listed under signTransaction above are still required.

danger

Form A0 and Form B are mutually exclusive. Sending method/args alongside extra.scaleHex is rejected with -32602"Polkadot accepts either 'extra.scaleHex' (Form A0) or 'method'+'args' (Form B), not both — remove one". The reason is drift: when the same recipient/amount lives in two places the two copies diverge — measured (2026-07-22), only the recipient inside the blob was changed while args[0] was left as is, so the displayed field and the signed bytes pointed at different recipients. The wallet refuses rather than picking a winner. If you send the blob, treat it as blind-signing.

FieldTypeRequiredSourceDescription
extra.scaleHexhex stringRequired-RealAppSCALE-encoded unsigned call/extrinsic body, hex-prefixed 0x…
all eight top-level fieldsRequiredAppera / nonce / tip / fee / specVersion / transactionVersion / blockHash / genesisHash — the blob encodes only the call, so every one of them is still sent as a top-level payload field (see the signTransaction table above)

Request

await dcent.sign({
method: 'signTransaction',
chainId: 'polkadot:91b171bb158e2d3848fa23a9f1c25182/slip44:354',
payload: {
keyPath: "m/44'/354'/0'/0/0",
transaction: {
"era": "0x00",
"nonce": 0,
"tip": 0,
"fee": "158000000",
"specVersion": 9430,
"transactionVersion": 24,
"blockHash": "0x91b1…",
"genesisHash": "0x91b1…",
"extra": {
"scaleHex": "0x0a00000000000000000000000000000000000000000000000000000000000000000000070010a5d4e8"
}
}
}
})
warning

The blob is chain-specific — not a portable representative encoding. A SCALE call begins with (pallet index, call index), and the pallet index differs per runtime. Encode extra.scaleHex against the chain the bridge actually signs on: for DOT that is Polkadot Asset Hub (Balances = pallet 10 → 0x0a00…), not the relay chain (pallet 5 → 0x0500…). The same bytes decode to a different call on the other chain — a relay-encoded balances.transferAllowDeath reads as preimage.notePreimage on Asset Hub, and it signs without error.

info

Missing or malformed extra.scaleHex returns -32602. This is the byte-authoritative field the device signs, but it only encodes the call (pallet + args) — era/nonce/tip/fee/specVersion/transactionVersion/blockHash/genesisHash are not inside the blob and must still be sent as top-level payload fields with real values, exactly as in the decoded form above.

warning

Non-native token transfer (Assets pallet) is not supported as a friendly, self-contained path. balances.transferAllowDeath/transferKeepAlive above move the native asset (DOT / ASTR / etc.) only. An assets.transfer(assetId, target, amount) extrinsic is only reachable by encoding it yourself into extra.scaleHex above and blind-signing it — the device shows no asset symbol or decimals, only the raw call bytes were verified. There is no descriptor form (no transaction.token) for Polkadot.

signMessage <raw bytes>

warning

Parachains only. signMessage on the relay chain (polkadot:91b171bb…/slip44:354) returns -32601 — the device supports it for parachain currencies only (Astar, Shibuya, Creditcoin). Measured 2026-07-21.

await dcent.sign({
method: 'signMessage',
chainId: 'polkadot:9eb76c5184c4ab8679d2d5d819fdf90b/slip44:810',
payload: {
keyPath: "m/44'/810'/0'/0/0",
message: '0x48656c6c6f',
meta: {
kind: 'raw'
}
}
})
// → body.parameter.signature
FieldTypeRequiredSourceDescription
keyPathstringRequired-RealWalletPath that signs the message
messagehex stringRequiredAppRaw bytes to sign (hex recommended)
meta.kindenumOptionalApp'raw'

Response

FieldTypeDescription
body.parameter.signaturehexRaw signature over the supplied bytes — a bare signature only, not a broadcast-ready transaction
header.statusstring"success" on completion

Substrate networks — Astar, Shibuya, Creditcoin

Astar, Shibuya (Astar's testnet) and Creditcoin (plus its testnet) sign through the same signTransaction / balances.transferAllowDeath path shown above — only the chainId and SS58 self-address encoding change. Unlike DOT (1 DOT = 1e10 Planck) these sidechains use 18 decimalsargs[1] for 1 token is 1000000000000000000. See Supported Substrate Networks for the full chainId / keyPath / SS58-prefix table (including Creditcoin Testnet).

Common Mistakes

danger

Amount not in Planck — 1 DOT = 1e10 Planck (not 1e12). args[1] is a Planck string. Sender not reachable / not on-chain — the bridge does not derive nonce / fee / era from the keyPath account; every one of those values comes from the app. Sending a placeholder (0/all-zero) or stale value for era / nonce / specVersion / etc. — the node rejects it. Omitting a field entirely does not make the wallet fill it in — the request fails with -32602 and nothing is signed. Fetch fresh real values from a live node before every sign. Wrong address formatargs[0] must be a Polkadot SS58 address (prefix 0), not a Kusama or generic-Substrate encoding.