Skip to main content

Stellar (XLM)

Sign Stellar transactions (native XLM or issued-asset payments) and Soroban authorization entries. The bridge builds the transaction from a plain-JSON Operation; issued assets are identified by asset.code + asset.issuer in the payload, so the chainId is the same for native and token sends.

FieldValue
FamilyStellar
coinType148
keyPathm/44'/148'/<accountIdx>'
amount unitstroop (1 XLM = 1e7) for payment; other structured operations take decimal units (see below)
chainIdstellar:pubnet/slip44:148
chainId · Testnetstellar:testnet/slip44:148

Supported Methods​

MethodSupported
getAddress✓
signTransaction✓
signAuthEntry Soroban✓
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.20.0 or higher1.0.0 or higher
signTransaction2.20.0 or higher — payment
every other structured type and { xdr } require 2.35.0
1.0.0 or higher
signMessage2.35.0 or higher1.0.0 or higher
signAuthEntry2.35.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. Only payment is signed with the Stellar coin command. Every other structured type (pathPaymentStrictSend / pathPaymentStrictReceive / manageData / createAccount / changeTrust / invokeHostFunction) and the { xdr } envelope are signed with the WalletConnect command, which requires firmware 2.35.0+ — see the stellar-soroban-invoke-* and stellar-xdr-passthrough-blind-sign playground presets.

info

Stellar uses an account-level path with no change/index segments — the default keyPath is m/44'/148'/0' (three levels), not the five-level EVM form.

getAddress — account address​

Retrieves the Stellar account address. Returns a G… public address (Ed25519 StrKey). Stellar has no address variants — omit addressFormat (ledger is rejected with -32602; legacy, segwit-wrapped, segwit-native, taproot and standard have no effect).

FieldTypeRequiredSourceDescription
chainIdstringRequiredAppCAIP-19 chain id — stellar:pubnet/slip44:148
keyPathstringRequiredWalletAccount path, default m/44'/148'/0'

Request

await dcent.getAddress({
chainId: 'stellar:pubnet/slip44:148',
keyPath: "m/44'/148'/0'"
})

Response

FieldTypeDescription
header.statusstringsuccess on completion; otherwise an error envelope (see Error Codes)
body.commandstringgetAddress
body.parameter.addressG-addressStellar account address in G… Ed25519 StrKey format. Stellar does not return a separate public-key field — the StrKey already encodes the Ed25519 public key.
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "getAddress"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "GDQP…"
}
}
}

signTransaction — Stellar Operation (payment)​

Signs a Stellar transaction built from a single Operation. This page shows payment; the structured shape also accepts type values pathPaymentStrictSend, pathPaymentStrictReceive, manageData, createAccount, changeTrust (for example, to open a trustline) and invokeHostFunction (see Soroban contract invocation below). Unlike payment, amounts on these operations (e.g. startingBalance, sendAmount, limit) are decimal asset units — startingBalance: "10" is 10 XLM, not stroops. The device signs the transaction; the signer attaches the signature and returns the signed envelope as base64 XDR.

Payload — native XLM payment​

warning

Send every consensus field yourself. Signing performs no on-chain lookup, so fee, sequenceNumber and timeBounds are all required — omitting any one fails with -32602. Keeping sequenceNumber fresh is the App's responsibility. For an operation this structured shape does not build, send a complete { xdr } envelope instead.

payload.transaction:

{
"type": "payment",
"destination": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
"asset": {
"code": "XLM",
"issuer": null
},
"amount": "100000000", // 10 XLM in stroops
"memo": {
"type": "none"
},
"fee": 100,
"sequenceNumber": "0",
"timeBounds": {
"minTime": "0",
"maxTime": "0"
}
}
FieldTypeRequiredSourceDescription
typestringRequiredAppOperation type, e.g. payment
destinationG-addressRequired-RealAppRecipient account (G…)
asset.codestringOptionalAppXLM for native; ticker for issued assets. Omit asset entirely for native XLM — send both code and issuer for an issued asset
asset.issuerG-address | nullOptionalAppnull for native XLM; issuer account for issued assets. If it is null or empty, the payment is signed as native XLM whatever asset.code says
amountstringRequiredAppNative: stroops (1 XLM = 1e7 — "10000000" = 1 XLM). Issued: token base units
memo.valuestringOptionalAppMemo text, attached as a text memo (MEMO_TEXT). memo.type is not read on this path, so an id or hash memo cannot be sent this way: a string value of up to 28 bytes is signed as text, a longer one (e.g. a 64-hex hash) fails with -32603, and a non-string value (e.g. a numeric id) is dropped with no memo attached. With no value — memo omitted, or { "type": "none" } as in the examples — no memo is attached
feenumberRequiredAppTotal fee in stroops (100 = base fee per op)
sequenceNumberstringRequired-RealAppAccount sequence for the sending account. Not fetched for you — read it from Horizon and keep it current
timeBounds.minTimestringRequiredAppLower time bound in Unix seconds; "0" for none
timeBounds.maxTimestringRequiredAppUpper time bound in Unix seconds; "0" for none

Request

