├── clippy.toml ├── crates ├── nostr │ ├── examples │ │ ├── embedded │ │ │ ├── .gitignore │ │ │ ├── rust-toolchain.toml │ │ │ ├── memory.x │ │ │ ├── justfile │ │ │ ├── Cargo.toml │ │ │ └── README.md │ │ ├── nip98.rs │ │ ├── nip19.rs │ │ ├── nip06.rs │ │ ├── nip13.rs │ │ ├── nip11.rs │ │ ├── nip09.rs │ │ ├── keys.rs │ │ ├── nip57.rs │ │ ├── nip15.rs │ │ ├── parser.rs │ │ ├── nip05.rs │ │ └── nip96.rs │ └── src │ │ ├── types │ │ ├── mod.rs │ │ └── image.rs │ │ ├── nips │ │ ├── nip02.rs │ │ ├── nip62.rs │ │ ├── nip17.rs │ │ ├── mod.rs │ │ ├── nip48.rs │ │ ├── nip10.rs │ │ └── nip58.rs │ │ ├── util │ │ └── hkdf.rs │ │ └── event │ │ ├── error.rs │ │ └── tag │ │ └── cow.rs ├── nostr-relay-pool │ ├── src │ │ ├── transport │ │ │ ├── mod.rs │ │ │ └── error.rs │ │ ├── pool │ │ │ ├── constants.rs │ │ │ ├── options.rs │ │ │ ├── output.rs │ │ │ └── error.rs │ │ ├── prelude.rs │ │ ├── stream.rs │ │ ├── lib.rs │ │ ├── relay │ │ │ └── constants.rs │ │ └── monitor.rs │ ├── Cargo.toml │ ├── examples │ │ └── pool.rs │ └── README.md ├── nostr-keyring │ ├── src │ │ └── prelude.rs │ ├── examples │ │ ├── blocking.rs │ │ └── async.rs │ ├── Cargo.toml │ ├── CHANGELOG.md │ └── README.md ├── nwc │ ├── src │ │ ├── prelude.rs │ │ ├── error.rs │ │ └── builder.rs │ ├── Cargo.toml │ ├── README.md │ └── examples │ │ └── nwc.rs ├── nostr-relay-builder │ ├── src │ │ ├── prelude.rs │ │ ├── lib.rs │ │ ├── local │ │ │ └── util.rs │ │ ├── mock.rs │ │ └── error.rs │ ├── examples │ │ ├── mock.rs │ │ ├── simple_relay.rs │ │ ├── local-with-hs.rs │ │ └── policy.rs │ └── Cargo.toml └── nostr-sdk │ ├── src │ ├── prelude.rs │ ├── lib.rs │ └── client │ │ └── middleware.rs │ ├── examples │ ├── stream-events.rs │ ├── limits.rs │ ├── monitor.rs │ ├── comment.rs │ ├── fetch-events.rs │ ├── nostrdb.rs │ ├── status.rs │ ├── code_snippet.rs │ ├── client.rs │ ├── switch-account.rs │ ├── tor.rs │ ├── nostr-connect.rs │ ├── blacklist.rs │ ├── gossip.rs │ ├── whitelist.rs │ ├── lmdb.rs │ └── aggregated-query.rs │ └── Cargo.toml ├── .github ├── FUNDING.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── enhancement_request.md │ └── bug_report.md └── pull_request_template.md ├── contrib ├── verify-commits │ └── trusted-keys ├── scripts │ ├── check-docs.sh │ ├── fmt.sh │ └── check-crates.sh ├── fund │ ├── rust-nostr-donations-bitcoin-address.txt │ ├── rust-nostr-donations-liquid-address.txt │ ├── rust-nostr-donations-bitcoin-address.txt.asc │ └── rust-nostr-donations-liquid-address.txt.asc └── release │ ├── RELEASE_STEPS.md │ └── ANNOUNCEMENT_TEMPLATE.txt ├── database ├── nostr-database │ ├── justfile │ ├── .gitignore │ ├── src │ │ ├── collections │ │ │ └── mod.rs │ │ ├── prelude.rs │ │ └── error.rs │ ├── fbs │ │ └── event.fbs │ ├── Cargo.toml │ └── README.md ├── nostr-sqlite │ ├── build.rs │ ├── src │ │ ├── prelude.rs │ │ ├── lib.rs │ │ ├── model.rs │ │ └── error.rs │ ├── CHANGELOG.md │ ├── examples │ │ └── sqlite-relay.rs │ ├── README.md │ ├── Cargo.toml │ └── migrations │ │ └── 001_init.sql ├── README.md ├── nostr-lmdb │ ├── src │ │ └── prelude.rs │ ├── README.md │ └── Cargo.toml ├── nostr-database-test-suite │ └── Cargo.toml ├── nostr-ndb │ ├── Cargo.toml │ ├── README.md │ └── CHANGELOG.md └── nostr-indexeddb │ ├── Cargo.toml │ ├── README.md │ ├── CHANGELOG.md │ └── src │ └── error.rs ├── SECURITY.md ├── gossip ├── nostr-gossip-sqlite │ ├── src │ │ ├── model.rs │ │ ├── prelude.rs │ │ ├── lib.rs │ │ ├── constant.rs │ │ └── error.rs │ ├── CHANGELOG.md │ ├── README.md │ ├── Cargo.toml │ └── migrations │ │ └── 001_init.sql ├── nostr-gossip-test-suite │ └── Cargo.toml ├── nostr-gossip-memory │ ├── src │ │ ├── prelude.rs │ │ ├── lib.rs │ │ └── constant.rs │ ├── Cargo.toml │ ├── README.md │ └── CHANGELOG.md └── nostr-gossip │ ├── src │ ├── prelude.rs │ ├── error.rs │ └── flags.rs │ ├── Cargo.toml │ ├── CHANGELOG.md │ └── README.md ├── .gitignore ├── rust-toolchain.toml ├── rustfmt.toml ├── rfs ├── nostr-http-file-storage │ ├── src │ │ └── prelude.rs │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── examples │ │ └── nip96_client.rs │ └── README.md └── nostr-blossom │ ├── src │ ├── prelude.rs │ ├── lib.rs │ └── bud02.rs │ ├── Cargo.toml │ ├── CHANGELOG.md │ ├── examples │ ├── delete.rs │ ├── download.rs │ ├── list.rs │ └── upload.rs │ └── README.md ├── signer ├── nostr-browser-signer-proxy │ ├── src │ │ ├── prelude.rs │ │ └── error.rs │ ├── style.css │ ├── README.md │ ├── index.html │ ├── CHANGELOG.md │ ├── Cargo.toml │ └── examples │ │ └── browser-signer-proxy.rs ├── nostr-connect │ ├── src │ │ ├── prelude.rs │ │ └── lib.rs │ ├── Cargo.toml │ ├── README.md │ ├── examples │ │ ├── handle-auth-url.rs │ │ └── nostr-connect-signer.rs │ └── CHANGELOG.md └── nostr-browser-signer │ ├── CHANGELOG.md │ ├── README.md │ └── Cargo.toml ├── justfile ├── LICENSE └── CONTRIBUTING.md /clippy.toml: -------------------------------------------------------------------------------- 1 | too-many-arguments-threshold = 13 -------------------------------------------------------------------------------- /crates/nostr/examples/embedded/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: [ 2 | 'https://rust-nostr.org/donate', 3 | ] -------------------------------------------------------------------------------- /contrib/verify-commits/trusted-keys: -------------------------------------------------------------------------------- 1 | 86F3105ADFA8AB587268DCD78D3DCD04249619D1 -------------------------------------------------------------------------------- /database/nostr-database/justfile: -------------------------------------------------------------------------------- 1 | flatbuf: 2 | flatc --rust -o ./src/flatbuffers ./fbs/event.fbs 3 | -------------------------------------------------------------------------------- /database/nostr-sqlite/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=migrations"); 3 | } 4 | -------------------------------------------------------------------------------- /database/nostr-database/.gitignore: -------------------------------------------------------------------------------- 1 | *.svg 2 | tracing.folded 3 | perf.data 4 | perf.data.old 5 | many-events.json* -------------------------------------------------------------------------------- /contrib/scripts/check-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | cargo doc --no-deps --all --all-features 6 | -------------------------------------------------------------------------------- /crates/nostr/examples/embedded/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | profile = "minimal" 4 | targets = ["thumbv7m-none-eabi"] -------------------------------------------------------------------------------- /crates/nostr/examples/embedded/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH : ORIGIN = 0x00000000, LENGTH = 512K 4 | RAM : ORIGIN = 0x20000000, LENGTH = 512K 5 | } 6 | -------------------------------------------------------------------------------- /database/README.md: -------------------------------------------------------------------------------- 1 | # Events database for nostr 2 | 3 | This directory contains the events database abstraction (`NostrDatabase`) and various implementations. 4 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a Vulnerability 2 | 3 | For security vulnerability reporting and our complete security policy, please see: https://github.com/rust-nostr/guidelines 4 | -------------------------------------------------------------------------------- /contrib/fund/rust-nostr-donations-bitcoin-address.txt: -------------------------------------------------------------------------------- 1 | The Rust Nostr Project has one official bitcoin (BTC) donation address: 2 | 3 | bc1quk478kpm45744q5pt3p9j42fnv72ykytmt3z0j 4 | 5 | Last updated: 2024-01-17 -------------------------------------------------------------------------------- /database/nostr-database/src/collections/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | pub mod events; 6 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-sqlite/src/model.rs: -------------------------------------------------------------------------------- 1 | use sqlx::FromRow; 2 | 3 | #[derive(FromRow)] 4 | pub(super) struct ListRow { 5 | pub(super) event_created_at: Option, 6 | pub(super) last_checked_at: Option, 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | db/ 3 | .DS_Store 4 | *.db 5 | *.db-shm 6 | *.db-wal 7 | *.mdb 8 | many-events.json 9 | many-events.json.zst 10 | .idea/ 11 | .vscode/ 12 | env/ 13 | .repomix-*.txt 14 | vibe-tools.config.json 15 | .cursor/rules/ 16 | -------------------------------------------------------------------------------- /database/nostr-lmdb/src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Prelude 2 | 3 | #![allow(unknown_lints)] 4 | #![allow(ambiguous_glob_reexports)] 5 | #![doc(hidden)] 6 | 7 | pub use nostr::prelude::*; 8 | pub use nostr_database::prelude::*; 9 | 10 | pub use crate::*; 11 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/transport/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Nostr transports 6 | 7 | pub mod error; 8 | pub mod websocket; 9 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.85.0" 3 | profile = "minimal" 4 | components = ["clippy", "rust-docs", "rustfmt"] 5 | targets = [ 6 | "wasm32-wasip2", # WASI 7 | "wasm32-unknown-unknown", # Browser and JS environments 8 | ] 9 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 4 2 | newline_style = "Auto" 3 | reorder_imports = true 4 | reorder_modules = true 5 | reorder_impl_items = false 6 | indent_style = "Block" 7 | normalize_comments = false 8 | imports_granularity = "Module" 9 | group_imports = "StdExternalCrate" -------------------------------------------------------------------------------- /contrib/fund/rust-nostr-donations-liquid-address.txt: -------------------------------------------------------------------------------- 1 | The Rust Nostr Project has one official bitcoin liquid (L-BTC) donation address: 2 | 3 | lq1qqdwn93gehkq4mtz2amsagawgfd6y9ksrkekal5u8tmle07f9fa0kgcnfez4lguhekeeyhy78nfqy8tyqvxayywgpwvm73t6av 4 | 5 | Last updated: 2024-11-15 6 | -------------------------------------------------------------------------------- /contrib/release/RELEASE_STEPS.md: -------------------------------------------------------------------------------- 1 | # Release steps 2 | 3 | - Bump versions in various `Cargo.toml` with `cargo release version --workspace --execute ` 4 | - Update CHANGELOGs 5 | - Commit and push: `Release vX.X.X` 6 | - Run `just release` to build and publish all the crates 7 | -------------------------------------------------------------------------------- /database/nostr-sqlite/src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Prelude 2 | 3 | #![allow(unknown_lints)] 4 | #![allow(ambiguous_glob_reexports)] 5 | #![doc(hidden)] 6 | 7 | pub use nostr_database::prelude::*; 8 | 9 | pub use crate::error::*; 10 | pub use crate::store::*; 11 | pub use crate::*; 12 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-test-suite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-gossip-test-suite" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | nostr-gossip.workspace = true 9 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 10 | -------------------------------------------------------------------------------- /database/nostr-database-test-suite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-database-test-suite" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | nostr-database.workspace = true 9 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 10 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/pool/constants.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Constants 6 | 7 | /// Relay Pool default notification channel size 8 | pub const DEFAULT_NOTIFICATION_CHANNEL_SIZE: usize = 4096; 9 | -------------------------------------------------------------------------------- /database/nostr-sqlite/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Nostr SQLite database 2 | 3 | #![forbid(unsafe_code)] 4 | #![warn(missing_docs)] 5 | #![warn(rustdoc::bare_urls)] 6 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 7 | #![doc = include_str!("../README.md")] 8 | 9 | pub mod error; 10 | mod model; 11 | pub mod prelude; 12 | pub mod store; 13 | -------------------------------------------------------------------------------- /crates/nostr-keyring/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | 13 | pub use crate::*; 14 | -------------------------------------------------------------------------------- /rfs/nostr-http-file-storage/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | 13 | pub use crate::*; 14 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-memory/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | 13 | pub use crate::store::*; 14 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-sqlite/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | 13 | pub use crate::error::*; 14 | pub use crate::store::*; 15 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer-proxy/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | 13 | pub use crate::error::*; 14 | pub use crate::*; 15 | -------------------------------------------------------------------------------- /crates/nostr/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Types 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | 10 | pub mod image; 11 | pub mod time; 12 | pub mod url; 13 | 14 | pub use self::image::*; 15 | pub use self::time::*; 16 | pub use self::url::*; 17 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-memory/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! In-memory gossip database 6 | 7 | #![forbid(unsafe_code)] 8 | #![warn(missing_docs)] 9 | #![warn(rustdoc::bare_urls)] 10 | #![warn(clippy::large_futures)] 11 | 12 | mod constant; 13 | pub mod prelude; 14 | pub mod store; 15 | -------------------------------------------------------------------------------- /gossip/nostr-gossip/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | 13 | pub use crate::error::*; 14 | pub use crate::flags::*; 15 | pub use crate::*; 16 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-memory/src/constant.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | pub(super) const PUBKEY_METADATA_OUTDATED_AFTER: Duration = Duration::from_secs(60 * 60); // 60 min 8 | pub(super) const MAX_NIP17_SIZE: usize = 7; 9 | pub(super) const MAX_NIP65_SIZE: usize = 7; 10 | -------------------------------------------------------------------------------- /database/nostr-database/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | 13 | pub use crate::ext::*; 14 | pub use crate::memory::{self, *}; 15 | pub use crate::*; 16 | -------------------------------------------------------------------------------- /signer/nostr-connect/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | 13 | pub use crate::client::*; 14 | pub use crate::error::*; 15 | pub use crate::signer::*; 16 | -------------------------------------------------------------------------------- /gossip/nostr-gossip/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-gossip" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "Nostr gossip traits" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "gossip"] 13 | 14 | [dependencies] 15 | nostr = { workspace = true, features = ["std"] } 16 | -------------------------------------------------------------------------------- /rfs/nostr-blossom/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unused_imports)] 8 | #![allow(unknown_lints)] 9 | #![allow(ambiguous_glob_reexports)] 10 | #![doc(hidden)] 11 | 12 | pub use crate::bud01::*; 13 | pub use crate::bud02::*; 14 | pub use crate::client::*; 15 | pub use crate::error::*; 16 | -------------------------------------------------------------------------------- /crates/nwc/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | pub use nostr_relay_pool::prelude::*; 13 | 14 | pub use crate::builder::*; 15 | pub use crate::error::*; 16 | pub use crate::*; 17 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-sqlite/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Nostr Gossip SQLite store. 6 | 7 | #![forbid(unsafe_code)] 8 | #![warn(missing_docs)] 9 | #![warn(rustdoc::bare_urls)] 10 | #![warn(clippy::large_futures)] 11 | 12 | mod constant; 13 | pub mod error; 14 | mod model; 15 | pub mod prelude; 16 | pub mod store; 17 | -------------------------------------------------------------------------------- /database/nostr-database/fbs/event.fbs: -------------------------------------------------------------------------------- 1 | namespace EventFbs; 2 | 3 | struct Fixed32Bytes { 4 | val: [ubyte:32]; 5 | } 6 | 7 | struct Fixed64Bytes { 8 | val: [ubyte:64]; 9 | } 10 | 11 | table StringVector { 12 | data: [string]; 13 | } 14 | 15 | table Event { 16 | id: Fixed32Bytes; 17 | pubkey: Fixed32Bytes; 18 | created_at: ulong; 19 | kind: ulong; 20 | tags: [StringVector]; 21 | content: string; 22 | sig: Fixed64Bytes; 23 | } 24 | 25 | root_type Event; -------------------------------------------------------------------------------- /signer/nostr-connect/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Nostr Connect (NIP46) 6 | //! 7 | //! 8 | 9 | #![forbid(unsafe_code)] 10 | #![warn(missing_docs)] 11 | #![warn(rustdoc::bare_urls)] 12 | #![warn(clippy::large_futures)] 13 | 14 | pub mod client; 15 | pub mod error; 16 | pub mod prelude; 17 | pub mod signer; 18 | -------------------------------------------------------------------------------- /contrib/release/ANNOUNCEMENT_TEMPLATE.txt: -------------------------------------------------------------------------------- 1 | ## rust-nostr v is out! 🦀 2 | 3 | ### Summary 4 | 5 | 6 | 7 | Full changelog: https://rust-nostr.org/changelog 8 | 9 | ### Contributors 10 | 11 | Thanks to all contributors! 12 | 13 | 14 | 15 | ### Links 16 | 17 | https://rust-nostr.org 18 | https://rust-nostr.org/donate 19 | 20 | #rustnostr #nostr #rustlang #programming #rust #python #csharp #dotnet #javascript #kotlin #swift #flutter 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-sqlite/src/constant.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_gossip::flags::GossipFlags; 8 | 9 | pub(super) const PUBKEY_METADATA_OUTDATED_AFTER: Duration = Duration::from_secs(60 * 60); // 60 min 10 | 11 | pub(super) const READ_WRITE_FLAGS: GossipFlags = { 12 | let mut flags = GossipFlags::READ; 13 | flags.add(GossipFlags::WRITE); 14 | flags 15 | }; 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement request 3 | about: Request a new feature or change to an existing feature 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the enhancement** 10 | 11 | 12 | **Use case** 13 | 14 | 15 | **Additional context** 16 | -------------------------------------------------------------------------------- /crates/nostr-relay-builder/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | pub use nostr_database::prelude::*; 13 | pub use nostr_relay_pool::relay::SyncOptions; 14 | 15 | pub use crate::builder::{self, *}; 16 | pub use crate::local::{self, *}; 17 | pub use crate::mock::{self, *}; 18 | pub use crate::*; 19 | -------------------------------------------------------------------------------- /contrib/scripts/fmt.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | version="nightly-2025-03-12" 6 | flags="" 7 | 8 | # Check if "check" is passed as an argument 9 | if [[ "$#" -gt 0 && "$1" == "check" ]]; then 10 | flags="--check" 11 | fi 12 | 13 | # Install toolchain 14 | cargo +$version --version || rustup install $version 15 | 16 | # Install rustfmt 17 | cargo +$version fmt --version || rustup component add rustfmt --toolchain $version 18 | 19 | # Check workspace crates 20 | cargo +$version fmt --all -- --config format_code_in_doc_comments=true $flags 21 | -------------------------------------------------------------------------------- /crates/nostr-sdk/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | pub use nostr::prelude::*; 12 | pub use nostr_database::prelude::*; 13 | pub use nostr_gossip::prelude::*; 14 | pub use nostr_relay_pool::prelude::*; 15 | 16 | pub use crate::client::builder::*; 17 | pub use crate::client::options::*; 18 | pub use crate::client::*; 19 | pub use crate::*; 20 | -------------------------------------------------------------------------------- /database/nostr-sqlite/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## Unreleased 27 | 28 | First release. 29 | -------------------------------------------------------------------------------- /crates/nostr-keyring/examples/blocking.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr_keyring::prelude::*; 6 | 7 | fn main() -> Result<()> { 8 | let keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; 9 | 10 | let keyring = NostrKeyring::new("rust-nostr-test"); 11 | 12 | keyring.set("test", &keys)?; 13 | 14 | let found_keys = keyring.get("test")?; 15 | 16 | assert_eq!(keys, found_keys); 17 | 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /database/nostr-ndb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-ndb" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "ndb (nostrdb) storage backend for Nostr apps" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "database", "ndb", "nostrdb"] 13 | 14 | [dependencies] 15 | hex = { workspace = true, features = ["std"] } 16 | nostr = { workspace = true, features = ["std"] } 17 | nostr-database.workspace = true 18 | nostrdb = "0.8" 19 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-sqlite/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## Unreleased 27 | 28 | First release. 29 | -------------------------------------------------------------------------------- /gossip/nostr-gossip/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## v0.44.0 - 2025/11/06 27 | 28 | First release. 29 | -------------------------------------------------------------------------------- /crates/nostr-relay-builder/examples/mock.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_relay_builder::prelude::*; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | tracing_subscriber::fmt::init(); 12 | 13 | let relay = MockRelay::run().await?; 14 | 15 | let url = relay.url().await; 16 | println!("Url: {url}"); 17 | 18 | // Keep up the program 19 | loop { 20 | tokio::time::sleep(Duration::from_secs(60)).await; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/nostr-keyring/examples/async.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr_keyring::prelude::*; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<()> { 9 | let keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; 10 | 11 | let keyring = NostrKeyring::new("rust-nostr-test"); 12 | 13 | keyring.set_async("test", &keys).await?; 14 | 15 | let found_keys = keyring.get_async("test").await?; 16 | 17 | assert_eq!(keys, found_keys); 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /database/nostr-indexeddb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-indexeddb" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "Web's IndexedDB Storage backend for Nostr apps" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "database", "indexeddb"] 13 | 14 | [dependencies] 15 | hex = { workspace = true, features = ["std"] } 16 | indexed_db_futures = "0.5" 17 | nostr = { workspace = true, features = ["std"] } 18 | nostr-database = { workspace = true, features = ["flatbuf"] } 19 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 4 | 5 | ### Notes to the reviewers 6 | 7 | 9 | 10 | ### Checklist 11 | 12 | - [ ] I followed the [contribution guidelines](https://github.com/rust-nostr/nostr/blob/master/CONTRIBUTING.md) 13 | - [ ] I updated the [CHANGELOG](https://github.com/rust-nostr/nostr/blob/master/CHANGELOG.md) (if applicable) 14 | - [ ] I personally wrote and understood all code in this PR 15 | -------------------------------------------------------------------------------- /crates/nostr-relay-builder/examples/simple_relay.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use nostr_lmdb::NostrLmdb; 4 | use nostr_relay_builder::prelude::*; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<()> { 8 | tracing_subscriber::fmt::init(); 9 | 10 | let db = NostrLmdb::open("./db/nostr-lmdb").await?; 11 | 12 | let relay = LocalRelay::builder().port(7777).database(db).build(); 13 | 14 | relay.run().await?; 15 | 16 | let url = relay.url().await; 17 | println!("Url: {url}"); 18 | 19 | // Keep up the program 20 | loop { 21 | tokio::time::sleep(Duration::from_secs(60)).await; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crates/nostr/examples/nip98.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<()> { 9 | let keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; 10 | 11 | let server_url: Url = Url::parse("https://example.com")?; 12 | let method = HttpMethod::GET; 13 | 14 | let auth = HttpData::new(server_url, method) 15 | .to_authorization(&keys) 16 | .await?; 17 | 18 | println!("{auth}"); 19 | 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /crates/nostr/examples/nip19.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | fn main() -> Result<()> { 8 | let pubkey = 9 | PublicKey::from_hex("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d")?; 10 | let profile = Nip19Profile::new( 11 | pubkey, 12 | [ 13 | RelayUrl::parse("wss://r.x.com").unwrap(), 14 | RelayUrl::parse("wss://djbas.sadkb.com").unwrap(), 15 | ], 16 | ); 17 | println!("{}", profile.to_bech32()?); 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer-proxy/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | padding: 20px; 4 | background-color: #f0f0f0; 5 | } 6 | .container { 7 | max-width: 600px; 8 | margin: 0 auto; 9 | background: white; 10 | padding: 20px; 11 | border-radius: 8px; 12 | box-shadow: 0 2px 10px rgba(0,0,0,0.1); 13 | } 14 | .connected { 15 | color: green; 16 | font-weight: bold; 17 | } 18 | .error { 19 | color: red; 20 | font-weight: bold; 21 | } 22 | .status-box { 23 | background: #f9f9f9; 24 | padding: 10px; 25 | border-radius: 4px; 26 | margin: 10px 0; 27 | border-left: 4px solid #ccc; 28 | } 29 | -------------------------------------------------------------------------------- /crates/nostr-relay-builder/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Nostr Relay Builder and Mock Relay for tests 6 | 7 | #![forbid(unsafe_code)] 8 | #![warn(missing_docs)] 9 | #![warn(rustdoc::bare_urls)] 10 | #![warn(clippy::large_futures)] 11 | #![doc = include_str!("../README.md")] 12 | 13 | pub mod builder; 14 | pub mod error; 15 | pub mod local; 16 | pub mod mock; 17 | pub mod prelude; 18 | 19 | pub use self::builder::LocalRelayBuilder; 20 | pub use self::error::Error; 21 | pub use self::local::LocalRelay; 22 | pub use self::mock::MockRelay; 23 | -------------------------------------------------------------------------------- /crates/nostr/examples/nip06.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::nips::nip06::FromMnemonic; 6 | use nostr::nips::nip19::ToBech32; 7 | use nostr::{Keys, Result}; 8 | 9 | const MNEMONIC_PHRASE: &str = "equal dragon fabric refuse stable cherry smoke allow alley easy never medal attend together lumber movie what sad siege weather matrix buffalo state shoot"; 10 | 11 | fn main() -> Result<()> { 12 | let keys = Keys::from_mnemonic(MNEMONIC_PHRASE, Some("mypassphrase"))?; 13 | println!("{}", keys.secret_key().to_bech32()?); 14 | 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /rfs/nostr-http-file-storage/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## v0.44.0 - 2025/11/06 27 | 28 | No notable changes in this release. 29 | 30 | ## v0.43.0 - 2025/07/28 31 | 32 | First release. 33 | -------------------------------------------------------------------------------- /crates/nostr/examples/nip13.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | fn main() -> Result<()> { 8 | let keys = Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?; 9 | 10 | let difficulty = 20; // leading zero bits 11 | let msg_content = "This is a Nostr message with embedded proof-of-work"; 12 | 13 | let event: Event = EventBuilder::text_note(msg_content) 14 | .pow(difficulty) 15 | .sign_with_keys(&keys)?; 16 | 17 | println!("{}", event.as_json()); 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-memory/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-gossip-memory" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "In-memory gossip database" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "gossip", "in-memory"] 13 | 14 | [dependencies] 15 | indexmap = "2.12" 16 | lru.workspace = true 17 | nostr = { workspace = true, features = ["std"] } 18 | nostr-gossip.workspace = true 19 | tokio = { workspace = true, features = ["sync"] } 20 | 21 | [dev-dependencies] 22 | nostr-gossip-test-suite.workspace = true 23 | -------------------------------------------------------------------------------- /crates/nostr/examples/embedded/justfile: -------------------------------------------------------------------------------- 1 | RUSTFLAGS := "-C link-arg=-Tlink.x" 2 | CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER := "qemu-system-arm -cpu cortex-m3 -machine mps2-an385 -nographic -semihosting-config enable=on,target=native -kernel" 3 | 4 | default: build 5 | 6 | init: 7 | sudo apt install -y gcc-arm-none-eabi qemu-system-arm gdb-multiarch 8 | 9 | build: 10 | RUSTFLAGS="{{RUSTFLAGS}}" CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER="{{CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER}}" cargo build --release --target thumbv7m-none-eabi 11 | 12 | run: 13 | RUSTFLAGS="{{RUSTFLAGS}}" CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER="{{CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER}}" cargo run --release --target thumbv7m-none-eabi -------------------------------------------------------------------------------- /crates/nostr/examples/nip11.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<()> { 9 | let url = Url::parse("https://relay.damus.io")?; 10 | 11 | let client = reqwest::Client::new(); 12 | let response = client 13 | .get(url) 14 | .header("Accept", "application/nostr+json") 15 | .send() 16 | .await?; 17 | let json: String = response.text().await?; 18 | 19 | let info = RelayInformationDocument::from_json(&json)?; 20 | 21 | println!("{info:#?}"); 22 | 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /crates/nostr/examples/embedded/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embedded" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # Prevent this from interfering with workspaces 7 | [workspace] 8 | members = ["."] 9 | 10 | [[bin]] 11 | name = "embedded" 12 | test = false 13 | bench = false 14 | 15 | [dependencies] 16 | alloc-cortex-m = "0.4.1" 17 | cortex-m = "0.6.0" 18 | cortex-m-rt = "0.6.10" 19 | cortex-m-semihosting = "0.3.3" 20 | nostr = { path = "../../../nostr", default-features = false, features = ["alloc", "nip06"] } 21 | 22 | [profile.release] 23 | opt-level = "z" 24 | codegen-units = 1 # better optimizations 25 | lto = true # better optimizations 26 | debug = true # symbols are nice and they don't increase the size on Flash 27 | -------------------------------------------------------------------------------- /gossip/nostr-gossip/README.md: -------------------------------------------------------------------------------- 1 | # Nostr gossip traits 2 | 3 | ## Changelog 4 | 5 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 6 | 7 | ## State 8 | 9 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 10 | 11 | ## Donations 12 | 13 | `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](https://rust-nostr.org/donate). 14 | 15 | ## License 16 | 17 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 18 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-memory/README.md: -------------------------------------------------------------------------------- 1 | # Gossip in-memory storage 2 | 3 | ## Changelog 4 | 5 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 6 | 7 | ## State 8 | 9 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 10 | 11 | ## Donations 12 | 13 | `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](https://rust-nostr.org/donate). 14 | 15 | ## License 16 | 17 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 18 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-sqlite/README.md: -------------------------------------------------------------------------------- 1 | # Nostr SQLite gossip backend 2 | 3 | ## Changelog 4 | 5 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 6 | 7 | ## State 8 | 9 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 10 | 11 | ## Donations 12 | 13 | `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](https://rust-nostr.org/donate). 14 | 15 | ## License 16 | 17 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | 11 | 12 | **To Reproduce** 13 | 14 | 15 | **Expected behavior** 16 | 17 | 18 | **Build environment** 19 | * Library: 20 | * Version/tag/commit: 21 | * OS+version: 22 | 23 | **Additional context** 24 | 25 | -------------------------------------------------------------------------------- /crates/nostr/examples/nip09.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | fn main() -> Result<()> { 8 | let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; 9 | 10 | let event_id = 11 | EventId::from_hex("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")?; 12 | 13 | let request = EventDeletionRequest::new() 14 | .id(event_id) 15 | .reason("these posts were published by accident"); 16 | 17 | let event: Event = EventBuilder::delete(request).sign_with_keys(&keys)?; 18 | println!("{}", event.as_json()); 19 | 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /rfs/nostr-blossom/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! This crate implements the Blossom protocol for decentralized content storage and retrieval. 6 | //! 7 | //! The Blossom protocol defines a standard for storing and retrieving blobs (binary large objects) 8 | //! in a decentralized manner, using the Nostr protocol for authorization and discovery. 9 | //! 10 | //! 11 | 12 | #![forbid(unsafe_code)] 13 | #![warn(missing_docs)] 14 | #![warn(rustdoc::bare_urls)] 15 | #![warn(clippy::large_futures)] 16 | 17 | pub mod bud01; 18 | pub mod bud02; 19 | pub mod client; 20 | pub mod error; 21 | pub mod prelude; 22 | -------------------------------------------------------------------------------- /database/nostr-lmdb/README.md: -------------------------------------------------------------------------------- 1 | # Nostr LMDB 2 | 3 | LMDB storage backend for nostr apps 4 | 5 | ## Changelog 6 | 7 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 8 | 9 | ## State 10 | 11 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 12 | 13 | ## Donations 14 | 15 | `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](https://rust-nostr.org/donate). 16 | 17 | ## License 18 | 19 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 20 | -------------------------------------------------------------------------------- /rfs/nostr-blossom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-blossom" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "A library for interacting with the Blossom protocol" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "blossom"] 13 | 14 | [dependencies] 15 | base64.workspace = true 16 | nostr = { workspace = true, features = ["std"] } 17 | reqwest = { workspace = true, default-features = false, features = ["json", "rustls-tls"] } 18 | serde = { workspace = true, features = ["derive"] } 19 | 20 | [dev-dependencies] 21 | clap = { workspace = true, features = ["derive"] } 22 | tokio = { workspace = true, features = ["full"] } 23 | -------------------------------------------------------------------------------- /crates/nostr-relay-builder/examples/local-with-hs.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_relay_builder::prelude::*; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | tracing_subscriber::fmt::init(); 12 | 13 | let tor = LocalRelayBuilderHiddenService::new("rust-nostr-local-hs-test"); 14 | let relay = LocalRelay::builder().tor(tor).build(); 15 | 16 | relay.run().await?; 17 | 18 | println!("Url: {}", relay.url().await); 19 | println!("Hidden service: {:?}", relay.hidden_service().await?); 20 | 21 | // Keep up the program 22 | loop { 23 | tokio::time::sleep(Duration::from_secs(60)).await; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## v0.44.1 - 2025/11/09 27 | 28 | ### Fixed 29 | 30 | - Fix doc building by removing `#![cfg_attr(docsrs, feature(doc_auto_cfg))]` 31 | 32 | ## v0.44.0 - 2025/11/06 33 | 34 | No notable changes in this release. 35 | 36 | ## v0.43.0 - 2025/07/28 37 | 38 | First release. 39 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Prelude 6 | 7 | #![allow(unknown_lints)] 8 | #![allow(ambiguous_glob_reexports)] 9 | #![doc(hidden)] 10 | 11 | // External crates 12 | pub use async_utility::futures_util::StreamExt; // Needed for `RelayPool::stream_events_of` 13 | pub use nostr::prelude::*; 14 | pub use nostr_database::*; 15 | 16 | // Internal modules 17 | pub use crate::monitor::{self, *}; 18 | pub use crate::policy::*; 19 | pub use crate::pool::builder::*; 20 | pub use crate::pool::constants::*; 21 | pub use crate::pool::options::*; 22 | pub use crate::pool::{self, *}; 23 | pub use crate::relay::{self, *}; 24 | pub use crate::stream::{self, *}; 25 | pub use crate::*; 26 | -------------------------------------------------------------------------------- /database/nostr-indexeddb/README.md: -------------------------------------------------------------------------------- 1 | # Nostr IndexedDB 2 | 3 | This crate implements a storage backend on IndexedDB for web environments. 4 | 5 | ## Changelog 6 | 7 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 8 | 9 | ## State 10 | 11 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 12 | 13 | ## Donations 14 | 15 | `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](https://rust-nostr.org/donate). 16 | 17 | ## License 18 | 19 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 20 | -------------------------------------------------------------------------------- /database/nostr-ndb/README.md: -------------------------------------------------------------------------------- 1 | # nostr-ndb 2 | 3 | This crate implements [nostrdb](https://github.com/damus-io/nostrdb) storage backend. 4 | 5 | ## Changelog 6 | 7 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 8 | 9 | ## State 10 | 11 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 12 | 13 | ## Donations 14 | 15 | `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](https://rust-nostr.org/donate). 16 | 17 | ## License 18 | 19 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 20 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-sqlite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-gossip-sqlite" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "Nostr SQLite gossip backend" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "gossip", "sqlite"] 13 | 14 | [features] 15 | default = [] 16 | unbundled = ["sqlx/sqlite-unbundled"] 17 | 18 | [dependencies] 19 | nostr = { workspace = true, features = ["std"] } 20 | nostr-gossip.workspace = true 21 | sqlx = { workspace = true, features = ["migrate", "runtime-tokio", "sqlite"] } 22 | tokio = { workspace = true, features = ["sync"] } 23 | 24 | [dev-dependencies] 25 | nostr-gossip-test-suite.workspace = true 26 | tempfile.workspace = true 27 | -------------------------------------------------------------------------------- /rfs/nostr-blossom/src/bud02.rs: -------------------------------------------------------------------------------- 1 | //! Implements data structures specific to BUD-02 2 | 3 | use nostr::hashes::sha256::Hash as Sha256Hash; 4 | use nostr::{Timestamp, Url}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | /// A descriptor for the blob 8 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 9 | pub struct BlobDescriptor { 10 | /// The URL at which the blob/file can be accessed 11 | pub url: Url, 12 | /// The SHA256 hash of the contents in the blob 13 | pub sha256: Sha256Hash, 14 | /// The size of the blob/file, in bytes 15 | pub size: u32, 16 | #[serde(rename = "type")] 17 | /// Mime type of the blob/file 18 | pub mime_type: Option, 19 | /// The date at which the blob was uploaded, as a UNIX timestamp (in seconds) 20 | pub uploaded: Timestamp, 21 | } 22 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-memory/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## Unreleased 27 | 28 | ### Breaking changes 29 | 30 | - Replace `usize` with `u8` for relay limits 31 | 32 | ### Changed 33 | 34 | - Replace `Mutex` with `RwLock` for better concurrency (https://github.com/rust-nostr/nostr/pull/1126) 35 | 36 | ## v0.44.0 - 2025/11/06 37 | 38 | First release. 39 | -------------------------------------------------------------------------------- /crates/nostr-relay-builder/src/local/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; 6 | 7 | use nostr::secp256k1::rand::rngs::OsRng; 8 | use nostr::secp256k1::rand::Rng; 9 | use tokio::net::TcpListener; 10 | 11 | pub async fn find_available_port() -> u16 { 12 | let mut rng: OsRng = OsRng; 13 | loop { 14 | let port: u16 = rng.gen_range(1024..=u16::MAX); 15 | if port_is_available(port).await { 16 | return port; 17 | } 18 | } 19 | } 20 | 21 | #[inline] 22 | pub async fn port_is_available(port: u16) -> bool { 23 | TcpListener::bind(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port))) 24 | .await 25 | .is_ok() 26 | } 27 | -------------------------------------------------------------------------------- /crates/nostr/examples/keys.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | fn main() -> Result<()> { 8 | // Random keys 9 | let keys = Keys::generate(); 10 | let public_key = keys.public_key(); 11 | let secret_key = keys.secret_key(); 12 | 13 | println!("Public key: {}", public_key); 14 | println!("Public key bech32: {}", public_key.to_bech32()?); 15 | println!("Secret key: {}", secret_key.to_secret_hex()); 16 | println!("Secret key bech32: {}", secret_key.to_bech32()?); 17 | 18 | // Bech32 keys 19 | let keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; 20 | println!("Public key: {}", keys.public_key()); 21 | 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /database/nostr-sqlite/examples/sqlite-relay.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_relay_builder::prelude::*; 8 | use nostr_sqlite::prelude::*; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | tracing_subscriber::fmt::init(); 13 | 14 | // Create a nostr db instance and run pending db migrations if any 15 | let db = NostrSqlite::open("nostr.sqlite").await?; 16 | 17 | // Create relay 18 | let relay = LocalRelay::builder().database(db).build(); 19 | 20 | relay.run().await?; 21 | 22 | println!("Url: {}", relay.url().await); 23 | 24 | // Keep up the program 25 | loop { 26 | tokio::time::sleep(Duration::from_secs(60)).await; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rfs/nostr-blossom/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## v0.44.0 - 2025/11/06 27 | 28 | No notable changes in this release. 29 | 30 | ## v0.43.0 - 2025/07/28 31 | 32 | No notable changes in this release. 33 | 34 | ## v0.42.1 - 2025/07/01 35 | 36 | ### Fixed 37 | 38 | - blossom: fix url serialization (https://github.com/rust-nostr/nostr/pull/956) 39 | 40 | ## v0.42.0 - 2025/05/20 41 | 42 | First release. 43 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer/README.md: -------------------------------------------------------------------------------- 1 | # Browser signer (NIP-07) 2 | 3 | ## Description 4 | 5 | Nostr Browser signer implementation ([NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md)). 6 | 7 | ## Changelog 8 | 9 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 10 | 11 | ## State 12 | 13 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 14 | 15 | ## Donations 16 | 17 | `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](https://rust-nostr.org/donate). 18 | 19 | ## License 20 | 21 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 22 | -------------------------------------------------------------------------------- /database/nostr-database/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-database" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "Events database abstraction and in-memory implementation" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "database"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [features] 19 | default = [] 20 | flatbuf = ["dep:flatbuffers"] 21 | 22 | [dependencies] 23 | btreecap = "0.1" 24 | flatbuffers = { version = "25.9", optional = true } 25 | lru.workspace = true 26 | nostr = { workspace = true, features = ["std"] } 27 | tokio = { workspace = true, features = ["sync"] } 28 | 29 | [dev-dependencies] 30 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] } 31 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer-proxy/README.md: -------------------------------------------------------------------------------- 1 | # Browser signer proxy (NIP-07) 2 | 3 | ## Description 4 | 5 | Proxy to use Nostr Browser signer ([NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md)) in native applications. 6 | 7 | ## Changelog 8 | 9 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 10 | 11 | ## State 12 | 13 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 14 | 15 | ## Donations 16 | 17 | `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](https://rust-nostr.org/donate). 18 | 19 | ## License 20 | 21 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 22 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/stream-events.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_sdk::prelude::*; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | tracing_subscriber::fmt::init(); 12 | 13 | let client = Client::default(); 14 | client.add_relay("wss://relay.damus.io").await?; 15 | client.add_relay("wss://nos.lol").await?; 16 | 17 | client.connect().await; 18 | 19 | // Stream events from all connected relays 20 | let filter = Filter::new().kind(Kind::TextNote).limit(100); 21 | let mut stream = client 22 | .stream_events(filter, Duration::from_secs(15)) 23 | .await?; 24 | 25 | while let Some(event) = stream.next().await { 26 | println!("{}", event.as_json()); 27 | } 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /crates/nwc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nwc" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "Nostr Wallet Connect client" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "nwc"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [features] 19 | default = [] 20 | # Enables the tor embedded client 21 | tor = ["nostr-relay-pool/tor"] 22 | 23 | [dependencies] 24 | nostr = { workspace = true, features = ["std", "nip47"] } 25 | nostr-relay-pool.workspace = true 26 | tracing = { workspace = true, features = ["std"] } 27 | 28 | [dev-dependencies] 29 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } 30 | tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } 31 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer-proxy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NIP-07 Proxy 5 | 6 | 7 | 8 |
9 |

NIP-07 Proxy

10 |

This page acts as a proxy between your native application and the NIP-07 browser extension.

11 |
12 | Status: Checking... 13 |
14 |

Keep this tab open while using your application. The page will automatically poll for requests from your native app.

15 | 16 |

Debug Info

17 |

Check the browser console (F12) for detailed logs.

18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /contrib/fund/rust-nostr-donations-bitcoin-address.txt.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCgAdFiEEhvMQWt+oq1hyaNzXjT3NBCSWGdEFAmWoE0kACgkQjT3NBCSW 4 | GdGTyQ/8CFJoIw674WySiI9n0kTvmrycsMu5xxm+AsAOP2DFXPKscdC3zRAW/scS 5 | 0qi6QsKbdXvf76NBsOx4VY6gK0Xkwp6M5ZwJLyS50EABECc47utronRzp8gGSWgz 6 | PYMB5s1WSsvph0Y9CXQN5UOVjPnpiMlvy49pBQvw1rexi/pAHPB+C73fGTuDbiMG 7 | PKk4+7q2RWNqgOzBGKUIqRLqmj8Vvx5GOk43ZQ9KoVqE/j1fQonwQ0tGmRkHhMEi 8 | 3sYQHlj3qgqD/wOa7FgOZJY+gwQErn3FXngINVhueSeVnnurBHZPLexhjdIUVvvT 9 | 0bavxChDJ0Q7+B4tBBbClS8A0kEHEciRWZOXY/edegcFP3rtTn0gQn2gyCEsNr3h 10 | gXHy5RoqkekrdtkCYxN0sJj3mLrPIhJz6ZjoEcczEWNaPNEMcSULG6iHES64U8JJ 11 | 4jZyRmJM3DyLilORiMCbPj+ADtV1p+L7B4a8yatLXX+DIQkyMdFVJ5oCM6Zo9joI 12 | vFt5YTr1igqPto6y0lRFYgATSzlOCnmy2/oTNes2RwnHwyGigjtjhbbsN7umzNpq 13 | 3PfmWl3KffQCc3/fnISrO2I31QObeG81VAn0T4BgDX5AcSRp0EJqyeOOgy4p/LEK 14 | FOotFLWX79XAYWRRgKAZRyDmBfiMrlfanhPTAE45hBC1yneqfPw= 15 | =foWS 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /contrib/fund/rust-nostr-donations-liquid-address.txt.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCgAdFiEEhvMQWt+oq1hyaNzXjT3NBCSWGdEFAmc3wz0ACgkQjT3NBCSW 4 | GdGNYRAAubXoaUcPN8TlXo/M8ctN0Lex+7tB/rPH/2Om3AgBfFl03rDM6zSaVTiI 5 | Ntbts82cascPzVCIwwwFXW2jyzeuK3Zpv42qoxMoNijlTkP6MjIn9pMO++0LEZZq 6 | vWrp381qd6PtzEWmEbbqa2XksR9t/VJYLSvX1APBUEqzmoDfpt+NyghZy/NtQuxM 7 | nmUuD/BkFoGTmJApRiCEvbMeKENltYGioG6eTqedUqgPIiltYz4F1G1zXTH6490C 8 | oz0z2p+/YqIlSeLSgLYgrXMbX2D29BpmKJ+12MCPDutuUvLH2nWN/42px5kIyKYP 9 | qMxOv3Vf7UhG5PkUOyJTYPzp2CTb78DjP3JyPOmP5cpvJsvvavApUp5uPrKxiVQJ 10 | voZBzC2/jcCSy64QZ5C7fkz9rBMvUJhf61L/9FlUAkV38ZzPIlJjhjLrx+9ZcWVp 11 | uT2z11FgcAY1YXYnXdq+SnDLsA5idUCo0SQBBHoCZDIFvdI6P70zs4KzdA+hG6Da 12 | Lqo9jwKtL5Z5X++9PGE8AjyGh6sqYyuh7sObBeJnIWRqERekvDu4HqZmUOf4CkLw 13 | ef65CuXhu2aCoD369rg2TRi4jVVLqVRs9muwGIJeL6iu0Hfw52ONB4y52tE+HK/m 14 | Rv75bEX3uiC5brogj7wW1vybr+wbwWt3rnFvv2pIrIDYShe85LU= 15 | =WOxw 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /database/nostr-sqlite/README.md: -------------------------------------------------------------------------------- 1 | # Nostr SQLite database backend 2 | 3 | SQLite storage backend for nostr apps. 4 | 5 | ## Crate Feature Flags 6 | 7 | The following crate feature flags are available: 8 | 9 | | Feature | Default | Description | 10 | |-------------|:-------:|-----------------------| 11 | | `unbundled` | No | Uses unbundled SQLite | 12 | 13 | ## State 14 | 15 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 16 | 17 | ## Donations 18 | 19 | `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](https://rust-nostr.org/donate). 20 | 21 | ## License 22 | 23 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 24 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-browser-signer" 3 | version = "0.44.1" 4 | edition = "2021" 5 | description = "Nostr Browser signer implementation (NIP-07)." 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "nip07", "browser", "signer"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [dependencies] 19 | js-sys = { version = "0.3", default-features = false, features = ["std"] } 20 | nostr = { workspace = true, default-features = false, features = ["std"] } 21 | wasm-bindgen = { version = "0.2", default-features = false, features = ["std"] } 22 | wasm-bindgen-futures = { version = "0.4", default-features = false } 23 | web-sys = { version = "0.3", default-features = false, features = ["std", "Window"] } 24 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env just --justfile 2 | 3 | set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] 4 | 5 | [private] 6 | default: 7 | @just --list 8 | 9 | # Execute the pre-commit checks 10 | precommit: fmt check-crates check-docs 11 | 12 | # Execute continuous integration (CI) checks 13 | ci: check-fmt check-crates check-docs 14 | 15 | # Format the entire Rust code 16 | fmt: 17 | @bash contrib/scripts/fmt.sh 18 | 19 | # Check if the Rust code is formatted 20 | [private] 21 | check-fmt: 22 | @bash contrib/scripts/fmt.sh check 23 | 24 | # Check all the crates 25 | [private] 26 | check-crates: 27 | @bash contrib/scripts/check-crates.sh 28 | 29 | # Check Rust docs 30 | [private] 31 | check-docs: 32 | @bash contrib/scripts/check-docs.sh 33 | 34 | # Release rust crates 35 | [confirm] 36 | release: 37 | cargo +stable publish --workspace 38 | 39 | # Run benches (unstable) 40 | bench: 41 | RUSTFLAGS='--cfg=bench' cargo +nightly bench 42 | -------------------------------------------------------------------------------- /rfs/nostr-http-file-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-http-file-storage" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "Nostr HTTP File Storage client (NIP-96)." 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "nip96", "storage", "http"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [features] 19 | default = [] 20 | # Enable support for SOCKS proxy 21 | socks = ["reqwest/socks"] 22 | 23 | [dependencies] 24 | nostr = { workspace = true, features = ["std", "nip96"] } 25 | reqwest = { workspace = true, features = ["json", "multipart", "rustls-tls"] } 26 | tokio = { workspace = true, features = ["sync"] } 27 | 28 | [dev-dependencies] 29 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 30 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer-proxy/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## v0.44.0 - 2025/11/06 27 | 28 | ### Added 29 | 30 | - `BrowserSignerProxy::is_started` function to know if the server currently running or not (https://github.com/rust-nostr/nostr/pull/1025) 31 | - `BrowserSignerProxy::is_session_active` returns whether there is an active session with the browser (https://github.com/rust-nostr/nostr/pull/1026) 32 | 33 | ## v0.43.0 - 2025/07/28 34 | 35 | First release. 36 | -------------------------------------------------------------------------------- /crates/nostr/src/nips/nip02.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! NIP02: Follow List 6 | //! 7 | //! 8 | 9 | use alloc::string::String; 10 | 11 | use crate::key::PublicKey; 12 | use crate::types::RelayUrl; 13 | 14 | /// Contact 15 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 16 | pub struct Contact { 17 | /// Public key 18 | pub public_key: PublicKey, 19 | /// Relay url 20 | pub relay_url: Option, 21 | /// Alias 22 | pub alias: Option, 23 | } 24 | 25 | impl Contact { 26 | /// Create new contact 27 | #[inline] 28 | pub fn new(public_key: PublicKey) -> Self { 29 | Self { 30 | public_key, 31 | relay_url: None, 32 | alias: None, 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/limits.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr_sdk::prelude::*; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<()> { 9 | tracing_subscriber::fmt::init(); 10 | 11 | // Customize relay limits 12 | let mut limits = RelayLimits::default(); 13 | limits.messages.max_size = Some(10_000); 14 | limits.events.max_size = Some(3_000); 15 | 16 | // OR, disable all limits 17 | let limits = RelayLimits::disable(); 18 | 19 | // Compose options and limits 20 | let opts = ClientOptions::new().relay_limits(limits); 21 | let client = Client::builder().opts(opts).build(); 22 | 23 | // Add relays and connect 24 | client.add_relay("wss://relay.damus.io").await?; 25 | client.add_relay("wss://nostr.oxtr.dev").await?; 26 | client.connect().await; 27 | 28 | // ... 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /gossip/nostr-gossip/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Nostr Gossip error 6 | 7 | use std::fmt; 8 | 9 | /// Gossip Error 10 | #[derive(Debug)] 11 | pub enum GossipError { 12 | /// An error happened in the underlying database backend. 13 | Backend(Box), 14 | } 15 | 16 | impl std::error::Error for GossipError {} 17 | 18 | impl fmt::Display for GossipError { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | match self { 21 | Self::Backend(e) => e.fmt(f), 22 | } 23 | } 24 | } 25 | 26 | impl GossipError { 27 | /// Create a new backend error 28 | /// 29 | /// Shorthand for `Error::Backend(Box::new(error))`. 30 | #[inline] 31 | pub fn backend(error: E) -> Self 32 | where 33 | E: std::error::Error + Send + Sync + 'static, 34 | { 35 | Self::Backend(Box::new(error)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/nostr-keyring/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-keyring" 3 | version = "0.44.1" 4 | edition = "2021" 5 | description = "Keyring for nostr" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version = "1.75.0" 12 | keywords = ["nostr", "keyring"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [features] 19 | default = [] 20 | # Enables async APIs 21 | async = ["dep:async-utility"] 22 | 23 | [dependencies] 24 | async-utility = { workspace = true, optional = true } 25 | keyring = { version = "3.6", features = ["apple-native", "linux-native", "linux-native-sync-persistent", "windows-native"] } # MSRV: 1.75.0 26 | nostr = { workspace = true, features = ["std"] } 27 | 28 | [dev-dependencies] 29 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 30 | 31 | [[example]] 32 | name = "async" 33 | required-features = ["async"] 34 | 35 | [[example]] 36 | name = "blocking" 37 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/transport/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Transport Error 6 | 7 | use core::fmt; 8 | 9 | /// Transport Error 10 | #[derive(Debug)] 11 | pub enum TransportError { 12 | /// An error happened in the underlying backend. 13 | Backend(Box), 14 | } 15 | 16 | impl std::error::Error for TransportError {} 17 | 18 | impl fmt::Display for TransportError { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | match self { 21 | Self::Backend(e) => e.fmt(f), 22 | } 23 | } 24 | } 25 | 26 | impl TransportError { 27 | /// Create a new backend error 28 | /// 29 | /// Shorthand for `Error::Backend(Box::new(error))`. 30 | #[inline] 31 | pub fn backend(error: E) -> Self 32 | where 33 | E: std::error::Error + Send + Sync + 'static, 34 | { 35 | Self::Backend(Box::new(error)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/nostr-sqlite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-sqlite" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "SQLite storage backend for nostr apps" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "database", "sqlite"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [features] 19 | default = [] 20 | unbundled = ["sqlx/sqlite-unbundled"] 21 | 22 | [dependencies] 23 | nostr = { workspace = true, features = ["std"] } 24 | nostr-database.workspace = true 25 | sqlx = { workspace = true, features = ["json", "migrate", "runtime-tokio", "sqlite"] } 26 | 27 | [dev-dependencies] 28 | nostr-database-test-suite.workspace = true 29 | nostr-relay-builder.workspace = true 30 | tempfile.workspace = true 31 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] } 32 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 33 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/monitor.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr_sdk::prelude::*; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<()> { 9 | tracing_subscriber::fmt::init(); 10 | 11 | let monitor = Monitor::new(4096); 12 | let client = Client::builder().monitor(monitor).build(); 13 | 14 | // Subscribe to monitor notifications 15 | let mut notifications = client.monitor().unwrap().subscribe(); 16 | 17 | client.add_relay("wss://relay.damus.io").await?; 18 | client.add_relay("wss://nostr.wine").await?; 19 | client.add_relay("wss://relay.rip").await?; 20 | 21 | client.connect().await; 22 | 23 | while let Ok(notification) = notifications.recv().await { 24 | match notification { 25 | MonitorNotification::StatusChanged { relay_url, status } => { 26 | println!("Relay status changed for {relay_url}: {status}") 27 | } 28 | } 29 | } 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /signer/nostr-connect/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-connect" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "Nostr Remote Signing (NIP46)" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "signer", "nip46", "nostr-connect"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [features] 19 | default = [] 20 | tor = ["nostr-relay-pool/tor"] 21 | 22 | [dependencies] 23 | async-utility.workspace = true 24 | nostr = { workspace = true, features = ["std", "nip04", "nip44", "nip46"] } 25 | nostr-relay-pool.workspace = true 26 | tokio = { workspace = true, features = ["sync"] } 27 | tracing.workspace = true 28 | 29 | [dev-dependencies] 30 | dialoguer = "0.11" 31 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 32 | webbrowser = "1.0" 33 | 34 | [[example]] 35 | name = "handle-auth-url" 36 | 37 | [[example]] 38 | name = "nostr-connect-signer" 39 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Stream 6 | 7 | use std::pin::Pin; 8 | use std::task::{Context, Poll}; 9 | 10 | use async_utility::futures_util::Stream; 11 | use tokio::sync::mpsc::Receiver; 12 | 13 | /// Boxed stream 14 | #[cfg(not(target_arch = "wasm32"))] 15 | pub type BoxedStream = Pin + Send>>; 16 | /// Boxed stream 17 | #[cfg(target_arch = "wasm32")] 18 | pub type BoxedStream = Pin>>; 19 | 20 | #[derive(Debug)] 21 | pub(crate) struct ReceiverStream { 22 | inner: Receiver, 23 | } 24 | 25 | impl ReceiverStream { 26 | #[inline] 27 | pub(crate) fn new(recv: Receiver) -> Self { 28 | Self { inner: recv } 29 | } 30 | } 31 | 32 | impl Stream for ReceiverStream { 33 | type Item = T; 34 | 35 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 36 | self.inner.poll_recv(cx) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /signer/nostr-connect/README.md: -------------------------------------------------------------------------------- 1 | # Nostr Connect (NIP46) 2 | 3 | ## Description 4 | 5 | This NIP introduces remote signing for Nostr, allowing for secure and 6 | decentralized signing of events using a hardware device or remote signer. The 7 | protocol enables 2-way communication between the remote signer and a Nostr 8 | client, providing a method for private key exposure minimization and improved 9 | security. 10 | 11 | ## Changelog 12 | 13 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 14 | 15 | ## State 16 | 17 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 18 | 19 | ## Donations 20 | 21 | `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](https://rust-nostr.org/donate). 22 | 23 | ## License 24 | 25 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 26 | -------------------------------------------------------------------------------- /crates/nostr/examples/nip57.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | fn main() -> Result<()> { 8 | let keys = Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?; 9 | 10 | let public_key = 11 | PublicKey::from_bech32("npub14f8usejl26twx0dhuxjh9cas7keav9vr0v8nvtwtrjqx3vycc76qqh9nsy")?; 12 | let relays = [RelayUrl::parse("wss://relay.damus.io").unwrap()]; 13 | let data = ZapRequestData::new(public_key, relays).message("Zap!"); 14 | 15 | let public_zap: Event = EventBuilder::public_zap_request(data.clone()).sign_with_keys(&keys)?; 16 | println!("Public zap request: {}", public_zap.as_json()); 17 | 18 | let anon_zap: Event = nip57::anonymous_zap_request(data.clone())?; 19 | println!("Anonymous zap request: {}", anon_zap.as_json()); 20 | 21 | let private_zap: Event = nip57::private_zap_request(data, &keys)?; 22 | println!("Private zap request: {}", private_zap.as_json()); 23 | 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /crates/nwc/README.md: -------------------------------------------------------------------------------- 1 | # NWC 2 | 3 | ## Description 4 | 5 | NWC client and zapper backend for Nostr apps 6 | 7 | ## Notifications Support 8 | 9 | NWC supports real-time payment notifications as specified in NIP-47. 10 | 11 | This allows applications to receive instant updates when payments are sent or received. 12 | See [`examples/notifications.rs`](examples/notifications.rs) for a simple example of how to use this feature. 13 | 14 | ## Changelog 15 | 16 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 17 | 18 | ## State 19 | 20 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 21 | 22 | ## Donations 23 | 24 | `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](https://rust-nostr.org/donate). 25 | 26 | ## License 27 | 28 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 29 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/comment.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_sdk::prelude::*; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | tracing_subscriber::fmt::init(); 12 | 13 | let keys = Keys::generate(); 14 | let client = Client::builder().signer(keys).build(); 15 | 16 | client.add_relay("wss://relay.damus.io/").await?; 17 | client.add_relay("wss://relay.primal.net/").await?; 18 | 19 | client.connect().await; 20 | 21 | let event_id = 22 | EventId::from_bech32("note1hrrgx2309my3wgeecx2tt6fl2nl8hcwl0myr3xvkcqpnq24pxg2q06armr")?; 23 | let events = client 24 | .fetch_events(Filter::new().id(event_id), Duration::from_secs(10)) 25 | .await?; 26 | 27 | let comment_to = events.first().unwrap(); 28 | let builder = EventBuilder::comment("This is a reply", CommentTarget::from(comment_to), None); 29 | 30 | let output = client.send_event_builder(builder).await?; 31 | println!("Output: {:?}", output); 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /database/nostr-database/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Nostr Database Error 6 | 7 | use std::fmt; 8 | 9 | /// Database Error 10 | #[derive(Debug)] 11 | pub enum DatabaseError { 12 | /// An error happened in the underlying database backend. 13 | Backend(Box), 14 | /// Not supported 15 | NotSupported, 16 | } 17 | 18 | impl std::error::Error for DatabaseError {} 19 | 20 | impl fmt::Display for DatabaseError { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | match self { 23 | Self::Backend(e) => e.fmt(f), 24 | Self::NotSupported => f.write_str("not supported"), 25 | } 26 | } 27 | } 28 | 29 | impl DatabaseError { 30 | /// Create a new backend error 31 | /// 32 | /// Shorthand for `Error::Backend(Box::new(error))`. 33 | #[inline] 34 | pub fn backend(error: E) -> Self 35 | where 36 | E: std::error::Error + Send + Sync + 'static, 37 | { 38 | Self::Backend(Box::new(error)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /crates/nostr-keyring/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## v0.44.1 - 2025/11/09 27 | 28 | ### Fixed 29 | 30 | - Fix doc building by removing `#![cfg_attr(docsrs, feature(doc_auto_cfg))]` 31 | 32 | ## v0.44.0 - 2025/11/06 33 | 34 | No notable changes in this release. 35 | 36 | ## v0.43.0 - 2025/07/28 37 | 38 | ### Changed 39 | 40 | - Re-export `keyring::Entry` and `keyring::Error` (https://github.com/rust-nostr/nostr/pull/974) 41 | 42 | ## v0.42.1 - 2025/06/28 43 | 44 | - Fix keys persistence between OS restarts on Linux (https://github.com/rust-nostr/nostr/pull/942) 45 | 46 | ## v0.42.0 - 2025/05/20 47 | 48 | No notable changes in this release. 49 | 50 | ## v0.41.0 - 2025/04/15 51 | 52 | First release. 53 | -------------------------------------------------------------------------------- /crates/nostr/examples/nip15.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 ProTom 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | fn main() -> Result<()> { 8 | let keys = Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?; 9 | 10 | let shipping = ShippingMethod::new("123", 5.50).name("DHL"); 11 | 12 | let stall = StallData::new("123", "my test stall", "USD") 13 | .description("this is a test stall") 14 | .shipping(vec![shipping.clone()]); 15 | 16 | let stall_event = EventBuilder::stall_data(stall).sign_with_keys(&keys)?; 17 | println!("{}", stall_event.as_json()); 18 | 19 | let product = ProductData::new("1", "123", "my test product", "USD") 20 | .description("this is a test product") 21 | .price(5.50) 22 | .shipping(vec![shipping.get_shipping_cost()]) 23 | .images(vec!["https://example.com/image.png".into()]) 24 | .categories(vec!["test".into()]); 25 | 26 | let product_event = EventBuilder::product_data(product).sign_with_keys(&keys)?; 27 | println!("{}", product_event.as_json()); 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /crates/nostr/src/nips/nip62.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! NIP-62: Request to Vanish 6 | //! 7 | //! 8 | 9 | use alloc::vec::Vec; 10 | 11 | use crate::RelayUrl; 12 | 13 | /// Request to Vanish target 14 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 | pub enum VanishTarget { 16 | /// Request to vanish from all relays 17 | AllRelays, 18 | /// Request to vanish from a specific list of relays. 19 | Relays(Vec), 20 | } 21 | 22 | impl VanishTarget { 23 | /// Vanish from a single relay 24 | #[inline] 25 | pub fn relay(relay: RelayUrl) -> Self { 26 | Self::Relays(vec![relay]) 27 | } 28 | 29 | /// Vanish from multiple relays 30 | #[inline] 31 | pub fn relays(relays: I) -> Self 32 | where 33 | I: IntoIterator, 34 | { 35 | Self::Relays(relays.into_iter().collect()) 36 | } 37 | 38 | /// Vanish from all relays 39 | pub fn all_relays() -> Self { 40 | Self::AllRelays 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 Yuki Kishimoto 4 | Copyright (c) 2023-2025 Rust Nostr Developers 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /rfs/nostr-blossom/examples/delete.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use nostr::hashes::sha256::Hash as Sha256Hash; 3 | use nostr::prelude::*; 4 | use nostr_blossom::prelude::*; 5 | 6 | #[derive(Parser, Debug)] 7 | #[command(author, version, about = "Delete a blob from a Blossom server", long_about = None)] 8 | struct Args { 9 | /// The server URL to connect to 10 | #[arg(long)] 11 | server: Url, 12 | 13 | /// The SHA256 hash of the blob to delete (in hex) 14 | #[arg(long)] 15 | sha256: Sha256Hash, 16 | 17 | /// Optional private key for signing the deletion 18 | #[arg(long, value_name = "PRIVATE_KEY")] 19 | private_key: SecretKey, 20 | } 21 | 22 | #[tokio::main] 23 | async fn main() -> Result<()> { 24 | let args = Args::parse(); 25 | 26 | let client = BlossomClient::new(args.server); 27 | 28 | // Create signer keys using the given private key 29 | let keys = Keys::new(args.private_key); 30 | 31 | println!("Attempting to delete blob with SHA256: {}", args.sha256); 32 | 33 | match client.delete_blob(args.sha256, None, &keys).await { 34 | Ok(()) => println!("Blob deleted successfully."), 35 | Err(e) => eprintln!("{e}"), 36 | } 37 | 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /rfs/nostr-blossom/README.md: -------------------------------------------------------------------------------- 1 | # Blossom 2 | 3 | ## Description 4 | 5 | A library for interacting with the [Blossom protocol](https://github.com/hzrd149/blossom). 6 | 7 | ## Implemented BUDs 8 | 9 | - **Basic data structures:** [BUD-01](https://github.com/hzrd149/blossom/blob/master/buds/01.md), [BUD-02](https://github.com/hzrd149/blossom/blob/master/buds/02.md) 10 | - **Client:** [BUD-01](https://github.com/hzrd149/blossom/blob/master/buds/01.md), [BUD-02](https://github.com/hzrd149/blossom/blob/master/buds/02.md) 11 | - **Server:** Not implemented 12 | 13 | ## Changelog 14 | 15 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 16 | 17 | ## State 18 | 19 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 20 | 21 | ## Donations 22 | 23 | `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](https://rust-nostr.org/donate). 24 | 25 | ## License 26 | 27 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 28 | -------------------------------------------------------------------------------- /crates/nostr-sdk/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! High level Nostr client library. 6 | 7 | #![forbid(unsafe_code)] 8 | #![warn(missing_docs)] 9 | #![warn(rustdoc::bare_urls)] 10 | #![warn(clippy::large_futures)] 11 | #![allow(unknown_lints)] // TODO: remove when MSRV >= 1.72.0, required for `clippy::arc_with_non_send_sync` 12 | #![allow(clippy::arc_with_non_send_sync)] 13 | #![allow(clippy::mutable_key_type)] // TODO: remove when possible. Needed to suppress false positive for `BTreeSet` 14 | #![cfg_attr(feature = "all-nips", doc = include_str!("../README.md"))] 15 | 16 | #[doc(hidden)] 17 | pub use async_utility; 18 | #[doc(hidden)] 19 | pub use nostr::{self, *}; 20 | #[doc(hidden)] 21 | pub use nostr_relay_pool::{ 22 | self as pool, AtomicRelayServiceFlags, Relay, RelayConnectionStats, RelayOptions, RelayPool, 23 | RelayPoolNotification, RelayPoolOptions, RelayServiceFlags, RelayStatus, 24 | SubscribeAutoCloseOptions, SubscribeOptions, SyncDirection, SyncOptions, 25 | }; 26 | 27 | pub mod client; 28 | mod gossip; 29 | pub mod prelude; 30 | 31 | pub use self::client::{Client, ClientBuilder, ClientOptions}; 32 | -------------------------------------------------------------------------------- /crates/nostr/src/nips/nip17.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! NIP17: Private Direct Message 6 | //! 7 | //! 8 | 9 | use crate::{Event, RelayUrl, TagStandard}; 10 | 11 | /// Extracts the relay list 12 | /// 13 | /// This function doesn't verify if the event kind is [`Kind::InboxRelays`](crate::Kind::InboxRelays)! 14 | pub fn extract_relay_list(event: &Event) -> impl Iterator { 15 | event.tags.iter().filter_map(|tag| { 16 | if let Some(TagStandard::Relay(url)) = tag.as_standardized() { 17 | Some(url) 18 | } else { 19 | None 20 | } 21 | }) 22 | } 23 | 24 | /// Extracts the relay list 25 | /// 26 | /// This function doesn't verify if the event kind is [`Kind::InboxRelays`](crate::Kind::InboxRelays)! 27 | pub fn extract_owned_relay_list(event: Event) -> impl Iterator { 28 | event.tags.into_iter().filter_map(|tag| { 29 | if let Some(TagStandard::Relay(url)) = tag.to_standardized() { 30 | Some(url) 31 | } else { 32 | None 33 | } 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/fetch-events.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_sdk::prelude::*; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | tracing_subscriber::fmt::init(); 12 | 13 | let public_key = 14 | PublicKey::from_bech32("npub1080l37pfvdpyuzasyuy2ytjykjvq3ylr5jlqlg7tvzjrh9r8vn3sf5yaph")?; 15 | 16 | let client = Client::default(); 17 | client.add_relay("wss://relay.damus.io").await?; 18 | client.add_relay("wss://relay.rip").await?; 19 | 20 | client.connect().await; 21 | 22 | let filter = Filter::new().author(public_key).kind(Kind::Metadata); 23 | let events = client.fetch_events(filter, Duration::from_secs(10)).await?; 24 | println!("{events:#?}"); 25 | 26 | let filter = Filter::new() 27 | .author(public_key) 28 | .kind(Kind::TextNote) 29 | .limit(3); 30 | let events = client 31 | .fetch_events_from( 32 | ["wss://relay.damus.io", "wss://relay.rip"], 33 | filter, 34 | Duration::from_secs(10), 35 | ) 36 | .await?; 37 | println!("{events:#?}"); 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /database/nostr-lmdb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-lmdb" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "LMDB storage backend for nostr apps" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "database", "lmdb"] 13 | 14 | [dependencies] 15 | async-utility.workspace = true 16 | flume = "0.11" 17 | nostr = { workspace = true, features = ["std"] } 18 | nostr-database = { workspace = true, features = ["flatbuf"] } 19 | tokio = { workspace = true, features = ["sync"] } 20 | tracing.workspace = true 21 | 22 | [target.'cfg(not(all(target_os = "macos", target_os = "ios")))'.dependencies] 23 | heed = { version = "0.20", default-features = false, features = ["read-txn-no-tls"] } 24 | 25 | # POSIX semaphores are required for Apple's App Sandbox on iOS & macOS 26 | [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] 27 | heed = { version = "0.20", default-features = false, features = ["read-txn-no-tls", "posix-sem"] } 28 | 29 | [dev-dependencies] 30 | futures = "0.3" 31 | nostr-database-test-suite.workspace = true 32 | tempfile.workspace = true 33 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 34 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-browser-signer-proxy" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "Proxy to use Nostr Browser signer (NIP-07) in native applications." 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "nip07", "browser", "signer", "proxy"] 13 | 14 | [dependencies] 15 | atomic-destructor.workspace = true 16 | bytes = "1.11" 17 | http-body-util = "0.1" 18 | hyper = { version = "1.6", features = ["server", "http1"] } 19 | hyper-util = { version = "0.1", features = ["tokio"] } 20 | nostr = { workspace = true, features = ["std"] } 21 | serde = { workspace = true, features = ["std", "derive"] } 22 | serde_json = { workspace = true, features = ["std"] } 23 | tokio = { workspace = true, features = ["macros", "net", "rt", "rt-multi-thread", "sync", "time"] } 24 | tracing = { workspace = true, features = ["std"] } 25 | uuid = { version = "1.18", features = ["serde", "v4"] } 26 | 27 | [dev-dependencies] 28 | nostr = { workspace = true, features = ["nip59"] } 29 | tokio = { workspace = true, features = ["signal"] } 30 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 31 | -------------------------------------------------------------------------------- /rfs/nostr-http-file-storage/examples/nip96_client.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr_http_file_storage::prelude::*; 6 | 7 | const FILE: &[u8] = &[ 8 | 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0, 9 | 0, 0, 31, 21, 196, 137, 0, 0, 0, 1, 115, 82, 71, 66, 0, 174, 206, 28, 233, 0, 0, 0, 4, 103, 65, 10 | 77, 65, 0, 0, 177, 143, 11, 252, 97, 5, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 28, 35, 0, 0, 28, 11 | 35, 1, 199, 111, 168, 100, 0, 0, 0, 12, 73, 68, 65, 84, 8, 29, 99, 248, 255, 255, 63, 0, 5, 12 | 254, 2, 254, 135, 150, 28, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130, 13 | ]; 14 | 15 | #[tokio::main] 16 | async fn main() -> Result<()> { 17 | let keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; 18 | 19 | let client = NostrHttpFileStorageClient::new(); 20 | 21 | let server_url: Url = Url::parse("https://NostrMedia.com")?; 22 | 23 | // Get config 24 | let config = client.get_server_config(&server_url).await?; 25 | 26 | // Upload 27 | let url: Url = client.upload(&keys, &config, FILE.to_vec(), None).await?; 28 | 29 | println!("File uploaded: {url}"); 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /crates/nwc/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! NWC error 6 | 7 | use std::fmt; 8 | 9 | use nostr::nips::nip47; 10 | use nostr_relay_pool::pool; 11 | 12 | /// NWC error 13 | #[derive(Debug)] 14 | pub enum Error { 15 | /// NIP47 error 16 | NIP47(nip47::Error), 17 | /// Relay Pool 18 | Pool(pool::Error), 19 | /// Premature exit 20 | PrematureExit, 21 | /// Request timeout 22 | Timeout, 23 | /// Handler error 24 | Handler(String), 25 | } 26 | 27 | impl std::error::Error for Error {} 28 | 29 | impl fmt::Display for Error { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | match self { 32 | Self::NIP47(e) => e.fmt(f), 33 | Self::Pool(e) => e.fmt(f), 34 | Self::PrematureExit => f.write_str("premature exit"), 35 | Self::Timeout => f.write_str("timeout"), 36 | Self::Handler(e) => f.write_str(e), 37 | } 38 | } 39 | } 40 | 41 | impl From for Error { 42 | fn from(e: nip47::Error) -> Self { 43 | Self::NIP47(e) 44 | } 45 | } 46 | 47 | impl From for Error { 48 | fn from(e: pool::Error) -> Self { 49 | Self::Pool(e) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-relay-pool" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "Nostr Relay Pool" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "relay", "pool"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [features] 19 | default = [] 20 | tor = ["async-wsocket/tor"] 21 | 22 | [dependencies] 23 | async-utility.workspace = true 24 | async-wsocket = { workspace = true, features = ["socks"] } 25 | atomic-destructor.workspace = true 26 | hex = { workspace = true, features = ["std"] } 27 | lru.workspace = true 28 | negentropy = { workspace = true, features = ["std"] } 29 | nostr = { workspace = true, features = ["std"] } 30 | nostr-database.workspace = true 31 | tokio = { workspace = true, features = ["macros", "sync"] } 32 | tracing.workspace = true 33 | 34 | [dev-dependencies] 35 | nostr-relay-builder.workspace = true 36 | tokio = { workspace = true, features = ["rt-multi-thread"] } 37 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 38 | 39 | [[example]] 40 | name = "pool" 41 | 42 | [lints.rust] 43 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(bench)'] } 44 | -------------------------------------------------------------------------------- /crates/nostr/src/util/hkdf.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! HKDF Util 6 | 7 | use alloc::vec::Vec; 8 | 9 | use hashes::hmac::{Hmac, HmacEngine}; 10 | use hashes::sha256::Hash as Sha256Hash; 11 | use hashes::{Hash, HashEngine}; 12 | 13 | /// HKDF extract 14 | #[inline] 15 | pub fn extract(salt: &[u8], input_key_material: &[u8]) -> Hmac { 16 | let mut engine: HmacEngine = HmacEngine::new(salt); 17 | engine.input(input_key_material); 18 | Hmac::from_engine(engine) 19 | } 20 | 21 | /// HKDF expand 22 | pub fn expand(prk: &[u8], info: &[u8], output_len: usize) -> Vec { 23 | let mut output: Vec = Vec::with_capacity(output_len); 24 | let mut t: Vec = Vec::with_capacity(32); 25 | 26 | let mut i: u8 = 1u8; 27 | while output.len() < output_len { 28 | let mut engine: HmacEngine = HmacEngine::new(prk); 29 | 30 | if !t.is_empty() { 31 | engine.input(&t); 32 | } 33 | 34 | engine.input(info); 35 | engine.input(&[i]); 36 | 37 | t = Hmac::from_engine(engine).to_byte_array().to_vec(); 38 | output.extend_from_slice(&t); 39 | 40 | i += 1; 41 | } 42 | 43 | output.truncate(output_len); 44 | output 45 | } 46 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/nostrdb.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr_ndb::NdbDatabase; 6 | use nostr_sdk::prelude::*; 7 | 8 | const BECH32_SK: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85"; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | tracing_subscriber::fmt::init(); 13 | 14 | let keys = Keys::parse(BECH32_SK)?; 15 | 16 | let database = NdbDatabase::open("./db/ndb")?; 17 | let client: Client = Client::builder() 18 | .signer(keys.clone()) 19 | .database(database) 20 | .build(); 21 | 22 | client.add_relay("wss://relay.damus.io").await?; 23 | client.add_relay("wss://atl.purplerelay.com").await?; 24 | client.connect().await; 25 | 26 | // Publish a text note 27 | let builder = EventBuilder::text_note("Hello world"); 28 | client.send_event_builder(builder).await?; 29 | 30 | // Negentropy reconcile 31 | let filter = Filter::new().author(keys.public_key()); 32 | client.sync(filter, &SyncOptions::default()).await?; 33 | 34 | // Query events from database 35 | let filter = Filter::new().author(keys.public_key()).limit(10); 36 | let events = client.database().query(filter).await?; 37 | println!("Events: {events:?}"); 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /gossip/nostr-gossip/src/flags.rs: -------------------------------------------------------------------------------- 1 | //! Gossip flags 2 | 3 | /// Gossip flags 4 | #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 5 | pub struct GossipFlags(u32); 6 | 7 | impl GossipFlags { 8 | /// Empty flags. 9 | pub const NONE: Self = Self(0); // 0 10 | 11 | /// Read flag. 12 | pub const READ: Self = Self(1 << 0); // 1 13 | 14 | /// Write flag. 15 | pub const WRITE: Self = Self(1 << 1); // 2 16 | 17 | /// Private message (NIP-17) flag. 18 | pub const PRIVATE_MESSAGE: Self = Self(1 << 2); // 4 19 | 20 | /// Hint flag. 21 | pub const HINT: Self = Self(1 << 3); // 8 22 | 23 | /// Received flag. 24 | pub const RECEIVED: Self = Self(1 << 4); // 16 25 | 26 | /// New empty flags. 27 | #[inline] 28 | pub const fn new() -> Self { 29 | Self::NONE 30 | } 31 | 32 | /// Add flag. 33 | #[inline] 34 | pub const fn add(&mut self, other: Self) { 35 | self.0 |= other.0; 36 | } 37 | 38 | /// Remove flag. 39 | #[inline] 40 | pub const fn remove(&mut self, other: Self) { 41 | self.0 ^= other.0; 42 | } 43 | 44 | /// Check if has flag. 45 | #[inline] 46 | pub const fn has(&self, other: Self) -> bool { 47 | self.0 & other.0 != 0 48 | } 49 | 50 | /// Get flags as [`u32`]. 51 | #[inline] 52 | pub const fn as_u32(&self) -> u32 { 53 | self.0 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/status.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_sdk::prelude::*; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | tracing_subscriber::fmt::init(); 12 | 13 | let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; 14 | let client = Client::new(keys); 15 | 16 | client.add_relay("wss://relay.damus.io").await?; 17 | client.add_relay("wss://nostr.wine").await?; 18 | client.add_relay("wss://relay.rip").await?; 19 | 20 | client.connect().await; 21 | 22 | // Send a General statuses event to relays 23 | let general = LiveStatus::new(StatusType::General); 24 | let builder = EventBuilder::live_status(general, "Building rust-nostr"); 25 | client.send_event_builder(builder).await?; 26 | 27 | // Send a Music statuses event to relays 28 | let music = LiveStatus { 29 | status_type: StatusType::Music, 30 | expiration: Some(Timestamp::now() + Duration::from_secs(60 * 60 * 24)), 31 | reference: Some("spotify:search:Intergalatic%20-%20Beastie%20Boys".into()), 32 | }; 33 | let builder = EventBuilder::live_status(music, "Intergalatic - Beastie Boys"); 34 | client.send_event_builder(builder).await?; 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/code_snippet.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr_sdk::prelude::*; 6 | 7 | const EXAMPLE_SNIPPET: &str = include_str!("code_snippet.rs"); 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | tracing_subscriber::fmt::init(); 12 | 13 | let keys = Keys::generate(); 14 | let client = Client::new(keys); 15 | 16 | client.add_relay("wss://relay.damus.io").await?; 17 | client.add_relay("wss://nos.lol").await?; 18 | client.add_relay("wss://nostr.mom").await?; 19 | 20 | client.connect().await; 21 | 22 | // Build a code snippet for this example :) 23 | let snippet: CodeSnippet = CodeSnippet::new(EXAMPLE_SNIPPET) 24 | .name("code_snippts.rs") 25 | .description("Snippet that snippet itself") 26 | .language("rust") 27 | .extension("rs") 28 | .license("MIT"); 29 | 30 | let builder = EventBuilder::code_snippet(snippet); 31 | 32 | let event = client.send_event_builder(builder).await?; 33 | let nevent = Nip19Event::new(*event.id()).relays(vec![ 34 | RelayUrl::parse("wss://nos.lol")?, 35 | RelayUrl::parse("wss://nostr.mom")?, 36 | ]); 37 | 38 | tracing::info!("Done, check the event `{}`", nevent.to_bech32()?); 39 | 40 | client.shutdown().await; 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /crates/nostr/src/nips/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! NIPs 6 | //! 7 | //! See all at 8 | 9 | pub mod nip01; 10 | pub mod nip02; 11 | #[cfg(feature = "nip04")] 12 | pub mod nip04; 13 | pub mod nip05; 14 | #[cfg(feature = "nip06")] 15 | pub mod nip06; 16 | pub mod nip09; 17 | pub mod nip10; 18 | pub mod nip11; 19 | pub mod nip13; 20 | pub mod nip15; 21 | pub mod nip17; 22 | pub mod nip19; 23 | pub mod nip21; 24 | pub mod nip22; 25 | pub mod nip25; 26 | pub mod nip34; 27 | pub mod nip35; 28 | pub mod nip38; 29 | pub mod nip39; 30 | pub mod nip42; 31 | #[cfg(feature = "nip44")] 32 | pub mod nip44; 33 | #[cfg(all(feature = "std", feature = "nip46"))] 34 | pub mod nip46; 35 | #[cfg(feature = "nip47")] 36 | pub mod nip47; 37 | pub mod nip48; 38 | #[cfg(feature = "nip49")] 39 | pub mod nip49; 40 | pub mod nip51; 41 | pub mod nip53; 42 | pub mod nip56; 43 | #[cfg(feature = "nip57")] 44 | pub mod nip57; 45 | pub mod nip58; 46 | #[cfg(feature = "nip59")] 47 | pub mod nip59; 48 | #[cfg(feature = "nip60")] 49 | pub mod nip60; 50 | pub mod nip62; 51 | pub mod nip65; 52 | pub mod nip73; 53 | pub mod nip88; 54 | pub mod nip90; 55 | pub mod nip94; 56 | #[cfg(all(feature = "std", feature = "nip96"))] 57 | pub mod nip96; 58 | #[cfg(feature = "nip98")] 59 | pub mod nip98; 60 | pub mod nipb0; 61 | pub mod nipc0; 62 | -------------------------------------------------------------------------------- /crates/nostr/examples/parser.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | fn main() -> Result<()> { 8 | let parser = NostrParser::new(); 9 | 10 | let text: &str = "I have never been very active in discussions but working on rust-nostr (at the time called nostr-rs-sdk) since September 2022 🦀 \n\nIf I remember correctly there were also nostr:nprofile1qqsqfyvdlsmvj0nakmxq6c8n0c2j9uwrddjd8a95ynzn9479jhlth3gpvemhxue69uhkv6tvw3jhytnwdaehgu3wwa5kuef0dec82c33w94xwcmdd3cxketedsux6ertwecrgues0pk8xdrew33h27pkd4unvvpkw3nkv7pe0p68gat58ycrw6ps0fenwdnvva48w0mzwfhkzerrv9ehg0t5wf6k2qgnwaehxw309ac82unsd3jhqct89ejhxtcpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsh8njvk and nostr:nprofile1qqswuyd9ml6qcxd92h6pleptfrcqucvvjy39vg4wx7mv9wm8kakyujgpypmhxue69uhkx6r0wf6hxtndd94k2erfd3nk2u3wvdhk6w35xs6z7qgwwaehxw309ahx7uewd3hkctcpypmhxue69uhkummnw3ezuetfde6kuer6wasku7nfvuh8xurpvdjj7a0nq40"; 11 | 12 | for token in parser.parse(text) { 13 | println!("{token:?}"); 14 | } 15 | 16 | for token in parser.parse("Check this: https://example.com/foo/bar.html") { 17 | println!("{token:?}"); 18 | } 19 | 20 | let opts = NostrParserOptions::disable_all().urls(true); 21 | 22 | for token in parser.parse("Check this: https://example.com").opts(opts) { 23 | println!("{token:?}"); 24 | } 25 | 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/client.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr_sdk::prelude::*; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<()> { 9 | tracing_subscriber::fmt::init(); 10 | 11 | let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; 12 | let client = Client::new(keys); 13 | 14 | client.add_relay("wss://relay.damus.io").await?; 15 | client.add_relay("wss://nostr.wine").await?; 16 | client.add_relay("wss://relay.rip").await?; 17 | 18 | client.connect().await; 19 | 20 | // Publish a text note 21 | let builder = EventBuilder::text_note("Hello world"); 22 | let output = client.send_event_builder(builder).await?; 23 | println!("Event ID: {}", output.id().to_bech32()?); 24 | println!("Sent to: {:?}", output.success); 25 | println!("Not sent to: {:?}", output.failed); 26 | 27 | // Create a text note POW event to relays 28 | let builder = EventBuilder::text_note("POW text note from rust-nostr").pow(20); 29 | client.send_event_builder(builder).await?; 30 | 31 | // Send a text note POW event to specific relays 32 | let builder = EventBuilder::text_note("POW text note from rust-nostr 16").pow(16); 33 | client 34 | .send_event_builder_to(["wss://relay.damus.io", "wss://relay.rip"], builder) 35 | .await?; 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-sqlite/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Gossip SQLite error 2 | 3 | use std::fmt; 4 | use std::num::TryFromIntError; 5 | 6 | use tokio::sync::AcquireError; 7 | 8 | /// Gossip SQLite error 9 | #[derive(Debug)] 10 | pub enum Error { 11 | /// TryFromInt error 12 | TryFromInt(TryFromIntError), 13 | /// SQLx error 14 | Sqlx(sqlx::Error), 15 | /// SQLx migration error 16 | Migrate(sqlx::migrate::MigrateError), 17 | /// Failed to acquire semaphore 18 | Acquire(AcquireError), 19 | } 20 | 21 | impl std::error::Error for Error {} 22 | 23 | impl fmt::Display for Error { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | match self { 26 | Self::TryFromInt(e) => e.fmt(f), 27 | Self::Sqlx(e) => e.fmt(f), 28 | Self::Migrate(e) => e.fmt(f), 29 | Self::Acquire(e) => e.fmt(f), 30 | } 31 | } 32 | } 33 | 34 | impl From for Error { 35 | fn from(err: TryFromIntError) -> Self { 36 | Self::TryFromInt(err) 37 | } 38 | } 39 | 40 | impl From for Error { 41 | fn from(err: sqlx::Error) -> Self { 42 | Self::Sqlx(err) 43 | } 44 | } 45 | 46 | impl From for Error { 47 | fn from(err: sqlx::migrate::MigrateError) -> Self { 48 | Self::Migrate(err) 49 | } 50 | } 51 | 52 | impl From for Error { 53 | fn from(err: AcquireError) -> Self { 54 | Self::Acquire(err) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /rfs/nostr-blossom/examples/download.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use clap::Parser; 4 | use nostr::hashes::sha256::Hash as Sha256Hash; 5 | use nostr::prelude::*; 6 | use nostr_blossom::prelude::*; 7 | 8 | #[derive(Parser, Debug)] 9 | #[command(author, version, about = "Download a blob from a Blossom server", long_about = None)] 10 | struct Args { 11 | /// The server URL to connect to 12 | #[arg(long)] 13 | server: Url, 14 | 15 | /// SHA256 hash of the blob to download 16 | #[arg(long)] 17 | sha256: Sha256Hash, 18 | 19 | /// Private key to use for authorization 20 | #[arg(long)] 21 | private_key: SecretKey, 22 | } 23 | 24 | #[tokio::main] 25 | async fn main() -> Result<()> { 26 | let args = Args::parse(); 27 | 28 | // Initialize the client. 29 | let client = BlossomClient::new(args.server); 30 | 31 | // Parse the private key. 32 | let keypair = Keys::new(args.private_key); 33 | 34 | // Download the blob with optional authorization. 35 | match client 36 | .get_blob(args.sha256, None, None, Some(&keypair)) 37 | .await 38 | { 39 | Ok(blob) => { 40 | println!("Successfully downloaded blob with {} bytes", blob.len()); 41 | let file_name = format!("{}", args.sha256); 42 | fs::write(&file_name, &blob)?; 43 | println!("Blob saved as {}", file_name); 44 | } 45 | Err(e) => { 46 | eprintln!("{e}"); 47 | } 48 | } 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /crates/nostr-relay-builder/src/mock.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! A mock relay for (unit) tests. 6 | 7 | use std::ops::Deref; 8 | 9 | use nostr::prelude::*; 10 | use nostr_database::prelude::*; 11 | 12 | use crate::builder::{LocalRelayBuilder, LocalRelayTestOptions}; 13 | use crate::error::Error; 14 | use crate::local::LocalRelay; 15 | 16 | /// A mock relay for (unit) tests. 17 | /// 18 | /// Check [`LocalRelay`] for more details. 19 | #[derive(Debug, Clone)] 20 | pub struct MockRelay { 21 | local: LocalRelay, 22 | } 23 | 24 | impl Deref for MockRelay { 25 | type Target = LocalRelay; 26 | 27 | fn deref(&self) -> &Self::Target { 28 | &self.local 29 | } 30 | } 31 | 32 | impl MockRelay { 33 | async fn new(builder: LocalRelayBuilder) -> Result { 34 | let relay: LocalRelay = builder.build(); 35 | relay.run().await?; 36 | Ok(Self { local: relay }) 37 | } 38 | 39 | /// Run mock relay 40 | #[inline] 41 | pub async fn run() -> Result { 42 | let builder = LocalRelayBuilder::default(); 43 | Self::new(builder).await 44 | } 45 | 46 | /// Run unresponsive relay 47 | #[inline] 48 | pub async fn run_with_opts(opts: LocalRelayTestOptions) -> Result { 49 | let builder = LocalRelayBuilder::default().test(opts); 50 | Self::new(builder).await 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crates/nwc/examples/nwc.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::str::FromStr; 6 | 7 | use nwc::prelude::*; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | tracing_subscriber::fmt::init(); 12 | 13 | let mut nwc_uri_string = String::new(); 14 | 15 | println!("Please enter a NWC string"); 16 | std::io::stdin() 17 | .read_line(&mut nwc_uri_string) 18 | .expect("Failed to read line"); 19 | 20 | // Parse URI and compose NWC client 21 | let uri: NostrWalletConnectUri = 22 | NostrWalletConnectUri::from_str(&nwc_uri_string).expect("Failed to parse NWC URI"); 23 | 24 | // Create monitor and subscribe to it 25 | let monitor = Monitor::new(100); 26 | let mut monitor_sub = monitor.subscribe(); 27 | tokio::spawn(async move { 28 | while let Ok(notification) = monitor_sub.recv().await { 29 | println!("Notification: {notification:?}"); 30 | } 31 | }); 32 | 33 | // Create NWC client with monitor 34 | let nwc = NostrWalletConnect::builder(uri).monitor(monitor).build(); 35 | 36 | // Get balance 37 | let balance = nwc.get_balance().await?; 38 | println!("Balance: {balance} msat"); 39 | 40 | let request: PayInvoiceRequest = PayInvoiceRequest::new(""); 41 | let response = nwc.pay_invoice(request).await?; 42 | println!("Response: {response:?}"); 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /crates/nostr/examples/nip05.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | fn main() -> Result<()> { 8 | let public_key = 9 | PublicKey::parse("b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a")?; 10 | 11 | let address = Nip05Address::parse("0xtr@oxtr.dev")?; 12 | 13 | println!("Url: {}", address.url()); 14 | 15 | let json = r#"{ 16 | "names": { 17 | "nostr-tool-test-user": "94a9eb13c37b3c1519169a426d383c51530f4fe8f693c62f32b321adfdd4ec7f", 18 | "robosatsob": "3b57518d02e6acfd5eb7198530b2e351e5a52278fb2499d14b66db2b5791c512", 19 | "0xtr": "b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a", 20 | "_": "b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a" 21 | }, 22 | "relays": { 23 | "b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a": [ "wss://nostr.oxtr.dev", "wss://relay.damus.io", "wss://relay.nostr.band" ] 24 | } 25 | }"#; 26 | 27 | if nip05::verify_from_raw_json(&public_key, &address, json)? { 28 | println!("NIP05 verified"); 29 | } else { 30 | println!("NIP05 NOT verified"); 31 | } 32 | 33 | let profile: Nip05Profile = Nip05Profile::from_raw_json(&address, json)?; 34 | println!("Public key: {}", profile.public_key); 35 | println!("Relays: {:?}", profile.relays); 36 | println!("Relays (NIP46): {:?}", profile.nip46); 37 | 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /crates/nostr/src/nips/nip48.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! NIP48: Proxy Tags 6 | //! 7 | //! 8 | 9 | use alloc::string::{String, ToString}; 10 | use core::fmt; 11 | 12 | /// NIP48 Proxy Protocol 13 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | pub enum Protocol { 15 | /// ActivityPub 16 | ActivityPub, 17 | /// AT Protocol 18 | ATProto, 19 | /// Rss 20 | Rss, 21 | /// Web 22 | Web, 23 | /// Custom 24 | Custom(String), 25 | } 26 | 27 | impl fmt::Display for Protocol { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | f.write_str(self.as_str()) 30 | } 31 | } 32 | 33 | impl Protocol { 34 | /// Get as `&str` 35 | pub fn as_str(&self) -> &str { 36 | match self { 37 | Self::ActivityPub => "activitypub", 38 | Self::ATProto => "atproto", 39 | Self::Rss => "rss", 40 | Self::Web => "web", 41 | Self::Custom(m) => m.as_str(), 42 | } 43 | } 44 | } 45 | 46 | impl From for Protocol 47 | where 48 | S: AsRef, 49 | { 50 | fn from(s: S) -> Self { 51 | match s.as_ref() { 52 | "activitypub" => Self::ActivityPub, 53 | "atproto" => Self::ATProto, 54 | "rss" => Self::Rss, 55 | "web" => Self::Web, 56 | s => Self::Custom(s.to_string()), 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Nostr Relay Pool 6 | 7 | #![forbid(unsafe_code)] 8 | #![warn(missing_docs)] 9 | #![warn(rustdoc::bare_urls)] 10 | #![warn(clippy::large_futures)] 11 | #![allow(unknown_lints)] // TODO: remove when MSRV >= 1.72.0, required for `clippy::arc_with_non_send_sync` 12 | #![allow(clippy::arc_with_non_send_sync)] 13 | #![allow(clippy::mutable_key_type)] // TODO: remove when possible. Needed to suppress false positive for `BTreeSet` 14 | #![cfg_attr(bench, feature(test))] 15 | 16 | #[cfg(bench)] 17 | extern crate test; 18 | 19 | pub use async_wsocket::ConnectionMode; 20 | 21 | pub mod monitor; 22 | pub mod policy; 23 | pub mod pool; 24 | pub mod prelude; 25 | pub mod relay; 26 | #[doc(hidden)] 27 | mod shared; 28 | pub mod stream; 29 | pub mod transport; 30 | 31 | pub use self::pool::options::RelayPoolOptions; 32 | pub use self::pool::{Output, RelayPool, RelayPoolNotification}; 33 | pub use self::relay::flags::{AtomicRelayServiceFlags, RelayServiceFlags}; 34 | pub use self::relay::limits::RelayLimits; 35 | pub use self::relay::options::{ 36 | RelayOptions, SubscribeAutoCloseOptions, SubscribeOptions, SyncDirection, SyncOptions, 37 | }; 38 | pub use self::relay::stats::RelayConnectionStats; 39 | pub use self::relay::{Reconciliation, Relay, RelayNotification, RelayStatus}; 40 | 41 | // Not public API. 42 | #[doc(hidden)] 43 | pub mod __private { 44 | #[doc(hidden)] 45 | pub use super::shared::{SharedState, SharedStateError}; 46 | } 47 | -------------------------------------------------------------------------------- /crates/nostr/examples/embedded/README.md: -------------------------------------------------------------------------------- 1 | # Embedded 2 | 3 | ## Running 4 | 5 | To run the embedded test, first prepare your environment: 6 | 7 | ```shell 8 | make init 9 | ``` 10 | 11 | Then: 12 | 13 | ```shell 14 | make run 15 | ``` 16 | 17 | Output should be something like: 18 | 19 | ```text 20 | heap size 262144 21 | 22 | Restored keys from bech32: 23 | - Secret Key (hex): 9571a568a42b9e05646a349c783159b906b498119390df9a5a02667155128028 24 | - Public Key (hex): aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4 25 | - Secret Key (bech32): nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99 26 | - Public Key (bech32): npub14f8usejl26twx0dhuxjh9cas7keav9vr0v8nvtwtrjqx3vycc76qqh9nsy 27 | 28 | Restore keys from mnemonic: 29 | - Secret Key (hex): 06992419a8fe821dd8de03d4c300614e8feefb5ea936b76f89976dcace8aebee 30 | - Public Key (hex): 648777b13344158549551f215ab2885d71af8861456eebea7102b1c729fc2de2 31 | - Secret Key (bech32): nsec1q6vjgxdgl6ppmkx7q02vxqrpf687a7674ymtwmufjaku4n52a0hq9glmaf 32 | - Public Key (bech32): npub1vjrh0vfngs2c2j24rus44v5gt4c6lzrpg4hwh6n3q2cuw20u9h3q4qf4pg 33 | 34 | Random keys (using FakeRng): 35 | - Secret Key (hex): 3939393939393939393939393939393939393939393939393939393939393939 36 | - Public Key (hex): 1ff10be221c7b140505038042f5cc86530e9851a0e6c70ee16c18268768c2e02 37 | - Secret Key (bech32): nsec18yunjwfe8yunjwfe8yunjwfe8yunjwfe8yunjwfe8yunjwfe8yusu2d2eh 38 | - Public Key (bech32): npub1rlcshc3pc7c5q5zs8qzz7hxgv5cwnpg6pek8pmskcxpxsa5v9cpqqk7k0t 39 | ``` 40 | 41 | Note that this heap size is required because of the amount of stack used by libsecp256k1 when initializing a context. 42 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer-proxy/examples/browser-signer-proxy.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_browser_signer_proxy::prelude::*; 8 | use tokio::{signal, time}; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | tracing_subscriber::fmt::init(); 13 | 14 | let proxy = BrowserSignerProxy::new(BrowserSignerProxyOptions::default()); 15 | 16 | proxy.start().await?; 17 | 18 | println!("Url: {}", proxy.url()); 19 | 20 | // Waits until the proxy session becomes active, checking every second. 21 | loop { 22 | if proxy.is_session_active() { 23 | break; 24 | } 25 | time::sleep(Duration::from_secs(1)).await; 26 | } 27 | 28 | // Get public key 29 | let public_key = proxy.get_public_key().await?; 30 | println!("Public key: {}", public_key); 31 | 32 | // Sign event 33 | let event = EventBuilder::text_note("Testing browser signer proxy") 34 | .sign(&proxy) 35 | .await?; 36 | println!("Event: {}", event.as_json()); 37 | 38 | // Build a gift wrap 39 | let receiver = 40 | PublicKey::parse("npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet")?; 41 | let rumor = EventBuilder::new(Kind::Custom(123), "test").build(public_key); 42 | let gift_wrap = EventBuilder::gift_wrap(&proxy, &receiver, rumor, []).await?; 43 | println!("Gift wrap: {}", gift_wrap.as_json()); 44 | 45 | // Keep up the program 46 | signal::ctrl_c().await?; 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /signer/nostr-connect/examples/handle-auth-url.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_connect::prelude::*; 8 | 9 | #[derive(Debug, Clone)] 10 | struct MyAuthUrlHandler; 11 | 12 | impl AuthUrlHandler for MyAuthUrlHandler { 13 | fn on_auth_url(&self, auth_url: Url) -> BoxedFuture> { 14 | Box::pin(async move { 15 | println!("Opening auth url: {auth_url}"); 16 | webbrowser::open(auth_url.as_str())?; 17 | Ok(()) 18 | }) 19 | } 20 | } 21 | 22 | #[tokio::main] 23 | async fn main() -> Result<()> { 24 | tracing_subscriber::fmt::init(); 25 | 26 | let uri = NostrConnectUri::parse("bunker://79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3?relay=wss://relay.nsec.app")?; 27 | let app_keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; 28 | let timeout = Duration::from_secs(120); 29 | 30 | let mut connect = NostrConnect::new(uri, app_keys, timeout, None)?; 31 | 32 | // Set auth_url handler 33 | connect.auth_url_handler(MyAuthUrlHandler); 34 | 35 | let receiver = 36 | PublicKey::parse("npub1acg6thl5psv62405rljzkj8spesceyfz2c32udakc2ak0dmvfeyse9p35c")?; 37 | let content = connect.nip44_encrypt(&receiver, "Hi").await?; 38 | println!("Content: {content}"); 39 | 40 | let event = EventBuilder::text_note("Testing rust-nostr") 41 | .sign(&connect) 42 | .await?; 43 | println!("Event: {}", event.as_json()); 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /rfs/nostr-blossom/examples/list.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use nostr::prelude::*; 3 | use nostr_blossom::prelude::*; 4 | 5 | #[derive(Parser, Debug)] 6 | #[command(author, version, about = "List blob on a Blossom server", long_about = None)] 7 | struct Args { 8 | /// The server URL to connect to 9 | #[arg(long)] 10 | server: Url, 11 | 12 | /// The public key to list blobs for 13 | #[arg(long)] 14 | pubkey: PublicKey, 15 | 16 | /// Optional private key for authorization (in hex) 17 | #[arg(long)] 18 | private_key: Option, 19 | } 20 | 21 | #[tokio::main] 22 | async fn main() -> Result<()> { 23 | let args = Args::parse(); 24 | let client = BlossomClient::new(args.server); 25 | 26 | // Check if a private key was provided and branch accordingly 27 | if let Some(private_key) = args.private_key { 28 | // Attempt to create the secret key, propagating error if parsing fails 29 | let keys = Keys::new(private_key); 30 | 31 | let descriptors = client 32 | .list_blobs(&args.pubkey, None, None, None, Some(&keys)) 33 | .await?; 34 | 35 | println!("Successfully listed blobs (with auth):"); 36 | for descriptor in descriptors { 37 | println!("{:?}", descriptor); 38 | } 39 | } else { 40 | let descriptors = client 41 | .list_blobs(&args.pubkey, None, None, None, None::<&Keys>) 42 | .await?; 43 | 44 | println!("Successfully listed blobs (without auth):"); 45 | for descriptor in descriptors { 46 | println!("{:?}", descriptor); 47 | } 48 | } 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Organization guidelines 4 | 5 | This project follows the rust-nostr organization guidelines: https://github.com/rust-nostr/guidelines 6 | 7 | ## Additional repository guidelines 8 | 9 | ### Commit Style 10 | 11 | The commit **must** be formatted as follows: 12 | 13 | ``` 14 | : 15 | 16 | 17 | ``` 18 | 19 | If applicable, link the `issue`/`PR` to be closed with: 20 | 21 | - Closes 22 | - Fixes 23 | 24 | The `context` **must be**: 25 | 26 | - `nostr` for changes to the `nostr` crate 27 | - `sdk`, `cli`, `relay-pool`, `connect`, `nwc` and so on for the others crates (remove the `nostr-` prefix) 28 | - `test` for changes to the unit tests 29 | - `doc` for changes to the documentation 30 | - `contrib` for changes to the scripts and tools 31 | - `ci` for changes to the CI code 32 | - `refactor` for structural changes that do not change behavior 33 | 34 | ### Examples 35 | 36 | ``` 37 | nostr: add NIP32 support 38 | 39 | Added kinds, tags and EventBuilder constructors to support NIP32. 40 | 41 | Closes https://.com/rust-nostr/nostr/issue/1234 42 | ``` 43 | 44 | ``` 45 | pool: fix connection issues 46 | 47 | Long description... 48 | 49 | Fixes https://.com/rust-nostr/nostr/issue/5612 50 | ``` 51 | 52 | ``` 53 | nwc: add `pay_multiple_invoices` support 54 | 55 | Closes https://.com/rust-nostr/nostr/issue/2222 56 | ``` 57 | 58 | ### Coding Conventions 59 | 60 | Install https://github.com/casey/just and use `just precommit` or `just ci` 61 | to format and check the code before committing. 62 | The CI also enforces this. 63 | -------------------------------------------------------------------------------- /crates/nostr/src/nips/nip10.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! NIP10: Conventions for clients' use of `e` and `p` tags in text events 6 | //! 7 | //! 8 | 9 | use core::fmt; 10 | use core::str::FromStr; 11 | 12 | /// NIP10 error 13 | #[derive(Debug, PartialEq, Eq)] 14 | pub enum Error { 15 | /// Invalid marker 16 | InvalidMarker, 17 | } 18 | 19 | #[cfg(feature = "std")] 20 | impl std::error::Error for Error {} 21 | 22 | impl fmt::Display for Error { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | match self { 25 | Self::InvalidMarker => f.write_str("invalid marker"), 26 | } 27 | } 28 | } 29 | 30 | /// Marker 31 | /// 32 | /// 33 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 34 | pub enum Marker { 35 | /// Root 36 | Root, 37 | /// Reply 38 | Reply, 39 | } 40 | 41 | impl fmt::Display for Marker { 42 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 43 | match self { 44 | Self::Root => f.write_str("root"), 45 | Self::Reply => f.write_str("reply"), 46 | } 47 | } 48 | } 49 | 50 | impl FromStr for Marker { 51 | type Err = Error; 52 | 53 | fn from_str(marker: &str) -> Result { 54 | match marker { 55 | "root" => Ok(Self::Root), 56 | "reply" => Ok(Self::Reply), 57 | _ => Err(Error::InvalidMarker), 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/nostr-relay-builder/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Relay builder error 6 | 7 | use std::{fmt, io}; 8 | 9 | #[cfg(feature = "tor")] 10 | use async_wsocket::native::tor; 11 | use nostr_relay_pool::pool; 12 | 13 | /// Relay builder error 14 | #[derive(Debug)] 15 | pub enum Error { 16 | /// I/O error 17 | IO(io::Error), 18 | /// Tor error 19 | #[cfg(feature = "tor")] 20 | Tor(tor::Error), 21 | /// Relay pool error 22 | RelayPool(pool::Error), 23 | /// Relay already running 24 | AlreadyRunning, 25 | /// Premature exit 26 | PrematureExit, 27 | } 28 | 29 | impl std::error::Error for Error {} 30 | 31 | impl fmt::Display for Error { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | match self { 34 | Self::IO(e) => write!(f, "{e}"), 35 | #[cfg(feature = "tor")] 36 | Self::Tor(e) => write!(f, "{e}"), 37 | Self::RelayPool(e) => e.fmt(f), 38 | Self::AlreadyRunning => write!(f, "the relay is already running"), 39 | Self::PrematureExit => write!(f, "premature exit"), 40 | } 41 | } 42 | } 43 | 44 | impl From for Error { 45 | fn from(e: io::Error) -> Self { 46 | Self::IO(e) 47 | } 48 | } 49 | 50 | #[cfg(feature = "tor")] 51 | impl From for Error { 52 | fn from(e: tor::Error) -> Self { 53 | Self::Tor(e) 54 | } 55 | } 56 | 57 | impl From for Error { 58 | fn from(e: pool::Error) -> Self { 59 | Self::RelayPool(e) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /crates/nostr-relay-builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-relay-builder" 3 | version = "0.44.0" 4 | edition = "2021" 5 | description = "Build your own custom nostr relay!" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "relay", "builder"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [features] 19 | default = [] 20 | tor = ["async-wsocket/tor-launch-service"] 21 | 22 | [dependencies] 23 | async-utility.workspace = true 24 | async-wsocket.workspace = true 25 | atomic-destructor.workspace = true 26 | hex = { workspace = true, features = ["std"] } 27 | negentropy = { workspace = true, features = ["std"] } 28 | nostr = { workspace = true, default-features = false, features = ["std"] } 29 | nostr-database.workspace = true 30 | nostr-relay-pool.workspace = true 31 | tokio = { workspace = true, features = ["macros", "net", "sync"] } 32 | tracing.workspace = true 33 | 34 | [dev-dependencies] 35 | base64 = { workspace = true, features = ["std"] } 36 | hyper = { version = "1.6", features = ["server", "http1"] } 37 | hyper-util = { version = "0.1", features = ["tokio"] } 38 | nostr-lmdb.workspace = true 39 | tokio = { workspace = true, features = ["signal"] } 40 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 41 | 42 | [[example]] 43 | name = "hyper" 44 | 45 | [[example]] 46 | name = "local-with-hs" 47 | required-features = ["tor"] 48 | 49 | [[example]] 50 | name = "mock" 51 | 52 | [[example]] 53 | name = "policy" 54 | 55 | [[example]] 56 | name = "simple_relay" 57 | -------------------------------------------------------------------------------- /database/nostr-sqlite/migrations/001_init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE events( 2 | id BLOB PRIMARY KEY NOT NULL CHECK(length(id) = 32), 3 | pubkey BLOB NOT NULL CHECK(length(pubkey) = 32), 4 | created_at INTEGER NOT NULL CHECK(created_at > 0), 5 | kind INTEGER NOT NULL CHECK(kind >= 0 AND kind <= 65535), 6 | content TEXT NOT NULL, 7 | tags JSONB NOT NULL, 8 | sig BLOB NOT NULL CHECK(length(sig) = 64) 9 | ) WITHOUT ROWID; 10 | 11 | CREATE INDEX idx_events_pubkey ON events(pubkey); 12 | CREATE INDEX idx_events_created_at ON events(created_at DESC); 13 | CREATE INDEX idx_events_kind ON events(kind); 14 | CREATE INDEX idx_events_pubkey_created_at ON events(pubkey, created_at DESC); 15 | CREATE INDEX idx_events_pubkey_kind_created_at ON events(pubkey, kind, created_at DESC); 16 | CREATE INDEX idx_events_kind_created_at ON events(kind, created_at DESC); 17 | 18 | CREATE TABLE event_tags( 19 | event_id BLOB NOT NULL CHECK(length(event_id) = 32), 20 | tag_name TEXT NOT NULL CHECK(length(tag_name) >= 1), 21 | tag_value TEXT NOT NULL, 22 | FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE, 23 | PRIMARY KEY (tag_name, tag_value, event_id) 24 | ); 25 | 26 | CREATE INDEX idx_tags_event_id ON event_tags(event_id); 27 | 28 | CREATE TABLE deleted_ids ( 29 | event_id BLOB PRIMARY KEY NOT NULL CHECK(length(event_id) = 32) 30 | ) WITHOUT ROWID; 31 | 32 | CREATE TABLE deleted_coordinates ( 33 | pubkey BLOB NOT NULL CHECK(length(pubkey) = 32), 34 | kind INTEGER NOT NULL CHECK(kind >= 0 AND kind <= 65535), 35 | identifier TEXT NOT NULL, 36 | deleted_at INTEGER NOT NULL CHECK(deleted_at > 0), 37 | PRIMARY KEY (pubkey, kind, identifier) 38 | ) WITHOUT ROWID; 39 | -------------------------------------------------------------------------------- /signer/nostr-connect/examples/nostr-connect-signer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use dialoguer::Confirm; 6 | use nostr_connect::prelude::*; 7 | 8 | const SIGNER_SECRET_KEY: &str = "nsec12kcgs78l06p30jz7z7h3n2x2cy99nw2z6zspjdp7qc206887mwvs95lnkx"; 9 | const USER_SECRET_KEY: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85"; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<()> { 13 | tracing_subscriber::fmt::init(); 14 | 15 | let keys = NostrConnectKeys { 16 | signer: Keys::parse(SIGNER_SECRET_KEY)?, 17 | user: Keys::parse(USER_SECRET_KEY)?, 18 | }; 19 | 20 | // Compose signer 21 | let signer = NostrConnectRemoteSigner::new(keys, ["wss://relay.nsec.app"], None, None)?; 22 | 23 | // Compose signer from URI 24 | // let uri = NostrConnectUri::parse("nostrconnect://...")?; 25 | // let signer = NostrConnectRemoteSigner::from_uri(uri, keys, None, None)?; 26 | 27 | // Print bunker URI 28 | let uri = signer.bunker_uri(); 29 | println!("\n{uri}\n"); 30 | 31 | // Serve signer 32 | signer.serve(CustomActions).await?; 33 | 34 | Ok(()) 35 | } 36 | 37 | struct CustomActions; 38 | 39 | impl NostrConnectSignerActions for CustomActions { 40 | fn approve(&self, public_key: &PublicKey, req: &NostrConnectRequest) -> bool { 41 | println!("Public key: {public_key}"); 42 | println!("{req:#?}\n"); 43 | Confirm::new() 44 | .with_prompt("Approve request?") 45 | .default(false) 46 | .interact() 47 | .unwrap_or_default() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /gossip/nostr-gossip-sqlite/migrations/001_init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE public_keys( 2 | id INTEGER PRIMARY KEY AUTOINCREMENT, 3 | public_key BLOB NOT NULL UNIQUE, 4 | CHECK (length(public_key) = 32) 5 | ); 6 | 7 | CREATE INDEX idx_public_keys_public_key ON public_keys(public_key); 8 | 9 | CREATE TABLE lists( 10 | id INTEGER PRIMARY KEY AUTOINCREMENT, 11 | public_key_id INTEGER NOT NULL, 12 | event_kind INTEGER NOT NULL, 13 | event_created_at INTEGER DEFAULT NULL, 14 | last_checked_at INTEGER DEFAULT NULL, 15 | UNIQUE(public_key_id, event_kind), 16 | FOREIGN KEY (public_key_id) REFERENCES public_keys(id) ON DELETE CASCADE 17 | ); 18 | 19 | CREATE INDEX idx_lists_pub_kind ON lists(public_key_id, event_kind); 20 | 21 | CREATE TABLE relays( 22 | id INTEGER PRIMARY KEY AUTOINCREMENT, 23 | url TEXT NOT NULL UNIQUE, 24 | CHECK (length(url) > 0) 25 | ); 26 | 27 | CREATE INDEX idx_relays_url ON relays(url); 28 | 29 | CREATE TABLE relays_per_user( 30 | id INTEGER PRIMARY KEY AUTOINCREMENT, 31 | public_key_id INTEGER NOT NULL, 32 | relay_id INTEGER NOT NULL, 33 | bitflags INTEGER NOT NULL DEFAULT 0, 34 | received_events INTEGER NOT NULL DEFAULT 0, 35 | last_received_event INTEGER NOT NULL DEFAULT 0, 36 | UNIQUE(public_key_id, relay_id), 37 | FOREIGN KEY (public_key_id) REFERENCES public_keys(id) ON DELETE CASCADE, 38 | FOREIGN KEY (relay_id) REFERENCES relays(id) ON DELETE CASCADE 39 | ); 40 | 41 | CREATE INDEX idx_rpu_pub_relay ON relays_per_user(public_key_id, relay_id); 42 | CREATE INDEX idx_rpu_pub_flags_rank ON relays_per_user(public_key_id, bitflags, received_events DESC, last_received_event DESC); 43 | CREATE INDEX idx_rpu_relay ON relays_per_user(relay_id); 44 | -------------------------------------------------------------------------------- /crates/nostr/src/event/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use alloc::string::{String, ToString}; 6 | use core::fmt; 7 | 8 | use crate::signer::SignerError; 9 | 10 | /// Event error 11 | #[derive(Debug, PartialEq)] 12 | pub enum Error { 13 | /// Error serializing or deserializing JSON data 14 | Json(String), 15 | /// Signer error 16 | Signer(String), 17 | /// Hex decode error 18 | Hex(hex::FromHexError), 19 | /// Unknown JSON event key 20 | UnknownKey(String), 21 | /// Invalid event ID 22 | InvalidId, 23 | /// Invalid signature 24 | InvalidSignature, 25 | } 26 | 27 | #[cfg(feature = "std")] 28 | impl std::error::Error for Error {} 29 | 30 | impl fmt::Display for Error { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | match self { 33 | Self::Json(e) => e.fmt(f), 34 | Self::Signer(e) => e.fmt(f), 35 | Self::Hex(e) => e.fmt(f), 36 | Self::UnknownKey(key) => write!(f, "Unknown key: {key}"), 37 | Self::InvalidId => f.write_str("Invalid event ID"), 38 | Self::InvalidSignature => f.write_str("Invalid signature"), 39 | } 40 | } 41 | } 42 | 43 | impl From for Error { 44 | fn from(e: serde_json::Error) -> Self { 45 | Self::Json(e.to_string()) 46 | } 47 | } 48 | 49 | impl From for Error { 50 | fn from(e: SignerError) -> Self { 51 | Self::Signer(e.to_string()) 52 | } 53 | } 54 | 55 | impl From for Error { 56 | fn from(e: hex::FromHexError) -> Self { 57 | Self::Hex(e) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/pool/options.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Pool options 6 | 7 | use super::constants::DEFAULT_NOTIFICATION_CHANNEL_SIZE; 8 | 9 | /// Relay Pool Options 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 11 | pub struct RelayPoolOptions { 12 | pub(super) max_relays: Option, 13 | pub(super) nip42_auto_authentication: bool, 14 | pub(super) notification_channel_size: usize, 15 | } 16 | 17 | impl Default for RelayPoolOptions { 18 | fn default() -> Self { 19 | Self { 20 | max_relays: None, 21 | nip42_auto_authentication: true, 22 | notification_channel_size: DEFAULT_NOTIFICATION_CHANNEL_SIZE, 23 | } 24 | } 25 | } 26 | 27 | impl RelayPoolOptions { 28 | /// New default options 29 | #[inline] 30 | pub fn new() -> Self { 31 | Self::default() 32 | } 33 | 34 | /// Max relays (default: None) 35 | #[inline] 36 | pub fn max_relays(mut self, num: Option) -> Self { 37 | self.max_relays = num; 38 | self 39 | } 40 | 41 | /// Auto authenticate to relays (default: true) 42 | /// 43 | /// 44 | #[inline] 45 | pub fn automatic_authentication(mut self, enabled: bool) -> Self { 46 | self.nip42_auto_authentication = enabled; 47 | self 48 | } 49 | 50 | /// Notification channel size (default: [`DEFAULT_NOTIFICATION_CHANNEL_SIZE`]) 51 | #[inline] 52 | pub fn notification_channel_size(mut self, size: usize) -> Self { 53 | self.notification_channel_size = size; 54 | self 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/switch-account.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_sdk::prelude::*; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<()> { 11 | tracing_subscriber::fmt::init(); 12 | 13 | // Account 1 14 | let keys1 = Keys::parse("nsec12kcgs78l06p30jz7z7h3n2x2cy99nw2z6zspjdp7qc206887mwvs95lnkx")?; 15 | let client = Client::new(keys1.clone()); 16 | 17 | client.add_relay("wss://relay.damus.io").await?; 18 | client.connect().await; 19 | 20 | // Subscribe 21 | let filter = Filter::new() 22 | .author(keys1.public_key) 23 | .kind(Kind::TextNote) 24 | .limit(10); 25 | client.subscribe(filter, None).await?; 26 | 27 | // Wait a little 28 | tokio::time::sleep(Duration::from_secs(20)).await; 29 | 30 | println!("Switching account..."); 31 | 32 | // Reset client to change account 33 | client.reset().await; 34 | 35 | // Account 2 36 | let keys2 = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; 37 | client.set_signer(keys2.clone()).await; 38 | 39 | client.add_relay("wss://nostr.oxtr.dev").await?; 40 | client.connect().await; 41 | 42 | println!("Account switched"); 43 | 44 | // Subscribe 45 | let filter = Filter::new() 46 | .author(keys2.public_key) 47 | .kind(Kind::TextNote) 48 | .limit(5); 49 | client.subscribe(filter, None).await?; 50 | 51 | client 52 | .handle_notifications(|notification| async move { 53 | println!("{notification:?}"); 54 | Ok(false) 55 | }) 56 | .await?; 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /database/nostr-indexeddb/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## v0.44.0 - 2025/11/06 27 | 28 | No notable changes in this release. 29 | 30 | ## v0.43.0 - 2025/07/28 31 | 32 | No notable changes in this release. 33 | 34 | ## v0.42.0 - 2025/05/20 35 | 36 | No notable changes in this release. 37 | 38 | ## v0.41.0 - 2025/04/15 39 | 40 | No notable changes in this release. 41 | 42 | ## v0.40.0 - 2025/03/18 43 | 44 | No notable changes in this release. 45 | 46 | ## v0.39.0 - 2025/01/31 47 | 48 | No notable changes in this release. 49 | 50 | ## v0.38.0 - 2024/12/31 51 | 52 | ### Removed 53 | 54 | - Remove `thiserror` and `tracing` deps 55 | 56 | ## v0.37.0 - 2024/11/27 57 | 58 | No notable changes in this release. 59 | 60 | ## v0.36.0 - 2024/11/05 61 | 62 | No notable changes in this release. 63 | 64 | ## v0.35.0 - 2024/09/19 65 | 66 | No notable changes in this release. 67 | 68 | ## v0.34.0 - 2024/08/15 69 | 70 | ### Added 71 | 72 | - Allow opening database with limited capacity 73 | 74 | ## v0.33.0 - 2024/07/16 75 | 76 | No notable changes in this release. 77 | 78 | ## v0.32.0 - 2024/06/07 79 | 80 | No notable changes in this release. 81 | 82 | ## v0.31.0 - 2024/05/17 83 | 84 | No notable changes in this release. 85 | 86 | ## v0.30.0 - 2024/04/15 87 | 88 | No notable changes in this release. 89 | -------------------------------------------------------------------------------- /contrib/scripts/check-crates.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | buildargs=( 6 | "-p nostr" # Only std feature 7 | "-p nostr --features all-nips" # std + all-nips 8 | "-p nostr --no-default-features --features alloc" # Only alloc feature 9 | "-p nostr --no-default-features --features alloc,all-nips" # alloc + all-nips 10 | "-p nostr-browser-signer --target wasm32-unknown-unknown" 11 | "-p nostr-browser-signer-proxy" 12 | "-p nostr-blossom" 13 | "-p nostr-http-file-storage" 14 | "-p nostr-database" 15 | "-p nostr-database-test-suite" 16 | "-p nostr-gossip" 17 | "-p nostr-gossip-memory" 18 | "-p nostr-gossip-sqlite" 19 | "-p nostr-gossip-test-suite" 20 | "-p nostr-lmdb" 21 | "-p nostr-sqlite" 22 | "-p nostr-indexeddb --target wasm32-unknown-unknown" 23 | "-p nostr-ndb" 24 | "-p nostr-keyring" 25 | "-p nostr-keyring --features async" 26 | "-p nostr-relay-pool" 27 | "-p nostr-relay-builder" 28 | "-p nostr-connect" 29 | "-p nwc" 30 | "-p nostr-sdk" # No default features 31 | "-p nostr-sdk --features all-nips" # Only NIPs features 32 | "-p nostr-sdk --features tor" # Embedded tor client 33 | "-p nostr-sdk --all-features" # All features 34 | ) 35 | 36 | for arg in "${buildargs[@]}"; 37 | do 38 | echo "Checking '$arg'" 39 | 40 | cargo check $arg 41 | 42 | if [[ $arg != *"--target wasm32-unknown-unknown"* ]]; 43 | then 44 | cargo test $arg 45 | fi 46 | 47 | cargo clippy $arg -- -D warnings 48 | 49 | echo 50 | done 51 | -------------------------------------------------------------------------------- /database/nostr-database/README.md: -------------------------------------------------------------------------------- 1 | # Nostr (Events) Database 2 | 3 | Events database abstraction and in-memory implementation for nostr apps. 4 | 5 | ## Nostr Database Trait 6 | 7 | This library contains the `NostrDatabase` and `NostrDatabaseExt` traits. You can use the [default backends](#default-backends) or implement your one (like PostgreSQL, ...). 8 | 9 | ## Default backends 10 | 11 | * Memory (RAM, both native and web), available in this library 12 | * LMDB (native), available at [`nostr-lmdb`](https://crates.io/crates/nostr-lmdb) 13 | * [nostrdb](https://github.com/damus-io/nostrdb) (native), available at [`nostr-ndb`](https://crates.io/crates/nostr-ndb) 14 | * IndexedDB (web), available at [`nostr-indexeddb`](https://crates.io/crates/nostr-indexeddb) 15 | 16 | ## Crate Feature Flags 17 | 18 | The following crate feature flags are available: 19 | 20 | | Feature | Default | Description | 21 | |-----------|:-------:|--------------------------------------------------------| 22 | | `flatbuf` | No | Enable `flatbuffers` de/serialization for nostr events | 23 | 24 | ## Changelog 25 | 26 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 27 | 28 | ## State 29 | 30 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 31 | 32 | ## Donations 33 | 34 | `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](https://rust-nostr.org/donate). 35 | 36 | ## License 37 | 38 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 39 | -------------------------------------------------------------------------------- /crates/nostr-sdk/src/client/middleware.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use nostr::util::BoxedFuture; 4 | use nostr::{Event, RelayUrl, SubscriptionId}; 5 | use nostr_gossip::NostrGossip; 6 | use nostr_relay_pool::policy::{AdmitPolicy, AdmitStatus, PolicyError}; 7 | 8 | #[derive(Debug)] 9 | pub(crate) struct AdmissionPolicyMiddleware { 10 | pub(crate) gossip: Option>, 11 | pub(crate) external_policy: Option>, 12 | } 13 | 14 | impl AdmitPolicy for AdmissionPolicyMiddleware { 15 | fn admit_connection<'a>( 16 | &'a self, 17 | relay_url: &'a RelayUrl, 18 | ) -> BoxedFuture<'a, Result> { 19 | Box::pin(async move { 20 | match &self.external_policy { 21 | Some(policy) => policy.admit_connection(relay_url).await, 22 | None => Ok(AdmitStatus::Success), 23 | } 24 | }) 25 | } 26 | 27 | fn admit_event<'a>( 28 | &'a self, 29 | relay_url: &'a RelayUrl, 30 | subscription_id: &'a SubscriptionId, 31 | event: &'a Event, 32 | ) -> BoxedFuture<'a, Result> { 33 | Box::pin(async move { 34 | // Process event in gossip 35 | if let Some(gossip) = &self.gossip { 36 | gossip 37 | .process(event, Some(relay_url)) 38 | .await 39 | .map_err(PolicyError::backend)?; 40 | } 41 | 42 | // Check if event is allowed by external policy 43 | match &self.external_policy { 44 | Some(policy) => policy.admit_event(relay_url, subscription_id, event).await, 45 | None => Ok(AdmitStatus::Success), 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /database/nostr-indexeddb/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::fmt; 6 | 7 | use nostr_database::DatabaseError; 8 | 9 | /// IndexedDB error 10 | #[derive(Debug)] 11 | pub enum IndexedDBError { 12 | /// DOM error 13 | DomException { 14 | /// DomException code 15 | code: u16, 16 | /// Specific name of the DomException 17 | name: String, 18 | /// Message given to the DomException 19 | message: String, 20 | }, 21 | /// Mutex poisoned 22 | MutexPoisoned, 23 | } 24 | 25 | impl std::error::Error for IndexedDBError {} 26 | 27 | impl fmt::Display for IndexedDBError { 28 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 29 | match self { 30 | Self::DomException { 31 | name, 32 | code, 33 | message, 34 | } => write!(f, "DomException {name} ({code}): {message}"), 35 | Self::MutexPoisoned => write!(f, "mutex poisoned"), 36 | } 37 | } 38 | } 39 | 40 | impl From for IndexedDBError { 41 | fn from(frm: indexed_db_futures::web_sys::DomException) -> Self { 42 | Self::DomException { 43 | name: frm.name(), 44 | message: frm.message(), 45 | code: frm.code(), 46 | } 47 | } 48 | } 49 | 50 | impl From for DatabaseError { 51 | fn from(e: IndexedDBError) -> Self { 52 | Self::backend(e) 53 | } 54 | } 55 | 56 | pub(crate) fn into_err(e: indexed_db_futures::web_sys::DomException) -> DatabaseError { 57 | let indexed_err: IndexedDBError = e.into(); 58 | DatabaseError::backend(indexed_err) 59 | } 60 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/examples/pool.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_relay_pool::prelude::*; 8 | use tracing_subscriber::fmt::format::FmtSpan; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | tracing_subscriber::fmt::fmt() 13 | .with_span_events(FmtSpan::CLOSE) 14 | .with_env_filter("info,nostr_relay_pool::relay::internal=trace") 15 | .init(); 16 | 17 | { 18 | let pool = RelayPool::default(); 19 | 20 | pool.add_relay("wss://relay.damus.io", RelayOptions::default()) 21 | .await?; 22 | pool.connect().await; 23 | 24 | let event = Event::from_json(r#"{"content":"","created_at":1698412975,"id":"f55c30722f056e330d8a7a6a9ba1522f7522c0f1ced1c93d78ea833c78a3d6ec","kind":3,"pubkey":"f831caf722214748c72db4829986bd0cbb2bb8b3aeade1c959624a52a9629046","sig":"5092a9ffaecdae7d7794706f085ff5852befdf79df424cc3419bb797bf515ae05d4f19404cb8324b8b4380a4bd497763ac7b0f3b1b63ef4d3baa17e5f5901808","tags":[["p","4ddeb9109a8cd29ba279a637f5ec344f2479ee07df1f4043f3fe26d8948cfef9","",""],["p","bb6fd06e156929649a73e6b278af5e648214a69d88943702f1fb627c02179b95","",""],["p","b8b8210f33888fdbf5cedee9edf13c3e9638612698fe6408aff8609059053420","",""],["p","9dcee4fabcd690dc1da9abdba94afebf82e1e7614f4ea92d61d52ef9cd74e083","",""],["p","3eea9e831fefdaa8df35187a204d82edb589a36b170955ac5ca6b88340befaa0","",""],["p","885238ab4568f271b572bf48b9d6f99fa07644731f288259bd395998ee24754e","",""],["p","568a25c71fba591e39bebe309794d5c15d27dbfa7114cacb9f3586ea1314d126","",""]]}"#).unwrap(); 25 | pool.send_event(&event).await?; 26 | } 27 | // Pool dropped 28 | 29 | tokio::time::sleep(Duration::from_secs(10)).await; 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /rfs/nostr-blossom/examples/upload.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | use clap::Parser; 5 | use nostr::prelude::*; 6 | use nostr_blossom::prelude::*; 7 | 8 | #[derive(Parser, Debug)] 9 | #[command(author, version, about = "Upload a blob to a Blossom server", long_about = None)] 10 | struct Args { 11 | /// The server URL to connect to 12 | #[arg(long)] 13 | server: Url, 14 | 15 | /// Path to the file to upload 16 | #[arg(long)] 17 | file: PathBuf, 18 | 19 | /// Optional content type (e.g., "application/octet-stream") 20 | #[arg(long)] 21 | content_type: Option, 22 | 23 | /// Optional private key for signing the upload 24 | #[arg(long)] 25 | private_key: Option, 26 | } 27 | 28 | #[tokio::main] 29 | async fn main() -> Result<()> { 30 | let args = Args::parse(); 31 | 32 | let client = BlossomClient::new(args.server); 33 | 34 | // Read file data from the specified file path. 35 | let data = fs::read(&args.file)?; 36 | 37 | // Use the provided content type or default to "application/octet-stream" 38 | let content_type = args 39 | .content_type 40 | .clone() 41 | .or_else(|| Some("application/octet-stream".to_string())); 42 | 43 | // Create signer keys. 44 | // If a private key is provided, try to use it; otherwise generate a new key. 45 | let keys = match args.private_key { 46 | Some(private_key) => Keys::new(private_key), 47 | None => Keys::generate(), 48 | }; 49 | 50 | match client 51 | .upload_blob(data, content_type, None, Some(&keys)) 52 | .await 53 | { 54 | Ok(descriptor) => { 55 | println!("Successfully uploaded blob: {:?}", descriptor); 56 | } 57 | Err(e) => { 58 | eprintln!("{e}"); 59 | } 60 | } 61 | 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /crates/nostr-keyring/README.md: -------------------------------------------------------------------------------- 1 | # Nostr Keyring 2 | 3 | Thin wrapper around the system keyring that stores `Keys` objects without forcing you to handle secret material manually. 4 | The crate keeps all serialization in-memory and relies on the OS-provided credential store (macOS Keychain, Windows Credential Manager, Secret Service, etc.). 5 | 6 | ## Getting started 7 | 8 | ```rust,no_run 9 | use nostr_keyring::prelude::*; 10 | 11 | fn main() -> Result<()> { 12 | let keyring = NostrKeyring::new("my-nostr-app"); 13 | 14 | // Save a key 15 | let keys = Keys::generate(); 16 | keyring.set("example", &keys)?; 17 | 18 | // Get it 19 | let restored: Keys = keyring.get("example")?; 20 | 21 | assert_eq!(keys.public_key(), restored.public_key()); 22 | 23 | Ok(()) 24 | } 25 | ``` 26 | 27 | More examples can be found in the [examples directory](./examples). 28 | 29 | ## Crate Feature Flags 30 | 31 | The following crate feature flags are available: 32 | 33 | | Feature | Default | Description | 34 | |---------|:-------:|-------------------------------------------| 35 | | `async` | No | Enable async APIs | 36 | 37 | ## Changelog 38 | 39 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 40 | 41 | ## State 42 | 43 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 44 | 45 | ## Donations 46 | 47 | `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](https://rust-nostr.org/donate). 48 | 49 | ## License 50 | 51 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 52 | -------------------------------------------------------------------------------- /database/nostr-sqlite/src/model.rs: -------------------------------------------------------------------------------- 1 | use nostr::event::Event; 2 | use nostr::secp256k1::schnorr::Signature; 3 | use nostr::{EventId, Kind, PublicKey, SingleLetterTag, Tags, Timestamp}; 4 | use sqlx::types::Json; 5 | use sqlx::FromRow; 6 | 7 | use crate::error::Error; 8 | 9 | #[derive(Debug, Clone, FromRow)] 10 | pub(crate) struct EventDb { 11 | pub id: Vec, 12 | pub pubkey: Vec, 13 | pub created_at: i64, 14 | pub kind: u16, 15 | pub content: String, 16 | pub tags: Json, 17 | pub sig: Vec, 18 | } 19 | 20 | impl EventDb { 21 | #[allow(clippy::wrong_self_convention)] 22 | pub(crate) fn to_event(self) -> Result { 23 | let id: EventId = EventId::from_slice(&self.id)?; 24 | let pubkey: PublicKey = PublicKey::from_slice(&self.pubkey)?; 25 | let created_at: Timestamp = self.created_at.try_into()?; 26 | let kind: Kind = Kind::from_u16(self.kind); 27 | let tags: Tags = self.tags.0; 28 | let sig: Signature = Signature::from_slice(&self.sig)?; 29 | 30 | Ok(Event::new( 31 | id, 32 | pubkey, 33 | created_at, 34 | kind, 35 | tags, 36 | self.content, 37 | sig, 38 | )) 39 | } 40 | } 41 | 42 | pub(crate) struct EventTagDb<'a> { 43 | pub event_id: &'a [u8], 44 | pub tag_name: SingleLetterTag, 45 | pub tag_value: &'a str, 46 | } 47 | 48 | pub(crate) fn extract_tags(event: &Event) -> impl Iterator { 49 | event.tags.iter().filter_map(|tag| { 50 | if let (Some(kind), Some(content)) = (tag.single_letter_tag(), tag.content()) { 51 | Some(EventTagDb { 52 | event_id: event.id.as_bytes(), 53 | tag_name: kind, 54 | tag_value: content, 55 | }) 56 | } else { 57 | None 58 | } 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /crates/nwc/src/builder.rs: -------------------------------------------------------------------------------- 1 | //! Nostr Wallet Connect builder 2 | 3 | use std::time::Duration; 4 | 5 | use nostr::nips::nip47::NostrWalletConnectUri; 6 | use nostr_relay_pool::monitor::Monitor; 7 | use nostr_relay_pool::relay::options::RelayOptions; 8 | 9 | use crate::NostrWalletConnect; 10 | 11 | /// Default timeout 12 | const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60); 13 | 14 | /// Nostr Wallet Connect builder 15 | #[derive(Debug, Clone)] 16 | pub struct NostrWalletConnectBuilder { 17 | /// NWC URI. 18 | pub uri: NostrWalletConnectUri, 19 | /// Requests timeout. 20 | pub timeout: Duration, 21 | /// Relay monitor. 22 | pub monitor: Option, 23 | /// Relay options. 24 | /// 25 | /// See [`RelayOptions`] for more details. 26 | pub relay: RelayOptions, 27 | } 28 | 29 | impl NostrWalletConnectBuilder { 30 | /// Construct a new Nostr Wallet Connect client builder. 31 | pub fn new(uri: NostrWalletConnectUri) -> Self { 32 | Self { 33 | uri, 34 | timeout: DEFAULT_TIMEOUT, 35 | monitor: None, 36 | relay: RelayOptions::default(), 37 | } 38 | } 39 | 40 | /// Set requests timeout (default: 60 secs) 41 | #[inline] 42 | pub fn timeout(mut self, timeout: Duration) -> Self { 43 | self.timeout = timeout; 44 | self 45 | } 46 | 47 | /// Set the relay monitor 48 | #[inline] 49 | pub fn monitor(mut self, monitor: Monitor) -> Self { 50 | self.monitor = Some(monitor); 51 | self 52 | } 53 | 54 | /// Set relay options 55 | #[inline] 56 | pub fn relay(mut self, opts: RelayOptions) -> Self { 57 | self.relay = opts; 58 | self 59 | } 60 | 61 | /// Build [`NostrWalletConnect`] client. 62 | #[inline] 63 | pub fn build(self) -> NostrWalletConnect { 64 | NostrWalletConnect::from_builder(self) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/pool/output.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::collections::{HashMap, HashSet}; 6 | use std::fmt::Debug; 7 | use std::ops::{Deref, DerefMut}; 8 | 9 | use nostr::{EventId, RelayUrl, SubscriptionId}; 10 | 11 | /// Output 12 | /// 13 | /// Send or negentropy reconciliation output 14 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 15 | pub struct Output 16 | where 17 | T: Debug, 18 | { 19 | /// Value 20 | pub val: T, 21 | /// Set of relays that success 22 | pub success: HashSet, 23 | /// Map of relays that failed, with related errors. 24 | pub failed: HashMap, 25 | } 26 | 27 | impl Deref for Output 28 | where 29 | T: Debug, 30 | { 31 | type Target = T; 32 | 33 | fn deref(&self) -> &Self::Target { 34 | &self.val 35 | } 36 | } 37 | 38 | impl DerefMut for Output 39 | where 40 | T: Debug, 41 | { 42 | fn deref_mut(&mut self) -> &mut Self::Target { 43 | &mut self.val 44 | } 45 | } 46 | 47 | impl Output 48 | where 49 | T: Debug, 50 | { 51 | /// Create a new output 52 | #[must_use] 53 | pub fn new(val: T) -> Self { 54 | Self { 55 | val, 56 | success: HashSet::new(), 57 | failed: HashMap::new(), 58 | } 59 | } 60 | 61 | /// Get inner value 62 | #[inline] 63 | #[must_use] 64 | pub fn into_inner(self) -> T { 65 | self.val 66 | } 67 | } 68 | 69 | impl Output { 70 | /// Get event ID 71 | #[inline] 72 | pub fn id(&self) -> &EventId { 73 | self.deref() 74 | } 75 | } 76 | 77 | impl Output { 78 | /// Get subscription ID 79 | #[inline] 80 | pub fn id(&self) -> &SubscriptionId { 81 | self.deref() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /database/nostr-ndb/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## v0.44.0 - 2025/11/06 27 | 28 | No notable changes in this release. 29 | 30 | ## v0.43.0 - 2025/07/28 31 | 32 | ### Changed 33 | 34 | - Bump `nostrdb` from 0.6 to 0.8 35 | 36 | ## v0.42.0 - 2025/05/20 37 | 38 | No notable changes in this release. 39 | 40 | ## v0.41.0 - 2025/04/15 41 | 42 | ### Changed 43 | 44 | - Bump `nostrdb` to 0.6.1 45 | 46 | ## v0.40.0 - 2025/03/18 47 | 48 | ### Changed 49 | 50 | - Return `None` in `NostrEventsDatabase::event_by_id` if event doesn't exist 51 | - Avoid event clone when calling `NostrEventsDatabase::save_event` 52 | 53 | ## v0.39.0 - 2025/01/31 54 | 55 | ### Changed 56 | 57 | - Refactor note-to-event conversion 58 | 59 | ## v0.38.0 - 2024/12/31 60 | 61 | ### Changed 62 | 63 | - Bump `nostrdb` to 0.5 64 | 65 | ## v0.37.0 - 2024/11/27 66 | 67 | No notable changes in this release. 68 | 69 | ## v0.36.0 - 2024/11/05 70 | 71 | No notable changes in this release. 72 | 73 | ## v0.35.0 - 2024/09/19 74 | 75 | No notable changes in this release. 76 | 77 | ## v0.34.0 - 2024/08/15 78 | 79 | No notable changes in this release. 80 | 81 | ## v0.33.0 - 2024/07/16 82 | 83 | No notable changes in this release. 84 | 85 | ## v0.32.0 - 2024/06/07 86 | 87 | No notable changes in this release. 88 | 89 | ## v0.31.0 - 2024/05/17 90 | 91 | ### Changed 92 | 93 | - Bump `nostrdb` to 0.3.3 94 | 95 | ## v0.30.0 - 2024/04/15 96 | 97 | No notable changes in this release. 98 | -------------------------------------------------------------------------------- /database/nostr-sqlite/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error 2 | 3 | use std::fmt; 4 | use std::num::TryFromIntError; 5 | 6 | use nostr::{event, key, secp256k1}; 7 | use sqlx::migrate::MigrateError; 8 | 9 | /// Nostr SQL error 10 | #[derive(Debug)] 11 | pub enum Error { 12 | /// TryFromInt error 13 | TryFromInt(TryFromIntError), 14 | /// SQLx error 15 | Sqlx(sqlx::Error), 16 | /// Migration error 17 | Migrate(MigrateError), 18 | /// Secp256k1 error 19 | Secp256k1(secp256k1::Error), 20 | /// Event error 21 | Event(event::Error), 22 | /// Key error 23 | Key(key::Error), 24 | } 25 | 26 | impl std::error::Error for Error {} 27 | 28 | impl fmt::Display for Error { 29 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 | match self { 31 | Self::TryFromInt(e) => write!(f, "{e}"), 32 | Self::Sqlx(e) => write!(f, "{e}"), 33 | Self::Migrate(e) => write!(f, "{e}"), 34 | Self::Secp256k1(e) => write!(f, "{e}"), 35 | Self::Event(e) => write!(f, "{e}"), 36 | Self::Key(e) => write!(f, "{e}"), 37 | } 38 | } 39 | } 40 | 41 | impl From for Error { 42 | fn from(e: TryFromIntError) -> Self { 43 | Self::TryFromInt(e) 44 | } 45 | } 46 | 47 | impl From for Error { 48 | fn from(e: sqlx::Error) -> Self { 49 | Self::Sqlx(e) 50 | } 51 | } 52 | 53 | impl From for Error { 54 | fn from(e: MigrateError) -> Self { 55 | Self::Migrate(e) 56 | } 57 | } 58 | 59 | impl From for Error { 60 | fn from(e: secp256k1::Error) -> Self { 61 | Self::Secp256k1(e) 62 | } 63 | } 64 | 65 | impl From for Error { 66 | fn from(e: event::Error) -> Self { 67 | Self::Event(e) 68 | } 69 | } 70 | 71 | impl From for Error { 72 | fn from(e: key::Error) -> Self { 73 | Self::Key(e) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/tor.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr_sdk::prelude::*; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<()> { 9 | tracing_subscriber::fmt::init(); 10 | 11 | // Parse keys 12 | let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; 13 | 14 | // Configure client to use embedded tor for `.onion` relays 15 | let connection: Connection = Connection::new() 16 | .embedded_tor() 17 | .target(ConnectionTarget::Onion); 18 | let opts = ClientOptions::new().connection(connection); 19 | let client = Client::builder().signer(keys.clone()).opts(opts).build(); 20 | 21 | // Add relays 22 | client.add_relay("wss://relay.damus.io").await?; 23 | client 24 | .add_relay("ws://oxtrdevav64z64yb7x6rjg4ntzqjhedm5b5zjqulugknhzr46ny2qbad.onion") 25 | .await?; 26 | client 27 | .add_relay("ws://2jsnlhfnelig5acq6iacydmzdbdmg7xwunm4xl6qwbvzacw4lwrjmlyd.onion") 28 | .await?; 29 | 30 | client.connect().await; 31 | 32 | let filter: Filter = Filter::new().pubkey(keys.public_key()).limit(0); 33 | client.subscribe(filter, None).await?; 34 | 35 | // Handle subscription notifications with `handle_notifications` method 36 | client 37 | .handle_notifications(|notification| async { 38 | if let RelayPoolNotification::Event { event, .. } = notification { 39 | if event.kind == Kind::GiftWrap { 40 | let UnwrappedGift { rumor, .. } = client.unwrap_gift_wrap(&event).await?; 41 | println!("Rumor: {}", rumor.as_json()); 42 | } else { 43 | println!("{:?}", event); 44 | } 45 | } 46 | Ok(false) // Set to true to exit from the loop 47 | }) 48 | .await?; 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/nostr-connect.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_connect::prelude::*; 8 | use nostr_sdk::prelude::*; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | tracing_subscriber::fmt::init(); 13 | 14 | let app_keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; 15 | 16 | // Compose signer from bunker URI 17 | let uri = NostrConnectUri::parse("bunker://79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3?relay=wss://relay.nsec.app")?; 18 | let signer = NostrConnect::new(uri, app_keys, Duration::from_secs(120), None)?; 19 | 20 | // Compose signer 21 | // let uri = NostrConnectUri::client( 22 | // app_keys.public_key(), 23 | // [Url::parse("wss://relay.nsec.app")?], 24 | // "Test app", 25 | // ); 26 | // println!("\n{uri}\n"); 27 | // let signer = NostrConnect::new(uri, app_keys, Duration::from_secs(120), None)?; 28 | 29 | // Get bunker URI for future connections 30 | let bunker_uri: NostrConnectUri = signer.bunker_uri().await?; 31 | println!("\nBunker URI: {bunker_uri}\n"); 32 | 33 | // Compose client 34 | let client = Client::new(signer); 35 | client.add_relay("wss://relay.damus.io").await?; 36 | client.connect().await; 37 | 38 | // Publish events 39 | let builder = EventBuilder::text_note("Testing rust-nostr NIP46 signer [bunker]"); 40 | let output = client.send_event_builder(builder).await?; 41 | println!("Published text note: {}\n", output.id()); 42 | 43 | let receiver = 44 | PublicKey::from_bech32("npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet")?; 45 | let output = client 46 | .send_private_msg(receiver, "Hello from rust-nostr", []) 47 | .await?; 48 | println!("Sent DM: {}", output.id()); 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /rfs/nostr-http-file-storage/README.md: -------------------------------------------------------------------------------- 1 | # Nostr HTTP File Storage client (NIP-96) 2 | 3 | Client for [NIP-96](https://github.com/nostr-protocol/nips/blob/master/96.md) working with servers. 4 | Handles discovery of `nip96.json`, authenticated uploads, and returns the download URL you can embed inside events. 5 | 6 | ```rust,no_run 7 | use nostr_http_file_storage::prelude::*; 8 | 9 | # #[tokio::main] 10 | async fn main() -> Result<(), Box> { 11 | let client = NostrHttpFileStorageClient::new(); 12 | let server = Url::parse("https://files.example.com")?; 13 | 14 | // Fetch nip96.json to learn limits and auth requirements 15 | let config = client.get_server_config(&server).await?; 16 | 17 | let signer = Keys::parse("")?; 18 | let download_url = client 19 | .upload( 20 | &signer, 21 | &config, 22 | b"hello nostr".to_vec(), 23 | Some("text/plain"), 24 | ) 25 | .await?; 26 | 27 | println!("File available at {download_url}"); 28 | Ok(()) 29 | } 30 | ``` 31 | 32 | ## Crate Feature Flags 33 | 34 | The following crate feature flags are available: 35 | 36 | | Feature | Default | Description | 37 | |---------|:-------:|--------------------------------| 38 | | `socks` | No | Enable support for socks proxy | 39 | 40 | ## Changelog 41 | 42 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 43 | 44 | ## State 45 | 46 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 47 | 48 | ## Donations 49 | 50 | `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](https://rust-nostr.org/donate). 51 | 52 | ## License 53 | 54 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 55 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/blacklist.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::collections::HashSet; 6 | use std::time::Duration; 7 | 8 | use nostr_sdk::prelude::*; 9 | 10 | #[derive(Debug, Default)] 11 | struct Filtering { 12 | muted_public_keys: HashSet, 13 | } 14 | 15 | impl AdmitPolicy for Filtering { 16 | fn admit_event<'a>( 17 | &'a self, 18 | _relay_url: &'a RelayUrl, 19 | _subscription_id: &'a SubscriptionId, 20 | event: &'a Event, 21 | ) -> BoxedFuture<'a, Result> { 22 | Box::pin(async move { 23 | if self.muted_public_keys.contains(&event.pubkey) { 24 | return Ok(AdmitStatus::rejected("Muted")); 25 | } 26 | 27 | Ok(AdmitStatus::success()) 28 | }) 29 | } 30 | } 31 | 32 | #[tokio::main] 33 | async fn main() -> Result<()> { 34 | tracing_subscriber::fmt::init(); 35 | 36 | let mut filtering = Filtering::default(); 37 | 38 | // Mute public key 39 | let muted_public_key = 40 | PublicKey::from_bech32("npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft")?; 41 | filtering.muted_public_keys.insert(muted_public_key); 42 | 43 | // Init client 44 | let client = Client::builder().admit_policy(filtering).build(); 45 | client.add_relay("wss://relay.damus.io").await?; 46 | client.connect().await; 47 | 48 | // Get events from all connected relays 49 | let public_key = 50 | PublicKey::from_bech32("npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s")?; 51 | let filter = Filter::new() 52 | .authors([muted_public_key, public_key]) 53 | .kind(Kind::Metadata); 54 | let events = client.fetch_events(filter, Duration::from_secs(10)).await?; 55 | println!("Received {} events.", events.len()); 56 | 57 | assert_eq!(events.len(), 1); 58 | assert_eq!(events.first_owned().unwrap().pubkey, public_key); 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /crates/nostr/src/types/image.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Image 6 | 7 | use core::fmt; 8 | use core::num::ParseIntError; 9 | use core::str::{FromStr, Split}; 10 | 11 | /// Image error 12 | #[derive(Debug, PartialEq, Eq)] 13 | pub enum Error { 14 | /// Impossible to parse integer 15 | ParseIntError(ParseIntError), 16 | /// Invalid Image Dimensions 17 | InvalidDimensions, 18 | } 19 | 20 | #[cfg(feature = "std")] 21 | impl std::error::Error for Error {} 22 | 23 | impl fmt::Display for Error { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | match self { 26 | Self::ParseIntError(e) => e.fmt(f), 27 | Self::InvalidDimensions => f.write_str("Invalid dimensions"), 28 | } 29 | } 30 | } 31 | 32 | impl From for Error { 33 | fn from(e: ParseIntError) -> Self { 34 | Self::ParseIntError(e) 35 | } 36 | } 37 | 38 | /// Simple struct to hold `width` x `height`. 39 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 40 | pub struct ImageDimensions { 41 | /// Width 42 | pub width: u64, 43 | /// Height 44 | pub height: u64, 45 | } 46 | 47 | impl ImageDimensions { 48 | /// Net image dimensions 49 | #[inline] 50 | pub fn new(width: u64, height: u64) -> Self { 51 | Self { width, height } 52 | } 53 | } 54 | 55 | impl fmt::Display for ImageDimensions { 56 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 57 | write!(f, "{}x{}", self.width, self.height) 58 | } 59 | } 60 | 61 | impl FromStr for ImageDimensions { 62 | type Err = Error; 63 | 64 | fn from_str(s: &str) -> Result { 65 | let mut spitted: Split = s.split('x'); 66 | if let (Some(width), Some(height)) = (spitted.next(), spitted.next()) { 67 | Ok(Self::new(width.parse()?, height.parse()?)) 68 | } else { 69 | Err(Error::InvalidDimensions) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/gossip.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_gossip_memory::prelude::*; 8 | use nostr_sdk::prelude::*; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | tracing_subscriber::fmt::init(); 13 | 14 | let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; 15 | let gossip = NostrGossipMemory::unbounded(); 16 | let client = Client::builder().signer(keys).gossip(gossip).build(); 17 | 18 | client.add_discovery_relay("wss://relay.damus.io").await?; 19 | client.add_discovery_relay("wss://purplepag.es").await?; 20 | //client.add_discovery_relay("ws://oxtrdevav64z64yb7x6rjg4ntzqjhedm5b5zjqulugknhzr46ny2qbad.onion").await?; 21 | 22 | // client.add_relay("wss://relay.snort.social").await?; 23 | // client.add_relay("wss://relay.damus.io").await?; 24 | 25 | client.connect().await; 26 | 27 | // Publish a text note 28 | let pubkey = 29 | PublicKey::parse("npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet")?; 30 | 31 | let builder = EventBuilder::text_note( 32 | "Hello world nostr:npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet", 33 | ) 34 | .tag(Tag::public_key(pubkey)); 35 | let output = client.send_event_builder(builder).await?; 36 | println!("Event ID: {}", output.to_bech32()?); 37 | 38 | println!("Sent to:"); 39 | for url in output.success.into_iter() { 40 | println!("- {url}"); 41 | } 42 | 43 | println!("Not sent to:"); 44 | for (url, reason) in output.failed.into_iter() { 45 | println!("- {url}: {reason:?}"); 46 | } 47 | 48 | // Get events 49 | let filter = Filter::new().author(pubkey).kind(Kind::TextNote).limit(3); 50 | let events = client.fetch_events(filter, Duration::from_secs(10)).await?; 51 | 52 | for event in events.into_iter() { 53 | println!("{}", event.as_json()); 54 | } 55 | 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/relay/constants.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Relay constants 6 | 7 | use core::ops::RangeInclusive; 8 | use core::time::Duration; 9 | 10 | pub(super) const WAIT_FOR_OK_TIMEOUT: Duration = Duration::from_secs(10); 11 | pub(super) const WAIT_FOR_AUTHENTICATION_TIMEOUT: Duration = Duration::from_secs(7); 12 | pub(super) const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_secs(60); 13 | 14 | /// Relay default notification channel size 15 | pub const DEFAULT_NOTIFICATION_CHANNEL_SIZE: usize = 2048; 16 | 17 | /// Max relay size 18 | pub const MAX_MESSAGE_SIZE: u32 = 5 * 1024 * 1024; // 5 MB 19 | 20 | pub(super) const DEFAULT_RETRY_INTERVAL: Duration = Duration::from_secs(10); 21 | // Not increase the max retry interval too much. 22 | // Keep it small, avoid huge waits before reconnection if internet was gone for much time and then come back. 23 | pub(super) const MAX_RETRY_INTERVAL: Duration = Duration::from_secs(60); 24 | pub(super) const JITTER_RANGE: RangeInclusive = -3..=3; 25 | 26 | pub(super) const NEGENTROPY_FRAME_SIZE_LIMIT: u64 = 60_000; // Default frame limit is 128k. Halve that (hex encoding) and subtract a bit (JSON msg overhead) 27 | pub(super) const NEGENTROPY_HIGH_WATER_UP: usize = 100; 28 | pub(super) const NEGENTROPY_LOW_WATER_UP: usize = 50; 29 | pub(super) const NEGENTROPY_BATCH_SIZE_DOWN: usize = 100; 30 | 31 | pub(super) const MIN_ATTEMPTS: usize = 1; 32 | pub(super) const MIN_SUCCESS_RATE: f64 = 0.90; 33 | 34 | pub(super) const PING_INTERVAL: Duration = Duration::from_secs(55); // Used also for latency calculation 35 | 36 | /// Sleep interval 37 | #[cfg(not(test))] 38 | pub(super) const SLEEP_INTERVAL: Duration = Duration::from_secs(60); 39 | /// Sleep interval for tests 40 | #[cfg(test)] 41 | pub(super) const SLEEP_INTERVAL: Duration = Duration::from_secs(1); 42 | 43 | pub(super) const WEBSOCKET_TX_TIMEOUT: Duration = Duration::from_secs(10); 44 | 45 | #[cfg(not(target_arch = "wasm32"))] 46 | pub(crate) const LATENCY_MIN_READS: u64 = 3; 47 | -------------------------------------------------------------------------------- /signer/nostr-browser-signer-proxy/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Error 6 | 7 | use std::{fmt, io}; 8 | 9 | use hyper::http; 10 | use nostr::event; 11 | use tokio::sync::oneshot::error::RecvError; 12 | 13 | /// Error 14 | #[derive(Debug)] 15 | pub enum Error { 16 | /// I/O error 17 | Io(io::Error), 18 | /// HTTP error 19 | Http(http::Error), 20 | /// Json error 21 | Json(serde_json::Error), 22 | /// Event error 23 | Event(event::Error), 24 | /// Oneshot channel receive error 25 | OneShotRecv(RecvError), 26 | /// Generic error 27 | Generic(String), 28 | /// Timeout 29 | Timeout, 30 | /// The server is shutdown 31 | Shutdown, 32 | } 33 | 34 | impl std::error::Error for Error {} 35 | 36 | impl fmt::Display for Error { 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | match self { 39 | Self::Io(e) => write!(f, "{e}"), 40 | Self::Http(e) => write!(f, "{e}"), 41 | Self::Json(e) => write!(f, "{e}"), 42 | Self::Event(e) => write!(f, "{e}"), 43 | Self::OneShotRecv(e) => write!(f, "{e}"), 44 | Self::Generic(e) => write!(f, "{e}"), 45 | Self::Timeout => write!(f, "timeout"), 46 | Self::Shutdown => write!(f, "server is shutdown"), 47 | } 48 | } 49 | } 50 | 51 | impl From for Error { 52 | fn from(e: io::Error) -> Self { 53 | Self::Io(e) 54 | } 55 | } 56 | 57 | impl From for Error { 58 | fn from(e: http::Error) -> Self { 59 | Self::Http(e) 60 | } 61 | } 62 | 63 | impl From for Error { 64 | fn from(e: serde_json::Error) -> Self { 65 | Self::Json(e) 66 | } 67 | } 68 | 69 | impl From for Error { 70 | fn from(e: event::Error) -> Self { 71 | Self::Event(e) 72 | } 73 | } 74 | 75 | impl From for Error { 76 | fn from(e: RecvError) -> Self { 77 | Self::OneShotRecv(e) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/whitelist.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::collections::HashSet; 6 | use std::time::Duration; 7 | 8 | use nostr_sdk::prelude::*; 9 | 10 | #[derive(Debug, Default)] 11 | struct WoT { 12 | allowed_public_keys: HashSet, 13 | } 14 | 15 | impl AdmitPolicy for WoT { 16 | fn admit_event<'a>( 17 | &'a self, 18 | _relay_url: &'a RelayUrl, 19 | _subscription_id: &'a SubscriptionId, 20 | event: &'a Event, 21 | ) -> BoxedFuture<'a, Result> { 22 | Box::pin(async move { 23 | if self.allowed_public_keys.contains(&event.pubkey) { 24 | return Ok(AdmitStatus::success()); 25 | } 26 | 27 | Ok(AdmitStatus::rejected("Not in whitelist")) 28 | }) 29 | } 30 | } 31 | 32 | #[tokio::main] 33 | async fn main() -> Result<()> { 34 | tracing_subscriber::fmt::init(); 35 | 36 | let mut wot = WoT::default(); 37 | 38 | // Allow public key 39 | let allowed_public_key = 40 | PublicKey::from_bech32("npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet")?; 41 | wot.allowed_public_keys.insert(allowed_public_key); 42 | 43 | // Init client 44 | let client = Client::builder().admit_policy(wot).build(); 45 | client.add_relay("wss://relay.damus.io").await?; 46 | client.connect().await; 47 | 48 | let not_in_whitelist_public_key = 49 | PublicKey::from_bech32("npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s")?; 50 | 51 | // Get events from all connected relays 52 | let filter = Filter::new() 53 | .authors([allowed_public_key, not_in_whitelist_public_key]) 54 | .kind(Kind::Metadata); 55 | let events = client.fetch_events(filter, Duration::from_secs(10)).await?; 56 | println!("Received {} events.", events.len()); 57 | 58 | assert_eq!(events.len(), 1); 59 | assert_eq!(events.first().unwrap().pubkey, allowed_public_key); 60 | 61 | for event in events.into_iter() { 62 | println!("{}", event.as_json()); 63 | } 64 | 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/monitor.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Monitor 6 | 7 | use nostr::RelayUrl; 8 | use tokio::sync::broadcast::{self, Receiver, Sender}; 9 | 10 | use crate::relay::RelayStatus; 11 | 12 | /// Relay monitor notification 13 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | pub enum MonitorNotification { 15 | /// Relay status changed 16 | StatusChanged { 17 | /// Relay URL 18 | relay_url: RelayUrl, 19 | /// Status 20 | status: RelayStatus, 21 | }, 22 | } 23 | 24 | /// Relay monitor 25 | #[derive(Debug, Clone)] 26 | pub struct Monitor { 27 | channel: Sender, 28 | } 29 | 30 | impl Monitor { 31 | /// Create a new monitor with the given channel size 32 | /// 33 | /// For more details, check [`broadcast::channel`]. 34 | pub fn new(channel_size: usize) -> Self { 35 | let (tx, ..) = broadcast::channel(channel_size); 36 | 37 | Self { channel: tx } 38 | } 39 | 40 | /// Subscribe to monitor notifications 41 | /// 42 | ///
When you call this method, you subscribe to the notifications channel from that precise moment. Anything received by relay/s before that moment is not included in the channel!
43 | #[inline] 44 | pub fn subscribe(&self) -> Receiver { 45 | self.channel.subscribe() 46 | } 47 | 48 | #[inline] 49 | fn notify(&self, notification: MonitorNotification) { 50 | let _ = self.channel.send(notification); 51 | } 52 | 53 | #[inline] 54 | pub(crate) fn notify_status_change(&self, relay_url: RelayUrl, status: RelayStatus) { 55 | self.notify(MonitorNotification::StatusChanged { relay_url, status }); 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | 63 | #[test] 64 | #[should_panic] 65 | fn test_monitor_capacity_is_zero() { 66 | Monitor::new(0); 67 | } 68 | 69 | #[test] 70 | #[should_panic] 71 | fn test_monitor_capacity_overflows() { 72 | let _ = Monitor::new(usize::MAX / 2); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/lmdb.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr_lmdb::NostrLmdb; 6 | use nostr_sdk::prelude::*; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<()> { 10 | tracing_subscriber::fmt::init(); 11 | 12 | let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; 13 | 14 | let database = NostrLmdb::open("./db/nostr-lmdb").await?; 15 | let client: Client = ClientBuilder::default() 16 | .signer(keys.clone()) 17 | .database(database) 18 | .build(); 19 | 20 | client.add_relay("wss://relay.damus.io").await?; 21 | client.add_relay("wss://nostr.wine").await?; 22 | client.add_relay("wss://nostr.oxtr.dev").await?; 23 | 24 | client.connect().await; 25 | 26 | // Publish a text note 27 | let builder = EventBuilder::text_note("Hello world"); 28 | client.send_event_builder(builder).await?; 29 | 30 | // Negentropy sync 31 | let filter = Filter::new().author(keys.public_key()); 32 | let (tx, mut rx) = SyncProgress::channel(); 33 | let opts = SyncOptions::default().progress(tx); 34 | 35 | tokio::spawn(async move { 36 | while rx.changed().await.is_ok() { 37 | let progress = *rx.borrow_and_update(); 38 | if progress.total > 0 { 39 | println!("{:.2}%", progress.percentage() * 100.0); 40 | } 41 | } 42 | }); 43 | let output = client.sync(filter, &opts).await?; 44 | 45 | println!("Local: {}", output.local.len()); 46 | println!("Remote: {}", output.remote.len()); 47 | println!("Sent: {}", output.sent.len()); 48 | println!("Received: {}", output.received.len()); 49 | println!("Failures:"); 50 | for (url, map) in output.send_failures.iter() { 51 | println!("* '{url}':"); 52 | for (id, e) in map.iter() { 53 | println!(" - {id}: {e}"); 54 | } 55 | } 56 | 57 | // Query events from database 58 | let filter = Filter::new().author(keys.public_key()).limit(10); 59 | let events = client.database().query(filter).await?; 60 | println!("Events: {events:?}"); 61 | 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/README.md: -------------------------------------------------------------------------------- 1 | # Nostr Relay Pool 2 | 3 | This library is the low-level building block used by [`nostr-sdk`](../nostr-sdk) to manage relays. 4 | 5 | If you’re just trying to write a nostr client or bot, you’re probably looking for [`nostr-sdk`](../nostr-sdk) instead. 6 | 7 | ## Usage 8 | 9 | ```rust,no_run 10 | use std::time::Duration; 11 | 12 | use nostr_relay_pool::prelude::*; 13 | 14 | #[tokio::main] 15 | async fn main() -> Result<(), Box> { 16 | let pool = RelayPool::new(); 17 | 18 | // Add relays with custom options (timeouts, flags, etc.) 19 | pool.add_relay("wss://relay.damus.io", RelayOptions::default()).await?; 20 | pool.add_relay("wss://relay.primal.net", RelayOptions::default()).await?; 21 | 22 | // Fire up the background tasks and, optionally, wait until we are connected 23 | pool.connect().await; 24 | pool.wait_for_connection(Duration::from_secs(5)).await; 25 | 26 | // Listen for notifications 27 | let mut notifications = pool.notifications(); 28 | while let Ok(notification) = notifications.recv().await { 29 | println!("Got notification: {:?}", notification); 30 | } 31 | 32 | Ok(()) 33 | } 34 | ``` 35 | 36 | More examples can be found in the [examples directory](./examples). 37 | 38 | ## Crate Feature Flags 39 | 40 | The following crate feature flags are available: 41 | 42 | | Feature | Default | Description | 43 | |---------|:-------:|-------------------------------------------| 44 | | `tor` | No | Enable support for embedded tor client | 45 | 46 | ## Changelog 47 | 48 | All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). 49 | 50 | ## State 51 | 52 | **This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. 53 | 54 | ## Donations 55 | 56 | `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](https://rust-nostr.org/donate). 57 | 58 | ## License 59 | 60 | This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details 61 | -------------------------------------------------------------------------------- /crates/nostr/src/event/tag/cow.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! Cow Tag 6 | 7 | use alloc::borrow::Cow; 8 | use alloc::string::String; 9 | use alloc::vec::Vec; 10 | use core::str::FromStr; 11 | 12 | use super::error::Error; 13 | use super::Tag; 14 | use crate::filter::SingleLetterTag; 15 | 16 | /// Cow Tag 17 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 18 | pub struct CowTag<'a> { 19 | buf: Vec>, 20 | } 21 | 22 | impl<'a> CowTag<'a> { 23 | /// Parse tag 24 | /// 25 | /// Return error if the tag is empty! 26 | pub fn parse(tag: Vec>) -> Result { 27 | // Check if it's empty 28 | if tag.is_empty() { 29 | return Err(Error::EmptyTag); 30 | } 31 | 32 | Ok(Self { buf: tag }) 33 | } 34 | 35 | /// Get the tag kind 36 | #[inline] 37 | pub fn kind(&self) -> &str { 38 | // SAFETY: we checked that buf is not empty 39 | self.buf[0].as_ref() 40 | } 41 | 42 | /// Return the **first** tag value (index `1`), if exists. 43 | #[inline] 44 | pub fn content(&self) -> Option<&str> { 45 | self.buf.get(1).map(|s| s.as_ref()) 46 | } 47 | 48 | /// Extract tag name and value 49 | pub fn extract(&self) -> Option<(SingleLetterTag, &str)> { 50 | if self.buf.len() >= 2 { 51 | let tag_name: SingleLetterTag = SingleLetterTag::from_str(&self.buf[0]).ok()?; 52 | let tag_value: &str = &self.buf[1]; 53 | Some((tag_name, tag_value)) 54 | } else { 55 | None 56 | } 57 | } 58 | 59 | /// Into owned tag 60 | pub fn into_owned(self) -> Tag { 61 | let buf: Vec = self.buf.into_iter().map(|t| t.into_owned()).collect(); 62 | Tag::new_with_empty_cell(buf) 63 | } 64 | 65 | /// Get inner value 66 | #[inline] 67 | pub fn into_inner(self) -> Vec> { 68 | self.buf 69 | } 70 | } 71 | 72 | impl<'a> From<&'a Tag> for CowTag<'a> { 73 | fn from(tag: &'a Tag) -> Self { 74 | Self { 75 | buf: tag.buf.iter().map(|v| Cow::Borrowed(v.as_str())).collect(), 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/nostr-relay-builder/examples/policy.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::collections::HashSet; 6 | use std::net::SocketAddr; 7 | use std::time::Duration; 8 | 9 | use nostr_relay_builder::prelude::*; 10 | 11 | /// Accept only certain event kinds 12 | #[derive(Debug)] 13 | struct AcceptKinds { 14 | pub kinds: HashSet, 15 | } 16 | 17 | impl WritePolicy for AcceptKinds { 18 | fn admit_event<'a>( 19 | &'a self, 20 | event: &'a Event, 21 | _addr: &'a SocketAddr, 22 | ) -> BoxedFuture<'a, PolicyResult> { 23 | Box::pin(async move { 24 | if self.kinds.contains(&event.kind) { 25 | PolicyResult::Accept 26 | } else { 27 | PolicyResult::Reject("kind not accepted".to_string()) 28 | } 29 | }) 30 | } 31 | } 32 | 33 | /// Reject requests if there are more than [limit] authors in the filter 34 | #[derive(Debug)] 35 | struct RejectAuthorLimit { 36 | pub limit: usize, 37 | } 38 | 39 | impl QueryPolicy for RejectAuthorLimit { 40 | fn admit_query<'a>( 41 | &'a self, 42 | query: &'a Filter, 43 | _addr: &'a SocketAddr, 44 | ) -> BoxedFuture<'a, PolicyResult> { 45 | Box::pin(async move { 46 | if query.authors.as_ref().map(|a| a.len()).unwrap_or(0) > self.limit { 47 | PolicyResult::Reject("query too expensive".to_string()) 48 | } else { 49 | PolicyResult::Accept 50 | } 51 | }) 52 | } 53 | } 54 | 55 | #[tokio::main] 56 | async fn main() -> Result<()> { 57 | tracing_subscriber::fmt::init(); 58 | 59 | let accept_profile_data = AcceptKinds { 60 | kinds: HashSet::from([Kind::Metadata, Kind::RelayList, Kind::ContactList]), 61 | }; 62 | 63 | let low_author_limit = RejectAuthorLimit { limit: 2 }; 64 | 65 | let relay = LocalRelay::builder() 66 | .write_policy(accept_profile_data) 67 | .query_policy(low_author_limit) 68 | .build(); 69 | 70 | relay.run().await?; 71 | 72 | let url = relay.url().await; 73 | println!("Url: {url}"); 74 | 75 | // Keep up the program 76 | loop { 77 | tokio::time::sleep(Duration::from_secs(60)).await; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /signer/nostr-connect/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | ## v0.44.0 - 2025/11/06 27 | 28 | ### Fixed 29 | 30 | - Fix `NostrConnectRequest::Connect` message handling (https://github.com/rust-nostr/nostr/pull/1111) 31 | 32 | ## v0.43.0 - 2025/07/28 33 | 34 | ### Breaking changes 35 | 36 | - Remove `NostrConnect::get_relays` (https://github.com/rust-nostr/nostr/pull/894) 37 | 38 | ## v0.42.0 - 2025/05/20 39 | 40 | ### Breaking changes 41 | 42 | - Encrypt NIP-46 events with NIP-44 instead of NIP-04 (https://github.com/rust-nostr/nostr/pull/862) 43 | - Drop support for NIP-46 event decryption with NIP-04 (https://github.com/rust-nostr/nostr/pull/864) 44 | 45 | ## v0.41.0 - 2025/04/15 46 | 47 | No notable changes in this release. 48 | 49 | ## v0.40.0 - 2025/03/18 50 | 51 | No notable changes in this release. 52 | 53 | ## v0.39.0 - 2025/01/31 54 | 55 | ### Breaking changes 56 | 57 | - Change `NostrConnect::shutdown` method signature 58 | 59 | ### Removed 60 | 61 | - Remove `thiserror` dep 62 | - Remove `async-trait` dep 63 | 64 | ## v0.38.0 - 2024/12/31 65 | 66 | ### Changed 67 | 68 | - Require `fmt::Debug`, `Send` and `Sync` for `AuthUrlHandler` 69 | - Improve secret matching for `NostrConnectRemoteSigner` 70 | - Support both NIP04 and NIP44 for message decryption 71 | 72 | ### Added 73 | 74 | - Add `NostrConnect::status` 75 | - Add pubkey in `NostrConnectSignerActions::approve` 76 | 77 | ## v0.37.0 - 2024/11/27 78 | 79 | ### Breaking changes 80 | 81 | - Refactor `NostrConnectRemoteSigner` to use distinct keys for signer and user 82 | - Refactor `NostrConnectRemoteSigner` to use synchronous constructors 83 | 84 | ### Added 85 | 86 | - Add `NostrConnect::non_secure_set_user_public_key` 87 | 88 | ## v0.36.0 - 2024/11/05 89 | 90 | ### Changed 91 | 92 | - Rename `Nip46Signer` to `NostrConnect` 93 | - Bootstrap NIP46 signer on demand 94 | 95 | ### Fixed 96 | 97 | - Fix `NostrConnect` according to NIP46 98 | -------------------------------------------------------------------------------- /crates/nostr/src/nips/nip58.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | //! NIP58: Badges 6 | //! 7 | //! 8 | 9 | use alloc::vec::Vec; 10 | use core::fmt; 11 | 12 | use crate::types::RelayUrl; 13 | use crate::{Event, Kind, PublicKey, Tag, TagStandard}; 14 | 15 | #[derive(Debug, PartialEq, Eq)] 16 | /// Badge Award error 17 | pub enum Error { 18 | /// Invalid length 19 | InvalidLength, 20 | /// Invalid kind 21 | InvalidKind, 22 | /// Identifier tag not found 23 | IdentifierTagNotFound, 24 | /// Mismatched badge definition or award 25 | MismatchedBadgeDefinitionOrAward, 26 | /// Badge awards lack the awarded public key 27 | BadgeAwardsLackAwardedPublicKey, 28 | } 29 | 30 | #[cfg(feature = "std")] 31 | impl std::error::Error for Error {} 32 | 33 | impl fmt::Display for Error { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | match self { 36 | Self::InvalidLength => f.write_str("invalid length"), 37 | Self::InvalidKind => f.write_str("invalid kind"), 38 | Self::IdentifierTagNotFound => f.write_str("identifier tag not found"), 39 | Self::MismatchedBadgeDefinitionOrAward => f.write_str("mismatched badge definition/award"), 40 | Self::BadgeAwardsLackAwardedPublicKey => f.write_str("badge award events lack the awarded public keybadge award events lack the awarded public key"), 41 | } 42 | } 43 | } 44 | 45 | /// Helper function to filter events for a specific [`Kind`] 46 | #[inline] 47 | pub(crate) fn filter_for_kind(events: Vec, kind_needed: &Kind) -> Vec { 48 | events 49 | .into_iter() 50 | .filter(|e| &e.kind == kind_needed) 51 | .collect() 52 | } 53 | 54 | /// Helper function to extract the awarded public key from an array of PubKey tags 55 | pub(crate) fn extract_awarded_public_key<'a>( 56 | tags: &'a [Tag], 57 | awarded_public_key: &PublicKey, 58 | ) -> Option<(&'a PublicKey, &'a Option)> { 59 | tags.iter().find_map(|t| match t.as_standardized() { 60 | Some(TagStandard::PublicKey { 61 | public_key, 62 | relay_url, 63 | .. 64 | }) if public_key == awarded_public_key => Some((public_key, relay_url)), 65 | _ => None, 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /crates/nostr-sdk/examples/aggregated-query.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::time::Duration; 6 | 7 | use nostr_lmdb::NostrLmdb; 8 | use nostr_sdk::prelude::*; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | tracing_subscriber::fmt::init(); 13 | 14 | let database = NostrLmdb::open("./db/nostr-lmdb").await?; 15 | let client: Client = Client::builder().database(database).build(); 16 | client.add_relay("wss://relay.damus.io").await?; 17 | 18 | client.connect().await; 19 | 20 | let public_key = 21 | PublicKey::from_bech32("npub1080l37pfvdpyuzasyuy2ytjykjvq3ylr5jlqlg7tvzjrh9r8vn3sf5yaph")?; 22 | 23 | // ################ Aggregated query with same filter ################ 24 | let filter = Filter::new() 25 | .author(public_key) 26 | .kind(Kind::TextNote) 27 | .limit(50); 28 | let stored_events = client.database().query(filter.clone()).await?; 29 | let fetched_events = client.fetch_events(filter, Duration::from_secs(10)).await?; 30 | let events = stored_events.merge(fetched_events); 31 | 32 | for event in events.into_iter() { 33 | println!("{}", event.as_json()); 34 | } 35 | 36 | // ################ Aggregated query with different filters ################ 37 | 38 | // Query events from database 39 | let filter = Filter::new().author(public_key).kind(Kind::TextNote); 40 | let stored_events = client.database().query(filter).await?; 41 | 42 | // Query events from relays 43 | let filter = Filter::new().author(public_key).kind(Kind::Metadata); 44 | let fetched_events = client.fetch_events(filter, Duration::from_secs(10)).await?; 45 | 46 | // Add temp relay and fetch other events 47 | client.add_relay("wss://nostr.oxtr.dev").await?; 48 | client.connect_relay("wss://nostr.oxtr.dev").await?; 49 | let filter = Filter::new().kind(Kind::ContactList).limit(100); 50 | let fetched_events_from = client 51 | .fetch_events_from(["wss://nostr.oxtr.dev"], filter, Duration::from_secs(10)) 52 | .await?; 53 | client.force_remove_relay("wss://nostr.oxtr.dev").await?; 54 | 55 | // Aggregate results (can be done many times) 56 | let events = stored_events 57 | .merge(fetched_events) 58 | .merge(fetched_events_from); 59 | 60 | for event in events.into_iter() { 61 | println!("{}", event.as_json()); 62 | } 63 | 64 | Ok(()) 65 | } 66 | -------------------------------------------------------------------------------- /crates/nostr-sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-sdk" 3 | version = "0.44.1" 4 | edition = "2021" 5 | description = "High level Nostr client library." 6 | authors.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | rust-version.workspace = true 12 | keywords = ["nostr", "sdk"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [features] 19 | default = [] 20 | tor = ["nostr-relay-pool/tor"] 21 | pow-multi-thread = ["nostr/pow-multi-thread"] 22 | all-nips = ["nostr/all-nips", "nip04", "nip06", "nip44", "nip47", "nip49", "nip57", "nip59", "nip96", "nip98"] 23 | nip03 = ["nostr/nip03"] 24 | nip04 = ["nostr/nip04"] 25 | nip06 = ["nostr/nip06"] 26 | nip44 = ["nostr/nip44"] 27 | nip47 = ["nostr/nip47"] 28 | nip49 = ["nostr/nip49"] 29 | nip57 = ["nostr/nip57"] 30 | nip59 = ["nostr/nip59"] 31 | nip96 = ["nostr/nip96"] 32 | nip98 = ["nostr/nip98"] 33 | 34 | [dependencies] 35 | async-utility.workspace = true 36 | nostr = { workspace = true, features = ["std"] } 37 | nostr-database.workspace = true 38 | nostr-gossip.workspace = true 39 | nostr-relay-pool.workspace = true 40 | tokio = { workspace = true, features = ["sync"] } 41 | tracing = { workspace = true, features = ["std"] } 42 | 43 | [dev-dependencies] 44 | nostr-connect.workspace = true 45 | nostr-lmdb.workspace = true 46 | nostr-ndb.workspace = true 47 | nostr-gossip-memory.workspace = true 48 | tokio = { workspace = true, features = ["macros"] } 49 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 50 | 51 | [[example]] 52 | name = "aggregated-query" 53 | 54 | [[example]] 55 | name = "blacklist" 56 | 57 | [[example]] 58 | name = "client" 59 | 60 | [[example]] 61 | name = "comment" 62 | 63 | [[example]] 64 | name = "fetch-events" 65 | 66 | [[example]] 67 | name = "gossip" 68 | required-features = ["all-nips"] 69 | 70 | [[example]] 71 | name = "monitor" 72 | 73 | [[example]] 74 | name = "nostr-connect" 75 | required-features = ["nip59"] 76 | 77 | [[example]] 78 | name = "bot" 79 | required-features = ["all-nips"] 80 | 81 | [[example]] 82 | name = "nostrdb" 83 | 84 | [[example]] 85 | name = "stream-events" 86 | 87 | [[example]] 88 | name = "subscriptions" 89 | 90 | [[example]] 91 | name = "switch-account" 92 | 93 | [[example]] 94 | name = "tor" 95 | required-features = ["nip59", "tor"] 96 | 97 | [[example]] 98 | name = "lmdb" 99 | 100 | [[example]] 101 | name = "whitelist" 102 | -------------------------------------------------------------------------------- /crates/nostr-relay-pool/src/pool/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use std::convert::Infallible; 6 | use std::fmt; 7 | 8 | use nostr::types::url; 9 | use nostr_database::DatabaseError; 10 | 11 | use crate::__private::SharedStateError; 12 | use crate::relay; 13 | 14 | /// Relay Pool error 15 | #[derive(Debug)] 16 | pub enum Error { 17 | /// Database error 18 | Database(DatabaseError), 19 | /// Shared state error 20 | SharedState(SharedStateError), 21 | /// Url parse error 22 | RelayUrl(url::Error), 23 | /// Relay error 24 | Relay(relay::Error), 25 | /// Notification Handler error 26 | Handler(String), 27 | /// Too many relays 28 | TooManyRelays { 29 | /// Max numer allowed 30 | limit: usize, 31 | }, 32 | /// No relays 33 | NoRelays, 34 | /// No relays specified 35 | NoRelaysSpecified, 36 | /// Relay not found 37 | RelayNotFound, 38 | /// Relay Pool is shutdown 39 | Shutdown, 40 | } 41 | 42 | impl std::error::Error for Error {} 43 | 44 | impl fmt::Display for Error { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | match self { 47 | Self::Database(e) => write!(f, "database: {e}"), 48 | Self::SharedState(e) => e.fmt(f), 49 | Self::RelayUrl(e) => e.fmt(f), 50 | Self::Relay(e) => e.fmt(f), 51 | Self::Handler(e) => e.fmt(f), 52 | Self::TooManyRelays { .. } => f.write_str("too many relays"), 53 | Self::NoRelays => f.write_str("no relays"), 54 | Self::NoRelaysSpecified => f.write_str("no relays specified"), 55 | Self::RelayNotFound => f.write_str("relay not found"), 56 | Self::Shutdown => f.write_str("relay pool is shutdown"), 57 | } 58 | } 59 | } 60 | 61 | impl From for Error { 62 | fn from(e: DatabaseError) -> Self { 63 | Self::Database(e) 64 | } 65 | } 66 | 67 | impl From for Error { 68 | fn from(e: SharedStateError) -> Self { 69 | Self::SharedState(e) 70 | } 71 | } 72 | 73 | impl From for Error { 74 | fn from(e: url::Error) -> Self { 75 | Self::RelayUrl(e) 76 | } 77 | } 78 | 79 | impl From for Error { 80 | fn from(e: relay::Error) -> Self { 81 | Self::Relay(e) 82 | } 83 | } 84 | 85 | impl From for Error { 86 | fn from(_: Infallible) -> Self { 87 | unreachable!() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /crates/nostr/examples/nip96.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-2023 Yuki Kishimoto 2 | // Copyright (c) 2023-2025 Rust Nostr Developers 3 | // Distributed under the MIT software license 4 | 5 | use nostr::prelude::*; 6 | 7 | const FILE: &[u8] = &[ 8 | 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0, 9 | 0, 0, 31, 21, 196, 137, 0, 0, 0, 1, 115, 82, 71, 66, 0, 174, 206, 28, 233, 0, 0, 0, 4, 103, 65, 10 | 77, 65, 0, 0, 177, 143, 11, 252, 97, 5, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 28, 35, 0, 0, 28, 11 | 35, 1, 199, 111, 168, 100, 0, 0, 0, 12, 73, 68, 65, 84, 8, 29, 99, 248, 255, 255, 63, 0, 5, 12 | 254, 2, 254, 135, 150, 28, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130, 13 | ]; 14 | 15 | #[tokio::main] 16 | async fn main() -> Result<()> { 17 | let keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; 18 | let server_url: Url = Url::parse("https://nostr.media")?; 19 | 20 | // Step 1: Get server configuration URL 21 | let config_url = nip96::get_server_config_url(&server_url)?; 22 | println!("Config URL: {}", config_url); 23 | 24 | // Mock server config response 25 | let config_json = r#"{ 26 | "api_url": "https://nostr.media/api/v1/nip96/upload", 27 | "download_url": "https://nostr.media" 28 | }"#; 29 | 30 | let config = nip96::ServerConfig::from_json(config_json)?; 31 | println!("Upload endpoint: {}", config.api_url); 32 | 33 | // Step 2: Prepare upload request 34 | let upload_request = nip96::UploadRequest::new(&keys, &config, FILE).await?; 35 | println!("Upload URL: {}", upload_request.url()); 36 | println!("Authorization: {}", upload_request.authorization()); 37 | 38 | // Step 3: Mock upload response 39 | let upload_response_json = r#"{ 40 | "status": "success", 41 | "message": "Upload successful", 42 | "nip94_event": { 43 | "tags": [["url", "https://nostr.media/file123.png"]] 44 | } 45 | }"#; 46 | 47 | // Parse response and extract URL 48 | let upload_response = nip96::UploadResponse::from_json(upload_response_json)?; 49 | match upload_response.download_url() { 50 | Ok(url) => println!("File would be available at: {url}"), 51 | Err(e) => println!("Upload simulation failed: {e}"), 52 | } 53 | 54 | println!("\n--- I/O-free NIP96 Demo Complete ---"); 55 | println!("This example shows how to use NIP96 without any specific HTTP client."); 56 | println!("Users can now choose reqwest, ureq, curl, or any HTTP implementation!"); 57 | 58 | Ok(()) 59 | } 60 | --------------------------------------------------------------------------------