{"id":"43f1b9cb7e6a1d4c214a82a0d1881853bfbfa10eb69e799dfafbdd1fa2b8deab","pubkey":"b22b06b051fd5232966a9344a634d956c3dc33a7f5ecdcad9ed11ddc4120a7f2","created_at":1786557125,"kind":30817,"tags":[["d","append-only-lists"],["title","Append-Only Lists"],["alt","Nostr Implementation Possibility: Append-Only Lists"],["k","1990","Add to list"],["k","1991","Remove from list"],["client","NostrHub"]],"content":"NIP Append-Only Lists\n======\n\nThis NIP defines append-only event kinds for list membership, as a complement to NIP-51. Tag layout, encrypted-content format, and `d` tag semantics are inherited from NIP-51 unchanged; only the storage model differs.\n\n## Motivation\n\nNIP-51 stores lists in *replaceable* events. The implied last-write-wins semantics is poorly adapted to:\n\n- **Local-first / offline-first workflows** — concurrent edits performed offline on different devices cannot be merged; the second publication silently overwrites the first.\n- **Automated, high-volume marking** — when a client emits many list operations per session, republishing the entire list per operation is wasteful and conflict-prone.\n\nThis NIP defines append-only counterparts: each addition or removal of an entry is its own event. State is computed client-side as additions minus subsequent removals, forming an OR-Set CRDT. Operations commute, so multi-device and offline edits converge without coordination.\n\n## Event kinds\n\n- `kind:1990` — Add to list\n- `kind:1991` — Remove from list\n\nBoth are *regular* (non-replaceable) event kinds.\n\n## Tags and content\n\nTag layout and encrypted-content format follow NIP-51:\n\n- A single `d` tag names the list. Standard NIP-51 lists have no `d` tag, so their append-only counterpart uses the kind number as the name (`[\"d\", \"10000\"]` for the mute list).\n- Entries are referenced via single-letter tags (`e`, `p`, `a`, `t`, …) and/or via the NIP-44 self-encrypted content, with the same plaintext format as NIP-51 (a JSON-stringified array of tag tuples).\n- The effective set of entries operated on by the event is the union of the public tag references and the entries decoded from the content.\n\n## Example\n\nA user maintains a list of favourite fruits using hashtag references. They add three fruits:\n\n```jsonc\n{\n  \"kind\": 1990,\n  \"created_at\": 1715000000,\n  \"tags\": [\n    [\"d\", \"fruits\"],\n    [\"t\", \"apple\"],\n    [\"t\", \"banana\"],\n    [\"t\", \"cherry\"]\n  ],\n  \"content\": \"\"\n}\n```\n\nLater, they remove `banana`:\n\n```jsonc\n{\n  \"kind\": 1991,\n  \"created_at\": 1715100000,\n  \"tags\": [\n    [\"d\", \"fruits\"],\n    [\"t\", \"banana\"]\n  ],\n  \"content\": \"\"\n}\n```\n\nThe resulting state of the `fruits` list for this author is `{apple, cherry}`.\n\n## State computation\n\nAn entry `e` is a member of list `L` for author `P` if and only if `P` has signed at least one Add event for `(L, e)` and has not signed a Remove event for `(L, e)` with a strictly later `created_at`.\n\nThis forms an OR-Set CRDT: Add and Remove operations commute, are associative, and idempotent. Devices may emit operations offline and publish them in any order; convergence to a consistent list state is guaranteed without explicit coordination.\n\n## Queryability\n\n- Full state of a list: `REQ {\"kinds\":[1990,1991], \"authors\":[\"<pubkey>\"], \"#d\":[\"<list_name>\"]}`\n- Membership state of a specific entry across lists: `REQ {\"kinds\":[1990,1991], \"authors\":[\"<pubkey>\"], \"#e\":[\"<entry_id>\"]}`\n- Incremental sync: add `\"since\": <timestamp>` to any of the above.\n\n## Relay storage\n\nAppend-only events accumulate over time. Clients SHOULD periodically consolidate (emit a fresh batch capturing the current state, then issue NIP-09 deletions on the superseded events).\n\n## Compatibility\n\nComplementary to NIP-51, not a replacement. The two are intended to coexist:\n\n- NIP-51 remains appropriate for human-curated, low-volume, mono-device lists explicitly edited as a whole by the user.\n- This NIP targets high-volume, automated, multi-device, and offline-first workflows.\n\nClients MAY mirror state between the two mechanisms for interoperability with clients that only support one.","sig":"3d5e7508e1ab3ab768d5727b02c14de9d00ab2143fa6e819bb3beeaaa05a66d3887ab0e7a8583aceed9cc31bc61f4daef1a71b2334b23e90dc7e9797ab31a2ee"}