nostr_nips 0.2.0
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-21nostr: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.