await dcent.sign({
method: 'signTransaction',
chainId: 'stellar:pubnet/slip44:148',
payload: {
keyPath: "m/44'/148'/0'",
transaction: {
type: 'payment',
destination: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF',
asset: {
code: 'XLM',
issuer: null
},
amount: '100000000', // 10 XLM in stroops
memo: {
type: 'none'
},
fee: 100,
sequenceNumber: '0',
timeBounds: {
minTime: '0',
maxTime: '0'
}
}
}
})

Response

FieldTypeDescription
header.statusstringsuccess when signing completes; otherwise an error envelope (see Error Codes)
body.commandstringsignTransaction
body.parameter.signaturebase64 XDRBroadcast-ready signed transaction — the full Stellar TransactionEnvelope (base64 XDR) with the device signature already applied, not a bare 64-byte signature. Submit it directly to Horizon's submitTransaction.
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "signTransaction"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "AAAAAg…"
}
}
} // signed envelope (base64 XDR)

Variant — issued asset (USDC trustline token)​

info

Token model: form-D descriptor. For an asset not on DCENT's supported list, send a compact token descriptor under transaction.token (instead of the plain asset.code/asset.issuer shape) with decimals: 7 — the bridge reads token.amount in units of 10^-decimals and converts it to stroops, and Stellar assets use 7-decimal precision, so any other value mis-scales the amount. See Core Concepts → Token descriptors. token.contract is a composite SYMBOL-ISSUER string, not a real Stellar contract id.

payload.transaction:

{
"token": {
"contract": "USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
"to": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC6PV",
"amount": "10000000",
"decimals": 7,
"symbol": "USDC"
},
"fee": 100,
"sequenceNumber": "0",
"timeBounds": {
"minTime": "0",
"maxTime": "0"
}
}
  • token.contract = {symbol}-{issuer G-address} composite string (here Circle USDC mainnet), not a bare address.
  • token.amount is in the token's base units — USDC has 7 decimals, so "10000000" = 1 USDC.
  • The chainId stays stellar:pubnet/slip44:148 for both native and issued-asset sends; the asset is identified only from the payload.
warning

The destination must already hold a trustline for the issued asset, otherwise the network rejects the payment with op_no_trust.

signMessage​

info

signMessage is a WalletConnect message-signing method and requires device firmware 2.35.0 or later (the walletconnect capability); without it the bridge returns a firmware-update recommendation.

Signs an arbitrary message with the account Ed25519 key. This is a DCENT-specific scheme: the signed digest is SHA-256("stellar.message.sign:" ‖ messageBytes). Set the kind as payload.meta.kind (default raw). The domain tag above is always applied, so personal is a synonym for raw and eip712 is rejected. The message is read as hex when it is 0x (or 0X) followed by an even number of hex digits (a bare 0x or an empty string is rejected); otherwise it is signed as UTF-8 bytes.

await dcent.sign({
method: 'signMessage',
chainId: 'stellar:pubnet/slip44:148',
payload: {
keyPath: "m/44'/148'/0'",
message: 'Hello Stellar'
}
})

Response

FieldTypeDescription
body.commandstringsignMessage
body.parameter.signaturebase64Ed25519 signature (64 bytes) over SHA-256("stellar.message.sign:" ‖ messageBytes), base64-encoded

signAuthEntry Soroban​

info

signAuthEntry (Soroban authorization) is a WalletConnect signing method and requires device firmware 2.35.0 or later (the walletconnect capability); without it the bridge returns a firmware-update recommendation.

Signs a Soroban SorobanAuthorizationEntry for smart-contract authorization. The entry is supplied as base64 XDR; the response carries the signed entry (or, for a HashIdPreimage input, the signature) plus the signer's address.

await dcent.sign({
method: 'signAuthEntry',
chainId: 'stellar:pubnet/slip44:148',
payload: {
keyPath: "m/44'/148'/0'",
authEntry: 'AAAAAQ…'
}
}) // SorobanAuthorizationEntry (base64 XDR)
// → body.parameter = { signedAuthEntry, signerAddress }
FieldTypeRequiredSourceDescription
keyPathstringRequiredWalletSigner path, default m/44'/148'/0'
authEntrybase64 XDRRequired-RealAppSoroban SorobanAuthorizationEntry to authorize, or a SEP-43 HashIdPreimage (ENVELOPE_TYPE_SOROBAN_AUTHORIZATION). An entry with source-account credentials is rejected (-32603) — it needs no separate signature

Response

FieldTypeDescription
header.statusstringsuccess when signing completes; otherwise an error envelope (see Error Codes)
body.commandstringsignAuthEntry
body.parameter.signedAuthEntrybase64 XDRFor a SorobanAuthorizationEntry input: the signed entry (base64 XDR), ready to attach to the Soroban transaction. For a HashIdPreimage input: the signature SCVal (base64 XDR), not an entry.
body.parameter.signerAddressG-addressThe Stellar account (G…, Ed25519 StrKey) that authorized the entry.
{
"header": {
"version": "1.0",
"status": "success",
"response_from": "signAuthEntry"
},
"body": {
"command": "signAuthEntry",
"parameter": {
"signedAuthEntry": "AAAAAQ…",
"signerAddress": "GDQP…"
}
}
}
info

