commit ade82b9f7cd01d4e85db455fd61a472dbd3f2e1a
parent 4968ac9ef76b64ec646e3ff968efdd3d1d8f378f
Author: triesap <tyson@radroots.org>
Date: Sun, 15 Feb 2026 18:20:52 +0000
trade: add shared envelope event builder
- add a reusable helper to build listing envelope event kind content and tags
- keep helper behind serde_json to preserve no_std and feature boundaries
- add unit tests for order-scoped and non-order-scoped envelope emission
- verify with cargo fmt cargo check and cargo test for `radroots-trade`
Diffstat:
1 file changed, 87 insertions(+), 0 deletions(-)
diff --git a/trade/src/listing/dvm.rs b/trade/src/listing/dvm.rs
@@ -22,6 +22,8 @@ use crate::listing::order::{
TradeAnswer, TradeDiscountDecision, TradeDiscountOffer, TradeDiscountRequest,
TradeFulfillmentUpdate, TradeOrder, TradeOrderRevision, TradeQuestion, TradeReceipt,
};
+#[cfg(feature = "serde_json")]
+use crate::listing::tags::trade_listing_dvm_tags;
use crate::listing::validation::TradeListingValidationError;
pub const TRADE_LISTING_DOMAIN: &str = "trade:listing";
@@ -169,6 +171,38 @@ impl<T> TradeListingEnvelope<T> {
}
}
+#[cfg(feature = "serde_json")]
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct TradeListingEnvelopeEvent {
+ pub kind: u16,
+ pub content: String,
+ pub tags: Vec<Vec<String>>,
+}
+
+#[cfg(feature = "serde_json")]
+pub fn trade_listing_envelope_event_build<T: serde::Serialize + Clone>(
+ recipient_pubkey: impl Into<String>,
+ message_type: TradeListingMessageType,
+ listing_addr: impl Into<String>,
+ order_id: Option<String>,
+ payload: &T,
+) -> Result<TradeListingEnvelopeEvent, serde_json::Error> {
+ let listing_addr = listing_addr.into();
+ let envelope = TradeListingEnvelope::new(
+ message_type,
+ listing_addr.clone(),
+ order_id.clone(),
+ payload.clone(),
+ );
+ let content = serde_json::to_string(&envelope)?;
+ let tags = trade_listing_dvm_tags(recipient_pubkey, &listing_addr, order_id.as_deref());
+ Ok(TradeListingEnvelopeEvent {
+ kind: message_type.kind(),
+ content,
+ tags,
+ })
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TradeListingEnvelopeError {
InvalidVersion { expected: u16, got: u16 },
@@ -376,4 +410,57 @@ mod tests {
TradeListingEnvelopeError::MissingOrderId
);
}
+
+ #[cfg(feature = "serde_json")]
+ #[test]
+ fn envelope_event_build_includes_order_tag() {
+ let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg");
+ let payload = TradeListingValidateRequest {
+ listing_event: None,
+ };
+ let built = super::trade_listing_envelope_event_build(
+ "pubkey",
+ TradeListingMessageType::OrderRequest,
+ listing_addr.clone(),
+ Some(String::from("order-1")),
+ &payload,
+ )
+ .unwrap();
+
+ assert_eq!(built.kind, TradeListingMessageType::OrderRequest.kind());
+
+ let envelope: TradeListingEnvelope<TradeListingValidateRequest> =
+ serde_json::from_str(&built.content).unwrap();
+ assert_eq!(envelope.listing_addr, listing_addr.clone());
+ assert_eq!(envelope.order_id.as_deref(), Some("order-1"));
+ assert_eq!(built.tags.len(), 3);
+ }
+
+ #[cfg(feature = "serde_json")]
+ #[test]
+ fn envelope_event_build_omits_order_tag_when_missing() {
+ let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg");
+ let payload = TradeListingValidateRequest {
+ listing_event: None,
+ };
+ let built = super::trade_listing_envelope_event_build(
+ "pubkey",
+ TradeListingMessageType::ListingValidateRequest,
+ listing_addr.clone(),
+ None,
+ &payload,
+ )
+ .unwrap();
+
+ assert_eq!(
+ built.kind,
+ TradeListingMessageType::ListingValidateRequest.kind()
+ );
+
+ let envelope: TradeListingEnvelope<TradeListingValidateRequest> =
+ serde_json::from_str(&built.content).unwrap();
+ assert_eq!(envelope.listing_addr, listing_addr);
+ assert!(envelope.order_id.is_none());
+ assert_eq!(built.tags.len(), 2);
+ }
}