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

Commit 9110d3f

Browse files
committed
Create nostr profile
1 parent 67849a5 commit 9110d3f

File tree

4 files changed

+147
-4
lines changed

4 files changed

+147
-4
lines changed

mutiny-core/src/lib.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ use crate::{nostr::NostrManager, utils::sleep};
6969
use ::nostr::key::XOnlyPublicKey;
7070
use ::nostr::nips::nip57;
7171
use ::nostr::prelude::ZapRequestData;
72-
use ::nostr::{Event, EventId, JsonUtil, Kind};
72+
use ::nostr::{Event, EventId, JsonUtil, Kind, Metadata};
7373
use async_lock::RwLock;
7474
use bdk_chain::ConfirmationTime;
7575
use bip39::Mnemonic;
@@ -80,6 +80,7 @@ use bitcoin::util::bip32::ExtendedPrivKey;
8080
use bitcoin::Network;
8181
use fedimint_core::{api::InviteCode, config::FederationId};
8282
use futures::{pin_mut, select, FutureExt};
83+
use futures_util::join;
8384
use lightning::ln::PaymentHash;
8485
use lightning::{log_debug, util::logger::Logger};
8586
use lightning::{log_error, log_info, log_warn};
@@ -1401,6 +1402,45 @@ impl<S: MutinyStorage> MutinyWallet<S> {
14011402
.map_err(|_| MutinyError::NostrError)
14021403
}
14031404

