nostrain
TypeScript icon, indicating that this package has built-in type declarations

0.9.4 • Public • Published

nostrain: Nostr client library with no strain

npm GitHub GitHub Workflow Status GitHub last commit   EN | JA | ES | ZH

  • A Nostr client library with modern TypeScript style.
  • Most of the functions are refactored from nostr-tools and are compatible with it.
  • Only depends on @noble and @scure packages.

Installation

npm install nostrain

Usage

Generating a private key and a public key

import { generatePrivateKey, getPublicKey } from 'nostrain';

const sk = generatePrivateKey(); // `sk` is a hex string
const pk = getPublicKey(sk); // `pk` is a hex string

console.log({ sk, pk });

Creating, signing and verifying events

import { validateEvent, verifySignature, signEvent, getEventHash, generatePrivateKey, getPublicKey } from 'nostrain';

const privateKey = generatePrivateKey();

const event = {
  kind: 1,
  created_at: Math.floor(Date.now() / 1000),
  tags: [],
  content: 'hello',
  pubkey: getPublicKey(privateKey),
  id: '',
  sig: '',
};

event.id = getEventHash(event);
event.sig = signEvent(event, privateKey);
const validateOk = validateEvent(event);
const verifyOk = verifySignature(event);

console.log({ event, validateOk, verifyOk });

Interacting with a relay

import crypto from 'node:crypto';
globalThis.crypto = crypto;

import 'websocket-polyfill';
import { relayInit, generatePrivateKey, getPublicKey, getEventHash, signEvent } from 'nostrain';

const relay = relayInit('wss://relay.damus.io');

relay.on('connect', () => {
  console.log(`connected to ${relay.url}`);
});
relay.on('error', () => {
  console.log(`failed to connect to ${relay.url}`);
});

await relay.connect();

{
  // let's query for an event that exists
  const sub = relay.sub([{ ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'] }]);

  sub.on('event', event => {
    console.log('we got the event we wanted:', event);
  });
  sub.on('eose', () => {
    sub.unsub();
  });
}

{
  // let's publish a new event while simultaneously monitoring the relay for it
  const sk = generatePrivateKey();
  const pk = getPublicKey(sk);

  const sub = relay.sub([{ kinds: [1], authors: [pk] }]);

  sub.on('event', event => {
    console.log('got event:', event);
  });

  const event = {
    kind: 1,
    pubkey: pk,
    created_at: Math.floor(Date.now() / 1000),
    tags: [],
    content: 'hello world',
  };
  event.id = getEventHash(event);
  event.sig = signEvent(event, sk);

  const pub = relay.publish(event);
  pub.on('ok', () => {
    console.log(`${relay.url} has accepted our event`);
  });
  pub.on('failed', reason => {
    console.log(`failed to publish to ${relay.url}: ${reason}`);
  });
}

{
  // let's query for events by list and get
  const events = await relay.list([{ kinds: [0, 1] }]);
  console.log(events.length);

  const event = await relay.get({ ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'] });
  console.log({ event });
}

relay.close();

To use this on Node.js you first must install websocket-polyfill and import it:

import 'websocket-polyfill';

Interacting with multiple relays

TODO: work in progress

Parsing references (mentions) from a content using NIP-10 and NIP-27

See src/references.test.ts.

Querying profile data from a NIP-05 address

import { nip05 } from 'nostrain';

const profile = await nip05.queryProfile('jb55.com');

console.log({ profile });

Encoding and decoding NIP-19 codes

import { nip19, generatePrivateKey, getPublicKey } from 'nostrain';

{
  const sk = generatePrivateKey();
  const nsec = nip19.nsecEncode(sk);
  const { type, data } = nip19.decode(nsec);
  console.log({ sk, nsec, type, data });
}

{
  const pk = getPublicKey(generatePrivateKey());
  const npub = nip19.npubEncode(pk);
  const { type, data } = nip19.decode(npub);
  console.log({ pk, npub, type, data });
}

{
  const pk = getPublicKey(generatePrivateKey());
  const relays = ['wss://relay.nostr.example.mydomain.example.com', 'wss://nostr.banana.com'];
  const nprofile = nip19.nprofileEncode({ pubkey: pk, relays });
  const { type, data } = nip19.decode(nprofile);
  console.log({ pk, relays, nprofile, type, data });
}

Encrypting and decrypting direct messages

import crypto from 'node:crypto';

globalThis.crypto = crypto;

import { nip04, getPublicKey, generatePrivateKey } from 'nostrain';

// sender
const sk1 = generatePrivateKey();
const pk1 = getPublicKey(sk1);

// receiver
const sk2 = generatePrivateKey();
const pk2 = getPublicKey(sk2);

// on the sender side
const message = 'hello';
const ciphertext = await nip04.encrypt(sk1, pk2, message);

// on the receiver side
const plaintext = await nip04.decrypt(sk2, pk1, ciphertext);

console.log({ message, ciphertext, plaintext });

Performing and checking for delegation

import { nip26, getPublicKey, generatePrivateKey } from 'nostrain';

// delegator
const sk1 = generatePrivateKey();
const pk1 = getPublicKey(sk1);

// delegatee
const sk2 = generatePrivateKey();
const pk2 = getPublicKey(sk2);

// generate delegation
const delegation = nip26.createDelegation(sk1, {
  pubkey: pk2,
  kind: 1,
  since: Math.round(Date.now() / 1000) - 1, // 1 second ago
  until: Math.round(Date.now() / 1000) + 60 * 60 * 24 * 30, // 30 days
});

console.log({ delegation });

// the delegatee uses the delegation when building an event
const event = {
  pubkey: pk2,
  kind: 1,
  created_at: Math.round(Date.now() / 1000),
  content: 'hello from a delegated key',
  tags: [['delegation', delegation.from, delegation.cond, delegation.sig]],
};

console.log({ tags: event.tags });

// finally any receiver of this event can check for the presence of a valid delegation tag
const delegator = nip26.getDelegator(event);

// will be null if there is no delegation tag or if it is invalid
console.log({ delegator, pk1, success: delegator === pk1 });

Development

git clone https://github.com/susumuota/nostrain.git
cd nostrain
npm ci
npm run build  # or npm run watch
npm run test
npm run format

Source code

Related Links

  • Nostr: The simplest open protocol that is able to create a censorship-resistant global "social" network once and for all.
  • NIPs: NIPs stand for Nostr Implementation Possibilities. They exist to document what may be implemented by Nostr-compatible relay and client software.
  • nostr-tools: Tools for developing Nostr clients.
  • noble-curves: Audited & minimal JavaScript implementation of elliptic curve cryptography.
  • scure-base: Secure, audited and 0-dep implementation of bech32, etc.

License

MIT License, see LICENSE file.

Author

S. Ota

Package Sidebar

Install

npm i nostrain

Weekly Downloads

2

Version

0.9.4

License

MIT

Unpacked Size

58.9 kB

Total Files

20

Last publish

Collaborators

  • susumuota