Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ tokio-util = { version = "0.7.15", features = ["compat"], optional = true }
[features]
default = ["tokio"]
tokio = ["dep:tokio", "tokio-util"]
frigate = []

[dev-dependencies]
async-std = "1.13.0"
Expand Down
21 changes: 21 additions & 0 deletions src/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//!
//! - [`Notification::Header`] for `"blockchain.headers.subscribe"`
//! - [`Notification::ScriptHash`] for `"blockchain.scripthash.subscribe"`
//! - [`Notification::SpSubscribe`] for `"blockchain.silentpayments.subscribe"` (requires the `frigate` feature)
//! - [`Notification::Unknown`] for unrecognized or unsupported methods
//!
//! Each variant wraps a struct that contains the deserialized payload for that notification type.
Expand Down Expand Up @@ -32,6 +33,11 @@ pub enum Notification {
/// status.
ScriptHash(ScriptHashNotification),

/// A notification from `"blockchain.silentpayments.subscribe"` indicating a new history
/// of transactions
#[cfg(feature = "frigate")]
SpSubscribe(SpNotification),

/// A catch-all for notifications with unrecognized methods.
///
/// The original [`RawNotification`] is preserved for downstream inspection.
Expand All @@ -52,6 +58,10 @@ impl Notification {
"blockchain.scripthash.subscribe" => {
ScriptHashNotification::deserialize(params).map(Notification::ScriptHash)
}
#[cfg(feature = "frigate")]
"blockchain.silentpayments.subscribe" => {
SpNotification::deserialize(params).map(Notification::SpSubscribe)
}
_ => Ok(Notification::Unknown(raw.clone())),
}
}
Expand Down Expand Up @@ -102,3 +112,14 @@ impl ScriptHashNotification {
self.param_1
}
}

/// A notification indicating new transactions.
///
/// Corresponds to `"blockchain.silentpayments.subscribe"` Frigate Electrum notification method.
#[cfg(feature = "frigate")]
#[derive(Debug, Clone, serde::Deserialize)]
pub struct SpNotification {
Comment thread
sdmg15 marked this conversation as resolved.
pub subscription: response::SpSubscribeResp,
pub progress: f32,
pub history: Vec<response::TxTweak>,
}
36 changes: 25 additions & 11 deletions src/pending_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub trait RequestExt: Request + Sized {
}

