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

Commit ec6f2c1

Browse files
authored
Gifting (#636)
1 parent 4da268d commit ec6f2c1

File tree

6 files changed

+285
-117
lines changed

6 files changed

+285
-117
lines changed

mutiny-core/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub use crate::ldkstorage::{CHANNEL_MANAGER_KEY, MONITORS_PREFIX_KEY};
4646

4747
use crate::auth::MutinyAuthClient;
4848
use crate::labels::{Contact, LabelStorage};
49+
use crate::nostr::nwc::SpendingConditions;
4950
use crate::storage::MutinyStorage;
5051
use crate::{error::MutinyError, nostr::ReservedProfile};
5152
use crate::{nodemanager::NodeManager, nostr::ProfileType};
@@ -296,7 +297,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
296297
.nostr
297298
.create_new_nwc_profile(
298299
ProfileType::Reserved(ReservedProfile::MutinySubscription),
299-
21_000,
300+
SpendingConditions::RequireApproval,
300301
)
301302
.await?;
302303
profile.nwc_uri

mutiny-core/src/nostr/mod.rs

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::error::MutinyError;
22
use crate::nodemanager::NodeManager;
33
use crate::nostr::nwc::{
4-
NostrWalletConnect, NwcProfile, PendingNwcInvoice, Profile, PENDING_NWC_EVENTS_KEY,
4+
NostrWalletConnect, NwcProfile, PendingNwcInvoice, Profile, SingleUseSpendingConditions,
5+
SpendingConditions, PENDING_NWC_EVENTS_KEY,
56
};
67
use crate::storage::MutinyStorage;
78
use bitcoin::hashes::sha256;
@@ -110,6 +111,7 @@ impl<S: MutinyStorage> NostrManager<S> {
110111
.read()
111112
.unwrap()
112113
.iter()
114+
.filter(|x| !x.profile.archived)
113115
.map(|x| x.nwc_profile())
114116
.collect()
115117
}
@@ -143,7 +145,7 @@ impl<S: MutinyStorage> NostrManager<S> {
143145
pub(crate) fn create_new_profile(
144146
&self,
145147
profile_type: ProfileType,
146-
max_single_amt_sats: u64,
148+
spending_conditions: SpendingConditions,
147149
) -> Result<NwcProfile, MutinyError> {
148150
let mut profiles = self.nwc.write().unwrap();
149151

@@ -166,10 +168,10 @@ impl<S: MutinyStorage> NostrManager<S> {
166168
let profile = Profile {
167169
name,
168170
index,
169-
max_single_amt_sats,
170171
relay: "wss://nostr.mutinywallet.com".to_string(),
171172
enabled: true,
172-
require_approval: true,
173+
archived: false,
174+
spending_conditions,
173175
};
174176
let nwc = NostrWalletConnect::new(&Secp256k1::new(), self.xprivkey, profile)?;
175177

@@ -193,9 +195,9 @@ impl<S: MutinyStorage> NostrManager<S> {
193195
pub async fn create_new_nwc_profile(
194196
&self,
195197
profile_type: ProfileType,
196-
max_single_amt_sats: u64,
198+
spending_conditions: SpendingConditions,
197199
) -> Result<NwcProfile, MutinyError> {
198-
let profile = self.create_new_profile(profile_type, max_single_amt_sats)?;
200+
let profile = self.create_new_profile(profile_type, spending_conditions)?;
199201

200202
let info_event = self.nwc.read().unwrap().iter().find_map(|nwc| {
201203
if nwc.profile.index == profile.index {
@@ -227,6 +229,21 @@ impl<S: MutinyStorage> NostrManager<S> {
227229
Ok(profile)
228230
}
229231

232+
pub async fn create_single_use_nwc(
233+
&self,
234+
name: String,
235+
amount_sats: u64,
236+
) -> Result<NwcProfile, MutinyError> {
237+
let profile = ProfileType::Normal { name };
238+
239+
let spending_conditions = SpendingConditions::SingleUse(SingleUseSpendingConditions {
240+
amount_sats,
241+
spent: false,
242+
});
243+
self.create_new_nwc_profile(profile, spending_conditions)
244+
.await
245+
}
246+
230247
/// Lists all pending NWC invoices
231248
pub fn get_pending_nwc_invoices(&self) -> Result<Vec<PendingNwcInvoice>, MutinyError> {
232249
Ok(self
@@ -405,21 +422,86 @@ impl<S: MutinyStorage> NostrManager<S> {
405422
.cloned()
406423
};
407424

408-
if let Some(nwc) = nwc {
409-
let event = nwc
425+
if let Some(mut nwc) = nwc {
426+
let (event, needs_save) = nwc
410427
.handle_nwc_request(
411428
event,
412429
node_manager,
413430
from_node,
414431
self.pending_nwc_lock.deref(),
415432
)
416433
.await?;
434+
435+
// update the profile if needed
436+
if needs_save {
437+
let mut vec = self.nwc.write().unwrap();
438+
439+
// update the profile
440+
for item in vec.iter_mut() {
441+
if item.profile.index == nwc.profile.index {
442+
item.profile = nwc.profile;
443+
break;
444+
}
445+
}
446+
447+
let profiles = vec.iter().map(|x| x.profile.clone()).collect::<Vec<_>>();
448+
drop(vec); // drop the lock, no longer needed
449+
450+
self.storage.set_data(NWC_STORAGE_KEY, profiles, None)?;
451+
}
452+
417453
Ok(event)
418454
} else {
419455
Ok(None)
420456
}
421457
}
422458

459+
pub async fn claim_single_use_nwc(
460+
&self,
461+
amount_sats: u64,
462+
nwc_uri: &str,
463+
node_manager: &NodeManager<S>,
464+
) -> anyhow::Result<EventId> {
465+
let nwc = NostrWalletConnectURI::from_str(nwc_uri)?;
466+
let secret = Keys::new(nwc.secret);
467+
let client = Client::new(&secret);
468+
469+
#[cfg(target_arch = "wasm32")]
470+
let add_relay_res = client.add_relay(nwc.relay_url.as_str()).await;
471+
472+
#[cfg(not(target_arch = "wasm32"))]
473+
let add_relay_res = client.add_relay(nwc.relay_url.as_str(), None).await;
474+
475+
add_relay_res.expect("Failed to add relays");
476+
client.connect().await;
477+
478+
let invoice = node_manager
479+
.create_invoice(Some(amount_sats), vec!["Gift".to_string()])
480+
.await?;
481+
482+
let req = Request {
483+
method: Method::PayInvoice,
484+
params: RequestParams {
485+
invoice: invoice.bolt11.unwrap().to_string(),
486+
},
487+
};
488+
let encrypted = encrypt(&nwc.secret, &nwc.public_key, req.as_json())?;
489+
let p_tag = Tag::PubKey(nwc.public_key, None);
490+
let request_event =
491+
EventBuilder::new(Kind::WalletConnectRequest, encrypted, &[p_tag]).to_event(&secret)?;
492+
493+
client
494+
.send_event(request_event.clone())
495+
.await
496+
.map_err(|e| {
497+
MutinyError::Other(anyhow::anyhow!("Failed to send request event: {e:?}"))
498+
})?;
499+
500+
client.disconnect().await?;
501+
502+
Ok(request_event.id)
503+
}
504+
423505
/// Derives the client and server keys for Nostr Wallet Connect given a profile index
424506
/// The left key is the client key and the right key is the server key
425507
pub(crate) fn derive_nwc_keys<C: Signing>(
@@ -524,24 +606,21 @@ mod test {
524606
let nostr_manager = create_nostr_manager();
525607

526608
let name = "test".to_string();
527-
let max_single_amt_sats = 1_000;
528609

529610
let profile = nostr_manager
530611
.create_new_profile(
531612
ProfileType::Normal { name: name.clone() },
532-
max_single_amt_sats,
613+
SpendingConditions::default(),
533614
)
534615
.unwrap();
535616

536617
assert_eq!(profile.name, name);
537618
assert_eq!(profile.index, 1000);
538-
assert_eq!(profile.max_single_amt_sats, max_single_amt_sats);
539619

540620
let profiles = nostr_manager.profiles();
541621
assert_eq!(profiles.len(), 1);
542622
assert_eq!(profiles[0].name, name);
543623
assert_eq!(profiles[0].index, 1000);
544-
assert_eq!(profiles[0].max_single_amt_sats, max_single_amt_sats);
545624

546625
let profiles: Vec<Profile> = nostr_manager
547626
.storage
@@ -552,32 +631,28 @@ mod test {
552631
assert_eq!(profiles.len(), 1);
553632
assert_eq!(profiles[0].name, name);
554633
assert_eq!(profiles[0].index, 1000);
555-
assert_eq!(profiles[0].max_single_amt_sats, max_single_amt_sats);
556634
}
557635

558636
#[test]
559637
fn test_create_reserve_profile() {
560638
let nostr_manager = create_nostr_manager();
561639

562640
let name = "Mutiny+ Subscription".to_string();
563-
let max_single_amt_sats = 1_000;
564641

565642
let profile = nostr_manager
566643
.create_new_profile(
567644
ProfileType::Reserved(ReservedProfile::MutinySubscription),
568-
max_single_amt_sats,
645+
SpendingConditions::default(),
569646
)
570647
.unwrap();
571648

572649
assert_eq!(profile.name, name);
573650
assert_eq!(profile.index, 0);
574-
assert_eq!(profile.max_single_amt_sats, max_single_amt_sats);
575651

576652
let profiles = nostr_manager.profiles();
577653
assert_eq!(profiles.len(), 1);
578654
assert_eq!(profiles[0].name, name);
579655
assert_eq!(profiles[0].index, 0);
580-
assert_eq!(profiles[0].max_single_amt_sats, max_single_amt_sats);
581656

582657
let profiles: Vec<Profile> = nostr_manager
583658
.storage
@@ -588,42 +663,37 @@ mod test {
588663
assert_eq!(profiles.len(), 1);
589664
assert_eq!(profiles[0].name, name);
590665
assert_eq!(profiles[0].index, 0);
591-
assert_eq!(profiles[0].max_single_amt_sats, max_single_amt_sats);
592666

593667
// now create normal profile
594668
let name = "test".to_string();
595-
let max_single_amt_sats = 1_000;
596669

597670
let profile = nostr_manager
598671
.create_new_profile(
599672
ProfileType::Normal { name: name.clone() },
600-
max_single_amt_sats,
673+
SpendingConditions::default(),
601674
)
602675
.unwrap();
603676

604677
assert_eq!(profile.name, name);
605678
assert_eq!(profile.index, 1000);
606-
assert_eq!(profile.max_single_amt_sats, max_single_amt_sats);
607679
}
608680

609681
#[test]
610682
fn test_edit_profile() {
611683
let nostr_manager = create_nostr_manager();
612684

613685
let name = "test".to_string();
614-
let max_single_amt_sats = 1_000;
615686

616687
let mut profile = nostr_manager
617688
.create_new_profile(
618689
ProfileType::Normal { name: name.clone() },
619-
max_single_amt_sats,
690+
SpendingConditions::default(),
620691
)
621692
.unwrap();
622693

623694
assert_eq!(profile.name, name);
624695
assert_eq!(profile.index, 1000);
625696
assert!(profile.enabled);
626-
assert_eq!(profile.max_single_amt_sats, max_single_amt_sats);
627697

628698
profile.enabled = false;
629699

@@ -634,7 +704,6 @@ mod test {
634704
assert_eq!(profiles[0].name, name);
635705
assert_eq!(profiles[0].index, 1000);
636706
assert!(!profiles[0].enabled);
637-
assert_eq!(profiles[0].max_single_amt_sats, max_single_amt_sats);
638707

639708
let profiles: Vec<Profile> = nostr_manager
640709
.storage
@@ -646,18 +715,16 @@ mod test {
646715
assert_eq!(profiles[0].name, name);
647716
assert_eq!(profiles[0].index, 1000);
648717
assert!(!profiles[0].enabled);
649-
assert_eq!(profiles[0].max_single_amt_sats, max_single_amt_sats);
650718
}
651719

652720
#[test]
653721
fn test_deny_invoice() {
654722
let nostr_manager = create_nostr_manager();
655723

656724
let name = "test".to_string();
657-
let max_single_amt_sats = 1_000;
658725

659726
let profile = nostr_manager
660-
.create_new_profile(ProfileType::Normal { name }, max_single_amt_sats)
727+
.create_new_profile(ProfileType::Normal { name }, SpendingConditions::default())
661728
.unwrap();
662729

663730
let inv = PendingNwcInvoice {

0 commit comments

Comments
 (0)