commit f8aa69ceeb40c0721b8a0881d11c5a5fc35ad983
parent 313ff445cffe806e98d83effd702b167a3ba5866
Author: triesap <tyson@radroots.org>
Date: Thu, 20 Aug 2026 05:17:41 +0000
refactor(events): enforce typed trade mutation indexes
- Intent: enforce exact typed trade-mutation authoring and validation.
- Why: remove generic structural-tag authority from public trade paths.
- Verification: pass focused contracts, full workspace, Clippy, and Rustdoc.
- Risk: changes governed trade tags and rejects legacy or malformed events.
Diffstat:
22 files changed, 1975 insertions(+), 427 deletions(-)
diff --git a/contracts/api_baselines/radroots_event_codec.txt b/contracts/api_baselines/radroots_event_codec.txt
@@ -182,6 +182,7 @@ pub radroots_event_codec::authoring::AuthoredPlanError::Envelope(radroots_event:
pub radroots_event_codec::authoring::AuthoredPlanError::FoodAvailability(radroots_event_codec::encode::food_availability::RadrootsFoodAvailabilityEncodeError)
pub radroots_event_codec::authoring::AuthoredPlanError::InvalidAuthor(radroots_identity::error::Error)
pub radroots_event_codec::authoring::AuthoredPlanError::Profile(radroots_event_codec::encode::profile::RadrootsAuthoredProfileEncodeError)
+pub radroots_event_codec::authoring::AuthoredPlanError::Trade(radroots_event_codec::decode::trade::RadrootsTradeMutationError)
impl radroots_event_codec::authoring::AuthoredPlanError
pub const fn radroots_event_codec::authoring::AuthoredPlanError::code(&self) -> &'static str
impl core::error::Error for radroots_event_codec::authoring::AuthoredPlanError
@@ -234,6 +235,7 @@ pub fn radroots_event_codec::authoring::AuthoredEventBody::from_nip10_reply(&rad
pub fn radroots_event_codec::authoring::AuthoredEventBody::from_nip22_comment(&radroots_event::post::comment::AuthoredNip22Comment) -> core::result::Result<Self, radroots_event_codec::authoring::AuthoredPlanError>
pub fn radroots_event_codec::authoring::AuthoredEventBody::from_photo_update(&radroots_event::post::AuthoredPhotoUpdate) -> core::result::Result<Self, radroots_event_codec::authoring::AuthoredPlanError>
pub fn radroots_event_codec::authoring::AuthoredEventBody::from_profile(&radroots_event::profile::AuthoredProfile) -> core::result::Result<Self, radroots_event_codec::authoring::AuthoredPlanError>
+pub fn radroots_event_codec::authoring::AuthoredEventBody::from_trade_mutation(radroots_event::trade::TradeMutationEnvelopeV1) -> core::result::Result<Self, radroots_event_codec::authoring::AuthoredPlanError>
pub fn radroots_event_codec::authoring::AuthoredEventBody::from_update(&radroots_event::post::AuthoredUpdate) -> core::result::Result<Self, radroots_event_codec::authoring::AuthoredPlanError>
pub struct radroots_event_codec::authoring::AuthoredEventPlan
impl radroots_event_codec::authoring::AuthoredEventPlan
@@ -254,6 +256,7 @@ pub fn radroots_event_codec::authoring::AuthoredEventPlan::from_nip10_reply(&rad
pub fn radroots_event_codec::authoring::AuthoredEventPlan::from_nip22_comment(&radroots_event::post::comment::AuthoredNip22Comment, u64, impl core::convert::AsRef<str>) -> core::result::Result<Self, radroots_event_codec::authoring::AuthoredPlanError>
pub fn radroots_event_codec::authoring::AuthoredEventPlan::from_photo_update(&radroots_event::post::AuthoredPhotoUpdate, u64, impl core::convert::AsRef<str>) -> core::result::Result<Self, radroots_event_codec::authoring::AuthoredPlanError>
pub fn radroots_event_codec::authoring::AuthoredEventPlan::from_profile(&radroots_event::profile::AuthoredProfile, u64, impl core::convert::AsRef<str>) -> core::result::Result<Self, radroots_event_codec::authoring::AuthoredPlanError>
+pub fn radroots_event_codec::authoring::AuthoredEventPlan::from_trade_mutation(radroots_event::trade::TradeMutationEnvelopeV1) -> core::result::Result<Self, radroots_event_codec::authoring::AuthoredPlanError>
pub fn radroots_event_codec::authoring::AuthoredEventPlan::from_update(&radroots_event::post::AuthoredUpdate, u64, impl core::convert::AsRef<str>) -> core::result::Result<Self, radroots_event_codec::authoring::AuthoredPlanError>
pub struct radroots_event_codec::authoring::BlossomAuthorizationPlan
impl radroots_event_codec::authoring::BlossomAuthorizationPlan
@@ -299,7 +302,7 @@ impl serde_core::ser::Serialize for radroots_event_codec::authoring::PlanWireV1
pub fn radroots_event_codec::authoring::PlanWireV1::serialize<S>(&self, S) -> core::result::Result<<S as serde_core::ser::Serializer>::Ok, <S as serde_core::ser::Serializer>::Error> where S: serde_core::ser::Serializer
pub const radroots_event_codec::authoring::PLAN_WIRE_MAX_BYTES: usize
pub const radroots_event_codec::authoring::PLAN_WIRE_VERSION_V1: u32
-pub const radroots_event_codec::authoring::REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS: [&str; 10]
+pub const radroots_event_codec::authoring::REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS: [&str; 15]
pub mod radroots_event_codec::canonical
pub fn radroots_event_codec::canonical::id(&radroots_event::envelope::EventEnvelope) -> core::result::Result<radroots_event::id::EventId, radroots_event_codec::canonical::CanonicalError>
pub fn radroots_event_codec::canonical::id_preimage(&radroots_event::envelope::EventEnvelope) -> core::result::Result<alloc::string::String, radroots_event_codec::canonical::CanonicalError>
@@ -617,22 +620,37 @@ pub fn radroots_event_codec::decode::seal::data_from_event(alloc::string::String
pub fn radroots_event_codec::decode::seal::parsed_from_event(alloc::string::String, alloc::string::String, u64, u32, alloc::string::String, alloc::vec::Vec<alloc::vec::Vec<alloc::string::String>>, alloc::string::String) -> core::result::Result<radroots_event_codec::decode::parsed::RadrootsParsedEvent<radroots_event::social::seal::Seal>, radroots_event_codec::decode::EventParseError>
pub fn radroots_event_codec::decode::seal::seal_from_parts(u32, &[alloc::vec::Vec<alloc::string::String>], &str) -> core::result::Result<radroots_event::social::seal::Seal, radroots_event_codec::decode::EventParseError>
pub mod radroots_event_codec::decode::trade
-pub enum radroots_event_codec::decode::trade::RadrootsTradeMutationParseError
-pub radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::Canonical(radroots_event::trade::TradeProtocolError)
-pub radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::ContractTagMismatch
-pub radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::CounterpartyTagMismatch
-pub radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::InvalidKind(u32)
-pub radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::InvalidTag(&'static str)
-pub radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::KindContractMismatch
-pub radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::MissingTag(&'static str)
-pub radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::ParentTagsMismatch
-pub radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::TradeIdTagMismatch
-impl core::convert::From<radroots_event::trade::TradeProtocolError> for radroots_event_codec::decode::trade::RadrootsTradeMutationParseError
-pub fn radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::from(radroots_event::trade::TradeProtocolError) -> Self
-impl core::error::Error for radroots_event_codec::decode::trade::RadrootsTradeMutationParseError
-impl core::fmt::Display for radroots_event_codec::decode::trade::RadrootsTradeMutationParseError
-pub fn radroots_event_codec::decode::trade::RadrootsTradeMutationParseError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub fn radroots_event_codec::decode::trade::trade_mutation_from_event(&radroots_event::envelope::EventEnvelope) -> core::result::Result<radroots_event::trade::TradeMutationEnvelopeV1, radroots_event_codec::decode::trade::RadrootsTradeMutationParseError>
+pub enum radroots_event_codec::decode::trade::RadrootsTradeMutationError
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::AuthorMismatch
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::AuthoredAtMismatch
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::CallerStructuralTagForbidden
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::CanonicalContentMismatch
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::ContractTagMismatch
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::DuplicateTradeTag
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::InvalidIdentifier
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::InvalidKind
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::InvalidTagShape
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::LegacyParentEventTag
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::MissingMutationTag
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::MissingParentTag
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::MissingRootTag
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::MutationTagMismatch
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::NoncanonicalParentOrder
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::ParentTagMismatch
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::PartyTagOrderMismatch
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::RootTagMismatch
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::TradeTagMismatch
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::UnexpectedParentTag
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::UnexpectedRootTag
+pub radroots_event_codec::decode::trade::RadrootsTradeMutationError::UnexpectedTag
+impl radroots_event_codec::decode::trade::RadrootsTradeMutationError
+pub const fn radroots_event_codec::decode::trade::RadrootsTradeMutationError::code(self) -> &'static str
+impl core::error::Error for radroots_event_codec::decode::trade::RadrootsTradeMutationError
+impl core::fmt::Display for radroots_event_codec::decode::trade::RadrootsTradeMutationError
+pub fn radroots_event_codec::decode::trade::RadrootsTradeMutationError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn radroots_event_codec::decode::trade::trade_mutation_from_event(&radroots_event::envelope::EventEnvelope) -> core::result::Result<radroots_event::trade::TradeMutationEnvelopeV1, radroots_event_codec::decode::trade::RadrootsTradeMutationError>
+pub fn radroots_event_codec::decode::trade::trade_mutation_from_verified_event(&crate::verification::RadrootsSignatureVerifiedEvent) -> core::result::Result<radroots_event::trade::TradeMutationEnvelopeV1, radroots_event_codec::decode::trade::RadrootsTradeMutationError>
+pub fn radroots_event_codec::decode::trade::validate_trade_mutation_tags(&radroots_event::trade::TradeMutationEnvelopeV1, &[alloc::vec::Vec<alloc::string::String>]) -> core::result::Result<(), radroots_event_codec::decode::trade::RadrootsTradeMutationError>
pub mod radroots_event_codec::decode::wire
pub fn radroots_event_codec::decode::wire::canonicalize_tags(&mut alloc::vec::Vec<alloc::vec::Vec<alloc::string::String>>)
pub fn radroots_event_codec::decode::wire::empty_content() -> alloc::string::String
@@ -1082,8 +1100,37 @@ impl radroots_event_codec::encode::tag_builders::RadrootsEventTagBuilder for rad
pub type radroots_event::social::seal::Seal::Error = radroots_event_codec::encode::EventEncodeError
pub fn radroots_event::social::seal::Seal::build_tags(&self) -> core::result::Result<alloc::vec::Vec<alloc::vec::Vec<alloc::string::String>>, Self::Error>
pub mod radroots_event_codec::encode::trade
-pub fn radroots_event_codec::encode::trade::trade_mutation_event_build(radroots_event::trade::TradeMutationEnvelopeV1) -> core::result::Result<radroots_event::wire::v1::Nip01EventWireParts, radroots_event_codec::encode::EventEncodeError>
-pub fn radroots_event_codec::encode::trade::trade_mutation_tags(&radroots_event::trade::TradeMutationEnvelopeV1) -> core::result::Result<alloc::vec::Vec<alloc::vec::Vec<alloc::string::String>>, radroots_event_codec::encode::EventEncodeError>
+pub enum radroots_event_codec::encode::trade::RadrootsTradeMutationError
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::AuthorMismatch
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::AuthoredAtMismatch
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::CallerStructuralTagForbidden
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::CanonicalContentMismatch
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::ContractTagMismatch
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::DuplicateTradeTag
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::InvalidIdentifier
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::InvalidKind
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::InvalidTagShape
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::LegacyParentEventTag
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::MissingMutationTag
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::MissingParentTag
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::MissingRootTag
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::MutationTagMismatch
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::NoncanonicalParentOrder
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::ParentTagMismatch
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::PartyTagOrderMismatch
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::RootTagMismatch
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::TradeTagMismatch
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::UnexpectedParentTag
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::UnexpectedRootTag
+pub radroots_event_codec::encode::trade::RadrootsTradeMutationError::UnexpectedTag
+impl radroots_event_codec::decode::trade::RadrootsTradeMutationError
+pub const fn radroots_event_codec::decode::trade::RadrootsTradeMutationError::code(self) -> &'static str
+impl core::error::Error for radroots_event_codec::decode::trade::RadrootsTradeMutationError
+impl core::fmt::Display for radroots_event_codec::decode::trade::RadrootsTradeMutationError
+pub fn radroots_event_codec::decode::trade::RadrootsTradeMutationError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn radroots_event_codec::encode::trade::trade_mutation_event_build(radroots_event::trade::TradeMutationEnvelopeV1) -> core::result::Result<radroots_event::wire::v1::Nip01EventWireParts, radroots_event_codec::decode::trade::RadrootsTradeMutationError>
+pub fn radroots_event_codec::encode::trade::trade_mutation_event_build_with_extra_tags(radroots_event::trade::TradeMutationEnvelopeV1, &[alloc::vec::Vec<alloc::string::String>]) -> core::result::Result<radroots_event::wire::v1::Nip01EventWireParts, radroots_event_codec::decode::trade::RadrootsTradeMutationError>
+pub fn radroots_event_codec::encode::trade::trade_mutation_tags(&radroots_event::trade::TradeMutationEnvelopeV1) -> core::result::Result<alloc::vec::Vec<alloc::vec::Vec<alloc::string::String>>, radroots_event_codec::decode::trade::RadrootsTradeMutationError>
#[non_exhaustive] pub enum radroots_event_codec::encode::EncodeError
pub radroots_event_codec::encode::EncodeError::Json
impl radroots_event_codec::encode::EncodeError
@@ -1251,6 +1298,7 @@ pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagSemantic::TimeZone
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagSemantic::Title
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagSemantic::Topic
+pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagSemantic::TradeMutation
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagSemantic::Url
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagSemantic::UtcDayCoverage
pub enum radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagValueType
@@ -1267,6 +1315,7 @@ pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagValueType::Geohash
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagValueType::IanaTimeZoneId
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagValueType::Kind
+pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagValueType::MutationId
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagValueType::Nip01Coordinate
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagValueType::PublicKey
pub radroots_event_codec::manifest::registry_v7::RadrootsEventContractRegistryV7TagValueType::RelayUrl
diff --git a/contracts/conformance/vectors/event/authored_operations.v1.json b/contracts/conformance/vectors/event/authored_operations.v1.json
@@ -475,11 +475,11 @@
}
},
{
- "id": "generic_trade_proposal_010",
+ "id": "typed_trade_proposal_010",
"kind": "authored_operations.wire",
"input": {
"contract_id": "radroots.trade.proposal.v1",
- "authoring": "generic"
+ "authoring": "typed"
},
"expected": {
"kind": 3470,
@@ -495,15 +495,228 @@
"11111111111111111111111111111111"
],
[
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "mutation"
+ ],
+ [
+ "p",
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"
+ ],
+ [
"p",
"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
]
],
"content": "{\"author_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"authored_at_unix_s\":1784347200,\"body\":{\"candidate\":{\"base_candidate_id\":null,\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"cancellation\":{\"buyer_pre_agreement\":true,\"post_agreement_cutoff_unix_s\":null,\"profile_id\":\"buyer-pre-agreement\"},\"candidate_id\":\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\",\"economics\":{\"adjustment_total_mantissa\":\"0\",\"adjustments\":[],\"currency_code\":\"USD\",\"currency_exponent\":2,\"discount_total_mantissa\":\"0\",\"profile_id\":\"mvp-fixed\",\"rounding_profile\":\"half-even\",\"subtotal_mantissa\":\"1000\",\"total_mantissa\":\"1000\"},\"farm_id\":\"farm-1\",\"fulfillment\":{\"ends_at_unix_s\":1800003600,\"fold\":0,\"location_class\":\"farmstand\",\"method\":\"pickup\",\"profile_id\":\"market-pickup\",\"requires_private_terms\":false,\"starts_at_unix_s\":1800000000,\"timezone\":\"America/New_York\",\"utc_offset_seconds\":-18000},\"line_tombstones\":[],\"lines\":[{\"bin_id\":\"bin-1\",\"currency_code\":\"USD\",\"line_id\":\"line-1\",\"line_subtotal_mantissa\":\"1000\",\"listing_addr\":\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\",\"listing_event_id\":\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"listing_snapshot_sha256\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"option_id\":null,\"product_id\":\"carrots\",\"quantity_mantissa\":\"2\",\"quantity_scale\":0,\"replaces_line_id\":null,\"unit_code\":\"count\",\"unit_price_mantissa\":\"500\",\"unit_profile\":\"mvp-count\"}],\"private_terms\":null,\"proposal_expires_at_unix_s\":1799999000,\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"supersession_intent\":null},\"mutation_type\":\"proposal\"},\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"contract_id\":\"radroots.trade.proposal.v1\",\"counterparty_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"farm_id\":\"farm-1\",\"mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent_mutation_ids\":[],\"root_mutation_id\":null,\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"trade_id\":\"11111111111111111111111111111111\"}",
- "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3470,[[\"contract\",\"radroots.trade.proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"parent_mutation_ids\\\":[],\\\"root_mutation_id\\\":null,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
- "event_id": "62d04ac41ad9d0d14b1f7f56a6d4d3cbcea73e712f2aa4ae5dec0218b578d078",
- "signature": "4fa5133b540dd67b3397a20ec419bc890cc8e71e85b886fd3c806e305394fdfd24c97961ce00aa949712e9f865a4e517e31117e0f9c9c6cf30a0297f4ac0298c",
- "raw_json": "{\"id\":\"62d04ac41ad9d0d14b1f7f56a6d4d3cbcea73e712f2aa4ae5dec0218b578d078\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3470,\"tags\":[[\"contract\",\"radroots.trade.proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"parent_mutation_ids\\\":[],\\\"root_mutation_id\\\":null,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"4fa5133b540dd67b3397a20ec419bc890cc8e71e85b886fd3c806e305394fdfd24c97961ce00aa949712e9f865a4e517e31117e0f9c9c6cf30a0297f4ac0298c\"}"
+ "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3470,[[\"contract\",\"radroots.trade.proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"mutation\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"parent_mutation_ids\\\":[],\\\"root_mutation_id\\\":null,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
+ "event_id": "58eacb99d9b4e4da7f614fb37ed84b482d2cb85e30785d49ea6a29b555bfcd3a",
+ "signature": "b39fc5d285826bda6462dc9fb8c20c6519b54d4c8ac9673f3c53c6424e2e1703762b4126282e579dcb11c6738477738a61396595fb6c13331e038c3b06b18517",
+ "raw_json": "{\"id\":\"58eacb99d9b4e4da7f614fb37ed84b482d2cb85e30785d49ea6a29b555bfcd3a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3470,\"tags\":[[\"contract\",\"radroots.trade.proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"mutation\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"parent_mutation_ids\\\":[],\\\"root_mutation_id\\\":null,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"b39fc5d285826bda6462dc9fb8c20c6519b54d4c8ac9673f3c53c6424e2e1703762b4126282e579dcb11c6738477738a61396595fb6c13331e038c3b06b18517\"}"
+ }
+ },
+ {
+ "id": "typed_trade_decision_013",
+ "kind": "authored_operations.wire",
+ "input": {
+ "contract_id": "radroots.trade.decision.v1",
+ "authoring": "typed"
+ },
+ "expected": {
+ "kind": 3471,
+ "created_at": 1784347200,
+ "pubkey": "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df",
+ "tags": [
+ [
+ "contract",
+ "radroots.trade.decision.v1"
+ ],
+ [
+ "d",
+ "11111111111111111111111111111111"
+ ],
+ [
+ "x",
+ "07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027",
+ "mutation"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "root"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "parent"
+ ],
+ [
+ "p",
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"
+ ],
+ [
+ "p",
+ "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
+ ]
+ ],
+ "content": "{\"author_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"authored_at_unix_s\":1784347200,\"body\":{\"candidate_id\":\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\",\"decision\":{\"decision\":\"declined\",\"reason\":\"inventory unavailable\"},\"mutation_type\":\"decision\",\"proposal_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"},\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"contract_id\":\"radroots.trade.decision.v1\",\"counterparty_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"farm_id\":\"farm-1\",\"mutation_id\":\"07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027\",\"parent_mutation_ids\":[\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"],\"root_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"trade_id\":\"11111111111111111111111111111111\"}",
+ "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3471,[[\"contract\",\"radroots.trade.decision.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"decision\\\":{\\\"decision\\\":\\\"declined\\\",\\\"reason\\\":\\\"inventory unavailable\\\"},\\\"mutation_type\\\":\\\"decision\\\",\\\"proposal_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.decision.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
+ "event_id": "7dbe947cbeb1cde706bb240657fee585aa4214baa61d728b7bec7ac5ee18350e",
+ "signature": "222a04c15f66c6028c5b6619116ae92958a5e853293697f2fdbe0450300a50f9bfd2f1f56f1ce3eb2bd037d938bf22f6d99ecdb924de8c2fd5f0da846c1d2817",
+ "raw_json": "{\"id\":\"7dbe947cbeb1cde706bb240657fee585aa4214baa61d728b7bec7ac5ee18350e\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3471,\"tags\":[[\"contract\",\"radroots.trade.decision.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"decision\\\":{\\\"decision\\\":\\\"declined\\\",\\\"reason\\\":\\\"inventory unavailable\\\"},\\\"mutation_type\\\":\\\"decision\\\",\\\"proposal_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.decision.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"222a04c15f66c6028c5b6619116ae92958a5e853293697f2fdbe0450300a50f9bfd2f1f56f1ce3eb2bd037d938bf22f6d99ecdb924de8c2fd5f0da846c1d2817\"}"
+ }
+ },
+ {
+ "id": "typed_trade_revision_proposal_014",
+ "kind": "authored_operations.wire",
+ "input": {
+ "contract_id": "radroots.trade.revision_proposal.v1",
+ "authoring": "typed"
+ },
+ "expected": {
+ "kind": 3472,
+ "created_at": 1784347200,
+ "pubkey": "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df",
+ "tags": [
+ [
+ "contract",
+ "radroots.trade.revision_proposal.v1"
+ ],
+ [
+ "d",
+ "11111111111111111111111111111111"
+ ],
+ [
+ "x",
+ "cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847",
+ "mutation"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "root"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "parent"
+ ],
+ [
+ "p",
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"
+ ],
+ [
+ "p",
+ "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
+ ]
+ ],
+ "content": "{\"author_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"authored_at_unix_s\":1784347200,\"body\":{\"candidate\":{\"base_candidate_id\":null,\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"cancellation\":{\"buyer_pre_agreement\":true,\"post_agreement_cutoff_unix_s\":null,\"profile_id\":\"buyer-pre-agreement\"},\"candidate_id\":\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\",\"economics\":{\"adjustment_total_mantissa\":\"0\",\"adjustments\":[],\"currency_code\":\"USD\",\"currency_exponent\":2,\"discount_total_mantissa\":\"0\",\"profile_id\":\"mvp-fixed\",\"rounding_profile\":\"half-even\",\"subtotal_mantissa\":\"1000\",\"total_mantissa\":\"1000\"},\"farm_id\":\"farm-1\",\"fulfillment\":{\"ends_at_unix_s\":1800003600,\"fold\":0,\"location_class\":\"farmstand\",\"method\":\"pickup\",\"profile_id\":\"market-pickup\",\"requires_private_terms\":false,\"starts_at_unix_s\":1800000000,\"timezone\":\"America/New_York\",\"utc_offset_seconds\":-18000},\"line_tombstones\":[],\"lines\":[{\"bin_id\":\"bin-1\",\"currency_code\":\"USD\",\"line_id\":\"line-1\",\"line_subtotal_mantissa\":\"1000\",\"listing_addr\":\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\",\"listing_event_id\":\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"listing_snapshot_sha256\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"option_id\":null,\"product_id\":\"carrots\",\"quantity_mantissa\":\"2\",\"quantity_scale\":0,\"replaces_line_id\":null,\"unit_code\":\"count\",\"unit_price_mantissa\":\"500\",\"unit_profile\":\"mvp-count\"}],\"private_terms\":null,\"proposal_expires_at_unix_s\":1799999000,\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"supersession_intent\":null},\"mutation_type\":\"revision_proposal\"},\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"contract_id\":\"radroots.trade.revision_proposal.v1\",\"counterparty_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"farm_id\":\"farm-1\",\"mutation_id\":\"cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847\",\"parent_mutation_ids\":[\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"],\"root_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"trade_id\":\"11111111111111111111111111111111\"}",
+ "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3472,[[\"contract\",\"radroots.trade.revision_proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"revision_proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.revision_proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
+ "event_id": "3be0e2a2f3f9eef21127eed0dab79ce31932e05927d6558999c5a50022d2eefe",
+ "signature": "777eb26ba6b01ee154a006907cb99ad43dace95208138a30dc48d36419c93f8e1e256d3707eb28a0dd42ed50ed3954b803656f83b0259afae02bb86cc7ae3742",
+ "raw_json": "{\"id\":\"3be0e2a2f3f9eef21127eed0dab79ce31932e05927d6558999c5a50022d2eefe\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3472,\"tags\":[[\"contract\",\"radroots.trade.revision_proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"revision_proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.revision_proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"777eb26ba6b01ee154a006907cb99ad43dace95208138a30dc48d36419c93f8e1e256d3707eb28a0dd42ed50ed3954b803656f83b0259afae02bb86cc7ae3742\"}"
+ }
+ },
+ {
+ "id": "typed_trade_revision_decision_015",
+ "kind": "authored_operations.wire",
+ "input": {
+ "contract_id": "radroots.trade.revision_decision.v1",
+ "authoring": "typed"
+ },
+ "expected": {
+ "kind": 3473,
+ "created_at": 1784347200,
+ "pubkey": "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df",
+ "tags": [
+ [
+ "contract",
+ "radroots.trade.revision_decision.v1"
+ ],
+ [
+ "d",
+ "11111111111111111111111111111111"
+ ],
+ [
+ "x",
+ "bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732",
+ "mutation"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "root"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "parent"
+ ],
+ [
+ "p",
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"
+ ],
+ [
+ "p",
+ "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
+ ]
+ ],
+ "content": "{\"author_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"authored_at_unix_s\":1784347200,\"body\":{\"candidate_id\":\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\",\"decision\":{\"decision\":\"declined\",\"reason\":\"inventory unavailable\"},\"mutation_type\":\"revision_decision\",\"proposal_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"},\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"contract_id\":\"radroots.trade.revision_decision.v1\",\"counterparty_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"farm_id\":\"farm-1\",\"mutation_id\":\"bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732\",\"parent_mutation_ids\":[\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"],\"root_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"trade_id\":\"11111111111111111111111111111111\"}",
+ "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3473,[[\"contract\",\"radroots.trade.revision_decision.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"decision\\\":{\\\"decision\\\":\\\"declined\\\",\\\"reason\\\":\\\"inventory unavailable\\\"},\\\"mutation_type\\\":\\\"revision_decision\\\",\\\"proposal_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.revision_decision.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
+ "event_id": "064a6208b3a064e472fd8f1c4d56c9ca49513693d1343feb0ceeb7a8814f93b6",
+ "signature": "60a9241287ba780bcccc8b3b5c85c102bfab40fa6898990f420d634f3eba01c4b9a76a6ad9efed64d111f1e39d5b5d923810c6cd2793f71d2bd19cd7fb21cdf3",
+ "raw_json": "{\"id\":\"064a6208b3a064e472fd8f1c4d56c9ca49513693d1343feb0ceeb7a8814f93b6\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3473,\"tags\":[[\"contract\",\"radroots.trade.revision_decision.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"decision\\\":{\\\"decision\\\":\\\"declined\\\",\\\"reason\\\":\\\"inventory unavailable\\\"},\\\"mutation_type\\\":\\\"revision_decision\\\",\\\"proposal_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.revision_decision.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"60a9241287ba780bcccc8b3b5c85c102bfab40fa6898990f420d634f3eba01c4b9a76a6ad9efed64d111f1e39d5b5d923810c6cd2793f71d2bd19cd7fb21cdf3\"}"
+ }
+ },
+ {
+ "id": "typed_trade_cancellation_016",
+ "kind": "authored_operations.wire",
+ "input": {
+ "contract_id": "radroots.trade.cancellation.v1",
+ "authoring": "typed"
+ },
+ "expected": {
+ "kind": 3474,
+ "created_at": 1784347200,
+ "pubkey": "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df",
+ "tags": [
+ [
+ "contract",
+ "radroots.trade.cancellation.v1"
+ ],
+ [
+ "d",
+ "11111111111111111111111111111111"
+ ],
+ [
+ "x",
+ "fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d",
+ "mutation"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "root"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "parent"
+ ],
+ [
+ "p",
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"
+ ],
+ [
+ "p",
+ "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
+ ]
+ ],
+ "content": "{\"author_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"authored_at_unix_s\":1784347200,\"body\":{\"mutation_type\":\"cancellation\",\"reason\":\"cancelled\",\"target_candidate_id\":\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\",\"target_claim_mutation_id\":null},\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"contract_id\":\"radroots.trade.cancellation.v1\",\"counterparty_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"farm_id\":\"farm-1\",\"mutation_id\":\"fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d\",\"parent_mutation_ids\":[\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"],\"root_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"trade_id\":\"11111111111111111111111111111111\"}",
+ "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3474,[[\"contract\",\"radroots.trade.cancellation.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"mutation_type\\\":\\\"cancellation\\\",\\\"reason\\\":\\\"cancelled\\\",\\\"target_candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"target_claim_mutation_id\\\":null},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.cancellation.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
+ "event_id": "2cb6574e48eb71825016b5fb94aed7df459948cab74b8f45f34cc1b42ca8767a",
+ "signature": "141c3eace0da665038626a73d336e48bc2ebc2bdee1f460bedd35034d85f199c1059f5b37c3c9b8a51730c4db0b8240223c00a0fc3fc2807cb46a7b6a0504a36",
+ "raw_json": "{\"id\":\"2cb6574e48eb71825016b5fb94aed7df459948cab74b8f45f34cc1b42ca8767a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3474,\"tags\":[[\"contract\",\"radroots.trade.cancellation.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"mutation_type\\\":\\\"cancellation\\\",\\\"reason\\\":\\\"cancelled\\\",\\\"target_candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"target_claim_mutation_id\\\":null},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.cancellation.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"141c3eace0da665038626a73d336e48bc2ebc2bdee1f460bedd35034d85f199c1059f5b37c3c9b8a51730c4db0b8240223c00a0fc3fc2807cb46a7b6a0504a36\"}"
}
}
]
diff --git a/contracts/conformance/vectors/trade/mutation_index_tags_decision.v1.json b/contracts/conformance/vectors/trade/mutation_index_tags_decision.v1.json
@@ -8,26 +8,26 @@
"input": {
"contract_id": "radroots.trade.revision_proposal.v1",
"trade_id": "11111111111111111111111111111111",
- "mutation_id": "2222222222222222222222222222222222222222222222222222222222222222",
+ "mutation_id": "65b018424d93792a061eb2bce370604b1e3590c5f84617f8cec45dfc67b3eb49",
"root_mutation_id": "3333333333333333333333333333333333333333333333333333333333333333",
"parent_mutation_ids": [
"4444444444444444444444444444444444444444444444444444444444444444",
"5555555555555555555555555555555555555555555555555555555555555555"
],
- "buyer_pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- "seller_pubkey": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+ "buyer_pubkey": "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df",
+ "seller_pubkey": "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
},
"expected": {
"kind": 3472,
"tags": [
["contract", "radroots.trade.revision_proposal.v1"],
["d", "11111111111111111111111111111111"],
- ["x", "2222222222222222222222222222222222222222222222222222222222222222", "mutation"],
+ ["x", "65b018424d93792a061eb2bce370604b1e3590c5f84617f8cec45dfc67b3eb49", "mutation"],
["x", "3333333333333333333333333333333333333333333333333333333333333333", "root"],
["x", "4444444444444444444444444444444444444444444444444444444444444444", "parent"],
["x", "5555555555555555555555555555555555555555555555555555555555555555", "parent"],
- ["p", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
- ["p", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"]
+ ["p", "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"],
+ ["p", "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"]
]
}
},
diff --git a/contracts/event_store/event_contract_registry_v7.inventory.json b/contracts/event_store/event_contract_registry_v7.inventory.json
@@ -5661,7 +5661,7 @@
"privacy": "public",
"author_role": "buyer",
"content_schema": "json_object",
- "authoring_policy": "generic_draft",
+ "authoring_policy": "typed_only",
"discriminator": {
"type": "content_json_field_equals",
"field": "contract_id",
@@ -5683,17 +5683,17 @@
"relay_indexed": true
},
{
- "name": "p",
- "cardinality": "required_one",
- "semantic": "counterparty",
- "value_type": "public_key",
+ "name": "x",
+ "cardinality": "required_many",
+ "semantic": "trade_mutation",
+ "value_type": "mutation_id",
"relay_indexed": true
},
{
- "name": "e",
- "cardinality": "optional_many",
- "semantic": "event_pointer",
- "value_type": "event_id",
+ "name": "p",
+ "cardinality": "required_many",
+ "semantic": "participant",
+ "value_type": "public_key",
"relay_indexed": true
}
],
@@ -5713,7 +5713,7 @@
"privacy": "public",
"author_role": "seller",
"content_schema": "json_object",
- "authoring_policy": "generic_draft",
+ "authoring_policy": "typed_only",
"discriminator": {
"type": "content_json_field_equals",
"field": "contract_id",
@@ -5735,17 +5735,17 @@
"relay_indexed": true
},
{
- "name": "p",
- "cardinality": "required_one",
- "semantic": "counterparty",
- "value_type": "public_key",
+ "name": "x",
+ "cardinality": "required_many",
+ "semantic": "trade_mutation",
+ "value_type": "mutation_id",
"relay_indexed": true
},
{
- "name": "e",
- "cardinality": "optional_many",
- "semantic": "event_pointer",
- "value_type": "event_id",
+ "name": "p",
+ "cardinality": "required_many",
+ "semantic": "participant",
+ "value_type": "public_key",
"relay_indexed": true
}
],
@@ -5765,7 +5765,7 @@
"privacy": "public",
"author_role": "any",
"content_schema": "json_object",
- "authoring_policy": "generic_draft",
+ "authoring_policy": "typed_only",
"discriminator": {
"type": "content_json_field_equals",
"field": "contract_id",
@@ -5787,17 +5787,17 @@
"relay_indexed": true
},
{
- "name": "p",
- "cardinality": "required_one",
- "semantic": "counterparty",
- "value_type": "public_key",
+ "name": "x",
+ "cardinality": "required_many",
+ "semantic": "trade_mutation",
+ "value_type": "mutation_id",
"relay_indexed": true
},
{
- "name": "e",
- "cardinality": "optional_many",
- "semantic": "event_pointer",
- "value_type": "event_id",
+ "name": "p",
+ "cardinality": "required_many",
+ "semantic": "participant",
+ "value_type": "public_key",
"relay_indexed": true
}
],
@@ -5817,7 +5817,7 @@
"privacy": "public",
"author_role": "any",
"content_schema": "json_object",
- "authoring_policy": "generic_draft",
+ "authoring_policy": "typed_only",
"discriminator": {
"type": "content_json_field_equals",
"field": "contract_id",
@@ -5839,17 +5839,17 @@
"relay_indexed": true
},
{
- "name": "p",
- "cardinality": "required_one",
- "semantic": "counterparty",
- "value_type": "public_key",
+ "name": "x",
+ "cardinality": "required_many",
+ "semantic": "trade_mutation",
+ "value_type": "mutation_id",
"relay_indexed": true
},
{
- "name": "e",
- "cardinality": "optional_many",
- "semantic": "event_pointer",
- "value_type": "event_id",
+ "name": "p",
+ "cardinality": "required_many",
+ "semantic": "participant",
+ "value_type": "public_key",
"relay_indexed": true
}
],
@@ -5869,7 +5869,7 @@
"privacy": "public",
"author_role": "buyer",
"content_schema": "json_object",
- "authoring_policy": "generic_draft",
+ "authoring_policy": "typed_only",
"discriminator": {
"type": "content_json_field_equals",
"field": "contract_id",
@@ -5891,17 +5891,17 @@
"relay_indexed": true
},
{
- "name": "p",
- "cardinality": "required_one",
- "semantic": "counterparty",
- "value_type": "public_key",
+ "name": "x",
+ "cardinality": "required_many",
+ "semantic": "trade_mutation",
+ "value_type": "mutation_id",
"relay_indexed": true
},
{
- "name": "e",
- "cardinality": "optional_many",
- "semantic": "event_pointer",
- "value_type": "event_id",
+ "name": "p",
+ "cardinality": "required_many",
+ "semantic": "participant",
+ "value_type": "public_key",
"relay_indexed": true
}
],
diff --git a/contracts/event_store/event_contract_registry_v7.inventory.sha256 b/contracts/event_store/event_contract_registry_v7.inventory.sha256
@@ -1 +1 @@
-ea434c925d773fdf7848a1795d1699701f178132439b28a275f8ffca4d42a52e
+20458a303373c4b51530a33cb07bc4e8ad208587838d5ccf31ba2fe28f539c2f
diff --git a/crates/event/src/contract/registry_v7.rs b/crates/event/src/contract/registry_v7.rs
@@ -14,7 +14,8 @@ use crate::{
envelope::EventEnvelope,
envelope::kind::*,
id::{
- AddressableCoordinate, DTag, EventId, Nip01Coordinate, parse_public_key, relay_url_is_valid,
+ AddressableCoordinate, DTag, EventId, MutationId, Nip01Coordinate, parse_public_key,
+ relay_url_is_valid,
},
listing::classified::{
ClassifiedListingPartition, TAG_RADROOTS_PRICE_UNIT, TAG_RADROOTS_QUANTITY,
@@ -197,6 +198,7 @@ pub enum TagSemantic {
Title,
Topic,
TimeZone,
+ TradeMutation,
Url,
UtcDayCoverage,
}
@@ -216,6 +218,7 @@ pub enum TagValueType {
Geohash,
IanaTimeZoneId,
Kind,
+ MutationId,
Nip01Coordinate,
PublicKey,
RelayUrl,
@@ -551,6 +554,20 @@ const TAG_P_MANY: TagContract = tag(
TagValueType::PublicKey,
true,
);
+const TAG_P_TRADE_PARTIES: TagContract = tag(
+ "p",
+ TagCardinality::RequiredMany,
+ TagSemantic::Participant,
+ TagValueType::PublicKey,
+ true,
+);
+const TAG_X_TRADE_MUTATIONS: TagContract = tag(
+ "x",
+ TagCardinality::RequiredMany,
+ TagSemantic::TradeMutation,
+ TagValueType::MutationId,
+ true,
+);
const TAG_CALENDAR_PARTICIPANT: TagContract = tag(
"p",
TagCardinality::OptionalMany,
@@ -1312,8 +1329,12 @@ const OPERATIONAL_LISTING_TAGS: &[TagContract] = &[
TAG_OPERATIONAL_LISTING_EXPIRES_AT,
TAG_OPERATIONAL_LISTING_DELIVERY,
];
-const TRADE_MUTATION_TAGS: &[TagContract] =
- &[TAG_CONTRACT_REQUIRED, TAG_D, TAG_P_REQUIRED, TAG_E_MANY];
+const TRADE_MUTATION_TAGS: &[TagContract] = &[
+ TAG_CONTRACT_REQUIRED,
+ TAG_D,
+ TAG_X_TRADE_MUTATIONS,
+ TAG_P_TRADE_PARTIES,
+];
const TRADE_VALIDATION_RECEIPT_TAGS: &[TagContract] =
&[TAG_E_ROOT, TAG_A_OPTIONAL, TAG_SERVICE_OUTPUT];
const KNOWLEDGE_SOURCE_TAGS: &[TagContract] = &[
@@ -3597,7 +3618,7 @@ static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[
GROUP_STATE_TAGS,
GROUP_REDUCERS
),
- event_contract!(
+ event_contract_with_authoring_policy!(
"radroots.trade.proposal.v1",
KIND_TRADE_PROPOSAL,
"Trade Proposal",
@@ -3606,6 +3627,7 @@ static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[
EventPrivacy::Public,
AuthorRole::Buyer,
ContentSchema::JsonObject,
+ EventAuthoringPolicy::TypedOnly,
EventDiscriminator::ContentJsonFieldEquals {
field: "contract_id",
value: "radroots.trade.proposal.v1",
@@ -3613,7 +3635,7 @@ static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[
TRADE_MUTATION_TAGS,
TRADE_MUTATION_REDUCERS
),
- event_contract!(
+ event_contract_with_authoring_policy!(
"radroots.trade.decision.v1",
KIND_TRADE_DECISION,
"Trade Decision",
@@ -3622,6 +3644,7 @@ static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[
EventPrivacy::Public,
AuthorRole::Seller,
ContentSchema::JsonObject,
+ EventAuthoringPolicy::TypedOnly,
EventDiscriminator::ContentJsonFieldEquals {
field: "contract_id",
value: "radroots.trade.decision.v1",
@@ -3629,7 +3652,7 @@ static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[
TRADE_MUTATION_TAGS,
TRADE_MUTATION_REDUCERS
),
- event_contract!(
+ event_contract_with_authoring_policy!(
"radroots.trade.revision_proposal.v1",
KIND_TRADE_REVISION_PROPOSAL,
"Trade Revision Proposal",
@@ -3638,6 +3661,7 @@ static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[
EventPrivacy::Public,
AuthorRole::Any,
ContentSchema::JsonObject,
+ EventAuthoringPolicy::TypedOnly,
EventDiscriminator::ContentJsonFieldEquals {
field: "contract_id",
value: "radroots.trade.revision_proposal.v1",
@@ -3645,7 +3669,7 @@ static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[
TRADE_MUTATION_TAGS,
TRADE_MUTATION_REDUCERS
),
- event_contract!(
+ event_contract_with_authoring_policy!(
"radroots.trade.revision_decision.v1",
KIND_TRADE_REVISION_DECISION,
"Trade Revision Decision",
@@ -3654,6 +3678,7 @@ static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[
EventPrivacy::Public,
AuthorRole::Any,
ContentSchema::JsonObject,
+ EventAuthoringPolicy::TypedOnly,
EventDiscriminator::ContentJsonFieldEquals {
field: "contract_id",
value: "radroots.trade.revision_decision.v1",
@@ -3661,7 +3686,7 @@ static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[
TRADE_MUTATION_TAGS,
TRADE_MUTATION_REDUCERS
),
- event_contract!(
+ event_contract_with_authoring_policy!(
"radroots.trade.cancellation.v1",
KIND_TRADE_CANCELLATION,
"Trade Cancellation",
@@ -3670,6 +3695,7 @@ static EVENT_CONTRACTS_REGISTRY_V7: &[EventContract] = &[
EventPrivacy::Public,
AuthorRole::Buyer,
ContentSchema::JsonObject,
+ EventAuthoringPolicy::TypedOnly,
EventDiscriminator::ContentJsonFieldEquals {
field: "contract_id",
value: "radroots.trade.cancellation.v1",
@@ -4277,6 +4303,9 @@ fn tag_value_is_valid_in_registry(
TagValueType::Geohash => geohash_is_valid(value),
TagValueType::IanaTimeZoneId => IanaTimeZoneId::parse(value).is_ok(),
TagValueType::Kind => value.parse::<u32>().is_ok(),
+ TagValueType::MutationId => {
+ MutationId::parse(value).is_ok_and(|identifier| identifier.to_hex() == value)
+ }
TagValueType::Nip01Coordinate => Nip01Coordinate::parse(value).is_ok(),
TagValueType::PublicKey => parse_public_key(value).is_ok(),
TagValueType::RelayUrl => relay_url_is_valid(value),
@@ -4362,6 +4391,7 @@ fn tag_value_type_expectation(value_type: TagValueType) -> &'static str {
TagValueType::Geohash => "geohash",
TagValueType::IanaTimeZoneId => "canonical_iana_time_zone_id",
TagValueType::Kind => "kind",
+ TagValueType::MutationId => "lowercase_64_hex_mutation_id",
TagValueType::Nip01Coordinate => "nip01_coordinate",
TagValueType::PublicKey => "public_key",
TagValueType::RelayUrl => "relay_url",
diff --git a/crates/event/src/contract/registry_v7/tests.rs b/crates/event/src/contract/registry_v7/tests.rs
@@ -544,16 +544,33 @@ fn calendar_time_contract_requires_exact_derived_bounded_day_coverage() {
#[test]
fn trade_mutation_contract_requires_exact_contract_tag() {
- let contract = event_contract("radroots.trade.proposal.v1").expect("trade proposal");
- let tag = contract
- .tags
- .iter()
- .find(|tag| tag.name == "contract")
- .expect("contract tag");
-
- assert_eq!(tag.semantic, TagSemantic::Contract);
- assert_eq!(tag.value_type, TagValueType::ContractId);
- assert!(!tag.relay_indexed);
+ for contract_id in [
+ "radroots.trade.proposal.v1",
+ "radroots.trade.decision.v1",
+ "radroots.trade.revision_proposal.v1",
+ "radroots.trade.revision_decision.v1",
+ "radroots.trade.cancellation.v1",
+ ] {
+ let contract = event_contract(contract_id).expect("trade mutation contract");
+ assert_eq!(contract.authoring_policy(), EventAuthoringPolicy::TypedOnly);
+ assert_eq!(
+ contract.tags.iter().map(|tag| tag.name).collect::<Vec<_>>(),
+ ["contract", "d", "x", "p"]
+ );
+ let contract_tag = &contract.tags[0];
+ assert_eq!(contract_tag.semantic, TagSemantic::Contract);
+ assert_eq!(contract_tag.value_type, TagValueType::ContractId);
+ assert!(!contract_tag.relay_indexed);
+ let mutation = &contract.tags[2];
+ assert_eq!(mutation.cardinality, TagCardinality::RequiredMany);
+ assert_eq!(mutation.semantic, TagSemantic::TradeMutation);
+ assert_eq!(mutation.value_type, TagValueType::MutationId);
+ assert!(mutation.relay_indexed);
+ let parties = &contract.tags[3];
+ assert_eq!(parties.cardinality, TagCardinality::RequiredMany);
+ assert_eq!(parties.semantic, TagSemantic::Participant);
+ assert_eq!(parties.value_type, TagValueType::PublicKey);
+ }
}
#[test]
diff --git a/crates/event_codec/src/authoring/typed.rs b/crates/event_codec/src/authoring/typed.rs
@@ -10,8 +10,6 @@ use std::string::String;
use std::vec::Vec;
use core::fmt;
-#[cfg(feature = "json")]
-use radroots_event::profile::AuthoredProfile;
use radroots_event::{
calendar::{AuthoredCalendarDateEvent, AuthoredCalendarTimeEvent},
contract::{ContractIdentityError, ContractKey, EventAuthoringPolicy},
@@ -26,6 +24,8 @@ use radroots_event::{
v1::DEFAULT_CONTENT_MAX_BYTES,
},
};
+#[cfg(feature = "json")]
+use radroots_event::{profile::AuthoredProfile, trade::TradeMutationEnvelopeV1};
use radroots_identity::{Error as PublicKeyError, PublicKey};
#[cfg(feature = "json")]
@@ -54,11 +54,14 @@ use crate::{
},
post::inbound::registry_v7::{RadrootsPostClassification, project_inbound_post_parts},
reply::inbound::registry_v7::{RadrootsNip10ReplyStyle, project_nip10_reply_parts},
+ trade::{
+ RadrootsTradeMutationError, trade_mutation_event_build, validate_trade_mutation_parts,
+ },
};
use super::{AuthoredEventBody, AuthoredEventPlan};
-pub const REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS: [&str; 10] = [
+pub const REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS: [&str; 15] = [
"radroots.profile.metadata.v1",
"radroots.social.update.v1",
"radroots.social.photo_update.v1",
@@ -69,6 +72,11 @@ pub const REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS: [&str; 10] = [
"radroots.calendar.date_event.v1",
"radroots.calendar.time_event.v1",
"radroots.food.availability.v1",
+ "radroots.trade.proposal.v1",
+ "radroots.trade.decision.v1",
+ "radroots.trade.revision_proposal.v1",
+ "radroots.trade.revision_decision.v1",
+ "radroots.trade.cancellation.v1",
];
#[non_exhaustive]
@@ -91,6 +99,8 @@ pub enum AuthoredPlanError {
CanonicalEventId(CanonicalEventIdError),
#[cfg(feature = "json")]
Profile(RadrootsAuthoredProfileEncodeError),
+ #[cfg(feature = "json")]
+ Trade(RadrootsTradeMutationError),
FoodAvailability(RadrootsFoodAvailabilityEncodeError),
Calendar(crate::encode::EventEncodeError),
}
@@ -108,6 +118,8 @@ impl AuthoredPlanError {
Self::CanonicalEventId(_) => "canonical_event_id",
#[cfg(feature = "json")]
Self::Profile(error) => error.code(),
+ #[cfg(feature = "json")]
+ Self::Trade(error) => error.code(),
Self::FoodAvailability(error) => error.code(),
Self::Calendar(error) => error.code(),
}
@@ -139,6 +151,8 @@ impl fmt::Display for AuthoredPlanError {
Self::CanonicalEventId(error) => write!(formatter, "{error}"),
#[cfg(feature = "json")]
Self::Profile(error) => write!(formatter, "{error}"),
+ #[cfg(feature = "json")]
+ Self::Trade(error) => write!(formatter, "{error}"),
Self::FoodAvailability(error) => write!(formatter, "{error}"),
Self::Calendar(error) => write!(formatter, "{error}"),
}
@@ -218,6 +232,15 @@ impl AuthoredEventBody {
let wire = time_to_wire_parts(event).map_err(AuthoredPlanError::Calendar)?;
build_typed_body("radroots.calendar.time_event.v1", wire)
}
+
+ #[cfg(feature = "json")]
+ pub fn from_trade_mutation(
+ envelope: TradeMutationEnvelopeV1,
+ ) -> Result<Self, AuthoredPlanError> {
+ let contract_id = envelope.contract_id.clone();
+ let wire = trade_mutation_event_build(envelope).map_err(AuthoredPlanError::Trade)?;
+ build_typed_body(&contract_id, wire)
+ }
}
impl AuthoredEventPlan {
@@ -364,6 +387,19 @@ impl AuthoredEventPlan {
expected_author,
)
}
+
+ #[cfg(feature = "json")]
+ pub fn from_trade_mutation(
+ envelope: TradeMutationEnvelopeV1,
+ ) -> Result<Self, AuthoredPlanError> {
+ let created_at = envelope.authored_at_unix_s;
+ let expected_author = envelope.author_pubkey.to_hex();
+ Self::bind(
+ AuthoredEventBody::from_trade_mutation(envelope)?,
+ created_at,
+ expected_author,
+ )
+ }
}
fn build_typed_body(
@@ -403,6 +439,7 @@ fn build_typed_body(
pub(super) fn validate_historical_typed_profile(
contract_id: &str,
created_at: u64,
+ expected_author: &str,
kind: u32,
tags: &[Vec<String>],
content: &str,
@@ -423,6 +460,15 @@ pub(super) fn validate_historical_typed_profile(
"radroots.food.availability.v1" => validate_food(created_at, kind, tags, content),
"radroots.calendar.date_event.v1" => validate_calendar_date(kind, tags, content),
"radroots.calendar.time_event.v1" => validate_calendar_time(kind, tags, content),
+ "radroots.trade.proposal.v1"
+ | "radroots.trade.decision.v1"
+ | "radroots.trade.revision_proposal.v1"
+ | "radroots.trade.revision_decision.v1"
+ | "radroots.trade.cancellation.v1" => {
+ validate_trade_mutation_parts(kind, created_at, expected_author, tags, content)
+ .map(|_| ())
+ .map_err(|error| error.code().to_string())
+ }
_ => Err("historical_typed_profile_unavailable".to_string()),
}
}
diff --git a/crates/event_codec/src/authoring/wire.rs b/crates/event_codec/src/authoring/wire.rs
@@ -154,6 +154,7 @@ impl PlanWireV1 {
EventAuthoringPolicy::TypedOnly => validate_historical_typed_profile(
definition.id,
wire.created_at,
+ &wire.expected_author,
wire.kind,
&wire.tags,
&wire.content,
diff --git a/crates/event_codec/src/decode.rs b/crates/event_codec/src/decode.rs
@@ -130,7 +130,10 @@ pub mod reply {
#[cfg(feature = "json")]
pub mod trade {
- pub use crate::trade::{RadrootsTradeMutationParseError, trade_mutation_from_event};
+ pub use crate::trade::{
+ RadrootsTradeMutationError, trade_mutation_from_event, trade_mutation_from_verified_event,
+ validate_trade_mutation_tags,
+ };
}
#[cfg(feature = "json")]
diff --git a/crates/event_codec/src/encode.rs b/crates/event_codec/src/encode.rs
@@ -123,7 +123,10 @@ pub mod reply {
#[cfg(feature = "json")]
pub mod trade {
- pub use crate::trade::{trade_mutation_event_build, trade_mutation_tags};
+ pub use crate::trade::{
+ RadrootsTradeMutationError, trade_mutation_event_build,
+ trade_mutation_event_build_with_extra_tags, trade_mutation_tags,
+ };
}
/// A failure while encoding an event.
diff --git a/crates/event_codec/src/manifest.rs b/crates/event_codec/src/manifest.rs
@@ -432,6 +432,7 @@ fn tag_semantic_label(value: TagSemantic) -> &'static str {
TagSemantic::Title => "title",
TagSemantic::Topic => "topic",
TagSemantic::TimeZone => "time_zone",
+ TagSemantic::TradeMutation => "trade_mutation",
TagSemantic::Url => "url",
TagSemantic::UtcDayCoverage => "utc_day_coverage",
}
@@ -452,6 +453,7 @@ fn tag_value_type_label(value: TagValueType) -> &'static str {
TagValueType::Geohash => "geohash",
TagValueType::IanaTimeZoneId => "iana_time_zone_id",
TagValueType::Kind => "kind",
+ TagValueType::MutationId => "mutation_id",
TagValueType::Nip01Coordinate => "nip01_coordinate",
TagValueType::PublicKey => "public_key",
TagValueType::RelayUrl => "relay_url",
diff --git a/crates/event_codec/src/manifest/registry_v7.rs b/crates/event_codec/src/manifest/registry_v7.rs
@@ -266,6 +266,7 @@ pub enum RadrootsEventContractRegistryV7TagSemantic {
Title,
Topic,
TimeZone,
+ TradeMutation,
Url,
UtcDayCoverage,
}
@@ -286,6 +287,7 @@ pub enum RadrootsEventContractRegistryV7TagValueType {
Geohash,
IanaTimeZoneId,
Kind,
+ MutationId,
Nip01Coordinate,
PublicKey,
RelayUrl,
@@ -752,6 +754,7 @@ fn tag_semantic_inventory(value: TagSemantic) -> RadrootsEventContractRegistryV7
TagSemantic::Title => RadrootsEventContractRegistryV7TagSemantic::Title,
TagSemantic::Topic => RadrootsEventContractRegistryV7TagSemantic::Topic,
TagSemantic::TimeZone => RadrootsEventContractRegistryV7TagSemantic::TimeZone,
+ TagSemantic::TradeMutation => RadrootsEventContractRegistryV7TagSemantic::TradeMutation,
TagSemantic::Url => RadrootsEventContractRegistryV7TagSemantic::Url,
TagSemantic::UtcDayCoverage => RadrootsEventContractRegistryV7TagSemantic::UtcDayCoverage,
}
@@ -780,6 +783,7 @@ fn tag_value_type_inventory(value: TagValueType) -> RadrootsEventContractRegistr
TagValueType::Geohash => RadrootsEventContractRegistryV7TagValueType::Geohash,
TagValueType::IanaTimeZoneId => RadrootsEventContractRegistryV7TagValueType::IanaTimeZoneId,
TagValueType::Kind => RadrootsEventContractRegistryV7TagValueType::Kind,
+ TagValueType::MutationId => RadrootsEventContractRegistryV7TagValueType::MutationId,
TagValueType::Nip01Coordinate => {
RadrootsEventContractRegistryV7TagValueType::Nip01Coordinate
}
diff --git a/crates/event_codec/src/trade/mod.rs b/crates/event_codec/src/trade/mod.rs
@@ -6,69 +6,144 @@ use alloc::{
};
#[cfg(feature = "json")]
+use crate::verification::RadrootsSignatureVerifiedEvent;
+#[cfg(feature = "json")]
use radroots_event::{
envelope::EventEnvelope,
envelope::kind::is_trade_mutation_event_kind,
- id::MutationId,
- tag::name::{TAG_D, TAG_E},
+ id::{MutationId, TradeId},
trade::{
- TradeMutationEnvelopeV1, TradeProtocolError, canonical_trade_mutation_content,
+ TradeMutationEnvelopeV1, TradeMutationKindV1, canonical_trade_mutation_content,
trade_mutation_from_canonical_content,
},
wire::Nip01EventWireParts,
};
+#[cfg(feature = "json")]
+use radroots_identity::PublicKey;
#[cfg(feature = "json")]
-use crate::error::EventEncodeError;
+const MAX_TRADE_MUTATION_TAGS: usize = 10;
#[cfg(feature = "json")]
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum RadrootsTradeMutationParseError {
- InvalidKind(u32),
- MissingTag(&'static str),
- InvalidTag(&'static str),
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum RadrootsTradeMutationError {
+ CallerStructuralTagForbidden,
+ DuplicateTradeTag,
+ LegacyParentEventTag,
+ MissingParentTag,
+ MissingMutationTag,
+ MissingRootTag,
+ NoncanonicalParentOrder,
+ PartyTagOrderMismatch,
+ UnexpectedParentTag,
+ UnexpectedRootTag,
+ InvalidKind,
+ AuthorMismatch,
+ AuthoredAtMismatch,
+ CanonicalContentMismatch,
+ InvalidIdentifier,
+ InvalidTagShape,
+ UnexpectedTag,
ContractTagMismatch,
- TradeIdTagMismatch,
- CounterpartyTagMismatch,
- ParentTagsMismatch,
- KindContractMismatch,
- Canonical(TradeProtocolError),
+ TradeTagMismatch,
+ MutationTagMismatch,
+ RootTagMismatch,
+ ParentTagMismatch,
}
#[cfg(feature = "json")]
-impl core::fmt::Display for RadrootsTradeMutationParseError {
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+impl RadrootsTradeMutationError {
+ #[must_use]
+ pub const fn code(self) -> &'static str {
match self {
- Self::InvalidKind(kind) => write!(f, "invalid trade mutation kind: {kind}"),
- Self::MissingTag(tag) => write!(f, "missing trade mutation tag: {tag}"),
- Self::InvalidTag(tag) => write!(f, "invalid trade mutation tag: {tag}"),
- Self::ContractTagMismatch => write!(f, "trade mutation contract tag mismatch"),
- Self::TradeIdTagMismatch => write!(f, "trade mutation trade id tag mismatch"),
- Self::CounterpartyTagMismatch => write!(f, "trade mutation counterparty tag mismatch"),
- Self::ParentTagsMismatch => write!(f, "trade mutation parent tags mismatch"),
- Self::KindContractMismatch => write!(f, "trade mutation kind contract mismatch"),
- Self::Canonical(error) => write!(f, "{error}"),
+ Self::CallerStructuralTagForbidden => "caller_structural_tag_forbidden",
+ Self::DuplicateTradeTag => "duplicate_trade_tag",
+ Self::LegacyParentEventTag => "legacy_parent_event_tag",
+ Self::MissingParentTag => "missing_parent_tag",
+ Self::MissingMutationTag => "missing_mutation_tag",
+ Self::MissingRootTag => "missing_root_tag",
+ Self::NoncanonicalParentOrder => "noncanonical_parent_order",
+ Self::PartyTagOrderMismatch => "party_tag_order_mismatch",
+ Self::UnexpectedParentTag => "unexpected_parent_tag",
+ Self::UnexpectedRootTag => "unexpected_root_tag",
+ Self::InvalidKind => "invalid_kind",
+ Self::AuthorMismatch => "author_mismatch",
+ Self::AuthoredAtMismatch => "authored_at_mismatch",
+ Self::CanonicalContentMismatch => "canonical_content_mismatch",
+ Self::InvalidIdentifier => "invalid_identifier",
+ Self::InvalidTagShape => "invalid_tag_shape",
+ Self::UnexpectedTag => "unexpected_tag",
+ Self::ContractTagMismatch => "contract_tag_mismatch",
+ Self::TradeTagMismatch => "trade_tag_mismatch",
+ Self::MutationTagMismatch => "mutation_tag_mismatch",
+ Self::RootTagMismatch => "root_tag_mismatch",
+ Self::ParentTagMismatch => "parent_tag_mismatch",
}
}
}
-#[cfg(all(feature = "std", feature = "json"))]
-impl std::error::Error for RadrootsTradeMutationParseError {}
-
#[cfg(feature = "json")]
-impl From<TradeProtocolError> for RadrootsTradeMutationParseError {
- fn from(value: TradeProtocolError) -> Self {
- Self::Canonical(value)
+impl core::fmt::Display for RadrootsTradeMutationError {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.write_str(match self {
+ Self::CallerStructuralTagForbidden => "caller supplied a governed trade tag",
+ Self::DuplicateTradeTag => "trade tag is duplicated",
+ Self::LegacyParentEventTag => "legacy trade parent tag is forbidden",
+ Self::MissingParentTag => "trade parent tag is missing",
+ Self::MissingMutationTag => "trade mutation tag is missing",
+ Self::MissingRootTag => "trade root tag is missing",
+ Self::NoncanonicalParentOrder => "trade parent tags are not canonical",
+ Self::PartyTagOrderMismatch => "trade party tags are not canonical",
+ Self::UnexpectedParentTag => "trade parent tag is unexpected",
+ Self::UnexpectedRootTag => "trade root tag is unexpected",
+ Self::InvalidKind => "trade mutation kind is invalid",
+ Self::AuthorMismatch => "trade mutation author does not match content",
+ Self::AuthoredAtMismatch => "trade mutation timestamp does not match content",
+ Self::CanonicalContentMismatch => "trade mutation content is not canonical",
+ Self::InvalidIdentifier => "trade mutation identifier is invalid",
+ Self::InvalidTagShape => "trade mutation tag shape is invalid",
+ Self::UnexpectedTag => "trade mutation tag is not permitted",
+ Self::ContractTagMismatch => "trade contract tag does not match content",
+ Self::TradeTagMismatch => "trade identifier tag does not match content",
+ Self::MutationTagMismatch => "trade mutation tag does not match content",
+ Self::RootTagMismatch => "trade root tag does not match content",
+ Self::ParentTagMismatch => "trade parent tag does not match content",
+ })
}
}
+#[cfg(all(feature = "std", feature = "json"))]
+impl std::error::Error for RadrootsTradeMutationError {}
+
#[cfg(feature = "json")]
pub fn trade_mutation_event_build(
envelope: TradeMutationEnvelopeV1,
-) -> Result<Nip01EventWireParts, EventEncodeError> {
+) -> Result<Nip01EventWireParts, RadrootsTradeMutationError> {
+ trade_mutation_event_build_with_extra_tags(envelope, &[])
+}
+
+#[cfg(feature = "json")]
+pub fn trade_mutation_event_build_with_extra_tags(
+ envelope: TradeMutationEnvelopeV1,
+ extra_tags: &[Vec<String>],
+) -> Result<Nip01EventWireParts, RadrootsTradeMutationError> {
+ if extra_tags.iter().any(|tag| {
+ matches!(
+ tag.first().map(String::as_str),
+ Some("contract" | "d" | "x" | "p" | "e")
+ )
+ }) {
+ return Err(RadrootsTradeMutationError::CallerStructuralTagForbidden);
+ }
+ if let Some(tag) = extra_tags.first() {
+ if tag.is_empty() {
+ return Err(RadrootsTradeMutationError::InvalidTagShape);
+ }
+ return Err(RadrootsTradeMutationError::UnexpectedTag);
+ }
let canonical = canonical_trade_mutation_content(envelope)
- .map_err(map_trade_protocol_error_to_encode_error)?;
- let tags = trade_mutation_tags(&canonical.envelope)?;
+ .map_err(|_| RadrootsTradeMutationError::CanonicalContentMismatch)?;
+ let tags = canonical_trade_mutation_tags(&canonical.envelope)?;
Ok(Nip01EventWireParts {
kind: canonical.envelope.mutation_kind().nostr_kind(),
content: canonical.content,
@@ -77,134 +152,314 @@ pub fn trade_mutation_event_build(
}
#[cfg(feature = "json")]
+/// Structurally parses and validates a trade-mutation event.
+///
+/// This boundary binds the event kind, declared author, timestamp, canonical
+/// content, and ordered tags. It does not verify the event signature; callers
+/// that require cryptographic verification must use
+/// [`trade_mutation_from_verified_event`].
pub fn trade_mutation_from_event(
event: &EventEnvelope,
-) -> Result<TradeMutationEnvelopeV1, RadrootsTradeMutationParseError> {
- if !is_trade_mutation_event_kind(event.kind_u32()) {
- return Err(RadrootsTradeMutationParseError::InvalidKind(
- event.kind_u32(),
- ));
+) -> Result<TradeMutationEnvelopeV1, RadrootsTradeMutationError> {
+ validate_trade_mutation_parts(
+ event.kind_u32(),
+ event.created_at_u64(),
+ &event.author().to_hex(),
+ &event.tags_as_vec(),
+ event.content(),
+ )
+}
+
+#[cfg(feature = "json")]
+pub(crate) fn validate_trade_mutation_parts(
+ kind: u32,
+ authored_at: u64,
+ author: &str,
+ tags: &[Vec<String>],
+ content: &str,
+) -> Result<TradeMutationEnvelopeV1, RadrootsTradeMutationError> {
+ if !is_trade_mutation_event_kind(kind) {
+ return Err(RadrootsTradeMutationError::InvalidKind);
+ }
+ let envelope = trade_mutation_from_canonical_content(content)
+ .map_err(|_| RadrootsTradeMutationError::CanonicalContentMismatch)?;
+ if envelope.mutation_id.is_none() {
+ return Err(RadrootsTradeMutationError::CanonicalContentMismatch);
}
- let envelope = trade_mutation_from_canonical_content(event.content())?;
- if envelope.mutation_kind().nostr_kind() != event.kind_u32() {
- return Err(RadrootsTradeMutationParseError::KindContractMismatch);
+ if envelope.mutation_kind().nostr_kind() != kind {
+ return Err(RadrootsTradeMutationError::InvalidKind);
}
- validate_trade_mutation_tags(&envelope, &event.tags_as_vec())?;
+ if canonical_public_key(author)? != envelope.author_pubkey.to_hex() {
+ return Err(RadrootsTradeMutationError::AuthorMismatch);
+ }
+ if envelope.authored_at_unix_s != authored_at {
+ return Err(RadrootsTradeMutationError::AuthoredAtMismatch);
+ }
+ validate_trade_mutation_tags(&envelope, tags)?;
Ok(envelope)
}
#[cfg(feature = "json")]
+/// Validates a trade mutation whose NIP-01 signature has already been verified.
+pub fn trade_mutation_from_verified_event(
+ event: &RadrootsSignatureVerifiedEvent,
+) -> Result<TradeMutationEnvelopeV1, RadrootsTradeMutationError> {
+ trade_mutation_from_event(event.event())
+}
+
+#[cfg(feature = "json")]
pub fn trade_mutation_tags(
envelope: &TradeMutationEnvelopeV1,
-) -> Result<Vec<Vec<String>>, EventEncodeError> {
- let mut tags = Vec::with_capacity(3 + envelope.parent_mutation_ids.len());
- push_tag(&mut tags, "contract", envelope.contract_id.clone())?;
- push_tag(&mut tags, TAG_D, envelope.trade_id.to_string())?;
- push_tag(&mut tags, "p", envelope.counterparty_pubkey.to_string())?;
- for parent in &envelope.parent_mutation_ids {
- push_tag(&mut tags, TAG_E, parent.to_string())?;
- }
- Ok(tags)
+) -> Result<Vec<Vec<String>>, RadrootsTradeMutationError> {
+ let canonical = canonical_trade_mutation_content(envelope.clone())
+ .map_err(|_| RadrootsTradeMutationError::CanonicalContentMismatch)?;
+ canonical_trade_mutation_tags(&canonical.envelope)
}
#[cfg(feature = "json")]
-fn validate_trade_mutation_tags(
+pub fn validate_trade_mutation_tags(
envelope: &TradeMutationEnvelopeV1,
tags: &[Vec<String>],
-) -> Result<(), RadrootsTradeMutationParseError> {
- let contract = required_tag_value(tags, "contract")?;
+) -> Result<(), RadrootsTradeMutationError> {
+ if tags.len() > MAX_TRADE_MUTATION_TAGS {
+ return Err(RadrootsTradeMutationError::InvalidTagShape);
+ }
+ let proposal = envelope.mutation_kind() == TradeMutationKindV1::Proposal;
+ if tags
+ .iter()
+ .any(|tag| tag.first().map(String::as_str) == Some("e"))
+ {
+ return Err(RadrootsTradeMutationError::LegacyParentEventTag);
+ }
+ let trade_count = count_named(tags, "d");
+ if trade_count > 1 {
+ return Err(RadrootsTradeMutationError::DuplicateTradeTag);
+ }
+ let mutation_count = count_marked(tags, "mutation");
+ let root_count = count_marked(tags, "root");
+ let parent_count = count_marked(tags, "parent");
+ if count_named(tags, "x") != mutation_count + root_count + parent_count {
+ return Err(RadrootsTradeMutationError::InvalidTagShape);
+ }
+ if mutation_count == 0 {
+ return Err(RadrootsTradeMutationError::MissingMutationTag);
+ }
+ if mutation_count != 1 {
+ return Err(RadrootsTradeMutationError::InvalidTagShape);
+ }
+ if proposal {
+ if root_count != 0 {
+ return Err(RadrootsTradeMutationError::UnexpectedRootTag);
+ }
+ if parent_count != 0 {
+ return Err(RadrootsTradeMutationError::UnexpectedParentTag);
+ }
+ } else {
+ if root_count == 0 {
+ return Err(RadrootsTradeMutationError::MissingRootTag);
+ }
+ if root_count != 1 {
+ return Err(RadrootsTradeMutationError::InvalidTagShape);
+ }
+ if parent_count == 0 {
+ return Err(RadrootsTradeMutationError::MissingParentTag);
+ }
+ if parent_count > 4 {
+ return Err(RadrootsTradeMutationError::InvalidTagShape);
+ }
+ }
+ if trade_count == 0 || count_named(tags, "contract") != 1 || count_named(tags, "p") != 2 {
+ return Err(RadrootsTradeMutationError::InvalidTagShape);
+ }
+ if tags.iter().any(|tag| {
+ !matches!(
+ tag.first().map(String::as_str),
+ Some("contract" | "d" | "x" | "p")
+ )
+ }) {
+ return Err(RadrootsTradeMutationError::UnexpectedTag);
+ }
+
+ let contract = exact_unmarked(tags.first(), "contract")?;
if contract != envelope.contract_id {
- return Err(RadrootsTradeMutationParseError::ContractTagMismatch);
+ return Err(RadrootsTradeMutationError::ContractTagMismatch);
}
- let trade_id = required_tag_value(tags, TAG_D)?;
- if trade_id != envelope.trade_id.to_hex() {
- return Err(RadrootsTradeMutationParseError::TradeIdTagMismatch);
+ let trade = exact_unmarked(tags.get(1), "d")?;
+ if canonical_trade_id(trade)? != envelope.trade_id.to_hex() {
+ return Err(RadrootsTradeMutationError::TradeTagMismatch);
}
- let counterparty = required_tag_value(tags, "p")?;
- if counterparty != envelope.counterparty_pubkey.to_hex() {
- return Err(RadrootsTradeMutationParseError::CounterpartyTagMismatch);
+ let mutation = exact_marked(tags.get(2), "mutation")?;
+ if canonical_mutation_id(mutation)?
+ != envelope
+ .mutation_id
+ .as_ref()
+ .ok_or(RadrootsTradeMutationError::CanonicalContentMismatch)?
+ .to_hex()
+ {
+ return Err(RadrootsTradeMutationError::MutationTagMismatch);
}
- let mut parents = Vec::new();
- for tag in tags
- .iter()
- .filter(|tag| tag.first().map(String::as_str) == Some(TAG_E))
+
+ let mut cursor = 3;
+ if !proposal {
+ let root = exact_marked(tags.get(cursor), "root")?;
+ if canonical_mutation_id(root)?
+ != envelope
+ .root_mutation_id
+ .as_ref()
+ .ok_or(RadrootsTradeMutationError::MissingRootTag)?
+ .to_hex()
+ {
+ return Err(RadrootsTradeMutationError::RootTagMismatch);
+ }
+ cursor += 1;
+ }
+ let mut parsed_parents = Vec::with_capacity(parent_count);
+ for tag in tags.iter().skip(cursor).take(parent_count) {
+ let parent = canonical_mutation_id(exact_marked(Some(tag), "parent")?)?;
+ parsed_parents.push(
+ MutationId::parse(parent).map_err(|_| RadrootsTradeMutationError::InvalidIdentifier)?,
+ );
+ }
+ if parsed_parents.windows(2).any(|pair| pair[0] >= pair[1]) {
+ return Err(RadrootsTradeMutationError::NoncanonicalParentOrder);
+ }
+ if parsed_parents != envelope.parent_mutation_ids {
+ return Err(RadrootsTradeMutationError::ParentTagMismatch);
+ }
+ cursor += parent_count;
+ let buyer = exact_unmarked(tags.get(cursor), "p")?;
+ let seller = exact_unmarked(tags.get(cursor + 1), "p")?;
+ if canonical_public_key(buyer)? != envelope.buyer_pubkey.to_hex()
+ || canonical_public_key(seller)? != envelope.seller_pubkey.to_hex()
{
- let value = tag
- .get(1)
- .map(String::as_str)
- .ok_or(RadrootsTradeMutationParseError::InvalidTag(TAG_E))?;
- let parent = MutationId::parse(value)
- .map_err(|_| RadrootsTradeMutationParseError::InvalidTag(TAG_E))?;
- parents.push(parent);
+ return Err(RadrootsTradeMutationError::PartyTagOrderMismatch);
}
- if parents != envelope.parent_mutation_ids {
- return Err(RadrootsTradeMutationParseError::ParentTagsMismatch);
+ if cursor + 2 != tags.len() {
+ return Err(RadrootsTradeMutationError::InvalidTagShape);
}
- Ok(())
+ validate_party_binding(envelope)
}
#[cfg(feature = "json")]
-fn required_tag_value<'a>(
- tags: &'a [Vec<String>],
- name: &'static str,
-) -> Result<&'a str, RadrootsTradeMutationParseError> {
- let tag = tags
- .iter()
- .find(|tag| tag.first().map(String::as_str) == Some(name))
- .ok_or(RadrootsTradeMutationParseError::MissingTag(name))?;
- let value = tag
- .get(1)
- .map(String::as_str)
- .ok_or(RadrootsTradeMutationParseError::InvalidTag(name))?;
- if value.trim().is_empty() {
- return Err(RadrootsTradeMutationParseError::InvalidTag(name));
- }
- Ok(value)
+fn canonical_trade_mutation_tags(
+ envelope: &TradeMutationEnvelopeV1,
+) -> Result<Vec<Vec<String>>, RadrootsTradeMutationError> {
+ validate_party_binding(envelope)?;
+ let mutation = envelope
+ .mutation_id
+ .as_ref()
+ .ok_or(RadrootsTradeMutationError::CanonicalContentMismatch)?;
+ let mut tags = Vec::with_capacity(5 + envelope.parent_mutation_ids.len());
+ tags.push(vec!["contract".to_string(), envelope.contract_id.clone()]);
+ tags.push(vec!["d".to_string(), envelope.trade_id.to_hex()]);
+ tags.push(vec![
+ "x".to_string(),
+ mutation.to_hex(),
+ "mutation".to_string(),
+ ]);
+ if let Some(root) = &envelope.root_mutation_id {
+ tags.push(vec!["x".to_string(), root.to_hex(), "root".to_string()]);
+ }
+ for parent in &envelope.parent_mutation_ids {
+ tags.push(vec!["x".to_string(), parent.to_hex(), "parent".to_string()]);
+ }
+ tags.push(vec!["p".to_string(), envelope.buyer_pubkey.to_hex()]);
+ tags.push(vec!["p".to_string(), envelope.seller_pubkey.to_hex()]);
+ Ok(tags)
}
#[cfg(feature = "json")]
-fn push_tag(
- tags: &mut Vec<Vec<String>>,
- name: &'static str,
- value: String,
-) -> Result<(), EventEncodeError> {
- if value.trim().is_empty() {
- return Err(EventEncodeError::EmptyRequiredField(name));
- }
- tags.push(vec![name.to_string(), value]);
+fn validate_party_binding(
+ envelope: &TradeMutationEnvelopeV1,
+) -> Result<(), RadrootsTradeMutationError> {
+ let expected_counterparty = if envelope.author_pubkey == envelope.buyer_pubkey {
+ &envelope.seller_pubkey
+ } else if envelope.author_pubkey == envelope.seller_pubkey {
+ &envelope.buyer_pubkey
+ } else {
+ return Err(RadrootsTradeMutationError::AuthorMismatch);
+ };
+ if &envelope.counterparty_pubkey != expected_counterparty {
+ return Err(RadrootsTradeMutationError::AuthorMismatch);
+ }
Ok(())
}
#[cfg(feature = "json")]
-fn map_trade_protocol_error_to_encode_error(error: TradeProtocolError) -> EventEncodeError {
- match error {
- TradeProtocolError::EmptyField(field) => EventEncodeError::EmptyRequiredField(field),
- TradeProtocolError::ContractMismatch { .. }
- | TradeProtocolError::InvalidField(_)
- | TradeProtocolError::InvalidIdentifier { .. }
- | TradeProtocolError::InvalidInitialParents
- | TradeProtocolError::MissingParentMutation
- | TradeProtocolError::TooManyParents { .. }
- | TradeProtocolError::UnsortedParents
- | TradeProtocolError::DuplicateParent
- | TradeProtocolError::SelfParent
- | TradeProtocolError::MissingLines
- | TradeProtocolError::TooManyLines { .. }
- | TradeProtocolError::TooManyAdjustments { .. }
- | TradeProtocolError::UnsupportedNumber
- | TradeProtocolError::ContentTooLarge { .. }
- | TradeProtocolError::InvalidTimeRange
- | TradeProtocolError::MissingReservationCommitments
- | TradeProtocolError::MissingCancellationTarget
- | TradeProtocolError::CandidateIdMismatch { .. }
- | TradeProtocolError::MutationIdMismatch { .. }
- | TradeProtocolError::InvalidSchemaVersion { .. } => {
- EventEncodeError::InvalidField("trade_mutation")
- }
- TradeProtocolError::DuplicateKey(_)
- | TradeProtocolError::InvalidJson(_)
- | TradeProtocolError::NonCanonicalJson => EventEncodeError::Json,
+fn count_named(tags: &[Vec<String>], name: &str) -> usize {
+ tags.iter()
+ .filter(|tag| tag.first().map(String::as_str) == Some(name))
+ .count()
+}
+
+#[cfg(feature = "json")]
+fn count_marked(tags: &[Vec<String>], marker: &str) -> usize {
+ tags.iter()
+ .filter(|tag| {
+ tag.first().map(String::as_str) == Some("x")
+ && tag.get(2).map(String::as_str) == Some(marker)
+ })
+ .count()
+}
+
+#[cfg(feature = "json")]
+fn exact_unmarked<'a>(
+ tag: Option<&'a Vec<String>>,
+ name: &str,
+) -> Result<&'a str, RadrootsTradeMutationError> {
+ let tag = tag.ok_or(RadrootsTradeMutationError::InvalidTagShape)?;
+ if tag.len() != 2 || tag.first().map(String::as_str) != Some(name) {
+ return Err(RadrootsTradeMutationError::InvalidTagShape);
+ }
+ Ok(&tag[1])
+}
+
+#[cfg(feature = "json")]
+fn exact_marked<'a>(
+ tag: Option<&'a Vec<String>>,
+ marker: &str,
+) -> Result<&'a str, RadrootsTradeMutationError> {
+ let tag = tag.ok_or(RadrootsTradeMutationError::InvalidTagShape)?;
+ if tag.len() != 3
+ || tag.first().map(String::as_str) != Some("x")
+ || tag.get(2).map(String::as_str) != Some(marker)
+ {
+ return Err(RadrootsTradeMutationError::InvalidTagShape);
+ }
+ Ok(&tag[1])
+}
+
+#[cfg(feature = "json")]
+fn canonical_trade_id(value: &str) -> Result<String, RadrootsTradeMutationError> {
+ let parsed =
+ TradeId::parse(value).map_err(|_| RadrootsTradeMutationError::InvalidIdentifier)?;
+ let canonical = parsed.to_hex();
+ if canonical != value {
+ return Err(RadrootsTradeMutationError::InvalidIdentifier);
+ }
+ Ok(canonical)
+}
+
+#[cfg(feature = "json")]
+fn canonical_mutation_id(value: &str) -> Result<String, RadrootsTradeMutationError> {
+ let parsed =
+ MutationId::parse(value).map_err(|_| RadrootsTradeMutationError::InvalidIdentifier)?;
+ let canonical = parsed.to_hex();
+ if canonical != value {
+ return Err(RadrootsTradeMutationError::InvalidIdentifier);
+ }
+ Ok(canonical)
+}
+
+#[cfg(feature = "json")]
+fn canonical_public_key(value: &str) -> Result<String, RadrootsTradeMutationError> {
+ let parsed =
+ PublicKey::from_hex(value).map_err(|_| RadrootsTradeMutationError::InvalidIdentifier)?;
+ let canonical = parsed.to_hex();
+ if canonical != value {
+ return Err(RadrootsTradeMutationError::InvalidIdentifier);
}
+ Ok(canonical)
}
#[cfg(all(test, feature = "json"))]
@@ -355,194 +610,175 @@ mod tests {
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn trade_mutation_codec_rejects_all_invalid_wire_shapes() {
- let parse_errors = [
- RadrootsTradeMutationParseError::InvalidKind(1),
- RadrootsTradeMutationParseError::MissingTag("contract"),
- RadrootsTradeMutationParseError::InvalidTag("contract"),
- RadrootsTradeMutationParseError::ContractTagMismatch,
- RadrootsTradeMutationParseError::TradeIdTagMismatch,
- RadrootsTradeMutationParseError::CounterpartyTagMismatch,
- RadrootsTradeMutationParseError::ParentTagsMismatch,
- RadrootsTradeMutationParseError::KindContractMismatch,
- RadrootsTradeMutationParseError::Canonical(TradeProtocolError::MissingLines),
- ];
- for error in parse_errors {
- assert!(!error.to_string().is_empty());
- }
- let canonical_error =
- RadrootsTradeMutationParseError::from(TradeProtocolError::MissingLines);
- assert!(matches!(
- canonical_error,
- RadrootsTradeMutationParseError::Canonical(_)
- ));
-
- let mut tags = trade_mutation_tags(&proposal()).unwrap();
+ let canonical = canonical_trade_mutation_content(proposal())
+ .unwrap()
+ .envelope;
+ let mut tags = trade_mutation_tags(&canonical).unwrap();
*tags
.iter_mut()
.find(|tag| tag.first().map(String::as_str) == Some("contract"))
.unwrap() = vec!["contract".into(), "wrong-contract".into()];
assert_eq!(
- validate_trade_mutation_tags(&proposal(), &tags).unwrap_err(),
- RadrootsTradeMutationParseError::ContractTagMismatch
+ validate_trade_mutation_tags(&canonical, &tags).unwrap_err(),
+ RadrootsTradeMutationError::ContractTagMismatch
);
- let mut tags = trade_mutation_tags(&proposal()).unwrap();
+ let mut tags = trade_mutation_tags(&canonical).unwrap();
*tags
.iter_mut()
- .find(|tag| tag.first().map(String::as_str) == Some(TAG_D))
- .unwrap() = vec![TAG_D.into(), "other-trade".into()];
+ .find(|tag| tag.first().map(String::as_str) == Some("d"))
+ .unwrap() = vec!["d".into(), hex_32('9')];
assert_eq!(
- validate_trade_mutation_tags(&proposal(), &tags).unwrap_err(),
- RadrootsTradeMutationParseError::TradeIdTagMismatch
+ validate_trade_mutation_tags(&canonical, &tags).unwrap_err(),
+ RadrootsTradeMutationError::TradeTagMismatch
);
- let mut tags = trade_mutation_tags(&proposal()).unwrap();
- *tags
- .iter_mut()
- .find(|tag| tag.first().map(String::as_str) == Some("p"))
- .unwrap() = vec!["p".into(), pubkey('c').to_hex()];
+ let mut tags = trade_mutation_tags(&canonical).unwrap();
+ let party = tags.len() - 2;
+ tags.swap(party, party + 1);
assert_eq!(
- validate_trade_mutation_tags(&proposal(), &tags).unwrap_err(),
- RadrootsTradeMutationParseError::CounterpartyTagMismatch
+ validate_trade_mutation_tags(&canonical, &tags).unwrap_err(),
+ RadrootsTradeMutationError::PartyTagOrderMismatch
);
- let mut missing_parent_value = trade_mutation_tags(&proposal()).unwrap();
- missing_parent_value.push(vec![TAG_E.into()]);
+ let mut legacy = trade_mutation_tags(&canonical).unwrap();
+ legacy.push(vec!["e".into(), hex_64('9')]);
assert_eq!(
- validate_trade_mutation_tags(&proposal(), &missing_parent_value).unwrap_err(),
- RadrootsTradeMutationParseError::InvalidTag(TAG_E)
+ validate_trade_mutation_tags(&canonical, &legacy).unwrap_err(),
+ RadrootsTradeMutationError::LegacyParentEventTag
);
- let mut invalid_parent = trade_mutation_tags(&proposal()).unwrap();
- invalid_parent.push(vec![TAG_E.into(), "not-an-event-id".into()]);
+ let mut missing_mutation = trade_mutation_tags(&canonical).unwrap();
+ missing_mutation.remove(2);
assert_eq!(
- validate_trade_mutation_tags(&proposal(), &invalid_parent).unwrap_err(),
- RadrootsTradeMutationParseError::InvalidTag(TAG_E)
+ validate_trade_mutation_tags(&canonical, &missing_mutation).unwrap_err(),
+ RadrootsTradeMutationError::MissingMutationTag
);
- let parent = MutationId::parse(hex_64('9')).unwrap();
- let mut parent_envelope = proposal();
- parent_envelope.parent_mutation_ids.push(parent);
- let parent_tags = trade_mutation_tags(&parent_envelope).unwrap();
- validate_trade_mutation_tags(&parent_envelope, &parent_tags).unwrap();
+ let mut duplicate_trade = trade_mutation_tags(&canonical).unwrap();
+ duplicate_trade.push(vec!["d".into(), hex_32('9')]);
+ assert_eq!(
+ validate_trade_mutation_tags(&canonical, &duplicate_trade).unwrap_err(),
+ RadrootsTradeMutationError::DuplicateTradeTag
+ );
- let mut mismatched_parent = trade_mutation_tags(&proposal()).unwrap();
- mismatched_parent.push(vec![TAG_E.into(), parent.to_string()]);
assert_eq!(
- validate_trade_mutation_tags(&proposal(), &mismatched_parent).unwrap_err(),
- RadrootsTradeMutationParseError::ParentTagsMismatch
+ trade_mutation_event_build_with_extra_tags(
+ proposal(),
+ &[vec!["d".into(), hex_32('9')]],
+ )
+ .unwrap_err(),
+ RadrootsTradeMutationError::CallerStructuralTagForbidden
);
+ }
+ #[test]
+ fn trade_event_parser_binds_kind_author_time_content_and_markers() {
+ let built = trade_mutation_event_build(proposal()).expect("trade event");
+ let event = |author: String, created_at, kind, tags: Vec<Vec<String>>, content: String| {
+ EventEnvelope::new(EventEnvelopeParts {
+ id: hex_64('e'),
+ author,
+ created_at,
+ kind,
+ tags,
+ content,
+ sig: core::iter::repeat_n('f', 128).collect(),
+ })
+ .expect("structural envelope")
+ };
assert_eq!(
- required_tag_value(&[], "contract").unwrap_err(),
- RadrootsTradeMutationParseError::MissingTag("contract")
+ trade_mutation_from_event(&event(
+ pubkey('a').to_hex(),
+ 1_799_000_000,
+ 1,
+ built.tags.clone(),
+ built.content.clone(),
+ ))
+ .unwrap_err(),
+ RadrootsTradeMutationError::InvalidKind
);
assert_eq!(
- required_tag_value(&[vec!["contract".into()]], "contract").unwrap_err(),
- RadrootsTradeMutationParseError::InvalidTag("contract")
+ trade_mutation_from_event(&event(
+ pubkey('c').to_hex(),
+ 1_799_000_000,
+ built.kind,
+ built.tags.clone(),
+ built.content.clone(),
+ ))
+ .unwrap_err(),
+ RadrootsTradeMutationError::AuthorMismatch
);
assert_eq!(
- required_tag_value(&[vec!["contract".into(), " ".into()]], "contract",).unwrap_err(),
- RadrootsTradeMutationParseError::InvalidTag("contract")
+ trade_mutation_from_event(&event(
+ pubkey('a').to_hex(),
+ 1_799_000_001,
+ built.kind,
+ built.tags.clone(),
+ built.content.clone(),
+ ))
+ .unwrap_err(),
+ RadrootsTradeMutationError::AuthoredAtMismatch
);
- assert!(matches!(
- push_tag(&mut Vec::new(), "contract", " ".into()).unwrap_err(),
- EventEncodeError::EmptyRequiredField("contract")
- ));
-
- let built = trade_mutation_event_build(proposal()).unwrap();
- let invalid_kind = EventEnvelope::new(EventEnvelopeParts {
- id: hex_64('e'),
- author: pubkey('a').to_hex(),
- created_at: 1_799_000_000,
- kind: 1,
- tags: built.tags.clone(),
- content: built.content.clone(),
- sig: core::iter::repeat_n('f', 128).collect(),
- })
- .unwrap();
assert_eq!(
- trade_mutation_from_event(&invalid_kind).unwrap_err(),
- RadrootsTradeMutationParseError::InvalidKind(1)
+ trade_mutation_from_event(&event(
+ pubkey('a').to_hex(),
+ 1_799_000_000,
+ built.kind,
+ built.tags.clone(),
+ "{}".to_owned(),
+ ))
+ .unwrap_err(),
+ RadrootsTradeMutationError::CanonicalContentMismatch
);
-
- let kind_contract_mismatch = EventEnvelope::new(EventEnvelopeParts {
- id: hex_64('e'),
- author: pubkey('a').to_hex(),
- created_at: 1_799_000_000,
- kind: radroots_event::envelope::kind::KIND_TRADE_DECISION,
- tags: built.tags,
- content: built.content,
- sig: core::iter::repeat_n('f', 128).collect(),
- })
- .unwrap();
+ let mut unknown_marker = built.tags.clone();
+ unknown_marker[2][2] = "unknown".to_owned();
assert_eq!(
- trade_mutation_from_event(&kind_contract_mismatch).unwrap_err(),
- RadrootsTradeMutationParseError::KindContractMismatch
+ trade_mutation_from_event(&event(
+ pubkey('a').to_hex(),
+ 1_799_000_000,
+ built.kind,
+ unknown_marker,
+ built.content,
+ ))
+ .unwrap_err(),
+ RadrootsTradeMutationError::InvalidTagShape
);
}
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
- fn trade_protocol_errors_map_to_stable_encode_categories() {
- let id_error = MutationId::parse("invalid").unwrap_err();
- let invalid_field_errors = [
- TradeProtocolError::ContractMismatch {
- expected: "expected",
- actual: "actual".into(),
- },
- TradeProtocolError::InvalidField("field"),
- TradeProtocolError::InvalidIdentifier {
- field: "id",
- error: id_error,
- },
- TradeProtocolError::InvalidInitialParents,
- TradeProtocolError::MissingParentMutation,
- TradeProtocolError::TooManyParents { max: 1, actual: 2 },
- TradeProtocolError::UnsortedParents,
- TradeProtocolError::DuplicateParent,
- TradeProtocolError::SelfParent,
- TradeProtocolError::MissingLines,
- TradeProtocolError::TooManyLines { max: 1, actual: 2 },
- TradeProtocolError::TooManyAdjustments { max: 1, actual: 2 },
- TradeProtocolError::UnsupportedNumber,
- TradeProtocolError::ContentTooLarge { max: 1, actual: 2 },
- TradeProtocolError::InvalidTimeRange,
- TradeProtocolError::MissingReservationCommitments,
- TradeProtocolError::MissingCancellationTarget,
- TradeProtocolError::CandidateIdMismatch {
- declared: "declared".into(),
- computed: "computed".into(),
- },
- TradeProtocolError::MutationIdMismatch {
- declared: "declared".into(),
- computed: "computed".into(),
- },
- TradeProtocolError::InvalidSchemaVersion {
- expected: 1,
- actual: 2,
- },
+ fn trade_errors_have_fixed_redacted_diagnostics() {
+ let errors = [
+ RadrootsTradeMutationError::CallerStructuralTagForbidden,
+ RadrootsTradeMutationError::DuplicateTradeTag,
+ RadrootsTradeMutationError::LegacyParentEventTag,
+ RadrootsTradeMutationError::MissingParentTag,
+ RadrootsTradeMutationError::MissingMutationTag,
+ RadrootsTradeMutationError::MissingRootTag,
+ RadrootsTradeMutationError::NoncanonicalParentOrder,
+ RadrootsTradeMutationError::PartyTagOrderMismatch,
+ RadrootsTradeMutationError::UnexpectedParentTag,
+ RadrootsTradeMutationError::UnexpectedRootTag,
+ RadrootsTradeMutationError::InvalidKind,
+ RadrootsTradeMutationError::AuthorMismatch,
+ RadrootsTradeMutationError::AuthoredAtMismatch,
+ RadrootsTradeMutationError::CanonicalContentMismatch,
+ RadrootsTradeMutationError::InvalidIdentifier,
+ RadrootsTradeMutationError::InvalidTagShape,
+ RadrootsTradeMutationError::UnexpectedTag,
+ RadrootsTradeMutationError::ContractTagMismatch,
+ RadrootsTradeMutationError::TradeTagMismatch,
+ RadrootsTradeMutationError::MutationTagMismatch,
+ RadrootsTradeMutationError::RootTagMismatch,
+ RadrootsTradeMutationError::ParentTagMismatch,
];
- for error in invalid_field_errors {
- assert!(matches!(
- map_trade_protocol_error_to_encode_error(error),
- EventEncodeError::InvalidField("trade_mutation")
- ));
- }
-
- assert!(matches!(
- map_trade_protocol_error_to_encode_error(TradeProtocolError::EmptyField("field")),
- EventEncodeError::EmptyRequiredField("field")
- ));
- for error in [
- TradeProtocolError::DuplicateKey("key".into()),
- TradeProtocolError::InvalidJson("json".into()),
- TradeProtocolError::NonCanonicalJson,
- ] {
- assert!(matches!(
- map_trade_protocol_error_to_encode_error(error),
- EventEncodeError::Json
- ));
+ for error in errors {
+ let display = error.to_string();
+ let debug = format!("{error:?}");
+ assert!(!display.is_empty());
+ assert!(!debug.contains(&hex_64('a')));
+ assert!(std::error::Error::source(&error).is_none());
}
}
}
diff --git a/crates/event_codec/tests/authored_typed_plans.rs b/crates/event_codec/tests/authored_typed_plans.rs
@@ -15,6 +15,7 @@ use radroots_event::{
FoodAvailabilityDetails, FoodAvailabilityDetailsParts, FoodAvailabilityStatus, FoodContent,
FoodCurrency, FoodIdentifier, FoodPrice, FoodPublishedAt, FoodQuantity, FoodText, FoodUnit,
},
+ id::{ClassifiedListingAddress, DTag, EventId, InventoryBinId, TradeId},
media::AuthoredImage,
post::{
AuthoredAsk, AuthoredPhotoUpdate, AuthoredPostImage, AuthoredUpdate, PostImageDimensions,
@@ -25,10 +26,19 @@ use radroots_event::{
reply::{AuthoredNip10Reply, Nip10ReplyReference},
},
profile::AuthoredProfile,
+ trade::{
+ FulfillmentProfileV1, RADROOTS_TRADE_CANCELLATION_CONTRACT_ID,
+ RADROOTS_TRADE_DECISION_CONTRACT_ID, RADROOTS_TRADE_PROPOSAL_CONTRACT_ID,
+ RADROOTS_TRADE_REVISION_DECISION_CONTRACT_ID, RADROOTS_TRADE_REVISION_PROPOSAL_CONTRACT_ID,
+ RADROOTS_TRADE_SCHEMA_VERSION, TradeCancellationProfileV1, TradeCandidateLineV1,
+ TradeCandidateTermsV1, TradeDecisionV1, TradeEconomicAdjustmentV1, TradeEconomicsProfileV1,
+ TradeMutationBodyV1, TradeMutationEnvelopeV1, canonical_trade_mutation_content,
+ },
};
use radroots_event_codec::authoring::{
AuthoredEventPlan, PlanDecodeError, PlanWireV1, REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS,
};
+use radroots_identity::PublicKey;
use serde::Deserialize;
use serde_json::Value;
@@ -172,6 +182,36 @@ fn calendar_plan_history_rejects_tolerated_or_noncanonical_inbound_shapes() {
));
}
+#[test]
+fn trade_plan_history_rejects_author_time_content_and_tag_drift() {
+ let plans = typed_plans();
+ let plan = plans
+ .get("radroots.trade.revision_proposal.v1")
+ .expect("trade plan");
+ for drifted in [
+ mutate_plan_wire(plan, |value| {
+ value["expected_author"] = Value::String(OTHER_AUTHOR.to_owned());
+ }),
+ mutate_plan_wire(plan, |value| {
+ value["created_at"] = Value::from(CREATED_AT + 1);
+ }),
+ mutate_plan_wire(plan, |value| {
+ value["content"] = Value::String("{}".to_owned());
+ }),
+ mutate_plan_wire(plan, |value| {
+ value["tags"][2][2] = Value::String("unknown".to_owned());
+ }),
+ mutate_plan_wire(plan, |value| {
+ value["tags"].as_array_mut().expect("tags").swap(0, 1);
+ }),
+ ] {
+ assert!(matches!(
+ PlanWireV1::from_json(&drifted),
+ Err(PlanDecodeError::HistoricalShape(_))
+ ));
+ }
+}
+
fn mutate_plan_wire(plan: &AuthoredEventPlan, mutator: impl FnOnce(&mut Value)) -> Vec<u8> {
let mut value =
serde_json::from_slice::<Value>(&PlanWireV1::from_plan(plan).to_json().expect("plan wire"))
@@ -297,9 +337,168 @@ fn typed_plans() -> BTreeMap<&'static str, AuthoredEventPlan> {
AuthoredEventPlan::from_food_availability(&food_details(), CREATED_AT, AUTHOR)
.expect("food plan"),
);
+ for mutation in trade_mutations() {
+ let contract_id = mutation.mutation_kind().contract_id();
+ plans.insert(
+ contract_id,
+ AuthoredEventPlan::from_trade_mutation(mutation).expect("trade plan"),
+ );
+ }
plans
}
+fn trade_mutations() -> Vec<TradeMutationEnvelopeV1> {
+ let proposal = canonical_trade_mutation_content(trade_proposal())
+ .expect("canonical proposal")
+ .envelope;
+ let proposal_id = proposal.mutation_id.expect("proposal mutation id");
+ let candidate = match &proposal.body {
+ TradeMutationBodyV1::Proposal { candidate } => candidate.clone(),
+ _ => unreachable!(),
+ };
+ let candidate_id = candidate.candidate_id.expect("candidate id");
+ let trade_id = proposal.trade_id;
+ let buyer = proposal.buyer_pubkey;
+ let seller = proposal.seller_pubkey;
+ let farm = proposal.farm_id.clone();
+ let author = proposal.author_pubkey;
+ let counterparty = proposal.counterparty_pubkey;
+ let child = |contract_id: &str, body| TradeMutationEnvelopeV1 {
+ mutation_id: None,
+ contract_id: contract_id.to_owned(),
+ schema_version: RADROOTS_TRADE_SCHEMA_VERSION,
+ trade_id,
+ root_mutation_id: Some(proposal_id),
+ buyer_pubkey: buyer,
+ seller_pubkey: seller,
+ farm_id: farm.clone(),
+ parent_mutation_ids: vec![proposal_id],
+ author_pubkey: author,
+ counterparty_pubkey: counterparty,
+ authored_at_unix_s: CREATED_AT,
+ body,
+ };
+ let decision = child(
+ RADROOTS_TRADE_DECISION_CONTRACT_ID,
+ TradeMutationBodyV1::Decision {
+ proposal_mutation_id: proposal_id,
+ candidate_id,
+ decision: TradeDecisionV1::Declined {
+ reason: "inventory unavailable".to_owned(),
+ },
+ },
+ );
+ let revision_proposal = child(
+ RADROOTS_TRADE_REVISION_PROPOSAL_CONTRACT_ID,
+ TradeMutationBodyV1::RevisionProposal { candidate },
+ );
+ let revision_decision = child(
+ RADROOTS_TRADE_REVISION_DECISION_CONTRACT_ID,
+ TradeMutationBodyV1::RevisionDecision {
+ proposal_mutation_id: proposal_id,
+ candidate_id,
+ decision: TradeDecisionV1::Declined {
+ reason: "inventory unavailable".to_owned(),
+ },
+ },
+ );
+ let cancellation = child(
+ RADROOTS_TRADE_CANCELLATION_CONTRACT_ID,
+ TradeMutationBodyV1::Cancellation {
+ target_candidate_id: Some(candidate_id),
+ target_claim_mutation_id: None,
+ reason: "cancelled".to_owned(),
+ },
+ );
+ vec![
+ proposal,
+ decision,
+ revision_proposal,
+ revision_decision,
+ cancellation,
+ ]
+}
+
+fn trade_proposal() -> TradeMutationEnvelopeV1 {
+ let buyer = PublicKey::from_hex(AUTHOR).expect("buyer key");
+ let seller = PublicKey::from_hex(OTHER_AUTHOR).expect("seller key");
+ TradeMutationEnvelopeV1 {
+ mutation_id: None,
+ contract_id: RADROOTS_TRADE_PROPOSAL_CONTRACT_ID.to_owned(),
+ schema_version: RADROOTS_TRADE_SCHEMA_VERSION,
+ trade_id: TradeId::parse("11111111111111111111111111111111").expect("trade id"),
+ root_mutation_id: None,
+ buyer_pubkey: buyer,
+ seller_pubkey: seller,
+ farm_id: DTag::parse("farm-1").expect("farm id"),
+ parent_mutation_ids: Vec::new(),
+ author_pubkey: buyer,
+ counterparty_pubkey: seller,
+ authored_at_unix_s: CREATED_AT,
+ body: TradeMutationBodyV1::Proposal {
+ candidate: TradeCandidateTermsV1 {
+ candidate_id: None,
+ schema_version: RADROOTS_TRADE_SCHEMA_VERSION,
+ base_candidate_id: None,
+ supersession_intent: None,
+ buyer_pubkey: buyer,
+ seller_pubkey: seller,
+ farm_id: DTag::parse("farm-1").expect("farm id"),
+ lines: vec![TradeCandidateLineV1 {
+ line_id: DTag::parse("line-1").expect("line id"),
+ listing_addr: ClassifiedListingAddress::parse(format!(
+ "30402:{OTHER_AUTHOR}:listing-1"
+ ))
+ .expect("listing address"),
+ listing_event_id: EventId::parse("c".repeat(64)).expect("listing event"),
+ listing_snapshot_sha256: "d".repeat(64),
+ product_id: "carrots".to_owned(),
+ option_id: None,
+ bin_id: InventoryBinId::parse("bin-1").expect("bin id"),
+ quantity_mantissa: "2".to_owned(),
+ quantity_scale: 0,
+ unit_code: "count".to_owned(),
+ unit_profile: "mvp-count".to_owned(),
+ unit_price_mantissa: "500".to_owned(),
+ currency_code: "USD".to_owned(),
+ line_subtotal_mantissa: "1000".to_owned(),
+ replaces_line_id: None,
+ }],
+ line_tombstones: Vec::new(),
+ economics: TradeEconomicsProfileV1 {
+ profile_id: "mvp-fixed".to_owned(),
+ currency_code: "USD".to_owned(),
+ currency_exponent: 2,
+ rounding_profile: "half-even".to_owned(),
+ subtotal_mantissa: "1000".to_owned(),
+ discount_total_mantissa: "0".to_owned(),
+ adjustment_total_mantissa: "0".to_owned(),
+ total_mantissa: "1000".to_owned(),
+ adjustments: Vec::<TradeEconomicAdjustmentV1>::new(),
+ },
+ fulfillment: FulfillmentProfileV1 {
+ profile_id: "market-pickup".to_owned(),
+ method: "pickup".to_owned(),
+ starts_at_unix_s: 1_800_000_000,
+ ends_at_unix_s: 1_800_003_600,
+ timezone: "America/New_York".to_owned(),
+ utc_offset_seconds: -18_000,
+ fold: 0,
+ location_class: "farmstand".to_owned(),
+ requires_private_terms: false,
+ },
+ cancellation: TradeCancellationProfileV1 {
+ profile_id: "buyer-pre-agreement".to_owned(),
+ buyer_pre_agreement: true,
+ post_agreement_cutoff_unix_s: None,
+ },
+ private_terms: None,
+ proposal_expires_at_unix_s: 1_799_999_000,
+ },
+ },
+ }
+}
+
fn authored_post_image() -> AuthoredPostImage {
let bytes = b"strawberries";
let hash = Sha256::digest(bytes);
diff --git a/crates/event_codec/tests/package_boundary.rs b/crates/event_codec/tests/package_boundary.rs
@@ -204,6 +204,11 @@ fn package_documentation_and_reviewed_api_baseline_are_complete() {
"pub mod radroots_event_codec::verify",
"pub use radroots_event_codec::VerificationError",
"pub struct radroots_event_codec::authoring::BlossomAuthorizationPlan",
+ "pub const radroots_event_codec::authoring::REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS: [&str; 15]",
+ "pub fn radroots_event_codec::authoring::AuthoredEventPlan::from_trade_mutation",
+ "pub fn radroots_event_codec::decode::trade::trade_mutation_from_verified_event",
+ "pub fn radroots_event_codec::encode::trade::trade_mutation_event_build_with_extra_tags",
+ "pub enum radroots_event_codec::decode::trade::RadrootsTradeMutationError",
] {
assert!(PUBLIC_API.contains(item), "API baseline is missing {item}");
}
@@ -213,6 +218,11 @@ fn package_documentation_and_reviewed_api_baseline_are_complete() {
"API baseline exposes forbidden host path {forbidden}"
);
}
+ let retired = "RadrootsTradeMutationParseError";
+ assert!(
+ !PUBLIC_API.contains(retired),
+ "API baseline retains unsafe trade authoring surface {retired}"
+ );
}
fn table_keys<'a>(manifest: &'a str, heading: &str) -> BTreeSet<&'a str> {
diff --git a/crates/event_codec/tests/services_hardening_event_decisions.rs b/crates/event_codec/tests/services_hardening_event_decisions.rs
@@ -2,7 +2,16 @@
use serde_json::Value;
use sha2::{Digest, Sha256};
-use std::collections::BTreeSet;
+use std::collections::{BTreeMap, BTreeSet};
+
+use radroots_event::{
+ id::{MutationId, TradeId},
+ trade::{TradeMutationEnvelopeV1, trade_mutation_from_canonical_content},
+};
+use radroots_event_codec::{
+ decode::trade::{RadrootsTradeMutationError, validate_trade_mutation_tags},
+ encode::trade::{trade_mutation_event_build, trade_mutation_event_build_with_extra_tags},
+};
const DECISION: &str =
include_str!("../../../contracts/architecture/decisions/services_hardening_events.v1.json");
@@ -14,6 +23,8 @@ const TRADE_VECTORS: &str = include_str!(
const RHI_VECTORS: &str = include_str!(
"../../../contracts/conformance/vectors/rhi/evidence_attestation_decision.v1.json"
);
+const AUTHORED_CORPUS: &str =
+ include_str!("../../../contracts/conformance/vectors/event/authored_operations.v1.json");
fn json(source: &str) -> Value {
serde_json::from_str(source).expect("services-hardening machine contract must be valid JSON")
@@ -181,6 +192,325 @@ fn services_hardening_trade_tag_cardinality_and_query_contract_is_exact() {
}
#[test]
+fn every_trade_mutation_vector_executes_the_public_boundary() {
+ let vectors = json(TRADE_VECTORS);
+ let mut bases = BTreeMap::new();
+ let mut positive_count = 0;
+ for vector in vectors["vectors"]
+ .as_array()
+ .expect("trade vectors")
+ .iter()
+ .filter(|vector| vector["kind"] == "trade.mutation_index_tags.valid")
+ {
+ let id = vector["id"].as_str().expect("vector id");
+ let envelope = trade_vector_envelope(vector);
+ let expected_kind = vector["expected"]["kind"].as_u64().expect("kind") as u32;
+ assert_eq!(envelope.mutation_kind().nostr_kind(), expected_kind, "{id}");
+ let built = trade_mutation_event_build(envelope).expect("typed trade builder");
+ assert_eq!(built.kind, expected_kind, "{id}");
+ let parsed = trade_mutation_from_canonical_content(&built.content)
+ .expect("builder emits canonical trade content");
+ if let Some(tags) = vector["expected"].get("tags") {
+ assert_eq!(built.tags, json_tags(tags), "{id}");
+ } else {
+ assert_eq!(
+ tag_semantics(&built.tags),
+ vector["expected"]["tag_names_and_semantics"]
+ .as_array()
+ .expect("tag semantics")
+ .iter()
+ .map(|value| value.as_str().expect("semantic").to_owned())
+ .collect::<Vec<_>>(),
+ "{id}"
+ );
+ assert_lineage_tags_match_vector(&built.tags, &vector["input"]);
+ }
+ validate_trade_mutation_tags(&parsed, &built.tags).expect("positive vector");
+ assert!(bases.insert(id.to_owned(), (parsed, built.tags)).is_none());
+ positive_count += 1;
+ }
+ assert_eq!(positive_count, 5);
+
+ let proposal = bases
+ .get("trade_mutation_index_proposal_002")
+ .expect("proposal base")
+ .0
+ .clone();
+ for vector in vectors["vectors"]
+ .as_array()
+ .expect("trade vectors")
+ .iter()
+ .filter(|vector| vector["kind"] == "trade.mutation_index_tags.invalid")
+ {
+ let id = vector["id"].as_str().expect("vector id");
+ let expected = vector["expected"]["error_code"]
+ .as_str()
+ .expect("error code");
+ let input = &vector["input"];
+ let actual = if let Some(extra_tags) = input.get("builder_extra_tags") {
+ assert_eq!(vector["expected"]["layer"], "builder", "{id}");
+ trade_mutation_event_build_with_extra_tags(proposal.clone(), &json_tags(extra_tags))
+ .expect_err("builder negative must fail")
+ } else {
+ assert_eq!(vector["expected"]["layer"], "wire", "{id}");
+ let base = input["base"].as_str().expect("wire negative base");
+ let (envelope, mut tags) = bases.get(base).expect("known positive base").clone();
+ if let Some(pattern) = input.get("remove_tag") {
+ let before = tags.len();
+ tags.retain(|tag| !tag_matches_pattern(tag, pattern));
+ assert_eq!(tags.len() + 1, before, "{id}");
+ }
+ if let Some(patterns) = input.get("remove_tags") {
+ for pattern in patterns.as_array().expect("remove tag patterns") {
+ let before = tags.len();
+ tags.retain(|tag| !tag_matches_pattern(tag, pattern));
+ assert!(tags.len() < before, "{id}");
+ }
+ }
+ if let Some(tag) = input.get("append_tag") {
+ tags.push(json_tag(tag));
+ }
+ if let Some(indexes) = input.get("swap_tag_indexes") {
+ let indexes = indexes.as_array().expect("swap indexes");
+ tags.swap(
+ indexes[0].as_u64().expect("left index") as usize,
+ indexes[1].as_u64().expect("right index") as usize,
+ );
+ }
+ if let Some(replacement) = input.get("replace_tag") {
+ let index = replacement["index"].as_u64().expect("replace index") as usize;
+ tags[index] = json_tag(&replacement["tag"]);
+ }
+ validate_trade_mutation_tags(&envelope, &tags).expect_err("negative vector must fail")
+ };
+ assert_eq!(actual.code(), expected, "{id}");
+ }
+}
+
+fn json_tag(value: &Value) -> Vec<String> {
+ value
+ .as_array()
+ .expect("tag array")
+ .iter()
+ .map(|value| value.as_str().expect("tag value").to_owned())
+ .collect()
+}
+
+fn json_tags(value: &Value) -> Vec<Vec<String>> {
+ value
+ .as_array()
+ .expect("tag list")
+ .iter()
+ .map(json_tag)
+ .collect()
+}
+
+fn tag_matches_pattern(tag: &[String], pattern: &Value) -> bool {
+ let pattern = json_tag(pattern);
+ if pattern.first().map(String::as_str) == Some("x") && pattern.len() == 2 {
+ tag.first() == pattern.first() && tag.get(2) == pattern.get(1)
+ } else {
+ tag == pattern
+ }
+}
+
+fn tag_semantics(tags: &[Vec<String>]) -> Vec<String> {
+ let mut party = 0;
+ tags.iter()
+ .map(|tag| match tag.first().map(String::as_str) {
+ Some("contract") => "contract".to_owned(),
+ Some("d") => "d:trade".to_owned(),
+ Some("x") => format!("x:{}", tag.get(2).expect("x marker")),
+ Some("p") => {
+ party += 1;
+ format!(
+ "p:{}",
+ if party == 1 {
+ "buyer-first"
+ } else {
+ "seller-second"
+ }
+ )
+ }
+ _ => panic!("unexpected structural tag"),
+ })
+ .collect()
+}
+
+fn trade_from_corpus(contract_id: &str) -> TradeMutationEnvelopeV1 {
+ let corpus = json(AUTHORED_CORPUS);
+ let content = corpus["vectors"]
+ .as_array()
+ .expect("authored vectors")
+ .iter()
+ .find(|vector| vector["input"]["contract_id"] == contract_id)
+ .and_then(|vector| vector["expected"]["content"].as_str())
+ .expect("typed trade content");
+ trade_mutation_from_canonical_content(content).expect("canonical trade mutation")
+}
+
+fn trade_vector_envelope(vector: &Value) -> TradeMutationEnvelopeV1 {
+ let input = &vector["input"];
+ let contract_id = input["contract_id"].as_str().expect("contract id");
+ let mut envelope = trade_from_corpus(contract_id);
+ envelope.mutation_id = None;
+ envelope.root_mutation_id = input
+ .get("root_mutation_id")
+ .and_then(Value::as_str)
+ .map(|value| MutationId::parse(value).expect("root mutation id"));
+ envelope.parent_mutation_ids = input
+ .get("parent_mutation_ids")
+ .and_then(Value::as_array)
+ .map(|parents| {
+ parents
+ .iter()
+ .map(|value| {
+ MutationId::parse(value.as_str().expect("parent mutation id"))
+ .expect("parent mutation id")
+ })
+ .collect()
+ })
+ .unwrap_or_default();
+ if let Some(trade_id) = input.get("trade_id").and_then(Value::as_str) {
+ envelope.trade_id = TradeId::parse(trade_id).expect("trade id");
+ }
+ if let Some(buyer) = input.get("buyer_pubkey").and_then(Value::as_str) {
+ envelope.buyer_pubkey = radroots_identity::PublicKey::from_hex(buyer).expect("buyer key");
+ envelope.author_pubkey = envelope.buyer_pubkey;
+ }
+ if let Some(seller) = input.get("seller_pubkey").and_then(Value::as_str) {
+ envelope.seller_pubkey =
+ radroots_identity::PublicKey::from_hex(seller).expect("seller key");
+ envelope.counterparty_pubkey = envelope.seller_pubkey;
+ }
+ envelope
+}
+
+fn assert_lineage_tags_match_vector(tags: &[Vec<String>], input: &Value) {
+ let root = tags
+ .iter()
+ .find(|tag| tag.get(2).map(String::as_str) == Some("root"))
+ .map(|tag| tag[1].as_str());
+ assert_eq!(root, input.get("root_mutation_id").and_then(Value::as_str));
+ let parents = tags
+ .iter()
+ .filter(|tag| tag.get(2).map(String::as_str) == Some("parent"))
+ .map(|tag| tag[1].as_str())
+ .collect::<Vec<_>>();
+ let expected: Vec<&str> = input
+ .get("parent_mutation_ids")
+ .and_then(Value::as_array)
+ .map(|values| values.iter().map(|value| value.as_str().unwrap()).collect())
+ .unwrap_or_default();
+ assert_eq!(parents, expected);
+}
+
+fn synthetic_all_fields_envelope() -> TradeMutationEnvelopeV1 {
+ trade_vector_envelope(&json(TRADE_VECTORS)["vectors"][0])
+}
+
+#[test]
+fn additional_trade_mutation_shape_permutations_and_bounds_fail_closed() {
+ let built = trade_mutation_event_build(synthetic_all_fields_envelope()).expect("trade builder");
+ let envelope = trade_mutation_from_canonical_content(&built.content).expect("trade content");
+ let tags = built.tags;
+
+ let mut malformed_x = tags.clone();
+ malformed_x[2].pop();
+ assert_eq!(
+ validate_trade_mutation_tags(&envelope, &malformed_x).unwrap_err(),
+ RadrootsTradeMutationError::InvalidTagShape
+ );
+
+ let mut unknown_marker = tags.clone();
+ unknown_marker[2][2] = "unknown".to_owned();
+ assert_eq!(
+ validate_trade_mutation_tags(&envelope, &unknown_marker).unwrap_err(),
+ RadrootsTradeMutationError::InvalidTagShape
+ );
+
+ let mut duplicate_mutation = tags.clone();
+ duplicate_mutation.insert(3, duplicate_mutation[2].clone());
+ assert_eq!(
+ validate_trade_mutation_tags(&envelope, &duplicate_mutation).unwrap_err(),
+ RadrootsTradeMutationError::InvalidTagShape
+ );
+
+ let mut duplicate_root = tags.clone();
+ duplicate_root.insert(4, duplicate_root[3].clone());
+ assert_eq!(
+ validate_trade_mutation_tags(&envelope, &duplicate_root).unwrap_err(),
+ RadrootsTradeMutationError::InvalidTagShape
+ );
+
+ let mut five_parents = tags.clone();
+ for value in ["6", "7", "8"] {
+ five_parents.insert(
+ five_parents.len() - 2,
+ vec!["x".to_owned(), value.repeat(64), "parent".to_owned()],
+ );
+ }
+ assert_eq!(
+ validate_trade_mutation_tags(&envelope, &five_parents).unwrap_err(),
+ RadrootsTradeMutationError::InvalidTagShape
+ );
+
+ let mut duplicate_parent = tags.clone();
+ duplicate_parent.insert(6, duplicate_parent[5].clone());
+ assert_eq!(
+ validate_trade_mutation_tags(&envelope, &duplicate_parent).unwrap_err(),
+ RadrootsTradeMutationError::NoncanonicalParentOrder
+ );
+
+ let mut uppercase_identifier = tags.clone();
+ uppercase_identifier[2][1] = "A".repeat(64);
+ assert_eq!(
+ validate_trade_mutation_tags(&envelope, &uppercase_identifier).unwrap_err(),
+ RadrootsTradeMutationError::InvalidIdentifier
+ );
+
+ let mut unknown_tag = tags.clone();
+ unknown_tag.push(vec!["t".to_owned(), "trade".to_owned()]);
+ assert_eq!(
+ validate_trade_mutation_tags(&envelope, &unknown_tag).unwrap_err(),
+ RadrootsTradeMutationError::UnexpectedTag
+ );
+
+ let mut missing_party = tags;
+ missing_party.pop();
+ assert_eq!(
+ validate_trade_mutation_tags(&envelope, &missing_party).unwrap_err(),
+ RadrootsTradeMutationError::InvalidTagShape
+ );
+
+ let oversized = vec![vec!["t".to_owned(), "trade".to_owned()]; 10_000];
+ assert_eq!(
+ validate_trade_mutation_tags(&envelope, &oversized).unwrap_err(),
+ RadrootsTradeMutationError::InvalidTagShape
+ );
+
+ assert_eq!(
+ trade_mutation_event_build_with_extra_tags(
+ trade_from_corpus("radroots.trade.proposal.v1"),
+ &[vec!["t".to_owned(), "trade".to_owned()]],
+ )
+ .unwrap_err(),
+ RadrootsTradeMutationError::UnexpectedTag
+ );
+ assert_eq!(
+ trade_mutation_event_build_with_extra_tags(
+ trade_from_corpus("radroots.trade.proposal.v1"),
+ &[
+ vec!["t".to_owned(), "trade".to_owned()],
+ vec!["p".to_owned(), "0".repeat(64)],
+ ],
+ )
+ .unwrap_err(),
+ RadrootsTradeMutationError::CallerStructuralTagForbidden
+ );
+}
+
+#[test]
fn services_hardening_attestation_is_immutable_and_fully_bound() {
let decision = json(DECISION);
let attestation = &decision["rhi_attestation"];
diff --git a/crates/nostr/tests/authored_wire_corpus.rs b/crates/nostr/tests/authored_wire_corpus.rs
@@ -11,6 +11,7 @@ use radroots_event::{
GenericEventDraft,
calendar::{AuthoredCalendarDateEvent, AuthoredCalendarTimeEvent, CalendarDate},
envelope::kind::KIND_CLASSIFIED_LISTING,
+ envelope::{EventEnvelope, EventEnvelopeParts},
food::availability::{
FoodAvailabilityDetails, FoodAvailabilityDetailsParts, FoodAvailabilityStatus, FoodContent,
FoodCurrency, FoodIdentifier, FoodPrice, FoodPublishedAt, FoodQuantity, FoodText, FoodUnit,
@@ -28,14 +29,19 @@ use radroots_event::{
},
profile::AuthoredProfile,
trade::{
- FulfillmentProfileV1, RADROOTS_TRADE_PROPOSAL_CONTRACT_ID, RADROOTS_TRADE_SCHEMA_VERSION,
- TradeCancellationProfileV1, TradeCandidateLineV1, TradeCandidateTermsV1,
- TradeEconomicAdjustmentV1, TradeEconomicsProfileV1, TradeMutationBodyV1,
- TradeMutationEnvelopeV1,
+ FulfillmentProfileV1, RADROOTS_TRADE_CANCELLATION_CONTRACT_ID,
+ RADROOTS_TRADE_DECISION_CONTRACT_ID, RADROOTS_TRADE_PROPOSAL_CONTRACT_ID,
+ RADROOTS_TRADE_REVISION_DECISION_CONTRACT_ID, RADROOTS_TRADE_REVISION_PROPOSAL_CONTRACT_ID,
+ RADROOTS_TRADE_SCHEMA_VERSION, TradeCancellationProfileV1, TradeCandidateLineV1,
+ TradeCandidateTermsV1, TradeDecisionV1, TradeEconomicAdjustmentV1, TradeEconomicsProfileV1,
+ TradeMutationBodyV1, TradeMutationEnvelopeV1, canonical_trade_mutation_content,
},
wire::canonical_nip01_event_id_preimage,
};
-use radroots_event_codec::encode::trade::trade_mutation_event_build;
+use radroots_event_codec::{
+ authoring::AuthoredEventPlan, decode::trade::trade_mutation_from_verified_event,
+ verify::verify_nip01_event,
+};
use radroots_identity::PublicKey;
use radroots_nostr::{
event::{
@@ -49,7 +55,7 @@ use serde::{Deserialize, Serialize};
use support::{
APPROVED_FIXTURE_NAMESPACE, FIXTURE_ALICE_PUBLIC_KEY_HEX, FIXTURE_ALICE_SECRET_KEY_HEX,
- FIXTURE_BOB_PUBLIC_KEY_HEX, RELAY_PRIMARY_WSS,
+ FIXTURE_BOB_PUBLIC_KEY_HEX, FIXTURE_BOB_SECRET_KEY_HEX, RELAY_PRIMARY_WSS,
};
const CREATED_AT: u64 = 1_784_347_200;
@@ -115,7 +121,7 @@ fn checked_in_authored_wire_corpus_is_exact_and_executable() {
);
}
assert_eq!(actual, expected);
- assert_eq!(actual.vectors.len(), 12);
+ assert_eq!(actual.vectors.len(), 16);
assert_eq!(APPROVED_FIXTURE_NAMESPACE, "radroots-approved-fixture-v1");
assert_eq!(
actual
@@ -123,7 +129,7 @@ fn checked_in_authored_wire_corpus_is_exact_and_executable() {
.iter()
.filter(|vector| vector.input.authoring == "typed")
.count(),
- 10
+ 15
);
assert!(actual.vectors.iter().all(|vector| {
!vector
@@ -144,10 +150,58 @@ fn authored_post_content_boundary_is_exact_without_bloating_the_corpus() {
assert!(AuthoredUpdate::new("x".repeat(RADROOTS_POST_CONTENT_MAX_BYTES + 1)).is_err());
}
+#[test]
+fn signature_verified_trade_boundary_rejects_a_different_valid_signer() {
+ let mutation = trade_mutations().remove(0);
+ let plan = AuthoredEventPlan::from_trade_mutation(mutation).expect("typed trade plan");
+ let signer = Keys::new(SecretKey::from_hex(FIXTURE_BOB_SECRET_KEY_HEX).expect("fixture key"));
+ let event = UnsignedEvent {
+ id: None,
+ pubkey: signer.public_key(),
+ created_at: Timestamp::from_secs(plan.created_at()),
+ kind: Kind::Custom(u16::try_from(plan.body().kind()).expect("trade kind")),
+ tags: nostr::Tags::from_list(
+ plan.body()
+ .tags()
+ .iter()
+ .cloned()
+ .map(Tag::parse)
+ .collect::<Result<Vec<_>, _>>()
+ .expect("trade tags"),
+ ),
+ content: plan.body().content().to_owned(),
+ }
+ .sign_with_ctx(nostr::SECP256K1, &mut CorpusAuxRng, &signer)
+ .expect("validly signed trade event");
+ let verified = verify_nip01_event(
+ EventEnvelope::new(EventEnvelopeParts {
+ id: event.id.to_hex(),
+ author: event.pubkey.to_hex(),
+ created_at: event.created_at.as_secs(),
+ kind: u32::from(event.kind.as_u16()),
+ tags: event
+ .tags
+ .iter()
+ .map(|tag| tag.as_slice().to_vec())
+ .collect(),
+ content: event.content,
+ sig: event.sig.to_string(),
+ })
+ .expect("trade event envelope"),
+ )
+ .expect("valid signature");
+ assert_eq!(
+ trade_mutation_from_verified_event(&verified)
+ .expect_err("different valid signer must fail")
+ .code(),
+ "author_mismatch"
+ );
+}
+
fn authored_wire_vectors() -> Vec<WireVector> {
let keys = fixture_keys();
let created_at = Timestamp::from_secs(CREATED_AT);
- let mut vectors = Vec::with_capacity(12);
+ let mut vectors = Vec::with_capacity(16);
let profile = AuthoredProfile::new("Alice \"Sprout\"")
.expect("profile name")
@@ -333,20 +387,84 @@ fn authored_wire_vectors() -> Vec<WireVector> {
&keys,
));
- let trade = trade_mutation_event_build(trade_proposal()).expect("trade wire parts");
- vectors.push(generic_vector(
- "generic_trade_proposal_010",
- RADROOTS_TRADE_PROPOSAL_CONTRACT_ID,
- CREATED_AT,
- trade.kind,
- trade.tags,
- trade.content,
- &keys,
- ));
+ for (id, mutation) in [
+ "typed_trade_proposal_010",
+ "typed_trade_decision_013",
+ "typed_trade_revision_proposal_014",
+ "typed_trade_revision_decision_015",
+ "typed_trade_cancellation_016",
+ ]
+ .into_iter()
+ .zip(trade_mutations())
+ {
+ vectors.push(typed_trade_vector(id, mutation, &keys));
+ }
vectors
}
+fn typed_trade_vector(id: &str, mutation: TradeMutationEnvelopeV1, keys: &Keys) -> WireVector {
+ let plan = AuthoredEventPlan::from_trade_mutation(mutation).expect("typed trade plan");
+ let tags = plan
+ .body()
+ .tags()
+ .iter()
+ .cloned()
+ .map(Tag::parse)
+ .collect::<Result<Vec<_>, _>>()
+ .expect("trade tags");
+ let event = UnsignedEvent {
+ id: Some(
+ nostr::EventId::from_hex(&plan.expected_event_id().to_hex()).expect("planned event id"),
+ ),
+ pubkey: keys.public_key(),
+ created_at: Timestamp::from_secs(plan.created_at()),
+ kind: Kind::Custom(u16::try_from(plan.body().kind()).expect("trade kind")),
+ tags: nostr::Tags::from_list(tags),
+ content: plan.body().content().to_owned(),
+ }
+ .sign_with_ctx(nostr::SECP256K1, &mut CorpusAuxRng, keys)
+ .expect("trade event");
+ assert_eq!(event.pubkey.to_hex(), plan.author().to_hex());
+ assert_eq!(
+ event
+ .tags
+ .iter()
+ .map(|tag| tag.as_slice().to_vec())
+ .collect::<Vec<_>>(),
+ plan.body().tags()
+ );
+ assert_eq!(event.id.to_hex(), plan.expected_event_id().to_hex());
+ let verified = verify_nip01_event(
+ EventEnvelope::new(EventEnvelopeParts {
+ id: event.id.to_hex(),
+ author: event.pubkey.to_hex(),
+ created_at: event.created_at.as_secs(),
+ kind: u32::from(event.kind.as_u16()),
+ tags: event
+ .tags
+ .iter()
+ .map(|tag| tag.as_slice().to_vec())
+ .collect(),
+ content: event.content.clone(),
+ sig: event.sig.to_string(),
+ })
+ .expect("trade event envelope"),
+ )
+ .expect("verified trade event");
+ let parsed = trade_mutation_from_verified_event(&verified).expect("validated trade event");
+ assert_eq!(
+ parsed.contract_id,
+ plan.body().contract().contract_id().as_str()
+ );
+ vector(
+ id,
+ plan.body().contract().contract_id().as_str(),
+ "typed",
+ event,
+ )
+}
+
fn generic_vector(
id: &str,
contract_id: &str,
@@ -663,6 +781,78 @@ fn trade_proposal() -> TradeMutationEnvelopeV1 {
}
}
+fn trade_mutations() -> Vec<TradeMutationEnvelopeV1> {
+ let proposal = canonical_trade_mutation_content(trade_proposal())
+ .expect("canonical proposal")
+ .envelope;
+ let proposal_id = proposal.mutation_id.expect("proposal mutation id");
+ let candidate = match &proposal.body {
+ TradeMutationBodyV1::Proposal { candidate } => candidate.clone(),
+ _ => unreachable!(),
+ };
+ let candidate_id = candidate.candidate_id.expect("candidate id");
+ let trade_id = proposal.trade_id;
+ let buyer = proposal.buyer_pubkey;
+ let seller = proposal.seller_pubkey;
+ let farm = proposal.farm_id.clone();
+ let author = proposal.author_pubkey;
+ let counterparty = proposal.counterparty_pubkey;
+ let child = |contract_id: &str, body| TradeMutationEnvelopeV1 {
+ mutation_id: None,
+ contract_id: contract_id.to_owned(),
+ schema_version: RADROOTS_TRADE_SCHEMA_VERSION,
+ trade_id,
+ root_mutation_id: Some(proposal_id),
+ buyer_pubkey: buyer,
+ seller_pubkey: seller,
+ farm_id: farm.clone(),
+ parent_mutation_ids: vec![proposal_id],
+ author_pubkey: author,
+ counterparty_pubkey: counterparty,
+ authored_at_unix_s: CREATED_AT,
+ body,
+ };
+ let decision = child(
+ RADROOTS_TRADE_DECISION_CONTRACT_ID,
+ TradeMutationBodyV1::Decision {
+ proposal_mutation_id: proposal_id,
+ candidate_id,
+ decision: TradeDecisionV1::Declined {
+ reason: "inventory unavailable".to_owned(),
+ },
+ },
+ );
+ let revision_proposal = child(
+ RADROOTS_TRADE_REVISION_PROPOSAL_CONTRACT_ID,
+ TradeMutationBodyV1::RevisionProposal { candidate },
+ );
+ let revision_decision = child(
+ RADROOTS_TRADE_REVISION_DECISION_CONTRACT_ID,
+ TradeMutationBodyV1::RevisionDecision {
+ proposal_mutation_id: proposal_id,
+ candidate_id,
+ decision: TradeDecisionV1::Declined {
+ reason: "inventory unavailable".to_owned(),
+ },
+ },
+ );
+ let cancellation = child(
+ RADROOTS_TRADE_CANCELLATION_CONTRACT_ID,
+ TradeMutationBodyV1::Cancellation {
+ target_candidate_id: Some(candidate_id),
+ target_claim_mutation_id: None,
+ reason: "cancelled".to_owned(),
+ },
+ );
+ vec![
+ proposal,
+ decision,
+ revision_proposal,
+ revision_decision,
+ cancellation,
+ ]
+}
+
fn conformance_vectors() -> Cow<'static, str> {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
if !manifest_dir.join(WORKSPACE_CONTRACT_MARKER_PATH).is_file() {
diff --git a/crates/nostr/tests/fixtures/authored_operations.v1.json b/crates/nostr/tests/fixtures/authored_operations.v1.json
@@ -475,11 +475,11 @@
}
},
{
- "id": "generic_trade_proposal_010",
+ "id": "typed_trade_proposal_010",
"kind": "authored_operations.wire",
"input": {
"contract_id": "radroots.trade.proposal.v1",
- "authoring": "generic"
+ "authoring": "typed"
},
"expected": {
"kind": 3470,
@@ -495,15 +495,228 @@
"11111111111111111111111111111111"
],
[
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "mutation"
+ ],
+ [
+ "p",
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"
+ ],
+ [
"p",
"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
]
],
"content": "{\"author_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"authored_at_unix_s\":1784347200,\"body\":{\"candidate\":{\"base_candidate_id\":null,\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"cancellation\":{\"buyer_pre_agreement\":true,\"post_agreement_cutoff_unix_s\":null,\"profile_id\":\"buyer-pre-agreement\"},\"candidate_id\":\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\",\"economics\":{\"adjustment_total_mantissa\":\"0\",\"adjustments\":[],\"currency_code\":\"USD\",\"currency_exponent\":2,\"discount_total_mantissa\":\"0\",\"profile_id\":\"mvp-fixed\",\"rounding_profile\":\"half-even\",\"subtotal_mantissa\":\"1000\",\"total_mantissa\":\"1000\"},\"farm_id\":\"farm-1\",\"fulfillment\":{\"ends_at_unix_s\":1800003600,\"fold\":0,\"location_class\":\"farmstand\",\"method\":\"pickup\",\"profile_id\":\"market-pickup\",\"requires_private_terms\":false,\"starts_at_unix_s\":1800000000,\"timezone\":\"America/New_York\",\"utc_offset_seconds\":-18000},\"line_tombstones\":[],\"lines\":[{\"bin_id\":\"bin-1\",\"currency_code\":\"USD\",\"line_id\":\"line-1\",\"line_subtotal_mantissa\":\"1000\",\"listing_addr\":\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\",\"listing_event_id\":\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"listing_snapshot_sha256\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"option_id\":null,\"product_id\":\"carrots\",\"quantity_mantissa\":\"2\",\"quantity_scale\":0,\"replaces_line_id\":null,\"unit_code\":\"count\",\"unit_price_mantissa\":\"500\",\"unit_profile\":\"mvp-count\"}],\"private_terms\":null,\"proposal_expires_at_unix_s\":1799999000,\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"supersession_intent\":null},\"mutation_type\":\"proposal\"},\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"contract_id\":\"radroots.trade.proposal.v1\",\"counterparty_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"farm_id\":\"farm-1\",\"mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent_mutation_ids\":[],\"root_mutation_id\":null,\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"trade_id\":\"11111111111111111111111111111111\"}",
- "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3470,[[\"contract\",\"radroots.trade.proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"parent_mutation_ids\\\":[],\\\"root_mutation_id\\\":null,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
- "event_id": "62d04ac41ad9d0d14b1f7f56a6d4d3cbcea73e712f2aa4ae5dec0218b578d078",
- "signature": "4fa5133b540dd67b3397a20ec419bc890cc8e71e85b886fd3c806e305394fdfd24c97961ce00aa949712e9f865a4e517e31117e0f9c9c6cf30a0297f4ac0298c",
- "raw_json": "{\"id\":\"62d04ac41ad9d0d14b1f7f56a6d4d3cbcea73e712f2aa4ae5dec0218b578d078\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3470,\"tags\":[[\"contract\",\"radroots.trade.proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"parent_mutation_ids\\\":[],\\\"root_mutation_id\\\":null,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"4fa5133b540dd67b3397a20ec419bc890cc8e71e85b886fd3c806e305394fdfd24c97961ce00aa949712e9f865a4e517e31117e0f9c9c6cf30a0297f4ac0298c\"}"
+ "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3470,[[\"contract\",\"radroots.trade.proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"mutation\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"parent_mutation_ids\\\":[],\\\"root_mutation_id\\\":null,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
+ "event_id": "58eacb99d9b4e4da7f614fb37ed84b482d2cb85e30785d49ea6a29b555bfcd3a",
+ "signature": "b39fc5d285826bda6462dc9fb8c20c6519b54d4c8ac9673f3c53c6424e2e1703762b4126282e579dcb11c6738477738a61396595fb6c13331e038c3b06b18517",
+ "raw_json": "{\"id\":\"58eacb99d9b4e4da7f614fb37ed84b482d2cb85e30785d49ea6a29b555bfcd3a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3470,\"tags\":[[\"contract\",\"radroots.trade.proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"mutation\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"parent_mutation_ids\\\":[],\\\"root_mutation_id\\\":null,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"b39fc5d285826bda6462dc9fb8c20c6519b54d4c8ac9673f3c53c6424e2e1703762b4126282e579dcb11c6738477738a61396595fb6c13331e038c3b06b18517\"}"
+ }
+ },
+ {
+ "id": "typed_trade_decision_013",
+ "kind": "authored_operations.wire",
+ "input": {
+ "contract_id": "radroots.trade.decision.v1",
+ "authoring": "typed"
+ },
+ "expected": {
+ "kind": 3471,
+ "created_at": 1784347200,
+ "pubkey": "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df",
+ "tags": [
+ [
+ "contract",
+ "radroots.trade.decision.v1"
+ ],
+ [
+ "d",
+ "11111111111111111111111111111111"
+ ],
+ [
+ "x",
+ "07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027",
+ "mutation"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "root"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "parent"
+ ],
+ [
+ "p",
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"
+ ],
+ [
+ "p",
+ "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
+ ]
+ ],
+ "content": "{\"author_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"authored_at_unix_s\":1784347200,\"body\":{\"candidate_id\":\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\",\"decision\":{\"decision\":\"declined\",\"reason\":\"inventory unavailable\"},\"mutation_type\":\"decision\",\"proposal_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"},\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"contract_id\":\"radroots.trade.decision.v1\",\"counterparty_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"farm_id\":\"farm-1\",\"mutation_id\":\"07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027\",\"parent_mutation_ids\":[\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"],\"root_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"trade_id\":\"11111111111111111111111111111111\"}",
+ "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3471,[[\"contract\",\"radroots.trade.decision.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"decision\\\":{\\\"decision\\\":\\\"declined\\\",\\\"reason\\\":\\\"inventory unavailable\\\"},\\\"mutation_type\\\":\\\"decision\\\",\\\"proposal_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.decision.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
+ "event_id": "7dbe947cbeb1cde706bb240657fee585aa4214baa61d728b7bec7ac5ee18350e",
+ "signature": "222a04c15f66c6028c5b6619116ae92958a5e853293697f2fdbe0450300a50f9bfd2f1f56f1ce3eb2bd037d938bf22f6d99ecdb924de8c2fd5f0da846c1d2817",
+ "raw_json": "{\"id\":\"7dbe947cbeb1cde706bb240657fee585aa4214baa61d728b7bec7ac5ee18350e\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3471,\"tags\":[[\"contract\",\"radroots.trade.decision.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"decision\\\":{\\\"decision\\\":\\\"declined\\\",\\\"reason\\\":\\\"inventory unavailable\\\"},\\\"mutation_type\\\":\\\"decision\\\",\\\"proposal_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.decision.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"07741093fa274aa94d045fb7e5ade2938f0c9192f34ecdf19b27f512c940b027\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"222a04c15f66c6028c5b6619116ae92958a5e853293697f2fdbe0450300a50f9bfd2f1f56f1ce3eb2bd037d938bf22f6d99ecdb924de8c2fd5f0da846c1d2817\"}"
+ }
+ },
+ {
+ "id": "typed_trade_revision_proposal_014",
+ "kind": "authored_operations.wire",
+ "input": {
+ "contract_id": "radroots.trade.revision_proposal.v1",
+ "authoring": "typed"
+ },
+ "expected": {
+ "kind": 3472,
+ "created_at": 1784347200,
+ "pubkey": "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df",
+ "tags": [
+ [
+ "contract",
+ "radroots.trade.revision_proposal.v1"
+ ],
+ [
+ "d",
+ "11111111111111111111111111111111"
+ ],
+ [
+ "x",
+ "cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847",
+ "mutation"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "root"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "parent"
+ ],
+ [
+ "p",
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"
+ ],
+ [
+ "p",
+ "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
+ ]
+ ],
+ "content": "{\"author_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"authored_at_unix_s\":1784347200,\"body\":{\"candidate\":{\"base_candidate_id\":null,\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"cancellation\":{\"buyer_pre_agreement\":true,\"post_agreement_cutoff_unix_s\":null,\"profile_id\":\"buyer-pre-agreement\"},\"candidate_id\":\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\",\"economics\":{\"adjustment_total_mantissa\":\"0\",\"adjustments\":[],\"currency_code\":\"USD\",\"currency_exponent\":2,\"discount_total_mantissa\":\"0\",\"profile_id\":\"mvp-fixed\",\"rounding_profile\":\"half-even\",\"subtotal_mantissa\":\"1000\",\"total_mantissa\":\"1000\"},\"farm_id\":\"farm-1\",\"fulfillment\":{\"ends_at_unix_s\":1800003600,\"fold\":0,\"location_class\":\"farmstand\",\"method\":\"pickup\",\"profile_id\":\"market-pickup\",\"requires_private_terms\":false,\"starts_at_unix_s\":1800000000,\"timezone\":\"America/New_York\",\"utc_offset_seconds\":-18000},\"line_tombstones\":[],\"lines\":[{\"bin_id\":\"bin-1\",\"currency_code\":\"USD\",\"line_id\":\"line-1\",\"line_subtotal_mantissa\":\"1000\",\"listing_addr\":\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\",\"listing_event_id\":\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"listing_snapshot_sha256\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"option_id\":null,\"product_id\":\"carrots\",\"quantity_mantissa\":\"2\",\"quantity_scale\":0,\"replaces_line_id\":null,\"unit_code\":\"count\",\"unit_price_mantissa\":\"500\",\"unit_profile\":\"mvp-count\"}],\"private_terms\":null,\"proposal_expires_at_unix_s\":1799999000,\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"supersession_intent\":null},\"mutation_type\":\"revision_proposal\"},\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"contract_id\":\"radroots.trade.revision_proposal.v1\",\"counterparty_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"farm_id\":\"farm-1\",\"mutation_id\":\"cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847\",\"parent_mutation_ids\":[\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"],\"root_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"trade_id\":\"11111111111111111111111111111111\"}",
+ "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3472,[[\"contract\",\"radroots.trade.revision_proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"revision_proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.revision_proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
+ "event_id": "3be0e2a2f3f9eef21127eed0dab79ce31932e05927d6558999c5a50022d2eefe",
+ "signature": "777eb26ba6b01ee154a006907cb99ad43dace95208138a30dc48d36419c93f8e1e256d3707eb28a0dd42ed50ed3954b803656f83b0259afae02bb86cc7ae3742",
+ "raw_json": "{\"id\":\"3be0e2a2f3f9eef21127eed0dab79ce31932e05927d6558999c5a50022d2eefe\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3472,\"tags\":[[\"contract\",\"radroots.trade.revision_proposal.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate\\\":{\\\"base_candidate_id\\\":null,\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"cancellation\\\":{\\\"buyer_pre_agreement\\\":true,\\\"post_agreement_cutoff_unix_s\\\":null,\\\"profile_id\\\":\\\"buyer-pre-agreement\\\"},\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"economics\\\":{\\\"adjustment_total_mantissa\\\":\\\"0\\\",\\\"adjustments\\\":[],\\\"currency_code\\\":\\\"USD\\\",\\\"currency_exponent\\\":2,\\\"discount_total_mantissa\\\":\\\"0\\\",\\\"profile_id\\\":\\\"mvp-fixed\\\",\\\"rounding_profile\\\":\\\"half-even\\\",\\\"subtotal_mantissa\\\":\\\"1000\\\",\\\"total_mantissa\\\":\\\"1000\\\"},\\\"farm_id\\\":\\\"farm-1\\\",\\\"fulfillment\\\":{\\\"ends_at_unix_s\\\":1800003600,\\\"fold\\\":0,\\\"location_class\\\":\\\"farmstand\\\",\\\"method\\\":\\\"pickup\\\",\\\"profile_id\\\":\\\"market-pickup\\\",\\\"requires_private_terms\\\":false,\\\"starts_at_unix_s\\\":1800000000,\\\"timezone\\\":\\\"America/New_York\\\",\\\"utc_offset_seconds\\\":-18000},\\\"line_tombstones\\\":[],\\\"lines\\\":[{\\\"bin_id\\\":\\\"bin-1\\\",\\\"currency_code\\\":\\\"USD\\\",\\\"line_id\\\":\\\"line-1\\\",\\\"line_subtotal_mantissa\\\":\\\"1000\\\",\\\"listing_addr\\\":\\\"30402:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:listing-1\\\",\\\"listing_event_id\\\":\\\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\",\\\"listing_snapshot_sha256\\\":\\\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\\\",\\\"option_id\\\":null,\\\"product_id\\\":\\\"carrots\\\",\\\"quantity_mantissa\\\":\\\"2\\\",\\\"quantity_scale\\\":0,\\\"replaces_line_id\\\":null,\\\"unit_code\\\":\\\"count\\\",\\\"unit_price_mantissa\\\":\\\"500\\\",\\\"unit_profile\\\":\\\"mvp-count\\\"}],\\\"private_terms\\\":null,\\\"proposal_expires_at_unix_s\\\":1799999000,\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"supersession_intent\\\":null},\\\"mutation_type\\\":\\\"revision_proposal\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.revision_proposal.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"cbb2837497fd46b47b599c916b246cd569e0697b2cd8d50a51363790ae587847\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"777eb26ba6b01ee154a006907cb99ad43dace95208138a30dc48d36419c93f8e1e256d3707eb28a0dd42ed50ed3954b803656f83b0259afae02bb86cc7ae3742\"}"
+ }
+ },
+ {
+ "id": "typed_trade_revision_decision_015",
+ "kind": "authored_operations.wire",
+ "input": {
+ "contract_id": "radroots.trade.revision_decision.v1",
+ "authoring": "typed"
+ },
+ "expected": {
+ "kind": 3473,
+ "created_at": 1784347200,
+ "pubkey": "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df",
+ "tags": [
+ [
+ "contract",
+ "radroots.trade.revision_decision.v1"
+ ],
+ [
+ "d",
+ "11111111111111111111111111111111"
+ ],
+ [
+ "x",
+ "bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732",
+ "mutation"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "root"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "parent"
+ ],
+ [
+ "p",
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"
+ ],
+ [
+ "p",
+ "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
+ ]
+ ],
+ "content": "{\"author_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"authored_at_unix_s\":1784347200,\"body\":{\"candidate_id\":\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\",\"decision\":{\"decision\":\"declined\",\"reason\":\"inventory unavailable\"},\"mutation_type\":\"revision_decision\",\"proposal_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"},\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"contract_id\":\"radroots.trade.revision_decision.v1\",\"counterparty_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"farm_id\":\"farm-1\",\"mutation_id\":\"bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732\",\"parent_mutation_ids\":[\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"],\"root_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"trade_id\":\"11111111111111111111111111111111\"}",
+ "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3473,[[\"contract\",\"radroots.trade.revision_decision.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"decision\\\":{\\\"decision\\\":\\\"declined\\\",\\\"reason\\\":\\\"inventory unavailable\\\"},\\\"mutation_type\\\":\\\"revision_decision\\\",\\\"proposal_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.revision_decision.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
+ "event_id": "064a6208b3a064e472fd8f1c4d56c9ca49513693d1343feb0ceeb7a8814f93b6",
+ "signature": "60a9241287ba780bcccc8b3b5c85c102bfab40fa6898990f420d634f3eba01c4b9a76a6ad9efed64d111f1e39d5b5d923810c6cd2793f71d2bd19cd7fb21cdf3",
+ "raw_json": "{\"id\":\"064a6208b3a064e472fd8f1c4d56c9ca49513693d1343feb0ceeb7a8814f93b6\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3473,\"tags\":[[\"contract\",\"radroots.trade.revision_decision.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"decision\\\":{\\\"decision\\\":\\\"declined\\\",\\\"reason\\\":\\\"inventory unavailable\\\"},\\\"mutation_type\\\":\\\"revision_decision\\\",\\\"proposal_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.revision_decision.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"bd7ab3a8f4bbff02000fa95446232f0959a3bd5fa2753f495742ad25a7809732\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"60a9241287ba780bcccc8b3b5c85c102bfab40fa6898990f420d634f3eba01c4b9a76a6ad9efed64d111f1e39d5b5d923810c6cd2793f71d2bd19cd7fb21cdf3\"}"
+ }
+ },
+ {
+ "id": "typed_trade_cancellation_016",
+ "kind": "authored_operations.wire",
+ "input": {
+ "contract_id": "radroots.trade.cancellation.v1",
+ "authoring": "typed"
+ },
+ "expected": {
+ "kind": 3474,
+ "created_at": 1784347200,
+ "pubkey": "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df",
+ "tags": [
+ [
+ "contract",
+ "radroots.trade.cancellation.v1"
+ ],
+ [
+ "d",
+ "11111111111111111111111111111111"
+ ],
+ [
+ "x",
+ "fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d",
+ "mutation"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "root"
+ ],
+ [
+ "x",
+ "05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799",
+ "parent"
+ ],
+ [
+ "p",
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"
+ ],
+ [
+ "p",
+ "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"
+ ]
+ ],
+ "content": "{\"author_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"authored_at_unix_s\":1784347200,\"body\":{\"mutation_type\":\"cancellation\",\"reason\":\"cancelled\",\"target_candidate_id\":\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\",\"target_claim_mutation_id\":null},\"buyer_pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"contract_id\":\"radroots.trade.cancellation.v1\",\"counterparty_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"farm_id\":\"farm-1\",\"mutation_id\":\"fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d\",\"parent_mutation_ids\":[\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\"],\"root_mutation_id\":\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"schema_version\":1,\"seller_pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"trade_id\":\"11111111111111111111111111111111\"}",
+ "preimage": "[0,\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",1784347200,3474,[[\"contract\",\"radroots.trade.cancellation.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"mutation_type\\\":\\\"cancellation\\\",\\\"reason\\\":\\\"cancelled\\\",\\\"target_candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"target_claim_mutation_id\\\":null},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.cancellation.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\"]",
+ "event_id": "2cb6574e48eb71825016b5fb94aed7df459948cab74b8f45f34cc1b42ca8767a",
+ "signature": "141c3eace0da665038626a73d336e48bc2ebc2bdee1f460bedd35034d85f199c1059f5b37c3c9b8a51730c4db0b8240223c00a0fc3fc2807cb46a7b6a0504a36",
+ "raw_json": "{\"id\":\"2cb6574e48eb71825016b5fb94aed7df459948cab74b8f45f34cc1b42ca8767a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1784347200,\"kind\":3474,\"tags\":[[\"contract\",\"radroots.trade.cancellation.v1\"],[\"d\",\"11111111111111111111111111111111\"],[\"x\",\"fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d\",\"mutation\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"root\"],[\"x\",\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\",\"parent\"],[\"p\",\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\"],[\"p\",\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\"]],\"content\":\"{\\\"author_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"authored_at_unix_s\\\":1784347200,\\\"body\\\":{\\\"mutation_type\\\":\\\"cancellation\\\",\\\"reason\\\":\\\"cancelled\\\",\\\"target_candidate_id\\\":\\\"cd6471a9bc91766a455e4adb59a63c8b26881c80e2f0d96a2e882bbf75b7d533\\\",\\\"target_claim_mutation_id\\\":null},\\\"buyer_pubkey\\\":\\\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\\\",\\\"contract_id\\\":\\\"radroots.trade.cancellation.v1\\\",\\\"counterparty_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"farm_id\\\":\\\"farm-1\\\",\\\"mutation_id\\\":\\\"fe65c35d4a280a66a309e2834a58712ad8cf837386edb8fd25b73c946b1a273d\\\",\\\"parent_mutation_ids\\\":[\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\"],\\\"root_mutation_id\\\":\\\"05623ca85936e3faeaeff1c7165926382d1534cfe7fe9650e8c029405d44a799\\\",\\\"schema_version\\\":1,\\\"seller_pubkey\\\":\\\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\\\",\\\"trade_id\\\":\\\"11111111111111111111111111111111\\\"}\",\"sig\":\"141c3eace0da665038626a73d336e48bc2ebc2bdee1f460bedd35034d85f199c1059f5b37c3c9b8a51730c4db0b8240223c00a0fc3fc2807cb46a7b6a0504a36\"}"
}
}
]
diff --git a/crates/sdk/src/trade.rs b/crates/sdk/src/trade.rs
@@ -3,14 +3,10 @@
use std::{error, fmt};
use radroots_event::{
- GenericEventDraft,
contract::AuthorRole,
trade::{TradeMutationEnvelopeV1, TradeProtocolError, canonical_trade_mutation_content},
};
-use radroots_event_codec::{
- authoring::AuthoredEventPlan, encode::EventEncodeError,
- encode::trade::trade_mutation_event_build,
-};
+use radroots_event_codec::authoring::{AuthoredEventPlan, AuthoredPlanError};
use radroots_signing::Actor;
use radroots_trade::{Projection, ReductionInput, WorkflowPlan, reducer::reduce_trade_records};
@@ -101,14 +97,10 @@ impl PrepareError {
Self::with_source(PrepareErrorKind::Workflow, source)
}
- fn encode(source: EventEncodeError) -> Self {
+ fn encode(source: AuthoredPlanError) -> Self {
Self::with_source(PrepareErrorKind::Encode, source)
}
- fn draft(source: radroots_event::draft::DraftError) -> Self {
- Self::with_source(PrepareErrorKind::Draft, source)
- }
-
fn with_source(
kind: PrepareErrorKind,
source: impl error::Error + Send + Sync + 'static,
@@ -174,19 +166,8 @@ pub fn prepare(request: PrepareRequest) -> Result<Plan, PrepareError> {
return Err(PrepareError::unauthorized_actor());
}
let workflow = WorkflowPlan::prepare(canonical.clone()).map_err(PrepareError::workflow)?;
- let parts = trade_mutation_event_build(canonical.clone()).map_err(PrepareError::encode)?;
- let authored_event = AuthoredEventPlan::from_generic(
- GenericEventDraft::new(
- canonical.contract_id.clone(),
- parts.kind,
- canonical.authored_at_unix_s,
- parts.tags,
- parts.content,
- canonical.author_pubkey.to_hex(),
- )
- .map_err(PrepareError::draft)?,
- )
- .map_err(PrepareError::draft)?;
+ let authored_event =
+ AuthoredEventPlan::from_trade_mutation(canonical.clone()).map_err(PrepareError::encode)?;
Ok(Plan {
actor: request.actor,
workflow,
diff --git a/crates/sdk/tests/package_boundary.rs b/crates/sdk/tests/package_boundary.rs
@@ -352,7 +352,7 @@ fn sync_operations_only_delegate_to_the_canonical_engine() {
fn authored_submission_is_one_protocol_neutral_boundary_for_all_typed_contracts() {
use radroots_event_codec::authoring::REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS;
- assert_eq!(REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS.len(), 10);
+ assert_eq!(REGISTRY_V7_TYPED_AUTHORING_CONTRACT_IDS.len(), 15);
assert!(SYNC.contains("pub async fn submit_push"));
assert!(SYNC.contains("request: PushRequest"));
assert!(SYNC.contains("Result<PushStatus, Error>"));
@@ -365,6 +365,17 @@ fn authored_submission_is_one_protocol_neutral_boundary_for_all_typed_contracts(
}
#[test]
+fn trade_planning_uses_only_the_typed_mutation_authoring_boundary() {
+ assert!(TRADE.contains("AuthoredEventPlan::from_trade_mutation"));
+ for forbidden in ["GenericEventDraft", "trade_mutation_event_build("] {
+ assert!(
+ !TRADE.contains(forbidden),
+ "trade planning retains generic authoring path {forbidden}"
+ );
+ }
+}
+
+#[test]
fn farm_operations_preserve_pure_planning_commit_and_privacy_boundaries() {
for required in [
"encode::farm::to_wire_parts",
diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs
@@ -2484,6 +2484,8 @@ const TRADE_PROPOSAL_WITNESSES: [EventBoundarySourceWitness; 3] = [
required_fragments: &[
"pub fn trade_mutation_event_build",
"pub fn trade_mutation_from_event",
+ "pub fn trade_mutation_from_verified_event",
+ "pub fn validate_trade_mutation_tags",
],
},
];
@@ -2505,6 +2507,8 @@ const TRADE_DECISION_WITNESSES: [EventBoundarySourceWitness; 3] = [
required_fragments: &[
"pub fn trade_mutation_event_build",
"pub fn trade_mutation_from_event",
+ "pub fn trade_mutation_from_verified_event",
+ "pub fn validate_trade_mutation_tags",
],
},
];
@@ -2526,6 +2530,8 @@ const TRADE_REVISION_PROPOSAL_WITNESSES: [EventBoundarySourceWitness; 3] = [
required_fragments: &[
"pub fn trade_mutation_event_build",
"pub fn trade_mutation_from_event",
+ "pub fn trade_mutation_from_verified_event",
+ "pub fn validate_trade_mutation_tags",
],
},
];
@@ -2547,6 +2553,8 @@ const TRADE_REVISION_DECISION_WITNESSES: [EventBoundarySourceWitness; 3] = [
required_fragments: &[
"pub fn trade_mutation_event_build",
"pub fn trade_mutation_from_event",
+ "pub fn trade_mutation_from_verified_event",
+ "pub fn validate_trade_mutation_tags",
],
},
];
@@ -2568,6 +2576,8 @@ const TRADE_CANCELLATION_WITNESSES: [EventBoundarySourceWitness; 3] = [
required_fragments: &[
"pub fn trade_mutation_event_build",
"pub fn trade_mutation_from_event",
+ "pub fn trade_mutation_from_verified_event",
+ "pub fn validate_trade_mutation_tags",
],
},
];