Skip to content

Commit

Permalink
Merge pull request #1130 from MutinyWallet/hermes-address-change
Browse files Browse the repository at this point in the history
  • Loading branch information
benthecarman authored Apr 12, 2024
2 parents 4032199 + ba2bf0f commit b5be4c6
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 27 deletions.
212 changes: 190 additions & 22 deletions mutiny-core/src/hermes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -45,6 +45,10 @@ 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);
const DISABLE_ZAPS_EVENT_KIND: Kind = Kind::Custom(93_188);

#[derive(Deserialize, Serialize)]
pub struct RegisterRequest {
pub name: Option<String>,
Expand Down Expand Up @@ -127,10 +131,10 @@ impl<S: MutinyStorage> HermesClient<S> {
/// 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<Keys>) -> Result<(), MutinyError> {
pub async fn start(&self, profile_key: Option<Keys>) -> 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 mut time_stamp = match last_sync_time {
let time_stamp = match last_sync_time {
None => {
let now = Timestamp::from(0);
self.storage.set_dm_sync_time(now.as_u64(), true)?;
Expand All @@ -139,35 +143,92 @@ impl<S: MutinyStorage> HermesClient<S> {
Some(time) => Timestamp::from(time + 1), // add one so we get only new events
};

// if we have a time stamp before the bug, reset to 0
if time_stamp.as_u64() < 1712862315 {
time_stamp = Timestamp::from(0);
}

// check to see if we currently have an address
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 let Some(f) = first_federation {
// if a registered federation exists and is what we have
// then there is no reason to update
if o.federation_id.is_some()
&& f.federation_id == o.federation_id.unwrap()
{
break;
}

// user has a federation registered but is different than current
// so we should update it
match change_federation_info(
&http_client_check_clone,
&base_url_check_clone,
nostr_client_check_clone,
current_address_check_clone,
f,
&logger_check_clone,
)
.await
{
Ok(_) => (),
Err(e) => {
log_error!(
logger_check_clone,
"could not change the federation: {e}"
);
}
}
} else {
// handle the case where the user no longer has a federation
// if user is already disabled, no need to call again
if !o.disabled_zaps {
match disable_zaps(
&http_client_check_clone,
&base_url_check_clone,
nostr_client_check_clone,
current_address_check_clone,
&logger_check_clone,
)
.await
{
Ok(_) => (),
Err(e) => {
log_error!(
logger_check_clone,
"could not disable zaps: {e}"
);
}
}
}
}

break;
}
Err(e) => {
Expand Down Expand Up @@ -257,6 +318,32 @@ impl<S: MutinyStorage> HermesClient<S> {
Ok(())
}

pub async fn change_federation_info(
&self,
federation: FederationIdentity,
) -> Result<(), MutinyError> {
change_federation_info(
&self.http_client,
&self.base_url,
self.client.clone(),
self.current_address.clone(),
federation,
&self.logger,
)
.await
}

pub async fn disable_zaps(&self) -> Result<(), MutinyError> {
disable_zaps(
&self.http_client,
&self.base_url,
self.client.clone(),
self.current_address.clone(),
&self.logger,
)
.await
}

pub async fn check_available_name(&self, name: String) -> Result<bool, MutinyError> {
check_name_request(&self.http_client, &self.base_url, name).await
}
Expand Down Expand Up @@ -333,8 +420,77 @@ impl<S: MutinyStorage> HermesClient<S> {
None => None,
}
}
}

#[derive(Serialize, Deserialize, Clone)]
pub struct ChangeFederationRequest {
pub name: Option<String>,
pub federation_id: Option<FederationId>,
}

async fn change_federation_info(
http_client: &reqwest::Client,
base_url: &str,
nostr_client: Client,
current_address: Arc<RwLock<(Option<String>, bool)>>,
federation: FederationIdentity,
logger: &MutinyLogger,
) -> Result<(), MutinyError> {
// make sure name is registered already
if current_address.read().await.0.is_none() {
return Ok(());
}

// 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)?;

log_info!(logger, "changed federation info to current federation");

Ok(())
}

async fn disable_zaps(
http_client: &reqwest::Client,
base_url: &str,
nostr_client: Client,
current_address: Arc<RwLock<(Option<String>, bool)>>,
logger: &MutinyLogger,
) -> Result<(), MutinyError> {
// make sure name is registered already
if current_address.read().await.0.is_none() {
return Ok(());
}

// create nostr event
let signer = nostr_client.signer().await?;
let event_builder = EventBuilder::new(DISABLE_ZAPS_EVENT_KIND, "", []);
let event = signer.sign_event_builder(event_builder).await?;

// send request
let url = Url::parse(&format!("{}/v1/disable-zaps", 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)?;

log_info!(logger, "disabled zaps for the user");

// TODO need a way to change the federation if the user's federation changes
Ok(())
}

fn find_hermes_token(
Expand All @@ -347,18 +503,30 @@ 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<String>,
pub federation_id: Option<FederationId>,
pub disabled_zaps: bool,
}

async fn check_hermes_registration_info(
http_client: &reqwest::Client,
base_url: &str,
pubkey: nostr::PublicKey,
) -> Result<Option<String>, MutinyError> {
let url = Url::parse(&format!("{}/v1/check-pubkey/{pubkey}", base_url,))
nostr_client: Client,
) -> Result<RegistrationInfo, MutinyError> {
// 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::<Option<String>>()
.json::<RegistrationInfo>()
.await
.map_err(|_| MutinyError::ConnectionFailed)?;

Expand Down
37 changes: 32 additions & 5 deletions mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@ impl<S: MutinyStorage> MutinyWalletBuilder<S> {
#[cfg(target_arch = "wasm32")]
NostrSigner::NIP07(_) => None,
};
mw.start_hermes(profile_key)?;
mw.start_hermes(profile_key).await?;

log_info!(
mw.logger,
Expand Down Expand Up @@ -2399,6 +2399,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
self.logger.clone(),
self.federation_storage.clone(),
self.federations.clone(),
self.hermes_client.clone(),
federation_code,
)
.await
Expand Down Expand Up @@ -2451,6 +2452,16 @@ impl<S: MutinyStorage> MutinyWallet<S> {
return Err(MutinyError::NotFound);
}

// remove the federation from hermes
if let Some(h) = self.hermes_client.as_ref() {
match h.disable_zaps().await {
Ok(_) => (),
Err(e) => {
log_error!(self.logger, "could not disable hermes zaps: {e}")
}
}
}

Ok(())
}

Expand Down Expand Up @@ -2853,9 +2864,9 @@ impl<S: MutinyStorage> MutinyWallet<S> {
}

/// Starts up the hermes client if available
pub fn start_hermes(&self, profile_key: Option<Keys>) -> Result<(), MutinyError> {
pub async fn start_hermes(&self, profile_key: Option<Keys>) -> Result<(), MutinyError> {
if let Some(hermes_client) = self.hermes_client.as_ref() {
hermes_client.start(profile_key)?
hermes_client.start(profile_key).await?
}
Ok(())
}
Expand Down Expand Up @@ -2966,6 +2977,7 @@ pub(crate) async fn create_new_federation<S: MutinyStorage>(
logger: Arc<MutinyLogger>,
federation_storage: Arc<RwLock<FederationStorage>>,
federations: Arc<RwLock<HashMap<FederationId, Arc<FederationClient<S>>>>>,
hermes_client: Option<Arc<HermesClient<S>>>,
federation_code: InviteCode,
) -> Result<FederationIdentity, MutinyError> {
// Begin with a mutex lock so that nothing else can
Expand Down Expand Up @@ -3018,15 +3030,30 @@ pub(crate) async fn create_new_federation<S: MutinyStorage>(
.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,
federation_name,
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)]
Expand Down

0 comments on commit b5be4c6

Please sign in to comment.