NAP-IDENTITY / Merged specification
Read-Only User Identity Queries
Full specification from the fetched upstream revision ↗. The document below is rendered from its original Markdown. Follow the latest source on GitHub ↗.
NAP-IDENTITY
Read-Only User Identity Queries
draft
NAP ID: NAP-IDENTITY
Domain: identity
Depends:
resource— capability · optional —picture/bannerURL bytes are fetched viaresource.bytesWeb binding (NIP-5D):window.napplet.identity; domain presence signals availability.
Description
NAP-IDENTITY provides read-only access to the shell-user identity: the currently connected user/signer pubkey and related public identity data. Napplets that need to display the user's profile, check their follow list, or read their relay preferences query the shell for this data. The shell resolves these queries against its local cache or connected relays and returns the results.
Napplets do not have direct access to the user's private key. They cannot sign events, encrypt, or decrypt. Identity queries are strictly read-only -- napplets learn about the user but cannot act as the user. Signing is delegated to the shell via relay.publish() (NAP-RELAY). Encryption is delegated via relay.publishEncrypted().
This shell-user identity is distinct from napplet identity. The runtime binds an authenticated endpoint to its verified manifest and artifact before execution, as defined by the web projection. NAP-IDENTITY only reports the user's connected signer identity; it does not establish or change the napplet's identity.
API Surface
| Operation | Parameters | Result | Wire |
|---|---|---|---|
getPublicKey |
none | tstr public key, or "" when no signer is connected |
identity.getPublicKey / identity.getPublicKey.result |
onChanged |
handler for tstr public key |
Subscription handle |
identity.changed |
getRelays |
none | RelayMap |
identity.getRelays / identity.getRelays.result |
getProfile |
none | ProfileData or null |
identity.getProfile / identity.getProfile.result |
getFollows |
none | list of tstr pubkeys |
identity.getFollows / identity.getFollows.result |
getList |
type (tstr) |
list of tstr entries |
identity.getList / identity.getList.result |
getZaps |
none | list of ZapReceipt |
identity.getZaps / identity.getZaps.result |
getMutes |
none | list of tstr pubkeys |
identity.getMutes / identity.getMutes.result |
getBlocked |
none | list of tstr pubkeys |
identity.getBlocked / identity.getBlocked.result |
getBadges |
none | list of Badge |
identity.getBadges / identity.getBadges.result |
Schemas
RelayPermissions fields:
| Field | Required | Type |
|---|---|---|
read |
yes | boolean |
write |
yes | boolean |
RelayMap is a map from relay URL text to RelayPermissions.
ProfileData fields:
| Field | Required | Type |
|---|---|---|
name |
no | text |
displayName |
no | text |
about |
no | text |
picture |
no | text |
banner |
no | text |
nip05 |
no | text |
lud16 |
no | text |
website |
no | text |
Resource resolution. picture and banner are URL strings. Napplets that
need their bytes MUST use resource.bytes when the optional resource domain
is exposed, and SHOULD use local placeholder artwork when it is absent. The
web projection applies the runtime's network policy;
an opaque sandbox origin alone does not block network requests. URLs do not
grant network authority. Standard resource policy applies to mediated fetches.
ZapReceipt fields:
| Field | Required | Type |
|---|---|---|
eventId |
yes | text |
sender |
yes | text |
amount |
yes | unsigned integer |
content |
no | text |
Badge fields:
| Field | Required | Type |
|---|---|---|
id |
yes | text |
name |
no | text |
description |
no | text |
image |
no | text |
thumbs |
no | list of text |
awardedBy |
yes | text |
getPublicKey() -- Returns the user's hex-encoded public key, or the empty string when no user/signer is connected. This is the most basic identity query. Every shell that implements NAP-IDENTITY MUST support this method.
onChanged(handler) -- Registers a handler for shell-pushed user identity changes. The handler receives the same public-key string shape as getPublicKey(): a non-empty hex pubkey when a user/signer is connected, or "" when the shell signs out or disconnects the signer.
getRelays() -- Returns the user's relay list as a record mapping relay URLs to read/write permissions (NIP-65 relay list metadata).
getProfile() -- Returns the user's kind 0 profile metadata. Returns null if no profile is found. The shell resolves this from its cache or relays.
getFollows() -- Returns an array of hex-encoded public keys from the user's kind 3 contact list.
getList(type) -- Returns entries from a user's categorized list. The type parameter specifies the list kind (e.g., "bookmarks", "interests", "pins"). Returns the tag values from the matching parameterized replaceable event.
getZaps() -- Returns zap receipts (kind 9735) sent to the user. Each receipt includes the sender, amount in millisats, and optional content.
getMutes() -- Returns the user's mute list (kind 10000) as an array of hex-encoded public keys.
getBlocked() -- Returns the user's block list as an array of hex-encoded public keys. Semantically distinct from mutes -- blocks are hard exclusions.
getBadges() -- Returns badges awarded to the user (NIP-58). Each badge includes the badge definition ID, metadata, and the awarding pubkey.
Wire Protocol
identity.* messages use the NIP-5D wire format ({ "type": "domain.action", ...payload }).
| Type | Direction | Payload fields |
|---|---|---|
identity.getPublicKey |
napplet -> shell | id |
identity.getPublicKey.result |
shell -> napplet | id, pubkey |
identity.changed |
shell -> napplet | pubkey |
identity.getRelays |
napplet -> shell | id |
identity.getRelays.result |
shell -> napplet | id, relays, error? |
identity.getProfile |
napplet -> shell | id |
identity.getProfile.result |
shell -> napplet | id, profile?, error? |
identity.getFollows |
napplet -> shell | id |
identity.getFollows.result |
shell -> napplet | id, pubkeys, error? |
identity.getList |
napplet -> shell | id, listType |
identity.getList.result |
shell -> napplet | id, entries, error? |
identity.getZaps |
napplet -> shell | id |
identity.getZaps.result |
shell -> napplet | id, zaps, error? |
identity.getMutes |
napplet -> shell | id |
identity.getMutes.result |
shell -> napplet | id, pubkeys, error? |
identity.getBlocked |
napplet -> shell | id |
identity.getBlocked.result |
shell -> napplet | id, pubkeys, error? |
identity.getBadges |
napplet -> shell | id |
identity.getBadges.result |
shell -> napplet | id, badges, error? |
Key design notes:
- All methods are request/result pairs using
idfor correlation. identity.changedis a push message. It has noidand is not a response to a request.- All results include an optional
errorfield for failure cases. getPublicKeyis the simplest query and MUST always succeed (noerrorfield). It returnspubkey: ""when no shell-user identity is currently connected.getProfilereturnsprofile: nullif no kind 0 is found -- this is not an error.getListincludes alistTypefield to specify which categorized list to query.
Recommended handshake:
- A napplet that needs user identity calls
identity.getPublicKeyonce on startup to get a snapshot. - The shell returns
identity.getPublicKey.resultwithpubkeyset to either a hex pubkey or"". - The napplet then listens for
identity.changedand re-runs identity-dependent work when the pushed pubkey changes.
Napplets SHOULD NOT poll identity.getPublicKey waiting for a signer to appear. Shells MUST use identity.changed to notify already-loaded napplets when the shell-user identity changes.
Examples
Get public key:
-> { "type": "identity.getPublicKey", "id": "q1" }
<- { "type": "identity.getPublicKey.result", "id": "q1", "pubkey": "ab12cd..." }
No user connected:
-> { "type": "identity.getPublicKey", "id": "q1b" }
<- { "type": "identity.getPublicKey.result", "id": "q1b", "pubkey": "" }
User identity changed:
<- { "type": "identity.changed", "pubkey": "ab12cd..." }
<- { "type": "identity.changed", "pubkey": "" }
Get profile:
-> { "type": "identity.getProfile", "id": "q2" }
<- { "type": "identity.getProfile.result", "id": "q2", "profile": { "name": "Alice", "about": "Nostr dev", "picture": "https://..." } }
Profile not found:
-> { "type": "identity.getProfile", "id": "q3" }
<- { "type": "identity.getProfile.result", "id": "q3", "profile": null }
Get follows:
-> { "type": "identity.getFollows", "id": "q4" }
<- { "type": "identity.getFollows.result", "id": "q4", "pubkeys": ["ab12...", "cd34...", "ef56..."] }
Get relay list:
-> { "type": "identity.getRelays", "id": "q5" }
<- { "type": "identity.getRelays.result", "id": "q5", "relays": { "wss://relay.example.com": { "read": true, "write": true }, "wss://readonly.example.com": { "read": true, "write": false } } }
Get categorized list:
-> { "type": "identity.getList", "id": "q6", "listType": "bookmarks" }
<- { "type": "identity.getList.result", "id": "q6", "entries": ["note1abc...", "note1def..."] }
Get mutes:
-> { "type": "identity.getMutes", "id": "q7" }
<- { "type": "identity.getMutes.result", "id": "q7", "pubkeys": ["spam1...", "spam2..."] }
Get zap receipts:
-> { "type": "identity.getZaps", "id": "q8" }
<- { "type": "identity.getZaps.result", "id": "q8", "zaps": [{ "eventId": "ev1...", "sender": "ab12...", "amount": 21000, "content": "Great post!" }] }
Get badges:
-> { "type": "identity.getBadges", "id": "q9" }
<- { "type": "identity.getBadges.result", "id": "q9", "badges": [{ "id": "badge1...", "name": "Early Adopter", "awardedBy": "cd34..." }] }
Error case:
-> { "type": "identity.getFollows", "id": "q10" }
<- { "type": "identity.getFollows.result", "id": "q10", "pubkeys": [], "error": "relay timeout" }
Error Handling
Result messages include an error field (string) when the shell cannot fulfill the request. Common errors: "relay timeout", "not found", "unsupported list type". When error is present, the primary result field contains a sensible default (empty array, null, etc.).
identity.getPublicKey.result MUST NOT include an error field. The shell returns pubkey: "" when no user/signer is connected.
Shell Behavior
- The shell MUST respond to every
identity.*request with the corresponding*.resultmessage carrying the sameid. - The shell MUST return
pubkeyforidentity.getPublicKeywithout error. If no user/signer is connected,pubkeyMUST be the empty string. - The shell MUST emit
identity.changedto loaded napplets whenever the shell-user identity changes. - The shell MUST emit
identity.changedwithpubkey: ""when the shell-user identity is cleared, the signer disconnects, or the user signs out. - The shell MAY emit an initial
identity.changedafter napplet load, but napplets MUST NOT depend on that initial push and SHOULD useidentity.getPublicKeyfor the startup snapshot. - The shell SHOULD resolve identity queries from its local cache when available.
- The shell MAY fetch data from relays if not cached.
- The shell MAY return stale data with a best-effort freshness policy.
- The shell MAY enforce ACL checks on identity capabilities (e.g., restricting which napplets can query follows or mutes).
- The shell MAY return empty results for queries it does not support (e.g., badges) rather than an error.
- The shell MUST NOT expose the user's private key or any signing capability through this interface.
- The shell MUST NOT provide encrypt or decrypt operations through this interface.
Security Considerations
- NAP-IDENTITY is strictly read-only. No method modifies user state, signs events, or performs cryptographic operations.
identity.changedreports only the shell-user identity. It does not change or renegotiate the napplet's NIP-5D session identity.- The user's public key is not secret, but follow lists, mute lists, and block lists reveal social graph information. Shells MAY restrict access to sensitive lists based on napplet trust level.
- Zap receipts reveal financial information. Shells SHOULD consider whether to expose zap data to all napplets or restrict it to trusted napplets.
- Profile data may contain URLs (picture, banner, website). Napplets that render these URLs should sanitize them. The shell is not responsible for sanitizing profile content.
- This interface intentionally excludes signing, encryption, and decryption. These operations are security-critical and belong to the shell. See NAP-RELAY for shell-mediated publish and encrypted publish.
- Block lists and mute lists are sensitive. A malicious napplet could use this data to target blocked users through side channels. Shells MAY redact or restrict access to these lists.
Implementations
- (none yet)
Changelog
a802d35- Introduced read-only user identity queries and optional resource fetching.06b1a1a- Adopted injected-domain availability, verified manifest and artifact identity, and graceful fallback for optional resource fetching.