winnow

Winnow compare · Lightning on iPhone

Lightning wallets compared

Winnow is adding Lightning. Here is how it compares with the open-source iPhone wallets that already run a Lightning node on the phone: features, code size, complexity and what each one depends on.

At a glance

Each wallet below holds its own keys and runs a Lightning node inside the app. The table shows defaults; most of these wallets let you change the chain source or the channel partner.

WalletBuilt withLightning engineChain dataChannel partnerOn iPhone
WinnowSwiftIts own, in Swift, in the same repository as the walletCompact block filters (BIP157/158) from Bitcoin peers Winnow finds itself README.md:7Your choice of Olympus, Megalith or LNServer LightningProviders.swift:19Research beta, internal TestFlight
PhoenixSwift on Kotlin Multiplatformlightning-kmp 1.12 (Kotlin)An Electrum server picked at random from a built-in list of 15 AppConfigurationManager.kt:66ACINQ NodeParamsManager.kt:87App Store
BitkitSwiftLDK Node (Synonym’s fork) on rust-lightning 0.2 (Rust)Synonym’s Electrum server Env.swift:188Blocktank, run by Synonym Env.swift:292App Store
BlixtReact Native (TypeScript)Embedded LND (Go)Neutrino (BIP157) through six default peers, four run by Blixt constants.ts:30Dunder, run by Blixt, or any node constants.ts:51TestFlight
ZeusReact Native (TypeScript)Embedded LND 0.21 (Go). LDK Node is an option, and Zeus can also drive a remote node.Neutrino (BIP157) through five default peers, two run by Olympus; the LDK Node option uses an Esplora server SettingsStore.ts:1476Olympus, run by Zeus, or any node SettingsStore.ts:1305App Store

Features

What a person can do in each iPhone app today, checked against the code. “Engine only” means the Lightning library supports it but the app does not offer it. Hover or focus a cell for the note; every cell links to its evidence below.

FeatureWinnowPhoenixBitkitBlixtZeus
Paying and receiving
Pay a BOLT 11 invoice No Yes Yes Yes Yes
Receive with a BOLT 11 invoice Yes Yes Yes Yes Yes
Pay a BOLT 12 offer Partial Yes Engine only No No
Receive with a reusable BOLT 12 offer Partial Yes Engine only No No
Receive while the app is closed Partial Yes Partial No Partial
LNURL No Yes Yes Yes Yes
Pay a Lightning address No Yes Yes Yes Yes
Your own Lightning address No Partial No No Partial
Pay a BIP 353 name No Yes No No Partial
Multipath payments No Partial Yes Yes Yes
Keysend No No Engine only Yes Yes
Channels and routing
Choose your channel partner Yes No Yes Yes Yes
Inbound channels from a provider Partial Yes Yes Yes Yes
Splicing No Yes Engine only No No
Anchor channels No Yes Yes Yes Yes
Finds its own routes No No Yes Yes Yes
Safety and recovery
Watchtower No Partial No Engine only Yes
Channel backup off the phone No Yes Yes Yes Yes
On-chain and privacy
On-chain funds without the channel partner Yes Partial Yes Yes Yes
Taproot receive addresses Yes Yes Yes Yes Yes
Multisig or shared accounts Yes No No No No
Hardware or external signer Partial No Yes Engine only Yes
Coin control No No Yes Engine only Yes
Built-in Tor No Partial No Yes No
License
Source licenseMITApache-2.0MITMITAGPL-3.0

Yes · Partial · Engine only · No

Notes and code evidence for every cell

Links open the exact line at the revision measured.

Winnow

Phoenix

Bitkit

Blixt

Zeus

Code size

Winnow ships 28,325 lines of code: 10,298 for the app, 13,120 for its Bitcoin library and peer networking, and 4,907 for the Lightning engine. The other wallets ship 3.6 to 15 times as many lines, without counting their Bitcoin libraries.

Winnow’s lines are denser, at about 36 characters each against 22–30 for the others. Counted in characters, the gap is 3.0 to 9.1 times.

  • App
  • Lightning engine
  • Bitcoin library
Winnow28k1.0M
Phoenix101k3.1M
Bitkit218k6.3M
Blixt276k6.5M
Zeus429k9.2M
Code that builds into each iPhone app, without tests, generated files or comments. Only Winnow’s count includes a Bitcoin library, because only Winnow’s is in the counted repository. Snapshot of 27 September 2026.
Lines of code by part
WalletAppLightning engineBitcoin libraryTotal linesCharactersvs Winnow
Winnow10,2984,90713,12028,3251.01M—
Phoenix72,76728,086Not counted (bitcoin-kmp)100,8533.07M3.6× · 3.0×
Bitkit91,893126,384Not counted (BDK, rust-bitcoin)218,2776.33M7.7× · 6.3×
Blixt36,360239,849Not counted (btcd, btcwallet, neutrino)276,2096.51M9.8× · 6.4×
Zeus189,559239,849Not counted (btcd, btcwallet, neutrino)429,4089.23M15.2× · 9.1×

“vs Winnow” divides each total by Winnow’s, first in lines, then in characters. The Lightning engines alone: Winnow’s is 4,907 lines, lightning-kmp 28,086, LDK Node with its rust-lightning crates 126,384, and LND 239,849.

Languages

