#nostr #relay #data #structures #protocols #ecosystem #nip-01

nostro2

Nostro2 is a simple toolset for interacting with the Nostr protocol

39 releases

new 0.2.2 Dec 8, 2024
0.1.35 Dec 6, 2024
0.1.34 Nov 30, 2024
0.1.25 May 22, 2024
0.1.7 Oct 25, 2023

#26 in WebSocket

Download history 2/week @ 2024-08-29 126/week @ 2024-09-12 91/week @ 2024-09-19 173/week @ 2024-09-26 10/week @ 2024-10-03 321/week @ 2024-10-10 9/week @ 2024-10-17 157/week @ 2024-10-24 75/week @ 2024-10-31 41/week @ 2024-11-07 1/week @ 2024-11-14 47/week @ 2024-11-21 525/week @ 2024-11-28 556/week @ 2024-12-05

1,129 downloads per month
Used in 2 crates

MIT license

82KB
2K SLoC

NostrO2

Simple yet powerful Rust tools for interacting with the Nostr ecosystem. Built on top of serde and tokio for easy integration with other Rust libraries. Supports WASM compilation for use in web applications.

Features

The library provides redy to use types for the main data structures of the Nostr protocol, as well as a simple interface for interacting with a relay, or a more complex interface for creating a relay pool.

NostrNotes

The main data structures of Nostr, as defined by NIP-01. NostrNotes can be created using default helpers like this:

    let note = NostrNote {
        content: "Hello World".to_string(),
        kind: 300,
        public_key: "0x1234567890abcdef".to_string(),
        ..Default::default()
    };

NostrKeypair

Can be created from a private key str or a mnemonic phrase and will allow you to sign Nostr Notes.

    let new_user = NostrKeypair::new("<64-bit hex string>").expect("Failed to create user keys");
    let mut note = NostrNote {
        content: "Hello World".to_string(),
        kind: 300,
        public_key: "0x1234567890abcdef".to_string(),
        ..Default::default()
    };
    note.add_tag(NostrTag::Custom("t", "myCustomTag"));
    user_key_pair.sign_nostr_event(&mut unsigned_note); // -> Modifies the note in place

Subscriptions

Create a new NostrSubscription using the default constructor and then add filters to it. Filters correspond to the object described by NIP-01. Using the relay_event() method, you can create a new event that can be sent to a relay.

let subscription = 
    NostrFilter {
        kinds: vec![0, 1],
        ..Default::default()
        vec![0, 1]
    ).relay_event();

println!("Subscribe to relay with id: {}", subscription.1);

NostrRelay

Ready-to-go connection to a relay. The NostrRelay has two main parts, a reader and a writer. The writer is reference counted and can be cloned to send events to the relay across multiple threads. The reader is a single stream that can be used to receive events from the relay. NostrRelay also holds its url and connection state internally.

let mut relay = NostrRelay::new("wss://relay.illuminodes.com").await?;
let filter = NostrSubscription {
    kinds: Some(vec![1]),
    limit: Some(3),
    ..Default::default()
}
.relay_subscription();
let id = relay.writer.subscribe(filter).await?;
_debug("Subscribed with id");

let mut finished = String::new();
let mut ws_stream = relay.relay_event_stream()?;
while let Some(event) = ws_stream.next().await {
    match event {
        RelayEvent::EndOfSubscription(EndOfSubscriptionEvent(_, id)) => {
            _debug(&format!("End of subscription: {}", id));
            finished = id;
            break;
        }
        _ => (),
    }
}

Relay Pool

The NostrRelayPool is a more complex interface for managing multiple relays. It's created with a list of relay urls and will connect to all relays concurrently. The pool will then manage the connections and distribute events to the relays in a round-robin fashion. Unique NostrNotes are kep in a library and duplicate notes are filtered out. The pool holds a RelayTable that keeps the connection state of each relay.

let mut pool = NostrRelayPool::new(vec![
    "wss://relay.arrakis.lat".to_string(),
    "wss://relay.illuminodes.com".to_string(),
    "wss://frens.nostr1.com".to_string(),
    "wss://bitcoiner.social".to_string(),
    "wss://bouncer.minibolt.info".to_string(),
    "wss://freespeech.casa".to_string(),
    "wss://junxingwang.org".to_string(),
    "wss://nostr.0x7e.xyz".to_string(),
])
.await
.expect("Failed to create pool");
let filter = NostrSubscription {
    kinds: Some(vec![1]),
    limit: Some(10),
    ..Default::default()
}
.relay_subscription();
pool.subscribe(filter.clone())
    .await
    .expect("Failed to subscribe");
let mut events = vec![];
while let Some((_, event)) = pool.listener.recv().await {
    if let RelayEvent::EndOfSubscription(EndOfSubscriptionEvent(_, ref subscription_id)) =
        event
    {
        events.push(subscription_id.clone());
        if events.len() == 5 {
            break;
        }
    }
    if let RelayEvent::NewNote(NoteEvent(_, _, _)) = event {}
}

Nostr Authentication

The NostrNotes objects also provide a verification method for both content and signatures.

    assert_eq!(signed_note.verify(), true);

Installation

Run cargo add nostro2 to get the latest version.

You can also add nostro2 to your Cargo.toml dependencies:

[dependencies]
nostro2 = "0.2.0"

Dependencies

~10–24MB
~340K SLoC