Skip to content
nostr-tools logo

nostr-tools Skill

nostr-tools

This skill should be used when working with nostr-tools library for Nostr protocol operations, including event creation, signing, filtering, relay communication, and NIP implementations. Provides comprehensive knowledge of nostr-tools APIs and patterns.

mattnigh/skills_collection0installs24stars

SKILL.md

Full skill instructions

nostr-tools Skill

This skill provides comprehensive knowledge and patterns for working with nostr-tools, the most popular JavaScript/​TypeScript library for Nostr protocol development.

When to Use This Skill

Use this skill when:

  • Building Nostr clients or applications
  • Creating and signing Nostr events
  • Connecting to Nostr relays
  • Implementing NIP features
  • Working with Nostr keys and cryptography
  • Filtering and querying events
  • Building relay pools or connections
  • Implementing NIP-44/​NIP-04 encryption

Core Concepts

nostr-tools Overview

nostr-tools provides:

  • Event handling - Create, sign, verify events
  • Key management - Generate, convert, encode keys
  • Relay communication - Connect, subscribe, publish
  • NIP implementations - NIP-04, NIP-05, NIP-19, NIP-44, etc.
  • Cryptographic operations - Schnorr signatures, encryption
  • Filter building - Query events by various criteria

Installation

npm install nostr-tools

Basic Imports

// Core functionality
import {
  SimplePool,
  generateSecretKey,
  getPublicKey,
  finalizeEvent,
  verifyEvent
} from 'nostr-tools';

// NIP-specific imports
import { nip04, nip05, nip19, nip44 } from 'nostr-tools';

// Relay operations
import { Relay } from 'nostr-tools/​relay';

Key Management

Generating Keys

import { generateSecretKey, getPublicKey } from 'nostr-tools/​pure';

// Generate new secret key (Uint8Array)
const secretKey = generateSecretKey();

// Derive public key
const publicKey = getPublicKey(secretKey);

console.log('Secret key:', bytesToHex(secretKey));
console.log('Public key:', publicKey); // hex string

Key Encoding (NIP-19)

import { nip19 } from 'nostr-tools';

// Encode to bech32
const nsec = nip19.nsecEncode(secretKey);
const npub = nip19.npubEncode(publicKey);
const note = nip19.noteEncode(eventId);

console.log(nsec); // nsec1...
console.log(npub); // npub1...
console.log(note); // note1...

// Decode from bech32
const { type, data } = nip19.decode(npub);
// type: 'npub', data: publicKey (hex)

// Encode profile reference (nprofile)
const nprofile = nip19.nprofileEncode({
  pubkey: publicKey,
  relays: ['wss://relay.example.com']
});

// Encode event reference (nevent)
const nevent = nip19.neventEncode({
  id: eventId,
  relays: ['wss://relay.example.com'],
  author: publicKey,
  kind: 1
});

// Encode address (naddr) for replaceable events
const naddr = nip19.naddrEncode({
  identifier: 'my-article',
  pubkey: publicKey,
  kind: 30023,
  relays: ['wss://relay.example.com']
});

Event Operations

Event Structure

// Unsigned event template
const eventTemplate = {
  kind: 1,
  created_at: Math.floor(Date.now() / 1000),
  tags: [],
  content: 'Hello Nostr!'
};

// Signed event (after finalizeEvent)
const signedEvent = {
  id: '...', // 32-byte sha256 hash as hex
  pubkey: '...', // 32-byte public key as hex
  created_at: 1234567890,
  kind: 1,
  tags: [],
  content: 'Hello Nostr!',
  sig: '...' // 64-byte Schnorr signature as hex
};

Creating and Signing Events

import { finalizeEvent, verifyEvent } from 'nostr-tools/​pure';

// Create event template
const eventTemplate = {
  kind: 1,
  created_at: Math.floor(Date.now() / 1000),
  tags: [
    ['p', publicKey], // Mention
    ['e', eventId, '', 'reply'], // Reply
    ['t', 'nostr'] // Hashtag
  ],
  content: 'Hello Nostr!'
};

// Sign event
const signedEvent = finalizeEvent(eventTemplate, secretKey);

