Awesome
nostr
A library for nostr protocol implemented in dart for flutter.
Dispute is a basic nostr client written in flutter with this library that will show you an implementation.
Getting started
flutter pub add nostr
NIPS
- NIP 01 Basic protocol flow description
- NIP 02 Contact List and Petnames
- NIP 04 Encrypted Direct Message
- NIP 05 Mapping Nostr keys to DNS-based internet identifiers
- NIP 10 Conventions for clients' use of e and p tags in text events
- NIP 15 End of Stored Events Notice
- NIP 19 bech32-encoded entities
- NIP 20 Command Results
- NIP 28 Public Chat
- NIP 50 Search Capability
- NIP 51 Lists
Usage
Events messages
import 'dart:io';
import 'package:nostr/nostr.dart';
void main() async {
// Use the Keychain class to manipulate private/public keys and use handy methods encapsulated from dart-bip340
var keys = Keychain(
"5ee1c8000ab28edd64d74a7d951ac2dd559814887b1b9e1ac7c5f89e96125c12",
);
assert(keys.public ==
"981cc2078af05b62ee1f98cff325aac755bf5c5836a265c254447b5933c6223b");
// or generate random keys
var randomKeys = Keychain.generate();
print(randomKeys.private);
// Instantiate an event with all the field
String id =
"4b697394206581b03ca5222b37449a9cdca1741b122d78defc177444e2536f49";
String pubkey = keys.public;
int createdAt = 1672175320;
int kind = 1;
List<List<String>> tags = [];
String content = "Ceci est une analyse du websocket";
String sig =
"797c47bef50eff748b8af0f38edcb390facf664b2367d72eb71c50b5f37bc83c4ae9cc9007e8489f5f63c66a66e101fd1515d0a846385953f5f837efb9afe885";
Event oneEvent = Event(
id,
pubkey,
createdAt,
kind,
tags,
content,
sig,
);
assert(oneEvent.id ==
"4b697394206581b03ca5222b37449a9cdca1741b122d78defc177444e2536f49");
// Create a partial event from nothing and fill it with data until it is valid
var partialEvent = Event.partial();
assert(partialEvent.isValid() == false);
partialEvent.createdAt = currentUnixTimestampSeconds();
partialEvent.pubkey =
"981cc2078af05b62ee1f98cff325aac755bf5c5836a265c254447b5933c6223b";
partialEvent.id = partialEvent.getEventId();
partialEvent.sig = partialEvent.getSignature(
"5ee1c8000ab28edd64d74a7d951ac2dd559814887b1b9e1ac7c5f89e96125c12",
);
assert(partialEvent.isValid() == true);
// Instantiate an event with a partial data and let the library sign the event with your private key
Event anotherEvent = Event.from(
kind: 1,
tags: [],
content: "vi veri universum vivus vici",
privkey:
"5ee1c8000ab28edd64d74a7d951ac2dd559814887b1b9e1ac7c5f89e96125c12", // DO NOT REUSE THIS PRIVATE KEY
);
assert(anotherEvent.pubkey ==
"981cc2078af05b62ee1f98cff325aac755bf5c5836a265c254447b5933c6223b");
// Connecting to a nostr relay using websocket
WebSocket webSocket = await WebSocket.connect(
'wss://relay.nostr.info', // or any nostr relay
);
// if the current socket fail try another one
// wss://nostr.sandwich.farm
// wss://relay.damus.io
// Send an event to the WebSocket server
webSocket.add(anotherEvent.serialize());
// Listen for events from the WebSocket server
await Future.delayed(Duration(seconds: 1));
webSocket.listen((event) {
print('Received event: $event');
});
// Close the WebSocket connection
await webSocket.close();
}
Request messages and filters
import 'dart:io';
import 'package:nostr/nostr.dart';
void main() async {
// Create a subscription message request with one or many filters
Request requestWithFilter = Request(generate64RandomHexChars(), [
Filter(
kinds: [0, 1, 2, 7],
since: 1674063680,
limit: 450,
)
]);
// Connecting to a nostr relay using websocket
WebSocket webSocket = await WebSocket.connect(
'wss://relay.nostr.info', // or any nostr relay
);
// if the current socket fail try another one
// wss://nostr.sandwich.farm
// wss://relay.damus.io
// Send a request message to the WebSocket server
webSocket.add(requestWithFilter.serialize());
// Listen for events from the WebSocket server
await Future.delayed(Duration(seconds: 1));
webSocket.listen((event) {
print('Received event: $event');
});
// Close the WebSocket connection
await webSocket.close();
}
Close subscription
import 'package:nostr/nostr.dart';
void main() async {
String subscriptionId = generate64RandomHexChars();
var close1 = Close(subscriptionId);
assert(close1.subscriptionId == subscriptionId);
var close2 = Close(subscriptionId);
assert(close2.serialize() == '["CLOSE","$subscriptionId"]');
var close3 = Close.deserialize(["CLOSE", subscriptionId]);
assert(close3.subscriptionId == subscriptionId);
}
Any nostr message deserializer
import 'package:nostr/nostr.dart';
void main() async {
var eventPayload =
'["EVENT","3979053091133091",{"id":"a60679692533b308f1d862c2a5ca5c08a304e5157b1df5cde0ff0454b9920605","pubkey":"7c579328cf9028a4548d5117afa4f8448fb510ca9023f576b7bc90fc5be6ce7e","created_at":1674405882,"kind":1,"tags":[],"content":"GM gm gm! Currently bathing my brain in coffee âï¸ hahaha. How many other nostrinos love coffee? ð¤ªð¤","sig":"10262aa6a83e0b744cda2097f06f7354357512b82846f6ef23ef7d997136b64815c343b613a0635a27da7e628c96ac2475f66dd72513c1fb8ce6560824eb25b8"}]';
var event = Message.deserialize(eventPayload);
assert(event.type == "EVENT");
assert(event.message.id ==
"a60679692533b308f1d862c2a5ca5c08a304e5157b1df5cde0ff0454b9920605");
String requestPayload =
'["REQ","22055752544101437",{"kinds":[0,1,2,7],"since":1674320733,"limit":450}]';
var req = Message.deserialize(requestPayload);
assert(req.type == "REQ");
assert(req.message.filters[0].limit == 450);
String closePayload = '["CLOSE","anyrandomstring"]';
var close = Message.deserialize(closePayload);
assert(close.type == "CLOSE");
assert(close.message.subscriptionId == "anyrandomstring");
String noticePayload =
'["NOTICE", "restricted: we can\'t serve DMs to unauthenticated users, does your client implement NIP-42?"]';
var notice = Message.deserialize(noticePayload);
assert(notice.type == "NOTICE");
String eosePayload = '["EOSE", "random"]';
var eose = Message.deserialize(eosePayload);
assert(eose.type == "EOSE");
String okPayload =
'["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", true, ""]';
var ok = Message.deserialize(okPayload);
assert(ok.type == "OK");
}
NIP 02 Contact List and Petnames
import 'package:nostr/nostr.dart';
void main() {
// Decode profiles from an event of kind=3
var event = Event.from(
kind: 3,
tags: [
["p", "91cf9..4e5ca", "wss://alicerelay.com/", "alice"],
["p", "14aeb..8dad4", "wss://bobrelay.com/nostr", "bob"],
["p", "612ae..e610f", "ws://carolrelay.com/ws", "carol"],
],
content: "",
privkey: "5ee1c8000ab28edd64d74a7d951ac2dd559814887b1b9e1ac7c5f89e96125c12",
);
List<Profile> someProfiles = Nip2.decode(event);
assert(someProfiles[0].key == "91cf9..4e5ca");
assert(someProfiles[1].relay == "wss://bobrelay.com/nostr");
assert(someProfiles[2].petname == "carol");
// Instantiate a new nip2 profile
String key = "91cf9..4e5ca";
String relay = "wss://alicerelay.com/";
String petname = "alice";
var alice = Profile(key, relay, petname);
List<Profile> profiles = [
alice,
Profile("21df6d143fb96c2ec9d63726bf9edc71", "", "erin")
];
// Encode profiles to nostr event.tags
List<List<String>> tags = Nip2.toTags(profiles);
assert(tags[1][0] == "p");
assert(tags[1][3] == "erin");
// Decode event.tags to profiles list
List<Profile> newProfiles = Nip2.toProfiles([
["p", "91cf9..4e5ca", "wss://alicerelay.com/", "alice"],
["p", "14aeb..8dad4", "wss://bobrelay.com/nostr", "bob"],
["p", "612ae..e610f", "ws://carolrelay.com/ws", "carol"]
]);
assert(newProfiles[2].petname == "carol");
}