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

Commit

Permalink
Create nostr profile
Browse files Browse the repository at this point in the history
  • Loading branch information
benthecarman committed Jan 25, 2024
1 parent 9ef7a9f commit 6f0e3c9
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 4 deletions.
41 changes: 40 additions & 1 deletion mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ use crate::{nostr::NostrManager, utils::sleep};
use ::nostr::key::XOnlyPublicKey;
use ::nostr::nips::nip57;
use ::nostr::prelude::ZapRequestData;
use ::nostr::{Event, EventId, JsonUtil, Kind};
use ::nostr::{Event, EventId, JsonUtil, Kind, Metadata};
use async_lock::RwLock;
use bdk_chain::ConfirmationTime;
use bip39::Mnemonic;
Expand All @@ -80,6 +80,7 @@ use bitcoin::util::bip32::ExtendedPrivKey;
use bitcoin::Network;
use fedimint_core::{api::InviteCode, config::FederationId};
use futures::{pin_mut, select, FutureExt};
use futures_util::join;
use lightning::ln::PaymentHash;
use lightning::{log_debug, util::logger::Logger};
use lightning::{log_error, log_info, log_warn};
Expand Down Expand Up @@ -1401,6 +1402,44 @@ impl<S: MutinyStorage> MutinyWallet<S> {
.map_err(|_| MutinyError::NostrError)
}

/// Syncs all of our nostr data from the configured primal instance
pub async fn sync_nostr(&self) -> Result<(), MutinyError> {
let contacts_fut = self.sync_nostr_contacts(self.nostr.public_key);
let profile_fut = self.sync_nostr_profile();

// join futures and handle result
let (contacts_res, profile_res) = join!(contacts_fut, profile_fut);
contacts_res?;
profile_res?;

Ok(())
}

/// Fetches our latest nostr profile from primal and saves to storage
async fn sync_nostr_profile(&self) -> Result<(), MutinyError> {
let url = self
.config
.primal_url
.as_deref()
.unwrap_or("https://primal-cache.mutinywallet.com/api");
let client = reqwest::Client::new();

let body = json!(["user_profile", { "pubkey": self.nostr.public_key } ]);
let data: Vec<Value> = Self::primal_request(&client, url, body).await?;

if let Some(json) = data.first().cloned() {
let event: Event = serde_json::from_value(json)?;
if event.kind != Kind::Metadata {
return Ok(());
}

let metadata: Metadata = serde_json::from_str(&event.content)?;
self.storage.set_nostr_profile(metadata)?;
}

Ok(())
}

