Skip to content

RaheemJnr/pocket-node

Repository files navigation

Pocket Node

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.

CI Platform Kotlin Min SDK License Latest Release

Quick links

  • 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

Features

  • 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.

Architecture

┌─────────────────────────────────────────────────────────────┐
│              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.

Sync modes

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.

Security model

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).

Install

GitHub Release (current)

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.

Google Play Store (coming soon)

The Play Store listing is in preparation. Once live, it will be the recommended install path.

Build

Prerequisites

  • 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/)

Commands

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 cargoBuild

Release 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.

Project structure

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

Tech stack

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-specific notes

  • 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

Roadmap

  • 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)

CKB Community DAO grant

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)

Contributing

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.

License

MIT. See LICENSE for details.

Resources

Acknowledgments

  • 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

About

A native Android CKB (Nervos) wallet that runs an embedded light client directly on the device via JNI — full sovereignty, no remote servers.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors