Requests
low level
Relay Misbehavior
Relays can misbehave and return events that do not match your query filters.
Usage Example
final response = ndk.requests.query(
filters: [
// Define a filter for the query
Filter(
// Query for fiatjaf npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6
authors: [
'3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d',
],
// Query for text note events (kind 1)
kinds: [Nip01Event.kTextNodeKind],
// Limit the results to 10 events
limit: 10,
),
],
);
int eventCount = 0;
// Process the events as they arrive
await for (final event in response.stream) {
print(event);
eventCount++;
}
When to use
Requests should be used when no other use case fits your needs.
There is .query and .subscription representing the nostr equivalent, .subscription should only be used when absolutely necessary. Many relays limit the amount of simultaneous subscriptions.
Relay Authentication (NIP-42)
Some relays only serve a request to a client that authenticated. A connection
carries at most one identity, chosen when it is opened and immutable for its
whole lifetime, so choosing an identity is really choosing a connection. The
auth parameter says which one a request may use:
final account = Account(
pubkey: myPubkey,
type: AccountType.privateKey,
signer: Bip340EventSigner(privateKey: myPrivkey, publicKey: myPubkey),
// Use NdkEventSigner from ndk_flutter for automatic web/native selection
);
// the account does not have to be one of ndk.accounts: handing it over is
// enough, and it can differ from the one that is logged in
final response = ndk.requests.query(
filter: Filter(kinds: [1059], authors: [myPubkey]),
auth: RelayAuth.allow(account),
);
The three policies
Report an event without ever being attributable for it:
ndk.requests.query(
filter: Filter(kinds: [1], ids: [suspiciousEventId]),
auth: const RelayAuth.never(),
);
Read your own encrypted data, which no relay should serve to anyone else:
ndk.requests.query(
filter: Filter(kinds: [1059], authors: [myPubkey]),
auth: RelayAuth.require(account),
);
What require guarantees, exactly
The request is never written to a connection other than one reserved for that account. It does not mean the relay knows who asked: NIP-42 has no way to authenticate unprompted, so a relay that never sends a challenge never learns the identity. It also does not mean no anonymous socket to that relay exists. NDK connects to its bootstrap relays anonymously at startup, and the JIT engine discovers relays on anonymous connections before routing the request onto the bound one, which costs a second socket to the same relay.
If the account cannot sign, no connection can carry the request at all. Rather
than fall back to the anonymous one, which is what require rules out, it is
sent to no relay and completes right away with whatever the local cache held.
Its timeout never fires: nothing timed out, the request was impossible from the
start.
One request, one identity
A request authenticates as at most one account. To read data belonging to several identities, issue one request per identity: each gets its own connection, and a relay never sees two of your identities on the same socket.
The default
Without auth, a request that meets auth-required authenticates as the
currently logged-in account. The relay therefore decides when your identity is
revealed. Pass auth explicitly whenever that matters. This default is
expected to change.
Migrating from authenticateAs
authenticateAs is deprecated. It is translated into a policy, and auth wins
when both are given:
A list never authenticated as more than one identity: only the first account
that could sign was ever used, and that is now what it says. If you passed
[a, b] expecting both to be covered, issue one request per identity, each
with its own auth.
One behaviour did change. authenticateAs opened an authenticated connection
before any relay asked for one, which revealed the identity even to relays that
would have served the request anonymously. allow waits for the refusal.
Event sources
When events are cached, NDK may also persist source relay information.
Current behavior:
- source relays are optional provenance data
- they answer where an event was observed
- they are separate from relay delivery targets
For most apps, normal request and usecase APIs are enough and you do not need to access source relay data directly.