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.
| Field | Value |
|---|---|
| Family | Stellar |
| coinType | 148 |
| keyPath | m/44'/148'/<accountIdx>' |
| amount unit | stroop (1 XLM = 1e7) |
| chainId | stellar:pubnet/slip44:148 |
Supported Methods
| Method | Supported |
|---|---|
| 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.
| Method | DCENT Biometric Wallet | DCENT X |
|---|---|---|
getAddress | 2.20.0 or higher | 1.0.0 or higher |
signTransaction | 2.20.0 or higher — plain transferApp (WalletConnect / Soroban) requests require 2.35.0 | 1.0.0 or higher |
signMessage | 2.35.0 or higher | 1.0.0 or higher |
signAuthEntry | 2.35.0 or higher | 1.0.0 or higher |
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.
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.
| Field | Type | Required | Source | Description |
|---|---|---|---|---|
chainId | string | Required | App | CAIP-19 chain id — stellar:pubnet/slip44:148 |
keyPath | string | Required | Wallet | Account path, default m/44'/148'/0' |
Request
await dcent.getAddress({
chainId: 'stellar:pubnet/slip44:148',
keyPath: "m/44'/148'/0'"
})
Response
| Field | Type | Description |
|---|---|---|
header.status | string | success on completion; otherwise an error envelope (see Error Codes) |
body.command | string | getAddress |
parameter.address | G-address | Stellar 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
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"
}
}
| Field | Type | Required | Source | Description |
|---|---|---|---|---|
type | string | Required | App | Operation type, e.g. payment |
destination | G-address | Required-Real | App | Recipient account (G…) |
asset.code | string | Required | App | XLM for native; ticker for issued assets |
asset.issuer | G-address | null | Required | App | null for native XLM; issuer account for issued assets |
amount | string | Required | App | Native: stroops (1 XLM = 1e7 — "10000000" = 1 XLM). Issued: token base units |
memo.type | enum | Optional | App | none / text / id / hash |
fee | number | Required | App | Total fee in stroops (100 = base fee per op) |
sequenceNumber | string | Required-Real | App | Account sequence for the sending account. Not fetched for you — read it from Horizon and keep it current |
timeBounds.minTime | string | Required | App | Lower time bound in Unix seconds; "0" for none |
timeBounds.maxTime | string | Required | App | Upper 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
| Field | Type | Description |
|---|---|---|
header.status | string | success when signing completes; otherwise an error envelope (see Error Codes) |
body.command | string | signTransaction |
parameter.signature | base64 XDR | Broadcast-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)
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.amountis in the token's base units — USDC has 7 decimals, so"10000000"= 1 USDC.- The
chainIdstaysstellar:pubnet/slip44:148for both native and issued-asset sends; the asset is identified only from the payload.
The destination must already hold a trustline for the issued asset, otherwise the network rejects the payment with op_no_trust.
signMessage
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
| Field | Type | Description |
|---|---|---|
body.command | string | signMessage |
parameter.signature | base64 | Ed25519 signature (64 bytes) over the SHA-256 message digest, base64-encoded |
signAuthEntry Soroban
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 }
| Field | Type | Required | Source | Description |
|---|---|---|---|---|
keyPath | string | Required | Wallet | Signer path, default m/44'/148'/0' |
authEntry | base64 XDR | Required-Real | App | Soroban SorobanAuthorizationEntry to authorize |
Response
| Field | Type | Description |
|---|---|---|
header.status | string | success when signing completes; otherwise an error envelope (see Error Codes) |
body.command | string | signAuthEntry |
parameter.signedAuthEntry | base64 XDR | The signed SorobanAuthorizationEntry (base64 XDR), ready to attach to the Soroban transaction — not a bare signature. |
parameter.signerAddress | G-address | The Stellar account (G…, Ed25519 StrKey) that authorized the entry. |
{
"header": {
"version": "1.0",
"status": "success"
},
"body": {
"command": "signAuthEntry",
"parameter": {
"signedAuthEntry": "AAAAAQ…",
"signerAddress": "GDQP…"
}
}
}
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
invokeHostFunctionoperation (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, andscv_raw_xdr(an escape hatch that takes a base64SCValXDR for anything the typed forms do not cover, such asvec/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 v1getStellarSignedTransactionpath.
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.
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" }
}
}
})
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
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.