Skip to main content

Tezos (XTZ)

Sign Tezos transaction operations. Provide a Taquito-style operation object together with extra.branch (a recent block hash); the bridge forges the operation locally and signs it with no network access, so counter, fee, gasLimit and storageLimit are used exactly as you supply them. You can instead send pre-forged operation bytes under unsignedTx. FA1.2 and FA2 token transfers are expressed as a smart-contract call to a KT1 contract.

FieldValue
coinType1729
keyPathm/44'/1729'/<accountIdx>'/0'
addressFormatomit or legacy (default account) / standard
amount unitmutez
chainIdtezos:NetXdQprcVkpaWU/slip44:1729
chainId · Testnettezos:NetXnHfVqm9iesp/slip44:1729

Supported Methods​

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

MethodDCENT Biometric WalletDCENT X
getAddress2.24.1 or higher1.0.0 or higher
signTransaction2.24.1 or higher1.0.0 or higher
info

The Standard account (addressFormat: 'standard', below) needs DCENT Biometric Wallet firmware 2.36.2 or higher and DCENT X firmware 1.0.1 or higher.

addressFormat — Standard account​

Tezos has two accounts on the same keyPath: DCENT's default account (SLIP-23 Ed25519) and a Standard account derived with standard SLIP-10 Ed25519. The two derive different keys, so they have different tz1… addresses. Because the keyPath is identical, it cannot tell them apart — addressFormat is the only thing that selects the Standard account.

addressFormatAccount
omitted, or legacyDCENT default account
standardStandard account (SLIP-10 Ed25519)

Send the same value on getAddress and on signTransaction for that account — native transfers, FA1.2 / FA2 token transfers, the form-D descriptor and unsignedTx all follow it. legacy is the value getAccountInfo reports for the default account, so passing it back is fine; ledger, segwit-wrapped, segwit-native and taproot are rejected with -32602. The testnet chainId has the same two accounts. Every field that names your account — source, from in the form-D descriptor, and the sender in a raw FA1.2 / FA2 transfer (from / from_) — should be the address getAddress returned for the same addressFormat; it is not checked, and the value is signed as sent.

getAddress — account address​

Retrieves the Tezos account. Returns a tz1… (ed25519) address derived from the keyPath.

FieldTypeRequiredDescription
chainIdstringRequiredFull CAIP-19 id — tezos:NetXdQprcVkpaWU/slip44:1729
keyPathstringRequiredBIP32 derivation path. Default m/44'/1729'/0'/0'
addressFormatstringOptionalOmit (or legacy) for the default account; standard for the Standard account — see addressFormat

Request

await dcent.getAddress({
chainId: 'tezos:NetXdQprcVkpaWU/slip44:1729',
keyPath: "m/44'/1729'/0'/0'"
})

Response

FieldTypeDescription
body.parameter.addressstringTezos account address in tz1… (ed25519) format, derived from the keyPath
header.statusstring"success" when the address is returned
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "getAddress"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "tz1…"
}
}
}

signTransaction​

Signs a Tezos operation. Returns the signed operation bytes / signature ready to inject. To sign with the Standard account, add addressFormat: 'standard' to the payload, next to keyPath.

Variant — native XTZ transfer​

payload.transaction:

{
"kind": "transaction",
"source": "tz1…",
"fee": "1420",
"counter": "<next counter>",
"gasLimit": "10307",
"storageLimit": "257",
"amount": "1000000",
"destination": "tz1burnburnburnburnburnburnburjAYjjX"
}
FieldTypeRequiredSourceUnitDescription
kindstringRequiredApp—Always "transaction"
sourcetz addressOptionalWallet—Device Tezos account (tz1…); must be revealed / funded. Defaults to the keyPath's account if omitted. The bridge does not check a supplied value against that account — it is forged as sent
destinationtz / KT addressRequired-RealApp—Recipient (tz1… for native; KT1… for token calls)
amountstringRequiredAppmutez1 XTZ = 1,000,000 mutez. "0" for token calls
feestringRequired-RealAppmutezOperation fee
counterstringRequired-RealApp—Account counter — the next counter for source. It is forged into the signed bytes as sent, so it must be the real value
gasLimit / storageLimitstringRequired-RealApp—Used exactly as supplied — the bridge does not re-estimate them
extra.branchbase58 block hashRequired-RealApp—Recent block hash (B…) so the bridge can forge the operation offline. Required unless you send pre-forged unsignedTx; with neither, the request fails with -32602

Request

