@@ -272,6 +272,59 @@ impl NostrDiscoveredFedimint {
272272 }
273273 }
274274 }
275+
276+ pub fn merge ( & mut self , other : & Self ) {
277+ // merge metadata
278+ if let Some ( other) = other. metadata . as_ref ( ) {
279+ match self . metadata . as_mut ( ) {
280+ Some ( self_metadata) => {
281+ if self_metadata. name . is_none ( ) {
282+ self_metadata. name = other. name . clone ( ) ;
283+ }
284+ if self_metadata. display_name . is_none ( ) {
285+ self_metadata. display_name = other. display_name . clone ( ) ;
286+ }
287+ if self_metadata. picture . is_none ( ) {
288+ self_metadata. picture = other. picture . clone ( ) ;
289+ }
290+ }
291+ None => {
292+ self . metadata = Some ( other. clone ( ) ) ;
293+ }
294+ }
295+ }
296+ // merge created_at
297+ match self . created_at . as_ref ( ) {
298+ Some ( self_created_at) => {
299+ if other
300+ . created_at
301+ . is_some_and ( |other| * self_created_at > other)
302+ {
303+ self . created_at = other. created_at ;
304+ }
305+ }
306+ None => {
307+ self . created_at = other. created_at ;
308+ }
309+ }
310+
311+ // merge invite codes
312+ let mut invite_codes = self . invite_codes . clone ( ) ;
313+ for other_invite_code in other. invite_codes . iter ( ) {
314+ if !invite_codes. contains ( other_invite_code) {
315+ invite_codes. push ( other_invite_code. clone ( ) ) ;
316+ }
317+ }
318+
319+ self . invite_codes = invite_codes;
320+
321+ // merge expire_timestamp
322+ if let Some ( other) = other. expire_timestamp {
323+ if other < self . expire_timestamp . unwrap_or ( 0 ) {
324+ self . expire_timestamp = Some ( other) ;
325+ }
326+ }
327+ }
275328}
276329
277330impl < S : MutinyStorage , P : PrimalApi , C : NostrClient > NostrManager < S , P , C > {
@@ -2045,70 +2098,77 @@ impl<S: MutinyStorage, P: PrimalApi, C: NostrClient> NostrManager<S, P, C> {
20452098 )
20462099 . await ?;
20472100
2048- let mut mints: Vec < NostrDiscoveredFedimint > = events
2049- . iter ( )
2050- . filter_map ( |event| {
2051- // only process federation announcements
2052- if event. kind != Kind :: from ( 38173 ) {
2053- return None ;
2054- }
2101+ let mut mints: HashMap < FederationId , NostrDiscoveredFedimint > = HashMap :: new ( ) ;
20552102
2056- let network_tag = event. tags . iter ( ) . find_map ( |tag| {
2057- if tag. kind ( ) == TagKind :: SingleLetter ( SingleLetterTag :: lowercase ( Alphabet :: N ) )
2058- {
2059- Some ( tag. as_vec ( ) . get ( 1 ) . cloned ( ) . unwrap_or_default ( ) )
2060- } else {
2061- None
2062- }
2063- } ) ;
2103+ for event in events. iter ( ) {
2104+ // only process federation announcements
2105+ if event. kind != Kind :: from ( 38173 ) {
2106+ continue ;
2107+ }
20642108
2065- // if the network tag is missing, we assume it is on mainnet
2066- let network_tag = network_tag
2067- . as_deref ( )
2068- . unwrap_or ( network_to_string ( Network :: Bitcoin ) ) ;
2069- // skip if the network doesn't match
2070- if network_tag != network_str {
2071- return None ;
2109+ let network_tag = event. tags . iter ( ) . find_map ( |tag| {
2110+ if tag. kind ( ) == TagKind :: SingleLetter ( SingleLetterTag :: lowercase ( Alphabet :: N ) ) {
2111+ Some ( tag. as_vec ( ) . get ( 1 ) . cloned ( ) . unwrap_or_default ( ) )
2112+ } else {
2113+ None
20722114 }
2115+ } ) ;
20732116
2074- let federation_id = event. tags . iter ( ) . find_map ( |tag| {
2075- if let Tag :: Identifier ( id) = tag {
2076- FederationId :: from_str ( id) . ok ( )
2077- } else {
2078- None
2079- }
2080- } ) ?;
2081-
2082- let invite_codes: Vec < InviteCode > = event
2083- . tags
2084- . iter ( )
2085- . filter_map ( |tag| parse_invite_code_from_tag ( tag, & federation_id) )
2086- . collect ( ) ;
2117+ // if the network tag is missing, we assume it is on mainnet
2118+ let network_tag = network_tag
2119+ . as_deref ( )
2120+ . unwrap_or ( network_to_string ( Network :: Bitcoin ) ) ;
2121+ // skip if the network doesn't match
2122+ if network_tag != network_str {
2123+ continue ;
2124+ }
20872125
2088- // if we have no invite codes left, skip
2089- if invite_codes . is_empty ( ) {
2090- None
2126+ let federation_id = event . tags . iter ( ) . find_map ( |tag| {
2127+ if let Tag :: Identifier ( id ) = tag {
2128+ FederationId :: from_str ( id ) . ok ( )
20912129 } else {
2092- // try to parse the metadata if available, it's okay if it fails
2093- // todo could lookup kind 0 of the federation to get the metadata as well
2094- let metadata = serde_json:: from_str ( & event. content ) . ok ( ) ;
2095- Some ( NostrDiscoveredFedimint {
2096- invite_codes,
2097- id : federation_id,
2098- pubkey : Some ( event. pubkey ) ,
2099- event_id : Some ( event. id ) ,
2100- created_at : Some ( event. created_at . as_u64 ( ) ) ,
2101- metadata,
2102- recommendations : vec ! [ ] , // we'll add these in the next step
2103- expire_timestamp : None ,
2104- } )
2130+ None
21052131 }
2106- } )
2107- . collect ( ) ;
2132+ } ) ;
2133+
2134+ let federation_id = match federation_id {
2135+ Some ( id) => id,
2136+ None => continue ,
2137+ } ;
2138+
2139+ let invite_codes: Vec < InviteCode > = event
2140+ . tags
2141+ . iter ( )
2142+ . filter_map ( |tag| parse_invite_code_from_tag ( tag, & federation_id) )
2143+ . collect ( ) ;
21082144
2109- // remove duplicates by federation id, keep the one with the newest event
2110- mints. sort_by ( |a, b| b. created_at . cmp ( & a. created_at ) ) ;
2111- mints. dedup_by ( |a, b| a. id == b. id ) ;
2145+ // if we have no invite codes left, skip
2146+ if !invite_codes. is_empty ( ) {
2147+ // try to parse the metadata if available, it's okay if it fails
2148+ // todo could lookup kind 0 of the federation to get the metadata as well
2149+ let metadata = serde_json:: from_str ( & event. content ) . ok ( ) ;
2150+ let mint = NostrDiscoveredFedimint {
2151+ invite_codes,
2152+ id : federation_id,
2153+ pubkey : Some ( event. pubkey ) ,
2154+ event_id : Some ( event. id ) ,
2155+ created_at : Some ( event. created_at . as_u64 ( ) ) ,
2156+ metadata,
2157+ recommendations : vec ! [ ] , // we'll add these in the next step
2158+ expire_timestamp : None ,
2159+ } ;
2160+
2161+ match mints. get_mut ( & federation_id) {
2162+ Some ( m) => {
2163+ // if we already have a mint for this federation, merge the two
2164+ m. merge ( & mint) ;
2165+ }
2166+ None => {
2167+ mints. insert ( federation_id, mint) ;
2168+ }
2169+ }
2170+ }
2171+ }
21122172
21132173 // add on contact recommendations to mints
21142174 for event in events {
@@ -2170,8 +2230,8 @@ impl<S: MutinyStorage, P: PrimalApi, C: NostrClient> NostrManager<S, P, C> {
21702230
21712231 // todo read `a` tag recommendations as well
21722232
2173- match mints. iter_mut ( ) . find ( |m | m. id == federation_id) {
2174- Some ( mint) => {
2233+ match mints. iter_mut ( ) . find ( |( _ , m ) | m. id == federation_id) {
2234+ Some ( ( _id , mint) ) => {
21752235 mint. recommendations . push ( contact) ;
21762236 }
21772237 None => {
@@ -2189,14 +2249,14 @@ impl<S: MutinyStorage, P: PrimalApi, C: NostrClient> NostrManager<S, P, C> {
21892249 recommendations : vec ! [ contact] ,
21902250 expire_timestamp : None ,
21912251 } ;
2192- mints. push ( mint) ;
2252+ mints. insert ( federation_id , mint) ;
21932253 }
21942254 }
21952255 }
21962256 }
21972257
21982258 // sort the recommendations by whether they are contacts or not and if they have an image
2199- for mint in mints. iter_mut ( ) {
2259+ for ( _ , mint) in mints. iter_mut ( ) {
22002260 mint. recommendations . sort_by ( |a, b| {
22012261 let a_is_contact = a
22022262 . npub
@@ -2228,6 +2288,7 @@ impl<S: MutinyStorage, P: PrimalApi, C: NostrClient> NostrManager<S, P, C> {
22282288 }
22292289
22302290 // sort mints by most recommended then by oldest
2291+ let mut mints: Vec < NostrDiscoveredFedimint > = mints. into_values ( ) . collect ( ) ;
22312292 mints. sort ( ) ;
22322293
22332294 // try to get federation info from client config if not in event
0 commit comments