nostr_nips 0.2.0 copy "nostr_nips: ^0.2.0" to clipboard
nostr_nips: ^0.2.0 copied to clipboard

A comprehensive Dart library implementing all Nostr protocol NIPs (Nostr Implementation Possibilities).

nostr_nips #

A comprehensive Dart library implementing the Nostr protocol with support for 75+ NIPs (Nostr Implementation Possibilities).

Built for Dart and Flutter applications. Single import, batteries included.

Features #

  • Key Management -- Generate keypairs, derive from BIP-39 mnemonics, encrypt with passwords (ncryptsec)
  • Event Handling -- Create, sign, verify, and serialize Nostr events of any kind
  • Relay Communication -- Connect to single relays or relay pools via WebSocket, subscribe with filters, handle all protocol messages, automatic reconnection with exponential backoff
  • Encoding -- Full NIP-19 bech32 encoding (npub, nsec, note, nprofile, nevent, naddr) and NIP-21 nostr: URIs
  • Encryption -- NIP-04 (legacy) and NIP-44 (modern) encrypted payloads, NIP-59 gift wrap for metadata protection
  • Error Handling -- Custom exception hierarchy (NostrException, RelayConnectionException, CryptographicException, etc.) with cause chaining
  • 75+ NIP Implementations -- Social features, messaging, content types, marketplace, payments, moderation, and more

Installation #

Add to your pubspec.yaml:

dependencies:
  nostr_nips: ^0.2.0

Or with the Dart CLI:

dart pub add nostr_nips

Requires Dart SDK >=3.11.0 <4.0.0

Quick Start #

import 'package:nostr_nips/nostr_nips.dart';

void main() async {
  // Generate a key pair
  final keyPair = KeyPair.generate();
  print('npub: ${Nip19.npubEncode(keyPair.publicKey)}');

  // Create and sign a text note
  final event = Event.sign(
    kind: EventKind.textNote,
    content: 'Hello Nostr!',
    privateKey: keyPair.privateKey,
  );

  // Connect to a relay and publish
  final relay = Relay('wss://relay.damus.io');
  await relay.connect();
  relay.sendEvent(event);

  // Subscribe to events
  relay.subscribe([Filter(kinds: [1], limit: 10)]);

  // Handle messages with pattern matching
  relay.messages.listen((message) {
    switch (message) {
      case EventMessage(:final event):
        print('Received: ${event.content}');
      case EoseMessage():
        print('End of stored events');
      case OkMessage(:final accepted):
        print('Event ${accepted ? "accepted" : "rejected"}');
      case NoticeMessage(:final message):
        print('Notice: $message');
      case AuthMessage(:final challenge):
        print('Auth challenge: $challenge');
      default:
        break;
    }
  });
}

Usage #

Key Generation & Derivation #

// Random keypair
final keyPair = KeyPair.generate();

// From existing private key
final fromPrivate = KeyPair.fromPrivateKey('deadbeef...');

// BIP-39 mnemonic derivation (NIP-06)
final mnemonic = KeyDerivation.generateMnemonic();
final derived = KeyDerivation.deriveKeyPair(mnemonic);

// Multiple accounts from one mnemonic
final accounts = KeyDerivation.deriveKeyPairs(mnemonic, count: 5);

// Encrypt private key with password (NIP-49)
final ncryptsec = Nip49.encrypt(privateKey: keyPair.privateKey, password: 'hunter2');
final decrypted = Nip49.decrypt(ncryptsec: ncryptsec, password: 'hunter2');

NIP-19 Encoding #

// Encode/decode bech32 identifiers
final npub = Nip19.npubEncode(publicKey);
final nsec = Nip19.nsecEncode(privateKey);
final note = Nip19.noteEncode(eventId);

// Rich identifiers with relay hints
final nprofile = Nip19.nprofileEncode(
  pubkey: publicKey,
  relays: ['wss://relay.damus.io'],
);

