Winnow compare · Bitcoin on iPhone
How Winnow compares with seven other open-source iPhone wallets that hold your own Bitcoin keys: features, code size, complexity and what each one depends on. Everything is measured from source and rebuilt every day.
At a glance
Each wallet below holds its own keys on the phone, with the exception noted in the fourth column. Four of them (Phoenix, Bitkit, Blixt and Zeus) are Lightning-first wallets with an on-chain side; this page looks only at their Bitcoin wallet and ignores their Lightning features. The table shows defaults; most of these wallets let you change the chain source.
| Wallet | Built with | Where the on-chain wallet lives | Chain data | Who else must sign | On iPhone |
|---|---|---|---|---|---|
| Winnow | Swift (SwiftUI) | Its own, in Swift (WalletCore), in the same repository: keys, PSBTs, scripts, compact-filter sync, peer networking Package. README. | BIP157/158 compact filters from Bitcoin peers found through a signed peer list and DNS seeds; Advanced mode adds your own peers README. CensusCatalog. SettingsView. | Nobody for the everyday wallet; shared accounts need the co-owners' keys, with no co-signing server README. SharedSavingsViews. | App Store; pre-release builds on TestFlight README. appstore-submit. |
| BlueWallet | React Native (TypeScript) with small native modules | Its own TypeScript (class/wallets) on bitcoinjs-lib 7 and @noble/secp256k1; chain data through a fork of the Electrum client abstract-hd-electrum-wallet. package. package. | Electrum servers: electrum1.bluewallet.io by default, a short hardcoded fallback list; any host and port can be set in Settings > Network BlueElectrum. ElectrumSettings. | Nobody for on-chain funds. The optional LNDhub (custodial) and Ark wallet types depend on their own servers abstract-hd-electrum-wallet. lightning-custodian-wallet. lightning-ark-wallet. | App Store; builds go through TestFlight README. Fastfile |
| Green | Swift (UIKit) over Blockstream's gdk | gdk 0.78.0 (C++ for multisig, Rust for singlesig), linked as a prebuilt static library fetch_gdk_binaries. session_impl. | Singlesig: Blockstream's Electrum server, or your own in Settings. Multisig: Blockstream's backend, not replaceable network_parameters. network_parameters. AppSettingsViewModel. | Nobody for singlesig. Multisig (2-of-2, 2-of-3) needs Blockstream's server to co-sign every spend ga_auth_handlers. ga_auth_handlers. | App Store |
| Muun | Swift (UIKit) with a Go library, libwallet | libwallet, Muun's own Go library in the same repository: keys, 2-of-2 and MuSig2 signing, addresses, fees. Muun's server holds the second key README. V5. | Muun's own server (Houston API): history, spendable coins, fees and block height. The person cannot change it Environment. SettingsPresenter. init. | Muun's server must co-sign every spend. Without it the person needs the Emergency Kit, the Recovery Code and Muun's separate Recovery Tool V4. V5. RecoveryToolViewController. | App Store, per its fastlane release lane |
| Phoenix | Swift (SwiftUI) over Kotlin Multiplatform shared code | lightning-kmp 1.13.2 (Kotlin Multiplatform): Electrum client, swap-in wallet and final (BIP84) wallet build. SwapInWallet. FinalWallet. | An Electrum server picked at random from a built-in list of 15, trusted as given; the person can enter their own server in Settings AppConfigurationManager. ElectrumConfigurationView. | ACINQ co-signs swap-in deposits (MuSig2 2-of-2) until a refund delay of about six months, and is the channel peer for every on-chain send. The final wallet is the person's alone SwapInProtocol. NodeParams. ValidateView. | App Store |
| Bitkit | Swift (SwiftUI) on Rust libraries | LDK Node 0.7.0-rc.66 (Synonym's fork, Rust); its on-chain wallet is BDK Cargo. builder. | Synonym's Electrum server (bitkit.to); the person can change it in Settings > Advanced > Electrum Server Env. ElectrumSettingsScreen. | Nobody LightningService. | App Store |
| Blixt | React Native (TypeScript) with Go (LND) | Embedded LND 0.21.1 (Go); the on-chain wallet is btcwallet, run in-process through react-native-turbo-lnd btcwallet. | Neutrino (BIP157/158) through six default peers, four run by Blixt; the person can change the peers in Settings constants. Settings. | Nobody OnChain. | TestFlight |
| Zeus | React Native (TypeScript) with native modules and prebuilt Go and Rust libraries | Embedded LND 0.21.4 (Go, Zeus's fork); the on-chain wallet is btcwallet. LDK Node (BDK) is an option, and remote nodes can be driven too btcwallet. WalletCreationUtils. WalletCreationUtils. | Neutrino (BIP157/158) through five default peers, two run by Olympus; the person can add peers or allow only their own. The LDK Node option uses an Esplora server SettingsStore. LndMobileUtils. LdkNodeUtils. | Nobody for on-chain funds; they sit in the embedded wallet under one key on the phone EmbeddedLND. btcwallet. | App Store |
Features
What a person can do in each iPhone app today with the wallet’s Bitcoin, checked against the code. Where a wallet has several account types, a cell shows the best one it offers, and its note says which. Hover or focus a cell for the note; every cell links to its evidence below. The cells were last checked by hand on 2 October 2026. Since that review, 11 of the 420 cited passages have changed (in Winnow, BlueWallet, Bitkit and Zeus) and 32 have moved within their file in the code measured below.
| Feature | Winnow | BlueWallet | Green | Muun | Phoenix | Bitkit | Blixt | Zeus |
|---|---|---|---|---|---|---|---|---|
| Keys and signing | ||||||||
| Multisig or shared accounts | Yes | Yes | Yes | Partial | Partial | No | No | No |
| Hardware or external signer | Partial | Yes | Yes | No | No | Yes | No | Yes |
| PSBT import and export | Partial | Yes | Yes | No | No | No | No | Yes |
| Watch-only wallets | Partial | Yes | Yes | No | No | Partial | No | Yes |
| BIP 39 passphrase | No | Partial | Yes | No | No | Yes | Partial | No |
| Spending | ||||||||
| Coin control | No | Yes | Yes | No | No | Yes | No | Yes |
| Speed up a stuck payment (RBF or CPFP) | Partial | Partial | Yes | Partial | Partial | Yes | Partial | Yes |
| Taproot receive addresses | Yes | Yes | Partial | Yes | Yes | Yes | Yes | Yes |
| Output descriptors | Partial | Yes | Yes | Partial | Partial | Yes | No | Partial |
| Chain data and privacy | ||||||||
| Use your own node or server | Yes | Yes | Yes | No | Yes | Yes | Yes | Yes |
| Checks chain data on the phone | Yes | No | No | No | No | Partial | Yes | Yes |
| Tor or I2P | Partial | Partial | Yes | No | Partial | No | Yes | No |
| Backup and recovery | ||||||||
| Standard BIP 39 recovery phrase | Yes | Yes | Yes | Partial | Partial | Yes | Partial | Partial |
| Encrypted backup off the phone | Yes | No | Partial | Yes | Yes | Partial | No | Partial |
| License | ||||||||
| Source license | MIT | MIT | GPL-3.0 | MIT | Apache-2.0 | MIT | MIT | AGPL-3.0 |
Yes · Partial · Engine only · No · Unknown
Notes and code evidence for every cell
Links open the exact line that was checked, at the revision reviewed on 2 October 2026.
Cited code that has changed since the review, and so is due a fresh look:
- BlueWallet, Speed up a stuck payment (RBF or CPFP):
TransactionStatus.has changedtsx :1128 - BlueWallet, Encrypted backup off the phone:
blue-app.has changedts :302 - Bitkit, Watch-only wallets:
HwWalletManager.has changedswift :5 - Bitkit, Encrypted backup off the phone:
BackupService.has changedswift :220 - Zeus, Source license:
README.has changedmd :145 - Winnow, Where the on-chain wallet lives:
README.has changedmd :7 - Winnow, Chain data:
README.has changedmd :7 - BlueWallet, Where the on-chain wallet lives:
package.has changedjson :132 - BlueWallet, Main libraries:
package.has changedjson :93 - Bitkit, Main libraries:
project.has changedpbxproj :1176 - Zeus, Prebuilt binaries:
fetch-libraries-versions.has changedjson :2
Winnow
- Multisig or shared accounts: Yes. k-of-n threshold accounts and all-keys MuSig2; add this phone's key and pasted keys from other signers.
VaultsView.swift :41 VaultsView.swift :47 SharedSavingsViews.swift :104 - Hardware or external signer: Partial. Cosigns by pasting PSBTs with another Winnow or Core; hardware wallets are stated as not yet verified.
VaultsView.swift :72 MuSig2SignView.swift :126 - PSBT import and export: Partial. Shared accounts only: copy or share a request or raw PSBT and paste one back; the everyday wallet signs in-app.
VaultSpendSession.swift :102 VaultSpendSession.swift :400 MuSig2SignView.swift :126 - Watch-only wallets: Partial. Descriptor-only backup JSON restores watch-only; shared accounts add from a card; no xpub or address entry.
OnboardingView.swift :39 ImportBundle.swift :61 SharedSavingsViews.swift :207 - BIP 39 passphrase: No. Seed is derived with an empty BIP39 passphrase; no passphrase field when creating, backing up or importing.
Wallet.swift :646 ImportBundle.swift :443 - Coin control: No. Send picks coins automatically, largest first, for the wallet and shared accounts; coin list is read-only.
AppModel.swift :1893 Vault.swift :589 - Speed up a stuck payment (RBF or CPFP): Partial. RBF Bump fee in Advanced mode for own pending sends with unspent change; none for shared accounts; no CPFP.
HomeView.swift :328 Wallet.swift :781 AppModel.swift :2040 - Taproot receive addresses: Yes. Receives to BIP86 bc1p addresses by default; shared accounts are Taproot too (script path or MuSig2).
Wallet.swift :646 ReceiveView.swift :151 README.md :8 - Output descriptors: Partial. Shared accounts show a copyable descriptor in Advanced mode; the everyday wallet's is only inside its backup JSON.
AccountDetailView.swift :120 VaultsView.swift :120 ImportBundle.swift :32 - Use your own node or server: Yes. Manual host:port peers under Advanced mode, tried first; peer must serve compact filters; pool still fills from others.
SettingsView.swift :83 SettingsView.swift :106 PeerPool.swift :635 - Checks chain data on the phone: Yes. Checks header work, cross-checks filter headers across peers, matches filters locally; verifies fetched blocks.
FilterSync.swift :81 README.md :5 - Tor or I2P: Partial. No bundled Tor or I2P client; routes peers through SOCKS gateways you run, found on Tailscale or set by hand.
README.md :11 PeerGatewaysSection.swift :17 TailnetGatewayDiscovery.swift :18 - Standard BIP 39 recovery phrase: Yes. 12-word BIP39 phrase on BIP86 paths; the app restores only from its backup file, not from the words alone.
Wallet.swift :646 BackupSection.swift :43 BIP86.swift :10 - Encrypted backup off the phone: Yes. On by default: AES-GCM backup to the person's private CloudKit database, key in iCloud Keychain; file backup too.
CloudBackupController.swift :24 CloudWalletBackup.swift :4 ICloudBackupStore.swift :116
BlueWallet
- Multisig or shared accounts: Yes. Vault wallet type: m-of-n (default 2-of-3); person can generate or import several keys in the app.
Add.tsx :294 WalletsAddMultisig.tsx :54 addMultisigStep2.tsx :226 - Hardware or external signer: Yes. Imports Coldcard/Cobo/Keystone-style keys by QR or file and signs via PSBT QR or file; no USB, Bluetooth or NFC link.
abstract-wallet.ts :321 abstract-wallet.ts :339 ScanQRCode.tsx :165 WalletDetails.tsx :813 - PSBT import and export: Yes. Unsigned PSBT shows as animated QR, file or clipboard; signed PSBT is scanned or opened from a file.
psbtWithHardwareWallet.tsx :259 psbtWithHardwareWallet.tsx :270 psbtWithHardwareWallet.tsx :314 - Watch-only wallets: Yes. Imports xpub/ypub/zpub, single-key descriptors or one address as watch-only; shows balance and history.
wallet-import.ts :502 watch-only-wallet.ts :64 - BIP 39 passphrase: Partial. Passphrase prompt only when importing a seed or adding a multisig seed key; creating a new wallet offers none.
AddWalletStack.tsx :246 wallet-import.ts :146 abstract-hd-wallet.ts :79 - Coin control: Yes. Coin Control screen selects UTXOs and hands them to Send; coins can also be frozen.
SendDetails.tsx :1056 CoinControl.tsx :289 CoinControl.tsx :301 - Speed up a stuck payment (RBF or CPFP): Partial. RBF bump, RBF cancel and CPFP exist, but allowRBF() is true only for native SegWit (and aezeed) wallets.
abstract-wallet.ts :170 hd-segwit-bech32-wallet.ts :22 TransactionStatus.tsx :1128 - Taproot receive addresses: Yes. Taproot (BIP86) is a wallet-type choice in Add Wallet; receive addresses are bc1p; default type is native SegWit.
AddWalletStack.tsx :155 Add.tsx :250 hd-taproot-wallet.ts :47 - Output descriptors: Yes. Imports wpkh/sh/pkh/tr and sortedmulti descriptors; exports a descriptor (tr()) only for Taproot wallets.
abstract-wallet.ts :238 multisig-hd-wallet.ts :634 xpub.tsx :72 - Use your own node or server: Yes. Electrum server setting takes any host and TCP/SSL port after a connection test; defaults are a hardcoded peer list.
ElectrumSettings.tsx :193 ElectrumSettings.tsx :210 BlueElectrum.ts :90 - Checks chain data on the phone: No. Balances and history are taken straight from Electrum replies; headers are fetched only for timestamps, no proofs.
BlueElectrum.ts :1034 BlueElectrum.ts :1128 BlueElectrum.ts :1609 - Tor or I2P: Partial. .onion Electrum hosts are accepted; no Tor in the app, the error text asks the person to connect the Orbot app.
ElectrumSettings.tsx :199 en.json :253 BlueElectrum.ts :1453 - Standard BIP 39 recovery phrase: Yes. New on-chain wallets get a 12-word BIP39 phrase (16 bytes of entropy), shown on a backup screen.
abstract-hd-electrum-wallet.ts :168 PleaseBackup.tsx :71 Add.tsx :284 - Encrypted backup off the phone: No. Wallet data saved in a this-device-only keychain item and Realm excluded from iCloud backup; only manual seed display.
blue-app.ts :133 blue-app.ts :302 blue-app.ts :709
Green
- Multisig or shared accounts: Yes. 2-of-3 account: your key, a recovery key you hold (phrase or xpub) and Blockstream's key; 2-of-2 with its server also offered.
CreateAccountViewModel.swift :59 AccountCreateRecoveryKeyViewController.swift :89 - Hardware or external signer: Yes. Jade over Bluetooth or QR and Ledger Nano X over Bluetooth sign on-chain transactions.
ConnectViewModel.swift :62 Jade.swift :154 BleLedger.swift :33 QrJadeConnection.swift :11 - PSBT import and export: Yes. Singlesig watch-only: export unsigned PSBT (QR, file, copy) and import signed (QR, file, paste); not multisig.
SendTxConfirmViewModel.swift :131 QRPsbtShowViewController.swift :102 QRPsbtAquireViewController.swift :105 - Watch-only wallets: Yes. Singlesig from xpub/ypub/zpub or descriptors; multisig by username and password; no address lists.
WOViewModel.swift :42 WOViewModel.swift :71 - BIP 39 passphrase: Yes. Wallet menu 'Log in with BIP39 passphrase' opens a different wallet from the same phrase; set at login.
LoginViewController.swift :485 LoginViewController.swift :579 signer.cpp :32 - Coin control: Yes. Select coins on the amount screen lists UTXOs with filter and sort; the picked coins fund the payment.
SendCoordinator.swift :296 SendAmountViewControllerLegacy.swift :396 SendAmountViewControllerLegacy.swift :684 - Speed up a stuck payment (RBF or CPFP): Yes. RBF only: Speed up Transaction on unconfirmed RBF sends; the app never uses gdk's can_cpfp.
TxDetailsViewModel.swift :208 TxDetailsViewModel.swift :255 account.rs :380 - Taproot receive addresses: Partial. Taproot account creation is commented out; a p2tr account shows only if restore finds BIP86 history.
AccountTypeOption.swift :10 AccountTypeCellModel.swift :25 ga_auth_handlers.cpp :1257 - Output descriptors: Yes. Singlesig accounts show, copy and QR their output descriptors; watch-only import takes descriptors; multisig none.
ManageAssetViewController.swift :600 AccountDescriptorCellModel.swift :7 WOViewModel.swift :92 - Use your own node or server: Yes. Settings: Personal Electrum server, a URL per network with TLS option; singlesig only, multisig uses Blockstream's backend.
AppSettingsViewModel.swift :52 AppSettingsViewController.swift :73 WalletManager.swift :423 - Checks chain data on the phone: No. gdk removed SPV in 0.75 and reports spv_verified as disabled; balances and history come from the server unchecked.
CHANGELOG.md :174 ga_session.cpp :2313 account.rs :589 - Tor or I2P: Yes. Settings: Connect with Tor starts gdk's built-in Tor client; a SOCKS5 proxy option is also offered.
AppSettingsViewModel.swift :40 AppSettingsCell.swift :45 session_impl.cpp :101 - Standard BIP 39 recovery phrase: Yes. New wallets get a 12-word BIP39 phrase on a singlesig BIP84 account; multisig funds also need Blockstream's co-signer.
OnboardViewModel.swift :134 OnboardViewModel.swift :161 account.rs :781 - Encrypted backup off the phone: Partial. No iCloud or file backup; multisig account names and tx notes sync encrypted to Blockstream's server; phrase is manual.
client_blob.cpp :19 ga_session.cpp :1048 gaiosRelease.entitlements :5
Muun
- Multisig or shared accounts: Partial. Fixed 2-of-2 with Muun's server (P2WSH or MuSig2 P2TR); the person holds one key, cannot add keys or choose cosigners.
AddressActions.swift :81 V4.go :47 - Hardware or external signer: No. Keys live in the app. A flag-gated NFC Security Card only signs a 2FA challenge (mock backend in libwallet), not transactions.
NewOperationPresenter.swift :646 sign_message_security_card_action_v2.go :65 init.go :170 - PSBT import and export: No. Server returns a partly signed tx in Muun's own API format, the app signs and pushes it back; no PSBT import or export.
OperationActions.swift :265 OperationActions.swift :293 - Watch-only wallets: No. Get Started offers only create new wallet or recover existing (email or recovery code); every wallet holds its own key.
GetStartedViewController.swift :50 CreateFirstSessionAction.swift :49 - BIP 39 passphrase: No. No BIP 39 phrase: the key is a random 32-byte seed; the optional password only encrypts the key backup, it is not a wallet variant.
WalletPrivateKey.swift :49 BuildChallengeSetupAction.swift :33 - Coin control: No. The server supplies the ordered UTXO list (next transaction size) and a send spends in that order; no coin picker.
HoustonService.swift :487 Operation.swift :37 - Speed up a stuck payment (RBF or CPFP): Partial. No speed-up button or RBF; a new send adds a CPFP bump for unconfirmed parent coins, only while the backend flag is on.
fees.go :84 NewOpLoadingPresenter.swift :121 - Taproot receive addresses: Yes. P2TR (MuSig2) offered in the Receive address picker and as default in Settings once active; default stays SegWit.
ReceiveAddressTypeSelectPresenter.swift :70 OnchainSettingsPresenter.swift :72 V5.go :16 - Output descriptors: Partial. Emergency Kit PDF prints wsh(multi) and tr(musig) descriptors with key fingerprints, not xpubs; no descriptor import.
descriptors.go :15 emergencykit.go :39 - Use your own node or server: No. Fixed to Muun's Houston API; no server setting. libwallet's Electrum client serves only a scan RPC the app never calls.
Environment.swift :42 SettingsPresenter.swift :154 init.go :160 - Checks chain data on the phone: No. History, UTXOs and block height come from Muun's server; the app has no header, filter or block checks.
HoustonService.swift :436 RealTimeDataAction.swift :58 - Tor or I2P: No. No Tor, I2P or proxy setting; the app talks to Muun's Houston HTTPS endpoint and Firebase/Google services.
HoustonService.swift :38 SettingsPresenter.swift :154 - Standard BIP 39 recovery phrase: Partial. Recovery Code (not BIP 39) plus Emergency Kit; in-app restore asks Muun's server, outside restore needs Muun's Recovery Tool.
recoverycode.go :37 LogInWithRCAction.swift :31 WalletPrivateKey.swift :49 - Encrypted backup off the phone: Yes. Emergency Kit PDF with keys encrypted to the Recovery Code is saved to iCloud Drive or Google Drive when the person exports it.
ShareEmergencyKitPresenter.swift :96 ICloudHelper.swift :36 EmergencyKitDataSelector.swift :25
Phoenix
- Multisig or shared accounts: Partial. Swap-in address is a MuSig2 2-of-2 of the user key and ACINQ's key; the person holds one key, never several.
SwapInProtocol.kt :11 SwapInProtocol.kt :19 SwapInView.swift :336 - Hardware or external signer: No. Keys come from the on-phone seed and spends are signed in the app; 'Swap-in signer' is a support tool, not a device link.
WalletManager.kt :47 SpendOnChainFunds.swift :583 SwapInSigner.swift :87 - PSBT import and export: No. No PSBT code; the Swap-in signer tool takes a raw transaction plus a server nonce, and spends are built, signed and sent in-app.
SwapInSignerHelper.kt :73 SpendOnChainFunds.swift :583 - Watch-only wallets: No. Wallets are created or restored from a 12-word phrase only; no xpub, descriptor or address import.
WalletManager.kt :47 RestoreWalletController.kt :56 - BIP 39 passphrase: No. The shared code can take a passphrase, but every app call passes an empty string and no screen asks for one.
BusinessManager.swift :464 RestoreWalletController.kt :59 - Coin control: No. On-chain spends are send-all (expired swap-ins, final wallet) or splice-outs; the swap-in UTXO list is view-only.
SpendOnChainFunds.swift :173 ElectrumMiniWallet.kt :99 SwapInWalletDetails.swift :223 - Speed up a stuck payment (RBF or CPFP): Partial. 'Accelerate transaction' does a CPFP splice, only for unconfirmed splice payments; no RBF and no bump for other sends.
SummaryView.swift :334 SummaryView.swift :884 CpfpView.swift :744 - Taproot receive addresses: Yes. The on-chain receive tab shows a P2TR swap-in address (MuSig2 plus refund script); a legacy P2WSH address is an option.
SwapInView.swift :28 SwapInView.swift :336 SwapInProtocol.kt :25 - Output descriptors: Partial. Swap-in wallet shows a copyable tr() descriptor (and a legacy wsh()); final wallet shows only an xpub; nothing imports.
WalletInfoView.swift :221 WalletInfoView.swift :344 KeyManager.kt :268 - Use your own node or server: Yes. Electrum server screen takes a custom host and port in place of the random list; the Lightning peer stays ACINQ's.
ElectrumConfigurationView.swift :59 AppConnectionsDaemon.kt :265 - Checks chain data on the phone: No. Tip, UTXOs and history come from the Electrum server; a merkle root is computed but no header or proof check is called.
ElectrumClient.kt :144 ElectrumMiniWallet.kt :197 ElectrumDataTypes.kt :142 - Tor or I2P: Partial. Use Tor switches to .onion Electrum and ACINQ endpoints but needs a third-party Tor VPN app such as Orbot; none is bundled.
TorConfigurationView.swift :66 PeerManager.kt :172 AppConfigurationManager.kt :66 - Standard BIP 39 recovery phrase: Partial. 12-word BIP39; the BIP84 final wallet restores elsewhere, but channel and swap-in funds need Phoenix and ACINQ.
RecoveryPhraseReveal.swift :104 LocalKeyManager.kt :42 NodeParams.kt :158 - Encrypted backup off the phone: Yes. iCloud switch saves the phrase (CloudKit encrypted field, end-to-end only with Apple ADP) and seed-encrypted history.
CloudBackupView.swift :105 SyncSeedManager.swift :380 PaymentsBackupView.swift :78
Bitkit
- Multisig or shared accounts: No. Single-key accounts only (BIP 44/49/84/86 templates); no multisig or shared-account flow.
builder.rs :1385 LDKNode+AddressType.swift :7 - Hardware or external signer: Yes. Trezor over Bluetooth; on-chain sends signed on the device. Only Trezor; bitkit-core's Jade code is never called.
HardwareWalletsSettingsScreen.swift :31 HwSendSignView.swift :115 TrezorTransport.swift :37 - PSBT import and export: No. PSBT is internal to Trezor signing; only a hidden dev screen copies an unsigned one; no import, file, QR or share.
HwWalletManager.swift :1177 DevSettingsView.swift :59 TrezorSendTransactionView.swift :269 - Watch-only wallets: Partial. Paired Trezor accounts are tracked by xpub (balance, history); pasting an xpub or address works only in hidden dev settings.
HwWalletManager.swift :5 HardwareWalletScreen.swift :4 TrezorWatcherView.swift :16 - BIP 39 passphrase: Yes. Advanced setup adds a passphrase to a new wallet; restore takes an optional one; both feed the BIP 39 seed.
OnboardingSlider.swift :11 CreateWalletWithPassphraseView.swift :65 RestoreWalletView.swift :198 - Coin control: Yes. Manual coin selection setting; the send flow then lists UTXOs to tick and spends exactly those.
CoinSelectionSettingsView.swift :128 SendAmountView.swift :302 SendUtxoSelectionView.swift :74 - Speed up a stuck payment (RBF or CPFP): Yes. Boost button on an unconfirmed on-chain activity: RBF for sent, CPFP for received; off for hardware wallet activity.
CoreService.swift :1648 ActivityItemView.swift :562 mod.rs :827 - Taproot receive addresses: Yes. Advanced > Address Type offers Taproot (BIP 86, bc1p); new wallets default to native SegWit.
AddressTypePreferenceView.swift :109 SettingsViewModel.swift :576 builder.rs :1399 - Output descriptors: Yes. Scanning a BTCPay SamRock QR sends the account's output descriptor to the store; no descriptor import or copy screen.
SamRockService.swift :273 SamRockService.swift :333 implementation.rs :194 - Use your own node or server: Yes. Settings > Advanced > Electrum Server takes host, port and TCP or TLS; default is Synonym's bitkit.to.
AdvancedSettingsView.swift :100 ElectrumSettingsScreen.swift :56 SettingsViewModel+Electrum.swift :46 - Checks chain data on the phone: Partial. Electrum sync through BDK, which checks merkle proofs against headers the server sends; no PoW, filter or block check.
electrum.rs :671 electrum.rs :785 Cargo.toml :50 - Tor or I2P: No. Electrum client is built with no proxy; settings offer only host, port and TCP/TLS; no Tor or I2P code in the app.
electrum.rs :660 ElectrumSettingsScreen.swift :83 - Standard BIP 39 recovery phrase: Yes. 12-word BIP 39 phrase shown for backup; accounts use standard BIP 44/49/84/86 paths; restore takes 12 or 24 words.
StartupHandler.swift :13 BackupMnemonic.swift :114 builder.rs :1385 - Encrypted backup off the phone: Partial. Settings, tags, activity and transfer records go encrypted to Synonym's fixed VSS server; no keys, no iCloud.
BackupService.swift :220 Env.swift :340 implementation.rs :248
Blixt
- Multisig or shared accounts: No. One wallet from one 24-word seed; the app has no m-of-n or MuSig2 account (LND signing RPCs are never called).
index.ts :795 OnChain.ts :165 - Hardware or external signer: No. Keys come from the on-phone seed; the generated lnd.conf has no remote signer and no device code exists in the app.
index.ts :795 index.ts :674 - PSBT import and export: No. Withdraw signs and broadcasts through LND sendCoins; there is no PSBT import, export or share.
Withdraw.tsx :50 OnChain.ts :217 - Watch-only wallets: No. Restore accepts a 24-word seed only; no xpub, descriptor or address import in the app.
Restore.tsx :69 index.ts :778 - BIP 39 passphrase: Partial. Optional aezeed passphrase on create and restore; not BIP 39: a wrong one is rejected, it does not open another wallet.
Start.tsx :63 Restore.tsx :310 errors.go :10 - Coin control: No. sendCoins gets address, amount and fee rate only, no outpoints; no coin list in the app (LND would accept outpoints).
OnChain.ts :217 Withdraw.tsx :50 - Speed up a stuck payment (RBF or CPFP): Partial. Bump Fee does CPFP on the change of a pending channel-open tx only; ordinary on-chain sends have no bump.
PendingChannelCard.tsx :117 PendingChannelCard.tsx :176 OnChain.ts :239 - Taproot receive addresses: Yes. Default receive address is P2TR (bc1p); long-pressing New Address gives P2WPKH.
OnChain.ts :164 OnChainInfo.tsx :62 - Output descriptors: No. On-chain screens are address, history, details and withdraw; no descriptor or xpub export or import.
index.tsx :19 OnChainInfo.tsx :129 - Use your own node or server: Yes. Settings > Bitcoin Node takes any BIP 157 peer. A bitcoind mode sits behind a hidden debug gate; the fee URL is fixed.
Settings.tsx :1670 Settings.tsx :750 PowerUserTools.tsx :696 - Checks chain data on the phone: Yes. Embedded LND runs neutrino (BIP 157/158): headers and filters are checked on the phone, not a server's balances.
index.ts :695 config_builder.go :1872 - Tor or I2P: Yes. Experimental toggle starts embedded Tor; Bitcoin and LN peers use it, rates and mempool.space calls stay clearnet.
Settings.tsx :1867 index.ts :295 en.json :958 - Standard BIP 39 recovery phrase: Partial. 24-word aezeed phrase (LND), not BIP 39; BIP 39 wallets cannot restore it and the app cannot import a BIP 39 phrase.
index.ts :628 Restore.tsx :70 cipherseed.go :15 - Encrypted backup off the phone: No. iCloud backup holds encrypted channel state only (off-chain funds); on-chain funds restore from the written phrase.
ICloudBackup.ts :102 en.json :1241
Zeus
- Multisig or shared accounts: No. Single-key accounts only; account import takes one xpub or a wpkh/sh(wpkh) descriptor; no m-of-n or MuSig2 code in the app.
ImportAccount.tsx :34 AddressUtils.ts :73 - Hardware or external signer: Yes. Imports signer xpub and fingerprint by QR (BC-UR, JSON export) and signs by PSBT QR; no named-device pairing; labelled experimental.
HandleAnythingQRScanner.tsx :114 PSBT.tsx :201 en.json :426 - PSBT import and export: Yes. Unsigned PSBT shown as animated QR (BC-UR, BBQr) or copied; signed PSBT scanned back and broadcast; for imported accounts only.
PSBT.tsx :201 handleAnything.ts :1050 HandleAnythingQRScanner.tsx :161 - Watch-only wallets: Yes. Imports an xpub or wpkh/sh(wpkh) descriptor as a watch-only LND account with its own balance; embedded LND rescans from a chosen height.
ImportingAccount.tsx :164 index.tsx :496 handleAnything.ts :1122 - BIP 39 passphrase: No. initWallet accepts a passphrase argument but no create or restore screen collects one; same for LDK Node.
LndMobileUtils.ts :1316 SeedRecovery.tsx :924 WalletCreationUtils.ts :126 - Coin control: Yes. On-chain Send has a UTXO picker to tick coins; each account also has a Coins list; LDK Node has it too.
Send.tsx :1015 UTXOPicker.tsx :208 - Speed up a stuck payment (RBF or CPFP): Yes. Speed up button on unconfirmed txs calls LND BumpFee on a wallet output (CPFP); LDK Node has no bump.
Transaction.tsx :404 walletkit.proto :316 LdkNode.ts :2177 - Taproot receive addresses: Yes. Receive settings offer Taproot (P2TR) beside P2WPKH and nested SegWit; P2WPKH is the default; LDK Node issues P2WPKH only.
Receive.tsx :1127 EmbeddedLND.ts :586 LdkNode.ts :2176 - Output descriptors: Partial. Imports single-key wpkh() and sh(wpkh()) descriptors as an account; no descriptor export; no tr(), pkh or multi-key forms.
AddressUtils.ts :73 handleAnything.ts :1094 - Use your own node or server: Yes. Add Neutrino peers by host and switch on 'don't allow other peers' to use only yours; LDK Node takes a custom Esplora URL.
LndMobileUtils.ts :222 NeutrinoPeers.tsx :213 NeutrinoPeers.tsx :258 - Checks chain data on the phone: Yes. Embedded LND runs Neutrino: headers and BIP 157/158 filters checked on the phone; LDK Node and remote nodes trust Esplora or the node.
LndMobileUtils.ts :270 config_builder.go :1876 - Tor or I2P: No. No Tor toggle for the embedded node (embeddedTor stays false); the built-in Tor client is offered only for remote nodes.
WalletConfiguration.tsx :1462 SettingsStore.ts :1608 Advanced.tsx :66 - Standard BIP 39 recovery phrase: Partial. Embedded LND shows a 24-word aezeed (LND format, not BIP 39); the LDK Node option shows a BIP 39 phrase.
LndMobileUtils.ts :1295 README.md :5 Seed.tsx :319 - Encrypted backup off the phone: Partial. Embedded LND uploads only encrypted channel state to Olympus, no on-chain data or keys; LDK Node keeps node state on a VSS server.
ChannelBackupStore.ts :63 ChannelBackupStore.ts :95 LdkNodeUtils.ts :47
Code size
Winnow counts 21,997 lines of code: 8,965 for the app and 13,032 for its own Bitcoin library, which lives in the same repository. The other wallets count 1.7 to 8.7 times as many lines. That is the app, plus a Bitcoin library of its own for Muun.
The count leaves out the engines that Green, Phoenix, Bitkit, Blixt and Zeus embed: gdk, lightning-kmp, LDK Node, and LND. Each is a separate project the wallet is built on, and four of the five are Lightning nodes, so their lines are shown in the table and not in the totals. BlueWallet takes its Bitcoin primitives from npm packages, which the dependency tables count. The gap would be wider with the engines added.
Winnow’s lines are denser, at about 33 characters each against 19–29 for the others. Counted in characters, the gap is 1.3 to 5.2 times.
- App
- Own Bitcoin library
Winnow22k719k
BlueWallet58k1.6M
Green70k2.0M
Muun88k2.4M
Phoenix73k1.9M
Bitkit101k2.8M
Blixt36k948k
Zeus192k3.7M
| Wallet | App | Own Bitcoin library | Total lines | Characters | vs Winnow | Embedded engine, not in the total |
|---|---|---|---|---|---|---|
| Winnow | 8,965 | 13,032 | 21,997 | 0.72M | — | None embedded |
| BlueWallet | 57,858 | — | 57,858 | 1.64M | 2.6× · 2.3× | None embedded |
| Green | 70,474 | — | 70,474 | 2.03M | 3.2× · 2.8× | 34,374 Blockstream/gdk release_0.78.0 |
| Muun | 59,660 | 28,469 | 88,129 | 2.44M | 4.0× · 3.4× | None embedded |
| Phoenix | 72,881 | — | 72,881 | 1.90M | 3.3× · 2.6× | 28,779 ACINQ/lightning-kmp v1.13.2 |
| Bitkit | 100,640 | — | 100,640 | 2.83M | 4.6× · 3.9× | 127,646 synonymdev/ldk-node 0.7.0-rc.66, lightningdevkit/rust-lightning v0.2.6 |
| Blixt | 36,360 | — | 36,360 | 0.95M | 1.7× · 1.3× | 238,947 hsjoberg/lnd@ae185f2 (0.21.1) |
| Zeus | 191,851 | — | 191,851 | 3.73M | 8.7× · 5.2× | 242,900 ZeusLN/lnd v0.21.4-beta-zeus.1 |
“vs Winnow” divides each total by Winnow’s, first in lines, then in characters. The engine column shows each engine at the version the app pins, so its size can be read beside the wallet’s.
Languages
Share of each wallet’s counted lines, app and own Bitcoin library together. Winnow is written in one language; most of the others combine a user-interface language with a library in another.
| Wallet | Swift | Kotlin | Rust | Go | TypeScript/JS | C family |
|---|---|---|---|---|---|---|
| Winnow | 100% | — | — | — | — | — |
| BlueWallet | 6% | — | — | — | 94% | <1% |
| Green | 68% | — | 9% | — | — | 23% |
| Muun | 68% | — | <1% | 32% | — | <1% |
| Phoenix | 60% | 40% | — | — | — | 1% |
| Bitkit | 44% | — | 56% | — | — | — |
| Blixt | <1% | — | — | 87% | 13% | <1% |
| Zeus | 1% | — | — | 56% | 43% | <1% |
Complexity and CRAP
CRAP scores each function by its cyclomatic complexity (CC, the number of paths through it) and how much of it the tests run: CRAP = CC² × (1 − coverage)³ + CC. Coverage needs each project’s test suite run with instrumentation, on a Mac for the Swift apps, so it is not measured here. Testing can only bring CRAP down to CC, so the complexity alone sets a floor: a function above 12 scores above 12 however well it is tested, and one above 30 fails crap4j’s default threshold whatever its tests. Complexity is measured on each wallet’s counted code, not on embedded engines.
1.5% of Winnow’s functions are above 12, against 0.8–8.0% for the others. Winnow’s most complex function scores 27. Its mean is 3.3; the others’ range from 1.9 to 5.3. Winnow’s CI also rejects any function over 8 as SwiftLint counts complexity, which leaves out case arms, &&, ||, ?? and ternaries; this page counts all of them, so its numbers are higher.
| Wallet | Functions | Mean CC | 90th pct | Max | CRAP above 12 at any coverage | CRAP above 30 at any coverage | |
|---|---|---|---|---|---|---|---|
| Winnow | 1,620 | 3.3 | 7 | 27 | 24 (1.5%) | 0 (0.00%) | |
| BlueWallet | 1,764 | 5.3 | 10 | 242 | 141 (8.0%) | 42 (2.38%) | |
| Green | 6,905 | 2.2 | 4 | 45 | 85 (1.2%) | 7 (0.10%) | |
| Muun | 6,679 | 1.9 | 4 | 63 | 54 (0.8%) | 2 (0.03%) | |
| Phoenix | 4,703 | 2.5 | 5 | 155 | 77 (1.6%) | 9 (0.19%) | |
| Bitkit | 6,014 | 3.0 | 6 | 119 | 109 (1.8%) | 14 (0.23%) | |
| Blixt | 1,057 | 3.6 | 7 | 134 | 56 (5.3%) | 19 (1.80%) | |
| Zeus | 4,820 | 4.7 | 10 | 252 | 350 (7.3%) | 77 (1.60%) |
By part: app and own Bitcoin library
Dependencies and vendored code
Winnow depends on 1 third-party package: swift-secp256k1, which builds Bitcoin Core’s libsecp256k1 from source. The others depend on 58 to 1,457. These counts include what the embedded engines pull in, because that code ships in the app too. Package counts from different ecosystems are not equal units: an npm package is often a few hundred lines, a Rust crate or Go module often more. Read them per row.
| Kind | Winnow | BlueWallet | Green | Muun | Phoenix | Bitkit | Blixt | Zeus |
|---|---|---|---|---|---|---|---|---|
| Swift packages | 1 | — | 31 | — | 19 | 5 | — | — |
| Kotlin libraries | — | — | — | — | 39 | — | — | — |
| Rust crates, inside prebuilt libraries | — | — | 126 | — | — | 693 | — | 407 |
| Go modules, inside the Go library | — | — | — | 111 | — | — | 120 | 140 |
| npm packages | — | 922 | — | — | — | — | 553 | 907 |
| CocoaPods from the trunk | — | 2 | — | 28 | — | — | 8 | 3 |
| Third-party packages | 1 | 924 | 157 | 139 | 58 | 698 | 681 | 1,457 |
| Wallet | Main libraries | Bitcoin code not counted above | Prebuilt binaries | Third-party code copied into the repository | Patched dependencies |
|---|---|---|---|---|---|
| Winnow | swift-secp256k1 0.23.2 (Bitcoin Core's libsecp256k1), the only third-party dependency Package. README. | — | None in the repository; the app links the swift-secp256k1 package that SwiftPM fetches Package. | None | None |
| BlueWallet | bitcoinjs-lib, bip32, bip39, ecpair, @noble/secp256k1, BlueWallet forks of coinselect, rn-electrum-client, bip47, SilentPayments and slip39-js, aezeed, bolt11, payjoin-client, bc-ur (Keystone and NGRAVE), realm, react-native-keychain, @arkade-os/sdk and boltz-swap, Bugsnag. package. | bitcoinjs-lib, bip32 and @noble/secp256k1, from npm | No binaries are checked in. The build links React Native's prebuilt Hermes and React-Core-prebuilt pods, and the patched react-native-background-fetch pod vendors the npm package's TSBackgroundFetch.xcframework. Podfile. Podfile. react-native-background-fetch+4. | 5,136 lines in blue_modules: pako, bbqr, uint8array-extras, bc-ur, bc-bech32 | Four patch-package patches on npm packages: react-native-background-fetch (iOS xcframework with Catalyst), react-native-notifications (Android token), react-native-context-menu-view (Android menus), react-native-screens (iOS accessibility id; patch is for 4.27.0 while package.json pins 4.28.0). Several dependencies also come from BlueWallet forks pinned to git commits. |
| Green | gdk (libwally-core, rust-bitcoin, Tor), Liquid Wallet Kit, Greenlight SDK, Firebase, Countly, Zendesk, Rive, Charts, SwiftCBOR, AsyncBluetooth Package. Package. Package. | libwally-core, rust-bitcoin, rust-elements (inside the prebuilt gdk) | gdk static library (libgreen_gdk_full.a) from Blockstream's release tarballs, checked against pinned SHA-256 sums; Google's grpc-binary and abseil-cpp-binary packages fetch_gdk_binaries. | None | Countly's iOS SDK comes from a fork (angelix/countly-sdk-ios 23.6.1-ave). gdk's own build patches libevent and bc-ur, outside the app. |
| Muun | Swift: Firebase (Analytics, Crashlytics, Messaging), GoogleSignIn and Google Drive API, GRDB.swift, RxSwift, Dip, grpc-swift, Lottie. Go: btcd, lnd (lnwire, zpay32), lightning-onion, go-lnurl, gofpdf, pdfcpu, gorm with SQLite, gRPC. Podfile Package. go. | btcd and lnd, as Go modules. Copies inside libwallet (btcsuitew, musig2v040) are counted | Rust zero-knowledge proof verifier (plonky2) linked as the static library multi-apple-ios-librs.a, built by libwallet/librs/makelibs.sh and not in the tree; FirebaseAnalytics and GoogleAppMeasurement xcframeworks from dl.google.com librs. pods_dependency_registry. pods_dependency_registry. | 101,694 lines in falcon/app/Pods: GRDB.swift, FirebaseCrashlytics, RxSwift, GTMSessionFetcher, FirebaseMessaging, GoogleAPIClientForREST and 19 more | Go 1.24.7 toolchain is patched for cgo struct alignment (falcon/0001-align-to-ptrsize.patch); golang.org/x/mobile is replaced by the muun/mobile fork (libwallet/go.mod:200) |
| Phoenix | bitcoin-kmp with secp256k1-kmp, Ktor, kotlinx-serialization, SQLDelight (Kotlin side); Firebase Messaging and a few small SwiftUI packages (Swift side) build. build. project. | bitcoin-kmp | Kotlin libraries (lightning-kmp, bitcoin-kmp, secp256k1-kmp native code, Ktor, SQLDelight) as compiled klibs from Maven Central; Firebase through Swift Package Manager build. build. | None | None |
| Bitkit | LDK Node (with BDK), bitkit-core, Paykit, VSS client, Lottie, swift-secp256k1 project. | BDK, rust-bitcoin | LDK Node, bitkit-core, Paykit and the VSS client, as XCFrameworks Package. Package. Package. Package. | None | None |
| Blixt | React Native, react-native-turbo-lnd, @bufbuild/protobuf, secp256k1, bech32, react-native-nitro-tor, react-native-keychain, easy-peasy package. package. package. package. package. | btcwallet (with btcd, neutrino) under LND | Lndmobile.xcframework (LND), downloaded from the react-native-turbo-lnd release for the pinned LND commit react-native-turbo-lnd. | None | None in the app (no patch files); LND is Blixt's own fork (hsjoberg/lnd) with an added gossip speedloader |
| Zeus | React Native, bitcoinjs-lib, noble and scure cryptography, bc-ur registries (Keystone, NGRAVE), Nostr (NDK), react-native-nitro-tor, Cashu CDK package. package. package. package. package. package. package. package. | btcwallet (with btcd, neutrino) under LND; bitcoinjs-lib; BDK in the optional LDK Node | Lndmobile.xcframework (LND), LDKNodeFFI.xcframework (LDK Node), and the Cashu CDK and restore xcframeworks, downloaded from release archives pinned by SHA-256 fetch-libraries-versions. fetch-libraries. | 4,605 lines in zeus_modules, zeus_modules/@lightninglabs: lnc-core, lnc-rn, bc-ur, bc-bech32, noble_ecc, ur | Six postinstall scripts rewrite installed npm packages (two are Android-only: jcenter and notifications; four reach iOS: NativeEventEmitter, keychain cloudSync, noble-hashes exports, reanimated C++); rn-nodeify shims crypto, stream and dgram |
- Swift packages are every package Swift Package Manager resolves. Phoenix links Firebase Messaging, which brings in Google’s package graph.
- Kotlin libraries are the 39 libraries Phoenix’s iOS framework links besides lightning-kmp, resolved for the iPhone target.
- Rust crates are compiled into prebuilt libraries. Bitkit’s four libraries each carry their own copies: 693 distinct crates, 1,085 counting each library’s copy. Green’s gdk adds 126. gdk’s C++ dependencies are not counted.
- Go modules are linked into Muun’s libwallet (111), and into LND in Blixt and Zeus.
- npm packages are everything the app’s runtime dependencies pull in (99 declared by BlueWallet, 87 by Blixt, 110 by Zeus), not build tools. The bundler includes only what the app imports.
- CocoaPods counts pods from the CocoaPods trunk. Another 109 pods in BlueWallet, 99 in Blixt and 112 in Zeus are the native halves of npm packages and React Native itself.
- Muun commits the sources of its CocoaPods into its repository: 101,694 lines of third-party code that build into the app, with a registry of each pod’s upstream and checksum.
Over time
The comparison is measured again every day from each wallet’s latest code. Wallet repositories are read at the head of their main branch, except Phoenix, which is read at its latest iOS release (ios-v2.8.4): when we checked, its default branch was configured for testnet. Every engine and library is read at the version its wallet pins. A snapshot is recorded whenever any of them moves: 9 since 2 October 2026, when this page began counting Bitcoin wallets only. Changes are shown against that first snapshot. Hover or focus a line for each snapshot. The 4 earlier snapshots (27 September 2026 to 1 October 2026) compared Lightning wallets and counted their Lightning engines; they are in data.json.
| Wallet | Lines of code | Third-party packages | Functions above CC 12 |
|---|---|---|---|
| Winnow | 21,997no change | 1no change | 24no change |
| BlueWallet | 57,858+233 (+0.4%) | 924−6 (−0.6%) | 141+2 (+1.4%) |
| Green | 70,474no change | 157no change | 85no change |
| Muun | 88,129no change | 139no change | 54no change |
| Phoenix | 72,881no change | 58no change | 77no change |
| Bitkit | 100,640+6,951 (+7.4%) | 698+7 (+1.0%) | 109+17 (+18.5%) |
| Blixt | 36,360no change | 681no change | 56no change |
| Zeus | 191,851+1,867 (+1.0%) | 1,457−10 (−0.7%) | 350+5 (+1.4%) |
Every snapshot
| Snapshot | Winnow | BlueWallet | Green | Muun | Phoenix | Bitkit | Blixt | Zeus | Sources that moved |
|---|---|---|---|---|---|---|---|---|---|
| 10 October 2026 | 21,997 | 57,858 | 70,474 | 88,129 | 72,881 | 100,640 | 36,360 | 191,851 | bitkit, paykit-rs, zeus |
| 9 October 2026 | 21,997 | 57,858 | 70,474 | 88,129 | 72,881 | 100,239 | 36,360 | 190,159 | bitkit, zeus, lnd-zeus |
| 8 October 2026 | 21,997 | 57,858 | 70,474 | 88,129 | 72,881 | 99,671 | 36,360 | 190,500 | bitkit, paykit-rs, zeus, bluewallet |
| 7 October 2026 | 21,997 | 57,858 | 70,474 | 88,129 | 72,881 | 98,068 | 36,360 | 190,456 | bitkit, zeus, bluewallet |
| 6 October 2026 | 21,997 | 57,816 | 70,474 | 88,129 | 72,881 | 97,921 | 36,360 | 190,382 | bitkit, zeus |
| 5 October 2026 | 21,997 | 57,816 | 70,474 | 88,129 | 72,881 | 94,774 | 36,360 | 190,363 | bitkit, zeus, zeus-ldk-node, bluewallet |
| 4 October 2026 | 21,997 | 57,816 | 70,474 | 88,129 | 72,881 | 93,689 | 36,360 | 190,001 | zeus, bluewallet |
| 3 October 2026 | 21,997 | 57,625 | 70,474 | 88,129 | 72,881 | 93,689 | 36,360 | 190,005 | winnow, zeus, lnd-zeus |
| 2 October 2026 | 21,997 | 57,625 | 70,474 | 88,129 | 72,881 | 93,689 | 36,360 | 189,984 | First snapshot |
| Snapshot | Winnow | BlueWallet | Green | Muun | Phoenix | Bitkit | Blixt | Zeus |
|---|---|---|---|---|---|---|---|---|
| 10 October 2026 | 1 | 924 | 157 | 139 | 58 | 698 | 681 | 1,457 |
| 9 October 2026 | 1 | 924 | 157 | 139 | 58 | 698 | 681 | 1,457 |
| 8 October 2026 | 1 | 924 | 157 | 139 | 58 | 698 | 681 | 1,457 |
| 7 October 2026 | 1 | 930 | 157 | 139 | 58 | 691 | 681 | 1,457 |
| 6 October 2026 | 1 | 930 | 157 | 139 | 58 | 691 | 681 | 1,466 |
| 5 October 2026 | 1 | 930 | 157 | 139 | 58 | 691 | 681 | 1,466 |
| 4 October 2026 | 1 | 930 | 157 | 139 | 58 | 691 | 681 | 1,466 |
| 3 October 2026 | 1 | 930 | 157 | 139 | 58 | 691 | 681 | 1,466 |
| 2 October 2026 | 1 | 930 | 157 | 139 | 58 | 691 | 681 | 1,467 |
| Snapshot | Winnow | BlueWallet | Green | Muun | Phoenix | Bitkit | Blixt | Zeus |
|---|---|---|---|---|---|---|---|---|
| 10 October 2026 | 24 | 141 | 85 | 54 | 77 | 109 | 56 | 350 |
| 9 October 2026 | 24 | 141 | 85 | 54 | 77 | 105 | 56 | 345 |
| 8 October 2026 | 24 | 141 | 85 | 54 | 77 | 101 | 56 | 347 |
| 7 October 2026 | 24 | 141 | 85 | 54 | 77 | 96 | 56 | 347 |
| 6 October 2026 | 24 | 141 | 85 | 54 | 77 | 96 | 56 | 347 |
| 5 October 2026 | 24 | 141 | 85 | 54 | 77 | 93 | 56 | 347 |
| 4 October 2026 | 24 | 141 | 85 | 54 | 77 | 92 | 56 | 347 |
| 3 October 2026 | 24 | 139 | 85 | 54 | 77 | 92 | 56 | 346 |
| 2 October 2026 | 24 | 139 | 85 | 54 | 77 | 92 | 56 | 345 |
What the numbers leave out
Less code is not proof of safety
A smaller codebase is quicker to read and review. It does not show the code is correct, and it says nothing about whether anyone independent has reviewed it. Check each project’s own security page.
The wallets do different amounts of work
Each count is the whole app, not only its on-chain wallet. Zeus also manages remote LND and Core Lightning nodes and includes Cashu, Nostr Wallet Connect and a point of sale. Bitkit includes Pubky contacts, Paykit and Trezor support. Winnow includes shared accounts with MuSig2 and 2-of-3 signing. BlueWallet includes Lightning wallet types (an LNDHub custodian and an Ark one). Green’s count includes its lightning folder. Muun includes Lightning payments through swaps.
Engines are not counted
Phoenix, Bitkit, Blixt, Zeus and Green build their Bitcoin wallet on an engine that is a separate project: lightning-kmp, LDK Node with BDK, LND and gdk. The code size and complexity here are for the wallet itself, so those wallets look smaller than everything they ship. Winnow, Muun and BlueWallet have no such engine. Winnow and Muun keep their own Bitcoin library in the repository, which is counted.
Who can move on-chain funds
Where a service has to cosign, the wallet cannot spend without it, or only after a delay. The fourth column of the table at the top says which wallets that applies to, and the notes under each cell give the account type.
How we measured
- Lines: cloc 2.06 counted non-blank, non-comment lines in each app’s programming languages, for code that builds into the iPhone app. Characters were counted after removing comments and all whitespace. Excluded: tests; Android, desktop and web code; build tools; files marked as generated (protobuf, sqlc and UniFFI bindings); and vendored third-party code. Rust test modules inside source files were removed.
- Complexity: each file was parsed with tree-sitter, and one rule applied to every language. CC is 1, plus 1 for each
iforguard, loop,catch, ternary,&&,||,??or elvis, and non-defaultcase,whenormatcharm. Closures, lambdas and nested functions count toward the function that contains them, so a React screen written as one function component counts as one function. Tree-sitter could not fully parse at most 5% of any wallet’s files; it recovers locally, so their functions are still counted. - Dependencies: read from each app’s own lockfiles (Swift Package Manager, CocoaPods, npm, yarn and bun). Kotlin libraries were resolved from Maven Central’s metadata for the iPhone target. Rust crates come from
cargo treeforaarch64-apple-iosat each prebuilt library’s pinned source. Go modules come fromgo list -depsfor iOS, with the build tags LND’s own iOS build uses or, for Muun, none. - Vendored code: each repository was searched for third-party code committed into it: vendor directories, committed CocoaPods, copied npm packages, license files below the root, committed binaries and dependency patches.
- Features: each cell was checked by hand in the app’s and the engine’s source on 2 October 2026, at the revisions in
reviewed.json. The build compares every cited passage with today’s code and lists any that have changed. The rows and what each value means are infeatures/rows.json. - Embedded engines: counted at the version each app pins, as the named library only, and kept out of every total. Phoenix’s lightning-kmp comes from its Gradle catalog. Bitkit’s LDK Node and libraries come from its Package.resolved, and rust-lightning from LDK Node’s lockfile. Blixt’s LND is the fork its react-native-turbo-lnd release builds. Zeus’s LND, LDK Node and Cashu versions come from its
fetch-libraries-versions.json, and its count uses its default embedded LND. Green’s gdk is the release itsfetch_gdk_binaries.shdownloads.
Exact sources
Each project changes often. These numbers are measured again every day, and each snapshot is dated. They are a measurement, not a ranking.
Reproduce it
Everything on this page is generated from the source repository. It pins every project to an exact commit, and data.json holds every measurement, including the full package lists. To rebuild it:
git clone https://github.com/winnowwallet/compare
cd compare
npm ci && pip install -r requirements.txt
./build # fetch the pinned sources, measure, render
git diff # nothing changes if the measurement reproduces
It needs Python 3.11, Node, Go and Cargo, plus network access to GitHub, crates.io, the Go module proxy and Maven Central. Feature cells and profile facts are reviewed by hand. The build checks that every citation resolves at the revision it was reviewed against, and reports which cited code has changed since.
Not compared
This comparison needs the iPhone app’s source, so closed-source and custodial wallets cannot be measured and are left out, however widely they are used. Other open-source wallets could be added.
- Exodus, Cash App and Strike are widely used iPhone wallets whose apps are closed source, so there is no code to measure or cite.
- Envoy, Bull Bitcoin, Bitcoin Keeper, Cake Wallet and Edge publish source code and may be added. Each needs a measured iPhone build and a hand review of its features.