WalletGroup, one seed for many wallets

Cake LabsSenior Mobile Engineer2024 to 20255 merged PRs

One BIP39 seed backing a wallet on every chain that can share it, grouped in the list under a name you chose. Getting the group's identity right took three attempts.

What it was

Until 2024 every wallet in Cake Wallet was its own thing, with its own seed. If you wanted Bitcoin, Ethereum and Solana you wrote down three seeds. WalletGroup let one BIP39 seed back a wallet on every chain that can share it, showed those wallets together in the list under a name you chose, and let you add a new chain to a group by picking the group instead of writing the seed out again. Wallets that cannot share a seed, Monero among them, stayed outside the scheme and the app said so before you committed to a seed type.

I wrote it between February 2024 and March 2025: five merged changes, about 2,400 lines added and 600 removed, across 127 file touches. The first one reached into every chain package’s credentials and wallet service, every proxy, the dependency graph, the router, the wallet list, the desktop dropdown and 25 translation files, and took seven months to land.

The decisions

The group’s first wallet is the seed source. No lead field, nothing extra to persist, nothing to keep in sync. When a sibling is created, the first wallet in the group is loaded and its seed read. The cost is loading a wallet and decrypting a seed on a user action, so the fetch sits behind a loading state, and “first” means list order rather than anything meaningful.

Identity took three goes. The group key was the shared parent address first, which is the obvious choice and wrong for a reason that surfaced twice: an address is per chain and per derivation, so a wallet that could not share the seed could still resolve to the same key. The second change excluded by derivation as well as by type. The third moved the key to a salted hash of the seed, which means “same seed” and nothing else.

Fall back rather than migrate up front. The identity change could have rewritten every wallet’s key at upgrade. Instead the lookup falls back to the old address logic, so an unmigrated install looks exactly as it did, and the rewrite happens the first time a wallet in that group is opened, carrying the group’s name across in the same step. No startup cost and no big-bang migration to get wrong, at the price of two grouping rules living in the code at once.

Compile errors over silent defaults. The function that decides whether a wallet type can share a BIP39 seed lists every non-BIP39 type as an explicit case, on a reviewer’s suggestion, so adding a chain and forgetting it fails the build instead of quietly answering no.

What did not land

Group names lived in preferences and did not survive a backup restore. I proposed moving the name onto the wallet’s own record, with a fallback and a write-back migration. It did not merge. I mention it because the case study structure on this site is context to result, and the result here includes the thing that stayed broken.

All case studies