lib

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

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:
Mcontracts/api_baselines/radroots_event_codec.txt | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mcontracts/conformance/vectors/event/authored_operations.v1.json | 225++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mcontracts/conformance/vectors/trade/mutation_index_tags_decision.v1.json | 12++++++------
Mcontracts/event_store/event_contract_registry_v7.inventory.json | 90++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mcontracts/event_store/event_contract_registry_v7.inventory.sha256 | 2+-
Mcrates/event/src/contract/registry_v7.rs | 46++++++++++++++++++++++++++++++++++++++--------
Mcrates/event/src/contract/registry_v7/tests.rs | 37+++++++++++++++++++++++++++----------
Mcrates/event_codec/src/authoring/typed.rs | 52+++++++++++++++++++++++++++++++++++++++++++++++++---
Mcrates/event_codec/src/authoring/wire.rs | 1+
Mcrates/event_codec/src/decode.rs | 5++++-
Mcrates/event_codec/src/encode.rs | 5++++-
Mcrates/event_codec/src/manifest.rs | 2++
Mcrates/event_codec/src/manifest/registry_v7.rs | 4++++
Mcrates/event_codec/src/trade/mod.rs | 790+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mcrates/event_codec/tests/authored_typed_plans.rs | 199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/event_codec/tests/package_boundary.rs | 10++++++++++
Mcrates/event_codec/tests/services_hardening_event_decisions.rs | 332++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mcrates/nostr/tests/authored_wire_corpus.rs | 228++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mcrates/nostr/tests/fixtures/authored_operations.v1.json | 225++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mcrates/sdk/src/trade.rs | 27++++-----------------------
Mcrates/sdk/tests/package_boundary.rs | 13++++++++++++-
Mtools/xtask/src/contract.rs | 10++++++++++
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", ], }, ];