// Verify event
const isValid = verifyEvent(signedEvent);
console.log('Event valid:', isValid);

Event Kinds

// Common event kinds
const KINDS = {
  Metadata: 0,           // Profile metadata (NIP-01)
  Text: 1,               // Short text note (NIP-01)
  RecommendRelay: 2,     // Relay recommendation
  Contacts: 3,           // Contact list (NIP-02)
  EncryptedDM: 4,        // Encrypted DM (NIP-04)
  EventDeletion: 5,      // Delete events (NIP-09)
  Repost: 6,             // Repost (NIP-18)
  Reaction: 7,           // Reaction (NIP-25)
  ChannelCreation: 40,   // Channel (NIP-28)
  ChannelMessage: 42,    // Channel message
  Zap: 9735,             // Zap receipt (NIP-57)
  Report: 1984,          // Report (NIP-56)
  RelayList: 10002,      // Relay list (NIP-65)
  Article: 30023,        // Long-form content (NIP-23)
};

Creating Specific Events

// Profile metadata (kind 0)
const profileEvent = finalizeEvent({
  kind: 0,
  created_at: Math.floor(Date.now() / 1000),
  tags: [],
  content: JSON.stringify({
    name: 'Alice',
    about: 'Nostr enthusiast',
    picture: 'https://example.com/​avatar.jpg',
    nip05: '[email protected]',
    lud16: '[email protected]'
  })
}, secretKey);

// Contact list (kind 3)
const contactsEvent = finalizeEvent({
  kind: 3,
  created_at: Math.floor(Date.now() / 1000),
  tags: [
    ['p', pubkey1, 'wss://relay1.com', 'alice'],
    ['p', pubkey2, 'wss://relay2.com', 'bob'],
    ['p', pubkey3, '', 'carol']
  ],
  content: '' // Or JSON relay preferences
}, secretKey);

// Reply to an event
const replyEvent = finalizeEvent({
  kind: 1,
  created_at: Math.floor(Date.now() / 1000),
  tags: [
    ['e', rootEventId, '', 'root'],
    ['e', parentEventId, '', 'reply'],
    ['p', parentEventPubkey]
  ],
  content: 'This is a reply'
}, secretKey);

// Reaction (kind 7)
const reactionEvent = finalizeEvent({
  kind: 7,
  created_at: Math.floor(Date.now() / 1000),
  tags: [
    ['e', eventId],
    ['p', eventPubkey]
  ],
  content: '+' // or '-' or emoji
}, secretKey);

// Delete event (kind 5)
const deleteEvent = finalizeEvent({
  kind: 5,
  created_at: Math.floor(Date.now() / 1000),
  tags: [
    ['e', eventIdToDelete],
    ['e', anotherEventIdToDelete]
  ],
  content: 'Deletion reason'
}, secretKey);

Relay Communication

Using SimplePool

SimplePool is the recommended way to interact with multiple relays:

import { SimplePool } from 'nostr-tools/​pool';

const pool = new SimplePool();
const relays = [
  'wss://relay.damus.io',
  'wss://nos.lol',
  'wss://relay.nostr.band'
];

// Subscribe to events
const subscription = pool.subscribeMany(
  relays,
  [
    {
      kinds: [1],
      authors: [publicKey],
      limit: 10
    }
  ],
  {
    onevent(event) {
      console.log('Received event:', event);
    },
    oneose() {
      console.log('End of stored events');
    }
  }
);

// Close subscription when done
subscription.close();

// Publish event to all relays
const results = await Promise.allSettled(
  pool.publish(relays, signedEvent)
);

// Query events (returns Promise)
const events = await pool.querySync(relays, {
  kinds: [0],
  authors: [publicKey]
});

// Get single event
const event = await pool.get(relays, {
  ids: [eventId]
});

// Close pool when done
pool.close(relays);

Direct Relay Connection

import { Relay } from 'nostr-tools/​relay';

const relay = await Relay.connect('wss://relay.damus.io');

console.log(`Connected to ${relay.url}`);

