From 63e6219b5fb3469706aac509d43f05313c8fd3aa Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Thu, 21 May 2026 14:48:04 -0400 Subject: [PATCH 01/10] fix(deps): migrate nostr crate from 0.36 to 0.44 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Breaking changes addressed: - EventBuilder::new switched from 3-arg to builder pattern (.tags()) - Tag::parse no longer takes a reference (&[...] → [...]) - generate_shared_key returns Result — added ? / .map_err / .unwrap() - nip44 and nip98 features must now be explicit - PublicKey.serialize() renamed to .to_bytes()/.as_bytes() - nostr::bitcoin::* re-exports removed — now nostr::secp256k1::* and nostr::hashes::* - RelayMessage and ClientMessage gained lifetime parameters - UncheckedUrl removed — replaced by Url/RelayUrl - EventBuilder::auth now requires RelayUrl not url::Url - Filter::custom_tag takes a single S: Into; use custom_tags for slices - ConversationKey::derive and Timestamp::as_u64 now deprecated/return Result - nostr::util::hex removed — use hex crate directly - nostr-compat alias removed from desktop (now same version as nostr) --- Cargo.lock | 97 +-------- Cargo.toml | 2 +- crates/git-credential-nostr/src/lib.rs | 6 +- crates/git-sign-nostr/src/lib.rs | 32 ++- crates/sprout-acp/src/engram_fetch.rs | 4 +- crates/sprout-acp/src/filter.rs | 6 +- crates/sprout-acp/src/lib.rs | 2 +- crates/sprout-acp/src/pool.rs | 14 +- crates/sprout-acp/src/queue.rs | 4 +- crates/sprout-acp/src/relay.rs | 38 ++-- crates/sprout-admin/src/main.rs | 30 +-- crates/sprout-auth/src/lib.rs | 2 +- crates/sprout-auth/src/nip42.rs | 2 +- crates/sprout-auth/src/nip98.rs | 10 +- crates/sprout-cli/src/client.rs | 22 +-- crates/sprout-cli/src/commands/social.rs | 5 +- crates/sprout-core/src/engram.rs | 21 +- crates/sprout-core/src/event.rs | 2 +- crates/sprout-core/src/filter.rs | 12 +- crates/sprout-core/src/lib.rs | 4 +- crates/sprout-core/src/observer.rs | 10 +- crates/sprout-core/src/pairing/crypto.rs | 14 +- crates/sprout-core/src/pairing/session.rs | 38 ++-- crates/sprout-core/src/verification.rs | 6 +- crates/sprout-db/src/channel.rs | 2 +- crates/sprout-db/src/event.rs | 28 +-- crates/sprout-db/src/user.rs | 4 +- crates/sprout-mcp/src/relay_client.rs | 55 +++--- crates/sprout-mcp/src/server.rs | 68 +++---- crates/sprout-mcp/src/upload.rs | 14 +- crates/sprout-media/src/auth.rs | 44 ++--- crates/sprout-pairing-cli/src/main.rs | 8 +- crates/sprout-proxy/Cargo.toml | 1 + crates/sprout-proxy/src/channel_map.rs | 6 +- crates/sprout-proxy/src/main.rs | 7 +- crates/sprout-proxy/src/server.rs | 10 +- crates/sprout-proxy/src/shadow_keys.rs | 2 +- crates/sprout-proxy/src/translate.rs | 101 +++++----- crates/sprout-proxy/src/upstream.rs | 27 ++- crates/sprout-pubsub/src/lib.rs | 2 +- crates/sprout-relay/src/api/bridge.rs | 19 +- crates/sprout-relay/src/api/git/transport.rs | 4 +- crates/sprout-relay/src/api/media.rs | 6 +- crates/sprout-relay/src/api/nip05.rs | 4 +- crates/sprout-relay/src/audio/handler.rs | 10 +- crates/sprout-relay/src/handlers/auth.rs | 18 +- .../src/handlers/command_executor.rs | 2 +- crates/sprout-relay/src/handlers/count.rs | 2 +- crates/sprout-relay/src/handlers/event.rs | 39 ++-- crates/sprout-relay/src/handlers/ingest.rs | 12 +- .../sprout-relay/src/handlers/relay_admin.rs | 2 +- crates/sprout-relay/src/handlers/req.rs | 28 +-- .../sprout-relay/src/handlers/side_effects.rs | 113 ++++++----- crates/sprout-relay/src/subscription.rs | 10 +- crates/sprout-relay/src/workflow_sink.rs | 10 +- crates/sprout-sdk/src/builders.rs | 111 +++++------ crates/sprout-sdk/src/nip_oa.rs | 20 +- crates/sprout-search/src/index.rs | 10 +- crates/sprout-search/src/lib.rs | 2 +- crates/sprout-test-client/src/bin/mention.rs | 6 +- crates/sprout-test-client/src/lib.rs | 16 +- crates/sprout-test-client/src/main.rs | 2 +- .../sprout-test-client/tests/e2e_long_form.rs | 24 +-- crates/sprout-test-client/tests/e2e_mcp.rs | 12 +- crates/sprout-test-client/tests/e2e_media.rs | 8 +- .../tests/e2e_media_extended.rs | 118 ++++++----- .../tests/e2e_media_video.rs | 52 +++-- .../tests/e2e_nostr_interop.rs | 73 ++++--- crates/sprout-test-client/tests/e2e_relay.rs | 102 +++++----- .../sprout-test-client/tests/e2e_rest_api.rs | 42 ++-- crates/sprout-test-client/tests/e2e_tokens.rs | 14 +- .../tests/e2e_user_status.rs | 16 +- .../sprout-test-client/tests/e2e_workflows.rs | 13 +- crates/sprout-workflow/src/lib.rs | 19 +- desktop/src-tauri/Cargo.lock | 185 +++--------------- desktop/src-tauri/Cargo.toml | 3 +- desktop/src-tauri/src/commands/agents.rs | 6 +- desktop/src-tauri/src/commands/identity.rs | 2 +- desktop/src-tauri/src/commands/pairing.rs | 13 +- desktop/src-tauri/src/relay.rs | 8 +- examples/countdown-bot/src/main.rs | 23 ++- 81 files changed, 833 insertions(+), 1108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2fbba4148..170adfac3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,17 +18,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -325,16 +314,6 @@ dependencies = [ "fastrand", ] -[[package]] -name = "base58ck" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" -dependencies = [ - "bitcoin-internals", - "bitcoin_hashes", -] - [[package]] name = "base64" version = "0.22.1" @@ -364,49 +343,12 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "bitcoin" -version = "0.32.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf93e61f2dbc3e3c41234ca26a65e2c0b0975c52e0f069ab9893ebbede584d3" -dependencies = [ - "base58ck", - "bech32", - "bitcoin-internals", - "bitcoin-io", - "bitcoin-units", - "bitcoin_hashes", - "hex-conservative", - "hex_lit", - "secp256k1 0.29.1", - "serde", -] - -[[package]] -name = "bitcoin-internals" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" -dependencies = [ - "serde", -] - [[package]] name = "bitcoin-io" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" -[[package]] -name = "bitcoin-units" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346568ebaab2918487cea76dd55dae13c27bb618cdb737c952e69eb2017c4118" -dependencies = [ - "bitcoin-internals", - "serde", -] - [[package]] name = "bitcoin_hashes" version = "0.14.1" @@ -1559,12 +1501,6 @@ dependencies = [ "arrayvec", ] -[[package]] -name = "hex_lit" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" - [[package]] name = "hkdf" version = "0.12.4" @@ -2341,18 +2277,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "negentropy" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe" - -[[package]] -name = "negentropy" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a88da9dd148bbcdce323dd6ac47d369b4769d4a3b78c6c52389b9269f77932" - [[package]] name = "nix" version = "0.31.3" @@ -2367,34 +2291,26 @@ dependencies = [ [[package]] name = "nostr" -version = "0.36.0" +version = "0.44.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ad56c1d9a59f4edc46b17bc64a217b38b99baefddc0080f85ad98a0855336d" +checksum = "08d8f0fe13526800300a36bf3b7c5f752e62e32ab81c74a8e5caa2865708625a" dependencies = [ - "aes", - "async-trait", "base64", "bech32", "bip39", - "bitcoin", + "bitcoin_hashes", "cbc", "chacha20 0.9.1", "chacha20poly1305", "getrandom 0.2.17", + "hex", "instant", - "js-sys", - "negentropy 0.3.1", - "negentropy 0.4.3", - "once_cell", - "reqwest 0.12.28", "scrypt", + "secp256k1 0.29.1", "serde", "serde_json", "unicode-normalization", "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", ] [[package]] @@ -3530,7 +3446,6 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes", "rand 0.8.6", "secp256k1-sys 0.10.1", "serde", @@ -3641,7 +3556,6 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap", "itoa", "memchr", "serde", @@ -4166,6 +4080,7 @@ dependencies = [ "chrono", "dashmap", "futures-util", + "hex", "hmac 0.13.0", "moka", "nostr", diff --git a/Cargo.toml b/Cargo.toml index c438aac29..200873617 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ redis = { version = "1.0", features = ["tokio-comp", "connection-manager" deadpool-redis = { version = "0.23", features = ["rt_tokio_1"] } # Nostr -nostr = { version = "0.36" } +nostr = { version = "0.44", features = ["nip44", "nip98"] } # Serialization serde = { version = "1", features = ["derive"] } diff --git a/crates/git-credential-nostr/src/lib.rs b/crates/git-credential-nostr/src/lib.rs index 48e3c7645..44986b38a 100644 --- a/crates/git-credential-nostr/src/lib.rs +++ b/crates/git-credential-nostr/src/lib.rs @@ -9,7 +9,8 @@ use std::io::{self, BufRead, Write}; use base64::Engine as _; use nostr::nips::nip98::{HttpData, HttpMethod}; -use nostr::{EventBuilder, Keys, UncheckedUrl}; +use nostr::types::Url; +use nostr::{EventBuilder, Keys}; use zeroize::Zeroize; // ── helpers ────────────────────────────────────────────────────────────────── @@ -204,7 +205,8 @@ pub fn run() -> i32 { }; raw_key.zeroize(); - let http_data = HttpData::new(UncheckedUrl::from(url.as_str()), method); + let parsed_url = Url::parse(&url).unwrap_or_else(|e| panic!("invalid URL {url:?}: {e}")); + let http_data = HttpData::new(parsed_url, method); let event = match EventBuilder::http_auth(http_data).sign_with_keys(&keys) { Ok(e) => e, Err(e) => { diff --git a/crates/git-sign-nostr/src/lib.rs b/crates/git-sign-nostr/src/lib.rs index 9647dad68..a7aa5e463 100644 --- a/crates/git-sign-nostr/src/lib.rs +++ b/crates/git-sign-nostr/src/lib.rs @@ -80,10 +80,10 @@ use std::time::{SystemTime, UNIX_EPOCH}; use base64::Engine as _; use chrono::DateTime; -use nostr::bitcoin::hashes::sha256::Hash as Sha256Hash; -use nostr::bitcoin::hashes::{Hash, HashEngine}; -use nostr::bitcoin::secp256k1::schnorr::Signature; -use nostr::bitcoin::secp256k1::{Keypair, Message, XOnlyPublicKey}; +use nostr::hashes::sha256::Hash as Sha256Hash; +use nostr::hashes::{Hash, HashEngine}; +use nostr::secp256k1::schnorr::Signature; +use nostr::secp256k1::{Keypair, Message, XOnlyPublicKey}; use nostr::{FromBech32, PublicKey, SecretKey, SECP256K1}; use zeroize::Zeroize; @@ -1236,8 +1236,14 @@ fn do_verify(sig_file: &str, status: &mut StatusWriter) -> Result<(), Error> { })?; // Verify BIP-340 signature - let xonly: &XOnlyPublicKey = &pk; - if SECP256K1.verify_schnorr(&sig, &message, xonly).is_err() { + let xonly = pk.xonly().map_err(|_| { + write_errsig(status, Some(&envelope.pk)); + Error::VerifyFailed { + pk: Some(envelope.pk.clone()), + msg: "invalid public key xonly conversion".to_string(), + } + })?; + if SECP256K1.verify_schnorr(&sig, &message, &xonly).is_err() { status.write_line("NEWSIG"); status.write_line(&format!("BADSIG {} {}", envelope.pk, envelope.pk)); return Err(Error::VerifyFailed { @@ -1543,8 +1549,14 @@ fn verify_oa(agent_pk_hex: &str, oa: &(String, String, String)) -> bool { } }; - let xonly: &XOnlyPublicKey = &owner_pk; - if SECP256K1.verify_schnorr(&sig, &message, xonly).is_err() { + let xonly = match owner_pk.xonly() { + Ok(x) => x, + Err(_) => { + eprintln!("warning: oa owner pubkey conversion to xonly failed"); + return false; + } + }; + if SECP256K1.verify_schnorr(&sig, &message, &xonly).is_err() { eprintln!("warning: NIP-OA owner attestation signature verification failed"); return false; } @@ -2028,7 +2040,7 @@ Initial commit" fn sign_payload(secret_hex: &str, payload: &[u8], t: u64) -> String { let keypair = Keypair::from_seckey_str(&SECP256K1, secret_hex).unwrap(); let (xonly, _) = keypair.x_only_public_key(); - let pk_hex = hex::encode(xonly.serialize()); + let pk_hex = hex::encode(xonly.to_bytes()); let hash = compute_signing_hash(t, None, payload); let message = Message::from_digest(hash); let sig = SECP256K1.sign_schnorr(&message, &keypair); @@ -2109,7 +2121,7 @@ Initial commit" let secret = "0000000000000000000000000000000000000000000000000000000000000003"; let keypair = Keypair::from_seckey_str(&SECP256K1, secret).unwrap(); let (xonly, _) = keypair.x_only_public_key(); - let pk_hex = hex::encode(xonly.serialize()); + let pk_hex = hex::encode(xonly.to_bytes()); let payload = test_payload(); let hash = compute_signing_hash(1700000000, None, &payload); let message = Message::from_digest(hash); diff --git a/crates/sprout-acp/src/engram_fetch.rs b/crates/sprout-acp/src/engram_fetch.rs index 0a2c45cc8..982f2a78a 100644 --- a/crates/sprout-acp/src/engram_fetch.rs +++ b/crates/sprout-acp/src/engram_fetch.rs @@ -73,8 +73,8 @@ async fn fetch_core_body( let filter = nostr::Filter::new() .kind(nostr::Kind::Custom(KIND_AGENT_ENGRAM as u16)) .author(agent_keys.public_key()) - .custom_tag(nostr::SingleLetterTag::lowercase(nostr::Alphabet::D), [d]) - .custom_tag( + .custom_tags(nostr::SingleLetterTag::lowercase(nostr::Alphabet::D), [d]) + .custom_tags( nostr::SingleLetterTag::lowercase(nostr::Alphabet::P), [owner.to_hex()], ) diff --git a/crates/sprout-acp/src/filter.rs b/crates/sprout-acp/src/filter.rs index b99120c7b..6b5cfc300 100644 --- a/crates/sprout-acp/src/filter.rs +++ b/crates/sprout-acp/src/filter.rs @@ -485,7 +485,7 @@ mod tests { /// Build a minimal test event with the given kind and content. fn make_event(kind: u32, content: &str) -> nostr::Event { let keys = Keys::generate(); - EventBuilder::new(Kind::Custom(kind as u16), content, []) + EventBuilder::new(Kind::Custom(kind as u16), content).tags( []) .sign_with_keys(&keys) .unwrap() } @@ -493,8 +493,8 @@ mod tests { /// Build a test event with an explicit `p` tag. fn make_event_with_p_tag(kind: u32, content: &str, p_hex: &str) -> nostr::Event { let keys = Keys::generate(); - let p_tag = Tag::parse(&["p", p_hex]).expect("tag parse"); - EventBuilder::new(Kind::Custom(kind as u16), content, [p_tag]) + let p_tag = Tag::parse(["p", p_hex]).expect("tag parse"); + EventBuilder::new(Kind::Custom(kind as u16), content).tags( [p_tag]) .sign_with_keys(&keys) .unwrap() } diff --git a/crates/sprout-acp/src/lib.rs b/crates/sprout-acp/src/lib.rs index 0a521b471..a6dae6e96 100644 --- a/crates/sprout-acp/src/lib.rs +++ b/crates/sprout-acp/src/lib.rs @@ -72,7 +72,7 @@ async fn publish_presence( use nostr::{EventBuilder, Kind}; use sprout_core::kind::KIND_PRESENCE_UPDATE; - let event = EventBuilder::new(Kind::Custom(KIND_PRESENCE_UPDATE as u16), status, []) + let event = EventBuilder::new(Kind::Custom(KIND_PRESENCE_UPDATE as u16), status).tags( []) .sign_with_keys(keys) .map_err(|e| relay::RelayError::Http(format!("presence sign error: {e}")))?; publisher.publish_event(event).await?; diff --git a/crates/sprout-acp/src/pool.rs b/crates/sprout-acp/src/pool.rs index e3b41cdb7..58c0daf4d 100644 --- a/crates/sprout-acp/src/pool.rs +++ b/crates/sprout-acp/src/pool.rs @@ -1287,7 +1287,7 @@ async fn fetch_channel_info(channel_id: Uuid, rest: &RestClient) -> Option Event { let keys = Keys::generate(); - EventBuilder::new(Kind::Custom(9), content, []) + EventBuilder::new(Kind::Custom(9), content).tags( []) .sign_with_keys(&keys) .unwrap() } @@ -1870,7 +1870,7 @@ mod tests { nostr::Tag::parse(&strs).unwrap() }) .collect(); - EventBuilder::new(Kind::Custom(9), content, nostr_tags) + EventBuilder::new(Kind::Custom(9), content).tags( nostr_tags) .sign_with_keys(&keys) .unwrap() } diff --git a/crates/sprout-acp/src/relay.rs b/crates/sprout-acp/src/relay.rs index b668d59f3..d5d5e1bd9 100644 --- a/crates/sprout-acp/src/relay.rs +++ b/crates/sprout-acp/src/relay.rs @@ -62,7 +62,7 @@ const CONNECT_TIMEOUT: Duration = Duration::from_secs(10); use std::time::Instant; use futures_util::{SinkExt, StreamExt}; -use nostr::{Event, EventBuilder, Keys, Kind, Tag, Url as NostrUrl}; +use nostr::{Event, EventBuilder, Keys, Kind, RelayUrl, Tag, Url as NostrUrl}; use serde_json::{json, Value}; use sprout_core::kind::{ KIND_AGENT_OBSERVER_FRAME, KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, @@ -130,23 +130,23 @@ impl RestClient { use base64::Engine; use sha2::{Digest, Sha256}; - let u_tag = Tag::parse(&["u", url]) + let u_tag = Tag::parse(["u", url]) .map_err(|e| RelayError::Http(format!("NIP-98 tag error: {e}")))?; - let method_tag = Tag::parse(&["method", method]) + let method_tag = Tag::parse(["method", method]) .map_err(|e| RelayError::Http(format!("NIP-98 tag error: {e}")))?; // Nonce prevents replay rejection for rapid-fire requests with identical bodies. - let nonce_tag = Tag::parse(&["nonce", &uuid::Uuid::new_v4().to_string()]) + let nonce_tag = Tag::parse(["nonce", &uuid::Uuid::new_v4().to_string()]) .map_err(|e| RelayError::Http(format!("NIP-98 tag error: {e}")))?; let mut tags = vec![u_tag, method_tag, nonce_tag]; if let Some(b) = body { let hash = hex::encode(Sha256::digest(b)); - let payload_tag = Tag::parse(&["payload", &hash]) + let payload_tag = Tag::parse(["payload", &hash]) .map_err(|e| RelayError::Http(format!("NIP-98 tag error: {e}")))?; tags.push(payload_tag); } - let event = EventBuilder::new(Kind::HttpAuth, "", tags) + let event = EventBuilder::new(Kind::HttpAuth, "").tags( tags) .sign_with_keys(&self.keys) .map_err(|e| RelayError::Http(format!("NIP-98 sign error: {e}")))?; let event_json = serde_json::to_string(&event) @@ -522,7 +522,7 @@ impl HarnessRelay { .kind(Kind::Custom( sprout_core::kind::KIND_NIP29_GROUP_MEMBERS as u16, )) - .custom_tag(p_tag, [pk_hex.as_str()]); + .custom_tags(p_tag, [pk_hex.as_str()]); let member_events = rest.query(&[member_filter]).await?; let member_arr = member_events @@ -555,12 +555,11 @@ impl HarnessRelay { // Step 2: Fetch metadata (kind:39000) for discovered channels. let d_tag = SingleLetterTag::lowercase(Alphabet::D); let d_values: Vec = channel_uuids.iter().map(|u| u.to_string()).collect(); - let d_refs: Vec<&str> = d_values.iter().map(|s| s.as_str()).collect(); let meta_filter = nostr::Filter::new() .kind(Kind::Custom( sprout_core::kind::KIND_NIP29_GROUP_METADATA as u16, )) - .custom_tag(d_tag, d_refs); + .custom_tags(d_tag, d_values); let meta_events = rest.query(&[meta_filter]).await?; // Build UUID → (name, channel_type) from metadata events. @@ -732,24 +731,24 @@ impl HarnessRelay { root_event_id: Option<&str>, parent_event_id: Option<&str>, ) -> Result { - let h_tag = Tag::parse(&["h", &channel_id.to_string()]) + let h_tag = Tag::parse(["h", &channel_id.to_string()]) .map_err(|e| RelayError::AuthFailed(e.to_string()))?; let mut tags = vec![h_tag]; if let Some(parent) = parent_event_id { if let Some(root) = root_event_id { if root != parent { tags.push( - Tag::parse(&["e", root, "", "root"]) + Tag::parse(["e", root, "", "root"]) .map_err(|e| RelayError::AuthFailed(e.to_string()))?, ); } } tags.push( - Tag::parse(&["e", parent, "", "reply"]) + Tag::parse(["e", parent, "", "reply"]) .map_err(|e| RelayError::AuthFailed(e.to_string()))?, ); } - let event = EventBuilder::new(Kind::Custom(KIND_TYPING_INDICATOR as u16), "", tags) + let event = EventBuilder::new(Kind::Custom(KIND_TYPING_INDICATOR as u16), "").tags( tags) .sign_with_keys(&self.keys)?; Ok(event) } @@ -2484,20 +2483,19 @@ async fn send_auth_response( keys: &Keys, auth_tag: Option<&nostr::Tag>, ) -> Result<(), RelayError> { - let relay_nostr_url: NostrUrl = relay_url - .parse() - .map_err(|e: url::ParseError| RelayError::Http(format!("invalid relay URL: {e}")))?; + let relay_nostr_url = RelayUrl::parse(relay_url) + .map_err(|e| RelayError::Http(format!("invalid relay URL: {e}")))?; let auth_event = if let Some(tag) = auth_tag { // Cannot use EventBuilder::auth() shortcut — it doesn't accept extra tags. let tags = vec![ - nostr::Tag::parse(&["relay", relay_url]) + nostr::Tag::parse(["relay", relay_url]) .map_err(|e| RelayError::Http(format!("tag parse error: {e}")))?, - nostr::Tag::parse(&["challenge", challenge]) + nostr::Tag::parse(["challenge", challenge]) .map_err(|e| RelayError::Http(format!("tag parse error: {e}")))?, tag.clone(), ]; - EventBuilder::new(nostr::Kind::Authentication, "", tags).sign_with_keys(keys)? + EventBuilder::new(nostr::Kind::Authentication, "").tags( tags).sign_with_keys(keys)? } else { EventBuilder::auth(challenge, relay_nostr_url).sign_with_keys(keys)? }; @@ -3077,7 +3075,7 @@ mod tests { /// control it, but we return it so callers can use it for dedup tests. fn make_test_event(keys: &nostr::Keys, created_at_secs: u64) -> Event { let ts = nostr::Timestamp::from(created_at_secs); - EventBuilder::new(nostr::Kind::TextNote, "test", []) + EventBuilder::new(nostr::Kind::TextNote, "test").tags( []) .custom_created_at(ts) .sign_with_keys(keys) .expect("signing should succeed") diff --git a/crates/sprout-admin/src/main.rs b/crates/sprout-admin/src/main.rs index cd83a03f0..b99868dfa 100644 --- a/crates/sprout-admin/src/main.rs +++ b/crates/sprout-admin/src/main.rs @@ -159,25 +159,25 @@ async fn reconcile_channels(relay_key_arg: Option) -> Result<()> { // kind:39000 — channel metadata { - let mut tags: Vec = vec![Tag::parse(&["d", &channel_id_str])?]; - tags.push(Tag::parse(&["name", &channel.name])?); + let mut tags: Vec = vec![Tag::parse(["d", &channel_id_str])?]; + tags.push(Tag::parse(["name", &channel.name])?); if let Some(ref desc) = channel.description { if !desc.is_empty() { - tags.push(Tag::parse(&["about", desc])?); + tags.push(Tag::parse(["about", desc])?); } } if channel.visibility == "private" { - tags.push(Tag::parse(&["private"])?); + tags.push(Tag::parse(["private"])?); } else { - tags.push(Tag::parse(&["public"])?); + tags.push(Tag::parse(["public"])?); } if channel.channel_type == "dm" { - tags.push(Tag::parse(&["hidden"])?); + tags.push(Tag::parse(["hidden"])?); } - tags.push(Tag::parse(&["closed"])?); - tags.push(Tag::parse(&["t", &channel.channel_type])?); + tags.push(Tag::parse(["closed"])?); + tags.push(Tag::parse(["t", &channel.channel_type])?); - let event = EventBuilder::new(Kind::Custom(39000), "", tags) + let event = EventBuilder::new(Kind::Custom(39000), "").tags( tags) .sign_with_keys(&relay_keys) .map_err(|e| anyhow::anyhow!("sign kind:39000: {e}"))?; db.replace_addressable_event(&event, Some(channel.id)) @@ -186,15 +186,15 @@ async fn reconcile_channels(relay_key_arg: Option) -> Result<()> { // kind:39001 — admins { - let mut tags: Vec = vec![Tag::parse(&["d", &channel_id_str])?]; + let mut tags: Vec = vec![Tag::parse(["d", &channel_id_str])?]; for m in members .iter() .filter(|m| m.role == "owner" || m.role == "admin") { let pk = hex::encode(&m.pubkey); - tags.push(Tag::parse(&["p", &pk, &m.role])?); + tags.push(Tag::parse(["p", &pk, &m.role])?); } - let event = EventBuilder::new(Kind::Custom(KIND_NIP29_GROUP_ADMINS as u16), "", tags) + let event = EventBuilder::new(Kind::Custom(KIND_NIP29_GROUP_ADMINS as u16), "").tags( tags) .sign_with_keys(&relay_keys) .map_err(|e| anyhow::anyhow!("sign kind:39001: {e}"))?; db.replace_addressable_event(&event, Some(channel.id)) @@ -203,12 +203,12 @@ async fn reconcile_channels(relay_key_arg: Option) -> Result<()> { // kind:39002 — members { - let mut tags: Vec = vec![Tag::parse(&["d", &channel_id_str])?]; + let mut tags: Vec = vec![Tag::parse(["d", &channel_id_str])?]; for m in &members { let pk = hex::encode(&m.pubkey); - tags.push(Tag::parse(&["p", &pk, "", &m.role])?); + tags.push(Tag::parse(["p", &pk, "", &m.role])?); } - let event = EventBuilder::new(Kind::Custom(39002), "", tags) + let event = EventBuilder::new(Kind::Custom(39002), "").tags( tags) .sign_with_keys(&relay_keys) .map_err(|e| anyhow::anyhow!("sign kind:39002: {e}"))?; db.replace_addressable_event(&event, Some(channel.id)) diff --git a/crates/sprout-auth/src/lib.rs b/crates/sprout-auth/src/lib.rs index 30ff51ba7..86f8ad4db 100644 --- a/crates/sprout-auth/src/lib.rs +++ b/crates/sprout-auth/src/lib.rs @@ -222,7 +222,7 @@ mod tests { #[tokio::test] async fn wrong_kind_rejected() { let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "not auth", []) + let event = EventBuilder::new(Kind::TextNote, "not auth").tags( []) .sign_with_keys(&keys) .expect("sign"); diff --git a/crates/sprout-auth/src/nip42.rs b/crates/sprout-auth/src/nip42.rs index fca8c78c9..605af6993 100644 --- a/crates/sprout-auth/src/nip42.rs +++ b/crates/sprout-auth/src/nip42.rs @@ -130,7 +130,7 @@ mod tests { #[test] fn wrong_kind_rejected() { let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "not auth", []) + let event = EventBuilder::new(Kind::TextNote, "not auth").tags( []) .sign_with_keys(&keys) .expect("sign"); assert!(matches!( diff --git a/crates/sprout-auth/src/nip98.rs b/crates/sprout-auth/src/nip98.rs index b3dd59342..f09b7d146 100644 --- a/crates/sprout-auth/src/nip98.rs +++ b/crates/sprout-auth/src/nip98.rs @@ -168,14 +168,14 @@ mod tests { use nostr::Tag; let mut tags = vec![ - Tag::parse(&["u", url]).unwrap(), - Tag::parse(&["method", method]).unwrap(), + Tag::parse(["u", url]).unwrap(), + Tag::parse(["method", method]).unwrap(), ]; if let Some(hex) = payload_hex { - tags.push(Tag::parse(&["payload", hex]).unwrap()); + tags.push(Tag::parse(["payload", hex]).unwrap()); } - let mut builder = EventBuilder::new(Kind::HttpAuth, "", tags); + let mut builder = EventBuilder::new(Kind::HttpAuth, "").tags( tags); if let Some(ts) = created_at { builder = builder.custom_created_at(ts); } @@ -195,7 +195,7 @@ mod tests { #[test] fn wrong_kind_rejected() { let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "", []) + let event = EventBuilder::new(Kind::TextNote, "").tags( []) .sign_with_keys(&keys) .expect("sign"); let json = serde_json::to_string(&event).unwrap(); diff --git a/crates/sprout-cli/src/client.rs b/crates/sprout-cli/src/client.rs index d14c6bd24..40cddf754 100644 --- a/crates/sprout-cli/src/client.rs +++ b/crates/sprout-cli/src/client.rs @@ -95,20 +95,20 @@ fn sign_nip98( body: Option<&[u8]>, ) -> Result { let mut tags = vec![ - Tag::parse(&["u", url]).map_err(|e| CliError::Other(format!("tag error: {e}")))?, - Tag::parse(&["method", method]).map_err(|e| CliError::Other(format!("tag error: {e}")))?, + Tag::parse(["u", url]).map_err(|e| CliError::Other(format!("tag error: {e}")))?, + Tag::parse(["method", method]).map_err(|e| CliError::Other(format!("tag error: {e}")))?, // Nonce prevents replay rejection for rapid-fire requests with identical bodies. - Tag::parse(&["nonce", &uuid::Uuid::new_v4().to_string()]) + Tag::parse(["nonce", &uuid::Uuid::new_v4().to_string()]) .map_err(|e| CliError::Other(format!("tag error: {e}")))?, ]; if let Some(b) = body { let hash = hex::encode(Sha256::digest(b)); tags.push( - Tag::parse(&["payload", &hash]) + Tag::parse(["payload", &hash]) .map_err(|e| CliError::Other(format!("tag error: {e}")))?, ); } - let event = EventBuilder::new(Kind::Custom(27235), "", tags) + let event = EventBuilder::new(Kind::Custom(27235), "").tags( tags) .sign_with_keys(keys) .map_err(|e| CliError::Other(format!("NIP-98 signing failed: {e}")))?; let json = event.as_json(); @@ -179,7 +179,7 @@ impl SproutClient { /// before calling this method. pub fn sign_event(&self, builder: EventBuilder) -> Result { let builder = if let Some(ref tag) = self.auth_tag { - builder.add_tags([tag.clone()]) + builder.tags([tag.clone()]) } else { builder }; @@ -336,9 +336,9 @@ impl SproutClient { let exp_str = (now + expiry).to_string(); let mut blossom_tags = vec![ - Tag::parse(&["t", "upload"]).map_err(|e| CliError::Other(e.to_string()))?, - Tag::parse(&["x", &sha256]).map_err(|e| CliError::Other(e.to_string()))?, - Tag::parse(&["expiration", &exp_str]).map_err(|e| CliError::Other(e.to_string()))?, + Tag::parse(["t", "upload"]).map_err(|e| CliError::Other(e.to_string()))?, + Tag::parse(["x", &sha256]).map_err(|e| CliError::Other(e.to_string()))?, + Tag::parse(["expiration", &exp_str]).map_err(|e| CliError::Other(e.to_string()))?, ]; // Extract server domain from relay URL for BUD-11 server tag if let Ok(parsed) = url::Url::parse(&self.relay_url) { @@ -348,12 +348,12 @@ impl SproutClient { None => host.to_string(), }; blossom_tags.push( - Tag::parse(&["server", &domain]).map_err(|e| CliError::Other(e.to_string()))?, + Tag::parse(["server", &domain]).map_err(|e| CliError::Other(e.to_string()))?, ); } } - let auth_event = EventBuilder::new(Kind::from(24242), "Upload file", blossom_tags) + let auth_event = EventBuilder::new(Kind::from(24242), "Upload file").tags( blossom_tags) .sign_with_keys(&self.keys) .map_err(|e| CliError::Other(format!("signing failed: {e}")))?; diff --git a/crates/sprout-cli/src/commands/social.rs b/crates/sprout-cli/src/commands/social.rs index 6faac2d2e..7cb3fdb59 100644 --- a/crates/sprout-cli/src/commands/social.rs +++ b/crates/sprout-cli/src/commands/social.rs @@ -141,8 +141,7 @@ fn parse_tags_json(tags_json: &str) -> Result, CliError> { raw_tags .iter() .map(|parts| { - let refs: Vec<&str> = parts.iter().map(String::as_str).collect(); - Tag::parse(&refs).map_err(|e| CliError::Usage(format!("invalid tag {parts:?}: {e}"))) + Tag::parse(parts.iter().map(String::as_str)).map_err(|e| CliError::Usage(format!("invalid tag {parts:?}: {e}"))) }) .collect::>() } @@ -167,7 +166,7 @@ pub async fn cmd_set_list( ))); } - let builder = EventBuilder::new(Kind::Custom(kind), content, tags); + let builder = EventBuilder::new(Kind::Custom(kind), content).tags( tags); let event = client.sign_event(builder)?; let resp = client.submit_event(event).await?; println!("{resp}"); diff --git a/crates/sprout-core/src/engram.rs b/crates/sprout-core/src/engram.rs index 8ab1a0bf2..d730c2bf3 100644 --- a/crates/sprout-core/src/engram.rs +++ b/crates/sprout-core/src/engram.rs @@ -134,7 +134,7 @@ pub fn normalize_slug(raw: &str) -> Result { /// /// `K_c` is symmetric: `derive(seckey_a, pubkey_o) == derive(seckey_o, pubkey_a)`. pub fn conversation_key(my_seckey: &SecretKey, their_pubkey: &PublicKey) -> ConversationKey { - ConversationKey::derive(my_seckey, their_pubkey) + ConversationKey::derive(my_seckey, their_pubkey).expect("valid keys produce conversation key") } /// Compute the `d` tag for a slug under a conversation key. @@ -403,12 +403,12 @@ pub fn build_event( let d = d_tag(&k_c, body.slug()); let tags = vec![ - Tag::parse(&["d", &d]).map_err(|e| EngramError::Encrypt(e.to_string()))?, - Tag::parse(&["p", &owner_pubkey.to_hex()]) + Tag::parse(["d", &d]).map_err(|e| EngramError::Encrypt(e.to_string()))?, + Tag::parse(["p", &owner_pubkey.to_hex()]) .map_err(|e| EngramError::Encrypt(e.to_string()))?, ]; - EventBuilder::new(Kind::Custom(KIND_AGENT_ENGRAM as u16), ciphertext, tags) + EventBuilder::new(Kind::Custom(KIND_AGENT_ENGRAM as u16), ciphertext).tags( tags) .custom_created_at(nostr::Timestamp::from(created_at)) .sign_with_keys(agent_keys) .map_err(|e| EngramError::Sign(e.to_string())) @@ -508,8 +508,8 @@ where I: IntoIterator, { events.into_iter().reduce(|a, b| { - let a_ts = a.created_at.as_u64(); - let b_ts = b.created_at.as_u64(); + let a_ts = a.created_at.as_secs(); + let b_ts = b.created_at.as_secs(); if b_ts > a_ts { return b; } @@ -797,14 +797,13 @@ mod tests { .unwrap(); let upper_d = d.to_uppercase(); let tags = vec![ - Tag::parse(&["d", &upper_d]).unwrap(), - Tag::parse(&["p", &owner.public_key().to_hex()]).unwrap(), + Tag::parse(["d", &upper_d]).unwrap(), + Tag::parse(["p", &owner.public_key().to_hex()]).unwrap(), ]; let tampered = EventBuilder::new( Kind::Custom(KIND_AGENT_ENGRAM as u16), - ev.content.clone(), - tags, - ) + ev.content.clone()).tags( + tags) .custom_created_at(ev.created_at) .sign_with_keys(&agent) .unwrap(); diff --git a/crates/sprout-core/src/event.rs b/crates/sprout-core/src/event.rs index b699a39a6..f30ea9f61 100644 --- a/crates/sprout-core/src/event.rs +++ b/crates/sprout-core/src/event.rs @@ -56,7 +56,7 @@ mod tests { fn make_event() -> nostr::Event { let keys = Keys::generate(); - EventBuilder::new(Kind::TextNote, "hello sprout", []) + EventBuilder::new(Kind::TextNote, "hello sprout").tags( []) .sign_with_keys(&keys) .expect("sign") } diff --git a/crates/sprout-core/src/filter.rs b/crates/sprout-core/src/filter.rs index 000c77c3a..d57bebbb9 100644 --- a/crates/sprout-core/src/filter.rs +++ b/crates/sprout-core/src/filter.rs @@ -91,7 +91,7 @@ mod tests { fn stored_with_tag(tag: Tag) -> StoredEvent { let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "test", [tag]) + let event = EventBuilder::new(Kind::TextNote, "test").tags( [tag]) .sign_with_keys(&keys) .expect("sign"); StoredEvent::with_received_at(event, Utc::now(), None, true) @@ -173,9 +173,8 @@ mod tests { // Event with NO h-tag but with a stored channel_id. let reaction = EventBuilder::new( Kind::Reaction, - "👍", - [Tag::event(nostr::EventId::all_zeros())], - ) + "👍").tags( + [Tag::event(nostr::EventId::all_zeros())]) .sign_with_keys(&keys) .expect("sign"); let stored = StoredEvent::with_received_at(reaction, Utc::now(), Some(channel_id), true); @@ -205,9 +204,8 @@ mod tests { let other_channel = uuid::Uuid::new_v4(); let msg_with_h = EventBuilder::new( Kind::Custom(9), - "hello", - [Tag::parse(&["h", &other_channel.to_string()]).unwrap()], - ) + "hello").tags( + [Tag::parse(["h", &other_channel.to_string()]).unwrap()]) .sign_with_keys(&keys) .expect("sign"); // channel_id matches the filter, but the h-tag points elsewhere. diff --git a/crates/sprout-core/src/lib.rs b/crates/sprout-core/src/lib.rs index a0395a060..77617f60d 100644 --- a/crates/sprout-core/src/lib.rs +++ b/crates/sprout-core/src/lib.rs @@ -47,14 +47,14 @@ pub mod test_helpers { /// Create a signed test event with the given kind and random keys. pub fn make_event(kind: Kind) -> nostr::Event { let keys = Keys::generate(); - EventBuilder::new(kind, "test", []) + EventBuilder::new(kind, "test").tags( []) .sign_with_keys(&keys) .expect("sign") } /// Create a signed test event with the given keys and kind. pub fn make_event_with_keys(keys: &Keys, kind: Kind) -> nostr::Event { - EventBuilder::new(kind, "test", []) + EventBuilder::new(kind, "test").tags( []) .sign_with_keys(keys) .expect("sign") } diff --git a/crates/sprout-core/src/observer.rs b/crates/sprout-core/src/observer.rs index b4972e924..e8c36adf4 100644 --- a/crates/sprout-core/src/observer.rs +++ b/crates/sprout-core/src/observer.rs @@ -126,9 +126,8 @@ mod tests { let event = EventBuilder::new( Kind::Custom(crate::kind::KIND_AGENT_OBSERVER_FRAME as u16), - encrypted, - [Tag::public_key(recipient.public_key())], - ) + encrypted).tags( + [Tag::public_key(recipient.public_key())]) .sign_with_keys(&sender) .expect("sign event"); let decrypted: serde_json::Value = @@ -142,9 +141,8 @@ mod tests { let recipient = Keys::generate(); let event = EventBuilder::new( Kind::Custom(crate::kind::KIND_AGENT_OBSERVER_FRAME as u16), - "not encrypted", - [Tag::public_key(recipient.public_key())], - ) + "not encrypted").tags( + [Tag::public_key(recipient.public_key())]) .sign_with_keys(&sender) .expect("sign event"); diff --git a/crates/sprout-core/src/pairing/crypto.rs b/crates/sprout-core/src/pairing/crypto.rs index 4a36e74c5..c548778b8 100644 --- a/crates/sprout-core/src/pairing/crypto.rs +++ b/crates/sprout-core/src/pairing/crypto.rs @@ -241,10 +241,10 @@ mod tests { // ECDH: source computes shared key with target's pubkey let ecdh_from_src = - nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()); + nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()).unwrap(); // ECDH: target computes shared key with source's pubkey (must match) let ecdh_from_tgt = - nostr::util::generate_shared_key(tgt_keys.secret_key(), &src_keys.public_key()); + nostr::util::generate_shared_key(tgt_keys.secret_key(), &src_keys.public_key()).unwrap(); assert_eq!(ecdh_from_src, ecdh_from_tgt, "ECDH must be symmetric"); @@ -267,7 +267,7 @@ mod tests { let tgt_keys = Keys::new(tgt_sk); let session_id = derive_session_id(&session_secret()); - let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()); + let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()).unwrap(); let (_, sas_input) = derive_sas(&ecdh, &session_secret()); let src_pk: [u8; 32] = src_keys.public_key().to_bytes(); @@ -301,7 +301,7 @@ mod tests { ); // ECDH - let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()); + let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()).unwrap(); assert_eq!( bytes_to_hex(&ecdh), "9b4b6d6990713d89d6d9982e506ee1bbcde6f05c54d9d2978696e8a7274d4408" @@ -343,7 +343,7 @@ mod tests { let tgt_keys = Keys::new(tgt_sk); let session_id = derive_session_id(&session_secret()); - let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()); + let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()).unwrap(); let (_, sas_input) = derive_sas(&ecdh, &session_secret()); let src_pk: [u8; 32] = src_keys.public_key().to_bytes(); @@ -398,9 +398,9 @@ mod tests { // Both sides compute ECDH (symmetric). let ecdh_src = - nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()); + nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()).unwrap(); let ecdh_tgt = - nostr::util::generate_shared_key(tgt_keys.secret_key(), &src_keys.public_key()); + nostr::util::generate_shared_key(tgt_keys.secret_key(), &src_keys.public_key()).unwrap(); assert_eq!(ecdh_src, ecdh_tgt, "ECDH must be symmetric"); // Both sides derive the same SAS. diff --git a/crates/sprout-core/src/pairing/session.rs b/crates/sprout-core/src/pairing/session.rs index 8a58825b7..7f6ccdec8 100644 --- a/crates/sprout-core/src/pairing/session.rs +++ b/crates/sprout-core/src/pairing/session.rs @@ -187,7 +187,8 @@ impl PairingSession { self.peer_pubkey = Some(peer); // Compute ECDH and SAS. Zero the ECDH shared secret after derivation. - let mut ecdh = nostr::util::generate_shared_key(self.keys.secret_key(), &peer); + let mut ecdh = nostr::util::generate_shared_key(self.keys.secret_key(), &peer) + .map_err(|e| PairingError::SigningError(e.to_string()))?; let (code, sas_input) = derive_sas(&ecdh, &self.session_secret); ecdh.zeroize(); self.sas_code = Some(code); @@ -294,7 +295,8 @@ impl PairingSession { // Compute ECDH and SAS immediately (target knows source pubkey from QR). // Zero the ECDH shared secret after derivation. - let mut ecdh = nostr::util::generate_shared_key(keys.secret_key(), &qr.source_pubkey); + let mut ecdh = nostr::util::generate_shared_key(keys.secret_key(), &qr.source_pubkey) + .map_err(|e| PairingError::SigningError(e.to_string()))?; let (code, sas_input) = derive_sas(&ecdh, &qr.session_secret); ecdh.zeroize(); @@ -584,15 +586,14 @@ impl PairingSession { // NIP-AB §: Implementations SHOULD set created_at to the current time // minus a random value between 0 and 30 seconds for metadata privacy. - let now = nostr::Timestamp::now().as_u64(); + let now = nostr::Timestamp::now().as_secs(); let jitter = rand::random::() % 31; // 0-30s jitter per NIP-AB §Metadata Privacy let ts = nostr::Timestamp::from(now.saturating_sub(jitter)); EventBuilder::new( Kind::Custom(PAIRING_KIND), - &encrypted, - [Tag::public_key(peer)], - ) + &encrypted).tags( + [Tag::public_key(peer)]) .custom_created_at(ts) .sign_with_keys(&self.keys) .map_err(|e| PairingError::SigningError(e.to_string())) @@ -929,9 +930,8 @@ mod tests { .unwrap(); let fake_abort = EventBuilder::new( Kind::Custom(crate::kind::KIND_PAIRING as u16), - &encrypted, - [Tag::public_key(source.pubkey())], - ) + &encrypted).tags( + [Tag::public_key(source.pubkey())]) .sign_with_keys(&rogue) .unwrap(); @@ -984,9 +984,8 @@ mod tests { .unwrap(); EventBuilder::new( Kind::Custom(crate::kind::KIND_PAIRING as u16), - &encrypted, - [Tag::public_key(source.pubkey())], - ) + &encrypted).tags( + [Tag::public_key(source.pubkey())]) .sign_with_keys(&keys) .unwrap() }; @@ -1044,9 +1043,8 @@ mod tests { .unwrap(); let fake_event = EventBuilder::new( Kind::Custom(PAIRING_KIND), - &encrypted, - [Tag::public_key(target.pubkey())], - ) + &encrypted).tags( + [Tag::public_key(target.pubkey())]) .sign_with_keys(&rogue_keys) .unwrap(); @@ -1265,9 +1263,8 @@ mod tests { .unwrap(); let wrong_event = EventBuilder::new( Kind::Custom(PAIRING_KIND), - &wrong_encrypted, - [Tag::public_key(target.pubkey())], - ) + &wrong_encrypted).tags( + [Tag::public_key(target.pubkey())]) .sign_with_keys(&source.keys) .unwrap(); @@ -1320,9 +1317,8 @@ mod tests { .unwrap(); let fail_event = EventBuilder::new( Kind::Custom(PAIRING_KIND), - &fail_encrypted, - [Tag::public_key(source.pubkey())], - ) + &fail_encrypted).tags( + [Tag::public_key(source.pubkey())]) .sign_with_keys(&target.keys) .unwrap(); diff --git a/crates/sprout-core/src/verification.rs b/crates/sprout-core/src/verification.rs index cb2e4b194..e8c6a879f 100644 --- a/crates/sprout-core/src/verification.rs +++ b/crates/sprout-core/src/verification.rs @@ -14,7 +14,7 @@ pub fn verify_event(event: &Event) -> Result<(), VerificationError> { &event.pubkey, &event.created_at, &event.kind, - event.tags.as_slice(), + &event.tags, &event.content, ) .to_hex(); @@ -38,7 +38,7 @@ mod tests { fn make_valid_event() -> Event { let keys = Keys::generate(); - EventBuilder::new(Kind::TextNote, "test content", []) + EventBuilder::new(Kind::TextNote, "test content").tags( []) .sign_with_keys(&keys) .expect("sign") } @@ -46,7 +46,7 @@ mod tests { #[test] fn rejects_tampered_id() { let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "original", []) + let event = EventBuilder::new(Kind::TextNote, "original").tags( []) .sign_with_keys(&keys) .expect("sign"); let mut json: serde_json::Value = serde_json::from_str(&event.as_json()).expect("parse"); diff --git a/crates/sprout-db/src/channel.rs b/crates/sprout-db/src/channel.rs index dbf8f0310..0ad22c4c5 100644 --- a/crates/sprout-db/src/channel.rs +++ b/crates/sprout-db/src/channel.rs @@ -1202,7 +1202,7 @@ mod tests { } fn random_pubkey() -> Vec { - Keys::generate().public_key().serialize().to_vec() + Keys::generate().public_key().to_bytes().to_vec() } /// Agent owner (non-admin) can remove their own bot from a channel. diff --git a/crates/sprout-db/src/event.rs b/crates/sprout-db/src/event.rs index 22b01f880..c54f787df 100644 --- a/crates/sprout-db/src/event.rs +++ b/crates/sprout-db/src/event.rs @@ -988,7 +988,7 @@ mod tests { fn make_event_with_kind_and_tags(kind: u16, tags: Vec) -> nostr::Event { let keys = Keys::generate(); - EventBuilder::new(Kind::Custom(kind), "test", tags) + EventBuilder::new(Kind::Custom(kind), "test").tags( tags) .sign_with_keys(&keys) .expect("sign") } @@ -997,7 +997,7 @@ mod tests { fn extract_d_tag_from_nip33_event() { let event = make_event_with_kind_and_tags( 30023, - vec![Tag::parse(&["d", "my-article-slug"]).unwrap()], + vec![Tag::parse(["d", "my-article-slug"]).unwrap()], ); assert_eq!(extract_d_tag(&event), Some("my-article-slug".to_string())); } @@ -1007,8 +1007,8 @@ mod tests { let event = make_event_with_kind_and_tags( 30023, vec![ - Tag::parse(&["d", "first"]).unwrap(), - Tag::parse(&["d", "second"]).unwrap(), + Tag::parse(["d", "first"]).unwrap(), + Tag::parse(["d", "second"]).unwrap(), ], ); assert_eq!(extract_d_tag(&event), Some("first".to_string())); @@ -1018,13 +1018,13 @@ mod tests { fn extract_d_tag_missing_becomes_empty_string() { // NIP-33: "if there is no d tag, the d tag is considered to be ''" let event = - make_event_with_kind_and_tags(30023, vec![Tag::parse(&["p", "abc123"]).unwrap()]); + make_event_with_kind_and_tags(30023, vec![Tag::parse(["p", "abc123"]).unwrap()]); assert_eq!(extract_d_tag(&event), Some(String::new())); } #[test] fn extract_d_tag_empty_value_preserved() { - let event = make_event_with_kind_and_tags(30023, vec![Tag::parse(&["d", ""]).unwrap()]); + let event = make_event_with_kind_and_tags(30023, vec![Tag::parse(["d", ""]).unwrap()]); assert_eq!(extract_d_tag(&event), Some(String::new())); } @@ -1033,7 +1033,7 @@ mod tests { // kind:1 (text note) — not parameterized replaceable let event = make_event_with_kind_and_tags( 1, - vec![Tag::parse(&["d", "should-be-ignored"]).unwrap()], + vec![Tag::parse(["d", "should-be-ignored"]).unwrap()], ); assert_eq!(extract_d_tag(&event), None); } @@ -1042,33 +1042,33 @@ mod tests { fn extract_d_tag_nip29_group_metadata() { // kind:39000 is in the 30000–39999 range — d_tag should be extracted let event = - make_event_with_kind_and_tags(39000, vec![Tag::parse(&["d", "group-id"]).unwrap()]); + make_event_with_kind_and_tags(39000, vec![Tag::parse(["d", "group-id"]).unwrap()]); assert_eq!(extract_d_tag(&event), Some("group-id".to_string())); } #[test] fn extract_d_tag_boundary_kinds() { // kind:29999 — just below range - let below = make_event_with_kind_and_tags(29999, vec![Tag::parse(&["d", "val"]).unwrap()]); + let below = make_event_with_kind_and_tags(29999, vec![Tag::parse(["d", "val"]).unwrap()]); assert_eq!(extract_d_tag(&below), None); // kind:30000 — lower bound - let lower = make_event_with_kind_and_tags(30000, vec![Tag::parse(&["d", "val"]).unwrap()]); + let lower = make_event_with_kind_and_tags(30000, vec![Tag::parse(["d", "val"]).unwrap()]); assert_eq!(extract_d_tag(&lower), Some("val".to_string())); // kind:39999 — upper bound - let upper = make_event_with_kind_and_tags(39999, vec![Tag::parse(&["d", "val"]).unwrap()]); + let upper = make_event_with_kind_and_tags(39999, vec![Tag::parse(["d", "val"]).unwrap()]); assert_eq!(extract_d_tag(&upper), Some("val".to_string())); // kind:40000 — just above range - let above = make_event_with_kind_and_tags(40000, vec![Tag::parse(&["d", "val"]).unwrap()]); + let above = make_event_with_kind_and_tags(40000, vec![Tag::parse(["d", "val"]).unwrap()]); assert_eq!(extract_d_tag(&above), None); } #[test] fn extract_d_tag_single_element_d_tag_ignored() { // A d tag with only one element (no value) should not match — parts.len() < 2 - let event = make_event_with_kind_and_tags(30023, vec![Tag::parse(&["d"]).unwrap()]); + let event = make_event_with_kind_and_tags(30023, vec![Tag::parse(["d"]).unwrap()]); // No d tag with a value → empty string per NIP-33 assert_eq!(extract_d_tag(&event), Some(String::new())); } @@ -1078,7 +1078,7 @@ mod tests { // extract_d_tag returns the full value — length enforcement is at the ingest layer. let long_val = "x".repeat(2048); let event = - make_event_with_kind_and_tags(30023, vec![Tag::parse(&["d", &long_val]).unwrap()]); + make_event_with_kind_and_tags(30023, vec![Tag::parse(["d", &long_val]).unwrap()]); let result = extract_d_tag(&event).unwrap(); assert_eq!(result.len(), 2048); assert_eq!(result, long_val); diff --git a/crates/sprout-db/src/user.rs b/crates/sprout-db/src/user.rs index 721e9c4e5..a8b365716 100644 --- a/crates/sprout-db/src/user.rs +++ b/crates/sprout-db/src/user.rs @@ -382,7 +382,7 @@ mod tests { } fn random_pubkey() -> Vec { - Keys::generate().public_key().serialize().to_vec() + Keys::generate().public_key().to_bytes().to_vec() } /// Setting an agent owner then reading back the policy should return @@ -549,7 +549,7 @@ mod tests { #[ignore = "requires Postgres"] async fn test_set_channel_add_policy_rejects_invalid() { let db = setup_db().await; - let pubkey = nostr::Keys::generate().public_key().serialize().to_vec(); + let pubkey = nostr::Keys::generate().public_key().to_bytes().to_vec(); ensure_user(&db.pool, &pubkey).await.unwrap(); let result = set_channel_add_policy(&db.pool, &pubkey, "invalid_policy").await; assert!(result.is_err(), "should reject invalid policy value"); diff --git a/crates/sprout-mcp/src/relay_client.rs b/crates/sprout-mcp/src/relay_client.rs index bc7a08843..7db858fcb 100644 --- a/crates/sprout-mcp/src/relay_client.rs +++ b/crates/sprout-mcp/src/relay_client.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::time::Duration; use futures_util::{SinkExt, StreamExt}; -use nostr::{Event, EventBuilder, Filter, Keys, Kind, Tag, Url}; +use nostr::{Event, EventBuilder, Filter, Keys, Kind, RelayUrl, Tag, Url}; use serde_json::{json, Value}; use thiserror::Error; use tokio::sync::{mpsc, oneshot}; @@ -330,32 +330,31 @@ fn build_auth_event( api_token: Option<&str>, auth_tag: Option<&Tag>, ) -> Result { - let relay_nostr_url: Url = relay_url - .parse() - .map_err(|e: url::ParseError| RelayClientError::Url(e.to_string()))?; + let relay_nostr_url = RelayUrl::parse(relay_url) + .map_err(|e| RelayClientError::Url(e.to_string()))?; if let Some(token) = api_token { let mut tags = vec![ - Tag::parse(&["relay", relay_url]) + Tag::parse(["relay", relay_url]) .map_err(|e| RelayClientError::EventBuilder(e.to_string()))?, - Tag::parse(&["challenge", challenge]) + Tag::parse(["challenge", challenge]) .map_err(|e| RelayClientError::EventBuilder(e.to_string()))?, - Tag::parse(&["auth_token", token]) + Tag::parse(["auth_token", token]) .map_err(|e| RelayClientError::EventBuilder(e.to_string()))?, ]; if let Some(t) = auth_tag { tags.push(t.clone()); } - Ok(EventBuilder::new(Kind::Authentication, "", tags).sign_with_keys(keys)?) + Ok(EventBuilder::new(Kind::Authentication, "").tags( tags).sign_with_keys(keys)?) } else if let Some(t) = auth_tag { // Cannot use EventBuilder::auth() shortcut — it doesn't accept extra tags. let tags = vec![ - Tag::parse(&["relay", relay_url]) + Tag::parse(["relay", relay_url]) .map_err(|e| RelayClientError::EventBuilder(e.to_string()))?, - Tag::parse(&["challenge", challenge]) + Tag::parse(["challenge", challenge]) .map_err(|e| RelayClientError::EventBuilder(e.to_string()))?, t.clone(), ]; - Ok(EventBuilder::new(Kind::Authentication, "", tags).sign_with_keys(keys)?) + Ok(EventBuilder::new(Kind::Authentication, "").tags( tags).sign_with_keys(keys)?) } else { Ok(EventBuilder::auth(challenge, relay_nostr_url).sign_with_keys(keys)?) } @@ -774,7 +773,7 @@ impl RelayClient { /// `self.auth_tag` is configured or not — is rejected immediately. pub fn sign_event(&self, builder: EventBuilder) -> Result { let builder = if let Some(ref tag) = self.auth_tag { - builder.add_tags([tag.clone()]) + builder.tags([tag.clone()]) } else { builder }; @@ -1303,12 +1302,12 @@ mod tests { let owner_pubkey = "a".repeat(64); let conditions = ""; let signature = "b".repeat(128); - let auth_tag = nostr::Tag::parse(&["auth", &owner_pubkey, conditions, &signature]).unwrap(); + let auth_tag = nostr::Tag::parse(["auth", &owner_pubkey, conditions, &signature]).unwrap(); // With auth_tag: the signed event must contain it. let client = make_client(keys.clone(), Some(auth_tag.clone())); let event = client - .sign_event(EventBuilder::new(Kind::TextNote, "hello", [])) + .sign_event(EventBuilder::new(Kind::TextNote, "hello").tags( [])) .expect("sign_event should succeed"); let tag_values: Vec> = event @@ -1328,7 +1327,7 @@ mod tests { // Without auth_tag: the signed event must NOT contain an auth tag. let client_no_auth = make_client(keys, None); let event_no_auth = client_no_auth - .sign_event(EventBuilder::new(Kind::TextNote, "hello", [])) + .sign_event(EventBuilder::new(Kind::TextNote, "hello").tags( [])) .expect("sign_event should succeed"); let has_auth_tag = event_no_auth @@ -1345,11 +1344,11 @@ mod tests { let owner_pubkey = "c".repeat(64); let conditions = ""; let signature = "d".repeat(128); - let auth_tag = nostr::Tag::parse(&["auth", &owner_pubkey, conditions, &signature]).unwrap(); + let auth_tag = nostr::Tag::parse(["auth", &owner_pubkey, conditions, &signature]).unwrap(); // Case 1: client has auth_tag configured, caller also pre-adds one → duplicate → reject. let client = make_client(keys.clone(), Some(auth_tag.clone())); - let builder = EventBuilder::new(Kind::TextNote, "oops", [auth_tag.clone()]); + let builder = EventBuilder::new(Kind::TextNote, "oops").tags( [auth_tag.clone()]); let result = client.sign_event(builder); assert!( result.is_err(), @@ -1363,7 +1362,7 @@ mod tests { // Case 2: client has NO auth_tag configured, but caller manually adds one → bypass → reject. let client_no_auth = make_client(keys, None); - let builder_with_manual = EventBuilder::new(Kind::TextNote, "bypass", [auth_tag]); + let builder_with_manual = EventBuilder::new(Kind::TextNote, "bypass").tags( [auth_tag]); let result2 = client_no_auth.sign_event(builder_with_manual); assert!( result2.is_err(), @@ -1381,14 +1380,14 @@ mod tests { #[tokio::test] async fn test_send_event_rejects_forged_auth_tag() { let keys = Keys::generate(); - let real_tag = nostr::Tag::parse(&["auth", &"a".repeat(64), "", &"b".repeat(128)]).unwrap(); + let real_tag = nostr::Tag::parse(["auth", &"a".repeat(64), "", &"b".repeat(128)]).unwrap(); let forged_tag = - nostr::Tag::parse(&["auth", &"c".repeat(64), "", &"d".repeat(128)]).unwrap(); + nostr::Tag::parse(["auth", &"c".repeat(64), "", &"d".repeat(128)]).unwrap(); let client = make_client(keys.clone(), Some(real_tag)); // Build event with forged auth tag, bypassing sign_event - let event = EventBuilder::new(Kind::TextNote, "forged", [forged_tag]) + let event = EventBuilder::new(Kind::TextNote, "forged").tags( [forged_tag]) .sign_with_keys(&keys) .unwrap(); @@ -1405,11 +1404,11 @@ mod tests { async fn test_send_event_rejects_auth_tag_when_unconfigured() { let keys = Keys::generate(); let sneaky_tag = - nostr::Tag::parse(&["auth", &"a".repeat(64), "", &"b".repeat(128)]).unwrap(); + nostr::Tag::parse(["auth", &"a".repeat(64), "", &"b".repeat(128)]).unwrap(); let client = make_client(keys.clone(), None); - let event = EventBuilder::new(Kind::TextNote, "sneaky", [sneaky_tag]) + let event = EventBuilder::new(Kind::TextNote, "sneaky").tags( [sneaky_tag]) .sign_with_keys(&keys) .unwrap(); @@ -1427,7 +1426,7 @@ mod tests { let client = make_client(client_keys, None); // Event signed by a different keypair - let event = EventBuilder::new(Kind::TextNote, "wrong author", []) + let event = EventBuilder::new(Kind::TextNote, "wrong author").tags( []) .sign_with_keys(&other_keys) .unwrap(); @@ -1593,7 +1592,7 @@ mod tests { let keys = Keys::generate(); let client = RelayClient::connect(&url, &keys, None, None).await.unwrap(); - let event = EventBuilder::new(Kind::Custom(9), "test", []) + let event = EventBuilder::new(Kind::Custom(9), "test").tags( []) .sign_with_keys(&keys) .unwrap(); let expected_id = event.id.to_hex(); @@ -1621,7 +1620,7 @@ mod tests { // Build 3 minimal valid events. let relay_keys = Keys::generate(); for i in 0u8..3 { - let ev = EventBuilder::new(Kind::TextNote, format!("msg {i}"), []) + let ev = EventBuilder::new(Kind::TextNote, format!("msg {i}")).tags( []) .sign_with_keys(&relay_keys) .unwrap(); let frame = serde_json::to_string(&serde_json::json!([ @@ -1671,7 +1670,7 @@ mod tests { let keys = Keys::generate(); let client = RelayClient::connect(&url, &keys, None, None).await.unwrap(); - let event = EventBuilder::new(Kind::Custom(9), "timeout-test", []) + let event = EventBuilder::new(Kind::Custom(9), "timeout-test").tags( []) .sign_with_keys(&keys) .unwrap(); @@ -1868,7 +1867,7 @@ mod tests { .unwrap(); // send_event should succeed on the new connection. - let event = EventBuilder::new(Kind::Custom(9), "after-reconnect", []) + let event = EventBuilder::new(Kind::Custom(9), "after-reconnect").tags( []) .sign_with_keys(&keys) .unwrap(); let ok = client.send_event(event).await.unwrap(); diff --git a/crates/sprout-mcp/src/server.rs b/crates/sprout-mcp/src/server.rs index 48453a9cf..e1d5b203c 100644 --- a/crates/sprout-mcp/src/server.rs +++ b/crates/sprout-mcp/src/server.rs @@ -123,7 +123,7 @@ async fn resolve_content_mentions( // Query channel membership (kind 39002, addressed by `d` tag). let filter = Filter::new() .kind(k(kind::KIND_NIP29_GROUP_MEMBERS)) - .custom_tag(tag_d(), [channel_id]) + .custom_tags(tag_d(), [channel_id]) .limit(1); let Ok(events) = client.query(vec![filter]).await else { return vec![]; @@ -1328,7 +1328,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi let mut filter = Filter::new() .kinds(message_kinds) - .custom_tag(tag_h(), [&p.channel_id]) + .custom_tags(tag_h(), [&p.channel_id]) .limit(limit); if let Some(before) = p.before { @@ -1476,7 +1476,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi // Canvas is kind:40100 with #h tag = channel_id let filter = Filter::new() .kind(k(kind::KIND_CANVAS)) - .custom_tag(tag_h(), [&p.channel_id]) + .custom_tags(tag_h(), [&p.channel_id]) .limit(1); match self.client.query(vec![filter]).await { @@ -1534,7 +1534,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi // Workflows are kind:30620 (param-replaceable) with #h tag = channel_id let filter = Filter::new() .kind(k(kind::KIND_WORKFLOW_DEF)) - .custom_tag(tag_h(), [&p.channel_id]) + .custom_tags(tag_h(), [&p.channel_id]) .limit(100); match self.client.query(vec![filter]).await { @@ -1576,10 +1576,10 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi // d-tag = workflow UUID, h-tag = channel_id, content = YAML. let workflow_id = uuid::Uuid::new_v4().to_string(); let tags = vec![ - Tag::parse(&["d", &workflow_id]).unwrap(), - Tag::parse(&["h", &p.channel_id]).unwrap(), + Tag::parse(["d", &workflow_id]).unwrap(), + Tag::parse(["h", &p.channel_id]).unwrap(), ]; - let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_DEF), &p.yaml_definition, tags); + let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_DEF), &p.yaml_definition).tags( tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign workflow event: {e}"), @@ -1606,8 +1606,8 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi return format!("Error: workflow_id '{}' is not a valid UUID", p.workflow_id); } // Publish a new kind:30620 event with the same d-tag to replace the existing one. - let tags = vec![Tag::parse(&["d", &p.workflow_id]).unwrap()]; - let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_DEF), &p.yaml_definition, tags); + let tags = vec![Tag::parse(["d", &p.workflow_id]).unwrap()]; + let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_DEF), &p.yaml_definition).tags( tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign workflow event: {e}"), @@ -1636,8 +1636,8 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi self.client.pubkey_hex(), p.workflow_id ); - let tags = vec![Tag::parse(&["a", &coordinate]).unwrap()]; - let builder = EventBuilder::new(k(kind::KIND_DELETION), "", tags); + let tags = vec![Tag::parse(["a", &coordinate]).unwrap()]; + let builder = EventBuilder::new(k(kind::KIND_DELETION), "").tags( tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign deletion event: {e}"), @@ -1665,8 +1665,8 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi .inputs .unwrap_or(serde_json::Value::Object(Default::default())); let content = serde_json::to_string(&inputs).unwrap_or_default(); - let tags = vec![Tag::parse(&["d", &p.workflow_id]).unwrap()]; - let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_TRIGGER), &content, tags); + let tags = vec![Tag::parse(["d", &p.workflow_id]).unwrap()]; + let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_TRIGGER), &content).tags( tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign trigger event: {e}"), @@ -1702,7 +1702,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi k(kind::KIND_WORKFLOW_TRIGGERED + 1), // completed k(kind::KIND_WORKFLOW_TRIGGERED + 2), // failed ]) - .custom_tag(tag_d(), [&p.workflow_id]) + .custom_tags(tag_d(), [&p.workflow_id]) .limit(limit); match self.client.query(vec![filter]).await { @@ -1745,8 +1745,8 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi let content = p.note.as_deref().unwrap_or(""); // The relay expects d-tag = hex(SHA256(token)), not the raw token UUID. let token_hash = hex::encode(Sha256::digest(p.approval_token.as_bytes())); - let tags = vec![Tag::parse(&["d", &token_hash]).unwrap()]; - let builder = EventBuilder::new(k(kind_num), content, tags); + let tags = vec![Tag::parse(["d", &token_hash]).unwrap()]; + let builder = EventBuilder::new(k(kind_num), content).tags( tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign approval event: {e}"), @@ -1780,7 +1780,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi // Query events that mention this agent (p-tag) or are in channels we're in. let my_pubkey = self.client.pubkey_hex(); - let mut filter = Filter::new().custom_tag(tag_p(), [&my_pubkey]).limit(limit); + let mut filter = Filter::new().custom_tags(tag_p(), [&my_pubkey]).limit(limit); if let Some(since) = p.since { filter = filter.since(nostr::Timestamp::from(since as u64)); @@ -1908,7 +1908,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi // Query membership list event (kind:39002 = NIP-29 group members) for this channel. let filter = Filter::new() .kind(k(kind::KIND_NIP29_GROUP_MEMBERS)) - .custom_tag(tag_d(), [&p.channel_id]) + .custom_tags(tag_d(), [&p.channel_id]) .limit(1); match self.client.query(vec![filter]).await { @@ -2011,7 +2011,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi // Query channel metadata (kind:41) with d-tag = channel_id. let filter = Filter::new() .kind(k(kind::KIND_CHANNEL_METADATA)) - .custom_tag(tag_d(), [&p.channel_id]) + .custom_tags(tag_d(), [&p.channel_id]) .limit(1); match self.client.query(vec![filter]).await { @@ -2227,8 +2227,8 @@ with kind:45003 comments)." // Query events that reference the root event via e-tag. let filter = Filter::new() - .custom_tag(tag_e(), [&p.event_id]) - .custom_tag(tag_h(), [&p.channel_id]) + .custom_tags(tag_e(), [&p.event_id]) + .custom_tags(tag_h(), [&p.channel_id]) .limit(limit); // Also fetch the root event itself. @@ -2282,13 +2282,13 @@ with kind:45003 comments)." let mut tags: Vec = p .pubkeys .iter() - .filter_map(|pk| Tag::parse(&["p", pk]).ok()) + .filter_map(|pk| Tag::parse(["p", pk]).ok()) .collect(); // Add a unique d-tag so the relay can deduplicate. let dm_id = uuid::Uuid::new_v4().to_string(); - tags.push(Tag::parse(&["d", &dm_id]).unwrap()); + tags.push(Tag::parse(["d", &dm_id]).unwrap()); - let builder = EventBuilder::new(k(kind::KIND_DM_OPEN), "", tags); + let builder = EventBuilder::new(k(kind::KIND_DM_OPEN), "").tags( tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign open_dm event: {e}"), @@ -2316,10 +2316,10 @@ with kind:45003 comments)." } // Command event kind:41011 with h-tag = DM channel, p-tag = new member. let tags = vec![ - Tag::parse(&["h", &p.channel_id]).unwrap(), - Tag::parse(&["p", &p.pubkey]).unwrap(), + Tag::parse(["h", &p.channel_id]).unwrap(), + Tag::parse(["p", &p.pubkey]).unwrap(), ]; - let builder = EventBuilder::new(k(kind::KIND_DM_ADD_MEMBER), "", tags); + let builder = EventBuilder::new(k(kind::KIND_DM_ADD_MEMBER), "").tags( tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign add_dm_member event: {e}"), @@ -2345,7 +2345,7 @@ with kind:45003 comments)." let my_pubkey = self.client.pubkey_hex(); let filter = Filter::new() .kind(k(kind::KIND_DM_CREATED)) - .custom_tag(tag_p(), [&my_pubkey]) + .custom_tags(tag_p(), [&my_pubkey]) .limit(100); match self.client.query(vec![filter]).await { @@ -2389,8 +2389,8 @@ with kind:45003 comments)." return format!("Error: {e}"); } // Command event kind:41012 with h-tag = DM channel. - let tags = vec![Tag::parse(&["h", &p.channel_id]).unwrap()]; - let builder = EventBuilder::new(k(kind::KIND_DM_HIDE), "", tags); + let tags = vec![Tag::parse(["h", &p.channel_id]).unwrap()]; + let builder = EventBuilder::new(k(kind::KIND_DM_HIDE), "").tags( tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign hide_dm event: {e}"), @@ -2447,7 +2447,7 @@ with kind:45003 comments)." let filter = Filter::new() .kind(k(kind::KIND_REACTION)) .author(my_pubkey_parsed) - .custom_tag(tag_e(), [&p.event_id]) + .custom_tags(tag_e(), [&p.event_id]) .limit(50); let events = match self.client.query(vec![filter]).await { @@ -2493,7 +2493,7 @@ with kind:45003 comments)." // Query kind:7 reactions referencing this event via e-tag. let filter = Filter::new() .kind(k(kind::KIND_REACTION)) - .custom_tag(tag_e(), [&p.event_id]) + .custom_tags(tag_e(), [&p.event_id]) .limit(200); match self.client.query(vec![filter]).await { @@ -2730,7 +2730,7 @@ with kind:45003 comments)." pub async fn set_presence(&self, Parameters(p): Parameters) -> String { // Validate status value. // Publish ephemeral presence event (kind:20001). - let builder = EventBuilder::new(k(kind::KIND_PRESENCE_UPDATE), p.status.as_str(), []); + let builder = EventBuilder::new(k(kind::KIND_PRESENCE_UPDATE), p.status.as_str()).tags( []); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign presence event: {e}"), @@ -2762,7 +2762,7 @@ with kind:45003 comments)." } // Store as a kind:10100 (agent profile) replaceable event with the policy in content. let content = serde_json::json!({ "channel_add_policy": p.policy }).to_string(); - let builder = EventBuilder::new(k(kind::KIND_AGENT_PROFILE), &content, []); + let builder = EventBuilder::new(k(kind::KIND_AGENT_PROFILE), &content).tags( []); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign agent profile event: {e}"), diff --git a/crates/sprout-mcp/src/upload.rs b/crates/sprout-mcp/src/upload.rs index 9ac75c2c0..993513860 100644 --- a/crates/sprout-mcp/src/upload.rs +++ b/crates/sprout-mcp/src/upload.rs @@ -5,7 +5,7 @@ //! `/media/upload` endpoint, and returns a [`BlobDescriptor`]. use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; -use nostr::util::hex as nostr_hex; +use hex; use nostr::{EventBuilder, JsonUtil, Keys, Kind, Tag, Timestamp}; use sha2::{Digest, Sha256}; @@ -183,7 +183,7 @@ pub async fn upload_file( } // 5. Compute SHA-256 - let sha256 = nostr_hex::encode(Sha256::digest(&bytes)); + let sha256 = hex::encode(Sha256::digest(&bytes)); // 6. Sign Blossom auth event (kind:24242) let now = Timestamp::now().as_u64(); @@ -195,19 +195,19 @@ pub async fn upload_file( let exp_str = (now + expiry).to_string(); let mut tags = vec![ - Tag::parse(&["t", "upload"]).map_err(|e| UploadError::SigningFailed(e.to_string()))?, - Tag::parse(&["x", &sha256]).map_err(|e| UploadError::SigningFailed(e.to_string()))?, - Tag::parse(&["expiration", &exp_str]) + Tag::parse(["t", "upload"]).map_err(|e| UploadError::SigningFailed(e.to_string()))?, + Tag::parse(["x", &sha256]).map_err(|e| UploadError::SigningFailed(e.to_string()))?, + Tag::parse(["expiration", &exp_str]) .map_err(|e| UploadError::SigningFailed(e.to_string()))?, ]; if let Some(domain) = server_domain { tags.push( - Tag::parse(&["server", domain]) + Tag::parse(["server", domain]) .map_err(|e| UploadError::SigningFailed(e.to_string()))?, ); } - let auth_event = EventBuilder::new(Kind::from(24242), "Upload file", tags) + let auth_event = EventBuilder::new(Kind::from(24242), "Upload file").tags( tags) .sign_with_keys(keys) .map_err(|e| UploadError::SigningFailed(e.to_string()))?; diff --git a/crates/sprout-media/src/auth.rs b/crates/sprout-media/src/auth.rs index 49e5db60d..45286d1c3 100644 --- a/crates/sprout-media/src/auth.rs +++ b/crates/sprout-media/src/auth.rs @@ -143,11 +143,11 @@ mod tests { let now = Timestamp::now().as_u64(); let exp_str = (now + 300).to_string(); let tags = vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", sha256]).unwrap(), - Tag::parse(&["expiration", &exp_str]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", sha256]).unwrap(), + Tag::parse(["expiration", &exp_str]).unwrap(), ]; - EventBuilder::new(Kind::from(24242), "Upload sprout-media", tags) + EventBuilder::new(Kind::from(24242), "Upload sprout-media").tags( tags) .sign_with_keys(keys) .unwrap() } @@ -187,11 +187,11 @@ mod tests { let now = Timestamp::now().as_u64(); let exp_str = (now + 300).to_string(); let tags = vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", &sha256]).unwrap(), - Tag::parse(&["expiration", &exp_str]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), + Tag::parse(["expiration", &exp_str]).unwrap(), ]; - let event = EventBuilder::new(Kind::from(27235), "wrong kind", tags) + let event = EventBuilder::new(Kind::from(27235), "wrong kind").tags( tags) .sign_with_keys(&keys) .unwrap(); assert!(matches!( @@ -208,12 +208,12 @@ mod tests { let now = Timestamp::now().as_u64(); let exp_str = (now + 300).to_string(); let tags = vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", &other_hash]).unwrap(), - Tag::parse(&["x", &sha256]).unwrap(), - Tag::parse(&["expiration", &exp_str]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", &other_hash]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), + Tag::parse(["expiration", &exp_str]).unwrap(), ]; - let event = EventBuilder::new(Kind::from(24242), "Upload multi-x", tags) + let event = EventBuilder::new(Kind::from(24242), "Upload multi-x").tags( tags) .sign_with_keys(&keys) .unwrap(); // Should pass because at least one x tag matches @@ -227,12 +227,12 @@ mod tests { let now = Timestamp::now().as_u64(); let exp_str = (now + 300).to_string(); let tags = vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", &sha256]).unwrap(), - Tag::parse(&["expiration", &exp_str]).unwrap(), - Tag::parse(&["server", "other.example.com"]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), + Tag::parse(["expiration", &exp_str]).unwrap(), + Tag::parse(["server", "other.example.com"]).unwrap(), ]; - let event = EventBuilder::new(Kind::from(24242), "Upload scoped", tags) + let event = EventBuilder::new(Kind::from(24242), "Upload scoped").tags( tags) .sign_with_keys(&keys) .unwrap(); // Should fail — server tag present but doesn't match our domain @@ -267,12 +267,12 @@ mod tests { let now = Timestamp::now().as_u64(); let exp_str = (now + 300).to_string(); let tags = vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", &sha256]).unwrap(), - Tag::parse(&["expiration", &exp_str]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), + Tag::parse(["expiration", &exp_str]).unwrap(), ]; // Empty content — BUD-11 requires a human-readable string - let event = EventBuilder::new(Kind::from(24242), "", tags) + let event = EventBuilder::new(Kind::from(24242), "").tags( tags) .sign_with_keys(&keys) .unwrap(); assert!(matches!( diff --git a/crates/sprout-pairing-cli/src/main.rs b/crates/sprout-pairing-cli/src/main.rs index 5f98d0070..73b6cb9c6 100644 --- a/crates/sprout-pairing-cli/src/main.rs +++ b/crates/sprout-pairing-cli/src/main.rs @@ -17,7 +17,7 @@ use std::time::Duration; use clap::{Parser, Subcommand}; use futures_util::{SinkExt, StreamExt}; -use nostr::{Event, EventBuilder, Keys, SecretKey, ToBech32}; +use nostr::{Event, EventBuilder, Keys, RelayUrl, SecretKey, ToBech32}; use sprout_core::kind::KIND_PAIRING; use sprout_core::pairing::session::PairingSession; use sprout_core::pairing::{ @@ -367,7 +367,8 @@ fn cmd_test_vectors() -> Result<(), CliError> { // Derive all values. let session_id = derive_session_id(&session_secret); let ecdh_shared = - nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()); + nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()) + .map_err(|e| CliError::Other(e.to_string()))?; let (sas_code_u32, sas_input) = derive_sas(&ecdh_shared, &session_secret); let sas_code = format_sas(sas_code_u32); let transcript_hash = derive_transcript_hash( @@ -463,8 +464,7 @@ where }; // Build and send the NIP-42 auth response using the session's ephemeral keys. - let relay_url_parsed: url::Url = relay_url - .parse() + let relay_url_parsed = RelayUrl::parse(relay_url) .map_err(|e| CliError::Other(format!("invalid relay URL: {e}")))?; let auth_event = session .sign_event(EventBuilder::auth(challenge, relay_url_parsed)) diff --git a/crates/sprout-proxy/Cargo.toml b/crates/sprout-proxy/Cargo.toml index 26d1ef8e5..770076acc 100644 --- a/crates/sprout-proxy/Cargo.toml +++ b/crates/sprout-proxy/Cargo.toml @@ -18,6 +18,7 @@ chrono = { workspace = true } tracing = { workspace = true } thiserror = { workspace = true } sha2 = { workspace = true } +hex = { workspace = true } hmac = { workspace = true } dashmap = { workspace = true } moka = { workspace = true } diff --git a/crates/sprout-proxy/src/channel_map.rs b/crates/sprout-proxy/src/channel_map.rs index fcb0d15cc..2b0973f0a 100644 --- a/crates/sprout-proxy/src/channel_map.rs +++ b/crates/sprout-proxy/src/channel_map.rs @@ -84,9 +84,9 @@ impl ChannelMap { }) .to_string(); - // nostr 0.36: EventBuilder::new(kind, content, tags) + // nostr 0.36: EventBuilder::new(kind, content).tags( tags) // SAFETY: signing with a pre-validated Keys instance cannot fail - EventBuilder::new(Kind::ChannelCreation, content, []) + EventBuilder::new(Kind::ChannelCreation, content).tags( []) .custom_created_at(Timestamp::from(created_at_unix)) .sign_with_keys(&self.server_keys) .expect("SAFETY: signing with valid keys cannot fail") @@ -109,7 +109,7 @@ impl ChannelMap { ); // SAFETY: signing with a pre-validated Keys instance cannot fail - EventBuilder::new(Kind::ChannelMetadata, content, [e_tag]) + EventBuilder::new(Kind::ChannelMetadata, content).tags( [e_tag]) .custom_created_at(Timestamp::from(info.created_at_unix)) .sign_with_keys(&self.server_keys) .expect("SAFETY: signing with valid keys cannot fail") diff --git a/crates/sprout-proxy/src/main.rs b/crates/sprout-proxy/src/main.rs index a293eef0f..99d67965a 100644 --- a/crates/sprout-proxy/src/main.rs +++ b/crates/sprout-proxy/src/main.rs @@ -66,7 +66,7 @@ async fn main() { // ── Parse salt ──────────────────────────────────────────────────────────── - let salt = nostr::util::hex::decode(&salt_hex).unwrap_or_else(|e| { + let salt = hex::decode(&salt_hex).unwrap_or_else(|e| { eprintln!("error: invalid SPROUT_PROXY_SALT (must be hex): {e}"); std::process::exit(1); }); @@ -148,9 +148,8 @@ async fn main() { tokio::spawn(async move { while let Some(event) = inbound_rx.recv().await { match event { - UpstreamEvent::RelayMessage(msg) => { - let json = msg.as_json(); - // Ignore send errors — no active subscribers is fine. + UpstreamEvent::RelayMessage(json) => { + // Already raw JSON — forward directly to the broadcast channel. let _ = bridge_events_tx.send(json); } UpstreamEvent::Connected => { diff --git a/crates/sprout-proxy/src/server.rs b/crates/sprout-proxy/src/server.rs index 160f4641a..9e898a079 100644 --- a/crates/sprout-proxy/src/server.rs +++ b/crates/sprout-proxy/src/server.rs @@ -165,7 +165,7 @@ fn constant_time_eq(a: &str, b: &str) -> bool { /// Helper: serialize a [`RelayMessage`] and send it over the socket. /// Returns `true` if the send succeeded. -async fn send_relay_msg(socket: &mut WebSocket, msg: RelayMessage) -> bool { +async fn send_relay_msg(socket: &mut WebSocket, msg: RelayMessage<'_>) -> bool { let json = msg.as_json(); socket.send(Message::Text(json.into())).await.is_ok() } @@ -323,7 +323,7 @@ async fn handle_ws(mut socket: WebSocket, state: ProxyState, token: String) { let _ = send_relay_msg( &mut socket, RelayMessage::closed( - subscription_id, + subscription_id.into_owned(), "auth-required: authenticate before subscribing", ), ) @@ -450,7 +450,7 @@ async fn handle_ws(mut socket: WebSocket, state: ProxyState, token: String) { } // FIX 1: NOTICE messages from upstream contain operational details. // Log them but do NOT forward to clients. - Ok(RelayMessage::Notice { message: notice_msg }) => { + Ok(RelayMessage::Notice(notice_msg)) => { debug!(notice = %notice_msg, "upstream notice (not forwarded to client)"); } Ok(_other) => { @@ -520,8 +520,8 @@ async fn handle_client_message( handle_req( socket, state, - subscription_id, - filters, + subscription_id.into_owned(), + filters.into_iter().map(|f| f.into_owned()).collect(), allowed_channels, conn_prefix, active_subs, diff --git a/crates/sprout-proxy/src/shadow_keys.rs b/crates/sprout-proxy/src/shadow_keys.rs index 33b9cad32..9e95138c8 100644 --- a/crates/sprout-proxy/src/shadow_keys.rs +++ b/crates/sprout-proxy/src/shadow_keys.rs @@ -21,8 +21,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use dashmap::DashMap; +use hex; use hmac::{Hmac, KeyInit, Mac}; -use nostr::util::hex; use nostr::{Keys, SecretKey}; use sha2::Sha256; diff --git a/crates/sprout-proxy/src/translate.rs b/crates/sprout-proxy/src/translate.rs index 0f97678a1..22bf1b2ff 100644 --- a/crates/sprout-proxy/src/translate.rs +++ b/crates/sprout-proxy/src/translate.rs @@ -234,7 +234,7 @@ impl Translator { let mut new_tags: Vec = Vec::new(); new_tags.push( - Tag::parse(&["e", &channel_info.kind40_event_id]).expect("e tag is always valid"), + Tag::parse(["e", &channel_info.kind40_event_id]).expect("e tag is always valid"), ); for tag in event.tags.iter() { let slice = tag.as_slice(); @@ -262,9 +262,8 @@ impl Translator { let translated = EventBuilder::new( Kind::Custom(u16::try_from(standard_kind).expect("standard kind must fit in u16")), - content, - new_tags, - ) + content).tags( + new_tags) .custom_created_at(event.created_at) .sign_with_keys(&shadow_keys) .map_err(|e| ProxyError::Upstream(format!("outbound signing failed: {e}")))?; @@ -333,8 +332,7 @@ impl Translator { let mut parts = vec!["e".to_string(), external_target_id]; parts.extend(slice.iter().skip(2).cloned()); - let part_refs: Vec<&str> = parts.iter().map(String::as_str).collect(); - new_tags.push(Tag::parse(&part_refs).map_err(|e| { + new_tags.push(Tag::parse(parts).map_err(|e| { ProxyError::Upstream(format!("outbound tag build failed: {e}")) })?); saw_target = true; @@ -351,7 +349,7 @@ impl Translator { .shadow_keys .get_or_create(&self.resolve_shadow_author_hex(event))?; - let translated = EventBuilder::new(kind, event.content.clone(), new_tags) + let translated = EventBuilder::new(kind, event.content.clone()).tags( new_tags) .custom_created_at(event.created_at) .sign_with_keys(&shadow_keys) .map_err(|e| ProxyError::Upstream(format!("outbound signing failed: {e}")))?; @@ -476,7 +474,7 @@ impl Translator { let mut new_tags: Vec = Vec::new(); // SAFETY: ["h", ] is always a valid 2-element tag structure new_tags.push( - Tag::parse(&["h", &channel_info.uuid.to_string()]) + Tag::parse(["h", &channel_info.uuid.to_string()]) .expect("SAFETY: [\"h\", uuid_string] is always a valid tag structure"), ); for tag in event.tags.iter() { @@ -508,9 +506,8 @@ impl Translator { u16::try_from(sprout_kind) .expect("SAFETY: sprout kind values (9, 40002, 40003) always fit in u16"), ), - &event.content, - new_tags, - ) + &event.content).tags( + new_tags) .custom_created_at(event.created_at) .sign_with_keys(&shadow_keys) .map_err(|e| ProxyError::Upstream(format!("inbound signing failed: {e}")))?; @@ -607,7 +604,7 @@ impl Translator { // Build translated tag list: prepend #h, translate all #e values, drop // any client-supplied #h tags (prevent unauthorized channel injection). let mut new_tags: Vec = - vec![Tag::parse(&["h", &channel_uuid.to_string()]).expect("h tag is always valid")]; + vec![Tag::parse(["h", &channel_uuid.to_string()]).expect("h tag is always valid")]; let mut saw_target = false; for tag in event.tags.iter() { @@ -632,9 +629,8 @@ impl Translator { })?; let mut parts = vec!["e".to_string(), internal_id.to_string()]; parts.extend(slice.iter().skip(2).cloned()); - let part_refs: Vec<&str> = parts.iter().map(String::as_str).collect(); new_tags.push( - Tag::parse(&part_refs).map_err(|e| { + Tag::parse(parts).map_err(|e| { ProxyError::Upstream(format!("{label} tag build failed: {e}")) })?, ); @@ -652,7 +648,7 @@ impl Translator { } let shadow_keys = self.shadow_keys.get_or_create(external_pubkey)?; - let translated = EventBuilder::new(kind, &event.content, new_tags) + let translated = EventBuilder::new(kind, &event.content).tags( new_tags) .custom_created_at(event.created_at) .sign_with_keys(&shadow_keys) .map_err(|e| ProxyError::Upstream(format!("inbound signing failed: {e}")))?; @@ -741,7 +737,7 @@ impl Translator { // Case 3: no #e filter, use full allowed scope allowed_channels.iter().map(|u| u.to_string()).collect() }; - f = f.custom_tag(SingleLetterTag::lowercase(Alphabet::H), uuid_strings); + f = f.custom_tags(SingleLetterTag::lowercase(Alphabet::H), uuid_strings); f } @@ -851,12 +847,11 @@ mod tests { let author_keys = Keys::generate(); // Build a synthetic kind:9 event with an #h tag. - let h_tag = Tag::parse(&["h", TEST_UUID]).unwrap(); + let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); let sprout_event = EventBuilder::new( Kind::Custom(KIND_STREAM_MESSAGE as u16), - "hello world", - [h_tag], - ) + "hello world").tags( + [h_tag]) .sign_with_keys(&author_keys) .unwrap(); @@ -905,8 +900,8 @@ mod tests { let external_pubkey = client_keys.public_key().to_hex(); // Build a synthetic kind:42 event with an #e tag. - let e_tag = Tag::parse(&["e", &kind40_event_id]).unwrap(); - let nip28_event = EventBuilder::new(Kind::Custom(42), "hello from client", [e_tag]) + let e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); + let nip28_event = EventBuilder::new(Kind::Custom(42), "hello from client").tags( [e_tag]) .sign_with_keys(&client_keys) .unwrap(); @@ -954,9 +949,9 @@ mod tests { let (translator, _) = make_translator(); let author_keys = Keys::generate(); - let h_tag = Tag::parse(&["h", TEST_UUID]).unwrap(); + let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); let sprout_event = - EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE as u16), "secret", [h_tag]) + EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE as u16), "secret").tags( [h_tag]) .sign_with_keys(&author_keys) .unwrap(); @@ -979,8 +974,8 @@ mod tests { let client_keys = Keys::generate(); let external_pubkey = client_keys.public_key().to_hex(); - let e_tag = Tag::parse(&["e", &kind40_event_id]).unwrap(); - let nip28_event = EventBuilder::new(Kind::Custom(42), "sneaky", [e_tag]) + let e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); + let nip28_event = EventBuilder::new(Kind::Custom(42), "sneaky").tags( [e_tag]) .sign_with_keys(&client_keys) .unwrap(); @@ -1002,12 +997,11 @@ mod tests { let author_keys = Keys::generate(); let v2_content = r#"{"text":"hello v2","attachments":[]}"#; - let h_tag = Tag::parse(&["h", TEST_UUID]).unwrap(); + let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); let sprout_event = EventBuilder::new( Kind::Custom(KIND_STREAM_MESSAGE_V2 as u16), - v2_content, - [h_tag], - ) + v2_content).tags( + [h_tag]) .sign_with_keys(&author_keys) .unwrap(); @@ -1085,7 +1079,7 @@ mod tests { let e_tag_key = SingleLetterTag::lowercase(Alphabet::E); let filter = Filter::new() .kind(Kind::Custom(42)) - .custom_tag(e_tag_key, [kind40_event_id]); + .custom_tags(e_tag_key, [kind40_event_id]); let translated = translator.translate_filter_inbound(&filter, &allowed()); @@ -1129,7 +1123,7 @@ mod tests { let e_tag_key = SingleLetterTag::lowercase(Alphabet::E); let filter = Filter::new() .kind(Kind::Custom(42)) - .custom_tag(e_tag_key, [unknown_event_id]); + .custom_tags(e_tag_key, [unknown_event_id]); let translated = translator.translate_filter_inbound(&filter, &allowed()); @@ -1171,14 +1165,13 @@ mod tests { // A reply event has two #e tags: one for the channel, one for the // message being replied to (NIP-10 threading). let reply_event_id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - let channel_e_tag = Tag::parse(&["e", &kind40_event_id]).unwrap(); - let reply_e_tag = Tag::parse(&["e", reply_event_id]).unwrap(); + let channel_e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); + let reply_e_tag = Tag::parse(["e", reply_event_id]).unwrap(); let nip28_event = EventBuilder::new( Kind::Custom(42), - "replying to a message", - [channel_e_tag, reply_e_tag], - ) + "replying to a message").tags( + [channel_e_tag, reply_e_tag]) .sign_with_keys(&client_keys) .unwrap(); @@ -1226,14 +1219,13 @@ mod tests { let author_keys = Keys::generate(); // An event with the channel #h tag AND an unrelated #h tag. - let channel_h_tag = Tag::parse(&["h", TEST_UUID]).unwrap(); - let other_h_tag = Tag::parse(&["h", "some-other-value"]).unwrap(); + let channel_h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); + let other_h_tag = Tag::parse(["h", "some-other-value"]).unwrap(); let sprout_event = EventBuilder::new( Kind::Custom(KIND_STREAM_MESSAGE as u16), - "message with extra h tag", - [channel_h_tag, other_h_tag], - ) + "message with extra h tag").tags( + [channel_h_tag, other_h_tag]) .sign_with_keys(&author_keys) .unwrap(); @@ -1304,8 +1296,8 @@ mod tests { let external_keys = Keys::generate(); // Build a kind:41 event with #e referencing the channel. - let e_tag = Tag::parse(&["e", &kind40_event_id]).unwrap(); - let nip28_event = EventBuilder::new(Kind::ChannelMetadata, "updated metadata", [e_tag]) + let e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); + let nip28_event = EventBuilder::new(Kind::ChannelMetadata, "updated metadata").tags( [e_tag]) .sign_with_keys(&external_keys) .unwrap(); @@ -1343,8 +1335,8 @@ mod tests { let (translator, kind40_event_id) = make_translator(); let external_keys = Keys::generate(); - let e_tag = Tag::parse(&["e", &kind40_event_id]).unwrap(); - let event = EventBuilder::new(Kind::Custom(9999), "nope", [e_tag]) + let e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); + let event = EventBuilder::new(Kind::Custom(9999), "nope").tags( [e_tag]) .sign_with_keys(&external_keys) .unwrap(); @@ -1362,12 +1354,11 @@ mod tests { let (translator, _) = make_translator(); let author_keys = Keys::generate(); - let h_tag = Tag::parse(&["h", TEST_UUID]).unwrap(); + let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); let sprout_event = EventBuilder::new( Kind::Custom(KIND_STREAM_MESSAGE_EDIT as u16), - "edited content", - [h_tag], - ) + "edited content").tags( + [h_tag]) .sign_with_keys(&author_keys) .unwrap(); @@ -1412,10 +1403,10 @@ mod tests { let external_keys = Keys::generate(); // Client tries to inject an #h tag for an unauthorized channel. - let e_tag = Tag::parse(&["e", &kind40_event_id]).unwrap(); - let injected_h = Tag::parse(&["h", "00000000-0000-0000-0000-000000000001"]).unwrap(); + let e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); + let injected_h = Tag::parse(["h", "00000000-0000-0000-0000-000000000001"]).unwrap(); let nip28_event = - EventBuilder::new(Kind::Custom(42), "sneaky message", [e_tag, injected_h]) + EventBuilder::new(Kind::Custom(42), "sneaky message").tags( [e_tag, injected_h]) .sign_with_keys(&external_keys) .unwrap(); @@ -1449,8 +1440,8 @@ mod tests { let author_keys = Keys::generate(); // A kind:9999 event (unknown Sprout kind) should be dropped. - let h_tag = Tag::parse(&["h", TEST_UUID]).unwrap(); - let event = EventBuilder::new(Kind::Custom(9999), "internal stuff", [h_tag]) + let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); + let event = EventBuilder::new(Kind::Custom(9999), "internal stuff").tags( [h_tag]) .sign_with_keys(&author_keys) .unwrap(); diff --git a/crates/sprout-proxy/src/upstream.rs b/crates/sprout-proxy/src/upstream.rs index 44b83f6a1..f017bdf50 100644 --- a/crates/sprout-proxy/src/upstream.rs +++ b/crates/sprout-proxy/src/upstream.rs @@ -18,8 +18,8 @@ use tracing::{debug, error, info, warn}; /// Messages forwarded from the upstream relay to the server layer. #[derive(Debug, Clone)] pub enum UpstreamEvent { - /// A relay message to route to a downstream client. - RelayMessage(RelayMessage), + /// A relay message to route to a downstream client (raw JSON text). + RelayMessage(String), /// The upstream connection was lost (reconnect in progress). Disconnected, /// The upstream connection was (re)established and authenticated. @@ -374,11 +374,7 @@ where event_id.to_hex() ); if inbound_tx - .send(UpstreamEvent::RelayMessage(RelayMessage::Ok { - event_id, - status: *status, - message: message.clone(), - })) + .send(UpstreamEvent::RelayMessage(text_str.to_string())) .await .is_err() { @@ -389,9 +385,9 @@ where } // ── All other messages → forward downstream ────────────── - other => { + _other => { if inbound_tx - .send(UpstreamEvent::RelayMessage(other)) + .send(UpstreamEvent::RelayMessage(text_str.to_string())) .await .is_err() { @@ -435,18 +431,17 @@ async fn respond_to_auth_challenge( inner: &Inner, write_tx: &mpsc::Sender, ) -> Result<(), Box> { - let relay_tag = Tag::parse(&["relay", &inner.relay_url]) + let relay_tag = Tag::parse(["relay", &inner.relay_url]) .map_err(|e| crate::ProxyError::Auth(format!("relay tag: {e}")))?; - let challenge_tag = Tag::parse(&["challenge", challenge]) + let challenge_tag = Tag::parse(["challenge", challenge]) .map_err(|e| crate::ProxyError::Auth(format!("challenge tag: {e}")))?; - let token_tag = Tag::parse(&["auth_token", &inner.api_token]) + let token_tag = Tag::parse(["auth_token", &inner.api_token]) .map_err(|e| crate::ProxyError::Auth(format!("auth_token tag: {e}")))?; let auth_event = EventBuilder::new( Kind::Authentication, // kind:22242 - "", - [relay_tag, challenge_tag, token_tag], - ) + "").tags( + [relay_tag, challenge_tag, token_tag]) .sign_with_keys(&inner.auth_keys) .map_err(|e| crate::ProxyError::Auth(format!("sign auth event: {e}")))?; @@ -483,7 +478,7 @@ mod tests { // Queue an EVENT message. let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "hello", []) + let event = EventBuilder::new(Kind::TextNote, "hello").tags( []) .sign_with_keys(&keys) .unwrap(); let event_id = event.id; diff --git a/crates/sprout-pubsub/src/lib.rs b/crates/sprout-pubsub/src/lib.rs index a538327d4..608f32f99 100644 --- a/crates/sprout-pubsub/src/lib.rs +++ b/crates/sprout-pubsub/src/lib.rs @@ -184,7 +184,7 @@ mod tests { let channel_id = Uuid::new_v4(); let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "hello pubsub", []) + let event = EventBuilder::new(Kind::TextNote, "hello pubsub").tags( []) .sign_with_keys(&keys) .expect("signing failed"); let event_id = event.id; diff --git a/crates/sprout-relay/src/api/bridge.rs b/crates/sprout-relay/src/api/bridge.rs index cb4f6d757..8ff98776f 100644 --- a/crates/sprout-relay/src/api/bridge.rs +++ b/crates/sprout-relay/src/api/bridge.rs @@ -135,7 +135,7 @@ pub async fn submit_event( state.config.require_auth_token, )?; check_nip98_replay(&state, event_id_bytes)?; - let pubkey_bytes = pubkey.serialize().to_vec(); + let pubkey_bytes = pubkey.to_bytes().to_vec(); // Enforce relay membership (with NIP-OA fallback via x-auth-tag header). let auth_tag = headers.get("x-auth-tag").and_then(|v| v.to_str().ok()); @@ -183,7 +183,7 @@ pub async fn query_events( state.config.require_auth_token, )?; check_nip98_replay(&state, event_id_bytes)?; - let pubkey_bytes = pubkey.serialize().to_vec(); + let pubkey_bytes = pubkey.to_bytes().to_vec(); let auth_tag = headers.get("x-auth-tag").and_then(|v| v.to_str().ok()); super::relay_members::enforce_relay_membership(&state, &pubkey_bytes, auth_tag).await?; @@ -284,7 +284,7 @@ pub async fn count_events( state.config.require_auth_token, )?; check_nip98_replay(&state, event_id_bytes)?; - let pubkey_bytes = pubkey.serialize().to_vec(); + let pubkey_bytes = pubkey.to_bytes().to_vec(); let auth_tag = headers.get("x-auth-tag").and_then(|v| v.to_str().ok()); super::relay_members::enforce_relay_membership(&state, &pubkey_bytes, auth_tag).await?; @@ -755,12 +755,11 @@ async fn synthesize_presence(state: &AppState, filters: &[nostr::Filter]) -> Opt let mut events = Vec::with_capacity(presence_map.len()); for (pubkey_hex, status) in &presence_map { // Build a synthetic event: relay-signed, content = status, p-tag = subject. - let tags = vec![nostr::Tag::parse(&["p", pubkey_hex]).ok()?]; + let tags = vec![nostr::Tag::parse(["p", pubkey_hex]).ok()?]; let event = nostr::EventBuilder::new( nostr::Kind::Custom(KIND_PRESENCE_UPDATE as u16), - status, - tags, - ) + status).tags( + tags) .custom_created_at(nostr::Timestamp::from(now)) .sign_with_keys(&state.relay_keypair) .ok()?; @@ -788,7 +787,7 @@ mod tests { nostr::TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::P)), [owner_hex], ); - let ev = EventBuilder::new(Kind::Custom(30174), "engram body", [d_tag, p_tag]) + let ev = EventBuilder::new(Kind::Custom(30174), "engram body").tags( [d_tag, p_tag]) .sign_with_keys(agent) .expect("sign engram"); sprout_core::StoredEvent::new(ev, None) @@ -815,7 +814,7 @@ mod tests { let p_tag = SingleLetterTag::lowercase(Alphabet::P); let filter = nostr::Filter::new() .kind(Kind::Custom(30174)) - .custom_tag(p_tag, [&owner_a]); + .custom_tags(p_tag, [&owner_a]); assert!( search_hit_accepted(&filter, &env_for_a, &[]), @@ -864,7 +863,7 @@ mod tests { let p_tag = SingleLetterTag::lowercase(Alphabet::P); let filter = nostr::Filter::new() .kind(Kind::Custom(30174)) - .custom_tag(p_tag, [&owner]); + .custom_tags(p_tag, [&owner]); assert!( !search_hit_accepted(&filter, &stored, &[]), diff --git a/crates/sprout-relay/src/api/git/transport.rs b/crates/sprout-relay/src/api/git/transport.rs index fa5423bc3..c380f59f0 100644 --- a/crates/sprout-relay/src/api/git/transport.rs +++ b/crates/sprout-relay/src/api/git/transport.rs @@ -179,7 +179,7 @@ impl axum::extract::FromRequestParts> for GitAuth { .headers .get("x-auth-tag") .and_then(|v| v.to_str().ok()); - if crate::api::relay_members::enforce_relay_membership(state, &pubkey.serialize(), auth_tag) + if crate::api::relay_members::enforce_relay_membership(state, pubkey.as_bytes(), auth_tag) .await .is_err() { @@ -452,7 +452,7 @@ pub async fn receive_pack( body: Body, ) -> Result { let repo_name = validate_repo_id(¶ms.owner, ¶ms.repo)?; - let pusher_hex = hex::encode(auth.pubkey.serialize()); + let pusher_hex = hex::encode(auth.pubkey.to_bytes()); let _permit = acquire_git_permit(&state)?; // **No per-repo advisory lock — by design.** Writer serialization is diff --git a/crates/sprout-relay/src/api/media.rs b/crates/sprout-relay/src/api/media.rs index 2153845e1..46f589680 100644 --- a/crates/sprout-relay/src/api/media.rs +++ b/crates/sprout-relay/src/api/media.rs @@ -90,7 +90,7 @@ impl FromRequestParts> for AuthenticatedUpload { let auth_tag = headers.get("x-auth-tag").and_then(|v| v.to_str().ok()); crate::api::relay_members::enforce_relay_membership( state, - &auth_event.pubkey.serialize(), + auth_event.pubkey.as_bytes(), auth_tag, ) .await @@ -581,7 +581,7 @@ async fn resolve_upload_scopes( } // Token owner must match the Blossom signer — prevents token theft attacks. - let blossom_bytes = blossom_pubkey.serialize().to_vec(); + let blossom_bytes = blossom_pubkey.to_bytes().to_vec(); if record.owner_pubkey != blossom_bytes { return Err(MediaError::PubkeyMismatch); } @@ -606,7 +606,7 @@ async fn resolve_upload_scopes( // 3. Pubkey allowlist check (dev mode only). if state.config.pubkey_allowlist_enabled { - let pubkey_bytes = blossom_pubkey.serialize().to_vec(); + let pubkey_bytes = blossom_pubkey.to_bytes().to_vec(); if !state .db .is_pubkey_allowed(&pubkey_bytes) diff --git a/crates/sprout-relay/src/api/nip05.rs b/crates/sprout-relay/src/api/nip05.rs index 54e6a11aa..db33a1b6a 100644 --- a/crates/sprout-relay/src/api/nip05.rs +++ b/crates/sprout-relay/src/api/nip05.rs @@ -7,7 +7,7 @@ use axum::{ http::HeaderValue, response::{IntoResponse, Json, Response}, }; -use nostr::util::hex as nostr_hex; +use hex; use serde::Deserialize; use crate::state::AppState; @@ -33,7 +33,7 @@ pub async fn nostr_nip05( let domain = extract_domain(&state.config.relay_url); match state.db.get_user_by_nip05(&name, &domain).await { Ok(Some(user)) => { - let hex_pubkey = nostr_hex::encode(&user.pubkey); + let hex_pubkey = hex::encode(&user.pubkey); let relay_url = state.config.relay_url.clone(); serde_json::json!({ "names": { (name): hex_pubkey.clone() }, diff --git a/crates/sprout-relay/src/audio/handler.rs b/crates/sprout-relay/src/audio/handler.rs index b9924c1a4..6802436e2 100644 --- a/crates/sprout-relay/src/audio/handler.rs +++ b/crates/sprout-relay/src/audio/handler.rs @@ -157,13 +157,13 @@ async fn handle_audio_connection(socket: WebSocket, state: Arc, channe let pubkey = auth_ctx.pubkey; let pubkey_hex = pubkey.to_hex(); - let pubkey_bytes = pubkey.serialize().to_vec(); + let pubkey_bytes = pubkey.to_bytes().to_vec(); let parent_channel_id = auth_msg.parent_channel_id; // ── Relay membership gate (with NIP-OA fallback) ──────────────────────────── if crate::api::relay_members::enforce_relay_membership( &state, - &pubkey.serialize(), + pubkey.as_bytes(), auth_tag_json.as_deref(), ) .await @@ -725,14 +725,14 @@ async fn emit_participant_event( ) { let content = serde_json::json!({"ephemeral_channel_id": channel_id.to_string()}).to_string(); - let h_tag = match Tag::parse(&["h", &parent_channel_id.to_string()]) { + let h_tag = match Tag::parse(["h", &parent_channel_id.to_string()]) { Ok(t) => t, Err(e) => { warn!("audio: failed to parse h tag: {e}"); return; } }; - let p_tag = match Tag::parse(&["p", participant_pubkey]) { + let p_tag = match Tag::parse(["p", participant_pubkey]) { Ok(t) => t, Err(e) => { warn!("audio: failed to parse p tag: {e}"); @@ -741,7 +741,7 @@ async fn emit_participant_event( }; let tags = vec![h_tag, p_tag]; - let event = match EventBuilder::new(kind, content, tags).sign_with_keys(&state.relay_keypair) { + let event = match EventBuilder::new(kind, content).tags( tags).sign_with_keys(&state.relay_keypair) { Ok(e) => e, Err(e) => { warn!("audio: failed to sign lifecycle event: {e}"); diff --git a/crates/sprout-relay/src/handlers/auth.rs b/crates/sprout-relay/src/handlers/auth.rs index 049ae62e4..c7eb0bfd7 100644 --- a/crates/sprout-relay/src/handlers/auth.rs +++ b/crates/sprout-relay/src/handlers/auth.rs @@ -87,7 +87,7 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: if state.config.pubkey_allowlist_enabled && auth_ctx.auth_method == sprout_auth::AuthMethod::Nip42 { - let allowed = match state.db.is_pubkey_allowed(&pubkey.serialize()).await { + let allowed = match state.db.is_pubkey_allowed(pubkey.as_bytes()).await { Ok(v) => v, Err(e) => { warn!(conn_id = %conn_id, pubkey = %pubkey.to_hex(), error = %e, @@ -112,7 +112,7 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: // Relay membership gate — uses the shared helper with NIP-OA fallback. let nip_oa_owner = match crate::api::relay_members::enforce_relay_membership( &state, - &pubkey.serialize(), + pubkey.as_bytes(), auth_tag_json.as_deref(), ) .await @@ -139,7 +139,7 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: let nip_oa_owner = nip_oa_owner.or_else(|| { if !state.config.require_relay_membership && auth_tag_json.is_some() { crate::api::relay_members::extract_nip_oa_owner( - &pubkey.serialize(), + pubkey.as_bytes(), auth_tag_json.as_deref(), ) } else { @@ -152,10 +152,10 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: if let Some(owner) = nip_oa_owner { // Ensure both agent and owner have users rows (BYO agents may not, // and agent_owner_pubkey has a FK constraint to users.pubkey). - if let Err(e) = state.db.ensure_user(&pubkey.serialize()).await { + if let Err(e) = state.db.ensure_user(pubkey.as_bytes()).await { warn!(conn_id = %conn_id, error = %e, "ensure_user(agent) failed during NIP-OA backfill"); } - if let Err(e) = state.db.ensure_user(&owner.serialize()).await { + if let Err(e) = state.db.ensure_user(owner.as_bytes()).await { warn!(conn_id = %conn_id, error = %e, "ensure_user(owner) failed during NIP-OA backfill"); } @@ -164,14 +164,14 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: // Returns Ok(true) if written, Ok(false) if already owned by someone else. match state .db - .set_agent_owner(&pubkey.serialize(), &owner.serialize()) + .set_agent_owner(pubkey.as_bytes(), owner.as_bytes()) .await { Ok(true) => { // Successfully materialized — this owner is authoritative. auth_ctx.agent_owner_pubkey = Some(owner); // Pre-warm the observer cache to avoid stale negatives. - let cache_key = (pubkey.serialize().to_vec(), owner.serialize().to_vec()); + let cache_key = (pubkey.to_bytes().to_vec(), owner.to_bytes().to_vec()); state.observer_owner_cache.insert(cache_key, true); } Ok(false) => { @@ -179,7 +179,7 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: // owner matches the existing DB record before trusting it. match state .db - .is_agent_owner(&pubkey.serialize(), &owner.serialize()) + .is_agent_owner(pubkey.as_bytes(), owner.as_bytes()) .await { Ok(true) => { @@ -218,7 +218,7 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: *conn.auth_state.write().await = AuthState::Authenticated(auth_ctx); state .conn_manager - .set_authenticated_pubkey(conn_id, pubkey.serialize().to_vec()); + .set_authenticated_pubkey(conn_id, pubkey.to_bytes().to_vec()); conn.send(RelayMessage::ok(&event_id_hex, true, "")); } Err(e) => { diff --git a/crates/sprout-relay/src/handlers/command_executor.rs b/crates/sprout-relay/src/handlers/command_executor.rs index 3254d8b24..43aa06660 100644 --- a/crates/sprout-relay/src/handlers/command_executor.rs +++ b/crates/sprout-relay/src/handlers/command_executor.rs @@ -37,7 +37,7 @@ pub async fn handle_command( ) -> Result { // Ensure the authenticated user exists in the users table (foreign key requirement). // The old REST handlers did this via extract_auth_context; command executor must do it explicitly. - let pubkey_bytes = auth.pubkey().serialize().to_vec(); + let pubkey_bytes = auth.pubkey().to_bytes().to_vec(); if let Err(e) = state.db.ensure_user(&pubkey_bytes).await { tracing::warn!("command_executor: ensure_user failed: {e}"); } diff --git a/crates/sprout-relay/src/handlers/count.rs b/crates/sprout-relay/src/handlers/count.rs index 953b07ffb..22331c9c8 100644 --- a/crates/sprout-relay/src/handlers/count.rs +++ b/crates/sprout-relay/src/handlers/count.rs @@ -33,7 +33,7 @@ pub async fn handle_count( let pubkey_bytes = { let auth = conn.auth_state.read().await; match &*auth { - AuthState::Authenticated(ctx) => ctx.pubkey.serialize().to_vec(), + AuthState::Authenticated(ctx) => ctx.pubkey.to_bytes().to_vec(), _ => { conn.send(RelayMessage::closed( &sub_id, diff --git a/crates/sprout-relay/src/handlers/event.rs b/crates/sprout-relay/src/handlers/event.rs index e5bb83df5..0674c94e8 100644 --- a/crates/sprout-relay/src/handlers/event.rs +++ b/crates/sprout-relay/src/handlers/event.rs @@ -170,7 +170,7 @@ pub async fn handle_event(event: Event, conn: Arc, state: Arc ( conn.conn_id, - ctx.pubkey.serialize().to_vec(), + ctx.pubkey.to_bytes().to_vec(), ctx.pubkey, ctx.scopes.clone(), ctx.channel_ids.clone(), @@ -550,8 +550,8 @@ async fn handle_agent_observer_event( } }; - let agent_bytes = route.agent.serialize().to_vec(); - let owner_bytes = route.owner.serialize().to_vec(); + let agent_bytes = route.agent.to_bytes().to_vec(); + let owner_bytes = route.owner.to_bytes().to_vec(); let cache_key = (agent_bytes.clone(), owner_bytes.clone()); let is_owner = if session_owner_match { true @@ -780,13 +780,12 @@ mod tests { .expect("encrypt observer payload"); let event = EventBuilder::new( Kind::Custom(KIND_AGENT_OBSERVER_FRAME as u16), - encrypted, + encrypted).tags( [ - Tag::parse(&["p", &owner.public_key().to_hex()]).expect("p tag"), - Tag::parse(&[OBSERVER_AGENT_TAG, &agent.public_key().to_hex()]).expect("agent tag"), - Tag::parse(&[OBSERVER_FRAME_TAG, OBSERVER_FRAME_TELEMETRY]).expect("frame tag"), - ], - ) + Tag::parse(["p", &owner.public_key().to_hex()]).expect("p tag"), + Tag::parse([OBSERVER_AGENT_TAG, &agent.public_key().to_hex()]).expect("agent tag"), + Tag::parse([OBSERVER_FRAME_TAG, OBSERVER_FRAME_TELEMETRY]).expect("frame tag"), + ]) .sign_with_keys(&agent) .expect("sign event"); @@ -810,13 +809,12 @@ mod tests { .expect("encrypt observer payload"); let event = EventBuilder::new( Kind::Custom(KIND_AGENT_OBSERVER_FRAME as u16), - encrypted, + encrypted).tags( [ - Tag::parse(&["p", &agent.public_key().to_hex()]).expect("p tag"), - Tag::parse(&[OBSERVER_AGENT_TAG, &agent.public_key().to_hex()]).expect("agent tag"), - Tag::parse(&[OBSERVER_FRAME_TAG, OBSERVER_FRAME_CONTROL]).expect("frame tag"), - ], - ) + Tag::parse(["p", &agent.public_key().to_hex()]).expect("p tag"), + Tag::parse([OBSERVER_AGENT_TAG, &agent.public_key().to_hex()]).expect("agent tag"), + Tag::parse([OBSERVER_FRAME_TAG, OBSERVER_FRAME_CONTROL]).expect("frame tag"), + ]) .sign_with_keys(&owner) .expect("sign event"); @@ -834,13 +832,12 @@ mod tests { let owner = Keys::generate(); let event = EventBuilder::new( Kind::Custom(KIND_AGENT_OBSERVER_FRAME as u16), - "not encrypted", + "not encrypted").tags( [ - Tag::parse(&["p", &owner.public_key().to_hex()]).expect("p tag"), - Tag::parse(&[OBSERVER_AGENT_TAG, &agent.public_key().to_hex()]).expect("agent tag"), - Tag::parse(&[OBSERVER_FRAME_TAG, OBSERVER_FRAME_TELEMETRY]).expect("frame tag"), - ], - ) + Tag::parse(["p", &owner.public_key().to_hex()]).expect("p tag"), + Tag::parse([OBSERVER_AGENT_TAG, &agent.public_key().to_hex()]).expect("agent tag"), + Tag::parse([OBSERVER_FRAME_TAG, OBSERVER_FRAME_TELEMETRY]).expect("frame tag"), + ]) .sign_with_keys(&agent) .expect("sign event"); diff --git a/crates/sprout-relay/src/handlers/ingest.rs b/crates/sprout-relay/src/handlers/ingest.rs index 3c53c6c05..426fbbde0 100644 --- a/crates/sprout-relay/src/handlers/ingest.rs +++ b/crates/sprout-relay/src/handlers/ingest.rs @@ -631,7 +631,7 @@ pub(crate) fn effective_message_author(event: &Event, relay_pubkey: &nostr::Publ } } } - event.pubkey.serialize().to_vec() + event.pubkey.to_bytes().to_vec() } /// Validate kind:40003 edit ownership — event.pubkey must match target's effective author. @@ -676,7 +676,7 @@ async fn validate_edit_ownership(event: &Event, state: &AppState) -> Result<(), } let author = effective_message_author(&target_event.event, &state.relay_keypair.public_key()); - let actor = event.pubkey.serialize().to_vec(); + let actor = event.pubkey.to_bytes().to_vec(); if author != actor { return Err("must be event author to edit".to_string()); } @@ -1144,7 +1144,7 @@ pub async fn ingest_event( } // ── 8. Membership check ────────────────────────────────────────────── - let pubkey_bytes = auth.pubkey().serialize().to_vec(); + let pubkey_bytes = auth.pubkey().to_bytes().to_vec(); if let Some(ch_id) = channel_id { // kind:9021 (join) doesn't require prior membership. // kind:9007 (create) — channel doesn't exist yet; creator becomes owner in step 16. @@ -1398,7 +1398,7 @@ pub async fn ingest_event( let ttl_seconds = super::resolve_ttl(&event, state.config.ephemeral_ttl_override); - let actor_bytes = event.pubkey.serialize().to_vec(); + let actor_bytes = event.pubkey.to_bytes().to_vec(); let (_, was_created) = state .db .create_channel_with_id( @@ -2024,7 +2024,7 @@ mod tests { fn make_dummy_event() -> Event { let keys = nostr::Keys::generate(); - nostr::EventBuilder::new(nostr::Kind::Custom(9), "", []) + nostr::EventBuilder::new(nostr::Kind::Custom(9), "").tags( []) .sign_with_keys(&keys) .unwrap() } @@ -2033,7 +2033,7 @@ mod tests { let keys = nostr::Keys::generate(); let nostr_tags: Vec = tags.iter().map(|t| nostr::Tag::parse(t).unwrap()).collect(); - nostr::EventBuilder::new(nostr::Kind::Custom(kind as u16), content, nostr_tags) + nostr::EventBuilder::new(nostr::Kind::Custom(kind as u16), content).tags( nostr_tags) .sign_with_keys(&keys) .unwrap() } diff --git a/crates/sprout-relay/src/handlers/relay_admin.rs b/crates/sprout-relay/src/handlers/relay_admin.rs index e50e649a0..7ef73bb4c 100644 --- a/crates/sprout-relay/src/handlers/relay_admin.rs +++ b/crates/sprout-relay/src/handlers/relay_admin.rs @@ -296,7 +296,7 @@ mod tests { .into_iter() .map(|parts| Tag::parse(&parts).expect("valid tag")) .collect(); - EventBuilder::new(Kind::from(kind), "", nostr_tags) + EventBuilder::new(Kind::from(kind), "").tags( nostr_tags) .sign_with_keys(&keys) .expect("signing failed") } diff --git a/crates/sprout-relay/src/handlers/req.rs b/crates/sprout-relay/src/handlers/req.rs index 0b8152031..1421fdc46 100644 --- a/crates/sprout-relay/src/handlers/req.rs +++ b/crates/sprout-relay/src/handlers/req.rs @@ -49,7 +49,7 @@ pub async fn handle_req( return; } - let pk_bytes = ctx.pubkey.serialize().to_vec(); + let pk_bytes = ctx.pubkey.to_bytes().to_vec(); let subs = conn.subscriptions.lock().await; if !subs.contains_key(&sub_id) && subs.len() >= MAX_SUBSCRIPTIONS { @@ -569,12 +569,12 @@ fn filter_to_query_params(filter: &Filter, channel_id: Option) -> Ev // Push author filter into SQL. Single-author uses the indexed `pubkey` column; // multi-author uses the `authors` IN-list pushdown added in the pure-nostr PR. let (pubkey, authors) = match filter.authors.as_ref() { - Some(a) if a.len() == 1 => (a.iter().next().map(|pk| pk.serialize().to_vec()), None), + Some(a) if a.len() == 1 => (a.iter().next().map(|pk| pk.to_bytes().to_vec()), None), Some(a) if !a.is_empty() => ( None, Some( a.iter() - .map(|pk| pk.serialize().to_vec()) + .map(|pk| pk.to_bytes().to_vec()) .collect::>(), ), ), @@ -880,14 +880,14 @@ mod tests { .kind(nostr::Kind::Custom( sprout_core::kind::KIND_AGENT_OBSERVER_FRAME as u16, )) - .custom_tag(p_tag, [other]); + .custom_tags(p_tag, [other]); assert!(!p_gated_filters_authorized(&[wrong_p], authed)); let matching_p = Filter::new() .kind(nostr::Kind::Custom( sprout_core::kind::KIND_AGENT_OBSERVER_FRAME as u16, )) - .custom_tag(p_tag, [authed]); + .custom_tags(p_tag, [authed]); assert!(p_gated_filters_authorized(&[matching_p], authed)); } @@ -898,33 +898,33 @@ mod tests { // NIP-33 kind with #d → pushdown active let nip33_filter = Filter::new() .kind(nostr::Kind::Custom(30023)) - .custom_tag(d_tag, ["my-slug"]); + .custom_tags(d_tag, ["my-slug"]); let q = filter_to_query_params(&nip33_filter, None); assert_eq!(q.d_tag, Some("my-slug".to_string())); // Non-NIP-33 kind with #d → pushdown NOT active (would miss rows with d_tag=NULL) let non_nip33_filter = Filter::new() .kind(nostr::Kind::Custom(1)) - .custom_tag(d_tag, ["some-value"]); + .custom_tags(d_tag, ["some-value"]); let q2 = filter_to_query_params(&non_nip33_filter, None); assert_eq!(q2.d_tag, None); // Mixed kinds (one NIP-33, one not) → pushdown NOT active let mixed_filter = Filter::new() .kinds([nostr::Kind::Custom(30023), nostr::Kind::Custom(1)]) - .custom_tag(d_tag, ["slug"]); + .custom_tags(d_tag, ["slug"]); let q3 = filter_to_query_params(&mixed_filter, None); assert_eq!(q3.d_tag, None); // No kinds specified → pushdown NOT active - let no_kinds_filter = Filter::new().custom_tag(d_tag, ["slug"]); + let no_kinds_filter = Filter::new().custom_tags(d_tag, ["slug"]); let q4 = filter_to_query_params(&no_kinds_filter, None); assert_eq!(q4.d_tag, None); // Multi-value #d → pushdown NOT active (can't push OR into single column match) let multi_d_filter = Filter::new() .kind(nostr::Kind::Custom(30023)) - .custom_tag(d_tag, ["slug-a", "slug-b"]); + .custom_tags(d_tag, ["slug-a", "slug-b"]); let q5 = filter_to_query_params(&multi_d_filter, None); assert_eq!(q5.d_tag, None); } @@ -965,7 +965,7 @@ mod tests { let f = Filter::new() .kind(nostr::Kind::Custom(KIND_AGENT_ENGRAM as u16)) .author(nostr::PublicKey::from_hex(&agent).unwrap()) - .custom_tag(p_tag, [&owner]); + .custom_tags(p_tag, [&owner]); assert!(engram_filters_authorized(&[f], &agent)); } @@ -977,7 +977,7 @@ mod tests { let f = Filter::new() .kind(nostr::Kind::Custom(KIND_AGENT_ENGRAM as u16)) .author(nostr::PublicKey::from_hex(&agent).unwrap()) - .custom_tag(p_tag, [&owner]); + .custom_tags(p_tag, [&owner]); assert!(engram_filters_authorized(&[f], &owner)); } @@ -988,7 +988,7 @@ mod tests { let p_tag = SingleLetterTag::lowercase(Alphabet::P); let f = Filter::new() .kind(nostr::Kind::Custom(KIND_AGENT_ENGRAM as u16)) - .custom_tag(p_tag, [&owner]); + .custom_tags(p_tag, [&owner]); assert!(engram_filters_authorized(&[f], &owner)); } @@ -1000,7 +1000,7 @@ mod tests { let f = Filter::new() .kind(nostr::Kind::Custom(KIND_AGENT_ENGRAM as u16)) .author(nostr::PublicKey::from_hex(&agent).unwrap()) - .custom_tag(p_tag, [&owner]); + .custom_tags(p_tag, [&owner]); assert!(!engram_filters_authorized(&[f], &attacker)); } diff --git a/crates/sprout-relay/src/handlers/side_effects.rs b/crates/sprout-relay/src/handlers/side_effects.rs index 41b52ad24..1c74d57bb 100644 --- a/crates/sprout-relay/src/handlers/side_effects.rs +++ b/crates/sprout-relay/src/handlers/side_effects.rs @@ -156,7 +156,7 @@ pub async fn validate_admin_event( let channel_id = extract_h_tag_channel(event).ok_or_else(|| anyhow::anyhow!("missing or invalid h tag"))?; - let actor_bytes = event.pubkey.serialize().to_vec(); + let actor_bytes = event.pubkey.to_bytes().to_vec(); // Reject mutations on archived channels — except kind:9002 with archived=false // (unarchive), which must be allowed through so the channel can be restored. @@ -419,9 +419,9 @@ pub async fn emit_system_message( channel_id: Uuid, content: serde_json::Value, ) -> anyhow::Result<()> { - let channel_tag = Tag::parse(&["h", &channel_id.to_string()])?; + let channel_tag = Tag::parse(["h", &channel_id.to_string()])?; - let event = EventBuilder::new(Kind::Custom(40099), content.to_string(), [channel_tag]) + let event = EventBuilder::new(Kind::Custom(40099), content.to_string()).tags( [channel_tag]) .sign_with_keys(&state.relay_keypair) .map_err(|e| anyhow::anyhow!("failed to sign system message: {e}"))?; @@ -453,9 +453,9 @@ pub async fn emit_membership_notification( let actor_hex = hex::encode(actor_pubkey); let channel_id_str = channel_id.to_string(); - let p_tag = Tag::parse(&["p", &target_hex]) + let p_tag = Tag::parse(["p", &target_hex]) .map_err(|e| anyhow::anyhow!("failed to build p tag: {e}"))?; - let h_tag = Tag::parse(&["h", &channel_id_str]) + let h_tag = Tag::parse(["h", &channel_id_str]) .map_err(|e| anyhow::anyhow!("failed to build h tag: {e}"))?; let event_type = match notification_kind { @@ -477,9 +477,8 @@ pub async fn emit_membership_notification( let event = EventBuilder::new( Kind::Custom(notification_kind as u16), - content, - [p_tag, h_tag], - ) + content).tags( + [p_tag, h_tag]) .sign_with_keys(&state.relay_keypair) .map_err(|e| anyhow::anyhow!("failed to sign membership notification: {e}"))?; @@ -549,7 +548,7 @@ async fn emit_addressable_discovery_event( }; let ts = now.max(min_ts); - let event = EventBuilder::new(Kind::Custom(kind as u16), "", tags) + let event = EventBuilder::new(Kind::Custom(kind as u16), "").tags( tags) .custom_created_at(nostr::Timestamp::from(ts)) .sign_with_keys(&state.relay_keypair) .map_err(|e| anyhow::anyhow!("failed to sign kind:{kind}: {e}"))?; @@ -580,61 +579,61 @@ pub async fn emit_group_discovery_events( let channel = state.db.get_channel(channel_id).await?; let members = state.db.get_members(channel_id).await?; - let relay_pubkey_hex = hex::encode(state.relay_keypair.public_key().serialize()); + let relay_pubkey_hex = hex::encode(state.relay_keypair.public_key().to_bytes()); let group_id = channel_id.to_string(); // ── kind:39000 group metadata ──────────────────────────────────────────── { - let mut tags: Vec = vec![Tag::parse(&["d", &group_id])?]; - tags.push(Tag::parse(&["name", &channel.name])?); + let mut tags: Vec = vec![Tag::parse(["d", &group_id])?]; + tags.push(Tag::parse(["name", &channel.name])?); if let Some(ref desc) = channel.description { if !desc.is_empty() { - tags.push(Tag::parse(&["about", desc])?); + tags.push(Tag::parse(["about", desc])?); } } if channel.visibility == "private" { - tags.push(Tag::parse(&["private"])?); + tags.push(Tag::parse(["private"])?); } else { // Explicit "public" tag complements NIP-29's absence-of-"private" convention, // making channel visibility self-describing for clients. - tags.push(Tag::parse(&["public"])?); + tags.push(Tag::parse(["public"])?); } // NIP-29 hidden tag: hint to clients not to show DMs in public group lists. // Not a security boundary — access control is handled by channel-scoped storage. if channel.channel_type == "dm" { - tags.push(Tag::parse(&["hidden"])?); + tags.push(Tag::parse(["hidden"])?); // Include participant pubkeys in kind:39000 for DMs so clients can // resolve display names without a separate kind:39002 fetch. for m in &members { let pubkey_hex = hex::encode(&m.pubkey); - tags.push(Tag::parse(&["p", &pubkey_hex])?); + tags.push(Tag::parse(["p", &pubkey_hex])?); } } // Sprout channels always require explicit membership - tags.push(Tag::parse(&["closed"])?); + tags.push(Tag::parse(["closed"])?); // Channel type tag so clients can distinguish stream/forum/dm without inference - tags.push(Tag::parse(&["t", &channel.channel_type])?); + tags.push(Tag::parse(["t", &channel.channel_type])?); // Optional topic / purpose for richer client UX if let Some(ref topic) = channel.topic { if !topic.is_empty() { - tags.push(Tag::parse(&["topic", topic])?); + tags.push(Tag::parse(["topic", topic])?); } } if let Some(ref purpose) = channel.purpose { if !purpose.is_empty() { - tags.push(Tag::parse(&["purpose", purpose])?); + tags.push(Tag::parse(["purpose", purpose])?); } } // Archived state — clients use this to hide channels from the sidebar. if channel.archived_at.is_some() { - tags.push(Tag::parse(&["archived", "true"])?); + tags.push(Tag::parse(["archived", "true"])?); } // Ephemeral channel TTL — clients use this to show countdown timers. if let Some(ttl) = channel.ttl_seconds { - tags.push(Tag::parse(&["ttl", &ttl.to_string()])?); + tags.push(Tag::parse(["ttl", &ttl.to_string()])?); } if let Some(ref deadline) = channel.ttl_deadline { - tags.push(Tag::parse(&["ttl_deadline", &deadline.to_rfc3339()])?); + tags.push(Tag::parse(["ttl_deadline", &deadline.to_rfc3339()])?); } emit_addressable_discovery_event( state, @@ -648,13 +647,13 @@ pub async fn emit_group_discovery_events( // ── kind:39001 group admins ────────────────────────────────────────────── { - let mut tags: Vec = vec![Tag::parse(&["d", &group_id])?]; + let mut tags: Vec = vec![Tag::parse(["d", &group_id])?]; for m in members .iter() .filter(|m| m.role == "owner" || m.role == "admin") { let pubkey_hex = hex::encode(&m.pubkey); - tags.push(Tag::parse(&["p", &pubkey_hex, &m.role])?); + tags.push(Tag::parse(["p", &pubkey_hex, &m.role])?); } emit_addressable_discovery_event( state, @@ -668,12 +667,12 @@ pub async fn emit_group_discovery_events( // ── kind:39002 group members ───────────────────────────────────────────── { - let mut tags: Vec = vec![Tag::parse(&["d", &group_id])?]; + let mut tags: Vec = vec![Tag::parse(["d", &group_id])?]; for m in &members { let pubkey_hex = hex::encode(&m.pubkey); // NIP-29 convention: ["p", pubkey, relay_url, role]. Empty relay_url // because the canonical relay is implicit (this event is signed by it). - tags.push(Tag::parse(&["p", &pubkey_hex, "", &m.role])?); + tags.push(Tag::parse(["p", &pubkey_hex, "", &m.role])?); } emit_addressable_discovery_event( state, @@ -721,7 +720,7 @@ async fn handle_kind0_profile(event: &Event, state: &Arc) -> anyhow::R .and_then(|raw| crate::api::nip05::canonicalize_nip05(raw, &state.config.relay_url).ok()); let nip05_handle = nip05_owned.as_deref().unwrap_or(""); - let pubkey_bytes = event.pubkey.serialize().to_vec(); + let pubkey_bytes = event.pubkey.to_bytes().to_vec(); state.db.ensure_user(&pubkey_bytes).await?; @@ -743,7 +742,7 @@ async fn handle_kind0_profile(event: &Event, state: &Arc) -> anyhow::R if let Err(ref e) = result { let msg = format!("{e}"); if msg.contains("duplicate key value") || msg.contains("23505") { - warn!(pubkey = %nostr::util::hex::encode(&pubkey_bytes), + warn!(pubkey = %hex::encode(&pubkey_bytes), "kind:0 NIP-05 handle contested, syncing profile without it"); state .db @@ -760,7 +759,7 @@ async fn handle_kind0_profile(event: &Event, state: &Arc) -> anyhow::R } } - info!(pubkey = %nostr::util::hex::encode(&pubkey_bytes), "kind:0 profile synced to users table"); + info!(pubkey = %hex::encode(&pubkey_bytes), "kind:0 profile synced to users table"); Ok(()) } @@ -775,7 +774,7 @@ async fn handle_put_user(event: &Event, state: &Arc) -> anyhow::Result .parse() .map_err(|_| anyhow::anyhow!("invalid role: {role_str}"))?; - let actor_bytes = event.pubkey.serialize().to_vec(); + let actor_bytes = event.pubkey.to_bytes().to_vec(); state .db @@ -783,8 +782,8 @@ async fn handle_put_user(event: &Event, state: &Arc) -> anyhow::Result .await?; state.invalidate_membership(channel_id, &target_pubkey); - let actor_hex = nostr::util::hex::encode(&actor_bytes); - let target_hex = nostr::util::hex::encode(&target_pubkey); + let actor_hex = hex::encode(&actor_bytes); + let target_hex = hex::encode(&target_pubkey); emit_system_message( state, channel_id, @@ -820,7 +819,7 @@ async fn handle_remove_user(event: &Event, state: &Arc) -> anyhow::Res let channel_id = extract_h_tag_channel(event).ok_or_else(|| anyhow::anyhow!("missing h tag"))?; let target_pubkey = extract_p_tag(event).ok_or_else(|| anyhow::anyhow!("missing p tag"))?; - let actor_bytes = event.pubkey.serialize().to_vec(); + let actor_bytes = event.pubkey.to_bytes().to_vec(); // Guard: prevent last-owner orphaning on self-removal (kind 9001). if target_pubkey == actor_bytes { @@ -843,8 +842,8 @@ async fn handle_remove_user(event: &Event, state: &Arc) -> anyhow::Res state.invalidate_membership(channel_id, &target_pubkey); evict_live_channel_subscriptions(state, channel_id, &target_pubkey).await; - let actor_hex = nostr::util::hex::encode(&actor_bytes); - let target_hex = nostr::util::hex::encode(&target_pubkey); + let actor_hex = hex::encode(&actor_bytes); + let target_hex = hex::encode(&target_pubkey); let msg_type = if target_pubkey == actor_bytes { "member_left" } else { @@ -883,8 +882,8 @@ async fn handle_remove_user(event: &Event, state: &Arc) -> anyhow::Res async fn handle_edit_metadata(event: &Event, state: &Arc) -> anyhow::Result<()> { let channel_id = extract_h_tag_channel(event).ok_or_else(|| anyhow::anyhow!("missing h tag"))?; - let actor_bytes = event.pubkey.serialize().to_vec(); - let actor_hex = nostr::util::hex::encode(&actor_bytes); + let actor_bytes = event.pubkey.to_bytes().to_vec(); + let actor_hex = hex::encode(&actor_bytes); for tag in event.tags.iter() { let key = tag.kind().to_string(); @@ -1047,7 +1046,7 @@ async fn handle_delete_event_side_effect( return Ok(()); // No-op: skip system message to avoid false audit records. } - let actor_hex = nostr::util::hex::encode(event.pubkey.serialize()); + let actor_hex = hex::encode(event.pubkey.to_bytes()); emit_system_message( state, channel_id, @@ -1078,7 +1077,7 @@ async fn handle_create_group(event: &Event, state: &Arc) -> anyhow::Re .parse() .map_err(|_| anyhow::anyhow!("invalid channel_type: {channel_type_str}"))?; - let actor_bytes = event.pubkey.serialize().to_vec(); + let actor_bytes = event.pubkey.to_bytes().to_vec(); let description = extract_tag_value(event, "about"); let ttl_seconds = super::resolve_ttl(event, state.config.ephemeral_ttl_override); @@ -1126,7 +1125,7 @@ async fn handle_create_group(event: &Event, state: &Arc) -> anyhow::Re state.invalidate_all_accessible_channels(); } - let actor_hex = nostr::util::hex::encode(&actor_bytes); + let actor_hex = hex::encode(&actor_bytes); emit_system_message( state, channel.id, @@ -1159,7 +1158,7 @@ async fn handle_create_group(event: &Event, state: &Arc) -> anyhow::Re async fn handle_delete_group(event: &Event, state: &Arc) -> anyhow::Result<()> { let channel_id = extract_h_tag_channel(event).ok_or_else(|| anyhow::anyhow!("missing h tag"))?; - let actor_bytes = event.pubkey.serialize().to_vec(); + let actor_bytes = event.pubkey.to_bytes().to_vec(); // Soft-delete the channel. let deleted = state @@ -1175,7 +1174,7 @@ async fn handle_delete_group(event: &Event, state: &Arc) -> anyhow::Re // Clean up NIP-29 discovery events for the deleted group. if let Err(e) = state .db - .soft_delete_discovery_events(channel_id, &state.relay_keypair.public_key().serialize()) + .soft_delete_discovery_events(channel_id, state.relay_keypair.public_key().as_bytes()) .await { warn!(channel = %channel_id, error = %e, "failed to clean up NIP-29 discovery events"); @@ -1185,7 +1184,7 @@ async fn handle_delete_group(event: &Event, state: &Arc) -> anyhow::Re // Stale is_member=true entries would bypass the DB's deleted_at guard. state.invalidate_channel_deleted(); - let actor_hex = nostr::util::hex::encode(&actor_bytes); + let actor_hex = hex::encode(&actor_bytes); emit_system_message( state, channel_id, @@ -1202,7 +1201,7 @@ async fn handle_delete_group(event: &Event, state: &Arc) -> anyhow::Re async fn handle_join_request(event: &Event, state: &Arc) -> anyhow::Result<()> { let channel_id = extract_h_tag_channel(event).ok_or_else(|| anyhow::anyhow!("missing h tag"))?; - let actor_bytes = event.pubkey.serialize().to_vec(); + let actor_bytes = event.pubkey.to_bytes().to_vec(); // Only open channels allow self-join via kind:9021. let channel = state @@ -1235,7 +1234,7 @@ async fn handle_join_request(event: &Event, state: &Arc) -> anyhow::Re .await?; state.invalidate_membership(channel_id, &actor_bytes); - let actor_hex = nostr::util::hex::encode(&actor_bytes); + let actor_hex = hex::encode(&actor_bytes); emit_system_message( state, channel_id, @@ -1271,7 +1270,7 @@ async fn handle_leave_request(event: &Event, state: &Arc) -> anyhow::R // Kind 9022: functionally identical to self-remove via kind 9001 let channel_id = extract_h_tag_channel(event).ok_or_else(|| anyhow::anyhow!("missing h tag"))?; - let actor_bytes = event.pubkey.serialize().to_vec(); + let actor_bytes = event.pubkey.to_bytes().to_vec(); // Guard: prevent last-owner orphaning on leave. let members = state.db.get_members(channel_id).await?; @@ -1292,7 +1291,7 @@ async fn handle_leave_request(event: &Event, state: &Arc) -> anyhow::R state.invalidate_membership(channel_id, &actor_bytes); evict_live_channel_subscriptions(state, channel_id, &actor_bytes).await; - let actor_hex = nostr::util::hex::encode(&actor_bytes); + let actor_hex = hex::encode(&actor_bytes); emit_system_message( state, channel_id, @@ -1594,7 +1593,7 @@ fn effective_message_author(event: &Event, relay_pubkey: &nostr::PublicKey) -> V } } } - event.pubkey.serialize().to_vec() + event.pubkey.to_bytes().to_vec() } /// True if the event carries any `e` tag at all, regardless of whether its @@ -1673,7 +1672,7 @@ async fn handle_git_repo_announcement(event: &Event, state: &Arc) -> a )); } - let owner_hex = nostr::util::hex::encode(event.pubkey.serialize()); + let owner_hex = hex::encode(event.pubkey.to_bytes()); // The relay holds no persistent per-repo disk state: runtime reads and // writes hydrate an ephemeral bare repo from object storage per request @@ -1935,16 +1934,16 @@ pub async fn publish_nip43_membership_list(state: &Arc) -> anyhow::Res let mut tags: Vec = Vec::with_capacity(members.len() + 1); // NIP-70 protected-event marker — prevents re-broadcasting by third parties. - tags.push(Tag::parse(&["-"]).map_err(|e| anyhow::anyhow!("failed to build '-' tag: {e}"))?); + tags.push(Tag::parse(["-"]).map_err(|e| anyhow::anyhow!("failed to build '-' tag: {e}"))?); for member in &members { tags.push( - Tag::parse(&["member", &member.pubkey, &member.role]) + Tag::parse(["member", &member.pubkey, &member.role]) .map_err(|e| anyhow::anyhow!("failed to build member tag: {e}"))?, ); } - let event = EventBuilder::new(Kind::Custom(KIND_NIP43_MEMBERSHIP_LIST as u16), "", tags) + let event = EventBuilder::new(Kind::Custom(KIND_NIP43_MEMBERSHIP_LIST as u16), "").tags( tags) .sign_with_keys(&state.relay_keypair) .map_err(|e| anyhow::anyhow!("failed to sign kind:13534: {e}"))?; @@ -1984,12 +1983,12 @@ async fn publish_nip43_delta( let relay_pubkey_hex = state.relay_keypair.public_key().to_hex(); let tags = vec![ - Tag::parse(&["-"]).map_err(|e| anyhow::anyhow!("failed to build '-' tag: {e}"))?, - Tag::parse(&["p", target_pubkey_hex]) + Tag::parse(["-"]).map_err(|e| anyhow::anyhow!("failed to build '-' tag: {e}"))?, + Tag::parse(["p", target_pubkey_hex]) .map_err(|e| anyhow::anyhow!("failed to build p tag: {e}"))?, ]; - let event = EventBuilder::new(Kind::Custom(kind), "", tags) + let event = EventBuilder::new(Kind::Custom(kind), "").tags( tags) .sign_with_keys(&state.relay_keypair) .map_err(|e| anyhow::anyhow!("failed to sign kind:{kind}: {e}"))?; diff --git a/crates/sprout-relay/src/subscription.rs b/crates/sprout-relay/src/subscription.rs index 9c56c03cc..63a700971 100644 --- a/crates/sprout-relay/src/subscription.rs +++ b/crates/sprout-relay/src/subscription.rs @@ -448,7 +448,7 @@ mod tests { fn make_stored_event(kind: Kind, channel_id: Option) -> StoredEvent { let keys = Keys::generate(); - let event = EventBuilder::new(kind, "test", []) + let event = EventBuilder::new(kind, "test").tags( []) .sign_with_keys(&keys) .expect("sign"); StoredEvent::with_received_at(event, Utc::now(), channel_id, true) @@ -456,7 +456,7 @@ mod tests { fn make_stored_event_with_p(kind: Kind, p: &str, channel_id: Option) -> StoredEvent { let keys = Keys::generate(); - let event = EventBuilder::new(kind, "test", [Tag::parse(&["p", p]).expect("valid p tag")]) + let event = EventBuilder::new(kind, "test").tags( [Tag::parse(["p", p]).expect("valid p tag")]) .sign_with_keys(&keys) .expect("sign"); StoredEvent::with_received_at(event, Utc::now(), channel_id, true) @@ -974,13 +974,13 @@ mod tests { registry.register( conn_a, "observer_a".to_string(), - vec![Filter::new().kind(kind).custom_tag(p_tag(), [p_a])], + vec![Filter::new().kind(kind).custom_tags(p_tag(), [p_a])], None, ); registry.register( conn_b, "observer_b".to_string(), - vec![Filter::new().kind(kind).custom_tag(p_tag(), [p_b])], + vec![Filter::new().kind(kind).custom_tags(p_tag(), [p_b])], None, ); @@ -1000,7 +1000,7 @@ mod tests { let conn_id = Uuid::new_v4(); let kind = Kind::Custom(sprout_core::kind::KIND_AGENT_OBSERVER_FRAME as u16); let p = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - let filter = Filter::new().kind(kind).custom_tag(p_tag(), [p]); + let filter = Filter::new().kind(kind).custom_tags(p_tag(), [p]); let key = GlobalPKindIndexKey { kind, p: p.to_string(), diff --git a/crates/sprout-relay/src/workflow_sink.rs b/crates/sprout-relay/src/workflow_sink.rs index b95a7b0ef..720e89219 100644 --- a/crates/sprout-relay/src/workflow_sink.rs +++ b/crates/sprout-relay/src/workflow_sink.rs @@ -87,7 +87,7 @@ impl ActionSink for RelayActionSink { let author_pubkey = nostr::PublicKey::from_hex(&author_pubkey).map_err(|e| { ActionSinkError::InvalidInput(format!("invalid author pubkey: {e}")) })?; - let author_pubkey_bytes = author_pubkey.serialize().to_vec(); + let author_pubkey_bytes = author_pubkey.to_bytes().to_vec(); let author_pubkey_hex = author_pubkey.to_hex(); let is_member = state .is_member_cached(channel_uuid, &author_pubkey_bytes) @@ -105,16 +105,16 @@ impl ActionSink for RelayActionSink { // - `h` tag scopes to the channel (NIP-29, canonical UUID) // - `sprout:workflow` tag prevents recursive workflow triggering let tags = vec![ - Tag::parse(&["p", &author_pubkey_hex]) + Tag::parse(["p", &author_pubkey_hex]) .map_err(|e| ActionSinkError::EventBuild(format!("p tag: {e}")))?, - Tag::parse(&["h", &channel_id_canonical]) + Tag::parse(["h", &channel_id_canonical]) .map_err(|e| ActionSinkError::EventBuild(format!("h tag: {e}")))?, - Tag::parse(&["sprout:workflow", "true"]) + Tag::parse(["sprout:workflow", "true"]) .map_err(|e| ActionSinkError::EventBuild(format!("workflow tag: {e}")))?, ]; let kind = Kind::from(KIND_STREAM_MESSAGE as u16); - let event = EventBuilder::new(kind, &text, tags) + let event = EventBuilder::new(kind, &text).tags( tags) .sign_with_keys(&state.relay_keypair) .map_err(|e| ActionSinkError::EventBuild(format!("signing: {e}")))?; diff --git a/crates/sprout-sdk/src/builders.rs b/crates/sprout-sdk/src/builders.rs index 7c91e9303..7db2ea368 100644 --- a/crates/sprout-sdk/src/builders.rs +++ b/crates/sprout-sdk/src/builders.rs @@ -23,7 +23,7 @@ use crate::{ChannelKind, DiffMeta, MemberRole, SdkError, ThreadRef, Visibility, /// Parse a tag slice, mapping errors to `SdkError::InvalidTag`. fn tag(parts: &[&str]) -> Result { - Tag::parse(parts).map_err(|e| SdkError::InvalidTag(e.to_string())) + Tag::parse(parts.iter().copied()).map_err(|e| SdkError::InvalidTag(e.to_string())) } /// Validate content byte length. @@ -89,7 +89,7 @@ fn mention_tags(mentions: &[&str], tags: &mut Vec) -> Result<(), SdkError> fn imeta_tags(media_tags: &[Vec], tags: &mut Vec) -> Result<(), SdkError> { for mt in media_tags { let parts: Vec<&str> = mt.iter().map(String::as_str).collect(); - tags.push(Tag::parse(&parts).map_err(|e| SdkError::InvalidTag(e.to_string()))?); + tags.push(Tag::parse(parts).map_err(|e| SdkError::InvalidTag(e.to_string()))?); } Ok(()) } @@ -122,7 +122,7 @@ pub fn build_message( tags.push(tag(&["broadcast", "1"])?); } imeta_tags(media_tags, &mut tags)?; - Ok(EventBuilder::new(Kind::Custom(9), content, tags)) + Ok(EventBuilder::new(Kind::Custom(9), content).tags( tags)) } // ── Builder: build_agent_observer_frame ───────────────────────────────────── @@ -159,9 +159,8 @@ pub fn build_agent_observer_frame( Ok(EventBuilder::new( Kind::Custom(KIND_AGENT_OBSERVER_FRAME as u16), - encrypted_content, - tags, - )) + encrypted_content).tags( + tags)) } // ── Builder 2: build_forum_post ─────────────────────────────────────────────── @@ -177,7 +176,7 @@ pub fn build_forum_post( let mut tags = vec![tag(&["h", &channel_id.to_string()])?]; mention_tags(mentions, &mut tags)?; imeta_tags(media_tags, &mut tags)?; - Ok(EventBuilder::new(Kind::Custom(45001), content, tags)) + Ok(EventBuilder::new(Kind::Custom(45001), content).tags( tags)) } // ── Builder 3: build_forum_comment ─────────────────────────────────────────── @@ -195,7 +194,7 @@ pub fn build_forum_comment( thread_tags(thread_ref, &mut tags)?; mention_tags(mentions, &mut tags)?; imeta_tags(media_tags, &mut tags)?; - Ok(EventBuilder::new(Kind::Custom(45003), content, tags)) + Ok(EventBuilder::new(Kind::Custom(45003), content).tags( tags)) } // ── Builder 4: build_diff_message ──────────────────────────────────────────── @@ -267,7 +266,7 @@ pub fn build_diff_message( if let Some(tr) = thread_ref { thread_tags(tr, &mut tags)?; } - Ok(EventBuilder::new(Kind::Custom(40008), content, tags)) + Ok(EventBuilder::new(Kind::Custom(40008), content).tags( tags)) } // ── Builder 5: build_edit ──────────────────────────────────────────────────── @@ -283,7 +282,7 @@ pub fn build_edit( tag(&["h", &channel_id.to_string()])?, tag(&["e", &target_event_id.to_hex()])?, ]; - Ok(EventBuilder::new(Kind::Custom(40003), new_content, tags)) + Ok(EventBuilder::new(Kind::Custom(40003), new_content).tags( tags)) } // ── Builder 6: build_delete_message ────────────────────────────────────────── @@ -297,7 +296,7 @@ pub fn build_delete_message( tag(&["h", &channel_id.to_string()])?, tag(&["e", &target_event_id.to_hex()])?, ]; - Ok(EventBuilder::new(Kind::Custom(9005), "", tags)) + Ok(EventBuilder::new(Kind::Custom(9005), "").tags( tags)) } // ── Builder 7: build_delete_compat ─────────────────────────────────────────── @@ -305,7 +304,7 @@ pub fn build_delete_message( /// Build a NIP-09 compatible deletion event (kind 5). pub fn build_delete_compat(target_event_id: nostr::EventId) -> Result { let tags = vec![tag(&["e", &target_event_id.to_hex()])?]; - Ok(EventBuilder::new(Kind::Custom(5), "", tags)) + Ok(EventBuilder::new(Kind::Custom(5), "").tags( tags)) } // ── Builder 8: build_vote ──────────────────────────────────────────────────── @@ -324,7 +323,7 @@ pub fn build_vote( tag(&["h", &channel_id.to_string()])?, tag(&["e", &target_event_id.to_hex()])?, ]; - Ok(EventBuilder::new(Kind::Custom(45002), content, tags)) + Ok(EventBuilder::new(Kind::Custom(45002), content).tags( tags)) } // ── Builder 9: build_reaction ──────────────────────────────────────────────── @@ -338,7 +337,7 @@ pub fn build_reaction( return Err(SdkError::EmojiTooLong); } let tags = vec![tag(&["e", &target_event_id.to_hex()])?]; - Ok(EventBuilder::new(Kind::Custom(7), emoji, tags)) + Ok(EventBuilder::new(Kind::Custom(7), emoji).tags( tags)) } // ── Builder 10: build_remove_reaction ──────────────────────────────────────── @@ -346,7 +345,7 @@ pub fn build_reaction( /// Build a deletion event targeting a reaction (kind 5). pub fn build_remove_reaction(reaction_event_id: nostr::EventId) -> Result { let tags = vec![tag(&["e", &reaction_event_id.to_hex()])?]; - Ok(EventBuilder::new(Kind::Custom(5), "", tags)) + Ok(EventBuilder::new(Kind::Custom(5), "").tags( tags)) } // ── Builder 11: build_set_canvas ───────────────────────────────────────────── @@ -354,7 +353,7 @@ pub fn build_remove_reaction(reaction_event_id: nostr::EventId) -> Result Result { let tags = vec![tag(&["h", &channel_id.to_string()])?]; - Ok(EventBuilder::new(Kind::Custom(40100), content, tags)) + Ok(EventBuilder::new(Kind::Custom(40100), content).tags( tags)) } // ── Builder 12: build_profile ──────────────────────────────────────────────── @@ -386,7 +385,7 @@ pub fn build_profile( map.insert("nip05".into(), serde_json::Value::String(v.into())); } let content = serde_json::Value::Object(map).to_string(); - Ok(EventBuilder::new(Kind::Custom(0), content, [])) + Ok(EventBuilder::new(Kind::Custom(0), content).tags( [])) } // ── Builder 13: build_add_member ───────────────────────────────────────────── @@ -405,7 +404,7 @@ pub fn build_add_member( if let Some(r) = role { tags.push(tag(&["role", r.as_str()])?); } - Ok(EventBuilder::new(Kind::Custom(9000), "", tags)) + Ok(EventBuilder::new(Kind::Custom(9000), "").tags( tags)) } // ── Builder 14: build_remove_member ────────────────────────────────────────── @@ -420,7 +419,7 @@ pub fn build_remove_member( tag(&["h", &channel_id.to_string()])?, tag(&["p", &target_pubkey.to_ascii_lowercase()])?, ]; - Ok(EventBuilder::new(Kind::Custom(9001), "", tags)) + Ok(EventBuilder::new(Kind::Custom(9001), "").tags( tags)) } // ── Builder 15: build_leave ────────────────────────────────────────────────── @@ -428,7 +427,7 @@ pub fn build_remove_member( /// Build a NIP-29 leave-request event (kind 9022). pub fn build_leave(channel_id: Uuid) -> Result { let tags = vec![tag(&["h", &channel_id.to_string()])?]; - Ok(EventBuilder::new(Kind::Custom(9022), "", tags)) + Ok(EventBuilder::new(Kind::Custom(9022), "").tags( tags)) } // ── Builder 16: build_update_channel ───────────────────────────────────────── @@ -451,7 +450,7 @@ pub fn build_update_channel( if let Some(a) = about { tags.push(tag(&["about", a])?); } - Ok(EventBuilder::new(Kind::Custom(9002), "", tags)) + Ok(EventBuilder::new(Kind::Custom(9002), "").tags( tags)) } // ── Builder 17: build_set_topic ────────────────────────────────────────────── @@ -462,7 +461,7 @@ pub fn build_set_topic(channel_id: Uuid, topic: &str) -> Result Result Result { let tags = vec![tag(&["h", &channel_id.to_string()])?]; - Ok(EventBuilder::new(Kind::Custom(9021), "", tags)) + Ok(EventBuilder::new(Kind::Custom(9021), "").tags( tags)) } // ── Builder 21: build_archive ──────────────────────────────────────────────── @@ -515,7 +514,7 @@ pub fn build_archive(channel_id: Uuid) -> Result { tag(&["h", &channel_id.to_string()])?, tag(&["archived", "true"])?, ]; - Ok(EventBuilder::new(Kind::Custom(9002), "", tags)) + Ok(EventBuilder::new(Kind::Custom(9002), "").tags( tags)) } // ── Builder 22: build_unarchive ────────────────────────────────────────────── @@ -526,7 +525,7 @@ pub fn build_unarchive(channel_id: Uuid) -> Result { tag(&["h", &channel_id.to_string()])?, tag(&["archived", "false"])?, ]; - Ok(EventBuilder::new(Kind::Custom(9002), "", tags)) + Ok(EventBuilder::new(Kind::Custom(9002), "").tags( tags)) } // ── Builder 23: build_delete_channel ───────────────────────────────────────── @@ -534,7 +533,7 @@ pub fn build_unarchive(channel_id: Uuid) -> Result { /// Build a NIP-29 delete-group event (kind 9008). pub fn build_delete_channel(channel_id: Uuid) -> Result { let tags = vec![tag(&["h", &channel_id.to_string()])?]; - Ok(EventBuilder::new(Kind::Custom(9008), "", tags)) + Ok(EventBuilder::new(Kind::Custom(9008), "").tags( tags)) } // ── Builder 24: build_note ─────────────────────────────────────────────────── @@ -554,7 +553,7 @@ pub fn build_note( if let Some(reply_id) = reply_to_event_id { tags.push(tag(&["e", &reply_id.to_hex(), "", "reply"])?); } - Ok(EventBuilder::new(Kind::Custom(1), content, tags)) + Ok(EventBuilder::new(Kind::Custom(1), content).tags( tags)) } // ── Builder 25: build_contact_list ─────────────────────────────────────────── @@ -619,7 +618,7 @@ pub fn build_contact_list( petname.unwrap_or(""), ])?); } - Ok(EventBuilder::new(Kind::Custom(3), "", tags)) + Ok(EventBuilder::new(Kind::Custom(3), "").tags( tags)) } // ── Huddle shared helper ────────────────────────────────────────────────────── @@ -649,7 +648,7 @@ fn build_huddle_event_sdk( map.insert((*k).into(), serde_json::Value::String(v.to_string())); } let content = serde_json::Value::Object(map).to_string(); - Ok(EventBuilder::new(Kind::Custom(kind), content, tags)) + Ok(EventBuilder::new(Kind::Custom(kind), content).tags( tags)) } // ── Builder 26: build_huddle_started ───────────────────────────────────────── @@ -895,9 +894,8 @@ pub fn build_repo_announcement( Ok(EventBuilder::new( Kind::Custom(KIND_GIT_REPO_ANNOUNCEMENT as u16), - "", - tags, - )) + "").tags( + tags)) } // ── Builder 31: build_workflow_def ──────────────────────────────────────────── @@ -919,9 +917,8 @@ pub fn build_workflow_def( ]; Ok(EventBuilder::new( Kind::Custom(KIND_WORKFLOW_DEF as u16), - yaml, - tags, - )) + yaml).tags( + tags)) } // ── Builder 32: build_workflow_update ───────────────────────────────────────── @@ -943,9 +940,8 @@ pub fn build_workflow_update( ]; Ok(EventBuilder::new( Kind::Custom(KIND_WORKFLOW_DEF as u16), - yaml, - tags, - )) + yaml).tags( + tags)) } // ── Builder 33: build_workflow_delete ───────────────────────────────────────── @@ -965,9 +961,8 @@ pub fn build_workflow_delete( ])?]; Ok(EventBuilder::new( Kind::Custom(KIND_DELETION as u16), - "", - tags, - )) + "").tags( + tags)) } // ── Builder 34: build_workflow_trigger ──────────────────────────────────────── @@ -977,9 +972,8 @@ pub fn build_workflow_trigger(workflow_id: Uuid) -> Result Result { } Ok(EventBuilder::new( Kind::Custom(KIND_DM_OPEN as u16), - "", - tags, - )) + "").tags( + tags)) } // ── Builder 37: build_dm_add_member ────────────────────────────────────────── @@ -1040,9 +1033,8 @@ pub fn build_dm_add_member(channel_id: Uuid, pubkey: &str) -> Result Result { let tags = vec![tag(&["status", status])?]; Ok(EventBuilder::new( Kind::Custom(KIND_PRESENCE_UPDATE as u16), - status, - tags, - )) + status).tags( + tags)) } // ── Tests ──────────────────────────────────────────────────────────────────── @@ -1086,7 +1077,7 @@ mod tests { fn event_id() -> EventId { let k = keys(); - EventBuilder::new(Kind::Custom(1), "x", []) + EventBuilder::new(Kind::Custom(1), "x").tags( []) .sign_with_keys(&k) .expect("sign") .id @@ -1745,8 +1736,8 @@ mod tests { #[test] fn extract_channel_id_invalid_uuid() { // Build an event with a malformed h-tag value - let tags = vec![Tag::parse(&["h", "not-a-uuid"]).unwrap()]; - let ev = EventBuilder::new(Kind::Custom(9), "x", tags) + let tags = vec![Tag::parse(["h", "not-a-uuid"]).unwrap()]; + let ev = EventBuilder::new(Kind::Custom(9), "x").tags( tags) .sign_with_keys(&keys()) .unwrap(); assert_eq!(extract_channel_id(&ev), None); @@ -1768,7 +1759,7 @@ mod tests { fn build_note_with_reply() { let keys = nostr::Keys::generate(); // Create a dummy event to get a valid EventId - let dummy = EventBuilder::new(Kind::Custom(1), "dummy", vec![]) + let dummy = EventBuilder::new(Kind::Custom(1), "dummy").tags( vec![]) .sign_with_keys(&keys) .unwrap(); let builder = build_note("reply text", Some(dummy.id)).unwrap(); diff --git a/crates/sprout-sdk/src/nip_oa.rs b/crates/sprout-sdk/src/nip_oa.rs index 4daff2d6b..28c6c2dc2 100644 --- a/crates/sprout-sdk/src/nip_oa.rs +++ b/crates/sprout-sdk/src/nip_oa.rs @@ -19,10 +19,10 @@ use core::str::FromStr; -use nostr::bitcoin::hashes::sha256::Hash as Sha256Hash; -use nostr::bitcoin::hashes::Hash; -use nostr::bitcoin::secp256k1::schnorr::Signature; -use nostr::bitcoin::secp256k1::{Message, XOnlyPublicKey}; +use nostr::hashes::sha256::Hash as Sha256Hash; +use nostr::hashes::Hash; +use nostr::secp256k1::schnorr::Signature; +use nostr::secp256k1::Message; use nostr::{Keys, PublicKey, Tag, SECP256K1}; use serde_json::Value; @@ -229,9 +229,11 @@ pub fn verify_auth_tag( let preimage = build_preimage(agent_pubkey, conditions); let message = hash_preimage(&preimage); - let xonly: &XOnlyPublicKey = &owner_pubkey; + let xonly = owner_pubkey + .xonly() + .map_err(|e| SdkError::InvalidInput(format!("owner pubkey xonly conversion failed: {e}")))?; SECP256K1 - .verify_schnorr(&sig, &message, xonly) + .verify_schnorr(&sig, &message, &xonly) .map_err(|e| SdkError::InvalidInput(format!("signature verification failed: {e}")))?; Ok(owner_pubkey) @@ -296,7 +298,7 @@ pub fn parse_auth_tag(json_str: &str) -> Result { ))); } - Tag::parse(&["auth", owner_pubkey_hex, conditions, sig_hex]) + Tag::parse(["auth", owner_pubkey_hex, conditions, sig_hex]) .map_err(|e| SdkError::InvalidInput(format!("failed to construct Tag: {e}"))) } @@ -329,9 +331,9 @@ mod tests { let message = hash_preimage(&preimage); let sig = Signature::from_str(SPEC_SIG_HEX).expect("spec sig must parse"); - let xonly: &XOnlyPublicKey = &owner_pubkey; + let xonly = owner_pubkey.xonly().expect("valid test pubkey"); SECP256K1 - .verify_schnorr(&sig, &message, xonly) + .verify_schnorr(&sig, &message, &xonly) .expect("spec test vector signature must verify"); } diff --git a/crates/sprout-search/src/index.rs b/crates/sprout-search/src/index.rs index 1390a9dcc..23bd2dd20 100644 --- a/crates/sprout-search/src/index.rs +++ b/crates/sprout-search/src/index.rs @@ -301,7 +301,7 @@ mod tests { fn make_stored_event(content: &str, kind: Kind, channel_id: Option) -> StoredEvent { let keys = Keys::generate(); - let event = EventBuilder::new(kind, content, []) + let event = EventBuilder::new(kind, content).tags( []) .sign_with_keys(&keys) .expect("signing failed"); StoredEvent::new(event, channel_id) @@ -459,8 +459,8 @@ mod tests { #[test] fn tag_flattening_uses_unit_separator() { let keys = Keys::generate(); - let tag = nostr::Tag::parse(&["e", "abc123def456"]).expect("tag parse"); - let event = EventBuilder::new(Kind::TextNote, "tagged", [tag]) + let tag = nostr::Tag::parse(["e", "abc123def456"]).expect("tag parse"); + let event = EventBuilder::new(Kind::TextNote, "tagged").tags( [tag]) .sign_with_keys(&keys) .expect("sign"); let stored = StoredEvent::new(event, None); @@ -511,8 +511,8 @@ mod tests { fn tag_with_colon_value_not_ambiguous() { let keys = Keys::generate(); // "r" tag with a URL value containing colons - let tag = nostr::Tag::parse(&["r", "wss://relay.example.com"]).expect("tag parse"); - let event = EventBuilder::new(Kind::TextNote, "relay ref", [tag]) + let tag = nostr::Tag::parse(["r", "wss://relay.example.com"]).expect("tag parse"); + let event = EventBuilder::new(Kind::TextNote, "relay ref").tags( [tag]) .sign_with_keys(&keys) .expect("sign"); let stored = StoredEvent::new(event, None); diff --git a/crates/sprout-search/src/lib.rs b/crates/sprout-search/src/lib.rs index 6d75dc378..c488af52d 100644 --- a/crates/sprout-search/src/lib.rs +++ b/crates/sprout-search/src/lib.rs @@ -181,7 +181,7 @@ mod integration_tests { fn make_stored_event(content: &str, kind: Kind) -> StoredEvent { let keys = Keys::generate(); - let event = EventBuilder::new(kind, content, []) + let event = EventBuilder::new(kind, content).tags( []) .sign_with_keys(&keys) .expect("signing failed"); StoredEvent::new(event, None) diff --git a/crates/sprout-test-client/src/bin/mention.rs b/crates/sprout-test-client/src/bin/mention.rs index 26f2c4731..07eefecd8 100644 --- a/crates/sprout-test-client/src/bin/mention.rs +++ b/crates/sprout-test-client/src/bin/mention.rs @@ -23,10 +23,10 @@ async fn main() -> anyhow::Result<()> { let mut client = SproutTestClient::connect(&url, &keys).await?; - let h_tag = Tag::parse(&["h", channel_id])?; - let p_tag = Tag::parse(&["p", target_pubkey])?; + let h_tag = Tag::parse(["h", channel_id])?; + let p_tag = Tag::parse(["p", target_pubkey])?; let event = - EventBuilder::new(Kind::Custom(9), message, [h_tag, p_tag]).sign_with_keys(&keys)?; + EventBuilder::new(Kind::Custom(9), message).tags( [h_tag, p_tag]).sign_with_keys(&keys)?; let ok = client.send_event(event).await?; if ok.accepted { diff --git a/crates/sprout-test-client/src/lib.rs b/crates/sprout-test-client/src/lib.rs index 41e3b477f..b9688caed 100644 --- a/crates/sprout-test-client/src/lib.rs +++ b/crates/sprout-test-client/src/lib.rs @@ -7,7 +7,7 @@ use std::collections::VecDeque; use std::time::Duration; use futures_util::{SinkExt, StreamExt}; -use nostr::{Event, EventBuilder, Filter, Keys, Kind, Tag, Url}; +use nostr::{Event, EventBuilder, Filter, Keys, Kind, RelayUrl, Tag, Url}; use serde_json::{json, Value}; use thiserror::Error; use tokio::time::timeout; @@ -125,10 +125,8 @@ impl SproutTestClient { pub async fn authenticate(&mut self, keys: &Keys) -> Result<(), TestClientError> { let challenge = self.wait_for_auth_challenge(Duration::from_secs(5)).await?; - let relay_url: Url = self - .relay_url - .parse() - .map_err(|e: url::ParseError| TestClientError::Url(e.to_string()))?; + let relay_url = RelayUrl::parse(&self.relay_url) + .map_err(|e| TestClientError::Url(e.to_string()))?; let auth_event = EventBuilder::auth(&challenge, relay_url).sign_with_keys(keys)?; let event_id = auth_event.id.to_hex(); @@ -159,9 +157,9 @@ impl SproutTestClient { content: &str, kind: u16, ) -> Result { - let h_tag = Tag::parse(&["h", channel_id]) + let h_tag = Tag::parse(["h", channel_id]) .map_err(|e| TestClientError::EventBuilder(e.to_string()))?; - let event = EventBuilder::new(Kind::Custom(kind), content, [h_tag]).sign_with_keys(keys)?; + let event = EventBuilder::new(Kind::Custom(kind), content).tags( [h_tag]).sign_with_keys(keys)?; self.send_event(event).await } @@ -537,8 +535,8 @@ mod tests { fn text_event_carries_h_tag() { let keys = Keys::generate(); let channel_id = "my-channel-123"; - let h_tag = Tag::parse(&["h", channel_id]).unwrap(); - let event = EventBuilder::new(Kind::Custom(9), "hello", [h_tag]) + let h_tag = Tag::parse(["h", channel_id]).unwrap(); + let event = EventBuilder::new(Kind::Custom(9), "hello").tags( [h_tag]) .sign_with_keys(&keys) .unwrap(); diff --git a/crates/sprout-test-client/src/main.rs b/crates/sprout-test-client/src/main.rs index 9863423a0..52e9749a8 100644 --- a/crates/sprout-test-client/src/main.rs +++ b/crates/sprout-test-client/src/main.rs @@ -103,7 +103,7 @@ async fn run_subscribe(url: &str, keys: &Keys, channel: &str, kind: u16) { }; let sub_id = format!("cli-sub-{}", uuid::Uuid::new_v4()); - let filter = Filter::new().kind(nostr::Kind::Custom(kind)).custom_tag( + let filter = Filter::new().kind(nostr::Kind::Custom(kind)).custom_tags( nostr::SingleLetterTag::lowercase(nostr::Alphabet::E), [channel], ); diff --git a/crates/sprout-test-client/tests/e2e_long_form.rs b/crates/sprout-test-client/tests/e2e_long_form.rs index 7f4cd806f..60f58ac45 100644 --- a/crates/sprout-test-client/tests/e2e_long_form.rs +++ b/crates/sprout-test-client/tests/e2e_long_form.rs @@ -42,11 +42,11 @@ fn build_long_form_event( extra_tags: Vec, ) -> nostr::Event { let mut tags = vec![ - Tag::parse(&["d", d_tag]).unwrap(), - Tag::parse(&["title", title]).unwrap(), + Tag::parse(["d", d_tag]).unwrap(), + Tag::parse(["title", title]).unwrap(), ]; tags.extend(extra_tags); - EventBuilder::new(Kind::Custom(KIND_LONG_FORM), content, tags) + EventBuilder::new(Kind::Custom(KIND_LONG_FORM), content).tags( tags) .sign_with_keys(keys) .unwrap() } @@ -147,7 +147,7 @@ async fn test_long_form_stray_h_tag_ignored() { &d_tag, "Stray H-Tag Article", "Should be stored globally despite h-tag.", - vec![Tag::parse(&["h", &fake_channel]).unwrap()], + vec![Tag::parse(["h", &fake_channel]).unwrap()], ); let event_id = event.id; @@ -222,7 +222,7 @@ async fn test_long_form_nip33_replacement() { let filter = Filter::new() .kind(Kind::Custom(KIND_LONG_FORM)) .author(keys.public_key()) - .custom_tag(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); client .subscribe(&sid, vec![filter]) .await @@ -262,10 +262,10 @@ async fn test_long_form_stale_write_rejected() { // Publish the "newer" event first (with a future-ish timestamp) let newer = { let tags = vec![ - Tag::parse(&["d", &d_tag]).unwrap(), - Tag::parse(&["title", "Newer Article"]).unwrap(), + Tag::parse(["d", &d_tag]).unwrap(), + Tag::parse(["title", "Newer Article"]).unwrap(), ]; - EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "Newer content.", tags) + EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "Newer content.").tags( tags) .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() + 100)) .sign_with_keys(&keys) .unwrap() @@ -277,10 +277,10 @@ async fn test_long_form_stale_write_rejected() { // Now try to publish an "older" event with the same d-tag but earlier timestamp let older = { let tags = vec![ - Tag::parse(&["d", &d_tag]).unwrap(), - Tag::parse(&["title", "Older Article"]).unwrap(), + Tag::parse(["d", &d_tag]).unwrap(), + Tag::parse(["title", "Older Article"]).unwrap(), ]; - EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "Older content.", tags) + EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "Older content.").tags( tags) .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() - 100)) .sign_with_keys(&keys) .unwrap() @@ -294,7 +294,7 @@ async fn test_long_form_stale_write_rejected() { let filter = Filter::new() .kind(Kind::Custom(KIND_LONG_FORM)) .author(keys.public_key()) - .custom_tag(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); client .subscribe(&sid, vec![filter]) .await diff --git a/crates/sprout-test-client/tests/e2e_mcp.rs b/crates/sprout-test-client/tests/e2e_mcp.rs index 2c05a51be..26967f2f5 100644 --- a/crates/sprout-test-client/tests/e2e_mcp.rs +++ b/crates/sprout-test-client/tests/e2e_mcp.rs @@ -63,12 +63,12 @@ async fn create_channel_for_test(keys: &Keys, name: &str) -> String { let pubkey_hex = keys.public_key().to_hex(); let channel_uuid = uuid::Uuid::new_v4(); let tags = vec![ - Tag::parse(&["h", &channel_uuid.to_string()]).unwrap(), - Tag::parse(&["name", name]).unwrap(), - Tag::parse(&["channel_type", "stream"]).unwrap(), - Tag::parse(&["visibility", "open"]).unwrap(), + Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), + Tag::parse(["name", name]).unwrap(), + Tag::parse(["channel_type", "stream"]).unwrap(), + Tag::parse(["visibility", "open"]).unwrap(), ]; - let event = EventBuilder::new(Kind::Custom(9007), "", tags) + let event = EventBuilder::new(Kind::Custom(9007), "").tags( tags) .sign_with_keys(keys) .unwrap(); let resp = client @@ -103,7 +103,7 @@ async fn set_profile_via_event( map.insert("about".into(), serde_json::Value::String(a.into())); } let content = serde_json::Value::Object(map).to_string(); - let event = EventBuilder::new(Kind::Custom(0), &content, []) + let event = EventBuilder::new(Kind::Custom(0), &content).tags( []) .sign_with_keys(keys) .unwrap(); let resp = client diff --git a/crates/sprout-test-client/tests/e2e_media.rs b/crates/sprout-test-client/tests/e2e_media.rs index f98496fba..8ca2b23e3 100644 --- a/crates/sprout-test-client/tests/e2e_media.rs +++ b/crates/sprout-test-client/tests/e2e_media.rs @@ -42,11 +42,11 @@ fn sign_blossom_auth(keys: &Keys, sha256: &str) -> nostr::Event { let now = Timestamp::now().as_u64(); let exp_str = (now + 300).to_string(); let tags = vec![ - Tag::parse(&["t", "upload"]).expect("t tag"), - Tag::parse(&["x", sha256]).expect("x tag"), - Tag::parse(&["expiration", &exp_str]).expect("expiration tag"), + Tag::parse(["t", "upload"]).expect("t tag"), + Tag::parse(["x", sha256]).expect("x tag"), + Tag::parse(["expiration", &exp_str]).expect("expiration tag"), ]; - EventBuilder::new(Kind::from(24242), "Upload test", tags) + EventBuilder::new(Kind::from(24242), "Upload test").tags( tags) .sign_with_keys(keys) .expect("sign blossom auth") } diff --git a/crates/sprout-test-client/tests/e2e_media_extended.rs b/crates/sprout-test-client/tests/e2e_media_extended.rs index a71a3baed..ea1b7733c 100644 --- a/crates/sprout-test-client/tests/e2e_media_extended.rs +++ b/crates/sprout-test-client/tests/e2e_media_extended.rs @@ -29,11 +29,11 @@ fn http_client() -> Client { fn sign_blossom_auth(keys: &Keys, sha256: &str) -> nostr::Event { let now = Timestamp::now().as_u64(); let tags = vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", sha256]).unwrap(), - Tag::parse(&["expiration", &(now + 300).to_string()]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", sha256]).unwrap(), + Tag::parse(["expiration", &(now + 300).to_string()]).unwrap(), ]; - EventBuilder::new(Kind::from(24242), "Upload test", tags) + EventBuilder::new(Kind::from(24242), "Upload test").tags( tags) .sign_with_keys(keys) .unwrap() } @@ -127,7 +127,7 @@ fn tiny_webp() -> Vec { // ── Auth edge case helpers ────────────────────────────────────────────────── fn sign_custom_auth(keys: &Keys, kind: u16, content: &str, tags: Vec) -> nostr::Event { - EventBuilder::new(Kind::from(kind), content, tags) + EventBuilder::new(Kind::from(kind), content).tags( tags) .sign_with_keys(keys) .unwrap() } @@ -233,9 +233,9 @@ async fn test_auth_wrong_kind() { 27235, "Upload test", vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", &sha256]).unwrap(), - Tag::parse(&["expiration", &(now + 300).to_string()]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), + Tag::parse(["expiration", &(now + 300).to_string()]).unwrap(), ], ); let resp = upload_with_auth(&client, &auth, &sha256, &jpeg).await; @@ -256,8 +256,8 @@ async fn test_auth_missing_t_tag() { 24242, "Upload test", vec![ - Tag::parse(&["x", &sha256]).unwrap(), - Tag::parse(&["expiration", &(now + 300).to_string()]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), + Tag::parse(["expiration", &(now + 300).to_string()]).unwrap(), ], ); let resp = upload_with_auth(&client, &auth, &sha256, &jpeg).await; @@ -277,8 +277,8 @@ async fn test_auth_missing_expiration() { 24242, "Upload test", vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", &sha256]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), ], ); let resp = upload_with_auth(&client, &auth, &sha256, &jpeg).await; @@ -299,9 +299,9 @@ async fn test_auth_expired_token() { 24242, "Upload test", vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", &sha256]).unwrap(), - Tag::parse(&["expiration", &(now - 60).to_string()]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), + Tag::parse(["expiration", &(now - 60).to_string()]).unwrap(), ], ); let resp = upload_with_auth(&client, &auth, &sha256, &jpeg).await; @@ -322,9 +322,9 @@ async fn test_auth_empty_content() { 24242, "", vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", &sha256]).unwrap(), - Tag::parse(&["expiration", &(now + 300).to_string()]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), + Tag::parse(["expiration", &(now + 300).to_string()]).unwrap(), ], ); let resp = upload_with_auth(&client, &auth, &sha256, &jpeg).await; @@ -345,10 +345,10 @@ async fn test_auth_server_tag_mismatch() { 24242, "Upload test", vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", &sha256]).unwrap(), - Tag::parse(&["expiration", &(now + 300).to_string()]).unwrap(), - Tag::parse(&["server", "evil.example.com"]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), + Tag::parse(["expiration", &(now + 300).to_string()]).unwrap(), + Tag::parse(["server", "evil.example.com"]).unwrap(), ], ); let resp = upload_with_auth(&client, &auth, &sha256, &jpeg).await; @@ -369,10 +369,10 @@ async fn test_auth_server_tag_correct() { 24242, "Upload test", vec![ - Tag::parse(&["t", "upload"]).unwrap(), - Tag::parse(&["x", &sha256]).unwrap(), - Tag::parse(&["expiration", &(now + 300).to_string()]).unwrap(), - Tag::parse(&["server", "localhost:3000"]).unwrap(), + Tag::parse(["t", "upload"]).unwrap(), + Tag::parse(["x", &sha256]).unwrap(), + Tag::parse(["expiration", &(now + 300).to_string()]).unwrap(), + Tag::parse(["server", "localhost:3000"]).unwrap(), ], ); let resp = upload_with_auth(&client, &auth, &sha256, &jpeg).await; @@ -489,14 +489,13 @@ async fn test_ws_valid_imeta() { let channel_name = format!("ws-imeta-test-{}", channel_uuid); let create_event = EventBuilder::new( Kind::from(9007), - "", + "").tags( vec![ - Tag::parse(&["h", &channel_uuid.to_string()]).unwrap(), - Tag::parse(&["name", &channel_name]).unwrap(), - Tag::parse(&["channel_type", "stream"]).unwrap(), - Tag::parse(&["visibility", "open"]).unwrap(), - ], - ) + Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), + Tag::parse(["name", &channel_name]).unwrap(), + Tag::parse(["channel_type", "stream"]).unwrap(), + Tag::parse(["visibility", "open"]).unwrap(), + ]) .sign_with_keys(&keys) .unwrap(); let create_resp = http @@ -525,10 +524,10 @@ async fn test_ws_valid_imeta() { // Send event with valid imeta let event = EventBuilder::new( Kind::from(9), - "image via ws", + "image via ws").tags( vec![ - Tag::parse(&["h", &channel_id]).unwrap(), - Tag::parse(&[ + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse([ "imeta", &format!("url http://localhost:3000/media/{sha256}.jpg"), "m image/jpeg", @@ -536,8 +535,7 @@ async fn test_ws_valid_imeta() { "size 347", ]) .unwrap(), - ], - ) + ]) .sign_with_keys(&keys) .unwrap(); @@ -565,14 +563,13 @@ async fn test_ws_invalid_imeta_external_url() { let channel_name = format!("ws-imeta-bad-{}", channel_uuid); let create_event = EventBuilder::new( Kind::from(9007), - "", + "").tags( vec![ - Tag::parse(&["h", &channel_uuid.to_string()]).unwrap(), - Tag::parse(&["name", &channel_name]).unwrap(), - Tag::parse(&["channel_type", "stream"]).unwrap(), - Tag::parse(&["visibility", "open"]).unwrap(), - ], - ) + Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), + Tag::parse(["name", &channel_name]).unwrap(), + Tag::parse(["channel_type", "stream"]).unwrap(), + Tag::parse(["visibility", "open"]).unwrap(), + ]) .sign_with_keys(&keys) .unwrap(); let create_resp = http @@ -593,10 +590,10 @@ async fn test_ws_invalid_imeta_external_url() { let event = EventBuilder::new( Kind::from(9), - "bad imeta", + "bad imeta").tags( vec![ - Tag::parse(&["h", &channel_id]).unwrap(), - Tag::parse(&[ + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse([ "imeta", &format!("url https://evil.com/media/{sha}.jpg"), "m image/jpeg", @@ -604,8 +601,7 @@ async fn test_ws_invalid_imeta_external_url() { "size 347", ]) .unwrap(), - ], - ) + ]) .sign_with_keys(&keys) .unwrap(); @@ -637,14 +633,13 @@ async fn test_ws_invalid_imeta_missing_fields() { let channel_name = format!("ws-imeta-miss-{}", channel_uuid); let create_event = EventBuilder::new( Kind::from(9007), - "", + "").tags( vec![ - Tag::parse(&["h", &channel_uuid.to_string()]).unwrap(), - Tag::parse(&["name", &channel_name]).unwrap(), - Tag::parse(&["channel_type", "stream"]).unwrap(), - Tag::parse(&["visibility", "open"]).unwrap(), - ], - ) + Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), + Tag::parse(["name", &channel_name]).unwrap(), + Tag::parse(["channel_type", "stream"]).unwrap(), + Tag::parse(["visibility", "open"]).unwrap(), + ]) .sign_with_keys(&keys) .unwrap(); let create_resp = http @@ -666,16 +661,15 @@ async fn test_ws_invalid_imeta_missing_fields() { // Only url, missing m/x/size let event = EventBuilder::new( Kind::from(9), - "incomplete imeta", + "incomplete imeta").tags( vec![ - Tag::parse(&["h", &channel_id]).unwrap(), - Tag::parse(&[ + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse([ "imeta", &format!("url http://localhost:3000/media/{sha}.jpg"), ]) .unwrap(), - ], - ) + ]) .sign_with_keys(&keys) .unwrap(); diff --git a/crates/sprout-test-client/tests/e2e_media_video.rs b/crates/sprout-test-client/tests/e2e_media_video.rs index 28d6c9eaf..db915fd6c 100644 --- a/crates/sprout-test-client/tests/e2e_media_video.rs +++ b/crates/sprout-test-client/tests/e2e_media_video.rs @@ -34,11 +34,11 @@ fn sign_blossom_auth(keys: &Keys, sha256: &str) -> nostr::Event { let now = Timestamp::now().as_u64(); let exp_str = (now + 300).to_string(); let tags = vec![ - Tag::parse(&["t", "upload"]).expect("t tag"), - Tag::parse(&["x", sha256]).expect("x tag"), - Tag::parse(&["expiration", &exp_str]).expect("expiration tag"), + Tag::parse(["t", "upload"]).expect("t tag"), + Tag::parse(["x", sha256]).expect("x tag"), + Tag::parse(["expiration", &exp_str]).expect("expiration tag"), ]; - EventBuilder::new(Kind::from(24242), "Upload test", tags) + EventBuilder::new(Kind::from(24242), "Upload test").tags( tags) .sign_with_keys(keys) .expect("sign blossom auth") } @@ -483,14 +483,13 @@ async fn test_video_poster_imeta_accepted_via_ws() { let channel_id = channel_uuid.to_string(); let create_event = EventBuilder::new( Kind::from(9007), - "", + "").tags( vec![ - Tag::parse(&["h", &channel_id]).unwrap(), - Tag::parse(&["name", &format!("video-poster-test-{channel_id}")]).unwrap(), - Tag::parse(&["channel_type", "stream"]).unwrap(), - Tag::parse(&["visibility", "open"]).unwrap(), - ], - ) + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse(["name", &format!("video-poster-test-{channel_id}")]).unwrap(), + Tag::parse(["channel_type", "stream"]).unwrap(), + Tag::parse(["visibility", "open"]).unwrap(), + ]) .sign_with_keys(&keys) .unwrap(); let resp = client @@ -542,10 +541,10 @@ async fn test_video_poster_imeta_accepted_via_ws() { let base = relay_http_url(); let event = EventBuilder::new( Kind::from(9), - format!("![video]({base}/media/{video_sha}.mp4)"), + format!("![video]({base}/media/{video_sha}.mp4)")).tags( vec![ - Tag::parse(&["h", &channel_id]).unwrap(), - Tag::parse(&[ + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse([ "imeta", &format!("url {base}/media/{video_sha}.mp4"), "m video/mp4", @@ -554,8 +553,7 @@ async fn test_video_poster_imeta_accepted_via_ws() { &format!("image {base}/media/{poster_sha}.jpg"), ]) .unwrap(), - ], - ) + ]) .sign_with_keys(&keys) .unwrap(); @@ -585,14 +583,13 @@ async fn test_video_poster_imeta_rejects_video_as_poster() { let channel_id = channel_uuid.to_string(); let create_event = EventBuilder::new( Kind::from(9007), - "", + "").tags( vec![ - Tag::parse(&["h", &channel_id]).unwrap(), - Tag::parse(&["name", &format!("poster-reject-test-{channel_id}")]).unwrap(), - Tag::parse(&["channel_type", "stream"]).unwrap(), - Tag::parse(&["visibility", "open"]).unwrap(), - ], - ) + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse(["name", &format!("poster-reject-test-{channel_id}")]).unwrap(), + Tag::parse(["channel_type", "stream"]).unwrap(), + Tag::parse(["visibility", "open"]).unwrap(), + ]) .sign_with_keys(&keys) .unwrap(); let resp = client @@ -630,10 +627,10 @@ async fn test_video_poster_imeta_rejects_video_as_poster() { let base = relay_http_url(); let event = EventBuilder::new( Kind::from(9), - "bad poster", + "bad poster").tags( vec![ - Tag::parse(&["h", &channel_id]).unwrap(), - Tag::parse(&[ + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse([ "imeta", &format!("url {base}/media/{video_sha}.mp4"), "m video/mp4", @@ -643,8 +640,7 @@ async fn test_video_poster_imeta_rejects_video_as_poster() { &format!("image {base}/media/{video_sha}.mp4"), ]) .unwrap(), - ], - ) + ]) .sign_with_keys(&keys) .unwrap(); diff --git a/crates/sprout-test-client/tests/e2e_nostr_interop.rs b/crates/sprout-test-client/tests/e2e_nostr_interop.rs index 21d2161c9..19ed75659 100644 --- a/crates/sprout-test-client/tests/e2e_nostr_interop.rs +++ b/crates/sprout-test-client/tests/e2e_nostr_interop.rs @@ -51,14 +51,13 @@ async fn create_test_channel(keys: &Keys) -> String { let event = EventBuilder::new( Kind::Custom(9007), - "", + "").tags( vec![ - Tag::parse(&["h", &channel_uuid.to_string()]).unwrap(), - Tag::parse(&["name", &channel_name]).unwrap(), - Tag::parse(&["channel_type", "stream"]).unwrap(), - Tag::parse(&["visibility", "open"]).unwrap(), - ], - ) + Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), + Tag::parse(["name", &channel_name]).unwrap(), + Tag::parse(["channel_type", "stream"]).unwrap(), + Tag::parse(["visibility", "open"]).unwrap(), + ]) .sign_with_keys(keys) .unwrap(); @@ -91,9 +90,8 @@ async fn send_rest_message(keys: &Keys, channel_id: &str, content: &str) -> Stri let pubkey_hex = keys.public_key().to_hex(); let event = EventBuilder::new( Kind::Custom(9), - content, - vec![Tag::parse(&["h", channel_id]).unwrap()], - ) + content).tags( + vec![Tag::parse(["h", channel_id]).unwrap()]) .sign_with_keys(keys) .unwrap(); let resp = client @@ -168,7 +166,7 @@ async fn test_nip50_search_returns_results_and_eose() { let filter = Filter::new() .kind(Kind::Custom(9)) .search(&unique_token) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); client .subscribe(&sid, vec![filter]) @@ -238,12 +236,12 @@ async fn test_nip50_search_mixed_filters_rejected() { let filter_search = Filter::new() .kind(Kind::Custom(9)) .search("hello") - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); // Filter 2: no search let filter_plain = Filter::new() .kind(Kind::Custom(9)) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); client .subscribe(&sid, vec![filter_search, filter_plain]) @@ -339,11 +337,11 @@ async fn test_nip10_thread_reply_creates_metadata() { .expect("connect"); // Build reply event with NIP-10 e-tag. - let h_tag = Tag::parse(&["h", &channel]).expect("h tag"); - let e_reply_tag = Tag::parse(&["e", &root_event_id, "", "reply"]).expect("e reply tag"); + let h_tag = Tag::parse(["h", &channel]).expect("h tag"); + let e_reply_tag = Tag::parse(["e", &root_event_id, "", "reply"]).expect("e reply tag"); let reply_content = format!("reply to root {}", uuid::Uuid::new_v4()); - let reply_event = EventBuilder::new(Kind::Custom(9), &reply_content, [h_tag, e_reply_tag]) + let reply_event = EventBuilder::new(Kind::Custom(9), &reply_content).tags( [h_tag, e_reply_tag]) .sign_with_keys(&keys) .expect("sign reply"); @@ -397,10 +395,10 @@ async fn test_nip10_unknown_parent_rejected() { // Use a random 32-byte hex as a nonexistent parent ID. let fake_parent_id = hex::encode([0xdeu8; 32]); - let h_tag = Tag::parse(&["h", &channel]).expect("h tag"); - let e_reply_tag = Tag::parse(&["e", &fake_parent_id, "", "reply"]).expect("e reply tag"); + let h_tag = Tag::parse(["h", &channel]).expect("h tag"); + let e_reply_tag = Tag::parse(["e", &fake_parent_id, "", "reply"]).expect("e reply tag"); - let event = EventBuilder::new(Kind::Custom(9), "orphan reply", [h_tag, e_reply_tag]) + let event = EventBuilder::new(Kind::Custom(9), "orphan reply").tags( [h_tag, e_reply_tag]) .sign_with_keys(&keys) .expect("sign event"); @@ -439,16 +437,15 @@ async fn test_nip10_root_mismatch_rejected() { .await .expect("connect"); - let h_tag = Tag::parse(&["h", &channel]).expect("h tag"); + let h_tag = Tag::parse(["h", &channel]).expect("h tag"); // wrong_root as "root" marker, real_parent as "reply" marker — mismatch. - let e_root_tag = Tag::parse(&["e", &wrong_root_id, "", "root"]).expect("e root tag"); - let e_reply_tag = Tag::parse(&["e", &real_parent_id, "", "reply"]).expect("e reply tag"); + let e_root_tag = Tag::parse(["e", &wrong_root_id, "", "root"]).expect("e root tag"); + let e_reply_tag = Tag::parse(["e", &real_parent_id, "", "reply"]).expect("e reply tag"); let event = EventBuilder::new( Kind::Custom(9), - "reply with wrong root", - [h_tag, e_root_tag, e_reply_tag], - ) + "reply with wrong root").tags( + [h_tag, e_root_tag, e_reply_tag]) .sign_with_keys(&keys) .expect("sign event"); @@ -487,9 +484,9 @@ async fn test_nip17_gift_wrap_accepted() { // Sign with a different ephemeral key — not the auth key. let ephemeral_keys = Keys::generate(); - let p_tag = Tag::parse(&["p", &recipient_keys.public_key().to_hex()]).expect("p tag"); + let p_tag = Tag::parse(["p", &recipient_keys.public_key().to_hex()]).expect("p tag"); - let gift_wrap = EventBuilder::new(Kind::Custom(1059), "encrypted-content", [p_tag]) + let gift_wrap = EventBuilder::new(Kind::Custom(1059), "encrypted-content").tags( [p_tag]) .sign_with_keys(&ephemeral_keys) .expect("sign gift wrap"); @@ -599,10 +596,10 @@ async fn test_nip17_gift_wrap_recipient_receives() { .expect("client A connect"); let ephemeral_keys = Keys::generate(); - let p_tag = Tag::parse(&["p", &b_pubkey_hex]).expect("p tag"); + let p_tag = Tag::parse(["p", &b_pubkey_hex]).expect("p tag"); let unique_content = format!("gift-wrap-{}", uuid::Uuid::new_v4()); - let gift_wrap = EventBuilder::new(Kind::Custom(1059), &unique_content, [p_tag]) + let gift_wrap = EventBuilder::new(Kind::Custom(1059), &unique_content).tags( [p_tag]) .sign_with_keys(&ephemeral_keys) .expect("sign gift wrap"); @@ -797,10 +794,10 @@ async fn test_nip10_thread_reply_not_in_top_level() { .expect("connect"); let reply_content = format!("reply-hidden-{}", uuid::Uuid::new_v4()); - let h_tag = Tag::parse(&["h", &channel]).expect("h tag"); - let e_reply_tag = Tag::parse(&["e", &root_event_id, "", "reply"]).expect("e reply tag"); + let h_tag = Tag::parse(["h", &channel]).expect("h tag"); + let e_reply_tag = Tag::parse(["e", &root_event_id, "", "reply"]).expect("e reply tag"); - let reply_event = EventBuilder::new(Kind::Custom(9), &reply_content, [h_tag, e_reply_tag]) + let reply_event = EventBuilder::new(Kind::Custom(9), &reply_content).tags( [h_tag, e_reply_tag]) .sign_with_keys(&keys) .expect("sign reply"); @@ -864,8 +861,8 @@ async fn test_nip17_gift_wrap_not_searchable() { // 1. Send kind:1059 gift wrap. let ephemeral_keys = Keys::generate(); - let p_tag = Tag::parse(&["p", &keys_b.public_key().to_hex()]).expect("p tag"); - let gift_wrap = EventBuilder::new(Kind::Custom(1059), &unique_token, [p_tag]) + let p_tag = Tag::parse(["p", &keys_b.public_key().to_hex()]).expect("p tag"); + let gift_wrap = EventBuilder::new(Kind::Custom(1059), &unique_token).tags( [p_tag]) .sign_with_keys(&ephemeral_keys) .expect("sign gift wrap"); let ok = client.send_event(gift_wrap).await.expect("send gift wrap"); @@ -964,7 +961,7 @@ async fn test_nip50_search_relevance_order() { let filter = Filter::new() .kind(Kind::Custom(9)) .search(&query) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); client .subscribe(&sid, vec![filter]) @@ -1020,13 +1017,13 @@ async fn test_historical_req_dedup_preserves_or_semantics() { // Filter A: restricts to wrong author — will not match our message. let filter_a = Filter::new() .kind(Kind::Custom(9)) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]) + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]) .author(wrong_author.public_key()); // Filter B: no author restriction — will match our message. let filter_b = Filter::new() .kind(Kind::Custom(9)) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); client .subscribe(&sid, vec![filter_a, filter_b]) @@ -1072,7 +1069,7 @@ async fn test_empty_kinds_returns_zero_events() { // kinds:[] = match nothing per NIP-01. let filter = Filter::new() .kinds(vec![] as Vec) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); client .subscribe(&sid, vec![filter]) diff --git a/crates/sprout-test-client/tests/e2e_relay.rs b/crates/sprout-test-client/tests/e2e_relay.rs index 30f77fe21..6ee05b2b4 100644 --- a/crates/sprout-test-client/tests/e2e_relay.rs +++ b/crates/sprout-test-client/tests/e2e_relay.rs @@ -48,14 +48,13 @@ async fn create_test_channel(keys: &Keys) -> String { let event = EventBuilder::new( Kind::Custom(9007), - "", + "").tags( vec![ - Tag::parse(&["h", &channel_uuid.to_string()]).unwrap(), - Tag::parse(&["name", &channel_name]).unwrap(), - Tag::parse(&["channel_type", "stream"]).unwrap(), - Tag::parse(&["visibility", "open"]).unwrap(), - ], - ) + Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), + Tag::parse(["name", &channel_name]).unwrap(), + Tag::parse(["channel_type", "stream"]).unwrap(), + Tag::parse(["visibility", "open"]).unwrap(), + ]) .sign_with_keys(keys) .unwrap(); @@ -112,7 +111,7 @@ async fn test_send_event_and_receive_via_subscription() { let sid = sub_id("send-recv"); let filter = Filter::new() .kind(Kind::Custom(kind)) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); client_a .subscribe(&sid, vec![filter]) @@ -171,7 +170,7 @@ async fn test_subscription_filters_by_kind() { let sid = sub_id("filter-kind"); let filter = Filter::new() .kind(Kind::Custom(target_kind)) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); client .subscribe(&sid, vec![filter]) @@ -241,7 +240,7 @@ async fn test_close_subscription_stops_delivery() { let sid = sub_id("close-sub"); let filter = Filter::new() .kind(Kind::Custom(kind)) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); client .subscribe(&sid, vec![filter]) @@ -334,7 +333,7 @@ async fn test_multiple_concurrent_clients() { let filter = Filter::new() .kind(Kind::Custom(kind)) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); for (i, client) in clients.iter_mut().enumerate() { let sid = format!("multi-{i}"); @@ -397,7 +396,7 @@ async fn test_stored_events_returned_before_eose() { let sid = sub_id("stored"); let filter = Filter::new() .kind(Kind::Custom(kind)) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); client .subscribe(&sid, vec![filter]) @@ -444,7 +443,7 @@ async fn test_ephemeral_event_not_stored() { let sid = sub_id("ephemeral"); let filter = Filter::new() .kind(Kind::Custom(ephemeral_kind)) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]); client .subscribe(&sid, vec![filter]) @@ -656,7 +655,7 @@ async fn test_eose_sent_for_empty_subscription() { let sid = sub_id("empty-eose"); let filter = Filter::new() .kind(Kind::Custom(kind)) - .custom_tag(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]) + .custom_tags(SingleLetterTag::lowercase(Alphabet::H), [channel.as_str()]) .since(nostr::Timestamp::now()); client @@ -718,7 +717,7 @@ async fn test_kind0_nip05_sync() { }) .to_string(); - let event = nostr::EventBuilder::new(Kind::Custom(0), kind0_content, []) + let event = nostr::EventBuilder::new(Kind::Custom(0), kind0_content).tags( []) .sign_with_keys(&keys) .expect("sign kind:0"); @@ -777,7 +776,7 @@ async fn test_kind0_nip05_sync() { }) .to_string(); - let event2 = nostr::EventBuilder::new(Kind::Custom(0), off_domain_content, []) + let event2 = nostr::EventBuilder::new(Kind::Custom(0), off_domain_content).tags( []) .sign_with_keys(&keys) .expect("sign kind:0 off-domain"); @@ -845,9 +844,9 @@ async fn test_nip29_put_user_default_policy_allows() { .expect("connect as channel_owner"); // Build kind 9000 PUT_USER event: h = channel_id, p = agent pubkey. - let h_tag = nostr::Tag::parse(&["h", &channel_id]).expect("h tag"); - let p_tag = nostr::Tag::parse(&["p", &agent_pubkey_hex]).expect("p tag"); - let event = nostr::EventBuilder::new(Kind::Custom(9000), "", [h_tag, p_tag]) + let h_tag = nostr::Tag::parse(["h", &channel_id]).expect("h tag"); + let p_tag = nostr::Tag::parse(["p", &agent_pubkey_hex]).expect("p tag"); + let event = nostr::EventBuilder::new(Kind::Custom(9000), "").tags( [h_tag, p_tag]) .sign_with_keys(&channel_owner_keys) .expect("sign kind 9000"); @@ -895,9 +894,9 @@ async fn test_nip29_put_user_nobody_blocks() { .expect("connect as channel_owner"); // Build kind 9000 PUT_USER event targeting the agent. - let h_tag = nostr::Tag::parse(&["h", &channel_id]).expect("h tag"); - let p_tag = nostr::Tag::parse(&["p", &agent_pubkey_hex]).expect("p tag"); - let event = nostr::EventBuilder::new(Kind::Custom(9000), "", [h_tag, p_tag]) + let h_tag = nostr::Tag::parse(["h", &channel_id]).expect("h tag"); + let p_tag = nostr::Tag::parse(["p", &agent_pubkey_hex]).expect("p tag"); + let event = nostr::EventBuilder::new(Kind::Custom(9000), "").tags( [h_tag, p_tag]) .sign_with_keys(&channel_owner_keys) .expect("sign kind 9000"); @@ -948,9 +947,9 @@ async fn test_nip29_put_user_self_add_bypasses_policy() { .expect("connect as agent"); // Build kind 9000 PUT_USER event where agent targets ITSELF. - let h_tag = nostr::Tag::parse(&["h", &channel_id]).expect("h tag"); - let p_tag = nostr::Tag::parse(&["p", &agent_pubkey_hex]).expect("p tag"); - let event = nostr::EventBuilder::new(Kind::Custom(9000), "", [h_tag, p_tag]) + let h_tag = nostr::Tag::parse(["h", &channel_id]).expect("h tag"); + let p_tag = nostr::Tag::parse(["p", &agent_pubkey_hex]).expect("p tag"); + let event = nostr::EventBuilder::new(Kind::Custom(9000), "").tags( [h_tag, p_tag]) .sign_with_keys(&agent_keys) .expect("sign kind 9000"); @@ -998,9 +997,9 @@ async fn test_nip29_put_user_owner_only_blocks() { .expect("connect as channel_owner"); // Build kind 9000 PUT_USER event targeting the agent. - let h_tag = nostr::Tag::parse(&["h", &channel_id]).expect("h tag"); - let p_tag = nostr::Tag::parse(&["p", &agent_pubkey_hex]).expect("p tag"); - let event = nostr::EventBuilder::new(Kind::Custom(9000), "", [h_tag, p_tag]) + let h_tag = nostr::Tag::parse(["h", &channel_id]).expect("h tag"); + let p_tag = nostr::Tag::parse(["p", &agent_pubkey_hex]).expect("p tag"); + let event = nostr::EventBuilder::new(Kind::Custom(9000), "").tags( [h_tag, p_tag]) .sign_with_keys(&channel_owner_keys) .expect("sign kind 9000"); @@ -1155,9 +1154,9 @@ async fn test_nip29_standard_client_flow() { }; // 5. Send a kind:7 reaction targeting the message. - let h_tag = Tag::parse(&["h", &channel_id]).expect("h tag"); - let e_tag = Tag::parse(&["e", &message_event_id]).expect("e tag"); - let reaction_event = EventBuilder::new(Kind::Custom(7), "+", [h_tag, e_tag]) + let h_tag = Tag::parse(["h", &channel_id]).expect("h tag"); + let e_tag = Tag::parse(["e", &message_event_id]).expect("e tag"); + let reaction_event = EventBuilder::new(Kind::Custom(7), "+").tags( [h_tag, e_tag]) .sign_with_keys(&keys) .expect("sign reaction"); let ok = client @@ -1171,9 +1170,9 @@ async fn test_nip29_standard_client_flow() { ); // 6. Send a kind:5 deletion targeting the message. - let h_tag2 = Tag::parse(&["h", &channel_id]).expect("h tag"); - let e_tag2 = Tag::parse(&["e", &message_event_id]).expect("e tag"); - let delete_event = EventBuilder::new(Kind::Custom(5), "test delete", [h_tag2, e_tag2]) + let h_tag2 = Tag::parse(["h", &channel_id]).expect("h tag"); + let e_tag2 = Tag::parse(["e", &message_event_id]).expect("e tag"); + let delete_event = EventBuilder::new(Kind::Custom(5), "test delete").tags( [h_tag2, e_tag2]) .sign_with_keys(&keys) .expect("sign deletion"); let ok = client @@ -1187,7 +1186,7 @@ async fn test_nip29_standard_client_flow() { ); // 7. Verify kind:9 without h tag is rejected. - let no_h_event = EventBuilder::new(Kind::Custom(9), "no h tag", []) + let no_h_event = EventBuilder::new(Kind::Custom(9), "no h tag").tags( []) .sign_with_keys(&keys) .expect("sign no-h event"); let ok = client @@ -1212,9 +1211,9 @@ async fn test_membership_notification_kind_rejected() { .await .expect("connect"); - let p_tag = Tag::parse(&["p", &keys.public_key().to_hex()]).expect("p tag"); - let h_tag = Tag::parse(&["h", &channel_id]).expect("h tag"); - let event = EventBuilder::new(Kind::Custom(44100), "", [p_tag, h_tag]) + let p_tag = Tag::parse(["p", &keys.public_key().to_hex()]).expect("p tag"); + let h_tag = Tag::parse(["h", &channel_id]).expect("h tag"); + let event = EventBuilder::new(Kind::Custom(44100), "").tags( [p_tag, h_tag]) .sign_with_keys(&keys) .expect("sign kind:44100"); @@ -1280,12 +1279,11 @@ async fn test_membership_notification_emitted_on_add() { let http_client = reqwest::Client::new(); let add_event = EventBuilder::new( Kind::Custom(9000), - "", + "").tags( vec![ - Tag::parse(&["h", &channel_id]).unwrap(), - Tag::parse(&["p", &agent_pubkey_hex]).unwrap(), - ], - ) + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse(["p", &agent_pubkey_hex]).unwrap(), + ]) .sign_with_keys(&owner_keys) .unwrap(); let resp = http_client @@ -1559,12 +1557,11 @@ async fn test_membership_notification_emitted_on_remove() { // Add agent to the channel via signed kind:9000 event. let add_event = EventBuilder::new( Kind::Custom(9000), - "", + "").tags( vec![ - Tag::parse(&["h", &channel_id]).unwrap(), - Tag::parse(&["p", &agent_pubkey_hex]).unwrap(), - ], - ) + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse(["p", &agent_pubkey_hex]).unwrap(), + ]) .sign_with_keys(&owner_keys) .unwrap(); let resp = http_client @@ -1601,12 +1598,11 @@ async fn test_membership_notification_emitted_on_remove() { // Remove agent from the channel via signed kind:9001 event. let remove_event = EventBuilder::new( Kind::Custom(9001), - "", + "").tags( vec![ - Tag::parse(&["h", &channel_id]).unwrap(), - Tag::parse(&["p", &agent_pubkey_hex]).unwrap(), - ], - ) + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse(["p", &agent_pubkey_hex]).unwrap(), + ]) .sign_with_keys(&owner_keys) .unwrap(); let resp = http_client diff --git a/crates/sprout-test-client/tests/e2e_rest_api.rs b/crates/sprout-test-client/tests/e2e_rest_api.rs index ef794fcfd..0279b286b 100644 --- a/crates/sprout-test-client/tests/e2e_rest_api.rs +++ b/crates/sprout-test-client/tests/e2e_rest_api.rs @@ -108,15 +108,15 @@ async fn create_channel_via_event( let pubkey_hex = keys.public_key().to_hex(); let channel_uuid = uuid::Uuid::new_v4(); let mut tags = vec![ - Tag::parse(&["h", &channel_uuid.to_string()]).unwrap(), - Tag::parse(&["name", name]).unwrap(), - Tag::parse(&["channel_type", channel_type]).unwrap(), - Tag::parse(&["visibility", visibility]).unwrap(), + Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), + Tag::parse(["name", name]).unwrap(), + Tag::parse(["channel_type", channel_type]).unwrap(), + Tag::parse(["visibility", visibility]).unwrap(), ]; if let Some(desc) = description { - tags.push(Tag::parse(&["about", desc]).unwrap()); + tags.push(Tag::parse(["about", desc]).unwrap()); } - let event = EventBuilder::new(Kind::Custom(9007), "", tags) + let event = EventBuilder::new(Kind::Custom(9007), "").tags( tags) .sign_with_keys(keys) .unwrap(); let resp = client @@ -160,7 +160,7 @@ async fn set_profile_via_event( content_obj.insert("nip05".to_string(), serde_json::json!(n)); } let content = serde_json::to_string(&serde_json::Value::Object(content_obj)).unwrap(); - let event = EventBuilder::new(Kind::Custom(0), &content, vec![]) + let event = EventBuilder::new(Kind::Custom(0), &content).tags( vec![]) .sign_with_keys(keys) .unwrap(); let resp = client @@ -395,8 +395,8 @@ async fn test_search_returns_indexed_event() { .await .expect("WebSocket connect failed"); - let h_tag = Tag::parse(&["h", &channel_id]).expect("tag parse failed"); - let event = nostr::EventBuilder::new(Kind::Custom(9), &content, [h_tag]) + let h_tag = Tag::parse(["h", &channel_id]).expect("tag parse failed"); + let event = nostr::EventBuilder::new(Kind::Custom(9), &content).tags( [h_tag]) .sign_with_keys(&keys) .expect("event sign failed"); @@ -484,7 +484,7 @@ async fn test_presence_set_and_query() { .await .expect("WebSocket connect failed"); - let presence_event = nostr::EventBuilder::new(Kind::Custom(20001), "online", []) + let presence_event = nostr::EventBuilder::new(Kind::Custom(20001), "online").tags( []) .sign_with_keys(&keys) .expect("event sign failed"); @@ -509,7 +509,7 @@ async fn test_presence_set_and_query() { "expected 'online' after sending presence event" ); - let offline_event = nostr::EventBuilder::new(Kind::Custom(20001), "offline", []) + let offline_event = nostr::EventBuilder::new(Kind::Custom(20001), "offline").tags( []) .sign_with_keys(&keys) .expect("event sign failed"); ws_client.send_event(offline_event).await.ok(); @@ -888,8 +888,8 @@ async fn test_feed_returns_activity() { .await .expect("WebSocket connect failed"); - let h_tag = Tag::parse(&["h", &channel_id]).expect("tag parse failed"); - let event = nostr::EventBuilder::new(Kind::Custom(9), &content, [h_tag]) + let h_tag = Tag::parse(["h", &channel_id]).expect("tag parse failed"); + let event = nostr::EventBuilder::new(Kind::Custom(9), &content).tags( [h_tag]) .sign_with_keys(&keys) .expect("event sign failed"); @@ -1532,7 +1532,7 @@ async fn test_get_event_returns_text_note() { let pubkey_hex = keys.public_key().to_hex(); let content = format!("e2e-note-{}", uuid::Uuid::new_v4().simple()); - let event = EventBuilder::new(Kind::Custom(1), &content, vec![]) + let event = EventBuilder::new(Kind::Custom(1), &content).tags( vec![]) .sign_with_keys(&keys) .unwrap(); let event_id = event.id.to_hex(); @@ -1596,7 +1596,7 @@ async fn test_get_user_notes_returns_paginated_notes() { // (created_at, event_id) correctly handles same-second events without skipping. for i in 0..3u8 { let content = format!("e2e-paginated-note-{}-{}", i, uuid::Uuid::new_v4().simple()); - let event = EventBuilder::new(Kind::Custom(1), &content, vec![]) + let event = EventBuilder::new(Kind::Custom(1), &content).tags( vec![]) .sign_with_keys(&keys) .unwrap(); let resp = client @@ -1693,10 +1693,10 @@ async fn test_get_contact_list_returns_latest() { let contact1 = Keys::generate().public_key().to_hex(); let contact2 = Keys::generate().public_key().to_hex(); let tags_v1 = vec![ - Tag::parse(&["p", &contact1]).unwrap(), - Tag::parse(&["p", &contact2]).unwrap(), + Tag::parse(["p", &contact1]).unwrap(), + Tag::parse(["p", &contact2]).unwrap(), ]; - let event_v1 = EventBuilder::new(Kind::Custom(3), "", tags_v1) + let event_v1 = EventBuilder::new(Kind::Custom(3), "").tags( tags_v1) .sign_with_keys(&keys) .unwrap(); @@ -1719,8 +1719,8 @@ async fn test_get_contact_list_returns_latest() { // ── Second contact list: 1 different contact ────────────────────────────── let contact3 = Keys::generate().public_key().to_hex(); - let tags_v2 = vec![Tag::parse(&["p", &contact3]).unwrap()]; - let event_v2 = EventBuilder::new(Kind::Custom(3), "", tags_v2) + let tags_v2 = vec![Tag::parse(["p", &contact3]).unwrap()]; + let event_v2 = EventBuilder::new(Kind::Custom(3), "").tags( tags_v2) .sign_with_keys(&keys) .unwrap(); @@ -1812,7 +1812,7 @@ async fn test_get_user_notes_invalid_before_returns_400() { // Publish one note so the user has something to return. let content = format!("e2e-overflow-note-{}", uuid::Uuid::new_v4().simple()); - let event = EventBuilder::new(Kind::Custom(1), &content, vec![]) + let event = EventBuilder::new(Kind::Custom(1), &content).tags( vec![]) .sign_with_keys(&keys) .unwrap(); let resp = client diff --git a/crates/sprout-test-client/tests/e2e_tokens.rs b/crates/sprout-test-client/tests/e2e_tokens.rs index c06af8dfc..7516011b2 100644 --- a/crates/sprout-test-client/tests/e2e_tokens.rs +++ b/crates/sprout-test-client/tests/e2e_tokens.rs @@ -57,12 +57,12 @@ fn build_nip98_header(keys: &Keys, url: &str, method: &str, body: &[u8]) -> Stri let payload_hash = hex::encode(Sha256::digest(body)); let tags = vec![ - Tag::parse(&["u", url]).expect("u tag"), - Tag::parse(&["method", method]).expect("method tag"), - Tag::parse(&["payload", &payload_hash]).expect("payload tag"), + Tag::parse(["u", url]).expect("u tag"), + Tag::parse(["method", method]).expect("method tag"), + Tag::parse(["payload", &payload_hash]).expect("payload tag"), ]; - let event = EventBuilder::new(Kind::HttpAuth, "", tags) + let event = EventBuilder::new(Kind::HttpAuth, "").tags( tags) .sign_with_keys(keys) .expect("signing must succeed"); @@ -78,11 +78,11 @@ fn build_nip98_header(keys: &Keys, url: &str, method: &str, body: &[u8]) -> Stri /// cryptographically bind the request body. fn build_nip98_header_no_payload(keys: &Keys, url: &str, method: &str) -> String { let tags = vec![ - Tag::parse(&["u", url]).expect("u tag"), - Tag::parse(&["method", method]).expect("method tag"), + Tag::parse(["u", url]).expect("u tag"), + Tag::parse(["method", method]).expect("method tag"), // Deliberately omit payload tag ]; - let event = EventBuilder::new(Kind::HttpAuth, "", tags) + let event = EventBuilder::new(Kind::HttpAuth, "").tags( tags) .sign_with_keys(keys) .expect("signing must succeed"); let json = event.as_json(); diff --git a/crates/sprout-test-client/tests/e2e_user_status.rs b/crates/sprout-test-client/tests/e2e_user_status.rs index 2a68f60e0..48e137966 100644 --- a/crates/sprout-test-client/tests/e2e_user_status.rs +++ b/crates/sprout-test-client/tests/e2e_user_status.rs @@ -40,9 +40,9 @@ fn build_user_status_event( content: &str, extra_tags: Vec, ) -> nostr::Event { - let mut tags = vec![Tag::parse(&["d", d_tag]).unwrap()]; + let mut tags = vec![Tag::parse(["d", d_tag]).unwrap()]; tags.extend(extra_tags); - EventBuilder::new(Kind::Custom(KIND_USER_STATUS), content, tags) + EventBuilder::new(Kind::Custom(KIND_USER_STATUS), content).tags( tags) .sign_with_keys(keys) .unwrap() } @@ -142,7 +142,7 @@ async fn test_user_status_nip33_replacement() { let filter = Filter::new() .kind(Kind::Custom(KIND_USER_STATUS)) .author(keys.public_key()) - .custom_tag(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); client .subscribe(&sid, vec![filter]) .await @@ -230,8 +230,8 @@ async fn test_user_status_stale_write_rejected() { // Publish the "newer" event first (with a future-ish timestamp) let newer = { - let tags = vec![Tag::parse(&["d", &d_tag]).unwrap()]; - EventBuilder::new(Kind::Custom(KIND_USER_STATUS), "Newer status", tags) + let tags = vec![Tag::parse(["d", &d_tag]).unwrap()]; + EventBuilder::new(Kind::Custom(KIND_USER_STATUS), "Newer status").tags( tags) .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() + 100)) .sign_with_keys(&keys) .unwrap() @@ -242,8 +242,8 @@ async fn test_user_status_stale_write_rejected() { // Now try to publish an "older" event with the same d-tag but earlier timestamp let older = { - let tags = vec![Tag::parse(&["d", &d_tag]).unwrap()]; - EventBuilder::new(Kind::Custom(KIND_USER_STATUS), "Older status", tags) + let tags = vec![Tag::parse(["d", &d_tag]).unwrap()]; + EventBuilder::new(Kind::Custom(KIND_USER_STATUS), "Older status").tags( tags) .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() - 100)) .sign_with_keys(&keys) .unwrap() @@ -257,7 +257,7 @@ async fn test_user_status_stale_write_rejected() { let filter = Filter::new() .kind(Kind::Custom(KIND_USER_STATUS)) .author(keys.public_key()) - .custom_tag(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); + .custom_tags(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); client .subscribe(&sid, vec![filter]) .await diff --git a/crates/sprout-test-client/tests/e2e_workflows.rs b/crates/sprout-test-client/tests/e2e_workflows.rs index 4bc408dbd..8bdfceb2a 100644 --- a/crates/sprout-test-client/tests/e2e_workflows.rs +++ b/crates/sprout-test-client/tests/e2e_workflows.rs @@ -349,8 +349,8 @@ steps: .await .expect("ws connect failed"); - let h_tag = Tag::parse(&["h", CHANNEL_GENERAL]).expect("tag parse failed"); - let event = nostr::EventBuilder::new(Kind::Custom(9), "trigger this workflow please", [h_tag]) + let h_tag = Tag::parse(["h", CHANNEL_GENERAL]).expect("tag parse failed"); + let event = nostr::EventBuilder::new(Kind::Custom(9), "trigger this workflow please").tags( [h_tag]) .sign_with_keys(&sender_keys) .expect("sign event"); @@ -443,12 +443,11 @@ steps: .expect("ws connect failed"); // ── Step 2: Send a message that does NOT match the filter ───────────────── - let h_tag = Tag::parse(&["h", CHANNEL_GENERAL]).expect("tag parse failed"); + let h_tag = Tag::parse(["h", CHANNEL_GENERAL]).expect("tag parse failed"); let non_matching = nostr::EventBuilder::new( Kind::Custom(9), - "this is a routine update, nothing urgent", - [h_tag.clone()], - ) + "this is a routine update, nothing urgent").tags( + [h_tag.clone()]) .sign_with_keys(&sender_keys) .expect("sign event"); @@ -476,7 +475,7 @@ steps: ); // ── Step 3: Send a message that DOES match the filter ───────────────────── - let matching = nostr::EventBuilder::new(Kind::Custom(9), "P1 alert: database is down", [h_tag]) + let matching = nostr::EventBuilder::new(Kind::Custom(9), "P1 alert: database is down").tags( [h_tag]) .sign_with_keys(&sender_keys) .expect("sign event"); diff --git a/crates/sprout-workflow/src/lib.rs b/crates/sprout-workflow/src/lib.rs index fa66bf8ad..f668fe4f4 100644 --- a/crates/sprout-workflow/src/lib.rs +++ b/crates/sprout-workflow/src/lib.rs @@ -1056,7 +1056,7 @@ steps: use nostr::{EventBuilder, Keys, Kind}; use uuid::Uuid; let keys = Keys::generate(); - let event = EventBuilder::new(Kind::Custom(9), "hello world", []) + let event = EventBuilder::new(Kind::Custom(9), "hello world").tags( []) .sign_with_keys(&keys) .expect("sign"); sprout_core::StoredEvent::new(event, Some(Uuid::new_v4())) @@ -1069,13 +1069,13 @@ steps: let keys = Keys::generate(); // Create a dummy target message ID (64-char hex). let target_keys = Keys::generate(); - let target_event = EventBuilder::new(Kind::Custom(9), "target msg", []) + let target_event = EventBuilder::new(Kind::Custom(9), "target msg").tags( []) .sign_with_keys(&target_keys) .expect("sign target"); let target_id_hex = target_event.id.to_hex(); // NIP-25: reaction references the target via an `e` tag. - let e_tag = Tag::parse(&["e", &target_id_hex]).expect("tag parse"); - let event = EventBuilder::new(Kind::Reaction, "👍", [e_tag]) + let e_tag = Tag::parse(["e", &target_id_hex]).expect("tag parse"); + let event = EventBuilder::new(Kind::Reaction, "👍").tags( [e_tag]) .sign_with_keys(&keys) .expect("sign"); ( @@ -1118,7 +1118,7 @@ steps: fn build_trigger_context_no_channel_id() { use nostr::{EventBuilder, Keys, Kind}; let keys = Keys::generate(); - let event = EventBuilder::new(Kind::Custom(9), "msg", []) + let event = EventBuilder::new(Kind::Custom(9), "msg").tags( []) .sign_with_keys(&keys) .expect("sign"); // channel_id = None (global/DM event) @@ -1169,12 +1169,11 @@ steps: let event = EventBuilder::new( Kind::Reaction, - "👍", + "👍").tags( [ - Tag::parse(&["e", &thread_root_id.to_hex()]).unwrap(), - Tag::parse(&["e", &direct_target_id.to_hex()]).unwrap(), - ], - ) + Tag::parse(["e", &thread_root_id.to_hex()]).unwrap(), + Tag::parse(["e", &direct_target_id.to_hex()]).unwrap(), + ]) .sign_with_keys(&keys) .expect("sign"); diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index b38a19b13..79a13ae31 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -403,16 +403,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "base58ck" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" -dependencies = [ - "bitcoin-internals", - "bitcoin_hashes", -] - [[package]] name = "base64" version = "0.21.7" @@ -463,49 +453,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bitcoin" -version = "0.32.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" -dependencies = [ - "base58ck", - "bech32", - "bitcoin-internals", - "bitcoin-io", - "bitcoin-units", - "bitcoin_hashes", - "hex-conservative", - "hex_lit", - "secp256k1", - "serde", -] - -[[package]] -name = "bitcoin-internals" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" -dependencies = [ - "serde", -] - [[package]] name = "bitcoin-io" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" -[[package]] -name = "bitcoin-units" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" -dependencies = [ - "bitcoin-internals", - "serde", -] - [[package]] name = "bitcoin_hashes" version = "0.14.1" @@ -1337,7 +1290,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1547,7 +1500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2208,12 +2161,6 @@ dependencies = [ "arrayvec", ] -[[package]] -name = "hex_lit" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" - [[package]] name = "hmac" version = "0.12.1" @@ -2343,7 +2290,6 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.6", ] [[package]] @@ -3029,7 +2975,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3062,18 +3008,6 @@ dependencies = [ "jni-sys 0.3.1", ] -[[package]] -name = "negentropy" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe" - -[[package]] -name = "negentropy" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a88da9dd148bbcdce323dd6ac47d369b4769d4a3b78c6c52389b9269f77932" - [[package]] name = "neteq" version = "0.8.3" @@ -3113,56 +3047,22 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "nostr" -version = "0.36.0" +version = "0.44.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ad56c1d9a59f4edc46b17bc64a217b38b99baefddc0080f85ad98a0855336d" +checksum = "08d8f0fe13526800300a36bf3b7c5f752e62e32ab81c74a8e5caa2865708625a" dependencies = [ - "aes", - "async-trait", "base64 0.22.1", "bech32", "bip39", - "bitcoin", - "cbc", - "chacha20 0.9.1", - "chacha20poly1305", - "getrandom 0.2.17", - "instant", - "js-sys", - "negentropy 0.3.1", - "negentropy 0.4.3", - "once_cell", - "reqwest 0.12.28", - "scrypt", - "serde", - "serde_json", - "unicode-normalization", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "nostr" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aad4b767bbed24ac5eb4465bfb83bc1210522eb99d67cf4e547ec2ec7e47786" -dependencies = [ - "async-trait", - "base64 0.22.1", - "bech32", - "bip39", - "bitcoin", + "bitcoin_hashes", "cbc", "chacha20 0.9.1", "chacha20poly1305", "getrandom 0.2.17", + "hex", "instant", - "negentropy 0.3.1", - "negentropy 0.4.3", - "once_cell", "scrypt", + "secp256k1", "serde", "serde_json", "unicode-normalization", @@ -4403,44 +4303,6 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 1.0.6", -] - [[package]] name = "reqwest" version = "0.13.3" @@ -4619,7 +4481,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4678,7 +4540,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4813,7 +4675,6 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes", "rand 0.8.5", "secp256k1-sys", "serde", @@ -4957,7 +4818,6 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -5210,7 +5070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5268,7 +5128,7 @@ dependencies = [ "chrono", "hex", "hmac 0.13.0", - "nostr 0.36.0", + "nostr", "percent-encoding", "rand 0.10.1", "serde", @@ -5298,12 +5158,11 @@ dependencies = [ "infer", "libc", "neteq", - "nostr 0.36.0", - "nostr 0.37.0", + "nostr", "opus", "png 0.18.1", "regex", - "reqwest 0.13.3", + "reqwest", "rodio", "rubato", "serde", @@ -5351,7 +5210,7 @@ dependencies = [ name = "sprout-sdk" version = "0.1.0" dependencies = [ - "nostr 0.36.0", + "nostr", "serde", "serde_json", "sprout-core", @@ -5764,7 +5623,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest 0.13.3", + "reqwest", "serde", "serde_json", "serde_repr", @@ -6025,7 +5884,7 @@ dependencies = [ "minisign-verify", "osakit", "percent-encoding", - "reqwest 0.13.3", + "reqwest", "rustls", "semver", "serde", @@ -6161,7 +6020,7 @@ dependencies = [ "serde_with", "swift-rs", "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", "url", "urlpattern", "uuid", @@ -6201,7 +6060,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6639,7 +6498,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6705,7 +6564,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7218,7 +7077,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 9cd699582..31dcd2554 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -47,8 +47,7 @@ opus = "0.3" neteq = { version = "0.8", default-features = false } serde = { version = "1", features = ["derive"] } serde_json = "1" -nostr = { version = "0.37", features = ["nip44"] } -nostr-compat = { package = "nostr", version = "0.36" } +nostr = { version = "0.44", features = ["nip44"] } zeroize = "1" reqwest = { version = "0.13", features = ["json", "query", "stream"] } url = "2" diff --git a/desktop/src-tauri/src/commands/agents.rs b/desktop/src-tauri/src/commands/agents.rs index 69b0e3736..d0ece9923 100644 --- a/desktop/src-tauri/src/commands/agents.rs +++ b/desktop/src-tauri/src/commands/agents.rs @@ -1,5 +1,5 @@ use nostr::{Keys, ToBech32}; -use nostr_compat; +use nostr; use tauri::{AppHandle, State}; use crate::{ @@ -278,9 +278,9 @@ pub async fn create_managed_agent( let auth_tag = { let owner_keys = state.keys.lock().map_err(|e| e.to_string())?; // Bridge nostr 0.37 → 0.36 (sprout-sdk) via hex round-trip. - let compat_owner = nostr_compat::Keys::parse(&owner_keys.secret_key().to_secret_hex()) + let compat_owner = nostr::Keys::parse(&owner_keys.secret_key().to_secret_hex()) .map_err(|e| format!("failed to bridge owner keys: {e}"))?; - let compat_agent = nostr_compat::PublicKey::from_hex(&agent_keys.public_key().to_hex()) + let compat_agent = nostr::PublicKey::from_hex(&agent_keys.public_key().to_hex()) .map_err(|e| format!("failed to bridge agent pubkey: {e}"))?; let tag = sprout_sdk::nip_oa::compute_auth_tag(&compat_owner, &compat_agent, "") .map_err(|e| format!("failed to compute NIP-OA auth tag: {e}"))?; diff --git a/desktop/src-tauri/src/commands/identity.rs b/desktop/src-tauri/src/commands/identity.rs index 4eec001db..8595954b4 100644 --- a/desktop/src-tauri/src/commands/identity.rs +++ b/desktop/src-tauri/src/commands/identity.rs @@ -1,5 +1,5 @@ use nostr::{nips::nip44, EventBuilder, JsonUtil, Keys, Kind, Tag, Timestamp, ToBech32}; -use nostr_compat::{ +use nostr::{ Event as CompatEvent, JsonUtil as CompatJsonUtil, Keys as CompatKeys, PublicKey as CompatPublicKey, }; diff --git a/desktop/src-tauri/src/commands/pairing.rs b/desktop/src-tauri/src/commands/pairing.rs index 2f730b2a1..bca30419b 100644 --- a/desktop/src-tauri/src/commands/pairing.rs +++ b/desktop/src-tauri/src/commands/pairing.rs @@ -363,21 +363,20 @@ where Err(_) => return Ok(()), }; - let relay_url_parsed: url::Url = relay_url - .parse() + let relay_url_parsed = nostr::RelayUrl::parse(relay_url) .map_err(|e| format!("invalid relay URL: {e}"))?; let auth_json = { let guard = session.lock().await; let s = guard.as_ref().ok_or("session gone during auth")?; let auth_event = s - .sign_event(nostr_compat::EventBuilder::auth( + .sign_event(nostr::EventBuilder::auth( challenge, relay_url_parsed, )) .map_err(|e| format!("sign auth event: {e}"))?; format!( "[\"AUTH\",{}]", - nostr_compat::JsonUtil::as_json(&auth_event) + nostr::JsonUtil::as_json(&auth_event) ) }; @@ -406,12 +405,12 @@ where } /// Serialize a nostr 0.36 Event to `["EVENT", ]` JSON string. -fn event_to_relay_json(event: &nostr_compat::Event) -> String { - format!("[\"EVENT\",{}]", nostr_compat::JsonUtil::as_json(event)) +fn event_to_relay_json(event: &nostr::Event) -> String { + format!("[\"EVENT\",{}]", nostr::JsonUtil::as_json(event)) } /// Parse a relay EVENT message into a nostr 0.36 Event (sprout-core compatible). -fn parse_relay_event(text: &str, sub_id: &str) -> Option { +fn parse_relay_event(text: &str, sub_id: &str) -> Option { let arr: serde_json::Value = serde_json::from_str(text).ok()?; let arr = arr.as_array()?; if arr.len() < 3 { diff --git a/desktop/src-tauri/src/relay.rs b/desktop/src-tauri/src/relay.rs index 5e349cef8..4ff6137ce 100644 --- a/desktop/src-tauri/src/relay.rs +++ b/desktop/src-tauri/src/relay.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use sha2::{Digest, Sha256}; // nostr 0.36 alias — required for cross-version bridging with sprout-sdk. -use nostr_compat; + use crate::app_state::AppState; @@ -215,7 +215,7 @@ fn build_profile_event( let builder = if let Some(tag_json) = auth_tag_json { // Bridge nostr 0.37 PublicKey → nostr 0.36 PublicKey via hex encoding. let agent_pubkey_hex = agent_keys.public_key().to_hex(); - let compat_pubkey = nostr_compat::PublicKey::from_hex(&agent_pubkey_hex) + let compat_pubkey = nostr::PublicKey::from_hex(&agent_pubkey_hex) .map_err(|e| format!("failed to convert agent pubkey for auth verification: {e}"))?; // Verify Schnorr signature before injecting into profile event. @@ -401,9 +401,9 @@ mod tests { /// `sprout_sdk::nip_oa::compute_auth_tag` expects nostr 0.36 types. /// The agent pubkey is bridged via hex encoding. fn make_valid_auth_tag(agent_keys: &nostr::Keys) -> String { - let owner_keys = nostr_compat::Keys::generate(); + let owner_keys = nostr::Keys::generate(); let agent_pubkey_hex = agent_keys.public_key().to_hex(); - let agent_compat_pubkey = nostr_compat::PublicKey::from_hex(&agent_pubkey_hex) + let agent_compat_pubkey = nostr::PublicKey::from_hex(&agent_pubkey_hex) .expect("valid hex pubkey should parse"); sprout_sdk::nip_oa::compute_auth_tag(&owner_keys, &agent_compat_pubkey, "") .expect("compute_auth_tag should not fail with distinct keys") diff --git a/examples/countdown-bot/src/main.rs b/examples/countdown-bot/src/main.rs index 4b077225b..1507f87cf 100644 --- a/examples/countdown-bot/src/main.rs +++ b/examples/countdown-bot/src/main.rs @@ -17,7 +17,7 @@ use std::time::Duration; use anyhow::{anyhow, bail, Context, Result}; use futures_util::{SinkExt, StreamExt}; use nostr::{ - Alphabet, Event, EventBuilder, Filter, JsonUtil, Keys, Kind, SingleLetterTag, Tag, Url, + Alphabet, Event, EventBuilder, Filter, JsonUtil, Keys, Kind, RelayUrl, SingleLetterTag, Tag, }; use serde_json::{json, Value}; use tokio_tungstenite::{connect_async, tungstenite::Message}; @@ -143,14 +143,14 @@ async fn connect_and_authenticate(config: &Config) -> Result { } fn build_auth_event(config: &Config, challenge: &str) -> Result { - let relay_url: Url = config.relay_url.parse()?; + let relay_url: RelayUrl = RelayUrl::parse(&config.relay_url)?; if let Some(auth_tag) = &config.owner_auth_tag { let tags = vec![ - Tag::parse(&["relay", config.relay_url.as_str()])?, - Tag::parse(&["challenge", challenge])?, + Tag::parse(["relay", config.relay_url.as_str()])?, + Tag::parse(["challenge", challenge])?, auth_tag.clone(), ]; - Ok(EventBuilder::new(Kind::Authentication, "", tags).sign_with_keys(&config.bot_keys)?) + Ok(EventBuilder::new(Kind::Authentication, "").tags( tags).sign_with_keys(&config.bot_keys)?) } else { Ok(EventBuilder::auth(challenge, relay_url).sign_with_keys(&config.bot_keys)?) } @@ -176,13 +176,12 @@ async fn publish_profile(ws: &mut Ws, config: &Config) -> Result<()> { async fn announce_channel_membership(ws: &mut Ws, config: &Config) -> Result<()> { let builder = EventBuilder::new( Kind::Custom(9000), - "", + "").tags( [ - Tag::parse(&["h", config.channel_id.as_str()])?, - Tag::parse(&["p", &config.bot_keys.public_key().to_hex()])?, - Tag::parse(&["role", "bot"])?, - ], - ); + Tag::parse(["h", config.channel_id.as_str()])?, + Tag::parse(["p", &config.bot_keys.public_key().to_hex()])?, + Tag::parse(["role", "bot"])?, + ]); let event = builder.sign_with_keys(&config.bot_keys)?; let event_id = event.id.to_hex(); @@ -199,7 +198,7 @@ async fn announce_channel_membership(ws: &mut Ws, config: &Config) -> Result<()> async fn subscribe_to_channel(ws: &mut Ws, channel_id: &str) -> Result<()> { let filter = Filter::new().kind(Kind::Custom(9)).custom_tag( SingleLetterTag::lowercase(Alphabet::H), - [channel_id.to_string()], + channel_id.to_string(), ); send_json(ws, json!(["REQ", SUBSCRIPTION_ID, filter])).await } From f456a4fc064f0c9f59f75f19014ae9e5ad50c76c Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Thu, 21 May 2026 17:41:25 -0400 Subject: [PATCH 02/10] fix(deps): address nostr 0.44 review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix EventBuilder::auth to use RelayUrl instead of Url (3 sites) - Fix custom_tag → custom_tags for array arguments (2 sites) - Fix cargo fmt violations from mechanical replacement - Replace deprecated Timestamp::as_u64() with as_secs() (8 sites) - Remove stale Compat* type aliases from identity.rs - Remove redundant use nostr statement from agents.rs --- crates/sprout-acp/src/filter.rs | 6 +- crates/sprout-acp/src/lib.rs | 3 +- crates/sprout-acp/src/pool.rs | 3 +- crates/sprout-acp/src/queue.rs | 6 +- crates/sprout-acp/src/relay.rs | 13 ++- crates/sprout-admin/src/main.rs | 9 +- crates/sprout-auth/src/lib.rs | 7 +- crates/sprout-auth/src/nip42.rs | 15 +-- crates/sprout-auth/src/nip98.rs | 11 +- crates/sprout-cli/src/client.rs | 6 +- crates/sprout-cli/src/commands/social.rs | 5 +- crates/sprout-core/src/engram.rs | 16 +-- crates/sprout-core/src/event.rs | 3 +- crates/sprout-core/src/filter.rs | 31 +++-- crates/sprout-core/src/lib.rs | 6 +- crates/sprout-core/src/observer.rs | 10 +- crates/sprout-core/src/pairing/crypto.rs | 21 ++-- crates/sprout-core/src/pairing/session.rs | 63 +++++----- crates/sprout-core/src/verification.rs | 6 +- crates/sprout-db/src/event.rs | 9 +- crates/sprout-mcp/src/relay_client.rs | 41 ++++--- crates/sprout-mcp/src/server.rs | 24 ++-- crates/sprout-mcp/src/upload.rs | 3 +- crates/sprout-media/src/auth.rs | 15 ++- crates/sprout-proxy/src/channel_map.rs | 6 +- crates/sprout-proxy/src/translate.rs | 94 ++++++++------- crates/sprout-proxy/src/upstream.rs | 8 +- crates/sprout-pubsub/src/lib.rs | 3 +- crates/sprout-relay/src/api/bridge.rs | 16 +-- crates/sprout-relay/src/audio/handler.rs | 5 +- crates/sprout-relay/src/handlers/event.rs | 33 +++--- crates/sprout-relay/src/handlers/ingest.rs | 6 +- .../sprout-relay/src/handlers/relay_admin.rs | 3 +- .../sprout-relay/src/handlers/side_effects.rs | 22 ++-- crates/sprout-relay/src/subscription.rs | 6 +- crates/sprout-relay/src/workflow_sink.rs | 3 +- crates/sprout-sdk/src/builders.rs | 108 +++++++----------- crates/sprout-sdk/src/nip_oa.rs | 6 +- crates/sprout-search/src/index.rs | 9 +- crates/sprout-search/src/lib.rs | 3 +- crates/sprout-test-client/src/bin/mention.rs | 5 +- crates/sprout-test-client/src/lib.rs | 11 +- .../sprout-test-client/tests/e2e_long_form.rs | 9 +- crates/sprout-test-client/tests/e2e_mcp.rs | 6 +- crates/sprout-test-client/tests/e2e_media.rs | 3 +- .../tests/e2e_media_extended.rs | 66 +++++------ .../tests/e2e_media_video.rs | 58 +++++----- .../tests/e2e_nostr_interop.rs | 48 ++++---- crates/sprout-test-client/tests/e2e_relay.rs | 70 ++++++------ .../sprout-test-client/tests/e2e_rest_api.rs | 33 ++++-- crates/sprout-test-client/tests/e2e_tokens.rs | 6 +- .../tests/e2e_user_status.rs | 9 +- .../sprout-test-client/tests/e2e_workflows.rs | 17 +-- crates/sprout-workflow/src/lib.rs | 22 ++-- desktop/src-tauri/src/commands/agents.rs | 1 - desktop/src-tauri/src/commands/identity.rs | 14 +-- examples/countdown-bot/src/main.rs | 17 ++- 57 files changed, 555 insertions(+), 503 deletions(-) diff --git a/crates/sprout-acp/src/filter.rs b/crates/sprout-acp/src/filter.rs index 6b5cfc300..5d9930177 100644 --- a/crates/sprout-acp/src/filter.rs +++ b/crates/sprout-acp/src/filter.rs @@ -485,7 +485,8 @@ mod tests { /// Build a minimal test event with the given kind and content. fn make_event(kind: u32, content: &str) -> nostr::Event { let keys = Keys::generate(); - EventBuilder::new(Kind::Custom(kind as u16), content).tags( []) + EventBuilder::new(Kind::Custom(kind as u16), content) + .tags([]) .sign_with_keys(&keys) .unwrap() } @@ -494,7 +495,8 @@ mod tests { fn make_event_with_p_tag(kind: u32, content: &str, p_hex: &str) -> nostr::Event { let keys = Keys::generate(); let p_tag = Tag::parse(["p", p_hex]).expect("tag parse"); - EventBuilder::new(Kind::Custom(kind as u16), content).tags( [p_tag]) + EventBuilder::new(Kind::Custom(kind as u16), content) + .tags([p_tag]) .sign_with_keys(&keys) .unwrap() } diff --git a/crates/sprout-acp/src/lib.rs b/crates/sprout-acp/src/lib.rs index a6dae6e96..3801d94c8 100644 --- a/crates/sprout-acp/src/lib.rs +++ b/crates/sprout-acp/src/lib.rs @@ -72,7 +72,8 @@ async fn publish_presence( use nostr::{EventBuilder, Kind}; use sprout_core::kind::KIND_PRESENCE_UPDATE; - let event = EventBuilder::new(Kind::Custom(KIND_PRESENCE_UPDATE as u16), status).tags( []) + let event = EventBuilder::new(Kind::Custom(KIND_PRESENCE_UPDATE as u16), status) + .tags([]) .sign_with_keys(keys) .map_err(|e| relay::RelayError::Http(format!("presence sign error: {e}")))?; publisher.publish_event(event).await?; diff --git a/crates/sprout-acp/src/pool.rs b/crates/sprout-acp/src/pool.rs index 58c0daf4d..1397e7f23 100644 --- a/crates/sprout-acp/src/pool.rs +++ b/crates/sprout-acp/src/pool.rs @@ -2329,7 +2329,8 @@ mod tests { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ]) .unwrap(); - let event = EventBuilder::new(Kind::Custom(9), "hello").tags( [p_tag]) + let event = EventBuilder::new(Kind::Custom(9), "hello") + .tags([p_tag]) .sign_with_keys(&keys) .unwrap(); let author_hex = event.pubkey.to_hex(); diff --git a/crates/sprout-acp/src/queue.rs b/crates/sprout-acp/src/queue.rs index 94edeb769..cbdae3338 100644 --- a/crates/sprout-acp/src/queue.rs +++ b/crates/sprout-acp/src/queue.rs @@ -1082,7 +1082,8 @@ mod tests { /// Build a test event with the given content and kind. fn make_event(content: &str) -> Event { let keys = Keys::generate(); - EventBuilder::new(Kind::Custom(9), content).tags( []) + EventBuilder::new(Kind::Custom(9), content) + .tags([]) .sign_with_keys(&keys) .unwrap() } @@ -1870,7 +1871,8 @@ mod tests { nostr::Tag::parse(&strs).unwrap() }) .collect(); - EventBuilder::new(Kind::Custom(9), content).tags( nostr_tags) + EventBuilder::new(Kind::Custom(9), content) + .tags(nostr_tags) .sign_with_keys(&keys) .unwrap() } diff --git a/crates/sprout-acp/src/relay.rs b/crates/sprout-acp/src/relay.rs index d5d5e1bd9..56806646d 100644 --- a/crates/sprout-acp/src/relay.rs +++ b/crates/sprout-acp/src/relay.rs @@ -146,7 +146,8 @@ impl RestClient { tags.push(payload_tag); } - let event = EventBuilder::new(Kind::HttpAuth, "").tags( tags) + let event = EventBuilder::new(Kind::HttpAuth, "") + .tags(tags) .sign_with_keys(&self.keys) .map_err(|e| RelayError::Http(format!("NIP-98 sign error: {e}")))?; let event_json = serde_json::to_string(&event) @@ -748,7 +749,8 @@ impl HarnessRelay { .map_err(|e| RelayError::AuthFailed(e.to_string()))?, ); } - let event = EventBuilder::new(Kind::Custom(KIND_TYPING_INDICATOR as u16), "").tags( tags) + let event = EventBuilder::new(Kind::Custom(KIND_TYPING_INDICATOR as u16), "") + .tags(tags) .sign_with_keys(&self.keys)?; Ok(event) } @@ -2495,7 +2497,9 @@ async fn send_auth_response( .map_err(|e| RelayError::Http(format!("tag parse error: {e}")))?, tag.clone(), ]; - EventBuilder::new(nostr::Kind::Authentication, "").tags( tags).sign_with_keys(keys)? + EventBuilder::new(nostr::Kind::Authentication, "") + .tags(tags) + .sign_with_keys(keys)? } else { EventBuilder::auth(challenge, relay_nostr_url).sign_with_keys(keys)? }; @@ -3075,7 +3079,8 @@ mod tests { /// control it, but we return it so callers can use it for dedup tests. fn make_test_event(keys: &nostr::Keys, created_at_secs: u64) -> Event { let ts = nostr::Timestamp::from(created_at_secs); - EventBuilder::new(nostr::Kind::TextNote, "test").tags( []) + EventBuilder::new(nostr::Kind::TextNote, "test") + .tags([]) .custom_created_at(ts) .sign_with_keys(keys) .expect("signing should succeed") diff --git a/crates/sprout-admin/src/main.rs b/crates/sprout-admin/src/main.rs index b99868dfa..8ad4027df 100644 --- a/crates/sprout-admin/src/main.rs +++ b/crates/sprout-admin/src/main.rs @@ -177,7 +177,8 @@ async fn reconcile_channels(relay_key_arg: Option) -> Result<()> { tags.push(Tag::parse(["closed"])?); tags.push(Tag::parse(["t", &channel.channel_type])?); - let event = EventBuilder::new(Kind::Custom(39000), "").tags( tags) + let event = EventBuilder::new(Kind::Custom(39000), "") + .tags(tags) .sign_with_keys(&relay_keys) .map_err(|e| anyhow::anyhow!("sign kind:39000: {e}"))?; db.replace_addressable_event(&event, Some(channel.id)) @@ -194,7 +195,8 @@ async fn reconcile_channels(relay_key_arg: Option) -> Result<()> { let pk = hex::encode(&m.pubkey); tags.push(Tag::parse(["p", &pk, &m.role])?); } - let event = EventBuilder::new(Kind::Custom(KIND_NIP29_GROUP_ADMINS as u16), "").tags( tags) + let event = EventBuilder::new(Kind::Custom(KIND_NIP29_GROUP_ADMINS as u16), "") + .tags(tags) .sign_with_keys(&relay_keys) .map_err(|e| anyhow::anyhow!("sign kind:39001: {e}"))?; db.replace_addressable_event(&event, Some(channel.id)) @@ -208,7 +210,8 @@ async fn reconcile_channels(relay_key_arg: Option) -> Result<()> { let pk = hex::encode(&m.pubkey); tags.push(Tag::parse(["p", &pk, "", &m.role])?); } - let event = EventBuilder::new(Kind::Custom(39002), "").tags( tags) + let event = EventBuilder::new(Kind::Custom(39002), "") + .tags(tags) .sign_with_keys(&relay_keys) .map_err(|e| anyhow::anyhow!("sign kind:39002: {e}"))?; db.replace_addressable_event(&event, Some(channel.id)) diff --git a/crates/sprout-auth/src/lib.rs b/crates/sprout-auth/src/lib.rs index 86f8ad4db..fb8cf534a 100644 --- a/crates/sprout-auth/src/lib.rs +++ b/crates/sprout-auth/src/lib.rs @@ -161,10 +161,10 @@ pub fn derive_pubkey_from_username(username: &str) -> Result nostr::Event { - let url: Url = relay_url.parse().expect("valid url"); + let url = RelayUrl::parse(relay_url).expect("valid url"); EventBuilder::auth(challenge, url) .sign_with_keys(keys) .expect("signing failed") @@ -222,7 +222,8 @@ mod tests { #[tokio::test] async fn wrong_kind_rejected() { let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "not auth").tags( []) + let event = EventBuilder::new(Kind::TextNote, "not auth") + .tags([]) .sign_with_keys(&keys) .expect("sign"); diff --git a/crates/sprout-auth/src/nip42.rs b/crates/sprout-auth/src/nip42.rs index 605af6993..a97150401 100644 --- a/crates/sprout-auth/src/nip42.rs +++ b/crates/sprout-auth/src/nip42.rs @@ -75,8 +75,8 @@ pub fn verify_nip42_event( return Err(AuthError::RelayUrlMismatch); } - let now = Timestamp::now().as_u64(); - let event_ts = event.created_at.as_u64(); + let now = Timestamp::now().as_secs(); + let event_ts = event.created_at.as_secs(); let delta = now.abs_diff(event_ts); if delta > TIMESTAMP_TOLERANCE_SECS { return Err(AuthError::EventExpired); @@ -88,12 +88,12 @@ pub fn verify_nip42_event( #[cfg(test)] mod tests { use super::*; - use nostr::{EventBuilder, Keys, Kind, Timestamp, Url as NostrUrl}; + use nostr::{EventBuilder, Keys, Kind, RelayUrl, Timestamp}; const TEST_RELAY: &str = "wss://relay.example.com"; fn make_auth_event(keys: &Keys, challenge: &str, relay_url: &str) -> Event { - let url: NostrUrl = relay_url.parse().expect("valid relay url"); + let url = RelayUrl::parse(relay_url).expect("valid relay url"); EventBuilder::auth(challenge, url) .sign_with_keys(keys) .expect("signing failed") @@ -130,7 +130,8 @@ mod tests { #[test] fn wrong_kind_rejected() { let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "not auth").tags( []) + let event = EventBuilder::new(Kind::TextNote, "not auth") + .tags([]) .sign_with_keys(&keys) .expect("sign"); assert!(matches!( @@ -143,8 +144,8 @@ mod tests { fn expired_event_rejected() { let keys = Keys::generate(); let challenge = generate_challenge(); - let url: NostrUrl = TEST_RELAY.parse().unwrap(); - let old_ts = Timestamp::from(Timestamp::now().as_u64().saturating_sub(120)); + let url = RelayUrl::parse(TEST_RELAY).unwrap(); + let old_ts = Timestamp::from(Timestamp::now().as_secs().saturating_sub(120)); let event = EventBuilder::auth(&challenge, url) .custom_created_at(old_ts) .sign_with_keys(&keys) diff --git a/crates/sprout-auth/src/nip98.rs b/crates/sprout-auth/src/nip98.rs index f09b7d146..5aefc4da0 100644 --- a/crates/sprout-auth/src/nip98.rs +++ b/crates/sprout-auth/src/nip98.rs @@ -75,8 +75,8 @@ pub fn verify_nip98_event( .map_err(|_| AuthError::Nip98Invalid("invalid Schnorr signature".to_string()))?; // 4. Verify created_at within ±60 seconds of now. - let now = Timestamp::now().as_u64(); - let event_ts = event.created_at.as_u64(); + let now = Timestamp::now().as_secs(); + let event_ts = event.created_at.as_secs(); let delta = now.abs_diff(event_ts); if delta > TIMESTAMP_TOLERANCE_SECS { return Err(AuthError::Nip98Invalid(format!( @@ -175,7 +175,7 @@ mod tests { tags.push(Tag::parse(["payload", hex]).unwrap()); } - let mut builder = EventBuilder::new(Kind::HttpAuth, "").tags( tags); + let mut builder = EventBuilder::new(Kind::HttpAuth, "").tags(tags); if let Some(ts) = created_at { builder = builder.custom_created_at(ts); } @@ -195,7 +195,8 @@ mod tests { #[test] fn wrong_kind_rejected() { let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "").tags( []) + let event = EventBuilder::new(Kind::TextNote, "") + .tags([]) .sign_with_keys(&keys) .expect("sign"); let json = serde_json::to_string(&event).unwrap(); @@ -206,7 +207,7 @@ mod tests { #[test] fn expired_timestamp_rejected() { let keys = Keys::generate(); - let old_ts = Timestamp::from(Timestamp::now().as_u64().saturating_sub(120)); + let old_ts = Timestamp::from(Timestamp::now().as_secs().saturating_sub(120)); let json = make_nip98_event(&keys, TEST_URL, TEST_METHOD, None, Some(old_ts)); let result = verify_nip98_event(&json, TEST_URL, TEST_METHOD, None); assert!(matches!(result, Err(AuthError::Nip98Invalid(_)))); diff --git a/crates/sprout-cli/src/client.rs b/crates/sprout-cli/src/client.rs index 40cddf754..846914db3 100644 --- a/crates/sprout-cli/src/client.rs +++ b/crates/sprout-cli/src/client.rs @@ -108,7 +108,8 @@ fn sign_nip98( .map_err(|e| CliError::Other(format!("tag error: {e}")))?, ); } - let event = EventBuilder::new(Kind::Custom(27235), "").tags( tags) + let event = EventBuilder::new(Kind::Custom(27235), "") + .tags(tags) .sign_with_keys(keys) .map_err(|e| CliError::Other(format!("NIP-98 signing failed: {e}")))?; let json = event.as_json(); @@ -353,7 +354,8 @@ impl SproutClient { } } - let auth_event = EventBuilder::new(Kind::from(24242), "Upload file").tags( blossom_tags) + let auth_event = EventBuilder::new(Kind::from(24242), "Upload file") + .tags(blossom_tags) .sign_with_keys(&self.keys) .map_err(|e| CliError::Other(format!("signing failed: {e}")))?; diff --git a/crates/sprout-cli/src/commands/social.rs b/crates/sprout-cli/src/commands/social.rs index 7cb3fdb59..81ee4cb7f 100644 --- a/crates/sprout-cli/src/commands/social.rs +++ b/crates/sprout-cli/src/commands/social.rs @@ -141,7 +141,8 @@ fn parse_tags_json(tags_json: &str) -> Result, CliError> { raw_tags .iter() .map(|parts| { - Tag::parse(parts.iter().map(String::as_str)).map_err(|e| CliError::Usage(format!("invalid tag {parts:?}: {e}"))) + Tag::parse(parts.iter().map(String::as_str)) + .map_err(|e| CliError::Usage(format!("invalid tag {parts:?}: {e}"))) }) .collect::>() } @@ -166,7 +167,7 @@ pub async fn cmd_set_list( ))); } - let builder = EventBuilder::new(Kind::Custom(kind), content).tags( tags); + let builder = EventBuilder::new(Kind::Custom(kind), content).tags(tags); let event = client.sign_event(builder)?; let resp = client.submit_event(event).await?; println!("{resp}"); diff --git a/crates/sprout-core/src/engram.rs b/crates/sprout-core/src/engram.rs index d730c2bf3..0a62612fe 100644 --- a/crates/sprout-core/src/engram.rs +++ b/crates/sprout-core/src/engram.rs @@ -408,7 +408,8 @@ pub fn build_event( .map_err(|e| EngramError::Encrypt(e.to_string()))?, ]; - EventBuilder::new(Kind::Custom(KIND_AGENT_ENGRAM as u16), ciphertext).tags( tags) + EventBuilder::new(Kind::Custom(KIND_AGENT_ENGRAM as u16), ciphertext) + .tags(tags) .custom_created_at(nostr::Timestamp::from(created_at)) .sign_with_keys(agent_keys) .map_err(|e| EngramError::Sign(e.to_string())) @@ -800,13 +801,12 @@ mod tests { Tag::parse(["d", &upper_d]).unwrap(), Tag::parse(["p", &owner.public_key().to_hex()]).unwrap(), ]; - let tampered = EventBuilder::new( - Kind::Custom(KIND_AGENT_ENGRAM as u16), - ev.content.clone()).tags( - tags) - .custom_created_at(ev.created_at) - .sign_with_keys(&agent) - .unwrap(); + let tampered = + EventBuilder::new(Kind::Custom(KIND_AGENT_ENGRAM as u16), ev.content.clone()) + .tags(tags) + .custom_created_at(ev.created_at) + .sign_with_keys(&agent) + .unwrap(); let err = validate_and_decrypt( &tampered, &agent.public_key(), diff --git a/crates/sprout-core/src/event.rs b/crates/sprout-core/src/event.rs index f30ea9f61..e95496d1b 100644 --- a/crates/sprout-core/src/event.rs +++ b/crates/sprout-core/src/event.rs @@ -56,7 +56,8 @@ mod tests { fn make_event() -> nostr::Event { let keys = Keys::generate(); - EventBuilder::new(Kind::TextNote, "hello sprout").tags( []) + EventBuilder::new(Kind::TextNote, "hello sprout") + .tags([]) .sign_with_keys(&keys) .expect("sign") } diff --git a/crates/sprout-core/src/filter.rs b/crates/sprout-core/src/filter.rs index d57bebbb9..9c1702847 100644 --- a/crates/sprout-core/src/filter.rs +++ b/crates/sprout-core/src/filter.rs @@ -91,7 +91,8 @@ mod tests { fn stored_with_tag(tag: Tag) -> StoredEvent { let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "test").tags( [tag]) + let event = EventBuilder::new(Kind::TextNote, "test") + .tags([tag]) .sign_with_keys(&keys) .expect("sign"); StoredEvent::with_received_at(event, Utc::now(), None, true) @@ -108,8 +109,8 @@ mod tests { ); let pubkey = keys.public_key(); let now_ts = nostr::Timestamp::now(); - let past = Timestamp::from(now_ts.as_u64() - 3600); - let future = Timestamp::from(now_ts.as_u64() + 3600); + let past = Timestamp::from(now_ts.as_secs() - 3600); + let future = Timestamp::from(now_ts.as_secs() + 3600); assert!(filters_match(&[Filter::new().kind(Kind::TextNote)], &ev)); assert!(!filters_match( @@ -171,15 +172,13 @@ mod tests { let keys = Keys::generate(); // Event with NO h-tag but with a stored channel_id. - let reaction = EventBuilder::new( - Kind::Reaction, - "👍").tags( - [Tag::event(nostr::EventId::all_zeros())]) - .sign_with_keys(&keys) - .expect("sign"); + let reaction = EventBuilder::new(Kind::Reaction, "👍") + .tags([Tag::event(nostr::EventId::all_zeros())]) + .sign_with_keys(&keys) + .expect("sign"); let stored = StoredEvent::with_received_at(reaction, Utc::now(), Some(channel_id), true); - let h_filter = Filter::new().kind(Kind::Reaction).custom_tag( + let h_filter = Filter::new().kind(Kind::Reaction).custom_tags( nostr::SingleLetterTag::lowercase(nostr::Alphabet::H), [channel_id.to_string()], ); @@ -188,7 +187,7 @@ mod tests { assert!(filters_match(std::slice::from_ref(&h_filter), &stored)); // Wrong channel should NOT match. - let wrong_channel = Filter::new().kind(Kind::Reaction).custom_tag( + let wrong_channel = Filter::new().kind(Kind::Reaction).custom_tags( nostr::SingleLetterTag::lowercase(nostr::Alphabet::H), [uuid::Uuid::new_v4().to_string()], ); @@ -202,12 +201,10 @@ mod tests { // Event WITH an explicit h-tag: tag is authoritative, channel_id fallback // must NOT override it. Prevents cross-channel leakage. let other_channel = uuid::Uuid::new_v4(); - let msg_with_h = EventBuilder::new( - Kind::Custom(9), - "hello").tags( - [Tag::parse(["h", &other_channel.to_string()]).unwrap()]) - .sign_with_keys(&keys) - .expect("sign"); + let msg_with_h = EventBuilder::new(Kind::Custom(9), "hello") + .tags([Tag::parse(["h", &other_channel.to_string()]).unwrap()]) + .sign_with_keys(&keys) + .expect("sign"); // channel_id matches the filter, but the h-tag points elsewhere. let stored_with_h = StoredEvent::with_received_at(msg_with_h, Utc::now(), Some(channel_id), true); diff --git a/crates/sprout-core/src/lib.rs b/crates/sprout-core/src/lib.rs index 77617f60d..b9e0d220f 100644 --- a/crates/sprout-core/src/lib.rs +++ b/crates/sprout-core/src/lib.rs @@ -47,14 +47,16 @@ pub mod test_helpers { /// Create a signed test event with the given kind and random keys. pub fn make_event(kind: Kind) -> nostr::Event { let keys = Keys::generate(); - EventBuilder::new(kind, "test").tags( []) + EventBuilder::new(kind, "test") + .tags([]) .sign_with_keys(&keys) .expect("sign") } /// Create a signed test event with the given keys and kind. pub fn make_event_with_keys(keys: &Keys, kind: Kind) -> nostr::Event { - EventBuilder::new(kind, "test").tags( []) + EventBuilder::new(kind, "test") + .tags([]) .sign_with_keys(keys) .expect("sign") } diff --git a/crates/sprout-core/src/observer.rs b/crates/sprout-core/src/observer.rs index e8c36adf4..67a3713b6 100644 --- a/crates/sprout-core/src/observer.rs +++ b/crates/sprout-core/src/observer.rs @@ -126,8 +126,9 @@ mod tests { let event = EventBuilder::new( Kind::Custom(crate::kind::KIND_AGENT_OBSERVER_FRAME as u16), - encrypted).tags( - [Tag::public_key(recipient.public_key())]) + encrypted, + ) + .tags([Tag::public_key(recipient.public_key())]) .sign_with_keys(&sender) .expect("sign event"); let decrypted: serde_json::Value = @@ -141,8 +142,9 @@ mod tests { let recipient = Keys::generate(); let event = EventBuilder::new( Kind::Custom(crate::kind::KIND_AGENT_OBSERVER_FRAME as u16), - "not encrypted").tags( - [Tag::public_key(recipient.public_key())]) + "not encrypted", + ) + .tags([Tag::public_key(recipient.public_key())]) .sign_with_keys(&sender) .expect("sign event"); diff --git a/crates/sprout-core/src/pairing/crypto.rs b/crates/sprout-core/src/pairing/crypto.rs index c548778b8..a006bc8a6 100644 --- a/crates/sprout-core/src/pairing/crypto.rs +++ b/crates/sprout-core/src/pairing/crypto.rs @@ -241,10 +241,12 @@ mod tests { // ECDH: source computes shared key with target's pubkey let ecdh_from_src = - nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()).unwrap(); + nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()) + .unwrap(); // ECDH: target computes shared key with source's pubkey (must match) let ecdh_from_tgt = - nostr::util::generate_shared_key(tgt_keys.secret_key(), &src_keys.public_key()).unwrap(); + nostr::util::generate_shared_key(tgt_keys.secret_key(), &src_keys.public_key()) + .unwrap(); assert_eq!(ecdh_from_src, ecdh_from_tgt, "ECDH must be symmetric"); @@ -267,7 +269,8 @@ mod tests { let tgt_keys = Keys::new(tgt_sk); let session_id = derive_session_id(&session_secret()); - let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()).unwrap(); + let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()) + .unwrap(); let (_, sas_input) = derive_sas(&ecdh, &session_secret()); let src_pk: [u8; 32] = src_keys.public_key().to_bytes(); @@ -301,7 +304,8 @@ mod tests { ); // ECDH - let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()).unwrap(); + let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()) + .unwrap(); assert_eq!( bytes_to_hex(&ecdh), "9b4b6d6990713d89d6d9982e506ee1bbcde6f05c54d9d2978696e8a7274d4408" @@ -343,7 +347,8 @@ mod tests { let tgt_keys = Keys::new(tgt_sk); let session_id = derive_session_id(&session_secret()); - let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()).unwrap(); + let ecdh = nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()) + .unwrap(); let (_, sas_input) = derive_sas(&ecdh, &session_secret()); let src_pk: [u8; 32] = src_keys.public_key().to_bytes(); @@ -398,9 +403,11 @@ mod tests { // Both sides compute ECDH (symmetric). let ecdh_src = - nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()).unwrap(); + nostr::util::generate_shared_key(src_keys.secret_key(), &tgt_keys.public_key()) + .unwrap(); let ecdh_tgt = - nostr::util::generate_shared_key(tgt_keys.secret_key(), &src_keys.public_key()).unwrap(); + nostr::util::generate_shared_key(tgt_keys.secret_key(), &src_keys.public_key()) + .unwrap(); assert_eq!(ecdh_src, ecdh_tgt, "ECDH must be symmetric"); // Both sides derive the same SAS. diff --git a/crates/sprout-core/src/pairing/session.rs b/crates/sprout-core/src/pairing/session.rs index 7f6ccdec8..2e1d22d0a 100644 --- a/crates/sprout-core/src/pairing/session.rs +++ b/crates/sprout-core/src/pairing/session.rs @@ -590,13 +590,11 @@ impl PairingSession { let jitter = rand::random::() % 31; // 0-30s jitter per NIP-AB §Metadata Privacy let ts = nostr::Timestamp::from(now.saturating_sub(jitter)); - EventBuilder::new( - Kind::Custom(PAIRING_KIND), - &encrypted).tags( - [Tag::public_key(peer)]) - .custom_created_at(ts) - .sign_with_keys(&self.keys) - .map_err(|e| PairingError::SigningError(e.to_string())) + EventBuilder::new(Kind::Custom(PAIRING_KIND), &encrypted) + .tags([Tag::public_key(peer)]) + .custom_created_at(ts) + .sign_with_keys(&self.keys) + .map_err(|e| PairingError::SigningError(e.to_string())) } /// Decrypt and parse a NIP-44 encrypted pairing message from an event. @@ -928,12 +926,11 @@ mod tests { nip44::Version::V2, ) .unwrap(); - let fake_abort = EventBuilder::new( - Kind::Custom(crate::kind::KIND_PAIRING as u16), - &encrypted).tags( - [Tag::public_key(source.pubkey())]) - .sign_with_keys(&rogue) - .unwrap(); + let fake_abort = + EventBuilder::new(Kind::Custom(crate::kind::KIND_PAIRING as u16), &encrypted) + .tags([Tag::public_key(source.pubkey())]) + .sign_with_keys(&rogue) + .unwrap(); // Source has no peer yet — must reject. let result = source.handle_abort(&fake_abort); @@ -982,12 +979,10 @@ mod tests { nip44::Version::V2, ) .unwrap(); - EventBuilder::new( - Kind::Custom(crate::kind::KIND_PAIRING as u16), - &encrypted).tags( - [Tag::public_key(source.pubkey())]) - .sign_with_keys(&keys) - .unwrap() + EventBuilder::new(Kind::Custom(crate::kind::KIND_PAIRING as u16), &encrypted) + .tags([Tag::public_key(source.pubkey())]) + .sign_with_keys(&keys) + .unwrap() }; // Source should reject the late abort. @@ -1041,12 +1036,10 @@ mod tests { nip44::Version::V2, ) .unwrap(); - let fake_event = EventBuilder::new( - Kind::Custom(PAIRING_KIND), - &encrypted).tags( - [Tag::public_key(target.pubkey())]) - .sign_with_keys(&rogue_keys) - .unwrap(); + let fake_event = EventBuilder::new(Kind::Custom(PAIRING_KIND), &encrypted) + .tags([Tag::public_key(target.pubkey())]) + .sign_with_keys(&rogue_keys) + .unwrap(); // Target should reject (wrong author). let result = target.handle_sas_confirm(&fake_event); @@ -1261,12 +1254,10 @@ mod tests { nip44::Version::V2, ) .unwrap(); - let wrong_event = EventBuilder::new( - Kind::Custom(PAIRING_KIND), - &wrong_encrypted).tags( - [Tag::public_key(target.pubkey())]) - .sign_with_keys(&source.keys) - .unwrap(); + let wrong_event = EventBuilder::new(Kind::Custom(PAIRING_KIND), &wrong_encrypted) + .tags([Tag::public_key(target.pubkey())]) + .sign_with_keys(&source.keys) + .unwrap(); // Target tries to handle as payload — fails (wrong type). let result = target.handle_payload(&wrong_event); @@ -1315,12 +1306,10 @@ mod tests { nip44::Version::V2, ) .unwrap(); - let fail_event = EventBuilder::new( - Kind::Custom(PAIRING_KIND), - &fail_encrypted).tags( - [Tag::public_key(source.pubkey())]) - .sign_with_keys(&target.keys) - .unwrap(); + let fail_event = EventBuilder::new(Kind::Custom(PAIRING_KIND), &fail_encrypted) + .tags([Tag::public_key(source.pubkey())]) + .sign_with_keys(&target.keys) + .unwrap(); // Source handles complete(false) — should error and abort. let result = source.handle_complete(&fail_event); diff --git a/crates/sprout-core/src/verification.rs b/crates/sprout-core/src/verification.rs index e8c6a879f..ab6ab3b26 100644 --- a/crates/sprout-core/src/verification.rs +++ b/crates/sprout-core/src/verification.rs @@ -38,7 +38,8 @@ mod tests { fn make_valid_event() -> Event { let keys = Keys::generate(); - EventBuilder::new(Kind::TextNote, "test content").tags( []) + EventBuilder::new(Kind::TextNote, "test content") + .tags([]) .sign_with_keys(&keys) .expect("sign") } @@ -46,7 +47,8 @@ mod tests { #[test] fn rejects_tampered_id() { let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "original").tags( []) + let event = EventBuilder::new(Kind::TextNote, "original") + .tags([]) .sign_with_keys(&keys) .expect("sign"); let mut json: serde_json::Value = serde_json::from_str(&event.as_json()).expect("parse"); diff --git a/crates/sprout-db/src/event.rs b/crates/sprout-db/src/event.rs index c54f787df..2c17c4fdb 100644 --- a/crates/sprout-db/src/event.rs +++ b/crates/sprout-db/src/event.rs @@ -988,7 +988,8 @@ mod tests { fn make_event_with_kind_and_tags(kind: u16, tags: Vec) -> nostr::Event { let keys = Keys::generate(); - EventBuilder::new(Kind::Custom(kind), "test").tags( tags) + EventBuilder::new(Kind::Custom(kind), "test") + .tags(tags) .sign_with_keys(&keys) .expect("sign") } @@ -1031,10 +1032,8 @@ mod tests { #[test] fn extract_d_tag_non_nip33_returns_none() { // kind:1 (text note) — not parameterized replaceable - let event = make_event_with_kind_and_tags( - 1, - vec![Tag::parse(["d", "should-be-ignored"]).unwrap()], - ); + let event = + make_event_with_kind_and_tags(1, vec![Tag::parse(["d", "should-be-ignored"]).unwrap()]); assert_eq!(extract_d_tag(&event), None); } diff --git a/crates/sprout-mcp/src/relay_client.rs b/crates/sprout-mcp/src/relay_client.rs index 7db858fcb..68ef2fd71 100644 --- a/crates/sprout-mcp/src/relay_client.rs +++ b/crates/sprout-mcp/src/relay_client.rs @@ -330,8 +330,8 @@ fn build_auth_event( api_token: Option<&str>, auth_tag: Option<&Tag>, ) -> Result { - let relay_nostr_url = RelayUrl::parse(relay_url) - .map_err(|e| RelayClientError::Url(e.to_string()))?; + let relay_nostr_url = + RelayUrl::parse(relay_url).map_err(|e| RelayClientError::Url(e.to_string()))?; if let Some(token) = api_token { let mut tags = vec![ Tag::parse(["relay", relay_url]) @@ -344,7 +344,9 @@ fn build_auth_event( if let Some(t) = auth_tag { tags.push(t.clone()); } - Ok(EventBuilder::new(Kind::Authentication, "").tags( tags).sign_with_keys(keys)?) + Ok(EventBuilder::new(Kind::Authentication, "") + .tags(tags) + .sign_with_keys(keys)?) } else if let Some(t) = auth_tag { // Cannot use EventBuilder::auth() shortcut — it doesn't accept extra tags. let tags = vec![ @@ -354,7 +356,9 @@ fn build_auth_event( .map_err(|e| RelayClientError::EventBuilder(e.to_string()))?, t.clone(), ]; - Ok(EventBuilder::new(Kind::Authentication, "").tags( tags).sign_with_keys(keys)?) + Ok(EventBuilder::new(Kind::Authentication, "") + .tags(tags) + .sign_with_keys(keys)?) } else { Ok(EventBuilder::auth(challenge, relay_nostr_url).sign_with_keys(keys)?) } @@ -1307,7 +1311,7 @@ mod tests { // With auth_tag: the signed event must contain it. let client = make_client(keys.clone(), Some(auth_tag.clone())); let event = client - .sign_event(EventBuilder::new(Kind::TextNote, "hello").tags( [])) + .sign_event(EventBuilder::new(Kind::TextNote, "hello").tags([])) .expect("sign_event should succeed"); let tag_values: Vec> = event @@ -1327,7 +1331,7 @@ mod tests { // Without auth_tag: the signed event must NOT contain an auth tag. let client_no_auth = make_client(keys, None); let event_no_auth = client_no_auth - .sign_event(EventBuilder::new(Kind::TextNote, "hello").tags( [])) + .sign_event(EventBuilder::new(Kind::TextNote, "hello").tags([])) .expect("sign_event should succeed"); let has_auth_tag = event_no_auth @@ -1348,7 +1352,7 @@ mod tests { // Case 1: client has auth_tag configured, caller also pre-adds one → duplicate → reject. let client = make_client(keys.clone(), Some(auth_tag.clone())); - let builder = EventBuilder::new(Kind::TextNote, "oops").tags( [auth_tag.clone()]); + let builder = EventBuilder::new(Kind::TextNote, "oops").tags([auth_tag.clone()]); let result = client.sign_event(builder); assert!( result.is_err(), @@ -1362,7 +1366,7 @@ mod tests { // Case 2: client has NO auth_tag configured, but caller manually adds one → bypass → reject. let client_no_auth = make_client(keys, None); - let builder_with_manual = EventBuilder::new(Kind::TextNote, "bypass").tags( [auth_tag]); + let builder_with_manual = EventBuilder::new(Kind::TextNote, "bypass").tags([auth_tag]); let result2 = client_no_auth.sign_event(builder_with_manual); assert!( result2.is_err(), @@ -1387,7 +1391,8 @@ mod tests { let client = make_client(keys.clone(), Some(real_tag)); // Build event with forged auth tag, bypassing sign_event - let event = EventBuilder::new(Kind::TextNote, "forged").tags( [forged_tag]) + let event = EventBuilder::new(Kind::TextNote, "forged") + .tags([forged_tag]) .sign_with_keys(&keys) .unwrap(); @@ -1408,7 +1413,8 @@ mod tests { let client = make_client(keys.clone(), None); - let event = EventBuilder::new(Kind::TextNote, "sneaky").tags( [sneaky_tag]) + let event = EventBuilder::new(Kind::TextNote, "sneaky") + .tags([sneaky_tag]) .sign_with_keys(&keys) .unwrap(); @@ -1426,7 +1432,8 @@ mod tests { let client = make_client(client_keys, None); // Event signed by a different keypair - let event = EventBuilder::new(Kind::TextNote, "wrong author").tags( []) + let event = EventBuilder::new(Kind::TextNote, "wrong author") + .tags([]) .sign_with_keys(&other_keys) .unwrap(); @@ -1592,7 +1599,8 @@ mod tests { let keys = Keys::generate(); let client = RelayClient::connect(&url, &keys, None, None).await.unwrap(); - let event = EventBuilder::new(Kind::Custom(9), "test").tags( []) + let event = EventBuilder::new(Kind::Custom(9), "test") + .tags([]) .sign_with_keys(&keys) .unwrap(); let expected_id = event.id.to_hex(); @@ -1620,7 +1628,8 @@ mod tests { // Build 3 minimal valid events. let relay_keys = Keys::generate(); for i in 0u8..3 { - let ev = EventBuilder::new(Kind::TextNote, format!("msg {i}")).tags( []) + let ev = EventBuilder::new(Kind::TextNote, format!("msg {i}")) + .tags([]) .sign_with_keys(&relay_keys) .unwrap(); let frame = serde_json::to_string(&serde_json::json!([ @@ -1670,7 +1679,8 @@ mod tests { let keys = Keys::generate(); let client = RelayClient::connect(&url, &keys, None, None).await.unwrap(); - let event = EventBuilder::new(Kind::Custom(9), "timeout-test").tags( []) + let event = EventBuilder::new(Kind::Custom(9), "timeout-test") + .tags([]) .sign_with_keys(&keys) .unwrap(); @@ -1867,7 +1877,8 @@ mod tests { .unwrap(); // send_event should succeed on the new connection. - let event = EventBuilder::new(Kind::Custom(9), "after-reconnect").tags( []) + let event = EventBuilder::new(Kind::Custom(9), "after-reconnect") + .tags([]) .sign_with_keys(&keys) .unwrap(); let ok = client.send_event(event).await.unwrap(); diff --git a/crates/sprout-mcp/src/server.rs b/crates/sprout-mcp/src/server.rs index e1d5b203c..57f95cec9 100644 --- a/crates/sprout-mcp/src/server.rs +++ b/crates/sprout-mcp/src/server.rs @@ -1579,7 +1579,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi Tag::parse(["d", &workflow_id]).unwrap(), Tag::parse(["h", &p.channel_id]).unwrap(), ]; - let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_DEF), &p.yaml_definition).tags( tags); + let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_DEF), &p.yaml_definition).tags(tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign workflow event: {e}"), @@ -1607,7 +1607,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi } // Publish a new kind:30620 event with the same d-tag to replace the existing one. let tags = vec![Tag::parse(["d", &p.workflow_id]).unwrap()]; - let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_DEF), &p.yaml_definition).tags( tags); + let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_DEF), &p.yaml_definition).tags(tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign workflow event: {e}"), @@ -1637,7 +1637,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi p.workflow_id ); let tags = vec![Tag::parse(["a", &coordinate]).unwrap()]; - let builder = EventBuilder::new(k(kind::KIND_DELETION), "").tags( tags); + let builder = EventBuilder::new(k(kind::KIND_DELETION), "").tags(tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign deletion event: {e}"), @@ -1666,7 +1666,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi .unwrap_or(serde_json::Value::Object(Default::default())); let content = serde_json::to_string(&inputs).unwrap_or_default(); let tags = vec![Tag::parse(["d", &p.workflow_id]).unwrap()]; - let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_TRIGGER), &content).tags( tags); + let builder = EventBuilder::new(k(kind::KIND_WORKFLOW_TRIGGER), &content).tags(tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign trigger event: {e}"), @@ -1746,7 +1746,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi // The relay expects d-tag = hex(SHA256(token)), not the raw token UUID. let token_hash = hex::encode(Sha256::digest(p.approval_token.as_bytes())); let tags = vec![Tag::parse(["d", &token_hash]).unwrap()]; - let builder = EventBuilder::new(k(kind_num), content).tags( tags); + let builder = EventBuilder::new(k(kind_num), content).tags(tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign approval event: {e}"), @@ -1780,7 +1780,9 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi // Query events that mention this agent (p-tag) or are in channels we're in. let my_pubkey = self.client.pubkey_hex(); - let mut filter = Filter::new().custom_tags(tag_p(), [&my_pubkey]).limit(limit); + let mut filter = Filter::new() + .custom_tags(tag_p(), [&my_pubkey]) + .limit(limit); if let Some(since) = p.since { filter = filter.since(nostr::Timestamp::from(since as u64)); @@ -2288,7 +2290,7 @@ with kind:45003 comments)." let dm_id = uuid::Uuid::new_v4().to_string(); tags.push(Tag::parse(["d", &dm_id]).unwrap()); - let builder = EventBuilder::new(k(kind::KIND_DM_OPEN), "").tags( tags); + let builder = EventBuilder::new(k(kind::KIND_DM_OPEN), "").tags(tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign open_dm event: {e}"), @@ -2319,7 +2321,7 @@ with kind:45003 comments)." Tag::parse(["h", &p.channel_id]).unwrap(), Tag::parse(["p", &p.pubkey]).unwrap(), ]; - let builder = EventBuilder::new(k(kind::KIND_DM_ADD_MEMBER), "").tags( tags); + let builder = EventBuilder::new(k(kind::KIND_DM_ADD_MEMBER), "").tags(tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign add_dm_member event: {e}"), @@ -2390,7 +2392,7 @@ with kind:45003 comments)." } // Command event kind:41012 with h-tag = DM channel. let tags = vec![Tag::parse(["h", &p.channel_id]).unwrap()]; - let builder = EventBuilder::new(k(kind::KIND_DM_HIDE), "").tags( tags); + let builder = EventBuilder::new(k(kind::KIND_DM_HIDE), "").tags(tags); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign hide_dm event: {e}"), @@ -2730,7 +2732,7 @@ with kind:45003 comments)." pub async fn set_presence(&self, Parameters(p): Parameters) -> String { // Validate status value. // Publish ephemeral presence event (kind:20001). - let builder = EventBuilder::new(k(kind::KIND_PRESENCE_UPDATE), p.status.as_str()).tags( []); + let builder = EventBuilder::new(k(kind::KIND_PRESENCE_UPDATE), p.status.as_str()).tags([]); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign presence event: {e}"), @@ -2762,7 +2764,7 @@ with kind:45003 comments)." } // Store as a kind:10100 (agent profile) replaceable event with the policy in content. let content = serde_json::json!({ "channel_add_policy": p.policy }).to_string(); - let builder = EventBuilder::new(k(kind::KIND_AGENT_PROFILE), &content).tags( []); + let builder = EventBuilder::new(k(kind::KIND_AGENT_PROFILE), &content).tags([]); let event = match self.client.sign_event(builder) { Ok(e) => e, Err(e) => return format!("Error: failed to sign agent profile event: {e}"), diff --git a/crates/sprout-mcp/src/upload.rs b/crates/sprout-mcp/src/upload.rs index 993513860..d602c8207 100644 --- a/crates/sprout-mcp/src/upload.rs +++ b/crates/sprout-mcp/src/upload.rs @@ -207,7 +207,8 @@ pub async fn upload_file( ); } - let auth_event = EventBuilder::new(Kind::from(24242), "Upload file").tags( tags) + let auth_event = EventBuilder::new(Kind::from(24242), "Upload file") + .tags(tags) .sign_with_keys(keys) .map_err(|e| UploadError::SigningFailed(e.to_string()))?; diff --git a/crates/sprout-media/src/auth.rs b/crates/sprout-media/src/auth.rs index 45286d1c3..4a9d21983 100644 --- a/crates/sprout-media/src/auth.rs +++ b/crates/sprout-media/src/auth.rs @@ -147,7 +147,8 @@ mod tests { Tag::parse(["x", sha256]).unwrap(), Tag::parse(["expiration", &exp_str]).unwrap(), ]; - EventBuilder::new(Kind::from(24242), "Upload sprout-media").tags( tags) + EventBuilder::new(Kind::from(24242), "Upload sprout-media") + .tags(tags) .sign_with_keys(keys) .unwrap() } @@ -191,7 +192,8 @@ mod tests { Tag::parse(["x", &sha256]).unwrap(), Tag::parse(["expiration", &exp_str]).unwrap(), ]; - let event = EventBuilder::new(Kind::from(27235), "wrong kind").tags( tags) + let event = EventBuilder::new(Kind::from(27235), "wrong kind") + .tags(tags) .sign_with_keys(&keys) .unwrap(); assert!(matches!( @@ -213,7 +215,8 @@ mod tests { Tag::parse(["x", &sha256]).unwrap(), Tag::parse(["expiration", &exp_str]).unwrap(), ]; - let event = EventBuilder::new(Kind::from(24242), "Upload multi-x").tags( tags) + let event = EventBuilder::new(Kind::from(24242), "Upload multi-x") + .tags(tags) .sign_with_keys(&keys) .unwrap(); // Should pass because at least one x tag matches @@ -232,7 +235,8 @@ mod tests { Tag::parse(["expiration", &exp_str]).unwrap(), Tag::parse(["server", "other.example.com"]).unwrap(), ]; - let event = EventBuilder::new(Kind::from(24242), "Upload scoped").tags( tags) + let event = EventBuilder::new(Kind::from(24242), "Upload scoped") + .tags(tags) .sign_with_keys(&keys) .unwrap(); // Should fail — server tag present but doesn't match our domain @@ -272,7 +276,8 @@ mod tests { Tag::parse(["expiration", &exp_str]).unwrap(), ]; // Empty content — BUD-11 requires a human-readable string - let event = EventBuilder::new(Kind::from(24242), "").tags( tags) + let event = EventBuilder::new(Kind::from(24242), "") + .tags(tags) .sign_with_keys(&keys) .unwrap(); assert!(matches!( diff --git a/crates/sprout-proxy/src/channel_map.rs b/crates/sprout-proxy/src/channel_map.rs index 2b0973f0a..0068a4d7d 100644 --- a/crates/sprout-proxy/src/channel_map.rs +++ b/crates/sprout-proxy/src/channel_map.rs @@ -86,7 +86,8 @@ impl ChannelMap { // nostr 0.36: EventBuilder::new(kind, content).tags( tags) // SAFETY: signing with a pre-validated Keys instance cannot fail - EventBuilder::new(Kind::ChannelCreation, content).tags( []) + EventBuilder::new(Kind::ChannelCreation, content) + .tags([]) .custom_created_at(Timestamp::from(created_at_unix)) .sign_with_keys(&self.server_keys) .expect("SAFETY: signing with valid keys cannot fail") @@ -109,7 +110,8 @@ impl ChannelMap { ); // SAFETY: signing with a pre-validated Keys instance cannot fail - EventBuilder::new(Kind::ChannelMetadata, content).tags( [e_tag]) + EventBuilder::new(Kind::ChannelMetadata, content) + .tags([e_tag]) .custom_created_at(Timestamp::from(info.created_at_unix)) .sign_with_keys(&self.server_keys) .expect("SAFETY: signing with valid keys cannot fail") diff --git a/crates/sprout-proxy/src/translate.rs b/crates/sprout-proxy/src/translate.rs index 22bf1b2ff..01b971482 100644 --- a/crates/sprout-proxy/src/translate.rs +++ b/crates/sprout-proxy/src/translate.rs @@ -233,9 +233,8 @@ impl Translator { }; let mut new_tags: Vec = Vec::new(); - new_tags.push( - Tag::parse(["e", &channel_info.kind40_event_id]).expect("e tag is always valid"), - ); + new_tags + .push(Tag::parse(["e", &channel_info.kind40_event_id]).expect("e tag is always valid")); for tag in event.tags.iter() { let slice = tag.as_slice(); if slice.first().map(|value| value.as_str()) == Some("h") @@ -262,8 +261,9 @@ impl Translator { let translated = EventBuilder::new( Kind::Custom(u16::try_from(standard_kind).expect("standard kind must fit in u16")), - content).tags( - new_tags) + content, + ) + .tags(new_tags) .custom_created_at(event.created_at) .sign_with_keys(&shadow_keys) .map_err(|e| ProxyError::Upstream(format!("outbound signing failed: {e}")))?; @@ -349,7 +349,8 @@ impl Translator { .shadow_keys .get_or_create(&self.resolve_shadow_author_hex(event))?; - let translated = EventBuilder::new(kind, event.content.clone()).tags( new_tags) + let translated = EventBuilder::new(kind, event.content.clone()) + .tags(new_tags) .custom_created_at(event.created_at) .sign_with_keys(&shadow_keys) .map_err(|e| ProxyError::Upstream(format!("outbound signing failed: {e}")))?; @@ -506,8 +507,9 @@ impl Translator { u16::try_from(sprout_kind) .expect("SAFETY: sprout kind values (9, 40002, 40003) always fit in u16"), ), - &event.content).tags( - new_tags) + &event.content, + ) + .tags(new_tags) .custom_created_at(event.created_at) .sign_with_keys(&shadow_keys) .map_err(|e| ProxyError::Upstream(format!("inbound signing failed: {e}")))?; @@ -648,7 +650,8 @@ impl Translator { } let shadow_keys = self.shadow_keys.get_or_create(external_pubkey)?; - let translated = EventBuilder::new(kind, &event.content).tags( new_tags) + let translated = EventBuilder::new(kind, &event.content) + .tags(new_tags) .custom_created_at(event.created_at) .sign_with_keys(&shadow_keys) .map_err(|e| ProxyError::Upstream(format!("inbound signing failed: {e}")))?; @@ -848,12 +851,11 @@ mod tests { // Build a synthetic kind:9 event with an #h tag. let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); - let sprout_event = EventBuilder::new( - Kind::Custom(KIND_STREAM_MESSAGE as u16), - "hello world").tags( - [h_tag]) - .sign_with_keys(&author_keys) - .unwrap(); + let sprout_event = + EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE as u16), "hello world") + .tags([h_tag]) + .sign_with_keys(&author_keys) + .unwrap(); let result = translator .translate_outbound(&sprout_event, &allowed()) @@ -901,7 +903,8 @@ mod tests { // Build a synthetic kind:42 event with an #e tag. let e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); - let nip28_event = EventBuilder::new(Kind::Custom(42), "hello from client").tags( [e_tag]) + let nip28_event = EventBuilder::new(Kind::Custom(42), "hello from client") + .tags([e_tag]) .sign_with_keys(&client_keys) .unwrap(); @@ -950,10 +953,10 @@ mod tests { let author_keys = Keys::generate(); let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); - let sprout_event = - EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE as u16), "secret").tags( [h_tag]) - .sign_with_keys(&author_keys) - .unwrap(); + let sprout_event = EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE as u16), "secret") + .tags([h_tag]) + .sign_with_keys(&author_keys) + .unwrap(); let result = translator .translate_outbound(&sprout_event, &no_channels()) @@ -975,7 +978,8 @@ mod tests { let external_pubkey = client_keys.public_key().to_hex(); let e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); - let nip28_event = EventBuilder::new(Kind::Custom(42), "sneaky").tags( [e_tag]) + let nip28_event = EventBuilder::new(Kind::Custom(42), "sneaky") + .tags([e_tag]) .sign_with_keys(&client_keys) .unwrap(); @@ -998,12 +1002,11 @@ mod tests { let v2_content = r#"{"text":"hello v2","attachments":[]}"#; let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); - let sprout_event = EventBuilder::new( - Kind::Custom(KIND_STREAM_MESSAGE_V2 as u16), - v2_content).tags( - [h_tag]) - .sign_with_keys(&author_keys) - .unwrap(); + let sprout_event = + EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE_V2 as u16), v2_content) + .tags([h_tag]) + .sign_with_keys(&author_keys) + .unwrap(); let translated = translator .translate_outbound(&sprout_event, &allowed()) @@ -1168,12 +1171,10 @@ mod tests { let channel_e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); let reply_e_tag = Tag::parse(["e", reply_event_id]).unwrap(); - let nip28_event = EventBuilder::new( - Kind::Custom(42), - "replying to a message").tags( - [channel_e_tag, reply_e_tag]) - .sign_with_keys(&client_keys) - .unwrap(); + let nip28_event = EventBuilder::new(Kind::Custom(42), "replying to a message") + .tags([channel_e_tag, reply_e_tag]) + .sign_with_keys(&client_keys) + .unwrap(); let translated = translator .translate_inbound(&nip28_event, &external_pubkey, &allowed()) @@ -1224,8 +1225,9 @@ mod tests { let sprout_event = EventBuilder::new( Kind::Custom(KIND_STREAM_MESSAGE as u16), - "message with extra h tag").tags( - [channel_h_tag, other_h_tag]) + "message with extra h tag", + ) + .tags([channel_h_tag, other_h_tag]) .sign_with_keys(&author_keys) .unwrap(); @@ -1297,7 +1299,8 @@ mod tests { // Build a kind:41 event with #e referencing the channel. let e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); - let nip28_event = EventBuilder::new(Kind::ChannelMetadata, "updated metadata").tags( [e_tag]) + let nip28_event = EventBuilder::new(Kind::ChannelMetadata, "updated metadata") + .tags([e_tag]) .sign_with_keys(&external_keys) .unwrap(); @@ -1336,7 +1339,8 @@ mod tests { let external_keys = Keys::generate(); let e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); - let event = EventBuilder::new(Kind::Custom(9999), "nope").tags( [e_tag]) + let event = EventBuilder::new(Kind::Custom(9999), "nope") + .tags([e_tag]) .sign_with_keys(&external_keys) .unwrap(); @@ -1357,8 +1361,9 @@ mod tests { let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); let sprout_event = EventBuilder::new( Kind::Custom(KIND_STREAM_MESSAGE_EDIT as u16), - "edited content").tags( - [h_tag]) + "edited content", + ) + .tags([h_tag]) .sign_with_keys(&author_keys) .unwrap(); @@ -1405,10 +1410,10 @@ mod tests { // Client tries to inject an #h tag for an unauthorized channel. let e_tag = Tag::parse(["e", &kind40_event_id]).unwrap(); let injected_h = Tag::parse(["h", "00000000-0000-0000-0000-000000000001"]).unwrap(); - let nip28_event = - EventBuilder::new(Kind::Custom(42), "sneaky message").tags( [e_tag, injected_h]) - .sign_with_keys(&external_keys) - .unwrap(); + let nip28_event = EventBuilder::new(Kind::Custom(42), "sneaky message") + .tags([e_tag, injected_h]) + .sign_with_keys(&external_keys) + .unwrap(); let translated = translator .translate_inbound( @@ -1441,7 +1446,8 @@ mod tests { // A kind:9999 event (unknown Sprout kind) should be dropped. let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); - let event = EventBuilder::new(Kind::Custom(9999), "internal stuff").tags( [h_tag]) + let event = EventBuilder::new(Kind::Custom(9999), "internal stuff") + .tags([h_tag]) .sign_with_keys(&author_keys) .unwrap(); diff --git a/crates/sprout-proxy/src/upstream.rs b/crates/sprout-proxy/src/upstream.rs index f017bdf50..ae15d72b1 100644 --- a/crates/sprout-proxy/src/upstream.rs +++ b/crates/sprout-proxy/src/upstream.rs @@ -440,8 +440,9 @@ async fn respond_to_auth_challenge( let auth_event = EventBuilder::new( Kind::Authentication, // kind:22242 - "").tags( - [relay_tag, challenge_tag, token_tag]) + "", + ) + .tags([relay_tag, challenge_tag, token_tag]) .sign_with_keys(&inner.auth_keys) .map_err(|e| crate::ProxyError::Auth(format!("sign auth event: {e}")))?; @@ -478,7 +479,8 @@ mod tests { // Queue an EVENT message. let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "hello").tags( []) + let event = EventBuilder::new(Kind::TextNote, "hello") + .tags([]) .sign_with_keys(&keys) .unwrap(); let event_id = event.id; diff --git a/crates/sprout-pubsub/src/lib.rs b/crates/sprout-pubsub/src/lib.rs index 608f32f99..01bb42449 100644 --- a/crates/sprout-pubsub/src/lib.rs +++ b/crates/sprout-pubsub/src/lib.rs @@ -184,7 +184,8 @@ mod tests { let channel_id = Uuid::new_v4(); let keys = Keys::generate(); - let event = EventBuilder::new(Kind::TextNote, "hello pubsub").tags( []) + let event = EventBuilder::new(Kind::TextNote, "hello pubsub") + .tags([]) .sign_with_keys(&keys) .expect("signing failed"); let event_id = event.id; diff --git a/crates/sprout-relay/src/api/bridge.rs b/crates/sprout-relay/src/api/bridge.rs index 8ff98776f..169fccd5f 100644 --- a/crates/sprout-relay/src/api/bridge.rs +++ b/crates/sprout-relay/src/api/bridge.rs @@ -756,13 +756,12 @@ async fn synthesize_presence(state: &AppState, filters: &[nostr::Filter]) -> Opt for (pubkey_hex, status) in &presence_map { // Build a synthetic event: relay-signed, content = status, p-tag = subject. let tags = vec![nostr::Tag::parse(["p", pubkey_hex]).ok()?]; - let event = nostr::EventBuilder::new( - nostr::Kind::Custom(KIND_PRESENCE_UPDATE as u16), - status).tags( - tags) - .custom_created_at(nostr::Timestamp::from(now)) - .sign_with_keys(&state.relay_keypair) - .ok()?; + let event = + nostr::EventBuilder::new(nostr::Kind::Custom(KIND_PRESENCE_UPDATE as u16), status) + .tags(tags) + .custom_created_at(nostr::Timestamp::from(now)) + .sign_with_keys(&state.relay_keypair) + .ok()?; if let Ok(v) = serde_json::to_value(&event) { events.push(v); @@ -787,7 +786,8 @@ mod tests { nostr::TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::P)), [owner_hex], ); - let ev = EventBuilder::new(Kind::Custom(30174), "engram body").tags( [d_tag, p_tag]) + let ev = EventBuilder::new(Kind::Custom(30174), "engram body") + .tags([d_tag, p_tag]) .sign_with_keys(agent) .expect("sign engram"); sprout_core::StoredEvent::new(ev, None) diff --git a/crates/sprout-relay/src/audio/handler.rs b/crates/sprout-relay/src/audio/handler.rs index 6802436e2..2eda31c9d 100644 --- a/crates/sprout-relay/src/audio/handler.rs +++ b/crates/sprout-relay/src/audio/handler.rs @@ -741,7 +741,10 @@ async fn emit_participant_event( }; let tags = vec![h_tag, p_tag]; - let event = match EventBuilder::new(kind, content).tags( tags).sign_with_keys(&state.relay_keypair) { + let event = match EventBuilder::new(kind, content) + .tags(tags) + .sign_with_keys(&state.relay_keypair) + { Ok(e) => e, Err(e) => { warn!("audio: failed to sign lifecycle event: {e}"); diff --git a/crates/sprout-relay/src/handlers/event.rs b/crates/sprout-relay/src/handlers/event.rs index 0674c94e8..4e053f496 100644 --- a/crates/sprout-relay/src/handlers/event.rs +++ b/crates/sprout-relay/src/handlers/event.rs @@ -778,16 +778,14 @@ mod tests { &serde_json::json!({"type": "acp_read"}), ) .expect("encrypt observer payload"); - let event = EventBuilder::new( - Kind::Custom(KIND_AGENT_OBSERVER_FRAME as u16), - encrypted).tags( - [ + let event = EventBuilder::new(Kind::Custom(KIND_AGENT_OBSERVER_FRAME as u16), encrypted) + .tags([ Tag::parse(["p", &owner.public_key().to_hex()]).expect("p tag"), Tag::parse([OBSERVER_AGENT_TAG, &agent.public_key().to_hex()]).expect("agent tag"), Tag::parse([OBSERVER_FRAME_TAG, OBSERVER_FRAME_TELEMETRY]).expect("frame tag"), ]) - .sign_with_keys(&agent) - .expect("sign event"); + .sign_with_keys(&agent) + .expect("sign event"); let route = super::agent_observer_route(&event) .expect("observer route") @@ -807,16 +805,14 @@ mod tests { &serde_json::json!({"type": "cancel_turn"}), ) .expect("encrypt observer payload"); - let event = EventBuilder::new( - Kind::Custom(KIND_AGENT_OBSERVER_FRAME as u16), - encrypted).tags( - [ + let event = EventBuilder::new(Kind::Custom(KIND_AGENT_OBSERVER_FRAME as u16), encrypted) + .tags([ Tag::parse(["p", &agent.public_key().to_hex()]).expect("p tag"), Tag::parse([OBSERVER_AGENT_TAG, &agent.public_key().to_hex()]).expect("agent tag"), Tag::parse([OBSERVER_FRAME_TAG, OBSERVER_FRAME_CONTROL]).expect("frame tag"), ]) - .sign_with_keys(&owner) - .expect("sign event"); + .sign_with_keys(&owner) + .expect("sign event"); let route = super::agent_observer_route(&event) .expect("observer route") @@ -832,12 +828,13 @@ mod tests { let owner = Keys::generate(); let event = EventBuilder::new( Kind::Custom(KIND_AGENT_OBSERVER_FRAME as u16), - "not encrypted").tags( - [ - Tag::parse(["p", &owner.public_key().to_hex()]).expect("p tag"), - Tag::parse([OBSERVER_AGENT_TAG, &agent.public_key().to_hex()]).expect("agent tag"), - Tag::parse([OBSERVER_FRAME_TAG, OBSERVER_FRAME_TELEMETRY]).expect("frame tag"), - ]) + "not encrypted", + ) + .tags([ + Tag::parse(["p", &owner.public_key().to_hex()]).expect("p tag"), + Tag::parse([OBSERVER_AGENT_TAG, &agent.public_key().to_hex()]).expect("agent tag"), + Tag::parse([OBSERVER_FRAME_TAG, OBSERVER_FRAME_TELEMETRY]).expect("frame tag"), + ]) .sign_with_keys(&agent) .expect("sign event"); diff --git a/crates/sprout-relay/src/handlers/ingest.rs b/crates/sprout-relay/src/handlers/ingest.rs index 426fbbde0..99ef3c727 100644 --- a/crates/sprout-relay/src/handlers/ingest.rs +++ b/crates/sprout-relay/src/handlers/ingest.rs @@ -2024,7 +2024,8 @@ mod tests { fn make_dummy_event() -> Event { let keys = nostr::Keys::generate(); - nostr::EventBuilder::new(nostr::Kind::Custom(9), "").tags( []) + nostr::EventBuilder::new(nostr::Kind::Custom(9), "") + .tags([]) .sign_with_keys(&keys) .unwrap() } @@ -2033,7 +2034,8 @@ mod tests { let keys = nostr::Keys::generate(); let nostr_tags: Vec = tags.iter().map(|t| nostr::Tag::parse(t).unwrap()).collect(); - nostr::EventBuilder::new(nostr::Kind::Custom(kind as u16), content).tags( nostr_tags) + nostr::EventBuilder::new(nostr::Kind::Custom(kind as u16), content) + .tags(nostr_tags) .sign_with_keys(&keys) .unwrap() } diff --git a/crates/sprout-relay/src/handlers/relay_admin.rs b/crates/sprout-relay/src/handlers/relay_admin.rs index 7ef73bb4c..66c76a018 100644 --- a/crates/sprout-relay/src/handlers/relay_admin.rs +++ b/crates/sprout-relay/src/handlers/relay_admin.rs @@ -296,7 +296,8 @@ mod tests { .into_iter() .map(|parts| Tag::parse(&parts).expect("valid tag")) .collect(); - EventBuilder::new(Kind::from(kind), "").tags( nostr_tags) + EventBuilder::new(Kind::from(kind), "") + .tags(nostr_tags) .sign_with_keys(&keys) .expect("signing failed") } diff --git a/crates/sprout-relay/src/handlers/side_effects.rs b/crates/sprout-relay/src/handlers/side_effects.rs index 1c74d57bb..f3e0feb24 100644 --- a/crates/sprout-relay/src/handlers/side_effects.rs +++ b/crates/sprout-relay/src/handlers/side_effects.rs @@ -421,7 +421,8 @@ pub async fn emit_system_message( ) -> anyhow::Result<()> { let channel_tag = Tag::parse(["h", &channel_id.to_string()])?; - let event = EventBuilder::new(Kind::Custom(40099), content.to_string()).tags( [channel_tag]) + let event = EventBuilder::new(Kind::Custom(40099), content.to_string()) + .tags([channel_tag]) .sign_with_keys(&state.relay_keypair) .map_err(|e| anyhow::anyhow!("failed to sign system message: {e}"))?; @@ -475,12 +476,10 @@ pub async fn emit_membership_notification( }) .to_string(); - let event = EventBuilder::new( - Kind::Custom(notification_kind as u16), - content).tags( - [p_tag, h_tag]) - .sign_with_keys(&state.relay_keypair) - .map_err(|e| anyhow::anyhow!("failed to sign membership notification: {e}"))?; + let event = EventBuilder::new(Kind::Custom(notification_kind as u16), content) + .tags([p_tag, h_tag]) + .sign_with_keys(&state.relay_keypair) + .map_err(|e| anyhow::anyhow!("failed to sign membership notification: {e}"))?; // Store with channel_id = None → globally scoped, reachable by global subscribers. let (stored, was_inserted) = state.db.insert_event(&event, None).await?; @@ -548,7 +547,8 @@ async fn emit_addressable_discovery_event( }; let ts = now.max(min_ts); - let event = EventBuilder::new(Kind::Custom(kind as u16), "").tags( tags) + let event = EventBuilder::new(Kind::Custom(kind as u16), "") + .tags(tags) .custom_created_at(nostr::Timestamp::from(ts)) .sign_with_keys(&state.relay_keypair) .map_err(|e| anyhow::anyhow!("failed to sign kind:{kind}: {e}"))?; @@ -1943,7 +1943,8 @@ pub async fn publish_nip43_membership_list(state: &Arc) -> anyhow::Res ); } - let event = EventBuilder::new(Kind::Custom(KIND_NIP43_MEMBERSHIP_LIST as u16), "").tags( tags) + let event = EventBuilder::new(Kind::Custom(KIND_NIP43_MEMBERSHIP_LIST as u16), "") + .tags(tags) .sign_with_keys(&state.relay_keypair) .map_err(|e| anyhow::anyhow!("failed to sign kind:13534: {e}"))?; @@ -1988,7 +1989,8 @@ async fn publish_nip43_delta( .map_err(|e| anyhow::anyhow!("failed to build p tag: {e}"))?, ]; - let event = EventBuilder::new(Kind::Custom(kind), "").tags( tags) + let event = EventBuilder::new(Kind::Custom(kind), "") + .tags(tags) .sign_with_keys(&state.relay_keypair) .map_err(|e| anyhow::anyhow!("failed to sign kind:{kind}: {e}"))?; diff --git a/crates/sprout-relay/src/subscription.rs b/crates/sprout-relay/src/subscription.rs index 63a700971..f0cee1651 100644 --- a/crates/sprout-relay/src/subscription.rs +++ b/crates/sprout-relay/src/subscription.rs @@ -448,7 +448,8 @@ mod tests { fn make_stored_event(kind: Kind, channel_id: Option) -> StoredEvent { let keys = Keys::generate(); - let event = EventBuilder::new(kind, "test").tags( []) + let event = EventBuilder::new(kind, "test") + .tags([]) .sign_with_keys(&keys) .expect("sign"); StoredEvent::with_received_at(event, Utc::now(), channel_id, true) @@ -456,7 +457,8 @@ mod tests { fn make_stored_event_with_p(kind: Kind, p: &str, channel_id: Option) -> StoredEvent { let keys = Keys::generate(); - let event = EventBuilder::new(kind, "test").tags( [Tag::parse(["p", p]).expect("valid p tag")]) + let event = EventBuilder::new(kind, "test") + .tags([Tag::parse(["p", p]).expect("valid p tag")]) .sign_with_keys(&keys) .expect("sign"); StoredEvent::with_received_at(event, Utc::now(), channel_id, true) diff --git a/crates/sprout-relay/src/workflow_sink.rs b/crates/sprout-relay/src/workflow_sink.rs index 720e89219..4fcd2597a 100644 --- a/crates/sprout-relay/src/workflow_sink.rs +++ b/crates/sprout-relay/src/workflow_sink.rs @@ -114,7 +114,8 @@ impl ActionSink for RelayActionSink { ]; let kind = Kind::from(KIND_STREAM_MESSAGE as u16); - let event = EventBuilder::new(kind, &text).tags( tags) + let event = EventBuilder::new(kind, &text) + .tags(tags) .sign_with_keys(&state.relay_keypair) .map_err(|e| ActionSinkError::EventBuild(format!("signing: {e}")))?; diff --git a/crates/sprout-sdk/src/builders.rs b/crates/sprout-sdk/src/builders.rs index 7db2ea368..963315ce3 100644 --- a/crates/sprout-sdk/src/builders.rs +++ b/crates/sprout-sdk/src/builders.rs @@ -122,7 +122,7 @@ pub fn build_message( tags.push(tag(&["broadcast", "1"])?); } imeta_tags(media_tags, &mut tags)?; - Ok(EventBuilder::new(Kind::Custom(9), content).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(9), content).tags(tags)) } // ── Builder: build_agent_observer_frame ───────────────────────────────────── @@ -159,8 +159,9 @@ pub fn build_agent_observer_frame( Ok(EventBuilder::new( Kind::Custom(KIND_AGENT_OBSERVER_FRAME as u16), - encrypted_content).tags( - tags)) + encrypted_content, + ) + .tags(tags)) } // ── Builder 2: build_forum_post ─────────────────────────────────────────────── @@ -176,7 +177,7 @@ pub fn build_forum_post( let mut tags = vec![tag(&["h", &channel_id.to_string()])?]; mention_tags(mentions, &mut tags)?; imeta_tags(media_tags, &mut tags)?; - Ok(EventBuilder::new(Kind::Custom(45001), content).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(45001), content).tags(tags)) } // ── Builder 3: build_forum_comment ─────────────────────────────────────────── @@ -194,7 +195,7 @@ pub fn build_forum_comment( thread_tags(thread_ref, &mut tags)?; mention_tags(mentions, &mut tags)?; imeta_tags(media_tags, &mut tags)?; - Ok(EventBuilder::new(Kind::Custom(45003), content).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(45003), content).tags(tags)) } // ── Builder 4: build_diff_message ──────────────────────────────────────────── @@ -266,7 +267,7 @@ pub fn build_diff_message( if let Some(tr) = thread_ref { thread_tags(tr, &mut tags)?; } - Ok(EventBuilder::new(Kind::Custom(40008), content).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(40008), content).tags(tags)) } // ── Builder 5: build_edit ──────────────────────────────────────────────────── @@ -282,7 +283,7 @@ pub fn build_edit( tag(&["h", &channel_id.to_string()])?, tag(&["e", &target_event_id.to_hex()])?, ]; - Ok(EventBuilder::new(Kind::Custom(40003), new_content).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(40003), new_content).tags(tags)) } // ── Builder 6: build_delete_message ────────────────────────────────────────── @@ -296,7 +297,7 @@ pub fn build_delete_message( tag(&["h", &channel_id.to_string()])?, tag(&["e", &target_event_id.to_hex()])?, ]; - Ok(EventBuilder::new(Kind::Custom(9005), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(9005), "").tags(tags)) } // ── Builder 7: build_delete_compat ─────────────────────────────────────────── @@ -304,7 +305,7 @@ pub fn build_delete_message( /// Build a NIP-09 compatible deletion event (kind 5). pub fn build_delete_compat(target_event_id: nostr::EventId) -> Result { let tags = vec![tag(&["e", &target_event_id.to_hex()])?]; - Ok(EventBuilder::new(Kind::Custom(5), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(5), "").tags(tags)) } // ── Builder 8: build_vote ──────────────────────────────────────────────────── @@ -323,7 +324,7 @@ pub fn build_vote( tag(&["h", &channel_id.to_string()])?, tag(&["e", &target_event_id.to_hex()])?, ]; - Ok(EventBuilder::new(Kind::Custom(45002), content).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(45002), content).tags(tags)) } // ── Builder 9: build_reaction ──────────────────────────────────────────────── @@ -337,7 +338,7 @@ pub fn build_reaction( return Err(SdkError::EmojiTooLong); } let tags = vec![tag(&["e", &target_event_id.to_hex()])?]; - Ok(EventBuilder::new(Kind::Custom(7), emoji).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(7), emoji).tags(tags)) } // ── Builder 10: build_remove_reaction ──────────────────────────────────────── @@ -345,7 +346,7 @@ pub fn build_reaction( /// Build a deletion event targeting a reaction (kind 5). pub fn build_remove_reaction(reaction_event_id: nostr::EventId) -> Result { let tags = vec![tag(&["e", &reaction_event_id.to_hex()])?]; - Ok(EventBuilder::new(Kind::Custom(5), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(5), "").tags(tags)) } // ── Builder 11: build_set_canvas ───────────────────────────────────────────── @@ -353,7 +354,7 @@ pub fn build_remove_reaction(reaction_event_id: nostr::EventId) -> Result Result { let tags = vec![tag(&["h", &channel_id.to_string()])?]; - Ok(EventBuilder::new(Kind::Custom(40100), content).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(40100), content).tags(tags)) } // ── Builder 12: build_profile ──────────────────────────────────────────────── @@ -385,7 +386,7 @@ pub fn build_profile( map.insert("nip05".into(), serde_json::Value::String(v.into())); } let content = serde_json::Value::Object(map).to_string(); - Ok(EventBuilder::new(Kind::Custom(0), content).tags( [])) + Ok(EventBuilder::new(Kind::Custom(0), content).tags([])) } // ── Builder 13: build_add_member ───────────────────────────────────────────── @@ -404,7 +405,7 @@ pub fn build_add_member( if let Some(r) = role { tags.push(tag(&["role", r.as_str()])?); } - Ok(EventBuilder::new(Kind::Custom(9000), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(9000), "").tags(tags)) } // ── Builder 14: build_remove_member ────────────────────────────────────────── @@ -419,7 +420,7 @@ pub fn build_remove_member( tag(&["h", &channel_id.to_string()])?, tag(&["p", &target_pubkey.to_ascii_lowercase()])?, ]; - Ok(EventBuilder::new(Kind::Custom(9001), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(9001), "").tags(tags)) } // ── Builder 15: build_leave ────────────────────────────────────────────────── @@ -427,7 +428,7 @@ pub fn build_remove_member( /// Build a NIP-29 leave-request event (kind 9022). pub fn build_leave(channel_id: Uuid) -> Result { let tags = vec![tag(&["h", &channel_id.to_string()])?]; - Ok(EventBuilder::new(Kind::Custom(9022), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(9022), "").tags(tags)) } // ── Builder 16: build_update_channel ───────────────────────────────────────── @@ -450,7 +451,7 @@ pub fn build_update_channel( if let Some(a) = about { tags.push(tag(&["about", a])?); } - Ok(EventBuilder::new(Kind::Custom(9002), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(9002), "").tags(tags)) } // ── Builder 17: build_set_topic ────────────────────────────────────────────── @@ -461,7 +462,7 @@ pub fn build_set_topic(channel_id: Uuid, topic: &str) -> Result Result Result { let tags = vec![tag(&["h", &channel_id.to_string()])?]; - Ok(EventBuilder::new(Kind::Custom(9021), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(9021), "").tags(tags)) } // ── Builder 21: build_archive ──────────────────────────────────────────────── @@ -514,7 +515,7 @@ pub fn build_archive(channel_id: Uuid) -> Result { tag(&["h", &channel_id.to_string()])?, tag(&["archived", "true"])?, ]; - Ok(EventBuilder::new(Kind::Custom(9002), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(9002), "").tags(tags)) } // ── Builder 22: build_unarchive ────────────────────────────────────────────── @@ -525,7 +526,7 @@ pub fn build_unarchive(channel_id: Uuid) -> Result { tag(&["h", &channel_id.to_string()])?, tag(&["archived", "false"])?, ]; - Ok(EventBuilder::new(Kind::Custom(9002), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(9002), "").tags(tags)) } // ── Builder 23: build_delete_channel ───────────────────────────────────────── @@ -533,7 +534,7 @@ pub fn build_unarchive(channel_id: Uuid) -> Result { /// Build a NIP-29 delete-group event (kind 9008). pub fn build_delete_channel(channel_id: Uuid) -> Result { let tags = vec![tag(&["h", &channel_id.to_string()])?]; - Ok(EventBuilder::new(Kind::Custom(9008), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(9008), "").tags(tags)) } // ── Builder 24: build_note ─────────────────────────────────────────────────── @@ -553,7 +554,7 @@ pub fn build_note( if let Some(reply_id) = reply_to_event_id { tags.push(tag(&["e", &reply_id.to_hex(), "", "reply"])?); } - Ok(EventBuilder::new(Kind::Custom(1), content).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(1), content).tags(tags)) } // ── Builder 25: build_contact_list ─────────────────────────────────────────── @@ -618,7 +619,7 @@ pub fn build_contact_list( petname.unwrap_or(""), ])?); } - Ok(EventBuilder::new(Kind::Custom(3), "").tags( tags)) + Ok(EventBuilder::new(Kind::Custom(3), "").tags(tags)) } // ── Huddle shared helper ────────────────────────────────────────────────────── @@ -648,7 +649,7 @@ fn build_huddle_event_sdk( map.insert((*k).into(), serde_json::Value::String(v.to_string())); } let content = serde_json::Value::Object(map).to_string(); - Ok(EventBuilder::new(Kind::Custom(kind), content).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(kind), content).tags(tags)) } // ── Builder 26: build_huddle_started ───────────────────────────────────────── @@ -892,10 +893,7 @@ pub fn build_repo_announcement( tags.push(tag(&relay_tag)?); } - Ok(EventBuilder::new( - Kind::Custom(KIND_GIT_REPO_ANNOUNCEMENT as u16), - "").tags( - tags)) + Ok(EventBuilder::new(Kind::Custom(KIND_GIT_REPO_ANNOUNCEMENT as u16), "").tags(tags)) } // ── Builder 31: build_workflow_def ──────────────────────────────────────────── @@ -915,10 +913,7 @@ pub fn build_workflow_def( tag(&["d", &workflow_id.to_string()])?, tag(&["h", &channel_id.to_string()])?, ]; - Ok(EventBuilder::new( - Kind::Custom(KIND_WORKFLOW_DEF as u16), - yaml).tags( - tags)) + Ok(EventBuilder::new(Kind::Custom(KIND_WORKFLOW_DEF as u16), yaml).tags(tags)) } // ── Builder 32: build_workflow_update ───────────────────────────────────────── @@ -938,10 +933,7 @@ pub fn build_workflow_update( tag(&["d", &workflow_id.to_string()])?, tag(&["h", &channel_id.to_string()])?, ]; - Ok(EventBuilder::new( - Kind::Custom(KIND_WORKFLOW_DEF as u16), - yaml).tags( - tags)) + Ok(EventBuilder::new(Kind::Custom(KIND_WORKFLOW_DEF as u16), yaml).tags(tags)) } // ── Builder 33: build_workflow_delete ───────────────────────────────────────── @@ -959,10 +951,7 @@ pub fn build_workflow_delete( "a", &format!("{}:{pk}:{workflow_id}", KIND_WORKFLOW_DEF), ])?]; - Ok(EventBuilder::new( - Kind::Custom(KIND_DELETION as u16), - "").tags( - tags)) + Ok(EventBuilder::new(Kind::Custom(KIND_DELETION as u16), "").tags(tags)) } // ── Builder 34: build_workflow_trigger ──────────────────────────────────────── @@ -970,10 +959,7 @@ pub fn build_workflow_delete( /// Build a workflow trigger event (kind 46020). pub fn build_workflow_trigger(workflow_id: Uuid) -> Result { let tags = vec![tag(&["d", &workflow_id.to_string()])?]; - Ok(EventBuilder::new( - Kind::Custom(KIND_WORKFLOW_TRIGGER as u16), - "").tags( - tags)) + Ok(EventBuilder::new(Kind::Custom(KIND_WORKFLOW_TRIGGER as u16), "").tags(tags)) } // ── Builder 35: build_workflow_approval ─────────────────────────────────────── @@ -1000,7 +986,7 @@ pub fn build_workflow_approval( KIND_APPROVAL_DENY }; let tags = vec![tag(&["d", token_hash])?]; - Ok(EventBuilder::new(Kind::Custom(kind as u16), note).tags( tags)) + Ok(EventBuilder::new(Kind::Custom(kind as u16), note).tags(tags)) } // ── Builder 36: build_dm_open ──────────────────────────────────────────────── @@ -1019,10 +1005,7 @@ pub fn build_dm_open(pubkeys: &[&str]) -> Result { let validated = check_pubkey_hex(pk, "pubkey")?; tags.push(tag(&["p", &validated])?); } - Ok(EventBuilder::new( - Kind::Custom(KIND_DM_OPEN as u16), - "").tags( - tags)) + Ok(EventBuilder::new(Kind::Custom(KIND_DM_OPEN as u16), "").tags(tags)) } // ── Builder 37: build_dm_add_member ────────────────────────────────────────── @@ -1031,10 +1014,7 @@ pub fn build_dm_open(pubkeys: &[&str]) -> Result { pub fn build_dm_add_member(channel_id: Uuid, pubkey: &str) -> Result { let pk = check_pubkey_hex(pubkey, "pubkey")?; let tags = vec![tag(&["h", &channel_id.to_string()])?, tag(&["p", &pk])?]; - Ok(EventBuilder::new( - Kind::Custom(KIND_DM_ADD_MEMBER as u16), - "").tags( - tags)) + Ok(EventBuilder::new(Kind::Custom(KIND_DM_ADD_MEMBER as u16), "").tags(tags)) } // ── Builder 38: build_presence_update ──────────────────────────────────────── @@ -1054,10 +1034,7 @@ pub fn build_presence_update(status: &str) -> Result { } } let tags = vec![tag(&["status", status])?]; - Ok(EventBuilder::new( - Kind::Custom(KIND_PRESENCE_UPDATE as u16), - status).tags( - tags)) + Ok(EventBuilder::new(Kind::Custom(KIND_PRESENCE_UPDATE as u16), status).tags(tags)) } // ── Tests ──────────────────────────────────────────────────────────────────── @@ -1077,7 +1054,8 @@ mod tests { fn event_id() -> EventId { let k = keys(); - EventBuilder::new(Kind::Custom(1), "x").tags( []) + EventBuilder::new(Kind::Custom(1), "x") + .tags([]) .sign_with_keys(&k) .expect("sign") .id @@ -1737,7 +1715,8 @@ mod tests { fn extract_channel_id_invalid_uuid() { // Build an event with a malformed h-tag value let tags = vec![Tag::parse(["h", "not-a-uuid"]).unwrap()]; - let ev = EventBuilder::new(Kind::Custom(9), "x").tags( tags) + let ev = EventBuilder::new(Kind::Custom(9), "x") + .tags(tags) .sign_with_keys(&keys()) .unwrap(); assert_eq!(extract_channel_id(&ev), None); @@ -1759,7 +1738,8 @@ mod tests { fn build_note_with_reply() { let keys = nostr::Keys::generate(); // Create a dummy event to get a valid EventId - let dummy = EventBuilder::new(Kind::Custom(1), "dummy").tags( vec![]) + let dummy = EventBuilder::new(Kind::Custom(1), "dummy") + .tags(vec![]) .sign_with_keys(&keys) .unwrap(); let builder = build_note("reply text", Some(dummy.id)).unwrap(); diff --git a/crates/sprout-sdk/src/nip_oa.rs b/crates/sprout-sdk/src/nip_oa.rs index 28c6c2dc2..43f33eb29 100644 --- a/crates/sprout-sdk/src/nip_oa.rs +++ b/crates/sprout-sdk/src/nip_oa.rs @@ -229,9 +229,9 @@ pub fn verify_auth_tag( let preimage = build_preimage(agent_pubkey, conditions); let message = hash_preimage(&preimage); - let xonly = owner_pubkey - .xonly() - .map_err(|e| SdkError::InvalidInput(format!("owner pubkey xonly conversion failed: {e}")))?; + let xonly = owner_pubkey.xonly().map_err(|e| { + SdkError::InvalidInput(format!("owner pubkey xonly conversion failed: {e}")) + })?; SECP256K1 .verify_schnorr(&sig, &message, &xonly) .map_err(|e| SdkError::InvalidInput(format!("signature verification failed: {e}")))?; diff --git a/crates/sprout-search/src/index.rs b/crates/sprout-search/src/index.rs index 23bd2dd20..98f1e215a 100644 --- a/crates/sprout-search/src/index.rs +++ b/crates/sprout-search/src/index.rs @@ -301,7 +301,8 @@ mod tests { fn make_stored_event(content: &str, kind: Kind, channel_id: Option) -> StoredEvent { let keys = Keys::generate(); - let event = EventBuilder::new(kind, content).tags( []) + let event = EventBuilder::new(kind, content) + .tags([]) .sign_with_keys(&keys) .expect("signing failed"); StoredEvent::new(event, channel_id) @@ -460,7 +461,8 @@ mod tests { fn tag_flattening_uses_unit_separator() { let keys = Keys::generate(); let tag = nostr::Tag::parse(["e", "abc123def456"]).expect("tag parse"); - let event = EventBuilder::new(Kind::TextNote, "tagged").tags( [tag]) + let event = EventBuilder::new(Kind::TextNote, "tagged") + .tags([tag]) .sign_with_keys(&keys) .expect("sign"); let stored = StoredEvent::new(event, None); @@ -512,7 +514,8 @@ mod tests { let keys = Keys::generate(); // "r" tag with a URL value containing colons let tag = nostr::Tag::parse(["r", "wss://relay.example.com"]).expect("tag parse"); - let event = EventBuilder::new(Kind::TextNote, "relay ref").tags( [tag]) + let event = EventBuilder::new(Kind::TextNote, "relay ref") + .tags([tag]) .sign_with_keys(&keys) .expect("sign"); let stored = StoredEvent::new(event, None); diff --git a/crates/sprout-search/src/lib.rs b/crates/sprout-search/src/lib.rs index c488af52d..d8cec5284 100644 --- a/crates/sprout-search/src/lib.rs +++ b/crates/sprout-search/src/lib.rs @@ -181,7 +181,8 @@ mod integration_tests { fn make_stored_event(content: &str, kind: Kind) -> StoredEvent { let keys = Keys::generate(); - let event = EventBuilder::new(kind, content).tags( []) + let event = EventBuilder::new(kind, content) + .tags([]) .sign_with_keys(&keys) .expect("signing failed"); StoredEvent::new(event, None) diff --git a/crates/sprout-test-client/src/bin/mention.rs b/crates/sprout-test-client/src/bin/mention.rs index 07eefecd8..37a4d86b9 100644 --- a/crates/sprout-test-client/src/bin/mention.rs +++ b/crates/sprout-test-client/src/bin/mention.rs @@ -25,8 +25,9 @@ async fn main() -> anyhow::Result<()> { let h_tag = Tag::parse(["h", channel_id])?; let p_tag = Tag::parse(["p", target_pubkey])?; - let event = - EventBuilder::new(Kind::Custom(9), message).tags( [h_tag, p_tag]).sign_with_keys(&keys)?; + let event = EventBuilder::new(Kind::Custom(9), message) + .tags([h_tag, p_tag]) + .sign_with_keys(&keys)?; let ok = client.send_event(event).await?; if ok.accepted { diff --git a/crates/sprout-test-client/src/lib.rs b/crates/sprout-test-client/src/lib.rs index b9688caed..563eb6a24 100644 --- a/crates/sprout-test-client/src/lib.rs +++ b/crates/sprout-test-client/src/lib.rs @@ -125,8 +125,8 @@ impl SproutTestClient { pub async fn authenticate(&mut self, keys: &Keys) -> Result<(), TestClientError> { let challenge = self.wait_for_auth_challenge(Duration::from_secs(5)).await?; - let relay_url = RelayUrl::parse(&self.relay_url) - .map_err(|e| TestClientError::Url(e.to_string()))?; + let relay_url = + RelayUrl::parse(&self.relay_url).map_err(|e| TestClientError::Url(e.to_string()))?; let auth_event = EventBuilder::auth(&challenge, relay_url).sign_with_keys(keys)?; let event_id = auth_event.id.to_hex(); @@ -159,7 +159,9 @@ impl SproutTestClient { ) -> Result { let h_tag = Tag::parse(["h", channel_id]) .map_err(|e| TestClientError::EventBuilder(e.to_string()))?; - let event = EventBuilder::new(Kind::Custom(kind), content).tags( [h_tag]).sign_with_keys(keys)?; + let event = EventBuilder::new(Kind::Custom(kind), content) + .tags([h_tag]) + .sign_with_keys(keys)?; self.send_event(event).await } @@ -536,7 +538,8 @@ mod tests { let keys = Keys::generate(); let channel_id = "my-channel-123"; let h_tag = Tag::parse(["h", channel_id]).unwrap(); - let event = EventBuilder::new(Kind::Custom(9), "hello").tags( [h_tag]) + let event = EventBuilder::new(Kind::Custom(9), "hello") + .tags([h_tag]) .sign_with_keys(&keys) .unwrap(); diff --git a/crates/sprout-test-client/tests/e2e_long_form.rs b/crates/sprout-test-client/tests/e2e_long_form.rs index 60f58ac45..9e0cc79fa 100644 --- a/crates/sprout-test-client/tests/e2e_long_form.rs +++ b/crates/sprout-test-client/tests/e2e_long_form.rs @@ -46,7 +46,8 @@ fn build_long_form_event( Tag::parse(["title", title]).unwrap(), ]; tags.extend(extra_tags); - EventBuilder::new(Kind::Custom(KIND_LONG_FORM), content).tags( tags) + EventBuilder::new(Kind::Custom(KIND_LONG_FORM), content) + .tags(tags) .sign_with_keys(keys) .unwrap() } @@ -265,7 +266,8 @@ async fn test_long_form_stale_write_rejected() { Tag::parse(["d", &d_tag]).unwrap(), Tag::parse(["title", "Newer Article"]).unwrap(), ]; - EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "Newer content.").tags( tags) + EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "Newer content.") + .tags(tags) .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() + 100)) .sign_with_keys(&keys) .unwrap() @@ -280,7 +282,8 @@ async fn test_long_form_stale_write_rejected() { Tag::parse(["d", &d_tag]).unwrap(), Tag::parse(["title", "Older Article"]).unwrap(), ]; - EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "Older content.").tags( tags) + EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "Older content.") + .tags(tags) .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() - 100)) .sign_with_keys(&keys) .unwrap() diff --git a/crates/sprout-test-client/tests/e2e_mcp.rs b/crates/sprout-test-client/tests/e2e_mcp.rs index 26967f2f5..1c239d05c 100644 --- a/crates/sprout-test-client/tests/e2e_mcp.rs +++ b/crates/sprout-test-client/tests/e2e_mcp.rs @@ -68,7 +68,8 @@ async fn create_channel_for_test(keys: &Keys, name: &str) -> String { Tag::parse(["channel_type", "stream"]).unwrap(), Tag::parse(["visibility", "open"]).unwrap(), ]; - let event = EventBuilder::new(Kind::Custom(9007), "").tags( tags) + let event = EventBuilder::new(Kind::Custom(9007), "") + .tags(tags) .sign_with_keys(keys) .unwrap(); let resp = client @@ -103,7 +104,8 @@ async fn set_profile_via_event( map.insert("about".into(), serde_json::Value::String(a.into())); } let content = serde_json::Value::Object(map).to_string(); - let event = EventBuilder::new(Kind::Custom(0), &content).tags( []) + let event = EventBuilder::new(Kind::Custom(0), &content) + .tags([]) .sign_with_keys(keys) .unwrap(); let resp = client diff --git a/crates/sprout-test-client/tests/e2e_media.rs b/crates/sprout-test-client/tests/e2e_media.rs index 8ca2b23e3..ddd0814a0 100644 --- a/crates/sprout-test-client/tests/e2e_media.rs +++ b/crates/sprout-test-client/tests/e2e_media.rs @@ -46,7 +46,8 @@ fn sign_blossom_auth(keys: &Keys, sha256: &str) -> nostr::Event { Tag::parse(["x", sha256]).expect("x tag"), Tag::parse(["expiration", &exp_str]).expect("expiration tag"), ]; - EventBuilder::new(Kind::from(24242), "Upload test").tags( tags) + EventBuilder::new(Kind::from(24242), "Upload test") + .tags(tags) .sign_with_keys(keys) .expect("sign blossom auth") } diff --git a/crates/sprout-test-client/tests/e2e_media_extended.rs b/crates/sprout-test-client/tests/e2e_media_extended.rs index ea1b7733c..0a3dae931 100644 --- a/crates/sprout-test-client/tests/e2e_media_extended.rs +++ b/crates/sprout-test-client/tests/e2e_media_extended.rs @@ -33,7 +33,8 @@ fn sign_blossom_auth(keys: &Keys, sha256: &str) -> nostr::Event { Tag::parse(["x", sha256]).unwrap(), Tag::parse(["expiration", &(now + 300).to_string()]).unwrap(), ]; - EventBuilder::new(Kind::from(24242), "Upload test").tags( tags) + EventBuilder::new(Kind::from(24242), "Upload test") + .tags(tags) .sign_with_keys(keys) .unwrap() } @@ -127,7 +128,8 @@ fn tiny_webp() -> Vec { // ── Auth edge case helpers ────────────────────────────────────────────────── fn sign_custom_auth(keys: &Keys, kind: u16, content: &str, tags: Vec) -> nostr::Event { - EventBuilder::new(Kind::from(kind), content).tags( tags) + EventBuilder::new(Kind::from(kind), content) + .tags(tags) .sign_with_keys(keys) .unwrap() } @@ -487,17 +489,15 @@ async fn test_ws_valid_imeta() { // Create channel via signed kind:9007 event let channel_uuid = uuid::Uuid::new_v4(); let channel_name = format!("ws-imeta-test-{}", channel_uuid); - let create_event = EventBuilder::new( - Kind::from(9007), - "").tags( - vec![ + let create_event = EventBuilder::new(Kind::from(9007), "") + .tags(vec![ Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), Tag::parse(["name", &channel_name]).unwrap(), Tag::parse(["channel_type", "stream"]).unwrap(), Tag::parse(["visibility", "open"]).unwrap(), ]) - .sign_with_keys(&keys) - .unwrap(); + .sign_with_keys(&keys) + .unwrap(); let create_resp = http .post(format!("{}/api/events", relay_http_url())) .header("X-Pubkey", &pubkey_hex) @@ -522,10 +522,8 @@ async fn test_ws_valid_imeta() { .unwrap(); // Send event with valid imeta - let event = EventBuilder::new( - Kind::from(9), - "image via ws").tags( - vec![ + let event = EventBuilder::new(Kind::from(9), "image via ws") + .tags(vec![ Tag::parse(["h", &channel_id]).unwrap(), Tag::parse([ "imeta", @@ -536,8 +534,8 @@ async fn test_ws_valid_imeta() { ]) .unwrap(), ]) - .sign_with_keys(&keys) - .unwrap(); + .sign_with_keys(&keys) + .unwrap(); let ok = client.send_event(event).await.unwrap(); assert!( @@ -561,17 +559,15 @@ async fn test_ws_invalid_imeta_external_url() { let channel_uuid = uuid::Uuid::new_v4(); let channel_name = format!("ws-imeta-bad-{}", channel_uuid); - let create_event = EventBuilder::new( - Kind::from(9007), - "").tags( - vec![ + let create_event = EventBuilder::new(Kind::from(9007), "") + .tags(vec![ Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), Tag::parse(["name", &channel_name]).unwrap(), Tag::parse(["channel_type", "stream"]).unwrap(), Tag::parse(["visibility", "open"]).unwrap(), ]) - .sign_with_keys(&keys) - .unwrap(); + .sign_with_keys(&keys) + .unwrap(); let create_resp = http .post(format!("{}/api/events", relay_http_url())) .header("X-Pubkey", &pubkey_hex) @@ -588,10 +584,8 @@ async fn test_ws_invalid_imeta_external_url() { .await .unwrap(); - let event = EventBuilder::new( - Kind::from(9), - "bad imeta").tags( - vec![ + let event = EventBuilder::new(Kind::from(9), "bad imeta") + .tags(vec![ Tag::parse(["h", &channel_id]).unwrap(), Tag::parse([ "imeta", @@ -602,8 +596,8 @@ async fn test_ws_invalid_imeta_external_url() { ]) .unwrap(), ]) - .sign_with_keys(&keys) - .unwrap(); + .sign_with_keys(&keys) + .unwrap(); let ok = client.send_event(event).await.unwrap(); assert!(!ok.accepted, "external URL imeta via WS must be rejected"); @@ -631,17 +625,15 @@ async fn test_ws_invalid_imeta_missing_fields() { let channel_uuid = uuid::Uuid::new_v4(); let channel_name = format!("ws-imeta-miss-{}", channel_uuid); - let create_event = EventBuilder::new( - Kind::from(9007), - "").tags( - vec![ + let create_event = EventBuilder::new(Kind::from(9007), "") + .tags(vec![ Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), Tag::parse(["name", &channel_name]).unwrap(), Tag::parse(["channel_type", "stream"]).unwrap(), Tag::parse(["visibility", "open"]).unwrap(), ]) - .sign_with_keys(&keys) - .unwrap(); + .sign_with_keys(&keys) + .unwrap(); let create_resp = http .post(format!("{}/api/events", relay_http_url())) .header("X-Pubkey", &pubkey_hex) @@ -659,10 +651,8 @@ async fn test_ws_invalid_imeta_missing_fields() { .unwrap(); // Only url, missing m/x/size - let event = EventBuilder::new( - Kind::from(9), - "incomplete imeta").tags( - vec![ + let event = EventBuilder::new(Kind::from(9), "incomplete imeta") + .tags(vec![ Tag::parse(["h", &channel_id]).unwrap(), Tag::parse([ "imeta", @@ -670,8 +660,8 @@ async fn test_ws_invalid_imeta_missing_fields() { ]) .unwrap(), ]) - .sign_with_keys(&keys) - .unwrap(); + .sign_with_keys(&keys) + .unwrap(); let ok = client.send_event(event).await.unwrap(); assert!(!ok.accepted, "incomplete imeta via WS must be rejected"); diff --git a/crates/sprout-test-client/tests/e2e_media_video.rs b/crates/sprout-test-client/tests/e2e_media_video.rs index db915fd6c..7c9e09ae4 100644 --- a/crates/sprout-test-client/tests/e2e_media_video.rs +++ b/crates/sprout-test-client/tests/e2e_media_video.rs @@ -38,7 +38,8 @@ fn sign_blossom_auth(keys: &Keys, sha256: &str) -> nostr::Event { Tag::parse(["x", sha256]).expect("x tag"), Tag::parse(["expiration", &exp_str]).expect("expiration tag"), ]; - EventBuilder::new(Kind::from(24242), "Upload test").tags( tags) + EventBuilder::new(Kind::from(24242), "Upload test") + .tags(tags) .sign_with_keys(keys) .expect("sign blossom auth") } @@ -481,17 +482,15 @@ async fn test_video_poster_imeta_accepted_via_ws() { // 1. Create a channel let channel_uuid = uuid::Uuid::new_v4(); let channel_id = channel_uuid.to_string(); - let create_event = EventBuilder::new( - Kind::from(9007), - "").tags( - vec![ + let create_event = EventBuilder::new(Kind::from(9007), "") + .tags(vec![ Tag::parse(["h", &channel_id]).unwrap(), Tag::parse(["name", &format!("video-poster-test-{channel_id}")]).unwrap(), Tag::parse(["channel_type", "stream"]).unwrap(), Tag::parse(["visibility", "open"]).unwrap(), ]) - .sign_with_keys(&keys) - .unwrap(); + .sign_with_keys(&keys) + .unwrap(); let resp = client .post(format!("{}/api/events", relay_http_url())) .header("X-Pubkey", &pubkey_hex) @@ -541,19 +540,20 @@ async fn test_video_poster_imeta_accepted_via_ws() { let base = relay_http_url(); let event = EventBuilder::new( Kind::from(9), - format!("![video]({base}/media/{video_sha}.mp4)")).tags( - vec![ - Tag::parse(["h", &channel_id]).unwrap(), - Tag::parse([ - "imeta", - &format!("url {base}/media/{video_sha}.mp4"), - "m video/mp4", - &format!("x {video_sha}"), - &format!("size {video_size}"), - &format!("image {base}/media/{poster_sha}.jpg"), - ]) - .unwrap(), + format!("![video]({base}/media/{video_sha}.mp4)"), + ) + .tags(vec![ + Tag::parse(["h", &channel_id]).unwrap(), + Tag::parse([ + "imeta", + &format!("url {base}/media/{video_sha}.mp4"), + "m video/mp4", + &format!("x {video_sha}"), + &format!("size {video_size}"), + &format!("image {base}/media/{poster_sha}.jpg"), ]) + .unwrap(), + ]) .sign_with_keys(&keys) .unwrap(); @@ -581,17 +581,15 @@ async fn test_video_poster_imeta_rejects_video_as_poster() { // 1. Create channel let channel_uuid = uuid::Uuid::new_v4(); let channel_id = channel_uuid.to_string(); - let create_event = EventBuilder::new( - Kind::from(9007), - "").tags( - vec![ + let create_event = EventBuilder::new(Kind::from(9007), "") + .tags(vec![ Tag::parse(["h", &channel_id]).unwrap(), Tag::parse(["name", &format!("poster-reject-test-{channel_id}")]).unwrap(), Tag::parse(["channel_type", "stream"]).unwrap(), Tag::parse(["visibility", "open"]).unwrap(), ]) - .sign_with_keys(&keys) - .unwrap(); + .sign_with_keys(&keys) + .unwrap(); let resp = client .post(format!("{}/api/events", relay_http_url())) .header("X-Pubkey", &pubkey_hex) @@ -625,10 +623,8 @@ async fn test_video_poster_imeta_rejects_video_as_poster() { .unwrap(); let base = relay_http_url(); - let event = EventBuilder::new( - Kind::from(9), - "bad poster").tags( - vec![ + let event = EventBuilder::new(Kind::from(9), "bad poster") + .tags(vec![ Tag::parse(["h", &channel_id]).unwrap(), Tag::parse([ "imeta", @@ -641,8 +637,8 @@ async fn test_video_poster_imeta_rejects_video_as_poster() { ]) .unwrap(), ]) - .sign_with_keys(&keys) - .unwrap(); + .sign_with_keys(&keys) + .unwrap(); let ok = ws.send_event(event).await.unwrap(); assert!( diff --git a/crates/sprout-test-client/tests/e2e_nostr_interop.rs b/crates/sprout-test-client/tests/e2e_nostr_interop.rs index 19ed75659..e46799b2d 100644 --- a/crates/sprout-test-client/tests/e2e_nostr_interop.rs +++ b/crates/sprout-test-client/tests/e2e_nostr_interop.rs @@ -49,17 +49,15 @@ async fn create_test_channel(keys: &Keys) -> String { let channel_uuid = uuid::Uuid::new_v4(); let channel_name = format!("interop-e2e-{}", channel_uuid); - let event = EventBuilder::new( - Kind::Custom(9007), - "").tags( - vec![ + let event = EventBuilder::new(Kind::Custom(9007), "") + .tags(vec![ Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), Tag::parse(["name", &channel_name]).unwrap(), Tag::parse(["channel_type", "stream"]).unwrap(), Tag::parse(["visibility", "open"]).unwrap(), ]) - .sign_with_keys(keys) - .unwrap(); + .sign_with_keys(keys) + .unwrap(); let resp = client .post(format!("{}/api/events", relay_http_url())) @@ -88,12 +86,10 @@ async fn create_test_channel(keys: &Keys) -> String { async fn send_rest_message(keys: &Keys, channel_id: &str, content: &str) -> String { let client = reqwest::Client::new(); let pubkey_hex = keys.public_key().to_hex(); - let event = EventBuilder::new( - Kind::Custom(9), - content).tags( - vec![Tag::parse(["h", channel_id]).unwrap()]) - .sign_with_keys(keys) - .unwrap(); + let event = EventBuilder::new(Kind::Custom(9), content) + .tags(vec![Tag::parse(["h", channel_id]).unwrap()]) + .sign_with_keys(keys) + .unwrap(); let resp = client .post(format!("{}/api/events", relay_http_url())) .header("X-Pubkey", &pubkey_hex) @@ -341,7 +337,8 @@ async fn test_nip10_thread_reply_creates_metadata() { let e_reply_tag = Tag::parse(["e", &root_event_id, "", "reply"]).expect("e reply tag"); let reply_content = format!("reply to root {}", uuid::Uuid::new_v4()); - let reply_event = EventBuilder::new(Kind::Custom(9), &reply_content).tags( [h_tag, e_reply_tag]) + let reply_event = EventBuilder::new(Kind::Custom(9), &reply_content) + .tags([h_tag, e_reply_tag]) .sign_with_keys(&keys) .expect("sign reply"); @@ -398,7 +395,8 @@ async fn test_nip10_unknown_parent_rejected() { let h_tag = Tag::parse(["h", &channel]).expect("h tag"); let e_reply_tag = Tag::parse(["e", &fake_parent_id, "", "reply"]).expect("e reply tag"); - let event = EventBuilder::new(Kind::Custom(9), "orphan reply").tags( [h_tag, e_reply_tag]) + let event = EventBuilder::new(Kind::Custom(9), "orphan reply") + .tags([h_tag, e_reply_tag]) .sign_with_keys(&keys) .expect("sign event"); @@ -442,12 +440,10 @@ async fn test_nip10_root_mismatch_rejected() { let e_root_tag = Tag::parse(["e", &wrong_root_id, "", "root"]).expect("e root tag"); let e_reply_tag = Tag::parse(["e", &real_parent_id, "", "reply"]).expect("e reply tag"); - let event = EventBuilder::new( - Kind::Custom(9), - "reply with wrong root").tags( - [h_tag, e_root_tag, e_reply_tag]) - .sign_with_keys(&keys) - .expect("sign event"); + let event = EventBuilder::new(Kind::Custom(9), "reply with wrong root") + .tags([h_tag, e_root_tag, e_reply_tag]) + .sign_with_keys(&keys) + .expect("sign event"); let ok = client.send_event(event).await.expect("send event"); @@ -486,7 +482,8 @@ async fn test_nip17_gift_wrap_accepted() { let ephemeral_keys = Keys::generate(); let p_tag = Tag::parse(["p", &recipient_keys.public_key().to_hex()]).expect("p tag"); - let gift_wrap = EventBuilder::new(Kind::Custom(1059), "encrypted-content").tags( [p_tag]) + let gift_wrap = EventBuilder::new(Kind::Custom(1059), "encrypted-content") + .tags([p_tag]) .sign_with_keys(&ephemeral_keys) .expect("sign gift wrap"); @@ -599,7 +596,8 @@ async fn test_nip17_gift_wrap_recipient_receives() { let p_tag = Tag::parse(["p", &b_pubkey_hex]).expect("p tag"); let unique_content = format!("gift-wrap-{}", uuid::Uuid::new_v4()); - let gift_wrap = EventBuilder::new(Kind::Custom(1059), &unique_content).tags( [p_tag]) + let gift_wrap = EventBuilder::new(Kind::Custom(1059), &unique_content) + .tags([p_tag]) .sign_with_keys(&ephemeral_keys) .expect("sign gift wrap"); @@ -797,7 +795,8 @@ async fn test_nip10_thread_reply_not_in_top_level() { let h_tag = Tag::parse(["h", &channel]).expect("h tag"); let e_reply_tag = Tag::parse(["e", &root_event_id, "", "reply"]).expect("e reply tag"); - let reply_event = EventBuilder::new(Kind::Custom(9), &reply_content).tags( [h_tag, e_reply_tag]) + let reply_event = EventBuilder::new(Kind::Custom(9), &reply_content) + .tags([h_tag, e_reply_tag]) .sign_with_keys(&keys) .expect("sign reply"); @@ -862,7 +861,8 @@ async fn test_nip17_gift_wrap_not_searchable() { // 1. Send kind:1059 gift wrap. let ephemeral_keys = Keys::generate(); let p_tag = Tag::parse(["p", &keys_b.public_key().to_hex()]).expect("p tag"); - let gift_wrap = EventBuilder::new(Kind::Custom(1059), &unique_token).tags( [p_tag]) + let gift_wrap = EventBuilder::new(Kind::Custom(1059), &unique_token) + .tags([p_tag]) .sign_with_keys(&ephemeral_keys) .expect("sign gift wrap"); let ok = client.send_event(gift_wrap).await.expect("send gift wrap"); diff --git a/crates/sprout-test-client/tests/e2e_relay.rs b/crates/sprout-test-client/tests/e2e_relay.rs index 6ee05b2b4..34169d304 100644 --- a/crates/sprout-test-client/tests/e2e_relay.rs +++ b/crates/sprout-test-client/tests/e2e_relay.rs @@ -46,17 +46,15 @@ async fn create_test_channel(keys: &Keys) -> String { let channel_uuid = uuid::Uuid::new_v4(); let channel_name = format!("relay-e2e-{}", channel_uuid); - let event = EventBuilder::new( - Kind::Custom(9007), - "").tags( - vec![ + let event = EventBuilder::new(Kind::Custom(9007), "") + .tags(vec![ Tag::parse(["h", &channel_uuid.to_string()]).unwrap(), Tag::parse(["name", &channel_name]).unwrap(), Tag::parse(["channel_type", "stream"]).unwrap(), Tag::parse(["visibility", "open"]).unwrap(), ]) - .sign_with_keys(keys) - .unwrap(); + .sign_with_keys(keys) + .unwrap(); let resp = client .post(format!("{}/api/events", relay_http_url())) @@ -717,7 +715,8 @@ async fn test_kind0_nip05_sync() { }) .to_string(); - let event = nostr::EventBuilder::new(Kind::Custom(0), kind0_content).tags( []) + let event = nostr::EventBuilder::new(Kind::Custom(0), kind0_content) + .tags([]) .sign_with_keys(&keys) .expect("sign kind:0"); @@ -776,7 +775,8 @@ async fn test_kind0_nip05_sync() { }) .to_string(); - let event2 = nostr::EventBuilder::new(Kind::Custom(0), off_domain_content).tags( []) + let event2 = nostr::EventBuilder::new(Kind::Custom(0), off_domain_content) + .tags([]) .sign_with_keys(&keys) .expect("sign kind:0 off-domain"); @@ -846,7 +846,8 @@ async fn test_nip29_put_user_default_policy_allows() { // Build kind 9000 PUT_USER event: h = channel_id, p = agent pubkey. let h_tag = nostr::Tag::parse(["h", &channel_id]).expect("h tag"); let p_tag = nostr::Tag::parse(["p", &agent_pubkey_hex]).expect("p tag"); - let event = nostr::EventBuilder::new(Kind::Custom(9000), "").tags( [h_tag, p_tag]) + let event = nostr::EventBuilder::new(Kind::Custom(9000), "") + .tags([h_tag, p_tag]) .sign_with_keys(&channel_owner_keys) .expect("sign kind 9000"); @@ -896,7 +897,8 @@ async fn test_nip29_put_user_nobody_blocks() { // Build kind 9000 PUT_USER event targeting the agent. let h_tag = nostr::Tag::parse(["h", &channel_id]).expect("h tag"); let p_tag = nostr::Tag::parse(["p", &agent_pubkey_hex]).expect("p tag"); - let event = nostr::EventBuilder::new(Kind::Custom(9000), "").tags( [h_tag, p_tag]) + let event = nostr::EventBuilder::new(Kind::Custom(9000), "") + .tags([h_tag, p_tag]) .sign_with_keys(&channel_owner_keys) .expect("sign kind 9000"); @@ -949,7 +951,8 @@ async fn test_nip29_put_user_self_add_bypasses_policy() { // Build kind 9000 PUT_USER event where agent targets ITSELF. let h_tag = nostr::Tag::parse(["h", &channel_id]).expect("h tag"); let p_tag = nostr::Tag::parse(["p", &agent_pubkey_hex]).expect("p tag"); - let event = nostr::EventBuilder::new(Kind::Custom(9000), "").tags( [h_tag, p_tag]) + let event = nostr::EventBuilder::new(Kind::Custom(9000), "") + .tags([h_tag, p_tag]) .sign_with_keys(&agent_keys) .expect("sign kind 9000"); @@ -999,7 +1002,8 @@ async fn test_nip29_put_user_owner_only_blocks() { // Build kind 9000 PUT_USER event targeting the agent. let h_tag = nostr::Tag::parse(["h", &channel_id]).expect("h tag"); let p_tag = nostr::Tag::parse(["p", &agent_pubkey_hex]).expect("p tag"); - let event = nostr::EventBuilder::new(Kind::Custom(9000), "").tags( [h_tag, p_tag]) + let event = nostr::EventBuilder::new(Kind::Custom(9000), "") + .tags([h_tag, p_tag]) .sign_with_keys(&channel_owner_keys) .expect("sign kind 9000"); @@ -1156,7 +1160,8 @@ async fn test_nip29_standard_client_flow() { // 5. Send a kind:7 reaction targeting the message. let h_tag = Tag::parse(["h", &channel_id]).expect("h tag"); let e_tag = Tag::parse(["e", &message_event_id]).expect("e tag"); - let reaction_event = EventBuilder::new(Kind::Custom(7), "+").tags( [h_tag, e_tag]) + let reaction_event = EventBuilder::new(Kind::Custom(7), "+") + .tags([h_tag, e_tag]) .sign_with_keys(&keys) .expect("sign reaction"); let ok = client @@ -1172,7 +1177,8 @@ async fn test_nip29_standard_client_flow() { // 6. Send a kind:5 deletion targeting the message. let h_tag2 = Tag::parse(["h", &channel_id]).expect("h tag"); let e_tag2 = Tag::parse(["e", &message_event_id]).expect("e tag"); - let delete_event = EventBuilder::new(Kind::Custom(5), "test delete").tags( [h_tag2, e_tag2]) + let delete_event = EventBuilder::new(Kind::Custom(5), "test delete") + .tags([h_tag2, e_tag2]) .sign_with_keys(&keys) .expect("sign deletion"); let ok = client @@ -1186,7 +1192,8 @@ async fn test_nip29_standard_client_flow() { ); // 7. Verify kind:9 without h tag is rejected. - let no_h_event = EventBuilder::new(Kind::Custom(9), "no h tag").tags( []) + let no_h_event = EventBuilder::new(Kind::Custom(9), "no h tag") + .tags([]) .sign_with_keys(&keys) .expect("sign no-h event"); let ok = client @@ -1213,7 +1220,8 @@ async fn test_membership_notification_kind_rejected() { let p_tag = Tag::parse(["p", &keys.public_key().to_hex()]).expect("p tag"); let h_tag = Tag::parse(["h", &channel_id]).expect("h tag"); - let event = EventBuilder::new(Kind::Custom(44100), "").tags( [p_tag, h_tag]) + let event = EventBuilder::new(Kind::Custom(44100), "") + .tags([p_tag, h_tag]) .sign_with_keys(&keys) .expect("sign kind:44100"); @@ -1277,15 +1285,13 @@ async fn test_membership_notification_emitted_on_add() { // Add agent to the channel via signed kind:9000 event. let http_client = reqwest::Client::new(); - let add_event = EventBuilder::new( - Kind::Custom(9000), - "").tags( - vec![ + let add_event = EventBuilder::new(Kind::Custom(9000), "") + .tags(vec![ Tag::parse(["h", &channel_id]).unwrap(), Tag::parse(["p", &agent_pubkey_hex]).unwrap(), ]) - .sign_with_keys(&owner_keys) - .unwrap(); + .sign_with_keys(&owner_keys) + .unwrap(); let resp = http_client .post(format!("{}/api/events", relay_http_url())) .header("X-Pubkey", &owner_keys.public_key().to_hex()) @@ -1555,15 +1561,13 @@ async fn test_membership_notification_emitted_on_remove() { let owner_pubkey_hex = owner_keys.public_key().to_hex(); // Add agent to the channel via signed kind:9000 event. - let add_event = EventBuilder::new( - Kind::Custom(9000), - "").tags( - vec![ + let add_event = EventBuilder::new(Kind::Custom(9000), "") + .tags(vec![ Tag::parse(["h", &channel_id]).unwrap(), Tag::parse(["p", &agent_pubkey_hex]).unwrap(), ]) - .sign_with_keys(&owner_keys) - .unwrap(); + .sign_with_keys(&owner_keys) + .unwrap(); let resp = http_client .post(format!("{}/api/events", relay_http_url())) .header("X-Pubkey", &owner_pubkey_hex) @@ -1596,15 +1600,13 @@ async fn test_membership_notification_emitted_on_remove() { } // Remove agent from the channel via signed kind:9001 event. - let remove_event = EventBuilder::new( - Kind::Custom(9001), - "").tags( - vec![ + let remove_event = EventBuilder::new(Kind::Custom(9001), "") + .tags(vec![ Tag::parse(["h", &channel_id]).unwrap(), Tag::parse(["p", &agent_pubkey_hex]).unwrap(), ]) - .sign_with_keys(&owner_keys) - .unwrap(); + .sign_with_keys(&owner_keys) + .unwrap(); let resp = http_client .post(format!("{}/api/events", relay_http_url())) .header("X-Pubkey", &owner_pubkey_hex) diff --git a/crates/sprout-test-client/tests/e2e_rest_api.rs b/crates/sprout-test-client/tests/e2e_rest_api.rs index 0279b286b..cf90003cd 100644 --- a/crates/sprout-test-client/tests/e2e_rest_api.rs +++ b/crates/sprout-test-client/tests/e2e_rest_api.rs @@ -116,7 +116,8 @@ async fn create_channel_via_event( if let Some(desc) = description { tags.push(Tag::parse(["about", desc]).unwrap()); } - let event = EventBuilder::new(Kind::Custom(9007), "").tags( tags) + let event = EventBuilder::new(Kind::Custom(9007), "") + .tags(tags) .sign_with_keys(keys) .unwrap(); let resp = client @@ -160,7 +161,8 @@ async fn set_profile_via_event( content_obj.insert("nip05".to_string(), serde_json::json!(n)); } let content = serde_json::to_string(&serde_json::Value::Object(content_obj)).unwrap(); - let event = EventBuilder::new(Kind::Custom(0), &content).tags( vec![]) + let event = EventBuilder::new(Kind::Custom(0), &content) + .tags(vec![]) .sign_with_keys(keys) .unwrap(); let resp = client @@ -396,7 +398,8 @@ async fn test_search_returns_indexed_event() { .expect("WebSocket connect failed"); let h_tag = Tag::parse(["h", &channel_id]).expect("tag parse failed"); - let event = nostr::EventBuilder::new(Kind::Custom(9), &content).tags( [h_tag]) + let event = nostr::EventBuilder::new(Kind::Custom(9), &content) + .tags([h_tag]) .sign_with_keys(&keys) .expect("event sign failed"); @@ -484,7 +487,8 @@ async fn test_presence_set_and_query() { .await .expect("WebSocket connect failed"); - let presence_event = nostr::EventBuilder::new(Kind::Custom(20001), "online").tags( []) + let presence_event = nostr::EventBuilder::new(Kind::Custom(20001), "online") + .tags([]) .sign_with_keys(&keys) .expect("event sign failed"); @@ -509,7 +513,8 @@ async fn test_presence_set_and_query() { "expected 'online' after sending presence event" ); - let offline_event = nostr::EventBuilder::new(Kind::Custom(20001), "offline").tags( []) + let offline_event = nostr::EventBuilder::new(Kind::Custom(20001), "offline") + .tags([]) .sign_with_keys(&keys) .expect("event sign failed"); ws_client.send_event(offline_event).await.ok(); @@ -889,7 +894,8 @@ async fn test_feed_returns_activity() { .expect("WebSocket connect failed"); let h_tag = Tag::parse(["h", &channel_id]).expect("tag parse failed"); - let event = nostr::EventBuilder::new(Kind::Custom(9), &content).tags( [h_tag]) + let event = nostr::EventBuilder::new(Kind::Custom(9), &content) + .tags([h_tag]) .sign_with_keys(&keys) .expect("event sign failed"); @@ -1532,7 +1538,8 @@ async fn test_get_event_returns_text_note() { let pubkey_hex = keys.public_key().to_hex(); let content = format!("e2e-note-{}", uuid::Uuid::new_v4().simple()); - let event = EventBuilder::new(Kind::Custom(1), &content).tags( vec![]) + let event = EventBuilder::new(Kind::Custom(1), &content) + .tags(vec![]) .sign_with_keys(&keys) .unwrap(); let event_id = event.id.to_hex(); @@ -1596,7 +1603,8 @@ async fn test_get_user_notes_returns_paginated_notes() { // (created_at, event_id) correctly handles same-second events without skipping. for i in 0..3u8 { let content = format!("e2e-paginated-note-{}-{}", i, uuid::Uuid::new_v4().simple()); - let event = EventBuilder::new(Kind::Custom(1), &content).tags( vec![]) + let event = EventBuilder::new(Kind::Custom(1), &content) + .tags(vec![]) .sign_with_keys(&keys) .unwrap(); let resp = client @@ -1696,7 +1704,8 @@ async fn test_get_contact_list_returns_latest() { Tag::parse(["p", &contact1]).unwrap(), Tag::parse(["p", &contact2]).unwrap(), ]; - let event_v1 = EventBuilder::new(Kind::Custom(3), "").tags( tags_v1) + let event_v1 = EventBuilder::new(Kind::Custom(3), "") + .tags(tags_v1) .sign_with_keys(&keys) .unwrap(); @@ -1720,7 +1729,8 @@ async fn test_get_contact_list_returns_latest() { // ── Second contact list: 1 different contact ────────────────────────────── let contact3 = Keys::generate().public_key().to_hex(); let tags_v2 = vec![Tag::parse(["p", &contact3]).unwrap()]; - let event_v2 = EventBuilder::new(Kind::Custom(3), "").tags( tags_v2) + let event_v2 = EventBuilder::new(Kind::Custom(3), "") + .tags(tags_v2) .sign_with_keys(&keys) .unwrap(); @@ -1812,7 +1822,8 @@ async fn test_get_user_notes_invalid_before_returns_400() { // Publish one note so the user has something to return. let content = format!("e2e-overflow-note-{}", uuid::Uuid::new_v4().simple()); - let event = EventBuilder::new(Kind::Custom(1), &content).tags( vec![]) + let event = EventBuilder::new(Kind::Custom(1), &content) + .tags(vec![]) .sign_with_keys(&keys) .unwrap(); let resp = client diff --git a/crates/sprout-test-client/tests/e2e_tokens.rs b/crates/sprout-test-client/tests/e2e_tokens.rs index 7516011b2..7e24cd9cc 100644 --- a/crates/sprout-test-client/tests/e2e_tokens.rs +++ b/crates/sprout-test-client/tests/e2e_tokens.rs @@ -62,7 +62,8 @@ fn build_nip98_header(keys: &Keys, url: &str, method: &str, body: &[u8]) -> Stri Tag::parse(["payload", &payload_hash]).expect("payload tag"), ]; - let event = EventBuilder::new(Kind::HttpAuth, "").tags( tags) + let event = EventBuilder::new(Kind::HttpAuth, "") + .tags(tags) .sign_with_keys(keys) .expect("signing must succeed"); @@ -82,7 +83,8 @@ fn build_nip98_header_no_payload(keys: &Keys, url: &str, method: &str) -> String Tag::parse(["method", method]).expect("method tag"), // Deliberately omit payload tag ]; - let event = EventBuilder::new(Kind::HttpAuth, "").tags( tags) + let event = EventBuilder::new(Kind::HttpAuth, "") + .tags(tags) .sign_with_keys(keys) .expect("signing must succeed"); let json = event.as_json(); diff --git a/crates/sprout-test-client/tests/e2e_user_status.rs b/crates/sprout-test-client/tests/e2e_user_status.rs index 48e137966..e7bbf81c1 100644 --- a/crates/sprout-test-client/tests/e2e_user_status.rs +++ b/crates/sprout-test-client/tests/e2e_user_status.rs @@ -42,7 +42,8 @@ fn build_user_status_event( ) -> nostr::Event { let mut tags = vec![Tag::parse(["d", d_tag]).unwrap()]; tags.extend(extra_tags); - EventBuilder::new(Kind::Custom(KIND_USER_STATUS), content).tags( tags) + EventBuilder::new(Kind::Custom(KIND_USER_STATUS), content) + .tags(tags) .sign_with_keys(keys) .unwrap() } @@ -231,7 +232,8 @@ async fn test_user_status_stale_write_rejected() { // Publish the "newer" event first (with a future-ish timestamp) let newer = { let tags = vec![Tag::parse(["d", &d_tag]).unwrap()]; - EventBuilder::new(Kind::Custom(KIND_USER_STATUS), "Newer status").tags( tags) + EventBuilder::new(Kind::Custom(KIND_USER_STATUS), "Newer status") + .tags(tags) .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() + 100)) .sign_with_keys(&keys) .unwrap() @@ -243,7 +245,8 @@ async fn test_user_status_stale_write_rejected() { // Now try to publish an "older" event with the same d-tag but earlier timestamp let older = { let tags = vec![Tag::parse(["d", &d_tag]).unwrap()]; - EventBuilder::new(Kind::Custom(KIND_USER_STATUS), "Older status").tags( tags) + EventBuilder::new(Kind::Custom(KIND_USER_STATUS), "Older status") + .tags(tags) .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() - 100)) .sign_with_keys(&keys) .unwrap() diff --git a/crates/sprout-test-client/tests/e2e_workflows.rs b/crates/sprout-test-client/tests/e2e_workflows.rs index 8bdfceb2a..1115b8af9 100644 --- a/crates/sprout-test-client/tests/e2e_workflows.rs +++ b/crates/sprout-test-client/tests/e2e_workflows.rs @@ -350,7 +350,8 @@ steps: .expect("ws connect failed"); let h_tag = Tag::parse(["h", CHANNEL_GENERAL]).expect("tag parse failed"); - let event = nostr::EventBuilder::new(Kind::Custom(9), "trigger this workflow please").tags( [h_tag]) + let event = nostr::EventBuilder::new(Kind::Custom(9), "trigger this workflow please") + .tags([h_tag]) .sign_with_keys(&sender_keys) .expect("sign event"); @@ -444,12 +445,11 @@ steps: // ── Step 2: Send a message that does NOT match the filter ───────────────── let h_tag = Tag::parse(["h", CHANNEL_GENERAL]).expect("tag parse failed"); - let non_matching = nostr::EventBuilder::new( - Kind::Custom(9), - "this is a routine update, nothing urgent").tags( - [h_tag.clone()]) - .sign_with_keys(&sender_keys) - .expect("sign event"); + let non_matching = + nostr::EventBuilder::new(Kind::Custom(9), "this is a routine update, nothing urgent") + .tags([h_tag.clone()]) + .sign_with_keys(&sender_keys) + .expect("sign event"); ws_client .send_event(non_matching) @@ -475,7 +475,8 @@ steps: ); // ── Step 3: Send a message that DOES match the filter ───────────────────── - let matching = nostr::EventBuilder::new(Kind::Custom(9), "P1 alert: database is down").tags( [h_tag]) + let matching = nostr::EventBuilder::new(Kind::Custom(9), "P1 alert: database is down") + .tags([h_tag]) .sign_with_keys(&sender_keys) .expect("sign event"); diff --git a/crates/sprout-workflow/src/lib.rs b/crates/sprout-workflow/src/lib.rs index f668fe4f4..55148bde1 100644 --- a/crates/sprout-workflow/src/lib.rs +++ b/crates/sprout-workflow/src/lib.rs @@ -1056,7 +1056,8 @@ steps: use nostr::{EventBuilder, Keys, Kind}; use uuid::Uuid; let keys = Keys::generate(); - let event = EventBuilder::new(Kind::Custom(9), "hello world").tags( []) + let event = EventBuilder::new(Kind::Custom(9), "hello world") + .tags([]) .sign_with_keys(&keys) .expect("sign"); sprout_core::StoredEvent::new(event, Some(Uuid::new_v4())) @@ -1069,13 +1070,15 @@ steps: let keys = Keys::generate(); // Create a dummy target message ID (64-char hex). let target_keys = Keys::generate(); - let target_event = EventBuilder::new(Kind::Custom(9), "target msg").tags( []) + let target_event = EventBuilder::new(Kind::Custom(9), "target msg") + .tags([]) .sign_with_keys(&target_keys) .expect("sign target"); let target_id_hex = target_event.id.to_hex(); // NIP-25: reaction references the target via an `e` tag. let e_tag = Tag::parse(["e", &target_id_hex]).expect("tag parse"); - let event = EventBuilder::new(Kind::Reaction, "👍").tags( [e_tag]) + let event = EventBuilder::new(Kind::Reaction, "👍") + .tags([e_tag]) .sign_with_keys(&keys) .expect("sign"); ( @@ -1118,7 +1121,8 @@ steps: fn build_trigger_context_no_channel_id() { use nostr::{EventBuilder, Keys, Kind}; let keys = Keys::generate(); - let event = EventBuilder::new(Kind::Custom(9), "msg").tags( []) + let event = EventBuilder::new(Kind::Custom(9), "msg") + .tags([]) .sign_with_keys(&keys) .expect("sign"); // channel_id = None (global/DM event) @@ -1167,15 +1171,13 @@ steps: let thread_root_id = EventId::all_zeros(); let direct_target_id = EventId::from_byte_array([0x42; 32]); - let event = EventBuilder::new( - Kind::Reaction, - "👍").tags( - [ + let event = EventBuilder::new(Kind::Reaction, "👍") + .tags([ Tag::parse(["e", &thread_root_id.to_hex()]).unwrap(), Tag::parse(["e", &direct_target_id.to_hex()]).unwrap(), ]) - .sign_with_keys(&keys) - .expect("sign"); + .sign_with_keys(&keys) + .expect("sign"); let stored = sprout_core::StoredEvent::new(event, Some(Uuid::new_v4())); let ctx = build_trigger_context(&stored); diff --git a/desktop/src-tauri/src/commands/agents.rs b/desktop/src-tauri/src/commands/agents.rs index d0ece9923..7e663ed41 100644 --- a/desktop/src-tauri/src/commands/agents.rs +++ b/desktop/src-tauri/src/commands/agents.rs @@ -1,5 +1,4 @@ use nostr::{Keys, ToBech32}; -use nostr; use tauri::{AppHandle, State}; use crate::{ diff --git a/desktop/src-tauri/src/commands/identity.rs b/desktop/src-tauri/src/commands/identity.rs index 8595954b4..e199512bf 100644 --- a/desktop/src-tauri/src/commands/identity.rs +++ b/desktop/src-tauri/src/commands/identity.rs @@ -1,8 +1,4 @@ -use nostr::{nips::nip44, EventBuilder, JsonUtil, Keys, Kind, Tag, Timestamp, ToBech32}; -use nostr::{ - Event as CompatEvent, JsonUtil as CompatJsonUtil, Keys as CompatKeys, - PublicKey as CompatPublicKey, -}; +use nostr::{nips::nip44, Event, EventBuilder, JsonUtil, Keys, Kind, PublicKey, Tag, Timestamp, ToBech32}; use tauri::Manager; use tauri::State; @@ -103,9 +99,9 @@ pub fn decrypt_observer_event( .to_bech32() .map_err(|error| format!("encode nsec: {error}"))? }; - let keys = CompatKeys::parse(&nsec).map_err(|error| format!("parse nsec: {error}"))?; + let keys = Keys::parse(&nsec).map_err(|error| format!("parse nsec: {error}"))?; let event = - CompatEvent::from_json(event_json).map_err(|error| format!("invalid event: {error}"))?; + Event::from_json(event_json).map_err(|error| format!("invalid event: {error}"))?; // Defense-in-depth: verify event ID and signature before decrypting. if !event.verify_id() { @@ -131,8 +127,8 @@ pub fn build_observer_control_event( .to_bech32() .map_err(|error| format!("encode nsec: {error}"))? }; - let keys = CompatKeys::parse(&nsec).map_err(|error| format!("parse nsec: {error}"))?; - let agent_pubkey = CompatPublicKey::from_hex(agent_pubkey.trim()) + let keys = Keys::parse(&nsec).map_err(|error| format!("parse nsec: {error}"))?; + let agent_pubkey = PublicKey::from_hex(agent_pubkey.trim()) .map_err(|error| format!("invalid agent pubkey: {error}"))?; let agent_pubkey_hex = agent_pubkey.to_hex(); let encrypted = sprout_core::observer::encrypt_observer_payload(&keys, &agent_pubkey, &payload) diff --git a/examples/countdown-bot/src/main.rs b/examples/countdown-bot/src/main.rs index 1507f87cf..14bcebe2e 100644 --- a/examples/countdown-bot/src/main.rs +++ b/examples/countdown-bot/src/main.rs @@ -150,7 +150,9 @@ fn build_auth_event(config: &Config, challenge: &str) -> Result { Tag::parse(["challenge", challenge])?, auth_tag.clone(), ]; - Ok(EventBuilder::new(Kind::Authentication, "").tags( tags).sign_with_keys(&config.bot_keys)?) + Ok(EventBuilder::new(Kind::Authentication, "") + .tags(tags) + .sign_with_keys(&config.bot_keys)?) } else { Ok(EventBuilder::auth(challenge, relay_url).sign_with_keys(&config.bot_keys)?) } @@ -174,14 +176,11 @@ async fn publish_profile(ws: &mut Ws, config: &Config) -> Result<()> { } async fn announce_channel_membership(ws: &mut Ws, config: &Config) -> Result<()> { - let builder = EventBuilder::new( - Kind::Custom(9000), - "").tags( - [ - Tag::parse(["h", config.channel_id.as_str()])?, - Tag::parse(["p", &config.bot_keys.public_key().to_hex()])?, - Tag::parse(["role", "bot"])?, - ]); + let builder = EventBuilder::new(Kind::Custom(9000), "").tags([ + Tag::parse(["h", config.channel_id.as_str()])?, + Tag::parse(["p", &config.bot_keys.public_key().to_hex()])?, + Tag::parse(["role", "bot"])?, + ]); let event = builder.sign_with_keys(&config.bot_keys)?; let event_id = event.id.to_hex(); From fc5ac87cd638c851ac247c735b98791800c0ee90 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Thu, 21 May 2026 17:50:25 -0400 Subject: [PATCH 03/10] style: fix cargo fmt in desktop Tauri workspace The previous commit ran cargo fmt at the root workspace but missed the desktop/src-tauri workspace which has its own Cargo.toml. --- desktop/src-tauri/src/commands/identity.rs | 7 ++++--- desktop/src-tauri/src/commands/pairing.rs | 14 ++++---------- desktop/src-tauri/src/relay.rs | 5 ++--- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/desktop/src-tauri/src/commands/identity.rs b/desktop/src-tauri/src/commands/identity.rs index e199512bf..f883298e1 100644 --- a/desktop/src-tauri/src/commands/identity.rs +++ b/desktop/src-tauri/src/commands/identity.rs @@ -1,4 +1,6 @@ -use nostr::{nips::nip44, Event, EventBuilder, JsonUtil, Keys, Kind, PublicKey, Tag, Timestamp, ToBech32}; +use nostr::{ + nips::nip44, Event, EventBuilder, JsonUtil, Keys, Kind, PublicKey, Tag, Timestamp, ToBech32, +}; use tauri::Manager; use tauri::State; @@ -100,8 +102,7 @@ pub fn decrypt_observer_event( .map_err(|error| format!("encode nsec: {error}"))? }; let keys = Keys::parse(&nsec).map_err(|error| format!("parse nsec: {error}"))?; - let event = - Event::from_json(event_json).map_err(|error| format!("invalid event: {error}"))?; + let event = Event::from_json(event_json).map_err(|error| format!("invalid event: {error}"))?; // Defense-in-depth: verify event ID and signature before decrypting. if !event.verify_id() { diff --git a/desktop/src-tauri/src/commands/pairing.rs b/desktop/src-tauri/src/commands/pairing.rs index bca30419b..6c5fc1868 100644 --- a/desktop/src-tauri/src/commands/pairing.rs +++ b/desktop/src-tauri/src/commands/pairing.rs @@ -363,21 +363,15 @@ where Err(_) => return Ok(()), }; - let relay_url_parsed = nostr::RelayUrl::parse(relay_url) - .map_err(|e| format!("invalid relay URL: {e}"))?; + let relay_url_parsed = + nostr::RelayUrl::parse(relay_url).map_err(|e| format!("invalid relay URL: {e}"))?; let auth_json = { let guard = session.lock().await; let s = guard.as_ref().ok_or("session gone during auth")?; let auth_event = s - .sign_event(nostr::EventBuilder::auth( - challenge, - relay_url_parsed, - )) + .sign_event(nostr::EventBuilder::auth(challenge, relay_url_parsed)) .map_err(|e| format!("sign auth event: {e}"))?; - format!( - "[\"AUTH\",{}]", - nostr::JsonUtil::as_json(&auth_event) - ) + format!("[\"AUTH\",{}]", nostr::JsonUtil::as_json(&auth_event)) }; write diff --git a/desktop/src-tauri/src/relay.rs b/desktop/src-tauri/src/relay.rs index 4ff6137ce..05d1370d6 100644 --- a/desktop/src-tauri/src/relay.rs +++ b/desktop/src-tauri/src/relay.rs @@ -7,7 +7,6 @@ use sha2::{Digest, Sha256}; // nostr 0.36 alias — required for cross-version bridging with sprout-sdk. - use crate::app_state::AppState; const DEFAULT_RELAY_WS_URL: &str = "ws://localhost:3000"; @@ -403,8 +402,8 @@ mod tests { fn make_valid_auth_tag(agent_keys: &nostr::Keys) -> String { let owner_keys = nostr::Keys::generate(); let agent_pubkey_hex = agent_keys.public_key().to_hex(); - let agent_compat_pubkey = nostr::PublicKey::from_hex(&agent_pubkey_hex) - .expect("valid hex pubkey should parse"); + let agent_compat_pubkey = + nostr::PublicKey::from_hex(&agent_pubkey_hex).expect("valid hex pubkey should parse"); sprout_sdk::nip_oa::compute_auth_tag(&owner_keys, &agent_compat_pubkey, "") .expect("compute_auth_tag should not fail with distinct keys") } From bac1241f202aa27f1de3aded7e86358606bdc707 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Thu, 21 May 2026 18:08:14 -0400 Subject: [PATCH 04/10] fix(deps): resolve clippy warnings in git-sign-nostr Remove unused XOnlyPublicKey import, explicit auto-deref on raw_key, and needless borrow on SECP256K1 constant. --- crates/git-sign-nostr/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/git-sign-nostr/src/lib.rs b/crates/git-sign-nostr/src/lib.rs index a7aa5e463..e13f7debc 100644 --- a/crates/git-sign-nostr/src/lib.rs +++ b/crates/git-sign-nostr/src/lib.rs @@ -83,7 +83,7 @@ use chrono::DateTime; use nostr::hashes::sha256::Hash as Sha256Hash; use nostr::hashes::{Hash, HashEngine}; use nostr::secp256k1::schnorr::Signature; -use nostr::secp256k1::{Keypair, Message, XOnlyPublicKey}; +use nostr::secp256k1::{Keypair, Message}; use nostr::{FromBech32, PublicKey, SecretKey, SECP256K1}; use zeroize::Zeroize; @@ -957,7 +957,7 @@ fn do_sign(key_id: &str, status: &mut StatusWriter) -> Result<(), Error> { // Parse directly into SecretKey — avoids nostr::Keys which stores // non-zeroizable copies of the secret material internally. - let mut secret_key = match SecretKey::parse(&*raw_key) { + let mut secret_key = match SecretKey::parse(&raw_key) { Ok(k) => k, Err(e) => { raw_key.zeroize(); @@ -970,7 +970,7 @@ fn do_sign(key_id: &str, status: &mut StatusWriter) -> Result<(), Error> { // Drop secret_key immediately after creating the keypair so it doesn't // linger on the stack through the rest of the function. // Wrapped in KeypairGuard so non_secure_erase() runs on ALL exit paths. - let keypair = KeypairGuard::new(Keypair::from_secret_key(&SECP256K1, &secret_key)); + let keypair = KeypairGuard::new(Keypair::from_secret_key(SECP256K1, &secret_key)); // Explicitly zero the SecretKey stack slot before dropping. nostr::SecretKey's // Drop calls inner.non_secure_erase(), but that operates on the moved value. // This write_bytes targets our local copy to minimize residual secret material. From e41eaa0f5eda7d2cb6659bb46c00dc29ace81621 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Thu, 21 May 2026 18:40:13 -0400 Subject: [PATCH 05/10] fix(deps): resolve all clippy warnings for nostr 0.44 - Replace deprecated Timestamp::as_u64() with as_secs() across all crates - Remove unused Url imports in sprout-mcp and sprout-acp - Fix Tag::parse trait bound error in sprout-acp test - Fix XOnlyPublicKey usage in git-sign-nostr test code --- crates/git-sign-nostr/src/lib.rs | 12 +++++----- crates/sprout-acp/src/filter.rs | 4 ++-- crates/sprout-acp/src/lib.rs | 4 ++-- crates/sprout-acp/src/queue.rs | 6 ++--- crates/sprout-acp/src/relay.rs | 8 +++---- crates/sprout-cli/src/client.rs | 2 +- crates/sprout-cli/src/commands/mem.rs | 8 +++---- crates/sprout-db/src/event.rs | 4 ++-- crates/sprout-db/src/lib.rs | 6 ++--- crates/sprout-mcp/src/relay_client.rs | 2 +- crates/sprout-mcp/src/server.rs | 22 ++++++++--------- crates/sprout-mcp/src/upload.rs | 2 +- crates/sprout-media/src/auth.rs | 14 +++++------ crates/sprout-proxy/src/server.rs | 4 ++-- crates/sprout-relay/src/api/bridge.rs | 4 ++-- crates/sprout-relay/src/bin/reindex_kind0.rs | 2 +- .../src/handlers/command_executor.rs | 2 +- crates/sprout-relay/src/handlers/event.rs | 2 +- crates/sprout-relay/src/handlers/ingest.rs | 20 +++++++++------- .../sprout-relay/src/handlers/relay_admin.rs | 4 ++-- crates/sprout-relay/src/handlers/req.rs | 10 ++++---- .../sprout-relay/src/handlers/side_effects.rs | 4 ++-- crates/sprout-relay/src/protocol.rs | 2 +- crates/sprout-relay/src/workflow_sink.rs | 2 +- crates/sprout-search/src/index.rs | 2 +- crates/sprout-test-client/src/lib.rs | 4 ++-- .../sprout-test-client/tests/e2e_long_form.rs | 4 ++-- crates/sprout-test-client/tests/e2e_media.rs | 2 +- .../tests/e2e_media_extended.rs | 14 +++++------ .../tests/e2e_media_video.rs | 2 +- .../tests/e2e_nostr_interop.rs | 11 ++++----- crates/sprout-test-client/tests/e2e_relay.rs | 24 +++++++++---------- .../tests/e2e_user_status.rs | 4 ++-- crates/sprout-workflow/src/lib.rs | 4 ++-- 34 files changed, 110 insertions(+), 111 deletions(-) diff --git a/crates/git-sign-nostr/src/lib.rs b/crates/git-sign-nostr/src/lib.rs index e13f7debc..632594aa1 100644 --- a/crates/git-sign-nostr/src/lib.rs +++ b/crates/git-sign-nostr/src/lib.rs @@ -2038,9 +2038,9 @@ Initial commit" /// Helper: sign a payload and return the armored signature fn sign_payload(secret_hex: &str, payload: &[u8], t: u64) -> String { - let keypair = Keypair::from_seckey_str(&SECP256K1, secret_hex).unwrap(); + let keypair = Keypair::from_seckey_str(SECP256K1, secret_hex).unwrap(); let (xonly, _) = keypair.x_only_public_key(); - let pk_hex = hex::encode(xonly.to_bytes()); + let pk_hex = hex::encode(xonly.serialize()); let hash = compute_signing_hash(t, None, payload); let message = Message::from_digest(hash); let sig = SECP256K1.sign_schnorr(&message, &keypair); @@ -2071,9 +2071,9 @@ Initial commit" let message = Message::from_digest(hash); let sig_bytes = hex::decode(&envelope.sig).map_err(|_| "bad sig hex")?; let sig = Signature::from_slice(&sig_bytes).map_err(|_| "bad sig")?; - let xonly: &XOnlyPublicKey = &pk; + let xonly = pk.xonly().map_err(|_| "xonly conversion failed")?; SECP256K1 - .verify_schnorr(&sig, &message, xonly) + .verify_schnorr(&sig, &message, &xonly) .map_err(|_| "signature verification failed")?; Ok(envelope) } @@ -2119,9 +2119,9 @@ Initial commit" fn test_verify_rejects_non_canonical_json() { // Build a valid signature but with extra whitespace in JSON let secret = "0000000000000000000000000000000000000000000000000000000000000003"; - let keypair = Keypair::from_seckey_str(&SECP256K1, secret).unwrap(); + let keypair = Keypair::from_seckey_str(SECP256K1, secret).unwrap(); let (xonly, _) = keypair.x_only_public_key(); - let pk_hex = hex::encode(xonly.to_bytes()); + let pk_hex = hex::encode(xonly.serialize()); let payload = test_payload(); let hash = compute_signing_hash(1700000000, None, &payload); let message = Message::from_digest(hash); diff --git a/crates/sprout-acp/src/filter.rs b/crates/sprout-acp/src/filter.rs index 5d9930177..1b901524a 100644 --- a/crates/sprout-acp/src/filter.rs +++ b/crates/sprout-acp/src/filter.rs @@ -49,7 +49,7 @@ impl FilterContext { author: event.pubkey.to_hex(), kind: event.kind.as_u16() as u32, channel_id: channel_id.to_string(), - timestamp: event.created_at.as_u64(), + timestamp: event.created_at.as_secs(), } } } @@ -537,7 +537,7 @@ mod tests { assert_eq!(ctx.author, event.pubkey.to_hex()); assert_eq!(ctx.kind, 9); assert_eq!(ctx.channel_id, channel_id.to_string()); - assert_eq!(ctx.timestamp, event.created_at.as_u64()); + assert_eq!(ctx.timestamp, event.created_at.as_secs()); } // ── evaluate_filter ─────────────────────────────────────────────────────── diff --git a/crates/sprout-acp/src/lib.rs b/crates/sprout-acp/src/lib.rs index 3801d94c8..9de5586bd 100644 --- a/crates/sprout-acp/src/lib.rs +++ b/crates/sprout-acp/src/lib.rs @@ -507,7 +507,7 @@ fn handle_relay_observer_control_event( // Freshness: reject stale/replayed frames outside ±5 minute window. let now = chrono::Utc::now().timestamp(); - let event_ts = event.created_at.as_u64() as i64; + let event_ts = event.created_at.as_secs() as i64; if (event_ts - now).unsigned_abs() > OBSERVER_CONTROL_FRESHNESS_SECS as u64 { tracing::warn!( event_ts, @@ -1352,7 +1352,7 @@ async fn tokio_main() -> Result<()> { || kind_u32 == KIND_MEMBER_REMOVED_NOTIFICATION { let ch = sprout_event.channel_id; - let ts = sprout_event.event.created_at.as_u64(); + let ts = sprout_event.event.created_at.as_secs(); let eid = sprout_event.event.id.to_hex(); // Two-layer membership dedup: diff --git a/crates/sprout-acp/src/queue.rs b/crates/sprout-acp/src/queue.rs index cbdae3338..4fb5865d6 100644 --- a/crates/sprout-acp/src/queue.rs +++ b/crates/sprout-acp/src/queue.rs @@ -755,9 +755,9 @@ fn format_event_block( let hex = be.event.pubkey.to_hex(); let npub = be.event.pubkey.to_bech32().unwrap_or_else(|_| hex.clone()); - let time = chrono::DateTime::from_timestamp(be.event.created_at.as_u64() as i64, 0) + let time = chrono::DateTime::from_timestamp(be.event.created_at.as_secs() as i64, 0) .map(|dt| dt.to_rfc3339()) - .unwrap_or_else(|| be.event.created_at.as_u64().to_string()); + .unwrap_or_else(|| be.event.created_at.as_secs().to_string()); let kind = be.event.kind.as_u16() as u32; let event_id = be.event.id.to_hex(); @@ -1868,7 +1868,7 @@ mod tests { .iter() .map(|t| { let strs: Vec<&str> = t.iter().map(|s| s.as_str()).collect(); - nostr::Tag::parse(&strs).unwrap() + nostr::Tag::parse(strs).unwrap() }) .collect(); EventBuilder::new(Kind::Custom(9), content) diff --git a/crates/sprout-acp/src/relay.rs b/crates/sprout-acp/src/relay.rs index 56806646d..aabc8a9fa 100644 --- a/crates/sprout-acp/src/relay.rs +++ b/crates/sprout-acp/src/relay.rs @@ -62,7 +62,7 @@ const CONNECT_TIMEOUT: Duration = Duration::from_secs(10); use std::time::Instant; use futures_util::{SinkExt, StreamExt}; -use nostr::{Event, EventBuilder, Keys, Kind, RelayUrl, Tag, Url as NostrUrl}; +use nostr::{Event, EventBuilder, Keys, Kind, RelayUrl, Tag}; use serde_json::{json, Value}; use sprout_core::kind::{ KIND_AGENT_OBSERVER_FRAME, KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, @@ -933,7 +933,7 @@ impl BgState { } // Update last_seen timestamp. - let ts = event.created_at.as_u64(); + let ts = event.created_at.as_secs(); self.last_seen .entry(channel_id) .and_modify(|t| *t = (*t).max(ts)) @@ -1618,7 +1618,7 @@ async fn handle_ws_message( ); return true; } - let ts = event.created_at.as_u64(); + let ts = event.created_at.as_secs(); let sprout_event = SproutEvent { channel_id: channel_uuid, event: *event, @@ -1659,7 +1659,7 @@ async fn handle_ws_message( Err(mpsc::error::TrySendError::Closed(_)) => return false, } } else if let Some(channel_id) = channel_id_from_sub_id(&subscription_id) { - let ts = event.created_at.as_u64(); + let ts = event.created_at.as_secs(); let event_id_hex = event.id.to_hex(); if state.record_event(channel_id, &event) { let sprout_event = SproutEvent { diff --git a/crates/sprout-cli/src/client.rs b/crates/sprout-cli/src/client.rs index 846914db3..79445b4cb 100644 --- a/crates/sprout-cli/src/client.rs +++ b/crates/sprout-cli/src/client.rs @@ -328,7 +328,7 @@ impl SproutClient { // 5. Sign Blossom auth event (kind:24242) use nostr::Timestamp; - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let expiry = if mime.starts_with("video/") { 3600 } else { diff --git a/crates/sprout-cli/src/commands/mem.rs b/crates/sprout-cli/src/commands/mem.rs index 2d3a9e44c..cb5bdb262 100644 --- a/crates/sprout-cli/src/commands/mem.rs +++ b/crates/sprout-cli/src/commands/mem.rs @@ -252,7 +252,7 @@ pub async fn cmd_ls( listings.push(Listing { slug: slug.clone(), event_id: head.id.to_hex(), - created_at: head.created_at.as_u64(), + created_at: head.created_at.as_secs(), }); } } @@ -359,7 +359,7 @@ pub async fn cmd_set( }; let agent_pubkey = client.keys().public_key(); let (head, _) = fetch_head(client, &agent_pubkey, &owner, &slug).await?; - let prior_created_at = head.map(|e| e.created_at.as_u64()); + let prior_created_at = head.map(|e| e.created_at.as_secs()); let created_at = engram::monotonic_created_at(now_secs(), prior_created_at); let agent = client.keys(); @@ -685,7 +685,7 @@ pub async fn cmd_patch( value: Some(new_value.clone()), } }; - let prior_created_at = Some(head.created_at.as_u64()); + let prior_created_at = Some(head.created_at.as_secs()); let created_at = engram::monotonic_created_at(now_secs(), prior_created_at); let agent = client.keys(); @@ -722,7 +722,7 @@ pub async fn cmd_rm( }; let agent_pubkey = client.keys().public_key(); let (head, _) = fetch_head(client, &agent_pubkey, &owner, &slug).await?; - let prior_created_at = head.map(|e| e.created_at.as_u64()); + let prior_created_at = head.map(|e| e.created_at.as_secs()); let created_at = engram::monotonic_created_at(now_secs(), prior_created_at); let agent = client.keys(); diff --git a/crates/sprout-db/src/event.rs b/crates/sprout-db/src/event.rs index 2c17c4fdb..e897d6035 100644 --- a/crates/sprout-db/src/event.rs +++ b/crates/sprout-db/src/event.rs @@ -120,7 +120,7 @@ pub async fn insert_event( let tags_json = serde_json::to_value(&event.tags)?; // Cast chain: nostr Kind (u16) → i32 (Postgres INT column). Safe: all Sprout kinds fit in i32. let kind_i32 = event_kind_i32(event); - let created_at_secs = event.created_at.as_u64() as i64; + let created_at_secs = event.created_at.as_secs() as i64; let created_at = DateTime::from_timestamp(created_at_secs, 0) .ok_or(DbError::InvalidTimestamp(created_at_secs))?; let received_at = Utc::now(); @@ -837,7 +837,7 @@ pub async fn insert_event_with_thread_metadata( let sig_bytes = event.sig.serialize(); let tags_json = serde_json::to_value(&event.tags)?; let kind_i32 = event_kind_i32(event); - let created_at_secs = event.created_at.as_u64() as i64; + let created_at_secs = event.created_at.as_secs() as i64; let created_at = DateTime::from_timestamp(created_at_secs, 0) .ok_or(DbError::InvalidTimestamp(created_at_secs))?; let received_at = Utc::now(); diff --git a/crates/sprout-db/src/lib.rs b/crates/sprout-db/src/lib.rs index 6a0ffd385..e910fa596 100644 --- a/crates/sprout-db/src/lib.rs +++ b/crates/sprout-db/src/lib.rs @@ -72,7 +72,7 @@ pub async fn insert_mentions( } let event_id_bytes = event.id.as_bytes(); - let created_at_secs = event.created_at.as_u64() as i64; + let created_at_secs = event.created_at.as_secs() as i64; let created_at = DateTime::from_timestamp(created_at_secs, 0) .ok_or(crate::error::DbError::InvalidTimestamp(created_at_secs))?; let kind = event.kind.as_u16() as u32; @@ -1451,7 +1451,7 @@ impl Db { ) -> Result<(StoredEvent, bool)> { let kind_i32 = sprout_core::kind::event_kind_i32(event); let pubkey_bytes = event.pubkey.to_bytes(); - let created_at_secs = event.created_at.as_u64() as i64; + let created_at_secs = event.created_at.as_secs() as i64; let created_at = chrono::DateTime::from_timestamp(created_at_secs, 0) .ok_or(DbError::InvalidTimestamp(created_at_secs))?; @@ -1605,7 +1605,7 @@ impl Db { ) -> Result<(StoredEvent, bool)> { let kind_i32 = sprout_core::kind::event_kind_i32(event); let pubkey_bytes = event.pubkey.to_bytes(); - let created_at_secs = event.created_at.as_u64() as i64; + let created_at_secs = event.created_at.as_secs() as i64; let created_at = chrono::DateTime::from_timestamp(created_at_secs, 0) .ok_or(DbError::InvalidTimestamp(created_at_secs))?; diff --git a/crates/sprout-mcp/src/relay_client.rs b/crates/sprout-mcp/src/relay_client.rs index 68ef2fd71..915bb4b28 100644 --- a/crates/sprout-mcp/src/relay_client.rs +++ b/crates/sprout-mcp/src/relay_client.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::time::Duration; use futures_util::{SinkExt, StreamExt}; -use nostr::{Event, EventBuilder, Filter, Keys, Kind, RelayUrl, Tag, Url}; +use nostr::{Event, EventBuilder, Filter, Keys, Kind, RelayUrl, Tag}; use serde_json::{json, Value}; use thiserror::Error; use tokio::sync::{mpsc, oneshot}; diff --git a/crates/sprout-mcp/src/server.rs b/crates/sprout-mcp/src/server.rs index 57f95cec9..3e5c29d5d 100644 --- a/crates/sprout-mcp/src/server.rs +++ b/crates/sprout-mcp/src/server.rs @@ -1349,7 +1349,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi "pubkey": e.pubkey.to_hex(), "kind": e.kind.as_u16(), "content": e.content, - "created_at": e.created_at.as_u64(), + "created_at": e.created_at.as_secs(), "tags": e.tags.iter().map(|t| t.as_slice()).collect::>(), }) }) @@ -1403,7 +1403,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi "channel_id": channel_id, "name": content.get("name").and_then(|v| v.as_str()).unwrap_or(""), "description": content.get("about").and_then(|v| v.as_str()).unwrap_or(""), - "created_at": e.created_at.as_u64(), + "created_at": e.created_at.as_secs(), }) }) .collect(); @@ -1552,7 +1552,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi serde_json::json!({ "workflow_id": d_tag, "content": e.content, - "created_at": e.created_at.as_u64(), + "created_at": e.created_at.as_secs(), "pubkey": e.pubkey.to_hex(), }) }) @@ -1714,7 +1714,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi "event_id": e.id.to_hex(), "kind": e.kind.as_u16(), "content": e.content, - "created_at": e.created_at.as_u64(), + "created_at": e.created_at.as_secs(), "tags": e.tags.iter().map(|t| t.as_slice()).collect::>(), }) }) @@ -1799,7 +1799,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi "pubkey": e.pubkey.to_hex(), "kind": e.kind.as_u16(), "content": e.content, - "created_at": e.created_at.as_u64(), + "created_at": e.created_at.as_secs(), "tags": e.tags.iter().map(|t| t.as_slice()).collect::>(), }) }) @@ -2025,7 +2025,7 @@ are not returned — use `get_thread` to fetch the full reply tree for a specifi "channel_id": p.channel_id, "name": content.get("name").and_then(|v| v.as_str()).unwrap_or(""), "description": content.get("about").and_then(|v| v.as_str()).unwrap_or(""), - "created_at": event.created_at.as_u64(), + "created_at": event.created_at.as_secs(), "pubkey": event.pubkey.to_hex(), }) .to_string() @@ -2251,7 +2251,7 @@ with kind:45003 comments)." "pubkey": e.pubkey.to_hex(), "kind": e.kind.as_u16(), "content": e.content, - "created_at": e.created_at.as_u64(), + "created_at": e.created_at.as_secs(), "tags": e.tags.iter().map(|t| t.as_slice()).collect::>(), }) }) @@ -2371,7 +2371,7 @@ with kind:45003 comments)." serde_json::json!({ "dm_id": dm_id, "participants": participants, - "created_at": e.created_at.as_u64(), + "created_at": e.created_at.as_secs(), }) }) .collect(); @@ -2672,7 +2672,7 @@ with kind:45003 comments)." "pubkey": e.pubkey.to_hex(), "kind": e.kind.as_u16(), "content": e.content, - "created_at": e.created_at.as_u64(), + "created_at": e.created_at.as_secs(), "tags": e.tags.iter().map(|t| t.as_slice()).collect::>(), }) }) @@ -2714,7 +2714,7 @@ with kind:45003 comments)." serde_json::json!({ "pubkey": e.pubkey.to_hex(), "status": e.content, - "updated_at": e.created_at.as_u64(), + "updated_at": e.created_at.as_secs(), }) }) .collect(); @@ -2999,7 +2999,7 @@ with kind:45003 comments)." "id": e.id.to_hex(), "pubkey": e.pubkey.to_hex(), "content": e.content, - "created_at": e.created_at.as_u64(), + "created_at": e.created_at.as_secs(), }) }) .collect(); diff --git a/crates/sprout-mcp/src/upload.rs b/crates/sprout-mcp/src/upload.rs index d602c8207..2f195d1c2 100644 --- a/crates/sprout-mcp/src/upload.rs +++ b/crates/sprout-mcp/src/upload.rs @@ -186,7 +186,7 @@ pub async fn upload_file( let sha256 = hex::encode(Sha256::digest(&bytes)); // 6. Sign Blossom auth event (kind:24242) - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let expiry = if mime.starts_with("video/") { 3600 } else { diff --git a/crates/sprout-media/src/auth.rs b/crates/sprout-media/src/auth.rs index 4a9d21983..91b7555e0 100644 --- a/crates/sprout-media/src/auth.rs +++ b/crates/sprout-media/src/auth.rs @@ -72,7 +72,7 @@ pub fn verify_blossom_auth_event( if !found_exp { return Err(MediaError::MissingTag("expiration")); } - let now = nostr::Timestamp::now().as_u64(); + let now = nostr::Timestamp::now().as_secs(); if exp_value <= now { return Err(MediaError::TokenExpired); } @@ -81,7 +81,7 @@ pub fn verify_blossom_auth_event( // older than 10 minutes. This bounds the replay window — even if the // expiration tag allows a longer lifetime, the token must have been // freshly minted. - let created = auth_event.created_at.as_u64(); + let created = auth_event.created_at.as_secs(); if created > now + 5 { return Err(MediaError::TimestampOutOfWindow); } @@ -140,7 +140,7 @@ mod tests { use nostr::{EventBuilder, Keys, Kind, Tag, Timestamp}; fn build_valid_auth(keys: &Keys, sha256: &str) -> nostr::Event { - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let exp_str = (now + 300).to_string(); let tags = vec![ Tag::parse(["t", "upload"]).unwrap(), @@ -185,7 +185,7 @@ mod tests { fn test_verify_wrong_kind() { let keys = Keys::generate(); let sha256 = "a".repeat(64); - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let exp_str = (now + 300).to_string(); let tags = vec![ Tag::parse(["t", "upload"]).unwrap(), @@ -207,7 +207,7 @@ mod tests { let keys = Keys::generate(); let sha256 = "a".repeat(64); let other_hash = "b".repeat(64); - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let exp_str = (now + 300).to_string(); let tags = vec![ Tag::parse(["t", "upload"]).unwrap(), @@ -227,7 +227,7 @@ mod tests { fn test_server_tag_enforcement() { let keys = Keys::generate(); let sha256 = "a".repeat(64); - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let exp_str = (now + 300).to_string(); let tags = vec![ Tag::parse(["t", "upload"]).unwrap(), @@ -268,7 +268,7 @@ mod tests { fn test_empty_content_rejected() { let keys = Keys::generate(); let sha256 = "a".repeat(64); - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let exp_str = (now + 300).to_string(); let tags = vec![ Tag::parse(["t", "upload"]).unwrap(), diff --git a/crates/sprout-proxy/src/server.rs b/crates/sprout-proxy/src/server.rs index 9e898a079..7bae3c42c 100644 --- a/crates/sprout-proxy/src/server.rs +++ b/crates/sprout-proxy/src/server.rs @@ -236,8 +236,8 @@ async fn handle_ws(mut socket: WebSocket, state: ProxyState, token: String) { // FIX 4: Timestamp recency check — must be within 10 minutes of now. let time_diff = Timestamp::now() - .as_u64() - .abs_diff(auth_event.created_at.as_u64()); + .as_secs() + .abs_diff(auth_event.created_at.as_secs()); if time_diff >= 600 { let _ = send_relay_msg( &mut socket, diff --git a/crates/sprout-relay/src/api/bridge.rs b/crates/sprout-relay/src/api/bridge.rs index 169fccd5f..708e37fa1 100644 --- a/crates/sprout-relay/src/api/bridge.rs +++ b/crates/sprout-relay/src/api/bridge.rs @@ -482,10 +482,10 @@ async fn handle_bridge_search( } } if let Some(since) = filter.since { - filter_parts.push(format!("created_at:>={}", since.as_u64())); + filter_parts.push(format!("created_at:>={}", since.as_secs())); } if let Some(until) = filter.until { - filter_parts.push(format!("created_at:<={}", until.as_u64())); + filter_parts.push(format!("created_at:<={}", until.as_secs())); } let filter_by = filter_parts.join(" && "); diff --git a/crates/sprout-relay/src/bin/reindex_kind0.rs b/crates/sprout-relay/src/bin/reindex_kind0.rs index 7b26e7158..58cd3bb4d 100644 --- a/crates/sprout-relay/src/bin/reindex_kind0.rs +++ b/crates/sprout-relay/src/bin/reindex_kind0.rs @@ -119,7 +119,7 @@ async fn main() -> anyhow::Result<()> { let tail = batch .last() .map(|ev| { - let ts = ev.event.created_at.as_u64() as i64; + let ts = ev.event.created_at.as_secs() as i64; let dt = DateTime::::from_timestamp(ts, 0).unwrap_or(cursor_until); let id_bytes = ev.event.id.to_bytes().to_vec(); (dt, id_bytes) diff --git a/crates/sprout-relay/src/handlers/command_executor.rs b/crates/sprout-relay/src/handlers/command_executor.rs index 43aa06660..781b44732 100644 --- a/crates/sprout-relay/src/handlers/command_executor.rs +++ b/crates/sprout-relay/src/handlers/command_executor.rs @@ -101,7 +101,7 @@ async fn persist_command_event( let tags_json = serde_json::to_value(&event.tags) .map_err(|e| IngestError::Internal(format!("error: serialize tags: {e}")))?; let kind_i32 = event.kind.as_u16() as i32; - let created_at_secs = event.created_at.as_u64() as i64; + let created_at_secs = event.created_at.as_secs() as i64; let created_at = chrono::DateTime::from_timestamp(created_at_secs, 0).ok_or_else(|| { IngestError::Rejected(format!("invalid: bad timestamp {created_at_secs}")) })?; diff --git a/crates/sprout-relay/src/handlers/event.rs b/crates/sprout-relay/src/handlers/event.rs index 4e053f496..e5d6a0e08 100644 --- a/crates/sprout-relay/src/handlers/event.rs +++ b/crates/sprout-relay/src/handlers/event.rs @@ -515,7 +515,7 @@ async fn handle_agent_observer_event( // Freshness check: reject observer frames with stale/future timestamps let now = chrono::Utc::now().timestamp(); - let event_ts = event.created_at.as_u64() as i64; + let event_ts = event.created_at.as_secs() as i64; if (event_ts - now).unsigned_abs() > 300 { conn.send(RelayMessage::ok( event_id_hex, diff --git a/crates/sprout-relay/src/handlers/ingest.rs b/crates/sprout-relay/src/handlers/ingest.rs index 99ef3c727..28eae2c55 100644 --- a/crates/sprout-relay/src/handlers/ingest.rs +++ b/crates/sprout-relay/src/handlers/ingest.rs @@ -499,7 +499,7 @@ pub(crate) async fn resolve_nip10_thread_meta( } let parent_created = - chrono::DateTime::from_timestamp(parent_event.event.created_at.as_u64() as i64, 0) + chrono::DateTime::from_timestamp(parent_event.event.created_at.as_secs() as i64, 0) .unwrap_or_else(Utc::now); let client_root_bytes = @@ -516,7 +516,7 @@ pub(crate) async fn resolve_nip10_thread_meta( } let root_ts = if let Ok(Some(root_ev)) = state.db.get_event_by_id(&effective_root).await { - chrono::DateTime::from_timestamp(root_ev.event.created_at.as_u64() as i64, 0) + chrono::DateTime::from_timestamp(root_ev.event.created_at.as_secs() as i64, 0) .unwrap_or(parent_created) } else { parent_created @@ -558,7 +558,7 @@ pub(crate) async fn resolve_nip10_thread_meta( let depth = if parent_root == parent_bytes { 1 } else { 2 }; let root_created = if parent_root != parent_bytes { if let Ok(Some(root_ev)) = state.db.get_event_by_id(&parent_root).await { - chrono::DateTime::from_timestamp(root_ev.event.created_at.as_u64() as i64, 0) + chrono::DateTime::from_timestamp(root_ev.event.created_at.as_secs() as i64, 0) .unwrap_or(parent_created) } else { parent_created @@ -575,7 +575,7 @@ pub(crate) async fn resolve_nip10_thread_meta( parts.len() >= 2 && parts[0] == "broadcast" && parts[1] == "1" }); - let event_created_at = chrono::DateTime::from_timestamp(event.created_at.as_u64() as i64, 0) + let event_created_at = chrono::DateTime::from_timestamp(event.created_at.as_secs() as i64, 0) .unwrap_or_else(Utc::now); Ok(Some(ThreadMetadataOwned { @@ -988,7 +988,7 @@ pub async fn ingest_event( if !auth.has_proxy_scope() { const MAX_TIMESTAMP_DRIFT_SECS: i64 = 900; // ±15 minutes let now = chrono::Utc::now().timestamp(); - let event_ts = event.created_at.as_u64() as i64; + let event_ts = event.created_at.as_secs() as i64; if (event_ts - now).abs() > MAX_TIMESTAMP_DRIFT_SECS { return Err(IngestError::Rejected( "invalid: event timestamp too far from server time".into(), @@ -1182,7 +1182,7 @@ pub async fn ingest_event( // Freshness check: reject events outside ±120s of now (same as admin commands). { - let event_ts = event.created_at.as_u64() as i64; + let event_ts = event.created_at.as_secs() as i64; let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|d| d.as_secs() as i64) @@ -1530,7 +1530,7 @@ pub async fn ingest_event( })?; let target_created_at = - chrono::DateTime::from_timestamp(target_event.event.created_at.as_u64() as i64, 0) + chrono::DateTime::from_timestamp(target_event.event.created_at.as_secs() as i64, 0) .unwrap_or_else(chrono::Utc::now); let actor_bytes = effective_message_author(&event, &state.relay_keypair.public_key()); @@ -2032,8 +2032,10 @@ mod tests { fn make_event_with_tags(kind: u32, content: &str, tags: &[&[&str]]) -> Event { let keys = nostr::Keys::generate(); - let nostr_tags: Vec = - tags.iter().map(|t| nostr::Tag::parse(t).unwrap()).collect(); + let nostr_tags: Vec = tags + .iter() + .map(|t| nostr::Tag::parse(t.iter().copied()).unwrap()) + .collect(); nostr::EventBuilder::new(nostr::Kind::Custom(kind as u16), content) .tags(nostr_tags) .sign_with_keys(&keys) diff --git a/crates/sprout-relay/src/handlers/relay_admin.rs b/crates/sprout-relay/src/handlers/relay_admin.rs index 66c76a018..742fc20d6 100644 --- a/crates/sprout-relay/src/handlers/relay_admin.rs +++ b/crates/sprout-relay/src/handlers/relay_admin.rs @@ -77,7 +77,7 @@ pub async fn handle_relay_admin_event(state: &Arc, event: &Event) -> R // of captured admin commands. The window is intentionally tight — admin // events should be freshly signed. { - let event_ts = event.created_at.as_u64() as i64; + let event_ts = event.created_at.as_secs() as i64; let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|d| d.as_secs() as i64) @@ -294,7 +294,7 @@ mod tests { let keys = Keys::generate(); let nostr_tags: Vec = tags .into_iter() - .map(|parts| Tag::parse(&parts).expect("valid tag")) + .map(|parts| Tag::parse(parts).expect("valid tag")) .collect(); EventBuilder::new(Kind::from(kind), "") .tags(nostr_tags) diff --git a/crates/sprout-relay/src/handlers/req.rs b/crates/sprout-relay/src/handlers/req.rs index 1421fdc46..0c6ca7346 100644 --- a/crates/sprout-relay/src/handlers/req.rs +++ b/crates/sprout-relay/src/handlers/req.rs @@ -346,10 +346,10 @@ async fn handle_search_req( } } if let Some(since) = filter.since { - filter_parts.push(format!("created_at:>={}", since.as_u64())); + filter_parts.push(format!("created_at:>={}", since.as_secs())); } if let Some(until) = filter.until { - filter_parts.push(format!("created_at:<={}", until.as_u64())); + filter_parts.push(format!("created_at:<={}", until.as_secs())); } let filter_by = filter_parts.join(" && "); @@ -557,10 +557,10 @@ fn filter_to_query_params(filter: &Filter, channel_id: Option) -> Ev let since = filter .since - .and_then(|s| chrono::DateTime::from_timestamp(s.as_u64() as i64, 0)); + .and_then(|s| chrono::DateTime::from_timestamp(s.as_secs() as i64, 0)); let until = filter .until - .and_then(|u| chrono::DateTime::from_timestamp(u.as_u64() as i64, 0)); + .and_then(|u| chrono::DateTime::from_timestamp(u.as_secs() as i64, 0)); let limit = filter .limit .map(|l| (l as i64).min(MAX_HISTORICAL_LIMIT)) @@ -791,7 +791,7 @@ mod tests { fn filter_with_channel(channel_id: uuid::Uuid) -> Filter { Filter::new().custom_tag( SingleLetterTag::lowercase(Alphabet::H), - [channel_id.to_string()], + channel_id.to_string(), ) } diff --git a/crates/sprout-relay/src/handlers/side_effects.rs b/crates/sprout-relay/src/handlers/side_effects.rs index f3e0feb24..f45960869 100644 --- a/crates/sprout-relay/src/handlers/side_effects.rs +++ b/crates/sprout-relay/src/handlers/side_effects.rs @@ -542,7 +542,7 @@ async fn emit_addressable_discovery_event( .unwrap_or_default(); existing .first() - .map(|e| e.event.created_at.as_u64() + 1) + .map(|e| e.event.created_at.as_secs() + 1) .unwrap_or(now) }; let ts = now.max(min_ts); @@ -1511,7 +1511,7 @@ async fn handle_standard_deletion_event( state.db.get_event_by_id(&react_target_id).await { let react_target_ts = chrono::DateTime::from_timestamp( - react_target_event.event.created_at.as_u64() as i64, + react_target_event.event.created_at.as_secs() as i64, 0, ) .unwrap_or_else(chrono::Utc::now); diff --git a/crates/sprout-relay/src/protocol.rs b/crates/sprout-relay/src/protocol.rs index 6cac61259..e997976e7 100644 --- a/crates/sprout-relay/src/protocol.rs +++ b/crates/sprout-relay/src/protocol.rs @@ -223,7 +223,7 @@ mod tests { use sprout_core::test_helpers::make_event; fn make_auth_event(keys: &Keys, challenge: &str, relay: &str) -> Event { - let url: nostr::Url = relay.parse().expect("url"); + let url: nostr::RelayUrl = relay.parse().expect("url"); EventBuilder::auth(challenge, url) .sign_with_keys(keys) .expect("sign") diff --git a/crates/sprout-relay/src/workflow_sink.rs b/crates/sprout-relay/src/workflow_sink.rs index 4fcd2597a..46a6b9cf7 100644 --- a/crates/sprout-relay/src/workflow_sink.rs +++ b/crates/sprout-relay/src/workflow_sink.rs @@ -124,7 +124,7 @@ impl ActionSink for RelayActionSink { let kind_u32 = KIND_STREAM_MESSAGE; let event_created_at = { - let ts = event.created_at.as_u64() as i64; + let ts = event.created_at.as_secs() as i64; chrono::DateTime::from_timestamp(ts, 0).unwrap_or_else(Utc::now) }; diff --git a/crates/sprout-search/src/index.rs b/crates/sprout-search/src/index.rs index 98f1e215a..29b4f4b9f 100644 --- a/crates/sprout-search/src/index.rs +++ b/crates/sprout-search/src/index.rs @@ -77,7 +77,7 @@ pub fn event_to_document(event: &StoredEvent) -> Result { "kind": event_kind_i32(nostr_event), "pubkey": nostr_event.pubkey.to_string(), "channel_id": channel_id_val, - "created_at": nostr_event.created_at.as_u64() as i64, + "created_at": nostr_event.created_at.as_secs() as i64, "tags_flat": tags_flat, }); diff --git a/crates/sprout-test-client/src/lib.rs b/crates/sprout-test-client/src/lib.rs index 563eb6a24..04d81194d 100644 --- a/crates/sprout-test-client/src/lib.rs +++ b/crates/sprout-test-client/src/lib.rs @@ -7,7 +7,7 @@ use std::collections::VecDeque; use std::time::Duration; use futures_util::{SinkExt, StreamExt}; -use nostr::{Event, EventBuilder, Filter, Keys, Kind, RelayUrl, Tag, Url}; +use nostr::{Event, EventBuilder, Filter, Keys, Kind, RelayUrl, Tag}; use serde_json::{json, Value}; use thiserror::Error; use tokio::time::timeout; @@ -509,7 +509,7 @@ mod tests { #[test] fn auth_event_has_relay_and_challenge_tags() { let keys = Keys::generate(); - let relay_url: Url = "ws://localhost:3000".parse().unwrap(); + let relay_url: RelayUrl = "ws://localhost:3000".parse().unwrap(); let event = EventBuilder::auth("test-challenge", relay_url) .sign_with_keys(&keys) .unwrap(); diff --git a/crates/sprout-test-client/tests/e2e_long_form.rs b/crates/sprout-test-client/tests/e2e_long_form.rs index 9e0cc79fa..2ed083edf 100644 --- a/crates/sprout-test-client/tests/e2e_long_form.rs +++ b/crates/sprout-test-client/tests/e2e_long_form.rs @@ -268,7 +268,7 @@ async fn test_long_form_stale_write_rejected() { ]; EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "Newer content.") .tags(tags) - .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() + 100)) + .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_secs() + 100)) .sign_with_keys(&keys) .unwrap() }; @@ -284,7 +284,7 @@ async fn test_long_form_stale_write_rejected() { ]; EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "Older content.") .tags(tags) - .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() - 100)) + .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_secs() - 100)) .sign_with_keys(&keys) .unwrap() }; diff --git a/crates/sprout-test-client/tests/e2e_media.rs b/crates/sprout-test-client/tests/e2e_media.rs index ddd0814a0..090a7454e 100644 --- a/crates/sprout-test-client/tests/e2e_media.rs +++ b/crates/sprout-test-client/tests/e2e_media.rs @@ -39,7 +39,7 @@ fn http_client() -> Client { /// Sign a kind:24242 Blossom upload auth event for the given sha256. fn sign_blossom_auth(keys: &Keys, sha256: &str) -> nostr::Event { - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let exp_str = (now + 300).to_string(); let tags = vec![ Tag::parse(["t", "upload"]).expect("t tag"), diff --git a/crates/sprout-test-client/tests/e2e_media_extended.rs b/crates/sprout-test-client/tests/e2e_media_extended.rs index 0a3dae931..7832d0e93 100644 --- a/crates/sprout-test-client/tests/e2e_media_extended.rs +++ b/crates/sprout-test-client/tests/e2e_media_extended.rs @@ -27,7 +27,7 @@ fn http_client() -> Client { } fn sign_blossom_auth(keys: &Keys, sha256: &str) -> nostr::Event { - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let tags = vec![ Tag::parse(["t", "upload"]).unwrap(), Tag::parse(["x", sha256]).unwrap(), @@ -229,7 +229,7 @@ async fn test_auth_wrong_kind() { let keys = Keys::generate(); let jpeg = tiny_jpeg(); let sha256 = hex::encode(Sha256::digest(&jpeg)); - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let auth = sign_custom_auth( &keys, 27235, @@ -252,7 +252,7 @@ async fn test_auth_missing_t_tag() { let keys = Keys::generate(); let jpeg = tiny_jpeg(); let sha256 = hex::encode(Sha256::digest(&jpeg)); - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let auth = sign_custom_auth( &keys, 24242, @@ -295,7 +295,7 @@ async fn test_auth_expired_token() { let keys = Keys::generate(); let jpeg = tiny_jpeg(); let sha256 = hex::encode(Sha256::digest(&jpeg)); - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let auth = sign_custom_auth( &keys, 24242, @@ -318,7 +318,7 @@ async fn test_auth_empty_content() { let keys = Keys::generate(); let jpeg = tiny_jpeg(); let sha256 = hex::encode(Sha256::digest(&jpeg)); - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let auth = sign_custom_auth( &keys, 24242, @@ -341,7 +341,7 @@ async fn test_auth_server_tag_mismatch() { let keys = Keys::generate(); let jpeg = tiny_jpeg(); let sha256 = hex::encode(Sha256::digest(&jpeg)); - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let auth = sign_custom_auth( &keys, 24242, @@ -365,7 +365,7 @@ async fn test_auth_server_tag_correct() { let keys = Keys::generate(); let jpeg = tiny_jpeg(); let sha256 = hex::encode(Sha256::digest(&jpeg)); - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let auth = sign_custom_auth( &keys, 24242, diff --git a/crates/sprout-test-client/tests/e2e_media_video.rs b/crates/sprout-test-client/tests/e2e_media_video.rs index 7c9e09ae4..79d365e18 100644 --- a/crates/sprout-test-client/tests/e2e_media_video.rs +++ b/crates/sprout-test-client/tests/e2e_media_video.rs @@ -31,7 +31,7 @@ fn http_client() -> Client { // ── Blossom auth helpers ────────────────────────────────────────────────────── fn sign_blossom_auth(keys: &Keys, sha256: &str) -> nostr::Event { - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let exp_str = (now + 300).to_string(); let tags = vec![ Tag::parse(["t", "upload"]).expect("t tag"), diff --git a/crates/sprout-test-client/tests/e2e_nostr_interop.rs b/crates/sprout-test-client/tests/e2e_nostr_interop.rs index e46799b2d..37737c3cf 100644 --- a/crates/sprout-test-client/tests/e2e_nostr_interop.rs +++ b/crates/sprout-test-client/tests/e2e_nostr_interop.rs @@ -573,7 +573,7 @@ async fn test_nip17_gift_wrap_recipient_receives() { let sid_b = sub_id("nip17-recv-b"); let filter_b = Filter::new().kind(Kind::Custom(1059)).custom_tag( SingleLetterTag::lowercase(Alphabet::P), - [b_pubkey_hex.as_str()], + b_pubkey_hex.as_str(), ); client_b @@ -662,7 +662,7 @@ async fn test_dm_discovery_events_emitted() { // We'll subscribe with #p = A's pubkey for membership notifications. let membership_filter = Filter::new().kind(Kind::Custom(44100)).custom_tag( SingleLetterTag::lowercase(Alphabet::P), - [a_pubkey_hex.as_str()], + a_pubkey_hex.as_str(), ); client_a @@ -679,10 +679,9 @@ async fn test_dm_discovery_events_emitted() { let channel_id = create_dm(&keys_a, &b_pubkey_hex).await; // Subscribe to 39000 discovery events for this specific DM channel. - let discovery_filter = Filter::new().kind(Kind::Custom(39000)).custom_tag( - SingleLetterTag::lowercase(Alphabet::D), - [channel_id.as_str()], - ); + let discovery_filter = Filter::new() + .kind(Kind::Custom(39000)) + .custom_tag(SingleLetterTag::lowercase(Alphabet::D), channel_id.as_str()); client_a .subscribe(&sid_discovery, vec![discovery_filter]) diff --git a/crates/sprout-test-client/tests/e2e_relay.rs b/crates/sprout-test-client/tests/e2e_relay.rs index 34169d304..babfe493c 100644 --- a/crates/sprout-test-client/tests/e2e_relay.rs +++ b/crates/sprout-test-client/tests/e2e_relay.rs @@ -471,7 +471,7 @@ async fn test_auth_event_kind_rejected() { .await .expect("connect"); - let relay_url_parsed: nostr::Url = url.replace("ws://", "http://").parse().unwrap(); + let relay_url_parsed: nostr::RelayUrl = url.parse().unwrap(); let auth_event = nostr::EventBuilder::auth("fake-challenge", relay_url_parsed) .sign_with_keys(&keys) .expect("sign"); @@ -1118,10 +1118,9 @@ async fn test_nip29_standard_client_flow() { // 2. Subscribe to channel messages (kind:9 + h tag). let msg_sid = sub_id("messages"); - let msg_filter = Filter::new().kind(Kind::Custom(9)).custom_tag( - SingleLetterTag::lowercase(Alphabet::H), - [channel_id.as_str()], - ); + let msg_filter = Filter::new() + .kind(Kind::Custom(9)) + .custom_tag(SingleLetterTag::lowercase(Alphabet::H), channel_id.as_str()); client .subscribe(&msg_sid, vec![msg_filter]) .await @@ -1268,7 +1267,7 @@ async fn test_membership_notification_emitted_on_add() { .kinds(vec![Kind::Custom(44100), Kind::Custom(44101)]) .custom_tag( SingleLetterTag::lowercase(Alphabet::P), - [agent_pubkey_hex.as_str()], + agent_pubkey_hex.as_str(), ) .since(nostr::Timestamp::now() - 5u64); @@ -1476,7 +1475,7 @@ async fn test_membership_notification_requires_own_p_filter() { .kinds(vec![Kind::Custom(44100), Kind::Custom(44101)]) .custom_tag( SingleLetterTag::lowercase(Alphabet::P), - [keys_b_pubkey_hex.as_str()], + keys_b_pubkey_hex.as_str(), ); client @@ -1542,7 +1541,7 @@ async fn test_membership_notification_emitted_on_remove() { .kinds(vec![Kind::Custom(44100), Kind::Custom(44101)]) .custom_tag( SingleLetterTag::lowercase(Alphabet::P), - [agent_pubkey_hex.as_str()], + agent_pubkey_hex.as_str(), ) .since(nostr::Timestamp::now() - 5u64); @@ -1687,7 +1686,7 @@ async fn test_membership_notification_multi_p_rejected() { // The relay must reject this because not all #p values match the authenticated pubkey. let filter = Filter::new() .kinds(vec![Kind::Custom(44100), Kind::Custom(44101)]) - .custom_tag( + .custom_tags( SingleLetterTag::lowercase(Alphabet::P), [keys_a_pubkey_hex.as_str(), keys_b_pubkey_hex.as_str()], ); @@ -1746,10 +1745,9 @@ async fn test_membership_notification_mixed_filter_rejected() { let sid = sub_id("mixed-filter"); // Filter 1: has #h + membership kinds (would skip per-filter #h check) - let filter1 = Filter::new().kinds(vec![Kind::Custom(44100)]).custom_tag( - SingleLetterTag::lowercase(Alphabet::H), - [channel_id.as_str()], - ); + let filter1 = Filter::new() + .kinds(vec![Kind::Custom(44100)]) + .custom_tag(SingleLetterTag::lowercase(Alphabet::H), channel_id.as_str()); // Filter 2: global filter (no #h) — makes the subscription globally scoped. // No kinds = wildcard, no #p = should trigger rejection. let filter2 = Filter::new().authors(vec![keys.public_key()]); diff --git a/crates/sprout-test-client/tests/e2e_user_status.rs b/crates/sprout-test-client/tests/e2e_user_status.rs index e7bbf81c1..40ce363d7 100644 --- a/crates/sprout-test-client/tests/e2e_user_status.rs +++ b/crates/sprout-test-client/tests/e2e_user_status.rs @@ -234,7 +234,7 @@ async fn test_user_status_stale_write_rejected() { let tags = vec![Tag::parse(["d", &d_tag]).unwrap()]; EventBuilder::new(Kind::Custom(KIND_USER_STATUS), "Newer status") .tags(tags) - .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() + 100)) + .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_secs() + 100)) .sign_with_keys(&keys) .unwrap() }; @@ -247,7 +247,7 @@ async fn test_user_status_stale_write_rejected() { let tags = vec![Tag::parse(["d", &d_tag]).unwrap()]; EventBuilder::new(Kind::Custom(KIND_USER_STATUS), "Older status") .tags(tags) - .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_u64() - 100)) + .custom_created_at(Timestamp::from(nostr::Timestamp::now().as_secs() - 100)) .sign_with_keys(&keys) .unwrap() }; diff --git a/crates/sprout-workflow/src/lib.rs b/crates/sprout-workflow/src/lib.rs index 55148bde1..9459f0aa6 100644 --- a/crates/sprout-workflow/src/lib.rs +++ b/crates/sprout-workflow/src/lib.rs @@ -688,7 +688,7 @@ pub fn build_trigger_context(event: &sprout_core::StoredEvent) -> executor::Trig .channel_id .map(|id| id.to_string()) .unwrap_or_default(), - timestamp: event.event.created_at.as_u64().to_string(), + timestamp: event.event.created_at.as_secs().to_string(), emoji, message_id, webhook_fields: HashMap::new(), @@ -1095,7 +1095,7 @@ steps: assert_eq!(ctx.text, "hello world"); assert_eq!(ctx.author, stored.event.pubkey.to_hex()); assert_eq!(ctx.channel_id, stored.channel_id.unwrap().to_string()); - assert_eq!(ctx.timestamp, stored.event.created_at.as_u64().to_string()); + assert_eq!(ctx.timestamp, stored.event.created_at.as_secs().to_string()); assert_eq!(ctx.message_id, stored.event.id.to_hex()); // Non-reaction events have empty emoji. assert_eq!(ctx.emoji, ""); From 705cddc6f47ebc1db2834ea15196223aee89f86e Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Thu, 21 May 2026 18:41:49 -0400 Subject: [PATCH 06/10] fix(deps): replace deprecated as_u64() in desktop Tauri workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same Timestamp::as_u64() → as_secs() migration as the root workspace, applied to the desktop/src-tauri crate which has its own Cargo.toml. --- desktop/src-tauri/src/commands/canvas.rs | 2 +- desktop/src-tauri/src/commands/media.rs | 2 +- desktop/src-tauri/src/commands/messages.rs | 6 +++--- desktop/src-tauri/src/commands/profile.rs | 2 +- desktop/src-tauri/src/commands/social.rs | 2 +- desktop/src-tauri/src/commands/workflows.rs | 6 +++--- desktop/src-tauri/src/nostr_convert.rs | 12 ++++++------ 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/desktop/src-tauri/src/commands/canvas.rs b/desktop/src-tauri/src/commands/canvas.rs index 1bad5fccb..c40e5d4e9 100644 --- a/desktop/src-tauri/src/commands/canvas.rs +++ b/desktop/src-tauri/src/commands/canvas.rs @@ -29,7 +29,7 @@ pub async fn get_canvas( Ok(serde_json::json!({ "content": event.content, "event_id": event.id.to_hex(), - "created_at": event.created_at.as_u64(), + "created_at": event.created_at.as_secs(), "pubkey": event.pubkey.to_hex(), })) } diff --git a/desktop/src-tauri/src/commands/media.rs b/desktop/src-tauri/src/commands/media.rs index be9811e57..f27fdb015 100644 --- a/desktop/src-tauri/src/commands/media.rs +++ b/desktop/src-tauri/src/commands/media.rs @@ -126,7 +126,7 @@ fn sign_blossom_upload_auth( expiry_secs: u64, base_url: &str, ) -> Result { - let now = Timestamp::now().as_u64(); + let now = Timestamp::now().as_secs(); let mut tags = vec![ Tag::parse(vec!["t", "upload"]).map_err(|e| e.to_string())?, Tag::parse(vec!["x", sha256]).map_err(|e| e.to_string())?, diff --git a/desktop/src-tauri/src/commands/messages.rs b/desktop/src-tauri/src/commands/messages.rs index eef7579f5..62bb01883 100644 --- a/desktop/src-tauri/src/commands/messages.rs +++ b/desktop/src-tauri/src/commands/messages.rs @@ -435,7 +435,7 @@ fn feed_item_from_event(ev: &nostr::Event, category: &str) -> FeedItemInfo { kind: ev.kind.as_u16() as u32, pubkey: ev.pubkey.to_hex(), content: ev.content.clone(), - created_at: ev.created_at.as_u64(), + created_at: ev.created_at.as_secs(), channel_id, channel_name: String::new(), channel_type: None, @@ -450,7 +450,7 @@ fn forum_message_from_event(ev: &nostr::Event, channel_id: &str) -> ForumMessage pubkey: ev.pubkey.to_hex(), content: ev.content.clone(), kind: ev.kind.as_u16() as u32, - created_at: ev.created_at.as_u64() as i64, + created_at: ev.created_at.as_secs() as i64, channel_id: channel_id.to_string(), tags: tags_to_vec(ev), thread_summary: Some(ThreadSummary { @@ -495,7 +495,7 @@ fn forum_reply_from_event( pubkey: ev.pubkey.to_hex(), content: ev.content.clone(), kind: ev.kind.as_u16() as u32, - created_at: ev.created_at.as_u64() as i64, + created_at: ev.created_at.as_secs() as i64, channel_id: channel_id.to_string(), tags: tags_to_vec(ev), parent_event_id: Some(parent), diff --git a/desktop/src-tauri/src/commands/profile.rs b/desktop/src-tauri/src/commands/profile.rs index 2246ead1c..8e2ee8d74 100644 --- a/desktop/src-tauri/src/commands/profile.rs +++ b/desktop/src-tauri/src/commands/profile.rs @@ -249,7 +249,7 @@ pub async fn get_presence( } }) .unwrap_or_else(|| ev.pubkey.to_hex()); - let ts = ev.created_at.as_u64(); + let ts = ev.created_at.as_secs(); let status = match ev.content.trim() { "online" => PresenceStatus::Online, "away" => PresenceStatus::Away, diff --git a/desktop/src-tauri/src/commands/social.rs b/desktop/src-tauri/src/commands/social.rs index 6baed6fbb..4efd38183 100644 --- a/desktop/src-tauri/src/commands/social.rs +++ b/desktop/src-tauri/src/commands/social.rs @@ -117,7 +117,7 @@ pub async fn get_notes_timeline( .map(|ev| UserNoteInfo { id: ev.id.to_hex(), pubkey: ev.pubkey.to_hex(), - created_at: ev.created_at.as_u64() as i64, + created_at: ev.created_at.as_secs() as i64, content: ev.content.clone(), }) .collect(); diff --git a/desktop/src-tauri/src/commands/workflows.rs b/desktop/src-tauri/src/commands/workflows.rs index e79bd0a21..ef752b377 100644 --- a/desktop/src-tauri/src/commands/workflows.rs +++ b/desktop/src-tauri/src/commands/workflows.rs @@ -72,7 +72,7 @@ pub async fn get_workflow_runs( "event_id": ev.id.to_hex(), "kind": ev.kind.as_u16(), "pubkey": ev.pubkey.to_hex(), - "created_at": ev.created_at.as_u64(), + "created_at": ev.created_at.as_secs(), "content": ev.content, "tags": ev.tags.iter().map(|t| t.as_slice().to_vec()).collect::>(), }) @@ -191,7 +191,7 @@ pub async fn get_run_approvals( "event_id": ev.id.to_hex(), "kind": ev.kind.as_u16(), "pubkey": ev.pubkey.to_hex(), - "created_at": ev.created_at.as_u64(), + "created_at": ev.created_at.as_secs(), "content": ev.content, "tags": ev.tags.iter().map(|t| t.as_slice().to_vec()).collect::>(), }) @@ -258,6 +258,6 @@ fn workflow_from_event(ev: &nostr::Event) -> Value { "yaml_definition": ev.content, "event_id": ev.id.to_hex(), "pubkey": ev.pubkey.to_hex(), - "created_at": ev.created_at.as_u64(), + "created_at": ev.created_at.as_secs(), }) } diff --git a/desktop/src-tauri/src/nostr_convert.rs b/desktop/src-tauri/src/nostr_convert.rs index 15957bdee..b08e7cfa4 100644 --- a/desktop/src-tauri/src/nostr_convert.rs +++ b/desktop/src-tauri/src/nostr_convert.rs @@ -112,7 +112,7 @@ pub fn channel_info_from_event( // so the frontend knows the channel is archived. The exact timestamp isn't available // from the tag alone, so we use the event's created_at as a proxy. let archived_at = if first_tag_value(event, "archived") == Some("true") { - Some(timestamp_to_iso(event.created_at.as_u64())) + Some(timestamp_to_iso(event.created_at.as_secs())) } else { None }; @@ -169,10 +169,10 @@ pub fn channel_detail_from_event(event: &Event) -> Result UserNotesResponse { .map(|ev| UserNoteInfo { id: ev.id.to_hex(), pubkey: ev.pubkey.to_hex(), - created_at: ev.created_at.as_u64() as i64, + created_at: ev.created_at.as_secs() as i64, content: ev.content.clone(), }) .collect(); @@ -484,7 +484,7 @@ pub fn contact_list_from_event(event: &Event) -> Result SearchResponse { pubkey: ev.pubkey.to_hex(), channel_id, channel_name: None, - created_at: ev.created_at.as_u64(), + created_at: ev.created_at.as_secs(), score, } }) From 617442a80f578e34ffaac3c0a080e29f0f3af407 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Fri, 22 May 2026 13:52:59 -0400 Subject: [PATCH 07/10] fix(sprout-cli): migrate notes.rs to nostr 0.44 API PR #719 (NIP-23 long-form notes) merged to main with pre-0.44 API calls. Apply the same EventBuilder and Tag::parse migrations. Also fix matching dms.rs breakage (same Tag::parse/EventBuilder::new pattern) that blocked compilation, and replace the deprecated Timestamp::as_u64() with as_secs() in notes.rs. --- crates/sprout-cli/src/commands/dms.rs | 6 +-- crates/sprout-cli/src/commands/notes.rs | 68 +++++++++++-------------- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/crates/sprout-cli/src/commands/dms.rs b/crates/sprout-cli/src/commands/dms.rs index cb59358fc..9b46f4f45 100644 --- a/crates/sprout-cli/src/commands/dms.rs +++ b/crates/sprout-cli/src/commands/dms.rs @@ -63,10 +63,10 @@ pub async fn cmd_open_dm(client: &SproutClient, pubkeys: &[String]) -> Result<() use nostr::{EventBuilder, Kind, Tag}; let mut tags: Vec = refs .iter() - .map(|pk| Tag::parse(&["p", pk]).map_err(|e| CliError::Other(format!("tag error: {e}")))) + .map(|pk| Tag::parse(["p", *pk]).map_err(|e| CliError::Other(format!("tag error: {e}")))) .collect::, _>>()?; - tags.push(Tag::parse(&["d", &dm_id]).map_err(|e| CliError::Other(format!("tag error: {e}")))?); - let builder = EventBuilder::new(Kind::Custom(41010), "", tags); + tags.push(Tag::parse(["d", &dm_id]).map_err(|e| CliError::Other(format!("tag error: {e}")))?); + let builder = EventBuilder::new(Kind::Custom(41010), "").tags(tags); let event = client.sign_event(builder)?; let resp = client.submit_event(event).await?; diff --git a/crates/sprout-cli/src/commands/notes.rs b/crates/sprout-cli/src/commands/notes.rs index 9fcd0f317..3876b6c52 100644 --- a/crates/sprout-cli/src/commands/notes.rs +++ b/crates/sprout-cli/src/commands/notes.rs @@ -149,7 +149,7 @@ impl NoteSnapshot { summary, tags, published_at, - updated_at: event.created_at.as_u64(), + updated_at: event.created_at.as_secs(), content: event.content.clone(), }) } @@ -487,20 +487,19 @@ pub fn build_set_event( let published_at: u64 = prior.and_then(|p| p.published_at).unwrap_or(now); let mut evt_tags: Vec = Vec::with_capacity(4 + topic_tags.len()); - evt_tags.push(Tag::parse(&["d", slug]).map_err(tag_err)?); - evt_tags.push(Tag::parse(&["title", title_value]).map_err(tag_err)?); + evt_tags.push(Tag::parse(["d", slug]).map_err(tag_err)?); + evt_tags.push(Tag::parse(["title", title_value]).map_err(tag_err)?); if let Some(s) = summary_value { - evt_tags.push(Tag::parse(&["summary", s]).map_err(tag_err)?); + evt_tags.push(Tag::parse(["summary", s]).map_err(tag_err)?); } for t in &topic_tags { - evt_tags.push(Tag::parse(&["t", t]).map_err(tag_err)?); + evt_tags.push(Tag::parse(["t", t.as_str()]).map_err(tag_err)?); } - evt_tags.push(Tag::parse(&["published_at", &published_at.to_string()]).map_err(tag_err)?); + evt_tags.push(Tag::parse(["published_at", &published_at.to_string()]).map_err(tag_err)?); - Ok( - EventBuilder::new(Kind::Custom(KIND_LONG_FORM), content, evt_tags) - .custom_created_at(Timestamp::from(now)), - ) + Ok(EventBuilder::new(Kind::Custom(KIND_LONG_FORM), content) + .tags(evt_tags) + .custom_created_at(Timestamp::from(now))) } fn tag_err(e: impl std::fmt::Display) -> CliError { @@ -716,8 +715,8 @@ pub async fn cmd_ls( /// would route to the per-event path and leave the live replaceable row /// intact — the note would survive the "deletion". Pure and unit-testable. pub fn build_rm_event(coord: &nostr::nips::nip01::Coordinate) -> Result { - let a_tag = Tag::parse(&["a", &coord.to_string()]).map_err(tag_err)?; - Ok(EventBuilder::new(Kind::EventDeletion, "", vec![a_tag])) + let a_tag = Tag::parse(["a", &coord.to_string()]).map_err(tag_err)?; + Ok(EventBuilder::new(Kind::EventDeletion, "").tags(vec![a_tag])) } pub async fn cmd_rm(client: &SproutClient, slug: &str) -> Result<(), CliError> { @@ -897,11 +896,12 @@ mod tests { content: &str, ) -> Event { let mut tags = vec![ - Tag::parse(&["d", slug]).unwrap(), - Tag::parse(&["title", title]).unwrap(), + Tag::parse(["d", slug]).unwrap(), + Tag::parse(["title", title]).unwrap(), ]; tags.extend(extra); - EventBuilder::new(Kind::Custom(KIND_LONG_FORM), content, tags) + EventBuilder::new(Kind::Custom(KIND_LONG_FORM), content) + .tags(tags) .custom_created_at(Timestamp::from(ts)) .sign_with_keys(keys) .unwrap() @@ -916,10 +916,10 @@ mod tests { "my-slug", "My Title", vec![ - Tag::parse(&["summary", "a short summary"]).unwrap(), - Tag::parse(&["t", "rust"]).unwrap(), - Tag::parse(&["t", "cli"]).unwrap(), - Tag::parse(&["published_at", "1700000000"]).unwrap(), + Tag::parse(["summary", "a short summary"]).unwrap(), + Tag::parse(["t", "rust"]).unwrap(), + Tag::parse(["t", "cli"]).unwrap(), + Tag::parse(["published_at", "1700000000"]).unwrap(), ], "# body", ); @@ -940,13 +940,10 @@ mod tests { let keys = Keys::generate(); // Synthesize an event without a `d` tag — has to be done with EventBuilder // directly since `build_30023` always inserts one. - let event = EventBuilder::new( - Kind::Custom(KIND_LONG_FORM), - "body", - vec![Tag::parse(&["title", "no-d"]).unwrap()], - ) - .sign_with_keys(&keys) - .unwrap(); + let event = EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "body") + .tags(vec![Tag::parse(["title", "no-d"]).unwrap()]) + .sign_with_keys(&keys) + .unwrap(); let err = NoteSnapshot::from_event(&event).unwrap_err(); assert!(matches!(err, CliError::Other(m) if m.contains("missing the required `d` tag"))); } @@ -954,13 +951,10 @@ mod tests { #[test] fn note_snapshot_rejects_wrong_kind() { let keys = Keys::generate(); - let event = EventBuilder::new( - Kind::TextNote, - "hi", - vec![Tag::parse(&["d", "ignored"]).unwrap()], - ) - .sign_with_keys(&keys) - .unwrap(); + let event = EventBuilder::new(Kind::TextNote, "hi") + .tags(vec![Tag::parse(["d", "ignored"]).unwrap()]) + .sign_with_keys(&keys) + .unwrap(); let err = NoteSnapshot::from_event(&event).unwrap_err(); assert!(matches!(err, CliError::Other(m) if m.contains("expected kind:30023"))); } @@ -976,7 +970,7 @@ mod tests { 1_000, "x", "T", - vec![Tag::parse(&["published_at", "not-a-number"]).unwrap()], + vec![Tag::parse(["published_at", "not-a-number"]).unwrap()], "", ); let snap = NoteSnapshot::from_event(&event).unwrap(); @@ -1061,13 +1055,13 @@ mod tests { let keys = Keys::generate(); let mut extra: Vec = Vec::new(); if let Some(s) = summary { - extra.push(Tag::parse(&["summary", s]).unwrap()); + extra.push(Tag::parse(["summary", s]).unwrap()); } for t in tags { - extra.push(Tag::parse(&["t", t]).unwrap()); + extra.push(Tag::parse(["t", t]).unwrap()); } if let Some(p) = published_at { - extra.push(Tag::parse(&["published_at", &p.to_string()]).unwrap()); + extra.push(Tag::parse(["published_at", &p.to_string()]).unwrap()); } NoteSnapshot::from_event(&build_30023(&keys, ts, slug, title, extra, content)).unwrap() } From 1d28654ffef3b44158312c76e980542e319a703a Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Fri, 22 May 2026 14:11:21 -0400 Subject: [PATCH 08/10] fix: migrate remaining nostr 0.44 API usage in tests PR #719 E2E test file and notes.rs test code still used deprecated Timestamp::as_u64(), old Tag::parse(&[]) signature, 3-arg EventBuilder::new(), and array-form custom_tag(). --- crates/sprout-cli/src/commands/notes.rs | 4 +- .../sprout-test-client/tests/e2e_long_form.rs | 63 ++++++++----------- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/crates/sprout-cli/src/commands/notes.rs b/crates/sprout-cli/src/commands/notes.rs index 3876b6c52..f28a51408 100644 --- a/crates/sprout-cli/src/commands/notes.rs +++ b/crates/sprout-cli/src/commands/notes.rs @@ -1080,7 +1080,7 @@ mod tests { assert_eq!(tag_value(&event, "title"), Some("Hello")); assert_eq!(tag_value(&event, "d"), Some("x")); assert_eq!(tag_value(&event, "published_at"), Some("1700000000")); - assert_eq!(event.created_at.as_u64(), 1_700_000_000); + assert_eq!(event.created_at.as_secs(), 1_700_000_000); // No `summary` tag should be present when none was specified. assert!(tag_value(&event, "summary").is_none()); assert!(t_tags(&event).is_empty()); @@ -1110,7 +1110,7 @@ mod tests { .unwrap(); assert_eq!(tag_value(&event, "title"), Some("Original Title")); assert_eq!(tag_value(&event, "published_at"), Some("1650000000")); - assert_eq!(event.created_at.as_u64(), 1_700_001_000); + assert_eq!(event.created_at.as_secs(), 1_700_001_000); assert_eq!(event.content, "new body"); } diff --git a/crates/sprout-test-client/tests/e2e_long_form.rs b/crates/sprout-test-client/tests/e2e_long_form.rs index 2ed083edf..8e2ee2612 100644 --- a/crates/sprout-test-client/tests/e2e_long_form.rs +++ b/crates/sprout-test-client/tests/e2e_long_form.rs @@ -349,7 +349,7 @@ async fn test_long_form_a_tag_deletion() { let filter_pre = Filter::new() .kind(Kind::Custom(KIND_LONG_FORM)) .author(keys.public_key()) - .custom_tag(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); + .custom_tag(SingleLetterTag::lowercase(Alphabet::D), d_tag.as_str()); client .subscribe(&sid_pre, vec![filter_pre]) .await @@ -370,13 +370,10 @@ async fn test_long_form_a_tag_deletion() { keys.public_key().to_hex(), d_tag ); - let del = EventBuilder::new( - Kind::EventDeletion, - "", - vec![Tag::parse(&["a", &a_coord]).unwrap()], - ) - .sign_with_keys(&keys) - .unwrap(); + let del = EventBuilder::new(Kind::EventDeletion, "") + .tags(vec![Tag::parse(["a", &a_coord]).unwrap()]) + .sign_with_keys(&keys) + .unwrap(); let ok_del = client.send_event(del).await.expect("send deletion"); assert!( ok_del.accepted, @@ -389,7 +386,7 @@ async fn test_long_form_a_tag_deletion() { let filter_post = Filter::new() .kind(Kind::Custom(KIND_LONG_FORM)) .author(keys.public_key()) - .custom_tag(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); + .custom_tag(SingleLetterTag::lowercase(Alphabet::D), d_tag.as_str()); client .subscribe(&sid_post, vec![filter_post]) .await @@ -434,16 +431,13 @@ async fn test_long_form_malformed_e_plus_a_does_not_delete() { keys.public_key().to_hex(), d_tag ); - let del = EventBuilder::new( - Kind::EventDeletion, - "", - vec![ - Tag::parse(&["e", "not-a-valid-event-id"]).unwrap(), - Tag::parse(&["a", &a_coord]).unwrap(), - ], - ) - .sign_with_keys(&keys) - .unwrap(); + let del = EventBuilder::new(Kind::EventDeletion, "") + .tags(vec![ + Tag::parse(["e", "not-a-valid-event-id"]).unwrap(), + Tag::parse(["a", &a_coord]).unwrap(), + ]) + .sign_with_keys(&keys) + .unwrap(); // Relay may accept-and-noop or reject; either is fine. The contract under // test is that the coordinate is NOT soft-deleted. let _ = client.send_event(del).await.expect("send mixed deletion"); @@ -452,7 +446,7 @@ async fn test_long_form_malformed_e_plus_a_does_not_delete() { let filter = Filter::new() .kind(Kind::Custom(KIND_LONG_FORM)) .author(keys.public_key()) - .custom_tag(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); + .custom_tag(SingleLetterTag::lowercase(Alphabet::D), d_tag.as_str()); client .subscribe(&sid, vec![filter]) .await @@ -496,7 +490,7 @@ async fn test_long_form_set_twice_preserves_published_at() { &d_tag, "First", "v1 body", - vec![Tag::parse(&["published_at", &original_published_at.to_string()]).unwrap()], + vec![Tag::parse(["published_at", &original_published_at.to_string()]).unwrap()], ); let ok1 = client.send_event(v1).await.expect("send v1"); assert!(ok1.accepted, "v1 should be accepted: {}", ok1.message); @@ -506,20 +500,17 @@ async fn test_long_form_set_twice_preserves_published_at() { // Re-publish carrying the original `published_at` forward — what // `notes set` does on update when `--title` (or nothing) changes. - let v2 = EventBuilder::new( - Kind::Custom(KIND_LONG_FORM), - "v2 body", - vec![ - Tag::parse(&["d", &d_tag]).unwrap(), - Tag::parse(&["title", "First"]).unwrap(), - Tag::parse(&["published_at", &original_published_at.to_string()]).unwrap(), - ], - ) - .custom_created_at(Timestamp::now()) - .sign_with_keys(&keys) - .unwrap(); + let v2 = EventBuilder::new(Kind::Custom(KIND_LONG_FORM), "v2 body") + .tags(vec![ + Tag::parse(["d", &d_tag]).unwrap(), + Tag::parse(["title", "First"]).unwrap(), + Tag::parse(["published_at", &original_published_at.to_string()]).unwrap(), + ]) + .custom_created_at(Timestamp::now()) + .sign_with_keys(&keys) + .unwrap(); let v2_id = v2.id; - let v2_created_at = v2.created_at.as_u64(); + let v2_created_at = v2.created_at.as_secs(); let ok2 = client.send_event(v2).await.expect("send v2"); assert!(ok2.accepted, "v2 should be accepted: {}", ok2.message); @@ -530,7 +521,7 @@ async fn test_long_form_set_twice_preserves_published_at() { let filter = Filter::new() .kind(Kind::Custom(KIND_LONG_FORM)) .author(keys.public_key()) - .custom_tag(SingleLetterTag::lowercase(Alphabet::D), [d_tag.as_str()]); + .custom_tag(SingleLetterTag::lowercase(Alphabet::D), d_tag.as_str()); client .subscribe(&sid, vec![filter]) .await @@ -545,7 +536,7 @@ async fn test_long_form_set_twice_preserves_published_at() { let live = &events[0]; assert_eq!(live.id, v2_id, "surviving event is v2"); assert_eq!( - live.created_at.as_u64(), + live.created_at.as_secs(), v2_created_at, "created_at advanced to v2's timestamp" ); From 77e3545586ffca1a91636a040f6e0b74bd79fbdf Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Fri, 22 May 2026 16:12:03 -0400 Subject: [PATCH 09/10] fix(deps): migrate manifest_event + transport to nostr 0.44 API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #726 introduced new code using old nostr 0.36 patterns: - EventBuilder::new() 3-arg form → 2-arg + .tags() - PublicKey::serialize() → .to_bytes() - Tag::parse(&[...]) → Tag::parse([...]) in e2e_git tests --- .../src/api/git/manifest_event.rs | 2 +- crates/sprout-relay/src/api/git/transport.rs | 2 +- crates/sprout-test-client/tests/e2e_git.rs | 34 ++++++++----------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/crates/sprout-relay/src/api/git/manifest_event.rs b/crates/sprout-relay/src/api/git/manifest_event.rs index 25161d489..15702f35a 100644 --- a/crates/sprout-relay/src/api/git/manifest_event.rs +++ b/crates/sprout-relay/src/api/git/manifest_event.rs @@ -109,7 +109,7 @@ pub fn build_ref_state_event( // p-tag: sprout extension (pusher or owner pubkey). tags.push(Tag::public_key(actor)); - let event = EventBuilder::new(Kind::Custom(30618), "", tags) + let event = EventBuilder::new(Kind::Custom(30618), "").tags(tags) .sign_with_keys(relay_keys) .map_err(|e| BuildError::Sign(e.to_string()))?; diff --git a/crates/sprout-relay/src/api/git/transport.rs b/crates/sprout-relay/src/api/git/transport.rs index c380f59f0..1833539d2 100644 --- a/crates/sprout-relay/src/api/git/transport.rs +++ b/crates/sprout-relay/src/api/git/transport.rs @@ -762,7 +762,7 @@ async fn finalize_push(state: &Arc, ctx: PushContext) -> Response { repo_id: &ctx.repo_id, head: &success.manifest.head, refs: &success.manifest.refs, - actor_pubkey_hex: &hex::encode(ctx.pusher.serialize()), + actor_pubkey_hex: &hex::encode(ctx.pusher.to_bytes()), }; match build_ref_state_event(&inputs, &state.relay_keypair) { Ok(event) => match state.db.insert_event(&event, None).await { diff --git a/crates/sprout-test-client/tests/e2e_git.rs b/crates/sprout-test-client/tests/e2e_git.rs index 88a6eba2c..3d7689e87 100644 --- a/crates/sprout-test-client/tests/e2e_git.rs +++ b/crates/sprout-test-client/tests/e2e_git.rs @@ -201,16 +201,13 @@ async fn git_clone_push_fetch_force_roundtrip() { let s3 = GitS3Probe::from_env(); // Announce the repo (kind:30617) so the relay creates the bare repo + hook. - let announce = EventBuilder::new( - Kind::from(30617), - "", - vec![ - Tag::parse(&["d", &repo]).unwrap(), - Tag::parse(&["name", "e2e git repo"]).unwrap(), - ], - ) - .sign_with_keys(&owner) - .unwrap(); + let announce = EventBuilder::new(Kind::from(30617), "") + .tags(vec![ + Tag::parse(["d", &repo]).unwrap(), + Tag::parse(["name", "e2e git repo"]).unwrap(), + ]) + .sign_with_keys(&owner) + .unwrap(); post_event(&announce).await; tokio::time::sleep(std::time::Duration::from_secs(2)).await; @@ -341,16 +338,13 @@ async fn git_concurrent_push_one_wins_and_repo_recovers() { let repo = format!("e2e-git-concurrent-{}", std::process::id()); let s3 = GitS3Probe::from_env(); - let announce = EventBuilder::new( - Kind::from(30617), - "", - vec![ - Tag::parse(&["d", &repo]).unwrap(), - Tag::parse(&["name", "e2e concurrent git repo"]).unwrap(), - ], - ) - .sign_with_keys(&owner) - .unwrap(); + let announce = EventBuilder::new(Kind::from(30617), "") + .tags(vec![ + Tag::parse(["d", &repo]).unwrap(), + Tag::parse(["name", "e2e concurrent git repo"]).unwrap(), + ]) + .sign_with_keys(&owner) + .unwrap(); post_event(&announce).await; tokio::time::sleep(std::time::Duration::from_secs(2)).await; From dfec78b0b01d8d6d0a0fcfdd3dd92887f627c298 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Fri, 22 May 2026 16:35:11 -0400 Subject: [PATCH 10/10] style: fix cargo fmt in manifest_event.rs --- crates/sprout-relay/src/api/git/manifest_event.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/sprout-relay/src/api/git/manifest_event.rs b/crates/sprout-relay/src/api/git/manifest_event.rs index 15702f35a..3ed050314 100644 --- a/crates/sprout-relay/src/api/git/manifest_event.rs +++ b/crates/sprout-relay/src/api/git/manifest_event.rs @@ -109,7 +109,8 @@ pub fn build_ref_state_event( // p-tag: sprout extension (pusher or owner pubkey). tags.push(Tag::public_key(actor)); - let event = EventBuilder::new(Kind::Custom(30618), "").tags(tags) + let event = EventBuilder::new(Kind::Custom(30618), "") + .tags(tags) .sign_with_keys(relay_keys) .map_err(|e| BuildError::Sign(e.to_string()))?;