final nevent = Nip19.neventEncode(
  eventId: eventId,
  relays: ['wss://relay.damus.io'],
  author: publicKey,
);

// Universal decoder
final decoded = Nip19.decodeBech32(anyBech32String);
switch (decoded) {
  case Nip19Npub(:final pubkey):
    print('Public key: $pubkey');
  case Nip19Nsec(:final seckey):
    print('Private key: $seckey');
  case Nip19NProfile(:final profile):
    print('Profile: ${profile.pubkey} at ${profile.relays}');
  // ... etc
}

// Nostr URIs (NIP-21)
final uri = Nip21.encodeNpub(publicKey); // nostr:npub1...
final refs = Nip21.extractUris(someText); // Extract all nostr: URIs

Events & Signing #

// Text note
final note = Event.sign(
  kind: EventKind.textNote,
  content: 'Hello world!',
  privateKey: privateKey,
);

// Verify any event
final isValid = note.verify();

// Unsigned event (for remote signing via NIP-46)
final unsigned = Event.unsigned(
  kind: EventKind.textNote,
  content: 'Sign me remotely',
  pubkey: publicKey,
);

// Sign an unsigned event later
final signed = Event.fromUnsigned(unsigned, privateKey);

// Re-sign an event with a different key
final reSigned = signed.withNewSignature(otherPrivateKey);

// Serialize
final json = event.toJson();
final restored = Event.fromJson(json);

// Event classification
event.isReplaceable;  // kind 0, 3, 10000-19999
event.isEphemeral;    // kind 20000-29999
event.isAddressable;  // kind 30000-39999

Relay Communication #

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

// Subscribe with filters
final subId = relay.subscribe([
  Filter(kinds: [1], authors: [pubkey], limit: 20),
]);

// Send events
relay.sendEvent(signedEvent);

// Close subscription
relay.closeSubscription(subId);

// Auto-reconnecting relay (exponential backoff + subscription replay)
final reconnecting = ReconnectingRelay(
  'wss://relay.damus.io',
  initialDelay: Duration(seconds: 1),
  maxDelay: Duration(seconds: 60),
);
await reconnecting.connect();
reconnecting.onReconnect.listen((_) => print('Reconnected!'));
reconnecting.subscribe([Filter(kinds: [1], limit: 10)]); // Replayed on reconnect

// Relay pool (multiple relays)
final pool = RelayPool([
  'wss://relay.damus.io',
  'wss://nos.lol',
  'wss://relay.nostr.band',
]);
final result = await pool.connectAll();
print('Connected: ${result.connected}, Failed: ${result.failed}');
pool.sendEvent(event); // Broadcasts to all connected

// Pool messages tagged with source relay
pool.messages.listen((msg) {
  print('From ${msg.url}: ${msg.message}');
});

// Relay information (NIP-11)
final info = await RelayInformation.fetch('wss://relay.damus.io');
print('Name: ${info.name}, NIPs: ${info.supportedNips}');
print('Relay pubkey: ${info.self}, Payments: ${info.paymentsUrl}');

// NIP-42 Authentication
relay.messages.listen((message) {
  if (message case AuthMessage(:final challenge)) {
    final auth = Nip42.createAuthEvent(
      challenge: challenge,
      relayUrl: 'wss://relay.damus.io',
      privateKey: privateKey,
    );
    relay.sendAuth(auth);
  }
});

// NIP-45 Counting
final countSubId = relay.count([Filter(kinds: [1], authors: [pubkey])]);

Social Features #

// User metadata (NIP-24)
final metadata = Nip24.createMetadata(
  privateKey: privateKey,
  name: 'satoshi',
  displayName: 'Satoshi Nakamoto',
  about: 'Creator of Bitcoin',
  picture: 'https://example.com/avatar.png',
  nip05: 'satoshi@example.com',
  lud16: 'satoshi@getalby.com',
);

