A non-custodial, multi-protocol Bitcoin wallet with an on-device AI assistant.
KaleidoSwap is a React Native (Expo) mobile wallet that brings Bitcoin, the Lightning Network, RGB assets, and Bitcoin L2s (Spark, Arkade, Bark) together under one self-custodial roof — driven by a private, on-device AI assistant and Nostr social payments. Your keys, your assets, and your AI all stay on your phone.
⚠️ Alpha software. KaleidoSwap is under active development and not production-ready. Expect bugs, breaking changes, and incomplete features. The KaleidoMind AI assistant is experimental and can make mistakes — always review actions before confirming. Use test networks and small amounts only; do not store funds you can't afford to lose.
- 🔑 Non-custodial & multi-protocol — one HD wallet (BIP39) across Bitcoin on-chain, Lightning, RGB assets, Spark, Arkade, and Bark.
- 🤖 On-device AI — a private assistant (KaleidoMind) that runs the LLM and speech models locally; nothing is sent to a cloud LLM by default.
- 🔁 Atomic swaps — trustless asset swaps via the KaleidoSwap maker network, plus Flashnet AMM pools on Spark.
- 🌐 Nostr-native — contacts, Lightning Zaps, Lightning Address, and NWC (Nostr Wallet Connect) to pair external apps.
- 🗺️ Real-world spending — discover Bitcoin-accepting merchants via BTC Map.
- 🔒 Hardened by default — biometric unlock, and seeds and keys held in the device secure store (never in the local database).
KaleidoSwap is built on a shared multi-protocol engine (@kaleidorg/wallet-engine)
that routes operations to the active protocol adapter:
| Protocol | What it covers | Backed by |
|---|---|---|
| Bitcoin / Lightning + RGB | On-chain BTC, Lightning payments, and RGB asset transfers via an RGB Lightning Node | kaleido-sdk (RLN) |
| Spark | Spark L2 Bitcoin + tokens | @buildonspark/spark-sdk |
| Arkade | Ark VTXOs | @arkade-os/sdk |
| Flashnet | Spark AMM DEX pools | @flashnet/sdk |
Swaps are KaleidoSwap-first: the wallet quotes and executes trustless atomic swaps against the KaleidoSwap maker API, with additional venues for breadth:
- KaleidoSwap — maker-based atomic swaps (primary).
- Flashnet — Spark AMM DEX pools.
The assistant is a natural-language interface to the wallet — ask it to check balances, create invoices, send payments, swap assets, or find merchants, in chat or by voice.
- Private by default. The LLM (Qwen3.5 0.8B / 2B, by device RAM) and Whisper speech-to-text run on device through the QVAC SDK. Conversations and transcription don't leave your phone.
- One agent for chat and voice. A single runner (
services/mindAgent.ts) powers both text chat and hands-free voice, built on the shared@kaleidorg/mindengine. - Physical device required. On-device inference is not available on a simulator / emulator (the rest of the wallet works fine on one). First launch downloads the models (~0.5–1.3 GB LLM + ~80 MB Whisper).
index.ts (polyfills) → App.tsx (Redux + PersistGate + Theme + ErrorBoundary) →
navigation/ (pre-auth setup flow ↔ main tabs + modals).
- State — Redux Toolkit + Redux Persist (
store/). - Service layer (
services/) keeps screens free of direct SDK calls. At its center is ProtocolManager (services/protocols/), which routes every operation to the active protocol adapter (Spark, Arkade, RGB/RLN, Flashnet). Alongside it: the KaleidoMind agent (mindAgent.ts+*Tools.ts), on-device AI lifecycle (QVACService.ts), Nostr + NWC, and biometric auth + encrypted SQLite. - Theming — design tokens in
theme/, shared UI primitives incomponents/.
A legacy single-node path (
RGBApiService.ts,WalletManager.ts) is deprecated, superseded by ProtocolManager.
- App — React Native 0.81 + Expo SDK 54 (New Architecture), TypeScript
- State — Redux Toolkit, Redux Persist
- Storage —
expo-sqlitefor wallet metadata;expo-secure-storefor seeds and keys - AI / voice —
@qvac/sdk(on-device LLM + Whisper),@kaleidorg/mind - Wallet engine —
@kaleidorg/wallet-engine+ protocol SDKs (Spark, Arkade, RGB, Flashnet) - Nostr —
@nostr-dev-kit/ndk,nostr-tools - Maps — BTC Map in
react-native-webview+ BTC Map API
Run pnpm audit and pnpm test:dependency-security after dependency updates.
The latter checks malformed URL handling and Expo Metro image parsing against
the installed packages, including the CommonJS compatibility patch for
decode-uri-component@0.5.0. The patch changes only its module export, preserving
the upstream decoder fix. Expo Metro 0.83.3 is also patched to pass image file
contents to image-size 2 instead of its removed filename API. Install with pnpm
so the overrides and patches apply.
As of 2026-10-07, the dependency audit reports three remaining advisories with no patched npm release: node-forge (Expo CLI), braces (Metro/Jest glob handling), and sprintf-js (Jest's configuration loader). They remain visible in the audit; no advisory is ignored. These dependency paths describe tooling usage, not a guarantee that all possible uses are safe. Recheck upstream fixes before release.
- Node.js ≥ 20 and pnpm ≥ 10 (
npm i -g pnpm). This repo uses pnpm; mixing innpm installis not supported. - Watchman (recommended):
brew install watchman. - For iOS: macOS with Xcode 16+ (iOS 18 SDK) and CocoaPods (
brew install cocoapods). A UTF-8 locale is required (CocoaPods on Ruby 3.4 crashes otherwise). - For Android: Android Studio + SDK (API 34+), a configured emulator or a connected device, and JDK 17.
The app uses the New Architecture (default on Expo SDK 54).
git clone https://github.com/kaleidoswap/Rate.git
cd Rate
pnpm install # postinstall prepares the Bark native module and QVAC addonspnpm install resolves the multi-protocol stack (Spark, RLN/RGB, Arkade, Bark). Several
are local file: siblings (../wallet-engine, ../wdk-wallet-*, ../arkade-wdk), so keep
those checked out next to this repo.
⚠️ Do not symlinknode_modules(e.g.ln -sinto another checkout). A self-referencing link causesELOOP: too many symbolic links. If you hit it:rm node_modules && pnpm install.
Bark's prebuilt native artifacts are fetched and prepared by this repo's own
postinstall (pnpm skips dependency postinstall scripts). If a native build
complains about missing Bark artifacts, run the step manually:
pnpm run setup:native# CocoaPods (Ruby 3.4) needs a UTF-8 locale — export it for the session:
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
# Build + install + launch on a simulator (runs prebuild + pod install + xcodebuild):
npx expo run:ios
# …or pick a specific simulator / device:
npx expo run:ios --device "iPhone 16 Pro"Notes:
- The first build is slow — it compiles the native modules, including Bark and the Spark/RGB SDKs.
- Bark 0.25.0 uses UniFFI 0.31.0-5.
scripts/prepare-bark-native.jsruns duringpostinstallandsetup:native: it copies Bark's exact header-only runtime into its native package, isolates C++ namespaces and FFI type names, and removes only Bark's shared CocoaPods runtime dependency, so another UniFFI-based module can't clash with it. Do not replace this with a global version override: UniFFI string/buffer APIs differ between versions. Native artifacts are fetched by Bark's checksum-verifying installer when missing. - When upgrading Bark or its UniFFI runtime, review the isolation script's version checks, run
pnpm run test:native-setup, and rebuild the native app. A JavaScript reload cannot add the Bark native module to an old client. - If
pod installcrashes with a RubyUnicode Normalization … ASCII-8BITerror, you forgot theLANG=en_US.UTF-8export above. - The QVAC AI assistant requires a physical device (no simulator support); the wallet itself runs fine on a simulator.
# Have an emulator running (or a device connected — check with `adb devices`), then:
npx expo run:androidNotes:
- First build compiles the native modules + Gradle — allow several minutes.
Once the app is installed on a simulator/device/emulator, you only need Metro for JS changes:
npx expo start --dev-client # then press `i` for iOS or `a` for AndroidThe AI assistant and voice mode (speech-to-text + text-to-speech) run on-device
via the QVAC SDK, which requires a physical device — they are unavailable on an
iOS simulator / Android emulator. The rest of the wallet works fine on a simulator.
QVAC logs are silent by default; set "loggerConsoleOutput": true (and
"loggerLevel": "debug" for native backend output) in qvac.config.json while
debugging.
The on-device AI ships as a bare worker bundle plus natively-linked addons. The JS
worker bundle and the native addons must stay in lockstep — a stale bundle can request
an addon version that isn't in the native app and crash with ADDON_NOT_FOUND.
pnpm install keeps them aligned via postinstall, but when you change QVAC-related deps
or hit addon errors, re-sync manually:
pnpm run sync-qvac-bundle # regenerate the QVAC worker bundle + relink native addonsNotes:
qvac.config.jsonlists the enabled QVAC plugins (LLM completion, Whisper transcription, TTS) — edit it to add/remove on-device capabilities, then re-runsync-qvac-bundle.@qvac/sdk0.21 ships the speech and LLM engines as split addons whose Android/iOS prebuilds live in per-platform packages (@qvac/*-android-arm64,@qvac/*-ios). They are pinned inpackage.jsonat the versions the SDK's addons need; when bumping@qvac/sdk,sync-qvac-bundlenames any that must change.- After changing native addons you must rebuild the app (
npx expo run:ios/android); a Metro reload alone won't pick them up. - Models are not bundled — they download on first launch on a physical device (~0.5–1.3 GB LLM + ~80 MB Whisper).
- Agent skills are bundled separately:
pnpm run bundle-skillsregeneratesskills.bundle.jsonfrom the shared@kaleidorg/mindskills plus the app-specific ones inskills/.
- Create or restore a wallet (12-word BIP39 recovery phrase).
- Secure it — set a password and enable biometric unlock.
- Back up your recovery phrase offline.
- Connect — the app configures the protocol adapters for your selected network.
- Send / Receive — across Bitcoin, Lightning, RGB, Spark, Arkade, and Bark; scan Bitcoin addresses, Lightning invoices, RGB invoices, and LNURL via the QR scanner.
- Swap — quote and execute atomic swaps via the KaleidoSwap maker network (and Flashnet AMM where available).
- Ask the assistant — "send 50,000 sats to John", "create an invoice for $25", "swap 10 USDT to BTC", or "find coffee shops that accept Bitcoin" — by text or voice.
- Social — Zap Nostr contacts, pay a Lightning Address, or connect an external app with NWC.
npm test # run all tests
npm run test:watch # watch mode
npm run test:coverage # coverage report
npx jest path/to/file.test.ts # single fileCoverage floor (enforced by jest.config.js and checked in CI): current coverage, about 36% statements/lines, 35% functions, 34% branches. Raise it as tests are added.
This is a prebuilt Expo project (it has ios/ and android/ directories), so use
expo run:*, not expo start --ios/--android.
npx expo run:ios # iOS simulator/device (first build compiles native code)
npx expo run:android # Android emulator/device
npx expo start --dev-client # JS-only changes after a native build is installedBuild profiles live in eas.json (development, preview, production):
eas build --profile development --platform ios # dev client, internal distribution
eas build --profile preview --platform android # internal test build
eas build --profile production --platform ios # store build (auto-increments version)rate/
├── screens/ # React Native screens
├── services/ # Business logic, protocol adapters, AI agent, integrations
├── store/ # Redux slices, hooks, selectors
├── components/ # Reusable UI primitives
├── navigation/ # Navigation configuration
├── theme/ # Design tokens (dark)
├── hooks/ # React hooks (e.g. useProtocol)
├── utils/ # Helpers (account routing, swap model, …)
├── skills/ # KaleidoMind agent skills
├── types/ # TypeScript definitions
└── assets/ # Static assets
| Symptom | Fix |
|---|---|
ELOOP: too many symbolic links … node_modules |
A self-referencing node_modules symlink. rm node_modules && pnpm install. |
iOS pod install → Unicode Normalization … ASCII-8BIT |
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 before running. |
npx expo prompts to install a different Expo version |
node_modules is broken — reinstall so the local expo is used. |
| AI assistant unavailable | On-device inference needs a physical device, or pair a desktop KaleidoMind provider. |
The next chapters of KaleidoSwap, grouped by horizon. Items move up as they land.
- Agentic-first wallet — promote the KaleidoMind agent from a tab to the primary interface: agent-driven send / receive / swap, multi-step task execution, proactive suggestions, and persistent on-device memory.
- White Noise chats by default — adopt White Noise (MLS-over-Nostr) as the default end-to-end-encrypted messaging layer for 1:1 and group chat, with payments embedded directly in conversation.
- Lite vs Advanced modes — a real mode switch: Lite hides protocol plumbing (one balance; send / receive / swap / chat), Advanced exposes per-protocol accounts, channels / LSP, RGB internals, and node settings.
- Nostr profile onboarding & key management — first-run Nostr identity setup (profile, relays, NIP-05), clean separation of the wallet seed from the Nostr identity key, import / export of the Nostr key, and per-app NWC key scoping.
- Light mode — finish the theming migration (move screens from the static dark
themeontouseAppTheme()so the Light / System toggle becomes real). - Background payments & notifications — reliable incoming-payment and swap-status push
via
expo-background-task+expo-notifications.
- Encrypted cloud backup & recovery — beyond the 12 words: encrypted backup of contacts, Nostr identity, and app settings.
- Hardware-signer / external-key support — sign with an external key device.
- Multi-language (i18n) — localization for a broader audience.
- Per-protocol UX maturity — Spark token discovery and Arkade onboarding surfaced consistently across the wallet.
- Fork the repository and create a feature branch.
- Make your changes and add tests where applicable.
- Ensure
npm testpasses. - Submit a pull request.
MIT License — see LICENSE.
Bark is enabled by default on signet. Rebuild the native app after updating; Expo Go and a JavaScript-only reload cannot add the native SDK.
pnpm install
EXPO_PUBLIC_BARK=1 EXPO_PUBLIC_BARK_NETWORK=signet pnpm exec expo prebuild --platform android
EXPO_PUBLIC_BARK=1 EXPO_PUBLIC_BARK_NETWORK=signet pnpm android --deviceOn macOS, use --platform ios and pnpm ios --device. Keep a separate test wallet.
- Open Dashboard → Bark, Receive → Receive on Bark, or Settings → Wallet Protocols → Bark.
- Check the network and connection/recovery status. Bark balances are shown separately and excluded from the dashboard total. Signet sats have no fiat valuation.
- Under Receive on Bark, choose Ark and generate a request. Fund it from Second's signet faucet, then tap Sync account.
- Lightning requires a positive whole-satoshi amount. On-chain funding creates a BDK funding address; after confirmation, Review boarding explicitly moves an entered amount into Bark, with a confirmation prompt and network fees.
- Send from Bark opens the normal send flow with Bark selected. Shared
ark1/tark1addresses require account selection when opened from the generic send flow; Bark and Arkade are separate servers. Check the network on the review screen. - Pending payments remain pending. For an unknown outcome, sync and check activity before retrying. Unknown fees are shown as unavailable.
- Restart the app and sync again to check persistence. Bark activity also appears in the shared history with its network label.
EXPO_PUBLIC_BARK=0 disables Bark. Mainnet requires explicit
EXPO_PUBLIC_BARK_NETWORK=mainnet, EXPO_PUBLIC_BARK_SERVER_URL, and
EXPO_PUBLIC_BARK_ESPLORA_URL; these values are bundled at build time.
Unilateral exit/recovery operations remain library APIs, without dedicated mobile controls.
Native device linking and funded flows must be verified in a development build.
The app pins @kaleidorg/mind to 0.11.0 and uses the in-process wallet bindings,
not the desktop Node provider. Normal installs use the published package. For
local engine development, build the sibling core and run
SYNC_MIND_LOCAL=1 pnpm sync-mind; launch Metro with the same flag to watch it.
SYNC_MIND_FORCE=1 also permits a mismatched local version and is for development only.
Reinstall with pnpm install --force --ignore-scripts to restore the published
package after an overlay. Dataset collection remains off; adding consent and
review UI is a separate integration. Validate voice/model behavior on a physical
device before shipping an app release.