From fe994567135da2118578ea3dbfd1c876999b857a Mon Sep 17 00:00:00 2001 From: Tony Giorgio Date: Wed, 10 Apr 2024 22:30:27 -0500 Subject: [PATCH] Change federation for hermes address --- mutiny-core/src/hermes.rs | 125 +++++++++++++++++++++++++++++++++----- mutiny-core/src/lib.rs | 27 ++++++-- 2 files changed, 131 insertions(+), 21 deletions(-) diff --git a/mutiny-core/src/hermes.rs b/mutiny-core/src/hermes.rs index b888b5f2c..7a979de94 100644 --- a/mutiny-core/src/hermes.rs +++ b/mutiny-core/src/hermes.rs @@ -16,9 +16,9 @@ use futures::{pin_mut, select, FutureExt}; use lightning::util::logger::Logger; use lightning::{log_error, log_info, log_warn}; use lightning_invoice::Bolt11Invoice; -use nostr::prelude::decrypt_received_private_zap_message; use nostr::secp256k1::SecretKey; use nostr::{nips::nip04::decrypt, Event, JsonUtil, Keys, Tag, ToBech32}; +use nostr::{prelude::decrypt_received_private_zap_message, EventBuilder}; use nostr::{Filter, Kind, Timestamp}; use nostr_sdk::{Client, RelayPoolNotification}; use reqwest::Method; @@ -44,6 +44,9 @@ const HERMES_SERVICE_ID: u32 = 1; const HERMES_FREE_PLAN_ID: u32 = 1; const HERMES_PAID_PLAN_ID: u32 = 2; +const REGISTRATION_CHECK_EVENT_KIND: Kind = Kind::Custom(93_186); +const NEW_FEDERATION_EVENT_KIND: Kind = Kind::Custom(93_187); + #[derive(Deserialize, Serialize)] pub struct RegisterRequest { pub name: Option, @@ -132,7 +135,7 @@ impl HermesClient { /// Starts the hermes background checker /// This should only error if there's an initial unrecoverable error /// Otherwise it will loop in the background until a stop signal - pub fn start(&self, profile_key: Option) -> Result<(), MutinyError> { + pub async fn start(&self, profile_key: Option) -> Result<(), MutinyError> { // if we haven't synced before, use now and save to storage let last_sync_time = self.storage.get_dm_sync_time(true)?; let time_stamp = match last_sync_time { @@ -148,26 +151,62 @@ impl HermesClient { let logger_check_clone = self.logger.clone(); let stop_check_clone = self.stop.clone(); let http_client_check_clone = self.http_client.clone(); - let public_key_check_clone = self.public_key; + let nostr_client_check_clone = self.client.clone(); let base_url_check_clone = self.base_url.clone(); let current_address_check_clone = self.current_address.clone(); + let first_federation = self.get_first_federation().await.clone(); utils::spawn(async move { loop { if stop_check_clone.load(Ordering::Relaxed) { break; }; - match check_hermes_name_for_pubkey( + match check_hermes_registration_info( &http_client_check_clone, &base_url_check_clone, - public_key_check_clone, + nostr_client_check_clone.clone(), ) .await { Ok(o) => { - let mut c = current_address_check_clone.write().await; - log_info!(logger_check_clone, "checked lightning address: {o:?}"); - *c = (o, true); + { + let mut c = current_address_check_clone.write().await; + log_info!( + logger_check_clone, + "checked lightning address: {:?}", + o.name + ); + *c = (o.name.clone(), true); + } + + // check that federation is still the same if the user has a name + if o.name.is_some() { + if let Some(f) = first_federation { + // user has a federation, set the current one as the address + match change_federation_info( + &http_client_check_clone, + &base_url_check_clone, + nostr_client_check_clone, + f, + ) + .await + { + Ok(_) => { + log_info!( + logger_check_clone, + "changed federation info to current federation" + ); + } + Err(e) => { + log_error!( + logger_check_clone, + "could not change the federation: {e}" + ); + } + } + } + } + break; } Err(e) => { @@ -257,6 +296,19 @@ impl HermesClient { Ok(()) } + pub async fn change_federation_info( + &self, + federation: FederationIdentity, + ) -> Result<(), MutinyError> { + change_federation_info( + &self.http_client, + &self.base_url, + self.client.clone(), + federation, + ) + .await + } + pub async fn check_available_name(&self, name: String) -> Result { check_name_request(&self.http_client, &self.base_url, name).await } @@ -333,8 +385,38 @@ impl HermesClient { None => None, } } +} - // TODO need a way to change the federation if the user's federation changes +#[derive(Serialize, Deserialize, Clone)] +pub struct ChangeFederationRequest { + pub name: Option, + pub federation_id: Option, +} + +async fn change_federation_info( + http_client: &reqwest::Client, + base_url: &str, + nostr_client: Client, + federation: FederationIdentity, +) -> Result<(), MutinyError> { + // create nostr event + let signer = nostr_client.signer().await?; + let event_builder = EventBuilder::new( + NEW_FEDERATION_EVENT_KIND, + federation.invite_code.to_string(), + [], + ); + let event = signer.sign_event_builder(event_builder).await?; + + // send request + let url = Url::parse(&format!("{}/v1/change-federation", base_url)) + .map_err(|_| MutinyError::ConnectionFailed)?; + let request = http_client.request(Method::POST, url).json(&event); + let _ = utils::fetch_with_timeout(http_client, request.build().expect("should build req")) + .await + .map_err(|_| MutinyError::ConnectionFailed)?; + + Ok(()) } fn find_hermes_token( @@ -347,18 +429,29 @@ fn find_hermes_token( .find(|token| token.service_id == service_id && token.plan_id == plan_id) } -async fn check_hermes_name_for_pubkey( +#[derive(Serialize, Deserialize, Clone)] +pub struct RegistrationInfo { + pub name: Option, + pub federation_id: Option, +} + +async fn check_hermes_registration_info( http_client: &reqwest::Client, base_url: &str, - pubkey: nostr::PublicKey, -) -> Result, MutinyError> { - let url = Url::parse(&format!("{}/v1/check-pubkey/{pubkey}", base_url,)) + nostr_client: Client, +) -> Result { + // create nostr event + let signer = nostr_client.signer().await?; + let event_builder = EventBuilder::new(REGISTRATION_CHECK_EVENT_KIND, "", []); + let event = signer.sign_event_builder(event_builder).await?; + + // send request + let url = Url::parse(&format!("{}/v1/check-registration", base_url)) .map_err(|_| MutinyError::ConnectionFailed)?; - let request = http_client.request(Method::GET, url); - + let request = http_client.request(Method::POST, url).json(&event); let res = utils::fetch_with_timeout(http_client, request.build().expect("should build req")) .await? - .json::>() + .json::() .await .map_err(|_| MutinyError::ConnectionFailed)?; diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 803b561d3..40d3edb0e 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -1078,7 +1078,7 @@ impl MutinyWalletBuilder { #[cfg(target_arch = "wasm32")] NostrSigner::NIP07(_) => None, }; - mw.start_hermes(profile_key)?; + mw.start_hermes(profile_key).await?; log_info!( mw.logger, @@ -2399,6 +2399,7 @@ impl MutinyWallet { self.logger.clone(), self.federation_storage.clone(), self.federations.clone(), + self.hermes_client.clone(), federation_code, ) .await @@ -2856,9 +2857,9 @@ impl MutinyWallet { } /// Starts up the hermes client if available - pub fn start_hermes(&self, profile_key: Option) -> Result<(), MutinyError> { + pub async fn start_hermes(&self, profile_key: Option) -> Result<(), MutinyError> { if let Some(hermes_client) = self.hermes_client.as_ref() { - hermes_client.start(profile_key)? + hermes_client.start(profile_key).await? } Ok(()) } @@ -2969,6 +2970,7 @@ pub(crate) async fn create_new_federation( logger: Arc, federation_storage: Arc>, federations: Arc>>>>, + hermes_client: Option>>, federation_code: InviteCode, ) -> Result { // Begin with a mutex lock so that nothing else can @@ -3021,7 +3023,7 @@ pub(crate) async fn create_new_federation( .await .insert(federation_id, Arc::new(new_federation)); - Ok(FederationIdentity { + let new_federation_identity = FederationIdentity { uuid: next_federation_uuid.clone(), federation_id, invite_code: federation_code, @@ -3029,7 +3031,22 @@ pub(crate) async fn create_new_federation( federation_expiry_timestamp, welcome_message, gateway_fees, - }) + }; + + // change the federation with hermes, if available + if let Some(h) = hermes_client { + match h + .change_federation_info(new_federation_identity.clone()) + .await + { + Ok(_) => (), + Err(e) => { + log_error!(logger, "could not change hermes federation: {e}") + } + } + } + + Ok(new_federation_identity) } #[derive(Deserialize)]