cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit acf95826e5f5c919b4633bcfc7f52c55b88c4198
parent c180b47d48b1e8efc16ff340be76491588452792
Author: triesap <tyson@radroots.org>
Date:   Thu,  7 May 2026 15:54:31 +0000

listing: preserve direct relay signed event

- add the signed Nostr event to direct relay publish receipts
- share signing through one helper before relay connection and publish
- assert receipt parity for event id, signature, timestamp, content, and tags
- update listing and order receipt consumers for the expanded receipt shape

Diffstat:
Msrc/runtime/direct_relay.rs | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/runtime/listing.rs | 3+++
Msrc/runtime/order.rs | 9+++++++++
3 files changed, 66 insertions(+), 7 deletions(-)

diff --git a/src/runtime/direct_relay.rs b/src/runtime/direct_relay.rs @@ -18,6 +18,7 @@ pub struct DirectRelayFailure { #[derive(Debug, Clone)] pub struct DirectRelayPublishReceipt { + pub event: RadrootsNostrEvent, pub event_id: String, pub created_at: u32, pub signature: String, @@ -187,11 +188,7 @@ async fn publish_parts_with_identity_async( relay_urls: &[String], parts: WireEventParts, ) -> Result<DirectRelayPublishReceipt, DirectRelayPublishError> { - let builder = radroots_nostr_build_event(parts.kind, parts.content, parts.tags) - .map_err(DirectRelayPublishError::Build)?; - let event = builder - .sign_with_keys(identity.keys()) - .map_err(|error| DirectRelayPublishError::Sign(error.into()))?; + let event = sign_parts_with_identity(identity, parts)?; let event_id = event.id.to_hex(); let created_at = event_created_at_u32(&event); let signature = event.sig.to_string(); @@ -245,6 +242,7 @@ async fn publish_parts_with_identity_async( } Ok(DirectRelayPublishReceipt { + event, event_id, created_at, signature, @@ -259,6 +257,17 @@ async fn publish_parts_with_identity_async( }) } +fn sign_parts_with_identity( + identity: &RadrootsIdentity, + parts: WireEventParts, +) -> Result<RadrootsNostrEvent, DirectRelayPublishError> { + let builder = radroots_nostr_build_event(parts.kind, parts.content, parts.tags) + .map_err(DirectRelayPublishError::Build)?; + builder + .sign_with_keys(identity.keys()) + .map_err(|error| DirectRelayPublishError::Sign(error.into())) +} + fn relay_failures_from_output<T: std::fmt::Debug>( output: &RadrootsNostrOutput<T>, ) -> Vec<DirectRelayFailure> { @@ -297,8 +306,9 @@ mod tests { use radroots_nostr::prelude::RadrootsNostrFilter; use super::{ - DirectRelayFetchError, DirectRelayPublishError, fetch_events_from_relays_async, - fetch_events_from_relays_with_timeout, publish_parts_with_identity, + DirectRelayFetchError, DirectRelayPublishError, event_created_at_u32, + fetch_events_from_relays_async, fetch_events_from_relays_with_timeout, + publish_parts_with_identity, sign_parts_with_identity, }; #[test] @@ -319,6 +329,43 @@ mod tests { } #[test] + fn direct_relay_signed_event_preserves_publish_receipt_parity() { + let identity = RadrootsIdentity::generate(); + let parts = WireEventParts { + kind: 30402, + content: "listing".to_owned(), + tags: vec![ + vec!["d".to_owned(), "listing-1".to_owned()], + vec!["title".to_owned(), "eggs".to_owned()], + ], + }; + let event = sign_parts_with_identity(&identity, parts.clone()).expect("signed event"); + let receipt = super::DirectRelayPublishReceipt { + event: event.clone(), + event_id: event.id.to_hex(), + created_at: event_created_at_u32(&event), + signature: event.sig.to_string(), + target_relays: vec!["ws://127.0.0.1:1234".to_owned()], + connected_relays: vec!["ws://127.0.0.1:1234".to_owned()], + acknowledged_relays: vec!["ws://127.0.0.1:1234".to_owned()], + failed_relays: Vec::new(), + }; + let tags = receipt + .event + .tags + .iter() + .map(|tag| tag.as_slice().to_vec()) + .collect::<Vec<_>>(); + + assert_eq!(receipt.event_id, receipt.event.id.to_hex()); + assert_eq!(receipt.signature, receipt.event.sig.to_string()); + assert_eq!(receipt.created_at, event_created_at_u32(&receipt.event)); + assert_eq!(receipt.event.kind.as_u16() as u32, parts.kind); + assert_eq!(receipt.event.content, parts.content); + assert_eq!(tags, parts.tags); + } + + #[test] fn fetch_events_requires_relays_before_runtime_work() { let err = fetch_events_from_relays_with_timeout( &[], diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs @@ -2009,6 +2009,7 @@ fn published_mutation_view( receipt: DirectRelayPublishReceipt, ) -> ListingMutationView { let DirectRelayPublishReceipt { + event: published_event, event_id, created_at, signature, @@ -2017,6 +2018,8 @@ fn published_mutation_view( acknowledged_relays, failed_relays, } = receipt; + debug_assert_eq!(event_id, published_event.id.to_hex()); + debug_assert_eq!(signature, published_event.sig.to_string()); event.event_id = Some(event_id.clone()); event.created_at = Some(created_at); event.signature = Some(signature); diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -7422,6 +7422,7 @@ fn published_order_revision_view( receipt: DirectRelayPublishReceipt, ) -> OrderRevisionProposalView { let DirectRelayPublishReceipt { + event: _, event_id, created_at: _, signature: _, @@ -7452,6 +7453,7 @@ fn published_order_revision_decision_view( receipt: DirectRelayPublishReceipt, ) -> OrderRevisionDecisionView { let DirectRelayPublishReceipt { + event: _, event_id, created_at: _, signature: _, @@ -7578,6 +7580,7 @@ fn published_order_fulfillment_view( receipt: DirectRelayPublishReceipt, ) -> OrderFulfillmentView { let DirectRelayPublishReceipt { + event: _, event_id, created_at: _, signature: _, @@ -7606,6 +7609,7 @@ fn published_order_cancellation_view( receipt: DirectRelayPublishReceipt, ) -> OrderCancellationView { let DirectRelayPublishReceipt { + event: _, event_id, created_at: _, signature: _, @@ -7633,6 +7637,7 @@ fn published_order_receipt_view( receipt: DirectRelayPublishReceipt, ) -> OrderReceiptView { let DirectRelayPublishReceipt { + event: _, event_id, created_at: _, signature: _, @@ -7668,6 +7673,7 @@ fn published_order_payment_view( receipt: DirectRelayPublishReceipt, ) -> OrderPaymentView { let DirectRelayPublishReceipt { + event: _, event_id, created_at: _, signature: _, @@ -7696,6 +7702,7 @@ fn published_order_settlement_view( receipt: DirectRelayPublishReceipt, ) -> OrderSettlementView { let DirectRelayPublishReceipt { + event: _, event_id, created_at: _, signature: _, @@ -8055,6 +8062,7 @@ fn published_order_decision_view( inventory: Option<OrderInventoryView>, ) -> OrderDecisionView { let DirectRelayPublishReceipt { + event: _, event_id, created_at: _, signature: _, @@ -9716,6 +9724,7 @@ fn published_order_submit_view( receipt: DirectRelayPublishReceipt, ) -> OrderSubmitView { let DirectRelayPublishReceipt { + event: _, event_id, created_at: _, signature: _,