Files
ObsidianDragon/src/data/address_book.h
DanS 72bf59149d feat(wallet): per-wallet data isolation foundation (P1)
First phase of multi-wallet support: keep each wallet's data separate so loading
a different wallet no longer shows the previous one's data, and lay the metadata
groundwork for the wallet-files list.

- Address book scoped per wallet: AddressBookEntry gains a "scope" ("global" or a
  wallet-identity hash); the Contacts tab shows global + current-wallet contacts
  (App::activeWalletIdentityHash) with a "show in every wallet" toggle + globe
  badge. Legacy entries migrate to "global". Scope-aware de-dup allows the same
  address across different wallets.
- Wallet metadata index (data/wallet_index -> wallets.json): file-keyed cache of
  balance / address count / identity / size / last-opened / synced-here, since
  those can't be read off a wallet.dat without loading it. Populated on connect +
  address refresh (change-detecting upsert). Plus an active_wallet_file setting
  for the -wallet=<name> switch coming in P2.

Unit-tested (testAddressBookScope, testWalletIndex): migration, visibility filter,
scope-aware de-dup, upsert change-detection, save/reload round-trip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:57:23 -05:00

124 lines
3.4 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <vector>
namespace dragonx {
namespace data {
/**
* @brief A single address book entry
*/
struct AddressBookEntry {
std::string label;
std::string address;
std::string notes;
// Per-wallet visibility scope: "global" (shown in every wallet) or a wallet-identity hash
// (shown only when that wallet is the active one). Empty is treated as "global" so legacy
// entries — written before multi-wallet scoping — keep showing everywhere.
std::string scope = "global";
AddressBookEntry() = default;
AddressBookEntry(const std::string& l, const std::string& a, const std::string& n = "",
const std::string& s = "global")
: label(l), address(a), notes(n), scope(s) {}
bool isGlobal() const { return scope.empty() || scope == "global"; }
// Visible under the wallet whose identity hash is walletHash (or globally).
bool visibleInWallet(const std::string& walletHash) const {
return isGlobal() || scope == walletHash;
}
};
/**
* @brief Address book manager
*
* Stores labeled addresses for easy access when sending.
*/
class AddressBook {
public:
AddressBook();
~AddressBook();
/**
* @brief Load address book from disk
* @return true if loaded successfully
*/
bool load();
/**
* @brief Save address book to disk
* @return true if saved successfully
*/
bool save();
/**
* @brief Get default address book file path
*/
static std::string getDefaultPath();
/**
* @brief Add a new entry
* @param entry The entry to add
* @return true if added (false if duplicate address)
*/
bool addEntry(const AddressBookEntry& entry);
/**
* @brief Update an existing entry
* @param index Index of entry to update
* @param entry New entry data
* @return true if updated
*/
bool updateEntry(size_t index, const AddressBookEntry& entry);
/**
* @brief Remove an entry
* @param index Index of entry to remove
* @return true if removed
*/
bool removeEntry(size_t index);
/**
* @brief Find entry by address (any scope). Used for contact-label lookups.
* @param address Address to search for
* @return Index or -1 if not found
*/
int findByAddress(const std::string& address) const;
/**
* @brief Is there an existing entry with this address that would be visible ALONGSIDE a new
* entry of the given scope? (Same wallet, or either side global.) Used to reject
* duplicates within a wallet while still allowing the same address in different wallets.
* @param excludeIndex Storage index to skip (for edit), or -1.
*/
bool hasVisibleDuplicate(const std::string& address, const std::string& scope,
int excludeIndex = -1) const;
/**
* @brief Get all entries
*/
const std::vector<AddressBookEntry>& entries() const { return entries_; }
/**
* @brief Get entry count
*/
size_t size() const { return entries_.size(); }
/**
* @brief Check if empty
*/
bool empty() const { return entries_.empty(); }
private:
std::vector<AddressBookEntry> entries_;
std::string file_path_;
};
} // namespace data
} // namespace dragonx