lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

decode.rs (59981B)


      1 #[cfg(all(not(feature = "std"), feature = "serde_json"))]
      2 use alloc::{borrow::ToOwned, format, string::String, vec::Vec};
      3 
      4 #[cfg(feature = "serde_json")]
      5 use radroots_events::{
      6     RadrootsNostrEvent, RadrootsNostrEventPtr,
      7     ids::{RadrootsEventId, RadrootsIdParseError, RadrootsListingAddress, RadrootsPublicKey},
      8     kinds::is_order_event_kind,
      9     order::{
     10         RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderEnvelope,
     11         RadrootsOrderEnvelopeError, RadrootsOrderEventType, RadrootsOrderPayloadError,
     12         RadrootsOrderRequest, RadrootsOrderRevisionDecision, RadrootsOrderRevisionProposal,
     13     },
     14     tags::{TAG_D, TAG_E_PREV, TAG_E_ROOT},
     15 };
     16 #[cfg(feature = "serde_json")]
     17 use serde::de::DeserializeOwned;
     18 
     19 #[cfg(feature = "serde_json")]
     20 use crate::order::tags::{
     21     TAG_LISTING_EVENT, parse_order_counterparty_tag, parse_order_listing_event_tag,
     22     parse_order_prev_tag, parse_order_root_tag,
     23 };
     24 
     25 #[cfg(feature = "serde_json")]
     26 #[derive(Clone, Debug, PartialEq, Eq)]
     27 pub enum RadrootsOrderEnvelopeParseError {
     28     InvalidKind(u32),
     29     InvalidJson,
     30     InvalidEnvelope(RadrootsOrderEnvelopeError),
     31     InvalidPayload(RadrootsOrderPayloadError),
     32     MessageTypeKindMismatch {
     33         event_kind: u32,
     34         message_type: RadrootsOrderEventType,
     35     },
     36     MissingTag(&'static str),
     37     InvalidTag(&'static str),
     38     ListingAddrTagMismatch,
     39     OrderIdTagMismatch,
     40     PayloadBindingMismatch(&'static str),
     41     AuthorMismatch,
     42     CounterpartyTagMismatch,
     43     InvalidListingAddr(RadrootsIdParseError),
     44 }
     45 
     46 #[cfg(feature = "serde_json")]
     47 impl core::fmt::Display for RadrootsOrderEnvelopeParseError {
     48     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
     49         match self {
     50             Self::InvalidKind(kind) => write!(f, "invalid order event kind: {kind}"),
     51             Self::InvalidJson => write!(f, "invalid order envelope json"),
     52             Self::InvalidEnvelope(error) => write!(f, "{error}"),
     53             Self::InvalidPayload(error) => write!(f, "{error}"),
     54             Self::MessageTypeKindMismatch {
     55                 event_kind,
     56                 message_type,
     57             } => write!(
     58                 f,
     59                 "order envelope type {message_type:?} does not match event kind {event_kind}"
     60             ),
     61             Self::MissingTag(tag) => write!(f, "missing required order tag: {tag}"),
     62             Self::InvalidTag(tag) => write!(f, "invalid order tag: {tag}"),
     63             Self::ListingAddrTagMismatch => {
     64                 write!(f, "order listing address tag does not match envelope")
     65             }
     66             Self::OrderIdTagMismatch => {
     67                 write!(f, "order order id tag does not match envelope")
     68             }
     69             Self::PayloadBindingMismatch(field) => {
     70                 write!(f, "order payload {field} does not match envelope")
     71             }
     72             Self::AuthorMismatch => write!(f, "order event author does not match payload"),
     73             Self::CounterpartyTagMismatch => {
     74                 write!(f, "order counterparty tag does not match payload")
     75             }
     76             Self::InvalidListingAddr(error) => write!(f, "{error}"),
     77         }
     78     }
     79 }
     80 
     81 #[cfg(all(feature = "std", feature = "serde_json"))]
     82 impl std::error::Error for RadrootsOrderEnvelopeParseError {
     83     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
     84         match self {
     85             Self::InvalidEnvelope(error) => Some(error),
     86             Self::InvalidPayload(error) => Some(error),
     87             Self::InvalidListingAddr(error) => Some(error),
     88             _ => None,
     89         }
     90     }
     91 }
     92 
     93 #[cfg(feature = "serde_json")]
     94 #[derive(Clone, Debug, PartialEq, Eq)]
     95 pub struct RadrootsOrderEventContext {
     96     pub counterparty_pubkey: RadrootsPublicKey,
     97     pub listing_event: Option<RadrootsNostrEventPtr>,
     98     pub root_event_id: Option<RadrootsEventId>,
     99     pub prev_event_id: Option<RadrootsEventId>,
    100 }
    101 
    102 #[cfg(feature = "serde_json")]
    103 pub fn order_envelope_from_event<T: DeserializeOwned>(
    104     event: &RadrootsNostrEvent,
    105 ) -> Result<RadrootsOrderEnvelope<T>, RadrootsOrderEnvelopeParseError> {
    106     if !is_order_event_kind(event.kind) {
    107         return Err(RadrootsOrderEnvelopeParseError::InvalidKind(event.kind));
    108     }
    109     let envelope = serde_json::from_str::<RadrootsOrderEnvelope<T>>(&event.content)
    110         .map_err(|_| RadrootsOrderEnvelopeParseError::InvalidJson)?;
    111     envelope
    112         .validate()
    113         .map_err(RadrootsOrderEnvelopeParseError::InvalidEnvelope)?;
    114     if envelope.message_type.kind() != event.kind {
    115         return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch {
    116             event_kind: event.kind,
    117             message_type: envelope.message_type,
    118         });
    119     }
    120 
    121     let listing_addr = required_order_tag_value(&event.tags, "a")?;
    122     if envelope.listing_addr != listing_addr {
    123         return Err(RadrootsOrderEnvelopeParseError::ListingAddrTagMismatch);
    124     }
    125     RadrootsListingAddress::parse(&envelope.listing_addr)
    126         .map_err(RadrootsOrderEnvelopeParseError::InvalidListingAddr)?;
    127 
    128     let tag_order_id = required_order_tag_value(&event.tags, TAG_D)?;
    129     if tag_order_id != envelope.order_id {
    130         return Err(RadrootsOrderEnvelopeParseError::OrderIdTagMismatch);
    131     }
    132 
    133     order_event_context_from_tags(envelope.message_type, &event.tags)?;
    134     Ok(envelope)
    135 }
    136 
    137 #[cfg(feature = "serde_json")]
    138 pub fn order_request_from_event(
    139     event: &RadrootsNostrEvent,
    140 ) -> Result<RadrootsOrderEnvelope<RadrootsOrderRequest>, RadrootsOrderEnvelopeParseError> {
    141     let envelope = order_envelope_from_event::<RadrootsOrderRequest>(event)?;
    142     if envelope.message_type != RadrootsOrderEventType::OrderRequested {
    143         return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch {
    144             event_kind: event.kind,
    145             message_type: envelope.message_type,
    146         });
    147     }
    148     envelope
    149         .payload
    150         .validate()
    151         .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?;
    152     validate_order_binding(
    153         event,
    154         &envelope,
    155         &envelope.payload.order_id,
    156         &envelope.payload.listing_addr,
    157         &envelope.payload.buyer_pubkey,
    158         &envelope.payload.seller_pubkey,
    159     )?;
    160     Ok(envelope)
    161 }
    162 
    163 #[cfg(feature = "serde_json")]
    164 pub fn order_decision_from_event(
    165     event: &RadrootsNostrEvent,
    166 ) -> Result<RadrootsOrderEnvelope<RadrootsOrderDecision>, RadrootsOrderEnvelopeParseError> {
    167     let envelope = order_envelope_from_event::<RadrootsOrderDecision>(event)?;
    168     if envelope.message_type != RadrootsOrderEventType::OrderDecision {
    169         return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch {
    170             event_kind: event.kind,
    171             message_type: envelope.message_type,
    172         });
    173     }
    174     envelope
    175         .payload
    176         .validate()
    177         .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?;
    178     validate_order_binding(
    179         event,
    180         &envelope,
    181         &envelope.payload.order_id,
    182         &envelope.payload.listing_addr,
    183         &envelope.payload.seller_pubkey,
    184         &envelope.payload.buyer_pubkey,
    185     )?;
    186     Ok(envelope)
    187 }
    188 
    189 #[cfg(feature = "serde_json")]
    190 pub fn order_revision_proposal_from_event(
    191     event: &RadrootsNostrEvent,
    192 ) -> Result<RadrootsOrderEnvelope<RadrootsOrderRevisionProposal>, RadrootsOrderEnvelopeParseError> {
    193     let envelope = order_envelope_from_event::<RadrootsOrderRevisionProposal>(event)?;
    194     if envelope.message_type != RadrootsOrderEventType::OrderRevisionProposed {
    195         return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch {
    196             event_kind: event.kind,
    197             message_type: envelope.message_type,
    198         });
    199     }
    200     envelope
    201         .payload
    202         .validate()
    203         .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?;
    204     validate_order_binding(
    205         event,
    206         &envelope,
    207         &envelope.payload.order_id,
    208         &envelope.payload.listing_addr,
    209         &envelope.payload.seller_pubkey,
    210         &envelope.payload.buyer_pubkey,
    211     )?;
    212     let context = order_event_context_from_tags(envelope.message_type, &event.tags)?;
    213     if context.root_event_id.as_deref() != Some(envelope.payload.root_event_id.as_str()) {
    214         return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch(
    215             "root_event_id",
    216         ));
    217     }
    218     if context.prev_event_id.as_deref() != Some(envelope.payload.prev_event_id.as_str()) {
    219         return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch(
    220             "prev_event_id",
    221         ));
    222     }
    223     Ok(envelope)
    224 }
    225 
    226 #[cfg(feature = "serde_json")]
    227 pub fn order_revision_decision_from_event(
    228     event: &RadrootsNostrEvent,
    229 ) -> Result<RadrootsOrderEnvelope<RadrootsOrderRevisionDecision>, RadrootsOrderEnvelopeParseError> {
    230     let envelope = order_envelope_from_event::<RadrootsOrderRevisionDecision>(event)?;
    231     if envelope.message_type != RadrootsOrderEventType::OrderRevisionDecision {
    232         return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch {
    233             event_kind: event.kind,
    234             message_type: envelope.message_type,
    235         });
    236     }
    237     envelope
    238         .payload
    239         .validate()
    240         .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?;
    241     validate_order_binding(
    242         event,
    243         &envelope,
    244         &envelope.payload.order_id,
    245         &envelope.payload.listing_addr,
    246         &envelope.payload.buyer_pubkey,
    247         &envelope.payload.seller_pubkey,
    248     )?;
    249     let context = order_event_context_from_tags(envelope.message_type, &event.tags)?;
    250     if context.root_event_id.as_deref() != Some(envelope.payload.root_event_id.as_str()) {
    251         return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch(
    252             "root_event_id",
    253         ));
    254     }
    255     if context.prev_event_id.as_deref() != Some(envelope.payload.prev_event_id.as_str()) {
    256         return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch(
    257             "prev_event_id",
    258         ));
    259     }
    260     Ok(envelope)
    261 }
    262 
    263 #[cfg(feature = "serde_json")]
    264 pub fn order_cancellation_from_event(
    265     event: &RadrootsNostrEvent,
    266 ) -> Result<RadrootsOrderEnvelope<RadrootsOrderCancellation>, RadrootsOrderEnvelopeParseError> {
    267     let envelope = order_envelope_from_event::<RadrootsOrderCancellation>(event)?;
    268     if envelope.message_type != RadrootsOrderEventType::OrderCancelled {
    269         return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch {
    270             event_kind: event.kind,
    271             message_type: envelope.message_type,
    272         });
    273     }
    274     envelope
    275         .payload
    276         .validate()
    277         .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?;
    278     validate_order_binding(
    279         event,
    280         &envelope,
    281         &envelope.payload.order_id,
    282         &envelope.payload.listing_addr,
    283         &envelope.payload.buyer_pubkey,
    284         &envelope.payload.seller_pubkey,
    285     )?;
    286     Ok(envelope)
    287 }
    288 
    289 #[cfg(feature = "serde_json")]
    290 pub fn order_event_context_from_tags(
    291     message_type: RadrootsOrderEventType,
    292     tags: &[Vec<String>],
    293 ) -> Result<RadrootsOrderEventContext, RadrootsOrderEnvelopeParseError> {
    294     let counterparty_pubkey =
    295         parse_order_counterparty_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?;
    296     let counterparty_pubkey = RadrootsPublicKey::parse(&counterparty_pubkey)
    297         .map_err(|_| RadrootsOrderEnvelopeParseError::InvalidTag("p"))?;
    298     let listing_event =
    299         parse_order_listing_event_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?;
    300     let root_event_id =
    301         parse_order_root_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?;
    302     let root_event_id = root_event_id
    303         .map(|id| {
    304             RadrootsEventId::parse(id)
    305                 .map_err(|_| RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_ROOT))
    306         })
    307         .transpose()?;
    308     let prev_event_id =
    309         parse_order_prev_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?;
    310     let prev_event_id = prev_event_id
    311         .map(|id| {
    312             RadrootsEventId::parse(id)
    313                 .map_err(|_| RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_PREV))
    314         })
    315         .transpose()?;
    316 
    317     if message_type.requires_listing_snapshot() && listing_event.is_none() {
    318         return Err(RadrootsOrderEnvelopeParseError::MissingTag(
    319             TAG_LISTING_EVENT,
    320         ));
    321     }
    322     if message_type.requires_order_chain() {
    323         if root_event_id.is_none() {
    324             return Err(RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_ROOT));
    325         }
    326         if prev_event_id.is_none() {
    327             return Err(RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_PREV));
    328         }
    329     }
    330 
    331     Ok(RadrootsOrderEventContext {
    332         counterparty_pubkey,
    333         listing_event,
    334         root_event_id,
    335         prev_event_id,
    336     })
    337 }
    338 
    339 #[cfg(feature = "serde_json")]
    340 fn required_order_tag_value<'a>(
    341     tags: &'a [Vec<String>],
    342     key: &'static str,
    343 ) -> Result<&'a str, RadrootsOrderEnvelopeParseError> {
    344     let tag = tags
    345         .iter()
    346         .find(|tag| tag.first().map(|value| value.as_str()) == Some(key))
    347         .ok_or(RadrootsOrderEnvelopeParseError::MissingTag(key))?;
    348     let value = tag
    349         .get(1)
    350         .map(|value| value.as_str())
    351         .ok_or(RadrootsOrderEnvelopeParseError::InvalidTag(key))?;
    352     if value.trim().is_empty() {
    353         return Err(RadrootsOrderEnvelopeParseError::InvalidTag(key));
    354     }
    355     Ok(value)
    356 }
    357 
    358 #[cfg(feature = "serde_json")]
    359 fn map_tag_parse_error_for_order_envelope(
    360     error: crate::error::EventParseError,
    361 ) -> RadrootsOrderEnvelopeParseError {
    362     match error {
    363         crate::error::EventParseError::MissingTag(tag) => {
    364             RadrootsOrderEnvelopeParseError::MissingTag(tag)
    365         }
    366         crate::error::EventParseError::InvalidTag(tag) => {
    367             RadrootsOrderEnvelopeParseError::InvalidTag(tag)
    368         }
    369         crate::error::EventParseError::InvalidKind { expected: _, got } => {
    370             RadrootsOrderEnvelopeParseError::InvalidKind(got)
    371         }
    372         crate::error::EventParseError::InvalidNumber(tag, _)
    373         | crate::error::EventParseError::InvalidJson(tag) => {
    374             RadrootsOrderEnvelopeParseError::InvalidTag(tag)
    375         }
    376     }
    377 }
    378 
    379 #[cfg(feature = "serde_json")]
    380 fn validate_order_binding<T>(
    381     event: &RadrootsNostrEvent,
    382     envelope: &RadrootsOrderEnvelope<T>,
    383     payload_order_id: &str,
    384     payload_listing_addr: &str,
    385     expected_author: &str,
    386     expected_counterparty: &str,
    387 ) -> Result<(), RadrootsOrderEnvelopeParseError> {
    388     if envelope.order_id != payload_order_id {
    389         return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch(
    390             "order_id",
    391         ));
    392     }
    393     if envelope.listing_addr != payload_listing_addr {
    394         return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch(
    395             "listing_addr",
    396         ));
    397     }
    398     if event.author != expected_author {
    399         return Err(RadrootsOrderEnvelopeParseError::AuthorMismatch);
    400     }
    401     let context = order_event_context_from_tags(envelope.message_type, &event.tags)?;
    402     if context.counterparty_pubkey.as_str() != expected_counterparty {
    403         return Err(RadrootsOrderEnvelopeParseError::CounterpartyTagMismatch);
    404     }
    405     Ok(())
    406 }
    407 
    408 #[cfg(all(test, feature = "serde_json"))]
    409 mod tests {
    410     use super::{
    411         RadrootsOrderEnvelopeParseError, map_tag_parse_error_for_order_envelope,
    412         order_cancellation_from_event, order_decision_from_event, order_envelope_from_event,
    413         order_event_context_from_tags, order_request_from_event,
    414         order_revision_decision_from_event, order_revision_proposal_from_event,
    415     };
    416     use crate::order::tags::TAG_LISTING_EVENT;
    417     use crate::{
    418         error::EventEncodeError,
    419         order::encode::{
    420             order_cancellation_event_build, order_decision_event_build, order_request_event_build,
    421             order_revision_decision_event_build, order_revision_proposal_event_build,
    422         },
    423     };
    424     use radroots_core::{
    425         RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreUnit,
    426     };
    427     use radroots_events::{
    428         RadrootsNostrEvent, RadrootsNostrEventPtr,
    429         ids::{
    430             RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId,
    431             RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey,
    432         },
    433         kinds::{
    434             KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST,
    435             KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL,
    436         },
    437         order::{
    438             RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome,
    439             RadrootsOrderEconomicItem, RadrootsOrderEconomicLine, RadrootsOrderEconomics,
    440             RadrootsOrderEnvelope, RadrootsOrderEnvelopeError, RadrootsOrderEventType,
    441             RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderPayloadError,
    442             RadrootsOrderPricingBasis, RadrootsOrderRequest, RadrootsOrderRevisionDecision,
    443             RadrootsOrderRevisionOutcome, RadrootsOrderRevisionProposal,
    444         },
    445         tags::{TAG_D, TAG_E_PREV, TAG_E_ROOT},
    446     };
    447 
    448     fn pubkey(character: char) -> RadrootsPublicKey {
    449         core::iter::repeat_n(character, 64)
    450             .collect::<String>()
    451             .parse()
    452             .unwrap()
    453     }
    454 
    455     fn buyer_pubkey() -> RadrootsPublicKey {
    456         pubkey('b')
    457     }
    458 
    459     fn seller_pubkey() -> RadrootsPublicKey {
    460         pubkey('a')
    461     }
    462 
    463     fn buyer_pubkey_wire() -> String {
    464         buyer_pubkey().into_string()
    465     }
    466 
    467     fn seller_pubkey_wire() -> String {
    468         seller_pubkey().into_string()
    469     }
    470 
    471     fn listing_addr() -> RadrootsListingAddress {
    472         format!("30402:{}:AAAAAAAAAAAAAAAAAAAAAg", seller_pubkey_wire())
    473             .parse()
    474             .unwrap()
    475     }
    476 
    477     fn listing_addr_wire() -> String {
    478         listing_addr().into_string()
    479     }
    480 
    481     fn order_id(raw: &str) -> RadrootsOrderId {
    482         raw.parse().unwrap()
    483     }
    484 
    485     fn revision_id(raw: &str) -> RadrootsOrderRevisionId {
    486         raw.parse().unwrap()
    487     }
    488 
    489     fn quote_id(raw: &str) -> RadrootsOrderQuoteId {
    490         raw.parse().unwrap()
    491     }
    492 
    493     fn bin_id(raw: &str) -> RadrootsInventoryBinId {
    494         raw.parse().unwrap()
    495     }
    496 
    497     fn event_id(character: char) -> RadrootsEventId {
    498         core::iter::repeat_n(character, 64)
    499             .collect::<String>()
    500             .parse()
    501             .unwrap()
    502     }
    503 
    504     fn event_id_wire(character: char) -> String {
    505         event_id(character).into_string()
    506     }
    507 
    508     fn order_request() -> RadrootsOrderRequest {
    509         RadrootsOrderRequest {
    510             order_id: order_id("order-1"),
    511             listing_addr: listing_addr(),
    512             buyer_pubkey: buyer_pubkey(),
    513             seller_pubkey: seller_pubkey(),
    514             items: vec![RadrootsOrderItem {
    515                 bin_id: bin_id("lb"),
    516                 bin_count: 3,
    517             }],
    518             economics: request_economics(),
    519         }
    520     }
    521 
    522     fn decimal(raw: &str) -> RadrootsCoreDecimal {
    523         raw.parse().unwrap()
    524     }
    525 
    526     fn usd(raw: &str) -> RadrootsCoreMoney {
    527         RadrootsCoreMoney::new(decimal(raw), RadrootsCoreCurrency::USD)
    528     }
    529 
    530     fn request_economics() -> RadrootsOrderEconomics {
    531         RadrootsOrderEconomics {
    532             quote_id: quote_id("quote-1"),
    533             quote_version: 1,
    534             pricing_basis: RadrootsOrderPricingBasis::ListingEvent,
    535             currency: RadrootsCoreCurrency::USD,
    536             items: vec![RadrootsOrderEconomicItem {
    537                 bin_id: bin_id("lb"),
    538                 bin_count: 3,
    539                 quantity_amount: decimal("1"),
    540                 quantity_unit: RadrootsCoreUnit::Each,
    541                 unit_price_amount: decimal("5"),
    542                 unit_price_currency: RadrootsCoreCurrency::USD,
    543                 line_subtotal: usd("15"),
    544             }],
    545             discounts: Vec::<RadrootsOrderEconomicLine>::new(),
    546             adjustments: Vec::<RadrootsOrderEconomicLine>::new(),
    547             subtotal: usd("15"),
    548             discount_total: usd("0"),
    549             adjustment_total: usd("0"),
    550             total: usd("15"),
    551         }
    552     }
    553 
    554     fn order_decision() -> RadrootsOrderDecision {
    555         RadrootsOrderDecision {
    556             order_id: order_id("order-1"),
    557             listing_addr: listing_addr(),
    558             buyer_pubkey: buyer_pubkey(),
    559             seller_pubkey: seller_pubkey(),
    560             decision: RadrootsOrderDecisionOutcome::Accepted {
    561                 inventory_commitments: vec![RadrootsOrderInventoryCommitment {
    562                     bin_id: bin_id("lb"),
    563                     bin_count: 3,
    564                 }],
    565             },
    566         }
    567     }
    568 
    569     fn order_revision_proposal() -> RadrootsOrderRevisionProposal {
    570         let mut economics = request_economics();
    571         economics.quote_id = quote_id("revision-quote-1");
    572         economics.quote_version = 2;
    573         economics.items[0].bin_count = 4;
    574         economics.items[0].line_subtotal = usd("20");
    575         economics.subtotal = usd("20");
    576         economics.total = usd("20");
    577         economics.canonicalize();
    578         RadrootsOrderRevisionProposal {
    579             revision_id: revision_id("rev-1"),
    580             order_id: order_id("order-1"),
    581             listing_addr: listing_addr(),
    582             buyer_pubkey: buyer_pubkey(),
    583             seller_pubkey: seller_pubkey(),
    584             root_event_id: event_id('1'),
    585             prev_event_id: event_id('2'),
    586             items: vec![RadrootsOrderItem {
    587                 bin_id: bin_id("lb"),
    588                 bin_count: 4,
    589             }],
    590             economics,
    591             reason: "update count".into(),
    592         }
    593     }
    594 
    595     fn order_revision_decision(
    596         decision: RadrootsOrderRevisionOutcome,
    597     ) -> RadrootsOrderRevisionDecision {
    598         RadrootsOrderRevisionDecision {
    599             revision_id: revision_id("rev-1"),
    600             order_id: order_id("order-1"),
    601             listing_addr: listing_addr(),
    602             buyer_pubkey: buyer_pubkey(),
    603             seller_pubkey: seller_pubkey(),
    604             root_event_id: event_id('1'),
    605             prev_event_id: event_id('3'),
    606             decision,
    607         }
    608     }
    609 
    610     fn order_cancelled() -> RadrootsOrderCancellation {
    611         RadrootsOrderCancellation {
    612             order_id: order_id("order-1"),
    613             listing_addr: listing_addr(),
    614             buyer_pubkey: buyer_pubkey(),
    615             seller_pubkey: seller_pubkey(),
    616             reason: "changed plans".into(),
    617         }
    618     }
    619 
    620     fn listing_event_ptr() -> RadrootsNostrEventPtr {
    621         RadrootsNostrEventPtr {
    622             id: event_id_wire('a'),
    623             relays: Some("wss://relay.example.com".into()),
    624         }
    625     }
    626 
    627     fn order_request_tags() -> Vec<Vec<String>> {
    628         vec![
    629             vec!["p".into(), seller_pubkey_wire()],
    630             vec!["a".into(), listing_addr_wire()],
    631             vec![TAG_D.into(), "order-1".into()],
    632             vec![TAG_LISTING_EVENT.into(), event_id_wire('a')],
    633         ]
    634     }
    635 
    636     fn order_chain_tags(counterparty_pubkey: String) -> Vec<Vec<String>> {
    637         vec![
    638             vec!["p".into(), counterparty_pubkey],
    639             vec!["a".into(), listing_addr_wire()],
    640             vec![TAG_D.into(), "order-1".into()],
    641             vec![TAG_E_ROOT.into(), event_id_wire('1')],
    642             vec![TAG_E_PREV.into(), event_id_wire('2')],
    643         ]
    644     }
    645 
    646     fn order_event_with_envelope<T: serde::Serialize>(
    647         kind: u32,
    648         author: String,
    649         message_type: RadrootsOrderEventType,
    650         listing_addr: impl Into<String>,
    651         order_id: impl Into<String>,
    652         payload: &T,
    653         tags: Vec<Vec<String>>,
    654     ) -> RadrootsNostrEvent {
    655         let envelope = RadrootsOrderEnvelope::new(message_type, listing_addr, order_id, payload);
    656         RadrootsNostrEvent {
    657             id: event_id_wire('e'),
    658             author,
    659             created_at: 1,
    660             kind,
    661             tags,
    662             content: serde_json::to_string(&envelope).unwrap(),
    663             sig: "sig".into(),
    664         }
    665     }
    666 
    667     #[test]
    668     fn listing_address_roundtrips() {
    669         let raw = format!("30402:{}:listing-1", seller_pubkey_wire());
    670         let addr = RadrootsListingAddress::parse(&raw).expect("parse listing address");
    671         assert_eq!(addr.as_str(), raw);
    672     }
    673 
    674     #[test]
    675     fn order_request_builder_emits_canonical_shape() {
    676         let payload = order_request();
    677         let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap();
    678         let envelope: RadrootsOrderEnvelope<RadrootsOrderRequest> =
    679             serde_json::from_str(&built.content).unwrap();
    680 
    681         assert_eq!(built.kind, KIND_ORDER_REQUEST);
    682         assert_eq!(
    683             envelope.message_type,
    684             RadrootsOrderEventType::OrderRequested
    685         );
    686         assert_eq!(envelope.order_id, "order-1");
    687         assert_eq!(built.tags[0], vec!["p".to_string(), seller_pubkey_wire()]);
    688         assert_eq!(built.tags[1], vec!["a".to_string(), listing_addr_wire()]);
    689         assert_eq!(
    690             built.tags[2],
    691             vec![TAG_D.to_string(), "order-1".to_string()]
    692         );
    693         assert_eq!(envelope.payload.economics.quote_id, "quote-1");
    694         assert_eq!(envelope.payload.economics.total, usd("15"));
    695         assert!(
    696             built
    697                 .tags
    698                 .iter()
    699                 .any(|tag| tag.first().map(String::as_str) == Some(TAG_LISTING_EVENT))
    700         );
    701         assert!(
    702             !built
    703                 .tags
    704                 .iter()
    705                 .any(|tag| tag.first().map(String::as_str) == Some(TAG_E_ROOT))
    706         );
    707     }
    708 
    709     #[test]
    710     fn order_decision_builder_emits_canonical_chain_shape() {
    711         let payload = order_decision();
    712         let root_event_id = event_id('1');
    713         let prev_event_id = event_id('9');
    714         let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap();
    715         let envelope: RadrootsOrderEnvelope<RadrootsOrderDecision> =
    716             serde_json::from_str(&built.content).unwrap();
    717 
    718         assert_eq!(built.kind, KIND_ORDER_DECISION);
    719         assert_eq!(envelope.message_type, RadrootsOrderEventType::OrderDecision);
    720         assert_eq!(built.tags[0], vec!["p".to_string(), buyer_pubkey_wire()]);
    721         assert_eq!(
    722             built.tags[2],
    723             vec![TAG_D.to_string(), "order-1".to_string()]
    724         );
    725         assert!(
    726             built
    727                 .tags
    728                 .iter()
    729                 .any(|tag| tag == &vec![TAG_E_ROOT.to_string(), event_id_wire('1')])
    730         );
    731         assert!(
    732             built
    733                 .tags
    734                 .iter()
    735                 .any(|tag| tag == &vec![TAG_E_PREV.to_string(), event_id_wire('9')])
    736         );
    737     }
    738 
    739     #[test]
    740     fn order_revision_proposal_builder_emits_canonical_chain_shape() {
    741         let payload = order_revision_proposal();
    742         let built = order_revision_proposal_event_build(
    743             &payload.root_event_id,
    744             &payload.prev_event_id,
    745             &payload,
    746         )
    747         .unwrap();
    748         let envelope: RadrootsOrderEnvelope<RadrootsOrderRevisionProposal> =
    749             serde_json::from_str(&built.content).unwrap();
    750 
    751         assert_eq!(built.kind, KIND_ORDER_REVISION_PROPOSAL);
    752         assert_eq!(
    753             envelope.message_type,
    754             RadrootsOrderEventType::OrderRevisionProposed
    755         );
    756         assert_eq!(built.tags[0], vec!["p".to_string(), buyer_pubkey_wire()]);
    757         assert_eq!(
    758             built.tags[2],
    759             vec![TAG_D.to_string(), "order-1".to_string()]
    760         );
    761         assert_eq!(envelope.payload.revision_id, "rev-1");
    762         assert_eq!(envelope.payload.economics.quote_version, 2);
    763         assert!(
    764             built
    765                 .tags
    766                 .iter()
    767                 .any(|tag| tag == &vec![TAG_E_ROOT.to_string(), event_id_wire('1')])
    768         );
    769         assert!(
    770             built
    771                 .tags
    772                 .iter()
    773                 .any(|tag| tag == &vec![TAG_E_PREV.to_string(), event_id_wire('2')])
    774         );
    775     }
    776 
    777     #[test]
    778     fn order_revision_decision_builder_emits_canonical_chain_shape() {
    779         let payload = order_revision_decision(RadrootsOrderRevisionOutcome::Accepted);
    780         let built = order_revision_decision_event_build(
    781             &payload.root_event_id,
    782             &payload.prev_event_id,
    783             &payload,
    784         )
    785         .unwrap();
    786         let envelope: RadrootsOrderEnvelope<RadrootsOrderRevisionDecision> =
    787             serde_json::from_str(&built.content).unwrap();
    788 
    789         assert_eq!(built.kind, KIND_ORDER_REVISION_DECISION);
    790         assert_eq!(
    791             envelope.message_type,
    792             RadrootsOrderEventType::OrderRevisionDecision
    793         );
    794         assert_eq!(built.tags[0], vec!["p".to_string(), seller_pubkey_wire()]);
    795         assert_eq!(
    796             built.tags[2],
    797             vec![TAG_D.to_string(), "order-1".to_string()]
    798         );
    799         assert_eq!(envelope.payload.revision_id, "rev-1");
    800         assert!(
    801             built
    802                 .tags
    803                 .iter()
    804                 .any(|tag| tag == &vec![TAG_E_ROOT.to_string(), event_id_wire('1')])
    805         );
    806         assert!(
    807             built
    808                 .tags
    809                 .iter()
    810                 .any(|tag| tag == &vec![TAG_E_PREV.to_string(), event_id_wire('3')])
    811         );
    812     }
    813 
    814     #[test]
    815     fn order_revision_builders_reject_mismatched_chain_context() {
    816         let proposal = order_revision_proposal();
    817         let wrong_root = event_id('9');
    818         let wrong_prev = event_id('8');
    819 
    820         let err =
    821             order_revision_proposal_event_build(&wrong_root, &proposal.prev_event_id, &proposal)
    822                 .unwrap_err();
    823         assert!(matches!(
    824             err,
    825             EventEncodeError::InvalidField("root_event_id")
    826         ));
    827 
    828         let err =
    829             order_revision_proposal_event_build(&proposal.root_event_id, &wrong_prev, &proposal)
    830                 .unwrap_err();
    831         assert!(matches!(
    832             err,
    833             EventEncodeError::InvalidField("prev_event_id")
    834         ));
    835 
    836         let decision = order_revision_decision(RadrootsOrderRevisionOutcome::Accepted);
    837         let err =
    838             order_revision_decision_event_build(&wrong_root, &decision.prev_event_id, &decision)
    839                 .unwrap_err();
    840         assert!(matches!(
    841             err,
    842             EventEncodeError::InvalidField("root_event_id")
    843         ));
    844 
    845         let err =
    846             order_revision_decision_event_build(&decision.root_event_id, &wrong_prev, &decision)
    847                 .unwrap_err();
    848         assert!(matches!(
    849             err,
    850             EventEncodeError::InvalidField("prev_event_id")
    851         ));
    852     }
    853 
    854     #[test]
    855     fn order_cancellation_builder_emits_canonical_buyer_chain_shape() {
    856         let payload = order_cancelled();
    857         let root_event_id = event_id('1');
    858         let prev_event_id = event_id('9');
    859         let built =
    860             order_cancellation_event_build(&root_event_id, &prev_event_id, &payload).unwrap();
    861         let envelope: RadrootsOrderEnvelope<RadrootsOrderCancellation> =
    862             serde_json::from_str(&built.content).unwrap();
    863 
    864         assert_eq!(built.kind, KIND_ORDER_CANCELLATION);
    865         assert_eq!(
    866             envelope.message_type,
    867             RadrootsOrderEventType::OrderCancelled
    868         );
    869         assert_eq!(envelope.payload.reason, payload.reason);
    870         assert_eq!(built.tags[0], vec!["p".to_string(), seller_pubkey_wire()]);
    871         assert_eq!(
    872             built.tags[2],
    873             vec![TAG_D.to_string(), "order-1".to_string()]
    874         );
    875         assert!(
    876             built
    877                 .tags
    878                 .iter()
    879                 .any(|tag| tag == &vec![TAG_E_ROOT.to_string(), event_id_wire('1')])
    880         );
    881         assert!(
    882             built
    883                 .tags
    884                 .iter()
    885                 .any(|tag| tag == &vec![TAG_E_PREV.to_string(), event_id_wire('9')])
    886         );
    887     }
    888 
    889     #[test]
    890     fn order_request_parse_roundtrips_and_validates_tags() {
    891         let payload = order_request();
    892         let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap();
    893         let event = RadrootsNostrEvent {
    894             id: event_id_wire('e'),
    895             author: buyer_pubkey_wire(),
    896             created_at: 1,
    897             kind: built.kind,
    898             tags: built.tags,
    899             content: built.content,
    900             sig: "sig".into(),
    901         };
    902         let envelope = order_request_from_event(&event).unwrap();
    903 
    904         assert_eq!(envelope.payload, payload);
    905         assert_eq!(
    906             envelope.message_type,
    907             RadrootsOrderEventType::OrderRequested
    908         );
    909     }
    910 
    911     #[test]
    912     fn order_request_parse_rejects_mismatched_economics() {
    913         let mut payload = order_request();
    914         let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap();
    915         payload.economics.items[0].bin_id = bin_id("other-bin");
    916         let envelope = RadrootsOrderEnvelope::new(
    917             RadrootsOrderEventType::OrderRequested,
    918             payload.listing_addr.clone(),
    919             payload.order_id.clone(),
    920             payload,
    921         );
    922         let event = RadrootsNostrEvent {
    923             id: event_id_wire('e'),
    924             author: buyer_pubkey_wire(),
    925             created_at: 1,
    926             kind: built.kind,
    927             tags: built.tags,
    928             content: serde_json::to_string(&envelope).unwrap(),
    929             sig: "sig".into(),
    930         };
    931         let err = order_request_from_event(&event).unwrap_err();
    932         assert_eq!(
    933             err,
    934             RadrootsOrderEnvelopeParseError::InvalidPayload(
    935                 RadrootsOrderPayloadError::InvalidOrderEconomicsBinding {
    936                     field: "items.bin_id"
    937                 }
    938             )
    939         );
    940     }
    941 
    942     #[test]
    943     fn order_decision_parse_roundtrips_and_validates_chain_tags() {
    944         let payload = order_decision();
    945         let root_event_id = event_id('1');
    946         let prev_event_id = event_id('9');
    947         let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap();
    948         let event = RadrootsNostrEvent {
    949             id: event_id_wire('e'),
    950             author: seller_pubkey_wire(),
    951             created_at: 1,
    952             kind: built.kind,
    953             tags: built.tags,
    954             content: built.content,
    955             sig: "sig".into(),
    956         };
    957         let envelope = order_decision_from_event(&event).unwrap();
    958 
    959         assert_eq!(envelope.payload, payload);
    960         assert_eq!(envelope.message_type, RadrootsOrderEventType::OrderDecision);
    961     }
    962 
    963     #[test]
    964     fn order_cancellation_parse_roundtrips_and_validates_buyer_actor() {
    965         let payload = order_cancelled();
    966         let root_event_id = event_id('1');
    967         let prev_event_id = event_id('9');
    968         let built =
    969             order_cancellation_event_build(&root_event_id, &prev_event_id, &payload).unwrap();
    970         let event = RadrootsNostrEvent {
    971             id: event_id_wire('e'),
    972             author: buyer_pubkey_wire(),
    973             created_at: 1,
    974             kind: built.kind,
    975             tags: built.tags,
    976             content: built.content,
    977             sig: "sig".into(),
    978         };
    979         let envelope = order_cancellation_from_event(&event).unwrap();
    980 
    981         assert_eq!(envelope.payload, payload);
    982         assert_eq!(
    983             envelope.message_type,
    984             RadrootsOrderEventType::OrderCancelled
    985         );
    986     }
    987 
    988     #[test]
    989     fn order_revision_proposal_parse_validates_actor_counterparty_and_chain_payload() {
    990         let payload = order_revision_proposal();
    991         let built = order_revision_proposal_event_build(
    992             &payload.root_event_id,
    993             &payload.prev_event_id,
    994             &payload,
    995         )
    996         .unwrap();
    997         let mut event = RadrootsNostrEvent {
    998             id: event_id_wire('e'),
    999             author: seller_pubkey_wire(),
   1000             created_at: 1,
   1001             kind: built.kind,
   1002             tags: built.tags,
   1003             content: built.content,
   1004             sig: "sig".into(),
   1005         };
   1006         let envelope = order_revision_proposal_from_event(&event).unwrap();
   1007         assert_eq!(envelope.payload, payload);
   1008 
   1009         event.author = buyer_pubkey_wire();
   1010         let err = order_revision_proposal_from_event(&event).unwrap_err();
   1011         assert_eq!(err, RadrootsOrderEnvelopeParseError::AuthorMismatch);
   1012     }
   1013 
   1014     #[test]
   1015     fn order_revision_decision_parse_validates_actor_counterparty_and_chain_payload() {
   1016         let payload = order_revision_decision(RadrootsOrderRevisionOutcome::Declined {
   1017             reason: "no change".into(),
   1018         });
   1019         let built = order_revision_decision_event_build(
   1020             &payload.root_event_id,
   1021             &payload.prev_event_id,
   1022             &payload,
   1023         )
   1024         .unwrap();
   1025         let mut event = RadrootsNostrEvent {
   1026             id: event_id_wire('e'),
   1027             author: buyer_pubkey_wire(),
   1028             created_at: 1,
   1029             kind: built.kind,
   1030             tags: built.tags,
   1031             content: built.content,
   1032             sig: "sig".into(),
   1033         };
   1034         let envelope = order_revision_decision_from_event(&event).unwrap();
   1035         assert_eq!(envelope.payload, payload);
   1036 
   1037         event.author = seller_pubkey_wire();
   1038         let err = order_revision_decision_from_event(&event).unwrap_err();
   1039         assert_eq!(err, RadrootsOrderEnvelopeParseError::AuthorMismatch);
   1040     }
   1041 
   1042     #[cfg(feature = "std")]
   1043     #[test]
   1044     fn order_parse_error_display_and_source_cover_variants() {
   1045         use std::error::Error as _;
   1046 
   1047         let invalid_envelope = RadrootsOrderEnvelopeParseError::InvalidEnvelope(
   1048             RadrootsOrderEnvelopeError::MissingOrderId,
   1049         );
   1050         let invalid_payload = RadrootsOrderEnvelopeParseError::InvalidPayload(
   1051             RadrootsOrderPayloadError::MissingItems,
   1052         );
   1053         let invalid_listing_addr = RadrootsOrderEnvelopeParseError::InvalidListingAddr(
   1054             RadrootsListingAddress::parse("not-a-listing-address").unwrap_err(),
   1055         );
   1056         let errors = [
   1057             RadrootsOrderEnvelopeParseError::InvalidKind(3431),
   1058             RadrootsOrderEnvelopeParseError::InvalidJson,
   1059             invalid_envelope.clone(),
   1060             invalid_payload.clone(),
   1061             RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch {
   1062                 event_kind: KIND_ORDER_REQUEST,
   1063                 message_type: RadrootsOrderEventType::OrderDecision,
   1064             },
   1065             RadrootsOrderEnvelopeParseError::MissingTag("a"),
   1066             RadrootsOrderEnvelopeParseError::InvalidTag("p"),
   1067             RadrootsOrderEnvelopeParseError::ListingAddrTagMismatch,
   1068             RadrootsOrderEnvelopeParseError::OrderIdTagMismatch,
   1069             RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("order_id"),
   1070             RadrootsOrderEnvelopeParseError::AuthorMismatch,
   1071             RadrootsOrderEnvelopeParseError::CounterpartyTagMismatch,
   1072             invalid_listing_addr.clone(),
   1073         ];
   1074 
   1075         for error in errors {
   1076             assert!(!error.to_string().is_empty());
   1077         }
   1078         assert!(invalid_envelope.source().is_some());
   1079         assert!(invalid_payload.source().is_some());
   1080         assert!(invalid_listing_addr.source().is_some());
   1081         assert!(
   1082             RadrootsOrderEnvelopeParseError::AuthorMismatch
   1083                 .source()
   1084                 .is_none()
   1085         );
   1086     }
   1087 
   1088     #[test]
   1089     fn order_envelope_parse_rejects_content_tag_and_envelope_mismatches() {
   1090         let payload = serde_json::json!({});
   1091         let invalid_json = RadrootsNostrEvent {
   1092             id: event_id_wire('e'),
   1093             author: buyer_pubkey_wire(),
   1094             created_at: 1,
   1095             kind: KIND_ORDER_REQUEST,
   1096             tags: Vec::new(),
   1097             content: "{".into(),
   1098             sig: "sig".into(),
   1099         };
   1100         assert_eq!(
   1101             order_envelope_from_event::<serde_json::Value>(&invalid_json).unwrap_err(),
   1102             RadrootsOrderEnvelopeParseError::InvalidJson
   1103         );
   1104 
   1105         let mut invalid_version_envelope = RadrootsOrderEnvelope::new(
   1106             RadrootsOrderEventType::OrderRequested,
   1107             listing_addr_wire(),
   1108             "order-1",
   1109             &payload,
   1110         );
   1111         invalid_version_envelope.version = 99;
   1112         let invalid_version = RadrootsNostrEvent {
   1113             id: event_id_wire('e'),
   1114             author: buyer_pubkey_wire(),
   1115             created_at: 1,
   1116             kind: KIND_ORDER_REQUEST,
   1117             tags: order_request_tags(),
   1118             content: serde_json::to_string(&invalid_version_envelope).unwrap(),
   1119             sig: "sig".into(),
   1120         };
   1121         assert!(matches!(
   1122             order_envelope_from_event::<serde_json::Value>(&invalid_version).unwrap_err(),
   1123             RadrootsOrderEnvelopeParseError::InvalidEnvelope(
   1124                 RadrootsOrderEnvelopeError::InvalidVersion { .. }
   1125             )
   1126         ));
   1127 
   1128         let message_type_mismatch = order_event_with_envelope(
   1129             KIND_ORDER_REQUEST,
   1130             buyer_pubkey_wire(),
   1131             RadrootsOrderEventType::OrderDecision,
   1132             listing_addr_wire(),
   1133             "order-1",
   1134             &payload,
   1135             Vec::new(),
   1136         );
   1137         assert_eq!(
   1138             order_envelope_from_event::<serde_json::Value>(&message_type_mismatch).unwrap_err(),
   1139             RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch {
   1140                 event_kind: KIND_ORDER_REQUEST,
   1141                 message_type: RadrootsOrderEventType::OrderDecision
   1142             }
   1143         );
   1144 
   1145         let listing_addr_mismatch = order_event_with_envelope(
   1146             KIND_ORDER_REQUEST,
   1147             buyer_pubkey_wire(),
   1148             RadrootsOrderEventType::OrderRequested,
   1149             listing_addr_wire(),
   1150             "order-1",
   1151             &payload,
   1152             vec![
   1153                 vec!["a".into(), "30402:pubkey:AAAAAAAAAAAAAAAAAAAAAg".into()],
   1154                 vec![TAG_D.into(), "order-1".into()],
   1155             ],
   1156         );
   1157         assert_eq!(
   1158             order_envelope_from_event::<serde_json::Value>(&listing_addr_mismatch).unwrap_err(),
   1159             RadrootsOrderEnvelopeParseError::ListingAddrTagMismatch
   1160         );
   1161 
   1162         let order_id_mismatch = order_event_with_envelope(
   1163             KIND_ORDER_REQUEST,
   1164             buyer_pubkey_wire(),
   1165             RadrootsOrderEventType::OrderRequested,
   1166             listing_addr_wire(),
   1167             "order-1",
   1168             &payload,
   1169             vec![
   1170                 vec!["a".into(), listing_addr_wire()],
   1171                 vec![TAG_D.into(), "other-order".into()],
   1172             ],
   1173         );
   1174         assert_eq!(
   1175             order_envelope_from_event::<serde_json::Value>(&order_id_mismatch).unwrap_err(),
   1176             RadrootsOrderEnvelopeParseError::OrderIdTagMismatch
   1177         );
   1178 
   1179         for tags in [
   1180             Vec::<Vec<String>>::new(),
   1181             vec![vec!["a".into()]],
   1182             vec![vec!["a".into(), " ".into()]],
   1183         ] {
   1184             let event = order_event_with_envelope(
   1185                 KIND_ORDER_REQUEST,
   1186                 buyer_pubkey_wire(),
   1187                 RadrootsOrderEventType::OrderRequested,
   1188                 listing_addr_wire(),
   1189                 "order-1",
   1190                 &payload,
   1191                 tags,
   1192             );
   1193             let err = order_envelope_from_event::<serde_json::Value>(&event).unwrap_err();
   1194             assert!(matches!(
   1195                 err,
   1196                 RadrootsOrderEnvelopeParseError::MissingTag("a")
   1197                     | RadrootsOrderEnvelopeParseError::InvalidTag("a")
   1198             ));
   1199         }
   1200 
   1201         let invalid_listing_addr = order_event_with_envelope(
   1202             KIND_ORDER_REQUEST,
   1203             buyer_pubkey_wire(),
   1204             RadrootsOrderEventType::OrderRequested,
   1205             "not-a-listing-address",
   1206             "order-1",
   1207             &payload,
   1208             vec![
   1209                 vec!["a".into(), "not-a-listing-address".into()],
   1210                 vec![TAG_D.into(), "order-1".into()],
   1211             ],
   1212         );
   1213         assert!(matches!(
   1214             order_envelope_from_event::<serde_json::Value>(&invalid_listing_addr).unwrap_err(),
   1215             RadrootsOrderEnvelopeParseError::InvalidListingAddr(_)
   1216         ));
   1217     }
   1218 
   1219     #[test]
   1220     fn order_typed_parsers_reject_message_type_mismatches() {
   1221         let request_payload = order_request();
   1222         let decision_payload = order_decision();
   1223         let proposal_payload = order_revision_proposal();
   1224         let revision_decision_payload =
   1225             order_revision_decision(RadrootsOrderRevisionOutcome::Accepted);
   1226         let cancellation_payload = order_cancelled();
   1227 
   1228         let request_as_decision = order_event_with_envelope(
   1229             KIND_ORDER_DECISION,
   1230             buyer_pubkey_wire(),
   1231             RadrootsOrderEventType::OrderDecision,
   1232             listing_addr_wire(),
   1233             "order-1",
   1234             &request_payload,
   1235             order_chain_tags(seller_pubkey_wire()),
   1236         );
   1237         assert!(matches!(
   1238             order_request_from_event(&request_as_decision).unwrap_err(),
   1239             RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. }
   1240         ));
   1241 
   1242         let decision_as_request = order_event_with_envelope(
   1243             KIND_ORDER_REQUEST,
   1244             seller_pubkey_wire(),
   1245             RadrootsOrderEventType::OrderRequested,
   1246             listing_addr_wire(),
   1247             "order-1",
   1248             &decision_payload,
   1249             order_request_tags(),
   1250         );
   1251         assert!(matches!(
   1252             order_decision_from_event(&decision_as_request).unwrap_err(),
   1253             RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. }
   1254         ));
   1255 
   1256         let proposal_as_cancellation = order_event_with_envelope(
   1257             KIND_ORDER_CANCELLATION,
   1258             seller_pubkey_wire(),
   1259             RadrootsOrderEventType::OrderCancelled,
   1260             listing_addr_wire(),
   1261             "order-1",
   1262             &proposal_payload,
   1263             order_chain_tags(buyer_pubkey_wire()),
   1264         );
   1265         assert!(matches!(
   1266             order_revision_proposal_from_event(&proposal_as_cancellation).unwrap_err(),
   1267             RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. }
   1268         ));
   1269 
   1270         let revision_decision_as_cancellation = order_event_with_envelope(
   1271             KIND_ORDER_CANCELLATION,
   1272             buyer_pubkey_wire(),
   1273             RadrootsOrderEventType::OrderCancelled,
   1274             listing_addr_wire(),
   1275             "order-1",
   1276             &revision_decision_payload,
   1277             order_chain_tags(seller_pubkey_wire()),
   1278         );
   1279         assert!(matches!(
   1280             order_revision_decision_from_event(&revision_decision_as_cancellation).unwrap_err(),
   1281             RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. }
   1282         ));
   1283 
   1284         let cancellation_as_decision = order_event_with_envelope(
   1285             KIND_ORDER_DECISION,
   1286             buyer_pubkey_wire(),
   1287             RadrootsOrderEventType::OrderDecision,
   1288             listing_addr_wire(),
   1289             "order-1",
   1290             &cancellation_payload,
   1291             order_chain_tags(seller_pubkey_wire()),
   1292         );
   1293         assert!(matches!(
   1294             order_cancellation_from_event(&cancellation_as_decision).unwrap_err(),
   1295             RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. }
   1296         ));
   1297     }
   1298 
   1299     #[test]
   1300     fn order_parse_rejects_payload_and_chain_binding_mismatches() {
   1301         let mut request_payload = order_request();
   1302         request_payload.order_id = order_id("other-order");
   1303         let request_built =
   1304             order_request_event_build(&listing_event_ptr(), &order_request()).unwrap();
   1305         let mut request_event = RadrootsNostrEvent {
   1306             id: event_id_wire('e'),
   1307             author: buyer_pubkey_wire(),
   1308             created_at: 1,
   1309             kind: request_built.kind,
   1310             tags: request_built.tags.clone(),
   1311             content: serde_json::to_string(&RadrootsOrderEnvelope::new(
   1312                 RadrootsOrderEventType::OrderRequested,
   1313                 listing_addr_wire(),
   1314                 "order-1",
   1315                 &request_payload,
   1316             ))
   1317             .unwrap(),
   1318             sig: "sig".into(),
   1319         };
   1320         assert_eq!(
   1321             order_request_from_event(&request_event).unwrap_err(),
   1322             RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("order_id")
   1323         );
   1324 
   1325         request_payload = order_request();
   1326         request_payload.listing_addr =
   1327             format!("30402:{}:BBBBBBBBBBBBBBBBBBBBBA", seller_pubkey_wire())
   1328                 .parse()
   1329                 .unwrap();
   1330         request_event.content = serde_json::to_string(&RadrootsOrderEnvelope::new(
   1331             RadrootsOrderEventType::OrderRequested,
   1332             listing_addr_wire(),
   1333             "order-1",
   1334             &request_payload,
   1335         ))
   1336         .unwrap();
   1337         assert_eq!(
   1338             order_request_from_event(&request_event).unwrap_err(),
   1339             RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("listing_addr")
   1340         );
   1341 
   1342         let proposal_payload = order_revision_proposal();
   1343         let proposal_built = order_revision_proposal_event_build(
   1344             &proposal_payload.root_event_id,
   1345             &proposal_payload.prev_event_id,
   1346             &proposal_payload,
   1347         )
   1348         .unwrap();
   1349         let mut proposal_event = RadrootsNostrEvent {
   1350             id: event_id_wire('e'),
   1351             author: seller_pubkey_wire(),
   1352             created_at: 1,
   1353             kind: proposal_built.kind,
   1354             tags: proposal_built.tags.clone(),
   1355             content: proposal_built.content.clone(),
   1356             sig: "sig".into(),
   1357         };
   1358         proposal_event
   1359             .tags
   1360             .iter_mut()
   1361             .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_ROOT))
   1362             .unwrap()[1] = event_id_wire('4');
   1363         assert_eq!(
   1364             order_revision_proposal_from_event(&proposal_event).unwrap_err(),
   1365             RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("root_event_id")
   1366         );
   1367 
   1368         proposal_event.tags = proposal_built.tags;
   1369         proposal_event
   1370             .tags
   1371             .iter_mut()
   1372             .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_PREV))
   1373             .unwrap()[1] = event_id_wire('5');
   1374         assert_eq!(
   1375             order_revision_proposal_from_event(&proposal_event).unwrap_err(),
   1376             RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("prev_event_id")
   1377         );
   1378 
   1379         let revision_decision_payload =
   1380             order_revision_decision(RadrootsOrderRevisionOutcome::Accepted);
   1381         let revision_decision_built = order_revision_decision_event_build(
   1382             &revision_decision_payload.root_event_id,
   1383             &revision_decision_payload.prev_event_id,
   1384             &revision_decision_payload,
   1385         )
   1386         .unwrap();
   1387         let mut revision_decision_event = RadrootsNostrEvent {
   1388             id: event_id_wire('e'),
   1389             author: buyer_pubkey_wire(),
   1390             created_at: 1,
   1391             kind: revision_decision_built.kind,
   1392             tags: revision_decision_built.tags.clone(),
   1393             content: revision_decision_built.content,
   1394             sig: "sig".into(),
   1395         };
   1396         revision_decision_event
   1397             .tags
   1398             .iter_mut()
   1399             .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_ROOT))
   1400             .unwrap()[1] = event_id_wire('6');
   1401         assert_eq!(
   1402             order_revision_decision_from_event(&revision_decision_event).unwrap_err(),
   1403             RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("root_event_id")
   1404         );
   1405 
   1406         revision_decision_event.tags = revision_decision_built.tags;
   1407         revision_decision_event
   1408             .tags
   1409             .iter_mut()
   1410             .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_PREV))
   1411             .unwrap()[1] = event_id_wire('7');
   1412         assert_eq!(
   1413             order_revision_decision_from_event(&revision_decision_event).unwrap_err(),
   1414             RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("prev_event_id")
   1415         );
   1416     }
   1417 
   1418     #[test]
   1419     fn order_event_context_and_parse_error_mapping_cover_missing_context() {
   1420         let err = order_event_context_from_tags(
   1421             RadrootsOrderEventType::OrderRequested,
   1422             &[vec!["p".into(), seller_pubkey_wire()]],
   1423         )
   1424         .unwrap_err();
   1425         assert_eq!(
   1426             err,
   1427             RadrootsOrderEnvelopeParseError::MissingTag(TAG_LISTING_EVENT)
   1428         );
   1429 
   1430         let err = order_event_context_from_tags(
   1431             RadrootsOrderEventType::OrderDecision,
   1432             &[
   1433                 vec!["p".into(), buyer_pubkey_wire()],
   1434                 vec![TAG_E_PREV.into(), event_id_wire('2')],
   1435             ],
   1436         )
   1437         .unwrap_err();
   1438         assert_eq!(err, RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_ROOT));
   1439 
   1440         let err = order_event_context_from_tags(
   1441             RadrootsOrderEventType::OrderDecision,
   1442             &[
   1443                 vec!["p".into(), buyer_pubkey_wire()],
   1444                 vec![TAG_E_ROOT.into(), event_id_wire('1')],
   1445                 vec![TAG_E_PREV.into(), "not-an-event-id".into()],
   1446             ],
   1447         )
   1448         .unwrap_err();
   1449         assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_PREV));
   1450 
   1451         let invalid_number = "x".parse::<u32>().unwrap_err();
   1452         assert_eq!(
   1453             map_tag_parse_error_for_order_envelope(crate::error::EventParseError::MissingTag("p")),
   1454             RadrootsOrderEnvelopeParseError::MissingTag("p")
   1455         );
   1456         assert_eq!(
   1457             map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidTag("p")),
   1458             RadrootsOrderEnvelopeParseError::InvalidTag("p")
   1459         );
   1460         assert_eq!(
   1461             map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidKind {
   1462                 expected: "1",
   1463                 got: 2,
   1464             }),
   1465             RadrootsOrderEnvelopeParseError::InvalidKind(2)
   1466         );
   1467         assert_eq!(
   1468             map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidNumber(
   1469                 "n",
   1470                 invalid_number,
   1471             )),
   1472             RadrootsOrderEnvelopeParseError::InvalidTag("n")
   1473         );
   1474         assert_eq!(
   1475             map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidJson(
   1476                 "json",
   1477             )),
   1478             RadrootsOrderEnvelopeParseError::InvalidTag("json")
   1479         );
   1480     }
   1481 
   1482     #[test]
   1483     fn order_revision_kinds_parse_with_chain_tags() {
   1484         for (kind, message_type) in [
   1485             (
   1486                 KIND_ORDER_REVISION_PROPOSAL,
   1487                 RadrootsOrderEventType::OrderRevisionProposed,
   1488             ),
   1489             (
   1490                 KIND_ORDER_REVISION_DECISION,
   1491                 RadrootsOrderEventType::OrderRevisionDecision,
   1492             ),
   1493         ] {
   1494             let payload = serde_json::json!({});
   1495             let envelope =
   1496                 RadrootsOrderEnvelope::new(message_type, listing_addr_wire(), "order-1", &payload);
   1497             let event = RadrootsNostrEvent {
   1498                 id: event_id_wire('e'),
   1499                 author: seller_pubkey_wire(),
   1500                 created_at: 1,
   1501                 kind,
   1502                 tags: vec![
   1503                     vec!["p".into(), buyer_pubkey_wire()],
   1504                     vec!["a".into(), listing_addr_wire()],
   1505                     vec![TAG_D.into(), "order-1".into()],
   1506                     vec![TAG_E_ROOT.into(), event_id_wire('1')],
   1507                     vec![TAG_E_PREV.into(), event_id_wire('9')],
   1508                 ],
   1509                 content: serde_json::to_string(&envelope).unwrap(),
   1510                 sig: "sig".into(),
   1511             };
   1512             let parsed = order_envelope_from_event::<serde_json::Value>(&event).unwrap();
   1513 
   1514             assert_eq!(parsed.message_type, message_type);
   1515             assert_eq!(parsed.order_id, "order-1");
   1516         }
   1517     }
   1518 
   1519     #[test]
   1520     fn order_parse_rejects_forbidden_kind() {
   1521         let event = RadrootsNostrEvent {
   1522             id: event_id_wire('e'),
   1523             author: seller_pubkey_wire(),
   1524             created_at: 1,
   1525             kind: 3431,
   1526             tags: Vec::new(),
   1527             content: "{}".into(),
   1528             sig: "sig".into(),
   1529         };
   1530         let err = order_envelope_from_event::<serde_json::Value>(&event).unwrap_err();
   1531         assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidKind(3431));
   1532     }
   1533 
   1534     #[test]
   1535     fn order_parse_rejects_missing_required_refs() {
   1536         let payload = order_decision();
   1537         let root_event_id = event_id('1');
   1538         let prev_event_id = event_id('9');
   1539         let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap();
   1540         let mut event = RadrootsNostrEvent {
   1541             id: event_id_wire('e'),
   1542             author: seller_pubkey_wire(),
   1543             created_at: 1,
   1544             kind: built.kind,
   1545             tags: built.tags,
   1546             content: built.content,
   1547             sig: "sig".into(),
   1548         };
   1549         event
   1550             .tags
   1551             .retain(|tag| tag.first().map(String::as_str) != Some(TAG_E_PREV));
   1552 
   1553         let err = order_decision_from_event(&event).unwrap_err();
   1554         assert_eq!(err, RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_PREV));
   1555     }
   1556 
   1557     #[test]
   1558     fn order_parse_rejects_author_and_counterparty_mismatch() {
   1559         let payload = order_request();
   1560         let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap();
   1561         let mut event = RadrootsNostrEvent {
   1562             id: event_id_wire('e'),
   1563             author: seller_pubkey_wire(),
   1564             created_at: 1,
   1565             kind: built.kind,
   1566             tags: built.tags.clone(),
   1567             content: built.content.clone(),
   1568             sig: "sig".into(),
   1569         };
   1570         let err = order_request_from_event(&event).unwrap_err();
   1571         assert_eq!(err, RadrootsOrderEnvelopeParseError::AuthorMismatch);
   1572 
   1573         event.author = buyer_pubkey_wire();
   1574         event.tags[0] = vec!["p".into(), pubkey('c').into_string()];
   1575         let err = order_request_from_event(&event).unwrap_err();
   1576         assert_eq!(
   1577             err,
   1578             RadrootsOrderEnvelopeParseError::CounterpartyTagMismatch
   1579         );
   1580     }
   1581 
   1582     #[test]
   1583     fn order_cancellation_parse_rejects_wrong_actor() {
   1584         let cancellation = order_cancelled();
   1585         let root_event_id = event_id('1');
   1586         let prev_event_id = event_id('9');
   1587         let cancellation_parts =
   1588             order_cancellation_event_build(&root_event_id, &prev_event_id, &cancellation).unwrap();
   1589         let cancellation_event = RadrootsNostrEvent {
   1590             id: event_id_wire('e'),
   1591             author: seller_pubkey_wire(),
   1592             created_at: 1,
   1593             kind: cancellation_parts.kind,
   1594             tags: cancellation_parts.tags,
   1595             content: cancellation_parts.content,
   1596             sig: "sig".into(),
   1597         };
   1598         let err = order_cancellation_from_event(&cancellation_event).unwrap_err();
   1599         assert_eq!(err, RadrootsOrderEnvelopeParseError::AuthorMismatch);
   1600     }
   1601 
   1602     #[test]
   1603     fn order_parse_rejects_invalid_protocol_tag_values() {
   1604         let payload = order_decision();
   1605         let root_event_id = event_id('1');
   1606         let prev_event_id = event_id('9');
   1607         let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap();
   1608         let mut event = RadrootsNostrEvent {
   1609             id: event_id_wire('e'),
   1610             author: seller_pubkey_wire(),
   1611             created_at: 1,
   1612             kind: built.kind,
   1613             tags: built.tags,
   1614             content: built.content,
   1615             sig: "sig".into(),
   1616         };
   1617 
   1618         event.tags[0] = vec!["p".into(), "not-a-pubkey".into()];
   1619         let err = order_decision_from_event(&event).unwrap_err();
   1620         assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidTag("p"));
   1621 
   1622         event.tags[0] = vec!["p".into(), buyer_pubkey_wire()];
   1623         let root_tag = event
   1624             .tags
   1625             .iter_mut()
   1626             .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_ROOT))
   1627             .unwrap();
   1628         root_tag[1] = "not-an-event-id".into();
   1629         let err = order_decision_from_event(&event).unwrap_err();
   1630         assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_ROOT));
   1631     }
   1632 }