rust-nostr

Project Homepage | Repository | Become a supporter

The nostr development kit to build fast, stable and efficient apps for any environment!

Why rust-nostr?

  • Multi-Language Support: We support native API's including Rust, Python, JavaScript, Kotlin, Swift and Flutter so you can build nostr apps in your preferred programming language.

  • Multi-Platform Support: Write nostr apps for desktop, server, mobile, web and/or embedded devices!

  • WASM Compatibility: Most of our libraries compile to WebAssembly so that they can be integrated into web applications.

  • High performance: Powered by Rust's unparalleled performance and memory safety, our libraries offers speed, stability and reliability. The same features are extended to its bindings in other languages, ensuring optimal stability across diverse development environments.

  • Broad NIP Support: Support to most relevant NIPs.

  • Customizable: The libraries are built in modular way, allowing to build customized nostr apps.

Communication

State

These libraries are in ALPHA state, things that are implemented generally work but the API will change in breaking ways.

Donations

rust-nostr is free and open-source. This means we do not earn any revenue by selling it. Instead, we rely on your financial support. If you actively use any of the rust-nostr libs/software/services, then please donate.

License

This project is distributed under the MIT software license.

Getting Started

Let’s start your rust-nostr journey! In this chapter, we’ll discuss:

  • Installing the nostr-sdk library
  • Writing a program that publish a Hello, rust-nostr! text note

Installing the library

Rust

Add the nostr-sdk dependency in your Cargo.toml file:

[dependencies]
nostr-sdk = "0.38"

Alternatively, you can add it directly from git source:

[dependencies]
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", tag = "v0.38.0" }

Info

To use a specific commit, use rev instead of tag.

Import the library in your code:

#![allow(unused)]
fn main() {
use nostr_sdk::prelude::*;
}
Python

The nostr-sdk package is available on the public PyPI:

pip install nostr-sdk 

Alternatively, you can manually add the dependency in your requrements.txt, setup.py, etc.:

nostr-sdk==0.38.0

Import the library in your code:

from nostr_sdk import *

Support matrix

The wheels are distributed for the following python versions and platforms. If your version/platform is not currently supported, you can compile the wheel by your self following these instructions.

Python version

3.83.93.103.113.123.13

Platform support

OSx64aarch64armi686
Linux
macOS
Windows

Known issues

No running event loop

If you receive no running event loop error at runtime, add the following line to your code:

import asyncio
from nostr_sdk import uniffi_set_event_loop

uniffi_set_event_loop(asyncio.get_running_loop())
JavaScript

The nostr-sdk package is available on the public npmjs:

npm i @rust-nostr/nostr-sdk

Alternatively, you can manually add the dependency in your package.json file:

{
    "dependencies": {
        "@rust-nostr/nostr-sdk": "0.38.0"
    }
}

WASM

This library to work require to load the WASM code.

Load in async context

import { loadWasmAsync } from "@rust-nostr/nostr-sdk";

async function main() {
    // Load WASM
    await loadWasmAsync();

    // ...
}

main();

Load in sync context

import { loadWasmSync } from "@rust-nostr/nostr-sdk";

function main() {
    // Load WASM
    loadWasmSync();

    // ...
}

main();
Kotlin

To use the Kotlin language bindings for nostr-sdk in your Android project add the following to your gradle dependencies:

repositories {
    mavenCentral()
}

dependencies { 
    implementation("org.rust-nostr:nostr-sdk:0.38.3")
}

Import the library in your code:

import rust.nostr.sdk.*

Known issues

JNA dependency

Depending on the JVM version you use, you might not have the JNA dependency on your classpath. The exception thrown will be

class file for com.sun.jna.Pointer not found

The solution is to add JNA as a dependency like so:

dependencies {
    // ...
    implementation("net.java.dev.jna:jna:5.15.0@aar")
}
Swift

Xcode

Via File > Add Packages..., add

https://github.com/rust-nostr/nostr-sdk-swift.git

as a package dependency in Xcode.

Swift Package

Add the following to the dependencies array in your Package.swift:

.package(url: "https://github.com/rust-nostr/nostr-sdk-swift.git", from: "0.38.0"),
Flutter

Add the following code to your package:

nostr_sdk:
    git:
        url: https://github.com/rust-nostr/nostr-sdk-flutter.git
        ref: 2e63f8f6cfac533061b8f8f776898aca3291d95a

Hello, rust-nostr!

Now that you’ve installed the SDK, it’s time to write your first nostr program.

Generate random keys and construct the client

The first step is to generate random keys needed for the client and construct the client instance.

Rust
let keys: Keys = Keys::generate();
let client = Client::new(keys);
Python
keys = Keys.generate()
client = Client(keys)
JavaScript
let keys: Keys = Keys.generate();
let signer = NostrSigner.keys(keys);
let client = new Client(signer);
Kotlin
val keys = Keys.generate()
val signer = NostrSigner.keys(keys)
val client = Client(signer = signer)
Swift
let keys = Keys.generate()
let signer = NostrSigner.keys(keys: keys)
let client = Client(signer: signer)
Flutter
Keys keys = Keys.generate();
NostrSigner signer = NostrSigner.keys(keys: keys);
Client client = Client.builder().signer(signer: signer).build();

Add some relays and connect

Next, add some relays to your client and connect to them.

Rust
client.add_relay("wss://relay.damus.io").await?;
client.connect().await;
Python
await client.add_relay("wss://relay.damus.io")
await client.connect()
JavaScript
await client.addRelay("wss://relay.damus.io")
await client.connect();
Kotlin
client.addRelay("wss://relay.damus.io")
client.connect()
Swift
try await client.addRelay(url: "wss://relay.damus.io")
await client.connect()
Flutter
await client.addRelay(url: "wss://relay.damus.io");
await client.connect();

Publish a text note

Now that the client is constructed and the relays are connected, build a text note with the EventBuilder and publish it to relays.

Rust
let builder = EventBuilder::text_note("Hello, rust-nostr!");
let output = client.send_event_builder(builder).await?;
Python
builder = EventBuilder.text_note("Hello, rust-nostr!")
output = await client.send_event_builder(builder)
JavaScript
let builder = EventBuilder.textNote("Hello, rust-nostr!");
let output = await client.sendEventBuilder(builder);
Kotlin
val builder = EventBuilder.textNote("Hello, rust-nostr!")
val output = client.sendEventBuilder(builder)
Swift
let builder = EventBuilder.textNote(content: "Hello, rust-nostr!")
let output = try await client.sendEventBuilder(builder: builder)
Flutter
EventBuilder builder = EventBuilder.textNote(content: "Hello, rust-nostr!");
SendEventOutput output = await client.sendEventBuilder(builder: builder);

Inspect the output

Published the event, you can inspect the output to ensure everything worked correctly.

Rust
println!("Event ID: {}", output.id().to_bech32()?);
println!("Sent to: {:?}", output.success);
println!("Not sent to: {:?}", output.failed);
Python
print(f"Event ID: {output.id.to_bech32()}")
print(f"Sent to: {output.success}")
print(f"Not send to: {output.failed}")
JavaScript
console.log("Event ID:", output.id.toBech32());
console.log("Sent to:", output.success);
console.log("Not sent to:", output.failed);
Kotlin
println("Event ID: ${output.id.toBech32()}")
println("Sent to: ${output.success}")
println("Not sent to: ${output.failed}")
Swift
print("Event ID: \(try output.id.toBech32())")
print("Sent to: \(output.success)")
print("Not sent to: \(output.failed)")
Flutter
print("Event ID: ${output.id}");
print("Sent to: ${output.success}");
print("Not sent to: ${output.failed}");

Full example

Here’s the full example that includes all the steps from generating keys to inspecting the output.

Rust
use nostr_sdk::prelude::*;

pub async fn hello() -> Result<()> {
    let keys: Keys = Keys::generate();
    let client = Client::new(keys);

    client.add_relay("wss://relay.damus.io").await?;
    client.connect().await;

    let builder = EventBuilder::text_note("Hello, rust-nostr!");
    let output = client.send_event_builder(builder).await?;

    println!("Event ID: {}", output.id().to_bech32()?);
    println!("Sent to: {:?}", output.success);
    println!("Not sent to: {:?}", output.failed);

    Ok(())
}
Python
from nostr_sdk import Keys, Client, EventBuilder


async def hello():
    keys = Keys.generate()
    client = Client(keys)

    await client.add_relay("wss://relay.damus.io")
    await client.connect()

    builder = EventBuilder.text_note("Hello, rust-nostr!")
    output = await client.send_event_builder(builder)

    print(f"Event ID: {output.id.to_bech32()}")
    print(f"Sent to: {output.success}")
    print(f"Not send to: {output.failed}")

JavaScript
import {Keys, Client, EventBuilder, NostrSigner, loadWasmAsync} from "@rust-nostr/nostr-sdk";

async function hello() {
    // Load WASM
    await loadWasmAsync();

    let keys: Keys = Keys.generate();
    let signer = NostrSigner.keys(keys);
    let client = new Client(signer);

    await client.addRelay("wss://relay.damus.io")
    await client.connect();

    let builder = EventBuilder.textNote("Hello, rust-nostr!");
    let output = await client.sendEventBuilder(builder);

    console.log("Event ID:", output.id.toBech32());
    console.log("Sent to:", output.success);
    console.log("Not sent to:", output.failed);
}

hello();
Kotlin
import rust.nostr.sdk.*

suspend fun hello() {
    val keys = Keys.generate()
    val signer = NostrSigner.keys(keys)
    val client = Client(signer = signer)

    client.addRelay("wss://relay.damus.io")
    client.connect()

    val builder = EventBuilder.textNote("Hello, rust-nostr!")
    val output = client.sendEventBuilder(builder)

    println("Event ID: ${output.id.toBech32()}")
    println("Sent to: ${output.success}")
    println("Not sent to: ${output.failed}")
}
Swift
import Foundation
import NostrSDK

func hello() async throws {
    let keys = Keys.generate()
    let signer = NostrSigner.keys(keys: keys)
    let client = Client(signer: signer)

    try await client.addRelay(url: "wss://relay.damus.io")
    await client.connect()

    let builder = EventBuilder.textNote(content: "Hello, rust-nostr!")
    let output = try await client.sendEventBuilder(builder: builder)

    print("Event ID: \(try output.id.toBech32())")
    print("Sent to: \(output.success)")
    print("Not sent to: \(output.failed)")
}
Flutter

import 'package:nostr_sdk/nostr_sdk.dart';

Future<void> hello() async {
  Keys keys = Keys.generate();
  NostrSigner signer = NostrSigner.keys(keys: keys);
  Client client = Client.builder().signer(signer: signer).build();

  await client.addRelay(url: "wss://relay.damus.io");
  await client.connect();

  EventBuilder builder = EventBuilder.textNote(content: "Hello, rust-nostr!");
  SendEventOutput output = await client.sendEventBuilder(builder: builder);

  print("Event ID: ${output.id}");
  print("Sent to: ${output.success}");
  print("Not sent to: ${output.failed}");
}

Signers

TODO: description and NostSigner abstraction

Keys

Generate new random keys

To generate a new key pair use the generate() method:

Rust
pub fn generate() -> Result<()> {
    let keys = Keys::generate();

    let public_key = keys.public_key();
    let secret_key = keys.secret_key();

    println!("Public key (hex): {}", public_key);
    println!("Secret key (hex): {}", secret_key.to_secret_hex());

    println!("Public key (bech32): {}", public_key.to_bech32()?);
    println!("Secret key (bech32): {}", secret_key.to_bech32()?);

    Ok(())
}
Python
def generate():
    keys = Keys.generate()

    public_key = keys.public_key()
    secret_key = keys.secret_key()

    print(f"Public key (hex): {public_key.to_hex()}")
    print(f"Secret key (hex): {secret_key.to_hex()}")

    print(f"Public key (bech32): {public_key.to_bech32()}")
    print(f"Secret key (bech32): {secret_key.to_bech32()}")
JavaScript
function generate() {
    let keys = Keys.generate();

    console.log("Public key (hex): ", keys.publicKey.toHex());
    console.log("Secret key (hex): ", keys.secretKey.toHex());

    console.log("Public key (bech32): ", keys.publicKey.toBech32());
    console.log("Secret key (bech32): ", keys.secretKey.toBech32());
}
Kotlin
fun generate() {
    val keys = Keys.generate()

    val publicKey = keys.publicKey()
    val secretKey = keys.secretKey()

    println("Public key (hex): ${publicKey.toHex()}")
    println("Secret key (hex): ${secretKey.toHex()}")

    println("Public key (bech32): ${publicKey.toBech32()}")
    println("Secret key (bech32): ${secretKey.toBech32()}")
}
Swift
func generate() throws {
    let keys = Keys.generate()

    let publicKey = keys.publicKey()
    let secretKey = keys.secretKey()

    print("Public key (hex): \(publicKey.toHex())")
    print("Secret key (hex): \(secretKey.toHex())")

    print("Public key (bech32): \(try publicKey.toBech32())")
    print("Secret key (bech32): \(try secretKey.toBech32())")
}
Flutter
void generate() {
  final keys = Keys.generate();

  final publicKey = keys.publicKey();
  final secretKey = keys.secretKey();

  print("Public key (hex): ${publicKey.toHex()}");
  print("Secret key (hex): ${secretKey.toSecretHex()}");

  print("Public key (bech32): ${publicKey.toBech32()}");
  print("Secret key (bech32): ${secretKey.toBech32()}");
}

