commit a993592640d581277b5bf9c6eae36682cebbe2a2
parent a3ae988c8c2aae8abf304cf64b7a26631235815e
Author: triesap <tyson@radroots.org>
Date: Tue, 3 Mar 2026 22:32:14 +0000
refactor: remove orphan modules and normalize feature manifest
Diffstat:
12 files changed, 2 insertions(+), 1013 deletions(-)
diff --git a/src/features/mod.rs b/src/features/mod.rs
@@ -0,0 +1 @@
+pub mod trade_listing;
diff --git a/src/features/trade_listing/domain/mod.rs b/src/features/trade_listing/domain/mod.rs
@@ -1 +0,0 @@
-pub mod pricing;
diff --git a/src/features/trade_listing/domain/pricing.rs b/src/features/trade_listing/domain/pricing.rs
@@ -1,71 +0,0 @@
-use radroots_events::listing::RadrootsListing;
-use radroots_trade::prelude::price_ext::BinPricingTryExt;
-use radroots_trade::prelude::stage::order::{
- TradeListingOrderRequestPayload, TradeListingOrderResult,
-};
-
-use crate::features::trade_listing::handlers::order::JobRequestOrderError;
-
-pub trait ListingOrderCalculator {
- fn calculate_order(
- &self,
- order: &TradeListingOrderRequestPayload,
- ) -> Result<TradeListingOrderResult, JobRequestOrderError>;
-}
-
-impl ListingOrderCalculator for RadrootsListing {
- fn calculate_order(
- &self,
- order: &TradeListingOrderRequestPayload,
- ) -> Result<TradeListingOrderResult, JobRequestOrderError> {
- if order.bin_id.trim().is_empty() {
- return Err(JobRequestOrderError::Unsatisfiable(format!(
- "requested bin id is empty"
- )));
- }
-
- if order.bin_count == 0 {
- return Err(JobRequestOrderError::Unsatisfiable(
- "requested bin count must be greater than 0".to_string(),
- ));
- }
-
- let bin = self
- .bins
- .iter()
- .find(|bin| bin.bin_id == order.bin_id)
- .ok_or_else(|| {
- JobRequestOrderError::Unsatisfiable(format!(
- "requested bin {} not available",
- order.bin_id
- ))
- })?;
-
- let out_price = bin.price_per_canonical_unit.clone();
- let out_subtotal = bin
- .try_subtotal_for_count(order.bin_count)
- .map_err(|err| {
- JobRequestOrderError::Unsatisfiable(format!(
- "failed to price requested bin: {err}"
- ))
- })?;
- let out_total = bin
- .try_total_for_count(order.bin_count)
- .map_err(|err| {
- JobRequestOrderError::Unsatisfiable(format!(
- "failed to total requested bin: {err}"
- ))
- })?;
-
- let discounts_out = self.discounts.clone().unwrap_or_default();
-
- Ok(TradeListingOrderResult {
- bin_id: order.bin_id.clone(),
- bin_count: order.bin_count,
- price: out_price,
- discounts: discounts_out,
- subtotal: out_subtotal,
- total: out_total,
- })
- }
-}
diff --git a/src/features/trade_listing/handlers/accept.rs b/src/features/trade_listing/handlers/accept.rs
@@ -1,148 +0,0 @@
-use radroots_nostr::prelude::{
- radroots_nostr_build_event,
- radroots_nostr_fetch_event_by_id,
- radroots_nostr_send_event,
- RadrootsNostrClient,
- RadrootsNostrEvent,
- RadrootsNostrKind,
- RadrootsNostrKeys,
-};
-use radroots_events_codec::job::{result::encode::job_result_build_tags, traits::JobEventBorrow};
-use thiserror::Error;
-use tracing::info;
-
-use radroots_events::{
- RadrootsNostrEventPtr,
- job::JobPaymentRequest,
- job_request::RadrootsJobInput,
- job_result::RadrootsJobResult,
- kinds::result_kind_for_request_kind,
- tags::{TAG_D, TAG_E_ROOT},
-};
-use radroots_trade::{
- listing::{
- kinds::{KIND_TRADE_LISTING_ACCEPT_RES, KIND_TRADE_LISTING_ORDER_RES},
- tags::push_trade_listing_chain_tags,
- },
- prelude::stage::accept::{TradeListingAcceptRequest, TradeListingAcceptResult},
-};
-
-use crate::{
- adapters::nostr::event::NostrEventAdapter,
- features::trade_listing::subscriber::{JobRequestCtx, JobRequestError},
-};
-
-#[derive(Debug, Error)]
-pub enum JobRequestAcceptError {
- #[error("Failed to parse accept request: {0}")]
- ParseRequest(String),
- #[error("Failed to fetch reference event: {0}")]
- FetchReference(String),
- #[error("Reference event not found: {0}")]
- MissingReference(String),
- #[error("Unauthorized: accepting profile must own the listing")]
- Unauthorized,
- #[error("Order result not kind 6301 or listing mismatch")]
- InvalidOrderResult,
- #[error("Failed to send job response")]
- ResponseSend(#[from] radroots_nostr::error::RadrootsNostrError),
-}
-
-pub async fn handle_job_request_trade_accept(
- event_job_request: RadrootsNostrEvent,
- keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- job_req: JobRequestCtx,
- job_req_input: RadrootsJobInput,
-) -> Result<(), JobRequestError> {
- let ev = NostrEventAdapter::new(&event_job_request);
-
- let req: TradeListingAcceptRequest = serde_json::from_str(&job_req_input.data)
- .map_err(|e| JobRequestAcceptError::ParseRequest(e.to_string()))?;
-
- let order_res_evt = radroots_nostr_fetch_event_by_id(client.clone(), &req.order_result_event_id)
- .await
- .map_err(|_| JobRequestAcceptError::FetchReference(req.order_result_event_id.clone()))?;
-
- let listing_evt = radroots_nostr_fetch_event_by_id(client.clone(), &req.listing_event_id)
- .await
- .map_err(|_| JobRequestAcceptError::FetchReference(req.listing_event_id.clone()))?;
-
- if listing_evt.pubkey != keys.public_key() {
- return Err(JobRequestAcceptError::Unauthorized.into());
- }
-
- if order_res_evt.kind != RadrootsNostrKind::Custom(KIND_TRADE_LISTING_ORDER_RES) {
- return Err(JobRequestAcceptError::InvalidOrderResult.into());
- }
- let order_refs_listing = order_res_evt.tags.iter().any(|t| {
- let s = t.as_slice();
- s.get(0).map(|k| k.as_str()) == Some(TAG_E_ROOT)
- && s.get(1).map(String::as_str) == Some(req.listing_event_id.as_str())
- });
- if !order_refs_listing {
- return Err(JobRequestAcceptError::InvalidOrderResult.into());
- }
-
- let accept_result = TradeListingAcceptResult {
- listing_event_id: req.listing_event_id.clone(),
- order_result_event_id: req.order_result_event_id.clone(),
- accepted_by: keys.public_key().to_string(),
- };
- let payload_json = serde_json::to_string(&accept_result)?;
-
- let result_kind = result_kind_for_request_kind(job_req.model.kind as u32)
- .unwrap_or(job_req.model.kind as u32 + 1000);
- debug_assert_eq!(result_kind as u16, KIND_TRADE_LISTING_ACCEPT_RES);
-
- let result_model = RadrootsJobResult {
- kind: result_kind as u16,
- request_event: RadrootsNostrEventPtr {
- id: ev.raw_id().to_string(),
- relays: None,
- },
- request_json: Some(serde_json::to_string(&job_req.model)?),
- inputs: job_req.model.inputs.clone(),
- customer_pubkey: Some(ev.raw_author().to_string()),
- payment: None::<JobPaymentRequest>,
- content: Some(payload_json.clone()),
- encrypted: false,
- };
-
- let mut tag_slices = job_result_build_tags(&result_model);
-
- let e_root = order_res_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_E_ROOT)).then(|| s.get(1).cloned())
- })
- .flatten()
- .unwrap_or_else(|| req.listing_event_id.clone());
-
- let trade_id = order_res_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_D)).then(|| s.get(1).cloned())
- })
- .flatten();
-
- push_trade_listing_chain_tags(
- &mut tag_slices,
- e_root.clone(),
- Some(req.order_result_event_id.clone()),
- trade_id,
- );
-
- let builder = radroots_nostr_build_event(result_kind as u32, payload_json, tag_slices)?;
- let job_result_event_id = radroots_nostr_send_event(client, builder).await?;
-
- info!(
- "job request trade/accept ({}={}) result sent: {:?}",
- TAG_E_ROOT, e_root, job_result_event_id
- );
- Ok(())
-}
diff --git a/src/features/trade_listing/handlers/conveyance.rs b/src/features/trade_listing/handlers/conveyance.rs
@@ -1,126 +0,0 @@
-use radroots_nostr::prelude::{
- radroots_nostr_build_event,
- radroots_nostr_fetch_event_by_id,
- radroots_nostr_send_event,
- RadrootsNostrClient,
- RadrootsNostrEvent,
- RadrootsNostrKind,
- RadrootsNostrKeys,
-};
-use radroots_events_codec::job::{result::encode::job_result_build_tags, traits::JobEventBorrow};
-use thiserror::Error;
-use tracing::info;
-
-use radroots_events::{
- RadrootsNostrEventPtr,
- job::JobPaymentRequest,
- job_request::RadrootsJobInput,
- job_result::RadrootsJobResult,
- kinds::result_kind_for_request_kind,
- tags::{TAG_D, TAG_E_ROOT},
-};
-use radroots_trade::{
- listing::{kinds::KIND_TRADE_LISTING_ACCEPT_RES, tags::push_trade_listing_chain_tags},
- prelude::stage::conveyance::{TradeListingConveyanceRequest, TradeListingConveyanceResult},
-};
-
-use crate::{
- adapters::nostr::event::NostrEventAdapter,
- features::trade_listing::subscriber::{JobRequestCtx, JobRequestError},
-};
-
-#[derive(Debug, Error)]
-pub enum JobRequestConveyanceError {
- #[error("Failed to parse conveyance request: {0}")]
- ParseRequest(String),
- #[error("Failed to fetch reference event: {0}")]
- FetchReference(String),
- #[error("Reference event not found: {0}")]
- MissingReference(String),
- #[error("Invalid accept result kind")]
- InvalidAcceptKind,
- #[error("Failed to send job response")]
- ResponseSend(#[from] radroots_nostr::error::RadrootsNostrError),
-}
-
-pub async fn handle_job_request_trade_conveyance(
- event_job_request: RadrootsNostrEvent,
- _keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- job_req: JobRequestCtx,
- job_req_input: RadrootsJobInput,
-) -> Result<(), JobRequestError> {
- let ev = NostrEventAdapter::new(&event_job_request);
-
- let req: TradeListingConveyanceRequest = serde_json::from_str(&job_req_input.data)
- .map_err(|e| JobRequestConveyanceError::ParseRequest(e.to_string()))?;
-
- let accept_evt = radroots_nostr_fetch_event_by_id(client.clone(), &req.accept_result_event_id)
- .await
- .map_err(|_| {
- JobRequestConveyanceError::FetchReference(req.accept_result_event_id.clone())
- })?;
- if accept_evt.kind != RadrootsNostrKind::Custom(KIND_TRADE_LISTING_ACCEPT_RES) {
- return Err(JobRequestConveyanceError::InvalidAcceptKind.into());
- }
-
- let conv_res = TradeListingConveyanceResult {
- verified: true,
- method: req.method,
- message: Some("conveyance method verified".into()),
- };
- let payload_json = serde_json::to_string(&conv_res)?;
-
- let result_kind = result_kind_for_request_kind(job_req.model.kind as u32)
- .unwrap_or(job_req.model.kind as u32 + 1000);
-
- let result_model = RadrootsJobResult {
- kind: result_kind as u16,
- request_event: RadrootsNostrEventPtr {
- id: ev.raw_id().to_string(),
- relays: None,
- },
- request_json: Some(serde_json::to_string(&job_req.model)?),
- inputs: job_req.model.inputs.clone(),
- customer_pubkey: Some(ev.raw_author().to_string()),
- payment: None::<JobPaymentRequest>,
- content: Some(payload_json.clone()),
- encrypted: false,
- };
-
- let mut tag_slices = job_result_build_tags(&result_model);
-
- let e_root = accept_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_E_ROOT)).then(|| s.get(1).cloned())
- })
- .flatten();
-
- let d_tag = accept_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_D)).then(|| s.get(1).cloned())
- })
- .flatten();
-
- push_trade_listing_chain_tags(
- &mut tag_slices,
- e_root.clone().unwrap_or_default(),
- Some(req.accept_result_event_id.clone()),
- d_tag,
- );
-
- let builder = radroots_nostr_build_event(result_kind as u32, payload_json, tag_slices)?;
- let job_result_event_id = radroots_nostr_send_event(client, builder).await?;
-
- info!(
- "job request trade/conveyance ({}={:?}) result sent: {:?}",
- TAG_E_ROOT, e_root, job_result_event_id
- );
- Ok(())
-}
diff --git a/src/features/trade_listing/handlers/fulfillment.rs b/src/features/trade_listing/handlers/fulfillment.rs
@@ -1,132 +0,0 @@
-use radroots_nostr::prelude::{
- radroots_nostr_build_event,
- radroots_nostr_fetch_event_by_id,
- radroots_nostr_send_event,
- RadrootsNostrClient,
- RadrootsNostrEvent,
- RadrootsNostrKind,
- RadrootsNostrKeys,
-};
-use radroots_events_codec::job::{result::encode::job_result_build_tags, traits::JobEventBorrow};
-use thiserror::Error;
-use tracing::info;
-
-use radroots_events::{
- RadrootsNostrEventPtr,
- job_request::RadrootsJobInput,
- job_result::RadrootsJobResult,
- kinds::result_kind_for_request_kind,
- tags::{TAG_D, TAG_E_ROOT},
-};
-use radroots_trade::{
- listing::tags::push_trade_listing_chain_tags,
- prelude::{
- kinds::KIND_TRADE_LISTING_PAYMENT_RES,
- stage::fulfillment::{
- TradeListingFulfillmentRequest, TradeListingFulfillmentResult,
- TradeListingFulfillmentState,
- },
- },
-};
-
-use crate::{
- adapters::nostr::event::NostrEventAdapter,
- features::trade_listing::subscriber::{JobRequestCtx, JobRequestError},
-};
-
-#[derive(Debug, Error)]
-pub enum JobRequestFulfillmentError {
- #[error("Failed to parse fulfillment request: {0}")]
- ParseRequest(String),
- #[error("Failed to fetch reference event: {0}")]
- FetchReference(String),
- #[error("Reference event not found: {0}")]
- MissingReference(String),
- #[error("Payment result not kind 6305 or missing chain")]
- InvalidPayment,
- #[error("Failed to send job response")]
- ResponseSend(#[from] radroots_nostr::error::RadrootsNostrError),
-}
-
-pub async fn handle_job_request_trade_fulfillment(
- event_job_request: RadrootsNostrEvent,
- _keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- job_req: JobRequestCtx,
- job_req_input: RadrootsJobInput,
-) -> Result<(), JobRequestError> {
- let ev = NostrEventAdapter::new(&event_job_request);
-
- let req: TradeListingFulfillmentRequest = serde_json::from_str(&job_req_input.data)
- .map_err(|e| JobRequestFulfillmentError::ParseRequest(e.to_string()))?;
-
- let payment_evt = radroots_nostr_fetch_event_by_id(client.clone(), &req.payment_result_event_id)
- .await
- .map_err(|_| {
- JobRequestFulfillmentError::FetchReference(req.payment_result_event_id.clone())
- })?;
- if payment_evt.kind != RadrootsNostrKind::Custom(KIND_TRADE_LISTING_PAYMENT_RES) {
- return Err(JobRequestFulfillmentError::InvalidPayment.into());
- }
-
- let e_root = payment_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_E_ROOT)).then(|| s.get(1).cloned())
- })
- .flatten()
- .ok_or(JobRequestFulfillmentError::InvalidPayment)?;
-
- let d_tag = payment_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_D)).then(|| s.get(1).cloned())
- })
- .flatten();
-
- let status = TradeListingFulfillmentResult {
- state: TradeListingFulfillmentState::Preparing,
- tracking: None,
- eta: None,
- notes: Some("order accepted and paid; preparing shipment".into()),
- };
- let payload_json = serde_json::to_string(&status)?;
-
- let result_kind = result_kind_for_request_kind(job_req.model.kind as u32)
- .unwrap_or(job_req.model.kind as u32 + 1000);
-
- let result_model = RadrootsJobResult {
- kind: result_kind as u16,
- request_event: RadrootsNostrEventPtr {
- id: ev.raw_id().to_string(),
- relays: None,
- },
- request_json: Some(serde_json::to_string(&job_req.model)?),
- inputs: job_req.model.inputs.clone(),
- customer_pubkey: Some(ev.raw_author().to_string()),
- payment: None,
- content: Some(payload_json.clone()),
- encrypted: false,
- };
-
- let mut tag_slices = job_result_build_tags(&result_model);
- push_trade_listing_chain_tags(
- &mut tag_slices,
- e_root.clone(),
- Some(req.payment_result_event_id.clone()),
- d_tag,
- );
-
- let builder = radroots_nostr_build_event(result_kind as u32, payload_json, tag_slices)?;
- let job_result_event_id = radroots_nostr_send_event(client, builder).await?;
-
- info!(
- "job request trade/fulfillment ({}={}) result sent: {:?}",
- TAG_E_ROOT, e_root, job_result_event_id
- );
- Ok(())
-}
diff --git a/src/features/trade_listing/handlers/invoice.rs b/src/features/trade_listing/handlers/invoice.rs
@@ -1,169 +0,0 @@
-use radroots_nostr::prelude::{
- radroots_nostr_build_event,
- radroots_nostr_fetch_event_by_id,
- radroots_nostr_send_event,
- RadrootsNostrClient,
- RadrootsNostrEvent,
- RadrootsNostrKind,
- RadrootsNostrKeys,
-};
-use radroots_events_codec::job::{result::encode::job_result_build_tags, traits::JobEventBorrow};
-use thiserror::Error;
-use tracing::info;
-
-use radroots_events::{
- RadrootsNostrEventPtr,
- job::JobPaymentRequest,
- job_request::{RadrootsJobInput, RadrootsJobParam},
- job_result::RadrootsJobResult,
- kinds::result_kind_for_request_kind,
- tags::{TAG_D, TAG_E_PREV, TAG_E_ROOT},
-};
-use radroots_trade::{
- listing::tags::push_trade_listing_chain_tags,
- prelude::{
- kinds::{
- KIND_TRADE_LISTING_ACCEPT_RES, KIND_TRADE_LISTING_INVOICE_RES,
- KIND_TRADE_LISTING_ORDER_RES,
- },
- stage::invoice::{TradeListingInvoiceRequest, TradeListingInvoiceResult},
- },
-};
-
-use crate::{
- adapters::nostr::event::NostrEventAdapter,
- features::trade_listing::subscriber::{JobRequestCtx, JobRequestError},
-};
-
-#[derive(Debug, Error)]
-pub enum JobRequestInvoiceError {
- #[error("Failed to parse invoice request: {0}")]
- ParseRequest(String),
- #[error("Failed to fetch reference event: {0}")]
- FetchReference(String),
- #[error("Reference event not found: {0}")]
- MissingReference(String),
- #[error("Accept result not kind 6302 or missing chain")]
- InvalidAccept,
- #[error("Failed to send job response")]
- ResponseSend(#[from] radroots_nostr::error::RadrootsNostrError),
-}
-
-fn param_lookup<'a>(params: &'a [RadrootsJobParam], key: &str) -> Option<&'a str> {
- params
- .iter()
- .find(|p| p.key == key)
- .map(|p| p.value.as_str())
-}
-
-pub async fn handle_job_request_trade_invoice(
- event_job_request: RadrootsNostrEvent,
- _keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- job_req: JobRequestCtx,
- job_req_input: RadrootsJobInput,
-) -> Result<(), JobRequestError> {
- let ev = NostrEventAdapter::new(&event_job_request);
-
- let req: TradeListingInvoiceRequest = serde_json::from_str(&job_req_input.data)
- .map_err(|e| JobRequestInvoiceError::ParseRequest(e.to_string()))?;
-
- let accept_evt = radroots_nostr_fetch_event_by_id(client.clone(), &req.accept_result_event_id)
- .await
- .map_err(|_| JobRequestInvoiceError::FetchReference(req.accept_result_event_id.clone()))?;
- if accept_evt.kind != RadrootsNostrKind::Custom(KIND_TRADE_LISTING_ACCEPT_RES) {
- return Err(JobRequestInvoiceError::InvalidAccept.into());
- }
-
- let e_root = accept_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_E_ROOT)).then(|| s.get(1).cloned())
- })
- .flatten()
- .ok_or(JobRequestInvoiceError::InvalidAccept)?;
-
- let d_tag = accept_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_D)).then(|| s.get(1).cloned())
- })
- .flatten();
-
- let order_res_id = accept_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_E_PREV)).then(|| s.get(1).cloned())
- })
- .flatten();
-
- if let Some(prev_id) = &order_res_id {
- if let Ok(prev_evt) = radroots_nostr_fetch_event_by_id(client.clone(), prev_id).await {
- if prev_evt.kind != RadrootsNostrKind::Custom(KIND_TRADE_LISTING_ORDER_RES) {}
- }
- }
-
- let amount_sat = param_lookup(&job_req.model.params, "amount_sat")
- .and_then(|v| v.parse::<u32>().ok())
- .or_else(|| {
- param_lookup(&job_req.model.params, "amount_msat")
- .and_then(|v| v.parse::<u64>().ok())
- .map(|msat| (msat / 1000) as u32)
- })
- .unwrap_or(0);
-
- let bolt11 = param_lookup(&job_req.model.params, "bolt11").map(|s| s.to_string());
- let note = param_lookup(&job_req.model.params, "note").map(|s| s.to_string());
- let expires_at =
- param_lookup(&job_req.model.params, "expires_at").and_then(|v| v.parse::<u32>().ok());
-
- let invoice = TradeListingInvoiceResult {
- total_sat: amount_sat,
- bolt11: bolt11.clone(),
- note,
- expires_at,
- };
- let payload_json = serde_json::to_string(&invoice)?;
-
- let result_kind = result_kind_for_request_kind(job_req.model.kind as u32)
- .unwrap_or(job_req.model.kind as u32 + 1000);
- debug_assert_eq!(result_kind as u16, KIND_TRADE_LISTING_INVOICE_RES);
-
- let result_model = RadrootsJobResult {
- kind: result_kind as u16,
- request_event: RadrootsNostrEventPtr {
- id: ev.raw_id().to_string(),
- relays: None,
- },
- request_json: Some(serde_json::to_string(&job_req.model)?),
- inputs: job_req.model.inputs.clone(),
- customer_pubkey: Some(ev.raw_author().to_string()),
- payment: Some(JobPaymentRequest { amount_sat, bolt11 }),
- content: Some(payload_json.clone()),
- encrypted: false,
- };
-
- let mut tag_slices = job_result_build_tags(&result_model);
-
- push_trade_listing_chain_tags(
- &mut tag_slices,
- e_root.clone(),
- Some(req.accept_result_event_id.clone()),
- d_tag,
- );
-
- let builder = radroots_nostr_build_event(result_kind as u32, payload_json, tag_slices)?;
- let job_result_event_id = radroots_nostr_send_event(client, builder).await?;
-
- info!(
- "job request trade/invoice ({}={}) result sent: {:?}",
- TAG_E_ROOT, e_root, job_result_event_id
- );
- Ok(())
-}
diff --git a/src/features/trade_listing/handlers/order.rs b/src/features/trade_listing/handlers/order.rs
@@ -1,111 +0,0 @@
-use radroots_nostr::prelude::{
- radroots_nostr_build_event,
- radroots_nostr_fetch_event_by_id,
- radroots_nostr_send_event,
- RadrootsNostrClient,
- RadrootsNostrEvent,
- RadrootsNostrKeys,
-};
-use radroots_events_codec::job::{result::encode::job_result_build_tags, traits::JobEventBorrow};
-use thiserror::Error;
-use tracing::info;
-
-use radroots_events::{
- RadrootsNostrEventPtr,
- job::JobPaymentRequest,
- job_request::RadrootsJobInput,
- job_result::RadrootsJobResult,
- kinds::result_kind_for_request_kind,
- listing::RadrootsListing,
-};
-use radroots_trade::prelude::{
- kinds::KIND_TRADE_LISTING_ORDER_RES, stage::order::TradeListingOrderRequest, tags,
-};
-
-use crate::{
- adapters::nostr::event::NostrEventAdapter,
- features::trade_listing::{
- domain::pricing::ListingOrderCalculator,
- subscriber::{JobRequestCtx, JobRequestError},
- },
-};
-
-#[derive(Debug, Error)]
-pub enum JobRequestOrderError {
- #[error("Failed to parse reference event: {0}")]
- ParseReference(String),
- #[error("Failed to fetch reference event: {0}")]
- FetchReference(String),
- #[error("Reference event not found: {0}")]
- MissingReference(String),
- #[error("Reference event does not meet request requirements: {0}")]
- MissingRequested(String),
- #[error("Failed to send job response")]
- ResponseSend(#[from] radroots_nostr::error::RadrootsNostrError),
- #[error("Request cannot be satisfied: {0}")]
- Unsatisfiable(String),
-}
-
-pub async fn handle_job_request_trade_order(
- event_job_request: RadrootsNostrEvent,
- _keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- job_req: JobRequestCtx,
- job_req_input: RadrootsJobInput,
-) -> Result<(), JobRequestError> {
- let ev = NostrEventAdapter::new(&event_job_request);
-
- let order_data: TradeListingOrderRequest = serde_json::from_str(&job_req_input.data)
- .map_err(|e| JobRequestOrderError::ParseReference(e.to_string()))?;
-
- let ref_id = &order_data.event.id;
- let ref_event = radroots_nostr_fetch_event_by_id(client.clone(), ref_id)
- .await
- .map_err(|_| JobRequestOrderError::FetchReference(ref_id.clone()))?;
-
- let listing: RadrootsListing = serde_json::from_str(&ref_event.content).map_err(|_| {
- JobRequestOrderError::ParseReference(format!("invalid listing content for {}", ref_id))
- })?;
-
- let order_result = listing.calculate_order(&order_data.payload)?;
-
- let result_kind = result_kind_for_request_kind(job_req.model.kind as u32)
- .unwrap_or(job_req.model.kind as u32 + 1000);
- debug_assert_eq!(result_kind as u16, KIND_TRADE_LISTING_ORDER_RES as u16);
-
- let payload_json = serde_json::to_string(&order_result)?;
-
- let result_model = RadrootsJobResult {
- kind: result_kind as u16,
- request_event: RadrootsNostrEventPtr {
- id: ev.raw_id().to_string(),
- relays: None,
- },
- request_json: Some(serde_json::to_string(&job_req.model)?),
- inputs: job_req.model.inputs.clone(),
- customer_pubkey: Some(ev.raw_author().to_string()),
- payment: None::<JobPaymentRequest>,
- content: Some(payload_json.clone()),
- encrypted: false,
- };
-
- let mut tag_slices = job_result_build_tags(&result_model);
-
- let e_root = ref_event.id.to_hex();
- let trade_id = format!("trade:{}:{}", e_root, event_job_request.id.to_hex());
- tags::push_trade_listing_chain_tags(
- &mut tag_slices,
- e_root.clone(),
- None::<String>,
- Some(trade_id.clone()),
- );
-
- let builder = radroots_nostr_build_event(result_kind as u32, payload_json, tag_slices)?;
- let job_result_event_id = radroots_nostr_send_event(client, builder).await?;
-
- info!(
- "job request trade/order (e_root={}) result sent: {:?}",
- e_root, job_result_event_id
- );
- Ok(())
-}
diff --git a/src/features/trade_listing/handlers/payment.rs b/src/features/trade_listing/handlers/payment.rs
@@ -1,123 +0,0 @@
-use radroots_nostr::prelude::{
- radroots_nostr_build_event,
- radroots_nostr_fetch_event_by_id,
- radroots_nostr_send_event,
- RadrootsNostrClient,
- RadrootsNostrEvent,
- RadrootsNostrKind,
- RadrootsNostrKeys,
-};
-use radroots_events_codec::job::{result::encode::job_result_build_tags, traits::JobEventBorrow};
-use thiserror::Error;
-use tracing::info;
-
-use radroots_events::{
- RadrootsNostrEventPtr,
- job_request::RadrootsJobInput,
- job_result::RadrootsJobResult,
- kinds::result_kind_for_request_kind,
- tags::{TAG_D, TAG_E_ROOT},
-};
-use radroots_trade::prelude::{
- kinds::KIND_TRADE_LISTING_INVOICE_RES,
- stage::payment::{TradeListingPaymentProofRequest, TradeListingPaymentResult},
- tags::push_trade_listing_chain_tags,
-};
-
-use crate::{
- adapters::nostr::event::NostrEventAdapter,
- features::trade_listing::subscriber::{JobRequestCtx, JobRequestError},
-};
-
-#[derive(Debug, Error)]
-pub enum JobRequestPaymentError {
- #[error("Failed to parse payment request: {0}")]
- ParseRequest(String),
- #[error("Failed to fetch reference event: {0}")]
- FetchReference(String),
- #[error("Reference event not found: {0}")]
- MissingReference(String),
- #[error("Invoice result not kind 6304 or missing chain")]
- InvalidInvoice,
- #[error("Failed to send job response")]
- ResponseSend(#[from] radroots_nostr::error::RadrootsNostrError),
-}
-
-pub async fn handle_job_request_trade_payment(
- event_job_request: RadrootsNostrEvent,
- _keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- job_req: JobRequestCtx,
- job_req_input: RadrootsJobInput,
-) -> Result<(), JobRequestError> {
- let ev = NostrEventAdapter::new(&event_job_request);
-
- let req: TradeListingPaymentProofRequest = serde_json::from_str(&job_req_input.data)
- .map_err(|e| JobRequestPaymentError::ParseRequest(e.to_string()))?;
-
- let invoice_evt = radroots_nostr_fetch_event_by_id(client.clone(), &req.invoice_result_event_id)
- .await
- .map_err(|_| JobRequestPaymentError::FetchReference(req.invoice_result_event_id.clone()))?;
- if invoice_evt.kind != RadrootsNostrKind::Custom(KIND_TRADE_LISTING_INVOICE_RES) {
- return Err(JobRequestPaymentError::InvalidInvoice.into());
- }
-
- let e_root = invoice_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_E_ROOT)).then(|| s.get(1).cloned())
- })
- .flatten()
- .ok_or(JobRequestPaymentError::InvalidInvoice)?;
-
- let d_tag = invoice_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_D)).then(|| s.get(1).cloned())
- })
- .flatten();
-
- let ack = TradeListingPaymentResult {
- verified: true,
- message: Some("payment proof accepted".into()),
- };
- let payload_json = serde_json::to_string(&ack)?;
-
- let result_kind = result_kind_for_request_kind(job_req.model.kind as u32)
- .unwrap_or(job_req.model.kind as u32 + 1000);
-
- let result_model = RadrootsJobResult {
- kind: result_kind as u16,
- request_event: RadrootsNostrEventPtr {
- id: ev.raw_id().to_string(),
- relays: None,
- },
- request_json: Some(serde_json::to_string(&job_req.model)?),
- inputs: job_req.model.inputs.clone(),
- customer_pubkey: Some(ev.raw_author().to_string()),
- payment: None,
- content: Some(payload_json.clone()),
- encrypted: false,
- };
-
- let mut tag_slices = job_result_build_tags(&result_model);
- push_trade_listing_chain_tags(
- &mut tag_slices,
- e_root.clone(),
- Some(req.invoice_result_event_id.clone()),
- d_tag,
- );
-
- let builder = radroots_nostr_build_event(result_kind as u32, payload_json, tag_slices)?;
- let job_result_event_id = radroots_nostr_send_event(client, builder).await?;
-
- info!(
- "job request trade/payment ({}={}) result sent: {:?}",
- TAG_E_ROOT, e_root, job_result_event_id
- );
- Ok(())
-}
diff --git a/src/features/trade_listing/handlers/receipt.rs b/src/features/trade_listing/handlers/receipt.rs
@@ -1,126 +0,0 @@
-use radroots_nostr::prelude::{
- radroots_nostr_build_event,
- radroots_nostr_fetch_event_by_id,
- radroots_nostr_send_event,
- RadrootsNostrClient,
- RadrootsNostrEvent,
- RadrootsNostrKind,
- RadrootsNostrKeys,
-};
-use radroots_events_codec::job::{result::encode::job_result_build_tags, traits::JobEventBorrow};
-use thiserror::Error;
-use tracing::info;
-
-use radroots_events::{
- RadrootsNostrEventPtr,
- job_request::RadrootsJobInput,
- job_result::RadrootsJobResult,
- kinds::result_kind_for_request_kind,
- tags::{TAG_D, TAG_E_ROOT},
-};
-use radroots_trade::prelude::{
- kinds::KIND_TRADE_LISTING_FULFILL_RES,
- stage::receipt::{TradeListingReceiptRequest, TradeListingReceiptResult},
- tags::push_trade_listing_chain_tags,
-};
-
-use crate::{
- adapters::nostr::event::NostrEventAdapter,
- features::trade_listing::subscriber::{JobRequestCtx, JobRequestError},
-};
-
-#[derive(Debug, Error)]
-pub enum JobRequestReceiptError {
- #[error("Failed to parse receipt request: {0}")]
- ParseRequest(String),
- #[error("Failed to fetch reference event: {0}")]
- FetchReference(String),
- #[error("Reference event not found: {0}")]
- MissingReference(String),
- #[error("Fulfillment result not kind 6306 or missing chain")]
- InvalidFulfillment,
- #[error("Failed to send job response")]
- ResponseSend(#[from] radroots_nostr::error::RadrootsNostrError),
-}
-
-pub async fn handle_job_request_trade_receipt(
- event_job_request: RadrootsNostrEvent,
- _keys: RadrootsNostrKeys,
- client: RadrootsNostrClient,
- job_req: JobRequestCtx,
- job_req_input: RadrootsJobInput,
-) -> Result<(), JobRequestError> {
- let ev = NostrEventAdapter::new(&event_job_request);
-
- let req: TradeListingReceiptRequest = serde_json::from_str(&job_req_input.data)
- .map_err(|e| JobRequestReceiptError::ParseRequest(e.to_string()))?;
-
- let fulfill_evt =
- radroots_nostr_fetch_event_by_id(client.clone(), &req.fulfillment_result_event_id)
- .await
- .map_err(|_| {
- JobRequestReceiptError::FetchReference(req.fulfillment_result_event_id.clone())
- })?;
- if fulfill_evt.kind != RadrootsNostrKind::Custom(KIND_TRADE_LISTING_FULFILL_RES) {
- return Err(JobRequestReceiptError::InvalidFulfillment.into());
- }
-
- let e_root = fulfill_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_E_ROOT)).then(|| s.get(1).cloned())
- })
- .flatten()
- .ok_or(JobRequestReceiptError::InvalidFulfillment)?;
-
- let d_tag = fulfill_evt
- .tags
- .iter()
- .find_map(|t| {
- let s = t.as_slice();
- (s.get(0).map(|k| k.as_str()) == Some(TAG_D)).then(|| s.get(1).cloned())
- })
- .flatten();
-
- let ack = TradeListingReceiptResult {
- acknowledged: true,
- at: event_job_request.created_at.as_u64() as u32,
- };
- let payload_json = serde_json::to_string(&ack)?;
-
- let result_kind = result_kind_for_request_kind(job_req.model.kind as u32)
- .unwrap_or(job_req.model.kind as u32 + 1000);
-
- let result_model = RadrootsJobResult {
- kind: result_kind as u16,
- request_event: RadrootsNostrEventPtr {
- id: ev.raw_id().to_string(),
- relays: None,
- },
- request_json: Some(serde_json::to_string(&job_req.model)?),
- inputs: job_req.model.inputs.clone(),
- customer_pubkey: Some(ev.raw_author().to_string()),
- payment: None,
- content: Some(payload_json.clone()),
- encrypted: false,
- };
-
- let mut tag_slices = job_result_build_tags(&result_model);
- push_trade_listing_chain_tags(
- &mut tag_slices,
- e_root.clone(),
- Some(req.fulfillment_result_event_id.clone()),
- d_tag,
- );
-
- let builder = radroots_nostr_build_event(result_kind as u32, payload_json, tag_slices)?;
- let job_result_event_id = radroots_nostr_send_event(client, builder).await?;
-
- info!(
- "job request trade/receipt ({}={}) result sent: {:?}",
- TAG_E_ROOT, e_root, job_result_event_id
- );
- Ok(())
-}
diff --git a/src/infra/mod.rs b/src/infra/mod.rs
@@ -1 +0,0 @@
-#![forbid(unsafe_code)]
diff --git a/src/lib.rs b/src/lib.rs
@@ -3,13 +3,9 @@
pub mod adapters;
pub mod cli;
pub mod config;
-pub mod infra;
+pub mod features;
pub mod rhi;
-pub mod features {
- pub mod trade_listing;
-}
-
pub use cli::Args as cli_args;
use anyhow::Result;