@nostr-dev-kit/ndk
Version:
NDK - Nostr Development Kit
1,349 lines (1,330 loc) • 191 kB
text/typescript
import { EventEmitter } from 'tseep';
import * as nostr_tools_lib_types_nip19_js from 'nostr-tools/lib/types/nip19.js';
import debug$1 from 'debug';
import { LRUCache } from 'typescript-lru-cache';
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,
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,
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,
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,
LegacyCashuWallet = 37375,
GroupMetadata = 39000,// NIP-29
GroupAdmins = 39001,// NIP-29
GroupMembers = 39002,// NIP-29
FollowPack = 39089,
MediaFollowPack = 39092,
AppRecommendation = 31989,
AppHandler = 31990
}
declare const NDKListKinds: NDKKind[];
/**
* 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;
}
declare function profileFromEvent(event: NDKEvent): NDKUserProfile;
declare function serializeProfile(profile: NDKUserProfile): string;
type Hexpubkey = string;
type Npub = string;
type ProfilePointer = {
pubkey: string;
relays?: string[];
nip46?: string[];
};
type EventPointer = {
id: string;
relays?: string[];
author?: string;
kind?: number;
};
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 a follow to this user's contact list
*
* @param newFollow {NDKUser} The user to follow
* @param currentFollowList {Set<NDKUser>} The current follow list
* @param kind {NDKKind} The kind to use for this contact list (defaults to `3`)
* @returns {Promise<boolean>} True if the follow was added, false if the follow already exists
*/
follow(newFollow: NDKUser, currentFollowList?: Set<NDKUser>, kind?: NDKKind): Promise<boolean>;
/**
* Remove a follow from this user's contact list
*
* @param user {NDKUser} The user to unfollow
* @param currentFollowList {Set<NDKUser>} The current follow list
* @param kind {NDKKind} The kind to use for this contact list (defaults to `3`)
* @returns The relays were the follow list was published or false if the user wasn't found
*/
unfollow(user: NDKUser, currentFollowList?: Set<NDKUser>, 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 for NDK signers.
*/
/**
* Interface for a serialized signer payload
*/
interface NDKSignerPayload {
type: string;
payload: string;
}
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;
}
/**
* Interface for Signer classes that support static deserialization
*/
interface NDKSignerStatic<T extends NDKSigner> {
/**
* Deserializes the signer from a payload string.
* @param payload The JSON string obtained from toPayload().
* @param ndk Optional NDK instance, required by some signers (e.g., NIP-46).
* @returns An instance of the specific signer class.
*/
fromPayload(payload: string, ndk?: NDK): Promise<T>;
}
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>;
poolBlacklistRelayUrls: Set<string>;
private debug;
private temporaryRelayTimers;
private flappingRelays;
private backoffTimes;
private ndk;
private disconnectionTimes;
private systemEventDetector?;
get blacklistRelayUrls(): Set<string>;
/**
* @param relayUrls - The URLs of the relays to connect to.
* @param blacklistedRelayUrls - URLs to blacklist for this pool IN ADDITION to those blacklisted at the ndk-level
* @param ndk - The NDK instance.
* @param opts - Options for the pool.
*/
constructor(relayUrls: WebSocket["url"][], blacklistedRelayUrls: 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>;
/**
* This policy will disconnect from relays that request authentication.
*/
declare function disconnect(pool: NDKPool, debug?: debug.Debugger): (relay: NDKRelay) => Promise<void>;
type ISignIn = {
ndk?: NDK;
signer?: NDKSigner;
debug?: debug.Debugger;
};
/**
* Uses the signer to sign an event and then authenticate with the relay.
* If no signer is provided the NDK signer will be used.
* If none is not available it will wait for one to be ready.
*/
declare function signIn({ ndk, signer, debug }?: ISignIn): (relay: NDKRelay, challenge: string) => Promise<NDKEvent>;
declare const NDKRelayAuthPolicies: {
disconnect: typeof disconnect;
signIn: typeof signIn;
};
type NDKFilterFingerprint = string;
/**
* Creates a fingerprint for this filter
*
* This a deterministic association of the filters
* used in a filters. When the combination of filters makes it
* possible to group them, the fingerprint is used to group them.
*
* The different filters in the array are differentiated so that
* filters can only be grouped with other filters that have the same signature
*
* The calculated group ID uses a + prefix to avoid grouping subscriptions
* that intend to close immediately after EOSE and those that are probably
* going to be kept open.
*
* @returns The fingerprint, or undefined if the filters are not groupable.
*/
declare function filterFingerprint(filters: NDKFilter[], closeOnEose: boolean): NDKFilterFingerprint | undefined;
/**
* Go through all the passed filters, which should be
* relatively similar, and merge them.
*/
declare function mergeFilters(filters: NDKFilter[]): NDKFilter[];
type NDKSubscriptionId = string;
/**
* This class monitors active subscriptions.
*/
declare class NDKSubscriptionManager {
subscriptions: Map<NDKSubscriptionId, NDKSubscription>;
seenEvents: Map<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 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
*/
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;
/**
* 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 number of matching events.
* @throws {Error} If attempting to send the count request on a closed relay connection.
*/
count(filters: NDKFilter[], params: {
id?: string | null;
}): Promise<number>;
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;
}
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;
}
/**
* SignatureVerificationStats - A class to track and report signature verification statistics
* for all relays in an NDK instance.
*/
declare class SignatureVerificationStats {
private ndk;
private debug;
private intervalId;
private intervalMs;
/**
* Creates a new SignatureVerificationStats instance
*
* @param ndk - The NDK instance to track stats for
* @param intervalMs - How often to print stats (in milliseconds)
*/
constructor(ndk: NDK, intervalMs?: number);
/**
* Start tracking and reporting signature verification statistics
*/
start(): void;
/**
* Stop tracking and reporting signature verification statistics
*/
stop(): void;
/**
* Report current signature verification statistics for all relays
*/
reportStats(): void;
/**
* Collect statistics from all relays
*/
private collectStats;
}
/**
* Create and start a signature verification stats tracker for the given NDK instance
*
* @param ndk - The NDK instance to track stats for
* @param intervalMs - How often to print stats (in milliseconds)
* @returns The created SignatureVerificationStats instance
*/
declare function startSignatureVerificationStats(ndk: NDK, intervalMs?: number): SignatureVerificationStats;
/** @deprecated Use `WebSocket['url']` instead. */
type NDKRelayUrl = WebSocket["url"];
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;
/**
* 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;
}
/**
* Creates a NDKRelaySet for the specified event.
* TODO: account for relays where tagged pubkeys or hashtags
* tend to write to.
* @param ndk {NDK}
* @param event {Event}
* @returns Promise<NDKRelaySet>
*/
declare function calculateRelaySetFromEvent(ndk: NDK, event: NDKEvent, requiredRelayCount?: number): Promise<NDKRelaySet>;
declare class NDKPublishError extends Error {
errors: Map<NDKRelay, Error>;
publishedToRelays: Set<NDKRelay>;
/**
* Intended relay set where the publishing was intended to happen.
*/
intendedRelaySet?: NDKRelaySet;
constructor(message: string, errors: Map<NDKRelay, Error>, publishedToRelays: Set<NDKRelay>, intendedRelaySet?: NDKRelaySet);
get relayErrors(): string;
}
/**
* A relay set is a group of relays. This grouping can be short-living, for a single
* REQ or can be long-lasting, for example for the explicit relay list the user
* has specified.
*
* Requests to relays should be sent through this interface.
*/
declare class NDKRelaySet {
readonly relays: Set<NDKRelay>;
private debug;
private ndk;
private pool;
constructor(relays: Set<NDKRelay>, ndk: NDK, pool?: NDKPool);
/**
* Adds a relay to this set.
*/
addRelay(relay: NDKRelay): void;
get relayUrls(): WebSocket["url"][];
/**
* Creates a relay set from a list of relay URLs.
*
* If no connection to the relay is found in the pool it will temporarily
* connect to it.
*
* @param relayUrls - list of relay URLs to include in this set
* @param ndk
* @param connect - whether to connect to the relay immediately if it was already in the pool but not connected
* @returns NDKRelaySet
*/
static fromRelayUrls(relayUrls: readonly string[], ndk: NDK, connect?: boolean, pool?: NDKPool): NDKRelaySet;
/**
* Publish an event to all relays in this relay set.
*
* This method implements a robust mechanism for publishing events to multiple relays with
* built-in handling for race conditions, timeouts, and partial failures. The implementation
* uses a dual-tracking mechanism to ensure accurate reporting of which relays successfully
* received an event.
*
* Key aspects of this implementation:
*
* 1. DUAL-TRACKING MECHANISM:
* - Promise-based tracking: Records successes/failures from the promises returned by relay.publish()
* - Event-based tracking: Listens for 'relay:published' events that indicate successful publishing
* This approach ensures we don't miss successful publishes even if there are subsequent errors in
* the promise chain.
*
* 2. RACE CONDITION HANDLING:
* - If a relay emits a success event but later fails in the promise chain, we still count it as a success
* - If a relay times out after successfully publishing, we still count it as a success
* - All relay operations happen in parallel, with proper tracking regardless of completion order
*
* 3. TIMEOUT MANAGEMENT:
* - Individual timeouts for each relay operation
* - Proper cleanup of timeouts to prevent memory leaks
* - Clear timeout error reporting
*
* 4. ERROR HANDLING:
* - Detailed tracking of specific errors for each failed relay
* - Special handling for ephemeral events (which don't expect acknowledgement)
* - RequiredRelayCount parameter to control the minimum success threshold
*
* @param event Event to publish
* @param timeoutMs Timeout in milliseconds for each relay publish operation
* @param requiredRelayCount The minimum number of relays we expect the event to be published to
* @returns A set of relays the event was published to
* @throws {NDKPublishError} If the event could not be published to at least `requiredRelayCount` relays
* @example
* ```typescript
* const relaySet = new NDKRelaySet(new Set([relay1, relay2]), ndk);
* const publishedToRelays = await relaySet.publish(event);
* // publishedToRelays can contain relay1, relay2, both, or none
* // depending on which relays the event was successfully published to
* if (publishedToRelays.size > 0) {
* console.log("Event published to at least one relay");
* }
* ```
*/
publish(event: NDKEvent, timeoutMs?: number, requiredRelayCount?: number): Promise<Set<NDKRelay>>;
get size(): number;
}
/**
* Options on how to handle when a relay hint doesn't respond
* with the requested event.
*
* When a tag includes a relay hint, and the relay hint doesn't come back
* with the event, the fallback options are used to try to fetch the event
* from somewhere else.
*/
type NDKFetchFallbackOptions = {
/**
* Relay set to use as a fallback when the hint relay doesn't respond.
* If not provided, the normal NDK calculation is used (whether explicit relays or outbox calculation)
* Default is `undefined`.
*/
relaySet?: NDKRelaySet;
/**
* Type of fallback to use when the hint relay doesn't respond.
* - "timeout" will wait for a timeout before falling back
* - "eose" will wait for the EOSE before falling back
* - "none" will not fall back
* Default is "timeout".
*/
type: "timeout" | "eose" | "none";
/**
* Timeout in milliseconds for the fallback relay.
* Default is 1500ms.
*/
timeout?: number;
};
type NDKZapConfirmationLN = {
preimage: string;
};
type NDKPaymentConfirmationLN = {
preimage: string;
};
type LNPaymentRequest = string;
type LnPaymentInfo = {
pr: LNPaymentRequest;
};
type NDKLUD18ServicePayerData = Partial<{
name: {
mandatory: boolean;
};
pubkey: {
mandatory: boolean;
};
identifier: {
mandatory: boolean;
};
email: {
mandatory: boolean;
};
auth: {
mandatory: boolean;
k1: string;
};
}> & Record<string, unknown>;
type NDKLnUrlData = {
tag: string;
callback: string;
minSendable: number;
maxSendable: number;
metadata: string;
payerData?: NDKLUD18ServicePayerData;
commentAllowed?: number;
/**
* Pubkey of the zapper that should publish zap receipts for this user
*/
nostrPubkey?: Hexpubkey;
allowsNostr?: boolean;
};
declare function getNip57ZapSpecFromLud({ lud06, lud16 }: {
lud06?: string;
lud16?: string;
}, ndk: NDK): Promise<NDKLnUrlData | undefined>;
type NDKCacheEntry<T> = T & {
cachedAt?: number;
};
interface NDKCacheAdapter {
/**
* Whether this cache adapter is expected to be fast.
* If this is true, the cache will be queried before the relays.
* When this is false, the cache will be queried in addition to the relays.
*/
locking: boolean;
/**
* Weather the cache is ready.
*/
ready?: boolean;
initializeAsync?(ndk: NDK): Promise<void>;
initialize?(ndk: NDK): void;
/**
* Either synchronously or asynchronously queries the cache.
*
* Cache adapters that return values synchronously should return an array of events.
* Asynchronous cache adapters should call the subscription.eventReceived method for each event.
*/
query(subscription: NDKSubscription): NDKEvent[] | Promise<NDKEvent[]>;
setEvent(event: NDKEvent, filters: NDKFilter[], relay?: NDKRelay): Promise<void>;
/**
* Called when an event is deleted by the client.
* Cache adapters should remove the event from their cache.
* @param eventIds - The ids of the events that were deleted.
*/
deleteEventIds?(eventIds: NDKEventId[]): Promise<void>;
/**
* Fetches a profile from the cache synchronously.
* @param pubkey - The pubkey of the profile to fetch.
* @returns The profile, or null if it is not in the cache.
*/
fetchProfileSync?(pubkey: Hexpubkey): NDKCacheEntry<NDKUserProfile> | null;
/**
* Retrieve all profiles from the cache synchronously.
* @returns A map of pubkeys to profiles.
*/
getAllProfilesSync?(): Map<Hexpubkey, NDKCacheEntry<NDKUserProfile>>;
/**
* Special purpose
*/
fetchProfile?(pubkey: Hexpubkey): Promise<NDKCacheEntry<NDKUserProfile> | null>;
saveProfile?(pubkey: Hexpubkey, profile: NDKUserProfile): void;
/**
* Fetches profiles that match the given filter.
* @param filter
* @returns NDKUserProfiles that match the filter.
* @example
* const searchFunc = (pubkey, profile) => profile.name.toLowerCase().includes("alice");
* const allAliceProfiles = await cache.getProfiles(searchFunc);
*/
getProfiles?: (filter: (pubkey: Hexpubkey, profile: NDKUserProfile) => boolean) => Promise<Map<Hexpubkey, NDKUserProfile> | undefined>;
loadNip05?(nip05: string, maxAgeForMissing?: number): Promise<ProfilePointer | null | "missing">;
saveNip05?(nip05: string, profile: ProfilePointer | null): void;
/**
* Fetches a user's LNURL data from the cache.
* @param pubkey The pubkey of the user to fetch the LNURL data for.
* @param maxAgeInSecs The maximum age of the data in seconds.
* @param maxAgeForMissing The maximum age of the data in seconds if it is missing before it returns that it should be refetched.
* @returns The LNURL data, null if it is not in the cache and under the maxAgeForMissing, or "missing" if i