Parsing

Rust
pub fn restore() -> Result<()> {
    // Parse keys directly from secret key
    let keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?;

    // Parse secret key and construct keys
    let secret_key =
        SecretKey::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?;
    let keys = Keys::new(secret_key);

    // Restore from bech32
    let secret_key =
        SecretKey::from_bech32("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?;
    let keys = Keys::new(secret_key);

    // Restore from hex
    let secret_key =
        SecretKey::from_hex("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?;
    let keys = Keys::new(secret_key);

    Ok(())
}
Python
def restore():
    keys = Keys.parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")

    secret_key = SecretKey.parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
    keys = Keys(secret_key)
JavaScript
function restore() {
    let keys = Keys.parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85");
    console.log("Secret key (hex): ", keys.secretKey.toHex());
}
Kotlin
fun restore() {
    // Parse secret key
    var keys = Keys.parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")

    // Parse from hex
    var secretKey = SecretKey.parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
    keys = Keys(secretKey = secretKey)
}
Swift
func restore() throws {
    let keys = try Keys.parse(secretKey: "nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")

    let publicKey = keys.publicKey()

    print("Public key: \(try publicKey.toBech32())")
}
Flutter
void restore() {
  // Parse keys directly from secret key (hex or bech32)
  var keys = Keys.parse(
    secretKey:
        "nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99",
  );

  // Parse secret key and construct keys
  var secretKey = SecretKey.parse(
    secretKey:
        "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e",
  );
  keys = Keys(secretKey: secretKey);

  print(keys);
}

NIP-07: window.nostr capability for web browsers

Nostr Connect

Event

JSON de/serialization

Rust
use nostr_sdk::prelude::*;

pub fn event() -> Result<()> {
    // Deserialize from json
    let json = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
    let event = Event::from_json(json)?;

    // Serialize as json
    let json = event.as_json();
    println!("{json}");

    Ok(())
}
Python
from nostr_sdk import Event

def event_json():
    # Deserialize from json
    json = '{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}'
    event = Event.from_json(json)

    # Serialize as json
    json = event.as_json()
    print("\nEvent JSON:")
    print(f" {json}")
JavaScript
import {Event, loadWasmSync} from "@rust-nostr/nostr-sdk";

function eventJson() {
    // Load WASM
    loadWasmSync();

    // Deserialize from json
    let json1 = '{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}'
    let event = Event.fromJson(json1)

    // Serialize as json
    let json2 = event.asJson()
    console.log(json2);
}

eventJson();
Kotlin
fun json() {
    // Deserialize from json
    val json =
        "{\"content\":\"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==\",\"created_at\":1640839235,\"id\":\"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45\",\"kind\":4,\"pubkey\":\"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785\",\"sig\":\"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd\",\"tags\":[[\"p\",\"13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d\"]]}"
    val event = Event.fromJson(json)

    // Serialize as json
    println(event.asJson())
}
Swift
import NostrSDK
import Foundation

func json() {
    // TODO
}
Flutter
import 'package:nostr_sdk/nostr_sdk.dart';

void event() {
  // Deserialize from json
  const json =
      '{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}';
  final event = Event.fromJson(json: json);

  // Serialize as json
  assert(json == event.asJson());
}

Compose with event builder

A convenient way to compose events is by using the EventBuilder. It allow to compose standard and/or custom events.

Rust
use nostr_sdk::prelude::*;

pub fn event() -> Result<()> {
    let keys = Keys::generate();

    // Compose custom event
    let custom_event = EventBuilder::new(Kind::Custom(1111), "").sign_with_keys(&keys)?;

    // Compose text note
    let textnote_event = EventBuilder::text_note("Hello").sign_with_keys(&keys)?;

    // Compose reply to above text note
    let reply_event = EventBuilder::text_note("Reply to hello")
        .tag(Tag::event(textnote_event.id))
        .sign_with_keys(&keys)?;

    // Compose POW event
    let pow_event =
        EventBuilder::text_note("Another reply with POW")
            .tag(Tag::event(textnote_event.id))
            .pow(20)
            .sign_with_keys(&keys)?;

    Ok(())
}
Python
from nostr_sdk import Keys, EventBuilder, Kind, Tag


def event_builder():
    keys = Keys.generate()

    # Compose custom event
    custom_event = EventBuilder(Kind(1111), "").sign_with_keys(keys)

    # Compose text note
    textnote_event = EventBuilder.text_note("Hello").sign_with_keys(keys)

    # Compose reply to above text note
    reply_event = EventBuilder.text_note("Reply to hello").tags([Tag.event(textnote_event.id())]).sign_with_keys(keys)

    # Compose POW event
    pow_event = EventBuilder.text_note("Another reply with POW").tags([Tag.event(textnote_event.id())]).pow(20).sign_with_keys(keys)
JavaScript
import {Keys, EventBuilder, Tag, Timestamp, Kind, loadWasmSync} from "@rust-nostr/nostr-sdk"

function eventBuilder() {
    // Load WASM
    loadWasmSync();

    let keys = Keys.generate();

    // Compose custom event
    let kind = new Kind(1111);
    let customEvent = new EventBuilder(kind, "").signWithKeys(keys);

    // Compose text note
    let textnoteEvent = EventBuilder.textNote("Hello").signWithKeys(keys);

    // Compose reply to above text note
    let replyEvent =
        EventBuilder.textNote("Reply to hello")
            .tags([Tag.event(textnoteEvent.id)])
            .signWithKeys(keys);

    // Compose POW event
    let powEvent =
        EventBuilder.textNote("Another reply with POW")
            .tags([Tag.event(textnoteEvent.id)])
            .pow(20)
            .signWithKeys(keys);

    // Compose note with custom timestamp
    let customTimestamp =
        EventBuilder.textNote("Note with custom timestamp")
            .customCreatedAt(Timestamp.fromSecs(12345678))
            .signWithKeys(keys);
}

eventBuilder();
Kotlin
fun builder() {
    val keys = Keys.generate();

    // Compose custom event
    val customEvent = EventBuilder(kind = Kind(1111u), content = "").signWithKeys(keys);

    // Compose text note
    val textNoteEvent = EventBuilder.textNote("Hello").signWithKeys(keys);

    // Compose reply to above text note
    val replyEvent = EventBuilder.textNote("Reply to hello")
        .tags(listOf(Tag.event(textNoteEvent.id())))
        .signWithKeys(keys);

    // Compose POW event
    val powEvent =
    EventBuilder.textNote("Another reply with POW")
        .tags(listOf(Tag.event(textNoteEvent.id())))
        .pow(20u)
        .signWithKeys(keys);
    println(powEvent.asJson())
}
Swift
import NostrSDK
import Foundation

func builder() {
    // TODO
}
Flutter
import 'package:nostr_sdk/nostr_sdk.dart';

void event() {
  final keys = Keys.generate();

  // Compose custom event
  final customEvent =
      EventBuilder(kind: 1111, content: "").signWithKeys(keys: keys);
  print(customEvent.asJson());

  // Compose text note
  final textNoteEvent =
      EventBuilder.textNote(content: "Hello").signWithKeys(keys: keys);

  // Compose reply to above text note
  final replyEvent = EventBuilder.textNote(content: "Reply to hello")
      .tag(tag: Tag.parse(tag: ['e', textNoteEvent.id().toHex()]))
      .signWithKeys(keys: keys);
  print(replyEvent.asJson());

  // Compose POW event
  final powEvent = EventBuilder.textNote(content: "Another reply with POW")
      .tag(tag: Tag.parse(tag: ['e', textNoteEvent.id().toHex()]))
      .pow(difficulty: 20)
      .signWithKeys(keys: keys);
  print(powEvent.asJson());
}

Event ID

An event ID is defined per the Nostr NIP-01 documentation as the 32-bytes lowercase hex-encoded sha256 of the serialised event data. It's fundamentally a unique identifier for an event generated from the hash of the content of a Nostr event object (excluding the signature).

Creation, Formatting and Parsing

Rust

TODO

Python

The EventId class can be called in order to construct event ids, although this is not necessary when building Event objects as it will be done automatically at that time.

Upon instantiation the following content are passed to the class instance to generate the event ID: public_key, created_at, kind, tags and content.

print("  Build Event ID:")
event_id = EventId(keys.public_key(), Timestamp.now(), Kind(1), [], "content")
print(f"     - {event_id}")

Once we have an event id object we are able to format and parse this using a few simple methods. To present as a hex, bech32, nostr uri or as bytes we need only call the relevant methods to_hex(), to_bech32(), to_nostr_uri() or to_bytes().

It is somewhat trivial to perform the reverse action given that this has been generalised across, hex/bech32 or nostr uri formats. This is achived by calling the parse() method and passing this the event id string matching one of these formats. The exception to this rule is for bytes where the from_bytes() method is to be used.

For more information/examples on the formatting of Nostr objects please refer to NIP-19 and NIP-21.

# To Hex and then Parse
print("  Event ID (hex):")
event_id_hex = event_id.to_hex()
print(f"     - Hex: {event_id_hex}")
print(f"     - Parse: {EventId.parse(event_id_hex)}")
# To Bech32 and then Parse
print("  Event ID (bech32):")
event_id_bech32 = event_id.to_bech32()
print(f"     - Bech32: {event_id_bech32}")
print(f"     - Parse: {EventId.parse(event_id_bech32)}")
# To Nostr URI and then Parse
print("  Event ID (nostr uri):")
event_id_nostr_uri = event_id.to_nostr_uri()
print(f"     - Nostr URI: {event_id_nostr_uri}")
print(f"     - Parse: {EventId.parse(event_id_nostr_uri)}")
# As Bytes and then Parse
print("  Event ID (bytes):")
event_id_bytes = event_id.as_bytes()
print(f"     - Bytes: {event_id_bytes}")
print(f"     - From Bytes: {EventId.from_bytes(event_id_bytes)}")
JavaScript

The EventId class can be called in order to construct event ids, although this is not necessary when building Event objects as it will be done automatically at that time.

Upon instantiation the following content are passed to the class instance to generate the event ID: publicKey, createdAt, kind, tags and content.

console.log("  Build Event ID:");
let event_id = new EventId(keys.publicKey, Timestamp.now(), new Kind(1), [], "");
console.log(`     - ${event_id}`);

Once we have an event id object we are able to format and parse this using a few simple methods. To present as a hex, bech32, nostr uri or as bytes we need only call the relevant methods toHex(), toBech32(), toNostrUri() or asBytes().

It is somewhat trivial to perform the reverse action given that this has been generalised across, hex/bech32 or nostr uri formats. This is achived by calling the parse() method and passing this the event id string matching one of these formats. The exception to this rule is for bytes where the fromBytes() method is to be used.

For more information/examples on the formatting of Nostr objects please refer to NIP-19 and NIP-21.

// To Hex and then Parse
console.log("  Event ID (hex):");
let event_id_hex = event_id.toHex();
console.log(`     - Hex: ${event_id_hex}`);
console.log(`     - Parse: ${EventId.parse(event_id_hex)}`);
// To Bech32 and then Parse
console.log("  Event ID (bech32):");
let event_id_bech32 = event_id.toBech32();
console.log(`     - Bech32: ${event_id_bech32}`);
console.log(`     - Parse: ${EventId.parse(event_id_bech32)}`);
// To Nostr URI and then Parse
console.log("  Event ID (nostr uri):");
let event_id_nostr_uri = event_id.toNostrUri();
console.log(`     - Nostr URI: ${event_id_nostr_uri}`);
console.log(`     - Parse: ${EventId.parse(event_id_nostr_uri)}`);
// As Bytes and then Parse
console.log("  Event ID (bytes):");
let event_id_bytes = event_id.asBytes();
console.log(`     - Bytes: ${event_id_bytes}`);
console.log(`     - From Bytes: ${EventId.fromSlice(event_id_bytes)}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Access and Verify

Rust

TODO

Python

In addition to directly creating/manipulating event ID objects we can also easily access these directly from events, by calling the id() method on and instance of the Event class, or, verify that the event id (and signature) for an event is valid, by using the verify() method.

# Event ID from Event & Verfiy
print("  Event ID from Event & Verify:")
event = EventBuilder.text_note("This is a note").sign_with_keys(keys)
print(f"     - Event ID: {event.id()}")
print(f"     - Verify the ID & Signature: {event.verify()}")
JavaScript

In addition to directly creating/manipulating event ID objects we can also easily access these directly from events, by calling the id() method on and instance of the Event class, or, verify that the event id (and signature) for an event is valid, by using verify() method for both Signature & ID or the verifyId() method for the ID alone.

// Event ID from Event & Verfiy
console.log("  Event ID from Event:");
let event = EventBuilder.textNote("This is a note").signWithKeys(keys);
console.log(`     - Event ID: ${event.id.toBech32()}`);
console.log(`     - Verify the ID & Signature: ${event.verify()}`);
console.log(`     - Verify the ID Only: ${event.verifyId()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Kind

As a core component of nostr objects, kinds are used to signal to clients how to parse the data contained within an event. A kind is represented by an integer between 0 and 65535 the most well known of which is the Kind 1, or text note which contains plaintext data to be displayed. Other commonly used kinds include kind 0 (user metadata) and Kind 3 (following/contact lists). For more details and to see the full range of proposed/adopted Kinds please refer to the Nostr NIPs documentation.

Kind by Integer and Enum

Rust

TODO

Python

Working with kinds is facilitated by the Kind and KindEnum classes. If you are familiar already with the specific integer value for a given Kind it is as simple as calling an instance of the class Kind() and passing the specific number for the Kind you wish to create.

In the example below we've used the common 0/1/3 Kinds (user metadata, text note and following list, respectively) as an illustration of this.

Once we've created the Kind object we can use the as_enum() method to present the Kind object as an easy to read KindEnum object.

print("  Kind from integer:")
kind = Kind(1)
print(f"     - Kind 1: {kind.as_enum()}")
kind = Kind(0)
print(f"     - Kind 0: {kind.as_enum()}")
kind = Kind(3)
print(f"     - Kind 3: {kind.as_enum()}")

Alternatively, if you are less familiar with the specific integer values for a Kind we can use the individual Kind classes, in conjunction with the KindEnum class, to generate the objects. Below we see the TEXT_NOTE(), METADATA() and CONTACT_LIST() enums being passed to an instance of the Kind class via the from_enum() method.

In order to present these as their integer values we can use the as_u16() method.

print("  Kind from enum:")
kind = Kind.from_enum(cast(KindEnum, KindEnum.TEXT_NOTE()))
print(f"     - Kind TEXT_NOTE: {kind.as_u16()}")
kind = Kind.from_enum(cast(KindEnum, KindEnum.METADATA()))
print(f"     - Kind METADATA: {kind.as_u16()}")
kind = Kind.from_enum(cast(KindEnum, KindEnum.CONTACT_LIST()))
print(f"     - Kind CONTRACT_LIST: {kind.as_u16()}")
JavaScript

Working with kinds is facilitated by the Kind class. Unlike the Python bindings there is no enumeration of the kinds, so it helps to be familiar with the specific integer value for a given Kind. The you call an instance of the class Kind() and passing the specific number for the Kind you wish to create.

In the example below we've used the common 0/1/3 Kinds (user metadata, text note and following list, respectively) as an illustration of this.

Once we've created the Kind object we can use the toString() method to present access a string of its integer value.

console.log("  Kind by number:");
let kind = new Kind(1);
console.log(`     - Kind 1: ${kind.toString()}`);
kind = new Kind(0);
console.log(`     - Kind 0: ${kind.toString()}`);
kind = new Kind(3);
console.log(`     - Kind 3: ${kind.toString()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Events and Kinds

Rust

TODO

Python

Although it's possible to construct EventBuilder objects by passing the Kind class as the first argument (see Event section for examples), one of the simplest ways of constructing Event objects is by using the purpose built methods available to the EventBuilder class. For example, the text_note() method can be used to quickly and efficiently create Kind 1 events, the metadata() and contact_list() methods can be used in much the same way.

print("  Kind methods EventBuilder:")
event  = EventBuilder.text_note("This is a note").sign_with_keys(keys)
print(f"     - Kind text_note(): {event.kind().as_u16()} - {event.kind().as_enum()}")
event  = EventBuilder.metadata(Metadata()).sign_with_keys(keys)
print(f"     - Kind metadata(): {event.kind().as_u16()} - {event.kind().as_enum()}")
event  = EventBuilder.contact_list([]).sign_with_keys(keys)
print(f"     - Kind contact_list(): {event.kind().as_u16()} - {event.kind().as_enum()}")

Occasionally you may want more generic usage of kinds, like if you wanted to create your own custom (or experimental) event type, or if you want to leverage one of the commonly defined event types (i.e. replaceable, ephemeral, regular, etc.).

kind = Kind(1337)
print(f"Custom Event Kind: {kind.as_u16()} - {kind.as_enum()}")
JavaScript

Although it's possible to construct EventBuilder objects by passing the Kind class as the first argument (see Event section for examples), one of the simplest ways of constructing Event objects is by using the purpose built methods available to the EventBuilder class. For example, the textNote() method can be used to quickly and efficiently create Kind 1 events, the metadata() and contactList() methods can be used in much the same way. In these examples we've used the asU16() method to present the value of these kinds for logging purposes, this is an alternative way to the integer value from the kind objects.

console.log("  Kind methods EventBuilder:");
let event = EventBuilder.textNote("This is a note").signWithKeys(keys);
console.log(`     - Kind textNote(): ${event.kind.asU16()}`);
event = EventBuilder.metadata(new Metadata()).signWithKeys(keys);
console.log(`     - Kind metadata(): ${event.kind.asU16()}`);
event = EventBuilder.contactList([]).signWithKeys(keys);
console.log(`     - Kind contactList(): ${event.kind.asU16()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Logical Tests

Rust

TODO

Python

In addition to the creation and preseentation of kind objects we may also wish to perform logical tests for specific kinds. This can be done for the main categories of kinds as described in the main Nostr protocol documentation.

This test are facilitated by a range of "is_..." method; is_addressable(), is_ephemeral(), is_job_request(), is_job_result(), is_regular() and is_replaceable().

print("  Kind Logical Tests:")
kind = Kind(30001)
print(f"     - Is {kind.as_u16()} addressable?: {kind.is_addressable()}")
kind = Kind(20001)
print(f"     - Is {kind.as_u16()} ephemeral?: {kind.is_ephemeral()}")
kind = Kind(5001)
print(f"     - Is {kind.as_u16()} job request?: {kind.is_job_request()}")
kind = Kind(6001)
print(f"     - Is {kind.as_u16()} job result?: {kind.is_job_result()}")
kind = Kind(1)
print(f"     - Is {kind.as_u16()} regular?: {kind.is_regular()}")
kind = Kind(10001)
print(f"     - Is {kind.as_u16()} relay replaceable?: {kind.is_replaceable()}")
JavaScript

In addition to the creation and presentation of kind objects we may also wish to perform logical tests for specific kinds. This can be done for the main categories of kinds as described in the main Nostr protocol documentation.

This test are facilitated by a range of "is..." methods; isAddressable(), isEphemeral(), isJobRequest(), isJobResult(), isRegular() and isReplaceable().

console.log("  Kind Logical Tests:");
kind = new Kind(30001);
console.log(`     - Is ${kind.toString()} addressable?: ${kind.isAddressable()}`);
kind = new Kind(20001);
console.log(`     - Is ${kind.toString()} ephemeral?: ${kind.isEphemeral()}`);
kind = new Kind(5001);
console.log(`     - Is ${kind.toString()} job request?: ${kind.isJobRequest()}`);
kind = new Kind(6001);
console.log(`     - Is ${kind.toString()} job result?: ${kind.isJobResult()}`);
kind = new Kind(1);
console.log(`     - Is ${kind.toString()} regular?: ${kind.isRegular()}`);
kind = new Kind(10001);
console.log(`     - Is ${kind.toString()} replaceable?: ${kind.isReplaceable()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Tag

Tags are one of the main element of Nostr event objects and allow for diverse functionality including referencing public keys p, relays r or even other events e. The format tags take is an array of strings where the first position in the array is reserved for the tag name and the subsequent strings are the values.

The Tag struct and TagKind enum can be used to create and manipulate Tag objects.

Please refer to the Standardized Tags section of the Nostr Protocol NIP repository for an exhaustive list of tags and their related uses within event kinds.

Creating Tags

Rust

TODO

Python

There are multiple methods by which we can create tag objects all of which form part of the Tag class. The simplest of which are the more commonly used single letter tags. In the example below the e, p, a, d, r and t tags are created passing the relevant object/string values to the tag methods event(), public_key(), coordinate(), identifier(), relay_metadata() and hashtag(), respectively.

print("  Single Letter Tags:")
# Event ID (hex)
tag = Tag.event(event.id())
print(f"     - Event ID (hex)     : {tag.as_vec()}")
# Public Key (hex)
tag = Tag.public_key(keys.public_key())
print(f"     - Public Key (hex)   : {tag.as_vec()}")
# Coordinate to event
tag = Tag.coordinate(Coordinate(Kind(0), keys.public_key()))
print(f"     - Coordinate to event: {tag.as_vec()}")
# Identifier
tag = Tag.identifier("This is an identifier value")
print(f"     - Identifier         : {tag.as_vec()}")
# Reference/Relay
tag = Tag.relay_metadata("wss://relay.example.com",RelayMetadata.READ)
print(f"     - Reference/Relays   : {tag.as_vec()}")
# Hashtag
tag = Tag.hashtag("#AskNostr")
print(f"     - Hashtag            : {tag.as_vec()}")

For the less commonly used but well defined tags the combination of the custom() method is used with an appropriate instance of the TagKind class. Please refer to the documentation for a more comprehensive list of the available options.

print("  Custom Tags:")
tag = Tag.custom(cast(TagKind, TagKind.SUMMARY()), ["This is a summary"])
print(f"     - Summary    : {tag.as_vec()}")
tag = Tag.custom(cast(TagKind, TagKind.AMOUNT()), ["42"])
print(f"     - Amount     : {tag.as_vec()}")
tag = Tag.custom(cast(TagKind, TagKind.TITLE()), ["This is a title"])
print(f"     - Title      : {tag.as_vec()}")
tag = Tag.custom(cast(TagKind, TagKind.SUBJECT()), ["This is a subject"])
print(f"     - Subject    : {tag.as_vec()}")
tag = Tag.custom(cast(TagKind, TagKind.DESCRIPTION()), ["This is a description"])
print(f"     - Description: {tag.as_vec()}")
tag = Tag.custom(cast(TagKind, TagKind.URL()), ["https://example.com"])
print(f"     - URL        : {tag.as_vec()}")

Finally, if you are looking to parse lists into tag objects the parse() method can be called and passed a list of strings where the first position in the list would represent the tag name and the subsequent strings represent the values.

print("  Parsing Tags:")
tag = Tag.parse(["L","Label Namespace"])
print(f"     - Label Namespace: {tag.as_vec()}")
tag = Tag.parse(["l","Label Value"])
print(f"     - Label Value    : {tag.as_vec()}")
JavaScript

TODO

Kotlin

TODO

Swift

TODO

Flutter

TODO

Serializing and Logical Tests

Rust

TODO

Python

Once you have a Tag object, it is relatively straight forward to access the attributes and other related content. The kind() method can be used to access the underlying TagKind object, the single_letter_tag() method returns the SingleLetterTag object and content() method will return the content of the first value position within the tag (position 1 in the array).

The as_standardized() and as_vec() methods will return the tag in both TagStandard (enum) format or as an array of strings, respectively.

print("  Working with Tags:")
tag = Tag.public_key(keys.public_key())
print(f"     - Kind     : {tag.kind()}")
print(f"     - Letter   : {tag.single_letter_tag()}")
print(f"     - Content  : {tag.content()}")
print(f"     - As Std   : {tag.as_standardized()}")
print(f"     - As Vector: {tag.as_vec()}")

One last point of note is that when processing non-single letter tags it is useful to be able to easily perform tests on these. We can use the kind() method to first surface the TagKind and then call the relevant "is_x" method (e.g. is_title() or is_summary() per the example below) to return a boolean result.

print("  Logical Tests:")
tag = Tag.custom(cast(TagKind, TagKind.SUMMARY()), ["This is a summary"])
print(f"     - Tag1 (Title?)  : {tag.kind().is_title()}")
print(f"     - Tag1 (Summary?): {tag.kind().is_summary()}")
JavaScript

TODO

Kotlin

TODO

Swift

TODO

Flutter

TODO

Event Builder

Filters

Though a web-socket subscription model relays can surface events that meet specific criteria on request. The means by which these requests maybe submitted are JSON filters objects which can be constructed using a range of attributes, including ids, authors, kinds and single letter tags, along with timestamps, since/until and record limit for the query.

Create Filters

Rust

TODO

Python

The following code examples all utilize the Filters() along with associated methods to create filter objects and print these in JSON format using the as_json() method.

Filtering events based on a specific event ID using id().

# Filter for specific ID
print("  Filter for specific Event ID:")
f = Filter().id(event.id())
print(f"     {f.as_json()}")

Filtering events by author using author().

# Filter for specific Author
print("  Filter for specific Author:")
f = Filter().author(keys.public_key())
print(f"     {f.as_json()}")

Filtering events based on multiple criteria. In this case, by public key using pubkey() and kind using kind().

# Filter by PK and Kinds
print("  Filter with PK and Kinds:")
f = Filter()\
    .pubkey(keys.public_key())\
    .kind(Kind(1))
print(f"     {f.as_json()}")

Filtering for specific text strings using search().

# Filter for specific string
print("  Filter for specific search string:")
f = Filter().search("Ask Nostr Anything")
print(f"     {f.as_json()}")

Restricting query results to specific timeframes (using since() and until()), as well as limiting search results to a maximum of 10 records using limit().

print("  Filter for events from specific public key within given timeframe:")
# Create timestamps
date = datetime.datetime(2009, 1, 3, 0, 0)
timestamp = int(time.mktime(date.timetuple()))
since_ts = Timestamp.from_secs(timestamp)
until_ts = Timestamp.now()

# Filter with timeframe
f = Filter()\
    .pubkey(keys.public_key())\
    .since(since_ts)\
    .until(until_ts)
print(f"     {f.as_json()}")
# Filter for specific PK with limit
print("  Filter for specific Author, limited to 10 Events:")
f = Filter()\
    .author(keys.public_key())\
    .limit(10)
print(f"     {f.as_json()}")

Finally, filtering using hashtags (hashtag()), NIP-12 reference tags (reference()) and identifiers (identifiers()), respectively.

# Filter for Hashtags
print("  Filter for a list of Hashtags:")
f = Filter().hashtags(["#Bitcoin", "#AskNostr", "#Meme"])
print(f"     {f.as_json()}")
# Filter for Reference
print("  Filter for a Reference:")
f = Filter().reference("This is my NIP-12 Reference")
print(f"     {f.as_json()}")
# Filter for Identifier
print("  Filter for a Identifier:")
identifier = event2.tags().identifier()
if identifier is not None:
    f = Filter().identifier(identifier)
    print(f"     {f.as_json()}")
JavaScript

The following code examples all utilize the Filters() along with associated methods to create filter objects and print these in JSON format using the asJson() method.

Filtering events based on a specific event ID using id().

// Filter for specific ID
console.log("  Filter for specific Event ID:");
let f = new Filter().id(event.id);
console.log(`     ${f.asJson()}`);

Filtering events by author using author().

// Filter for specific Author
console.log("  Filter for specific Author:");
f = new Filter().author(keys.publicKey);
console.log(`     ${f.asJson()}`);

Filtering events based on multiple criteria. In this case, by public key using pubkey() and kind using kind().

// Filter by PK and Kinds
console.log("  Filter with PK and Kinds:");
f = new Filter()
    .pubkey(keys.publicKey)
    .kind(kind1);
console.log(`     ${f.asJson()}`);

Filtering for specific text strings using search().

// Filter for specific string
console.log("  Filter for specific search string:");
f = new Filter().search("Ask Nostr Anything");
console.log(`     ${f.asJson()}`);

Restricting query results to specific timeframes (using since() and until()), as well as limiting search results to a maximum of 10 records using limit().

console.log("  Filter for events from specific public key within given timeframe:");
// Create timestamps
const date = new Date(2009, 1, 3, 0, 0);
const timestamp = Math.floor(date.getTime() / 1000);
const sinceTs = Timestamp.fromSecs(timestamp);
const untilTs = Timestamp.now();

// Filter with timeframe
f = new Filter()
    .pubkey(keys.publicKey)
    .since(sinceTs)
    .until(untilTs);
console.log(`     ${f.asJson()}`);
// Filter for specific PK with limit
console.log("  Filter for specific Author, limited to 10 Events:");
f = new Filter()
    .author(keys.publicKey)
    .limit(10);
console.log(`     ${f.asJson()}`);

Finally, filtering using hashtags (hashtags()), NIP-12 reference tags (reference()) and identifiers (identifiers()), respectively.

// Filter for Hashtags
console.log("  Filter for a list of Hashtags:");
f = new Filter().hashtags(["#Bitcoin", "#AskNostr", "#Meme"]);
console.log(`     ${f.asJson()}`);
// Filter for Reference
console.log("  Filter for a Reference:");
f = new Filter().reference("This is my NIP-12 Reference");
console.log(`     ${f.asJson()}`);
// Filter for Identifier
console.log("  Filter for a Identifier:");
f = new Filter().identifier("This is my NIP-12 Identifier");
console.log(`     ${f.asJson()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Modify Filters

Rust

TODO

Python

Adding more conditions to existing objects can be done by simply calling the relevant method on the instance of the object. In this example we create a initial filter with pubkeys(), ids(), kinds() and a single author() then modify the object further to include another kind (4) to the existing list of kinds (0, 1).

Similarly, the range of 'remove' methods (e.g. remove_kinds()) allow us to take an existing filter and remove unwanted conditions without needed to reconstruct the filter object from scratch.

# Modifying Filters (adding/removing)
f = Filter()\
    .pubkeys([keys.public_key(), keys2.public_key()])\
    .ids([event.id(), event2.id()])\
    .kinds([Kind(0), Kind(1)])\
    .author(keys.public_key())

# Add an additional Kind to existing filter
f = f.kinds([Kind(4)])

# Print Results
print("  Before:")
print(f"     {f.as_json()}")
print()

# Remove PKs, Kinds and IDs from filter
f = f.remove_pubkeys([keys2.public_key()])
print(" After (remove pubkeys):")
print(f"     {f.as_json()}")

f = f.remove_kinds([Kind(0), Kind(4)])
print("  After (remove kinds):")
print(f"     {f.as_json()}")

f = f.remove_ids([event2.id()])
print("  After (remove IDs):")
print(f"     {f.as_json()}")
JavaScript

Adding more conditions to existing objects can be done by simply calling the relevant method on the instance of the object. In this example we create a initial filter with pubkeys(), ids(), kinds() and a single author() then modify the object further to include another kind (4) to the existing list of kinds (0, 1).

Similarly, the range of 'remove' methods (e.g. removekinds()) allow us to take an existing filter and remove unwanted conditions without needed to reconstruct the filter object from scratch.

// Modifying Filters (adding/removing)
f = new Filter()
    .pubkeys([keys.publicKey, keys2.publicKey])
    .ids([event.id, event2.id])
    .kinds([kind0, kind1])
    .author(keys.publicKey);

// Add an additional Kind to existing filter
f = f.kinds([kind4]);

// Print Results
console.log("  Before:");
console.log(`     ${f.asJson()}`);
console.log();

// Remove PKs, Kinds and IDs from filter
f = f.removePubkeys([keys2.publicKey]);
console.log(" After (remove pubkeys):");
console.log(`     ${f.asJson()}`);
const kind_rem0 = new Kind(0);
const kind_rem4 = new Kind(4);
f = f.removeKinds([kind_rem0, kind_rem4]);
console.log("  After (remove kinds):");
console.log(`     ${f.asJson()}`);

f = f.removeIds([event2.id]);
console.log("  After (remove IDs):");
console.log(`     ${f.asJson()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Other Filter Operations

Rust

TODO

Python

We can parse existing filter JSON object using the from_json() method when instantiating a filter object.

# Parse filter
print("  Parse Filter from Json:")
f_json = f.as_json()
f = Filter().from_json(f_json)
print(f"     {f.as_record()}")

Furthermore, it is possible to create filter records more formally using the FilterRecord class.

print("  Construct Filter Record and extract author:")
# Filter Record
fr = FilterRecord(ids=[event.id()],authors=[keys.public_key()], kinds=[Kind(0)], search="", since=None, until=None, limit=1, generic_tags=[])
f = Filter().from_record(fr)
print(f"     {f.as_json()}")

To perform a logical test and determine if a given event object matches existing filter conditions the match_event() method can be used.

print("  Logical tests:")
f = Filter().author(keys.public_key()).kind(Kind(1))
print(f"     Event match for filter: {f.match_event(event)}")
print(f"     Event2 match for filter: {f.match_event(event2)}")
JavaScript

We can parse existing filter JSON object using the fromJson() method when instantiating a filter object.

// Parse filter
console.log("  Parse Filter from Json:");
const fJson = f.asJson();
f = Filter.fromJson(fJson);
console.log(`     ${f.asJson()}`);

To perform a logical test and determine if a given event object matches existing filter conditions the matchEvent() method can be used.

console.log("  Logical tests:");
const kind_match = new Kind(1);
f = new Filter().author(keys.publicKey).kind(kind_match);
console.log(`     Event match for filter: ${f.matchEvent(event)}`);
console.log(`     Event2 match for filter: ${f.matchEvent(event2)}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Client

Options

TODO

Proxy

TODO

Embedded Tor

NIPs

NIP-01: Basic protocol flow description

User metadata

Rust

Use the Metadata struct to deserialize the content of an event into a struct.

let event = EventBuilder::new(Kind::Metadata, content).sign_with_keys(&keys)?;
let metadata = Metadata::from_json(&event.content)?;

If you have an existing metadata object, it can be used with the EventBuilder struct to create an EventBuilder with the metadata already attached.

let metadata = Metadata::from_json(content)?;
let event = EventBuilder::metadata(&metadata).sign_with_keys(&keys)?;

For documentation on the available struct attributes, check out the Metadata documentation.

Python

Using the Metadata class to build the metadata object and the EventBuilder class to create a Metadata event.

# Create metadata object with desired content
metadata_content = Metadata()\
    .set_name("TestName")\
    .set_display_name("PyTestur")\
    .set_about("This is a Test Account for Rust Nostr Python Bindings")\
    .set_website("https://rust-nostr.org/")\
    .set_picture("https://avatars.githubusercontent.com/u/123304603?s=200&v=4")\
    .set_banner("https://nostr-resources.com/assets/images/cover.png")\
    .set_nip05("TestName@rustNostr.com")

# Build metadata event and assign content
builder = EventBuilder.metadata(metadata_content)

# Signed event and print details
print("Creating Metadata Event:")
event = builder.sign_with_keys(keys)

print(" Event Details:")
print(f"     Author    : {event.author().to_bech32()}")
print(f"     Kind      : {event.kind().as_u16()}")
print(f"     Content   : {event.content()}")
print(f"     Datetime  : {event.created_at().to_human_datetime()}")
print(f"     Signature : {event.signature()}")
print(f"     Verify    : {event.verify()}")
print(f"     JSON      : {event.as_json()}")

Use the Metadata class to deserialize the content of an exsiting metadata event.

# Deserialize Metadata from event
print("Deserializing Metadata Event:")
metadata = Metadata().from_json(event.content())

print(" Metadata Details:")
print(f"     Name      : {metadata.get_name()}")
print(f"     Display   : {metadata.get_display_name()}")
print(f"     About     : {metadata.get_about()}")
print(f"     Website   : {metadata.get_website()}")
print(f"     Picture   : {metadata.get_picture()}")
print(f"     Banner    : {metadata.get_banner()}")
print(f"     NIP05     : {metadata.get_nip05()}")
JavaScript

Using the Metadata class to build the metadata object and the EventBuilder class to create a Metadata event.

// Create metadata object with desired content
let metadataContent = new Metadata()
    .name("TestName")
    .displayName("JsTestur")
    .about("This is a Test Account for Rust Nostr JS Bindings")
    .website("https://rust-nostr.org/")
    .picture("https://avatars.githubusercontent.com/u/123304603?s=200&v=4")
    .banner("https://nostr-resources.com/assets/images/cover.png")
    .nip05("TestName@rustNostr.com");

// Build metadata event and assign content
let builder = EventBuilder.metadata(metadataContent);

// Signed event and print details
console.log("Creating Metadata Event:");
let event = builder.signWithKeys(keys);

console.log(" Event Details:");
console.log(`     Author    : ${event.author.toBech32()}`);
console.log(`     Kind      : ${event.kind.valueOf()}`);
console.log(`     Content   : ${event.content.toString()}`);
console.log(`     Datetime  : ${event.createdAt.toHumanDatetime()}`);
console.log(`     Signature : ${event.signature.toString()}`);
console.log(`     Verify    : ${event.verify()}`);
console.log(`     JSON      : ${event.asJson()}`);

Use the Metadata class to deserialize the content of an exsiting metadata event.

// Deserialize Metadata from event
console.log("Deserializing Metadata Event:");
let metadata = Metadata.fromJson(event.content);

console.log(" Metadata Details:");
console.log(`     Name      : ${metadata.getName()}`);
console.log(`     Display   : ${metadata.getDisplayName()}`);
console.log(`     About     : ${metadata.getAbout()}`);
console.log(`     Website   : ${metadata.getWebsite()}`);
console.log(`     Picture   : ${metadata.getPicture()}`);
console.log(`     Banner    : ${metadata.getBanner()}`);
console.log(`     NIP05     : ${metadata.getNip05()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

NIP-05: Mapping Nostr keys to DNS-based internet identifiers

As a part of the kind 0 metadata events the optional key nip05 is used to set and internet identifier value (e.g. TestName@rustNostr.com). Clients can then use this information to make GET requests with the form https://<domain>/.well-known/nostr.json?name=<local-part>.

Rust

TODO

Python

Using the Metadata class to build the metadata object and incorporate the NIP-05 identifier with the set_nip05() method.

For more details on metadata (or general) events please refer back to the examples provided for NIP-01.

# Create metadata object with name and NIP05
metadata = Metadata() \
    .set_name("TestName") \
    .set_nip05("TestName@rustNostr.com")

For verification of NIP-05 identifiers associated with a given PublicKey object we can the verify_nip05() function as follows:

print("Verify NIP-05:")
nip_05 = "yuki@yukikishimoto.com"
public_key = PublicKey.parse("npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet")
proxy = None
if await verify_nip05(public_key, nip_05, proxy):
    print(f"     '{nip_05}' verified, for {public_key.to_bech32()}")
else:
    print(f"     Unable to verify NIP-05, for {public_key.to_bech32()}")

To get the NIP-05 profile data (ex. user public key and relays) the get_nip05_profile() function can be called:

print("Profile NIP-05:")
nip_05 = "yuki@yukikishimoto.com"
profile = await get_nip05_profile(nip_05)
print(f"     {nip_05} Public key: {profile.public_key().to_bech32()}")
JavaScript

Using the Metadata class to build the metadata object and incorporate the NIP-05 identifier with the nip05() method.

For more details on metadata (or general) events please refer back to the examples provided for NIP-01.

// Create metadata object with name and NIP05
let metadata = new Metadata()
    .name("TestName")
    .nip05("TestName@rustNostr.com");

For verification of NIP-05 identifiers associated with a given PublicKey object we can the verifyNip05() function as follows:

console.log("Verify NIP-05:");
let nip05 = "Rydal@gitlurker.info";
let publicKey = PublicKey.parse("npub1zwnx29tj2lnem8wvjcx7avm8l4unswlz6zatk0vxzeu62uqagcash7fhrf");
if (await verifyNip05(publicKey, nip05)) {
    console.log(`     '${nip05}' verified, for ${publicKey.toBech32()}`);
} else {
    console.log(`     Unable to verify NIP-05, for ${publicKey.toBech32()}`);
}

To get the NIP-05 profile data (ex. user public key and relays) the getNip05Profile() function can be called:

console.log("Get NIP-05 profile:");
let nip_05 = "yuki@yukikishimoto.com";
let profile = await getNip05Profile(nip_05);
console.log(`     ${nip_05} Public key: ${profile.publicKey().toBech32()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

NIP-06: Basic key derivation from mnemonic seed phrase

In accordance with the BIP-32 and BIP-39 we can derive Nostr keys using seed phrases as a source of entropy.

The default functionality is to generate a single key-pair at the derivation path 0. However, it is also possible to perform more advanced derivations by incrementing the account, enabling the generation of many sets of keys from a single mnemonic seed phrase.

Rust

TODO

Python

Using the from_mnemonic() method in conjunction with the Keys class to derived a basic set of Nostr keys from a 24 word seed phrase.

Note that this example uses the Mnemonic class from the python-mnemonic package (the reference implementation of BIP-39) to randomly generate example seed phrases.

# Generate random Seed Phrase (24 words e.g. 256 bits entropy)
print("Keys from 24 word Seed Phrase:")
words = Mnemonic("english").generate(strength=256)
passphrase = ""

# Use Seed Phrase to generate basic Nostr keys
keys = Keys.from_mnemonic(words, passphrase)

print(f" Seed Words (24)  : {words}")
print(f" Public key bech32: {keys.public_key().to_bech32()}")
print(f" Secret key bech32: {keys.secret_key().to_bech32()}")

As well as deriving basic keys from a 24 word seed we can also use seed phrases of other lengths such as 18 words or, as in this example, 12 words.

# Generate random Seed Phrase (12 words e.g. 128 bits entropy)
print("Keys from 12 word Seed Phrase:")
words = Mnemonic("english").generate(strength=128)
passphrase = ""

# Use Seed Phrase to generate basic Nostr keys
keys = Keys.from_mnemonic(words, passphrase)

print(f" Seed Words (12)  : {words}")
print(f" Public key bech32: {keys.public_key().to_bech32()}")
print(f" Secret key bech32: {keys.secret_key().to_bech32()}")

Advanced key derivation functionality (for accounts) can be accessed by the from_mnemonic() method. To do this we use the account argument which accepts an integer to specify the derivation path.

# Advanced (with accounts) from the example wordlist
words = "leader monkey parrot ring guide accident before fence cannon height naive bean"
passphrase = ""

print("Accounts (0-5) from 12 word Seed Phrase (with passphrase):")
print(f" Seed Words (12): {words}")
print(" Accounts (0-5) :")

# Use Seed Phrase and account to multiple Nostr keys
for account in range(0,6):
    nsec = Keys.from_mnemonic(words, passphrase, account).secret_key().to_bech32()
    print(f"     Account #{account} bech32: {nsec}")

This final example utilizes the same seed as for the previous example, but also includes a passphrase. It illustrates the effect of inclusion of a passphrase on the key derivation.

# Advanced (with accounts) from the same wordlist with in inclusion of passphrase
words = "leader monkey parrot ring guide accident before fence cannon height naive bean"
passphrase = "RustNostr"
print("Accounts (0-5) from 12 word Seed Phrase (with passphrase):")
print(f" Seed Words (12): {words}")
print(f" Passphrase     : {passphrase}")
print(" Accounts (0-5) :")

# Use Seed Phrase and account to multiple Nostr keys
for account in range(0,6):
    nsec = Keys.from_mnemonic(words, passphrase, account).secret_key().to_bech32()
    print(f"     Account #{account} bech32: {nsec}")
JavaScript

Using the fromMnemonic() method in conjunction with the Keys class to derived a basic set of Nostr keys from a 24 word seed phrase.

Note that this example uses the generateMnemonic() method from the bip39 package, a commonly used JavaScript implementation of Bitcoin BIP39, to randomly generate example seed phrases.

// Generate random Seed Phrase (24 words e.g. 256 bits entropy)
let words256 = generateMnemonic(256);
console.log("Generated Random Seed Phrase and Derived Keys:");
console.log(`\t - Seed Words (24): ${words256}`);
let passphrase256 = "";

// Use Seed Phrase to generate basic Nostr keys
let keys256 = Keys.fromMnemonic(words256, passphrase256);

// Print Results
console.log(`\t - Private (hex)  : ${keys256.secretKey.toHex()}`);
console.log(`\t - Private (nsec) : ${keys256.secretKey.toBech32()}`);
console.log(`\t - Public (hex)   : ${keys256.publicKey.toHex()}`);
console.log(`\t - Public (npub)  : ${keys256.publicKey.toBech32()}`);

As well as deriving basic keys from a 24 word seed we can also use seed phrases of other lengths such as 18 words or, as in this example, 12 words.

// Generate random Seed Phrase (12 words e.g. 128 bits entropy)
let words128 = generateMnemonic(128);
console.log("Generated Random Seed Phrase and Derived Keys:");
console.log(`\t - Seed Words (12): ${words128}`);
let passphrase128 = "";

// Use Seed Phrase to generate basic Nostr keys
let keys128 = Keys.fromMnemonic(words128, passphrase128);

// Print Results
console.log(`\t - Private (hex)  : ${keys128.secretKey.toHex()}`);
console.log(`\t - Private (nsec) : ${keys128.secretKey.toBech32()}`);
console.log(`\t - Public (hex)   : ${keys128.publicKey.toHex()}`);
console.log(`\t - Public (npub)  : ${keys128.publicKey.toBech32()}`);

Advanced key derivation functionality (for accounts) can be accessed by the fromMnemonic() method. To do this we use the account argument which accepts an integer to specify the derivation path.

// Advanced (with accounts) from the same wordlist
let words = "leader monkey parrot ring guide accident before fence cannon height naive bean";
let passphrase  = "";
console.log("Generated Accounts:");
console.log(`\t - Seed Words (12): ${words}`);

// Use Seed Phrase and account to multiple Nostr keys
for (let account = 0; account < 6; account++) {
    let nsec = Keys.fromMnemonic(words, passphrase, account).secretKey.toBech32();
    console.log(`\t - Private (nsec) Account #${account}: ${nsec}`);
}

This final example utilizes the same seed as for the previous example, but also includes a passphrase. It illustrates the effect of inclusion of a passphrase on the key derivation.

// Advanced (with accounts) from the same wordlist with in inclusion of PassPhrase
words = "leader monkey parrot ring guide accident before fence cannon height naive bean";
passphrase = "RustNostr";
console.log("Generated Accounts:");
console.log(`\t - Seed Words (12): ${words}`);

// Use Seed Phrase, passphrase and account to multiple Nostr keys
for (let account = 0; account < 6; account++) {
    let nsec = Keys.fromMnemonic(words, passphrase, account).secretKey.toBech32();
    console.log(`\t - Private (nsec) Account #${account}: ${nsec}`);
}
Kotlin

TODO

Swift

TODO

Flutter

TODO

NIP-07: window.nostr capability for web browsers

NIP-17: Private Direct Messages

NIP-17 defines an encrypted direct messaging scheme using NIP-44 encryption, NIP-59 seals and gift wraps. See NIP-17 for a detailed description.

Rust
// Sender
let alice_keys =
    Keys::parse("5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a")?;
let alice_client = Client::new(alice_keys);
alice_client.add_relay(url.clone()).await?;
alice_client.connect().await;

// Receiver
let bob_keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?;
let bob_client = Client::new(bob_keys.clone());
bob_client.add_relay(url.clone()).await?;
bob_client.connect().await;

// Initialize subscription on Bob's side before sending the message
// Limit is set to 0 to get only new events!
// Timestamp::now() CAN'T be used for gift wrap since the timestamps are tweaked!
let message_filter = Filter::new()
    .kind(Kind::GiftWrap)
    .pubkey(bob_keys.public_key())
    .limit(0);
let subscription_id = bob_client.subscribe(vec![message_filter], None).await?;

// Alice sends private message to Bob
alice_client.send_private_msg(bob_keys.public_key(), "Hello Bob!", []).await?;
println!("Sent private message to Bob");

// Bob receives private message
bob_client.handle_notifications(|notification| async {
    if let RelayPoolNotification::Event { event, .. } = notification {
        if event.kind == Kind::GiftWrap {
            let UnwrappedGift { rumor, sender } = bob_client.unwrap_gift_wrap(&event).await?;
            if rumor.kind == Kind::PrivateDirectMessage {
                println!("Bob received private message from {sender}: {}", &rumor.content);
                return Ok(true); // Message received, exit.
            }
        }
    }
    Ok(false)
}).await?;

bob_client.unsubscribe(subscription_id.val).await;
Python

TODO

JavaScript

TODO

Kotlin

TODO

Swift

TODO

Flutter

TODO

NIP-19: bech32-encoded entities

Bech32 encoding is used for the primary purpose of transportability between users/client/applications. In addition to bech-32 encoding of the data a series of prefixes are also used to help easily differentiate between different data objects. npub/nsec for public and private keys, respectively and note for note ids.

Extra metadata may be included when communicating between applications to make cross compatibility more streamlined. These follow a type-length-value structure and have the following possible prefixes: nprofile, nevent, nrelay and naddr.

Rust

TODO

Python

For most of these examples you will see that the to_bech32() and from_bech32() methods generally facilitate encoding or decoding objects per the NIP-19 standard.

Public and Private (or secret) keys in npub and nsec formats.

print(f" Public key: {keys.public_key().to_bech32()}")
print(f" Secret key: {keys.secret_key().to_bech32()}")

Simple note presented in NIP-19 format.

event = EventBuilder.text_note("Hello from rust-nostr Python bindings!").sign_with_keys(keys)
print(f" Event     : {event.id().to_bech32()}")

Using the Nip19Profile class to create a shareable nprofile that includes relay data to help other applications to locate the profile data.

# Create NIP-19 profile including relays data
relays = ["wss://relay.damus.io"]
nprofile = Nip19Profile(keys.public_key(),relays)
print(f" Profile (encoded): {nprofile.to_bech32()}")

Using the Nip19 class to decode the previously shared profile data. This class helps generalize the decoding process for all NIP-19 objects.

# Decode NIP-19 profile
decode_nprofile = Nip19.from_bech32(nprofile.to_bech32())
print(f" Profile (decoded): {decode_nprofile}")

Using the Nip19Event class to create a shareable nevent that includes author and relay data. This is followed by decoding the event object.

# Create NIP-19 event including author and relays data
nevent = Nip19Event(event.id(), keys.public_key(), kind=None, relays=relays)
print(f" Event (encoded): {nevent.to_bech32()}")
# Decode NIP-19 event
decode_nevent = Nip19.from_bech32(nevent.to_bech32())
print(f" Event (decoded): {decode_nevent}")

Using the Coordinate class to generate the coordinates for a replaceable event (in this case Metadata). This is followed by decoding the object.

# Create NIP-19 coordinate
coord = Coordinate(Kind(0),keys.public_key())
print(f" Coordinate (encoded): {coord.to_bech32()}")
# Decode NIP-19 coordinate
decode_coord = Nip19.from_bech32(coord.to_bech32())
print(f" Coordinate (decoded): {decode_coord}")
JavaScript

For most of these examples you will see that the toBech32() and fromBech32() methods generally facilitate encoding or decoding objects per the NIP-19 standard.

Public and Private (or secret) keys in npub and nsec formats.

console.log(` Public key: ${keys.publicKey.toBech32()}`);
console.log(` Secret key: ${keys.secretKey.toBech32()}`);

Simple note presented in NIP-19 format.

let event = EventBuilder.textNote("Hello from Rust Nostr JS Bindings!").signWithKeys(keys);
console.log(` Event     : ${event.id.toBech32()}`);

Using the Nip19Profile class to create a shareable nprofile that includes relay data to help other applications to locate the profile data. This is followed by decoding the event object.

// Create NIP-19 profile including relays data
let relays = ["wss://relay.damus.io"];
let nprofile = new Nip19Profile(keys.publicKey, relays);
console.log(` Profile (encoded): ${nprofile.toBech32()}`);
// Decode NIP-19 profile
let decode_nprofile = Nip19Profile.fromBech32(nprofile.toBech32());
console.log(` Profile (decoded): ${decode_nprofile.publicKey().toBech32()}`);

Using the Nip19Event class to create a shareable nevent that includes author and relay data. This is followed by decoding the event object.

// Create NIP-19 event including author and relays data
let nevent = new Nip19Event(event.id, keys.publicKey, undefined, relays);
console.log(` Event (encoded): ${nevent.toBech32()}`);
// Decode NIP-19 event
let decode_nevent = Nip19Event.fromBech32(nevent.toBech32());
console.log(` Event (decoded): ${decode_nevent.eventId().toBech32()}`);

Using the Coordinate class to generate the coordinates for a replaceable event (in this case Metadata). This is followed by decoding the object which uses the parse method.

// Create NIP-19 coordinate
let kind = new Kind(0);
let coord = new Coordinate(kind, keys.publicKey);
console.log(` Coordinate (encoded): ${coord.toBech32()}`);
// Decode NIP-19 coordinate
let decode_coord = Coordinate.parse(coord.toBech32());
console.log(` Coordinate (decoded): ${decode_coord}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

NIP-21: nostr URI scheme

This NIP is intended to extend the interoperability of the network be defining the URI scheme for Nostr as nostr:. This prefix is then followed by identifiers as specified in NIP-19 (with the exclusion of nsec). For more information on the bech32 encoding used for NIP-19 please refer to the earlier examples.

Rust

TODO

Python

Generally speaking the simplest way for handling NIP-21 objects is by the to_nostr_uri() and from_nostr_uri() methods for encoding or decoding data, respectively.

Additionally, if it is unclear what type of Nip21 object we're handling then the Nip21 class, in conjunction with the parse() and as_enum() methods, can be used to parse these objects without knowing ahead of what they are.

Public key:

keys = Keys.generate()

# URI npub
pk_uri = keys.public_key().to_nostr_uri()
print(f" Public key (URI):    {pk_uri}")

# bech32 npub
pk_parse = Nip21.parse(pk_uri)
if pk_parse.as_enum().is_pubkey():
    pk_bech32 = PublicKey.parse(pk_uri).to_bech32()
    print(f" Public key (bech32): {pk_bech32}")

Note:

event = EventBuilder.text_note("Hello from rust-nostr Python bindings!").sign_with_keys(keys)

# URI note
note_uri = event.id().to_nostr_uri()
print(f" Event (URI):    {note_uri}")

# bech32 note
note_pasre = Nip21.parse(note_uri)
if note_pasre.as_enum().is_note():
    event_bech32 = EventId.parse(note_uri).to_bech32()
    print(f" Event (bech32): {event_bech32}")

Profile identifier:

relays = ["wss://relay.damus.io"]
nprofile = Nip19Profile(keys.public_key(), relays)

# URI nprofile
nprofile_uri = nprofile.to_nostr_uri()
print(f" Profile (URI):    {nprofile_uri}")

# bech32 nprofile
nprofile_parse = Nip21.parse(nprofile_uri)
if nprofile_parse.as_enum().is_profile():
    nprofile_bech32 = Nip19Profile.from_nostr_uri(nprofile_uri).to_bech32()
    print(f" Profile (bech32): {nprofile_bech32}")

Event identifier:

relays = ["wss://relay.damus.io"]
nevent = Nip19Event(event.id(), keys.public_key(), kind=None, relays=relays)

# URI nevent
nevent_uri = nevent.to_nostr_uri()
print(f" Event (URI):    {nevent_uri}")

# bech32 nevent
nevent_parse = Nip21.parse(nevent_uri)
if nevent_parse.as_enum().is_event():
    nevent_bech32 = Nip19Event.from_nostr_uri(nevent_uri).to_bech32()
    print(f" Event (bech32): {nevent_bech32}")

Coordinate identifier:

coord = Coordinate(Kind(0), keys.public_key())

# URI naddr
coord_uri = coord.to_nostr_uri()
print(f" Coordinate (URI):    {coord_uri}")

# bech32 naddr
coord_parse = Nip21.parse(coord_uri)
if coord_parse.as_enum().is_coord():
    coord_bech32 = Coordinate.parse(coord_uri).to_bech32()
    print(f" Coordinate (bech32): {coord_bech32}")
JavaScript

Generally speaking the simplest way for handling NIP-21 objects is by the toNostrUri() and fromNostrUri() methods for encoding or decoding data, respectively.

Public key:

let pk_uri = keys.publicKey.toNostrUri();
console.log(` Public key (URI): ${pk_uri}`);

Note:

let event = EventBuilder.textNote("Hello from rust-nostr JS bindings!").signWithKeys(keys);
let note_uri = event.id.toNostrUri()
console.log(` Event (URI): ${note_uri}`);

Profile identifier:

let relays = ["wss://relay.damus.io"];
let nprofile = new Nip19Profile(keys.publicKey, relays);

// URI nprofile
let nprofile_uri = nprofile.toNostrUri();
console.log(` Profile (URI):    ${nprofile_uri}`);

// bech32 nprofile
let nprofile_bech32 = Nip19Profile.fromNostrUri(nprofile_uri).toBech32();
console.log(` Profile (bech32): ${nprofile_bech32}`);

Event identifier:

let nevent = new Nip19Event(event.id, keys.publicKey, undefined, relays);

// URI nevent
let nevent_uri = nevent.toNostrUri();
console.log(` Event (URI):    ${nevent_uri}`);

// bech32 nevent
let nevent_bech32 = Nip19Event.fromNostrUri(nevent_uri).toBech32();
console.log(` Event (bech32): ${nevent_bech32}`);

Coordinate identifier:

// URI naddr
let coord_uri = new Coordinate(event.kind, keys.publicKey).toNostrUri();
console.log(` Coordinate (URI):    ${coord_uri}`);

// bech32 naddr
let coord_bech32 = new Coordinate(event.kind, keys.publicKey).toBech32();
console.log(` Coordinate (bech32): ${coord_bech32}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

NIP-44: Encrypted Payloads (Versioned)

Rust
use nostr_sdk::prelude::*;

pub fn run() -> Result<()> {
    let keys = Keys::generate();

    let pk =
        PublicKey::from_hex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")?;

    let ciphertext = nip44::encrypt(keys.secret_key(), &pk, "my message", nip44::Version::V2)?;
    println!("Encrypted: {ciphertext}");

    let plaintext = nip44::decrypt(keys.secret_key(), &pk, ciphertext)?;
    println!("Decrypted: {plaintext}");

    Ok(())
}
Python
from nostr_sdk import Keys, PublicKey, nip44_encrypt, nip44_decrypt, Nip44Version

def nip44():
    print("\nEncrypting and Decrypting Messages (NIP-44):")
    keys = Keys.generate()

    pk = PublicKey.parse("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")

    ciphertext = nip44_encrypt(keys.secret_key(), pk, "my message", Nip44Version.V2)
    print(f" Encrypted: {ciphertext}")

    plaintext = nip44_decrypt(keys.secret_key(), pk, ciphertext)
    print(f" Decrypted: {plaintext}")
JavaScript
import {Keys, PublicKey, nip44Encrypt, nip44Decrypt, NIP44Version, loadWasmSync} from "@rust-nostr/nostr-sdk";

function run() {
    // Load WASM
    loadWasmSync();

    let keys = Keys.generate();

    let public_key = PublicKey.parse("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798");

    let ciphertext = nip44Encrypt(keys.secretKey, public_key, "my message", NIP44Version.V2)
    console.log("Encrypted: " + ciphertext)

    let plaintext = nip44Decrypt(keys.secretKey, public_key, ciphertext)
    console.log("Decrypted: " + plaintext)
}

run();
Kotlin

TODO

Swift
import Foundation
import NostrSDK

func nip44() throws {
    let keys = Keys.generate()

    let publicKey = try PublicKey.parse(publicKey: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")

    let ciphertext = try nip44Encrypt(secretKey: keys.secretKey(), publicKey: publicKey, content: "my message", version: Nip44Version.v2)
    print("Encrypted: \(ciphertext)");

    let plaintext = try nip44Decrypt(secretKey: keys.secretKey(), publicKey: publicKey, payload: ciphertext)
    print("Decrypted: \(plaintext)");
}
Flutter

TODO

NIP-47: Nostr Wallet Connect

Full example

Rust
use nwc::prelude::*;

pub async fn run() -> Result<()> {
    // Parse NWC uri
    let uri = NostrWalletConnectURI::parse("nostr+walletconnect://..")?;

    // Initialize NWC client
    let nwc = NWC::new(uri);

    // Get info
    let info = nwc.get_info().await?;
    println!("Supported methods: {:?}", info.methods);

    // Get balance
    let balance = nwc.get_balance().await?;
    println!("Balance: {balance} SAT");

    // Pay an invoice
    let params = PayInvoiceRequest::new("lnbc..");
    nwc.pay_invoice(params).await?;

    // Make an invoice
    let params = MakeInvoiceRequest {
        amount: 100,
        description: None,
        description_hash: None,
        expiry: None,
    };
    let result = nwc.make_invoice(params).await?;
    println!("Invoice: {}", result.invoice);

    Ok(())
}
Python
from nostr_sdk import NostrWalletConnectUri, Nwc, PayInvoiceRequest, MakeInvoiceRequest


async def main():
    # Parse NWC uri
    uri = NostrWalletConnectUri.parse("nostr+walletconnect://..")

    # Initialize NWC client
    nwc = Nwc(uri)

    # Get info
    info = await nwc.get_info()
    print(info)

    # Get balance
    balance = await nwc.get_balance()
    print(f"Balance: {balance} SAT")

    # Pay an invoice
    params = PayInvoiceRequest(invoice = "lnbc..", id = None, amount = None)
    await nwc.pay_invoice(params)

    # Make an invoice
    params = MakeInvoiceRequest(amount = 100, description = None, description_hash = None, expiry = None)
    result = await nwc.make_invoice(params)
    print(f"Invoice: {result.invoice}")

JavaScript
import {NWC, NostrWalletConnectURI, PayInvoiceRequest, MakeInvoiceRequest, loadWasmAsync} from "@rust-nostr/nostr-sdk";

async function main() {
    // Load WASM
    await loadWasmAsync();

    // Parse NWC uri
    let uri = NostrWalletConnectURI.parse("nostr+walletconnect://..");

    // Initialize NWC client
    let nwc = new NWC(uri);

    // Get info
    let info = await nwc.getInfo();
    console.log("Supported methods: ", info.methods);

    // Get balance
    let balance = await nwc.getBalance();
    console.log("Balance: " + balance + " SAT");

    // Pay an invoice
    let payInvoiceParams = new PayInvoiceRequest();
    payInvoiceParams.invoice = "lnbc..";
    await nwc.payInvoice(payInvoiceParams);

    // Make an invoice
    let makeInvoiceParams = new MakeInvoiceRequest();
    makeInvoiceParams.amount = BigInt(100);
    const result = await nwc.makeInvoice(makeInvoiceParams)
    console.log("Invoice: " + result.invoice);

    // Drop client
    nwc.free();
}

main();
Kotlin
import rust.nostr.sdk.*

suspend fun nip47() {
    // Parse NWC uri
    val uri = NostrWalletConnectUri.parse("nostr+walletconnect://..")

    // Initialize NWC client
    val nwc = Nwc(uri)

    // Get info
    val info = nwc.getInfo()
    println("Supported methods: ${info.methods}")

    // Get balance
    val balance = nwc.getBalance()
    println("Balance: $balance SAT")

    // Pay an invoice
    val payInvoiceParams = PayInvoiceRequest(invoice = "lnbc...", amount = null, id = null)
    nwc.payInvoice(payInvoiceParams)

    // Make an invoice
    val makeInvoiceParams = MakeInvoiceRequest(amount = 100u, description = null, descriptionHash = null, expiry = null)
    val result = nwc.makeInvoice(makeInvoiceParams)
    println("Invoice: ${result.invoice}")
}
Swift

TODO

Flutter

TODO

NIP-59: Gift Wrap

Rust
use nostr_sdk::prelude::*;

pub async fn run() -> Result<()> {
    // Sender keys
    let alice_keys =
        Keys::parse("5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a")?;

    // Receiver Keys
    let bob_keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?;

    // Compose rumor
    let rumor: EventBuilder = EventBuilder::text_note("Test");

    // Build gift wrap with sender keys
    let gw: Event = EventBuilder::gift_wrap(&alice_keys, &bob_keys.public_key(), rumor, None).await?;
    println!("Gift Wrap: {}", gw.as_json());

    // Extract rumor from gift wrap with receiver keys
    let UnwrappedGift { sender, rumor } = nip59::extract_rumor(&bob_keys, &gw).await?;
    println!("Sender: {sender}");
    println!("Rumor: {}", rumor.as_json());

    Ok(())
}
Python
from nostr_sdk import Keys, EventBuilder, Event, gift_wrap, UnwrappedGift, UnsignedEvent, NostrSigner


async def nip59():
    print("\nGift Wrapping (NIP-59):")
    # Sender Keys
    alice_keys = Keys.parse("5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a")
    alice_signer = NostrSigner.keys(alice_keys)

    # Receiver Keys
    bob_keys = Keys.parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")
    bob_signer = NostrSigner.keys(bob_keys)

    # Compose rumor
    rumor = EventBuilder.text_note("Test")

    # Build gift wrap with sender keys
    gw: Event = await gift_wrap(alice_signer, bob_keys.public_key(), rumor, None)
    print(f" Gift Wrap:\n{gw.as_json()}")

    # Extract rumor from gift wrap with receiver keys
    print("\n Unwrapped Gift:")
    unwrapped_gift = await UnwrappedGift.from_gift_wrap(bob_signer, gw)
    sender = unwrapped_gift.sender()
    unwrapped_rumor: UnsignedEvent = unwrapped_gift.rumor()
    print(f"     Sender: {sender.to_bech32()}")
    print(f"     Rumor: {unwrapped_rumor.as_json()}")
JavaScript
import {Keys, EventBuilder, UnwrappedGift, NostrSigner, loadWasmAsync} from "@rust-nostr/nostr-sdk";

async function run() {
    // Load WASM
    await loadWasmAsync();

    // Sender Keys
    const alice_keys = Keys.parse("5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a");
    const alice_signer = NostrSigner.keys(alice_keys);

    // Receiver Keys
    const bob_keys = Keys.parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99");
    const bob_signer = NostrSigner.keys(bob_keys);

    // Compose rumor
    const rumor = EventBuilder.textNote("Test")

    // Build gift wrap with sender keys
    const gw = await EventBuilder.giftWrap(alice_signer, bob_keys.publicKey, rumor)
    console.log("Gift Wrap: " + gw.asJson())

    // Extract rumor from gift wrap with receiver keys
    let unwrapped_gift = await UnwrappedGift.fromGiftWrap(bob_signer, gw);
    console.log("Sender: ", unwrapped_gift.sender.toBech32())
    console.log("Rumor: ", unwrapped_gift.rumor.asJson())
}

run();
Kotlin

TODO

Swift
import NostrSDK
import Foundation

func nip59() {
    // TODO
}
Flutter

TODO

NIP-65: Relay List Metadata

Either the Event Builder struct and associated relay_list() function, or, the Tag struct and associated relay_metadata() function, can be used to construct NIP-65 compliant events (kind:10002), which are designed to advertise user's preferred relays from which content can be retrieved and/or published.

Rust

TODO

Python

The simplest way to create relay metadata events is via the relay_list() method and EventBuilder class. To do this we pass the method a dictionary containing the relay URL (key) and READ/WRITE (value), which is set using the RelayMetadata class.

Note that where no read or write value is specified (e.g. None), these should be handled as both read and write by clients (as indicated in the NIP-65 specification).

# Create relay dictionary
relays_dict = {
    "wss://relay.damus.io": RelayMetadata.READ,
    "wss://relay.primal.net": RelayMetadata.WRITE,
    "wss://relay.nostr.band": None
}

# Build/sign event
builder = EventBuilder.relay_list(relays_dict)
event = builder.sign_with_keys(keys)

# Print event as json
print(f" Event: {event.as_json()}")

As an alternative approach, the Tag class and relay_metadata() method can be used to create individual tag objects for inclusion in a purpose built kind:10002 event.

# Create relay metadata tags
tag1 = Tag.relay_metadata("wss://relay.damus.io", RelayMetadata.READ)
tag2 = Tag.relay_metadata("wss://relay.primal.net", RelayMetadata.WRITE)
tag3 = Tag.relay_metadata("wss://relay.nostr.band", None)

# Build/sign event
kind = Kind(10002)
builder = EventBuilder(kind = kind, content = "").tags([tag1, tag2, tag3])
event = builder.sign_with_keys(keys)

# Print event as json
print(f" Event: {event.as_json()}")
JavaScript

The simplest way to create relay metadata events is via the relayList() method and EventBuilder class. To do this we can simply use the RelayListItem class to create a list of relay objects containing the relay URL and READ/WRITE, which is set using the RelayMetadata class.

Note that where no read or write value is specified (e.g. null), these should be handled as both read and write by clients (as indicated in the NIP-65 specification).

// Create relay list
let relays = [
    new RelayListItem("wss://relay.damus.io", RelayMetadata.Read),
    new RelayListItem("wss://relay.primal.net", RelayMetadata.Write),
    new RelayListItem("wss://relay.nostr.band")
];

// Build/sign event
let builder = EventBuilder.relayList(relays);
let event = builder.signWithKeys(keys);

// Print event as json
console.log(` Event: ${event.asJson()}`);

As an alternative approach, the Tag class and relayMetadata() method can be used to create individual tag objects for inclusion in a purpose built kind:10002 event.

// Create relay metadata tags
let tag1 = Tag.relayMetadata("wss://relay.damus.io", RelayMetadata.Read);
let tag2 = Tag.relayMetadata("wss://relay.primal.net", RelayMetadata.Write);
let tag3 = Tag.relayMetadata("wss://relay.nostr.band");

// Build/sign event
let kind = new Kind(10002);
builder = new EventBuilder(kind, "").tags([tag1, tag2, tag3]);
event = builder.signWithKeys(keys);

// Print event as json
console.log(` Event: ${event.asJson()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Enabling logging

Messages

Underpinning the Nostr Protocol is a relatively simplistic messaging system by which clients (read: applications) communicate with relays (read: databases) to retrieve and store data in a JSON format. This communication process is documented in more detail in NIP-01 - Communication between clients and relays but at a very high level is broken down into three main components:

  • Client Messages - Which define the specific formats/structure by which communication from the client to the relay is performed
  • Relay Messages - The pre-defined ways in which relays will communicate with/respond to clients

The messages themselves (for both client and relay) are passed in the form of a JSON array where the first item in the array is used to identify the type of message (e.g. "EVENT") and the subsequent items provide the relevant parameter values associated with the message in the order specified by the protocol documentation.

Navigate to the relevant sections linked above to see the implementation of the communication rules in more detail.

Client Message

One of the biggest strengths of the Nostr network is the almost limitless possibilities for interoperable user-facing applications. In protocol terminology these applications are often referred to as Clients. Where relays provide a data housing mechanism the clients get that data in front of users in myriad of wild and wonderful ways. Clients use WebSockets as a means to connect to relays and pass relevant data back and forth around the network. In accordance with the protocol base specification (NIP-01) there are 3 main types of messages which clients construct and pass to relays as JSON arrays. This section is concerned with the construction of these message objects using the Client Message Module.

JSON de/serialization

Rust

TODO

Python

The ClientMessage class easily handles the construction of the 3 main message types EVENT, REQ, and CLOSE. In the examples below we can utilize the relevant class methods event(), req() and close(), respectively, to create the client message objects.

Once we have the ClientMessage objects we can use the as_enum() or as_json() methods to present their content. Note that when using as_enum() we unlock some additional methods associated with the ClientMessageEnum class. These allow for logical tests to be performed to establish the type of message object being assessed (for example, is_req() will return a bool result assessing if the object represents an REQ message type).

# Event client message
print("  Event Client Message:")
message = ClientMessage.event(event)
print(f"     - Event Message: {message.as_enum().is_event_msg()}")
print(f"     - JSON: {message.as_json()}")

Note that when constructing a REQ we want to pass through a Filter object which will allow the relay to return data meeting a given set of criteria. Please jump to the Filter section for more details on how to construct these objects.

# Request client message
print("  Request Client Message:")
f = Filter().id(event.id())
message = ClientMessage.req(subscription_id="ABC123", filters=[f])
print(f"     - Request Message: {message.as_enum().is_req()}")
print(f"     - JSON: {message.as_json()}")
# Close client message
print("  Close Client Message:")
message = ClientMessage.close("ABC123")
print(f"     - Close Message: {message.as_enum().is_close()}")
print(f"     - JSON: {message.as_json()}")

When presented with a client message object as either a JSON or an instance of the ClientMessageEnum class we can parse these data using the from_json() or from_enum() methods, respectively.

# Parse Messages from JSON and/or Enum
print("  Parse Client Messages:")
message = ClientMessage.from_json('["REQ","ABC123",{"#p":["421a4dd67be773903f805bcb7975b4d3377893e0e09d7563b8972ee41031f551"]}]')
print(f"     - ENUM: {message.as_enum()}")
f = Filter().pubkey(keys.public_key())
message = ClientMessage.from_enum(cast(ClientMessageEnum, ClientMessageEnum.REQ("ABC123", filters=[f])))
print(f"     - JSON: {message.as_json()}")
JavaScript

The ClientMessage class easily handles the construction of the 3 main message types EVENT, REQ, and CLOSE. In the examples below we can utilize the relevant class methods event(), req() and close(), respectively, to create the client message objects.

Once we have the ClientMessage objects we can use the asJson() method to present their content.

// Create Event client message
console.log("  Event Client Message:");
let clientMessage = ClientMessage.event(event);
console.log(`     - JSON: ${clientMessage.asJson()}`);

Note that when constructing a REQ we want to pass through a Filter object which will allow the relay to return data meeting a given set of criteria. Please jump to the Filter section for more details on how to construct these objects.

// Create Request client message
console.log("  Request Client Message:");
let f = new Filter().id(event.id);
clientMessage = ClientMessage.req("ABC123", [f]);
console.log(`     - JSON: ${clientMessage.asJson()}`);
// Create Close client message
console.log("  Close Client Message:");
clientMessage = ClientMessage.close("ABC123");
console.log(`     - JSON: ${clientMessage.asJson()}`);

When presented with a client message object as either a JSON using the fromJson() method.

// Parse Messages from JSON
console.log("  Parse Client Messages:");
clientMessage = ClientMessage.fromJson('["REQ","ABC123",{"#p":["421a4dd67be773903f805bcb7975b4d3377893e0e09d7563b8972ee41031f551"]}]');
console.log(`     - JSON: ${clientMessage.asJson()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Authorization and Count Messages

Rust

TODO

Python

As an extension of the client messaging section of the protocol NIP-42 and NIP-45 introduce two new messaging types AUTH and COUNT.

The AUTH type is designed to facilitate a method by which clients can authenticate with a given relay. Whereas the COUNT type offers a method for clients can request simple counts of events from relays. These are constructed in much the same way as the earlier message examples, by using the ClientMessage class in conjunction with the relevant methods auth() and count(). As before the as_enum() method can be used to unlock logical test methods (e.g., is_auth()) associated with these message objects.

# Auth client message  (NIP42)
print("  Auth Client Message:")
message = ClientMessage.auth(event)
print(f"     - Auth Message: {message.as_enum().is_auth()}")
print(f"     - JSON: {message.as_json()}")

Note that COUNT is effectively a specific type of REQ message therefore it utilizes the Filter object in constructing the criteria which should be used by the relay to return the count value.

# Count client message (NIP45)
print("  Count Client Message:")
f = Filter().pubkey(keys.public_key())
message = ClientMessage.count(subscription_id="ABC123", filters=[f])
print(f"     - Count Message: {message.as_enum().is_count()}")
print(f"     - JSON: {message.as_json()}")
JavaScript

As an extension of the client messaging section of the protocol NIP-42 and NIP-45 introduce two new messaging types AUTH and COUNT.

The AUTH type is designed to facilitate a method by which clients can authenticate with a given relay. Whereas the COUNT type offers a method for clients can request simple counts of events from relays. These are constructed in much the same way as the earlier message examples, by using the ClientMessage class in conjunction with the relevant methods auth() and count().

// Create Auth client message  (NIP42)
console.log("  Auth Client Message:");
clientMessage = ClientMessage.auth(event);
console.log(`     - JSON: ${clientMessage.asJson()}`);

Note that COUNT is effectively a specific type of REQ message therefore it utilizes the Filter object in constructing the criteria which should be used by the relay to return the count value.

// Create Count client message (NIP45)
console.log("  Count Client Message:");
f = new Filter().pubkey(keys.publicKey);
clientMessage = ClientMessage.count("ABC123", [f]);
console.log(`     - JSON: ${clientMessage.asJson()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Negentropy Messages

Rust

TODO

Python

Finally, the ClientMessageEnum class also opens up three additional message types NEG_OPEN(), NEG_CLOSE() and NEG_MSG(). These do not form part of the standard protocol specification but instead form part of an additional protocol Negentropy for handling set-reconciliation.

To construct these we need to first create them as instance of the ClientMessageEnum class and then pass these into a ClientMessage object using the from_enum() method.

# Negative Open Message
print("  Negative Client Message (open):")
message = ClientMessage.from_enum(cast(ClientMessageEnum, ClientMessageEnum.NEG_OPEN("ABC123", filter=f, id_size=32, initial_message="<hex-msg>")))
print(f"     - Negative Error Open: {message.as_enum().is_neg_open()}")
print(f"     - JSON: {message.as_json()}")
# Negative Close Message
print("  Negative Client Message (close):")
message = ClientMessage.from_enum(cast(ClientMessageEnum, ClientMessageEnum.NEG_CLOSE("ABC123")))
print(f"     - Negative Error Close: {message.as_enum().is_neg_close()}")
print(f"     - JSON: {message.as_json()}")
# Negative Error Message
print("  Negative Client Message (message):")
enum_msg = ClientMessageEnum.NEG_MSG("ABC123", message="This is not the message you are looking for")
message = ClientMessage.from_enum(cast(ClientMessageEnum, enum_msg))
print(f"     - JSON: {message.as_json()}")
print(f"     - Negative Error Message: {message.as_enum().is_neg_msg()}")
JavaScript

Not currently available in the Javascript Bindings.

Kotlin

TODO

Swift

TODO

Flutter

TODO

Relay Message

The backbone of the Nostr network is built on relays rather than application specific centralized databases. Clients use WebSockets as a means to connect to relays and pass relevant data back and forth around the network. In accordance with the protocol base specification (NIP-01) there are 5 main types of messages which relays construct as JSON arrays. This section is concerned with the construction of these message objects using the Relay Message Module.

JSON de/serialization

Rust
use nostr_sdk::prelude::*;

pub fn relay_message() -> Result<()> {
    // Deserialize from json
    let json = r#"["EVENT", "random_string", {"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe","created_at":1612809991,"kind":1,"tags":[],"content":"test","sig":"273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"}]"#;
    let msg = RelayMessage::from_json(json)?;

    // Serialize as json
    let json = msg.as_json();
    println!("{json}");

    Ok(())
}
Python

The RelayMessage class easily handles the construction of the 5 main message types EVENT, OK, EOSE (end of stored events), CLOSED and NOTICE. In the examples below we can utilize the relevant class methods event(), ok(), eose(), closed() and notice(), respectively, to create the relay message objects.

Once we have the RelayMessage objects we can use the as_enum() or as_json() methods to present their content. Note that when using as_enum() we unlock some additional methods associated with the RelayMessageEnum class. These allow for logical tests to be performed to establish the type of message object being assessed (for example, is_ok() will return a bool result assessing if the object represents an OK message type).

# Create Event relay message
print("  Event Relay Message:")
message = RelayMessage.event("subscription_ID_abc123", event)
print(f"     - Event Message: {message.as_enum().is_event_msg()}")
print(f"     - JSON: {message.as_json()}")
# Create event acceptance relay message
print("  Event Acceptance Relay Message:")
message = RelayMessage.ok(event.id(), False, "You have no power here, Gandalf The Grey")
print(f"     - Event Acceptance Message: {message.as_enum().is_ok()}")
print(f"     - JSON: {message.as_json()}")
# Create End of Stored Events relay message
print("  End of Stored Events Relay Message:")
message = RelayMessage.eose("subscription_ID_abc123")
print(f"     - End of Stored Events Message: {message.as_enum().is_end_of_stored_events()}")
print(f"     - JSON: {message.as_json()}")
# Create Closed relay message
print("  Closed Relay Message:")
message = RelayMessage.closed("subscription_ID_abc123", "So long and thanks for all the fish")
print(f"     - Closed Message: {message.as_enum().is_closed()}")
print(f"     - JSON: {message.as_json()}")
# Create Notice relay message
print("  Notice Relay Message:")
message = RelayMessage.notice("You have been served")
print(f"     - Notice Message: {message.as_enum().is_notice()}")
print(f"     - JSON: {message.as_json()}")

When presented with a relay message object as either a JSON or an instance of the RelayMessageEnum class we can parse these data using the from_json() or from_enum() methods, respectively.

# Parse Messages from JSON and/or Enum
print("  Parse Relay Messages:")
message = RelayMessage.from_json('["NOTICE","You have been served"]')
print(f"     - ENUM: {message.as_enum()}")
message = RelayMessage.from_enum(cast(RelayMessageEnum, RelayMessageEnum.NOTICE("You have been served")))
print(f"     - JSON: {message.as_json()}")
JavaScript

The RelayMessage class easily handles the construction of the 5 main message types EVENT, OK, EOSE (end of stored events), CLOSED and NOTICE. In the examples below we can utilize the relevant class methods event(), ok(), eose(), closed() and notice(), respectively, to create the relay message objects.

Once we have the RelayMessage objects we can use the asJson() method to present their content.

console.log("  Event Relay Message:");
let relayMessage = RelayMessage.event("subscription_ID_abc123", event);
console.log(`     - JSON: ${relayMessage.asJson()}`);
console.log("  Event Acceptance Relay Message:");
relayMessage = RelayMessage.ok(event.id, false, "You have no power here, Gandalf The Grey");
console.log(`     - JSON: ${relayMessage.asJson()}`);
console.log("  End of Stored Events Relay Message:");
relayMessage = RelayMessage.eose("subscription_ID_abc123");
console.log(`     - JSON: ${relayMessage.asJson()}`);
console.log("  Closed Relay Message:");
relayMessage = RelayMessage.closed("subscription_ID_abc123", "So long and thanks for all the fish");
console.log(`     - JSON: ${relayMessage.asJson()}`);
console.log("  Notice Relay Message:");
relayMessage = RelayMessage.notice("You have been served");
console.log(`     - JSON: ${relayMessage.asJson()}`);

When presented with a relay message object as either a JSON we can parse these data using the fromJson() method.

console.log("  Parse Relay Message:");
relayMessage = RelayMessage.fromJson('["NOTICE","You have been served"]');
console.log(`     - JSON: ${relayMessage.asJson()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Authorization and Count Messages

Rust

TODO

Python

As an extension of the relay messaging section of the protocol NIP-42 and NIP-45 introduce two new messaging types AUTH and COUNT.

The AUTH type is designed to facilitate a method by which clients can authenticate with a given relay. Whereas the COUNT type offers a method for relays to provide simple counts of events to clients (upon request). These are constructed in much the same way as the earlier message examples, by using the RelayMessage class in conjunction with the relevant methods auth() and count(). As before the as_enum() method can be used to unlock logical test methods (e.g., is_auth()) associated with these message objects.

# Create Authorization relay message (NIP42)
print("  Auth Relay Message:")
message = RelayMessage.auth("I Challenge You To A Duel! (or some other challenge string)")
print(f"     - Auth Message: {message.as_enum().is_auth()}")
print(f"     - JSON: {message.as_json()}")
# Create Count relay message (NIP45)
print("  Count Relay Message:")
message = RelayMessage.count("subscription_ID_abc123", 42)
print(f"     - Count Message: {message.as_enum().is_count()}")
print(f"     - JSON: {message.as_json()}")
JavaScript

As an extension of the relay messaging section of the protocol NIP-42 and NIP-45 introduce two new messaging types AUTH and COUNT.

The AUTH type is designed to facilitate a method by which clients can authenticate with a given relay. Whereas the COUNT type offers a method for relays to provide simple counts of events to clients (upon request). These are constructed in much the same way as the earlier message examples, by using the RelayMessage class in conjunction with the relevant methods auth() and count().

console.log("  Auth Relay Message:");
relayMessage = RelayMessage.auth("I Challenge You To A Duel! (or some other challenge string)");
console.log(`     - JSON: ${relayMessage.asJson()}`);
console.log("  Count Relay Message:");
relayMessage = RelayMessage.count("subscription_ID_abc123", 42);
console.log(`     - JSON: ${relayMessage.asJson()}`);
Kotlin

TODO

Swift

TODO

Flutter

TODO

Error Messages

Rust

TODO

Python

Finally, the RelayMessageEnum class also opens up two additional message types NEG_ERR() and NEG_CODE(). These do not form part of the standard protocol specification but do have specific uses when it comes to providing methods by which error messaging (or error codes) can be handled by relays. To construct these we need to first create them as instance of the RelayMessageEnum class and then pass these into a RelayMessage object using the from_enum() method.

# Negative Error Code
print("  Negative Relay Message (code):")
relay_message_neg = RelayMessageEnum.NEG_ERR("subscription_ID_abc123", "404")
message = RelayMessage.from_enum(cast(RelayMessageEnum, relay_message_neg))
print(f"     - Negative Error Code: {message.as_enum().is_neg_err()}")
print(f"     - JSON: {message.as_json()}")
# Negative Error Message
print("  Negative Relay Message (message):")
relay_message_neg = RelayMessageEnum.NEG_MSG("subscription_ID_abc123", "This is not the message you are looking for")
message = RelayMessage.from_enum(cast(RelayMessageEnum, relay_message_neg))
print(f"     - Negative Error Message: {message.as_enum().is_neg_msg()}")
print(f"     - JSON: {message.as_json()}")
JavaScript

Not available currently with JavaScript bindings.

Kotlin

TODO

Swift

TODO

Flutter

TODO

Your donation directly supports the continued development of rust-nostr!

Give a one-time donation

Bitcoin

Addresses
  • 🔗 On-chain: bc1quk478kpm45744q5pt3p9j42fnv72ykytmt3z0j
  • ⚡ Lightning: pay@yukikishimoto.com
  • 💧 Liquid: lq1qqdwn93gehkq4mtz2amsagawgfd6y9ksrkekal5u8tmle07f9fa0kgcnfez4lguhekeeyhy78nfqy8tyqvxayywgpwvm73t6av
Payment processors

Altcoins

We don't accept coins different from bitcoin. If you are interested to support rust-nostr with these, are available some swap services to convert them directly into bitcoin:

Disclaimer: rust-nostr is not affiliated in any way with these services!

Recurring donations

Please consider becoming a sustaining supporter of the rust-nostr project by giving a recurring monthly donation. If we know how much funding to expect every month, we can better plan our efforts and the use of available resources.

You can setup an automatically recurring donation here:

Verify donation details

The donation details can be verified via the rust-nostr/nostr repository, specifically in the fund directory.

Changelog

MIT License

Copyright (c) 2022-2023 Yuki Kishimoto

Copyright (c) 2023-2024 Rust Nostr Developers

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.