A sovereign Android wallet for Nervos CKB that runs an embedded light client directly on your device via JNI. No remote indexers, no third-party servers for blockchain access. Your keys, your node, your wallet.
- End users: read the User Guide for install, backup, sync, send/receive, DAO, troubleshooting, and FAQ
- Download the app: latest GitHub Release (Play Store coming soon)
- Contributors: read CONTRIBUTING.md for development setup, code style, and the manual sync-stall smoke procedure
- Security: see SECURITY.md for the security model and the vulnerability disclosure process
- Grant deliverable: the M4 Completion Report summarises what shipped under the CKB Community DAO grant
- Embedded light client. A Rust CKB light client runs natively in-process via JNI. Balance, history, and transaction broadcast are all computed locally; no remote indexer.
- Multi-wallet with HD sub-accounts. Unlimited wallets per install. Sub-accounts derived from a single parent recovery phrase under BIP44, so one backup covers all of them.
- Address book. Contacts with smart suggestions and Send-screen autocomplete. After every successful send the app offers to save the recipient if it isn't already in your book.
- Nervos DAO. Native deposit, two-phase withdrawal (initiate + complete after the protocol lock period), per-cell compensation tracking computed from on-chain header DAO fields.
- Hardware-backed key storage. Private keys encrypted with an AES-256-GCM key bound to user authentication, generated by the Android Keystore. On Android 9+ the key is hardware-backed via the TEE or Secure Element.
- Argon2id PIN derivation. PIN unlocks the wallet via Argon2id with 64 MB memory cost, making offline brute-force impractically expensive. Cumulative 24-hour-decay lockout with permanent lockout at 10 failures.
- Biometric authentication. Fingerprint or face unlock on top of the PIN. Per-operation auth for signing and recovery-phrase display.
- Four sync modes. New wallet (instant), Recent (about 2 minutes), Custom block height (with an explorer-deeplink helper), Full history (overnight on mainnet).
- Sync stall detector. If the light client stops advancing for 5 minutes while away from tip, the home screen shows a one-tap Use Recent recovery banner.
- In-app updater. Telegram-style banner with progress reporting; Ktor downloader; permission-resume flow when Android prompts for install-from-unknown-sources.
- QR code scanner. CameraX + ZXing for recipient address scanning.
- Mainnet and testnet. Switch networks from Settings; per-network state isolation.
- Dark mode. System, Light, or Dark, settable from Settings.
┌─────────────────────────────────────────────────────────────┐
│ Pocket Node Android app (Kotlin) │
│ Jetpack Compose + Hilt + MVVM + Room │
└──────────────────────────┬──────────────────────────────────┘
│ JNI (in-process)
▼
┌─────────────────────────────────────────────────────────────┐
│ Embedded CKB light client (libckb_light_client_lib) │
│ Rust, vendored from nervosnetwork/ckb-light-client │
└──────────────────────────┬──────────────────────────────────┘
│ libp2p TCP to CKB bootnodes
▼
┌─────────────────────────────────────────────────────────────┐
│ CKB mainnet / testnet network │
└─────────────────────────────────────────────────────────────┘
Data flows unidirectionally: UI observes StateFlow from ViewModels. ViewModels call repository suspend functions. The repository delegates to the JNI bridge or to local crypto classes. The light client syncs directly with CKB peers; no intermediary server is involved.
| Mode | What it does | Typical first-sync time |
|---|---|---|
| New wallet | Starts from the current tip; no history scan | Instant |
| Recent activity | Scans the last roughly 200,000 blocks (about 30 days) | About 2 minutes |
| From a specific date (CUSTOM) | Scans from a user-chosen block height with an explorer-deeplink helper | Minutes to hours depending on depth |
| All history (FULL_HISTORY) | Scans from genesis (block 0) | Overnight on mainnet |
See the User Guide for picking the right mode.
| Layer | Protection |
|---|---|
| Wallet key storage | AES-256-GCM key in the Android Keystore, bound to user authentication (setUserAuthenticationRequired(true), setInvalidatedByBiometricEnrollment(true)), hardware-backed on Android 9+ |
| PIN derivation | Argon2id with 64 MB memory cost (t=3, p=1); silent migration from legacy Blake2b on first unlock after upgrade |
| PIN lockout | Cumulative 24-hour-decay window; permanent lockout at 10+ failures, recoverable only via the recovery phrase |
| Biometric | Per-operation auth via BiometricPrompt; enrollment changes invalidate the key and require re-import from the recovery phrase |
| Mnemonic display | FLAG_SECURE on every sensitive screen; never written to the clipboard |
| Backup | android:allowBackup="false"; the wallet's keys cannot be extracted via Android auto-backup |
| Release logging | All Log.* calls stripped by ProGuard in release builds |
| Transaction signing | Performed locally; private keys never leave the device |
Internal Phase 1 audits (JNI, Keystore, dependency review) all completed with every Severity ≥ High finding resolved. See #186, #187, #188 for the audit reports and PR-map summaries. A third-party security review is planned post-v1.7.0 (tracked in #204).
Visit the releases page and download PocketNode-vX.Y.Z.apk. Tap the APK on your phone. Android prompts you to allow installs from this source the first time. After granting, future updates are delivered through the in-app updater.
The Play Store listing is in preparation. Once live, it will be the recommended install path.
- Android Studio (latest stable)
- JDK 17
- Android SDK (min 26, target 35, compile 36)
- Rust toolchain with Android cross-compilation targets:
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android
- Android NDK (auto-detected or at
~/Library/Android/sdk/ndk/)
cd android
# Debug build (triggers Cargo build for the JNI library if needed)
./gradlew assembleDebug
# Release build (R8 minification + resource shrinking; requires release keystore env vars)
./gradlew assembleRelease
# Install on a connected device
./gradlew installDebug
# Kotlin-only iteration (skip the Cargo build)
./gradlew assembleDebug -x cargoBuild
# Unit tests (currently 628 tests on main)
./gradlew test -x cargoBuildRelease signing fails closed without the KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_ALIAS, and KEY_PASSWORD environment variables. This is by design; locally-built release APKs cannot accidentally sign with the debug keystore.
pocket-node/
├── android/ Android project root
│ ├── app/
│ │ ├── build.gradle.kts AGP config + Cargo build hook
│ │ ├── proguard-rules.pro R8 keep rules
│ │ └── src/main/
│ │ ├── AndroidManifest.xml
│ │ ├── assets/ Per-network light client TOML configs
│ │ └── java/
│ │ ├── com/nervosnetwork/ckblightclient/
│ │ │ └── LightClientNative.kt JNI bridge (24 entry points)
│ │ └── com/rjnr/pocketnode/
│ │ ├── data/ Repos, DAOs, crypto, sync, key storage
│ │ ├── di/ Hilt modules
│ │ └── ui/screens/ Home, Send, Receive, DAO, Wallets,
│ │ Contacts, Settings, Auth, Onboarding
│ └── gradle/libs.versions.toml Version catalog
├── external/
│ └── ckb-light-client/ Vendored Rust light client + JNI exports
├── website/ Static site at pocket-node.com (Vercel)
├── docs/
│ ├── USER_GUIDE.md Public end-user guide
│ └── GRANT_COMPLETION_REPORT.md M4 / DAO grant completion report
├── CONTRIBUTING.md Contributor setup + smoke procedures
├── SECURITY.md Threat model + disclosure process
└── .github/workflows/ CI + release pipelines
| Layer | Technology |
|---|---|
| Language | Kotlin 2.1.0 |
| UI | Jetpack Compose + Material 3 |
| DI | Hilt 2.57.2 |
| State | StateFlow + MutableStateFlow |
| Storage | Room 2.8.4 + AndroidKeystore AES-256-GCM |
| PIN KDF | Argon2id via BouncyCastle 1.70 (version pinned by ckb-sdk-java compatibility) |
| Serialization | kotlinx.serialization 1.8.0 |
| Network update | Ktor 3.0.3 (in-app updater download) |
| Light client | JNI to embedded Rust ckb-light-client |
| Crypto | CKB SDK Java 4.0.0, BouncyCastle 1.70, secp256k1-kmp 0.21.0 |
| Auth | AndroidX Biometric 1.1.0 |
| Camera | CameraX 1.4.2 + ZXing |
| Build | AGP 8.13.2, Gradle Kotlin DSL, Cargo for the JNI library |
- CKB uses a Cell (UTXO-like) model, not an account model
- Minimum cell capacity: 61 CKB (6,100,000,000 shannons)
- 1 CKB = 100,000,000 shannons (8 decimal places)
- Lock script: secp256k1-blake160 (
0x9bd7...cce8) - BIP44 derivation path:
m/44'/309'/N'/0/0, where N is the account index - Transaction fee: computed dynamically from serialized transaction size at 1000 shannons per kilobyte (the standard minimum relay rate); a typical send works out to well under 1000 shannons
- Core wallet (send, receive, balance, history)
- BIP39 mnemonic backup and recovery
- Biometric + PIN authentication
- Mainnet production hardening
- Testnet support with network switching
- Nervos DAO integration (deposit, withdraw, compensation tracking)
- Multi-wallet support with HD sub-accounts
- Address book with smart suggestions
- Hardware-backed Keystore key chain (Argon2id PIN, auth-bound encryption)
- In-app updater (Telegram-style banner, Ktor downloader)
- Sync stall detector with one-tap recovery
- Google Play Store release (in preparation)
- Third-party security review (post-v1.7.0)
- Translations (zh-CN, es-ES, and community-driven additions)
- sUDT / xUDT token support (post-v2.0.0)
This project is funded by a CKB Community DAO grant. The full deliverables and evidence are in GRANT_COMPLETION_REPORT.md.
| Milestone | Scope | Status |
|---|---|---|
| M1 | Mainnet ready, BIP39, biometrics, PIN, CI/CD | Done (v1.1.0) |
| M2 | Nervos DAO integration | Done (v1.3.0 / v1.4.0) |
| M3 | Multi-wallet, sync optimization, key storage redesign | Done (v1.5.x) |
| M4 | Security audits, Address Book, Play Store launch | Phase 1 + 2 Done (v1.6.x). Phase 3 in flight (v1.7.0 + v2.0.0 launch) |
See CONTRIBUTING.md for development setup, code style, the manual sync-stall smoke procedure, and PR conventions.
Issues and pull requests are welcome. For security issues, please follow the disclosure process in SECURITY.md rather than opening a public issue.
MIT. See LICENSE for details.
- Nervos CKB documentation
- CKB light client (upstream)
- CKB SDK Java
- CKB explorer (mainnet)
- Nervos forum thread
- Nervos Foundation for the upstream
ckb-light-client, the Rust + Java SDKs, and the patient engineering support during JNI integration - CKB community DAO graders for funding the work
- Early Telegram-channel users whose real-world reports drove product changes in v1.5.x and v1.6.x
- CodeRabbit and the Codex security-scan tooling for review automation that caught issues that would otherwise have shipped
- Neon and Matt; CKBuilders Cohort