Files
welshman/docs/domain/profile.md
T
hodlbod 5b8fef5b23
tests / tests (push) Failing after 5m8s
Fix NIP conformance in domain kinds; add domain docs/skill
NIP fixes:
- RelayMembers (13534): use NIP-43 `member` tags (not `p`) and set the required
  NIP-70 `-` protected tag.
- Profile (kind 0): remove display-name support entirely (getter, setter, display()
  fallback, and the search weight).
- Comment (1111): A/a tags now carry a real address, not the event id.
- BlossomServerList (10063): normalize server URLs with normalizeUrl (HTTP), not
  normalizeRelayUrl (which forced wss://).
- HandlerRecommendation (31989): fix inverted removeRecommendation filter; add
  setSupportedKind()/supportedKind() for the NIP-89 d-tag.
- Report (1984): place the report-type string on the e tag (note reports) or p tag
  (profile reports); always emit the p tag.

Docs/skills:
- Add @welshman/domain docs (docs/domain/) and the welshman-domain skill.
- Prune @welshman/util docs/skill of the moved Profile/List/Handler/Encryptable
  helpers; register domain in the sidebar, index, and skills README.
- Apply accuracy fixes to the @welshman/app docs/skill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMjvv7krpZeHK1Njeneru
2026-06-20 14:55:21 +00:00

3.4 KiB

Profile

Profile / ProfileBuilder model NIP-01 kind-0 metadata — the JSON blob that carries a user's name, picture, NIP-05, lightning address, and so on. Like every kind in @welshman/domain, it is a thin pair of classes over the base Reader/Builder machinery: a read-only view plus a chainable producer of an event template.

The content of a kind-0 event is a JSON object, so Profile.parse decodes it into a values record and the getters read fields off that.

Reading

import {Profile} from "@welshman/domain"

const profile = await Profile.fromEvent(event)   // no signer needed — kind 0 is not encrypted

profile.name()            // string | undefined
profile.displayName()     // values.display_name
profile.about()           // string | undefined
profile.picture()         // string | undefined
profile.banner()          // string | undefined
profile.website()         // string | undefined
profile.nip05()           // string | undefined
profile.lnurl()           // lud16/lud06 → lnurl, via parseLnUrl
profile.values            // the raw decoded JSON object

display(fallback = "") is the best-effort label you usually want in UI. It prefers name, then display_name (each truncated to 60 chars via ellipsize), and finally falls back to a shortened npub:

profile.display()             // "alice"  ·  "npub1abc…wxyz"  ·  fallback
profile.display("anonymous")  // fallback used only when there is nothing else

Building

Construct empty to author a new profile, or from a reader to edit one. Setters are chainable; finish with toTemplate() / toEvent(signer).

import {ProfileBuilder} from "@welshman/domain"

const template = await new ProfileBuilder()
  .setName("alice")
  .setAbout("hello nostr")
  .setPicture("https://example.com/avatar.png")
  .setNip05("alice@example.com")
  .toTemplate()             // EventTemplate {kind: 0, content, tags: []}

Editing round-trips through the reader. buildContent re-serializes values to JSON, so unknown profile fields you never touched are preserved:

const signed = await profile.builder()
  .setAbout("updated bio")
  .toEvent(signer)         // SignedEvent

Available setters: setName, setDisplayName, setNip05, setAbout, setBanner, setPicture, setWebsite, plus update(values) to merge an arbitrary object into values.

new ProfileBuilder().update({name: "alice", lud16: "alice@walletofsatoshi.com"})

::: warning Asymmetric display name setDisplayName(x) writes values.displayName (camelCase), but displayName() reads values.display_name (snake_case, the NIP-01 field). They are not symmetric — a value you set with setDisplayName will not be read back by displayName(). Use update({display_name: x}) if you need the getter to see it. :::

Free functions

Profile.ts also exports two standalone helpers, used internally by the getters above but available on their own:

import {parseLnUrl, displayPubkey} from "@welshman/domain"

// Resolve an lnurl from a metadata object: checks lud06 then lud16.
parseLnUrl({lud16: "alice@example.com"})   // string | undefined

// A short, human-readable npub: first 8 chars + "…" + last 5.
displayPubkey(pubkey)                       // "npub1abc…wxyz"

See also