Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.

Commit b3e1094

Browse files
committed
Notification client
1 parent 1493806 commit b3e1094

File tree

5 files changed

+192
-1
lines changed

5 files changed

+192
-1
lines changed

mutiny-core/src/lib.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ mod networking;
3131
mod node;
3232
pub mod nodemanager;
3333
pub mod nostr;
34+
pub mod notifications;
3435
mod onchain;
3536
mod peermanager;
3637
pub mod redshift;
@@ -54,13 +55,16 @@ use crate::labels::{Contact, LabelStorage};
5455
use crate::nostr::nwc::{
5556
BudgetPeriod, BudgetedSpendingConditions, NwcProfileTag, SpendingConditions,
5657
};
58+
use crate::notifications::MutinyNotificationClient;
5759
use crate::storage::{MutinyStorage, DEVICE_ID_KEY, EXPECTED_NETWORK_KEY, NEED_FULL_SYNC_KEY};
5860
use crate::{error::MutinyError, nostr::ReservedProfile};
5961
use crate::{nodemanager::NodeManager, nostr::ProfileType};
6062
use crate::{nostr::NostrManager, utils::sleep};
6163
use ::nostr::key::XOnlyPublicKey;
6264
use ::nostr::{Event, Kind, Metadata};
6365
use bip39::Mnemonic;
66+
use bitcoin::hashes::hex::ToHex;
67+
use bitcoin::hashes::{sha256, Hash};
6468
use bitcoin::secp256k1::PublicKey;
6569
use bitcoin::util::bip32::ExtendedPrivKey;
6670
use bitcoin::Network;
@@ -86,6 +90,7 @@ pub struct MutinyWalletConfig {
8690
auth_client: Option<Arc<MutinyAuthClient>>,
8791
subscription_url: Option<String>,
8892
scorer_url: Option<String>,
93+
notification_url: Option<String>,
8994
do_not_connect_peers: bool,
9095
skip_device_lock: bool,
9196
pub safe_mode: bool,
@@ -103,6 +108,7 @@ impl MutinyWalletConfig {
103108
auth_client: Option<Arc<MutinyAuthClient>>,
104109
subscription_url: Option<String>,
105110
scorer_url: Option<String>,
111+
notification_url: Option<String>,
106112
skip_device_lock: bool,
107113
) -> Self {
108114
Self {
@@ -113,6 +119,7 @@ impl MutinyWalletConfig {
113119
user_esplora_url,
114120
user_rgs_url,
115121
scorer_url,
122+
notification_url,
116123
lsp_url,
117124
auth_client,
118125
subscription_url,
@@ -142,6 +149,7 @@ pub struct MutinyWallet<S: MutinyStorage> {
142149
pub storage: S,
143150
pub node_manager: Arc<NodeManager<S>>,
144151
pub nostr: Arc<NostrManager<S>>,
152+
pub notification_client: Option<Arc<MutinyNotificationClient>>,
145153
}
146154

147155
impl<S: MutinyStorage> MutinyWallet<S> {
@@ -163,10 +171,36 @@ impl<S: MutinyStorage> MutinyWallet<S> {
163171

164172
NodeManager::start_sync(node_manager.clone());
165173

174+
let notification_client = match config.notification_url.clone() {
175+
Some(url) => {
176+
let client = match config.auth_client.clone() {
177+
Some(auth_client) => MutinyNotificationClient::new_authenticated(
178+
auth_client,
179+
url,
180+
node_manager.logger.clone(),
181+
),
182+
None => {
183+
// hash key and use that as identifier
184+
let hash = sha256::Hash::hash(&config.xprivkey.private_key.secret_bytes());
185+
let identifier_key = hash.to_hex();
186+
MutinyNotificationClient::new_unauthenticated(
187+
url,
188+
identifier_key,
189+
node_manager.logger.clone(),
190+
)
191+
}
192+
};
193+
194+
Some(Arc::new(client))
195+
}
196+
None => None,
197+
};
198+
166199
// create nostr manager
167200
let nostr = Arc::new(NostrManager::from_mnemonic(
168201
node_manager.xprivkey,
169202
storage.clone(),
203+
notification_client.clone(),
170204
node_manager.logger.clone(),
171205
)?);
172206

@@ -175,6 +209,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
175209
storage,
176210
node_manager,
177211
nostr,
212+
notification_client,
178213
};
179214

180215
#[cfg(not(test))]
@@ -632,6 +667,7 @@ mod tests {
632667
None,
633668
None,
634669
None,
670+
None,
635671
false,
636672
);
637673
let mw = MutinyWallet::new(storage.clone(), config)
@@ -662,6 +698,7 @@ mod tests {
662698
None,
663699
None,
664700
None,
701+
None,
665702
false,
666703
);
667704
let mut mw = MutinyWallet::new(storage.clone(), config)
@@ -698,6 +735,7 @@ mod tests {
698735
None,
699736
None,
700737
None,
738+
None,
701739
false,
702740
);
703741
let mut mw = MutinyWallet::new(storage.clone(), config)
@@ -735,6 +773,7 @@ mod tests {
735773
None,
736774
None,
737775
None,
776+
None,
738777
false,
739778
);
740779
let mw = MutinyWallet::new(storage.clone(), config)
@@ -760,6 +799,7 @@ mod tests {
760799
None,
761800
None,
762801
None,
802+
None,
763803
false,
764804
);
765805
let mw2 = MutinyWallet::new(storage2.clone(), config2.clone())

mutiny-core/src/nodemanager.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2692,6 +2692,7 @@ mod tests {
26922692
None,
26932693
None,
26942694
None,
2695+
None,
26952696
false,
26962697
);
26972698
NodeManager::new(c, storage.clone())
@@ -2722,6 +2723,7 @@ mod tests {
27222723
None,
27232724
None,
27242725
None,
2726+
None,
27252727
false,
27262728
);
27272729
let nm = NodeManager::new(c, storage)
@@ -2773,6 +2775,7 @@ mod tests {
27732775
None,
27742776
None,
27752777
None,
2778+
None,
27762779
false,
27772780
);
27782781
let c = c.with_safe_mode();
@@ -2809,6 +2812,7 @@ mod tests {
28092812
None,
28102813
None,
28112814
None,
2815+
None,
28122816
false,
28132817
);
28142818
let nm = NodeManager::new(c, storage)

mutiny-core/src/nostr/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::nostr::nwc::{
66
PendingNwcInvoice, Profile, SingleUseSpendingConditions, SpendingConditions,
77
PENDING_NWC_EVENTS_KEY,
88
};
9+
use crate::notifications::MutinyNotificationClient;
910
use crate::storage::MutinyStorage;
1011
use crate::{error::MutinyError, utils::get_random_bip32_child_index};
1112
use crate::{utils, HTLCStatus};
@@ -69,6 +70,8 @@ pub struct NostrManager<S: MutinyStorage> {
6970
pub storage: S,
7071
/// Lock for pending nwc invoices
7172
pending_nwc_lock: Arc<Mutex<()>>,
73+
/// Notification Client
74+
pub notifications: Option<Arc<MutinyNotificationClient>>,
7275
/// Logger
7376
pub logger: Arc<MutinyLogger>,
7477
}
@@ -350,6 +353,17 @@ impl<S: MutinyStorage> NostrManager<S> {
350353
let _ = client.disconnect().await;
351354
}
352355

356+
// register for subscriptions
357+
if let Some(notifications) = &self.notifications {
358+
// just created, unwrap is safe
359+
let uri = NostrWalletConnectURI::from_str(&profile.nwc_uri).expect("invalid uri");
360+
let author = uri.secret.x_only_public_key(nostr::SECP256K1).0;
361+
362+
notifications
363+
.register_nwc(author, uri.public_key, &profile.relay, &profile.name)
364+
.await?;
365+
}
366+
353367
Ok(profile)
354368
}
355369

@@ -840,6 +854,7 @@ impl<S: MutinyStorage> NostrManager<S> {
840854
pub fn from_mnemonic(
841855
xprivkey: ExtendedPrivKey,
842856
storage: S,
857+
notifications: Option<Arc<MutinyNotificationClient>>,
843858
logger: Arc<MutinyLogger>,
844859
) -> Result<Self, MutinyError> {
845860
let context = Secp256k1::new();
@@ -862,6 +877,7 @@ impl<S: MutinyStorage> NostrManager<S> {
862877
nwc: Arc::new(RwLock::new(nwc)),
863878
storage,
864879
pending_nwc_lock: Arc::new(Mutex::new(())),
880+
notifications,
865881
logger,
866882
})
867883
}
@@ -889,7 +905,7 @@ mod test {
889905

890906
let logger = Arc::new(MutinyLogger::default());
891907

892-
NostrManager::from_mnemonic(xprivkey, storage, logger).unwrap()
908+
NostrManager::from_mnemonic(xprivkey, storage, None, logger).unwrap()
893909
}
894910

895911
#[test]

mutiny-core/src/notifications.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use crate::auth::MutinyAuthClient;
2+
use crate::{error::MutinyError, logging::MutinyLogger};
3+
use anyhow::anyhow;
4+
use lightning::util::logger::*;
5+
use lightning::{log_error, log_info};
6+
use nostr::secp256k1::XOnlyPublicKey;
7+
use reqwest::{Method, Url};
8+
use serde_json::{json, Value};
9+
use std::sync::Arc;
10+
11+
#[derive(Clone)]
12+
pub struct MutinyNotificationClient {
13+
auth_client: Option<Arc<MutinyAuthClient>>,
14+
client: Option<reqwest::Client>,
15+
url: String,
16+
id: Option<String>,
17+
pub logger: Arc<MutinyLogger>,
18+
}
19+
20+
impl MutinyNotificationClient {
21+
pub fn new_authenticated(
22+
auth_client: Arc<MutinyAuthClient>,
23+
url: String,
24+
logger: Arc<MutinyLogger>,
25+
) -> Self {
26+
log_info!(logger, "Creating authenticated notification client");
27+
Self {
28+
auth_client: Some(auth_client),
29+
client: None,
30+
url,
31+
id: None, // we get this from the auth client
32+
logger,
33+
}
34+
}
35+
36+
pub fn new_unauthenticated(
37+
url: String,
38+
identifier_key: String,
39+
logger: Arc<MutinyLogger>,
40+
) -> Self {
41+
log_info!(logger, "Creating unauthenticated notification client");
42+
Self {
43+
auth_client: None,
44+
client: Some(reqwest::Client::new()),
45+
url,
46+
id: Some(identifier_key),
47+
logger,
48+
}
49+
}
50+
51+
async fn make_request(
52+
&self,
53+
method: Method,
54+
url: Url,
55+
body: Option<Value>,
56+
) -> Result<reqwest::Response, MutinyError> {
57+
match (self.auth_client.as_ref(), self.client.as_ref()) {
58+
(Some(auth), _) => auth.request(method, url, body).await,
59+
(None, Some(client)) => {
60+
let mut request = client.request(method, url);
61+
if let Some(body) = body {
62+
request = request.json(&body);
63+
}
64+
request.send().await.map_err(|e| {
65+
log_error!(self.logger, "Error making request: {e}");
66+
MutinyError::Other(anyhow!("Error making request: {e}"))
67+
})
68+
}
69+
(None, None) => unreachable!("No auth client or http client"),
70+
}
71+
}
72+
73+
pub async fn register(&self, info: Value) -> Result<(), MutinyError> {
74+
let url = Url::parse(&format!("{}/register", self.url)).map_err(|e| {
75+
log_error!(self.logger, "Error parsing register url: {e}");
76+
MutinyError::InvalidArgumentsError
77+
})?;
78+
79+
let body = json!({"id": self.id, "info": info});
80+
81+
self.make_request(Method::POST, url, Some(body)).await?;
82+
83+
Ok(())
84+
}
85+
86+
pub async fn register_nwc(
87+
&self,
88+
author: XOnlyPublicKey,
89+
tagged: XOnlyPublicKey,
90+
relay: &str,
91+
name: &str,
92+
) -> Result<(), MutinyError> {
93+
let url = Url::parse(&format!("{}/register-nwc", self.url)).map_err(|e| {
94+
log_error!(self.logger, "Error parsing register url: {e}");
95+
MutinyError::InvalidArgumentsError
96+
})?;
97+
98+
let body = json!({"id": self.id, "author": author, "tagged": tagged, "relay": relay, "name": name});
99+
100+
self.make_request(Method::POST, url, Some(body)).await?;
101+
102+
Ok(())
103+
}
104+
}

0 commit comments

Comments
 (0)