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:
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
+ );
+ }
}