6.4 KiB
NIP-777
Spells: Shareable REQ Subscription Templates
draft optional
This NIP defines kind 777 events that encode Nostr REQ subscription filters as shareable, reusable templates called "spells."
Motivation
Users often want to share interesting subscription queries (feeds, searches, curated lists) without recipients needing to manually construct filters. Spells allow:
- Shareability: Publish a useful filter as a discoverable event
- Dynamic evaluation: Support relative timestamps (
7d,now) and account aliases ($me,$contacts) - Forking: Reference and modify existing spells
- Discoverability: Tag spells with topics for categorization
Event Structure
{
"kind": 777,
"content": "<human-readable description>",
"tags": [
["cmd", "REQ"],
["client", "<client-identifier>"],
...filter tags...
...metadata tags...
]
}
Required Tags
| Tag | Description |
|---|---|
["cmd", "REQ"] |
Command type (currently only REQ is defined) |
["client", "<name>"] |
Client that created the spell |
Filter Tags
Filter parameters are encoded as tags. At least one filter constraint must be present.
Kind Filter
Kinds are encoded as individual tags (queryable via NIP-50):
["k", "1"]
["k", "3"]
["k", "7"]
Author Filter
Authors are encoded as a single tag with multiple values:
["authors", "<hex-pubkey>", "<hex-pubkey>", "$me", "$contacts"]
Special aliases:
$me— Resolves to the executing user's pubkey at runtime$contacts— Expands to the executing user's contact list (kind 3ptags)
Aliases are case-insensitive and preserved as literal strings in the event.
Tag Filters
Generic tag filters use the format ["tag", "<letter>", ...values]:
["tag", "e", "<event-id>", "<event-id>"] → filter["#e"]
["tag", "p", "<pubkey>", "$me"] → filter["#p"]
["tag", "t", "bitcoin", "nostr"] → filter["#t"]
["tag", "d", "<identifier>"] → filter["#d"]
["tag", "a", "<kind>:<pubkey>:<d-tag>"] → filter["#a"]
The #p and #P tag filters also support $me and $contacts aliases.
Scalar Filters
["limit", "<number>"]
["search", "<query>"]
Time Bounds
Time bounds support both absolute Unix timestamps and relative expressions:
["since", "1704067200"] Absolute Unix timestamp
["since", "7d"] 7 days ago (relative)
["since", "24h"] 24 hours ago
["since", "30m"] 30 minutes ago
["since", "2w"] 2 weeks ago
["since", "1y"] 1 year ago
["until", "now"] Current time at execution
["until", "1704153600"] Absolute Unix timestamp
Relative formats:
<number>s— seconds ago<number>m— minutes ago<number>h— hours ago<number>d— days ago<number>w— weeks ago<number>mo— months ago (30 days)<number>y— years ago (365 days)now— current timestamp
Clients MUST evaluate relative timestamps at execution time, not at event creation.
Metadata Tags
| Tag | Description |
|---|---|
["name", "<spell-name>"] |
Display name for the spell |
["alt", "<description>"] |
NIP-31 alt text for clients that don't understand kind 777 |
["t", "<topic>"] |
Topic tags for categorization (repeatable) |
["relays", "<url>", ...] |
Suggested relay URLs for executing the spell |
["close-on-eose"] |
Flag indicating subscription should close after EOSE |
Provenance Tags
| Tag | Description |
|---|---|
["e", "<event-id>"] |
References the spell this was forked from |
Example Events
Basic Feed Spell
{
"kind": 777,
"content": "Recent text notes from my network",
"tags": [
["cmd", "REQ"],
["client", "grimoire"],
["name", "My Network Feed"],
["k", "1"],
["authors", "$me", "$contacts"],
["since", "7d"],
["limit", "100"],
["alt", "Grimoire REQ spell: Recent text notes from my network"]
]
}
Bitcoin Discussion Spell
{
"kind": 777,
"content": "All posts mentioning bitcoin",
"tags": [
["cmd", "REQ"],
["client", "grimoire"],
["name", "Bitcoin Feed"],
["k", "1"],
["tag", "t", "bitcoin", "btc"],
["search", "bitcoin"],
["limit", "50"],
["t", "bitcoin"],
["alt", "Grimoire REQ spell: All posts mentioning bitcoin"]
]
}
Mentions of Me
{
"kind": 777,
"content": "Events that mention me",
"tags": [
["cmd", "REQ"],
["client", "grimoire"],
["name", "My Mentions"],
["k", "1"],
["k", "7"],
["tag", "p", "$me"],
["since", "24h"],
["alt", "Grimoire REQ spell: Events that mention me"]
]
}
Execution
To execute a spell:
- Parse filter tags into a NIP-01 filter object
- Resolve aliases (
$me,$contacts) using the active account - Evaluate relative timestamps against current time
- Construct REQ message and send to relays
If a spell uses $me or $contacts but no account is active, clients SHOULD display an error rather than silently fail.
Client Behavior
Required
- Validate
["cmd", "REQ"]tag exists - Support at minimum:
k,authors,limittags - Preserve aliases in stored/republished events
Recommended
- Support all tag types defined in this NIP
- Support relative timestamp evaluation
- Allow forking spells with provenance tracking
- Display spell metadata (name, description, topics)
Optional
- Local spell storage with aliases for quick access
- Spell discovery via topic tags
- Command-line interface for spell creation
Relation to Other NIPs
- NIP-01: Spells encode standard REQ filters
- NIP-31: Uses
alttag for fallback description - NIP-50: Individual
ktags enable kind-based search
Security Considerations
- Clients MUST NOT auto-execute spells from untrusted sources
- The
$contactsalias may expose the user's social graph to relay operators - Relative timestamps should be bounded to prevent resource exhaustion
Appendix: Alias Resolution
$me → active_account.pubkey
$contacts → active_account.contacts.map(c => c.pubkey)
Resolution is performed at execution time. If $me appears in a filter but no account is active, execution fails. If $contacts appears but the contact list is empty or unavailable, it resolves to an empty array.
Both aliases are case-insensitive: $ME, $Me, $me are equivalent.