move AcountData into UserAccount

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-06-29 15:20:47 -04:00
parent a962d67536
commit 329385bd90
7 changed files with 161 additions and 136 deletions

View File

@@ -1,13 +1,13 @@
use tracing::{debug, error, info}; use tracing::{debug, info};
use crate::account::cache::AccountCache; use crate::account::cache::AccountCache;
use crate::account::mute::AccountMutedData; use crate::account::mute::AccountMutedData;
use crate::account::relay::{AccountRelayData, RelayDefaults}; use crate::account::relay::{AccountRelayData, RelayDefaults};
use crate::user_account::UserAccountSerializable; use crate::user_account::UserAccountSerializable;
use crate::{AccountStorage, MuteFun, RelaySpec, SingleUnkIdAction, UserAccount}; use crate::{AccountStorage, MuteFun, RelaySpec, SingleUnkIdAction, UnknownIds, UserAccount};
use enostr::{ClientMessage, FilledKeypair, Keypair, Pubkey, RelayPool}; use enostr::{ClientMessage, FilledKeypair, Keypair, Pubkey, RelayPool};
use nostrdb::{Ndb, Note, Transaction}; use nostrdb::{Ndb, Note, Transaction};
use std::collections::{BTreeMap, BTreeSet}; use std::collections::BTreeSet;
// TODO: remove this // TODO: remove this
use std::sync::Arc; use std::sync::Arc;
@@ -17,7 +17,6 @@ use std::sync::Arc;
pub struct Accounts { pub struct Accounts {
pub cache: AccountCache, pub cache: AccountCache,
key_store: Option<AccountStorage>, key_store: Option<AccountStorage>,
account_data: BTreeMap<[u8; 32], AccountData>,
relay_defaults: RelayDefaults, relay_defaults: RelayDefaults,
needs_relay_config: bool, needs_relay_config: bool,
} }
@@ -27,14 +26,29 @@ impl Accounts {
key_store: Option<AccountStorage>, key_store: Option<AccountStorage>,
forced_relays: Vec<String>, forced_relays: Vec<String>,
fallback: Pubkey, fallback: Pubkey,
ndb: &Ndb,
txn: &Transaction,
unknown_ids: &mut UnknownIds,
) -> Self { ) -> Self {
let (mut cache, _) = AccountCache::new(UserAccount::new(Keypair::only_pubkey(fallback))); let (mut cache, unknown_id) = AccountCache::new(UserAccount::new(
Keypair::only_pubkey(fallback),
AccountData {
relay: AccountRelayData::new(ndb, txn, fallback.bytes()),
muted: AccountMutedData::new(ndb, txn, fallback.bytes()),
},
));
unknown_id.process_action(unknown_ids, ndb, txn);
if let Some(keystore) = &key_store { if let Some(keystore) = &key_store {
match keystore.get_accounts() { match keystore.get_accounts() {
Ok(accounts) => { Ok(accounts) => {
for account in accounts { for account in accounts {
// TODO(kernelkind): this will get processed in a later commit add_account_from_storage(&mut cache, ndb, txn, account).process_action(
let _ = add_account_from_storage(&mut cache, account); unknown_ids,
ndb,
txn,
)
} }
} }
Err(e) => { Err(e) => {
@@ -46,14 +60,11 @@ impl Accounts {
} }
}; };
let account_data = BTreeMap::new();
let relay_defaults = RelayDefaults::new(forced_relays); let relay_defaults = RelayDefaults::new(forced_relays);
Accounts { Accounts {
cache, cache,
key_store, key_store,
account_data,
relay_defaults, relay_defaults,
needs_relay_config: true, needs_relay_config: true,
} }
@@ -82,7 +93,12 @@ impl Accounts {
} }
#[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"] #[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"]
pub fn add_account(&mut self, kp: Keypair) -> Option<AddAccountResponse> { pub fn add_account(
&mut self,
ndb: &Ndb,
txn: &Transaction,
kp: Keypair,
) -> Option<AddAccountResponse> {
let acc = if let Some(acc) = self.cache.get_mut(&kp.pubkey) { let acc = if let Some(acc) = self.cache.get_mut(&kp.pubkey) {
if kp.secret_key.is_none() || acc.key.secret_key.is_some() { if kp.secret_key.is_none() || acc.key.secret_key.is_some() {
tracing::info!("Already have account, not adding"); tracing::info!("Already have account, not adding");
@@ -92,7 +108,14 @@ impl Accounts {
acc.key = kp.clone(); acc.key = kp.clone();
AccType::Acc(&*acc) AccType::Acc(&*acc)
} else { } else {
AccType::Entry(self.cache.add(UserAccount::new(kp.clone()))) let new_account_data = AccountData {
relay: AccountRelayData::new(ndb, txn, kp.pubkey.bytes()),
muted: AccountMutedData::new(ndb, txn, kp.pubkey.bytes()),
};
AccType::Entry(
self.cache
.add(UserAccount::new(kp.clone(), new_account_data)),
)
}; };
if let Some(key_store) = &self.key_store { if let Some(key_store) = &self.key_store {
@@ -154,32 +177,35 @@ impl Accounts {
self.cache.selected_mut() self.cache.selected_mut()
} }
pub fn get_selected_account_data(&mut self) -> Option<&mut AccountData> { fn get_selected_account_data(&self) -> &AccountData {
let account_pubkey = *self.selected_account_pubkey_bytes(); &self.cache.selected().data
self.account_data.get_mut(&account_pubkey) }
fn get_selected_account_data_mut(&mut self) -> &mut AccountData {
&mut self.cache.selected_mut().data
} }
pub fn select_account(&mut self, pk: &Pubkey) { pub fn select_account(&mut self, pk: &Pubkey) {
if self.cache.select(*pk) { if !self.cache.select(*pk) {
if let Some(key_store) = &self.key_store { return;
if let Err(e) = key_store.select_key(Some(*pk)) { }
tracing::error!("Could not select key {:?}: {e}", pk);
} if let Some(key_store) = &self.key_store {
if let Err(e) = key_store.select_key(Some(*pk)) {
tracing::error!("Could not select key {:?}: {e}", pk);
} }
} }
} }
pub fn mutefun(&self) -> Box<MuteFun> { pub fn mutefun(&self) -> Box<MuteFun> {
let pubkey = self.cache.selected().key.pubkey.bytes(); let account_data = self.get_selected_account_data();
if let Some(account_data) = self.account_data.get(pubkey) {
let muted = Arc::clone(&account_data.muted.muted); let muted = Arc::clone(&account_data.muted.muted);
return Box::new(move |note: &Note, thread: &[u8; 32]| muted.is_muted(note, thread)); Box::new(move |note: &Note, thread: &[u8; 32]| muted.is_muted(note, thread))
}
Box::new(|_: &Note, _: &[u8; 32]| false)
} }
pub fn send_initial_filters(&mut self, pool: &mut RelayPool, relay_url: &str) { pub fn send_initial_filters(&mut self, pool: &mut RelayPool, relay_url: &str) {
for data in self.account_data.values() { for data in (&self.cache).into_iter().map(|(_, acc)| &acc.data) {
// send the active account's relay list subscription // send the active account's relay list subscription
if let Some(relay_subid) = &data.relay.subid { if let Some(relay_subid) = &data.relay.subid {
pool.send_to( pool.send_to(
@@ -202,49 +228,28 @@ impl Accounts {
fn delta_accounts(&self) -> (Vec<[u8; 32]>, Vec<[u8; 32]>) { fn delta_accounts(&self) -> (Vec<[u8; 32]>, Vec<[u8; 32]>) {
let mut added = Vec::new(); let mut added = Vec::new();
for pubkey in (&self.cache).into_iter().map(|(pk, _)| pk.bytes()) { for pubkey in (&self.cache).into_iter().map(|(pk, _)| pk.bytes()) {
if !self.account_data.contains_key(pubkey) { if !self.cache.contains(pubkey) {
added.push(*pubkey); added.push(*pubkey);
} }
} }
let mut removed = Vec::new(); let mut removed = Vec::new();
for pubkey in self.account_data.keys() { for (pubkey, _) in &self.cache {
if self.cache.get_bytes(pubkey).is_none() { if self.cache.get_bytes(pubkey).is_none() {
removed.push(*pubkey); removed.push(**pubkey);
} }
} }
(added, removed) (added, removed)
} }
fn handle_added_account(&mut self, ndb: &Ndb, pubkey: &[u8; 32]) {
debug!("handle_added_account {}", hex::encode(pubkey));
// Create the user account data
let new_account_data = AccountData {
relay: AccountRelayData::new(ndb, pubkey),
muted: AccountMutedData::new(ndb, pubkey),
};
self.account_data.insert(*pubkey, new_account_data);
}
fn handle_removed_account(&mut self, pubkey: &[u8; 32]) {
debug!("handle_removed_account {}", hex::encode(pubkey));
// FIXME - we need to unsubscribe here
self.account_data.remove(pubkey);
}
fn poll_for_updates(&mut self, ndb: &Ndb) -> bool { fn poll_for_updates(&mut self, ndb: &Ndb) -> bool {
let mut changed = false; let mut changed = false;
for (pubkey, data) in &mut self.account_data { for (pubkey, data) in &mut self.cache.iter_mut().map(|(pk, a)| (pk, &mut a.data)) {
if let Some(sub) = data.relay.sub { if let Some(sub) = data.relay.sub {
let nks = ndb.poll_for_notes(sub, 1); let nks = ndb.poll_for_notes(sub, 1);
if !nks.is_empty() { if !nks.is_empty() {
let txn = Transaction::new(ndb).expect("txn"); let txn = Transaction::new(ndb).expect("txn");
let relays = AccountRelayData::harvest_nip65_relays(ndb, &txn, &nks); let relays = AccountRelayData::harvest_nip65_relays(ndb, &txn, &nks);
debug!( debug!("pubkey {}: updated relays {:?}", pubkey.hex(), relays);
"pubkey {}: updated relays {:?}",
hex::encode(pubkey),
relays
);
data.relay.advertised = relays.into_iter().collect(); data.relay.advertised = relays.into_iter().collect();
changed = true; changed = true;
} }
@@ -254,7 +259,7 @@ impl Accounts {
if !nks.is_empty() { if !nks.is_empty() {
let txn = Transaction::new(ndb).expect("txn"); let txn = Transaction::new(ndb).expect("txn");
let muted = AccountMutedData::harvest_nip51_muted(ndb, &txn, &nks); let muted = AccountMutedData::harvest_nip51_muted(ndb, &txn, &nks);
debug!("pubkey {}: updated muted {:?}", hex::encode(pubkey), muted); debug!("pubkey {}: updated muted {:?}", pubkey.hex(), muted);
data.muted.muted = Arc::new(muted); data.muted.muted = Arc::new(muted);
changed = true; changed = true;
} }
@@ -278,10 +283,9 @@ impl Accounts {
// Compose the desired relay lists from the selected account // Compose the desired relay lists from the selected account
if desired_relays.is_empty() { if desired_relays.is_empty() {
if let Some(data) = self.get_selected_account_data() { let data = self.get_selected_account_data_mut();
desired_relays.extend(data.relay.local.iter().cloned()); desired_relays.extend(data.relay.local.iter().cloned());
desired_relays.extend(data.relay.advertised.iter().cloned()); desired_relays.extend(data.relay.advertised.iter().cloned());
}
} }
// If no relays are specified at this point use the bootstrap list // If no relays are specified at this point use the bootstrap list
@@ -331,32 +335,28 @@ impl Accounts {
// Do we need to deactivate any existing account subs? // Do we need to deactivate any existing account subs?
let selected = self.cache.selected(); let selected = self.cache.selected().key.pubkey;
for (pk, account) in &self.cache { for (pk, account) in &mut self.cache.iter_mut() {
if *pk != selected.key.pubkey { if *pk == selected {
// this account is not currently selected continue;
if let Some(data) = self.account_data.get_mut(account.key.pubkey.bytes()) { }
if data.relay.sub.is_some() {
// this account has relay subs, deactivate them let data = &mut account.data;
data.relay.deactivate(ndb, pool); // this account is not currently selected
} if data.relay.sub.is_some() {
if data.muted.sub.is_some() { // this account has relay subs, deactivate them
// this account has muted subs, deactivate them data.relay.deactivate(ndb, pool);
data.muted.deactivate(ndb, pool); }
} if data.muted.sub.is_some() {
} // this account has muted subs, deactivate them
data.muted.deactivate(ndb, pool);
} }
} }
// Were any accounts added or removed? // Were any accounts added or removed?
let (added, removed) = self.delta_accounts(); let (added, removed) = self.delta_accounts();
for pk in added { if !added.is_empty() || !removed.is_empty() {
self.handle_added_account(ndb, &pk);
need_reconfig = true;
}
for pk in removed {
self.handle_removed_account(&pk);
need_reconfig = true; need_reconfig = true;
} }
@@ -370,15 +370,14 @@ impl Accounts {
} }
// Do we need to activate account subs? // Do we need to activate account subs?
if let Some(data) = self.get_selected_account_data() { let data = self.get_selected_account_data_mut();
if data.relay.sub.is_none() { if data.relay.sub.is_none() {
// the currently selected account doesn't have relay subs, activate them // the currently selected account doesn't have relay subs, activate them
data.relay.activate(ndb, pool); data.relay.activate(ndb, pool);
} }
if data.muted.sub.is_none() { if data.muted.sub.is_none() {
// the currently selected account doesn't have muted subs, activate them // the currently selected account doesn't have muted subs, activate them
data.muted.activate(ndb, pool); data.muted.activate(ndb, pool);
}
} }
} }
@@ -398,34 +397,30 @@ impl Accounts {
RelayAction::Remove => info!("remove advertised relay \"{}\"", relay_url), RelayAction::Remove => info!("remove advertised relay \"{}\"", relay_url),
} }
let selected = self.cache.selected(); let selected = self.cache.selected_mut();
let key_bytes: [u8; 32] = *self.cache.selected().key.pubkey.bytes(); let account_data = &mut selected.data;
match self.account_data.get_mut(&key_bytes) {
None => error!("no account data found for the provided key."),
Some(account_data) => {
let advertised = &mut account_data.relay.advertised;
if advertised.is_empty() {
// If the selected account has no advertised relays,
// initialize with the bootstrapping set.
advertised.extend(self.relay_defaults.bootstrap_relays.iter().cloned());
}
match action {
RelayAction::Add => {
advertised.insert(RelaySpec::new(relay_url, false, false));
}
RelayAction::Remove => {
advertised.remove(&RelaySpec::new(relay_url, false, false));
}
}
self.needs_relay_config = true;
// If we have the secret key publish the NIP-65 relay list let advertised = &mut account_data.relay.advertised;
if let Some(secretkey) = &selected.key.secret_key { if advertised.is_empty() {
account_data // If the selected account has no advertised relays,
.relay // initialize with the bootstrapping set.
.publish_nip65_relays(&secretkey.to_secret_bytes(), pool); advertised.extend(self.relay_defaults.bootstrap_relays.iter().cloned());
} }
match action {
RelayAction::Add => {
advertised.insert(RelaySpec::new(relay_url, false, false));
} }
RelayAction::Remove => {
advertised.remove(&RelaySpec::new(relay_url, false, false));
}
}
self.needs_relay_config = true;
// If we have the secret key publish the NIP-65 relay list
if let Some(secretkey) = &selected.key.secret_key {
account_data
.relay
.publish_nip65_relays(&secretkey.to_secret_bytes(), pool);
} }
} }
@@ -454,9 +449,11 @@ impl<'a> AccType<'a> {
fn add_account_from_storage( fn add_account_from_storage(
cache: &mut AccountCache, cache: &mut AccountCache,
ndb: &Ndb,
txn: &Transaction,
user_account_serializable: UserAccountSerializable, user_account_serializable: UserAccountSerializable,
) -> SingleUnkIdAction { ) -> SingleUnkIdAction {
let Some(acc) = get_acc_from_storage(user_account_serializable) else { let Some(acc) = get_acc_from_storage(ndb, txn, user_account_serializable) else {
return SingleUnkIdAction::NoAction; return SingleUnkIdAction::NoAction;
}; };
@@ -466,8 +463,16 @@ fn add_account_from_storage(
SingleUnkIdAction::pubkey(pk) SingleUnkIdAction::pubkey(pk)
} }
fn get_acc_from_storage(user_account_serializable: UserAccountSerializable) -> Option<UserAccount> { fn get_acc_from_storage(
ndb: &Ndb,
txn: &Transaction,
user_account_serializable: UserAccountSerializable,
) -> Option<UserAccount> {
let keypair = user_account_serializable.key; let keypair = user_account_serializable.key;
let new_account_data = AccountData {
relay: AccountRelayData::new(ndb, txn, keypair.pubkey.bytes()),
muted: AccountMutedData::new(ndb, txn, keypair.pubkey.bytes()),
};
let mut wallet = None; let mut wallet = None;
if let Some(wallet_s) = user_account_serializable.wallet { if let Some(wallet_s) = user_account_serializable.wallet {
@@ -483,6 +488,7 @@ fn get_acc_from_storage(user_account_serializable: UserAccountSerializable) -> O
Some(UserAccount { Some(UserAccount {
key: keypair, key: keypair,
wallet, wallet,
data: new_account_data,
}) })
} }

View File

@@ -15,7 +15,7 @@ pub(crate) struct AccountMutedData {
} }
impl AccountMutedData { impl AccountMutedData {
pub fn new(ndb: &Ndb, pubkey: &[u8; 32]) -> Self { pub fn new(ndb: &Ndb, txn: &Transaction, pubkey: &[u8; 32]) -> Self {
// Construct a filter for the user's NIP-51 muted list // Construct a filter for the user's NIP-51 muted list
let filter = Filter::new() let filter = Filter::new()
.authors([pubkey]) .authors([pubkey])
@@ -24,15 +24,14 @@ impl AccountMutedData {
.build(); .build();
// Query the ndb immediately to see if the user's muted list is already there // Query the ndb immediately to see if the user's muted list is already there
let txn = Transaction::new(ndb).expect("transaction");
let lim = filter.limit().unwrap_or(crate::filter::default_limit()) as i32; let lim = filter.limit().unwrap_or(crate::filter::default_limit()) as i32;
let nks = ndb let nks = ndb
.query(&txn, &[filter.clone()], lim) .query(txn, &[filter.clone()], lim)
.expect("query user muted results") .expect("query user muted results")
.iter() .iter()
.map(|qr| qr.note_key) .map(|qr| qr.note_key)
.collect::<Vec<NoteKey>>(); .collect::<Vec<NoteKey>>();
let muted = Self::harvest_nip51_muted(ndb, &txn, &nks); let muted = Self::harvest_nip51_muted(ndb, txn, &nks);
debug!("pubkey {}: initial muted {:?}", hex::encode(pubkey), muted); debug!("pubkey {}: initial muted {:?}", hex::encode(pubkey), muted);
AccountMutedData { AccountMutedData {

View File

@@ -17,7 +17,7 @@ pub(crate) struct AccountRelayData {
} }
impl AccountRelayData { impl AccountRelayData {
pub fn new(ndb: &Ndb, pubkey: &[u8; 32]) -> Self { pub fn new(ndb: &Ndb, txn: &Transaction, pubkey: &[u8; 32]) -> Self {
// Construct a filter for the user's NIP-65 relay list // Construct a filter for the user's NIP-65 relay list
let filter = Filter::new() let filter = Filter::new()
.authors([pubkey]) .authors([pubkey])
@@ -26,15 +26,14 @@ impl AccountRelayData {
.build(); .build();
// Query the ndb immediately to see if the user list is already there // Query the ndb immediately to see if the user list is already there
let txn = Transaction::new(ndb).expect("transaction");
let lim = filter.limit().unwrap_or(crate::filter::default_limit()) as i32; let lim = filter.limit().unwrap_or(crate::filter::default_limit()) as i32;
let nks = ndb let nks = ndb
.query(&txn, &[filter.clone()], lim) .query(txn, &[filter.clone()], lim)
.expect("query user relays results") .expect("query user relays results")
.iter() .iter()
.map(|qr| qr.note_key) .map(|qr| qr.note_key)
.collect::<Vec<NoteKey>>(); .collect::<Vec<NoteKey>>();
let relays = Self::harvest_nip65_relays(ndb, &txn, &nks); let relays = Self::harvest_nip65_relays(ndb, txn, &nks);
debug!( debug!(
"pubkey {}: initial relays {:?}", "pubkey {}: initial relays {:?}",
hex::encode(pubkey), hex::encode(pubkey),

View File

@@ -176,16 +176,23 @@ impl Notedeck {
None None
}; };
let mut accounts = Accounts::new(keystore, parsed_args.relays.clone(), FALLBACK_PUBKEY());
let mut unknown_ids = UnknownIds::default(); let mut unknown_ids = UnknownIds::default();
let ndb = Ndb::new(&dbpath_str, &config).expect("ndb"); let ndb = Ndb::new(&dbpath_str, &config).expect("ndb");
let txn = Transaction::new(&ndb).expect("txn");
let mut accounts = Accounts::new(
keystore,
parsed_args.relays.clone(),
FALLBACK_PUBKEY(),
&ndb,
&txn,
&mut unknown_ids,
);
{ {
let txn = Transaction::new(&ndb).expect("txn");
for key in &parsed_args.keys { for key in &parsed_args.keys {
info!("adding account: {}", &key.pubkey); info!("adding account: {}", &key.pubkey);
if let Some(resp) = accounts.add_account(key.clone()) { if let Some(resp) = accounts.add_account(&ndb, &txn, key.clone()) {
resp.unk_id_action resp.unk_id_action
.process_action(&mut unknown_ids, &ndb, &txn); .process_action(&mut unknown_ids, &ndb, &txn);
} }

View File

@@ -1,16 +1,24 @@
use enostr::{Keypair, KeypairUnowned}; use enostr::{Keypair, KeypairUnowned};
use tokenator::{ParseError, TokenParser, TokenSerializable}; use tokenator::{ParseError, TokenParser, TokenSerializable};
use crate::wallet::{WalletSerializable, ZapWallet}; use crate::{
wallet::{WalletSerializable, ZapWallet},
AccountData,
};
pub struct UserAccount { pub struct UserAccount {
pub key: Keypair, pub key: Keypair,
pub wallet: Option<ZapWallet>, pub wallet: Option<ZapWallet>,
pub data: AccountData,
} }
impl UserAccount { impl UserAccount {
pub fn new(key: Keypair) -> Self { pub fn new(key: Keypair, data: AccountData) -> Self {
Self { key, wallet: None } Self {
key,
wallet: None,
data,
}
} }
pub fn keypair(&self) -> KeypairUnowned { pub fn keypair(&self) -> KeypairUnowned {

View File

@@ -94,7 +94,7 @@ pub fn render_accounts_route(
} }
} }
AccountsRouteResponse::AddAccount(response) => { AccountsRouteResponse::AddAccount(response) => {
let action = process_login_view_response(accounts, decks, col, response); let action = process_login_view_response(accounts, decks, col, ndb, response);
*login_state = Default::default(); *login_state = Default::default();
let router = get_active_columns_mut(accounts, decks) let router = get_active_columns_mut(accounts, decks)
.column_mut(col) .column_mut(col)
@@ -144,17 +144,20 @@ pub fn process_login_view_response(
manager: &mut Accounts, manager: &mut Accounts,
decks: &mut DecksCache, decks: &mut DecksCache,
col: usize, col: usize,
ndb: &Ndb,
response: AccountLoginResponse, response: AccountLoginResponse,
) -> AddAccountAction { ) -> AddAccountAction {
let (r, pubkey) = match response { let (r, pubkey) = match response {
AccountLoginResponse::CreateNew => { AccountLoginResponse::CreateNew => {
let kp = FullKeypair::generate().to_keypair(); let kp = FullKeypair::generate().to_keypair();
let pubkey = kp.pubkey; let pubkey = kp.pubkey;
(manager.add_account(kp), pubkey) let txn = Transaction::new(ndb).expect("txn");
(manager.add_account(ndb, &txn, kp), pubkey)
} }
AccountLoginResponse::LoginWith(keypair) => { AccountLoginResponse::LoginWith(keypair) => {
let pubkey = keypair.pubkey; let pubkey = keypair.pubkey;
(manager.add_account(keypair), pubkey) let txn = Transaction::new(ndb).expect("txn");
(manager.add_account(ndb, &txn, keypair), pubkey)
} }
}; };

View File

@@ -770,9 +770,12 @@ pub fn set_demo(
) { ) {
let fallback = decks_cache.get_fallback_pubkey(); let fallback = decks_cache.get_fallback_pubkey();
let txn = Transaction::new(ndb).expect("txn"); let txn = Transaction::new(ndb).expect("txn");
if let Some(resp) = if let Some(resp) = accounts.add_account(
accounts.add_account(Keypair::only_pubkey(*decks_cache.get_fallback_pubkey())) ndb,
{ &txn,
Keypair::only_pubkey(*decks_cache.get_fallback_pubkey()),
) {
let txn = Transaction::new(ndb).expect("txn");
resp.unk_id_action.process_action(unk_ids, ndb, &txn); resp.unk_id_action.process_action(unk_ids, ndb, &txn);
} }
accounts.select_account(fallback); accounts.select_account(fallback);