Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This project follows the rust-nostr organization guidelines: https://github.com/
The commit **must** be formatted as follows:

```
<context>: <short descriptrion>
<context>: <short description>

<description explaining reasons for the changes>
```
Expand All @@ -24,7 +24,7 @@ If applicable, link the `issue`/`PR` to be closed with:
The `context` **must be**:

- `nostr` for changes to the `nostr` crate
- `sdk`, `cli`, `relay-pool`, `connect`, `nwc` and so on for the others crates (remote the `nostr-` prefix)
- `sdk`, `cli`, `relay-pool`, `connect`, `nwc` and so on for the others crates (remove the `nostr-` prefix)
- `test` for changes to the unit tests
- `doc` for changes to the documentation
- `contrib` for changes to the scripts and tools
Expand Down Expand Up @@ -60,3 +60,17 @@ Closes https://<domain>.com/rust-nostr/nostr/issue/2222
Install https://github.com/casey/just and use `just precommit` or `just check`
to format and check the code before committing.
The CI also enforces this.

## Local development workflow

1. Install the workspace toolchain (`rustup show 1.85.0`) and `just`.
2. Run `just check` early—this executes formatting, clippy, doctests, and `cargo check` for every crate.
3. Use `cargo test -p <crate>` while iterating, then `cargo test --workspace` before opening a PR.
4. Keep feature-gated code tested by passing `--all-features` (or at least the features you touched).
5. When documentation changes reference code, build the docs locally with `cargo doc --workspace --no-deps`.

## Documentation contributions

- Edit Markdown in `README.md`, crate-level READMEs under `crates/*/README.md`, or the book at <https://rust-nostr.org>.
- Prefer short runnable examples; keep them in sync with the APIs by compiling them locally (use `cargo test --doc`).
- If you add scripts or tooling, document them under `contrib/README.md` so future contributors can discover them.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,36 @@ Check the example in the [`embedded/`](./crates/nostr/examples/embedded) directo
## Book

Learn more about `rust-nostr` at <https://rust-nostr.org>.
For focused topics that haven’t landed in the public book yet (e.g., NIP-09 deletions), see the notes under [`docs/`](./docs/nip09-deletions.md).

## Getting started

### Prerequisites

