Adds 16 project skills (plus 3 runnable diagnostic scripts) that encode
the project's architecture contracts, backend capability matrix, storage
and migration protocols, build/run/debug runbooks, failure archaeology,
Lightning domain reference, and research discipline, so contributors and
AI coding sessions can operate at maintainer standard without rederiving
project knowledge.
All facts were verified against master c5fd094fb and adversarially
reviewed (factual/doctrine/usability passes) before inclusion. Also
updates .gitignore to share .claude/skills/ while keeping all other
.claude/ files (e.g. settings.local.json) local.
30 KiB
name, description
| name | description |
|---|---|
| zeus-diagnostics-and-tooling | How to MEASURE Zeus instead of eyeballing it — find and tail logs (embedded LND lnd.log paths on Android/iOS, LDK Node ldk_node.log, adb logcat tags, Metro/Xcode consoles), use in-app diagnostics (Developer Tools view, Keychain Recovery scanner, log viewers), inspect network traffic (REST/WebSocket/Tor paths, TLS caveats), grep the codebase without generated-file noise, and run the shipped scripts (find-debt.sh, capability-matrix.sh, list-settings-defaults.sh). Load this when you need to answer "where are the logs?", "why is the node stuck syncing/connecting?", "which backend supports feature X?" (measured, not guessed), "how do I see the app's HTTP requests?", or when a grep is drowning in uniffi/protobuf bindings. |
Zeus Diagnostics and Tooling
Zeus is a React Native (RN) Bitcoin/Lightning wallet supporting 7 node backends. This skill is the measurement toolbox: where every log lives, how to read it, and runnable scripts that extract ground truth from the code instead of from stale docs.
When to use / When NOT to use
Use this skill when you need to:
- Find or tail node logs (embedded LND, LDK Node) on device, emulator, or in-app.
- Watch native-layer behavior via
adb logcat/ Xcode console. - Inspect what the app sends over the network (REST, WebSocket, Tor).
- Query the live capability matrix, settings defaults, or debt markers.
- Interpret a log: is this node healthy, syncing, or wedged?
Use a sibling skill instead for:
- Symptom → root-cause triage flowcharts: zeus-debugging-playbook (this skill tells you how to observe; that one tells you what the observation means for known failure classes).
- What
yarn verifyruns and how to add tests: zeus-validation-and-qa. - Backend quirks and the add-an-RPC recipe: zeus-backends-and-capabilities (this skill only ships the script that prints the matrix).
- Keychain/storage semantics and migration safety: zeus-storage-and-migrations (the Keychain Recovery tool is described here only as a diagnostic; never change storage behavior without that skill's gated process).
- Building the dev environment / running the app / connecting Polar nodes: zeus-build-and-env and zeus-run-and-operate.
- Formal race/dispatch analysis techniques: zeus-proof-and-analysis-toolkit.
Vocabulary (defined once)
| Term | Meaning here |
|---|---|
| Backend / implementation | One of 7 node-connection strategies selected per wallet: embedded-lnd, ldk-node, lnd (remote REST), lightning-node-connect (LNC), cln-rest, lndhub, nostr-wallet-connect (NWC). Dispatch lives in utils/BackendUtils.ts. |
| Embedded LND | A full LND Lightning node compiled to a mobile library (gomobile) running inside the app process tree. |
| LDK Node | An alternative embedded node built on the Lightning Dev Kit, integrated via uniffi (Rust→Kotlin/Swift FFI bindings). |
| Neutrino | LND's light-client chain backend (BIP-157/158 compact block filters); embedded LND uses it instead of a full bitcoind. |
| Metro | React Native's JS bundler/dev server (yarn start). In dev, all JS console.* output appears in the Metro terminal. |
| logcat | Android's system log stream, read with adb logcat. Native (Java/Kotlin) Log.d/i/e calls land here, filtered by TAG. |
| uniffi bindings | Machine-generated Kotlin/Swift glue code for Rust libraries — huge files you must exclude from greps. |
| Tor | Anonymity network; Zeus routes REST calls through it per-node via react-native-nitro-tor (0.6.0 as of 2026-07-06). |
1) Log access per node type
Summary table
| Node type | Node's own log | In-app viewer | Off-device access |
|---|---|---|---|
| embedded-lnd | lnd.log (paths below), debuglevel=info hardcoded |
Settings → Embedded node → "LND Logs" (views/Settings/EmbeddedNode/LNDLogs.tsx, tail 100 + live follow, Copy button) |
adb shell run-as (debug builds) / simulator container |
| ldk-node | ldk_node.log in the node dir, LogLevel.DEBUG hardcoded |
Settings → Embedded node → "LDK Node Logs" (views/Settings/EmbeddedNode/LDKLogs.tsx) |
same |
| lnd / cln-rest / lndhub (remote) | on the remote server, not the phone | — (use Developer Tools for RPC probes) | server-side (e.g. Polar, below) |
| lightning-node-connect | on the litd server | — | server-side |
| nostr-wallet-connect | remote wallet service | — | relay/service side |
| App itself (JS layer) | console.* |
— | Metro terminal (dev), ReactNativeJS logcat tag (Android), Xcode console (iOS) |
Embedded LND log file paths (verified in native code)
Android (android/app/src/main/java/com/zeus/LndMobileTools.java, tailLog/observeLndLogFile):
- Legacy first wallet (
lndDir == 'lnd'):<filesDir>/logs/bitcoin/<network>/lnd.log - Every other wallet (uuid dir):
<filesDir>/<lndDir>/logs/bitcoin/<network>/lnd.log
where <filesDir> = /data/user/0/app.zeusln.zeus/files and <network> is
mainnet or testnet. Note the asymmetry: the legacy 'lnd' wallet lives at
the files-dir root, not in a lnd/ subfolder.
iOS (ios/LndMobile/LndMobileTools.swift, tailLog): always
<Application Support>/<lndDir>/logs/bitcoin/<network>/lnd.log — the lndDir
subfolder is used even for 'lnd' (unlike Android).
lndDir per wallet: stores/SettingsStore.ts sets this.lndDir = node.lndDir || 'lnd'; new wallets get uuidv4() dirs
(views/Settings/WalletConfiguration.tsx). To map uuid → wallet, use Tools →
"Export/Import Wallet Configurations".
Verbosity: writeLndConfig in utils/LndMobileUtils.ts hardcodes
debuglevel=info in the generated lnd.conf. For a local debugging session you
may bump it to debug there (dev-only; never commit).
Trap — readLndLog is a stub. lndmobile/index.ts readLndLog returns
[''] with a TODO remove comment. Do not build tooling on it; the real
mechanisms are NativeModules.LndMobileTools.tailLog(numberOfLines, lndDir, network) and observeLndLogFile(lndDir, network) which emits lndlog
device events (used by both the LNDLogs view and SyncStore rescan tracking).
Pulling the file off a device (debug builds only — run-as requires
android:debuggable):
# legacy 'lnd' wallet on mainnet:
adb shell "run-as app.zeusln.zeus tail -n 200 files/logs/bitcoin/mainnet/lnd.log"
# list wallet dirs to find uuid dirs:
adb shell "run-as app.zeusln.zeus ls files/"
iOS simulator:
APP=$(xcrun simctl get_app_container booted com.zeusln.zeus data)
find "$APP/Library/Application Support" -name lnd.log
(com.zeusln.zeus = iOS bundle id; Android applicationId is
app.zeusln.zeus — they differ.)
LDK Node logs
- Path:
<DocumentDirectoryPath>/ldk-node/<ldkNodeDir-uuid>/ldk_node.log. The storage dir comes fromutils/LdkNodeUtils.tsgetLdkNodeStoragePath(nodeDir);ldkNodeDiris auuidv4()per wallet. On AndroidDocumentDirectoryPath=/data/user/0/app.zeusln.zeus/files; on iOS it is the app sandboxDocuments/folder. - Log level:
LogLevel.DEBUG/.debug, hardcoded atbuilder.setFilesystemLogger(...)inandroid/app/src/main/java/app/zeusln/zeus/LdkNodeModule.ktandios/LdkNodeMobile/LdkNodeModule.swift. Not user-configurable. - In-app:
views/Settings/EmbeddedNode/LDKLogs.tsxcallsNativeModules.LdkNodeModule.tailLdkNodeLog(100)and subscribes toldklogevents fromobserveLdkNodeLogFile()(backed byLogFileObserver.kt/LogFileObserver.swift). - The native module ALSO logs its own lifecycle to logcat under tag
LdkNodeModule(e.g.[timing] FFI buildWithDualStore completed in Nms) — often more useful than ldk_node.log for start/stop races.
adb logcat — verified filter tags
All tags below appear in the checked-in native sources
(git grep -n 'TAG = "' android/app/src/main/java to re-verify — with one
exception: LdkNodeModule has no TAG constant and uses inline
Log.d("LdkNodeModule", ...) literals; re-verify that one with
git grep -n '"LdkNodeModule"' android/app/src/main/java/app/zeusln/zeus/LdkNodeModule.kt):
| Tag | Source | What you see |
|---|---|---|
ReactNativeJS |
RN runtime (standard) | all JS console.* on device |
LndMobileService |
com/zeus/LndMobileService.java |
embedded LND process/IPC lifecycle |
LndMobile |
com/zeus/LndMobile.java |
RN↔LND bridge module |
LndMobileTools |
com/zeus/LndMobileTools.java |
log observation, chain tools |
LndScheduledSyncWorker |
com/zeus/LndMobileScheduledSyncWorker.java |
background sync worker |
LndMobileScheduledSync |
com/zeus/LndMobileScheduledSync.java |
sync scheduling |
LdkNodeModule |
app/zeusln/zeus/LdkNodeModule.kt |
LDK Node build/start/stop, VSS, timing |
LdkNodeService |
app/zeusln/zeus/LdkNodeService.kt |
LDK foreground service |
NostrConnectService / NostrConnectModule |
com/zeus/NostrConnect*.java |
NWC background service |
CashuDevKitModule |
com/zeus/cashudevkit/CashuDevKitModule.kt |
Cashu FFI |
MainActivity |
com/zeus/MainActivity.kt |
activity lifecycle, intents |
MobileTools |
com/zeus/MobileTools.java |
misc native tools |
StealthMode |
app/zeusln/zeus/StealthMode.java |
icon-disguise switching |
Copy-paste starters:
# Embedded LND session:
adb logcat -s LndMobileService:* LndMobile:* LndMobileTools:* ReactNativeJS:*
# LDK Node session (start/stop/delete races):
adb logcat -s LdkNodeModule:* LdkNodeService:* ReactNativeJS:*
# NWC background service:
adb logcat -s NostrConnectService:* NostrConnectModule:* ReactNativeJS:*
iOS console & Metro
- Xcode: run the
zeusscheme (ios/zeus.xcworkspace) — the debug console shows both nativeNSLog/os_logand JS console output. - Console.app: filter by process
zeuson a connected device (release-ish builds where Xcode isn't attached). Standard macOS tooling. - Metro:
yarn start(script =react-native start, verified in package.json). All dev-mode JS logs appear there. Pressjin the Metro terminal to open React Native DevTools (network + console + profiler) — standard RN ≥0.76 tooling, not Zeus-specific. - JS-side embedded-LND logging goes through
lndmobile/log.ts: a tiny wrapper that prefixesconsole.*output with a tag, e.g. lines startingutils/LndMobileUtils.ts: ...(seeconst log = Log('utils/LndMobileUtils.ts')). Grep Metro/logcat output for that prefix during embedded-LND triage.
2) In-app diagnostics
Developer Tools view
- File:
views/Tools/DeveloperTools.tsx; routeDeveloperTools; reached via Menu → Tools ("Developers" row). - Gating: shown when
BackendUtils.supportsDevTools()is true, which isisLNDBased() || call('supportsDevTools')(utils/BackendUtils.ts). Measured availability (2026-07-06):- Available:
lnd,embedded-lnd,lightning-node-connect(isLNDBased = true), pluscln-restandlndhub(declaresupportsDevTools = () => true). - Not available:
ldk-node,nostr-wallet-connect. - There is NO developer-mode setting; gating is purely capability-based.
- Available:
- Contents: 18 raw RPC probes in 4 accordions (General, Lightning, On-chain,
Channels):
getMyNodeInfo,getNetworkInfo,getLightningBalance,getInvoices,getPayments,getBlockchainBalance,getTransactions,getUTXOs,getNewAddress,listAccounts,listAddresses,getChannels,getPendingChannels,getClosedChannels,getChannelInfo,getFees,getForwardingHistory,abandonChannel. Each command carries acompatibleImplementationsallowlist, so what you see varies per backend. Responses render as copyable JSON — this is the fastest way to see exactly what a backend returns without adding temporary logging. - Danger rail:
abandonChannel(drops a channel without an on-chain close; can burn funds) requires an "I know what I am doing" switch AND a native confirm dialog. Do not script around it.
Keychain Recovery tool
- File:
views/Tools/NodeConfigExportImport.tsx("Export/Import Wallet Configurations" in Tools), backed byutils/KeychainRecoveryUtils.ts. - The "Recover Lost Configurations" flow scans four sources for orphaned
data:
current,unprefixed-local,unprefixed-cloud(old iCloud-synchronizable keychain entries), andencrypted-storage(legacyzeus-settingsblob). It exists because pre-migration keychain copies are deliberately never deleted — see zeus-storage-and-migrations for the whole saga and the rules before touching any of this. - As a diagnostic, a scan tells you which storage generation a user's data is in — run it (read-only until the user restores) before hypothesizing about "lost wallets".
- Same view's Export flow is also the sanctioned way to inspect a device's
wallet configuration set (per-wallet
lndDir/ldkNodeDiruuids included).
Activity / store inspection
- Tools → "Activity export" (
views/Tools/ActivityExport.tsx) dumps invoices/payments/transactions to CSV — use it to diff what the app thinks happened against node truth (Developer ToolsgetPaymentsetc.). - Sync observability is a store, not a screen:
stores/SyncStore.tspollsgetMyNodeInfoevery 2s while syncing, comparesblock_heightagainst a mempool.space tip (refetched every ~30s), and flipsisSyncingoff onsynced_to_chain. Rescans are tracked by parsing lnd.log lines matching/Rescanned through block.*\(height (\d+)\)/via thelndlogevent stream. - MobX stores are module singletons (
stores/Stores.ts); in a dev build you can inspect them from the React Native DevTools console via imports in scope of any breakpoint. No Reactotron/Flipper integration is configured (verified: no such deps in package.json).
3) Network debugging
How requests flow (verified in backends/LND.ts + utils/TorUtils.ts)
view → BackendUtils.<method> → call(funcName) → active backend class
REST (clearnet): ReactNativeBlobUtil.config({ trusty: !certVerification }).fetch(...)
REST (Tor): doTorRequest(...) [react-native-nitro-tor]
Streaming: wsReq(...) → new WebSocket(wss://host:port/route?method=...)
Embedded backends: no network at this layer (gomobile IPC / uniffi FFI)
Facts you need before reaching for a proxy:
- TLS verification is OFF by default for remote REST.
restReq'scertVerificationparameter defaults tofalse, and blob-util is called withtrusty: !certVerification— i.e. invalid/self-signed certs are accepted unless the user enabled "Certificate Verification" on the node config. Consequence: a MITM proxy (mitmproxy, Charles, Proxyman) can intercept remote-node REST traffic without installing its CA cert, as long as certVerification is off and you route the emulator/device through the proxy (Android emulator:-http-proxy; iOS simulator honors macOS system proxy). There is no cert pinning anywhere in the REST path. - WebSocket streams bypass Tor entirely.
wsReqand the othernew WebSocket(...)call sites inbackends/LND.tsbuildwss://URLs straight from host/port — even when the node has Tor enabled. When debugging "subscription works but payments leak clearnet" (or vice versa), remember these are two different transports. - Tor path cannot be trivially proxied. Tor requests go through the
embedded nitro-tor daemon (
utils/TorUtils.tsdoTorRequest), not the system proxy. TLS bypass on this path is scoped to HTTPS.onionURLs only (isOnionHttpsUrl); clearnet-over-Tor keeps strict validation anddoTorRequesttreats HTTP ≥ 300 as errors. To observe Tor traffic, instrument at the far end (your node) instead. - The request-dedup cache can replay failures.
backends/LND.tskeeps a module-levelcallsMap keyed by url+body. On the Tor branch the entry is only deleted on success, so a rejected promise is returned again for identical retries untilclearCachedCalls()runs — which happens on reconnect viaSettingsStore.setConnectingStatus(true). If "the same error keeps coming back instantly with no network activity", suspect this cache, not the network. (backends/CLNRest.tshas the same structure.) - Cheap first probe: Developer Tools →
getMyNodeInfoexercises the whole REST stack with one tap; React Native DevTools' Network panel shows fetch-based traffic in dev (blob-util requests may not appear there — fall back to the proxy or server-side logs).
Server-side (Polar) inspection
Polar (https://github.com/jamaljsr/polar) is the recommended local Lightning network for dev (CONTRIBUTING.md). For connecting Zeus to it, see zeus-run-and-operate. Diagnostics side (external tooling — verify against your Polar version):
- Each Polar node is a Docker container; view logs in the Polar UI (node →
Logs) or
docker logs -f <container>(containers are named per network/node, e.g.polar-n1-alice;docker pslists them). - lnd-side truth beats app-side guesses: watch the lnd container log while reproducing a payment to see whether the failure is client (Zeus) or node.
- LNC (
lightning-node-connect) requires a litd (Lightning Terminal) instance to pair against; its session/mailbox logs live on that server. (litd-in-Polar support depends on Polar version — unverified here.)
4) Grep hygiene
Generated and vendored files will bury your signal: the two uniffi bindings
alone are ~31k lines, proto/lightning.js is ~133k lines, and zeus_modules/
is vendored third-party code with its own node_modules. Canonical grep:
git grep -nIw -E 'YOUR_PATTERN' -- . \
':(exclude)zeus_modules' \
':(exclude)proto/lightning.js' \
':(exclude)proto/lightning.d.ts' \
':(exclude)ios/LdkNodeMobile/LDKNode.swift' \
':(exclude)android/app/src/main/java/org/lightningdevkit/ldknode/ldk_node.kt' \
':(exclude)android/app/src/main/java/uniffi/zeus_cashu_restore/zeus_cashu_restore.kt' \
':(exclude)ios/CashuDevKit/CashuDevKit.swift' \
':(exclude)ios/CashuDevKit/zeus_cashu_restore.swift'
Rules of thumb:
- Prefer
git grepovergrep -r:node_modules/,ios/Pods/, andandroid/app/build/are gitignored, so git grep skips them for free (verified withgit check-ignore). Plaingrep -rand somergsetups will wade straight into them. - Use
-w, not\b: this git's ERE engine (macOS) does not support\band fails silently with zero matches — a nasty way to conclude "no hits".-wgives whole-word matching portably. - Known false positive:
utils/AddressUtils.test.tscontainsXXXinside a base64 PSBT fixture (line ~923).-wfilters it; substring searches won't. fetch-libraries.shdefines a shell function literally namedjq()(a python3 wrapper) — a misleading hit when grepping for jq usage.- Config blind spots: tsconfig excludes
node_modules,zeus_modules,android,ios; ESLint additionally ignoresproto/; Prettier ignores ONLYzeus_modules/(per.prettierignore— so regeneratedproto/*.d.tscan still fail the Prettier CI check). Code in excluded dirs is neither linted nor type-checked, so absence of CI complaints proves nothing about it.
5) Scripts
All live in .claude/skills/zeus-diagnostics-and-tooling/scripts/, are
executable, safe (read-only), and were run against master c5fd094fb.
Run them from anywhere inside the repo; they cd to the git root themselves.
find-debt.sh — debt-marker census
.claude/skills/zeus-diagnostics-and-tooling/scripts/find-debt.sh # full listing
.claude/skills/zeus-diagnostics-and-tooling/scripts/find-debt.sh --summary # per-file counts
.claude/skills/zeus-diagnostics-and-tooling/scripts/find-debt.sh TODO # single marker
Sample output (2026-07-06, --summary, top of list):
5 android/app/src/main/java/com/zeus/LndMobile.java
4 lndmobile/wallet.ts
3 proto/lightning.proto
3 lndmobile/index.ts
3 android/app/src/main/java/com/zeus/LndMobileService.java
2 views/Wallet/Wallet.tsx
2 views/Activity/Activity.tsx
...
---
total markers: 63
Sample detail lines:
android/app/src/main/java/com/zeus/LndMobile.java:71:// TODO break this class up
android/app/src/main/java/com/zeus/LndMobile.java:350: // TODO unbind LndMobileService?
Interpretation: the debt concentrates in the Blixt-derived embedded-LND
native layer (com/zeus/*.java, lndmobile/*.ts) — expected; see
zeus-failure-archaeology for which of those TODOs have bitten before.
Standalone HACK/XXX markers: zero in hand-written code (as of the date
above).
capability-matrix.sh — live supports* matrix
.claude/skills/zeus-diagnostics-and-tooling/scripts/capability-matrix.sh # all flags
.claude/skills/zeus-diagnostics-and-tooling/scripts/capability-matrix.sh supportsOffers # filter (regex)
Cell legend: T/F literal, expr computed (usually LND version gates like
this.supports('v0.13.0')), ^T/^F/^expr inherited from LND.ts
(EmbeddedLND and LndHub extend LND — inherited T is the classic silent-bug
pattern), - not declared anywhere → BackendUtils.call() returns literal
false. Composite flags computed in utils/BackendUtils.ts
(supportsDevTools, supportsLightningAddress,
supportsForwardingHistoryChannelFilter) are listed in the script header,
not the matrix.
Sample output (2026-07-06, excerpt; 56 flags total):
FLAG LND eLND LNC LDK CLN Hub NWC
isLNDBased T T T F F F F
supportsCashuWallet F T F T F F F
supportsChannelFundMax T T T T T ^T F
supportsDevTools - - - - T T -
supportsOffers F F F T T F F
supportsTaproot expr expr expr T T F F
supportsWatchtowerClient T T T F - F -
Hand-verified against source (3 flags): supportsCashuWallet T only in
backends/EmbeddedLND.ts and backends/LdkNode.ts; supportsOffers T only
in backends/LdkNode.ts and backends/CLNRest.ts; supportsChannelFundMax
absent from backends/LndHub.ts hence inherited ^T from backends/LND.ts
(the known LndHub override gap).
list-settings-defaults.sh — defaults drift review
.claude/skills/zeus-diagnostics-and-tooling/scripts/list-settings-defaults.sh # whole block
.claude/skills/zeus-diagnostics-and-tooling/scripts/list-settings-defaults.sh 'Fee' # filtered
Prints the inline @observable settings: Settings = { ... } defaults object
of stores/SettingsStore.ts with real line numbers (located by brace
counting, not hardcoded lines — 147 lines spanning 1467–1613 as of
2026-07-06). Sample:
1467 @observable settings: Settings = {
1468 privacy: {
1469 defaultBlockExplorer: 'mempool.space',
...
1612 selectNodeOnStartup: false
1613 };
Why it matters: these literals are what FRESH installs get; existing users keep old values unless a migration runs. Diff this output between two commits to catch silent default drift. The catalog of every axis lives in zeus-config-and-flags; the migration recipe in zeus-storage-and-migrations.
6) Interpretation guides — healthy vs unhealthy
Embedded LND startup (the state machine Zeus itself watches)
utils/LndMobileUtils.ts subscribes to LND's state stream and drives on
lnrpc.WalletState: NON_EXISTING → LOCKED → UNLOCKED → RPC_ACTIVE → SERVER_ACTIVE. Readiness is declared at RPC_ACTIVE/SERVER_ACTIVE (after an
RPC-ready check); the hard ceiling is LND_READY_TIMEOUT_MS = 60000 (60s),
after which the slow-start path triggers.
Healthy session, in order:
- logcat
LndMobileService: process/service start, no restarts. - State log line
Current LND state: ...advancing through the machine (JS side, tag prefixutils/LndMobileUtils.ts:in Metro/ReactNativeJS). lnd.log: wallet opened/unlocked, neutrino peers connected, sync progress advancing. (Exact lnd.log wording is upstream-LND behavior and varies by version — treat specific phrasings as unverified here; the one pattern Zeus's own code depends on isRescanned through block ... (height N)instores/SyncStore.ts.)- App: Wallet screen leaves "connecting", balances populate.
Unhealthy signatures (all from utils/LndMobileErrors.ts, the canonical
error-code → raw-pattern map — read it before inventing new matching):
| Symptom / matched pattern | Code | Meaning |
|---|---|---|
wallet locked |
WALLET_LOCKED |
normal right after start; app should auto-unlock — persistent means unlock failed |
already started, already running |
LND_ALREADY_RUNNING |
informational; a stop didn't complete before a start (lifecycle race territory — see zeus-node-lifecycle-campaign) |
starting up, not yet ready |
RPC_NOT_READY |
transient; only a problem if still there near the 60s ceiling |
| stuck LOCKED→nothing, then timeout | RPC_READY_TIMEOUT / LND_READY_TIMEOUT |
RPC never came up; check lnd.log for the real cause |
no such file or directory etc. |
LND_FOLDER_MISSING |
wallet dir gone (post-delete or dir-mapping bug) |
unable to read TLS cert, connection refused during stop |
STOP_LND_EXPECTED |
expected shutdown noise — NOT an error |
Neutrino sync
stores/SyncStore.ts semantics: numBlocksUntilSynced = bestBlockHeight − currentBlockHeight where best comes from mempool.space and current from
getMyNodeInfo().block_height (polled every 2s).
- Healthy:
currentBlockHeightstrictly increasing between polls; gap shrinking; ends withsynced_to_chain: true. - Unhealthy: current height frozen across many polls with peers configured →
neutrino peer trouble (check
[Neutrino]section peers in generated lnd.conf,neutrino.connectvsaddpeeris controlled by thedontAllowOtherPeerssetting); best height 0/erroring → the mempool.space fetch failed and the store falls back to current height, which can mask remaining sync distance. - Recovery (seed restore) is separate:
getRecoveryInfopolled at 2s; healthy =progressmonotonically rising torecovery_finished.
LDK Node startup
Healthy (logcat, tag LdkNodeModule): applyBuilderSettings: Enabling filesystem logger → Building node with dual store (VSS + local): <url> →
[timing] FFI buildWithDualStore completed in <N>ms → started/running.
Unhealthy: [timing] FFI buildWithDualStore failed in <N>ms: <msg>;
JS-layer sentinels Node not initialized (native ref still rebuilding —
transient by design, retried) and LDK Node not running yet (both defined
in utils/LdkNodeUtils.ts). Repeated build attempts or a stop that never
returns → lifecycle race; gather the logcat trace FIRST, then switch to
zeus-node-lifecycle-campaign.
Remote backends (lnd REST / cln-rest / lndhub)
- Healthy: Developer Tools
getMyNodeInforeturns JSON quickly; Wallet populates. - Error-shaped success trap: LND REST
payLightningInvoicetimeouts resolve with{ payment_error: localeString('views.SendingLightning.paymentTimedOut') }(a locale-dependent "Payment timed out…" message) — a "successful" response that is actually a failure. Don't classify by promise resolution, and don't match the string literally. - Instantly repeating identical error with no traffic → stale dedup-cache rejection (section 3, item 4).
- Method returns literal
false(and.thencrashes with "false is not a function"-style errors) → the active backend simply lacks that method; check the capability matrix, not the network.
Provenance and maintenance
Facts verified 2026-07-06 against master c5fd094fb (v13.1.3-alpha) by
reading the cited files and running every command/script in this skill
(scripts run to completion with the sample outputs shown; adb/simulator/
proxy commands are standard platform tooling verified by source read of the
paths they target, not executed against a device).
One-line re-verification commands for volatile facts:
| Fact | Re-verify with |
|---|---|
readLndLog still a stub |
git grep -n -A3 'readLndLog = async' lndmobile/index.ts |
| Android lnd.log paths | git grep -n 'logs/bitcoin' android/app/src/main/java/com/zeus/LndMobileTools.java |
| iOS lnd.log path | git grep -n 'lnd.log' ios/LndMobile/LndMobileTools.swift |
lnd debuglevel=info |
git grep -n 'debuglevel' utils/LndMobileUtils.ts |
| LDK log path + DEBUG level | git grep -n 'setFilesystemLogger' android/app/src/main/java/app/zeusln/zeus/LdkNodeModule.kt ios/LdkNodeMobile/LdkNodeModule.swift |
| logcat TAG list | git grep -n 'TAG = "' android/app/src/main/java (exception: LdkNodeModule uses inline literals — git grep -n '"LdkNodeModule"' android/app/src/main/java/app/zeusln/zeus/LdkNodeModule.kt) |
| DevTools gating rule | git grep -n -A2 'supportsDevTools' utils/BackendUtils.ts backends/ |
| DevTools command list | git grep -n "name: '" views/Tools/DeveloperTools.tsx |
trusty: TLS default |
git grep -n 'trusty' backends/LND.ts |
| WS-bypasses-Tor | git grep -n 'new WebSocket' backends/LND.ts |
| Tor onion-only bypass | git grep -n 'isOnionHttpsUrl' utils/TorUtils.ts backends/LND.ts |
| nitro-tor version | git grep -n 'react-native-nitro-tor' package.json |
| dedup-cache clear site | git grep -n 'clearCachedCalls' stores/SettingsStore.ts backends/ |
| WalletState machine + 60s timeout | git grep -n 'LND_READY_TIMEOUT_MS|WalletState\.' utils/LndMobileUtils.ts |
| Rescan log regex | git grep -n 'Rescanned through block' stores/SyncStore.ts |
| defaults block location | .claude/skills/zeus-diagnostics-and-tooling/scripts/list-settings-defaults.sh | head -2 |
| capability matrix | rerun capability-matrix.sh and spot-check any 3 flags against backends/*.ts |
| app/bundle ids | git grep -n 'applicationId' android/app/build.gradle; git grep -n 'PRODUCT_BUNDLE_IDENTIFIER = com' ios/zeus.xcodeproj/project.pbxproj |
If any script errors with "pattern changed", the source file moved under it — fix the script in the same change and re-paste fresh sample output here.