Skip to content
This repository has been archived by the owner on Feb 3, 2025. It is now read-only.

Commit

Permalink
Mint discoverability
Browse files Browse the repository at this point in the history
  • Loading branch information
benthecarman committed Mar 8, 2024
1 parent 8369dfa commit cccc1ee
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 1 deletion.
147 changes: 146 additions & 1 deletion mutiny-core/src/nostr/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::labels::Contact;
use crate::logging::MutinyLogger;
use crate::nostr::nip49::{NIP49BudgetPeriod, NIP49URI};
use crate::nostr::nwc::{
Expand All @@ -13,6 +14,8 @@ use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey};
use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::{Secp256k1, Signing};
use bitcoin::{hashes::hex::FromHex, secp256k1::ThirtyTwoByteHash};
use fedimint_core::api::InviteCode;
use fedimint_core::config::FederationId;
use futures::{pin_mut, select, FutureExt};
use futures_util::lock::Mutex;
use lightning::util::logger::Logger;
Expand All @@ -24,7 +27,8 @@ use nostr::nips::nip04::{decrypt, encrypt};
use nostr::nips::nip47::*;
use nostr::{Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind, Metadata, Tag, Timestamp};
use nostr_sdk::{Client, NostrSigner, RelayPoolNotification};
use std::collections::HashSet;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::{atomic::Ordering, Arc, RwLock};
use std::time::Duration;
use std::{str::FromStr, sync::atomic::AtomicBool};
Expand Down Expand Up @@ -98,6 +102,25 @@ pub struct NostrManager<S: MutinyStorage> {
pub client: Client,
}

/// A fedimint we discovered on nostr
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NostrDiscoveredFedimint {
/// Invite Code to join the federation
pub invite_codes: Vec<InviteCode>,
/// The federation id
pub id: FederationId,
/// Pubkey of the nostr event
pub pubkey: nostr::PublicKey,
/// Event id of the nostr event
pub event_id: EventId,
/// Date this fedimint was announced on nostr
pub created_at: u64,
/// Metadata about the fedimint
pub metadata: Option<Metadata>,
/// Contacts that recommend this fedimint
pub recommendations: Vec<Contact>,
}

impl<S: MutinyStorage> NostrManager<S> {
/// Connect to the nostr relays
pub async fn connect(&self) -> Result<(), MutinyError> {
Expand Down Expand Up @@ -1168,6 +1191,128 @@ impl<S: MutinyStorage> NostrManager<S> {
Ok(event_id)
}

/// Queries our relays for federation announcements
pub async fn discover_federations(&self) -> Result<Vec<NostrDiscoveredFedimint>, MutinyError> {
// get contacts by npub
let npubs: HashMap<nostr::PublicKey, Contact> = self
.storage
.get_contacts()?
.into_iter()
.filter_map(|(_, c)| c.npub.map(|npub| (npub, c)))
.collect();

// filter for finding mint announcements
let mints = Filter::new().kind(Kind::from(38173));
// filter for finding federation recommendations
let recommendations = Filter::new()
.kind(Kind::from(18173))
.authors(npubs.keys().copied());
let events = self
.client
.get_events_of(vec![mints, recommendations], Some(Duration::from_secs(5)))
.await?;

let mut mints = events
.iter()
.filter_map(|event| {
// only process federation announcements
if event.kind != Kind::from(38173) {
return None;
}

let federation_id = event.tags.iter().find_map(|tag| {
if let Tag::Identifier(id) = tag {
FederationId::from_str(id).ok()
} else {
None
}
})?;

let invite_codes: Vec<InviteCode> = event
.tags
.iter()
.filter_map(|tag| {
if let Tag::AbsoluteURL(code) = tag {
InviteCode::from_str(&code.to_string())
.ok()
// remove any invite codes that point to different federation
.filter(|c| c.federation_id() == federation_id)
} else {
None
}
})
.collect();

// if we have no invite codes left, skip
if invite_codes.is_empty() {
None
} else {
// try to parse the metadata if available, it's okay if it fails
// todo could lookup kind 0 of the federation to get the metadata as well
let metadata = serde_json::from_str(&event.content).ok();
Some(NostrDiscoveredFedimint {
invite_codes,
id: federation_id,
pubkey: event.pubkey,
event_id: event.id,
created_at: event.created_at.as_u64(),
metadata,
recommendations: vec![],
})
}
})
.collect();

// if we have no npubs, we can't get recommendations
if npubs.is_empty() {
return Ok(mints);
}

// add on contact recommendations to mints
for event in events {
// only process federation recommendations
if event.kind != Kind::from(18173) {
continue;
}

let contact = match npubs.get(&event.pubkey) {
Some(contact) => contact.clone(),
None => continue,
};

let recommendations: Vec<InviteCode> = event
.tags
.iter()
.filter_map(|tag| {
let vec = tag.as_vec();
// if there's 3 elements, make sure the identifier is for a fedimint
// if there's 2 elements, just try to parse the invite code
if (vec.len() == 3 && vec[0] == "u" && vec[2] == "fedimint")
|| (vec.len() == 2 && vec[0] == "u")
{
InviteCode::from_str(&vec[1]).ok()
} else {
None
}
})
.collect();

for invite_code in recommendations {
if let Some(mint) = mints
.iter_mut()
.find(|m| m.invite_codes.contains(&invite_code))
{
mint.recommendations.push(contact.clone());
}
}
}

// sort by most recommended
mints.sort_by(|a, b| b.recommendations.len().cmp(&a.recommendations.len()));

Ok(mints)
}

/// Derives the client and server keys for Nostr Wallet Connect given a profile index
/// The left key is the client key and the right key is the server key
pub(crate) fn derive_nwc_keys<C: Signing>(
Expand Down
8 changes: 8 additions & 0 deletions mutiny-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,14 @@ impl MutinyWallet {
Ok(self.inner.recover_federation_backups().await?)
}

/// Queries our relays for federation announcements
pub async fn discover_federations(
&self,
) -> Result<JsValue /* Vec<NostrDiscoveredFedimint> */, MutinyJsError> {
let federations = self.inner.nostr.discover_federations().await?;
Ok(JsValue::from_serde(&federations)?)
}

pub fn get_address_labels(
&self,
) -> Result<JsValue /* Map<Address, Vec<String>> */, MutinyJsError> {
Expand Down

0 comments on commit cccc1ee

Please sign in to comment.