signAuthEntry is implemented in wallet-models' Stellar family; the bridge has no Stellar-specific handler for it (only the firmware gate above applies), and a known non-Stellar chainId (with a keyPath valid for it) returns -32601. The connector-wire field name is authEntry (confirmed).

Soroban contract invocation​

Beyond classic payments, the bridge can sign Soroban smart-contract calls. Two paths are available:

  • Structured invocation — send a plain-JSON invokeHostFunction operation (contractAddress, functionName, args, sorobanData). The bridge builds the Soroban operation envelope and signs it locally — it performs no on-chain lookup and no broadcast, so every consensus input has to come from your app. The wire builder accepts 13 SCVal argument types: scv_bool, scv_void, scv_u32, scv_i32, scv_u64, scv_i64, scv_u128, scv_i128, scv_symbol, scv_string, scv_bytes, scv_address, and scv_raw_xdr (an escape hatch that takes a base64 SCVal XDR for anything the typed forms do not cover, such as vec/map).
  • XDR passthrough (blind sign) — send a pre-built base64 transaction envelope as transaction.xdr. The device blind-signs the 32-byte signature hash, so any operation the structured path does not yet build (accountMerge, manageOffer, setOptions, …) can still be signed. It replaces the v1 getStellarSignedTransaction call: instead of unsignedTx (hex, per the v1 guide) + fee, send the base64 TransactionEnvelope XDR as transaction.xdr.
warning

sorobanData is required — and only your app can produce it. Signing does not call a Soroban RPC, so the footprint and resource fee must arrive in the payload. Call simulateTransaction on an envelope built with the same source account, sequence, fee and operation, take transactionData from the response, and send it as sorobanData (base64 SorobanTransactionData XDR). Omitting it fails with -32602.

Then recompute fee: Soroban requires fee >= inclusionFee + resourceFee, and the simulation returns minResourceFee. A preset-style fee: 100 signs fine but the network rejects the transaction. Simulate again whenever you change the contract, function, arguments or sequence — a footprint computed for a different call is invalid.

info

Soroban signTransaction (the App / WalletConnect path) uses the device's walletconnect command; unlike signMessage / signAuthEntry, the bridge does not check that capability first.

await dcent.sign({
method: 'signTransaction',
chainId: 'stellar:pubnet/slip44:148',
payload: {
keyPath: "m/44'/148'/0'",
transaction: {
type: "invokeHostFunction",
contractAddress: "CAAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQC526",
functionName: "hello",
args: [
{ type: "scv_symbol", value: "world" }
],
// from simulateTransaction — response.transactionData
sorobanData: "AAAAAAAAAAEAAAAG…",
// inclusionFee + minResourceFee, not the preset's 100
fee: 12379,
// the account's CURRENT sequence; the builder increments it by 1
sequenceNumber: "259820571942988912",
timeBounds: { minTime: "0", maxTime: "0" }
}
}
})
info

Required fields. fee, sequenceNumber, timeBounds and sorobanData are all required for invokeHostFunction; each missing one fails with its own -32602. sequenceNumber must be a decimal string of at most 2^63−1 — a JSON number is rejected because sequences above 2^53 lose precision and would silently sign a different value. maxTime: "0" means no upper bound ({"0","0"} is fully unbounded). args must always be an array — send args: [] for a zero-argument method; omitting it fails with -32602. auth is optional — pass an array of base64 SorobanAuthorizationEntry XDR strings only for contracts that call require_auth.
Limits: at most 64 args entries and 32 auth entries; an scv_symbol value (and functionName) at most 32 UTF-8 bytes; a { xdr } envelope is bounded by the connector's limit on the whole serialized payload (65,536 characters — above that sign() rejects with a ProviderError (-32602) before the popup opens; no response is returned).

Which fields to edit: replace contractAddress with your deployed Soroban contract C-address (must be a checksum-valid C-strkey), set functionName to the target method, and populate args — e.g. a single scv_address for set_admin(new_admin). The sequenceNumber shown is illustrative — it is not fetched for you. Signing performs no on-chain lookup on this path either, exactly as for structured payments above, so read the sending account's current sequence from Horizon (/accounts/{id} → sequence) immediately before signing; a stale value yields tx_bad_seq. For operations outside the structured set, put a base64 envelope in transaction.xdr instead of type/contractAddress/functionName/args.

Common Mistakes​

danger

Mixing amount units — native XLM amount is in stroops ("10000000" = 1 XLM); issued-asset amount is in the token's base units (USDC 7 decimals → "10000000" = 1 USDC). fee is always in stroops. Wrong asset.issuer — use null for native XLM and the real issuer G… account for issued assets; an issuer that is not on DCENT's supported list is rejected with -32602 in the raw asset shape — send the form-D token descriptor with decimals instead (see the issued-asset variant above). Missing destination trustline — issued-asset payments fail with op_no_trust if the recipient has not established a trustline. Stale sequenceNumber — refresh the account sequence from Horizon immediately before signing; a stale value yields tx_bad_seq.