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:
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"),
);