1405+
/// Syncs all of our nostr data from the configured primal instance
1406+
pub async fn sync_nostr(&self) -> Result<(), MutinyError> {
1407+
let contacts_fut = self.sync_nostr_contacts(self.nostr.public_key);
1408+
let profile_fut = self.sync_nostr_profile();
1409+
1410+
// join futures and handle result
1411+
let (contacts_res, profile_res) = join!(contacts_fut, profile_fut);
1412+
contacts_res?;
1413+
profile_res?;
1414+
1415+
Ok(())
1416+
}
1417+
1418+
/// Fetches our latest nostr profile from primal and saves to storage
1419+
async fn sync_nostr_profile(&self) -> Result<(), MutinyError> {
1420+
let url = self
1421+
.config
1422+
.primal_url
1423+
.as_deref()
1424+
.unwrap_or("https://primal-cache.mutinywallet.com/api");
1425+
let client = reqwest::Client::new();
1426+
1427+
let body = json!(["user_profile", { "pubkey": self.nostr.public_key } ]);
1428+
let data: Vec<Value> = Self::primal_request(&client, url, body).await?;
1429+
1430+
if let Some(json) = data.first().cloned() {
1431+
let event: Event = serde_json::from_value(json).map_err(|_| MutinyError::NostrError)?;
1432+
if event.kind != Kind::Metadata {
1433+
return Ok(());
1434+
}
1435+
1436+
let metadata: Metadata =
1437+
serde_json::from_str(&event.content).map_err(|_| MutinyError::NostrError)?;
1438+
self.storage.set_nostr_profile(metadata)?;
1439+
}
1440+
1441+
Ok(())
1442+
}
1443+
14041444
/// Get contacts from the given npub and sync them to the wallet
14051445
pub async fn sync_nostr_contacts(&self, npub: XOnlyPublicKey) -> Result<(), MutinyError> {
14061446
let url = self

mutiny-core/src/nostr/mod.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ use bitcoin::{
1919
use futures::{pin_mut, select, FutureExt};
2020
use futures_util::lock::Mutex;
2121
use lightning::util::logger::Logger;
22-
use lightning::{log_debug, log_error, log_warn};
22+
use lightning::{log_debug, log_error, log_info, log_warn};
2323
use lightning_invoice::Bolt11Invoice;
24+
use lnurl::lnurl::LnUrl;
2425
use nostr::key::{SecretKey, XOnlyPublicKey};
2526
use nostr::nips::nip47::*;
2627
use nostr::prelude::{decrypt, encrypt};
27-
use nostr::{Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind, Tag, Timestamp};
28+
use nostr::{
29+
url::Url, Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind, Metadata, Tag, Timestamp,
30+
};
2831
use nostr_sdk::{Client, ClientSigner, RelayPoolNotification};
2932
use std::collections::HashSet;
3033
use std::sync::{atomic::Ordering, Arc, RwLock};
@@ -189,6 +192,52 @@ impl<S: MutinyStorage> NostrManager<S> {
189192
Ok(nwc)
190193
}
191194

195+
/// Sets the user's nostr profile metadata
196+
pub async fn edit_profile(
197+
&self,
198+
name: Option<String>,
199+
img_url: Option<Url>,
200+
lnurl: Option<LnUrl>,
201+
nip05: Option<String>,
202+
) -> Result<Metadata, MutinyError> {
203+
let current = self.get_profile()?;
204+
205+
let with_name = if let Some(name) = name {
206+
current.name(name)
207+
} else {
208+
current
209+
};
210+
let with_img = if let Some(img_url) = img_url {
211+
with_name.picture(img_url)
212+
} else {
213+
with_name
214+
};
215+
let with_lnurl = if let Some(lnurl) = lnurl {
216+
if let Some(ln_addr) = lnurl.lightning_address() {
217+
with_img.lud16(ln_addr.to_string())
218+
} else {
219+
with_img.lud06(lnurl.to_string())
220+
}
221+
} else {
222+
with_img
223+
};
224+
let with_nip05 = if let Some(nip05) = nip05 {
225+
with_lnurl.nip05(nip05)
226+
} else {
227+
with_lnurl
228+
};
229+
230+
let event_id = self.client.set_metadata(&with_nip05).await?;
231+
log_info!(self.logger, "New kind 0: {event_id}");
232+
self.storage.set_nostr_profile(with_nip05.clone())?;
233+
234+
Ok(with_nip05)
235+
}
236+
237+
pub fn get_profile(&self) -> Result<Metadata, MutinyError> {
238+
Ok(self.storage.get_nostr_profile()?.unwrap_or_default())
239+
}
240+
192241
pub fn get_nwc_uri(&self, index: u32) -> Result<Option<NostrWalletConnectURI>, MutinyError> {
193242
let opt = self
194243
.nwc

mutiny-core/src/storage.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use bitcoin::hashes::Hash;
1919
use hex::FromHex;
2020
use lightning::{ln::PaymentHash, util::logger::Logger};
2121
use lightning::{log_error, log_trace};
22+
use nostr::Metadata;
2223
use serde::{Deserialize, Serialize};
2324
use serde_json::Value;
2425
use std::collections::HashMap;
@@ -40,6 +41,7 @@ pub(crate) const EXPECTED_NETWORK_KEY: &str = "network";
4041
const PAYMENT_INBOUND_PREFIX_KEY: &str = "payment_inbound/";
4142
const PAYMENT_OUTBOUND_PREFIX_KEY: &str = "payment_outbound/";
4243
pub const LAST_DM_SYNC_TIME_KEY: &str = "last_dm_sync_time";
44+
pub const NOSTR_PROFILE_METADATA: &str = "nostr_profile_metadata";
4345

4446
fn needs_encryption(key: &str) -> bool {
4547
match key {
@@ -439,6 +441,14 @@ pub trait MutinyStorage: Clone + Sized + Send + Sync + 'static {
439441
}
440442
}
441443

444+
fn get_nostr_profile(&self) -> Result<Option<Metadata>, MutinyError> {
445+
self.get_data(NOSTR_PROFILE_METADATA)
446+
}
447+
448+
fn set_nostr_profile(&self, metadata: Metadata) -> Result<(), MutinyError> {
449+
self.set_data(NOSTR_PROFILE_METADATA.to_string(), metadata, None)
450+
}
451+
442452
fn get_device_id(&self) -> Result<String, MutinyError> {
443453
match self.get_data(DEVICE_ID_KEY)? {
444454
Some(id) => Ok(id),

mutiny-wasm/src/lib.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ impl MutinyWallet {
419419
self.mnemonic.to_string()
420420
}
421421

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

1600+
/// Returns the user's nostr profile data
1601+
#[wasm_bindgen]
1602+
pub fn get_nostr_profile(&self) -> Result<JsValue, MutinyJsError> {
1603+
let profile = self.inner.nostr.get_profile()?;
1604+
Ok(JsValue::from_serde(&profile)?)
1605+
}
1606+
1607+
/// Sets the user's nostr profile data
1608+
#[wasm_bindgen]
1609+
pub async fn edit_nostr_profile(
1610+
&self,
1611+
name: Option<String>,
1612+
img_url: Option<String>,
1613+
lnurl: Option<String>,
1614+
nip05: Option<String>,
1615+
) -> Result<JsValue, MutinyJsError> {
1616+
let img_url = img_url
1617+
.map(|i| nostr::url::Url::from_str(&i))
1618+
.transpose()
1619+
.map_err(|_| MutinyJsError::InvalidArgumentsError)?;
1620+
1621+
let lnurl = lnurl
1622+
.map(|l| {
1623+
LightningAddress::from_str(&l)
1624+
.map(|a| a.lnurl())
1625+
.or(LnUrl::from_str(&l))
1626+
})
1627+
.transpose()
1628+
.map_err(|_| MutinyJsError::InvalidArgumentsError)?;
1629+
1630+
let profile = self
1631+
.inner
1632+
.nostr
1633+
.edit_profile(name, img_url, lnurl, nip05)
1634+
.await?;
1635+
Ok(JsValue::from_serde(&profile)?)
1636+
}
1637+
1638+
/// Syncs all of our nostr data from the configured primal instance
1639+
pub async fn sync_nostr(&self) -> Result<(), MutinyJsError> {
1640+
self.inner.sync_nostr().await?;
1641+
Ok(())
1642+
}
1643+
16001644
/// Get contacts from the given npub and sync them to the wallet
16011645
pub async fn sync_nostr_contacts(&self, npub_str: String) -> Result<(), MutinyJsError> {
16021646
let npub = parse_npub_or_nip05(&npub_str).await?;

0 commit comments

Comments
 (0)