Kgothatso Ngako 24ca50ca48 feat(identity): list and start identities from both stores, and fix the other entrance
Phase 3 of docs/nsec-sign-in.md, and the submodule bump that brings Phase 2's
NostrKeyManager in (lightning-kmp-app 01489b8 -> 59c11ed, branch
claude/nostr-key-store). Nothing can create an nsec identity yet -- that is
Phase 4 -- but from here one that exists is listed, selected, started and
routed like any wallet.

StoredIdentity is what the startup screen now reads: a sealed type over
Mnemonic(userWallet) and NostrSecret(id, privateKey), both carrying the nostr
public key, because that is the one thing the two kinds share and the one
thing a duplicate check has to compare. StoredIdentity.merge folds seed.dat
and nostr-keys.dat into one map keyed by WalletId; a wallet's pubkey is one
more LocalKeyManager over words that SeedManager has already derived once.

SovereignWalletViewModel.listAvailableWallets becomes listIdentities. It
reads both files and surfaces a failure in either rather than skipping it: a
corrupt nostr-keys.dat would otherwise drop every imported identity from the
list without a word, and the seed file has always been handled this way.
Metadata registration is unchanged -- it keys on WalletId and does not care
what is behind one.

SovereignWalletStartupScreen picks a StoredIdentity, and LoadWallet, the
screen-lock gate, takes the identity rather than the wallet: it only ever read
the id, to look up the lock preferences, and it now sits outside the branch on
kind so a future lock is not something to remember to add twice. The Mnemonic
branch is the old path -- startupNode, setActiveWallet. The NostrSecret branch
builds the Identity directly with business = null and sets it; no
platformStartupLogic, no schedulePlatformLogic, no StartupViewState. Setting
it recomposes into the existing `activeIdentity != null` branch, which is what
reports the startup, so the nsec path does not double-report as the mnemonic
path does. WalletsSelector shows the npub on the second line for both kinds;
it used to show the node id, which a bare key does not have.

The other entrance. NavigationViewModel.loadNostrProfile(startupRoute) read

    getLocalAccounts().firstOrNull()?.profile?.publicKey

which is wrong twice for this plan. It takes the first kind-0 account on the
device whichever identity is active -- harmless with one wallet, wrong half
the time with two, and an imported key is how a device gets its second. And
it keys off the Profile row, which only the notary's *signing* writes, so a
placeholder account planted by signInToProfile (or a profile created moments
ago and not yet signed) read as null and was answered with Landing, while
observeProfile, reading the same rows, answered the sync screen: two writers
to one state in whichever order the coroutines ran, and Landing pops the back
stack. It now selects the account by the active identity's pubkey, matched on
the unsigned event's pubKey, and passes it straight to processLocalAccount
rather than fetching it a second time.

The seed writer, hoisted. platformWriteSeed was an expect with three
byte-identical actuals -- android, jvm, ios -- using nothing but commonMain.
It is one function now, IdentityWriter.writeMnemonic, and the second writer,
IdentityWriter.writeNostrKey, sits next to it rather than being triplicated
in turn. Both refuse a duplicate by nostr public key as well as by id: a
wallet and an imported key can be the same npub under different ids, and the
database is keyed by pubkey. The three platform files lose the copy and the
imports that only it used; the jvm file's header, which described the copy,
now describes what is left.

Tests. NavigationRoutingTest gains the placeholder fixture -- kind 0 at
GENESIS_AT, no Profile, no sync request -- and asserts it routes to
UnqueuedProfileSynchronization and never Landing. The jvm routing test gains
a device with two accounts and asserts the startup entrance reads the active
identity's. StoredIdentityJvmTest pins the merge against NIP-06's own vector:
the words "leader monkey parrot ..." derive 17162c92...cd917, the same key an
nsec import of that secret produces, which is the whole basis of the
duplicate check; that a wallet and its own nostr key as a bare secret land
under different ids (why the writers dedupe by pubkey); and that a key filed
under a pubkey it does not derive is dropped.

Verified with :composeApp:compileDebugKotlinAndroid, :composeApp:jvmTest (870
tests) and :composeApp:m3Audit. The submodule commit is local to the branch
named above and has to be pushed with this one.

Replayed onto Mantra by docs/curated-to-mantra.md: gitlink -> 59c11ed9, the pin this commit compiles against (git had fast-forwarded it to the newer one).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pulled-From: curated/curated@366b017787
2026-09-13 12:00:19 +02:00
2026-09-09 17:04:09 +02:00
2026-03-24 04:47:19 +02:00
2026-03-23 01:41:39 +02:00
2026-03-23 01:41:39 +02:00
2026-03-23 01:41:39 +02:00

This is a Kotlin Multiplatform project targeting Android, iOS, Desktop (JVM).

  • /composeApp is for code that will be shared across your Compose Multiplatform applications. It contains several subfolders:

    • commonMain is for code that’s common for all targets.
    • Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, the iosMain folder would be the right place for such calls. Similarly, if you want to edit the Desktop (JVM) specific part, the jvmMain folder is the appropriate location.
  • /iosApp contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.

Build and Run Android Application

To build and run the development version of the Android app, use the run configuration from the run widget in your IDE’s toolbar or build it directly from the terminal:

  • on macOS/Linux
    ./gradlew :composeApp:assembleDebug
    
  • on Windows
    .\gradlew.bat :composeApp:assembleDebug
    

Build and Run Desktop (JVM) Application

To build and run the development version of the desktop app, use the run configuration from the run widget in your IDE’s toolbar or run it directly from the terminal:

  • on macOS/Linux
    ./gradlew :composeApp:run
    
  • on Windows
    .\gradlew.bat :composeApp:run
    

Build and Run iOS Application

To build and run the development version of the iOS app, use the run configuration from the run widget in your IDE’s toolbar or open the /iosApp directory in Xcode and run it from there.


Learn more about Kotlin Multiplatform…

Description
Mantra as a kotlin multiplatform project
https://mantra.press
Readme 13 MiB
Languages
Kotlin 100%