Cove is a simple, intuitive, but powerful Bitcoin wallet. The goal is to help new users get up and running quickly while still supporting the features power users need, such as hardware wallet support.
That balance matters whenever adding new functionality. Every feature must earn its place by remaining simple and intuitive. If we cannot make a feature feel simple and intuitive, we probably should not add it.
Prefer direct, structurally correct fixes over temporary workarounds. The right solution usually starts with the correct data model: represent the domain state, ownership, and invariants explicitly, then let the UI and orchestration code follow from that model. A larger diff is acceptable when it is needed to solve the requested change cleanly across the affected layers.
- Rust
- Just (
cargo install just) cargo-nextest(cargo install cargo-nextest)- iOS: Xcode 16.0+, swiftformat
- Android: Android Studio + NDK, Java 17/JDK
- Optional: bacon, watchexec
Copy .envrc.example to your preferred local environment setup and load the Android variables before running Android builds. At minimum, make sure ANDROID_HOME, ANDROID_SDK_ROOT, ANDROID_NDK_HOME, and JAVA_HOME are set correctly for your machine.
The COVE_KEYSTORE_* variables in .envrc.example are only needed for signed Android release builds and bundles. Regular development work does not require them.
- Clone the repository
- Build the Rust library and bindings:
- iOS:
just build-ios(just bi) for simulator orjust build-ios-debug-device(just bidd) for device - Android:
just build-android(just ba)
- iOS:
- Build and run on a device:
- iOS:
just build-run-ios --udid <device-udid>or setIOS_DEVICE_UDIDand runjust build-run-ios - Android: open Android Studio (
android/) or use the Android run/install recipes
- iOS:
just testflightThis bumps the iOS build number, rebuilds the release iOS bindings, archives the app, and uploads it to App Store Connect for TestFlight processing using App Store Connect API key credentials.
Set ASC_API_KEY_PATH, ASC_API_KEY_ID, and ASC_API_ISSUER_ID. The API key must have access to cloud-managed distribution certificates.
just build-android-release # alias: just barThen build a signed APK/AAB via Android Studio (Build → Generate Signed Bundle/APK).
- Rust-only changes: Use
just baconorjust bcheckfor continuous feedback - UI changes (no Rust API changes): Use
just compile-iosorjust compile-androidfor faster iteration - Rust API or UniFFI changes: Run
just build-iosorjust build-androidto rebuild Rust and regenerate bindings - Run iOS on a physical device: Use
just build-run-ios --udid <device-udid>when bindings may be stale, orjust run-ios --udid <device-udid>when generated bindings are already current - Tests: Run
just watch-test(just wtest) in a separate terminal for continuous test feedback
just build-ios and just build-android rebuild the Rust core, regenerate UniFFI bindings, and update the mobile projects. just compile-ios and just compile-android only rebuild the native apps, so use them when Rust exports have not changed.
just build-run-ios rebuilds the debug iOS bindings, builds the Xcode app with checkout-specific DerivedData, installs it with devicectl, and launches it without opening Xcode. It targets the first available paired iOS device by default; pass --udid <device-udid>, --device-name <device-name>, or set IOS_DEVICE_UDID / IOS_DEVICE_NAME to choose a specific phone. Repeat -d for multiple devices, for example just bri -d main -d se. The phone must be connected, paired, in Developer Mode, and unlocked before launch.
just run-android / just bra install and launch on Android; repeat -D for multiple devices, for example just ra -D main -D sim.
just build-run-all builds both apps and runs them on the default iOS and Android devices. Repeat -d for each iOS target and -D for each Android target, for example just brall -d main -d se -D main -D sim. Each app is built once before it is installed and launched on its selected devices.
| Command | Alias | Description |
|---|---|---|
just build-android |
just ba |
Build Android debug Rust FFI and Kotlin bindings for all ABIs |
just build-android-connected-device |
just bad |
Build Android debug Rust FFI and Kotlin bindings for the connected device ABI |
just build-android-release |
just bar |
Build Android release |
just build-ios |
just bi |
Build iOS debug simulator |
just build-ios-debug-device |
just bidd |
Build iOS debug device |
just build-ios-release |
just bir |
Build iOS release |
just build-run-ios |
just bri |
Rebuild iOS bindings, install, and run on device or simulator |
just run-ios |
just ri |
Install and run iOS using existing generated bindings |
just compile-ios |
- | Compile iOS without regenerating bindings |
just compile-android |
- | Compile Android without regenerating bindings |
just test |
- | Run the Rust test suite with nextest |
just watch-test |
just wtest |
Watch and re-run tests on Rust file changes |
just fmt |
- | Format Rust, Swift, and Android code |
just ci |
- | Run format, lint, compile, and test checks |
just clean |
- | Remove build artifacts |
Run just to see the public recipes. Aliases are shortcuts for commands you use often.
- Do not manually edit generated UniFFI bindings
- Regenerate bindings with
just build-iosorjust build-androidafter changing exported Rust APIs - Use
just compile-iosandjust compile-androidonly when Rust exports have not changed
- iOS builds stuck? Try
just xcode-resetto clear Xcode caches - Clean slate needed? Run
just cleanto remove all build artifacts - UniFFI binding issues? Regenerate bindings after changing Rust exports
- Run
just fmtto format all code - Run
just cito execute all checks (format, lint, clippy, tests, compilation) - Fix any issues reported by CI checks
- If clippy reports warnings, run
just fixfirst to auto-fix what's possible - If you changed Rust exports that generate bindings, run
just build-iosandjust build-androidbefore committing - Merge the latest
masterinto your branch ifmasterhas changed since you started your work
Write clear, concise commit messages that explain what changed and why. Let the code describe how.
Helpful defaults:
- Use imperative mood: "Add feature" not "Added feature"
- Capitalize the subject line
- No period at the end of the subject
- Add a body when it helps explain context or motivation
Example:
Add UTXO locking for coin control
Prevent selected UTXOs from being spent by other transactions
while a send flow is in progress. This avoids conflicts when
the user is manually selecting coins.
A good subject line completes: "If applied, this commit will ___"
See How to Write a Git Commit Message for the full guide.
- If you are addressing review feedback, add follow-up commits instead of squashing so reviewers can easily see what changed since the last review
- Merge the latest
masterinto your branch when needed instead of rebasing. We squash commits when the pull request is merged - If changes were requested on your pull request and you addressed them, request review again
- If you do not get a review within two days, ping Praveen on Discord or tag him in the GitHub pull request
- ARCHITECTURE.md - System design, Rust core, UniFFI, mobile patterns
- docs/ios_android_parity.md - iOS/Android UI parity patterns
- docs/icloud_drive.md - iCloud Drive behavior and file coordination notes
- docs/passkeys.md - Passkey behavior and Cloud Backup confirmation notes