No description
  • Kotlin 52.1%
  • Swift 44.1%
  • Python 3.2%
  • Shell 0.5%
Find a file
tsk 9b2c64ebf8
feat(tips): offer optional tips after settled NFC payments
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.
2026-10-01 14:45:50 +00:00
.agents/skills Onboarding restyle: fixed chassis, ambient ASCII terrain, and a handoff that erodes (#293) 2026-08-14 13:57:52 +02:00
.github/workflows Build release APKs directly and repair API 26 compatibility CI (#386) 2026-10-01 15:10:10 +02:00
android feat(tips): offer optional tips after settled NFC payments 2026-10-01 14:45:50 +00:00
CI Fix duplicate BOLT11 mint retry receipts on iOS and Android (#374) 2026-09-22 10:05:38 +02:00
docs Fix duplicate BOLT11 mint retry receipts on iOS and Android (#374) 2026-09-22 10:05:38 +02:00
ios feat(tips): offer optional tips after settled NFC payments 2026-10-01 14:45:50 +00:00
plans Polish native UI, accessibility, and motion (#329) 2026-09-01 12:35:47 +02:00
.gitignore Add cross-platform payment safety and live mint UI coverage (#360) 2026-09-09 10:55:29 +02:00
AGENTS.md Docs: require native UI components 2026-08-03 13:25:26 +02:00
LICENSE Add MIT License 2026-07-12 22:18:36 +03:00
README.md feat(tips): offer optional tips after settled NFC payments 2026-10-01 14:45:50 +00:00
SECURITY.md docs: add SECURITY.md policy (#294) 2026-08-14 14:31:19 +02:00
skills-lock.json Onboarding restyle: fixed chassis, ambient ASCII terrain, and a handoff that erodes (#293) 2026-08-14 13:57:52 +02:00

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
iOS onboarding iOS wallet iOS payment received iOS send amount
Android onboarding Android wallet Android payment received Android send amount

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 in docs/ios/ and docs/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

License

MIT