feat(cli,commons): amy login + amy create, share defaults with Amethyst
Move the "defaults a new Amethyst account gets seeded with" into commons
so the UI and amy agree byte-for-byte on what a fresh account looks like.
commons additions:
- commons/defaults/Constants.kt — relay URL constants (moved from amethyst/)
- commons/defaults/AmethystDefaults.kt — DefaultChannels, DefaultNIP65RelaySet,
DefaultNIP65List, DefaultGlobalRelays,
DefaultDMRelayList, DefaultSearchRelayList,
DefaultIndexerRelayList (moved from
amethyst/model/AccountSettings.kt)
- commons/account/AccountBootstrapEvents.kt — data class + bootstrapAccountEvents(signer, name)
that builds the nine events a new Amethyst account publishes: kind:0, 3,
10002, 10050, 10051, 10099, 50, 51, + channel list. One source of truth.
Retrofits:
- AccountSessionManager.createNewAccount now delegates event construction to
the commons helper; it still packages them into AccountSettings for the
Android storage path.
- DefaultSignerPermissions stays in AccountSettings.kt — it uses Android
NIP-55 Permission/CommandType types.
- 19 import updates across amethyst/ to point at the new commons paths.
New CLI verbs:
- amy create [--name NAME]: mint a keypair, write identity.json, seed
relays.json with the Amethyst defaults, publish all nine bootstrap events
to DefaultNIP65RelaySet via NostrClient.publishAndConfirmDetailed.
- amy login KEY [--password X]: accept any identifier form Amethyst's login
screen accepts — nsec1, ncryptsec (+ --password, NIP-49), BIP-39 mnemonic
(NIP-06), npub1, nprofile1, 64-hex pubkey (default) or 64-hex privkey
(with --private), NIP-05 (name@domain.tld, HTTP lookup). Read-only keys
land with privKeyHex=null; Identity now carries that cleanly.
https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
This commit is contained in:
+1
-1
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.service.okhttp.DefaultContentTypeInterceptor
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
|
||||
@@ -37,7 +37,6 @@ import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.list.tags.ChannelTag
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip37Drafts.privateOutbox.PrivateOutboxRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
@@ -55,8 +54,6 @@ import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
|
||||
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.Permission
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayInfo
|
||||
import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayType
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListEvent
|
||||
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
|
||||
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListEvent
|
||||
@@ -68,31 +65,6 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
val DefaultChannels =
|
||||
listOf(
|
||||
// Anigma's Nostr
|
||||
ChannelTag("25e5c82273a271cb1a840d0060391a0bf4965cafeb029d5ab55350b418953fbb", Constants.nos),
|
||||
// Amethyst's Group
|
||||
ChannelTag("42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5", Constants.nos),
|
||||
)
|
||||
|
||||
val DefaultNIP65RelaySet = setOf(Constants.mom, Constants.nos, Constants.bitcoiner)
|
||||
|
||||
val DefaultNIP65List =
|
||||
listOf(
|
||||
AdvertisedRelayInfo(Constants.mom, AdvertisedRelayType.BOTH),
|
||||
AdvertisedRelayInfo(Constants.nos, AdvertisedRelayType.BOTH),
|
||||
AdvertisedRelayInfo(Constants.bitcoiner, AdvertisedRelayType.BOTH),
|
||||
)
|
||||
|
||||
val DefaultGlobalRelays = listOf(Constants.wine, Constants.news)
|
||||
|
||||
val DefaultDMRelayList = listOf(Constants.auth, Constants.oxchat, Constants.nos)
|
||||
|
||||
val DefaultSearchRelayList = setOf(Constants.wine, Constants.where, Constants.nostoday, Constants.antiprimal, Constants.ditto)
|
||||
|
||||
val DefaultIndexerRelayList = setOf(Constants.purplepages, Constants.coracle, Constants.userkinds, Constants.yabu, Constants.nostr1)
|
||||
|
||||
val DefaultSignerPermissions =
|
||||
listOf(
|
||||
Permission(CommandType.SIGN_EVENT, RelayAuthEvent.KIND),
|
||||
|
||||
+1
-1
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.nip51Lists.indexerRelays
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.NoteState
|
||||
|
||||
+1
-1
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.nip51Lists.searchRelays
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.NoteState
|
||||
|
||||
+1
-1
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.nip65RelayList
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.NoteState
|
||||
|
||||
+1
-1
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.topNavFeeds
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.NoteState
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
+3
-3
@@ -20,10 +20,10 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.follows
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.model.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||
|
||||
+1
-1
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.BaseEoseManager
|
||||
import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.searchCommand.subassemblies
|
||||
|
||||
import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.SearchQueryState
|
||||
|
||||
@@ -58,7 +58,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastEvent
|
||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastStatus
|
||||
import com.vitorpamplona.amethyst.service.broadcast.RelayResult
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastEvent
|
||||
import com.vitorpamplona.amethyst.service.broadcast.BroadcastStatus
|
||||
import com.vitorpamplona.amethyst.service.broadcast.RelayResult
|
||||
|
||||
+15
-36
@@ -23,29 +23,18 @@ package com.vitorpamplona.amethyst.ui.screen
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.AccountInfo
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultNIP65RelaySet
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.DefaultChannels
|
||||
import com.vitorpamplona.amethyst.model.DefaultDMRelayList
|
||||
import com.vitorpamplona.amethyst.model.DefaultGlobalRelays
|
||||
import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.model.DefaultNIP65List
|
||||
import com.vitorpamplona.amethyst.model.DefaultNIP65RelaySet
|
||||
import com.vitorpamplona.amethyst.model.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.tags.ContactTag
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Client
|
||||
import com.vitorpamplona.quartz.nip06KeyDerivation.Nip06
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
@@ -57,12 +46,7 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NSec
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListEvent
|
||||
import com.vitorpamplona.quartz.nip49PrivKeyEnc.Nip49
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CancellationException
|
||||
@@ -303,28 +287,23 @@ class AccountSessionManager(
|
||||
|
||||
fun createNewAccount(name: String? = null): AccountSettings {
|
||||
val keyPair = KeyPair()
|
||||
val tempSigner = NostrSignerSync(keyPair)
|
||||
|
||||
val bootstrap =
|
||||
com.vitorpamplona.amethyst.commons.account.bootstrapAccountEvents(
|
||||
signer = NostrSignerSync(keyPair),
|
||||
name = name,
|
||||
)
|
||||
return AccountSettings(
|
||||
keyPair = keyPair,
|
||||
transientAccount = false,
|
||||
backupUserMetadata = tempSigner.sign(MetadataEvent.newUser(name)),
|
||||
backupContactList =
|
||||
ContactListEvent.createFromScratch(
|
||||
followUsers = listOf(ContactTag(keyPair.pubKey.toHexKey(), null, null)),
|
||||
relayUse = emptyMap(),
|
||||
signer = tempSigner,
|
||||
),
|
||||
backupNIP65RelayList = AdvertisedRelayListEvent.create(DefaultNIP65List, tempSigner),
|
||||
backupDMRelayList = ChatMessageRelayListEvent.create(DefaultDMRelayList, tempSigner),
|
||||
// MIP-00: advertise the default outbox relays as KeyPackage hosts
|
||||
// so other users can discover and fetch this account's KeyPackage
|
||||
// events without having to guess where they were published.
|
||||
backupKeyPackageRelayList = KeyPackageRelayListEvent.create(DefaultNIP65RelaySet.toList(), tempSigner),
|
||||
backupSearchRelayList = SearchRelayListEvent.create(DefaultSearchRelayList.toList(), tempSigner),
|
||||
backupIndexRelayList = IndexerRelayListEvent.create(DefaultIndexerRelayList.toList(), tempSigner),
|
||||
backupChannelList = ChannelListEvent.create(emptyList(), DefaultChannels, tempSigner),
|
||||
backupRelayFeedsList = RelayFeedsListEvent.create(DefaultGlobalRelays, tempSigner),
|
||||
backupUserMetadata = bootstrap.userMetadata,
|
||||
backupContactList = bootstrap.contactList,
|
||||
backupNIP65RelayList = bootstrap.nip65RelayList,
|
||||
backupDMRelayList = bootstrap.dmRelayList,
|
||||
backupKeyPackageRelayList = bootstrap.keyPackageRelayList,
|
||||
backupSearchRelayList = bootstrap.searchRelayList,
|
||||
backupIndexRelayList = bootstrap.indexerRelayList,
|
||||
backupChannelList = bootstrap.channelList,
|
||||
backupRelayFeedsList = bootstrap.relayFeedsList,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -48,10 +48,10 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
+3
-3
@@ -49,9 +49,9 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.DefaultDMRelayList
|
||||
import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.model.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultDMRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.ui.components.M3ActionDialog
|
||||
import com.vitorpamplona.amethyst.ui.components.M3ActionRow
|
||||
import com.vitorpamplona.amethyst.ui.components.M3ActionSection
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.DefaultDMRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultDMRelayList
|
||||
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.SavingTopBar
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultSearchRelayList
|
||||
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.SavingTopBar
|
||||
|
||||
@@ -31,20 +31,36 @@ import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNsec
|
||||
import java.io.File
|
||||
|
||||
/** Persisted identity (hex keys + cached bech32 forms for convenience). */
|
||||
/**
|
||||
* Persisted identity.
|
||||
*
|
||||
* [privKeyHex] may be null for read-only accounts imported from an `npub`,
|
||||
* `nprofile` or NIP-05 — in that case [nsec] is also null and any CLI verb
|
||||
* that needs to sign will fail with a clear error. `keyPair()` materialises
|
||||
* a [KeyPair] with only `pubKey` set when no private key is available.
|
||||
*/
|
||||
data class Identity(
|
||||
val privKeyHex: String,
|
||||
val privKeyHex: String?,
|
||||
val pubKeyHex: String,
|
||||
val nsec: String,
|
||||
val nsec: String?,
|
||||
val npub: String,
|
||||
) {
|
||||
fun keyPair(): KeyPair = KeyPair(privKey = privKeyHex.hexToByteArray(), pubKey = pubKeyHex.hexToByteArray())
|
||||
val hasPrivateKey: Boolean get() = privKeyHex != null
|
||||
|
||||
fun keyPair(): KeyPair =
|
||||
if (privKeyHex != null) {
|
||||
KeyPair(privKey = privKeyHex.hexToByteArray(), pubKey = pubKeyHex.hexToByteArray())
|
||||
} else {
|
||||
KeyPair(pubKey = pubKeyHex.hexToByteArray())
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(): Identity {
|
||||
val kp = KeyPair()
|
||||
val priv = kp.privKey!!
|
||||
val pub = kp.pubKey
|
||||
fun create(): Identity = fromPrivateKey(KeyPair().privKey!!)
|
||||
|
||||
fun fromNsec(nsec: String): Identity = fromPrivateKey(nsec.bechToBytes())
|
||||
|
||||
fun fromPrivateKey(priv: ByteArray): Identity {
|
||||
val pub = KeyPair(privKey = priv).pubKey
|
||||
return Identity(
|
||||
privKeyHex = priv.toHexKey(),
|
||||
pubKeyHex = pub.toHexKey(),
|
||||
@@ -53,17 +69,15 @@ data class Identity(
|
||||
)
|
||||
}
|
||||
|
||||
fun fromNsec(nsec: String): Identity {
|
||||
val priv = nsec.bechToBytes()
|
||||
val kp = KeyPair(privKey = priv)
|
||||
return Identity(
|
||||
privKeyHex = priv.toHexKey(),
|
||||
pubKeyHex = kp.pubKey.toHexKey(),
|
||||
nsec = priv.toNsec(),
|
||||
npub = kp.pubKey.toNpub(),
|
||||
/** Read-only identity (no private key). */
|
||||
fun fromPublicKeyHex(pubHex: String): Identity =
|
||||
Identity(
|
||||
privKeyHex = null,
|
||||
pubKeyHex = pubHex.lowercase(),
|
||||
nsec = null,
|
||||
npub = pubHex.hexToByteArray().toNpub(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -107,6 +107,14 @@ private suspend fun dispatch(argv: Array<String>): Int {
|
||||
Commands.init(dataDir, Args(tail))
|
||||
}
|
||||
|
||||
"create" -> {
|
||||
Commands.create(dataDir, tail)
|
||||
}
|
||||
|
||||
"login" -> {
|
||||
Commands.login(dataDir, tail)
|
||||
}
|
||||
|
||||
"whoami" -> {
|
||||
Commands.whoami(dataDir)
|
||||
}
|
||||
@@ -171,7 +179,9 @@ private fun printUsage() {
|
||||
| amy [--data-dir PATH] <cmd> [args...]
|
||||
|
|
||||
|Identity:
|
||||
| init [--nsec NSEC] create or import identity
|
||||
| init [--nsec NSEC] create or import a bare identity (no defaults published)
|
||||
| create [--name NAME] provision a full Amethyst-style account + publish bootstrap events
|
||||
| login KEY [--password X] import (nsec|ncryptsec|mnemonic|npub|nprofile|hex|nip05)
|
||||
| whoami print current identity
|
||||
|
|
||||
|Relays:
|
||||
|
||||
@@ -34,6 +34,16 @@ object Commands {
|
||||
args: Args,
|
||||
): Int = InitCommands.init(dataDir, args)
|
||||
|
||||
suspend fun create(
|
||||
dataDir: DataDir,
|
||||
tail: Array<String>,
|
||||
): Int = CreateCommand.run(dataDir, tail)
|
||||
|
||||
suspend fun login(
|
||||
dataDir: DataDir,
|
||||
tail: Array<String>,
|
||||
): Int = LoginCommand.run(dataDir, tail)
|
||||
|
||||
suspend fun whoami(dataDir: DataDir): Int = InitCommands.whoami(dataDir)
|
||||
|
||||
suspend fun relay(
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.cli.commands
|
||||
|
||||
import com.vitorpamplona.amethyst.cli.Args
|
||||
import com.vitorpamplona.amethyst.cli.Context
|
||||
import com.vitorpamplona.amethyst.cli.DataDir
|
||||
import com.vitorpamplona.amethyst.cli.Identity
|
||||
import com.vitorpamplona.amethyst.cli.Json
|
||||
import com.vitorpamplona.amethyst.cli.RelayConfig
|
||||
import com.vitorpamplona.amethyst.commons.account.bootstrapAccountEvents
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultDMRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultNIP65List
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultNIP65RelaySet
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
|
||||
/**
|
||||
* `amy create [--name NAME]` — provision a brand-new Nostr account with the
|
||||
* same defaults Amethyst uses, publish the nine bootstrap events to the
|
||||
* default NIP-65 relay set, and seed this data-dir's relay config so
|
||||
* subsequent `amy marmot …` commands immediately target the right relays.
|
||||
*
|
||||
* The heavy lifting (which events to sign, with which defaults) lives in
|
||||
* `commons/.../AccountBootstrap.kt` so this command stays assembly-thin
|
||||
* and the on-relay shape matches the in-app flow byte-for-byte.
|
||||
*/
|
||||
object CreateCommand {
|
||||
suspend fun run(
|
||||
dataDir: DataDir,
|
||||
rest: Array<String>,
|
||||
): Int {
|
||||
if (dataDir.loadIdentityOrNull() != null) {
|
||||
return Json.error("exists", "identity already exists at ${dataDir.identityFile}")
|
||||
}
|
||||
val args = Args(rest)
|
||||
val name = args.flag("name")
|
||||
|
||||
// 1. Mint identity + seed relay config.
|
||||
val identity = Identity.create()
|
||||
dataDir.saveIdentity(identity)
|
||||
dataDir.saveRelays(defaultRelayConfig())
|
||||
|
||||
// 2. Build the nine signed bootstrap events via the shared helper.
|
||||
val signer = NostrSignerSync(identity.keyPair())
|
||||
val bootstrap = bootstrapAccountEvents(signer, name)
|
||||
|
||||
// 3. Open a Context so we reuse publishAndConfirmDetailed + the
|
||||
// NostrClient plumbing instead of reinventing a second client.
|
||||
val ctx = Context.open(dataDir)
|
||||
val accepted = mutableMapOf<String, List<String>>()
|
||||
try {
|
||||
ctx.prepare()
|
||||
for (event in bootstrap.all()) {
|
||||
val ack = ctx.publish(event, DefaultNIP65RelaySet)
|
||||
accepted[event.kind.toString()] =
|
||||
ack.filterValues { it }.keys.map { it.url }
|
||||
}
|
||||
} finally {
|
||||
ctx.close()
|
||||
}
|
||||
|
||||
Json.writeLine(
|
||||
mapOf(
|
||||
"npub" to identity.npub,
|
||||
"hex" to identity.pubKeyHex,
|
||||
"name" to (name ?: ""),
|
||||
"data_dir" to dataDir.root.absolutePath,
|
||||
"published_kinds" to bootstrap.all().map { it.kind },
|
||||
"accepted_by" to accepted,
|
||||
"relays" to
|
||||
mapOf(
|
||||
"nip65" to DefaultNIP65List.map { it.relayUrl.url },
|
||||
"inbox" to DefaultDMRelayList.map { it.url },
|
||||
"key_package" to DefaultNIP65RelaySet.map { it.url },
|
||||
),
|
||||
),
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror of what Amethyst's in-app defaults would write on disk — NIP-65
|
||||
* outbox, DM inbox (kind:10050), and KeyPackage host relays (kind:10051).
|
||||
* Keeping these in sync with the signed events above is load-bearing:
|
||||
* `amy marmot key-package publish` later reads `relays.json` to decide
|
||||
* where to publish, and if those diverge from the advertised kind:10051
|
||||
* nobody will find the KPs.
|
||||
*/
|
||||
private fun defaultRelayConfig(): RelayConfig {
|
||||
val cfg = RelayConfig()
|
||||
DefaultNIP65List.forEach { cfg.add("nip65", it.relayUrl.url) }
|
||||
DefaultDMRelayList.forEach { cfg.add("inbox", it.url) }
|
||||
DefaultNIP65RelaySet.forEach { cfg.add("key_package", it.url) }
|
||||
return cfg
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.cli.commands
|
||||
|
||||
import com.vitorpamplona.amethyst.cli.Args
|
||||
import com.vitorpamplona.amethyst.cli.DataDir
|
||||
import com.vitorpamplona.amethyst.cli.Identity
|
||||
import com.vitorpamplona.amethyst.cli.Json
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Client
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.OkHttpNip05Fetcher
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.resolveUserHexOrNull
|
||||
import com.vitorpamplona.quartz.nip06KeyDerivation.Nip06
|
||||
import com.vitorpamplona.quartz.nip49PrivKeyEnc.Nip49
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
/**
|
||||
* `amy login KEY [--password X]` — import any of the identifier forms
|
||||
* Amethyst's login screen accepts and persist the identity to the
|
||||
* data-dir. Mirrors [AccountSessionManager.loginSync] but on the JVM.
|
||||
*
|
||||
* Accepted forms (tried in this order):
|
||||
* - nsec1… → full account
|
||||
* - ncryptsec… + --password X → NIP-49 decrypt → full
|
||||
* - BIP-39 mnemonic (space-separated) → NIP-06 derive → full
|
||||
* - 64-hex private key (with --private) → full
|
||||
* - npub1… / nprofile1… / 64-hex pubkey → read-only
|
||||
* - NIP-05 identifier (name@domain.tld) → read-only (HTTP lookup)
|
||||
*/
|
||||
object LoginCommand {
|
||||
suspend fun run(
|
||||
dataDir: DataDir,
|
||||
rest: Array<String>,
|
||||
): Int {
|
||||
if (rest.isEmpty()) {
|
||||
return Json.error("bad_args", "login <nsec|ncryptsec|mnemonic|npub|nprofile|hex|nip05> [--password X]")
|
||||
}
|
||||
if (dataDir.loadIdentityOrNull() != null) {
|
||||
return Json.error("exists", "identity already exists at ${dataDir.identityFile}; use a fresh --data-dir or delete it first")
|
||||
}
|
||||
|
||||
val key = rest[0].trim()
|
||||
val args = Args(rest.drop(1).toTypedArray())
|
||||
|
||||
val identity =
|
||||
resolveIdentity(key, args)
|
||||
?: return Json.error(
|
||||
"bad_key",
|
||||
"could not parse '$key' as any supported identifier",
|
||||
)
|
||||
|
||||
dataDir.saveIdentity(identity)
|
||||
Json.writeLine(
|
||||
mapOf(
|
||||
"npub" to identity.npub,
|
||||
"hex" to identity.pubKeyHex,
|
||||
"read_only" to !identity.hasPrivateKey,
|
||||
"data_dir" to dataDir.root.absolutePath,
|
||||
),
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
private suspend fun resolveIdentity(
|
||||
key: String,
|
||||
args: Args,
|
||||
): Identity? {
|
||||
// 1. ncryptsec — password mandatory.
|
||||
if (key.startsWith("ncryptsec")) {
|
||||
val pw =
|
||||
args.flag("password") ?: args.flag("pw")
|
||||
?: throw IllegalArgumentException("ncryptsec input requires --password")
|
||||
val privHex = Nip49().decrypt(key, pw)
|
||||
return Identity.fromPrivateKey(
|
||||
com.vitorpamplona.quartz.utils.Hex
|
||||
.decode(privHex),
|
||||
)
|
||||
}
|
||||
// 2. nsec
|
||||
if (key.startsWith("nsec1")) return Identity.fromNsec(key)
|
||||
// 3. mnemonic (space-separated, 12/24 words)
|
||||
if (key.contains(' ') && Nip06().isValidMnemonic(key)) {
|
||||
val priv = Nip06().privateKeyFromMnemonic(key)
|
||||
return Identity.fromPrivateKey(priv)
|
||||
}
|
||||
// 4. 64-hex privkey — only when explicitly asked; otherwise a bare
|
||||
// hex string is ambiguous with a pubkey and we default to public.
|
||||
if (args.bool("private") && isHex64(key)) {
|
||||
return Identity.fromPrivateKey(
|
||||
com.vitorpamplona.quartz.utils.Hex
|
||||
.decode(key),
|
||||
)
|
||||
}
|
||||
// 5. everything else — defer to the shared resolver (npub / nprofile /
|
||||
// hex pubkey / NIP-05). Read-only.
|
||||
val pubHex = resolveUserHexOrNull(key, nip05Client()) ?: return null
|
||||
return Identity.fromPublicKeyHex(pubHex)
|
||||
}
|
||||
|
||||
private fun isHex64(s: String): Boolean = s.length == 64 && s.all { it.isDigit() || it.lowercaseChar() in 'a'..'f' }
|
||||
|
||||
private fun nip05Client(): Nip05Client {
|
||||
// Build a throwaway OkHttp client — we don't hold a Context here and
|
||||
// login is a one-shot CLI invocation anyway.
|
||||
val http = OkHttpClient.Builder().build()
|
||||
return Nip05Client(fetcher = OkHttpNip05Fetcher { _ -> http })
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.account
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultChannels
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultDMRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultGlobalRelays
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultIndexerRelayList
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultNIP65List
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultNIP65RelaySet
|
||||
import com.vitorpamplona.amethyst.commons.defaults.DefaultSearchRelayList
|
||||
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.tags.ContactTag
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListEvent
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
|
||||
/**
|
||||
* Full set of "new Amethyst account" bootstrap events: the nine signed
|
||||
* events that [com.vitorpamplona.amethyst.ui.screen.AccountSessionManager.createNewAccount]
|
||||
* produces for a brand-new user, in the exact same order and with the same
|
||||
* relay defaults. Used by:
|
||||
*
|
||||
* - Amethyst's in-app "Create Account" flow (builds an `AccountSettings`
|
||||
* from these).
|
||||
* - `amy create` (writes the keypair to disk, publishes these to the
|
||||
* default NIP-65 relay set).
|
||||
*
|
||||
* Each event is optional in the sense that the builder lets you null out
|
||||
* individual ones via the args — but the sensible default is "all of them".
|
||||
*/
|
||||
data class AccountBootstrapEvents(
|
||||
val userMetadata: MetadataEvent,
|
||||
val contactList: ContactListEvent,
|
||||
val nip65RelayList: AdvertisedRelayListEvent,
|
||||
val dmRelayList: ChatMessageRelayListEvent,
|
||||
val keyPackageRelayList: KeyPackageRelayListEvent,
|
||||
val searchRelayList: SearchRelayListEvent,
|
||||
val indexerRelayList: IndexerRelayListEvent,
|
||||
val channelList: ChannelListEvent,
|
||||
val relayFeedsList: RelayFeedsListEvent,
|
||||
) {
|
||||
/** All nine signed events in publication order. */
|
||||
fun all(): List<com.vitorpamplona.quartz.nip01Core.core.Event> =
|
||||
listOf(
|
||||
userMetadata,
|
||||
contactList,
|
||||
nip65RelayList,
|
||||
dmRelayList,
|
||||
keyPackageRelayList,
|
||||
searchRelayList,
|
||||
indexerRelayList,
|
||||
channelList,
|
||||
relayFeedsList,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and sign the nine events that comprise a freshly-created Amethyst
|
||||
* account. The caller is responsible for publishing them — typically to
|
||||
* [DefaultNIP65RelaySet] — after at least a short delay so relay
|
||||
* connections are established.
|
||||
*
|
||||
* [name] is the optional display name; null or blank produces a kind:0
|
||||
* event with no `name` field.
|
||||
*/
|
||||
fun bootstrapAccountEvents(
|
||||
signer: NostrSignerSync,
|
||||
name: String? = null,
|
||||
): AccountBootstrapEvents =
|
||||
AccountBootstrapEvents(
|
||||
userMetadata = signer.sign(MetadataEvent.newUser(name)),
|
||||
contactList =
|
||||
ContactListEvent.createFromScratch(
|
||||
followUsers = listOf(ContactTag(signer.keyPair.pubKey.toHexKey(), null, null)),
|
||||
relayUse = emptyMap(),
|
||||
signer = signer,
|
||||
),
|
||||
nip65RelayList = AdvertisedRelayListEvent.create(DefaultNIP65List, signer),
|
||||
dmRelayList = ChatMessageRelayListEvent.create(DefaultDMRelayList, signer),
|
||||
// MIP-00: advertise the default outbox relays as KeyPackage hosts
|
||||
// so other users can discover and fetch this account's KeyPackage
|
||||
// events without having to guess where they were published.
|
||||
keyPackageRelayList = KeyPackageRelayListEvent.create(DefaultNIP65RelaySet.toList(), signer),
|
||||
searchRelayList = SearchRelayListEvent.create(DefaultSearchRelayList.toList(), signer),
|
||||
indexerRelayList = IndexerRelayListEvent.create(DefaultIndexerRelayList.toList(), signer),
|
||||
channelList = ChannelListEvent.create(emptyList(), DefaultChannels, signer),
|
||||
relayFeedsList = RelayFeedsListEvent.create(DefaultGlobalRelays, signer),
|
||||
)
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.defaults
|
||||
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.list.tags.ChannelTag
|
||||
import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayInfo
|
||||
import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayType
|
||||
|
||||
/**
|
||||
* Default relay/channel bundles applied to every freshly-created Amethyst
|
||||
* account. Both the Android UI (`AccountSessionManager.createNewAccount`)
|
||||
* and the `amy` CLI (`amy create`) seed new accounts from these so users
|
||||
* land in the same connected state regardless of which client they start
|
||||
* with.
|
||||
*
|
||||
* Pure data — no platform deps, no runtime config, no i18n. Change here,
|
||||
* both clients follow.
|
||||
*/
|
||||
|
||||
val DefaultChannels =
|
||||
listOf(
|
||||
// Anigma's Nostr
|
||||
ChannelTag("25e5c82273a271cb1a840d0060391a0bf4965cafeb029d5ab55350b418953fbb", Constants.nos),
|
||||
// Amethyst's Group
|
||||
ChannelTag("42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5", Constants.nos),
|
||||
)
|
||||
|
||||
val DefaultNIP65RelaySet = setOf(Constants.mom, Constants.nos, Constants.bitcoiner)
|
||||
|
||||
val DefaultNIP65List =
|
||||
listOf(
|
||||
AdvertisedRelayInfo(Constants.mom, AdvertisedRelayType.BOTH),
|
||||
AdvertisedRelayInfo(Constants.nos, AdvertisedRelayType.BOTH),
|
||||
AdvertisedRelayInfo(Constants.bitcoiner, AdvertisedRelayType.BOTH),
|
||||
)
|
||||
|
||||
val DefaultGlobalRelays = listOf(Constants.wine, Constants.news)
|
||||
|
||||
val DefaultDMRelayList = listOf(Constants.auth, Constants.oxchat, Constants.nos)
|
||||
|
||||
val DefaultSearchRelayList =
|
||||
setOf(Constants.wine, Constants.where, Constants.nostoday, Constants.antiprimal, Constants.ditto)
|
||||
|
||||
val DefaultIndexerRelayList =
|
||||
setOf(Constants.purplepages, Constants.coracle, Constants.userkinds, Constants.yabu, Constants.nostr1)
|
||||
+6
-1
@@ -18,10 +18,15 @@
|
||||
* 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.model
|
||||
package com.vitorpamplona.amethyst.commons.defaults
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
|
||||
/**
|
||||
* Relay URLs that Amethyst (and `amy`) seed new accounts with and use as
|
||||
* sensible defaults across the app. Pure data — lives in commons so JVM /
|
||||
* iOS / desktop builds get the same URLs without duplicating the list.
|
||||
*/
|
||||
object Constants {
|
||||
val nos = RelayUrlNormalizer.normalize("wss://nos.lol")
|
||||
val mom = RelayUrlNormalizer.normalize("wss://nostr.mom")
|
||||
Reference in New Issue
Block a user