/// Get contacts from the given npub and sync them to the wallet
pub async fn sync_nostr_contacts(&self, npub: XOnlyPublicKey) -> Result<(), MutinyError> {
let url = self
Expand Down
53 changes: 51 additions & 2 deletions mutiny-core/src/nostr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ use bitcoin::{
use futures::{pin_mut, select, FutureExt};
use futures_util::lock::Mutex;
use lightning::util::logger::Logger;
use lightning::{log_debug, log_error, log_warn};
use lightning::{log_debug, log_error, log_info, log_warn};
use lightning_invoice::Bolt11Invoice;
use lnurl::lnurl::LnUrl;
use nostr::key::{SecretKey, XOnlyPublicKey};
use nostr::nips::nip47::*;
use nostr::prelude::{decrypt, encrypt};
use nostr::{Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind, Tag, Timestamp};
use nostr::{
url::Url, Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind, Metadata, Tag, Timestamp,
};
use nostr_sdk::{Client, ClientSigner, RelayPoolNotification};
use std::collections::HashSet;
use std::sync::{atomic::Ordering, Arc, RwLock};
Expand Down Expand Up @@ -188,6 +191,52 @@ impl<S: MutinyStorage> NostrManager<S> {
Ok(nwc)
}

/// Sets the user's nostr profile metadata
pub async fn edit_profile(
&self,
name: Option<String>,
img_url: Option<Url>,
lnurl: Option<LnUrl>,
nip05: Option<String>,
) -> Result<Metadata, MutinyError> {
let current = self.get_profile()?;

let with_name = if let Some(name) = name {
current.name(name)
} else {
current
};
let with_img = if let Some(img_url) = img_url {
with_name.picture(img_url)
} else {
with_name
};
let with_lnurl = if let Some(lnurl) = lnurl {
if let Some(ln_addr) = lnurl.lightning_address() {
with_img.lud16(ln_addr.to_string())
} else {
with_img.lud06(lnurl.to_string())
}
} else {
with_img
};
let with_nip05 = if let Some(nip05) = nip05 {
with_lnurl.nip05(nip05)
} else {
with_lnurl
};

let event_id = self.client.set_metadata(&with_nip05).await?;
log_info!(self.logger, "New kind 0: {event_id}");
self.storage.set_nostr_profile(with_nip05.clone())?;

Ok(with_nip05)
}

pub fn get_profile(&self) -> Result<Metadata, MutinyError> {
Ok(self.storage.get_nostr_profile()?.unwrap_or_default())
}

pub fn get_nwc_uri(&self, index: u32) -> Result<Option<NostrWalletConnectURI>, MutinyError> {
let opt = self
.nwc
Expand Down
10 changes: 10 additions & 0 deletions mutiny-core/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use bitcoin::hashes::Hash;
use hex::FromHex;
use lightning::{ln::PaymentHash, util::logger::Logger};
use lightning::{log_error, log_trace};
use nostr::Metadata;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
Expand All @@ -40,6 +41,7 @@ pub(crate) const EXPECTED_NETWORK_KEY: &str = "network";
const PAYMENT_INBOUND_PREFIX_KEY: &str = "payment_inbound/";
const PAYMENT_OUTBOUND_PREFIX_KEY: &str = "payment_outbound/";
pub const LAST_DM_SYNC_TIME_KEY: &str = "last_dm_sync_time";
pub const NOSTR_PROFILE_METADATA: &str = "nostr_profile_metadata";

fn needs_encryption(key: &str) -> bool {
match key {
Expand Down Expand Up @@ -439,6 +441,14 @@ pub trait MutinyStorage: Clone + Sized + Send + Sync + 'static {
}
}

fn get_nostr_profile(&self) -> Result<Option<Metadata>, MutinyError> {
self.get_data(NOSTR_PROFILE_METADATA)
}

fn set_nostr_profile(&self, metadata: Metadata) -> Result<(), MutinyError> {
self.set_data(NOSTR_PROFILE_METADATA.to_string(), metadata, None)
}

fn get_device_id(&self) -> Result<String, MutinyError> {
match self.get_data(DEVICE_ID_KEY)? {
Some(id) => Ok(id),
Expand Down
46 changes: 45 additions & 1 deletion mutiny-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ impl MutinyWallet {
self.mnemonic.to_string()
}

/// Returns the npub for receiving dms
/// Returns the user's npub
#[wasm_bindgen]
pub fn get_npub(&self) -> String {
self.inner.nostr.public_key.to_bech32().expect("bech32")
Expand Down Expand Up @@ -1597,6 +1597,50 @@ impl MutinyWallet {
Ok(())
}

/// Returns the user's nostr profile data
#[wasm_bindgen]
pub fn get_nostr_profile(&self) -> Result<JsValue, MutinyJsError> {
let profile = self.inner.nostr.get_profile()?;
Ok(JsValue::from_serde(&profile)?)
}

/// Returns the user's nostr profile data
#[wasm_bindgen]
pub async fn edit_nostr_profile(
&self,
name: Option<String>,
img_url: Option<String>,
lnurl: Option<String>,
nip05: Option<String>,
) -> Result<JsValue, MutinyJsError> {
let img_url = img_url
.map(|i| nostr::url::Url::from_str(&i))
.transpose()
.map_err(|_| MutinyJsError::InvalidArgumentsError)?;

let lnurl = lnurl
.map(|l| {
LightningAddress::from_str(&l)
.map(|a| a.lnurl())
.or(LnUrl::from_str(&l))
})
.transpose()
.map_err(|_| MutinyJsError::InvalidArgumentsError)?;

let profile = self
.inner
.nostr
.edit_profile(name, img_url, lnurl, nip05)
.await?;
Ok(JsValue::from_serde(&profile)?)
}

/// Syncs all of our nostr data from the configured primal instance
pub async fn sync_nostr(&self) -> Result<(), MutinyJsError> {
self.inner.sync_nostr().await?;
Ok(())
}

/// Get contacts from the given npub and sync them to the wallet
pub async fn sync_nostr_contacts(&self, npub_str: String) -> Result<(), MutinyJsError> {
let npub = parse_npub_or_nip05(&npub_str).await?;
Expand Down

0 comments on commit 6f0e3c9

Please sign in to comment.