Files
mantra-kmp/composeApp
Kgothatso Ngako bfba7e186f refactor(identity): put an Identity in front of the wallet, and read the key off it
Phase 1 of docs/nsec-sign-in.md. No behaviour changes; this is the type the
rest of the plan stands on, landed alone so that its diff is boring.

Every consumer of "the active user" in the app was the same expression,

    activeWallet?.business?.walletManager?.keyManager?.value?.nostrPrivateKey()

ten times in eight files, and none of them wanted anything from the node but
those 32 bytes. The new to.curare.compose.identity.Identity carries the nostr
private key, the x-only public key (computed once), the per-id preferences and
an optional PhoenixBusiness, with an IdentityKind saying whether it came from
twelve words or a bare secret. It replaces fr.acinq.phoenix.data.ActiveWallet
in the app rather than wrapping it: ActiveWallet is declared in the library but
nothing in the library reads it, and flattening its fields into Identity is
what lets the recovery screens, which read internalPrefs off the active
wallet, keep working for an identity that will never have a wallet.

SovereignWalletViewModel.activeWalletInUI becomes activeIdentity;
setActiveWallet(walletId, business) builds the Mnemonic identity from the node
it just started and delegates to a new setActiveIdentity(identity), which is
the entry the nsec kind will use. The twenty-five StateFlow<ActiveWallet?>
parameters across eighteen files become StateFlow<Identity?>, a rename.

The ten sites become a read of the identity's key. Two got simpler:
NotaryViewModel dropped the flatMapLatest from the wallet flow into the node's
key-manager flow, which only existed because the key arrived after the node,
and RelaysSocketManager likewise; both keep their per-key cancellation.
NavigationViewModel.observeProfile lost its `business == null -> Startup`
branch -- the one thing that would have made a nodeless identity loop -- and
the "Couldn't get the nostrKey" dead end, since an identity is whole from the
moment it exists. The unconstructed NostrNotaryRepository now takes the
identity flow instead of a WalletManager, so that it compiles against the
same seam.

WalletId stays as the id for both kinds. Every preference in the library keys
on it, and a second id type would mean a second copy of each; for an nsec
identity XonlyPublicKey.toWalletId() gives hash160 of the x-only key, the same
forty-hex shape as hash160 of a node id.

Tests: NavigationRoutingTest keeps passing with the flow retyped, and a new
jvm test pins the two things this refactor is for. A NostrSecret identity with
business = null and a synced account on the device routes to ProfileLoaded
rather than back to startup; and Identity.nostrPublicKey equals the key quartz
derives for the same secret -- the x-only form, sixty-four hex characters,
which is not what the library's own LocalKeyManager.nostrPublicKey() returns.
That test runs under runBlocking with a real-time timeout because the
observer waits 2.1 seconds on Dispatchers.IO, and runTest's virtual clock
would time out first.

The app's WalletManagerExtension.nostrPublicKey() stays: CreateProfileViewModel
still derives a fresh key's pubkey with it. The plan said it could go; it was
wrong by one caller.

Verified with :composeApp:compileDebugKotlinAndroid, :composeApp:jvmTest (863
tests) and :composeApp:m3Audit.

Replayed onto Mantra by docs/curated-to-mantra.md: RelaysSocketManager.kt: this commit's import block taken whole -- Mantra had already moved the nostrPublicKey() import to the library's (39fb64b6), and this commit removes the call it served; NavigationViewModel.kt: this commit's import block taken whole -- Mantra had already moved the nostrPublicKey() import to the library's (39fb64b6), and this commit removes the call it served.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pulled-From: curated/curated@42f3a6971a
2026-09-13 12:00:19 +02:00
..