Expand description
NIP-30 / NIP-51 custom emoji + pack support.
Phase 1 (read path):
- Parse kind 30030 emoji sets and kind 10030 user emoji lists.
- Fetch a user’s subscribed packs from relays, persist them locally.
- Expose a flat
EmojiPackAPI to the frontend for picker rendering.
Spec: https://nips.nostr.com/30, https://nips.nostr.com/51.
Metadata interop: NIP-51 standardises title / image / description
tags on kind 30030. Ditto and Nostria emit non-standard name /
picture / about instead. Vector reads both with spec preference
and (eventually) dual-writes both on publish.
Structs§
- Emoji
Pack - Pack
Address - Parsed NIP-19 / NIP-51 set address.
- Pack
Emoji
Enums§
- Pack
Fetch Outcome - What one refresh sweep learned about a subscribed pack.
Constants§
- KIND_
EMOJI_ SET - NIP-51 kind for an “Emoji set” (parameterised replaceable).
- MAX_
EMOJIS_ PER_ PACK - Base (free, tier-0) emojis-per-own-pack cap. Shared packs received from the network may exceed this — only what we author is capped, and the frontend truncates oversized received packs at display time.
- MAX_
EQUIPPED_ PACKS - Base (free, tier-0) equipped-pack cap. Named const for the frontend mirror.
- PACK_
STATUS_ ACTIVE - Pack is live on its relays (or hasn’t been judged otherwise).
- PACK_
STATUS_ MISSING - No tombstone, but the pack has been absent across enough clean sweeps of
live relays (see the gauntlet in
apply_pack_health) that it’s considered gone. - PACK_
STATUS_ REVOKED - A deterministic tombstone was seen: the author replaced the pack with an EMPTY kind 30030 (Vector’s own delete flow) or published a kind-5 deletion naming it.
Functions§
- apply_
pack_ health - Apply one refresh sweep’s verdict to a pack’s persisted health. Returns
truewhen the pack’s STATUS changed (caller emits a UI refresh). - clear_
nip65_ cache - Drop every cached relay list (account swap — entries hold contact-graph metadata from the prior session, mirroring the inbox-relay cache’s swap hygiene).
- decrypt_
subscribed_ addresses - Decrypt + parse a kind 10030 event’s encrypted subscription list.
- delete_
own_ pack - Tombstone one of the user’s own packs by publishing an empty kind
30030 with just the
dtag (relays replace the prior payload), drop the local subscription, and republish kind 10030. - effective_
display_ cap - Per-pack emoji cap mirrored from the frontend’s
MAX_DISPLAY_EMOJIS_PER_PACK(applyBadgeLimits). The send resolver caps each subscribed pack to this many items so its~Ndisambiguation of duplicate shortcodes matches exactly what the picker displayed — otherwise a large pack’s hidden tail (the picker only shows the first N) would enter the candidate set and shift the indices. - effective_
max_ emojis_ per_ pack - Per-pack emoji authoring cap for the current account, scaled by effective tier.
- effective_
max_ equipped_ packs - In-app equipped-pack cap for the current account, scaled by effective tier. Gate the in-app subscribe/create action on this — never the load/display path.
- fetch_
pack_ by_ naddr - Preview-only fetch by naddr — resolves + parses but never touches local DB. Lets the UI render a “Pack Preview” card without committing to a subscription.
- fetch_
subscribed_ packs - Fetch the user’s kind 10030 list, resolve every referenced pack, and persist the result locally. Session-guarded against an account swap landing the new account’s pack list in account A’s DB.
- get_
or_ fetch_ theme_ pack - Resolve a theme pack cache-first: return the locally-persisted copy
instantly when present (and refresh it in the background), otherwise fetch
live, persist, and return. Theme packs are pinned by the active theme, not
subscribed —
save_packpersists their data without a subscription row, so they survive restarts (no per-session relay round-trip) yet never occupy an equip slot or land in the kind-10030 list. ReturnsNoneif uncached and the live fetch finds nothing. - get_
theme_ slot_ anchor - Return the theme-slot anchor as a NADDR (the pack the theme slot renders
immediately after), or
""when the slot is at the top. Degrades to top on a malformed stored anchor rather than breaking the picker. - load_
all_ packs - Load every locally-cached pack the user is currently subscribed to (plus their own packs, which always count). Hydrated with items. Cached non-subscribed pack rows stay in the DB so historic reactions still resolve their image URLs — they’re just hidden from the picker.
- load_
cached_ pack - Load a single cached pack by its raw
kind:pubkey:identifieraddr, regardless of subscription status. Used by the theme-pack path: a theme pack is persisted viasave_pack(so it loads instantly across sessions) but never gets a subscription row, soload_all_packsrightly hides it. - load_
subscriptions - naddr_
from_ addr - Encode a pack
addr(kind:pubkey:identifier) into a NIP-19naddr1...bech32 string. Used by the share-pack flow to put a portable reference on the user’s clipboard. - parse_
naddr - Decode a NIP-19
naddr1...into aPackAddress. Rejects coordinates that don’t point at kind 30030 so a malformed paste can’t pull in an unrelated replaceable event. - parse_
pack_ address - Parse a
kind:pubkey-hex:d-tagaddress as found in kind 10030atags. Rejects anything that isn’t kind 30030 — we don’t want a malformed list pulling in random replaceable events. - parse_
pack_ from_ event - Parse a kind 30030 event into an EmojiPack. Returns
Noneif the event is missing adtag or has zero valid emoji rows. - publish_
emoji_ list - Publish a kind 10030 “Emojis” list containing every subscribed pack.
- publish_
pack - Publish (or replace) one of the user’s own packs as a kind 30030 event, persist it locally, and add it to the subscription list so the picker surfaces it immediately. SessionGuard-gated so a mid- network account swap can’t push account A’s pack signed by B’s key.
- refresh_
subscribed_ packs - Convenience entry point that grabs the client + my_pubkey internally
and runs the full subscribed-packs refresh. Intended for the boot
path; in-app commands pass an explicit
SessionGuardvia the lower helper to make the safety contract visible at every call site. - reorder_
emoji_ packs - Persist a user-defined display order for the equipped packs (including the theme slot) and republish kind 10030 so it syncs across devices.
- republish_
emoji_ list_ debounced - Coalesce rapid subscribe/unsubscribe taps into one network publish.
Captures
SessionGuardBEFORE the spawn boundary so a mid-debounce account swap can’t sign account A’s pack list with account B’s key. - resolve_
outbound_ emoji_ tags - Scan
contentfor:shortcode:patterns and resolve them against the user’s currently-subscribed packs (plus the active theme pack). Returns deduped emoji tags in first-match order. Used by the send pipeline to attach NIP-30 emoji tags so recipients without the pack subscribed still render. - save_
pack - save_
subscriptions - set_
theme_ emoji_ tags - Register (or clear, with an empty vec) the active theme pack’s emoji so the send resolver can tag them even though they aren’t a DB subscription.
- share_
naddr - Mint the SHAREABLE form of a pack naddr: the same coordinate plus up to 3 relay hints naming the pack’s own home. A pack lives on its AUTHOR’s write relays (NIP-65), never the sharer’s — a subscribed pack must not get the sharer’s personal set stamped onto it. An author with no published kind 10002 falls back to this client’s pool, which is where an own pack was published and where a subscribed one was actually seen.
- subscribe_
pack - Subscribe to a pack by naddr: fetch the pack, persist it + the subscription, then schedule a debounced republish of kind 10030. Returns the hydrated pack on success.
- unsubscribe_
pack - Unsubscribe locally and republish kind 10030 without the pack.
The pack row itself stays in
emoji_packs(caller may still want to render old reactions); only the subscription link is dropped.