- Rust **1.85.0** (automatically enforced through `rust-toolchain.toml`). Install it with `rustup` and make sure the `clippy`, `rustfmt`, and `rust-docs` components are available.
- [`just`](https://github.com/casey/just) for the repo’s helper recipes (optional but strongly encouraged).

### Clone and check

```bash
git clone https://github.com/rust-nostr/nostr.git
cd nostr
just check # fmt + lint + doc + workspace checks
cargo test # run the full workspace test suite
```

Run only the formatting and lint gate the CI expects with `just precommit`, or call any script directly from `contrib/scripts/`.

### Examples and docs

- High-level client examples live in `crates/nostr-sdk/examples/`; run one with `cargo run --example client --package nostr-sdk`.
- Low-level protocol examples live under `crates/nostr/examples/`.
- To browse the API documentation offline, run `cargo doc --workspace --no-deps --open`. This generates the same content that powers <https://docs.rs> and complements the mdBook available at <https://rust-nostr.org>.

## Supported NIPs

The table below lists which NIPs have implementations somewhere in this workspace. Some features are behind crate flags (for example `nostr-sdk --features nip44,nip57`) or only available when using a specific crate (e.g., signer integrations). ✅ means “implemented and tested behind the relevant feature flag”, ❌ means “not available anywhere in this repo yet”.

| Supported | NIP |
|:---------:|-----------------------------------------------------------------------------------------------------------------|
| ✅ | [01 - Basic protocol flow description](https://github.com/nostr-protocol/nips/blob/master/01.md) |
Expand Down
7 changes: 6 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Reporting a Vulnerability

For security vulnerability reporting and our complete security policy, please see: https://github.com/rust-nostr/guidelines
1. **Preferred channel:** Open a private report via GitHub Security Advisories: <https://github.com/rust-nostr/nostr/security/advisories/new>. This keeps the discussion confidential until a fix ships.
2. **Alternative channel:** If GitHub is unavailable for you, send an encrypted message following the instructions in the organization guidelines (<https://github.com/rust-nostr/guidelines>). That document lists the current security PGP keys.
3. **What to include:** affected crate(s) and versions, a minimal proof-of-concept, the impact you observed, and any suggested mitigation ideas. Logs and environment info (`rustc -V`, OS, enabled features) are extremely helpful.
4. **Response targets:** we aim to acknowledge new reports within **5 business days** and keep you posted every time we cross a milestone (triage, fix ready, release, disclosure).

Please do **not** open public issues for security problems. We appreciate coordinated disclosure and will credit you in the release notes unless you ask otherwise.
26 changes: 26 additions & 0 deletions contrib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Contrib utilities

Helper scripts and docs that keep the workspace consistent.

## Scripts

Located under `contrib/scripts/` and wired into the root `justfile`.

| Script | Purpose |
| --- | --- |
| `check-fmt.sh [check]` | Format the entire workspace (default) or verify formatting when called with `check`. |
| `check-crates.sh` | Runs `cargo check`/`clippy` across the workspace with the default feature set. |
| `check-docs.sh` | Ensures `cargo doc` builds for every crate, catching broken intra-doc links. |
| `check-deny.sh` | Executes `cargo deny check` using the repo’s `deny.toml`. |
| `contributors.py` | Generates the CONTRIBUTORS list used for release notes. |

Invoke them directly (`bash contrib/scripts/check-crates.sh`) or via the `just` recipes (`just check`, `just precommit`).

## Release playbooks

`contrib/release/RELEASE_STEPS.md` contains the checklist we follow before publishing crates. Keep it updated whenever the release process changes.

## Funding + verification

- `contrib/fund` stores the assets shown on <https://rust-nostr.org/donate>.
- `contrib/verify-commits` documents the GPG/SSH verification steps used by the maintainers.
34 changes: 34 additions & 0 deletions crates/nostr-keyring/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# Nostr Keyring

Thin wrapper around the system keyring that stores `nostr::Keys` objects without forcing you to handle secret material manually. The crate keeps all serialization in-memory and relies on the OS-provided credential store (macOS Keychain, Windows Credential Manager, Secret Service, etc.).

```rust
use nostr::prelude::*;
use nostr_keyring::NostrKeyring;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let keyring = NostrKeyring::new("my-nostr-app");
let keys = Keys::generate();

keyring.set("default", &keys)?;
let restored = keyring.get("default")?;
assert_eq!(keys.public_key(), restored.public_key());

Ok(())
}
```

Enable the `async` feature to offload OS keyring access to a blocking thread pool when running inside async executors:

```rust,no_run
use nostr::prelude::*;
use nostr_keyring::NostrKeyring;

# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
let keyring = NostrKeyring::new("bot");
let keys = keyring.get_async("default").await?;
println!("Using {}", keys.public_key());
# Ok(()) }
```

## Crate Feature Flags

The following crate feature flags are available:
Expand All @@ -8,6 +40,8 @@ The following crate feature flags are available:
|---------|:-------:|-------------------------------------------|
| `async` | No | Enable async APIs |

Install with `cargo add nostr-keyring --features async` to opt into the Tokio-friendly async helpers.

## Changelog

All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md).
Expand Down
46 changes: 40 additions & 6 deletions crates/nostr-relay-builder/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
# Nostr Relay Builder

## Description

Build your own custom nostr relay!

This library contains all the stuff to easily build a nostr relay.
It also contains a ready-to-use `MockRelay` for unit tests.
`nostr-relay-builder` helps you stand up fully configurable relays (local or hidden-service) without re-implementing policies, storage, or protocol minutiae. The crate exposes two main entry points:

- `LocalRelay` – run a fully fledged relay inside your process.
- `MockRelay` – deterministic relay for unit/integration tests.

## Quick start

```rust,no_run
use std::net::Ipv4Addr;

use nostr::prelude::*;
use nostr_database::MemoryDatabase;
use nostr_relay_builder::{RelayBuilder, LocalRelay, RateLimit};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let relay = LocalRelay::new(
RelayBuilder::default()
.addr(Ipv4Addr::LOCALHOST.into())
.port(7777)
.database(MemoryDatabase::default())
.rate_limit(RateLimit {
max_reqs: 128,
notes_per_minute: 30,
}),
);

relay.run().await?;
println!("relay listening on {}", relay.url().await);

Ok(())
}
```

See the `local` and `mock` modules plus `examples/` for advanced policies such as:

- Enforcing NIP-42 auth via `RelayBuilder::nip42`.
- Only accepting writes from a given pubkey (`RelayBuilderMode::PublicKey`).
- Plugging in your own `NostrDatabase` backend and rate limits.
- Injecting events from tests via `MockRelay::notify_event`.

## Changelog

Expand Down
36 changes: 36 additions & 0 deletions crates/nostr-relay-pool/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
# Nostr Relay Pool

Nostr Relay Pool is the low-level building block used by `nostr-sdk` to manage many relay connections in parallel. Use it when you need fine-grained control over relay policies, admission rules, or when embedding the gossip stack in your own executor.

## Usage

```rust,no_run
use nostr::prelude::*;
use nostr_relay_pool::{RelayOptions, RelayPool, RelayPoolNotification};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let pool = RelayPool::builder().build();

// Add relays with custom options (timeouts, flags, etc.)
pool.add_relay("wss://relay.damus.io", RelayOptions::default()).await?;
pool.add_relay("wss://relay.primal.net", RelayOptions::default()).await?;

// Fire up the background tasks and wait until we are connected
pool.connect().await;
pool.wait_for_connection(std::time::Duration::from_secs(5)).await;

// Listen for broadcast notifications straight from the relays
let mut notifications = pool.notifications();
while let Ok(notification) = notifications.recv().await {
if let RelayPoolNotification::Event { event, .. } = notification {
println!("Got event {} -> {}", event.author(), event.content());
}
}

Ok(())
}
```

See `crates/nostr-relay-pool/examples/` for more involved setups that mix monitors, sync policies, and custom transports.

## Crate Feature Flags

The following crate feature flags are available:
Expand All @@ -8,6 +42,8 @@ The following crate feature flags are available:
|---------|:-------:|-------------------------------------------|
| `tor` | No | Enable support for embedded tor client |

Enable the feature with `cargo add nostr-relay-pool --features tor` (native targets only).

## Changelog

All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md).
Expand Down
30 changes: 30 additions & 0 deletions crates/nostr-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,36 @@ async fn main() -> Result<()> {

More examples can be found in the [examples/](https://github.com/rust-nostr/nostr/tree/master/crates/nostr-sdk/examples) directory.

### Deleting events (NIP-09)

Use [`EventDeletionRequest`](https://docs.rs/nostr/latest/nostr/nips/nip09/struct.EventDeletionRequest.html) when you need to retract one or more events you previously signed. NIP-09 is advisory: relays and clients may ignore the request, so always wait for confirmations.

```rust,no_run
use nostr_sdk::prelude::*;

# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::default();
client.add_relay("wss://relay.example.com").await?;
client.connect().await;

// Collect the events (or coordinates) you want to delete
let delete = EventDeletionRequest::new()
.id(EventId::from_hex("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")?)
.reason("published by accident");

client
.send_event_builder(EventBuilder::delete(delete))
.await?;
# Ok(()) }
```

Guidelines:

- Only the author’s keys can sign a deletion event for a given note.
- Include `Coordinate` tags (kind:pubkey:d) when deleting replaceable or parameterized replaceable events.
- Keep local state until relays confirm the deletion; replays may still surface the original content.

## WASM

This crate supports the `wasm32` targets.
Expand Down
39 changes: 39 additions & 0 deletions docs/nip09-deletions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Managing deletions (NIP-09)

NIP-09 defines a best-effort mechanism for retracting events you previously published. It does **not** guarantee erasure—relays and downstream clients are allowed to cache or ignore deletion requests—so treat it as a courtesy protocol for well-behaved peers.

## When to send a deletion event

1. You control the secret key that signed the original event.
2. You know the event IDs (or coordinates for replaceable/parameterized events) you want to retract.
3. You accept that the payload remains public even after the deletion request propagates.

## Building the request

```rust
use nostr::prelude::*;

let delete = EventDeletionRequest::new()
.id(EventId::from_hex("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")?)
// optionally add coordinates for replaceable events
// .coordinate(Coordinate::parse("30023:pubkey:identifier")?)
.reason("these posts were published by accident");

let event = EventBuilder::delete(delete).sign_with_keys(&keys)?;
```

Use `Tag::event` for concrete IDs (`e` tags) and `Tag::coordinate` for replaceable events (`a` tags). The textual `reason` is optional but helps other clients explain why the content disappeared.

## Broadcasting and follow-up

1. Send the deletion event to every relay that received the original event.
2. Keep a local record of the IDs you attempted to delete. Some relays respond with `OK` messages or emit `NOTICE`s when they refuse to honor the request; handle both cases.
3. Be prepared for race conditions—if someone republishes the original content, you may have to re-issue a deletion.

## Caveats

- Relays are free to ignore deletion events entirely or only apply them to new subscribers.
- Archive relays and scrapers can continue serving the old content indefinitely.
- Deleting a parameterized replaceable event without its coordinate will have no effect.

In short: use NIP-09 as part of your UX, but do not treat it as a hard delete.
33 changes: 33 additions & 0 deletions gossip/nostr-gossip-memory/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
# Gossip in-memory storage

Reference `NostrGossip` implementation that stores relay metadata in an LRU cache. Ideal for bots or clients that want a drop-in gossip engine without running a database.

```rust,no_run
use std::num::NonZeroUsize;

use nostr::prelude::*;
use nostr_gossip::{BestRelaySelection, NostrGossip};
use nostr_gossip_memory::NostrGossipMemory;

# #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let gossip = NostrGossipMemory::bounded(NonZeroUsize::new(2048).unwrap());
let relay = RelayUrl::parse("wss://relay.primal.net")?;

// Every event coming from your relay pool should be forwarded here
let event = EventBuilder::text_note("demo note").sign_with_keys(&Keys::generate())?;
gossip.process(&event, Some(&relay)).await?;

// Later on, ask for the best relays for a profile
let best = gossip
.get_best_relays(
&event.pubkey,
BestRelaySelection::PrivateMessage { limit: 2 },
)
.await?;
println!("DM relays -> {:?}", best);

Ok(())
}
```

Use `NostrGossipMemory::unbounded()` for testing or small bots, and `bounded(limit)` to cap memory usage in long-running clients.

## Changelog

All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md).
Expand Down
Loading
Loading