lib

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

commit 00b59bdba1cb2b6a5e02494a0825d6f798a711d3
parent 98f327eaebc17e9adf4ddb1a578c9cfd6ae60662
Author: triesap <tyson@radroots.org>
Date:   Mon, 22 Dec 2025 19:40:44 +0000

trade: expand listing lifecycle mappings and markers


- add lifecycle marker exports for request and result stages
- add request/result kind mapping helpers for trade listings
- add stage accessors for kind and marker resolution
- add tests for mapping roundtrips and marker expectations

Diffstat:
Mtrade/bindings/ts/src/lib.ts | 9+++++++--
Mtrade/src/listing/kinds.rs | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtrade/src/listing/meta.rs | 196++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 288 insertions(+), 3 deletions(-)

diff --git a/trade/bindings/ts/src/lib.ts b/trade/bindings/ts/src/lib.ts @@ -1,7 +1,13 @@ export const MARKER_LISTING = "listing"; export const MARKER_PAYLOAD = "payload"; export const MARKER_PREVIOUS = "previous"; +export const MARKER_ORDER_RESULT = "order_result"; export const MARKER_ACCEPT_RESULT = "accept_result"; +export const MARKER_CONVEYANCE_RESULT = "conveyance_result"; export const MARKER_INVOICE_RESULT = "invoice_result"; +export const MARKER_PAYMENT_RESULT = "payment_result"; export const MARKER_FULFILLMENT_RESULT = "fulfillment_result"; -export const MARKER_PROOF = "proof"; -\ No newline at end of file +export const MARKER_RECEIPT_RESULT = "receipt_result"; +export const MARKER_CANCEL_RESULT = "cancel_result"; +export const MARKER_REFUND_RESULT = "refund_result"; +export const MARKER_PROOF = "proof"; diff --git a/trade/src/listing/kinds.rs b/trade/src/listing/kinds.rs @@ -58,3 +58,89 @@ pub const fn is_trade_listing_request_kind(kind: u16) -> bool { | KIND_TRADE_LISTING_REFUND_REQ ) } + +#[inline] +pub const fn is_trade_listing_result_kind(kind: u16) -> bool { + matches!( + kind, + KIND_TRADE_LISTING_ORDER_RES + | KIND_TRADE_LISTING_ACCEPT_RES + | KIND_TRADE_LISTING_CONVEYANCE_RES + | KIND_TRADE_LISTING_INVOICE_RES + | KIND_TRADE_LISTING_PAYMENT_RES + | KIND_TRADE_LISTING_FULFILL_RES + | KIND_TRADE_LISTING_RECEIPT_RES + | KIND_TRADE_LISTING_CANCEL_RES + | KIND_TRADE_LISTING_REFUND_RES + ) +} + +#[inline] +pub const fn trade_listing_result_kind_for_request(kind: u16) -> Option<u16> { + match kind { + KIND_TRADE_LISTING_ORDER_REQ => Some(KIND_TRADE_LISTING_ORDER_RES), + KIND_TRADE_LISTING_ACCEPT_REQ => Some(KIND_TRADE_LISTING_ACCEPT_RES), + KIND_TRADE_LISTING_CONVEYANCE_REQ => Some(KIND_TRADE_LISTING_CONVEYANCE_RES), + KIND_TRADE_LISTING_INVOICE_REQ => Some(KIND_TRADE_LISTING_INVOICE_RES), + KIND_TRADE_LISTING_PAYMENT_REQ => Some(KIND_TRADE_LISTING_PAYMENT_RES), + KIND_TRADE_LISTING_FULFILL_REQ => Some(KIND_TRADE_LISTING_FULFILL_RES), + KIND_TRADE_LISTING_RECEIPT_REQ => Some(KIND_TRADE_LISTING_RECEIPT_RES), + KIND_TRADE_LISTING_CANCEL_REQ => Some(KIND_TRADE_LISTING_CANCEL_RES), + KIND_TRADE_LISTING_REFUND_REQ => Some(KIND_TRADE_LISTING_REFUND_RES), + _ => None, + } +} + +#[inline] +pub const fn trade_listing_request_kind_for_result(kind: u16) -> Option<u16> { + match kind { + KIND_TRADE_LISTING_ORDER_RES => Some(KIND_TRADE_LISTING_ORDER_REQ), + KIND_TRADE_LISTING_ACCEPT_RES => Some(KIND_TRADE_LISTING_ACCEPT_REQ), + KIND_TRADE_LISTING_CONVEYANCE_RES => Some(KIND_TRADE_LISTING_CONVEYANCE_REQ), + KIND_TRADE_LISTING_INVOICE_RES => Some(KIND_TRADE_LISTING_INVOICE_REQ), + KIND_TRADE_LISTING_PAYMENT_RES => Some(KIND_TRADE_LISTING_PAYMENT_REQ), + KIND_TRADE_LISTING_FULFILL_RES => Some(KIND_TRADE_LISTING_FULFILL_REQ), + KIND_TRADE_LISTING_RECEIPT_RES => Some(KIND_TRADE_LISTING_RECEIPT_REQ), + KIND_TRADE_LISTING_CANCEL_RES => Some(KIND_TRADE_LISTING_CANCEL_REQ), + KIND_TRADE_LISTING_REFUND_RES => Some(KIND_TRADE_LISTING_REFUND_REQ), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn request_to_result_roundtrip() { + let pairs = [ + (KIND_TRADE_LISTING_ORDER_REQ, KIND_TRADE_LISTING_ORDER_RES), + (KIND_TRADE_LISTING_ACCEPT_REQ, KIND_TRADE_LISTING_ACCEPT_RES), + ( + KIND_TRADE_LISTING_CONVEYANCE_REQ, + KIND_TRADE_LISTING_CONVEYANCE_RES, + ), + (KIND_TRADE_LISTING_INVOICE_REQ, KIND_TRADE_LISTING_INVOICE_RES), + (KIND_TRADE_LISTING_PAYMENT_REQ, KIND_TRADE_LISTING_PAYMENT_RES), + (KIND_TRADE_LISTING_FULFILL_REQ, KIND_TRADE_LISTING_FULFILL_RES), + (KIND_TRADE_LISTING_RECEIPT_REQ, KIND_TRADE_LISTING_RECEIPT_RES), + (KIND_TRADE_LISTING_CANCEL_REQ, KIND_TRADE_LISTING_CANCEL_RES), + (KIND_TRADE_LISTING_REFUND_REQ, KIND_TRADE_LISTING_REFUND_RES), + ]; + + for (req, res) in pairs { + assert_eq!(trade_listing_result_kind_for_request(req), Some(res)); + assert_eq!(trade_listing_request_kind_for_result(res), Some(req)); + assert!(is_trade_listing_request_kind(req)); + assert!(is_trade_listing_result_kind(res)); + } + } + + #[test] + fn request_to_result_rejects_non_trade_kinds() { + assert_eq!(trade_listing_result_kind_for_request(5000), None); + assert_eq!(trade_listing_request_kind_for_result(6000), None); + assert!(!is_trade_listing_request_kind(5000)); + assert!(!is_trade_listing_result_kind(6000)); + } +} diff --git a/trade/src/listing/meta.rs b/trade/src/listing/meta.rs @@ -1,3 +1,6 @@ +//! Trade listing metadata and NIP-90 lifecycle mapping. +//! See refs/nips/90.md for the base data vending machine flow. + use core::fmt; use core::str::FromStr; @@ -5,11 +8,27 @@ pub const MARKER_LISTING: &str = "listing"; pub const MARKER_PAYLOAD: &str = "payload"; pub const MARKER_PREVIOUS: &str = "previous"; +pub const MARKER_ORDER_RESULT: &str = "order_result"; pub const MARKER_ACCEPT_RESULT: &str = "accept_result"; +pub const MARKER_CONVEYANCE_RESULT: &str = "conveyance_result"; pub const MARKER_INVOICE_RESULT: &str = "invoice_result"; +pub const MARKER_PAYMENT_RESULT: &str = "payment_result"; pub const MARKER_FULFILLMENT_RESULT: &str = "fulfillment_result"; +pub const MARKER_RECEIPT_RESULT: &str = "receipt_result"; +pub const MARKER_CANCEL_RESULT: &str = "cancel_result"; +pub const MARKER_REFUND_RESULT: &str = "refund_result"; pub const MARKER_PROOF: &str = "proof"; +const MARKERS_ORDER_REQUEST: [&str; 2] = [MARKER_LISTING, MARKER_PAYLOAD]; +const MARKERS_ACCEPT_REQUEST: [&str; 2] = [MARKER_ORDER_RESULT, MARKER_LISTING]; +const MARKERS_CONVEYANCE_REQUEST: [&str; 2] = [MARKER_ACCEPT_RESULT, MARKER_PAYLOAD]; +const MARKERS_INVOICE_REQUEST: [&str; 1] = [MARKER_ACCEPT_RESULT]; +const MARKERS_PAYMENT_REQUEST: [&str; 2] = [MARKER_INVOICE_RESULT, MARKER_PROOF]; +const MARKERS_FULFILLMENT_REQUEST: [&str; 1] = [MARKER_PAYMENT_RESULT]; +const MARKERS_RECEIPT_REQUEST: [&str; 2] = [MARKER_FULFILLMENT_RESULT, MARKER_PAYLOAD]; +const MARKERS_CANCEL_REQUEST: [&str; 2] = [MARKER_PREVIOUS, MARKER_PAYLOAD]; +const MARKERS_REFUND_REQUEST: [&str; 2] = [MARKER_PAYMENT_RESULT, MARKER_PAYLOAD]; + #[cfg_attr(feature = "typeshare", typeshare::typeshare)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -44,6 +63,110 @@ impl TradeListingStage { TradeListingStage::Refund => "refund", } } + + #[inline] + pub const fn request_kind(&self) -> u16 { + match self { + TradeListingStage::Order => crate::listing::kinds::KIND_TRADE_LISTING_ORDER_REQ, + TradeListingStage::Accept => crate::listing::kinds::KIND_TRADE_LISTING_ACCEPT_REQ, + TradeListingStage::Conveyance => { + crate::listing::kinds::KIND_TRADE_LISTING_CONVEYANCE_REQ + } + TradeListingStage::Invoice => crate::listing::kinds::KIND_TRADE_LISTING_INVOICE_REQ, + TradeListingStage::Payment => crate::listing::kinds::KIND_TRADE_LISTING_PAYMENT_REQ, + TradeListingStage::Fulfillment => crate::listing::kinds::KIND_TRADE_LISTING_FULFILL_REQ, + TradeListingStage::Receipt => crate::listing::kinds::KIND_TRADE_LISTING_RECEIPT_REQ, + TradeListingStage::Cancel => crate::listing::kinds::KIND_TRADE_LISTING_CANCEL_REQ, + TradeListingStage::Refund => crate::listing::kinds::KIND_TRADE_LISTING_REFUND_REQ, + } + } + + #[inline] + pub const fn result_kind(&self) -> u16 { + self.request_kind() + 1000 + } + + #[inline] + pub const fn request_markers(&self) -> &'static [&'static str] { + match self { + TradeListingStage::Order => &MARKERS_ORDER_REQUEST, + TradeListingStage::Accept => &MARKERS_ACCEPT_REQUEST, + TradeListingStage::Conveyance => &MARKERS_CONVEYANCE_REQUEST, + TradeListingStage::Invoice => &MARKERS_INVOICE_REQUEST, + TradeListingStage::Payment => &MARKERS_PAYMENT_REQUEST, + TradeListingStage::Fulfillment => &MARKERS_FULFILLMENT_REQUEST, + TradeListingStage::Receipt => &MARKERS_RECEIPT_REQUEST, + TradeListingStage::Cancel => &MARKERS_CANCEL_REQUEST, + TradeListingStage::Refund => &MARKERS_REFUND_REQUEST, + } + } + + #[inline] + pub const fn result_marker(&self) -> &'static str { + match self { + TradeListingStage::Order => MARKER_ORDER_RESULT, + TradeListingStage::Accept => MARKER_ACCEPT_RESULT, + TradeListingStage::Conveyance => MARKER_CONVEYANCE_RESULT, + TradeListingStage::Invoice => MARKER_INVOICE_RESULT, + TradeListingStage::Payment => MARKER_PAYMENT_RESULT, + TradeListingStage::Fulfillment => MARKER_FULFILLMENT_RESULT, + TradeListingStage::Receipt => MARKER_RECEIPT_RESULT, + TradeListingStage::Cancel => MARKER_CANCEL_RESULT, + TradeListingStage::Refund => MARKER_REFUND_RESULT, + } + } + + #[inline] + pub const fn from_request_kind(kind: u16) -> Option<Self> { + match kind { + crate::listing::kinds::KIND_TRADE_LISTING_ORDER_REQ => Some(TradeListingStage::Order), + crate::listing::kinds::KIND_TRADE_LISTING_ACCEPT_REQ => Some(TradeListingStage::Accept), + crate::listing::kinds::KIND_TRADE_LISTING_CONVEYANCE_REQ => { + Some(TradeListingStage::Conveyance) + } + crate::listing::kinds::KIND_TRADE_LISTING_INVOICE_REQ => { + Some(TradeListingStage::Invoice) + } + crate::listing::kinds::KIND_TRADE_LISTING_PAYMENT_REQ => { + Some(TradeListingStage::Payment) + } + crate::listing::kinds::KIND_TRADE_LISTING_FULFILL_REQ => { + Some(TradeListingStage::Fulfillment) + } + crate::listing::kinds::KIND_TRADE_LISTING_RECEIPT_REQ => { + Some(TradeListingStage::Receipt) + } + crate::listing::kinds::KIND_TRADE_LISTING_CANCEL_REQ => Some(TradeListingStage::Cancel), + crate::listing::kinds::KIND_TRADE_LISTING_REFUND_REQ => Some(TradeListingStage::Refund), + _ => None, + } + } + + #[inline] + pub const fn from_result_kind(kind: u16) -> Option<Self> { + match kind { + crate::listing::kinds::KIND_TRADE_LISTING_ORDER_RES => Some(TradeListingStage::Order), + crate::listing::kinds::KIND_TRADE_LISTING_ACCEPT_RES => Some(TradeListingStage::Accept), + crate::listing::kinds::KIND_TRADE_LISTING_CONVEYANCE_RES => { + Some(TradeListingStage::Conveyance) + } + crate::listing::kinds::KIND_TRADE_LISTING_INVOICE_RES => { + Some(TradeListingStage::Invoice) + } + crate::listing::kinds::KIND_TRADE_LISTING_PAYMENT_RES => { + Some(TradeListingStage::Payment) + } + crate::listing::kinds::KIND_TRADE_LISTING_FULFILL_RES => { + Some(TradeListingStage::Fulfillment) + } + crate::listing::kinds::KIND_TRADE_LISTING_RECEIPT_RES => { + Some(TradeListingStage::Receipt) + } + crate::listing::kinds::KIND_TRADE_LISTING_CANCEL_RES => Some(TradeListingStage::Cancel), + crate::listing::kinds::KIND_TRADE_LISTING_REFUND_RES => Some(TradeListingStage::Refund), + _ => None, + } + } } impl fmt::Display for TradeListingStage { @@ -91,7 +214,11 @@ impl FromStr for TradeListingStage { #[cfg(test)] mod tests { - use super::{TradeListingStage, TradeListingStageParseError}; + use super::{ + MARKER_CONVEYANCE_RESULT, MARKER_FULFILLMENT_RESULT, MARKER_INVOICE_RESULT, MARKER_LISTING, + MARKER_ORDER_RESULT, MARKER_PAYLOAD, MARKER_PAYMENT_RESULT, MARKER_PROOF, + MARKER_RECEIPT_RESULT, TradeListingStage, TradeListingStageParseError, + }; #[test] fn stage_roundtrip() { @@ -119,4 +246,71 @@ mod tests { let err = "unknown".parse::<TradeListingStage>().unwrap_err(); assert_eq!(err, TradeListingStageParseError::UnknownStage); } + + #[test] + fn stage_kinds_follow_nip_90() { + let cases = [ + TradeListingStage::Order, + TradeListingStage::Accept, + TradeListingStage::Conveyance, + TradeListingStage::Invoice, + TradeListingStage::Payment, + TradeListingStage::Fulfillment, + TradeListingStage::Receipt, + TradeListingStage::Cancel, + TradeListingStage::Refund, + ]; + + for stage in cases { + assert_eq!(stage.result_kind(), stage.request_kind() + 1000); + assert_eq!( + TradeListingStage::from_request_kind(stage.request_kind()), + Some(stage) + ); + assert_eq!( + TradeListingStage::from_result_kind(stage.result_kind()), + Some(stage) + ); + } + } + + #[test] + fn stage_markers_cover_expected_inputs() { + assert_eq!( + TradeListingStage::Order.request_markers(), + &[MARKER_LISTING, MARKER_PAYLOAD] + ); + assert_eq!( + TradeListingStage::Accept.request_markers(), + &[MARKER_ORDER_RESULT, MARKER_LISTING] + ); + assert_eq!( + TradeListingStage::Payment.request_markers(), + &[MARKER_INVOICE_RESULT, MARKER_PROOF] + ); + assert_eq!( + TradeListingStage::Fulfillment.request_markers(), + &[MARKER_PAYMENT_RESULT] + ); + assert_eq!( + TradeListingStage::Receipt.request_markers(), + &[MARKER_FULFILLMENT_RESULT, MARKER_PAYLOAD] + ); + assert_eq!( + TradeListingStage::Refund.request_markers(), + &[MARKER_PAYMENT_RESULT, MARKER_PAYLOAD] + ); + assert_eq!( + TradeListingStage::Order.result_marker(), + MARKER_ORDER_RESULT + ); + assert_eq!( + TradeListingStage::Conveyance.result_marker(), + MARKER_CONVEYANCE_RESULT + ); + assert_eq!( + TradeListingStage::Receipt.result_marker(), + MARKER_RECEIPT_RESULT + ); + } }