// Subscribe
const sub = relay.subscribe([
  {
    kinds: [1],
    limit: 100
  }
], {
  onevent(event) {
    console.log('Event:', event);
  },
  oneose() {
    console.log('EOSE');
    sub.close();
  }
});

// Publish
await relay.publish(signedEvent);

// Close
relay.close();

Handling Connection States

import { Relay } from 'nostr-tools/​relay';

const relay = await Relay.connect('wss://relay.example.com');

// Listen for disconnect
relay.onclose = () => {
  console.log('Relay disconnected');
};

// Check connection status
console.log('Connected:', relay.connected);

Filters

Filter Structure

const filter = {
  // Event IDs
  ids: ['abc123...'],

  // Authors (pubkeys)
  authors: ['pubkey1', 'pubkey2'],

  // Event kinds
  kinds: [1, 6, 7],

  // Tags (single-letter keys)
  '#e': ['eventId1', 'eventId2'],
  '#p': ['pubkey1'],
  '#t': ['nostr', 'bitcoin'],
  '#d': ['article-identifier'],

  // Time range
  since: 1704067200, // Unix timestamp
  until: 1704153600,

  // Limit results
  limit: 100,

  // Search (NIP-50, if relay supports)
  search: 'nostr protocol'
};

Common Filter Patterns

// User's recent posts
const userPosts = {
  kinds: [1],
  authors: [userPubkey],
  limit: 50
};

// User's profile
const userProfile = {
  kinds: [0],
  authors: [userPubkey]
};

// User's contacts
const userContacts = {
  kinds: [3],
  authors: [userPubkey]
};

// Replies to an event
const replies = {
  kinds: [1],
  '#e': [eventId]
};

// Reactions to an event
const reactions = {
  kinds: [7],
  '#e': [eventId]
};

// Feed from followed users
const feed = {
  kinds: [1, 6],
  authors: followedPubkeys,
  limit: 100
};

// Events mentioning user
const mentions = {
  kinds: [1],
  '#p': [userPubkey],
  limit: 50
};

// Hashtag search
const hashtagEvents = {
  kinds: [1],
  '#t': ['bitcoin'],
  limit: 100
};

// Replaceable event by d-tag
const replaceableEvent = {
  kinds: [30023],
  authors: [authorPubkey],
  '#d': ['article-slug']
};

Multiple Filters

// Subscribe with multiple filters (OR logic)
const filters = [
  { kinds: [1], authors: [userPubkey], limit: 20 },
  { kinds: [1], '#p': [userPubkey], limit: 20 }
];

pool.subscribeMany(relays, filters, {
  onevent(event) {
    // Receives events matching ANY filter
  }
});

Encryption

NIP-04 (Legacy DMs)

import { nip04 } from 'nostr-tools';

// Encrypt message
const ciphertext = await nip04.encrypt(
  secretKey,
  recipientPubkey,
  'Hello, this is secret!'
);

// Create encrypted DM event
const dmEvent = finalizeEvent({
  kind: 4,
  created_at: Math.floor(Date.now() / 1000),
  tags: [['p', recipientPubkey]],
  content: ciphertext
}, secretKey);

// Decrypt message
const plaintext = await nip04.decrypt(
  secretKey,
  senderPubkey,
  ciphertext
);

NIP-44 (Modern Encryption)

import { nip44 } from 'nostr-tools';

// Get conversation key (cache this for multiple messages)
const conversationKey = nip44.getConversationKey(
  secretKey,
  recipientPubkey
);

// Encrypt
const ciphertext = nip44.encrypt(
  'Hello with NIP-44!',
  conversationKey
);

// Decrypt
const plaintext = nip44.decrypt(
  ciphertext,
  conversationKey
);

NIP Implementations

NIP-05 (DNS Identifier)

import { nip05 } from 'nostr-tools';

// Query NIP-05 identifier
const profile = await nip05.queryProfile('[email protected]');

if (profile) {
  console.log('Pubkey:', profile.pubkey);
  console.log('Relays:', profile.relays);
}

// Verify NIP-05 for a pubkey
const isValid = await nip05.queryProfile('[email protected]')
  .then(p => p?.pubkey === expectedPubkey);

