Cardano (ADA)
Sign Cardano transactions (CIP-30 CBOR or structured inputs/outputs) and CIP-8/CIP-30/CIP-95 data.
| Field | Value |
|---|---|
| Family | Cardano |
| coinType | 1815 |
| chainId | cip34:1-764824073 |
| chainId · Testnet | cip34:0-2 (Preview) |
| keyPath | m/1852'/1815'/<accountIdx>'/0/0 |
| stake | m/1852'/1815'/<accountIdx>'/2/0 |
| DRep | m/1852'/1815'/<accountIdx>'/3/0 |
Derivation purpose — what 1852' and 44' each support
DCENT resolves two Cardano key trees from the same chainId. The purpose segment of the
keyPath you send selects which one you get.
| keyPath | Purpose | What it supports |
|---|---|---|
m/1852'/1815'/… | CIP-1852 | Everything — address, plain transfer, reward (stake) address, CIP-30 App connection, staking / withdrawal certificates, CIP-95 governance, signData |
m/44'/1815'/… | BIP-44 (legacy) | Plain transfer only — getAddress (address, without rewardAddress), getPublicKey on m/44'/1815'/<account>'/0/<index> or …/1/<index> (single-key shape), and ordinary signTransaction |
Send m/1852' unless you are working with existing legacy accounts. Everything on this page
uses 1852'.
Why legacy is transfer-only
On purpose 44' DCENT X builds every address with a fixed stake credential derived at
m/44'/1815'/0'/2/0 — regardless of the account index you asked for. So for account index ≥ 1, a
stake key derived at m/44'/1815'/<accountIdx>'/2/0 is not the one embedded in that account's
address. Signing a certificate with it would register or delegate a credential that holds none of
that account's funds, and the result would look successful.
Rather than hand back a key that does not match the address, these surfaces are refused on 44'.
What legacy returns instead
| Call | On m/1852' | On m/44' |
|---|---|---|
getAddress | address + rewardAddress | address only — no rewardAddress field |
getPublicKey | { payment, stake, drep } bundle | role segment 2 or 3, in any notation (2, 2', or the hardened integer) → method_not_found (-32601) — this check comes first, whatever the path length; m/44'/1815'/<account>'/0/<index> or …/1/<index> (account hardened, role and index unhardened) → single { keyPath, publicKey }; any other form → param_error (-32602, Invalid keyPath) |
signData (CIP-8 / 30 / 95) | ✓ | method_not_found (-32601) |
signTransaction — ordinary transfer | ✓ | ✓ |
signTransaction — CIP-30 CBOR | ✓ | param_error (-32602) |
signTransaction — certificates / withdrawal / governance / mint / Plutus, auxiliaryData, requiredSigners, includeNetworkId: true | ✓ | param_error (-32602) |
getPublicKey on 44' returns the single-key shape, not a partial bundle. Branch with
'payment' in response.body.parameter rather than
assuming a bundle whenever the chain is Cardano.
Codes above are shown as body.error.code (the v1 string) with the numeric JSON-RPC semantic in
parentheses. Branch on the string — the numeric value never appears in body.error.code. See
Error Codes.
Supported Methods
Support below is for m/1852'. See Derivation purpose
for what m/44' refuses.
| Method | Supported |
|---|---|
| getAddress | ✓ |
| signTransaction | ✓ |
| signMessage | ✗ |
| signTypedData | ✗ |
| signData CIP-8 / 30 / 95 | ✓ |
| getPublicKey | ✓ |
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 device_fw_incompatible (5005) and the bridge prompts to update.
The numbers below are for m/1852', which is what this page documents.
The device exposes the CIP-1852 tree as a separate coin entry, added in DCENT Biometric Wallet 2.36.2 / DCENT X 1.0.1. Below that version the device does not report it at all, so every call on m/1852' fails with device_fw_incompatible (5005) — the method does not matter. Check the device firmware before you branch on the method table.
| Method | DCENT Biometric Wallet | DCENT X |
|---|---|---|
getAddress | 2.36.2 or higher | 1.0.1 or higher |
signTransaction | 2.36.2 or higher | 1.0.1 or higher |
signData | 2.36.2 or higher | 1.0.1 or higher |
getPublicKey | 2.36.2 or higher | 1.0.1 or higher |
Legacy m/44' predates the CIP-1852 coin entry, so the floors above apply to m/1852' and not to it — its own floor is older and lower. getAddress and ordinary signTransaction work on firmware below the CIP-1852 floor on both models, as far back as the device reports the CARDANO coin entry. getPublicKey and signData sit behind a different gate, the WalletConnect capability floor (DCENT Biometric Wallet 2.35.0; every DCENT X firmware meets its 1.0.0 floor), and below it they return device_fw_incompatible (5005).
The surfaces that 44' refuses are listed under Derivation purpose, and no upgrade opens them. The param_error (-32602) refusals — CIP-30 CBOR and the extended structured shapes — are decided host-side, without a signing request ever reaching the device. signData and stake / DRep getPublicKey return method_not_found (-32601) once the request clears the capability floor above; on a Biometric wallet below 2.35.0 you see 5005 instead, and on DCENT X you always see -32601. Use m/1852' instead.
getAddress — account address
Retrieves the Cardano account address for a chainId + keyPath. Returns a Shelley-era bech32 address (addr1… base address; addr_test1… on testnet) plus a rewardAddress (stake address) when the keyPath uses purpose 1852' — on purpose 44' the rewardAddress field is absent. getAddress does not return a public key — call getPublicKey for the payment / stake / DRep key bundle. The keyPath purpose, not addressFormat, picks the account — omit addressFormat.
Parameters
| Field | Type | Required | Source | Notes |
|---|---|---|---|---|
chainId | string | Required | App | Full CAIP value cip34:1-764824073 |
keyPath | string | Required | App | CIP-1852, purpose 1852'. purpose 44' also returns an address but without rewardAddress — see Derivation purpose |
Request
await dcent.getAddress({
chainId: 'cip34:1-764824073',
keyPath: "m/1852'/1815'/0'/0/0"
})
Response
| Field | Type | Notes |
|---|---|---|
body.parameter.address | string | Shelley bech32 base address (addr1…, or addr_test1… on testnet) combining payment + stake credentials |
body.parameter.rewardAddress | string | Stake (reward) address (stake1…, or stake_test1… on testnet). Present only on purpose 1852' — absent on purpose 44'. For public keys call getPublicKey. |
header.status | string | "success" on completion; "failure" otherwise |
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "getAddress"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "addr1…",
"rewardAddress": "stake1…"
}
}
}
Cardano uses the Shelley bech32 address format with the addr1 prefix (addr_test1 on testnet). The keyPath purpose (1852' or 44') picks the account, not addressFormat — omit it. On purpose 1852' the same call also returns a rewardAddress (stake address). For public keys — the payment / stake / DRep bundle — call getPublicKey.
getPublicKey — payment / stake / DRep keys
Returns the Cardano account public keys, each with its derivation path. Split out of getAddress — the pubkey bundle is no longer returned by getAddress.
Parameters
| Field | Type | Required | Source | Notes |
|---|---|---|---|---|
chainId | string | Required | App | Full CAIP value cip34:1-764824073 |
keyPath | string | Required | App | Account path, purpose 1852' (e.g. m/1852'/1815'/0'/0/0). Only the account index is read — the bundle is always derived at …/0/0, …/2/0 and …/3/0, so payment.keyPath may differ from the keyPath you sent. On purpose 44' this verb returns the single-key shape, not the role bundle |
addressFormat? | enum | Optional | App | Omit it — the keyPath purpose picks the account. The Bitcoin encoding values are ignored. ledger is accepted only with m/1852', where it names the same account; standard, or ledger with m/44', → -32602 |
Request
await dcent.getPublicKey({
chainId: 'cip34:1-764824073',
keyPath: "m/1852'/1815'/0'/0/0"
})
Response
| Field | Type | Notes |
|---|---|---|
header.status | string | "success" on completion; "failure" otherwise |
body.command | string | getPublicKey |
body.parameter.payment | { keyPath, publicKey } | Payment (spending) key — role 0 (…/0/0) |
body.parameter.stake | { keyPath, publicKey } | Stake (rewards / delegation) key — role 2 (…/2/0) |
body.parameter.drep | { keyPath, publicKey } | DRep governance key — role 3 (…/3/0) |
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "getPublicKey"
},
"body": {
"command": "getPublicKey",
"parameter": {
"payment": {
"keyPath": "m/1852'/1815'/0'/0/0",
"publicKey": "…"
},
"stake": {
"keyPath": "m/1852'/1815'/0'/2/0",
"publicKey": "…"
},
"drep": {
"keyPath": "m/1852'/1815'/0'/3/0",
"publicKey": "…"
}
}
}
}
The response shape is chain-specific: this { payment, stake, drep } bundle is Cardano's on purpose 1852'. Every other family — and Cardano on purpose 44' — returns a single { keyPath, publicKey }. See the getPublicKey page.
signTransaction
Simple send (senderAddress / receiverAddress / lovelaceToSend) needs UTXO lookup (RPC) and is unsupported over the signTransaction wire (returns -32602); send a completed txCbor or structured (inputs/outputs) transaction instead.
Variant — CIP-30 CBOR
payload.transaction:
{
"txCbor": "84a3008182582…"
} // built by the app with @emurgo/csl
The device signs the CBOR body hash (it does not validate UTXOs).
Variant — CIP-95 governance (DRep vote)
An App request: the app builds a CIP-95 transaction whose CBOR carries governance fields (a voting_procedures DRep vote, or a vote-delegation certificate). Governance signing has no floor of its own — it is covered by the CIP-1852 requirement above (DCENT Biometric Wallet 2.36.2 / DCENT X 1.0.1). Below that the device does not report the CIP-1852 coin entry at all and every call on m/1852' returns the firmware-update error device_fw_incompatible (5005).
payload.transaction — DRep vote:
{
"txCbor": "84a40081825820aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00018182581d61f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f01a000f4240021a00030d4013a18202581c33333333333333333333333333333333333333333333333333333333a1825820bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb008201f6a0f5f6"
} // CIP-95 voting_procedures: DRep key voter + one governance action
// source preset: ada-cbor-signtx-cip95-drep-vote
The example CBOR uses placeholder inputs (fixture data) — it exercises the signing path only and is not broadcastable. Rebuild it with your own stake key, live UTXOs and DRep id. The bridge detects the voter / certificate and adds the DRep or stake witness path; unsupported voter types (CC hot key or script, DRep script, SPO) and committee certificates (hot-auth / cold-resign) fail closed. A vote-delegation example is preset ada-cbor-signtx-cip95-vote-deleg. Spec: CIP-95.
Up to three device signatures. Governance / voting (CIP-95) transactions can require up to three device signatures — the payment witness is always signed, plus a DRep witness (e.g. votes, DRep certificates) and/or a stake witness (e.g. stake certificates, withdrawals) — so they take longer than a normal signature. The request timeout (setTimeOutMs) defaults to 180000 (180 s), which covers this.
Response
| Field | Type | Notes |
|---|---|---|
body.parameter.signature | hex | The serialized signed transaction — Cardano transaction CBOR (body + witness set), not a bare signature. On this txCbor path the transaction body bytes you supplied are kept byte-for-byte, and the result is always the 4-element array (84…): a 3-element (83…) input comes back as 84… with is_valid = true added. The structured path frames its result differently depending on the shape you send — see signTransaction — structured. |
header.status | string | "success" on completion; "failure" otherwise |
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "signTransaction"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "84a4…"
}
}
}
signTransaction — structured
Besides the opaque txCbor passthrough (the app builds the whole CBOR with @emurgo/csl and the device signs its body hash), Cardano also accepts a structured transaction: the app passes individual fields — inputs, outputs, fee, ttl, plus optional certificate / withdrawal / vote / mint / Plutus blocks — and the bridge assembles the CBOR host-side before signing. Unlike txCbor, every field stays editable, which is the point of this path.
Response — structured path
How the result is framed depends on which structured shape you send — the two are assembled by different code paths.
Ordinary transfer only — inputs / outputs / fee / ttl with none of the extended fields below present as keys. A key set to [] or {} still counts as present (except includeNetworkId, which counts only when it is true) and routes to the extended path, which rejects it with -32602, so omit it (or set it to null) rather than sending an empty value. This shape comes back as a 3-element [body, witness_set, auxiliary] array (83…). This differs from the 4-element (84…) result of every other path, so test it on a testnet before relying on it; if you need a known-good result today, build the CBOR yourself and use the opaque txCbor path.
Every other structured operation — anything carrying certificates, withdrawals, voting_procedures, mint, auxiliaryData, collateralInputs / collateral, collateralReturn, totalCollateral, referenceInputs, scriptDataHash, requiredSigners, or includeNetworkId: true — is assembled host-side with CSL and signed by the same signer as txCbor, so it comes back as the 4-element Conway array (84…).
Supported structured operations
| Operation | Structured field | Preset |
|---|---|---|
| Ordinary transfer | inputs / outputs | ada-structured-signtx-ordinary |
| Vote delegation | certificates[].VOTE_DELEGATION | ada-structured-signtx-vote-deleg |
| Stake registration + delegation | certificates[].STAKE_REGISTRATION / STAKE_DELEGATION | ada-structured-signtx-stake-registration |
| Reward withdrawal | withdrawals[] | ada-structured-signtx-withdrawal |
| DRep governance vote | voting_procedures[] | ada-structured-signtx-drep-vote |
| Native token mint / burn | mint[] | ada-structured-signtx-mint — 🔴 no script witness derived (see note below) |
| Plutus script | collateralInputs / referenceInputs / scriptDataHash | ada-structured-signtx-plutus — 🔴 no script witness derived (see note below) |
This table lists the operations documented here, not the full set the bridge accepts — certificates[].type also accepts STAKE_DEREGISTRATION, STAKE_AND_VOTE_DELEGATION, STAKE_REGISTRATION_AND_DELEGATION, STAKE_VOTE_REGISTRATION_AND_DELEGATION, DREP_REGISTRATION, DREP_UPDATE and DREP_DEREGISTRATION, and the base shape also accepts auxiliaryData and requiredSigners (body key 14).
Moving an existing multi-asset (native) token is not one of the structured operations above. outputs[].amount in every structured variant, including Ordinary transfer, is lovelace-only. outputs[].tokenBundle is recognized but rejected with -32602 param_error in this shape, so there is no supported way to attach a token balance to an output. mint[] above only mints or burns supply; it does not move an existing balance either. To send an existing native token, build the full CBOR yourself (including the multi-asset value) with @emurgo/csl and use the opaque CIP-30 CBOR path instead — the device signs the body hash without needing the token in any registry.
Example — ordinary transfer
payload.transaction:
{
"signingMode": "ORDINARY_TRANSACTION",
"inputs": [
{
"prev_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1v8e09uhj7te09uhj7te09uhj7te09uhj7te09uhj7te09us0kxhfc",
"amount": "2000000"
}
],
"fee": "170000",
"ttl": "100000000",
"networkId": 1,
"protocolMagic": 764824073
}
Before broadcasting, replace inputs[].prev_hash / prev_index with your own UTXO and outputs[].address with a valid bech32 recipient — the placeholder input (aaaa…) signs but cannot be broadcast. amount / fee are in lovelace (1 ADA = 1e6). The signing key is the top-level Key Path (m/1852'/1815'/0'/0/0); the per-input path is ignored. 🔴 Per-input amount changes the validation mode. Supply it on every input and the bridge enforces Σinput == Σoutput + fee exactly, rejecting anything else with -32602 param_error — so you must include your own change output. Omit it on any input and the check is skipped entirely — input amounts are never encoded into the transaction body, so the body is byte-identical either way and an unbalanced transaction signs without complaint, only to be rejected at submission. For certificate / withdrawal / vote operations the bridge derives the witness path automatically from the structured fields (stake or DRep role). 🔴 mint and Plutus are not covered. These paths do return a complete transaction, but its witness set carries vkey witnesses only — the policy script (mint) and the Plutus script, datum and redeemer are neither derived nor accepted as input (there is no field for them), so a structured mint / Plutus transaction signs but cannot be submitted. Build the complete CBOR yourself — body and the full witness set — and send it through the opaque txCbor path instead.
Structured base fields
Every structured variant shares this base transaction shape; each variant below adds its own fields.
| Field | Type | Required | Notes |
|---|---|---|---|
signingMode | string | Optional | Defaults to ORDINARY_TRANSACTION when omitted or null. Only the ordinary-transfer path validates it (any other value → -32602); on the extended variants it is not read at all, so the value shown in those examples is inert. |
inputs[].prev_hash | string (hex) | Required-Real | 32-byte source-tx hash of your UTXO (txHash alias accepted). Placeholder aaaa… signs but cannot broadcast. Per-input path/amount are optional; path is always ignored, and amount is read only on the ordinary-transfer path (where it selects the balance-check mode — see that example). The extended variants ignore it, so they perform no balance check. |
inputs[].prev_index | number | Required | Output index within the source tx (index alias accepted). |
outputs[].address | string (bech32) | Required-Real | Recipient address; CSL checksum-validated. |
outputs[].amount | string | Required | Lovelace (1 ADA = 1e6). |
fee | string | Required | Transaction fee in lovelace. |
ttl | string | Optional | Time-to-live — absolute slot upper bound. Encoded only when present. |
networkId | number | Optional | 1 = mainnet, 0 = testnet. Advisory on the ordinary path — not encoded there; the chainId decides the network. 🔴 On the extended path it is encoded (body key 15) when includeNetworkId is true, and then it must be exactly 0 or 1 or the request is -32602. |
protocolMagic | number | Optional | Mainnet 764824073; present in the ordinary example. |
Variant — CIP-95 vote delegation
Variant-specific fields (in addition to the base fields above):
| Field | Type | Required | Notes |
|---|---|---|---|
certificates[].type | string (enum) | Required | "VOTE_DELEGATION". |
certificates[].keyHash | string (hex) | Required-Real | 28-byte stake credential key-hash. |
certificates[].drep | object | Required-Real | Target DRep — {type:"KEY_HASH",keyHash} | {type:"ALWAYS_ABSTAIN"} | {type:"ALWAYS_NO_CONFIDENCE"}. |
Delegate your stake credential's voting power to a DRep via a certificates[] entry of type VOTE_DELEGATION. The bridge detects the certificate and signs with the stake witness path automatically.
payload.transaction:
{
"signingMode": "ORDINARY_TRANSACTION",
"inputs": [
{
"prev_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1qyenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxv6yg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqjeq96c",
"amount": "1000000"
}
],
"fee": "200000",
"ttl": "21084865",
"networkId": 1,
"certificates": [
{
"type": "VOTE_DELEGATION",
"keyHash": "a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0",
"drep": {
"type": "KEY_HASH",
"keyHash": "c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0"
}
}
]
}
Edit certificates[].keyHash (your 28-byte stake credential) and certificates[].drep — {type:'KEY_HASH',keyHash}, or {type:'ALWAYS_ABSTAIN'} / {type:'ALWAYS_NO_CONFIDENCE'}. The placeholder hashes sign but cannot be broadcast.
Variant — Stake registration + delegation
Variant-specific fields (in addition to the base fields above):
| Field | Type | Required | Notes |
|---|---|---|---|
certificates[].type | string (enum) | Required | STAKE_REGISTRATION or STAKE_DELEGATION. |
certificates[].keyHash | string (hex) | Required-Real | 28-byte stake credential (same on both certificates). |
certificates[].deposit | string | Optional | On STAKE_REGISTRATION — protocol key deposit in lovelace (e.g. 2000000). Omit it to emit the implicit-deposit (pre-Conway) certificate form. |
certificates[].poolKeyHash | string (hex) | Required-Real | On STAKE_DELEGATION — 28-byte target pool key-hash. |
Register a stake key (STAKE_REGISTRATION with deposit) and delegate it to a pool (STAKE_DELEGATION with poolKeyHash) in one transaction. The bridge signs both certificates with the stake witness path.
payload.transaction:
{
"signingMode": "ORDINARY_TRANSACTION",
"inputs": [
{
"prev_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1qyenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxv6yg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqjeq96c",
"amount": "1000000"
}
],
"fee": "200000",
"ttl": "21084865",
"networkId": 1,
"certificates": [
{
"type": "STAKE_REGISTRATION",
"keyHash": "a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0",
"deposit": "2000000"
},
{
"type": "STAKE_DELEGATION",
"keyHash": "a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0",
"poolKeyHash": "b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0"
}
]
}
Set both certificates[].keyHash to your stake credential, poolKeyHash to the target pool's key-hash, and deposit to the protocol key deposit (e.g. 2 ADA = 2000000 lovelace).
Variant — Reward withdrawal
Variant-specific fields (in addition to the base fields above):
| Field | Type | Required | Notes |
|---|---|---|---|
withdrawals[].rewardAddress | string (bech32) | Required-Real | Reward/stake address (stake1… / stake_test1…); key-credential only — a script credential is rejected at signing time with -32601 method_not_found, not at CBOR assembly. -32601 is the shared code for nearly every witness-analysis rejection, not a marker unique to this one (see Common Mistakes). |
withdrawals[].amount | string | Required | Reward lovelace to withdraw. |
Withdraw staking rewards via withdrawals[]. The bridge recognises a key-credential reward address and signs with the stake witness path (script-credential reward addresses are rejected).
payload.transaction:
{
"signingMode": "ORDINARY_TRANSACTION",
"inputs": [
{
"prev_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1qyenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxv6yg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqjeq96c",
"amount": "1000000"
}
],
"fee": "200000",
"ttl": "21084865",
"networkId": 1,
"withdrawals": [
{
"rewardAddress": "stake1u8gdp5xs6rgdp5xs6rgdp5xs6rgdp5xs6rgdp5xs6rgdp5qsy63p4",
"amount": "1500000"
}
]
}
Replace withdrawals[].rewardAddress with your bech32 reward address (stake1… / stake_test1…) and amount with the reward lovelace to claim.
Variant — CIP-95 DRep governance vote
Variant-specific fields (in addition to the base fields above):
| Field | Type | Required | Notes |
|---|---|---|---|
voting_procedures[].voter | object | Required-Real | {type:"DREP_KEY",keyHash} — 28-byte DRep key-hash. Any other voter type is rejected with -32602 (this shape cannot express it). |
voting_procedures[].govActionId | object | Required-Real | {txHash,index} — 32-byte governance-action tx hash + index. |
voting_procedures[].vote | string (enum) | Required | YES / NO / ABSTAIN. |
A registered DRep casts a vote on a governance action via voting_procedures[]. The bridge detects the DRep-key voter and signs with the DRep witness path (CC-Hot / DRep-script / SPO voters are rejected).
payload.transaction:
{
"signingMode": "ORDINARY_TRANSACTION",
"inputs": [
{
"prev_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1qyenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxv6yg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqjeq96c",
"amount": "1000000"
}
],
"fee": "200000",
"ttl": "21084865",
"networkId": 1,
"voting_procedures": [
{
"voter": {
"type": "DREP_KEY",
"keyHash": "c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0"
},
"govActionId": {
"txHash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"index": 0
},
"vote": "YES"
}
]
}
Set voter.keyHash to your 28-byte DRep key-hash, govActionId (txHash + index) to the target action, and vote to YES / NO / ABSTAIN.
Variant — Native token mint / burn
Variant-specific fields (in addition to the base fields above):
| Field | Type | Required | Notes |
|---|---|---|---|
mint[].policyId | string (hex) | Required-Real | 28-byte minting policy id. |
mint[].tokens[].assetNameHex | string (hex) | Required | 0–32-byte asset name (744443454e54 = "tDCENT"; default asset = ""). assetNameBytes alias accepted. |
mint[].tokens[].amount | string | Required | Signed 64-bit integer: positive = mint, negative = burn, 0 rejected. |
Mint or burn native tokens via mint[] (transaction body key 9). amount is a signed 64-bit integer string — positive mints, negative burns, zero is rejected.
payload.transaction:
{
"signingMode": "ORDINARY_TRANSACTION",
"inputs": [
{
"prev_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1qyenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxv6yg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqjeq96c",
"amount": "1000000"
}
],
"fee": "200000",
"ttl": "21084865",
"networkId": 1,
"mint": [
{
"policyId": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"tokens": [
{
"assetNameHex": "744443454e54",
"amount": "1000000"
}
]
}
]
}
🔴 The returned transaction's witness set carries vkey witnesses only — the policy-script witness is neither derived nor accepted as input, so this signs but cannot be submitted. Build the complete CBOR (body and witness set) and use the opaque txCbor path instead. Set mint[].policyId (28-byte policy), tokens[].assetNameHex (0–32-byte asset name; 744443454e54 = 'tDCENT', default asset = ''), and the signed amount.
Variant — Plutus script (collateral / reference inputs)
Variant-specific fields (in addition to the base fields above):
| Field | Type | Required | Notes |
|---|---|---|---|
collateralInputs[] | array | Required-Real | Collateral UTXOs {prev_hash,prev_index} (collateral alias accepted). |
scriptDataHash | string (hex) | Required-Real | 32-byte hash of redeemer + datum + cost-models. The bridge does not require it — a transaction built without it still reaches the device — but when present it must be 32-byte hex and needs collateralInputs, otherwise -32602 |
collateralReturn | object | Optional | Change for unused collateral {address,amount}. |
totalCollateral | string | Optional | Total collateral in lovelace. |
referenceInputs[] | array | Optional | Reference-script / inline-datum UTXOs {prev_hash,prev_index}. |
Assemble a Plutus-script transaction with scriptDataHash (body key 11), collateralInputs (13), collateralReturn (16), totalCollateral (17), and referenceInputs (18).
payload.transaction:
{
"signingMode": "ORDINARY_TRANSACTION",
"inputs": [
{
"prev_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1qyenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxv6yg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqjeq96c",
"amount": "1000000"
}
],
"fee": "200000",
"ttl": "21084865",
"networkId": 1,
"collateralInputs": [
{
"prev_hash": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"prev_index": 1
}
],
"collateralReturn": {
"address": "addr1qyenxvenxvenxvenxvenxvenxvenxvenxvenxvenxvenxv6yg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqjeq96c",
"amount": "4500000"
},
"totalCollateral": "500000",
"referenceInputs": [
{
"prev_hash": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"prev_index": 2
}
],
"scriptDataHash": "abababababababababababababababababababababababababababababababab"
}
🔴 The returned transaction's witness set carries vkey witnesses only — the Plutus witness (script, datum, redeemer) is neither derived nor accepted as input, so this signs but cannot be submitted. Build the complete CBOR (body and witness set) and use the opaque txCbor path instead. Replace the collateral / reference UTXOs (prev_hash / prev_index) with your own and scriptDataHash with the redeemer + datum + cost-model hash for your script.
signData CIP-8 / CIP-30 / CIP-95
await dcent.sign({
method: 'signData',
chainId: 'cip34:1-764824073',
payload: {
keyPath: "m/1852'/1815'/0'/0/0",
address: '<hex credential>',
payload: '<hex bytes>'
}
})
// → body.parameter = { signature, key } // CIP-30 DataSignature (key = COSE_Key)
| Field | Type | Required | Source | Description |
|---|---|---|---|---|
keyPath | string | Required | App | BIP-44 path. Account path on purpose 1852'. 🔴 The signing role is chosen by address, not by the role segment you send. A 29-byte stake address (0xe0/0xe1) → role 2 and a 28-byte DRep credential → role 3 — for those two only the account index of your keyPath is used. A 29-byte enterprise address (0x60/0x61) — the ordinary CIP-30 message case — signs with the payment key at exactly the keyPath you sent (e.g. …/0/0), as does any other payment address. Script credentials (0xf0/0xf1), any other unrecognized 29-byte header, a non-empty address shorter than 28 bytes, a stake address whose network header does not match the wallet (0xe0 testnet against a mainnet wallet or the reverse), and a payload whose decoded bytes exceed 65,536 (64 KiB) (reachable only through the UTF-8 fall-through — a hex payload is capped earlier by the connector, see the payload row) or the degenerate "0x" are all refused — these surface as internal_error (-32603), not the method_not_found / param_error used elsewhere on this page. |
address | string (hex) | Required | App | The address bytes as hex (the CIP-30 Address hex you were handed), not a bech32 addr1… / stake1… string. Length selects the role, so do not strip it: 29 bytes 0xe0/0xe1 = stake address, 29 bytes 0x60/0x61 = enterprise payment address, and 28 bytes is read as a DRep credential — trimming a base address down to its payment key hash silently lands in the DRep branch and fails the ownership check with internal_error (-32603). |
payload | string (hex) | Required | App | The message / data bytes to sign, hex-encoded (bare or 0x-prefixed). The connector caps the whole serialized payload object at 65,536 characters — thrown by sign() before the popup opens, not returned as an error envelope — so a hex payload tops out near 32 KB of data bytes; a decoded payload over 65,536 bytes (64 KiB) is rejected as well. 🔴 Only even-length hex is read as bytes — anything else, including an odd-length hex string, is silently signed as its own UTF-8 text rather than rejected. |
Response
| Field | Type | Notes |
|---|---|---|
body.parameter.signature | hex | CIP-30 DataSignature — the COSE_Sign1 structure over the payload (not a broadcastable transaction) |
body.parameter.key | hex | COSE_Key — the public key for verifying the signature |
header.status | string | "success" on completion; "failure" otherwise |
Common Mistakes
Invalid txCbor — must be valid CIP-30 CBOR. bech32 instead of hex address bytes in signData.address, or trimming an address to a bare 28-byte key hash (read as a DRep credential). Unfunded input UTXO in the structured inputs[] (the simple-send form is not accepted over this wire — see the note at the top of signTransaction).
Sending a m/44' keyPath to a stake or governance call. signData and stake / DRep
getPublicKey return -32601 method_not_found, and CBOR / certificate / withdrawal /
governance / mint / Plutus transactions return -32602 param_error — legacy Cardano is
plain-transfer only. If you are reading a rewardAddress from getAddress and finding it
missing, that is the same cause. Switch the keyPath to m/1852'/1815'/… — see
Derivation purpose.
🔴 A legacy keyPath is not the only source of -32601. On m/1852' the same code is returned
for every witness-analysis rejection except the one noted below: an unsupported certificate
kind, a committee certificate (hot-auth / cold-resign, which needs the committee cold key), an
unsupported voter (CC hot key or script, DRep script, SPO), a script or unknown reward credential,
a sparse voter/proposal entry, an unrecognized voter kind, a malformed certificate kind, and a pool
registration requiring the operator cold key. Read body.error.message, which names the specific
cause, before concluding the keyPath is wrong.
(An over-long CIP-100 anchor URL is the one witness-analysis rejection that returns param_error (-32602) instead.)
On the structured path (certificates / voting_procedures fields), an unsupported certificate type or a voter other than DREP_KEY is rejected earlier, while the transaction is built, with param_error (-32602).
Assuming getPublicKey always returns the { payment, stake, drep } bundle on Cardano. It does
on m/1852'; on a m/44' path whose role segment is 2 or 3 it fails with method_not_found
(-32601); on m/44'/1815'/<account>'/0/<index> or …/1/<index> (account hardened, role and index unhardened) it returns a single { keyPath, publicKey }; any other m/44' form is rejected with param_error (-32602). Reading body.parameter.stake without checking gives
undefined.