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
andFlutter
so you can build nostr apps in your preferred programming language. -
Multi-Platform Support: Write nostr apps for
desktop
,server
,mobile
,web
and/orembedded
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 unparalleledperformance
andmemory safety
, our libraries offersspeed
,stability
andreliability
. The same features are extended to itsbindings
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
- Nostr community: nostr:naddr1qvzqqqyx7cpzq6xcz9jerqgqkldy8lpg7lglcyj4g3nwzy2cs6u70wejdaj7csnjqyg8wumn8ghj7mn0wd68ytnddakj7qgawaehxw309ahx7um5wghx6at5d9h8jampd3kx2apwvdhk6tcqpfe82um594hx7um5wguyvg2q
- Nostr public channel: nostr:nevent1qvzqqqqq9qpzq6xcz9jerqgqkldy8lpg7lglcyj4g3nwzy2cs6u70wejdaj7csnjqyg8wumn8ghj7mn0wd68ytnddakj7qg4waehxw309ahx7um5wghx77r5wghxgetk9uqzpw86ja20989g7vupv08fq78z03aevh5kheqr6al07v7sslzqxmhhxau0ah
- Matrix space with some rooms at #rustnostr:matrix.org.
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
Add the nostr-sdk
dependency in your Cargo.toml
file:
[dependencies]
nostr-sdk = "0.36"
Alternatively, you can add it directly from git
source:
[dependencies]
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", tag = "v0.36.0" }
Import the library in your code:
#![allow(unused)] fn main() { use nostr_sdk::prelude::*; }
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.36.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.8 | 3.9 | 3.10 | 3.11 | 3.12 | 3.13 |
---|---|---|---|---|---|
❌ | ✅ | ✅ | ✅ | ✅ | ❌ |
Platform support
OS | x64 | aarch64 | arm | i686 |
---|---|---|---|---|
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())
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.36.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();
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.36.0")
}
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.12.0@aar")
}
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.36.0"),
Add the following code to your package:
nostr_sdk:
git:
url: https://github.com/rust-nostr/nostr.git
ref: v0.36.0
path: bindings/nostr-sdk-flutter
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
let keys: Keys = Keys::generate();
let client = Client::new(keys);
keys = Keys.generate()
client = Client(keys)
let keys: Keys = Keys.generate();
let signer = NostrSigner.keys(keys);
let client = new Client(signer);
//val keys = Keys.generate()
//val client = Client(signer = keys) // TODO: uncomment when fixed
TODO
TODO
Add some relays and connect
client.add_relay("wss://relay.damus.io").await?;
client.connect().await;
await client.add_relay("wss://relay.damus.io")
await client.connect()
await client.addRelay("wss://relay.damus.io")
await client.connect();
//client.addRelay("wss://relay.damus.io")
//client.connect()
TODO
TODO
Publish a text note
let builder = EventBuilder::text_note("Hello, rust-nostr!", []);
client.send_event_builder(builder).await?;
builder = EventBuilder.text_note("Hello, rust-nostr!", [])
await client.send_event_builder(builder)
let builder = EventBuilder.textNote("Hello, rust-nostr!", []);
await client.sendEventBuilder(builder);
//val builder = EventBuilder.textNote("Hello, rust-nostr!", listOf())
//client.sendEventBuilder(builder)
TODO
TODO
Full example
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!", []);
client.send_event_builder(builder).await?;
Ok(())
}
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!", [])
await client.send_event_builder(builder)
import {Keys, Client, EventBuilder, NostrSigner} from "@rust-nostr/nostr-sdk";
export async function hello() {
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!", []);
await client.sendEventBuilder(builder);
}
import rust.nostr.sdk.*
suspend fun hello() {
//val keys = Keys.generate()
//val client = Client(signer = keys) // TODO: uncomment when fixed
//client.addRelay("wss://relay.damus.io")
//client.connect()
//val builder = EventBuilder.textNote("Hello, rust-nostr!", listOf())
//client.sendEventBuilder(builder)
}
TODO
TODO
Signers
TODO: description and NostSigner
abstraction
Keys
Generate new random keys
To generate a new key pair use the generate()
method:
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!("Public key (bech32): {}", public_key.to_bech32()?);
println!("Secret key (hex): {}", keys.secret_key().to_secret_hex());
println!("Secret key (bech32): {}", secret_key.to_bech32()?);
Ok(())
}
def generate():
keys = Keys.generate()
public_key = keys.public_key()
secret_key = keys.secret_key()
print("Keys:")
print(" Public keys:")
print(f" hex: {public_key.to_hex()}")
print(f" bech32: {public_key.to_bech32()}")
print()
print(" Secret keys:")
print(f" hex: {secret_key.to_hex()}")
print(f" bech32: {secret_key.to_bech32()}")
export function generate() {
// Generate new random keys
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());
}
fun generate() {
val keys = Keys.generate();
val publicKey = keys.publicKey();
val secretKey = keys.secretKey();
println("Public key (hex): ${publicKey.toHex()}");
println("Public key (bech32): ${publicKey.toBech32()}");
println("Secret key (hex): ${secretKey.toHex()}");
println("Secret key (bech32): ${secretKey.toHex()}");
}
import NostrSDK
import Foundation
func keys() {
// TODO
}
TODO
Parsing
pub fn restore() -> Result<()> {
// Parse keys directly from secret key
let keys = Keys::parse("secret-key")?;
// Parse secret key and construct keys
let secret_key =
SecretKey::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?;
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(())
}
def restore():
keys = Keys.parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")
secret_key = SecretKey.from_hex("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
keys = Keys(secret_key)
secret_key = SecretKey.from_bech32("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")
keys = Keys(secret_key)
export function restore() {
// Parse directly from secret key
let keys = Keys.parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85");
console.log("Secret key (hex): ", keys.secretKey.toHex());
}
fun restore() {
var keys = Keys.parse("hex or bech32 secret key")
// Parse from hex
var secretKey = SecretKey.fromHex("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
keys = Keys(secretKey = secretKey)
secretKey = SecretKey.fromBech32("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")
keys = Keys(secretKey = secretKey)
}
TODO
TODO
Generate vanity keys
pub fn vanity() -> Result<()> {
let keys = Keys::vanity(vec!["0000", "yuk", "yuk0"], true, 8)?;
println!("Secret key: {}", keys.secret_key().to_bech32()?);
println!("Public key: {}", keys.public_key().to_bech32()?);
Ok(())
}
def vanity():
keys = Keys.vanity(["yuk0"], True, 8)
print(" Vanity:")
print(f" Public keys: {keys.public_key().to_bech32()}")
print(f" Secret keys: {keys.secret_key().to_bech32()}")
export function vanity() {
// NOTE: NOT SUPPORTED YET!
// Generate vanity keys
// let keys = Keys.vanity(["yuk0"], true, 1);
// console.log("Public key (bech32): ", keys.publicKey.toBech32());
// console.log("Secret key (bech32): ", keys.secretKey.toBech32());
}
fun vanity() {
val keys = Keys.vanity(listOf("yuk0"), true, 4u)
println("Public key: ${keys.publicKey().toBech32()}");
println("Secret key: ${keys.secretKey().toHex()}");
}
import NostrSDK
import Foundation
func vanity() {
// TODO
}
TODO
NIP-07: window.nostr
capability for web browsers
Nostr Connect
Event
JSON de/serialization
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(())
}
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}")
import { Event } from "@rust-nostr/nostr-sdk";
export function eventJson() {
// 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);
}
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())
}
import NostrSDK
import Foundation
func json() {
// TODO
}
TODO
Compose with event builder
A convenient way to compose events is by using the EventBuilder
. It allow to compose standard
and/or custom
events.
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::event(textnote_event.id)])
.sign_with_keys(&keys)?;
// Compose POW event
let pow_event =
EventBuilder::text_note("Another reply with POW", [Tag::event(textnote_event.id)])
.pow(20)
.sign_with_keys(&keys)?;
Ok(())
}
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", [Tag.event(textnote_event.id())]).sign_with_keys(keys)
# Compose POW event
pow_event = EventBuilder.text_note("Another reply with POW", [Tag.event(textnote_event.id())]).pow(20).sign_with_keys(keys)
import { Keys, EventBuilder, Tag, Timestamp, Kind } from "@rust-nostr/nostr-sdk"
export function eventBuilder() {
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", [Tag.event(textnoteEvent.id)])
.signWithKeys(keys);
// Compose POW event
let powEvent =
EventBuilder.textNote("Another reply with POW", [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);
}
fun builder() {
val keys = Keys.generate();
// Compose custom event
val customEvent = EventBuilder(Kind(1111u), "", listOf()).signWithKeys(keys);
// Compose text note
val textNoteEvent = EventBuilder.textNote("Hello", listOf()).signWithKeys(keys);
// Compose reply to above text note
val replyEvent = EventBuilder.textNote("Reply to hello", listOf(Tag.event(textNoteEvent.id())))
.signWithKeys(keys);
// Compose POW event
val powEvent =
EventBuilder.textNote("Another reply with POW", listOf(Tag.event(textNoteEvent.id())))
.pow(20u)
.signWithKeys(keys);
println(powEvent.asJson())
}
import NostrSDK
import Foundation
func builder() {
// TODO
}
TODO
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
TODO
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()
.
Similarly, we can parse these different representations of the event ID by using the opposite 'from' methods: from_hex()
, from_bech32()
, from_nostr_uri()
or from_bytes()
.
In the event that we want to generalise and simplify this process, across hex/bech32 or nostr uri formats, we can instead simply call parse()
method and pass this the event id string matching one of these formats.
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)}")
print(f" - From Hex: {EventId.from_hex(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)}")
print(f" - From Bech32: {EventId.from_bech32(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)}")
print(f" - From Nostr URI: {EventId.from_nostr_uri(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)}")
TODO
TODO
TODO
TODO
Access and Verify
TODO
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()}")
TODO
TODO
TODO
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
TODO
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 and 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()
or as_u64()
methods.
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()}")
TODO
TODO
TODO
TODO
Events and Kinds
TODO
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.).
To do this we can use the Kind
class along with the from_enum()
method much as we did in previous examples,
but we can leverage enums representing these types of events e.g. CUSTOM()
or REPLACEABLE()
and pass them the specific Kind integer for the new type of event we're creating.
A good example of this may be events termed as "Parameterized Replaceable Lists".
In the Nostr NIP-01 documentation we see a recommended range for these lists as 30000 <= n < 40000
,
however at the time of writing, only kinds 30000
, 30002
, 30003
, 30004
, 30005
, 30015
, 30030
and 30063
are currently well defined.
Therefore, if we wanted to extend this to say create a new list event of our favourite memes, Kind 30420
,
then we could do this using the PARAMETERIZED_REPLACEABLE(30420)
enum to define the type of event as in the example below.
print(" Kinds Representing types of Events:")
kind = Kind.from_enum(cast(KindEnum, KindEnum.CUSTOM(1337)))
print(f" - Custom Event Kind: {kind.as_u16()} - {kind.as_enum()}")
kind = Kind.from_enum(cast(KindEnum, KindEnum.REPLACEABLE(10420)))
print(f" - Replacable Event Kind: {kind.as_u16()} - {kind.as_enum()}")
kind = Kind.from_enum(cast(KindEnum, KindEnum.PARAMETERIZED_REPLACEABLE(30420)))
print(f" - Parameterised Replaceable Event Kind: {kind.as_u16()} - {kind.as_enum()}")
kind = Kind.from_enum(cast(KindEnum, KindEnum.EPHEMERAL(21420)))
print(f" - Ephemeral Event Kind: {kind.as_u16()} - {kind.as_enum()}")
kind = Kind.from_enum(cast(KindEnum, KindEnum.REGULAR(420)))
print(f" - Regular Event Kind: {kind.as_u16()} - {kind.as_enum()}")
kind = Kind.from_enum(cast(KindEnum, KindEnum.JOB_REQUEST(123)))
print(f" - Job Request Event Kind: {kind.as_u16()} - {kind.as_enum()}")
kind = Kind.from_enum(cast(KindEnum, KindEnum.JOB_RESULT(321)))
print(f" - Job Result Event Kind: {kind.as_u16()} - {kind.as_enum()}")
TODO
TODO
TODO
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
TODO
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()}")
TODO
TODO
TODO
TODO
Serializing and Logical Tests
TODO
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()}")
TODO
TODO
TODO
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
TODO
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()}")
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()}`);
TODO
TODO
TODO
Modify Filters
TODO
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()}")
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()}`);
TODO
TODO
TODO
Other Filter Operations
TODO
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)}")
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)}`);
TODO
TODO
TODO
Client
Options
TODO
Proxy
TODO
Embedded Tor
NIPs
NIP-01: Basic protocol flow description
User metadata
Use the Metadata
struct to deserialize the content of an event into a struct.
let event = EventBuilder::new(Kind::Metadata, content, vec![]).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.
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()}")
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()}`);
TODO
TODO
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>
.
TODO
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()}")
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()}`);
TODO
TODO
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.
TODO
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}")
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}`);
}
TODO
TODO
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.
// 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!".to_string(), None).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;
TODO
TODO
TODO
TODO
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
.
TODO
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}")
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}`);
TODO
TODO
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.
TODO
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.from_nostr_uri(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.from_nostr_uri(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.from_nostr_uri(coord_uri).to_bech32()
print(f" Coordinate (bech32): {coord_bech32}")
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}`);
TODO
TODO
TODO
NIP-44: Encrypted Payloads (Versioned)
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(())
}
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.from_hex("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}")
import {Keys, PublicKey, nip44Encrypt, nip44Decrypt, NIP44Version} from "@rust-nostr/nostr-sdk";
export function run() {
let keys = Keys.generate();
let public_key = PublicKey.fromHex("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)
}
TODO
import NostrSDK
import Foundation
func nip44() {
// TODO
}
TODO
NIP-47: Nostr Wallet Connect
Full example
use nostr_sdk::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
nwc.pay_invoice("lnbc..").await?;
// Make an invoice
let params = MakeInvoiceRequestParams {
amount: 100,
description: None,
description_hash: None,
expiry: None,
};
let result = nwc.make_invoice(params).await?;
println!("Invoice: {}", result.invoice);
Ok(())
}
from nostr_sdk import NostrWalletConnectUri, Nwc, MakeInvoiceRequestParams
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
await nwc.pay_invoice("lnbc..")
# Make an invoice
params = MakeInvoiceRequestParams(amount=100, description=None, description_hash=None, expiry=None)
result = await nwc.make_invoice(params)
print(f"Invoice: {result.invoice}")
import { NWC, NostrWalletConnectURI, MakeInvoiceRequestParams } from "@rust-nostr/nostr-sdk";
export async function main() {
// 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
await nwc.payInvoice("lnbc..")
// Make an invoice
let params = new MakeInvoiceRequestParams();
params.amount = BigInt(100);
const result = await nwc.makeInvoice(params)
console.log("Invoice: " + result.invoice);
// Drop client
nwc.free();
}
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
nwc.payInvoice("lnbc..")
// Make an invoice
val params = MakeInvoiceRequestParams(amount = 100u, description = null, descriptionHash = null, expiry = null)
val result = nwc.makeInvoice(params)
println("Invoice: ${result.invoice}")
}
TODO
TODO
NIP-59: Gift Wrap
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: UnsignedEvent =
EventBuilder::text_note("Test", []).build(alice_keys.public_key());
// 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(())
}
from nostr_sdk import Keys, EventBuilder, Event, gift_wrap, UnwrappedGift, UnsignedEvent
async def nip59():
print("\nGift Wrapping (NIP-59):")
# Sender Keys
alice_keys = Keys.parse("5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a")
# Receiver Keys
bob_keys = Keys.parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")
# Compose rumor
rumor = EventBuilder.text_note("Test", []).build(alice_keys.public_key())
# Build gift wrap with sender keys
gw: Event = await gift_wrap(alice_keys, 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_keys, gw)
sender = unwrapped_gift.sender()
rumor: UnsignedEvent = unwrapped_gift.rumor()
print(f" Sender: {sender.to_bech32()}")
print(f" Rumor: {rumor.as_json()}")
import {Keys, EventBuilder, UnwrappedGift, NostrSigner} from "@rust-nostr/nostr-sdk";
export async function run() {
// 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(alice_keys.publicKey)
// 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())
}
TODO
import NostrSDK
import Foundation
func nip59() {
// TODO
}
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.
TODO
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)
content = ""
tags = [tag1,tag2,tag3]
builder = EventBuilder(kind,content,tags)
event = builder.sign_with_keys(keys)
# Print event as json
print(f" Event: {event.as_json()}")
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);
let content = "";
let tags = [tag1, tag2, tag3];
builder = new EventBuilder(kind, content, tags);
event = builder.signWithKeys(keys);
// Print event as json
console.log(` Event: ${event.asJson()}`);
TODO
TODO
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
TODO
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()}")
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()}`);
TODO
TODO
TODO
Authorization and Count Messages
TODO
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()}")
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()}`);
TODO
TODO
TODO
Negentropy Messages
TODO
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()}")
Not currently available in the Javascript Bindings.
TODO
TODO
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
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(())
}
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()}")
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()}`);
TODO
TODO
TODO
Authorization and Count Messages
TODO
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()}")
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()}`);
TODO
TODO
TODO
Error Messages
TODO
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()}")
Not available currently with JavaScript bindings.
TODO
TODO
TODO
Donate 💜
Your donation directly supports the continued development of rust-nostr
!
Give a one-time donation
Bitcoin
- 🔗 On-chain:
bc1quk478kpm45744q5pt3p9j42fnv72ykytmt3z0j
- ⚡ Lightning:
pay@yukikishimoto.com
- 💧 Liquid:
lq1qqdwn93gehkq4mtz2amsagawgfd6y9ksrkekal5u8tmle07f9fa0kgcnfez4lguhekeeyhy78nfqy8tyqvxayywgpwvm73t6av
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
Unreleased
Summary
Breaking changes
- nostr: change
EventBuilder::gift_wrap
(and linked methods) args to takeextra_tags
instead ofexpiration
(erskingardner) - nostr: change
EventBuilder::gift_wrap
(and linked methods) args to take anEventBuilder
rumor instead ofUnsignedEvent
(Yuki Kishimoto) - nostr: change
EventBuilder::private_msg_rumor
arg to takeextra_tags
instead ofreply_to
(Yuki Kishimoto) - nostr: remove
tags
arg fromEventBuilder::new
(Yuki Kishimoto) - nostr: remove
tags
arg fromEventBuilder::text_note
(Yuki Kishimoto) - nostr: remove
tags
arg fromEventBuilder::long_form_text_note
(Yuki Kishimoto) - nostr: remove
tags
arg fromEventBuilder::job_request
(Yuki Kishimoto) - nostr: disable all default features except
std
(Yuki Kishimoto) - nostr: change
Timestamp::to_human_datetime
fingerprint (Yuki Kishimoto) - pool: switch from async to sync message sending for
Relay
(Yuki Kishimoto) - sdk: disable all default features (Yuki Kishimoto)
- sdk: set
Client::from_builder
as private (Yuki Kishimoto) - ffi: convert
NostrSigner
trait to an object (Yuki Kishimoto)
Changed
- nostr: rewrite
e
tag de/serialization (Yuki Kishimoto) - pool: rework latency tracking (Yuki Kishimoto)
- pool: increase negentropy batch size down to 100 (Yuki Kishimoto)
- pool: increase ping interval to 55 secs (Yuki Kishimoto)
- pool: increase max retry interval to 10 min (Yuki Kishimoto)
- pool: update retry interval calculation (Yuki Kishimoto)
- pool: try fetch relay information document only once every hour (Yuki Kishimoto)
- pool: not allow to add relays after
RelayPool
shutdown (Yuki Kishimoto) - pool: rename
RelayOptions::retry_sec
toRelayOptions::retry_interval
(Yuki Kishimoto) - pool: rename
RelayOptions::adjust_retry_sec
toRelayOptions::adjust_retry_interval
(Yuki Kishimoto) - pool: request NIP11 document only after a successful WebSocket connection (Yuki Kishimoto)
- pool: immediately terminate relay connection on
Relay::disconnect
call (Yuki Kishimoto) - pool: return error if relay doesn't exist when removing it (Yuki Kishimoto)
- sdk: cleanup
Client
methods (Yuki Kishimoto) - relay-builder: port selection by using random port generation (Yuki Kishimoto)
- lmdb: optimize vector initialization in unit tests (Xiao Yu)
- lmdb: commit also read txn and force sync after delete operations (Yuki Kishimoto)
- nwc: increase default timeout to 60 secs (Yuki Kishimoto)
- book: convert JS snippets to TypeScript (Yuki Kishimoto)
- book: use
pyright
to check python snippets (Yuki Kishimoto)
Added
- nostr: add NIP104 tag and event kinds (erskingardner)
- nostr: add
SingleLetterTag::as_str
andTagKind::as_str
(Yuki Kishimoto) - nostr: add
Kind::Comment
(reyamir) - nostr: add
EventBuilder::comment
(reyamir) - nostr: add uppercase field to
TagStandard::Coordinate
andTagStandard::ExternalIdentity
variants (reyamir) - nostr: add
TagStandard::Quote
(reyamir) - nostr: add
Event::coordinate
(Yuki Kishimoto) - nostr: add
A/a
tags inEventBuilder::comment
(NIP22) events (Yuki Kishimoto) - nostr: add NIP73 support (Yuki Kishimoto)
- nostr: add
NostrSigner::backend
(Yuki Kishimoto) - nostr: add
EventBuilder::private_msg
(Yuki Kishimoto) - nostr: add
EventBuilder::tag
andEventBuilder::tags
(Yuki Kishimoto) - pool: add relay reconnection and disconnection unit tests (Yuki Kishimoto)
- sdk: allow to specify relay pool notification channel size in
Options
(Yuki Kishimoto) - relay-builder: add
RelayTestOptions
(Yuki Kishimoto) - connect: add
NostrConnect::non_secure_set_user_public_key
(Yuki Kishimoto) - ffi: add
make_private_msg
func (Yuki Kishimoto) - ffi: add
CustomNostrSigner
trait (Yuki Kishimoto) - book: add some examples (RydalWater and Yuki Kishimoto)
Fixed
- nostr: fix
TagStandard
de/serialization unit tests (Yuki Kishimoto) - nostr: fix NIP90 kind ranges (Janek)
- pool: fix relay can't manually connect if reconnection is disabled (Yuki Kishimoto)
- pool: fix reconnect loop not break if relay is disconnected while calling
Relay::disconnect
(Yuki Kishimoto)
Removed
- nostr: remove
Marker::Custom
variant (Yuki Kishimoto) - pool: remove
Relay::support_negentropy
(Yuki Kishimoto) - pool: remove
Error::NotConnectedStatusChanged
variant (Yuki Kishimoto)
Deprecated
- nostr: deprecate
EventBuilder::add_tags
(Yuki Kishimoto) - pool: deprecate
RelayPoolNotification::RelayStatus
variant (Yuki Kishimoto) - sdk: deprecate
Client::with_opts
(Yuki Kishimoto) - sdk: deprecate
Options::connection_timeout
(Yuki Kishimoto)
v0.36.0
Summary
Many, many improvements to Relay
and RelayPool
performance (reduced atomic operations and switched to async concurrency),
add NostrSigner
trait, better methods and struct names (fetch_events
instead of get_events_of
, sync
instead of reconcile
,
NostrConnect
instead of Nip46Signer
and so on), add LocalRelay
and allow to easily serve it as hidden onion service with the embedded
tor client, allow to keep track of negentropy sync progress, almost halved the weight of JavaScript SDK bindings (from ~6.3MB to ~3.6MB), some fixes and many more!
Note for Python, Kotlin, Swift and JavaScript devs: unfortunately I can't mark things as deprecated in bindings, so this release have many breaking changes, sorry :(
Note for devs who are using nostr-protocol
(Python), org.rust-nostr:nostr
(Kotlin), nostr-swift
(Swift) or @rust-nostr/nostr
(JavaScript) libraries: these packages are now deprecated. Only the nostr-sdk
library will be released, which include everything that was in the nostr
library.
Changed
- Bump toolchain channel to
1.82.0
- Convert
nostr-signer
crate tonostr-connect
(Yuki Kishimoto) - nostr: move
TagsIndexes
intoTags
struct (Yuki Kishimoto) - nostr: use
OnceCell
implementation fromstd
lib instead ofonce_cell
(Yuki Kishimoto) - nostr: remove redundant public key from repost events (Yuki Kishimoto)
- nostr: change
impl Ord for Event
behaviour (descending order instead of ascending) (Yuki Kishimoto) - nostr: change
TagStandard::Relays
variant value fromVec<UncheckedUrl>
toVec<Url>
(Yuki Kishimoto) - nostr: reserve capacity for tags when POW is enabled in
EventBuilder
(Yuki Kishimoto) - nostr: add
sign
,sign_with_keys
,sign_with_ctx
,build
andbuild_with_supplier
methods toEventBuilder
(Yuki Kishimoto) - nostr: deprecate
to_event
,to_event_with_ctx
andto_unsigned_event
methods (Yuki Kishimoto) - relay-builder: refactor
Session::check_rate_limit
method (Yuki Kishimoto) - relay-builder: return error if event was deleted (Yuki Kishimoto)
- pool: changes in
RelayPool::remove_relay
behavior (Yuki Kishimoto) - pool: allow multi-filter reconciliation (Yuki Kishimoto)
- pool: increase negentropy frame size limit to
60_000
(Yuki Kishimoto) - pool: set default max relay message size to 5MB (Yuki Kishimoto)
- pool: return error when receive
RelayNotification::Shutdown
variant (Yuki Kishimoto) - pool: rename
NegentropyOptions
andNegentropyDirection
toSyncOptions
andSyncDirection
(Yuki Kishimoto) - pool: join futures instead of spawning threads in
RelayPool
methods (Yuki Kishimoto) - pool: reduce overhead by maintaining only one atomic reference count for
RelayConnectionStats
andRelayFiltering
structs (Yuki Kishimoto) - pool: switch to atomic operations for
RelayStatus
(Yuki Kishimoto) - pool: replace
RwLock
withOnceCell
forexternal_notification_sender
(Yuki Kishimoto) - pool: convert
InternalRelay::send_notification
and linked methods to sync (Yuki Kishimoto) - pool: avoid
RelayNotification
cloning when not needed inInternalRelay::send_notification
(Yuki Kishimoto) - pool: avoid full
InnerRelay
clone when requesting NIP11 document (Yuki Kishimoto) - pool: rework relay connection methods and auto-connection logic (Yuki Kishimoto)
- pool: increase
MAX_ADJ_RETRY_SEC
to 120 secs (Yuki Kishimoto) - pool: return reference instead of cloned structs for some getter methods of
Relay
andRelayPool
(Yuki Kishimoto) - pool: removed unnecessary timeout during the shutdown notification process (Yuki Kishimoto)
- pool: deprecate
RelaySendOptions::skip_disconnected
(Yuki Kishimoto) - pool: deprecate
RelayConnectionStats::uptime
(Yuki Kishimoto) - pool: better error for health check if relay status is
Initialized
(Yuki Kishimoto) - pool: connect in chunks if too many relays (Yuki Kishimoto)
- pool: dynamic channel size for streaming of events (Yuki Kishimoto)
- pool: allow to define a limit of relays allowed in
RelayPool
(Yuki Kishimoto) - pool: refactor
Relay::batch_event
andRelay::auth
(Yuki Kishimoto) - pool: deprecate
RelaySendOptions
(Yuki Kishimoto) - sdk: deprecate
Client::get_events_of
andClient::get_events_from
methods (Yuki Kishimoto) - sdk: use
Events
instead ofVec<Event>
in fetch and query methods (Yuki Kishimoto) - sdk: rename
stream_events_of
tostream_events
(Yuki Kishimoto) - sdk: deprecate
Client::reconcile
andClient::reconcile_with
(Yuki Kishimoto) - sdk: use by default tor for onion relays if
tor
feature is enabled on non-mobile targets (Yuki Kishimoto) - sdk: return reference to
RelayPool
instead of clone inClient:pool
(Yuki Kishimoto) - sdk: immediately return error if gossip filters are empty (Yuki Kishimoto)
- signer: auto enable
nip44
feature ifnip59
is enabled (Yuki Kishimoto) - connect: rename
Nip46Signer
toNostrConnect
(Yuki Kishimoto) - database: improve
BTreeCappedSet
(Yuki Kishimoto) - database: not save invalid event deletion (Yuki Kishimoto)
- lmdb: not save event deletion (Yuki Kishimoto)
- lmdb: return iterator instead of vector in
Lmdb::single_filter_query
(Yuki Kishimoto) - lmdb: mark event as deleted only if database have the target event (Yuki Kishimoto)
- signer: bootstrap NIP46 signer on demand (Yuki Kishimoto)
- bindings(nostr): adj.
tag
module (Yuki Kishimoto) - ffi: merge
nostr-ffi
innostr-sdk-ffi
(Yuki Kishimoto) - js: merge
nostr-js
intonostr-sdk-js
(Yuki Kishimoto) - js: change
opt-level
toz
(Yuki Kishimoto)
Added
- nostr: add
TagKind::Client
variant (Yuki Kishimoto) - nostr: add some shorthand constructors for
TagKind::SingleLetter
(Yuki Kishimoto) - nostr: add
Tags
struct (Yuki Kishimoto) - nostr: add
d
tag extraction test fromTags
(Yuki Kishimoto) - nostr: add
TagStandard::GitClone
andTagKind::Clone
variants (Yuki Kishimoto) - nostr: add
TagStandard::GitCommit
andTagKind::Commit
variants (Yuki Kishimoto) - nostr: add
TagStandard::GitEarliestUniqueCommitId
variant (Yuki Kishimoto) - nostr: add
TagStandard::GitMaintainers
andTagKind::Maintainers
variants (Yuki Kishimoto) - nostr: add
TagStandard::Web
andTagKind::Web
variants (Yuki Kishimoto) - nostr: add
EventBuilder::git_repository_announcement
(Yuki Kishimoto) - nostr: add
EventBuilder::git_issue
(Yuki Kishimoto) - nostr: add
EventBuilder::git_patch
(Yuki Kishimoto) - nostr: add
Tag::reference
constructor (Yuki Kishimoto) - nostr: add
nip59::make_seal
function (Yuki Kishimoto) - nostr: add
NostrSigner
trait (Yuki Kishimoto) - database: add
Backend::is_persistent
method (Yuki Kishimoto) - database: add
Events
struct (Yuki Kishimoto) - relay-builder: add
LocalRelay
andRelayBuilder
(Yuki Kishimoto) - relay-builder: allow to serve local relay as hidden service (Yuki Kishimoto)
- relay-builder: allow to set number of max connections allowed (Yuki Kishimoto)
- relay-builder: add
RelayBuilderMode
(Yuki Kishimoto) - relay-builder: add min POW difficulty option to
RelayBuilder
(Yuki Kishimoto) - relay-builder: handle ephemeral events (Yuki Kishimoto)
- pool: add
RelayPool::force_remove_relay
method (Yuki Kishimoto) - pool: add
RelayFiltering::overwrite_public_keys
method (Yuki Kishimoto) - pool: add
RelayPool::sync_targeted
(Yuki Kishimoto) - pool: add
Relay::reconcile_multi
(Yuki Kishimoto) - pool: negentropy sync progress (Yuki Kishimoto)
- pool: add
RelayConnectionStats::success_rate
(Yuki Kishimoto) - sdk: add
Client::fetch_events
andClient::fetch_events_from
methods (Yuki Kishimoto) - sdk: add
Client::sync
andClient::sync_with
methods (Yuki Kishimoto) - sdk: add gossip support to
Client::sync
(Yuki Kishimoto) - sdk: add
Client::force_remove_all_relays
(Yuki Kishimoto) - sdk: add
Client::reset
andswitch-account
example (Yuki Kishimoto) - signer: add
NostrSigner::gift_wrap
(Yuki Kishimoto) - zapper: add
WebLNZapper
struct (moved fromnostr-webln
crate) (Yuki Kishimoto) - ffi(nostr): add
tag_kind_to_string
func (Yuki Kishimoto) - ffi(nostr): add
Tag::kind_str
method (Yuki Kishimoto) - ffi(nostr): impl
Display
forKind
(Yuki Kishimoto) - js(nostr): add
JsKind::_to_string
method (Yuki Kishimoto) - js(nostr): expose
from_nostr_uri
andto_nostr_uri
forPublicKey
andEventId
(Yuki Kishimoto) - cli: show negentropy sync progress (Yuki Kishimoto)
- book: add some examples (RydalWater)
- book: add NIP17 example (rodant)
Fixed
- nostr: adj.
NostrConnectURI
de/serialization according to NIP46 (Yuki Kishimoto) - connect: fix
NostrConnect
according to NIP46 - lmdb: add missing commit method call in
Store::delete
(Yuki Kishimoto) - lmdb: fix unit tests (Yuki Kishimoto)
- lmdb: fix
Store::save_event
issues (Yuki Kishimoto) - sdk: fix
filters empty
error when gossip option is enabled (Yuki Kishimoto)
Removed
- Remove deprecated (Yuki Kishimoto)
- pool: remove
RelayPool::reconcile_advanced
(Yuki Kishimoto) - pool: remove
RelayPool::reconcile_with_items
(Yuki Kishimoto) - webln: remove
nostr-webln
crate (Yuki Kishimoto) - sqlite: remove
nostr-sqlite
crate (Yuki Kishimoto)
v0.35.0
Summary
Add gossip model support, deprecate SQLite
database in favor of LMDB
(fork of pocket database),
add support to negentropy v1 (old version is still supported!), add MockRelay
(a local disposable relay for tests),
allow usage of embedded tor client on mobile devices, many improvements, bugs fix and more!
Changed
- nostr: bump
bitcoin
tov0.32
(Yuki Kishimoto) - nostr: bump
base64
tov0.22
(Yuki Kishimoto) - nostr: deprecate
Event::from_value
(Yuki Kishimoto) - nostr: deprecate
Tag::as_vec
(Yuki Kishimoto) - nostr: re-write
RawRelayMessage
parsing (Yuki Kishimoto) - nostr: update
Event
fields (Yuki Kishimoto) - nostr: deprecate
Event::is_*
kind related methods (Yuki Kishimoto) - nostr: change
TryIntoUrl::Err
toInfallible
forUrl
(Yuki Kishimoto) - nostr: change
Event::verify_id
andEvent::verify_signature
fingerprint (Yuki Kishimoto) - nostr: impl custom
Debug
,PartialEq
andEq
forKeys
(Yuki Kishimoto) - nostr: impl
PartialOrd
,Ord
andHash
forKeys
(Yuki Kishimoto) - nostr: change
Keys::secret_key
andKeys::sign_schnorr
methods fingerprint (Yuki Kishimoto) - nostr: deprecate
Keys::generate_without_keypair
(Yuki Kishimoto) - nostr: change NIP26 functions fingerprint (Yuki Kishimoto)
- nostr: improve
NostrWalletConnectURI
parsing (Yuki Kishimoto) - nostr: update
EventBuilder::job_feedback
method fingerprint (Yuki Kishimoto) - nostr: deprecate
EventBuilder::to_pow_event
(Yuki Kishimoto) - nostr: impl
Display
forMachineReadablePrefix
(Yuki Kishimoto) - nostr: improve
Keys
docs (Yuki Kishimoto) - nostr: change visibility of
public_key
field inKeys
struct (Yuki Kishimoto) - nostr: deprecate
Keys::public_key_ref
(Yuki Kishimoto) - nostr: use
OsRng
instead ofThreadRng
forSECP256K1
global context and schnorr signing (Yuki Kishimoto) - nostr: improve
Timestamp::to_human_datetime
performance (Yuki Kishimoto) - nostr: deprecate
EventId::owned
(Yuki Kishimoto) - nostr: convert
EventId::all_zeroes
to const function (Yuki Kishimoto) - nostr: convert
Timestamp::from_secs
to const function (Yuki Kishimoto) - nostr: deprecate
Kind::as_u32
andKind::as_u64
(Yuki Kishimoto) - database: update
NostrDatabase
supertraits (Yuki Kishimoto) - database: impl
Clone
forMemoryDatabase
(Yuki Kishimoto) - database: update
NostrDatabase::event_by_id
fingerprint (Yuki Kishimoto) - relay-builder: bump
tokio-tungstenite
tov0.24
(Yuki Kishimoto) - pool: bump
async-wsocket
tov0.8
(Yuki Kishimoto) - pool: avoid unnecessary
Url
andRelay
clone inRelayPool
methods (Yuki Kishimoto) - pool: avoid
Relay
clone inRelayPool::connect_relay
method (Yuki Kishimoto) - pool:
RelayPool::send_event
andRelayPool::batch_event
send only to relays withWRITE
flag (Yuki Kishimoto) - pool:
RelayPool::subscribe_with_id
,RelayPool::get_events_of
andRelayPool::stream_events_of
REQ events only to relays withREAD
flag (Yuki Kishimoto) - pool: bump
async-wsocket
tov0.9
(Yuki Kishimoto) - pool: improve
Relay::support_negentropy
method (Yuki Kishimoto) - pool: change handle relay message log level from
error
towarn
(Yuki Kishimoto) - signer: update NIP04 and NIP44 methods signature (Yuki Kishimoto)
- webln: bump
webln
tov0.3
(Yuki Kishimoto) - sqlite: deprecate
SQLiteDatabase
in favor of LMDB (Yuki Kishimoto) - sdk: bump
lnurl-pay
tov0.6
(Yuki Kishimoto) - sdk: update
Client::gift_wrap
andClient::gift_wrap_to
methods signature (Yuki Kishimoto) - sdk: document and rename
Client::metadata
toClient::fetch_metadata
(Janek) - sdk: update
Client::shutdown
method fingerprint (Yuki Kishimoto) - sdk: deprecate
Client::add_relay_with_opts
andClient::add_relays
(Yuki Kishimoto) - sdk: deprecate
RelayPool::send_msg
andRelayPool::batch_msg
(Yuki Kishimoto) - sdk: inherit pool subscriptions only when calling
Client::add_relay
orClient::add_read_relay
methods (Yuki Kishimoto) - ffi(nostr): impl
Display
forCoordinate
(Yuki Kishimoto) - ffi(sdk): change
Connection::embedded_tor
fingerprint forandroid
andios
targets (Yuki Kishimoto) - cli: rename
open
command toshell
(Yuki Kishimoto) - cli: rename
serve-signer
command tobunker
(Yuki Kishimoto)
Added
- nostr: impl
TryFrom<Vec<Tag>>
forLiveEvent
(w3irdrobot) - nostr: add
Tag::as_slice
(Yuki Kishimoto) - nostr: add
NostrWalletConnectURI::parse
(Yuki Kishimoto) - nostr: add
JobFeedbackData
struct (Yuki Kishimoto) - nostr: add
EventBuilder::pow
method (Yuki Kishimoto) - nostr: add
TagKind::custom
constructor (Yuki Kishimoto) - nostr: add
Timestamp::from_secs
(Yuki Kishimoto) - nostr: add
EventId::from_byte_array
(Yuki Kishimoto) - nostr: add
Timestamp::min
andTimestamp::max
(Yuki Kishimoto) - nostr: add
nip65::extract_owned_relay_list
(Yuki Kishimoto) - nostr: add
Kind::from_u16
(Yuki Kishimoto) - database: add
DatabaseHelper::fast_query
(Yuki Kishimoto) - database: add
NostrDatabase::check_id
(Yuki Kishimoto) - database: add
NostrDatabaseExt::relay_lists
(Yuki Kishimoto) - lmdb: add LMDB storage backend (Yuki Kishimoto)
- relay-builder: add
MockRelay
(Yuki Kishimoto) - pool: add
RelayPool::disconnect_relay
method (Yuki Kishimoto) - pool: add
RelayPool::relays_with_flag
andRelayPool::all_relays
(Yuki Kishimoto) - pool: add support to negentropy v1 (Yuki Kishimoto)
- pool: add whitelist support (Yuki Kishimoto)
- sdk: add
Client::add_discovery_relay
(Yuki Kishimoto) - sdk: add
Client::add_read_relay
andClient::add_write_relay
(Yuki Kishimoto) - sdk: add
Client::stream_events_targeted
(Yuki Kishimoto) - sdk: add
Client::subscribe_targeted
(Yuki Kishimoto) - sdk: add gossip support to
Client::send_event
(Yuki Kishimoto) - sdk: add gossip support to
Client::get_events_of
andClient::stream_events_of
(Yuki Kishimoto) - sdk: add gossip support to
Client::subscribe
andClient::subscribe_with_id
(Yuki Kishimoto) - bindings(nostr): expose
as_pretty_json
for some structs (Yuki Kishimoto) - bindings(sdk): expose
Client::fetch_metadata
(Yuki Kishimoto) - bindings(sdk): expose
Client::pool
method (Yuki Kishimoto) - ffi(nostr): expose
Kind::is_*
methods (Yuki Kishimoto) - ffi(sdk): expose
MockRelay
(Yuki Kishimoto) - js(nostr): add
Kind
object (Yuki Kishimoto) - js(nostr): expose
getNip05Profile
function (Yuki Kishimoto) - js(nostr): expose missing methods to
JsCoordinate
(Yuki Kishimoto) - js(sdk): expose
RelayPool::relays
(Yuki Kishimoto) - cli: add
serve
command (Yuki Kishimoto) - cli: add shell history (Yuki Kishimoto)
- book: add some examples (RydalWater)
Fixed
- nostr: fix
TagStanderd::to_vec
(nanikamado) - nostr: fix broken intra doc links (Yuki Kishimoto)
- nostr: fix
JsonUtil::try_as_pretty_json
method (Yuki Kishimoto) - nostr: fix
Kind::is_regular
method (Yuki Kishimoto)
Removed
- Drop support for
rocksdb
(Yuki Kishimoto) - nostr: remove
bech32
from the public API (Yuki Kishimoto) - nostr: remove
Keys::from_public_key
(Yuki Kishimoto) - nostr: remove
tracing
dep (Yuki Kishimoto) - nostr: remove impl
fmt::Display
forSecretKey
(Yuki Kishimoto) - database: remove
has_event_already_been_saved
,has_event_already_been_seen
andhas_event_id_been_deleted
methods fromNostrDatabase
(Yuki Kishimoto) - database: remove
Err
fromNostrDatabase
(Yuki Kishimoto) - database: remove
NostrDatabase::bulk_import
(Yuki Kishimoto) - database: remove
DatabaseError::NotFound
variant (Yuki Kishimoto) - database: remove
DatabaseError::Nostr
variant (Yuki Kishimoto) - database: remove
Order
enum (Yuki Kishimoto) - database: remove
order
arg fromNostrDatabase::query
(Yuki Kishimoto) - pool: remove high latency log (Yuki Kishimoto)
- pool: remove
Error::OneShotRecvError
variant (Yuki Kishimoto) - zapper: remove
Err
fromNostrZapper
and unnecessary variants fromZapperError
(Yuki Kishimoto) - js(nostr): remove
Keys::vanity
(Yuki Kishimoto) - cli: remove
reverse
flag fromquery
command (Yuki Kishimoto)
v0.34.0
Summary
Add embedded tor client support, allow to open databases with a limited capacity (automatically discard old events when max capacity is reached),
add Client::stream_events_of
as alternative method to Client::get_events_of
(stream events instead of waiting for EOSE
and collect into a list),
add search capability (NIP50) support to Filter::match_event
and databases, add NIP31 and NIP70 support,
add option to autoconnect relay on Client::add_relay
method call (currently disabled by default), rework the get_events_of
methods behaviour for
better consistency (RelayPool::get_events_of
and Relay::get_events_of
get events only from remote relay/s while
Client::get_events_of
allow to choose the source of events: database
, relays
or both
), bugs fix and more!
Changed
- Bump MSRV to v1.70.0 (Yuki Kishimoto)
- Bump toolchain channel to
1.80.1
(Yuki Kishimoto) - nostr: deprecate
Event::author_ref
andEvent::iter_tags
(Yuki Kishimoto) - nostr: calculate
EventId
inEventBuilder::to_unsigned_event_with_supplier
(Yuki Kishimoto) - nostr: ensure that NIP59 rumor has
EventId
(Yuki Kishimoto) - nostr: update
PartialEvent
methods (Yuki Kishimoto) - nostr: change
EventBuilder::award_badge
fingerprint (Yuki Kishimoto) - nostr: add NIP50 support to
Filter::match_event
method (Yuki Kishimoto) - nostr: remove
Arc<T>
fromOnceCell<T>
inEvent
andTag
(Yuki Kishimoto) - nostr: move
sig
field fromPartialEvent
toMissingPartialEvent
(Yuki Kishimoto) - nostr: better
Debug
trait impl forEventId
,PublicKey
andTag
(Yuki Kishimoto) - nostr: improve
SubscriptionId::generate_with_rng
(Yuki Kishimoto) - pool: take mutex ownership instead of clone in
InternalRelayPool::get_events_from
(Yuki Kishimoto) - pool: remove IDs collection from
InternalRelayPool::get_events_from
(Yuki Kishimoto) - pool: better checks before perform queries or send messages to relays (Yuki Kishimoto)
- pool: bump
async-wsocket
tov0.7
(Yuki Kishimoto) - pool: get events only from remote relay when calling
get_events_of
orget_events_from
(Yuki Kishimoto) - database: avoid to copy
EventId
inEvent::decode
(Yuki Kishimoto) - database: use
Vec
instead ofBTreeSet
as inner value forTagIndexValues
(Yuki Kishimoto) - database: rework
DatabaseIndexes
and rename toDatabaseHelper
(Yuki Kishimoto) - database: allow to set max capacity to
DatabaseHelper
(Yuki Kishimoto) - database: speedup helper bulk load (Yuki Kishimoto)
- database: set a default logic for
NostrDatabase::negentropy_items
(Yuki Kishimoto) - sdk: rename
Proxy
andProxyTarget
toConnection
andConnectionTarget
(Yuki Kishimoto) - sdk: allow to skip slow relays (Yuki Kishimoto)
- sdk: allow to specify the source of events for
Client::get_events_of
method (Yuki Kishimoto) - sdk: deprecate
Client::get_events_of_with_opts
(Yuki Kishimoto) - sqlite: use
ValueRef
instead of owned one (Yuki Kishimoto) - cli: improve
sync
command (Yuki Kishimoto) - cli: allow to specify relays in
open
command (Yuki Kishimoto)
Added
- nostr: add NIP31 support (Yuki Kishimoto)
- nostr: add NIP70 support (Yuki Kishimoto)
- nostr: add
EventId::LEN
const (Yuki Kishimoto) - nostr: add
UnsignedEvent::ensure_id
method (Yuki Kishimoto) - nostr: add missing
payload
arg toEventBuilder::job_result
(Yuki Kishimoto) - nostr: add
ConversationKey::new
(Yuki Kishimoto) - nostr: add
Request::multi_pay_invoice
constructor (Yuki Kishimoto) - nostr: add
Jsonutil::as_pretty_json
andJsonUtil::try_as_pretty_json
methods (Yuki Kishimoto) - nostr: add
Coordinate::has_identifier
(Yuki Kishimoto) - pool: add
RelayPoolNotification::Authenticated
variant (Yuki Kishimoto) - pool: add
RelayPool::save_subscription
(Yuki Kishimoto) - sqlite/rocksdb/indexeddb: allow to open database with limited capacity (Yuki Kishimoto)
- sdk: add
Client::gift_wrap_to
andClient::send_private_msg_to
(reyamir) - sdk: add option to autoconnect relay on
Client::add_relay
method call (Yuki Kishimoto) - sdk: add support to embedded tor client (Yuki Kishimoto)
- sdk: add
Options::max_avg_latency
(Yuki Kishimoto) - sdk: add
Client::stream_events_of
andClient::stream_events_from
methods (Yuki Kishimoto) - ffi(nostr): add
EventBuilder::seal
constructor (Yuki Kishimoto) - cli: add
generate
command (Yuki Kishimoto) - cli: add
json
flag toquery
command (Yuki Kishimoto) - book: add some python examples (RydalWater)
Fixed
- pool: fix
Event
notification variant sent also for events sent by the SDK (Yuki Kishimoto) - database: fix indexes
QueryPattern
(Yuki Kishimoto) - database: fix query issue due to wrong tag value order (Yuki Kishimoto)
Removed
- Remove deprecated methods/functions (Yuki Kishimoto)
- nostr: remove support for
nrelay
NIP19 entity (Yuki Kishimoto) - nostr: remove support for NIP44 v1 (Yuki Kishimoto)
- nostr: remove
EventBuilder::encrypted_direct_msg
(Yuki Kishimoto) - database: remove
TempEvent
(Yuki Kishimoto) - database: remove
NostrDatabase::event_ids_by_filters
(Yuki Kishimoto) - sdk: remove
Client::send_direct_msg
(Yuki Kishimoto) - cli: remove
tracing-subscriber
dep (Yuki Kishimoto)
v0.33.0
Summary
Better outputs for send/batch/reconcile methods (ex. you can now easily know where a message/event is successfully published and where/why failed), allow to change NIP42 option after client initialization, increase max stack size for JS bindings to prevent "memory access out of bounds" error, expose more objects/methods for JS bindings, dry run option for negentropy reconciliation, get NIP46 relay from NIP05 profile, bug fixes (NIP-42 auth not works correctly, NIP-46 "ACK" message not handled, ...) and more!
Changed
- Bump
uniffi
tov0.28.0
(Yuki Kishimoto) - nostr: rename NIP-51
EventBuilder
set constructors andKind
variants (Yuki Kishimoto) - nostr: small adj. to NIP-47
ListTransactionsRequestParams
andLookupInvoiceResponseResult
structs (Yuki Kishimoto) - nostr: add
identifier
arg to NIP-51EventBuilder
set constructors (Yuki Kishimoto) - nostr: change
nip65::extract_relay_list
fingerprint (Yuki Kishimoto) - nostr: avoid allocation where possible in NIP-05 module (Yuki Kishimoto)
- nostr: get NIP-46 relays from NIP-05 address (DanConwayDev)
- nostr: deprecate
EventBuilder::encrypted_direct_msg
(Yuki Kishimoto) - pool: use per-purpose dedicated relay channels (Yuki Kishimoto)
- pool: return relay urls to which
messages
/events
have or not been sent forsend_*
andbatch_*
methods (Yuki Kishimoto) - pool: return relay urls to which
subscription
have or not been success forsubscribe*
methods (Yuki Kishimoto) - pool: rename
Relay::terminate
toRelay::disconnect
(Yuki Kishimoto) - pool: always send
RelayPoolNotification::Message
variant (Yuki Kishimoto) - pool: return report for negentropy reconciliation (Yuki Kishimoto)
- signer: use
limit(0)
instead ofsince
forNip46Signer
subscription filter (Yuki Kishimoto) - signer: deprecate
NostrConnectRemoteSigner::nostr_connect_uri
andNip46Signer::nostr_connect_uri
(Yuki Kishimoto) - sdk: allow to change auto authentication to relays option (NIP-42) after client initialization (Yuki Kishimoto)
- sdk: retrieve contact list public keys only from the latest events (Xiao Yu)
- sdk: re-subscribe closed subscriptions after NIP-42 authentication (Yuki Kishimoto)
- bindings(nostr): allow to specify coordinates in
EventBuilder::delete
constructor (Yuki Kishimoto) - ffi(sdk): convert
RelayPool::handle_notifications
method to async/future (Yuki Kishimoto) - js: increase max stack size to
0x1E84800
bytes (32 MiB) (Yuki Kishimoto) - js(nostr): adj. method names to camelcase format (Yuki Kishimoto)
Added
- nostr: add
EventBuilder::interest_set
(Yuki Kishimoto) - nostr: add
title
,image
anddescription
constructors toTag
(Yuki Kishimoto) - nostr: add
Timestamp::zero
andTimestamp::is_zero
methods (Yuki Kishimoto) - nostr: add
Nip05Profile
struct (Yuki Kishimoto) - nostr: add
nip05::profile
function (Yuki Kishimoto) - nostr: add
LEN
const toPublicKey
,SecretKey
andEncryptedSecretKey
(Yuki Kishimoto) - nostr: add
Report::Malware
variant (Daniel Cadenas) - nostr: add
coordinate
methods toFilter
struct (DanConwayDev) - nostr: add NIP-34 kinds (DanConwayDev)
- nostr: add
MachineReadablePrefix
enum (Yuki Kishimoto) - nostr: add
ClientMessage::is_auth
(Yuki Kishimoto) - pool: add
Output<T>
struct (Yuki Kishimoto) - pool: add
Output<EventId>::id
andOutput<SubscriptionId>::id
methods (Yuki Kishimoto) - pool: add dry run option for negentropy reconciliation (Yuki Kishimoto)
- signer: add
NostrSigner::unwrap_gift_wrap
method (Yuki Kishimoto) - signer: add
bunker_uri
method to NIP-46 client and signer (Yuki Kishimoto) - sdk: add
Client::unwrap_gift_wrap
method (Yuki Kishimoto) - js(nostr): complete
JsFilter
struct (Yuki Kishimoto) - js(sdk): partially expose
JsRelayPool
(Yuki Kishimoto) - book: add some python examples (RydalWater)
Fixed
- nostr: fix NIP-47
list_transactions
response deserialization (Yuki Kishimoto and lnbc1QWFyb24) - pool: fix shutdown notification sent to external channel on
Relay::terminate
method call (Yuki Kishimoto) - pool: fix
RelayPool::reconcile_advanced
method uses database items instead of the passed ones (Yuki Kishimoto) - signer: add missing NIP-46 connect "ACK" message handling (Yuki Kishimoto)
- sdk: fix NIP-42 client authentication (Yuki Kishimoto)
- js: fix "RuntimeError: memory access out of bounds" WASM error (Yuki Kishimoto)
Removed
- pool: remove
RelayPoolNotification::Stop
(Yuki Kishimoto) - pool: remove
RelayStatus::Stop
(Yuki Kishimoto) - Remove all
start
andstop
methods (Yuki Kishimoto)
v0.32.0
Summary
Added async
/future
support to Python, Kotlin and Swift, added automatic authentication to relays (NIP-42, can be deactivated in client options),
improvements to relay limits, many bug fixes (relays not auto reconnect, wrong query order for SQLite,
tokio panic when using SQLite database in bindings) and more!
Note for kotlin devs: from this release the packages will be published at org.rust-nostr
instead of io.github.rust-nostr
.
Changed
- Bump
atomic-destructor
tov0.2
(Yuki Kishimoto) - Bump
uniffi
tov0.27.2
(Yuki Kishimoto) - nostr: ignore malformed public keys during NIP19 event (
nevent
) parsing (Yuki Kishimoto) - nostr: update
Event::pubic_keys
andEvent_event_ids
methods (Yuki Kishimoto) - nostr: adj. NIP-10 support (Yuki Kishimoto)
- nostr: change fingerprint of
nip05::verify
(Yuki Kishimoto) - nostr: rework
TagStandard::parse
(Yuki Kishimoto) - nostr: add
a
tag to zap receipts (benthecarman) - nostr: change NIP-07
Error::Wasm
variant value fromJsValue
toString
(Yuki Kishimoto) - nostr: update
EventBuilder::live_event_msg
fingerprint (Yuki Kishimoto) - nostr: set
kind
arg inEventBuilder::reaction_extended
as optional (Yuki Kishimoto) - pool: increase default kind 3 event limit to
840000
bytes and10000
tags (Yuki Kishimoto) - pool: improve accuracy of latency calculation (Yuki Kishimoto)
- pool: refactoring and adj.
relay
internal module (Yuki Kishimoto) - pool: log when websocket messages are successfully sent (Yuki Kishimoto)
- pool: always close the WebSocket when receiver loop is terminated (Yuki Kishimoto)
- pool: use timeout for WebSocket message sender (Yuki Kishimoto)
- pool: bump
async-wsocket
tov0.5
(Yuki Kishimoto) - sdk: send NIP-42 event only to target relay (Yuki Kishimoto)
- sqlite: bump
rusqlite
tov0.31
(Yuki Kishimoto) - nwc: change
NWC::new
andNWC::with_opts
fingerprint (Yuki Kishimoto) - ffi: migrate kotlin packages to
org.rust-nostr
(Yuki Kishimoto) - bindings(sdk): log git hash after logger initialization (Yuki Kishimoto)
- ffi(nostr): set default args values where possible (Yuki Kishimoto)
- ffi(nostr): convert
verify_nip05
andget_nip05_profile
to async functions (Yuki Kishimoto) - ffi(nostr): convert
RelayInformationDocument::get
to async (Yuki Kishimoto) - ffi(nostr): merge
Keys::from_mnemonic_*
constructors intoKeys::from_menmonic
(Yuki Kishimoto) - ffi(sdk): add
async/future
support (convert from blocking to async) (Yuki Kishimoto) - ffi(sdk): no longer spawn a thread when calling
handle_notifications
(Yuki Kishimoto) - js(sdk): change
JsNostrZapper::nwc
fingerprint (Yuki Kishimoto) - js(sdk): rename
JsNip46Signer::new
toJsNip46Signer::init
(Yuki Kishimoto) - ci: build python wheels for
manylinux_2_28_x86_64
(Yuki Kishimoto)
Added
- nostr: add
Tag::is_root
method (Xiao Yu) - nostr: add
JsonUtil::try_as_json
method (Yuki Kishimoto) - nostr: add
public_key
field toTagStandard::Event
(Yuki Kishimoto) - nostr: add support to
nrelay
NIP-19 entity (Yuki Kishimoto) - nostr: add
Event::get_tag_content
method (Yuki Kishimoto) - nostr: add
Event::get_tags_content
method (Yuki Kishimoto) - nostr: add
Event::hashtags
method (Yuki Kishimoto) - pool: allow to set event limits per kind (Yuki Kishimoto)
- pool: log warn when high latency (Yuki Kishimoto)
- sdk: add support to automatic authentication to relays (NIP-42) (Yuki Kishimoto)
- ffi(nostr): add
Nip46Request
(Yuki Kishimoto) - ffi(sdk): add
NostrConnectRemoteSigner
(Yuki Kishimoto) - js(nostr): add missing NIP-57 functions (Yuki Kishimoto)
- js(nostr): expose missing methods to
JsEvent
(Yuki Kishimoto) - book: add some python examples (RydalWater)
Fixed
- nostr: fix re-serialization of events that contains unknown keys during deserialization (Yuki Kishimoto)
- nostr: fix
Nip21::to_nostr_uri
serialization (Yuki Kishimoto) - pool: fix relay doesn't auto reconnect in certain cases (Yuki Kishimoto)
- nostr: add missing
TagStandard::PublicKeyLiveEvent
variant toEvent::public_keys
(Yuki Kishimoto) - sqlite: fix SQLite database panics when used outside the client context in bindings (Yuki Kishimoto)
- sqlite: fix wrong event order when querying (Yuki Kishimoto)
Removed
- nostr: remove
verify_blocking
andget_profile_blocking
functions (Yuki Kishimoto) - nostr: remove
RelayInformationDocument::get_blocking
(Yuki Kishimoto) - nostr: remove
blocking
feature (Yuki Kishimoto) - sqlite: removed
deadpool-sqlite
dep (Yuki Kishimoto) - ffi(nostr): remove
Keys::from_mnemonic_with_account
andKeys::from_mnemonic_advanced
(Yuki Kishimoto)
v0.31.0
Summary
Reworked Tag
, added TagStandard
enum, simplified the way to subscribe and/or reconcile to subset of relays
(respectively, client.subscribe_to
and client.reconcile_with
), added blacklist support to mute public keys or event IDs,
removed zap split from client.zap
method, many improvements and more!
Changed
- Bump
uniffi
tov0.27.1
(Yuki Kishimoto) - nostr: update fingerprint of NIP26 functions (Yuki Kishimoto)
- nostr: update fingerprint of
EventBuilder::zap_receipt
constructor (Yuki Kishimoto) - nostr: update
EventId::new
fingerprint (Yuki Kishimoto) - nostr: update fingerprint of
nip05::verify
function (Yuki Kishimoto) - nostr: improve performance of
Filter::match_event
(Yuki Kishimoto) - nostr: adj. kind to be
u16
instead ofu64
according to NIP01 (Yuki Kishimoto) - nostr: improve NIP19 serialization performance (Yuki Kishimoto)
- nostr: improve
EventId::from_hex
performance (Yuki Kishimoto) - nostr: rename
Tag
enum toTagStandard
(Yuki Kishimoto) - nostr: adj. NIP17 naming (Yuki Kishimoto)
- nostr: allow to set a
Timestamp
tweak range (Yuki Kishimoto) - nostr: adj. NIP59 timestamp tweak range (Yuki Kishimoto)
- nostr: reorganize
tag
module (Yuki Kishimoto) - nostr: manually impl
fmt::Debug
forPublickey
(Yuki Kishimoto) - database: small improvements to flatbuffers
Event::encode
(Yuki Kishimoto) - ndb: bump
nostrdb
to0.3.3
(Yuki Kishimoto) - rocksdb: bump
rocksdb
to0.22
and set MSRV to1.66.0
(Yuki Kishimoto) - pool: inline
RelayPool
methods (Yuki Kishimoto) - sdk: inline
Client
,ClientBuilder
andOptions
methods (Yuki Kishimoto) - sdk: update
tokio
features (Yuki Kishimoto) - sdk: update visibility of
Options
field (Yuki Kishimoto) - sdk: remove zap split to support
rust-nostr
development fromClient::zap
method (Yuki Kishimoto) - signer: update fingerprint of
NostrConnectRemoteSigner::serve
method (Yuki Kishimoto) - ffi(nostr): set default args for
Nip19Profile
andNip19Event
constructors (Yuki Kishimoto) - ffi(nostr): set default args for
nip05::verify
function (Yuki Kishimoto) - ffi(sdk): set default args for
Client
constructors (Yuki Kishimoto) - js: enable support for Reference Types (Yuki Kishimoto)
- js(nostr): rewrite
JsMetadata
methods and add getters (Yuki Kishimoto)
Added
- nostr: impl TryIntoUrl for &String (Yuki Kishimoto)
- nostr: derive default traits for
HttpData
,LiveEventHost
andLiveEvent
(Yuki Kishimoto) - nostr: expose NIP49
log_n
(DanConwayDev) - nostr: add tags indexes to
Event
(Yuki Kishimoto) - nostr: add
hex::decode_to_slice
(Yuki Kishimoto) - nostr: add
SecretKey::generate
(Yuki Kishimoto) - nostr: add
Tag
struct (Yuki Kishimoto) - nostr: add
EventBuilder::add_tags
method (Yuki Kishimoto) - database: add
author
index (Yuki Kishimoto) - pool: add
RelayPool::start
(Yuki Kishimoto) - pool: add
NegentropyDirection
default (Yuki Kishimoto) - sdk: add
Client::builder()
(Yuki Kishimoto) - sdk: add
Client::update_min_pow_difficulty
method (Yuki Kishimoto) - sdk: add
Client::connect_with_timeout
(Yuki Kishimoto) - sdk: add
Client::reconcile_with
andClient::reconcile_advanced
(Yuki Kishimoto) - sdk: add
Client::subscribe_to
andClient::subscribe_with_id_to
methods (Yuki Kishimoto) - sdk: add initial blacklist support (Yuki Kishimoto)
- sdk: deprecate
Client::send_direct_msg
(Yuki Kishimoto) - ffi(nostr): add
gift_wrap_from_seal
func (Yuki Kishimoto) - js(nostr): add missing methods to
JsContact
(Yuki Kishimoto) - js(nostr): expose
util::generate_shared_key
(Yuki Kishimoto) - js(sdk): expose
Relay::subscribe
andRelay::subscribe_with_id
methods (Yuki Kishimoto) - js(sdk): partially complete
JsRelay
(Yuki Kishimoto) - cli: add
sync
command (Yuki Kishimoto)
Fixed
- nostr: fix NIP19 event (
nevent
) serialization (Yuki Kishimoto)
Removed
- nostr: remove
GenericTagValue
(Yuki Kishimoto) - ffi(nostr): remove
Kind::match*
methods (Yuki Kishimoto)
v0.30.0
Summary
Adapted NIP46 to last changes, added NostrConnectRemoteSigner
to easily build remote signers (just construct it and call serve
method),
improved proxy options (allow to specify the proxy target: all relays or only .onion
ones),
improvements to NWC client, fixed equality operator for bindings (Python, Kotlin and Swift),
added nostrdb
storage backend, added NIP32 and completed NIP51 support and more!
Changed
- Bump
uniffi
tov0.27
(Yuki Kishimoto) - Adapted NIP46 to last changes (Yuki Kishimoto)
- nostr: change
Tag::parse
arg fromVec<S>
to&[S]
(Yuki Kishimoto) - nostr: allow to parse public key from NIP21 uri with
PublicKey::parse
(Yuki Kishimoto) - nostr: allow to parse event ID from NIP21 uri with
EventId::parse
(Yuki Kishimoto) - nostr: construct
GenericTagValue
based onSingleLetterTag
indeserialize_generic_tags
(Yuki Kishimoto) - nostr: set
UnsignedEvent
ID as optional (Yuki Kishimoto) - nostr: update
TryIntoUrl::try_into_url
fingerprint (Yuki Kishimoto) - nostr: bump
bitcoin
to0.31
(Yuki Kishimoto) - sdk: bump
lnurl-pay
to0.4
(Yuki Kishimoto) - sdk: improve
proxy
options (Yuki Kishimoto) - pool: bump
async-wsocket
to0.4
(Yuki Kishimoto) - pool: return error if
urls
arg is empty inInternalRelayPool::get_events_from
(Yuki Kishimoto) - pool: allow to disable
RelayLimits
(Yuki Kishimoto) - signer: re-work
nip46
module (Yuki Kishimoto) - nwc: avoid to open and close subscription for every request (Yuki Kishimoto)
- nwc: allow to customize requests timeout (Yuki Kishimoto)
- js(nostr): consume
JsEventBuilder
when buildingEvent
orUnsignedEvent
(Yuki Kishimoto)
Added
- Add support to
nostrdb
storage backend (Yuki Kishimoto) - nostr: add
Report::Other
variant (Daniel Cadenas) - nostr: add
EventBuilder::reaction_extended
(Yuki Kishimoto) - nostr: add NIP32 support (rustedmoon)
- pool: add
Relay::handle_notifications
(Yuki Kishimoto) - cli: add command to serve
Nostr Connect
signer (Yuki Kishimoto) - ffi(nostr): added
FilterRecord
, to allow to access fields inFilter
(Yuki Kishimoto) - ffi(nostr): add missing NIP51 constructors (rustedmoon)
- ffi(sdk): add
AbortHandle
(Yuki Kishimoto) - ffi(sdk): add
sqlite
andndb
features (Yuki Kishimoto) - js(nostr): add missing NIP51 constructors (rustedmoon)
- js(nostr): add NIP47 request params and response results structs (Yuki Kishimoto)
- js(sdk): add
NWC
client (Yuki Kishimoto) - js(sdk): add
NostrDatabase::save_event
method (Xiao Yu)
Fixed
- nostr: fix
Tag::content
return alwaysNone
whenTag::Generic
(Yuki Kishimoto) - nostr: fix NIP46
Request::from_message
deserialization (Yuki Kishimoto) - nostr: fix
NostrConnectURI
serialization (Yuki Kishimoto) - nostr: fix
LookupInvoiceParams
(benthecarman) - ffi: fix equality operator (
==
) (Yuki Kishimoto) - js(nostr): fix
Keys
method calls in examples (Xiao Yu)
Removed
- Removed deprecated (Yuki Kishimoto)
v0.29.4
- pool: fix
InternalRelay::get_events_of_with_callback
timeout (Yuki Kishimoto)
v0.29.3
- pool: check filter limit in
InternalRelayPool::get_events_from
(Yuki Kishimoto)
v0.29.2
Fixed
- pool: fix
get_events_of
issues (Yuki Kishimoto)
v0.29.1
Fixed
- nostr: fix deserialization issues for events with non-standard
k
andx
tags (Yuki Kishimoto) - pool: fix spurious send_event timeout error (DanConwayDev in https://github.com/rust-nostr/nostr/pull/375)
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.