diff --git a/mutiny-core/src/federation.rs b/mutiny-core/src/federation.rs index 0398df691..a3b4f400a 100644 --- a/mutiny-core/src/federation.rs +++ b/mutiny-core/src/federation.rs @@ -1,4 +1,6 @@ -use crate::utils::{convert_from_fedimint_invoice, convert_to_fedimint_invoice, now, spawn}; +use crate::utils::{ + convert_from_fedimint_invoice, convert_to_fedimint_invoice, fetch_with_timeout, now, spawn, +}; use crate::{ error::{MutinyError, MutinyStorageError}, event::PaymentInfo, @@ -56,6 +58,7 @@ use futures_util::{pin_mut, StreamExt}; use hex_conservative::{DisplayHex, FromHex}; use lightning::{log_debug, log_error, log_info, log_trace, log_warn, util::logger::Logger}; use lightning_invoice::Bolt11Invoice; +use reqwest::Method; use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[cfg(not(target_arch = "wasm32"))] use std::time::Instant; @@ -152,7 +155,6 @@ pub struct FederationIdentity { pub meta_external_url: Option, pub onchain_deposits_disabled: Option, pub preview_message: Option, - pub sites: Option, pub public: Option, pub tos_url: Option, pub popup_end_timestamp: Option, @@ -162,6 +164,38 @@ pub struct FederationIdentity { pub social_recovery_disabled: Option, } +#[derive(Serialize, Deserialize, Debug)] +struct FederationMetaConfig { + #[serde(flatten)] + pub federations: std::collections::HashMap, +} + +// This is the FederationUrlConfig that refer to a specific federation +// Normal config information that might exist from their URL. +#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)] +pub struct FederationMeta { + // https://github.com/fedimint/fedimint/tree/master/docs/meta_fields + pub federation_name: Option, + pub federation_expiry_timestamp: Option, + pub welcome_message: Option, + pub gateway_fees: Option, + // undocumented parameters that fedi uses: https://meta.dev.fedibtc.com/meta.json + pub default_currency: Option, + pub federation_icon_url: Option, + pub max_balance_msats: Option, + pub max_invoice_msats: Option, + pub meta_external_url: Option, + pub onchain_deposits_disabled: Option, + pub preview_message: Option, + pub public: Option, + pub tos_url: Option, + pub popup_end_timestamp: Option, + pub popup_countdown_message: Option, + pub invite_codes_disabled: Option, + pub stability_pool_disabled: Option, + pub social_recovery_disabled: Option, +} + #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Site { pub id: Option, @@ -687,7 +721,9 @@ impl FederationClient { self.fedimint_client.clone(), self.invite_code.clone(), gateway_fees, + self.logger.clone(), ) + .await } // delete_fedimint_storage is not suggested at the moment due to the lack of easy restores @@ -697,53 +733,174 @@ impl FederationClient { } } -pub(crate) fn get_federation_identity( +pub(crate) async fn get_federation_identity( uuid: String, fedimint_client: ClientHandleArc, invite_code: InviteCode, gateway_fees: Option, + + logger: Arc, ) -> FederationIdentity { + let federation_id = fedimint_client.federation_id(); + let meta_external_url = fedimint_client.get_meta("meta_external_url"); + let config = if let Some(ref url) = meta_external_url { + log_info!( + logger, + "Getting config for {federation_id} from meta_external_url: {url}" + ); + let http_client = reqwest::Client::new(); + let request = http_client.request(Method::GET, url); + + match fetch_with_timeout(&http_client, request.build().expect("should build req")).await { + Ok(r) => match r.json::().await { + Ok(c) => + { + #[allow(clippy::map_clone)] + c.federations + .get(&federation_id.to_string()) + .map(|f| f.clone()) + } + Err(e) => { + log_error!(logger, "Error parsing meta config: {e}"); + None + } + }, + Err(e) => { + log_error!(logger, "Error fetching meta config: {e}"); + None + } + } + } else { + None + }; + FederationIdentity { uuid: uuid.clone(), - federation_id: fedimint_client.federation_id(), + federation_id, invite_code: invite_code.clone(), - federation_name: fedimint_client.get_meta("federation_name"), - federation_expiry_timestamp: fedimint_client.get_meta("federation_expiry_timestamp"), - welcome_message: fedimint_client.get_meta("welcome_message"), - gateway_fees, - default_currency: fedimint_client.get_meta("default_currency"), - federation_icon_url: fedimint_client.get_meta("federation_icon_url"), - max_balance_msats: fedimint_client - .get_meta("max_balance_msats") - .map(|v| v.parse().unwrap_or(0)), - max_invoice_msats: fedimint_client - .get_meta("max_invoice_msats") - .map(|v| v.parse().unwrap_or(0)), - meta_external_url: fedimint_client.get_meta("meta_external_url"), - onchain_deposits_disabled: fedimint_client - .get_meta("onchain_deposits_disabled") - .map(|v| v.parse().unwrap_or(false)), - preview_message: fedimint_client.get_meta("preview_message"), - sites: fedimint_client - .get_meta("sites") - .map(|v| serde_json::from_str(&v).unwrap_or_default()), - public: fedimint_client - .get_meta("public") - .map(|v| v.parse().unwrap_or(false)), - tos_url: fedimint_client.get_meta("tos_url"), - popup_end_timestamp: fedimint_client - .get_meta("popup_end_timestamp") - .map(|v| v.parse().unwrap_or(0)), - popup_countdown_message: fedimint_client.get_meta("popup_countdown_message"), - invite_codes_disabled: fedimint_client - .get_meta("invite_codes_disabled") - .map(|v| v.parse().unwrap_or(false)), - stability_pool_disabled: fedimint_client - .get_meta("stability_pool_disabled") - .map(|v| v.parse().unwrap_or(false)), - social_recovery_disabled: fedimint_client - .get_meta("social_recovery_disabled") - .map(|v| v.parse().unwrap_or(false)), + federation_name: merge_values( + fedimint_client.get_meta("federation_name").clone(), + config.as_ref().and_then(|c| c.federation_name.clone()), + ), + federation_expiry_timestamp: merge_values( + fedimint_client.get_meta("federation_expiry_timestamp"), + config + .as_ref() + .and_then(|c| c.federation_expiry_timestamp.clone()), + ), + welcome_message: merge_values( + fedimint_client.get_meta("welcome_message"), + config.as_ref().and_then(|c| c.welcome_message.clone()), + ), + gateway_fees, // Already merged using helper function... + default_currency: merge_values( + fedimint_client.get_meta("default_currency"), + config.as_ref().and_then(|c| c.default_currency.clone()), + ), + federation_icon_url: merge_values( + fedimint_client.get_meta("federation_icon_url"), + config.as_ref().and_then(|c| c.federation_icon_url.clone()), + ), + max_balance_msats: merge_values( + fedimint_client + .get_meta("max_balance_msats") + .map(|v| v.parse().unwrap_or(0)), + config + .as_ref() + .and_then(|c| c.max_balance_msats.clone().map(|v| v.parse().unwrap_or(0))), + ), + max_invoice_msats: merge_values( + fedimint_client + .get_meta("max_invoice_msats") + .map(|v| v.parse().unwrap_or(0)), + config + .as_ref() + .and_then(|c| c.max_invoice_msats.clone().map(|v| v.parse().unwrap_or(0))), + ), + meta_external_url, // Already set... + onchain_deposits_disabled: merge_values( + fedimint_client + .get_meta("onchain_deposits_disabled") + .map(|v| v.parse().unwrap_or(false)), + config.as_ref().and_then(|c| { + c.onchain_deposits_disabled + .clone() + .map(|v| v.parse().unwrap_or(false)) + }), + ), + preview_message: merge_values( + fedimint_client.get_meta("preview_message"), + config.as_ref().and_then(|c| c.preview_message.clone()), + ), + public: merge_values( + fedimint_client + .get_meta("public") + .map(|v| v.parse().unwrap_or(false)), + config + .as_ref() + .and_then(|c| c.public.clone().map(|v| v.parse().unwrap_or(false))), + ), + tos_url: merge_values( + fedimint_client.get_meta("tos_url"), + config.as_ref().and_then(|c| c.tos_url.clone()), + ), + popup_end_timestamp: merge_values( + fedimint_client + .get_meta("popup_end_timestamp") + .map(|v| v.parse().unwrap_or(0)), + config.as_ref().and_then(|c| { + c.popup_end_timestamp + .clone() + .map(|v| v.parse().unwrap_or(0)) + }), + ), + popup_countdown_message: merge_values( + fedimint_client + .get_meta("popup_countdown_message") + .map(|v| v.to_string()), + config + .as_ref() + .and_then(|c| c.popup_countdown_message.clone()), + ), + invite_codes_disabled: merge_values( + fedimint_client + .get_meta("invite_codes_disabled") + .map(|v| v.parse().unwrap_or(false)), + config.as_ref().and_then(|c| { + c.invite_codes_disabled + .clone() + .map(|v| v.parse().unwrap_or(false)) + }), + ), + stability_pool_disabled: merge_values( + fedimint_client + .get_meta("stability_pool_disabled") + .map(|v| v.parse().unwrap_or(false)), + config.as_ref().and_then(|c| { + c.stability_pool_disabled + .clone() + .map(|v| v.parse().unwrap_or(false)) + }), + ), + social_recovery_disabled: merge_values( + fedimint_client + .get_meta("social_recovery_disabled") + .map(|v| v.parse().unwrap_or(false)), + config.as_ref().and_then(|c| { + c.social_recovery_disabled + .clone() + .map(|v| v.parse().unwrap_or(false)) + }), + ), + } +} + +fn merge_values(a: Option, b: Option) -> Option { + match (a, b) { + // If a has value return that; otherwise, use the one from b if available. + (Some(val), _) => Some(val), + (None, Some(val)) => Some(val), + (None, None) => None, } } diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 52a209518..02e710e54 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -3201,7 +3201,9 @@ pub(crate) async fn create_new_federation( new_federation.fedimint_client.clone(), federation_code.clone(), gateway_fees, - ); + logger.clone(), + ) + .await; federations .write()