NIP-10 (Reply Threading)

import { nip10 } from 'nostr-tools';

// Parse reply tags
const parsed = nip10.parse(event);

console.log('Root:', parsed.root);     // Original event
console.log('Reply:', parsed.reply);   // Direct parent
console.log('Mentions:', parsed.mentions); // Other mentions
console.log('Profiles:', parsed.profiles); // Mentioned pubkeys

NIP-21 (nostr: URIs)

// Parse nostr: URIs
const uri = 'nostr:npub1...';
const { type, data } = nip19.decode(uri.replace('nostr:', ''));

NIP-27 (Content References)

// Parse nostr:npub and nostr:note references in content
const content = 'Check out nostr:npub1abc... and nostr:note1xyz...';

const references = content.match(/​nostr:(n[a-z]+1[a-z0-9]+)/​g);
references?.forEach(ref => {
  const decoded = nip19.decode(ref.replace('nostr:', ''));
  console.log(decoded.type, decoded.data);
});

NIP-57 (Zaps)

import { nip57 } from 'nostr-tools';

// Validate zap receipt
const zapReceipt = await pool.get(relays, {
  kinds: [9735],
  '#e': [eventId]
});

const validatedZap = await nip57.validateZapRequest(zapReceipt);

Utilities

Hex and Bytes Conversion

import { bytesToHex, hexToBytes } from '@noble/​hashes/​utils';

// Convert secret key to hex
const secretKeyHex = bytesToHex(secretKey);

// Convert hex back to bytes
const secretKeyBytes = hexToBytes(secretKeyHex);

Event ID Calculation

import { getEventHash } from 'nostr-tools/​pure';

// Calculate event ID without signing
const eventId = getEventHash(unsignedEvent);

Signature Operations

import {
  getSignature,
  verifyEvent
} from 'nostr-tools/​pure';

// Sign event data
const signature = getSignature(unsignedEvent, secretKey);

// Verify complete event
const isValid = verifyEvent(signedEvent);

Best Practices

Connection Management

  1. Use SimplePool - Manages connections efficiently
  2. Limit concurrent connections - Don't connect to too many relays
  3. Handle disconnections - Implement reconnection logic
  4. Close subscriptions - Always close when done

Event Handling

  1. Verify events - Always verify signatures
  2. Deduplicate - Events may come from multiple relays
  3. Handle replaceable events - Latest by created_at wins
  4. Validate content - Don't trust event content blindly

Key Security

  1. Never expose secret keys - Keep in secure storage
  2. Use NIP-07 in browsers - Let extensions handle signing
  3. Validate input - Check key formats before use

Performance

  1. Cache events - Avoid re-fetching
  2. Use filters wisely - Be specific, use limits
  3. Batch operations - Combine related queries
  4. Close idle connections - Free up resources

Common Patterns

Building a Feed

const pool = new SimplePool();
const relays = ['wss://relay.damus.io', 'wss://nos.lol'];

async function loadFeed(followedPubkeys) {
  const events = await pool.querySync(relays, {
    kinds: [1, 6],
    authors: followedPubkeys,
    limit: 100
  });

  // Sort by timestamp
  return events.sort((a, b) => b.created_at - a.created_at);
}

Real-time Updates

function subscribeToFeed(followedPubkeys, onEvent) {
  return pool.subscribeMany(
    relays,
    [{ kinds: [1, 6], authors: followedPubkeys }],
    {
      onevent: onEvent,
      oneose() {
        console.log('Caught up with stored events');
      }
    }
  );
}

Profile Loading

async function loadProfile(pubkey) {
  const [metadata] = await pool.querySync(relays, {
    kinds: [0],
    authors: [pubkey],
    limit: 1
  });

  if (metadata) {
    return JSON.parse(metadata.content);
  }
  return null;
}

Event Deduplication

const seenEvents = new Set();

function handleEvent(event) {
  if (seenEvents.has(event.id)) {
    return; // Skip duplicate
  }
  seenEvents.add(event.id);

  // Process event...
}

Troubleshooting

Common Issues

Events not publishing:

  • Check relay is writable
  • Verify event is properly signed
  • Check relay's accepted kinds

