New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More β†’
Socket
Sign inDemoInstall
Socket

@nostr-dev-kit/ndk

Package Overview
Dependencies
Maintainers
1
Versions
200
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nostr-dev-kit/ndk

NDK - Nostr Development Kit

  • 0.0.29
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
2.2K
decreased by-21.06%
Maintainers
1
Weekly downloads
Β 
Created
Source

NDK

drawing

NDK is a Nostr development kit that makes the experience of building Nostr-related applications, whether they are relays, clients, or anything in between, better, more reliable and overall nicer to work with than existing solutions.

Besides improving the developer experience, the core goal of NDK is to improve the decentralization of Nostr via intelligent conventions and data discovery features without depending on any one central point of coordination (such as large relays or centralized search providers).

Installation

npm add @nostr-dev-kit/ndk

Support

NDK NIP-28 group chat

  • note15m6rdfvlmd0z836hk83sg7r59xtv23qnmamhsslq5uc6744fdm4qfkeat3
    • Coracle
    • Nostrchat
  • WIP documentation

Features

  • NIP-01
  • Caching adapters
    • Server-side
    • Client-side
      • LocalStorage
      • IndexDB
  • NIP-26: Event delegation
  • NIP-41: Relay authentication
  • NIP-57: Zaps
    • LUD06
    • LUD16
  • NIP-65: Contacts' Relay list
  • Subscription Management
    • Buffered queries
    • Auto-closing subscriptions
  • Signing Adapters
    • Private key
    • NIP-07
    • NIP-26
    • NIP-46
  • Relay discovery
    • Gossip-model (NIP-65)
    • Implicit relays discovery following pubkey usage
    • Implicit relays discovery following t tag usage
    • Explicit relays blacklist
  • nostr-tools/SimplePool drop-in replacement interface

Instantiate an NDK instance

You can pass an object with several options to a newly created instance of NDK.

  • explicitRelayUrls – an array of relay URLs.
  • signer - an instance of a signer.
  • cacheAdapter - an instance of a Cache Adapter
  • debug - boolean true/false to turn on degbugging
// Import the package
import NDK from "@nostr-dev-kit/ndk";
// Create a new NDK instance with explicit relays
ndk = new NDK({ explicitRelayUrls: ["wss://a.relay", "wss://another.relay"] });

Note: In normal client use, it's best practice to instantiate NDK as a singleton class. See more below.

Connecting

After you've instatiated NDK, you need to tell it to connect before you'll be able to interact with any relays.

// Import the package
import NDK from "@nostr-dev-kit/ndk";
// Create a new NDK instance with explicit relays
ndk = new NDK({ explicitRelayUrls: ["wss://a.relay", "wss://another.relay"] });

// Now connect to specified relays
await ndk.connect();

Signers

