When restoring an ldk-node wallet from seed, the node starts syncing from the current chain tip. Any on-chain deposits made before the restore are never seen by synchronize_listeners, so the wallet balance shows zero even though funds exist on-chain.
This affects all seed restores, but pruned nodes are hit hardest: historical blocks are gone, so there is no workaround via rescan.
There is an existing TODO acknowledging this:
// TODO: Use a proper wallet birthday once BDK supports it.
(src/builder.rs:1327)
Impact
- On-chain balance shows 0 after seed restore
- Cannot open Lightning channels (no visible funds)
- Funds are safe on-chain but inaccessible through ldk-node
- Mobile wallets (where reinstalls and data loss are common) are the primary use case
What we did
We hit this building Bitcoin Pocket Node, an Android app running bitcoind + ldk-node on GrapheneOS with pruned storage.
Our workaround overrides chain_tip_opt in the Builder when a birthday height is provided. Caller flow:
- User restores seed words
- App uses bitcoind
scantxoutset to find UTXOs and their block heights (works on pruned nodes)
- Sets birthday to
min(heights) - 10 as safety margin
- ldk-node syncs from there, balance appears with zero transaction fees
Working fork with the changes (~100 lines): FreeOnlineUser/ldk-node@upstream/wallet-birthday
Verified end-to-end on real hardware: seed restore, UTXO scan, birthday set, 110,628 sats recovered in seconds.
Upstream context
BDK is working on related pieces:
- bdk#2126: Adding
start_height to FilterIter::new
- bdk#2050: Pruned node support via
scantxoutset
Once BDK ships native birthday support, ldk-node can use that directly (which is what the TODO anticipates). Our fork is a pragmatic solution in the meantime.
We are also the folks working on the watchtower improvements discussed in #813.