Skip to main content

Solana (SOL)

Sign Solana transactions and messages. The most reliable input is a base58-serialized transaction; plain-JSON and several data encodings are also accepted.

FieldValue
FamilySolana
coinType501
keyPathm/44'/501'/<accountIdx>' (also accepted on mainnet: m/44'/501'/<accountIdx>'/0' — a separate account)
chainIdsolana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501
chainId · Devnetsolana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1/slip44:501

Supported Methods​

MethodSupported
getAddress✓
signTransaction✓
signMessage✓
signTypedData✗
signData✗

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.16.5 or higher1.0.0 or higher
signTransaction2.16.5 or higher1.0.0 or higher
signMessage2.31.0 or higher1.0.0 or higher
info

Firmware versions above are for the DCENT Biometric Wallet line — DCENT X supports them from 1.0.0. SPL token support was added in firmware 2.17.1.

getAddress — account address​

Retrieves the Solana account address for a chainId + keyPath. A Solana address is the base58-encoded ed25519 public key (32 bytes); there are no address variants — omit addressFormat (on mainnet only legacy is accepted and every other value fails with -32602; on Devnet ledger fails while the other listed values are ignored; any non-null value outside the addressFormat set fails with -32602). Besides the default m/44'/501'/<accountIdx>', on mainnet the 4-level path m/44'/501'/<accountIdx>'/0' is also accepted — it derives a different account, selected by the keyPath alone.

Parameters​

FieldTypeRequiredSourceNotes
chainIdstringRequiredAppFull CAIP-19 — solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501
keyPathstringRequiredAppBIP-44, default m/44'/501'/0'

Request

await dcent.getAddress({
chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501',
keyPath: "m/44'/501'/0'"
})

Response

FieldTypeNotes
body.parameter.addressstringBase58 ed25519 account address — this is the Solana address
header.statusstring"success" on success
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "getAddress"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "7Eq…"
}
}
}

signTransaction​

payload.transaction:

"4uQeVj5tqViQh7yWWGStvkEG1Zmhx6u…" // VersionedTransaction: bs58.encode(tx.serialize())
// legacy Transaction: bs58.encode(tx.serialize({ requireAllSignatures: false }))

A base64-encoded serialized VersionedTransaction string is also accepted.

Variant — plain JSON​

payload.transaction:

{
"version": 0,
"feePayer": "<pubkey>",
"instructions": [
{
"programId": "11111111111111111111111111111111",
"keys": [
{
"pubkey": "<from>",
"isSigner": true,
"isWritable": true
},
{
"pubkey": "<to>",
"isSigner": false,
"isWritable": true
}
],
"data": {
"instruction": 2,
"lamports": 1000000
}
}
],
"recentBlockhash": "<blockhash>"
}
FieldTypeRequiredSourceNotes
versionnumberOptionalApp0 builds a v0 message; omitted or any other value builds a legacy message
feePayerpubkeyRequired-RealWalletUsually the signing wallet
instructions[].programIdstringRequiredAppbase58 program id
instructions[].keysmeta[]OptionalAppReal account metas / ATAs (omitted → an empty list). The wallet must be one of the transaction's required signers (feePayer or a key marked isSigner: true)
instructions[].databytes | string | objectOptionalAppnumber[] (0–255) or Uint8Array, a 0x-hex string, or a base58 string (use the 0x prefix for hex — a string without it is tried as base58 first, then as hex). A malformed string is not rejected: hex decoding keeps only the bytes it could parse before the first unparseable byte pair, so e.g. "0xzz" or "hello world!" is signed with empty data — check your encoding. Omitted → empty. An object is accepted only for a SystemProgram transfer — { instruction: 2, lamports }
recentBlockhashstringRequired-RealAppReal, current blockhash — the app must supply it (form-E); not auto-filled

An object missing feePayer, instructions or recentBlockhash fails with param_error (-32602). Other problems — a malformed feePayer, a bad programId / pubkey, a data object or array that is not one of the accepted forms, or a wallet that is not among the required signers (checked after the device signs) — fail with internal_error (-32603).

