This file provides guidance to Claude Code (claude.ai/code) when working with the Stellar SDK demo app.
The demo app's primary purpose is to showcase SDK functionality for developers learning how to use the SDK. Key principles:
- Showcase SDK Usage: Demonstrate real-world SDK patterns, not simplified implementations
- Educational Value: Clear, well-documented examples that developers can learn from
- Production Patterns: Show best practices (error handling, async operations, UI patterns)
- Don't Reimplement SDK: Use SDK functionality directly - don't duplicate or simplify SDK features
This demo uses project dependencies (api(project(":stellar-sdk"))) to enable advanced features:
- Native Swift interop in macOS app (requires
export(project(":stellar-sdk"))) - Direct framework access for educational purposes
Most external developers should use Maven artifacts (implementation("com.soneso.stellar:stellar-sdk:1.10.0")):
- Works perfectly for Compose Multiplatform apps (Android, iOS, Desktop, Web)
- Standard KMP apps where SDK calls happen in Kotlin
- Simpler dependency management
See platform guides in ../docs/platforms/ for correct setup instructions for each platform.
Example (Correct):
// Good: Uses SDK directly
val keypair = KeyPair.random()
val signature = keypair.sign(data)Example (Incorrect):
// Bad: Reimplementing SDK cryptography
fun myOwnSign(data: ByteArray): ByteArray {
// Custom signing logic - DON'T DO THIS
}The demo app is a Kotlin Multiplatform application showcasing the Stellar SDK across all platforms:
- Shared Module (
demo/shared): 100% of UI (Compose) and business logic - Platform Apps: Lightweight entry points to launch shared UI
- Exception: macOS native app uses SwiftUI (30 Swift files) instead of Compose
demo/shared (Compose Multiplatform)
├── UI Screens (11 screens, 1 per feature + main menu)
├── Stellar Integration (11 feature modules using SDK)
└── Platform APIs (clipboard only)
Platform Apps (lightweight entry points)
├── androidApp (MainActivity.kt)
├── iosApp (StellarDemoApp.swift)
├── desktopApp (Main.kt)
├── webApp (Main.kt + HTML)
└── macosApp (SwiftUI - 29 files, native alternative)
The demo includes 11 comprehensive features:
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/KeyGenerationScreen.kt - Logic:
stellar/KeyPairGeneration.kt - Demonstrates:
KeyPair.random(),sign(),verify(),getAccountId(),getSecretSeed()
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/FundAccountScreen.kt - Logic:
stellar/AccountFunding.kt - Demonstrates:
FriendBot.fundTestnetAccount()
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/AccountDetailsScreen.kt - Logic:
stellar/AccountDetails.kt - Demonstrates:
Server.accounts(), account data parsing, balance display
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/TrustAssetScreen.kt - Logic:
stellar/TrustAsset.kt - Demonstrates:
ChangeTrustOperation, transaction building, signing, submission
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/SendPaymentScreen.kt - Logic:
stellar/SendPayment.kt - Demonstrates:
PaymentOperation, transaction building, asset handling
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/FetchTransactionScreen.kt - Logic:
stellar/FetchTransaction.kt - Demonstrates:
HorizonServer.transactions(),SorobanServer.getTransaction(), operation/event parsing, human-readable SCVal formatting
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/ContractDetailsScreen.kt - Logic:
stellar/ContractDetails.kt - Demonstrates: Soroban RPC, contract WASM parsing, metadata display,
funcResToNative()usage for automatic result parsing
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/DeployContractScreen.kt - Logic:
stellar/DeployContract.kt - Resources:
resources/wasm/(3 example WASM files) - Demonstrates:
ContractClient.deploy(),install(),deployFromWasmId(), platform-specific resource loading
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/InvokeHelloWorldContractScreen.kt - Logic:
stellar/InvokeHelloWorldContract.kt - Demonstrates:
ContractClient.invoke()with Map-based arguments, automatic type conversion,funcResToNative()for result parsing, beginner-friendly contract invocation API
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/InvokeAuthContractScreen.kt - Logic:
stellar/InvokeAuthContract.kt - Demonstrates: Dynamic authorization handling with
needsNonInvokerSigningBy(), conditionalsignAuthEntries(), unified pattern for same-invoker and different-invoker scenarios, production-ready authorization pattern
- Location:
shared/src/commonMain/kotlin/com/soneso/demo/ - UI:
ui/screens/InvokeTokenContractScreen.kt - Logic:
stellar/InvokeTokenContract.kt - Demonstrates: SEP-41 compliant token contract interaction,
ContractClient.buildInvoke()for advanced multi-signature workflows, function selection from contract spec, automatic type conversion for token operations (mint, transfer, balance)
When adding a new demo feature, follow this pattern:
Create shared/src/commonMain/kotlin/com/soneso/demo/stellar/NewFeature.kt:
package com.soneso.demo.stellar
import com.soneso.stellar.sdk.* // Import SDK classes
/**
* Demonstrates [SDK Feature Name] functionality.
*
* This shows developers how to:
* - Point 1
* - Point 2
* - Point 3
*/
suspend fun performNewFeature(params): Result<Data> {
try {
// Use SDK functionality directly
val server = Server("https://horizon-testnet.stellar.org")
// ... implementation using SDK
return Result.success(data)
} catch (e: Exception) {
return Result.failure(e)
}
}Important:
- Use SDK classes and methods directly
- Don't reimplement SDK functionality
- Include comprehensive error handling
- Add KDoc comments explaining what SDK features are being demonstrated
- Use
suspendfunctions for async operations
Create shared/src/commonMain/kotlin/com/soneso/demo/ui/screens/NewFeatureScreen.kt:
package com.soneso.demo.ui.screens
import androidx.compose.runtime.*
import androidx.compose.material3.*
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import com.soneso.demo.stellar.performNewFeature
import kotlinx.coroutines.launch
class NewFeatureScreen : Screen {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val scope = rememberCoroutineScope()
var isLoading by remember { mutableStateOf(false) }
var result by remember { mutableStateOf<Result<Data>?>(null) }
Scaffold(
topBar = {
TopAppBar(
title = { Text("New Feature") },
navigationIcon = {
IconButton(onClick = { navigator.pop() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
}
}
)
}
) { padding ->
Column(
modifier = Modifier.fillMaxSize().padding(padding).padding(16.dp)
) {
// UI implementation
Button(
onClick = {
scope.launch {
isLoading = true
result = performNewFeature(params)
isLoading = false
}
},
enabled = !isLoading
) {
Text(if (isLoading) "Loading..." else "Execute")
}
// Display result
result?.let { /* ... */ }
}
}
}
}UI Guidelines:
- Use Material 3 components
- Follow existing screen patterns
- Include loading states
- Show errors to users
- Make UI educational (show what's happening)
Edit shared/src/commonMain/kotlin/com/soneso/demo/ui/screens/MainScreen.kt:
val demoTopics = listOf(
// ... existing topics
DemoTopic(
title = "New Feature",
description = "Brief description of what this demonstrates",
icon = Icons.Default.Star, // Choose appropriate icon
screen = NewFeatureScreen()
)
)# Android
./gradlew :demo:androidApp:installDebug
# Desktop (easiest for quick testing)
./gradlew :demo:desktopApp:run
# Web (Vite dev server)
./gradlew :demo:webApp:viteDev
# iOS (requires Xcode)
./gradlew :demo:shared:linkDebugFrameworkIosSimulatorArm64
cd demo/iosApp && xcodegen generate && open StellarDemo.xcodeproj
# macOS Native (requires Xcode + libsodium)
./gradlew :demo:shared:linkDebugFrameworkMacosArm64
cd demo/macosApp && xcodegen generate && open StellarDemo.xcodeprojshared/src/commonMain/kotlin/com/soneso/demo/
├── App.kt # Main app entry point
├── ui/
│ ├── screens/ # One screen per feature
│ │ ├── MainScreen.kt # Main menu (list of features)
│ │ ├── KeyGenerationScreen.kt
│ │ ├── FundAccountScreen.kt
│ │ ├── AccountDetailsScreen.kt
│ │ ├── TrustAssetScreen.kt
│ │ ├── SendPaymentScreen.kt
│ │ ├── FetchTransactionScreen.kt
│ │ ├── ContractDetailsScreen.kt
│ │ ├── DeployContractScreen.kt
│ │ ├── InvokeHelloWorldContractScreen.kt
│ │ ├── InvokeAuthContractScreen.kt
│ │ └── InvokeTokenContractScreen.kt
│ └── theme/
│ └── Theme.kt # Material 3 theme
├── stellar/ # SDK integration modules
│ ├── KeyPairGeneration.kt
│ ├── AccountFunding.kt
│ ├── AccountDetails.kt
│ ├── TrustAsset.kt
│ ├── SendPayment.kt
│ ├── FetchTransaction.kt
│ ├── ContractDetails.kt
│ ├── DeployContract.kt
│ ├── InvokeHelloWorldContract.kt
│ ├── InvokeAuthContract.kt
│ └── InvokeTokenContract.kt
└── platform/
└── Clipboard.kt # Expect/actual for clipboard
Minimize platform-specific code. Only use for:
- Clipboard access: Each platform has
Clipboard.*.kt - Entry points: Minimal code to launch Compose UI
- macOS native: Full SwiftUI reimplementation (exception)
Example of acceptable platform-specific code:
// shared/src/commonMain/kotlin/com/soneso/demo/platform/Clipboard.kt
expect object Clipboard {
fun copy(text: String)
}
// shared/src/androidMain/kotlin/com/soneso/demo/platform/Clipboard.android.kt
actual object Clipboard {
private var clipboardManager: ClipboardManager? = null
fun init(context: Context) {
clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
}
actual fun copy(text: String) {
clipboardManager?.setPrimaryClip(ClipData.newPlainText("", text))
}
}The shared module uses:
dependencies {
// Stellar SDK (includes ktor, serialization, coroutines)
api(project(":stellar-sdk"))
// Compose Multiplatform UI
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.materialIconsExtended)
// Navigation
implementation("cafe.adriel.voyager:voyager-navigator:1.1.0-beta02")
implementation("cafe.adriel.voyager:voyager-transitions:1.1.0-beta02")
// Coroutines (for async operations)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
}Key points:
- Use
api(project(":stellar-sdk"))to expose SDK to demo code - Stellar SDK provides ktor, serialization, coroutines transitively
- Compose and Voyager for UI only
The demo uses Material 3 design system:
- Primary: Teal/Blue theme (
MaterialTheme.colorScheme.primary) - Surfaces: Card backgrounds, containers
- Error: For error states
- Card: For list items, results display
- TextField: For user input
- Button: For actions
- TopAppBar: Screen titles and navigation
- IconButton: Back buttons, actions
- Card padding: 16.dp
- Content padding: 16.dp
- Vertical spacing: 8-12.dp between elements
- Titles:
MaterialTheme.typography.titleLarge - Subtitles:
MaterialTheme.typography.titleMedium - Body:
MaterialTheme.typography.bodyMedium - Labels:
MaterialTheme.typography.labelMedium
Good:
val server = Server("https://horizon-testnet.stellar.org")
val account = server.accounts().account(accountId)Bad:
// Don't create simplified wrappers
fun getAccount(id: String): SimpleAccount {
// Simplified implementation
}suspend fun fetchAccount(accountId: String): Result<AccountResponse> {
return try {
val server = Server("https://horizon-testnet.stellar.org")
val account = server.accounts().account(accountId)
Result.success(account)
} catch (e: Exception) {
Result.failure(e) // Preserve full exception for user
}
}// Correct: All SDK crypto operations are suspend functions
suspend fun generateKey(): KeyPair {
return KeyPair.random() // This is a suspend function
}
// In UI, call with coroutineScope
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
val key = generateKey()
}
})// Good: Shows developers the actual SDK pattern
val transaction = TransactionBuilder(sourceAccount, Network.TESTNET)
.addOperation(
ChangeTrustOperation.Builder(
ChangeTrustAsset.create(assetCode, issuer),
limit
).build()
)
.build()
transaction.sign(keypair)
server.submitTransaction(transaction)Add comments explaining SDK concepts:
// Create a payment operation to transfer XLM or issued assets
// The asset can be native (XLM) or an issued asset (code + issuer)
val operation = PaymentOperation.Builder(
destination, // Recipient's public key
asset, // Asset to send (native or issued)
amount // Amount in stroops (1 XLM = 10,000,000 stroops)
).build()var isLoading by remember { mutableStateOf(false) }
var result by remember { mutableStateOf<Result<Data>?>(null) }
val scope = rememberCoroutineScope()
Button(
onClick = {
scope.launch {
isLoading = true
result = performOperation()
isLoading = false
}
},
enabled = !isLoading && inputValid
) {
Text(if (isLoading) "Loading..." else "Execute")
}
if (isLoading) {
CircularProgressIndicator()
}
result?.let { res ->
res.onSuccess { data ->
// Display success
}.onFailure { error ->
// Display error
}
}var input by remember { mutableStateOf("") }
val isValid = remember(input) {
// Validation logic
input.isNotBlank() && input.startsWith("G") && input.length == 56
}
OutlinedTextField(
value = input,
onValueChange = { input = it },
label = { Text("Account ID") },
isError = input.isNotEmpty() && !isValid,
supportingText = {
if (input.isNotEmpty() && !isValid) {
Text("Invalid account ID format")
}
}
)result?.let { res ->
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = if (res.isSuccess)
MaterialTheme.colorScheme.primaryContainer
else
MaterialTheme.colorScheme.errorContainer
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = if (res.isSuccess) "Success" else "Error",
style = MaterialTheme.typography.titleMedium
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = res.getOrNull()?.toString() ?: res.exceptionOrNull()?.message.orEmpty(),
style = MaterialTheme.typography.bodyMedium
)
}
}
}- Test each feature on Desktop (fastest iteration)
- Verify on Android and Web
- Test iOS and macOS native before releases
- All features should work against Stellar testnet
- Use Friendbot for account funding
- Test with real network calls (not mocks)
- Minimum SDK: API 24 (Android 7.0)
- Use Activity Compose
- Initialize clipboard in MainActivity
- Deployment target: iOS 14.0
- Uses Compose via UIViewControllerRepresentable
- Requires Swift Package Manager for libsodium
- Uses SwiftUI (not Compose)
- Requires libsodium via Homebrew
- 30 Swift files with Views/Components/Utilities structure
- Runs on macOS, Windows, Linux
- Recommended for macOS users wanting Compose UI
- Simple window wrapper around shared Compose
- Stable Kotlin/JS with Compose
- Bundle size: ~955 KB production (220 KB gzipped)
- Supports all modern browsers
# Build all
./gradlew build
# Run desktop (fastest for development)
./gradlew :demo:desktopApp:run
# Run Android
./gradlew :demo:androidApp:installDebug
# Run web (Vite dev server with webpack bundling)
./gradlew :demo:webApp:viteDev
# Build web production bundle
./gradlew :demo:webApp:productionDist
# Build iOS framework
./gradlew :demo:shared:linkDebugFrameworkIosSimulatorArm64
# Build macOS framework
./gradlew :demo:shared:linkDebugFrameworkMacosArm64- ❌ Don't reimplement SDK functionality in the demo
- ❌ Don't create simplified wrappers around SDK classes
- ❌ Don't use mock data when real network calls work
- ❌ Don't bypass error handling to simplify code
- ❌ Don't ignore suspend functions - they're required for JavaScript
- ❌ Don't add platform-specific code unless absolutely necessary
- ❌ Don't create new dependencies without considering all platforms
- ✅ Use SDK classes and methods directly
- ✅ Show real-world patterns developers will use
- ✅ Handle errors comprehensively
- ✅ Add educational comments explaining SDK concepts
- ✅ Test on multiple platforms before committing
- ✅ Follow Material 3 design for consistency
- ✅ Keep platform-specific code minimal
- ✅ Document new features in README files
- Main SDK Documentation:
../CLAUDE.md - Demo App README:
README.md - Platform READMEs:
{platform}App/README.md - Stellar Docs: https://developers.stellar.org/
- Compose Multiplatform: https://www.jetbrains.com/lp/compose-multiplatform/
When working on the demo app:
- Check this file first
- Look at existing features for patterns
- Review platform-specific READMEs
- Refer to main SDK documentation at
../CLAUDE.md