Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-03-15 13:16:14 -04:00
parent 4522920939
commit b1215f1932
2 changed files with 227 additions and 0 deletions

View File

@@ -25,6 +25,7 @@ pub mod ui;
mod unknowns;
mod urls;
mod user_account;
mod wallet;
pub use accounts::{AccountData, Accounts, AccountsAction, AddAccountAction, SwitchAccountAction};
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 urls::{supported_mime_hosted_at_url, SupportedMimeType, UrlMimes};
pub use user_account::UserAccount;
pub use wallet::{GlobalWallet, Wallet, WalletError, WalletState, WalletType, WalletUIState};
// export libs
pub use enostr;

View 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);
}
}