npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

animiq-nip76-tools

v1.0.5

Published

Tools for handling Private Channel NOSTR Events defined in NIP-76.

Downloads

2

Readme

animiq-nip76-tools

Tools for developing Private Channels Nostr clients.

If NIP-76 Pull Request is accepted, we will rename this package to "nip76-tools".

Depends on @scure, @noble & nostr-tools packages.

Installation

 npm install animiq-nip76-tools # or yarn add animiq-nip76-tools

Usage

Creating a nip76 wallet with a private key and a public key.

Here we create the pk and sk, but you can use any key pair. Currently only web storage is deomnstrated. More storage types coming soon.

import {generatePrivateKey, getPublicKey} from 'nostr-tools';
import {Nip76WebWalletStorage} from 'animiq-nip76-tools';

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

const wallet = await Nip76WebWalletStorage.fromStorage({ ps, sk });
wallet.saveWallet(sk);

Creating New Private Channels

let privateKey = await someMethodToGetTheProfilePrivateKey();

let channel = wallet.createChannel();
channel.content.name = 'My New Channel';
channel.content.about = 'Whatever we want';
const event = await wallet.documentsIndex.createEvent(channel, privateKey);

someMethodToSendTheEventToRelays(event);

Creating a Text Note on a Private Channel

import { PostDocument } from 'animiq-nip76-tools';

let privateKey = await someMethodToGetTheProfilePrivateKey();

let postDocument = new PostDocument();
postDocument.content = {
    'Hello World',
    pubkey: wallet.ownerPubKey,
    kind: nostrTools.Kind.Text
}
let event = await channel.dkxPost.createEvent(postDocument, privateKey);

someMethodToSendTheEventToRelays(event);

Creating a Text Reply or Reaction to Private Channel Note

import { PostDocument } from 'animiq-nip76-tools';

let privateKey = await someMethodToGetTheProfilePrivateKey();

let replyDocument = new PostDocument();
replyDocument.content = {
    'Hello Back',
    pubkey: wallet.ownerPubKey,
    kind: nostrTools.Kind.Text,  // or use nostrTools.Kind.Reaction
    tags: [['e', post.nostrEvent.id]]
}
let event = await channel.dkxPost.createEvent(replyDocument, privateKey);

someMethodToSendTheEventToRelays(event);

Saving an Invitation for a public key

let privateKey = await someMethodToGetTheProfilePrivateKey();

let invite = new Invitation();
invite.docIndex = channel.dkxInvite.documents.length + 1;
invite.content = {
    kind: NostrKinds.PrivateChannelInvitation,
    docIndex: invite.docIndex,
    for: '(pubkey-hex)',
    pubkey: channel.dkxPost.signingParent.nostrPubKey,
    signingParent: channel.dkxPost.signingParent,
    cryptoParent: channel.dkxPost.cryptoParent,
}
let event = await channel.dkxInvite.createEvent(invite, privateKey);

someMethodToSendTheEventToRelays(event);

let invitationTextToSend = await invite.getPointer();

Saving an Invitation that uses a password

let privateKey = await someMethodToGetTheProfilePrivateKey();

let invite = new Invitation();
invite.docIndex = channel.dkxInvite.documents.length + 1;
invite.content = {
    kind: NostrKinds.PrivateChannelInvitation,
    docIndex: invite.docIndex,
    password: 'the password',
    pubkey: channel.dkxPost.signingParent.nostrPubKey,
    signingParent: channel.dkxPost.signingParent,
    cryptoParent: channel.dkxPost.cryptoParent,
}
let event = await channel.dkxInvite.createEvent(invite, privateKey);

someMethodToSendTheEventToRelays(event);

let invitationTextToSend = await invite.getPointer();

Reading an Invitation

(NOTE: We are working to make this easier to implement.)

import { nip19Extension, HDKIndex } from 'animiq-nip76-tools';

let pointer = await nip19Extension.decode(channelPointer, 'privateKeyHexOrPassword').data;

if ((pointer.type & nip19Extension.PointerType.FullKeySet) === nip19Extension.PointerType.FullKeySet) {
    // Unmanaged Invitation
    const signingParent = new HDKey({ publicKey: pointer.signingKey, chainCode: pointer.signingChain, version: Versions.nip76API1 });
    const cryptoParent = new HDKey({ publicKey: pointer.cryptoKey, chainCode: pointer.cryptoChain, version: Versions.nip76API1 });
    const invite = new Invitation();
    pointer.docIndex = -1;
    invite.pointer = pointer;
    invite.content = {
        kind: NostrKinds.PrivateChannelInvitation,
        pubkey: signingParent.nostrPubKey,
        docIndex: pointer.docIndex,
        signingParent,
        cryptoParent
    };

    channelIndex = new HDKIndex(HDKIndexType.Singleton, invite.content.signingParent!, invite.content.cryptoParent!);
    relayService.subscribe(
      [{ authors: [channelIndex.signingParent.nostrPubKey], kinds: [17761], limit: 1 }]
    );
    // the nostrEvent returned is the channel
} else {
    // Managed Invitation
    const inviteIndex = HDKIndex.fromChannelPointer(pointer);
    relayService.subscribe(
      [{ authors: [inviteIndex.signingParent.nostrPubKey], kinds: [17761], limit: 1 }]
    );
    // the nostrEvent returned is an invitation from which we can read the channel
}

Reading Notes on a Channel

relayService.subscribe([
      { '#e': [channel.dkxPost.eventTag], kinds: [17761], limit: length },
      { '#e': [channel.dkxRsvp.eventTag], kinds: [17761], limit: length },
    ]);

///events from the stream are then read like:
if (channel.dkxPost.eventTag === nostrEvent.tags[0][1]) {
    let post = await channel.dkxPost.readEvent(nostrEvent);
} else if (channel.dkxRsvp.eventTag === nostrEvent.tags[0][1]) {
    let rsvp = await channel.dkxRsvp.readEvent(nostrEvent);
}

License

MIT