nostr-tools

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

nostr-tools Skill

nostr-tools 技能

This skill provides comprehensive knowledge and patterns for working with nostr-tools, the most popular JavaScript/TypeScript library for Nostr protocol development.
本技能提供了使用nostr-tools的全面知识和模式,nostr-tools是用于Nostr协议开发的最流行JavaScript/TypeScript库。

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
在以下场景使用本技能:
  • 构建Nostr客户端或应用
  • 创建并签名Nostr事件
  • 连接到Nostr中继
  • 实现NIP特性
  • 处理Nostr密钥与加密
  • 过滤和查询事件
  • 构建中继池或连接
  • 实现NIP-44/NIP-04加密

Core Concepts

核心概念

nostr-tools Overview

nostr-tools 概述

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
nostr-tools提供以下功能:
  • 事件处理 - 创建、签名、验证事件
  • 密钥管理 - 生成、转换、编码密钥
  • 中继通信 - 连接、订阅、发布
  • NIP实现 - NIP-04、NIP-05、NIP-19、NIP-44等
  • 加密操作 - Schnorr签名、加密
  • 过滤器构建 - 按各种条件查询事件

Installation

安装

bash
npm install nostr-tools
bash
npm install nostr-tools

Basic Imports

基础导入

javascript
// 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';
javascript
// 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

生成密钥

javascript
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
javascript
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)

密钥编码(NIP-19)

javascript
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']
});
javascript
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

事件结构

javascript
// 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
};
javascript
// 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

创建与签名事件

javascript
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);
javascript
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

事件类型

javascript
// 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)
};
javascript
// 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

创建特定事件

javascript
// 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: 'alice@example.com',
    lud16: 'alice@getalby.com'
  })
}, 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);
javascript
// 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: 'alice@example.com',
    lud16: 'alice@getalby.com'
  })
}, 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

SimplePool is the recommended way to interact with multiple relays:
javascript
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);
SimplePool是与多个中继交互的推荐方式:
javascript
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

直接中继连接

javascript
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();
javascript
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

处理连接状态

javascript
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);
javascript
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

过滤器结构

javascript
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'
};
javascript
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

常见过滤器模式

javascript
// 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']
};
javascript
// 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

多过滤器

javascript
// 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
  }
});
javascript
// 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)

NIP-04(传统私信)

javascript
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
);
javascript
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)

NIP-44(现代加密)

javascript
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
);
javascript
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实现

NIP-05 (DNS Identifier)

NIP-05(DNS标识符)

javascript
import { nip05 } from 'nostr-tools';

// Query NIP-05 identifier
const profile = await nip05.queryProfile('alice@example.com');

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

// Verify NIP-05 for a pubkey
const isValid = await nip05.queryProfile('alice@example.com')
  .then(p => p?.pubkey === expectedPubkey);
javascript
import { nip05 } from 'nostr-tools';

// Query NIP-05 identifier
const profile = await nip05.queryProfile('alice@example.com');

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

// Verify NIP-05 for a pubkey
const isValid = await nip05.queryProfile('alice@example.com')
  .then(p => p?.pubkey === expectedPubkey);

NIP-10 (Reply Threading)

NIP-10(回复线程)

javascript
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
javascript
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)

NIP-21(nostr: URI)

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

NIP-27 (Content References)

NIP-27(内容引用)

javascript
// 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);
});
javascript
// 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)

NIP-57(打赏收据)

javascript
import { nip57 } from 'nostr-tools';

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

const validatedZap = await nip57.validateZapRequest(zapReceipt);
javascript
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

十六进制与字节转换

javascript
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);
javascript
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

事件ID计算

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

// Calculate event ID without signing
const eventId = getEventHash(unsignedEvent);
javascript
import { getEventHash } from 'nostr-tools/pure';

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

Signature Operations

签名操作

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

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

// Verify complete event
const isValid = verifyEvent(signedEvent);
javascript
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
  1. 使用SimplePool - 高效管理连接
  2. 限制并发连接 - 不要连接过多中继
  3. 处理断开连接 - 实现重连逻辑
  4. 关闭订阅 - 使用完毕后务必关闭

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
  1. 验证事件 - 始终验证签名
  2. 去重 - 事件可能来自多个中继
  3. 处理可替换事件 - 最新的事件(按created_at)优先
  4. 验证内容 - 不要盲目信任事件内容

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
  1. 绝不暴露密钥 - 存储在安全位置
  2. 在浏览器中使用NIP-07 - 让扩展程序处理签名
  3. 验证输入 - 使用前检查密钥格式

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
  1. 缓存事件 - 避免重复获取
  2. 合理使用过滤器 - 明确条件,使用限制参数
  3. 批量操作 - 合并相关查询
  4. 关闭空闲连接 - 释放资源

Common Patterns

常见模式

Building a Feed

构建信息流

javascript
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);
}
javascript
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

实时更新

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

Profile Loading

加载用户资料

javascript
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;
}
javascript
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

事件去重

javascript
const seenEvents = new Set();

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

  // Process event...
}
javascript
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
事件发布失败:
  • 检查中继是否可写入
  • 验证事件是否正确签名
  • 检查中继接受的事件类型
订阅未收到事件:
  • 验证过滤器语法
  • 检查中继是否有匹配事件
  • 确保订阅未被关闭
签名验证失败:
  • 检查事件结构是否正确
  • 验证密钥格式是否正确
  • 确保事件未被修改
NIP-05查询失败:
  • 检查服务器CORS头
  • 验证.well-known路径是否正确
  • 处理网络超时

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
  • nostr - Nostr协议基础
  • svelte - 使用Svelte构建Nostr界面
  • applesauce-core - 高阶Nostr客户端工具
  • applesauce-signers - Nostr签名抽象层