NDK uses signers optionally passed in to sign events. Note that it is possible to use NDK without signing events (e.g. to get someone's profile).

Signing adapters can be passed in when NDK is instantiated or later during runtime.

Using a NIP-07 browser extension (e.g. Alby, nos2x)

Instatiate NDK with a NIP-07 signer

// Import the package, NIP-07 signer and NDK event
import NDK, { NDKNip07Signer, NDKEvent } from "@nostr-dev-kit/ndk";

const nip07signer = new NDKNip07Signer();
const ndk = new NDK({ signer: nip07signer });

NDK can now ask for permission, via their NIP-07 extension, to...

Read the user's public key

nip07signer.user().then(async (user) => {
    if (!!user.npub) {
        console.log("Permission granted to read their public key:", user.npub);
    }
});

Sign & publish events

const ndkEvent = new NDKEvent(ndk);
ndkEvent.kind = 1;
ndkEvent.content = "Hello, world!";
ndkEvent.publish(); // This will trigger the extension to ask the user to confirm signing.

Caching

NDK provides database-agnostic caching functionality out-of-the-box to improve the performance of your application and reduce load on relays.

NDK will eventually allow you to use multiple caches simultaneously and allow for selective storage of data in the cache store that makes the most sense for your application.

Where to look is more important that long-term storage

The most important data to cache is where a user or note might be found. UX suffers profoundly when this type of data cannot be found. By design, the Nostr protocol leaves beadcrums of where a user or note might be found and NDK does it's best to store this data automatically and use it when you query for events.

Instantiating and using a cache adapter

const redisAdapter = new RedisAdapter(redisUrl);
const ndk = new NDK({ cacheAdapter: redisAdapter });

Buffered queries (COMING SOON)

Clients often need to load data (e.g. profile data) from individual components at once (e.g. initial page render). This typically causes multiple subscriptions to be submitted fetching the same information and causing poor performance or getting rate-limited/maxed out by relays.

NDK implements a convenient subscription model, buffered queries, where a named subscription will be created after a customizable amount of time, so that multiple components can append queries.

// Component 1
ndk.bufferedSubscription({ kinds: [0], authors: ["pubkey-1"] }, "profiles", 500);

// Component 2
ndk.bufferedSubscription({ kinds: [0], authors: ["pubkey-2"] }, "profiles", 500);

In this example, NDK will wait 500ms before creating a subscription with the filter:

{kinds: [0], authors: ['pubkey-1', 'pubkey-2'] }

Intelligent relay selection

When a client submits a request through NDK, NDK will calculate which relays are most likely able to satisfy this request.

Queries submitted by the client might be broken into different queries if NDK computes different relays.

For example, say npub-A follows npub-B and npub-C. If the NDK client uses:

const ndk = new NDK({ explicitRelays: ["wss://nos.lol"] });
const npubA = ndk.getUser("npub-A");
const feedEvents = await npubA.feed();

This would result in the following request:

{ "kinds": [1], "authors": ["npub-B", "npub-C"] }

But if NDK has observed that npub-B tends to write to wss://userb.xyz and npub-C tends to write to wss://userc.io, NDK will instead send the following queries.

// to npub-A's explicit relay wss://nos.lol *if* npub-B and npub-C have been seen on that relay
{ "kinds": [1], "authors": [ "npub-B", "npub-C" ] }

// to wss://userb.xyz
{ "kinds": [1], "authors": [ "npub-B" ] }

// to wss://userc.io
{ "kinds": [1], "authors": [ "npub-C" ] }

Auto-closing subscriptions

Often, clients need to fetch data but don't need to maintain an open connection to the relay. This is true of profile metadata requests especially.

  • The autoclose flag will make the connection close immediately after EOSE is seen.
  • An integer autoclose will close the connection after that amount of ms after EOSE is seen.
ndk.subscription({ kinds: [0], authors: ["..."] }, { autoclose: true });

Convenience methods

NDK implements several conveience methods for common queries.

Instantiate a user by npub or hex pubkey

This is a handy method for instantiating a new NDKUser and associating the current NDK instance with that user for future calls.

const pablo = ndk.getUser({
    npub: "npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft"
});

const jeff = ndk.getUser({
    hexpubkey: "1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef"
});

Fetch a user's profile and publish updates

You can easily fetch a user's profile data from kind: 0 events on relays. Calling .fetchProfile() will update the profile attribute on the user object instead of returning the profile directly. NDK then makes it trivial to update values and publish those updates back to relays.

const pablo = ndk.getUser({
    npub: "npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft"
});
await pablo.fetchProfile();

const pabloFullProfile = pablo.profile;

pablo.profile.name = "Pablo";
// COMING SOON
await pablo.publish(); // Triggers signing via signer

Finding a single event or all events matching a filter

You can fetch the first event or all events that match a given set of filters.

// Create a filter
const filter: NDKFilter = { kinds: [1], authors: [hexpubkey1, hexpubkey2] };

// Will return only the first event
event = await ndk.fetchEvent(filter);

// Will return all found events
events = await ndk.fetchEvents(filter);

Creating & publishing events

const ndk = new NDK({ explicitRelays, signer });
const event = new NDKEvent(ndk);
event.kind = 1;
event.content = "PV Nostr! πŸ€™πŸΌ";
await ndk.publish(event);

Reacting to an event

// Find the first event from @jack, and react/like it.
const event = await ndk.fetchEvent({ author: "jack@cashapp.com" })[0];
await event.react("πŸ€™");

Zap an event

// Find the first event from @jack, and zap it.
const event = await ndk.fetchEvent({ author: "jack@cashapp.com" })[0];
await event.zap(1337, "Zapping your post!"); // Returns a zap request

Architecture decisions & suggestions

  • Users of NDK should instantiate a single NDK instance.
  • That instance tracks state with all relays connected, explicit and otherwise.
  • All relays are tracked in a single pool that handles connection errors/reconnection logic.
  • RelaySets are assembled ad-hoc as needed depending on the queries set, although some RelaySets might be long-lasting, like the explicitRelays specified by the user.
  • RelaySets are always a subset of the pool of all available relays.

Keywords

FAQs

Package last updated on 11 Apr 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚑️ by Socket Inc