Skip to content

fix(cli)(#23): detectWalletKind ignores empty {} wallet.json placeholder - #27

Merged
vrogojin merged 1 commit into
integration/all-fixesfrom
fix/detect-wallet-kind-empty-placeholder
May 23, 2026
Merged

fix(cli)(#23): detectWalletKind ignores empty {} wallet.json placeholder#27
vrogojin merged 1 commit into
integration/all-fixesfrom
fix/detect-wallet-kind-empty-placeholder

Conversation

@vrogojin

Copy link
Copy Markdown
Contributor

Summary

PR #25's detectWalletKind (sphere-cli#23) classified an empty {} wallet.json as legacy — but sphere wallet use <name> writes that empty placeholder as a connect-time side-effect of the FileStorageProvider, BEFORE any wallet data exists. The migrate gate then fired on every fresh wallet, blocking sphere init.

Repro (manual-test-full-recovery.sh §1)

sphere wallet create alice
sphere wallet use alice           # FileStorageProvider.connect() writes {} to .sphere-cli-alice/wallet.json
sphere init --network testnet --nametag alice  # ← exits 75 with "Legacy wallet detected"

Fix

Parse wallet.json in detectWalletKind:

  • {} (empty top-level object) → fresh — the no-content sentinel from a connect-time write
  • any key present ({"mnemonic":"..."}, etc.) → legacy — real wallet state, route through migrate
  • unparseable / non-object shape → legacy — conservative; corrupted or unrecognized data shouldn't be silently clobbered by a Profile boot

Tests (5 new in src/shared/sphere-providers.test.ts)

  1. empty {} placeholder → fresh
  2. {} with whitespace → fresh
  3. single key ({"mnemonic":"..."}) → legacy (unchanged)
  4. unparseable garbage → legacy (conservative)
  5. array [] → legacy (unexpected shape)

All 119 unit tests pass. npm run typecheck clean. No new lint warnings.

Adversarial review

  • What if a real wallet was last-saved during a write but only the connect-time empty placeholder hit disk? The connect()-time write would never overwrite existing data — loadFromFile() reads first and only writes back what it read. So {} only appears if the file is genuinely fresh.
  • Could attacker write {} to bypass migrate? No — {} means there's nothing to migrate. The migrate gate exists to preserve legacy wallet data, and {} carries none.
  • What about a real wallet with all-removed keys (legitimate {})? Theoretically possible via an admin clearing operation, but in practice the FileStorageProvider only writes after set() calls. A legitimately emptied wallet has no token data to migrate, so treating it as fresh is correct.

Refs sphere-cli#23, PR #25.

…older

The `sphere wallet use <name>` flow constructs a FileStorageProvider
whose `connect()` writes an empty `{}` JSON to wallet.json as a side
effect — BEFORE any wallet data exists. PR #25's `detectWalletKind`
classified that placeholder as `legacy` and tripped the migrate gate
on every fresh wallet, blocking `sphere init` for first-time users.

Caught by `manual-test-full-recovery.sh §1`: peer1-alice `sphere init`
exited 75 immediately with "Legacy wallet detected" instead of
proceeding to mint the nametag.

Fix: parse `wallet.json` and classify an empty top-level object as
`fresh`. A wallet.json with any key is still treated as `legacy`
(real wallet data → migrate triage). An unparseable / non-object
file is also conservatively routed through `legacy` so a corrupted
or unrecognized file isn't silently clobbered by a Profile boot.

Tests (5 new in `src/shared/sphere-providers.test.ts`):
  • empty `{}` placeholder → fresh
  • `{}` with whitespace → fresh
  • single key (`{"mnemonic":"..."}`) → legacy (unchanged)
  • unparseable garbage → legacy (conservative)
  • array shape `[]` → legacy (unexpected shape)

All 119 unit tests pass. Typecheck clean. No new lint warnings.

Refs sphere-cli#23, PR #25.
@vrogojin
vrogojin merged commit ead69f1 into integration/all-fixes May 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant