Skip to content

Commit

Permalink
feat: NIP-87 federation discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
tvolk131 committed Oct 13, 2024
1 parent b844ad4 commit 9feeea8
Show file tree
Hide file tree
Showing 4 changed files with 361 additions and 14 deletions.
1 change: 1 addition & 0 deletions assets/icons/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 64 additions & 6 deletions src/nostr.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Debug;
use std::str::FromStr;
use std::time::Duration;

use fedimint_core::config::FederationId;
use fedimint_core::invite_code::InviteCode;
use iced::Subscription;
use nostr_relay_pool::RelayStatus;
use nostr_sdk::Url;
use nostr_sdk::{Alphabet, EventSource, Filter, Kind, PublicKey, SingleLetterTag, TagKind, Url};

#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct NostrState {
Expand Down Expand Up @@ -46,7 +49,7 @@ impl NostrModule {
pub fn subscription(&self) -> Subscription<NostrState> {
const POLL_DURATION: Duration = Duration::from_millis(200);

let client = self.client.clone();
let this = self.clone();

Subscription::run_with_id(
std::any::TypeId::of::<NostrState>(),
Expand All @@ -56,8 +59,9 @@ impl NostrModule {
// ID is new.
async_stream::stream! {
let mut last_state = NostrState::default();

loop {
let new_state = Self::get_state(&client).await;
let new_state = this.get_state().await;
if new_state != last_state {
yield new_state.clone();
last_state = new_state;
Expand All @@ -69,13 +73,67 @@ impl NostrModule {
)
}

pub async fn find_federations(
&self,
) -> Result<
BTreeMap<FederationId, (BTreeSet<PublicKey>, BTreeSet<InviteCode>)>,
nostr_sdk::client::Error,
> {
let fedimint_recommendation_events = self
.client
.get_events_of(
vec![Filter::new()
.kind(Kind::Custom(38_000))
.custom_tag(SingleLetterTag::lowercase(Alphabet::K), vec!["38173"])
.custom_tag(SingleLetterTag::lowercase(Alphabet::N), vec!["mainnet"])],
EventSource::both(None),
)
.await?;

let mut federations = BTreeMap::new();

for recommendation_event in &fedimint_recommendation_events {
for d_tag in recommendation_event.get_tags_content(TagKind::SingleLetter(
SingleLetterTag::lowercase(Alphabet::D),
)) {
if let Ok(federation_id) = FederationId::from_str(d_tag) {
let (recommenders, invite_codes) = federations
.entry(federation_id)
.or_insert_with(|| (BTreeSet::new(), BTreeSet::new()));

recommenders.insert(recommendation_event.pubkey);
for u_tag in recommendation_event.get_tags_content(TagKind::SingleLetter(
SingleLetterTag::lowercase(Alphabet::U),
)) {
if let Ok(invite_code) = InviteCode::from_str(u_tag) {
if invite_code.federation_id() == federation_id {
invite_codes.insert(invite_code);
}
}
}
}
}
}

println!("{federations:#?}");
println!(
"Found {} recommendations for {} federations",
fedimint_recommendation_events.len(),
federations.len()
);

federations.retain(|_, (_, invite_codes)| !invite_codes.is_empty());

Ok(federations)
}

/// Fetches the current state of the Nostr SDK client.
/// Note: This is async because it's grabbing read locks
/// on the relay `RwLock`s. No network requests are made.
async fn get_state(client: &nostr_sdk::Client) -> NostrState {
async fn get_state(&self) -> NostrState {
let mut relay_connections = BTreeMap::new();

for (url, relay) in client.relays().await {
for (url, relay) in self.client.relays().await {
relay_connections.insert(url.clone(), relay.status().await);
}

Expand Down
Loading

0 comments on commit 9feeea8

Please sign in to comment.