radrootsd

JSON-RPC bridge for Radroots event publishing
git clone https://radroots.dev/git/radrootsd.git
Log | Files | Refs | README | LICENSE

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:
Msrc/api/jsonrpc/methods/domains/trade/listing/validate.rs | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/api/jsonrpc/methods/events/dvm_request/publish.rs | 8++++++++
Msrc/api/jsonrpc/methods/events/dvm_result/publish.rs | 8++++++++
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()))?;