@nostr-dev-kit/ndk
Version:
NDK - Nostr Development Kit. Includes AI Guardrails to catch common mistakes during development.
1,383 lines (1,366 loc) • 161 kB
text/typescript
import { EventEmitter } from 'tseep';
import debug$1 from 'debug';
import { LRUCache } from 'typescript-lru-cache';
import { nip19 } from 'nostr-tools';
import { EventEmitter as EventEmitter$1 } from 'node:events';
declare enum NDKKind {
Metadata = 0,
Text = 1,
RecommendRelay = 2,
Contacts = 3,
EncryptedDirectMessage = 4,
EventDeletion = 5,
Repost = 6,
Reaction = 7,
BadgeAward = 8,
GroupChat = 9,
Thread = 11,
GroupReply = 12,
GiftWrapSeal = 13,
PrivateDirectMessage = 14,
Image = 20,
Video = 21,
ShortVideo = 22,
Story = 23,
Vanish = 62,
CashuWalletBackup = 375,
GiftWrap = 1059,
GenericRepost = 16,
ChannelCreation = 40,
ChannelMetadata = 41,
ChannelMessage = 42,
ChannelHideMessage = 43,
ChannelMuteUser = 44,
WikiMergeRequest = 818,
GenericReply = 1111,
Media = 1063,
VoiceMessage = 1222,
VoiceReply = 1244,
DraftCheckpoint = 1234,
Task = 1934,
Report = 1984,
Label = 1985,
DVMReqTextExtraction = 5000,
DVMReqTextSummarization = 5001,
DVMReqTextTranslation = 5002,
DVMReqTextGeneration = 5050,
DVMReqImageGeneration = 5100,
DVMReqTextToSpeech = 5250,
DVMReqDiscoveryNostrContent = 5300,
DVMReqDiscoveryNostrPeople = 5301,
DVMReqTimestamping = 5900,
DVMEventSchedule = 5905,
DVMJobFeedback = 7000,
Subscribe = 7001,
Unsubscribe = 7002,
SubscriptionReceipt = 7003,
CashuReserve = 7373,
CashuQuote = 7374,
CashuToken = 7375,
CashuWalletTx = 7376,
GroupAdminAddUser = 9000,
GroupAdminRemoveUser = 9001,
GroupAdminEditMetadata = 9002,
GroupAdminEditStatus = 9006,
GroupAdminCreateGroup = 9007,
GroupAdminRequestJoin = 9021,
MuteList = 10000,
PinList = 10001,
RelayList = 10002,
BookmarkList = 10003,
CommunityList = 10004,
PublicChatList = 10005,
BlockRelayList = 10006,
SearchRelayList = 10007,
SimpleGroupList = 10009,
RelayFeedList = 10012,
InterestList = 10015,
CashuMintList = 10019,
EmojiList = 10030,
DirectMessageReceiveRelayList = 10050,
BlossomList = 10063,
NostrWaletConnectInfo = 13194,
TierList = 17000,
CashuWallet = 17375,
FollowSet = 30000,
CategorizedPeopleList = 30000,// Deprecated but left for backwards compatibility
CategorizedBookmarkList = 30001,// Deprecated but left for backwards compatibility
RelaySet = 30002,
CategorizedRelayList = 30002,// Deprecated but left for backwards compatibility
BookmarkSet = 30003,
/**
* @deprecated Use ArticleCurationSet instead
*/
CurationSet = 30004,// Deprecated but left for backwards compatibility
ArticleCurationSet = 30004,
VideoCurationSet = 30005,
ImageCurationSet = 30006,
InterestSet = 30015,
InterestsList = 30015,// Deprecated but left for backwards compatibility
ProjectTemplate = 30717,
EmojiSet = 30030,
ModularArticle = 30040,
ModularArticleItem = 30041,
Wiki = 30818,
Draft = 31234,
Project = 31933,
SubscriptionTier = 37001,
EcashMintRecommendation = 38000,
CashuMintAnnouncement = 38172,
FedimintMintAnnouncement = 38173,
P2POrder = 38383,
CollaborativeEvent = 39382,
HighlightSet = 39802,
CategorizedHighlightList = 39802,// Deprecated but left for backwards compatibility
Nutzap = 9321,
ZapRequest = 9734,
Zap = 9735,
Highlight = 9802,
ClientAuth = 22242,
NostrWalletConnectReq = 23194,
NostrWalletConnectRes = 23195,
NostrConnect = 24133,
BlossomUpload = 24242,
HttpAuth = 27235,
ProfileBadge = 30008,
BadgeDefinition = 30009,
MarketStall = 30017,
MarketProduct = 30018,
Article = 30023,
AppSpecificData = 30078,
Classified = 30402,
HorizontalVideo = 34235,
VerticalVideo = 34236,
GroupMetadata = 39000,// NIP-29
GroupAdmins = 39001,// NIP-29
GroupMembers = 39002,// NIP-29
FollowPack = 39089,
MediaFollowPack = 39092,
AppRecommendation = 31989,
AppHandler = 31990
}
/**
* Represents an HLL (HyperLogLog) data structure for cardinality estimation.
* Contains 256 uint8 registers as specified in NIP-45.
*/
declare class NDKCountHll {
/**
* The 256 uint8 registers used for HLL estimation
*/
readonly registers: Uint8Array;
constructor(registers?: Uint8Array);
/**
* Creates an NDKCountHll from a hex-encoded string (512 characters).
* Each register is a uint8 value encoded as 2 hex characters.
*
* @param hex - The hex string (512 characters = 256 bytes)
* @returns A new NDKCountHll instance
* @throws Error if the hex string is invalid
*/
static fromHex(hex: string): NDKCountHll;
/**
* Converts the HLL registers to a hex-encoded string.
*
* @returns The hex string representation (512 characters)
*/
toHex(): string;
/**
* Merges this HLL with another HLL by taking the maximum value for each register.
* This is the standard HLL merge operation that allows combining counts
* from multiple relays without double-counting.
*
* @param other - The other HLL to merge with
* @returns A new NDKCountHll with the merged registers
*/
merge(other: NDKCountHll): NDKCountHll;
/**
* Merges multiple HLLs by taking the maximum value for each register.
*
* @param hlls - Array of HLLs to merge
* @returns A new NDKCountHll with the merged registers
*/
static merge(hlls: NDKCountHll[]): NDKCountHll;
/**
* Estimates the cardinality (unique count) using the HyperLogLog algorithm.
*
* Uses the standard HLL formula with bias correction for small and large cardinalities.
*
* @returns The estimated unique count
*/
estimate(): number;
/**
* Checks if this HLL is empty (all registers are zero).
*
* @returns True if all registers are zero
*/
isEmpty(): boolean;
/**
* Creates a copy of this HLL.
*
* @returns A new NDKCountHll with the same register values
*/
clone(): NDKCountHll;
}
/**
* Result of a COUNT request, including optional HLL data.
*/
interface NDKCountResult {
/**
* The count value returned by the relay.
* This is the exact count if HLL is not available,
* or an approximate count if HLL is used.
*/
count: number;
/**
* The HLL data if returned by the relay.
* Can be used to merge counts from multiple relays.
*/
hll?: NDKCountHll;
}
/**
* Aggregated result from multiple relays for a COUNT request.
*/
interface NDKAggregatedCountResult {
/**
* The best estimate of the count.
* If HLL data is available from multiple relays, this is computed from the merged HLL.
* Otherwise, it's the maximum count from all relays.
*/
count: number;
/**
* The merged HLL from all relays that returned HLL data.
* Can be used for further aggregation or analysis.
*/
mergedHll?: NDKCountHll;
/**
* Individual results from each relay.
*/
relayResults: Map<string, NDKCountResult>;
}
/**
* Options for count requests.
*/
interface NDKCountOptions {
/**
* Custom ID for the count request.
*/
id?: string;
/**
* Timeout in milliseconds for the count request.
* @default 5000
*/
timeout?: number;
}
/**
* NDKUserProfile represents a user's kind 0 profile metadata
*/
interface NDKUserProfile {
[key: string]: string | number | undefined;
created_at?: number;
name?: string;
displayName?: string;
/**
* @deprecated Use picture instead
*/
image?: string;
picture?: string;
banner?: string;
bio?: string;
nip05?: string;
lud06?: string;
lud16?: string;
about?: string;
website?: string;
profileEvent?: string;
}
type Hexpubkey = string;
type Npub = string;
type ProfilePointer = {
pubkey: string;
relays?: string[];
nip46?: string[];
};
interface NDKUserParams {
npub?: Npub;
hexpubkey?: Hexpubkey;
pubkey?: Hexpubkey;
nip05?: string;
relayUrls?: string[];
nip46Urls?: string[];
nprofile?: string;
}
/**
* Represents a pubkey.
*/
declare class NDKUser {
ndk: NDK | undefined;
profile?: NDKUserProfile;
profileEvent?: NDKEvent;
private _npub?;
private _pubkey?;
relayUrls: string[];
readonly nip46Urls: string[];
constructor(opts: NDKUserParams);
get npub(): string;
get nprofile(): string;
set npub(npub: Npub);
/**
* Get the user's pubkey
* @returns {string} The user's pubkey
*/
get pubkey(): string;
/**
* Set the user's pubkey
* @param pubkey {string} The user's pubkey
*/
set pubkey(pubkey: string);
/**
* Equivalent to NDKEvent.filters().
* @returns {NDKFilter}
*/
filter(): NDKFilter;
/**
* Gets NIP-57 and NIP-61 information that this user has signaled
*
* @param getAll {boolean} Whether to get all zap info or just the first one
*/
getZapInfo(timeoutMs?: number): Promise<Map<NDKZapMethod, NDKZapMethodInfo>>;
/**
* Instantiate an NDKUser from a NIP-05 string
* @param nip05Id {string} The user's NIP-05
* @param ndk {NDK} An NDK instance
* @param skipCache {boolean} Whether to skip the cache or not
* @returns {NDKUser | undefined} An NDKUser if one is found for the given NIP-05, undefined otherwise.
*/
static fromNip05(nip05Id: string, ndk: NDK, skipCache?: boolean): Promise<NDKUser | undefined>;
/**
* Fetch a user's profile
* @param opts {NDKSubscriptionOptions} A set of NDKSubscriptionOptions
* @param storeProfileEvent {boolean} Whether to store the profile event or not
* @returns User Profile
*/
fetchProfile(opts?: NDKSubscriptionOptions, storeProfileEvent?: boolean): Promise<NDKUserProfile | null>;
/**
* Returns a set of users that this user follows.
*
* @deprecated Use followSet instead
*/
follows: (opts?: NDKSubscriptionOptions | undefined, outbox?: boolean | undefined, kind?: number | undefined) => Promise<Set<NDKUser>>;
/**
* Returns a set of pubkeys that this user follows.
*
* @param opts - NDKSubscriptionOptions
* @param outbox - boolean
* @param kind - number
*/
followSet(opts?: NDKSubscriptionOptions, outbox?: boolean, kind?: number): Promise<Set<Hexpubkey>>;
/** @deprecated Use referenceTags instead. */
/**
* Get the tag that can be used to reference this user in an event
* @returns {NDKTag} an NDKTag
*/
tagReference(): NDKTag;
/**
* Get the tags that can be used to reference this user in an event
* @returns {NDKTag[]} an array of NDKTag
*/
referenceTags(marker?: string): NDKTag[];
/**
* Publishes the current profile.
*/
publish(): Promise<void>;
/**
* Add one or more follows to this user's contact list
*
* @param newFollow {NDKUser | Hexpubkey | Array} The user(s) to follow
* @param currentFollowList {Set<NDKUser | Hexpubkey>} The current follow list
* @param kind {NDKKind} The kind to use for this contact list (defaults to `3`)
* @returns {Promise<boolean>} True if any follows were added, false if all already exist
*/
follow(newFollow: NDKUser | Hexpubkey | (NDKUser | Hexpubkey)[], currentFollowList?: Set<NDKUser | Hexpubkey>, kind?: NDKKind): Promise<boolean>;
/**
* Remove one or more follows from this user's contact list
*
* @param user {NDKUser | Hexpubkey | Array} The user(s) to unfollow
* @param currentFollowList {Set<NDKUser | Hexpubkey>} The current follow list
* @param kind {NDKKind} The kind to use for this contact list (defaults to `3`)
* @returns The relays where the follow list was published or false if none were found
*/
unfollow(user: NDKUser | Hexpubkey | (NDKUser | Hexpubkey)[], currentFollowList?: Set<NDKUser | Hexpubkey>, kind?: NDKKind): Promise<Set<NDKRelay> | boolean>;
/**
* Validate a user's NIP-05 identifier (usually fetched from their kind:0 profile data)
*
* @param nip05Id The NIP-05 string to validate
* @returns {Promise<boolean | null>} True if the NIP-05 is found and matches this user's pubkey,
* False if the NIP-05 is found but doesn't match this user's pubkey,
* null if the NIP-05 isn't found on the domain or we're unable to verify (because of network issues, etc.)
*/
validateNip05(nip05Id: string): Promise<boolean | null>;
}
interface NDKSigner {
/**
* Synchronously get the public key of the signer.
* @throws {Error} "Not ready" when the signer is not ready to provide a pubkey synchronously (e.g., NIP-07 or NIP-46 signers)
* @returns The public key in hex format
*/
get pubkey(): string;
/**
* Blocks until the signer is ready and returns the associated NDKUser.
* @returns A promise that resolves to the NDKUser instance.
*/
blockUntilReady(): Promise<NDKUser>;
/**
* Getter for the user property.
* @returns A promise that resolves to the NDKUser instance.
*/
user(): Promise<NDKUser>;
get userSync(): NDKUser;
/**
* Signs the given Nostr event.
* @param event - The Nostr event to be signed.
* @returns A promise that resolves to the signature of the signed event.
*/
sign(event: NostrEvent): Promise<string>;
/**
* Getter for the preferred relays.
* @returns A promise containing a simple map of preferred relays and their read/write policies.
*/
relays?(ndk?: NDK): Promise<NDKRelay[]>;
/**
* Determine the types of encryption (by nip) that this signer can perform.
* Implementing classes SHOULD return a value even for legacy (only nip04) third party signers.
* @nip Optionally returns an array with single supported nip or empty, to check for truthy or falsy.
* @return A promised list of any (or none) of these strings ['nip04', 'nip44']
*/
encryptionEnabled?(scheme?: NDKEncryptionScheme): Promise<NDKEncryptionScheme[]>;
/**
* Encrypts the given Nostr event for the given recipient.
* Implementing classes SHOULD equate legacy (only nip04) to nip == `nip04` || undefined
* @param recipient - The recipient (pubkey or conversationKey) of the encrypted value.
* @param value - The value to be encrypted.
* @param nip - which NIP is being implemented ('nip04', 'nip44')
*/
encrypt(recipient: NDKUser, value: string, scheme?: NDKEncryptionScheme): Promise<string>;
/**
* Decrypts the given value.
* Implementing classes SHOULD equate legacy (only nip04) to nip == `nip04` || undefined
* @param sender - The sender (pubkey or conversationKey) of the encrypted value
* @param value - The value to be decrypted
* @param scheme - which NIP is being implemented ('nip04', 'nip44', 'nip49')
*/
decrypt(sender: NDKUser, value: string, scheme?: NDKEncryptionScheme): Promise<string>;
/**
* Serializes the signer's essential data into a storable format.
* @returns A JSON string containing the type and payload.
*/
toPayload(): string;
}
type NDKPoolStats = {
total: number;
connected: number;
disconnected: number;
connecting: number;
};
/**
* Handles connections to all relays. A single pool should be used per NDK instance.
*
* @emit connecting - Emitted when a relay in the pool is connecting.
* @emit connect - Emitted when all relays in the pool are connected, or when the specified timeout has elapsed, and some relays are connected.
* @emit notice - Emitted when a relay in the pool sends a notice.
* @emit flapping - Emitted when a relay in the pool is flapping.
* @emit relay:connect - Emitted when a relay in the pool connects.
* @emit relay:ready - Emitted when a relay in the pool is ready to serve requests.
* @emit relay:disconnect - Emitted when a relay in the pool disconnects.
*/
declare class NDKPool extends EventEmitter<{
notice: (relay: NDKRelay, notice: string) => void;
flapping: (relay: NDKRelay) => void;
connect: () => void;
"relay:connecting": (relay: NDKRelay) => void;
/**
* Emitted when a relay in the pool connects.
* @param relay - The relay that connected.
*/
"relay:connect": (relay: NDKRelay) => void;
"relay:ready": (relay: NDKRelay) => void;
"relay:disconnect": (relay: NDKRelay) => void;
"relay:auth": (relay: NDKRelay, challenge: string) => void;
"relay:authed": (relay: NDKRelay) => void;
}> {
private _relays;
private status;
autoConnectRelays: Set<string>;
private debug;
private temporaryRelayTimers;
private flappingRelays;
private backoffTimes;
private ndk;
private disconnectionTimes;
private systemEventDetector?;
/**
* @param relayUrls - The URLs of the relays to connect to.
* @param ndk - The NDK instance.
* @param opts - Options for the pool.
*/
constructor(relayUrls: WebSocket["url"][], ndk: NDK, { debug, name, }?: {
debug?: debug$1.Debugger;
name?: string;
});
get relays(): Map<string, NDKRelay>;
set relayUrls(urls: WebSocket["url"][]);
private _name;
get name(): string;
set name(name: string);
/**
* Adds a relay to the pool, and sets a timer to remove it if it is not used within the specified time.
* @param relay - The relay to add to the pool.
* @param removeIfUnusedAfter - The time in milliseconds to wait before removing the relay from the pool after it is no longer used.
*/
useTemporaryRelay(relay: NDKRelay, removeIfUnusedAfter?: number, filters?: NDKFilter[] | string): void;
/**
* Adds a relay to the pool.
*
* @param relay - The relay to add to the pool.
* @param connect - Whether or not to connect to the relay.
*/
addRelay(relay: NDKRelay, connect?: boolean): void;
/**
* Removes a relay from the pool.
* @param relayUrl - The URL of the relay to remove.
* @returns {boolean} True if the relay was removed, false if it was not found.
*/
removeRelay(relayUrl: string): boolean;
/**
* Checks whether a relay is already connected in the pool.
*/
isRelayConnected(url: WebSocket["url"]): boolean;
/**
* Fetches a relay from the pool, or creates a new one if it does not exist.
*
* New relays will be attempted to be connected.
*/
getRelay(url: WebSocket["url"], connect?: boolean, temporary?: boolean, filters?: NDKFilter[]): NDKRelay;
private handleRelayConnect;
private handleRelayReady;
/**
* Attempts to establish a connection to each relay in the pool.
*
* @async
* @param {number} [timeoutMs] - Optional timeout in milliseconds for each connection attempt.
* @returns {Promise<void>} A promise that resolves when all connection attempts have completed.
* @throws {Error} If any of the connection attempts result in an error or timeout.
*/
connect(timeoutMs?: number): Promise<void>;
private checkOnFlappingRelays;
/**
* Records when a relay disconnects to detect system-wide events
*/
private recordDisconnection;
/**
* Checks if multiple relays disconnected simultaneously, indicating a system event
*/
private checkForSystemWideDisconnection;
/**
* Handles system-wide reconnection (e.g., after sleep/wake or network change)
*/
private handleSystemWideReconnection;
private handleFlapping;
size(): number;
/**
* Returns the status of each relay in the pool.
* @returns {NDKPoolStats} An object containing the number of relays in each status.
*/
stats(): NDKPoolStats;
connectedRelays(): NDKRelay[];
permanentAndConnectedRelays(): NDKRelay[];
/**
* Get a list of all relay urls in the pool.
*/
urls(): string[];
}
/**
* NDKAuthPolicies are functions that are called when a relay requests authentication
* so that you can define a behavior for your application.
*
* @param relay The relay that requested authentication.
* @param challenge The challenge that the relay sent.
*/
type NDKAuthPolicy = (relay: NDKRelay, challenge: string) => Promise<boolean | undefined | NDKEvent>;
type NDKFilterFingerprint = string;
type NDKSubscriptionId = string;
/**
* This class monitors active subscriptions.
*/
declare class NDKSubscriptionManager {
subscriptions: Map<NDKSubscriptionId, NDKSubscription>;
seenEvents: LRUCache<string, NDKRelay[]>;
constructor();
add(sub: NDKSubscription): void;
seenEvent(eventId: NDKEventId, relay: NDKRelay): void;
/**
* Whenever an event comes in, this function is called.
* This function matches the received event against all the
* known (i.e. active) NDKSubscriptions, and if it matches,
* it sends the event to the subscription.
*
* This is the single place in the codebase that matches
* incoming events with parties interested in the event.
*
* This is also what allows for reactivity in NDK apps, such that
* whenever an active subscription receives an event that some
* other active subscription would want to receive, both receive it.
*
* TODO This also allows for subscriptions that overlap in meaning
* to be collapsed into one.
*
* I.e. if a subscription with filter: kinds: [1], authors: [alice]
* is created and EOSEs, and then a subsequent subscription with
* kinds: [1], authors: [alice] is created, once the second subscription
* EOSEs we can safely close it, increment its refCount and close it,
* and when the first subscription receives a new event from Alice this
* code will make the second subscription receive the event even though
* it has no active subscription on a relay.
* @param event Raw event received from a relay
* @param relay Relay that sent the event
* @param optimisticPublish Whether the event is coming from an optimistic publish
*/
dispatchEvent(event: NostrEvent, relay?: NDKRelay, optimisticPublish?: boolean): void;
}
type Item = {
subscription: NDKSubscription;
filters: NDKFilter[];
};
declare enum NDKRelaySubscriptionStatus {
INITIAL = 0,
/**
* The subscription is pending execution.
*/
PENDING = 1,
/**
* The subscription is waiting for the relay to be ready.
*/
WAITING = 2,
/**
* The subscription is currently running.
*/
RUNNING = 3,
CLOSED = 4
}
/**
* Groups together a number of NDKSubscriptions (as created by the user),
* filters (as computed internally), executed, or to be executed, within
* a single specific relay.
*/
declare class NDKRelaySubscription {
fingerprint: NDKFilterFingerprint;
items: Map<NDKSubscriptionInternalId, Item>;
topSubManager: NDKSubscriptionManager;
debug: debug.Debugger;
/**
* Tracks the status of this REQ.
*/
status: NDKRelaySubscriptionStatus;
onClose?: (sub: NDKRelaySubscription) => void;
private relay;
/**
* Whether this subscription has reached EOSE.
*/
private eosed;
/**
* Timeout at which this subscription will
* start executing.
*/
private executionTimer?;
/**
* Track the time at which this subscription will fire.
*/
private fireTime?;
/**
* The delay type that the current fireTime was calculated with.
*/
private delayType?;
/**
* The filters that have been executed.
*/
executeFilters?: NDKFilter[];
readonly id: string;
/**
*
* @param fingerprint The fingerprint of this subscription.
*/
constructor(relay: NDKRelay, fingerprint: NDKFilterFingerprint | null, topSubManager: NDKSubscriptionManager);
private _subId?;
get subId(): string;
private subIdParts;
private addSubIdPart;
addItem(subscription: NDKSubscription, filters: NDKFilter[]): void;
/**
* A subscription has been closed, remove it from the list of items.
* @param subscription
*/
removeItem(subscription: NDKSubscription): void;
private close;
cleanup(): void;
private evaluateExecutionPlan;
private schedule;
private executeOnRelayReady;
private finalizeSubId;
private reExecuteAfterAuth;
private execute;
onstart(): void;
onevent(event: NostrEvent): void;
oneose(subId: string): void;
onclose(_reason?: string): void;
onclosed(reason?: string): void;
/**
* Grabs the filters from all the subscriptions
* and merges them into a single filter.
*/
private compileFilters;
}
declare class NDKRelayConnectivity {
private ndkRelay;
private ws?;
private _status;
private timeoutMs?;
private connectedAt?;
private _connectionStats;
private debug;
netDebug?: NDKNetDebug;
private connectTimeout;
private reconnectTimeout;
private ndk?;
openSubs: Map<string, NDKRelaySubscription>;
private openCountRequests;
private openEventPublishes;
private pendingAuthPublishes;
private serial;
baseEoseTimeout: number;
private keepalive?;
private wsStateMonitor?;
private sleepDetector?;
private lastSleepCheck;
private lastMessageSent;
private wasIdle;
constructor(ndkRelay: NDKRelay, ndk?: NDK);
/**
* Sets up keepalive, WebSocket state monitoring, and sleep detection
*/
private setupMonitoring;
/**
* Handles detection of a stale connection by cleaning up and triggering reconnection.
*/
private handleStaleConnection;
/**
* Handles possible system wake event
*/
private handlePossibleWake;
/**
* Resets the reconnection state for system-wide events
* Used by NDKPool when detecting system sleep/wake
*/
resetReconnectionState(): void;
/**
* Connects to the NDK relay and handles the connection lifecycle.
*
* This method attempts to establish a WebSocket connection to the NDK relay specified in the `ndkRelay` object.
* If the connection is successful, it updates the connection statistics, sets the connection status to `CONNECTED`,
* and emits `connect` and `ready` events on the `ndkRelay` object.
*
* If the connection attempt fails, it handles the error by either initiating a reconnection attempt or emitting a
* `delayed-connect` event on the `ndkRelay` object, depending on the `reconnect` parameter.
*
* @param timeoutMs - The timeout in milliseconds for the connection attempt. If not provided, the default timeout from the `ndkRelay` object is used.
* @param reconnect - Indicates whether a reconnection should be attempted if the connection fails. Defaults to `true`.
* @returns A Promise that resolves when the connection is established, or rejects if the connection fails.
*/
connect(timeoutMs?: number, reconnect?: boolean): Promise<void>;
/**
* Disconnects the WebSocket connection to the NDK relay.
* This method sets the connection status to `NDKRelayStatus.DISCONNECTING`,
* attempts to close the WebSocket connection, and sets the status to
* `NDKRelayStatus.DISCONNECTED` if the disconnect operation fails.
*/
disconnect(): void;
/**
* Handles the error that occurred when attempting to connect to the NDK relay.
* If `reconnect` is `true`, this method will initiate a reconnection attempt.
* Otherwise, it will emit a `delayed-connect` event on the `ndkRelay` object,
* indicating that a reconnection should be attempted after a delay.
*
* @param reconnect - Indicates whether a reconnection should be attempted.
*/
onConnectionError(reconnect: boolean): void;
/**
* Handles the connection event when the WebSocket connection is established.
* This method is called when the WebSocket connection is successfully opened.
* It clears any existing connection and reconnection timeouts, updates the connection statistics,
* sets the connection status to `CONNECTED`, and emits `connect` and `ready` events on the `ndkRelay` object.
*/
private onConnect;
/**
* Handles the disconnection event when the WebSocket connection is closed.
* This method is called when the WebSocket connection is successfully closed.
* It updates the connection statistics, sets the connection status to `DISCONNECTED`,
* initiates a reconnection attempt if we didn't disconnect ourselves,
* and emits a `disconnect` event on the `ndkRelay` object.
*/
private onDisconnect;
/**
* Handles incoming messages from the NDK relay WebSocket connection.
* This method is called whenever a message is received from the relay.
* It parses the message data and dispatches the appropriate handling logic based on the message type.
*
* @param event - The MessageEvent containing the received message data.
*/
private onMessage;
/**
* Handles an authentication request from the NDK relay.
*
* If an authentication policy is configured, it will be used to authenticate the connection.
* Otherwise, the `auth` event will be emitted to allow the application to handle the authentication.
*
* @param challenge - The authentication challenge provided by the NDK relay.
*/
private onAuthRequested;
/**
* Handles errors that occur on the WebSocket connection to the relay.
* @param error - The error or event that occurred.
*/
private onError;
/**
* Gets the current status of the NDK relay connection.
* @returns {NDKRelayStatus} The current status of the NDK relay connection.
*/
get status(): NDKRelayStatus;
/**
* Checks if the NDK relay connection is currently available.
* @returns {boolean} `true` if the relay connection is in the `CONNECTED` status, `false` otherwise.
*/
isAvailable(): boolean;
/**
* Checks if the NDK relay connection is flapping, which means the connection is rapidly
* disconnecting and reconnecting. This is determined by analyzing the durations of the
* last three connection attempts. If the standard deviation of the durations is less
* than 1000 milliseconds, the connection is considered to be flapping.
*
* @returns {boolean} `true` if the connection is flapping, `false` otherwise.
*/
private isFlapping;
/**
* Handles a notice received from the NDK relay.
* If the notice indicates the relay is complaining (e.g. "too many" or "maximum"),
* the method disconnects from the relay and attempts to reconnect after a 2-second delay.
* A debug message is logged with the relay URL and the notice text.
* The "notice" event is emitted on the ndkRelay instance with the notice text.
*
* @param notice - The notice text received from the NDK relay.
*/
private onNotice;
/**
* Attempts to reconnect to the NDK relay after a connection is lost.
* This function is called recursively to handle multiple reconnection attempts.
* It checks if the relay is flapping and emits a "flapping" event if so.
* It then calculates a delay before the next reconnection attempt based on the number of previous attempts.
* The function sets a timeout to execute the next reconnection attempt after the calculated delay.
* If the maximum number of reconnection attempts is reached, a debug message is logged.
*
* @param attempt - The current attempt number (default is 0).
*/
private handleReconnection;
/**
* Sends a message to the NDK relay if the connection is in the CONNECTED state and the WebSocket is open.
* If the connection is not in the CONNECTED state or the WebSocket is not open, logs a debug message and throws an error.
*
* @param message - The message to send to the NDK relay.
* @throws {Error} If attempting to send on a closed relay connection.
*/
send(message: string): Promise<void>;
/**
* Authenticates the NDK event by sending it to the NDK relay and returning a promise that resolves with the result.
*
* @param event - The NDK event to authenticate.
* @returns A promise that resolves with the authentication result.
*/
private auth;
/**
* Clears all pending publish promises by rejecting them with the provided error.
* This is called on disconnection to prevent memory leaks and ensure promises
* don't hang indefinitely.
* @param error The error to reject the promises with
*/
private clearPendingPublishes;
/**
* Retries all pending publishes that failed due to auth-required.
* Called after successful authentication.
*/
private retryPendingAuthPublishes;
/**
* Rejects all pending publishes that failed due to auth-required.
* Called when authentication fails.
*/
private rejectPendingAuthPublishes;
/**
* Publishes an NDK event to the relay and returns a promise that resolves with the result.
*
* @param event - The NDK event to publish.
* @returns A promise that resolves with the result of the event publication.
* @throws {Error} If attempting to publish on a closed relay connection.
*/
publish(event: NostrEvent): Promise<string>;
/**
* Counts the number of events that match the provided filters.
*
* @param filters - The filters to apply to the count request.
* @param params - An optional object containing a custom id for the count request.
* @returns A promise that resolves with the count result including optional HLL data.
* @throws {Error} If attempting to send the count request on a closed relay connection.
*/
count(filters: NDKFilter[], params: {
id?: string | null;
}): Promise<NDKCountResult>;
close(subId: string, reason?: string): void;
/**
* Subscribes to the NDK relay with the provided filters and parameters.
*
* @param filters - The filters to apply to the subscription.
* @param params - The subscription parameters, including an optional custom id.
* @returns A new NDKRelaySubscription instance.
*/
req(relaySub: NDKRelaySubscription): void;
/**
* Utility functions to update the connection stats.
*/
private updateConnectionStats;
/** Returns the connection stats. */
get connectionStats(): NDKRelayConnectionStats;
/** Returns the relay URL */
get url(): WebSocket["url"];
get connected(): boolean;
}
/**
* NIP-11: Relay Information Document
* https://github.com/nostr-protocol/nips/blob/master/11.md
*/
interface NDKRelayInformation {
name?: string;
description?: string;
banner?: string;
icon?: string;
pubkey?: string;
contact?: string;
supported_nips?: number[];
software?: string;
version?: string;
privacy_policy?: string;
terms_of_service?: string;
limitation?: {
max_message_length?: number;
max_subscriptions?: number;
max_subid_length?: number;
max_limit?: number;
max_event_tags?: number;
max_content_length?: number;
min_pow_difficulty?: number;
auth_required?: boolean;
payment_required?: boolean;
restricted_writes?: boolean;
created_at_lower_limit?: number;
created_at_upper_limit?: number;
default_limit?: number;
};
retention?: Array<{
kinds?: Array<number | [number, number]>;
time?: number | null;
count?: number;
}>;
relay_countries?: string[];
language_tags?: string[];
tags?: string[];
posting_policy?: string;
payments_url?: string;
fees?: {
admission?: Array<{
amount: number;
unit: string;
}>;
subscription?: Array<{
amount: number;
unit: string;
period: number;
}>;
publication?: Array<{
kinds?: number[];
amount: number;
unit: string;
}>;
};
[key: string]: unknown;
}
type NDKRelayScore = number;
/**
* The subscription manager of an NDKRelay is in charge of orchestrating the subscriptions
* that are created and closed in a given relay.
*
* The manager is responsible for:
* * restarting subscriptions when they are unexpectedly closed
* * scheduling subscriptions that are received before the relay is connected
* * grouping similar subscriptions to be compiled into individual REQs
*/
declare class NDKRelaySubscriptionManager {
private relay;
subscriptions: Map<NDKFilterFingerprint, NDKRelaySubscription[]>;
private generalSubManager;
/**
* @param relay - The relay instance.
* @param generalSubManager - The subscription manager instance.
*/
constructor(relay: NDKRelay, generalSubManager: NDKSubscriptionManager);
/**
* Adds a subscription to the manager.
*/
addSubscription(sub: NDKSubscription, filters: NDKFilter[]): void;
createSubscription(_sub: NDKSubscription, _filters: NDKFilter[], fingerprint?: NDKFilterFingerprint): NDKRelaySubscription;
private onRelaySubscriptionClose;
}
/**
* Protocol handler function type for handling custom relay messages.
* @param relay The relay that received the message
* @param message The parsed message array from the relay
*/
type NDKProtocolHandler = (relay: NDKRelay, message: unknown[]) => void;
declare enum NDKRelayStatus {
DISCONNECTING = 0,// 0
DISCONNECTED = 1,// 1
RECONNECTING = 2,// 2
FLAPPING = 3,// 3
CONNECTING = 4,// 4
CONNECTED = 5,// 5
AUTH_REQUESTED = 6,// 6
AUTHENTICATING = 7,// 7
AUTHENTICATED = 8
}
interface NDKRelayConnectionStats {
/**
* The number of times a connection has been attempted.
*/
attempts: number;
/**
* The number of times a connection has been successfully established.
*/
success: number;
/**
* The durations of the last 100 connections in milliseconds.
*/
durations: number[];
/**
* The time the current connection was established in milliseconds.
*/
connectedAt?: number;
/**
* Timestamp of the next reconnection attempt.
*/
nextReconnectAt?: number;
/**
* Signature validation ratio for this relay.
* @see NDKRelayOptions.validationRatio
*/
validationRatio?: number;
}
/**
* The NDKRelay class represents a connection to a relay.
*
* @emits NDKRelay#connect
* @emits NDKRelay#ready
* @emits NDKRelay#disconnect
* @emits NDKRelay#notice
* @emits NDKRelay#event
* @emits NDKRelay#published when an event is published to the relay
* @emits NDKRelay#publish:failed when an event fails to publish to the relay
* @emits NDKRelay#eose when the relay has reached the end of stored events
* @emits NDKRelay#auth when the relay requires authentication
* @emits NDKRelay#authed when the relay has authenticated
* @emits NDKRelay#delayed-connect when the relay will wait before reconnecting
*/
declare class NDKRelay extends EventEmitter<{
connect: () => void;
ready: () => void;
/**
* Emitted when the relay has reached the end of stored events.
*/
disconnect: () => void;
flapping: (stats: NDKRelayConnectionStats) => void;
notice: (notice: string) => void;
auth: (challenge: string) => void;
authed: () => void;
"auth:failed": (error: Error) => void;
published: (event: NDKEvent) => void;
"publish:failed": (event: NDKEvent, error: Error) => void;
"delayed-connect": (delayInMs: number) => void;
}> {
readonly url: WebSocket["url"];
readonly scores: Map<NDKUser, NDKRelayScore>;
connectivity: NDKRelayConnectivity;
subs: NDKRelaySubscriptionManager;
private publisher;
authPolicy?: NDKAuthPolicy;
/**
* Protocol handlers for custom relay message types (e.g., NEG-OPEN, NEG-MSG).
* Allows external packages to handle non-standard relay messages.
*/
private protocolHandlers;
/**
* Cached relay information from NIP-11.
*/
private _relayInfo?;
/**
* The lowest validation ratio this relay can reach.
*/
lowestValidationRatio?: number;
/**
* Current validation ratio this relay is targeting.
*/
targetValidationRatio?: number;
validationRatioFn?: (relay: NDKRelay, validatedCount: number, nonValidatedCount: number) => number;
/**
* This tracks events that have been seen by this relay
* with a valid signature.
*/
validatedEventCount: number;
/**
* This tracks events that have been seen by this relay
* but have not been validated.
*/
nonValidatedEventCount: number;
/**
* Whether this relay is trusted.
*
* Trusted relay's events do not get their signature verified.
*/
trusted: boolean;
complaining: boolean;
readonly debug: debug$1.Debugger;
static defaultValidationRatioUpdateFn: (relay: NDKRelay, validatedCount: number, _nonValidatedCount: number) => number;
constructor(url: WebSocket["url"], authPolicy: NDKAuthPolicy | undefined, ndk: NDK);
private updateValidationRatio;
get status(): NDKRelayStatus;
get connectionStats(): NDKRelayConnectionStats;
/**
* Connects to the relay.
*/
connect(timeoutMs?: number, reconnect?: boolean): Promise<void>;
/**
* Disconnects from the relay.
*/
disconnect(): void;
/**
* Queues or executes the subscription of a specific set of filters
* within this relay.
*
* @param subscription NDKSubscription this filters belong to.
* @param filters Filters to execute
*/
subscribe(subscription: NDKSubscription, filters: NDKFilter[]): void;
/**
* Publishes an event to the relay with an optional timeout.
*
* If the relay is not connected, the event will be published when the relay connects,
* unless the timeout is reached before the relay connects.
*
* @param event The event to publish
* @param timeoutMs The timeout for the publish operation in milliseconds
* @returns A promise that resolves when the event has been published or rejects if the operation times out
*/
publish(event: NDKEvent, timeoutMs?: number): Promise<boolean>;
referenceTags(): NDKTag[];
addValidatedEvent(): void;
addNonValidatedEvent(): void;
/**
* The current validation ratio this relay has achieved.
*/
get validationRatio(): number;
shouldValidateEvent(): boolean;
get connected(): boolean;
req: (relaySub: NDKRelaySubscription) => void;
close: (subId: string) => void;
/**
* Registers a protocol handler for a specific message type.
* This allows external packages to handle custom relay messages (e.g., NIP-77 NEG-* messages).
*
* @param messageType The message type to handle (e.g., "NEG-OPEN", "NEG-MSG")
* @param handler The function to call when a message of this type is received
*
* @example
* ```typescript
* relay.registerProtocolHandler('NEG-MSG', (relay, message) => {
* console.log('Received NEG-MSG:', message);
* });
* ```
*/
registerProtocolHandler(messageType: string, handler: NDKProtocolHandler): void;
/**
* Unregisters a protocol handler for a specific message type.
*
* @param messageType The message type to stop handling
*/
unregisterProtocolHandler(messageType: string): void;
/**
* Checks if a protocol handler is registered for a message type.
* This is used internally by the connectivity layer to route messages.
*
* @internal
* @param messageType The message type to check
* @returns The handler function if registered, undefined otherwise
*/
getProtocolHandler(messageType: string): NDKProtocolHandler | undefined;
/**
* Fetches relay information (NIP-11) from the relay.
* Results are cached in persistent storage when cache adapter is available (24-hour TTL).
* Falls back to in-memory cache. Pass force=true to bypass all caches.
*
* @param force Force a fresh fetch, bypassing all caches
* @returns The relay information document
* @throws Error if the fetch fails
*
* @example
* ```typescript
* const info = await relay.fetchInfo();
* console.log(`Relay: ${info.name}`);
* console.log(`Supported NIPs: ${info.supported_nips?.join(', ')}`);
* ```
*/
fetchInfo(force?: boolean): Promise<NDKRelayInformation>;
/**
* Returns cached relay information if available, undefined otherwise.
* Use fetchInfo() to retrieve fresh information.
*/
get info(): NDKRelayInformation | undefined;
}
type NDKSubscriptionInternalId = string;
type NDKSubscriptionDelayedType = "at-least" | "at-most";
type NDKFilter<K extends number = NDKKind> = {
ids?: string[];
kinds?: K[];
authors?: string[];
since?: number;
until?: number;
limit?: number;
search?: string;
[key: `#${string}`]: string[] | undefined;
};
declare enum NDKSubscriptionCacheUsage {
ONLY_CACHE = "ONLY_CACHE",
CACHE_FIRST = "CACHE_FIRST",
PARALLEL = "PARALLEL",
ONLY_RELAY = "ONLY_RELAY"
}
interface NDKSubscriptionOptions {
/**
* Whether to close the subscription when all relays have reached the end of the event stream.
* @default false
*/
closeOnEose?: boolean;
cacheUsage?: NDKSubscriptionCacheUsage;
/**
* Whether to skip caching events coming from this subscription
**/
dontSaveToCache?: boolean;
/**
* Groupable subscriptions are created with a slight time
* delayed to allow similar filters to be grouped together.
*/
groupable?: boolean;
/**
* The delay to use when grouping subscriptions, specified in milliseconds.
* @default 100
* @example
* const sub1 = ndk.subscribe({ kinds: [1], authors: ["alice"] }, { groupableDelay: 100 });
* const sub2 = ndk.subscribe({ kinds: [0], authors: ["alice"] }, { groupableDelay: 1000 });
* // sub1 and sub2 will be grouped together and executed 100ms after sub1 was created
*/
groupableDelay?: number;
/**
* Specifies how this delay should be interpreted.
* "at-least" means "wait at least this long before sending the subscription"
* "at-most" means "wait at most this long before sending the subscription"
* @default "at-most"
* @example
* const sub1 = ndk.subscribe({ kinds: [1], authors: ["alice"] }, { groupableDelay: 100, groupableDelayType: "at-least" }); // 3 args
* const sub2 = ndk.subscribe({ kinds: [0], authors: ["alice"] }, { groupableDelay: 1000, groupableDelayType: "at-most" }); // 3 args
* // sub1 and sub2 will be grouped together and executed 1000ms after sub1 was created
*/
groupableDelayType?: NDKSubscriptionDelayedType;
/**
* The subscription ID to use for the subscription.
*/
subId?: string;
/**
* Pool to use
*/
pool?: NDKPool;
/**
* Skip signature verification
* @default false
*/
skipVerification?: boolean;
/**
* Skip event validation. Event validation, checks whether received
* kinds conform to what the expected schema of that kind should look like.rtwle
* @default false
*/
skipValidation?: boolean;
/**
* Skip emitting on events before they are received from a relay. (skip optimistic publish)
* @default false
*/
skipOptimisticPublishEvent?: boolean;
/**
* Remove filter constraints when querying the cache.
*
* This allows setting more aggressive filters that will be removed when hitting the cache.
*
* Useful uses of this include removing `since` or `until` constraints or `limit` filters.
*
* @example
* ndk.subscribe({ kinds: [1], since: 1710000000, limit: 10 }, { cacheUnconstrainFilter: ['since', 'limit'] }); // 3 args
*
* This will hit relays with the since and limit constraints, while loading from the cache without them.
*/
cacheUnconstrainFilter?: (keyof NDKFilter)[];
/**
* Whether to wrap events in kind-specific classes when possible.
* @default false
*/
wrap?: boolean;
/**
* Explicit relay set to use for this subscription instead of calculating it.
* If `relayUrls` is also provided in the options, this `relaySet` takes precedence.
* @since 2.13.0 Moved from `ndk.subscribe` parameter to options.
*/
relaySet?: NDKRelaySet;
/**
* Explicit relay URLs to use for this subscription instead of calculating the relay set.
* An `NDKRelaySet` will be created internally from these URLs.
* If `relaySet` is also provided in the options, the explicit `relaySet` takes precedence over these URLs.
* @since 2.13.0
*/
relayUrls?: string[];
/**
* When set, the cache will be queried first, and, when hitting relays,
* a `since` filter will be added to the subscription that is one second
* after the last event received from the cache.
*
* This option implies cacheUsage: CACHE_FIRST.
*/
addSinceFromCache?: boolean;
/**
* Include muted events in subscription results.
* When false (default), events that match ndk.muteFilter are filtered out.
* When true, muted events are included.
* @default false
*/
includeMuted?: boolean;
/**
* Number of relays to query for each author in the subscription.
* This controls the outbox model relay selection when the filter has authors.
* Hig