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:
+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)
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.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")
|
||||
val primal = RelayUrlNormalizer.normalize("wss://relay.primal.net")
|
||||
val damus = RelayUrlNormalizer.normalize("wss://relay.damus.io")
|
||||
val wine = RelayUrlNormalizer.normalize("wss://nostr.wine")
|
||||
|
||||
val where = RelayUrlNormalizer.normalize("wss://relay.noswhere.com")
|
||||
val elites = RelayUrlNormalizer.normalize("wss://nostrelites.org")
|
||||
|
||||
val bitcoiner = RelayUrlNormalizer.normalize("wss://nostr.bitcoiner.social")
|
||||
val oxtr = RelayUrlNormalizer.normalize("wss://nostr.oxtr.dev")
|
||||
|
||||
val nostoday = RelayUrlNormalizer.normalize("wss://search.nos.today")
|
||||
val antiprimal = RelayUrlNormalizer.normalize("wss://antiprimal.net")
|
||||
val ditto = RelayUrlNormalizer.normalize("wss://relay.ditto.pub")
|
||||
|
||||
val auth = RelayUrlNormalizer.normalize("wss://auth.nostr1.com")
|
||||
val oxchat = RelayUrlNormalizer.normalize("wss://relay.0xchat.com")
|
||||
|
||||
val news = RelayUrlNormalizer.normalize("wss://news.utxo.one")
|
||||
|
||||
val purplepages = RelayUrlNormalizer.normalize("wss://purplepag.es")
|
||||
val coracle = RelayUrlNormalizer.normalize("wss://indexer.coracle.social")
|
||||
val userkinds = RelayUrlNormalizer.normalize("wss://user.kindpag.es")
|
||||
val yabu = RelayUrlNormalizer.normalize("wss://directory.yabu.me")
|
||||
val nostr1 = RelayUrlNormalizer.normalize("wss://profiles.nostr1.com")
|
||||
|
||||
val bootstrapInbox = setOf(damus, primal, mom, nos, bitcoiner, oxtr, yabu)
|
||||
val eventFinderRelays = setOf(wine, damus, primal, mom, nos, bitcoiner, oxtr)
|
||||
}
|
||||
Reference in New Issue
Block a user