// Follow list (NIP-02)
final follows = Nip02.createFollowList(
  privateKey: privateKey,
  follows: [
    FollowEntry(pubkey: somePubkey, relayUrl: 'wss://relay.damus.io'),
  ],
);

// Reactions (NIP-25)
final like = Nip25.like(
  privateKey: privateKey,
  eventId: targetEventId,
  eventAuthor: targetPubkey,
);

// Replies with threading (NIP-10)
final reply = Nip10.createReply(
  privateKey: privateKey,
  content: 'Great post!',
  replyToId: parentEventId,
  replyToAuthor: parentPubkey,
  rootId: rootEventId,
  rootAuthor: rootPubkey,
);

// Reposts (NIP-18)
final repost = Nip18.createRepost(
  privateKey: privateKey,
  event: originalEvent,
);

// Event deletion (NIP-09)
final deletion = Nip09.createDeletionRequest(
  privateKey: privateKey,
  eventIds: [eventId1, eventId2],
  reason: 'Posted by mistake',
);

// DNS identity verification (NIP-05)
final result = await Nip05.verify('satoshi@example.com');
print('Pubkey: ${result?.pubkey}, Relays: ${result?.relays}');

Content Types #

// Long-form articles (NIP-23)
final article = Nip23.createArticle(
  privateKey: privateKey,
  identifier: 'my-first-post',
  content: '# Hello World\n\nThis is my article...',
  title: 'My First Post',
  summary: 'An introduction',
  image: 'https://example.com/cover.jpg',
  hashtags: ['nostr', 'intro'],
);

// Comments on any content (NIP-22)
final comment = Nip22.createComment(
  privateKey: privateKey,
  content: 'Interesting article!',
  rootEventId: articleEventId,
  rootEventKind: 30023,
  rootEventPubkey: authorPubkey,
);

// Picture posts (NIP-68)
final picture = Nip68.createPicture(
  privateKey: privateKey,
  content: 'Beautiful sunset',
  imageUrls: ['https://example.com/sunset.jpg'],
  altText: 'A golden sunset over the ocean',
);

// Video events (NIP-71)
final video = Nip71.createVideoEvent(
  privateKey: privateKey,
  identifier: 'my-video',
  title: 'Demo Video',
  url: 'https://example.com/video.mp4',
  duration: 120,
);

// Wiki articles (NIP-54)
final wiki = Nip54.createArticle(
  privateKey: privateKey,
  topic: 'Nostr Protocol',
  content: 'Nostr is a simple, open protocol...',
);

// Code snippets (NIP-C0)
final snippet = NipC0.createCodeSnippet(
  privateKey: privateKey,
  code: 'void main() => print("Hello");',
  language: 'dart',
  description: 'Hello world in Dart',
);

Encryption & Private Messaging #

// NIP-44 encryption (recommended)
final encrypted = Nip44.encrypt(senderPrivateKey, recipientPublicKey, 'Secret message');
final decrypted = Nip44.decrypt(recipientPrivateKey, senderPublicKey, encrypted);

// NIP-04 encryption (legacy, deprecated)
final legacy = Nip04.encrypt(senderPrivateKey, recipientPublicKey, 'Secret');

// Private direct messages (NIP-17 + NIP-59 gift wrap)
final dm = Nip17.createDirectMessage(
  senderPrivateKey: privateKey,
  recipientPublicKey: recipientPubkey,
  content: 'Private message with full metadata protection',
);

// Gift wrap for metadata protection (NIP-59)
final wrapped = Nip59.createGiftWrap(
  senderPrivateKey: privateKey,
  recipientPublicKey: recipientPubkey,
  rumor: unsignedEvent,
);
final unwrapped = Nip59.unwrapGiftWrap(
  recipientPrivateKey: privateKey,
  giftWrap: wrapped,
);

Payments & Wallets #

