lib

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

commit ed4a837381b0c974754785704d86f3bcd53e2399
parent 118dd1b1c3c181f22dfe1deabf639977cb9d396c
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 13:26:00 -0700

trade: split canonical listing addresses

- expose public and draft listing addresses separately
- store canonical listing data directly on drafts
- route listing mutation addresses by lifecycle intent
- cover publish update and save-draft address selection

Diffstat:
Mcrates/trade/src/listing/draft.rs | 65+++++++++++++++++++++++++++++++++++++++++------------------------
Mcrates/trade/src/listing/mutation.rs | 57+++++++++++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 92 insertions(+), 30 deletions(-)

diff --git a/crates/trade/src/listing/draft.rs b/crates/trade/src/listing/draft.rs @@ -12,7 +12,7 @@ use radroots_events::{ ids::{ RadrootsIdParseError, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsPublicKey, }, - kinds::KIND_LISTING_DRAFT, + kinds::{KIND_LISTING, KIND_LISTING_DRAFT}, listing::RadrootsListing, }; use thiserror::Error; @@ -32,21 +32,24 @@ impl RadrootsListingDraftDocumentV1 { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug)] pub struct RadrootsCanonicalListingDraft { + pub listing: RadrootsListing, pub seller_pubkey: RadrootsPublicKey, - pub listing_addr: RadrootsListingAddress, - pub document: RadrootsListingDraftDocumentV1, + pub public_listing_addr: RadrootsListingAddress, + pub draft_listing_addr: RadrootsListingAddress, } impl RadrootsCanonicalListingDraft { pub fn new( + listing: RadrootsListing, seller_pubkey: RadrootsPublicKey, - listing_addr: RadrootsListingAddress, - document: RadrootsListingDraftDocumentV1, + public_listing_addr: RadrootsListingAddress, + draft_listing_addr: RadrootsListingAddress, ) -> Self { Self { + listing, seller_pubkey, - listing_addr, - document, + public_listing_addr, + draft_listing_addr, } } } @@ -120,7 +123,13 @@ pub fn canonicalize_listing_draft( return Err(RadrootsListingDraftError::MissingPrimaryBin { primary_bin_id }); } - let listing_addr = RadrootsListingAddress::parse(format!( + let public_listing_addr = RadrootsListingAddress::parse(format!( + "{KIND_LISTING}:{}:{}", + seller_pubkey.as_str(), + document.listing.d_tag.as_str() + )) + .map_err(RadrootsListingDraftError::InvalidListingAddress)?; + let draft_listing_addr = RadrootsListingAddress::parse(format!( "{KIND_LISTING_DRAFT}:{}:{}", seller_pubkey.as_str(), document.listing.d_tag.as_str() @@ -128,9 +137,10 @@ pub fn canonicalize_listing_draft( .map_err(RadrootsListingDraftError::InvalidListingAddress)?; Ok(RadrootsCanonicalListingDraft::new( + document.listing, seller_pubkey, - listing_addr, - document, + public_listing_addr, + draft_listing_addr, )) } @@ -145,7 +155,7 @@ mod tests { contract::RadrootsActorRole, farm::RadrootsFarmRef, ids::{RadrootsDTag, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsPublicKey}, - kinds::KIND_LISTING_DRAFT, + kinds::{KIND_LISTING, KIND_LISTING_DRAFT}, listing::{RadrootsListing, RadrootsListingBin, RadrootsListingProduct}, }; @@ -235,26 +245,29 @@ mod tests { } #[test] - fn canonical_draft_carries_seller_and_address() { + fn canonical_draft_carries_seller_listing_and_addresses() { let seller_pubkey = RadrootsPublicKey::parse(SELLER).expect("seller"); - let listing_addr = RadrootsListingAddress::parse(format!( + let public_listing_addr = RadrootsListingAddress::parse(format!( + "{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" + )) + .expect("public listing address"); + let draft_listing_addr = RadrootsListingAddress::parse(format!( "{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" )) - .expect("listing address"); - let document = RadrootsListingDraftDocumentV1::new(listing()); + .expect("draft listing address"); + let listing = listing(); let canonical = RadrootsCanonicalListingDraft::new( + listing, seller_pubkey.clone(), - listing_addr.clone(), - document, + public_listing_addr.clone(), + draft_listing_addr.clone(), ); assert_eq!(canonical.seller_pubkey, seller_pubkey); - assert_eq!(canonical.listing_addr, listing_addr); - assert_eq!( - canonical.document.listing.d_tag.as_str(), - "AAAAAAAAAAAAAAAAAAAAAg" - ); + assert_eq!(canonical.public_listing_addr, public_listing_addr); + assert_eq!(canonical.draft_listing_addr, draft_listing_addr); + assert_eq!(canonical.listing.d_tag.as_str(), "AAAAAAAAAAAAAAAAAAAAAg"); } #[test] @@ -284,10 +297,14 @@ mod tests { assert_eq!(canonical.seller_pubkey.as_str(), SELLER); assert_eq!( - canonical.listing_addr.as_str(), + canonical.public_listing_addr.as_str(), + format!("{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg") + ); + assert_eq!( + canonical.draft_listing_addr.as_str(), format!("{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg") ); - assert_eq!(canonical.document.listing.farm.pubkey, SELLER); + assert_eq!(canonical.listing.farm.pubkey, SELLER); } #[test] diff --git a/crates/trade/src/listing/mutation.rs b/crates/trade/src/listing/mutation.rs @@ -99,6 +99,14 @@ impl RadrootsListingMutation { Self::Archive { .. } => Err(RadrootsListingMutationError::UnsupportedMutation), } } + + pub fn listing_addr(&self) -> Result<&RadrootsListingAddress, RadrootsListingMutationError> { + match self { + Self::Publish { draft } | Self::Update { draft } => Ok(&draft.public_listing_addr), + Self::SaveDraft { draft } => Ok(&draft.draft_listing_addr), + Self::Archive { .. } => Err(RadrootsListingMutationError::UnsupportedMutation), + } + } } #[cfg(feature = "serde_json")] @@ -117,7 +125,7 @@ pub fn build_listing_mutation_draft( return Err(RadrootsListingMutationError::UnsupportedMutation); } }; - let parts = to_wire_parts_with_kind(&draft.document.listing, kind) + let parts = to_wire_parts_with_kind(&draft.listing, kind) .map_err(|error| RadrootsListingMutationError::EncodeListing(error.to_string()))?; to_frozen_draft(parts, contract_id, draft.seller_pubkey.as_str(), created_at) .map_err(RadrootsListingMutationError::FrozenDraft) @@ -141,7 +149,7 @@ mod tests { }, }; - use crate::listing::draft::{RadrootsCanonicalListingDraft, RadrootsListingDraftDocumentV1}; + use crate::listing::draft::RadrootsCanonicalListingDraft; use crate::listing::validation::validate_listing_event; use super::{ @@ -224,12 +232,16 @@ mod tests { fn canonical_draft() -> RadrootsCanonicalListingDraft { RadrootsCanonicalListingDraft::new( + listing(), RadrootsPublicKey::parse(SELLER).expect("seller"), RadrootsListingAddress::parse(format!( + "{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" + )) + .expect("public listing address"), + RadrootsListingAddress::parse(format!( "{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" )) - .expect("listing address"), - RadrootsListingDraftDocumentV1::new(listing()), + .expect("draft listing address"), ) } @@ -273,13 +285,42 @@ mod tests { save_draft.canonical_draft().expect("draft").seller_pubkey, SELLER ); + assert_eq!( + publish + .canonical_draft() + .expect("draft") + .listing + .d_tag + .as_str(), + "AAAAAAAAAAAAAAAAAAAAAg" + ); + } + + #[test] + fn supported_mutations_report_listing_addresses() { + let publish = RadrootsListingMutation::publish(canonical_draft()); + let update = RadrootsListingMutation::update(canonical_draft()); + let save_draft = RadrootsListingMutation::save_draft(canonical_draft()); + + assert_eq!( + publish.listing_addr().expect("address").as_str(), + format!("{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg") + ); + assert_eq!( + update.listing_addr().expect("address").as_str(), + format!("{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg") + ); + assert_eq!( + save_draft.listing_addr().expect("address").as_str(), + format!("{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg") + ); } #[test] fn archive_is_explicitly_unsupported() { let archive = RadrootsListingMutation::archive( RadrootsListingAddress::parse(format!( - "{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" + "{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" )) .expect("listing address"), ); @@ -292,6 +333,10 @@ mod tests { archive.canonical_draft().unwrap_err(), RadrootsListingMutationError::UnsupportedMutation ); + assert_eq!( + archive.listing_addr().unwrap_err(), + RadrootsListingMutationError::UnsupportedMutation + ); } #[test] @@ -327,7 +372,7 @@ mod tests { fn build_listing_mutation_draft_rejects_archive() { let archive = RadrootsListingMutation::archive( RadrootsListingAddress::parse(format!( - "{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" + "{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg" )) .expect("listing address"), );