@@ -25,6 +25,7 @@ pub mod ui;
|
|||||||
mod unknowns;
|
mod unknowns;
|
||||||
mod urls;
|
mod urls;
|
||||||
mod user_account;
|
mod user_account;
|
||||||
|
mod wallet;
|
||||||
|
|
||||||
pub use accounts::{AccountData, Accounts, AccountsAction, AddAccountAction, SwitchAccountAction};
|
pub use accounts::{AccountData, Accounts, AccountsAction, AddAccountAction, SwitchAccountAction};
|
||||||
pub use app::{App, Notedeck};
|
pub use app::{App, Notedeck};
|
||||||
@@ -52,6 +53,7 @@ pub use timecache::TimeCached;
|
|||||||
pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds};
|
pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds};
|
||||||
pub use urls::{supported_mime_hosted_at_url, SupportedMimeType, UrlMimes};
|
pub use urls::{supported_mime_hosted_at_url, SupportedMimeType, UrlMimes};
|
||||||
pub use user_account::UserAccount;
|
pub use user_account::UserAccount;
|
||||||
|
pub use wallet::{GlobalWallet, Wallet, WalletError, WalletState, WalletType, WalletUIState};
|
||||||
|
|
||||||
// export libs
|
// export libs
|
||||||
pub use enostr;
|
pub use enostr;
|
||||||
|
|||||||
225
crates/notedeck/src/wallet.rs
Normal file
225
crates/notedeck/src/wallet.rs
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use nwc::{
|
||||||
|
nostr::nips::nip47::{NostrWalletConnectURI, PayInvoiceRequest, PayInvoiceResponse},
|
||||||
|
NWC,
|
||||||
|
};
|
||||||
|
use poll_promise::Promise;
|
||||||
|
use tokenator::TokenSerializable;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::{DataPath, TokenHandler};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum WalletState<'a> {
|
||||||
|
Wallet {
|
||||||
|
wallet: &'a mut Wallet,
|
||||||
|
can_create_local_wallet: bool,
|
||||||
|
},
|
||||||
|
NoWallet {
|
||||||
|
state: &'a mut WalletUIState,
|
||||||
|
show_local_only: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||||
|
pub enum WalletType {
|
||||||
|
Auto,
|
||||||
|
Local,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct WalletUIState {
|
||||||
|
pub buf: String,
|
||||||
|
pub error_msg: Option<WalletError>,
|
||||||
|
pub for_local_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum WalletError {
|
||||||
|
InvalidURI,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Wallet {
|
||||||
|
pub uri: String,
|
||||||
|
wallet: Arc<RwLock<NWC>>,
|
||||||
|
balance: Option<Promise<Result<u64, nwc::Error>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Wallet {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "Wallet({})", self.uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wallet {
|
||||||
|
pub fn new(uri: String) -> Result<Self, crate::Error> {
|
||||||
|
let nwc_uri = NostrWalletConnectURI::parse(uri.clone())
|
||||||
|
.map_err(|e| crate::Error::Generic(e.to_string()))?;
|
||||||
|
|
||||||
|
let nwc = NWC::new(nwc_uri);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
uri,
|
||||||
|
wallet: Arc::new(RwLock::new(nwc)),
|
||||||
|
balance: Default::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_balance(&mut self) -> Option<&Result<u64, nwc::Error>> {
|
||||||
|
if self.balance.is_none() {
|
||||||
|
self.balance = Some(get_balance(self.wallet.clone()));
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let promise = self.balance.as_ref().unwrap();
|
||||||
|
|
||||||
|
if let Some(bal) = promise.ready() {
|
||||||
|
Some(bal)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pay_invoice(
|
||||||
|
&mut self,
|
||||||
|
invoice: &str,
|
||||||
|
) -> Promise<Result<PayInvoiceResponse, nwc::Error>> {
|
||||||
|
pay_invoice(
|
||||||
|
self.wallet.clone(),
|
||||||
|
PayInvoiceRequest::new(invoice.to_owned()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_balance(nwc: Arc<RwLock<NWC>>) -> Promise<Result<u64, nwc::Error>> {
|
||||||
|
let (sender, promise) = Promise::new();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
sender.send(nwc.read().await.get_balance().await);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pay_invoice(
|
||||||
|
nwc: Arc<RwLock<NWC>>,
|
||||||
|
invoice: PayInvoiceRequest,
|
||||||
|
) -> Promise<Result<PayInvoiceResponse, nwc::Error>> {
|
||||||
|
let (sender, promise) = Promise::new();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
sender.send(nwc.read().await.pay_invoice(invoice).await);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TokenSerializable for Wallet {
|
||||||
|
fn parse_from_tokens<'a>(
|
||||||
|
parser: &mut tokenator::TokenParser<'a>,
|
||||||
|
) -> Result<Self, tokenator::ParseError<'a>> {
|
||||||
|
parser.parse_all(|p| {
|
||||||
|
p.parse_token("nwc_uri")?;
|
||||||
|
|
||||||
|
let raw_uri = p.pull_token()?;
|
||||||
|
|
||||||
|
let wallet =
|
||||||
|
Wallet::new(raw_uri.to_owned()).map_err(|_| tokenator::ParseError::DecodeFailed)?;
|
||||||
|
|
||||||
|
Ok(wallet)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tokens(&self, writer: &mut tokenator::TokenWriter) {
|
||||||
|
writer.write_token("nwc_uri");
|
||||||
|
writer.write_token(&self.uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct GlobalWallet {
|
||||||
|
pub wallet: Option<Wallet>,
|
||||||
|
pub ui_state: WalletUIState,
|
||||||
|
wallet_handler: TokenHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl GlobalWallet {
|
||||||
|
pub fn new(path: &DataPath) -> Self {
|
||||||
|
let wallet_handler =
|
||||||
|
TokenHandler::new(path, crate::DataPathType::Setting, "global_wallet.txt");
|
||||||
|
let wallet = construct_global_wallet(&wallet_handler);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
wallet,
|
||||||
|
ui_state: WalletUIState::default(),
|
||||||
|
wallet_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_wallet(&self) {
|
||||||
|
let Some(wallet) = &self.wallet else {
|
||||||
|
// saving with no wallet means delete
|
||||||
|
if let Err(e) = self.wallet_handler.clear() {
|
||||||
|
tracing::error!("Could not clear wallet: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.wallet_handler.save(wallet, "\t") {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => tracing::error!("Could not save global wallet: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn construct_global_wallet(wallet_handler: &TokenHandler) -> Option<Wallet> {
|
||||||
|
let Ok(res) = wallet_handler.load::<Wallet>("\t") else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let wallet = match res {
|
||||||
|
Ok(wallet) => wallet,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error parsing wallet: {:?}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(wallet)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use tokenator::{TokenParser, TokenSerializable, TokenWriter};
|
||||||
|
|
||||||
|
use crate::Wallet;
|
||||||
|
|
||||||
|
const URI: &str = "nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c&lud16=nostr%40nostr.com";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
|
||||||
|
fn test_uri() {
|
||||||
|
assert!(Wallet::new(URI.to_owned()).is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wallet_serialize_deserialize() {
|
||||||
|
let wallet = Wallet::new(URI.to_owned()).unwrap();
|
||||||
|
|
||||||
|
let mut writer = TokenWriter::new("\t");
|
||||||
|
wallet.serialize_tokens(&mut writer);
|
||||||
|
let serialized = writer.str();
|
||||||
|
|
||||||
|
let data = &serialized.split("\t").collect::<Vec<&str>>();
|
||||||
|
let mut parser = TokenParser::new(data);
|
||||||
|
let m_new_wallet = Wallet::parse_from_tokens(&mut parser);
|
||||||
|
|
||||||
|
assert!(m_new_wallet.is_ok());
|
||||||
|
|
||||||
|
let new_wallet = m_new_wallet.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(wallet.uri, new_wallet.uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user