// Lightning zap request (NIP-57)
final zapRequest = Nip57.createZapRequest(
  privateKey: privateKey,
  recipientPubkey: recipientPubkey,
  relays: ['wss://relay.damus.io'],
  amountMillisats: 21000,
  content: 'Great post!',
);

// LNURL-pay endpoint resolution (NIP-57)
final lnurlResponse = await Nip57.fetchLnurlPayEndpoint(lnurlString);
print('Callback: ${lnurlResponse.callback}');
print('Nostr zaps supported: ${lnurlResponse.allowsNostr}');
print('Range: ${lnurlResponse.minSendable}-${lnurlResponse.maxSendable} msats');

// Nostr Wallet Connect (NIP-47)
final payRequest = Nip47.payInvoice(
  privateKey: privateKey,
  walletPubkey: walletServicePubkey,
  invoice: 'lnbc...',
);

// Zap goals (NIP-75)
final goal = Nip75.createZapGoal(
  privateKey: privateKey,
  content: 'Fund my project',
  amount: 1000000, // sats
);

// Ecash wallet (NIP-60)
// Cashu token events, spending history, mint quotes

// Peer-to-peer orders (NIP-69)
final order = Nip69.createOrder(
  privateKey: privateKey,
  identifier: 'order-001',
  orderType: OrderType.sell,
  currency: 'USD',
  status: OrderStatus.pending,
  amount: 100000,
  paymentMethods: ['paypal', 'bank-transfer'],
);
final updatedOrder = Nip69.updateOrderStatus(
  privateKey: privateKey,
  order: Nip69.parseOrder(order),
  newStatus: OrderStatus.inProgress,
);

Lists & Organization #

// Mute list (NIP-51)
final muteList = Nip51.createMuteList(
  privateKey: privateKey,
  pubkeys: [spammerPubkey],
  eventIds: [spamEventId],
);

// Bookmark list
final bookmarks = Nip51.createBookmarkList(
  privateKey: privateKey,
  eventIds: [savedEventId],
  coordinates: ['30023:pubkey:article-id'],
);

// Relay list (NIP-65)
final relayList = Nip65.createRelayList(
  privateKey: privateKey,
  relays: [
    RelayListEntry(url: 'wss://relay.damus.io', read: true, write: true),
    RelayListEntry(url: 'wss://relay.nostr.band', read: true, write: false),
  ],
);

Communities & Groups #

// Public chat channels (NIP-28)
final channel = Nip28.createChannel(
  privateKey: privateKey,
  name: 'Dart Nostr Dev',
  about: 'Discussion about nostr_nips',
);

// Relay-based groups (NIP-29)
final groupMsg = Nip29.createGroupMessage(
  privateKey: privateKey,
  groupId: 'my-group',
  content: 'Hello group!',
);

// Group administration (NIP-29)
Nip29.adminCreateGroup(privateKey: adminKey, groupId: 'new-group');
Nip29.adminAddUser(privateKey: adminKey, groupId: 'my-group', userPubkey: pubkey);
Nip29.adminSetName(privateKey: adminKey, groupId: 'my-group', name: 'New Name');
Nip29.adminAddPermission(privateKey: adminKey, groupId: 'my-group', userPubkey: pubkey, permission: 'write');
Nip29.adminCreateInvite(privateKey: adminKey, groupId: 'my-group', code: 'invite123');

// Moderated communities (NIP-72)
final community = Nip72.createCommunity(
  privateKey: privateKey,
  identifier: 'dart-devs',
  description: 'Dart developers community',
  moderators: [modPubkey],
);

// Live activities (NIP-53)
final liveEvent = Nip53.createLiveEvent(
  privateKey: privateKey,
  identifier: 'my-stream',
  title: 'Live coding session',
  status: LiveStatus.live,
  streaming: 'https://stream.example.com/live',
);

Advanced Features #

// Proof of work (NIP-13)
final powEvent = Nip13.generateWithPow(
  kind: 1,
  content: 'This has proof of work',
  privateKey: privateKey,
  targetDifficulty: 20,
);