Variant — structured native SOL transfer​

payload.transaction:

{
"type": "transfer",
"sender": "Gv8u221N315GoRx2C6yBNF2jNq379FdvaAp2bL8ywSDk",
"recipient": "11111111111111111111111111111112",
"amount": "1000000",
"recentBlockhash": "2zxvZMg8YScxKqW2QQqQHpNzkLgwvVLj8Q1ZQZoFcTZ6",
"preparedFee": { "fee": 5000 }
}
FieldTypeRequiredSourceNotes
typestringRequiredApp"transfer"
senderstringRequiredWalletbase58 32-byte address
recipientstringRequiredAppbase58 32-byte address
amountstringRequiredAppPositive integer, in lamports (at most 2^53−1)
recentBlockhashstringRequired-RealAppbase58 32-byte, current
preparedFee.feenumberRequiredAppFee in lamports

Any missing field fails with -32602. Unlike form-E, this shape is signed as a native SOL transfer rather than as an opaque message.

info

form-E — completed serialized message. A serialized or plain-JSON transaction is sent to the device as an opaque message to sign — the bridge does not identify any token in it. Use the form-D descriptor below for a token transfer. SPL token transfer: programId = Tokenkeg…, data = 0x0c + amount (u64 LE) + decimals (u8), keys = [source ATA, mint, dest ATA, owner] — all four are Required-Real, plus a real feePayer + recentBlockhash (form-E).

info

form-D — structured SPL token transfer. Instead of instructions, send transaction.token = { contract (mint), to (the recipient's owner wallet, not an ATA), amount (base units), decimals, symbol }. decimals is required for a mint that is not on DCENT's supported list (without it -32602); a registered mint uses its registered values. symbol is optional (defaults to the first 10 characters of the mint). This is the only shape in which the device can show an SPL token's symbol and amount — a form-E serialized message is signed as opaque bytes. Because the bridge signs with no network access, a Solana form-D transfer must also carry a sender (the fee-paying wallet; from or feePayer is also read — without any of them the call fails with param_error) and these completed fields: recentBlockhash (base58 32-byte, no surrounding whitespace), preparedFee.fee (positive integer lamports — this is what the device displays as the fee), and extra.isAssociated (boolean — whether the recipient's associated token account already exists; false makes the bridge add an ATA-create instruction). Missing any of the three gives -32602. Do not send the recipient ATA — the bridge derives it offline from owner + mint, so what the device shows cannot be redirected by the app.

Response

FieldTypeNotes
body.parameter.signaturestring (hex)The whole serialized Solana transaction with the wallet's signature added, hex-encoded (no 0x) — not a bare 64-byte signature. Only the wallet's signature is filled in, so it is ready to broadcast only when the wallet is the sole required signer; decode it with Buffer.from(signature, 'hex') before sendRawTransaction.
header.statusstring"success" on success
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "signTransaction"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "<signed tx hex>"
}
}
} // hex serialized tx (wallet signature applied)

signMessage​

await dcent.sign({
method: 'signMessage',
chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501',
payload: {
keyPath: "m/44'/501'/0'",
message: 'Hello Solana!',
meta: {
kind: 'raw'
}
}
})

message is a string. An even-length hex string (with or without 0x) is signed as those bytes; any other non-empty string is signed as its UTF-8 bytes. meta.kind accepts 'raw' or 'personal' — 'eip712' is rejected.

Response

FieldTypeNotes
body.parameter.signaturestringBase58-encoded ed25519 signature (64 bytes) over the message bytes
header.statusstring"success" on success
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "signMessage"
},
"body": {
"command": "signMessage",
"parameter": {
"signature": "…"
}
}
} // ed25519 signature over the message

Common Mistakes​

danger

Expired blockhash — fetch recentBlockhash immediately before signing. Unsigned transaction — VersionedTransaction.serialize() works without signatures; for a legacy Transaction use serialize({ requireAllSignatures: false }). Placeholder ATAs — source/dest ATAs must be the real on-chain accounts.