From a87d4ffd6fbb0779b0e04b5d1037057b679f2817 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 02:03:27 +0000 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../amethyst/model/AccountSettings.kt | 15 ------ .../loggedOff/login/ExternalSignerButton.kt | 17 ++++++- .../nipBCOnchainZaps/OnchainWalletState.kt | 2 + .../preferences/GeohashIdentitySecrets.kt | 48 +++++++++++++++++++ .../model/preferences/AccountSecrets.kt | 27 ----------- 5 files changed, 66 insertions(+), 43 deletions(-) rename commons/src/{jvmAndroid => commonMain}/kotlin/com/vitorpamplona/amethyst/commons/model/nipBCOnchainZaps/OnchainWalletState.kt (99%) create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/GeohashIdentitySecrets.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt index 211eb1de7f..c64fb85ab9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt @@ -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, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/ExternalSignerButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/ExternalSignerButton.kt index d6e6c4fbc5..e7e82641c7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/ExternalSignerButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/ExternalSignerButton.kt @@ -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), + ) diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/nipBCOnchainZaps/OnchainWalletState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nipBCOnchainZaps/OnchainWalletState.kt similarity index 99% rename from commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/nipBCOnchainZaps/OnchainWalletState.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nipBCOnchainZaps/OnchainWalletState.kt index 279510fb2f..2bff0602ec 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/nipBCOnchainZaps/OnchainWalletState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nipBCOnchainZaps/OnchainWalletState.kt @@ -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 diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/GeohashIdentitySecrets.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/GeohashIdentitySecrets.kt new file mode 100644 index 0000000000..22ddda8087 --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/GeohashIdentitySecrets.kt @@ -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, +) diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/AccountSecrets.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/AccountSecrets.kt index 46769ead3d..0f27330d76 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/AccountSecrets.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/AccountSecrets.kt @@ -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)