macro_rules! gen_pending_request_types {
($($name:ident),*) => {
($($(#[$attr:meta])* $name:ident),* $(,)?) => {
/// A successfully handled request and its decoded server response.
///
/// This enum is returned when a request has been fully processed and the server replied
Expand All @@ -33,10 +33,13 @@ macro_rules! gen_pending_request_types {
/// [`Event::Response`]: crate::Event::Response
#[derive(Debug, Clone)]
pub enum CompletedRequest {
$($name {
req: crate::request::$name,
resp: <crate::request::$name as Request>::Response,
}),*,
$(
$(#[$attr])*
$name {
req: crate::request::$name,
resp: <crate::request::$name as Request>::Response,
},
)*
}

/// A request that received an error response from the Electrum server.
Expand All @@ -53,23 +56,32 @@ macro_rules! gen_pending_request_types {
/// [`Event::ResponseError`]: crate::Event::ResponseError
#[derive(Debug, Clone)]
pub enum FailedRequest {
$($name {
req: crate::request::$name,
error: ResponseError,
}),*,
$(
$(#[$attr])*
$name {
req: crate::request::$name,
error: ResponseError,
},
)*
}

impl core::fmt::Display for FailedRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(Self::$name { req, error } => write!(f, "Server responsed to {:?} with error: {}", req, error)),*,
$(
$(#[$attr])*
Self::$name { req, error } => {
write!(f, "Server responsed to {:?} with error: {}", req, error)
}
)*
}
}
}

impl std::error::Error for FailedRequest {}

$(
$(#[$attr])*
impl RequestExt for crate::request::$name {
fn into_completed(self, resp: <Self as Request>::Response) -> CompletedRequest {
CompletedRequest::$name { req: self, resp }
Expand Down Expand Up @@ -103,7 +115,9 @@ gen_pending_request_types! {
GetFeeHistogram,
Banner,
Ping,
Custom

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason you dropped Custom here?

@sdmg15 sdmg15 Aug 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this, I chose the wrong gen_pending_request_types! between the two former existing one.

Custom,
#[cfg(feature = "frigate")] SpSubscribe,
#[cfg(feature = "frigate")] SpUnsubscribe
}

type Handler =
Expand Down
84 changes: 84 additions & 0 deletions src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,3 +656,87 @@ impl Request for Ping {
("server.ping".into(), vec![])
}
}

/// A request to subscribe to payment outputs belonging to the provided keys
///
/// This corresponds to the `"blockchain.silentpayments.subscribe"` Frigate Electrum RPC method.
/// It returns the silent payment address that has been subscribed.
///
/// Supported Frigate version: <= 1.4.1
///
/// See: <https://github.com/sparrowwallet/frigate/tree/1.4.1#blockchainsilentpaymentssubscribe>
#[cfg(feature = "frigate")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpSubscribe {
// A 64 character string containing the hex of the scan private key.
pub scan_priv_key: bitcoin::secp256k1::SecretKey,

// A 66 character string containing the hex of the spend public key.
pub spend_pub_key: bitcoin::secp256k1::PublicKey,

// Block height or timestamp to start scanning from. Values above 500,000,000 are treated as seconds from the start of the epoch.
pub start_height: Option<u32>,
Comment thread
sdmg15 marked this conversation as resolved.

// An array of positive integers specifying additional silent payment labels to scan for.
pub labels: Option<Vec<u32>>,
}

#[cfg(feature = "frigate")]
impl Request for SpSubscribe {
type Response = String;
Comment thread
sdmg15 marked this conversation as resolved.

fn to_method_and_params(&self) -> MethodAndParams {
let mut params = vec![
serde_json::json!(self.scan_priv_key),
serde_json::json!(self.spend_pub_key),
];

match (self.start_height, &self.labels) {
(Some(start_height), Some(labels)) => {
params.extend([start_height.into(), labels.clone().into()]);
}

(Some(start_height), None) => params.push(start_height.into()),
(None, Some(labels)) => {
params.extend([serde_json::Value::Null, labels.clone().into()]);
}
(None, None) => {}
}

("blockchain.silentpayments.subscribe".into(), params)
}
}

/// A request to unsubscribe from payment outputs belonging to the provided keys
///
/// This corresponds to the `"blockchain.silentpayments.unsubscribe"` Frigate Electrum RPC method.
/// It returns the silent payment address that has been unsubscribed. This should cancel any scans
/// that may be currently running for this address.
///
/// Supported Frigate version <= 1.4.1
///
/// See: <https://github.com/sparrowwallet/frigate/tree/1.4.1#blockchainsilentpaymentsunsubscribe>
#[cfg(feature = "frigate")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpUnsubscribe {
// A 64 character string containing the hex of the scan private key.
pub scan_priv_key: bitcoin::secp256k1::SecretKey,

// A 66 character string containing the hex of the spend public key.
pub spend_pub_key: bitcoin::secp256k1::PublicKey,
}

#[cfg(feature = "frigate")]
impl Request for SpUnsubscribe {
type Response = String;

fn to_method_and_params(&self) -> MethodAndParams {
(
"blockchain.silentpayments.unsubscribe".into(),
vec![
serde_json::json!(self.scan_priv_key),
serde_json::json!(self.spend_pub_key),
],
)
}
}
16 changes: 16 additions & 0 deletions src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,19 @@ pub struct ServerHostValues {
/// TCP Port.
pub tcp_port: Option<u16>,
}

#[cfg(feature = "frigate")]
#[derive(Debug, Clone, serde::Deserialize)]
pub struct SpSubscribeResp {
pub address: String,
pub labels: Vec<u32>,
pub start_height: u32,
}

#[cfg(feature = "frigate")]
#[derive(Debug, Clone, serde::Deserialize)]
pub struct TxTweak {
pub height: u32,
pub tx_hash: bitcoin::Txid,
pub tweak_key: bitcoin::secp256k1::PublicKey,
}