Skip to content

ethicnology/dart-nostr

Repository files navigation

License: LGPL v3 nostr CI pub package codecov

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

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