| name | kmp-stellar-sdk |
| description | Build Stellar blockchain applications with the Soneso KMP (Kotlin Multiplatform) SDK. Covers keypair generation, transaction building, Horizon queries, Soroban smart contracts, smart accounts (OpenZeppelin) with passkey / WebAuthn authentication, XDR encoding, and SEP integrations. Use when the developer is working with Kotlin, KMP, or Android and mentions Stellar, blockchain, cryptocurrency, passkey, or smart wallet operations. |
| license | Apache-2.0 |
| compatibility | Requires Kotlin 2.2+ and com.soneso.stellar:stellar-sdk 1.6.1. Supports JVM (Java 17+), Android (API 24+), iOS, macOS, and JavaScript (Browser/Node.js). |
| metadata | {"author":"soneso","version":"1.1.3","sdk_repo":"https://github.com/Soneso/kmp-stellar-sdk"} |
Stellar SDK for Kotlin Multiplatform
Overview
The KMP Stellar SDK (com.soneso.stellar:stellar-sdk) is a Kotlin Multiplatform library for building Stellar blockchain applications on JVM/Android, iOS/macOS, and JavaScript (Browser/Node.js). It provides full Horizon API coverage, full Soroban RPC coverage, 27 Stellar operations, and 14 SEP implementations. Crypto operations (KeyPair, signing) are suspend functions. Uses Ktor for networking and kotlinx.coroutines for async.
Installation
dependencies {
implementation("com.soneso.stellar:stellar-sdk:1.6.1")
}
All code examples below assume import com.soneso.stellar.sdk.* and run inside a suspend context (coroutine).
If you can't find a constructor or method in this file or the topic references, grep references/api_reference.md.
1. Stellar Basics
Keys and KeyPairs
val keyPair = KeyPair.random()
val accountId: String = keyPair.getAccountId()
val secretSeed: CharArray? = keyPair.getSecretSeed()
val restored = KeyPair.fromSecretSeed("S_YOUR_SECRET_SEED_HERE")
val publicOnly = KeyPair.fromAccountId(accountId)
Accounts
import com.soneso.stellar.sdk.horizon.HorizonServer
import com.soneso.stellar.sdk.horizon.responses.AccountResponse
val server = HorizonServer("https://horizon-testnet.stellar.org")
val keyPair = KeyPair.random()
val accountId = keyPair.getAccountId()
FriendBot.fundTestnetAccount(accountId)
val account: AccountResponse = server.accounts().account(accountId)
println("Sequence: ${account.sequenceNumber}")
for (balance in account.balances) {
if (balance.assetType == "native") println("XLM: ${balance.balance}")
else println("${balance.assetCode}: ${balance.balance}")
}
for (signer in account.signers) {
println("${signer.key} weight=${signer.weight} type=${signer.type}")
}
Assets
val xlm: Asset = AssetTypeNative
val usdc = Asset.createNonNativeAsset("USDC", "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN")
val parsed = Asset.create("USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN")
if (usdc is AssetTypeCreditAlphaNum) {
val code: String = usdc.code
val issuer: String = usdc.issuer
}
val server = HorizonServer("https://horizon-testnet.stellar.org")
val publicServer = HorizonServer("https://horizon.stellar.org")
2. Horizon API - Fetching Data
Query patterns for retrieving blockchain data. All request builders support .cursor(), .limit(), .order() for pagination.
Query Accounts
import com.soneso.stellar.sdk.horizon.requests.RequestBuilder.Order
val server = HorizonServer("https://horizon-testnet.stellar.org")
val account = server.accounts().account(accountId)
val bySigner = server.accounts()
.forSigner(accountId)
.limit(10)
.order(Order.DESC)
.execute()
for (acct in bySigner.records) {
println("Account: ${acct.accountId}")
}
Query Transactions
val txPage = server.transactions()
.forAccount(accountId)
.order(Order.DESC)
.limit(5)
.execute()
for (tx in txPage.records) {
println("${tx.hash} ledger=${tx.ledger}")
}
val nextPage = server.transactions()
.forAccount(accountId)
.cursor(txPage.records.last().pagingToken)
.limit(5).order(Order.DESC).execute()
val single = server.transactions().transaction("abc123...")
For all Horizon endpoints, advanced queries, and pagination patterns:
Horizon API Reference
3. Horizon API - Streaming
Real-time update patterns using Server-Sent Events (SSE). Set cursor to "now" for real-time events. Store the SSEStream to close later.
Stream Payments
import com.soneso.stellar.sdk.horizon.requests.EventListener
import com.soneso.stellar.sdk.horizon.requests.SSEStream
import com.soneso.stellar.sdk.horizon.responses.operations.OperationResponse
import com.soneso.stellar.sdk.horizon.responses.operations.PaymentOperationResponse
val stream = server.payments().forAccount(accountId).cursor("now")
.stream(
serializer = OperationResponse.serializer(),
listener = object : EventListener<OperationResponse> {
override fun onEvent(event: OperationResponse) {
if (event is PaymentOperationResponse) {
println("${event.amount} from ${event.from}")
}
}
override fun onFailure(error: Throwable?, responseCode: Int?) {
println("Stream error: ${error?.message}")
}
}
)
Streams reconnect automatically. For all streaming endpoints: Horizon Streaming Guide
4. Transactions & Operations
Complete transaction lifecycle: Build -> Sign -> Submit.
Transaction Lifecycle
val server = HorizonServer("https://horizon-testnet.stellar.org")
val network = Network.TESTNET
val senderKeyPair = KeyPair.fromSecretSeed(senderSecret)
val senderAccountId = senderKeyPair.getAccountId()
val senderAccount = server.loadAccount(senderAccountId)
val transaction = TransactionBuilder(senderAccount, network)
.addOperation(
PaymentOperation(
destination = destinationAccountId,
asset = AssetTypeNative,
amount = "100.50"
)
)
.addMemo(MemoText("payment"))
.setBaseFee(200)
.setTimeout(300)
.build()
transaction.sign(senderKeyPair)
val response = server.submitTransaction(transaction.toEnvelopeXdrBase64())
if (response.successful) {
println("Success! Hash: ${response.hash}")
} else {
println("Failed! Check result XDR: ${response.resultXdr}")
}
Common Operations
Change Trust (Establish Trustline):
val usdc = Asset.createNonNativeAsset("USDC", issuerAccountId)
val trustline = ChangeTrustOperation(asset = usdc)
Manage Sell Offer (DEX):
val selling = AssetTypeNative
val buying = Asset.createNonNativeAsset("USDC", issuerAccountId)
val newOffer = ManageSellOfferOperation(
selling = selling,
buying = buying,
amount = "100.0",
price = Price.fromString("0.5")
)
For all 27 operations with parameters and examples:
Operations Reference
5. Soroban RPC API
RPC endpoint patterns for Soroban smart contract queries.
import com.soneso.stellar.sdk.rpc.SorobanServer
val rpcServer = SorobanServer("https://soroban-testnet.stellar.org:443")
val health = rpcServer.getHealth()
For all RPC methods including event queries and transaction simulation:
RPC Reference
6. Smart Contracts
Contract deployment and invocation using the high-level ContractClient.
Deploy Contract
import com.soneso.stellar.sdk.contract.ContractClient
val keyPair = KeyPair.fromSecretSeed(secretSeed)
val rpcUrl = "https://soroban-testnet.stellar.org:443"
val source = keyPair.getAccountId()
val client = ContractClient.deploy(
wasmBytes = wasmBytes, source = source, signer = keyPair,
network = Network.TESTNET, rpcUrl = rpcUrl
)
println("Contract ID: ${client.contractId}")
val wasmId = ContractClient.install(
wasmBytes = wasmBytes, source = source, signer = keyPair,
network = Network.TESTNET, rpcUrl = rpcUrl
)
val client2 = ContractClient.deployFromWasmId(
wasmId = wasmId, source = source, signer = keyPair,
network = Network.TESTNET, rpcUrl = rpcUrl
)
Invoke Contract Function
val client = ContractClient.forContract(
contractId = "CABC...",
rpcUrl = rpcUrl,
network = Network.TESTNET
)
val result = client.invoke(
functionName = "get_count",
arguments = emptyMap(),
source = keyPair.getAccountId(),
signer = null
)
val writeResult = client.invoke(
functionName = "increment",
arguments = mapOf("value" to 5),
source = keyPair.getAccountId(),
signer = keyPair
)
val tx = client.buildInvoke(
functionName = "transfer",
arguments = mapOf("from" to fromAddr, "to" to toAddr, "amount" to 1000),
source = keyPair.getAccountId(),
signer = keyPair
)
tx.signAndSubmit(keyPair)
For contract authorization, multi-auth workflows, and low-level deploy/invoke:
Smart Contracts Guide
7. Smart Accounts (OpenZeppelin)
Passkey-authenticated Soroban smart accounts: biometric auth, multiple signers (passkey/delegated/Ed25519), context rules, policies, and optional fee sponsoring via a relayer. Entry point: OZSmartAccountKit.create(OZSmartAccountConfig(...)) — requires rpcUrl, networkPassphrase, accountWasmHash (hex), webauthnVerifierAddress (C-address), plus platform-specific webauthnProvider and storage.
- Smart Accounts Guide — kit config, wallet create/connect, signers, transactions, credentials, events,
submit / fundWallet, external signer manager, indexer
- Context Rules & Policies — context rules, policies, multi-signer, common scenarios (recovery, rotation,
__check_auth debugging), contract error codes
- WebAuthn Platform Setup — Android, iOS, macOS, Web adapters + rpId/DAL/AASA
8. XDR Encoding & Decoding
XDR (External Data Representation) is Stellar's binary serialization format.
Transaction XDR Roundtrip
val xdrBase64: String = transaction.toEnvelopeXdrBase64()
val decoded = AbstractTransaction.fromEnvelopeXdr(xdrBase64, Network.TESTNET)
if (decoded is Transaction) {
println("Source: ${decoded.sourceAccount}, Fee: ${decoded.fee}")
println("Operations: ${decoded.operations.size}")
}
Working with Soroban XDR Values (Scv)
import com.soneso.stellar.sdk.scval.Scv
import com.soneso.stellar.sdk.xdr.SCValXdr
val symVal = Scv.toSymbol("transfer")
val addrVal = Scv.toAddress(Address("GABC...").toSCAddress())
val vecVal = Scv.toVec(listOf(Scv.toUint32(1u), Scv.toUint32(2u)))
val mapVal = Scv.toMap(linkedMapOf(symVal to Scv.toUint32(42u)))
val intValue: UInt = Scv.fromUint32(Scv.toUint32(42u))
To submit a pre-signed XDR envelope: server.submitTransaction(signedXdrBase64).
For all Scv factory methods and type mapping:
XDR Reference | Contract Arguments
9. Error Handling & Troubleshooting
Horizon Errors
import com.soneso.stellar.sdk.horizon.exceptions.*
try {
val account = server.accounts().account(accountId)
} catch (e: BadRequestException) {
println("Horizon error ${e.code}: ${e.body}")
} catch (e: TooManyRequestsException) {
println("Rate limited: ${e.code}")
} catch (e: ConnectionErrorException) {
println("Connection error: ${e.message}")
}
Transaction Submission Errors
val response = server.submitTransaction(transaction.toEnvelopeXdrBase64())
if (!response.successful) {
println("Result XDR: ${response.resultXdr}")
}
Soroban RPC Errors
import com.soneso.stellar.sdk.rpc.responses.GetHealthResponse
val health = rpcServer.getHealth()
if (health.status != "healthy") { }
For the full error catalog and solutions:
Troubleshooting Guide
10. Security Best Practices
Covers secret key management (getSecretSeed() returns CharArray?), transaction verification, StrKey validation, and amount precision. See Security Guide.
11. SEP Implementations
The KMP SDK implements 14 SEPs: 01, 02, 05, 06, 08, 09, 10, 12, 24, 30, 31, 38, 45, 53. See SEP Implementations Guide.
Reference Documentation
- Operations - All 27 Stellar operations with examples
- Horizon API - Complete Horizon endpoint coverage
- Horizon Streaming - SSE patterns for all streaming endpoints
- RPC - All Soroban RPC methods
- Smart Contracts - Contract deployment, invocation, auth
- Smart Accounts - OZ kit core: config, wallet creation/connect, signers, transactions, credentials, events
- Smart Accounts - Policies - Context rules, policies, multi-signer operations
- Smart Accounts - WebAuthn - Platform adapters for Android, iOS, macOS, Web
- XDR - XDR encoding/decoding and debugging
- Troubleshooting - Error codes, platform & environment info
- Security - Platform-specific key storage, production deployment
- SEP Implementations - 14 SEPs with per-SEP references: 01, 02, 05, 06, 08, 09, 10, 12, 24, 30, 31, 38, 45, 53
- Advanced - Multi-sig, sponsorship, fee bumps, liquidity pools, muxed accounts
- API Reference - All public class/method signatures
Common Pitfalls
Suspend functions everywhere: KeyPair.random(), KeyPair.fromSecretSeed(), transaction.sign(), and all network calls are suspend functions. Must be called from a coroutine context.
suspend fun createAccount() { val kp = KeyPair.random() }
Amounts are always Strings: All payment amounts, balances, and prices are String types (7 decimal places max).
Sequence number management: build() increments the source account's sequence number. Reload the account before building a new transaction. Don't increment manually or you get tx_bad_seq.
setBaseFee is required: TransactionBuilder requires setBaseFee() before build(). Omitting it throws IllegalStateException. Fee is per operation: N ops at setBaseFee(200) = N * 200 stroops total.
submitTransaction accepts String: server.submitTransaction(transaction.toEnvelopeXdrBase64()) -- does NOT accept a Transaction object directly.
SecretSeed is CharArray: keyPair.getSecretSeed() returns CharArray? for secure memory handling. Convert with String(charArray) when needed.