Share of each wallet’s counted lines, app and Lightning engine together. Winnow is written in one language; the others combine a user-interface language with a Lightning engine in another.

WalletSwiftKotlinRustGoTypeScript/JSC family
Winnow100%—————
Phoenix60%40%———<1%
Bitkit42%—58%———
Blixt<1%——87%13%<1%
Zeus1%——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 three 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.

1.3% of Winnow’s functions are above 12, against 1.8–6.0% for the others. Winnow’s most complex function scores 31. Its average is 3.3, higher than Phoenix’s and Bitkit’s, because those codebases have more very small functions. Winnow’s CI also rejects any function over 8 as SwiftLint counts complexity, which leaves out case arms, &&, ||, ?? and ternaries.

Cyclomatic complexity per function, app and Lightning engine together
WalletFunctionsMean CC90th pctMaxCRAP above 12 at any coverageCRAP above 30 at any coverage
Winnow2,1643.373128 (1.3%)1 (0.05%)
Phoenix7,2182.45168132 (1.8%)22 (0.30%)
Bitkit10,6683.06171246 (2.3%)46 (0.43%)
Blixt11,5163.98147616 (5.3%)85 (0.74%)
Zeus15,1314.29252904 (6.0%)141 (0.93%)
By part: app, Lightning engine and Bitcoin library
PartFunctionsMean CC90th pctMaxCC > 12CC > 30Most complex
Winnow · app6953.3731111init
Winnow · lightning engine4643.371330apply
Winnow · bitcoin library1,0053.3727140decode
Phoenix · app4,6942.55155779(computed_property)
Phoenix · lightning engine2,5242.341685513processInternal
Bitkit · app5,3313.06127858(computed_property)
Bitkit · lightning engine5,3373.0617116138read
Blixt · app1,0573.671345619Settings
Blixt · lightning engine10,4593.9914756066ValidateConfig
Zeus · app4,6724.71025234475render
Zeus · lightning engine10,4593.9914756066ValidateConfig

Blixt and Zeus share the same LND, so their engine rows match.

Dependencies and vendored code

Winnow depends on 1 third-party package: swift-secp256k1, which builds Bitcoin Core’s libsecp256k1 from source. Phoenix depends on 59; the others on up to 1,421. 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.

Third-party packages each app resolves and ships, beyond the code counted above
KindWinnowPhoenixBitkitBlixtZeus
Swift packages1195——
Kotlin libraries—40———
Rust crates, inside prebuilt libraries——686—407
Go modules, inside LND———114114
npm packages———553897
CocoaPods from the trunk———83
Third-party packages1596916751,421
Main libraries, and code that arrives other than as source packages
WalletMain librariesPrebuilt binariesThird-party code copied into the repositoryPatched dependencies
Winnowlibsecp256k1, through swift-secp256k1. Nothing else. Package.swift:19NoneNoneNone
Phoenixbitcoin-kmp, secp256k1-kmp, Ktor, SQLDelight, Firebase Messaging project.pbxproj:386Kotlin libraries, as compiled klibs from Maven CentralNoneNone
BitkitBDK (inside LDK Node), bitkit-core, Paykit, VSS client, LottieLDK Node, bitkit-core, Paykit and the VSS client, as XCFrameworks Package.swift:28 Package.swift:29 Package.swift:27 Package.swift:28NoneNone
BlixtReact Native, react-native-turbo-lnd, secp256k1, protobuf, Nitro TorLND, from react-native-turbo-lnd releases package.json:134NoneNone
ZeusReact Native, bitcoinjs-lib, noble and scure cryptography, Cashu CDK, Nostr (NDK)LND, LDK Node and Cashu CDK, pinned by SHA-256 fetch-libraries-versions.json:24,551 lines in zeus_modules: @lightninglabs/lnc-core, @lightninglabs/lnc-rn, bc-bech32, bc-ur, noble_ecc, urFive iOS-relevant scripts rewrite installed npm packages (a sixth targets Android); rn-nodeify shims crypto and stream

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. Winnow’s Lightning engine is tested against Core Lightning, LDK and Bitcoin Core on regtest, but it has not had an independent security review.

The engines do different amounts of work

LND and LDK are general-purpose nodes. They keep the network graph, find their own routes and can forward other people’s payments. lightning-kmp supports splicing and dual-funded channels. Winnow’s engine has no general graph routing, pays only BOLT 12 offers, and does not implement multipath payments, anchor channels or splicing. The feature table shows the rest.

The apps do different amounts of work

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. Each count is the whole app, not only its Lightning features.

Who can move on-chain funds

Phoenix has no separate on-chain wallet. Its Bitcoin address needs ACINQ’s signature alongside yours; without ACINQ, your key alone can spend it after about six months. Sending to a Bitcoin address takes the amount out of your ACINQ channel, which ACINQ co-signs. Winnow, Bitkit, Blixt and Zeus each keep a single-key on-chain wallet that works without their channel partner.

Recovery differs

Winnow keeps channel state in an encrypted journal on the phone. Keep the phone and its files while a channel is open. The recovery phrase restores the on-chain wallet, not current channel state.

How we measured

Exact sources

Each project changes often. These numbers are a dated 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 one of their citations still resolves at the pinned revision.

Other approaches

Some iPhone wallets offer Lightning without a channel on the phone. They make different trade-offs and are not counted above.