- Kotlin 52.1%
- Swift 44.1%
- Python 3.2%
- Shell 0.5%
Read the second Cashu request as an optional amountless Nostr tip on Android and iOS. Confirm the sale before offering percentage or custom sats amounts, then send the tip independently of NFC. Preserve Lightning fallback metadata and handle retired send operations when checking sale claims. Add focused tests and document flow limits. |
||
|---|---|---|
| .agents/skills | ||
| .github/workflows | ||
| android | ||
| CI | ||
| docs | ||
| ios | ||
| plans | ||
| .gitignore | ||
| AGENTS.md | ||
| LICENSE | ||
| README.md | ||
| SECURITY.md | ||
| skills-lock.json | ||
Cashu Wallet
Native Cashu ecash wallets for iOS and Android.
Cashu is Bitcoin-backed ecash: a mint issues bearer tokens against sats deposited over Lightning, and payments between wallets are private by design — the mint does not learn who pays whom. Cashu Wallet wraps that model in a first-class native app on each platform — SwiftUI on iOS, Jetpack Compose on Android — with the same surfaces, copy, and behavior on both.
iOS on the first row, Android on the second — same journey on both platforms:
| Onboarding | Wallet | Receive | Send |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
The full capture set for both platforms — onboarding through settings — lives
in docs/screenshots/. All shots show a test wallet
funded from the public Testnut mint; the
balances are not real sats.
Features
- Ecash — hold sats as Cashu tokens; send and receive them as QR codes or copyable strings, without touching a Lightning channel.
- Lightning — fund the wallet and pay invoices through the mint, with BOLT11 invoices and BOLT12 offers.
- On-chain Bitcoin — receive to and send from mints that support it.
- Cashu Requests (NUT-18) — publish a reusable, address-shaped payment request that senders pay over Nostr relays, without leaking your mint or balance to anyone watching a single endpoint.
- NFC contactless — tap-to-pay ecash between devices.
- Locked ecash (P2PK) — lock a send to the recipient's key so only they can redeem it.
- Multi-mint — add mints by URL or QR, discover public mints, and keep per-mint balances visible.
- Nostr Wallet Connect — let other apps spend from the wallet with scoped permissions.
- Backup & restore — BIP-39 seed phrase backup and restore, plus an App Lock behind the platform's biometrics.
Tips after NFC sales
Numo can bundle two ordered Cashu Requests in a BIP-321 NFC URI: the fixed sale first and a reusable, amountless sats request with Nostr transport second. Both Android and iOS pay the sale separately, then offer 5%, 10%, 15%, 20%, or a custom sats tip. Sending a tip requires a separate confirmation and no second NFC tap. A merchant can configure a server's Cashu Request so the tip goes directly to the server's wallet.
For NFC ecash, the wallet checks that the sale token was claimed before opening the tip prompt (for up to one minute). For a Lightning fallback it waits for settlement. Failed or pending sales do not offer a tip. A missing, malformed, fixed-amount, single-use, non-sat, or non-Nostr second request is ignored. The first request remains the sale in all cases. Tip delivery failures leave the sale unchanged; inspect Activity before attempting another tip because a relay failure may leave a recoverable pending send.
This ordered-tip convention is shared with Numo. Although BIP-321 permits repeated payment instructions, it does not standardize tipping. Tips use normal NUT-18 Nostr delivery. The recipient must retain their request and Nostr keys, and enable request listening. A prompt is currently tied to the open payment flow; it is not a saved reminder after dismissal or app restart.
Focused coverage: Android DeferredTipAmountTest (JVM) and
DeferredTipRequestTest (instrumentation); iOS DeferredTipRequestTests.
Device verification should cover ecash and Lightning, a different recipient,
skipping, repeated tips to one recipient, insufficient tip balance, and relay
failure after the sale, with the devices removed from NFC range before tipping.
Repository layout
ios/— SwiftUI app, Xcode project, unit + UI tests, helper scripts. Targets iOS 18+.android/— Kotlin / Jetpack Compose app and Gradle project.minSdk 26,targetSdk 36.docs/product/— shared product, design, and copy contracts both apps implement. Platform notes live indocs/ios/anddocs/android/.CI/— local mint infrastructure for integration tests: Nutshell and CDK mints running a FakeWallet Lightning backend, so end-to-end payment flows run without a real Lightning node.
Both apps are built on the Cashu Dev Kit (CDK) through its native FFI bindings.
Building
iOS (requires Xcode with the iOS 18+ SDK):
cd ios
xcodebuild -project CashuWallet.xcodeproj \
-scheme CashuWallet \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
build
Android (requires JDK 17 and an Android SDK; point local.properties or
ANDROID_HOME at it):
cd android
./gradlew --no-daemon :app:assembleDebug
Testing
Unit tests:
# iOS
cd ios && xcodebuild test -project CashuWallet.xcodeproj -scheme CashuWallet \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
-only-testing:CashuWalletTests
# Android
cd android && ./gradlew --no-daemon :app:testDebugUnitTest
Integration tests run the apps against real mint implementations started
locally — see CI/README.md for the full setup:
./CI/setup-nutshell.sh && ./CI/start-nutshell.sh
./CI/setup-cdk.sh && ./CI/start-cdk.sh







