Chapter 67 - Relay Ops
67
Nostr Implementation Possibility - Hex № 67

EOSE Completeness Hint

One extra element on the EOSE message tells the client whether the relay sent everything it has — no more pagination guesswork.

1 Overview

This NIP extends the EOSE message from NIP-01. A relay may append a third element: an array of hint strings. The hints tell the client whether the relay sent every stored event that matches the subscription's filters.

Plain EOSE marks the boundary between stored events and real-time events. It does not say whether more stored events exist. Relays commonly enforce an internal cap, for example 500 events per filter. This cap is independent of the client's limit. So clients today guess completeness with a heuristic. If fewer than limit events arrive, the client assumes the result is complete. Otherwise the client sends another REQ with until set to the oldest received created_at, and repeats.

This heuristic fails in two known ways. First, when the relay's internal cap is lower than the client's limit — cap 300, client asks for 500 — the client receives 300 events and wrongly concludes the result is complete. It silently misses data. Second, when the count of matching events equals the relay's cap exactly, the client cannot tell. It must send one more REQ just to observe zero results. A relay that caps at 300 is always queried at least twice for any subscription that exhausts the cap.

A single extra field on EOSE removes this ambiguity.

Hint!

Read the subscription lifecycle in NIP-01 first. This chapter changes exactly one message in that flow: the EOSE that ends the stored-event phase.

2 The Hinted EOSE Message

A relay that implements this NIP MAY append a third element to EOSE. The element is an array of hint strings:

Wire format - extended EOSE
["EOSE", <subscription_id>, [<hint>, ...]]

The spec currently defines three hint values:

Hint values
HintMeaningClient action
finishThe relay sent every stored event that matches the subscription's filters.The client SHOULD NOT paginate this subscription further.
moreThe relay holds more matching stored events than it sent. A relay MAY send this hint but is not required to. Detecting whether more events exist is not cheap on every storage backend.The client SHOULD paginate to retrieve the rest.
authThe relay may hold more matching stored events that unlock after NIP-42 AUTH. The relay MUST send an AUTH message with a challenge before the EOSE that carries this hint.Authenticate, then query again.

The array MAY carry multiple hints at the same time. The array MAY be empty. Clients MUST ignore unknown hint values without error. Future revisions of this NIP may define more hint values. Implementations SHOULD accept unknown hints and treat them as if absent.

The hints describe stored events only. They have no effect on real-time delivery. New events keep streaming under the regular NIP-01 rules until the client sends CLOSE or the relay sends CLOSED.

Warning!

Presence is definitive; absence is not. When the relay omits the third element, or sends neither finish nor more, do not assume anything. Fall back to the legacy heuristic: paginate with until = oldest received created_at.

Client Relay ["REQ", "s1", {"kinds":[1],"limit":500}] ["EVENT", "s1", {…}] × 300 (internal cap) ["EOSE", "s1", ["more"]] ["REQ", "s1", {"kinds":[1],"until":<oldest>,"limit":500}] ["EVENT", "s1", {…}] × remaining ["EOSE", "s1", ["finish"]] The client stops paginating. Real-time delivery continues as normal.
Fig. 67-1 — Hinted pagination. The relay's cap is below the client's limit. The more hint keeps the client from stopping early; finish ends the loop.

3 Ties on created_at

When a relay does not send finish, several events may share the oldest created_at in the response. A client that paginates with until = that timestamp risks missing events with the same timestamp. So relays SHOULD, when possible, advance the internal cursor past the boundary. Include all events with the boundary created_at in one response. Emit finish only when no older events remain.

older newer 3 events, same created_at (boundary) one response covers the whole boundary group
Fig. 67-2 — Boundary group. The relay stretches the response so no event with the boundary timestamp is left behind.

4 Compatibility and Advertisement

The extension is backward compatible in both directions. Clients that do not implement this NIP ignore the extra element. Conforming JSON parsers accept arrays with trailing elements, and existing implementations index EOSE by position. Relays that do not implement this NIP keep sending two-element EOSE messages. Clients then fall back to the legacy heuristic from section 1.

Relays that implement this NIP SHOULD include 67 in the supported_nips field of their NIP-11 relay information document.

Tech Note

The third example in the source spec has mismatched subscription IDs: the REQ uses "sub2b" while the EVENT, AUTH response, and EOSE use "sub4". It also reuses "sub2b" from the second example. The transcripts below use one consistent ID per session.

5 Worked Examples

Three sessions against one relay. Orange lines go to the relay. Blue lines come from the relay.

Session transcript - wss://relay.example.com
-- 1. The relay sent every matching stored event --
→ ["REQ", "sub2", {"ids": ["abcd…"]}]
← ["EVENT", "sub2", {"id":"abcd…", "kind":1, "content":"hello", …}]
← ["EOSE", "sub2", ["finish"]]
-- the client does not paginate further --

-- 2. The relay holds more matching events --
→ ["REQ", "sub2b", {"authors": ["abcd…"], "kinds": [1]}]
← ["EVENT", "sub2b", {…}]
   … 300 events total (internal cap) …
← ["EOSE", "sub2b", ["more"]]
→ ["REQ", "sub2b", {"authors": ["abcd…"], "kinds": [1], "until": 1751234567}]

-- 3. More events exist behind AUTH; the public part is complete --
→ ["REQ", "sub4", {"authors": ["abcd…"], "kinds": [1]}]
← ["EVENT", "sub4", {…}]
← ["AUTH", "challenge…"]
← ["EOSE", "sub4", ["auth", "finish"]]
-- AUTH arrived before the EOSE, as required --

In session 3 the array carries two hints. finish says the unauthenticated result is complete. auth says authentication may unlock more. The client authenticates per NIP-42 and queries again.

6 Version History

NIP-67 is young and small. Notable changes, newest first:

  • 20262026-09fiatjaf added the auth hint and the rule that AUTH must precede the hinted EOSE (#2371).
  • Origin2026-06mattn published the initial spec with the finish and more hints (#2317).

Original Proposal - Credits

Authormattn
First commit2026-06-06 — “NIP-67: EOSE Completeness Hint” (PR #2317)
Statusdraft - optional — relays and clients work fine without it
Source67.md on GitHub - full change history

7 See Also

  • NIP-01 — defines REQ, filters, and the base EOSE message this NIP extends.
  • NIP-42 — the AUTH flow referenced by the auth hint.
  • NIP-11 — the relay information document that advertises support via supported_nips.