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)
chainIdstellar:pubnet/slip44:148

Supported Methods

MethodSupported
getAddress
signTransaction
signAuthEntry Soroban
signMessage
signTypedData

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 — plain transfer
App (WalletConnect / Soroban) requests 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 below are for the DCENT Biometric Wallet line — DCENT X supports them from 1.0.0. The App (WalletConnect / Soroban) signTransaction path requires firmware 2.35.0+ and is supported — 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, so addressFormat is not used.

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
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"
},
"body": {
"command": "getAddress",
"parameter": {
"address": "GDQP…"
}
}
}

signTransaction — Stellar Operation (payment)

Signs a Stellar transaction built from a single payment Operation. The device signs the transaction hash 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.codestringRequiredAppXLM for native; ticker for issued assets
asset.issuerG-address | nullRequiredAppnull for native XLM; issuer account for issued assets
amountstringRequiredAppNative: stroops (1 XLM = 1e7 — "10000000" = 1 XLM). Issued: token base units
memo.typeenumOptionalAppnone / text / id / hash
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
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"
},
"body": {
"command": "signTransaction",
"parameter": {
"signature": "AAAAAg…"
}
}
} // signed envelope (base64 XDR)

Variant — issued asset (USDC trustline token)

info

Token model: form-D descriptor. Always send a compact token descriptor under transaction.token (instead of the plain asset.code/asset.issuer shape) with trustworthy decimals from the chain RPC — 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 (SEP-53 style). Default kind is raw — Stellar has no EIP-191-style prefix, so personal is a synonym and eip712 is rejected. The message is UTF-8 bytes, or hex when it starts with 0x.

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

Response

FieldTypeDescription
body.commandstringsignMessage
parameter.signaturebase64Ed25519 signature (64 bytes) over the SHA-256 message digest, 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 device returns the signed entry 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

Response

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

Stellar signAuthEntry routing is registered in the bridge's Stellar adapter (the slot exists alongside signTransaction). 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. This is the upward-compatible successor to the v1 getStellarSignedTransaction path.
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) requires device firmware 2.35.0 or later (the walletconnect capability).

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 u64 decimal string — 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 at most 180,000 base64 characters.

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 unknown/unregistered issuer still signs — send the form-D token descriptor with decimals (see the issued-asset variant above) rather than relying on the registry. 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.