Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.
Draft
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
40 changes: 40 additions & 0 deletions mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ mod networking;
mod node;
pub mod nodemanager;
pub mod nostr;
pub mod notifications;
mod onchain;
mod peermanager;
pub mod redshift;
Expand All @@ -54,13 +55,16 @@ use crate::labels::{Contact, LabelStorage};
use crate::nostr::nwc::{
BudgetPeriod, BudgetedSpendingConditions, NwcProfileTag, SpendingConditions,
};
use crate::notifications::MutinyNotificationClient;
use crate::storage::{MutinyStorage, DEVICE_ID_KEY, EXPECTED_NETWORK_KEY, NEED_FULL_SYNC_KEY};
use crate::{error::MutinyError, nostr::ReservedProfile};
use crate::{nodemanager::NodeManager, nostr::ProfileType};
use crate::{nostr::NostrManager, utils::sleep};
use ::nostr::key::XOnlyPublicKey;
use ::nostr::{Event, Kind, Metadata};
use bip39::Mnemonic;
use bitcoin::hashes::hex::ToHex;
use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::PublicKey;
use bitcoin::util::bip32::ExtendedPrivKey;
use bitcoin::Network;
Expand All @@ -86,6 +90,7 @@ pub struct MutinyWalletConfig {
auth_client: Option<Arc<MutinyAuthClient>>,
subscription_url: Option<String>,
scorer_url: Option<String>,
notification_url: Option<String>,
do_not_connect_peers: bool,
skip_device_lock: bool,
pub safe_mode: bool,
Expand All @@ -103,6 +108,7 @@ impl MutinyWalletConfig {
auth_client: Option<Arc<MutinyAuthClient>>,
subscription_url: Option<String>,
scorer_url: Option<String>,
notification_url: Option<String>,
skip_device_lock: bool,
) -> Self {
Self {
Expand All @@ -113,6 +119,7 @@ impl MutinyWalletConfig {
user_esplora_url,
user_rgs_url,
scorer_url,
notification_url,
lsp_url,
auth_client,
subscription_url,
Expand Down Expand Up @@ -142,6 +149,7 @@ pub struct MutinyWallet<S: MutinyStorage> {
pub storage: S,
pub node_manager: Arc<NodeManager<S>>,
pub nostr: Arc<NostrManager<S>>,
pub notification_client: Option<Arc<MutinyNotificationClient>>,
}

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

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

let notification_client = match config.notification_url.clone() {
Some(url) => {
let client = match config.auth_client.clone() {
Some(auth_client) => MutinyNotificationClient::new_authenticated(
auth_client,
url,
node_manager.logger.clone(),
),
None => {
// hash key and use that as identifier
let hash = sha256::Hash::hash(&config.xprivkey.private_key.secret_bytes());
let identifier_key = hash.to_hex();
MutinyNotificationClient::new_unauthenticated(
url,
identifier_key,
node_manager.logger.clone(),
)
}
};

Some(Arc::new(client))
}
None => None,
};

// create nostr manager
let nostr = Arc::new(NostrManager::from_mnemonic(
node_manager.xprivkey,
storage.clone(),
notification_client.clone(),
node_manager.logger.clone(),
)?);

Expand All @@ -175,6 +209,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
storage,
node_manager,
nostr,
notification_client,
};

#[cfg(not(test))]
Expand Down Expand Up @@ -632,6 +667,7 @@ mod tests {
None,
None,
None,
None,
false,
);
let mw = MutinyWallet::new(storage.clone(), config)
Expand Down Expand Up @@ -662,6 +698,7 @@ mod tests {
None,
None,
None,
None,
false,
);
let mut mw = MutinyWallet::new(storage.clone(), config)
Expand Down Expand Up @@ -698,6 +735,7 @@ mod tests {
None,
None,
None,
None,
false,
);
let mut mw = MutinyWallet::new(storage.clone(), config)
Expand Down Expand Up @@ -735,6 +773,7 @@ mod tests {
None,
None,
None,
None,
false,
);
let mw = MutinyWallet::new(storage.clone(), config)
Expand All @@ -760,6 +799,7 @@ mod tests {
None,
None,
None,
None,
false,
);
let mw2 = MutinyWallet::new(storage2.clone(), config2.clone())
Expand Down
4 changes: 4 additions & 0 deletions mutiny-core/src/nodemanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2692,6 +2692,7 @@ mod tests {
None,
None,
None,
None,
false,
);
NodeManager::new(c, storage.clone())
Expand Down Expand Up @@ -2722,6 +2723,7 @@ mod tests {
None,
None,
None,
None,
false,
);
let nm = NodeManager::new(c, storage)
Expand Down Expand Up @@ -2773,6 +2775,7 @@ mod tests {
None,
None,
None,
None,
false,
);
let c = c.with_safe_mode();
Expand Down Expand Up @@ -2809,6 +2812,7 @@ mod tests {
None,
None,
None,
None,
false,
);
let nm = NodeManager::new(c, storage)
Expand Down
18 changes: 17 additions & 1 deletion mutiny-core/src/nostr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::nostr::nwc::{
PendingNwcInvoice, Profile, SingleUseSpendingConditions, SpendingConditions,
PENDING_NWC_EVENTS_KEY,
};
use crate::notifications::MutinyNotificationClient;
use crate::storage::MutinyStorage;
use crate::{error::MutinyError, utils::get_random_bip32_child_index};
use crate::{utils, HTLCStatus};
Expand Down Expand Up @@ -69,6 +70,8 @@ pub struct NostrManager<S: MutinyStorage> {
pub storage: S,
/// Lock for pending nwc invoices
pending_nwc_lock: Arc<Mutex<()>>,
/// Notification Client
pub notifications: Option<Arc<MutinyNotificationClient>>,
/// Logger
pub logger: Arc<MutinyLogger>,
}
Expand Down Expand Up @@ -350,6 +353,17 @@ impl<S: MutinyStorage> NostrManager<S> {
let _ = client.disconnect().await;
}

// register for subscriptions
if let Some(notifications) = &self.notifications {
// just created, unwrap is safe
let uri = NostrWalletConnectURI::from_str(&profile.nwc_uri).expect("invalid uri");
let author = uri.secret.x_only_public_key(nostr::SECP256K1).0;

notifications
.register_nwc(author, uri.public_key, &profile.relay, &profile.name)
.await?;
}

Ok(profile)
}

Expand Down Expand Up @@ -840,6 +854,7 @@ impl<S: MutinyStorage> NostrManager<S> {
pub fn from_mnemonic(
xprivkey: ExtendedPrivKey,
storage: S,
notifications: Option<Arc<MutinyNotificationClient>>,
logger: Arc<MutinyLogger>,
) -> Result<Self, MutinyError> {
let context = Secp256k1::new();
Expand All @@ -862,6 +877,7 @@ impl<S: MutinyStorage> NostrManager<S> {
nwc: Arc::new(RwLock::new(nwc)),
storage,
pending_nwc_lock: Arc::new(Mutex::new(())),
notifications,
logger,
})
}
Expand Down Expand Up @@ -889,7 +905,7 @@ mod test {

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

NostrManager::from_mnemonic(xprivkey, storage, logger).unwrap()
NostrManager::from_mnemonic(xprivkey, storage, None, logger).unwrap()
}

#[test]
Expand Down
104 changes: 104 additions & 0 deletions mutiny-core/src/notifications.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use crate::auth::MutinyAuthClient;
use crate::{error::MutinyError, logging::MutinyLogger};
use anyhow::anyhow;
use lightning::util::logger::*;
use lightning::{log_error, log_info};
use nostr::secp256k1::XOnlyPublicKey;
use reqwest::{Method, Url};
use serde_json::{json, Value};
use std::sync::Arc;

#[derive(Clone)]
pub struct MutinyNotificationClient {
auth_client: Option<Arc<MutinyAuthClient>>,
client: Option<reqwest::Client>,
url: String,
id: Option<String>,
pub logger: Arc<MutinyLogger>,
}

impl MutinyNotificationClient {
pub fn new_authenticated(
auth_client: Arc<MutinyAuthClient>,
url: String,
logger: Arc<MutinyLogger>,
) -> Self {
log_info!(logger, "Creating authenticated notification client");
Self {
auth_client: Some(auth_client),
client: None,
url,
id: None, // we get this from the auth client
logger,
}
}

pub fn new_unauthenticated(
url: String,
identifier_key: String,
logger: Arc<MutinyLogger>,
) -> Self {
log_info!(logger, "Creating unauthenticated notification client");
Self {
auth_client: None,
client: Some(reqwest::Client::new()),
url,
id: Some(identifier_key),
logger,
}
}

async fn make_request(
&self,
method: Method,
url: Url,
body: Option<Value>,
) -> Result<reqwest::Response, MutinyError> {
match (self.auth_client.as_ref(), self.client.as_ref()) {
(Some(auth), _) => auth.request(method, url, body).await,
(None, Some(client)) => {
let mut request = client.request(method, url);
if let Some(body) = body {
request = request.json(&body);
}
request.send().await.map_err(|e| {
log_error!(self.logger, "Error making request: {e}");
MutinyError::Other(anyhow!("Error making request: {e}"))
})
}
(None, None) => unreachable!("No auth client or http client"),
}
}

pub async fn register(&self, info: Value) -> Result<(), MutinyError> {
let url = Url::parse(&format!("{}/register", self.url)).map_err(|e| {
log_error!(self.logger, "Error parsing register url: {e}");
MutinyError::InvalidArgumentsError
})?;

let body = json!({"id": self.id, "info": info});

self.make_request(Method::POST, url, Some(body)).await?;

Ok(())
}

pub async fn register_nwc(
&self,
author: XOnlyPublicKey,
tagged: XOnlyPublicKey,
relay: &str,
name: &str,
) -> Result<(), MutinyError> {
let url = Url::parse(&format!("{}/register-nwc", self.url)).map_err(|e| {
log_error!(self.logger, "Error parsing register url: {e}");
MutinyError::InvalidArgumentsError
})?;

let body = json!({"id": self.id, "author": author, "tagged": tagged, "relay": relay, "name": name});

self.make_request(Method::POST, url, Some(body)).await?;

Ok(())
}
}
Loading