refactor: clear three small jvmAndroid dependencies of Account's group

- DefaultSignerPermissions moves from model/AccountSettings.kt to
  ExternalSignerButton, its only user. It is the NIP-55 permission request
  sent to an Android signer app at login, built from quartz androidMain types
  (CommandType, Permission); AccountSettings never read it.
- GeohashIdentitySecrets, a plain data class, moves out of the jvmAndroid
  AccountSecrets.kt (which is jvmAndroid for its DataStore keys and legacy
  readers) into its own commonMain file, same package.
- OnchainWalletState moves from commons jvmAndroid to commonMain. It only
  needed kotlinx.coroutines.IO and kotlin.concurrent.Volatile imports; every
  quartz type it uses is already commonMain.

No behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P
This commit is contained in:
Claude
2026-09-28 02:03:27 +00:00
parent 9fce45d91f
commit a87d4ffd6f
5 changed files with 66 additions and 43 deletions
@@ -62,9 +62,7 @@ import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
import com.vitorpamplona.quartz.nip17Dm.settings.DmRelayListEvent
import com.vitorpamplona.quartz.nip28PublicChat.list.PublicChatListEvent
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
import com.vitorpamplona.quartz.nip37Drafts.privateOutbox.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.favoriteAlgoFeedsList.FavoriteAlgoFeedsListEvent
@@ -76,8 +74,6 @@ import com.vitorpamplona.quartz.nip51Lists.relayLists.FavoriteRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.simpleGroupList.SimpleGroupListEvent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.Permission
import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent
import com.vitorpamplona.quartz.nip60Cashu.wallet.CashuWalletEvent
import com.vitorpamplona.quartz.nip61Nutzaps.info.NutzapInfoEvent
@@ -97,17 +93,6 @@ import kotlinx.serialization.Serializable
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
val DefaultSignerPermissions =
listOf(
Permission(CommandType.SIGN_EVENT, RelayAuthEvent.KIND),
Permission(CommandType.SIGN_EVENT, DraftWrapEvent.KIND),
Permission(CommandType.NIP04_ENCRYPT),
Permission(CommandType.NIP04_DECRYPT),
Permission(CommandType.NIP44_DECRYPT),
Permission(CommandType.NIP44_DECRYPT),
Permission(CommandType.DECRYPT_ZAP_EVENT),
)
@Stable
class AccountSettings(
val keyPair: KeyPair,
@@ -64,9 +64,12 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.ui.theme.Size0dp
import com.vitorpamplona.amethyst.commons.ui.theme.Size20dp
import com.vitorpamplona.amethyst.commons.ui.theme.Size40dp
import com.vitorpamplona.amethyst.model.DefaultSignerPermissions
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.PubKeyResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.Permission
import com.vitorpamplona.quartz.nip55AndroidSigner.client.ExternalSignerLogin
import com.vitorpamplona.quartz.nip55AndroidSigner.client.getExternalSignersInstalled
import com.vitorpamplona.quartz.utils.Log
@@ -196,3 +199,15 @@ fun ExternalSignerButton(loginViewModel: LoginViewModel) {
)
}
}
/** What Amethyst asks a NIP-55 signer app to pre-approve when the user logs in with it. */
val DefaultSignerPermissions =
listOf(
Permission(CommandType.SIGN_EVENT, RelayAuthEvent.KIND),
Permission(CommandType.SIGN_EVENT, DraftWrapEvent.KIND),
Permission(CommandType.NIP04_ENCRYPT),
Permission(CommandType.NIP04_DECRYPT),
Permission(CommandType.NIP44_DECRYPT),
Permission(CommandType.NIP44_DECRYPT),
Permission(CommandType.DECRYPT_ZAP_EVENT),
)
@@ -31,12 +31,14 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.concurrent.Volatile
/**
* A snapshot of the account's NIP-BC on-chain wallet: what is sitting on the
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.model.preferences
/**
* The account's location-chat identity: the seed its per-geohash throwaway keys
* come from, and the handle it posts under.
*
* # Why this is not two more fields on [AccountSecrets]
*
* Every account save mirrors a whole [AccountSecrets], built field by field from
* `AccountSettings` — which does not hold these, because they are owned by
* `GeohashChatIdentityState` rather than by the settings object. Folding them in
* would make each save write null over them, and the group save uses
* `putOrRemove`, so null *deletes*. The seed would vanish on the next unrelated
* save and every geohash identity the user has would silently change. A separate
* group with its own save path cannot be wiped by a save that does not know
* about it.
*
* # Why encrypted
*
* The whole point of the seed is that the identities derived from it are
* unlinkable to the npub. Anyone who can read it can link every cell the user
* has ever posted in, to each other and to the device, which is exactly what the
* feature exists to prevent. It was in an encrypted file before; it stays in one.
*/
data class GeohashIdentitySecrets(
val deviceSeed: String? = null,
val nickname: String? = null,
)
@@ -152,33 +152,6 @@ fun readLegacyAccountSecrets(source: LegacyPreferenceSource) =
legacyZapPaymentRequestServer = source.getString(LegacyAccountSecretNames.ZAP_PAYMENT_REQUEST_SERVER),
)
/**
* The account's location-chat identity: the seed its per-geohash throwaway keys
* come from, and the handle it posts under.
*
* # Why this is not two more fields on [AccountSecrets]
*
* Every account save mirrors a whole [AccountSecrets], built field by field from
* `AccountSettings` — which does not hold these, because they are owned by
* `GeohashChatIdentityState` rather than by the settings object. Folding them in
* would make each save write null over them, and the group save uses
* `putOrRemove`, so null *deletes*. The seed would vanish on the next unrelated
* save and every geohash identity the user has would silently change. A separate
* group with its own save path cannot be wiped by a save that does not know
* about it.
*
* # Why encrypted
*
* The whole point of the seed is that the identities derived from it are
* unlinkable to the npub. Anyone who can read it can link every cell the user
* has ever posted in, to each other and to the device, which is exactly what the
* feature exists to prevent. It was in an encrypted file before; it stays in one.
*/
data class GeohashIdentitySecrets(
val deviceSeed: String? = null,
val nickname: String? = null,
)
/** Keys for [GeohashIdentitySecrets] inside an [EncryptedDataStore]. */
internal object GeohashIdentityKeys {
val deviceSeed = stringPreferencesKey(LegacyAccountSecretNames.GEOHASH_DEVICE_SEED)