Skip to content

Commit c637f09

Browse files
committed
feat: NIP-87 federation discovery
1 parent 84c2330 commit c637f09

File tree

4 files changed

+351
-10
lines changed

4 files changed

+351
-10
lines changed

assets/icons/search.svg

+1
Loading

src/nostr.rs

+62-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
use std::collections::BTreeMap;
1+
use std::collections::{BTreeMap, BTreeSet};
22
use std::fmt::Debug;
3+
use std::str::FromStr;
34
use std::time::Duration;
45

6+
use fedimint_core::config::FederationId;
7+
use fedimint_core::invite_code::InviteCode;
58
use iced::Subscription;
69
use nostr_relay_pool::RelayStatus;
7-
use nostr_sdk::Url;
10+
use nostr_sdk::{Alphabet, EventSource, Filter, Kind, PublicKey, SingleLetterTag, TagKind, Url};
11+
12+
const D_TAG: TagKind = TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::D));
13+
const U_TAG: TagKind = TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::U));
814

915
#[derive(Default, Debug, Clone, PartialEq, Eq)]
1016
pub struct NostrState {
@@ -69,6 +75,60 @@ impl NostrModule {
6975
)
7076
}
7177

78+
/// Fetches recommendations for Fedimint federations.
79+
/// For every federation, this function returns a tuple containing the set of nostr npubs of
80+
/// users who have recommended the federation, and the set of all invite codes that have been
81+
/// provided for the federation. All federations returned are guaranteed to contain at least
82+
/// one nostr npub and one invite code.
83+
pub async fn find_federations(
84+
&self,
85+
) -> Result<
86+
BTreeMap<FederationId, (BTreeSet<PublicKey>, BTreeSet<InviteCode>)>,
87+
nostr_sdk::client::Error,
88+
> {
89+
let fedimint_recommendation_events = self
90+
.client
91+
.get_events_of(
92+
vec![Filter::new()
93+
.kind(Kind::Custom(38_000))
94+
.custom_tag(SingleLetterTag::lowercase(Alphabet::K), vec!["38173"])
95+
.custom_tag(SingleLetterTag::lowercase(Alphabet::N), vec!["mainnet"])],
96+
EventSource::both(None),
97+
)
98+
.await?;
99+
100+
let mut federations = BTreeMap::new();
101+
102+
for recommendation_event in &fedimint_recommendation_events {
103+
for d_tag in recommendation_event.get_tags_content(D_TAG) {
104+
let Ok(federation_id) = FederationId::from_str(d_tag) else {
105+
continue;
106+
};
107+
108+
let (recommenders, invite_codes) = federations
109+
.entry(federation_id)
110+
.or_insert_with(|| (BTreeSet::new(), BTreeSet::new()));
111+
112+
recommenders.insert(recommendation_event.pubkey);
113+
114+
for u_tag in recommendation_event.get_tags_content(U_TAG) {
115+
if let Ok(invite_code) = InviteCode::from_str(u_tag) {
116+
if invite_code.federation_id() == federation_id {
117+
invite_codes.insert(invite_code);
118+
}
119+
}
120+
}
121+
}
122+
}
123+
124+
// It's possible for a user to recommend a federation without providing any invite codes.
125+
// If a federation has no recommendations that include any invite codes, we don't want to
126+
// include it in the list of federations since it's not possible to join it.
127+
federations.retain(|_, (_, invite_codes)| !invite_codes.is_empty());
128+
129+
Ok(federations)
130+
}
131+
72132
/// Fetches the current state of the Nostr SDK client.
73133
/// Note: This is async because it's grabbing read locks
74134
/// on the relay `RwLock`s. No network requests are made.

0 commit comments

Comments
 (0)