diff --git a/Cargo.toml b/Cargo.toml index 5e3c99a..3a42602 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/notification.rs b/src/notification.rs index 6975110..aeed417 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -5,6 +5,7 @@ //! //! - [`Notification::Header`] for `"blockchain.headers.subscribe"` //! - [`Notification::ScriptHash`] for `"blockchain.scripthash.subscribe"` +//! - [`Notification::SpSubscribe`] for `"blockchain.silentpayments.subscribe"` //! - [`Notification::Unknown`] for unrecognized or unsupported methods //! //! Each variant wraps a struct that contains the deserialized payload for that notification type. @@ -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. @@ -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())), } } @@ -102,3 +112,14 @@ impl ScriptHashNotification { self.param_1 } } + +/// A notification indicating new confirmed transactions +/// +/// Corresponds to `"blockchain.silentpayments.subscribe"` Frigate Electrum notification method +#[cfg(feature = "frigate")] +#[derive(Debug, Clone, serde::Deserialize)] +pub struct SpNotification { + pub subscription: response::SpSubscribeResp, + pub progress: f32, + pub history: Vec, +} diff --git a/src/pending_request.rs b/src/pending_request.rs index 4686b9c..493971e 100644 --- a/src/pending_request.rs +++ b/src/pending_request.rs @@ -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 @@ -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: ::Response, - }),*, + $( + $(#[$attr])* + $name { + req: crate::request::$name, + resp: ::Response, + }, + )* } /// A request that received an error response from the Electrum server. @@ -53,16 +56,24 @@ 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) + } + )* } } } @@ -70,6 +81,7 @@ macro_rules! gen_pending_request_types { impl std::error::Error for FailedRequest {} $( + $(#[$attr])* impl RequestExt for crate::request::$name { fn into_completed(self, resp: ::Response) -> CompletedRequest { CompletedRequest::$name { req: self, resp } @@ -103,7 +115,8 @@ gen_pending_request_types! { GetFeeHistogram, Banner, Ping, - Custom + #[cfg(feature = "frigate")] SpSubscribe, + #[cfg(feature = "frigate")] SpUnsubscribe } type Handler = diff --git a/src/request.rs b/src/request.rs index 9973529..b0043ce 100644 --- a/src/request.rs +++ b/src/request.rs @@ -656,3 +656,74 @@ 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 { + pub scan_priv_key: bitcoin::secp256k1::SecretKey, + pub scan_pub_key: bitcoin::secp256k1::PublicKey, + pub start_height: Option, + pub labels: Option>, +} + +#[cfg(feature = "frigate")] +impl Request for SpSubscribe { + type Response = String; + + fn to_method_and_params(&self) -> MethodAndParams { + let mut params = vec![ + serde_json::json!(self.scan_priv_key), + serde_json::json!(self.scan_pub_key), + ]; + + if let Some(start_height) = self.start_height { + params.push(start_height.into()); + } + + if let Some(labels) = &self.labels { + params.push(serde_json::Value::Null); + params.push(labels.clone().into()); + } + + ("blockchain.silentpayments.subscribe".into(), params) + } +} + +/// A request to unsubscribe to 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 subscribed.This should cancel any scans that +/// may be currently running for this address. +/// +/// Supported Frigate version <= 1.4.1 +/// +/// See: See: https://github.com/sparrowwallet/frigate/tree/1.4.1#blockchainsilentpaymentsunsubscribe +#[cfg(feature = "frigate")] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SpUnsubscribe { + pub scan_priv_key: bitcoin::secp256k1::SecretKey, + pub scan_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.scan_pub_key), + ], + ) + } +} diff --git a/src/response.rs b/src/response.rs index 7510ec4..a4b9b1e 100644 --- a/src/response.rs +++ b/src/response.rs @@ -318,3 +318,19 @@ pub struct ServerHostValues { /// TCP Port. pub tcp_port: Option, } + +#[cfg(feature = "frigate")] +#[derive(Debug, Clone, serde::Deserialize)] +pub struct SpSubscribeResp { + pub address: String, + pub labels: Vec, + 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, +}