move (de)serialization of wallets & accounts to own structs

for easy cloning

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-06-25 16:49:24 -04:00
parent 10d6d740b8
commit f357935cca
5 changed files with 190 additions and 88 deletions

View File

@@ -3,6 +3,7 @@ use tracing::{debug, error, info};
use crate::account::cache::AccountCache;
use crate::account::mute::AccountMutedData;
use crate::account::relay::{AccountRelayData, RelayDefaults};
use crate::user_account::UserAccountSerializable;
use crate::{AccountStorage, MuteFun, RelaySpec, SingleUnkIdAction, UserAccount};
use enostr::{ClientMessage, FilledKeypair, Keypair, Pubkey, RelayPool};
use nostrdb::{Ndb, Note, Transaction};
@@ -32,7 +33,8 @@ impl Accounts {
match keystore.get_accounts() {
Ok(accounts) => {
for account in accounts {
cache.add(account);
// TODO(kernelkind): this will get processed in a later commit
let _ = add_account_from_storage(&mut cache, account);
}
}
Err(e) => {
@@ -94,7 +96,7 @@ impl Accounts {
};
if let Some(key_store) = &self.key_store {
if let Err(e) = key_store.write_account(acc.get_acc()) {
if let Err(e) = key_store.write_account(&acc.get_acc().into()) {
tracing::error!("Could not add key for {:?}: {e}", kp.pubkey);
}
}
@@ -118,7 +120,7 @@ impl Accounts {
return false;
};
if let Err(err) = key_store.write_account(cur_acc) {
if let Err(err) = key_store.write_account(&cur_acc.into()) {
tracing::error!("Could not add account {:?} to storage: {err}", cur_acc.key);
return false;
}
@@ -450,6 +452,40 @@ impl<'a> AccType<'a> {
}
}
fn add_account_from_storage(
cache: &mut AccountCache,
user_account_serializable: UserAccountSerializable,
) -> SingleUnkIdAction {
let Some(acc) = get_acc_from_storage(user_account_serializable) else {
return SingleUnkIdAction::NoAction;
};
let pk = acc.key.pubkey;
cache.add(acc);
SingleUnkIdAction::pubkey(pk)
}
fn get_acc_from_storage(user_account_serializable: UserAccountSerializable) -> Option<UserAccount> {
let keypair = user_account_serializable.key;
let mut wallet = None;
if let Some(wallet_s) = user_account_serializable.wallet {
let m_wallet: Result<crate::ZapWallet, crate::Error> = wallet_s.into();
match m_wallet {
Ok(w) => wallet = Some(w),
Err(e) => {
tracing::error!("Problem creating wallet from disk: {e}");
}
};
}
Some(UserAccount {
key: keypair,
wallet,
})
}
enum RelayAction {
Add,
Remove,

View File

@@ -1,4 +1,4 @@
use crate::{Result, UserAccount};
use crate::{user_account::UserAccountSerializable, Result};
use enostr::{Keypair, Pubkey, SerializableKeypair};
use tokenator::{TokenParser, TokenSerializable, TokenWriter};
@@ -21,7 +21,7 @@ impl AccountStorage {
}
}
pub fn write_account(&self, account: &UserAccount) -> Result<()> {
pub fn write_account(&self, account: &UserAccountSerializable) -> Result<()> {
let mut writer = TokenWriter::new("\t");
account.serialize_tokens(&mut writer);
write_file(
@@ -31,7 +31,7 @@ impl AccountStorage {
)
}
pub fn get_accounts(&self) -> Result<Vec<UserAccount>> {
pub fn get_accounts(&self) -> Result<Vec<UserAccountSerializable>> {
let keys = self
.accounts_directory
.get_files()?
@@ -79,16 +79,18 @@ impl AccountStorage {
}
}
fn deserialize_storage(serialized: &str) -> Result<UserAccount> {
fn deserialize_storage(serialized: &str) -> Result<UserAccountSerializable> {
let data = serialized.split("\t").collect::<Vec<&str>>();
let mut parser = TokenParser::new(&data);
if let Ok(acc) = UserAccount::parse_from_tokens(&mut parser) {
if let Ok(acc) = UserAccountSerializable::parse_from_tokens(&mut parser) {
return Ok(acc);
}
// try old deserialization way
Ok(UserAccount::new(old_deserialization(serialized)?))
Ok(UserAccountSerializable::new(old_deserialization(
serialized,
)?))
}
fn old_deserialization(serialized: &str) -> Result<Keypair> {
@@ -118,7 +120,7 @@ mod tests {
fn test_basic() {
let kp = enostr::FullKeypair::generate().to_keypair();
let storage = AccountStorage::mock().unwrap();
let resp = storage.write_account(&UserAccount::new(kp.clone()));
let resp = storage.write_account(&UserAccountSerializable::new(kp.clone()));
assert!(resp.is_ok());
assert_num_storage(&storage.get_accounts(), 1);
@@ -127,7 +129,7 @@ mod tests {
assert_num_storage(&storage.get_accounts(), 0);
}
fn assert_num_storage(keys_response: &Result<Vec<UserAccount>>, n: usize) {
fn assert_num_storage(keys_response: &Result<Vec<UserAccountSerializable>>, n: usize) {
match keys_response {
Ok(keys) => {
assert_eq!(keys.len(), n);
@@ -143,7 +145,7 @@ mod tests {
let kp = enostr::FullKeypair::generate().to_keypair();
let storage = AccountStorage::mock().unwrap();
let _ = storage.write_account(&UserAccount::new(kp.clone()));
let _ = storage.write_account(&UserAccountSerializable::new(kp.clone()));
assert_num_storage(&storage.get_accounts(), 1);
let resp = storage.select_key(Some(kp.pubkey));

View File

@@ -1,7 +1,7 @@
use enostr::{Keypair, KeypairUnowned};
use tokenator::{ParseError, TokenParser, TokenSerializable};
use crate::wallet::ZapWallet;
use crate::wallet::{WalletSerializable, ZapWallet};
pub struct UserAccount {
pub key: Keypair,
@@ -26,12 +26,37 @@ impl UserAccount {
}
}
enum UserAccountRoute {
Key(Keypair),
Wallet(ZapWallet),
pub struct UserAccountSerializable {
pub key: Keypair,
pub wallet: Option<WalletSerializable>,
}
impl TokenSerializable for UserAccount {
impl UserAccountSerializable {
pub fn new(key: Keypair) -> Self {
Self { key, wallet: None }
}
pub fn with_wallet(mut self, wallet: WalletSerializable) -> Self {
self.wallet = Some(wallet);
self
}
}
impl From<&UserAccount> for UserAccountSerializable {
fn from(value: &UserAccount) -> Self {
Self {
key: value.key.clone(),
wallet: value.wallet.as_ref().map(|z| z.into()),
}
}
}
enum UserAccountRoute {
Key(Keypair),
Wallet(WalletSerializable),
}
impl TokenSerializable for UserAccountSerializable {
fn parse_from_tokens<'a>(
parser: &mut tokenator::TokenParser<'a>,
) -> Result<Self, tokenator::ParseError<'a>> {
@@ -43,7 +68,11 @@ impl TokenSerializable for UserAccount {
parser,
&[
|p| Ok(UserAccountRoute::Key(Keypair::parse_from_tokens(p)?)),
|p| Ok(UserAccountRoute::Wallet(ZapWallet::parse_from_tokens(p)?)),
|p| {
Ok(UserAccountRoute::Wallet(
WalletSerializable::parse_from_tokens(p)?,
))
},
],
);
@@ -63,7 +92,7 @@ impl TokenSerializable for UserAccount {
return Err(ParseError::DecodeFailed);
};
let mut user_acc = UserAccount::new(key);
let mut user_acc = UserAccountSerializable::new(key);
if let Some(wallet) = m_wallet {
user_acc = user_acc.with_wallet(wallet);
@@ -88,17 +117,15 @@ mod tests {
use enostr::FullKeypair;
use tokenator::{TokenParser, TokenSerializable, TokenWriter};
use crate::Wallet;
use super::UserAccount;
use crate::{user_account::UserAccountSerializable, wallet::WalletSerializable};
const URI: &str = "nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c&lud16=nostr%40nostr.com";
#[test]
fn test_user_account_serialize_deserialize() {
let kp = FullKeypair::generate();
let acc = UserAccount::new(kp.to_keypair())
.with_wallet(Wallet::new(URI.to_owned()).unwrap().into());
let acc = UserAccountSerializable::new(kp.to_keypair())
.with_wallet(WalletSerializable::new(URI.to_owned()));
let mut writer = TokenWriter::new("\t");
acc.serialize_tokens(&mut writer);
@@ -107,7 +134,7 @@ mod tests {
let data = &serialized.split("\t").collect::<Vec<&str>>();
let mut parser = TokenParser::new(data);
let m_new_acc = UserAccount::parse_from_tokens(&mut parser);
let m_new_acc = UserAccountSerializable::parse_from_tokens(&mut parser);
assert!(m_new_acc.is_ok());
let new_acc = m_new_acc.unwrap();
@@ -118,6 +145,6 @@ mod tests {
panic!();
};
assert_eq!(wallet.wallet.uri, URI);
assert_eq!(wallet.uri, URI);
}
}

View File

@@ -62,6 +62,21 @@ pub struct Wallet {
balance: Option<Promise<Result<u64, nwc::Error>>>,
}
#[derive(Clone)]
pub struct WalletSerializable {
pub uri: String,
pub default_mzap: Option<UserZapMsats>,
}
impl WalletSerializable {
pub fn new(uri: String) -> Self {
Self {
uri,
default_mzap: None,
}
}
}
impl std::fmt::Debug for Wallet {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Wallet({})", self.uri)
@@ -127,26 +142,6 @@ fn pay_invoice(
promise
}
impl TokenSerializable for Wallet {
fn parse_from_tokens<'a>(
parser: &mut tokenator::TokenParser<'a>,
) -> Result<Self, tokenator::ParseError<'a>> {
parser.parse_token("nwc_uri")?;
let raw_uri = parser.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);
}
}
pub struct GlobalWallet {
pub wallet: Option<ZapWallet>,
pub ui_state: WalletUIState,
@@ -176,7 +171,8 @@ impl GlobalWallet {
return;
};
match self.wallet_handler.save(wallet, "\t") {
let serializable: WalletSerializable = wallet.into();
match self.wallet_handler.save(&serializable, "\t") {
Ok(_) => {}
Err(e) => tracing::error!("Could not save global wallet: {e}"),
}
@@ -184,12 +180,15 @@ impl GlobalWallet {
}
fn construct_global_wallet(wallet_handler: &TokenHandler) -> Option<ZapWallet> {
let Ok(res) = wallet_handler.load::<ZapWallet>("\t") else {
let Ok(res) = wallet_handler.load::<WalletSerializable>("\t") else {
return None;
};
let wallet = match res {
Ok(wallet) => wallet,
Ok(wallet) => {
let m_zap_wallet: Result<ZapWallet, crate::Error> = wallet.into();
m_zap_wallet.ok()?
}
Err(e) => {
tracing::error!("Error parsing wallet: {:?}", e);
return None;
@@ -206,11 +205,49 @@ pub struct ZapWallet {
}
enum ZapWalletRoute {
Wallet(Wallet),
Wallet(String),
DefaultZapMsats(UserZapMsats),
}
impl TokenSerializable for ZapWallet {
impl ZapWallet {
pub fn new(wallet: Wallet) -> Self {
Self {
wallet,
default_zap: DefaultZapMsats::default(),
}
}
pub fn with_default_zap_msats(mut self, msats: u64) -> Self {
self.default_zap.set_user_selection(msats);
self
}
}
impl From<Wallet> for ZapWallet {
fn from(value: Wallet) -> Self {
ZapWallet::new(value)
}
}
impl From<&ZapWallet> for WalletSerializable {
fn from(value: &ZapWallet) -> Self {
Self {
uri: value.wallet.uri.to_string(),
default_mzap: value.default_zap.try_into_user(),
}
}
}
impl From<WalletSerializable> for Result<ZapWallet, crate::Error> {
fn from(value: WalletSerializable) -> Result<ZapWallet, crate::Error> {
Ok(ZapWallet {
wallet: Wallet::new(value.uri)?,
default_zap: DefaultZapMsats::from_user(value.default_mzap),
})
}
}
impl TokenSerializable for WalletSerializable {
fn parse_from_tokens<'a>(
parser: &mut tokenator::TokenParser<'a>,
) -> Result<Self, tokenator::ParseError<'a>> {
@@ -220,7 +257,12 @@ impl TokenSerializable for ZapWallet {
let res = TokenParser::alt(
parser,
&[
|p| Ok(ZapWalletRoute::Wallet(Wallet::parse_from_tokens(p)?)),
|p| {
p.parse_token("nwc_uri")?;
let raw_uri = p.pull_token()?;
Ok(ZapWalletRoute::Wallet(raw_uri.to_string()))
},
|p| {
Ok(ZapWalletRoute::DefaultZapMsats(
UserZapMsats::parse_from_tokens(p)?,
@@ -245,49 +287,27 @@ impl TokenSerializable for ZapWallet {
return Err(ParseError::DecodeFailed);
};
let mut zap_wallet = ZapWallet::new(wallet);
let default_zap = DefaultZapMsats::from_user(m_default_zap);
zap_wallet.default_zap = default_zap;
Ok(zap_wallet)
Ok(WalletSerializable {
uri: wallet,
default_mzap: m_default_zap,
})
}
fn serialize_tokens(&self, writer: &mut tokenator::TokenWriter) {
self.wallet.serialize_tokens(writer);
writer.write_token("nwc_uri");
writer.write_token(&self.uri);
if let Some(user_zap_msats) = self.default_zap.try_into_user() {
user_zap_msats.serialize_tokens(writer);
if let Some(msats) = &self.default_mzap {
msats.serialize_tokens(writer);
}
}
}
impl ZapWallet {
pub fn new(wallet: Wallet) -> Self {
Self {
wallet,
default_zap: DefaultZapMsats::default(),
}
}
pub fn with_default_zap_msats(mut self, msats: u64) -> Self {
self.default_zap.set_user_selection(msats);
self
}
}
impl From<Wallet> for ZapWallet {
fn from(value: Wallet) -> Self {
ZapWallet::new(value)
}
}
#[cfg(test)]
mod tests {
use tokenator::{TokenParser, TokenSerializable, TokenWriter};
use crate::Wallet;
use crate::{wallet::WalletSerializable, Wallet};
use super::ZapWallet;
@@ -301,7 +321,7 @@ mod tests {
#[test]
fn test_wallet_serialize_deserialize() {
let wallet = Wallet::new(URI.to_owned()).unwrap();
let wallet = WalletSerializable::new(URI.to_owned());
let mut writer = TokenWriter::new("\t");
wallet.serialize_tokens(&mut writer);
@@ -309,7 +329,7 @@ mod tests {
let data = &serialized.split("\t").collect::<Vec<&str>>();
let mut parser = TokenParser::new(data);
let m_new_wallet = Wallet::parse_from_tokens(&mut parser);
let m_new_wallet = WalletSerializable::parse_from_tokens(&mut parser);
assert!(m_new_wallet.is_ok());
@@ -325,13 +345,20 @@ mod tests {
ZapWallet::new(Wallet::new(URI.to_owned()).unwrap()).with_default_zap_msats(MSATS);
let mut writer = TokenWriter::new("\t");
zap_wallet.serialize_tokens(&mut writer);
let serializable: WalletSerializable = (&zap_wallet).into();
serializable.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_zap_wallet = ZapWallet::parse_from_tokens(&mut parser);
let m_deserialized = WalletSerializable::parse_from_tokens(&mut parser);
assert!(m_deserialized.is_ok());
let deserialized = m_deserialized.unwrap();
let m_new_zap_wallet: Result<ZapWallet, crate::Error> = deserialized.into();
assert!(m_new_zap_wallet.is_ok());

View File

@@ -11,6 +11,16 @@ pub struct DefaultZapMsats {
}
impl DefaultZapMsats {
pub fn from_msats(msats: Option<u64>) -> Self {
let mut default = DefaultZapMsats::default();
if let Some(msats) = msats {
default.set_user_selection(msats);
default.pending.write_msats(msats);
}
default
}
pub fn from_user(value: Option<UserZapMsats>) -> Self {
let mut obj = match value {
Some(user_msats) => {
@@ -50,7 +60,7 @@ impl DefaultZapMsats {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct UserZapMsats {
pub msats: u64,
}