commit 593cf9810aa05c87714a94048dc93af92610adc2
parent 817e4346093b4982bfccb4914f988452a96b5308
Author: triesap <triesap@radroots.dev>
Date: Sun, 4 Jan 2026 22:12:15 +0000
jsonrpc: add trade listing validate request and result publishing
- Add trade.listing.validate.request to publish validate request envelopes to a recipient
- Publish validate results to recipient_pubkey via tagged DVM result events
- Reuse radroots_nostr_build_event/send_event helpers and shared publish_response wrapper
- Reject trade listing DVM kinds in generic dvm_request/dvm_result publish methods
Diffstat:
3 files changed, 141 insertions(+), 2 deletions(-)
diff --git a/src/api/jsonrpc/methods/domains/trade/listing/validate.rs b/src/api/jsonrpc/methods/domains/trade/listing/validate.rs
@@ -10,7 +10,9 @@ use radroots_events::kinds::KIND_FARM;
use radroots_events::listing::RadrootsListingFarmRef;
use radroots_events::{RadrootsNostrEvent as RadrootsWireEvent, RadrootsNostrEventPtr};
use radroots_nostr::prelude::{
+ radroots_nostr_build_event,
radroots_nostr_parse_pubkey,
+ radroots_nostr_send_event,
RadrootsNostrClient,
RadrootsNostrEvent as RadrootsRawEvent,
RadrootsNostrEventId,
@@ -18,10 +20,15 @@ use radroots_nostr::prelude::{
RadrootsNostrKind,
};
use radroots_nostr::util::event_created_at_u32_saturating;
-use radroots_trade::listing::dvm::TradeListingValidateResult;
+use radroots_trade::listing::dvm::{
+ TradeListingEnvelope,
+ TradeListingMessageType,
+ TradeListingValidateRequest,
+ TradeListingValidateResult,
+};
use radroots_trade::listing::validation::{validate_listing_event, TradeListingValidationError};
-use crate::api::jsonrpc::nostr::event_tags;
+use crate::api::jsonrpc::nostr::{event_tags, publish_response, PublishResponse};
use crate::api::jsonrpc::params::timeout_or;
use crate::api::jsonrpc::{MethodRegistry, RpcContext, RpcError};
@@ -34,9 +41,62 @@ struct TradeListingValidateParams {
listing_event: Option<RadrootsNostrEventPtr>,
#[serde(default)]
timeout_secs: Option<u64>,
+ #[serde(default)]
+ recipient_pubkey: Option<String>,
}
pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> {
+ registry.track("trade.listing.validate.request");
+ m.register_async_method("trade.listing.validate.request", |params, ctx, _| async move {
+ if ctx.state.client.relays().await.is_empty() {
+ return Err(RpcError::NoRelays);
+ }
+
+ let TradeListingValidateRequestParams {
+ listing_addr,
+ recipient_pubkey,
+ listing_event,
+ } = params
+ .parse()
+ .map_err(|e| RpcError::InvalidParams(e.to_string()))?;
+
+ let addr = parse_listing_addr(&listing_addr)?;
+ let listing_addr = addr.as_str();
+
+ let recipient = radroots_nostr_parse_pubkey(&recipient_pubkey)
+ .map_err(|e| RpcError::InvalidParams(format!("invalid recipient_pubkey: {e}")))?;
+
+ let payload = TradeListingValidateRequest { listing_event };
+ let envelope = TradeListingEnvelope::new(
+ TradeListingMessageType::ListingValidateRequest,
+ listing_addr.clone(),
+ None,
+ payload,
+ );
+ envelope
+ .validate()
+ .map_err(|e| RpcError::InvalidParams(e.to_string()))?;
+ let content = serde_json::to_string(&envelope)
+ .map_err(|e| RpcError::Other(format!("failed to encode envelope: {e}")))?;
+ let tags = vec![
+ vec!["p".to_string(), recipient.to_string()],
+ vec!["a".to_string(), listing_addr.clone()],
+ ];
+
+ let builder = radroots_nostr_build_event(
+ TradeListingMessageType::ListingValidateRequest.kind() as u32,
+ content,
+ tags,
+ )
+ .map_err(|e| RpcError::Other(format!("failed to build validate request event: {e}")))?;
+
+ let output = radroots_nostr_send_event(&ctx.state.client, builder)
+ .await
+ .map_err(|e| RpcError::Other(format!("failed to publish validate request: {e}")))?;
+
+ Ok::<PublishResponse, RpcError>(publish_response(output))
+ })?;
+
registry.track("trade.listing.validate");
m.register_async_method("trade.listing.validate", |params, ctx, _| async move {
if ctx.state.client.relays().await.is_empty() {
@@ -47,11 +107,13 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res
listing_addr,
listing_event,
timeout_secs,
+ recipient_pubkey,
} = params
.parse()
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
let addr = parse_listing_addr(&listing_addr)?;
+ let listing_addr = addr.as_str();
let timeout_secs = timeout_or(timeout_secs);
let listing_event = if let Some(ptr) = listing_event {
@@ -105,11 +167,72 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res
valid: errors.is_empty(),
errors,
};
+
+ if let Some(recipient_pubkey) = recipient_pubkey {
+ publish_validate_result(
+ &ctx.state.client,
+ &listing_addr,
+ &recipient_pubkey,
+ &result,
+ )
+ .await?;
+ }
Ok::<TradeListingValidateResult, RpcError>(result)
})?;
Ok(())
}
+#[derive(Debug, Deserialize)]
+struct TradeListingValidateRequestParams {
+ listing_addr: String,
+ recipient_pubkey: String,
+ #[serde(default)]
+ listing_event: Option<RadrootsNostrEventPtr>,
+}
+
+async fn publish_validate_result(
+ client: &RadrootsNostrClient,
+ listing_addr: &str,
+ recipient_pubkey: &str,
+ result: &TradeListingValidateResult,
+) -> Result<(), RpcError> {
+ let recipient = radroots_nostr_parse_pubkey(recipient_pubkey)
+ .map_err(|e| RpcError::InvalidParams(format!("invalid recipient_pubkey: {e}")))?;
+ let envelope = TradeListingEnvelope::new(
+ TradeListingMessageType::ListingValidateResult,
+ listing_addr.to_string(),
+ None,
+ result.clone(),
+ );
+ envelope
+ .validate()
+ .map_err(|e| RpcError::InvalidParams(e.to_string()))?;
+ let content = serde_json::to_string(&envelope)
+ .map_err(|e| RpcError::Other(format!("failed to encode envelope: {e}")))?;
+ let tags = vec![
+ vec!["p".to_string(), recipient.to_string()],
+ vec!["a".to_string(), listing_addr.to_string()],
+ ];
+
+ let builder = radroots_nostr_build_event(
+ TradeListingMessageType::ListingValidateResult.kind() as u32,
+ content,
+ tags,
+ )
+ .map_err(|e| RpcError::Other(format!("failed to build validate result event: {e}")))?;
+
+ let output = radroots_nostr_send_event(client, builder)
+ .await
+ .map_err(|e| RpcError::Other(format!("failed to publish validate result: {e}")))?;
+ if !output.failed.is_empty() {
+ return Err(RpcError::Other(format!(
+ "validate result delivery failed: {:?}",
+ output.failed
+ )));
+ }
+ Ok(())
+}
+
async fn fetch_event_by_id(
client: &RadrootsNostrClient,
event_id: RadrootsNostrEventId,
diff --git a/src/api/jsonrpc/methods/events/dvm_request/publish.rs b/src/api/jsonrpc/methods/events/dvm_request/publish.rs
@@ -10,6 +10,7 @@ use radroots_events::job_request::RadrootsJobRequest;
use radroots_events_codec::job::encode::canonicalize_tags;
use radroots_events_codec::job::request::encode::to_wire_parts;
use radroots_nostr::prelude::{radroots_nostr_build_event, radroots_nostr_send_event};
+use radroots_trade::listing::dvm_kinds::is_trade_listing_dvm_request_kind;
#[derive(Debug, Deserialize)]
struct PublishDvmRequestParams {
@@ -35,6 +36,13 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res
.parse()
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
+ if is_trade_listing_dvm_request_kind(request.kind) {
+ return Err(RpcError::InvalidParams(
+ "trade listing request kinds must use trade.listing.validate.request or trade.listing.order"
+ .to_string(),
+ ));
+ }
+
let content = content.unwrap_or_default();
let mut parts = to_wire_parts(&request, &content)
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
diff --git a/src/api/jsonrpc/methods/events/dvm_result/publish.rs b/src/api/jsonrpc/methods/events/dvm_result/publish.rs
@@ -10,6 +10,7 @@ use radroots_events::job_result::RadrootsJobResult;
use radroots_events_codec::job::encode::canonicalize_tags;
use radroots_events_codec::job::result::encode::to_wire_parts;
use radroots_nostr::prelude::{radroots_nostr_build_event, radroots_nostr_send_event};
+use radroots_trade::listing::dvm_kinds::is_trade_listing_dvm_result_kind;
#[derive(Debug, Deserialize)]
struct PublishDvmResultParams {
@@ -29,6 +30,13 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res
.parse()
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
+ if is_trade_listing_dvm_result_kind(result.kind) {
+ return Err(RpcError::InvalidParams(
+ "trade listing result kinds must use trade.listing.validate or trade.listing.order"
+ .to_string(),
+ ));
+ }
+
let content = result.content.clone().unwrap_or_default();
let mut parts = to_wire_parts(&result, &content)
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;