This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Mandacaru is an Android application that runs a lightweight Bitcoin validation node powered by Utreexo and Floresta. It bridges Rust-based Bitcoin node implementation with a modern Kotlin/Compose Android UI.
# Debug build
./gradlew assembleDebug
# Release build
./gradlew assembleRelease
# Install debug build on connected device
./gradlew installDebug# Run unit tests
./gradlew test
# Run instrumented tests (requires connected device/emulator)
./gradlew connectedAndroidTest
# Run specific test class
./gradlew test --tests "com.github.jvsena42.mandacaru.SpecificTestClass"# Clean build
./gradlew clean
# Check for updates
./gradlew dependencyUpdates- Requires Android 10 (API 29) minimum
- ARM64 device only (arm64-v8a) - the Rust library is compiled for ARM64 only
- Java 11 language target
- Android Studio with Compose support recommended
presentation/ # UI layer with Jetpack Compose
├── ui/screens/ # Screen-specific ViewModels and Composables
│ ├── main/ # MainActivity and navigation
│ ├── node/ # Node status screen
│ ├── search/ # Transaction search screen
│ └── settings/ # Settings and configuration
├── ui/components/ # Reusable Compose components
└── utils/ # UI utilities (EventFlow, notifications)
domain/ # Business logic layer
├── floresta/ # Floresta daemon integration
│ ├── FlorestaDaemon.kt # Daemon lifecycle interface
│ ├── FlorestaDaemonImpl.kt # Rust/FFI integration
│ ├── FlorestaService.kt # Android foreground service
│ └── FlorestaRpcImpl.kt # RPC client implementation
└── model/ # Domain models and RPC DTOs
data/ # Data layer
├── FlorestaRpc.kt # RPC interface
└── PreferencesDataSource.kt # Preferences abstraction
- MVVM: ViewModels expose
StateFlow<UiState>consumed by Compose UI - Repository Pattern:
FlorestaRpcinterface abstracts RPC communication - Dependency Injection: Koin manages singleton instances (configured in
MandacaruApplication.kt) - Coroutines + Flow: All async operations use
suspendfunctions andFlow<Result<T>> - Foreground Service:
FlorestaServicekeeps the Bitcoin node alive in background
The app communicates with Rust code via UniFFI-generated bindings:
- Native Library:
libuniffi_floresta.so(ARM64 only) - Kotlin Bindings:
com.florestad.florestad.kt(auto-generated) - Main Classes:
Florestad: Rust daemon wrapper withstart()/stop()methodsConfig: Configuration object passed to Rust (network, dataDir, etc.)Network: Enum for Bitcoin/Testnet/Signet/Regtest
FlorestaService.onStartCommand()triggersFlorestaDaemon.start()FlorestaDaemonImpl.start()reads network preference, createsConfig, calls FFI- Rust daemon initializes Bitcoin node with Utreexo, binds RPC server to
127.0.0.1:<port> - Android app communicates via JSON-RPC over HTTP
Network preferences stored in SharedPreferences:
- CURRENT_NETWORK: "Bitcoin", "Testnet", "Signet", or "Regtest"
- CURRENT_RPC_PORT: Automatically set based on network (see
Constants.kt)
Port mappings (defined in domain/model/Constants.kt):
- Bitcoin: 8332
- Testnet: 18332
- Signet: 38332
- Regtest: 18443
The app uses OkHttp3 to make HTTP POST requests to the Rust daemon's localhost RPC server.
Request Format:
{
"jsonrpc": "2.0",
"method": "getblockchaininfo",
"params": [],
"id": 1
}getblockchaininfo: Blockchain sync status, height, Utreexo forest stategetpeerinfo: Connected peer listgettransaction: Transaction lookup by txidloaddescriptor: Add wallet descriptor for address trackinglistdescriptors: List loaded descriptorsaddnode: Connect to specific Bitcoin noderescan: Rescan blockchain for wallet transactionsstop: Gracefully stop the node
Located in domain/model/florestaRPC/response/:
GetBlockchainInfoResponse: Contains Utreexo-specific fields (leafCount,rootCount,rootHashes)GetTransactionResponse: Full transaction details with inputs/outputsGetPeerInfoResponse: Peer connection infoAddNodeResponse: Node connection status
RPC methods return Flow<Result<T>>. Check for Result.isFailure to handle errors:
florestaRpc.getTransaction(txId).collect { result ->
result.onSuccess { tx -> /* handle success */ }
result.onFailure { error -> /* handle error */ }
}Each screen has a ViewModel with:
private val _uiState = MutableStateFlow(UiState())val uiState = _uiState.asStateFlow()(read-only exposure)- Action handlers that update state via
_uiState.update { ... }
Compose screens observe state:
val state by viewModel.uiState.collectAsState()NodeViewModel implements a 10-second polling loop (getInLoop()) to refresh blockchain info while the screen is active.
When the user changes network in Settings:
- New network is saved to SharedPreferences
SettingsEvents.OnNetworkChangedis broadcastMainActivityreceives event and callsrestartApplication()- App restarts with new
Configpassed to Rust daemon
- Service notification is mandatory (Android O+)
- Channel: "floresta_service_channel"
- Notification actions: Open app, Stop service
- POST_NOTIFICATIONS permission required for Android 13+
The search field validates txid format (64-character hex string) before making RPC calls. Debouncing (500ms) prevents excessive API requests.
When loading wallet descriptors via Settings, use standard Bitcoin descriptor format (e.g., wpkh([fingerprint/path]xpub...)). The Rust daemon validates and derives addresses.
- Test ViewModels with fake repositories
- Mock
FlorestaRpcinterface for predictable responses - Use Koin test utilities to inject test dependencies
- Requires Android emulator or physical device
- Service tests need proper permission setup
- RPC tests require running Rust daemon
- Add enum to
RpcMethods.kt - Create response model in
domain/model/florestaRPC/response/ - Add method to
FlorestaRpcinterface - Implement in
FlorestaRpcImpl.sendJsonRpcRequest() - Call from ViewModel, update UI state
- Create package under
presentation/ui/screens/ - Create ViewModel with
UiStatedata class - Create Composable screen function
- Add route to
MainActivityNavHost - Register ViewModel in Koin
presentationModule
- Update
Configdata class inFlorestaDaemonImpl.kt - Ensure matching fields exist in Rust
Configstruct - Rebuild UniFFI bindings (handled by Rust build)
- Update
start()initialization logic
Key libraries (see gradle/libs.versions.toml):
- Compose BOM: 2025.09.01 (Material Design 3)
- Navigation Compose: 2.9.5
- Koin BOM: 4.1.1 (DI framework)
- OkHttp: 5.1.0 (HTTP client)
- Gson: 2.11.0 (JSON serialization)
- JNA: 5.14.0 (FFI bridge)
When publishing changes that touch the Mandacaru build chain — Floresta-mandacaru, floresta-mandacaru-ffi, or mandacaru — open PRs against the fork's own default branch (jvsena42/<repo>:master or :main). Never open PRs against an upstream maintainer repo (e.g. vinteumorg/Floresta) without explicit per-task authorization from the user — "if necessary" or similar conditional phrasing does not count as authorization.
The personal forks carry Mandacaru-specific patches and the user reviews/lands changes there first; upstream PRs are a separate, deliberate decision.
- Floresta Core - Underlying Rust implementation
- Utreexo - Accumulator design
- UniFFI - Rust FFI bindings generator