// Remote signing / Nostr Connect (NIP-46)
final connUri = Nip46.parseConnectionUri('bunker://pubkey?relay=wss://...');

// Calendar events (NIP-52)
final calEvent = Nip52.createTimeEvent(
  privateKey: privateKey,
  identifier: 'meetup-2024',
  title: 'Nostr Meetup',
  start: DateTime(2024, 6, 15, 18, 0),
  end: DateTime(2024, 6, 15, 20, 0),
);

// Marketplace (NIP-15)
final product = Nip15.createProduct(
  privateKey: privateKey,
  stallId: 'my-stall',
  identifier: 'widget-001',
  name: 'Nostr Widget',
  currency: 'USD',
  price: 9.99,
);

// Labeling (NIP-32)
final label = Nip32.createEventLabel(
  privateKey: privateKey,
  eventId: targetEventId,
  namespace: 'quality',
  labels: ['high-quality'],
);

// Data Vending Machines (NIP-90)
final jobRequest = Nip90.createJobRequest(
  privateKey: privateKey,
  kind: Nip90.textGeneration,
  inputs: [JobInput.text('Write a haiku about Nostr')],
);

// Negentropy syncing (NIP-77)
final state = NegentropyState(records);
final initMsg = Nip77.createNegOpen(filter: filter, initialMessage: state.initiate());

// Relay management API (NIP-86)
final management = Nip86.createClient(
  relayUrl: 'https://relay.example.com',
  privateKey: adminPrivateKey,
);
await management.banPubkey(spammerPubkey, reason: 'Spam');

// Bluetooth LE communication (NIP-BE)
final chunks = NipBE.chunkMessage(eventJson, mtu: 512);

Architecture #

lib/
  nostr_nips.dart              # Barrel export (single import)
  src/
    core/
      event.dart               # Event model, signing, verification
      event_kind.dart          # 100+ event kind constants
      filter.dart              # Subscription filters
      key_pair.dart            # Key generation and validation
      exceptions.dart          # Custom exception hierarchy
    crypto/
      signer.dart              # BIP-340 Schnorr signing
      verifier.dart            # Signature verification
      nip04.dart               # AES-256-CBC encryption (legacy)
      nip44.dart               # ChaCha20 + HMAC encryption (modern)
      key_derivation.dart      # BIP-39/BIP-32 mnemonic derivation
    relay/
      relay.dart               # Single WebSocket relay connection
      reconnecting_relay.dart  # Auto-reconnect with backoff
      relay_pool.dart          # Multi-relay management
      relay_info.dart          # NIP-11 relay information documents
      message.dart             # Sealed message type hierarchy
    encoding/
      bech32.dart              # BIP-173 bech32 codec
      tlv.dart                 # Type-Length-Value binary encoding
      nip19.dart               # npub/nsec/note/nprofile/nevent/naddr
      nip21.dart               # nostr: URI scheme
    nips/
      nip02.dart ... nip_ee.dart  # Individual NIP implementations

Supported NIPs #

