
Account State Management
FreeManage user-specific states in Amethyst seamlessly.
Free · Opens the source repo
What Account State Management does
The Account State Management skill is designed for developers working with the Amethyst client, specifically focusing on user account states and the associated event store patterns. This skill provides a structured approach to managing per-user state objects, such as follow lists, relay lists, and bookmarks, through the Account.kt class. It helps streamline the interaction between user-specific data and the UI, ensuring that changes in account states are efficiently reflected in the application.
At the core of this skill is the LocalCache, which serves as the central event store for all user notes and metadata. This singleton object maintains a comprehensive cache of Note and User instances, allowing for efficient retrieval and updates. Developers can leverage this skill when adding new account-scoped settings or when deciding whether to query the LocalCache or subscribe to a StateFlow. The skill emphasizes a clear mental model of how account events propagate from the relay to the UI, helping developers understand the flow of data and the importance of state management in the Amethyst architecture.
In addition to managing state, the skill also provides guidance on how to implement new features related to user accounts. By following the outlined steps for adding a new account-scoped setting, developers can easily extend the functionality of the Amethyst client. This includes creating new state classes, managing encrypted lists, and ensuring that all changes are properly reflected in the UI through reactive programming techniques.
Overall, this skill is essential for any developer looking to work with user-specific states in Amethyst, providing the necessary tools and patterns to effectively manage account data and enhance the user experience.
When to use it
Use this skill when developing features that require managing user account states or when interacting with the LocalCache for user data.
When not to use it
This skill may not be suitable for projects outside the Amethyst ecosystem or for tasks not involving user-specific state management.
What you can build with it
Adding a New Account Setting
When you need to implement new features like custom relay lists, this skill provides a clear recipe for adding account-scoped settings.
Managing User Metadata
Use this skill to read and write user metadata effectively, ensuring that all changes are reflected in the UI.
Handling Event Propagation
This skill helps understand how account events cascade from relay arrival to UI state, crucial for responsive applications.
How to install Account State Management
View source1. Install with the skills CLI
npx skills add vitorpamplona/amethyst/account-state --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by vitorpamplonaAccount & Local Cache State
The backbone of Amethyst's client state: one Account per signed-in user, plus the singleton LocalCache that holds every Note and User the client has seen.
When to Use This Skill
- Working on
amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt - Working on
amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt - Adding a new account-scoped setting (mutes, bookmarks, custom relay lists, private lists)
- Reading/writing user metadata (
User) or note state (Note) - Deciding whether to query
LocalCachevs subscribe to anAccountStateFlow
Mental Model
Relay frame ──► LocalCache.insertOrUpdateNote() ──► LocalCacheFlow emits change
│
▼
Account state objects pin the relevant addressable notes
│
▼
State-object `.flow` updates (kind3FollowList, nip65RelayList, muteList, …)
│
▼
ViewModels collect
│
▼
Composables render
LocalCache is the event store. Account is the derived per-user view (follow list, relays, mutes, emojis, bookmarks, etc.). UI listens to the .flow of Account's state objects, not directly to LocalCache, except for note-level rendering.
Key Files
Account.kt (singleton-per-session)
class Account(...)— holds 50+ state objects, one per feature, each wired to a specific Nostr kind:kind3FollowList = Kind3FollowListState(...)← NIP-02 ContactList (kind 3)nip65RelayList = Nip65RelayListState(...)← NIP-65 RelayList (kind 10002), plus siblingsdmRelayList,searchRelayList,blockedRelayList,trustedRelayList,proxyRelayList,broadcastRelayList,indexerRelayList, …muteList = MuteListState(...)← NIP-51 MuteList (kind 10000)bookmarkState = BookmarkListState(...)← NIP-51 Bookmarks (kind 10003), pluslabeledBookmarkLists,pinState,interestSets,peopleLists,followLists,hashtagList,geohashList,communityList,emoji,blossomServers, …- Derived/merged views:
hiddenUsers,allFollows,homeRelays,outboxRelays,dmRelays,notificationRelays,trustedRelays, and thelive*FollowListsPerRelayoutbox loaders.
- The pattern: each
XStateclass pins its addressable note viacache.getOrCreateAddressableNote(address)(a long-term reference so GC/eviction can't drop it), exposesval flow: StateFlow<…>derived from the note's metadata flow (decrypted through a per-featureDecryptionCache, with backup fallback fromAccountSettings,stateIn(scope, Eagerly, …)), and offers suspend mutation helpers (e.g.MuteListState.hideUser(pubkey)) that build the updated signed event. Consumers readaccount.muteList.flow, never a rawMutableStateFlowonAccount. - Encrypted lists pair the state object with a
DecryptionCachesibling (muteListDecryptionCache,peopleListDecryptionCache, …) so NIP-44 decryption results are cached per event. - UI reads via
collectAsStateWithLifecycleon Android andcollectAsStateon Desktop. - Sibling files per feature live alongside:
AccountSettings.kt,AccountSyncedSettings.kt, plus per-NIP state classes undermodel/nip02FollowLists/,model/nip51Lists/,model/nip65RelayList/, etc.
LocalCache.kt
object LocalCache : ILocalCache, ICacheProvider— the singleton event store.- Primary structures (all
LargeCache— seenostr-expert/references/large-cache.md):notes: LargeCache<HexKey, Note>— every seen event (regular + addressable + replaceable) keyed by id or d-address.users: LargeCache<HexKey, User>— every seen pubkey, lazily populated.addressables: LargeCache<Address, Note>— secondary index forkind:pubkey:d-taglookups.channels,deletionIndex,hashtagIndex, …
LocalCacheFlowemits coarse-grained "something changed, recheck" signals. Fine-grained reactivity lives inAccount's per-kind StateFlows.- Eviction is driven by
MemoryTrimmingService(android service) under pressure.
Model classes
User.kt— mutable profile holder. Contains metadata, follow/follower counts, relay lists, liveset of notes authored.Note.kt— mutable note holder. Contains the underlyingEvent, replies, reactions, zaps. Mutation viaaddReply,addReaction,addZap, emitted onNote.flowSetflows.Constants.kt— DEFAULT_RELAYS, magic kinds/limits not covered by quartz.
Adding a New Account-Scoped Setting
Typical recipe:
- If the setting is persisted as a Nostr event, pick the right kind (e.g. NIP-51 list, NIP-78 app-specific data, NIP-65 relay list).
- Add a model folder under
amethyst/.../model/nipXX…/with anXStateclass modeled on an existing one (MuteListStatefor an encrypted list,BookmarkListStatefor a plain one):- Pin the addressable note:
val xNote = cache.getOrCreateAddressableNote(XEvent.createAddress(signer.pubKey)). - Expose
val flow: StateFlow<…>mapped fromxNote.flow().metadata.stateFlow, decrypting through a per-featureDecryptionCacheif the list is private, with backup fallback fromAccountSettings, thenstateIn(scope, Eagerly, default). - Add suspend mutation helpers that build the updated event via the quartz event class (
XEvent.add/remove/create) and return it signed.
- Pin the addressable note:
- In
Account.kt, instantiate the state object (and itsDecryptionCachesibling if encrypted) as aval. Publishing the returned event goes throughAccount's send path; the relay subscription side is the relayClient pattern (seerelay-clientskill). - Add UI that
collectsaccount.x.flow. Settings screens live inamethyst/.../ui/screen/loggedIn/settings/.
LocalCache vs Account Flow — Which to Read?
- Are you rendering a specific note / user you hold an id for? →
LocalCache.getOrCreateNote(id)+ collectnote.flowSet.metadata. - Are you rendering "my follows", "my mutes", "my relays"? →
account.<feature>.flow(e.g.account.kind3FollowList.flow,account.muteList.flow,account.nip65RelayList.flow). - Are you rendering a feed? → Use a
FeedFilter+FeedViewModel(seefeed-patternsskill). Don't scanLocalCachein a composable.
Gotchas
LocalCacheis a singleton across accounts. Switching accounts doesn't wipe it —Accountre-derives its flows from the same cache.- Don't store Flows inside
Note/Userexpecting them to survive eviction. Eviction drops the whole object. - State-object mutation helpers return a signed event — publishing it is the caller's job. A locally updated list without a publish means other clients won't see it.
Noteis mutable — treat instances as identity-based (same id → same Note). Use.flowSetwhen you need reactive state.MemoryTrimmingServicecan evict aggressively on Android under pressure. Don't assume a previously-seen note is still resident.
References
references/account-state-flow.md— catalog of majorAccountstate objects and their source kinds.references/local-cache.md—LocalCacheinternals, insertion path, indexes.- Complements:
nostr-expert(event parsing),relay-client(subscription wiring),feed-patterns(how feeds consume this state),auth-signers(how mutation signs events).
Frequently asked questions about Account State Management
Similar skills
Python PyPI Package Builder
Streamline the process of creating and publishing Python packages.
Minecraft Plugin Development
Streamline your Minecraft server plugin creation.
MCP Server Builder
Easily build .NET MCP servers with the latest standards.
CommunityToolkit.Mvvm Messenger
Decoupled communication for ViewModels in .NET applications.
MVVM Toolkit DI
Streamline ViewModel integration with Dependency Injection in .NET.
MCP Apps Builder
Essential guidelines for MCP server development.