Subscription not receiving events:

  • Verify filter syntax
  • Check relay has matching events
  • Ensure subscription isn't closed

Signature verification fails:

  • Check event structure is correct
  • Verify keys are in correct format
  • Ensure event hasn't been modified

NIP-05 lookup fails:

  • Check CORS headers on server
  • Verify .well-known path is correct
  • Handle network timeouts

References

Related Skills

  • nostr - Nostr protocol fundamentals
  • svelte - Building Nostr UIs with Svelte
  • applesauce-core - Higher-level Nostr client utilities
  • applesauce-signers - Nostr signing abstractions

More skills from mattnigh

spring-framework-patterns logo
mattnigh/skills_collection

spring-framework-patterns

Comprehensive Spring Framework and Spring Boot best practices including dependency injection patterns, bean lifecycle and scopes, REST API development, Spring Data JPA, service layer design, Spring Security, testing strategies, caching, AOP, async processing, error handling, and common anti-patte...

24 0
View

TypeScript厳格モードによる型安全性設計を専門とするスキル。 📖 参照書籍: - 『Effective TypeScript』(Dan Vanderkam): 型設計 📚 リソース参照: - `resources/Level1_basics.md`: レベル1の基礎ガイド - `resources/Level2_intermediate.md`: レベル2の実務ガイド - `resources/Level3_advanced.md`: レベル3の応用ガイド - `resources/Level4_expert.md`: レベル4の専門ガイド - `resources/di...

24 0
View
deploying-cloud-k8s logo
mattnigh/skills_collection

deploying-cloud-k8s

Deploys applications to cloud Kubernetes (AKS/GKE/DOKS) with CI/CD pipelines. Use when deploying to production, setting up GitHub Actions, troubleshooting deployments. Covers build-time vs runtime vars, architecture matching, and battle-tested debugging.

24 0
View
moai-alfred-agent-guide logo
mattnigh/skills_collection

moai-alfred-agent-guide

19-agent team structure, decision trees for agent selection, Haiku vs Sonnet model selection, and agent collaboration principles. Use when deciding which sub-agent to invoke, understanding team responsibilities, or learning multi-agent orchestration.

24 0
View
mermaid-diagram-generator logo
mattnigh/skills_collection

mermaid-diagram-generator

Converts architecture descriptions, module specs, or workflow docs into Mermaid diagrams. Use when visualizing brick module relationships, workflows (DDD, investigation), or system architecture. Supports: flowcharts, sequence diagrams, class diagrams, state machines, entity relationship diagrams,...

24 0
View
pre-flight-check logo
mattnigh/skills_collection

pre-flight-check

INVOKE FIRST before any code work. Validates git workflow (branch, issue, worklog) and checks approach. Use at START of every task and END before completing. Prevents skipped steps.

24 0
View
browser-dev-tools logo
mattnigh/skills_collection

browser-dev-tools

This skill should be used when working with browser-rendered artifacts (i.e. Bun, React/React Native) to proactively validate that development work on localhost is being built appropriately and to support debugging browser-rendered content. Use this skill after making frontend changes to verify t...

24 0
View
lead-dev logo
mattnigh/skills_collection

lead-dev

Lead Développeur - Coordination technique opérationnelle, code review, mentoring et livraison. Pair de web-dev-process au niveau OPÉRATIONS.

24 0
View
worker-monitor logo
mattnigh/skills_collection

worker-monitor

Monitor Docker workers, RQ queue health, and auto-scale workers (max 4). Use when: checking job progress, monitoring queue depth, scaling workers up/down, diagnosing slow processing, or waiting for jobs to complete. Referenced by data-quality skill during reprocessing.

24 0
View
quality-advisor logo
mattnigh/skills_collection

quality-advisor

Proactive quality guidance system that monitors artifact creation and provides real-time feedback on documentation quality

24 0
View
bump-version logo
mattnigh/skills_collection

bump-version

This skill should be used when the user wants to bump the version number in the workspace. It updates versions across all pyproject.toml files (root, tde, and tda packages) and the CHANGELOG.md to keep them synchronized.

24 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View