-
Notifications
You must be signed in to change notification settings - Fork 602
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reinstate NIP-88: Polls on Nostr #1507
base: master
Are you sure you want to change the base?
Changes from 2 commits
b83b0c1
802d7ab
35e7eb0
b10a353
5ff355b
83f78a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
# NIP-88 | ||
|
||
## Polls | ||
|
||
`draft` `optional` | ||
|
||
This NIP defines the event scheme that describe Polls on nostr. | ||
|
||
## Events | ||
|
||
### Poll Event | ||
|
||
The poll event is defined as a `kind:1068` event. | ||
|
||
- **content** key holds the label for the poll. | ||
|
||
Major tags in the poll event are: | ||
|
||
- **option**: The option tags contain an OptionId(any alphanumeric) field, followed by an option label field. | ||
- **relay**: One or multiple tags that the poll is expecting respondents to respond on. | ||
- **polltype**: can be "singlechoice", "multiplechoice", or "rankedchoice". Polls that do not have a polltype should be considered a "singlechoice" poll. | ||
- **endsAt**: signifying at which unix timestamp the poll is meant to end. | ||
|
||
Example Event | ||
|
||
```json | ||
{ | ||
"content": "Pineapple on pizza", | ||
"created_at": 1719888496, | ||
"id": "9d1b6b9562e66f2ecf35eb0a3c2decc736c47fddb13d6fb8f87185a153ea3634", | ||
"kind": 1068, | ||
"pubkey": "dee45a23c4f1d93f3a2043650c5081e4ac14a778e0acbef03de3768e4f81ac7b", | ||
"sig": "7fa93bf3c430eaef784b0dacc217d3cd5eff1c520e7ef5d961381bc0f014dde6286618048d924808e54d1be03f2f2c2f0f8b5c9c2082a4480caf45a565ca9797", | ||
"tags": [ | ||
["option", "qj518h583", "Yay"], | ||
["option", "gga6cdnqj", "Nay"], | ||
["relay", "<relay url1>"], | ||
["relay", "<relay url2>"], | ||
["polltype", "singlechoice"], | ||
["endsAt", "<unix timestamp in seconds>"] | ||
] | ||
} | ||
``` | ||
|
||
### Responses | ||
|
||
The 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. | ||
|
||
- **response** : The tag contains "response" as it's first positional argument followed by the option Id selected. | ||
|
||
The responses are meant to be published to the relays specified in the poll event. | ||
|
||
Example Response Event | ||
|
||
```json | ||
{ | ||
"content": "", | ||
"created_at": 1720097117, | ||
"id": "60a005e32e9596c3f544a841a9bc4e46d3020ca3650d6a739c95c1568e33f6d8", | ||
"kind": 30068, | ||
abhay-raizada marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"pubkey": "1bc70a0148b3f316da33fe7e89f23e3e71ac4ff998027ec712b905cd24f6a411", | ||
"sig": "30071a633c65db8f3a075c7a8de757fbd8ce65e3607f4ba287fe6d7fbf839a380f94ff4e826fbba593f6faaa13683b7ea9114ade140720ecf4927010ebf3e44f", | ||
"tags": [ | ||
["e", "1fc80cf813f1af33d5a435862b7ef7fb96b47e68a48f1abcadf8081f5a545550"], | ||
["response", "gga6cdnqj"], | ||
["response", "m3agjsdq1"] | ||
Comment on lines
+65
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we make the response tag indexable, A client might omit some poll options, e.g. in cases where the option list is ridiculously long or a label is deemed to be inappropriate. When this is the case it would be nice to be able to only fetch responses to options that are displayed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ridiculously long option lists are an attack, just like ridiculously long notes, ridiculously long hellthreads and all that. Both clients and relays should eventually guard against these things. Forcing relays to index more stuff unnecessarily doesn't sound like a fair tradeoff. |
||
] | ||
} | ||
``` | ||
|
||
### Poll Types | ||
|
||
The polltype setting dictates how multiple response tags are handled in the `kind:1018` event. | ||
|
||
- **polltype: singlechoice**: The first response tag is to be considered the actual response. | ||
- **polltype: multiplechoice**: The first response tag pointing to each id is considered the actual response, without considering the order of the response tags. | ||
- **polltype: rankedchoice**: The first response tag pointing to each id is the considered the actual response, while also taking into account the order of the response tags. | ||
|
||
### Counting Results | ||
|
||
Results can be queried by fetching `kind:1018` events from the relays specified in the poll. | ||
The results displayed should only be 1 vote event per pubkey. | ||
In case of multiple events for a pubkey, the event with the largest timestamp within the poll limits should be considered. | ||
|
||
Example for querying polls. | ||
|
||
```ts | ||
const fetchVoteEvents = (filterPubkeys: string[]) => { | ||
let resultFilter: Filter = { | ||
"#e": [pollEvent.id], | ||
kinds: [1070, 1018], | ||
}; | ||
if (filterPubkeys?.length) { | ||
resultFilter.authors = filterPubkeys; | ||
} | ||
if (pollExpiration) { | ||
resultFilter.until = Number(pollExpiration); | ||
} | ||
poolRef.current.subscribeMany(relays, [resultFilter], { | ||
onevent: handleResultEvent, | ||
}); | ||
}; | ||
``` | ||
|
||
Example for maintaining OneVotePerPubkey | ||
|
||
```ts | ||
const oneVotePerPubkey = (events: Event[]) => { | ||
const eventMap = new Map<string, Event>(); | ||
|
||
events.forEach((event) => { | ||
if ( | ||
!eventMap.has(event.pubkey) || | ||
event.created_at > eventMap.get(event.pubkey)!.created_at | ||
) { | ||
eventMap.set(event.pubkey, event); | ||
} | ||
}); | ||
|
||
return Array.from(eventMap.values()); | ||
}; | ||
``` | ||
|
||
### Relays | ||
|
||
It is advisable for the users to use relays that do not allow back dated events i.e. events with timestamp not matching the current time, to achieve authenticity in the poll results. | ||
|
||
### Curation | ||
|
||
The 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. | ||
Clients can also employ other curation algorithms, like Proof Of Work and Web of Trust scores for result curations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to have different kind numbers for each poll type instead of this
polltype
tag, so clients can specifically query the ones they support.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The usecase make sense, should we use an indexable tag like ["T", poll-type] instead? that way we wouldn't be polluting kinds
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok either way but I think having different kinds is cleaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, multiple types make more sense because you can always expand to new poll types without breaking current clients with new types as tags.