mirror of
https://github.com/nostr-protocol/nips.git
synced 2025-12-09 08:38:50 +00:00
Compare commits
2 Commits
nip93-nson
...
Giszmo-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5906545f8 | ||
|
|
535cfcc7e5 |
3
01.md
3
01.md
@@ -99,7 +99,7 @@ This NIP defines no rules for how `NOTICE` messages should be sent or treated.
|
||||
## Basic Event Kinds
|
||||
|
||||
- `0`: `set_metadata`: the `content` is set to a stringified JSON object `{name: <username>, about: <string>, picture: <url, string>}` describing the user who created the event. A relay may delete past `set_metadata` events once it gets a new one for the same pubkey.
|
||||
- `1`: `text_note`: the `content` is set to the plaintext content of a note (anything the user wants to say). Do not use Markdown! Clients should not have to guess how to interpret content like `[]()`. Use different event kinds for parsable content.
|
||||
- `1`: `text_note`: the `content` is set to the plaintext content of a note (anything the user wants to say). Markdown links (`[]()` stuff) are not plaintext.
|
||||
- `2`: `recommend_server`: the `content` is set to the URL (e.g., `wss://somerelay.com`) of a relay the event creator wants to recommend to its followers.
|
||||
|
||||
A relay may choose to treat different message kinds differently, and it may or may not choose to have a default way to handle kinds it doesn't know about.
|
||||
@@ -109,4 +109,3 @@ A relay may choose to treat different message kinds differently, and it may or m
|
||||
- Clients should not open more than one websocket to each relay. One channel can support an unlimited number of subscriptions, so clients should do that.
|
||||
- The `tags` array can store a tag identifier as the first element of each subarray, plus arbitrary information afterward (always as strings). This NIP defines `"p"` — meaning "pubkey", which points to a pubkey of someone that is referred to in the event —, and `"e"` — meaning "event", which points to the id of an event this event is quoting, replying to or referring to somehow. See [NIP-10](https://github.com/nostr-protocol/nips/blob/127d5518bfa9a4e4e7510490c0b8d95e342dfa4b/10.md) for a detailed description of "e" and "p" tags.
|
||||
- The `<recommended relay URL>` item present on the `"e"` and `"p"` tags is an optional (could be set to `""`) URL of a relay the client could attempt to connect to fetch the tagged event or other events from a tagged profile. It MAY be ignored, but it exists to increase censorship resistance and make the spread of relay addresses more seamless across clients.
|
||||
- Clients should use the created_at field to judge the age of a metadata event and completely replace older metadata events with newer metadata events regardless of the order in which they arrive. Clients should not merge any filled fields within older metadata events into empty fields of newer metadata events.
|
||||
|
||||
60
11.md
60
11.md
@@ -69,18 +69,18 @@ are rejected or fail immediately.
|
||||
```json
|
||||
{
|
||||
...
|
||||
"limitation": {
|
||||
"max_message_length": 16384,
|
||||
"max_subscriptions": 20,
|
||||
"max_filters": 100,
|
||||
"max_limit": 5000,
|
||||
"max_subid_length": 100,
|
||||
"min_prefix": 4,
|
||||
"max_event_tags": 100,
|
||||
"max_content_length": 8196,
|
||||
"min_pow_difficulty": 30,
|
||||
"auth_required": true,
|
||||
"payment_required": true,
|
||||
limitation: {
|
||||
max_message_length: 16384,
|
||||
max_subscriptions: 20,
|
||||
max_filters: 100,
|
||||
max_limit: 5000,
|
||||
max_subid_length: 100,
|
||||
min_prefix: 4,
|
||||
max_event_tags: 100,
|
||||
max_content_length: 8196,
|
||||
min_pow_difficulty: 30,
|
||||
auth_required: true,
|
||||
payment_required: true,
|
||||
}
|
||||
...
|
||||
}
|
||||
@@ -141,11 +141,11 @@ all, and preferably an error will be provided when those are received.
|
||||
```json
|
||||
{
|
||||
...
|
||||
"retention": [
|
||||
{ "kinds": [0, 1, [5, 7], [40, 49]], "time": 3600 },
|
||||
{ "kinds": [[40000, 49999]], "time": 100 },
|
||||
{ "kinds": [[30000, 39999]], "count": 1000 },
|
||||
{ "time": 3600, "count": 10000 }
|
||||
retention: [
|
||||
{ kinds: [0, 1, [5, 7], [40, 49]], time: 3600 },
|
||||
{ kinds: [[40000, 49999], time: 100 },
|
||||
{ kinds: [[30000, 39999], count: 1000 },
|
||||
{ time: 3600, count: 10000 }
|
||||
]
|
||||
...
|
||||
}
|
||||
@@ -154,7 +154,7 @@ all, and preferably an error will be provided when those are received.
|
||||
`retention` is a list of specifications: each will apply to either all kinds, or
|
||||
a subset of kinds. Ranges may be specified for the kind field as a tuple of inclusive
|
||||
start and end values. Events of indicated kind (or all) are then limited to a `count`
|
||||
and/or time period.
|
||||
and or time period.
|
||||
|
||||
It is possible to effectively blacklist Nostr-based protocols that rely on
|
||||
a specific `kind` number, by giving a retention time of zero for those `kind` values.
|
||||
@@ -175,8 +175,8 @@ It is not possible to describe the limitations of each country's laws
|
||||
and policies which themselves are typically vague and constantly shifting.
|
||||
|
||||
Therefore, this field allows the relay operator to indicate which
|
||||
countries' laws might end up being enforced on them, and then
|
||||
indirectly on their users' content.
|
||||
country's' laws might end up being enforced on them, and then
|
||||
indirectly on their users's content.
|
||||
|
||||
Users should be able to avoid relays in countries they don't like,
|
||||
and/or select relays in more favourable zones. Exposing this
|
||||
@@ -185,7 +185,7 @@ flexibility is up to the client software.
|
||||
```json
|
||||
{
|
||||
...
|
||||
"relay_countries": [ "CA", "US" ],
|
||||
relay_countries: [ 'CA', 'US' ],
|
||||
...
|
||||
}
|
||||
```
|
||||
@@ -208,9 +208,9 @@ To support this goal, relays MAY specify some of the following values.
|
||||
```json
|
||||
{
|
||||
...
|
||||
"language_tags": [ "en", "en-419" ],
|
||||
"tags": [ "sfw-only", "bitcoin-only", "anime" ],
|
||||
"posting_policy": "https://example.com/posting-policy.html",
|
||||
language_tags: [ 'en', 'en-419' ],
|
||||
tags: [ 'sfw-only', 'bitcoin-only', 'anime' ],
|
||||
posting_policy: 'https://example.com/posting-policy.html',
|
||||
...
|
||||
}
|
||||
```
|
||||
@@ -220,7 +220,7 @@ To support this goal, relays MAY specify some of the following values.
|
||||
the major languages spoken on the relay.
|
||||
|
||||
- `tags` is a list of limitations on the topics to be discussed.
|
||||
For example `sfw-only` indicates that only "Safe For Work" content
|
||||
For example `sfw-only` indicates hat only "Safe For Work" content
|
||||
is encouraged on this relay. This relies on assumptions of what the
|
||||
"work" "community" feels "safe" talking about. In time, a common
|
||||
set of tags may emerge that allow users to find relays that suit
|
||||
@@ -245,11 +245,11 @@ Relays that require payments may want to expose their fee schedules.
|
||||
```json
|
||||
{
|
||||
...
|
||||
"payments_url": "https://my-relay/payments",
|
||||
"fees": {
|
||||
"admission": [{ "amount": 1000000, "unit": "msats" }],
|
||||
"subscription": [{ "amount": 5000000, "unit": "msats", "period": 2592000 }],
|
||||
"publication": [{ "kinds": [4], "amount": 100, "unit": "msats" }],
|
||||
payments_url: "https://my-relay/payments",
|
||||
fees: {
|
||||
"admission": [{ amount: 1000000, unit: 'msats' }],
|
||||
"subscription": [{ amount: 5000000, unit: 'msats', period: 2592000 }],
|
||||
"publication": [{ kinds: [4], amount: 100, unit: 'msats' }],
|
||||
},
|
||||
...
|
||||
}
|
||||
|
||||
8
45.md
8
45.md
@@ -14,13 +14,13 @@ Some queries a client may want to execute against connected relays are prohibiti
|
||||
|
||||
## Filters and return values
|
||||
|
||||
This NIP defines the verb `COUNT`, which accepts a subscription id and filters as specified in [NIP 01](01.md) for the verb `REQ`. Multiple filters are OR'd together and aggregated into a single count result.
|
||||
This NIP defines the verb `COUNT`, which accepts a subscription id and filters as specified in [NIP 01](01.md) for the verb `REQ`.
|
||||
|
||||
```
|
||||
["COUNT", <subscription_id>, <filters JSON>...]
|
||||
```
|
||||
|
||||
Counts are returned using a `COUNT` response in the form `{"count": <integer>}`. Relays may use probabilistic counts to reduce compute requirements.
|
||||
Counts are returned using a `COUNT` response with positional "count" objects in the form `{"count": <integer>}`. Relays may use probabilistic counts to reduce compute requirements.
|
||||
|
||||
```
|
||||
["COUNT", <subscription_id>, {"count": <integer>}]
|
||||
@@ -36,4 +36,8 @@ Examples:
|
||||
# Count posts and reactions
|
||||
["COUNT", <subscription_id>, {"kinds": [1, 7], "authors": [<pubkey>]}]
|
||||
["COUNT", <subscription_id>, {"count": 5}]
|
||||
|
||||
# Both of the above in one `COUNT`
|
||||
["COUNT", <subscription_id>, {"kinds": [3], "#p": [<pubkey>]}, {"kinds": [1, 7], "authors": [<pubkey>]}]
|
||||
["COUNT", <subscription_id>, {"count": 238}, {"count": 5}]
|
||||
```
|
||||
|
||||
20
47.md
20
47.md
@@ -12,18 +12,8 @@ This NIP describes a way for clients to access a remote Lightning wallet through
|
||||
|
||||
## Terms
|
||||
|
||||
* **client**: Nostr app on any platform that wants to pay Lightning invoices.
|
||||
* **user**: The person using the **client**, and want's to connect their wallet app to their **client**.
|
||||
* **wallet service**: Nostr app that typically runs on an always-on computer (eg. in the cloud or on a Raspberry Pi). This app has access to the APIs of the wallets it serves.
|
||||
|
||||
## Theory of Operation
|
||||
1. **Users** who which to use this NIP to send lightning payments to other nostr users must first acquire a special "connection" URI from their NIP-47 compliant wallet application. The wallet application may provide this URI using a QR screen, or a pasteable string, or some other means.
|
||||
|
||||
2. The **user** should then copy this URI into their **client(s)** by pasting, or scanning the QR, etc. The **client(s)** should save this URI and use it later whenever the **user** makes a payment. The **client** should then request an `info` (13194) event from the relay(s) specified in the URI. The **wallet service** will have sent that event to those relays earlier, and the relays will hold it as a replaceable event.
|
||||
|
||||
3. When the **user** initiates a payment their nostr **client** create a `pay_invoice` request, encrypts it using a token from the URI, and sends it (kind 23194) to the relay(s) specified in the connection URI. The **wallet service** will be listening on those relays and will decrypt the request and then contact the **user's** wallet application to send the payment. The **wallet service** will know how to talk to the wallet application because the connection URI specified relay(s) that have access to the wallet app API.
|
||||
|
||||
4. Once the payment is complete the **wallet service** will send an encrypted `response` (kind 23195) to the **user** over the relay(s) in the URI.
|
||||
* **client**: Nostr app on any platform that wants to pay Lightning invoices
|
||||
* **wallet service**: Nostr app that typically runs on an always-on computer (eg. in the cloud or on a Raspberry Pi).
|
||||
|
||||
## Events
|
||||
|
||||
@@ -34,8 +24,7 @@ There are three event kinds:
|
||||
|
||||
The info event should be a replaceable event that is published by the **wallet service** on the relay to indicate which commands it supports. The content should be
|
||||
a plaintext string with the supported commands, space-seperated, eg. `pay_invoice get_balance`. Only the `pay_invoice` command is described in this NIP, but other commands might be defined in different NIPs.
|
||||
|
||||
Both the request and response events SHOULD contain one `p` tag, containing the public key of the **wallet service** if this is a request, and the public key of the **user** if this is a response. The response event SHOULD contain an `e` tag with the id of the request event it is responding to.
|
||||
Both the request and response events SHOULD contain one `p` tag, containing the public key of the **wallet service** if this is a request, and the public key of the **client** if this is a response. The response event SHOULD contain an `e` tag with the id of the request event it is responding to.
|
||||
|
||||
The content of requests and responses is encrypted with [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md), and is a JSON-RPCish object with a semi-fixed structure:
|
||||
|
||||
@@ -88,7 +77,6 @@ The **wallet service** generates this connection URI with protocol `nostr+wallet
|
||||
- The user can have different keys for different applications. Keys can be revoked and created at will and have arbitrary constraints (eg. budgets).
|
||||
- The key is harder to leak since it is not shown to the user and backed up.
|
||||
- It improves privacy because the user's main key would not be linked to their payments.
|
||||
- `lud16` Recommended. A lightning address that clients can use to automatically setup the `lud16` field on the user's profile if they have none configured.
|
||||
|
||||
The **client** should then store this connection and use it when the user wants to perform actions like paying an invoice. Due to this NIP using ephemeral events, it is recommended to pick relays that do not close connections on inactivity to not drop events.
|
||||
|
||||
@@ -129,7 +117,7 @@ Errors:
|
||||
## Example pay invoice flow
|
||||
|
||||
0. The user scans the QR code generated by the **wallet service** with their **client** application, they follow a `nostr+walletconnect:` deeplink or configure the connection details manually.
|
||||
1. **client** sends an event to the **wallet service** service with kind `23194`. The content is a `pay_invoice` request. The private key is the secret from the connection string above.
|
||||
1. **client** sends an event to with **wallet service** service with kind `23194`. The content is a `pay_invoice` request. The private key is the secret from the connection string above.
|
||||
2. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment.
|
||||
3. **wallet service** responds to the event by sending an event with kind `23195` and content being a response either containing an error message or a preimage.
|
||||
|
||||
|
||||
180
93.md
180
93.md
@@ -1,180 +0,0 @@
|
||||
NIP-93
|
||||
======
|
||||
|
||||
NSON
|
||||
----
|
||||
|
||||
`draft` `optional` `author:fiatjaf`
|
||||
|
||||
### Preamble
|
||||
|
||||
Some [benchmarks](https://github.com/fiatjaf/nostr-json-benchmarks/tree/2f254fff91b3ad063ef9726bb4a3d25316cf12d8) made using all libraries available on Golang show that JSON decoding is very slow. And even when people do assembly-level optimizations things only improve up to a point (e.g. for decoding a Nostr event, the "Sonic" library uses about 50% of that of the standard library).
|
||||
|
||||
Meanwhile, doing a simple TLV encoding reduces the decoding time to 35% and a simpler static binary format for Nostr events makes that number drop to 4%. However, it would be bad for Nostr if a binary encoding was introduced, as it would be likely to cause compatibility issues, centralize the protocol and/or increase the work for everybody, more about this in [this comment](https://github.com/nostr-protocol/nips/pull/512#issuecomment-1542368664).
|
||||
|
||||
### The actual NIP
|
||||
|
||||
NSON is a crazy idea that, according to the benchmarks above, reduces the decoding time to 14% of that of the standard library. It works by having the JSON sender encode the event _as JSON_, but in a specific, very strict, order of fields (taking advantage of the fact that Nostr events have static fields, static lengths for some fields, and an overall rigid structure) and include in the JSON object a new field called `"nson"` that contains metadata the JSON receiver can read to help in the decoding process.
|
||||
|
||||
Here's an example of a NSON-encoded Nostr event:
|
||||
|
||||
`{"id":"57ff66490a6a2af3992accc26ae95f3f60c6e5f84ed0ddf6f59c534d3920d3d2","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","sig":"504d142aed7fa7e0f6dab5bcd7eed63963b0277a8e11bbcb03b94531beb4b95a12f1438668b02746bd5362161bc782068e6b71494060975414e793f9e19f57ea","created_at":1683762317,"nson":"2801000b0203000100400005040001004000000014","kind":1,"content":"hello world","tags":[["e","b6de44a9dd47d1c000f795ea0453046914f44ba7d5e369608b04867a575ea83e","reply"],["p","c26f7b252cea77a5b94f42b1a4771021be07d4df766407e47738605f7e3ab774","","wss://relay.damus.io"]]}`
|
||||
|
||||
The idea is that `"id"` comes first, so it can be accessed by reading a slice of the string from character `7` to character `71`, `pubkey` from character `83` to `147` and so on. `"content"`, `"kind"` and `"tags"` have dynamic sizes, so their sizes are given by the values inside the `"nson"` field (which is also dynamic, its size given by its first byte).
|
||||
|
||||
### Anatomy of the `"nson"` field
|
||||
|
||||
It is hex-encoded. Some fields are a single byte, others are two bytes (4 characters), big-endian.
|
||||
|
||||
tt: number of tags (let's say it's two)
|
||||
nn: number of items on the first tag (let's say it's 3)
|
||||
1111: number of chars on the first item
|
||||
2222: number of chars on the second item
|
||||
3333: number of chars on the third item
|
||||
nn: number of items on the second tag (let's say it's 2)
|
||||
1111: number of chars on the first item
|
||||
2222: number of chars on the second item
|
||||
"nson":"xxkkccccttnn111122223333nn11112222"
|
||||
xx: nson size
|
||||
kk: kind chars
|
||||
cccc: content chars
|
||||
|
||||
### Reference implementation
|
||||
|
||||
```go
|
||||
func decodeNson(data string) *Event {
|
||||
evt := &Event{}
|
||||
|
||||
// static fields
|
||||
evt.ID = data[7 : 7+64]
|
||||
evt.PubKey = data[83 : 83+64]
|
||||
evt.Sig = data[156 : 156+128]
|
||||
ts, _ := strconv.ParseInt(data[299:299+10], 10, 64)
|
||||
evt.CreatedAt = Timestamp(ts)
|
||||
|
||||
// nson values
|
||||
nsonSizeBytes, _ := hex.DecodeString(data[318 : 318+2])
|
||||
nsonSize := int(nsonSizeBytes[0])
|
||||
nsonDescriptors, _ := hex.DecodeString(data[320 : 320+nsonSize])
|
||||
|
||||
// dynamic fields
|
||||
// kind
|
||||
kindChars := int(nsonDescriptors[0])
|
||||
kindStart := 320 + nsonSize + 9 // len(`","kind":`)
|
||||
evt.Kind, _ = strconv.Atoi(data[kindStart : kindStart+kindChars])
|
||||
|
||||
// content
|
||||
contentChars := int(binary.BigEndian.Uint16(nsonDescriptors[1:3]))
|
||||
contentStart := kindStart + kindChars + 12 // len(`,"content":"`)
|
||||
evt.Content, _ = strconv.Unquote(`"` + data[contentStart:contentStart+contentChars] + `"`)
|
||||
|
||||
// tags
|
||||
nTags := int(nsonDescriptors[3])
|
||||
evt.Tags = make(Tags, nTags)
|
||||
tagsStart := contentStart + contentChars + 9 // len(`","tags":`)
|
||||
|
||||
nsonIndex := 3
|
||||
tagsIndex := tagsStart
|
||||
for t := 0; t < nTags; t++ {
|
||||
nsonIndex++
|
||||
tagsIndex += 1 // len(`[`) or len(`,`)
|
||||
nItems := int(nsonDescriptors[nsonIndex])
|
||||
tag := make(Tag, nItems)
|
||||
for n := 0; n < nItems; n++ {
|
||||
nsonIndex++
|
||||
itemStart := tagsIndex + 2 // len(`["`) or len(`,"`)
|
||||
itemChars := int(binary.BigEndian.Uint16(nsonDescriptors[nsonIndex:]))
|
||||
nsonIndex++
|
||||
tag[n], _ = strconv.Unquote(`"` + data[itemStart:itemStart+itemChars] + `"`)
|
||||
tagsIndex = itemStart + itemChars + 1 // len(`"`)
|
||||
}
|
||||
tagsIndex += 1 // len(`]`)
|
||||
evt.Tags[t] = tag
|
||||
}
|
||||
|
||||
return evt
|
||||
}
|
||||
|
||||
func encodeNson(evt *Event) string {
|
||||
// start building the nson descriptors (without the first byte that represents the nson size)
|
||||
nsonBuf := make([]byte, 256)
|
||||
|
||||
// build the tags
|
||||
nTags := len(evt.Tags)
|
||||
nsonBuf[3] = uint8(nTags)
|
||||
nsonIndex := 3 // start here
|
||||
|
||||
tagBuilder := strings.Builder{}
|
||||
tagBuilder.Grow(1000) // a guess
|
||||
tagBuilder.WriteString(`[`)
|
||||
for t, tag := range evt.Tags {
|
||||
nItems := len(tag)
|
||||
nsonIndex++
|
||||
nsonBuf[nsonIndex] = uint8(nItems)
|
||||
|
||||
tagBuilder.WriteString(`[`)
|
||||
for i, item := range tag {
|
||||
v := strconv.Quote(item)
|
||||
nsonIndex++
|
||||
binary.BigEndian.PutUint16(nsonBuf[nsonIndex:], uint16(len(v)-2))
|
||||
nsonIndex++
|
||||
tagBuilder.WriteString(v)
|
||||
if nItems > i+1 {
|
||||
tagBuilder.WriteString(`,`)
|
||||
}
|
||||
}
|
||||
tagBuilder.WriteString(`]`)
|
||||
if nTags > t+1 {
|
||||
tagBuilder.WriteString(`,`)
|
||||
}
|
||||
}
|
||||
tagBuilder.WriteString(`]}`)
|
||||
nsonBuf = nsonBuf[0 : nsonIndex+1]
|
||||
|
||||
kind := strconv.Itoa(evt.Kind)
|
||||
kindChars := len(kind)
|
||||
nsonBuf[0] = uint8(kindChars)
|
||||
|
||||
content := strconv.Quote(evt.Content)
|
||||
contentChars := len(content) - 2
|
||||
binary.BigEndian.PutUint16(nsonBuf[1:3], uint16(contentChars))
|
||||
|
||||
// actually build the json
|
||||
base := strings.Builder{}
|
||||
base.Grow(320 + // everything up to "nson":
|
||||
2 + len(nsonBuf)*2 + // nson
|
||||
9 + kindChars + // kind and its label
|
||||
12 + contentChars + // content and its label
|
||||
9 + tagBuilder.Len() + // tags and its label
|
||||
2, // the end
|
||||
)
|
||||
base.WriteString(`{"id":"` + evt.ID + `","pubkey":"` + evt.PubKey + `","sig":"` + evt.Sig + `","created_at":` + strconv.FormatInt(int64(evt.CreatedAt), 10) + `,"nson":"`)
|
||||
base.WriteString(hex.EncodeToString([]byte{uint8(len(nsonBuf) * 2)})) // nson size
|
||||
base.WriteString(hex.EncodeToString(nsonBuf)) // nson descriptors
|
||||
base.WriteString(`","kind":` + kind + `,"content":` + content + `,"tags":`)
|
||||
base.WriteString(tagBuilder.String() /* includes the end */)
|
||||
|
||||
return base.String()
|
||||
}
|
||||
```
|
||||
|
||||
### Other restrictions
|
||||
|
||||
Besides the field ordering and the presence of the `"nson"` field, other restrictions must be applied:
|
||||
|
||||
- the `"created_at"` field must have 10, characters, which gives us a range of dates from about 20 years ago up to 250 years in the future.
|
||||
- to simplify decoding of `"content"` and `"tags"` strings, escape codes like `\uXXXX` are forbidden in NSON, UTF-8 must be used instead. `\n`, `\\` and `\"` are the only valid escaped sequences.
|
||||
|
||||
### Backwards-compatibility
|
||||
|
||||
Any reader who is not aware of the NSON-encoding can receive these events and decode them using whatever means they want. The `"nson"` field will just be ignored and life will continue as normal.
|
||||
|
||||
Also, other event fields that may be present (for example, the NIP-03 `"ots"` field) can be added at the end, after `"tags"`, with no loss to anyone.
|
||||
|
||||
### Other points worth mentioning
|
||||
|
||||
- Relays can receive non-nsonified events from clients, then reformat and store them nsonified so they can serve future clients better by sending them NSON always.
|
||||
|
||||
### Open questions to be edited out of the NIP
|
||||
|
||||
- How to signal NSON support? I thought it would work to have an initial field `"n":1` (before `"id"`) on the JSON, which could be read very fast, but I don't know.
|
||||
@@ -1,7 +1,7 @@
|
||||
# NIPs
|
||||
|
||||
NIPs stand for **Nostr Implementation Possibilities**.
|
||||
They exist to document what may be implemented by [Nostr](https://github.com/nostr-protocol/nostr)-compatible _relay_ and _client_ software.
|
||||
They exist to document what may be implemented by [Nostr](https://github.com/fiatjaf/nostr)-compatible _relay_ and _client_ software.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user