A native, self-custodial mobile wallet for eCash.
A native mobile wallet for eCash — the Layer Two Labs Bitcoin
hardfork that activates Drivechain (BIP300/301) and airdrops eCash 1:1 to BTC holders. One Swift +
SwiftUI codebase ships as a native SwiftUI app on iOS and native Jetpack Compose on Android via
Skip. All key material, signing, and consensus logic is handled by
BDK (bdk-swift / bdk-android).
Multi-wallet and multi-network from day one. Bundled networks: Bitcoin mainnet (0'),
L2L Signet (1'), and the eCash dry-run chain (drynet2, unit ECX); further eCash / L2L
networks slot in as NetworkRegistry entries.
We're not accepting outside code contributions right now. The codebase is under heavy active development and changing quickly, and we'd like it in a more stable place before opening it up to pull requests.
Feature ideas are very welcome, though — open an issue describing what you'd like to see and we'll take it under consideration. Bug reports are appreciated too.
| Layer | Choice |
|---|---|
| Language / UI | Swift 6 · SwiftUI → Compose (Skip) |
| Wallet engine | BDK 2.3.x (bdk-swift on iOS, bdk-android on Android) |
| Secure storage | SkipKeychain (iOS Keychain / Android Keystore) — mnemonics only |
| Local storage | BDK-owned SQLite (chain data) + JSON wallet-list metadata (no SkipSQL) |
| Min OS | iOS 26+ · Android 9+ (API 28) |
ECashWalletMobile/ ← the Skip Fuse app (native Swift): UI, view models, state
Sources/ECashWalletMobile/ App/ · DesignSystem/ · Screens/ · Components/ · Resources/
Packages/WalletService/ ← the BDK seam, a SEPARATE transpiled (Skip Lite) package
Sources/WalletService/ WalletEngine, BDKWalletEngineFactory, NetworkRegistry,
WalletManager, KeyStore, WalletStore, Descriptors, Models…
Darwin/ ← iOS app target
Android/ ← generated Compose output + Kotlin glue
The app is Fuse (native Swift compiled for Android); WalletService is Lite (transpiled to
Kotlin) so it can call bdk-android directly. This split is the one architectural subtlety — see
CLAUDE.md §5.
- macOS with Xcode (Swift 6 toolchain, iOS Simulator).
- Android Studio + Android SDK/NDK, with an emulator created (Device Manager) and running before you launch the Android app.
- Skip CLI:
brew install skiptools/skip/skip, then verify the toolchain withskip checkup.
Warning
Building from source isn't recommended yet. The wallet is under heavy active development — APIs, storage layout, and screens change frequently, and it targets test networks by default. If you build anyway, treat it as a preview: don't put real funds in a wallet built from an in-development checkout. (Bitcoin mainnet is selectable but the app is pre-release.)
# Both platforms at once (iOS Simulator + running Android emulator):
skip app launch
# iOS: open the workspace in Xcode and run the "ECashWalletMobile App" target.
open Project.xcworkspace
# Android only (quick): build the debug APK and install to the running emulator:
skip export --debug
adb install -r .build/skip-export/ECashWalletMobile-debug.apkiOS logs appear in the Xcode console; Android logs in Android Studio's Logcat or adb logcat.
Simulator and Android builds need no signing setup. To run on a real iPhone, supply your own Apple Developer Team ID — it's deliberately not committed:
cp Darwin/DeveloperSettings.xcconfig.example Darwin/DeveloperSettings.xcconfig
# edit the file and set DEVELOPMENT_TEAM = <your team id> (Xcode → Settings → Accounts)That file is gitignored; the project's xcconfig includes it optionally, so signing then works in
Xcode, xcodebuild, and fastlane. With a device connected + trusted, you can build/install/launch
from the command line:
scripts/run-ios-device.sh # Debug (default)
scripts/run-ios-device.sh Release # Release# Fast: host tests for the app view models + Thunder crypto (Swift Testing, on the Mac).
swift test
# The wallet engine package — transpiled JUnit on the JVM (Robolectric), so both platforms in one run:
swift test --package-path Packages/WalletService
# Verify the Android build actually transpiles + compiles (the real Android gate):
skip export --debug
# Real-BDK integration on a device runtime:
ANDROID_SERIAL=emulator-5554 swift test # Android instrumented
WALLETSERVICE_LIVE=1 swift test --filter testLiveSignetSync # opt-in live L2L Signetswift build only checks Apple + transpilation; skip export --debug is the real Android check.
No change to WalletService or a view model merges without tests in the same PR.
Your keys stay on your device, and the app is built to keep them exposed as little as possible.
- Keys never leave the device. The recovery phrase (and only the phrase) is stored in the OS
secure store — iOS Keychain (
WhenUnlockedThisDeviceOnly, no iCloud sync) / Android Keystore-backed encrypted storage — keyed per wallet. Everything else (xpub descriptors, labels, the transaction cache) is public data. - BDK owns all cryptography for the Bitcoin/eCash wallet. Key derivation, signing, PSBT building, and coin selection are handled by the Bitcoin Dev Kit — no hand-rolled crypto. (The one exception is the in-development Thunder sidechain engine — a non-BDK chain, not yet user-facing — which uses swift-crypto + BLAKE3 with derivation and serialization pinned to published test vectors. See the acknowledgements table.)
- Watch-only + sign-on-demand. Day-to-day the wallet runs watch-only: balance, address derivation, syncing, and even building a transaction use only the public descriptors — the secret store is never read. Your phrase is loaded for exactly one purpose, at one moment: to sign a transaction you've confirmed. It's pulled into a transient in-memory signer, used, and dropped — so the private key's lifetime in memory is a single signing operation.
- Nothing secret on disk, nothing secret in logs. Only public data is persisted. Errors are typed and scrubbed before they reach the UI or any log — a signing failure says "signing failed," never the key (enforced by an automated no-leak test).
- Device-auth gates. Optional biometric/passcode lock on launch and on returning to the foreground (with a configurable grace window so a quick trip to another app doesn't re-prompt), plus an independent auth gate on the confirm-send step. On a device with no biometric/passcode enrolled the gate passes through rather than locking you out.
- Guarded backup. Revealing the phrase is behind an explicit gate + device auth, and you confirm a few words before it's marked backed up. Screenshots aren't blocked — capturing your own recovery phrase is your call (the app advises against it) — though the app-switcher snapshot stays obscured.
- Spendable balance (0-conf policy). Only confirmed coins + your own unconfirmed change are
treated as spendable. Coins you received that are still unconfirmed (0-conf) are shown separately
as pending and are kept out of coin selection until they confirm — because an unconfirmed
incoming payment can still be double-spent or RBF-replaced, which would orphan anything you tried to
send on top of it. This mirrors Bitcoin Core's trusted/untrusted rule (BDK gives us the
confirmed/trustedPending/untrustedPendingsplit; the send path also marks untrusted outpoints unspendable). A configurable confirmation threshold — and potentially a per-network one (e.g. stricter on mainnet than on testnets) — is a planned option. - Clean removal. Removing a wallet purges its phrase from the secure store plus all of its on-device data.
The decision records behind this live in docs/key-storage.md and docs/key-derivation.md; the
non-negotiable rules are CLAUDE.md §2 (Golden Rules) and §7 (Security model).
CLAUDE.md— architecture bible (the what and why; wins on conflicts).PLAN.md— full build plan + tracked checklist.DESIGN.md— visual spec (tokens, type, components, voice).docs/release.md— build / sign / ship to App Store + Google Play.docs/— decision records: key storage & derivation, wallet/network model, accounts & labels, backends & endpoints, CoinNews, and more.
eCash.com Wallet stands on these projects. The same list is shown in-app under Settings → About →
Open-source licenses, sourced from a single array (OpenSourceLicense.all in
Sources/ECashWalletMobile/App/OpenSourceLicense.swift) — add or edit a credit there and both the
app screen and this table should be kept in sync.
| Project | Use | License |
|---|---|---|
| Skip | Swift→Kotlin cross-platform toolchain + frameworks | MPL-2.0 |
Bitcoin Dev Kit (bdk-swift / bdk-android) |
Wallet engine | Apache-2.0 / MIT |
| swift-crypto | ed25519 signing (Thunder sidechain) | Apache-2.0 |
| SwiftBlake3 | BLAKE3 hashing (Thunder addresses) | ISC (bundled BLAKE3 C: CC0-1.0 / Apache-2.0) |
| SkipKeychain | Secure mnemonic storage | LGPL-3.0 |
| swift-qrcode-generator | Receive QR codes | MIT |
| SkipQRCode | Send QR scanning (Android camera) | LGPL-3.0 |
| JetBrains Mono | Mono / numeric typeface | OFL-1.1 |
| Space Grotesk | Display typeface | OFL-1.1 |
| Material Symbols | Icon set (.symbolset) |
Apache-2.0 |
Release note: bundling the full license texts / copyright notices (required by MIT/Apache/OFL) is still TODO — the in-app screen currently links out. See
PLAN.mdMilestone F.
Copyright (C) 2026 LayerTwo Labs and contributors.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. See LICENSE.txt for the full text (SPDX: GPL-2.0-or-later).