await dcent.sign({
method: 'signTransaction',
chainId: 'tezos:NetXdQprcVkpaWU/slip44:1729',
payload: {
keyPath: "m/44'/1729'/0'/0'",
transaction: {
kind: 'transaction',
source: 'tz1…',
fee: '1420',
counter: '<next counter>',
gasLimit: '10307',
storageLimit: '257',
amount: '1000000',
destination: 'tz1burnburnburnburnburnburnburjAYjjX',
extra: { branch: 'BLockGenesisGenesisGenesisGenesisGenesisf79b5d1CoW2' }
}
}
})

Response

FieldTypeDescription
body.parameter.signaturestringThe signed operation bytes (hex): the forged operation bytes followed by the signature. Submit it as-is through an RPC injection/operation call — it is not a bare edsig… signature.
body.commandstring"signTransaction"
header.statusstring"success" when signing completes on the device
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "signTransaction"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "<forged operation hex><signature hex>"
}
}
} // signed operation bytes, ready to inject

Variant — FA1.2 token transfer TZIP-7​

info

Registered tokens only. The bridge identifies the KT1… contract and looks it up among the tokens DCENT already supports (see Supported Coins & Tokens); an unsupported token sent in this raw shape fails with -32602. For any other FA token, use the form-D descriptor variant below and include token.decimals. The bridge never queries a node for you: you build the correct parameters.value yourself.

A fungible token transfer is a smart-contract call to the token's KT1… contract. The outer amount is "0" (no native XTZ moves); the token amount lives in the Michelson parameters.value.

payload.transaction:

{
"kind": "transaction",
"destination": "KT1TwzD6zV3WeJ39ukuqxcfK2fJCnhvrdN1X",
"amount": "0",
"fee": "1420",
"counter": "<next counter>",
"gasLimit": "10307",
"storageLimit": "257",
"extra": {
"branch": "BLockGenesisGenesisGenesisGenesisGenesisf79b5d1CoW2"
},
"parameters": {
"entrypoint": "transfer",
"value": {
"prim": "Pair",
"args": [
{
"string": "tz1…"
},
{
"prim": "Pair",
"args": [
{
"string": "tz1…"
},
{
"int": "1000"
}
]
}
]
}
}
}
FieldTypeRequiredSourceDescription
destinationKT addressRequired-RealAppFA1.2 token contract (e.g. SMAK, 3 decimals)
amountstringRequiredAppOuter native XTZ = "0"
parameters.entrypointstringRequiredAppAlways "transfer"
value → args[0].stringtz addressRequired-RealWalletfrom — your device account (tz1…)
value → args[1].args[0].stringtz addressRequired-RealAppto — recipient (tz1…)
value → args[1].args[1].intint stringRequiredAppToken base units (1000 = 1 SMAK at 3 decimals)

The bridge does not check these fields one by one: if the transfer shape does not match (for example a missing from, to or amount), the payload is not recognised as a token transfer and is signed as a plain contract call to the KT1… address — without the registered-token check.

Variant — FA2 token transfer TZIP-12​

info

Registered tokens only. The bridge identifies the KT1… contract and looks it up among the tokens DCENT already supports (see Supported Coins & Tokens); an unsupported token sent in this raw shape fails with -32602. For any other FA token, use the form-D descriptor variant below and include token.decimals. The bridge never queries a node for you: you build the correct parameters.value yourself.

FA2 (multi-asset) uses a list-shaped transfer batch with an explicit token_id. Outer amount is again "0".

payload.transaction:

{
"kind": "transaction",
"destination": "KT193D4vozYnhGJQVtw7CoxxqphqUEEwK6Vb",
"amount": "0",
"fee": "1420",
"counter": "<next counter>",
"gasLimit": "10307",
"storageLimit": "257",
"extra": {
"branch": "BLockGenesisGenesisGenesisGenesisGenesisf79b5d1CoW2"
},
"parameters": {
"entrypoint": "transfer",
"value": [
{
"prim": "Pair",
"args": [
{
"string": "tz1…"
},
[
{
"prim": "Pair",
"args": [
{
"string": "tz1…"
},
{
"prim": "Pair",
"args": [
{
"int": "0"
},
{
"int": "1000000"
}
]
}
]
}
]
]
}
]
}
}
FieldTypeRequiredSourceDescription
destinationKT addressRequired-RealAppFA2 token contract (e.g. QUIPU, 6 decimals)
value[0].args[0].stringtz addressRequired-RealWalletfrom_ — your device account (tz1…)
…args[1][0].args[0].stringtz addressRequired-RealAppto_ — recipient (tz1…)
inner Pair → first intint stringRequiredApptoken_id — must be 0 (the signer supports token_id 0 only); a positive value is rejected with -32602
inner Pair → second intint stringRequiredAppamount in base units (1000000 = 1 QUIPU at 6 decimals)

The bridge does not check these fields one by one: if the transfer shape does not match (for example a missing from, to or amount), the payload is not recognised as a token transfer and is signed as a plain contract call to the KT1… address — without the registered-token check.

Variant — FA1.2 / FA2 form-D descriptor (compact shape)​

Instead of hand-building the Michelson Pair/int tree above, send a compact token descriptor and the bridge assembles the same operation for you. Omit tokenId for FA1.2; set tokenId: "0" for FA2 (only token_id 0 is supported this way — a non-zero string is rejected (a numeric tokenId is ignored and the operation is built as FA1.2)).

payload.transaction:

{
"token": {
"contract": "KT1TwzD6zV3WeJ39ukuqxcfK2fJCnhvrdN1X",
"to": "tz1burnburnburnburnburnburnburjAYjjX",
"amount": "1000",
"decimals": 3,
"symbol": "SMAK"
},
"from": "tz1…",
"fee": "1420",
"counter": "<next counter>",
"gasLimit": "10307",
"storageLimit": "257",
"extra": {
"branch": "BLockGenesisGenesisGenesisGenesisGenesisf79b5d1CoW2"
}
} // tokenId omitted = FA1.2. Add "tokenId": "0" for FA2.
FieldTypeRequiredDescription
token.contractKT addressRequired-RealFA contract address, must start with KT1
token.totz addressRequired-RealRecipient tz account — cannot be a KT1 contract (rejected to prevent misdirected funds)
token.amountstringRequiredBase units, non-negative integer string
token.tokenIdstringOptionalOmit for FA1.2. Set to the string "0" for FA2 — any other string is rejected with -32602 (signer only supports token_id 0). A non-string value — e.g. the number 0 or 1 — is ignored and the operation is built as FA1.2
token.decimalsnumberRequired for unsupported tokensToken decimals. A token DCENT does not already support fails with -32602 without it
token.symbolstringOptionalToken symbol
fromtz addressRequired-RealSender — your device account (sender / source / from_address / fromAddress / owner_address / ownerAddress also accepted). Missing → -32602
info

Both token variants go to a KT1… contract with entrypoint: "transfer" and outer amount: "0". The bridge identifies the token transfer and synthesizes the token account — set the inner from / from_ to your own device account. Every token shape (raw FA1.2 / FA2 and form-D) still needs fee, counter, gasLimit, storageLimit and extra.branch exactly as in the native transfer — without them the request fails with -32602.

Variant — unsignedTx (pre-forged operation)​

Send a pre-forged Tezos operation (hex bytes: branch + contents, already built and encoded outside the device) under unsignedTx — the same field name Hedera uses (no new field). The bytes are signed as sent. DCENT X decodes them: it accepts only reveal and transaction operations (any other operation kind, or a second transaction, is rejected) and shows that transaction's recipient and amount. This is the only fully self-contained signTransaction form for Tezos; missing or malformed bytes return -32602.

FieldTypeRequiredSourceDescription
unsignedTxhex stringRequired-RealAppPre-forged operation bytes (hex): branch(32B) + contents, built outside the device

Request

await dcent.sign({
method: 'signTransaction',
chainId: 'tezos:NetXdQprcVkpaWU/slip44:1729',
payload: {
keyPath: "m/44'/1729'/0'/0'",
transaction: {
"unsignedTx": "288f8bb117200c366cade5c8f22e0854cc99429853dcc9b35287cc9cdcc8e3c66c00338cdba49ec68d3e14903e4fd37db865a990ed22900301e80700c0843d000041812f66a66258718eb77f7bdc4f89e2f151af8a00"
}
}
})

Common Mistakes​

danger

Non-zero outer amount on a token transfer — for FA1.2 / FA2 the outer amount must be "0", otherwise that native XTZ is moved as well, and DCENT X shows the outer amount instead of the token amount. tz address as token destination — token transfers go to the KT1… contract, not the recipient tz1…. Whole-token amounts — the Michelson int is base units (SMAK 3 decimals, QUIPU 6 decimals), not display units. Unrevealed source — a brand-new source account must be revealed / funded before its operation can be injected.