Skip to content
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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions 88.md
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.
Copy link
Contributor

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.

Copy link
Contributor Author

@abhay-raizada abhay-raizada Sep 28, 2024

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

Copy link
Contributor

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.

Copy link
Collaborator

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.

- **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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make the response tag indexable, response -> r ?

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.

Copy link
Member

@fiatjaf fiatjaf Oct 13, 2024

Choose a reason for hiding this comment

The 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.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
- [NIP-75: Zap Goals](75.md)
- [NIP-78: Application-specific data](78.md)
- [NIP-84: Highlights](84.md)
- [NIP-88: Polls](88.md)
- [NIP-89: Recommended Application Handlers](89.md)
- [NIP-90: Data Vending Machines](90.md)
- [NIP-92: Media Attachments](92.md)
Expand Down Expand Up @@ -118,11 +119,13 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
| `44` | Channel Mute User | [28](28.md) |
| `64` | Chess (PGN) | [64](64.md) |
| `818` | Merge Requests | [54](54.md) |
| `1018` | Poll Response | [88](88.md) |
| `1021` | Bid | [15](15.md) |
| `1022` | Bid confirmation | [15](15.md) |
| `1040` | OpenTimestamps | [03](03.md) |
| `1059` | Gift Wrap | [59](59.md) |
| `1063` | File Metadata | [94](94.md) |
| `1068` | Poll | [88](88.md) |
| `1311` | Live Chat Message | [53](53.md) |
| `1617` | Patches | [34](34.md) |
| `1621` | Issues | [34](34.md) |
Expand Down