{"id":"357bc960b1edf2a440601f05b991bbdcc9446ace1b1b1f0fd22e0b5f9948a0e9","pubkey":"2b39b4ffe62933df970e19366c22c1e092f953f83fcfed754e0f04d5a3b459f9","created_at":1767117877,"kind":30817,"tags":[["d","nip-88"],["title","NIP-88: Polls"],["summary","Kind 1068, a poll with its options, its type and its deadline, answered on the relays it names."],["s","draft"],["t","nostr"],["t","nip"],["k","1018","Poll Response"],["k","1068","Poll"],["alt","A specification: NIP-88: Polls"],["client","openspecs-import"],["published_at","1719305882"],["proxy","https://github.com/nostr-protocol/nips/blob/656cecc7c0a815b6a2b218d3b5d6f078b3f4dbab/88.md","web"],["x","6d09c9c41f3e9d46306ab47dd77c2107ad8b1bd10eac9277512c22506ef41906"]],"content":"NIP-88\n======\n\nPolls\n-----\n\n`draft` `optional`\n\nThis NIP defines the event scheme that describe Polls on nostr.\n\n## Events\n\n### Poll Event\n\nThe poll event is defined as a `kind:1068` event.\n\n- **content** key holds the label for the poll.\n\nMajor tags in the poll event are:\n\n- **option**: The option tags contain an OptionId(any alphanumeric) field, followed by an option label field.\n- **relay**: One or multiple tags that the poll is expecting respondents to respond on.\n- **polltype**: can be \"singlechoice\" or \"multiplechoice\". Polls that do not have a polltype should be considered a \"singlechoice\" poll.\n- **endsAt**: signifying at which unix timestamp the poll is meant to end.\n\nExample Event\n\n```json\n{\n  \"content\": \"Pineapple on pizza\",\n  \"created_at\": 1719888496,\n  \"id\": \"9d1b6b9562e66f2ecf35eb0a3c2decc736c47fddb13d6fb8f87185a153ea3634\",\n  \"kind\": 1068,\n  \"pubkey\": \"dee45a23c4f1d93f3a2043650c5081e4ac14a778e0acbef03de3768e4f81ac7b\",\n  \"sig\": \"7fa93bf3c430eaef784b0dacc217d3cd5eff1c520e7ef5d961381bc0f014dde6286618048d924808e54d1be03f2f2c2f0f8b5c9c2082a4480caf45a565ca9797\",\n  \"tags\": [\n    [\"option\", \"qj518h583\", \"Yay\"],\n    [\"option\", \"gga6cdnqj\", \"Nay\"],\n    [\"relay\", \"<relay url1>\"],\n    [\"relay\", \"<relay url2>\"],\n    [\"polltype\", \"singlechoice\"],\n    [\"endsAt\", \"<unix timestamp in seconds>\"]\n  ]\n}\n```\n\n### Responses\n\nThe response event is a `kind:1018` event. It contains an e tag with the poll event it is referencing, followed by one or more response tags.\n\n- **response** : The tag contains \"response\" as its first positional argument followed by the option Id selected.\n\nThe responses are meant to be published to the relays specified in the poll event.\n\nExample Response Event\n\n```json\n{\n  \"content\": \"\",\n  \"created_at\": 1720097117,\n  \"id\": \"60a005e32e9596c3f544a841a9bc4e46d3020ca3650d6a739c95c1568e33f6d8\",\n  \"kind\": 1018,\n  \"pubkey\": \"1bc70a0148b3f316da33fe7e89f23e3e71ac4ff998027ec712b905cd24f6a411\",\n  \"sig\": \"30071a633c65db8f3a075c7a8de757fbd8ce65e3607f4ba287fe6d7fbf839a380f94ff4e826fbba593f6faaa13683b7ea9114ade140720ecf4927010ebf3e44f\",\n  \"tags\": [\n    [\"e\", \"1fc80cf813f1af33d5a435862b7ef7fb96b47e68a48f1abcadf8081f5a545550\"],\n    [\"response\", \"gga6cdnqj\"],\n    [\"response\", \"m3agjsdq1\"]\n  ]\n}\n```\n\n### Poll Types\n\nThe polltype setting dictates how multiple response tags are handled in the `kind:1018` event.\n\n- **polltype: singlechoice**: The first response tag is to be considered the actual response.\n- **polltype: multiplechoice**: The first response tag pointing to each id is considered the actual response, without considering the order of the response tags.\n\n### Counting Results\n\nResults can be queried by fetching `kind:1018` events from the relays specified in the poll.\nThe results displayed should only be 1 vote event per pubkey.\nIn case of multiple events for a pubkey, the event with the largest timestamp within the poll limits should be considered.\n\nExample for querying polls.\n\n```ts\nconst fetchVoteEvents = (filterPubkeys: string[]) => {\n  let resultFilter: Filter = {\n    \"#e\": [pollEvent.id],\n    kinds: [1018],\n  };\n  if (filterPubkeys?.length) {\n    resultFilter.authors = filterPubkeys;\n  }\n  if (pollExpiration) {\n    resultFilter.until = Number(pollExpiration);\n  }\n  pool.subscribeMany(relays, [resultFilter], {\n    onevent: handleResultEvent,\n  });\n};\n```\n\nExample for maintaining OneVotePerPubkey\n\n```ts\nconst oneVotePerPubkey = (events: Event[]) => {\n  const eventMap = new Map<string, Event>();\n\n  events.forEach((event) => {\n    if (\n      !eventMap.has(event.pubkey) ||\n      event.created_at > eventMap.get(event.pubkey)!.created_at\n    ) {\n      eventMap.set(event.pubkey, event);\n    }\n  });\n\n  return Array.from(eventMap.values());\n};\n```\n\n### Relays\n\nIt is advisable for poll authors to use relays that do not allow backdated events and do not honor kind:5 (delete) requests for vote events in order to maintain the integrity of poll results after the poll has ended.\n\n### Curation\n\nThe clients may configure fetching results by specific people. This can be achieved by creating `kind:30000` follow sets, and fetching results only from the follow set.\nClients can also employ other curation algorithms, like Proof Of Work and Web of Trust scores for result curations.\n","sig":"b2ea4cc4e84b3d2ef94bf2014acd0963df151358e8d045f6e09c19d95c64efe506415b4cd29e65e442789f3a459a54a5f97dafbc1a1ce09f66a42eff270e7d8d"}