NIP Name Status
NIP-01 Basic Protocol Core (Event, Filter, KeyPair, Relay)
NIP-02 Follow List Nip02
NIP-03 OpenTimestamps Nip03
NIP-04 Encrypted DMs (legacy) Nip04 (deprecated)
NIP-05 DNS Identifiers Nip05
NIP-06 Key Derivation KeyDerivation
NIP-07 Browser Signer Nip07Signer (interface)
NIP-08 Mentions (legacy) Nip08 (deprecated)
NIP-09 Event Deletion Nip09
NIP-10 Text Threads Nip10
NIP-11 Relay Information RelayInformation
NIP-13 Proof of Work Nip13
NIP-14 Subject Tag Nip14
NIP-15 Marketplace Nip15
NIP-17 Private DMs Nip17
NIP-18 Reposts Nip18
NIP-19 Bech32 Encoding Nip19
NIP-21 Nostr URIs Nip21
NIP-22 Comments Nip22
NIP-23 Long-form Content Nip23
NIP-24 Extra Metadata Nip24
NIP-25 Reactions Nip25
NIP-26 Delegation (legacy) Nip26 (deprecated)
NIP-27 Text References Nip27
NIP-28 Public Chat Nip28
NIP-29 Relay Groups Nip29
NIP-30 Custom Emoji Nip30
NIP-31 Unknown Events Nip31
NIP-32 Labeling Nip32
NIP-34 Git Stuff Nip34
NIP-35 Torrents Nip35
NIP-36 Content Warning Nip36
NIP-37 Drafts Nip37
NIP-38 User Statuses Nip38
NIP-39 External Identities Nip39
NIP-40 Expiration Nip40
NIP-42 Relay Auth Nip42
NIP-43 Relay Access Nip43
NIP-44 Encryption v2 Nip44
NIP-45 Counting Nip45
NIP-46 Remote Signing Nip46
NIP-47 Wallet Connect Nip47
NIP-48 Proxy Tags Nip48
NIP-49 Key Encryption Nip49
NIP-50 Search Nip50
NIP-51 Lists Nip51
NIP-52 Calendar Events Nip52
NIP-53 Live Activities Nip53
NIP-54 Wiki Nip54
NIP-55 Android Signer Nip55 (interface)
NIP-56 Reporting Nip56
NIP-57 Lightning Zaps Nip57
NIP-58 Badges Nip58
NIP-59 Gift Wrap Nip59
NIP-60 Cashu Wallet Nip60
NIP-61 Nutzaps Nip61
NIP-62 Request to Vanish Nip62
NIP-64 Chess (PGN) Nip64
NIP-65 Relay List Nip65
NIP-66 Relay Discovery Nip66
NIP-68 Picture Posts Nip68
NIP-69 P2P Orders Nip69
NIP-70 Protected Events Nip70
NIP-71 Video Events Nip71
NIP-72 Communities Nip72
NIP-73 External Content IDs Nip73
NIP-75 Zap Goals Nip75
NIP-77 Negentropy Sync Nip77
NIP-78 App Data Nip78
NIP-84 Highlights Nip84
NIP-85 Trusted Assertions Nip85
NIP-86 Relay Management Nip86
NIP-87 Ecash Mints Nip87
NIP-88 Polls Nip88
NIP-89 App Handlers Nip89
NIP-90 Data Vending Machines Nip90
NIP-92 Media Attachments Nip92
NIP-94 File Metadata Nip94
NIP-96 File Storage Nip96 (deprecated)
NIP-98 HTTP Auth Nip98
NIP-99 Classified Listings Nip99
NIP-7D Threads Nip7D
NIP-A0 Voice Messages NipA0
NIP-A4 Public Messages NipA4
NIP-B0 Web Bookmarks NipB0
NIP-B7 Blossom (Blob Storage) NipB7
NIP-BE BLE Communication NipBE
NIP-C0 Code Snippets NipC0
NIP-C7 Chats NipC7
NIP-EE E2EE (MLS) NipEE (deprecated)

Dependencies #

Package Purpose
bip340 Schnorr signatures (BIP-340)
pointycastle Cryptographic primitives (AES, ECDH, HMAC, ChaCha20)
bip39 Mnemonic seed phrase generation
web_socket_channel WebSocket relay communication
http HTTP for NIP-05, NIP-11, NIP-86, NIP-96
convert Hex encoding/decoding
crypto SHA-256 hashing
unorm_dart Unicode NFKC normalization (NIP-49)

Running Tests #

dart test

License #

MIT License -- see LICENSE for details.

0
likes
140
points
11
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A comprehensive Dart library implementing all Nostr protocol NIPs (Nostr Implementation Possibilities).

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

bip340, bip39, convert, crypto, http, pointycastle, unorm_dart, web_socket_channel

More

Packages that depend on nostr_nips