// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #pragma once #include #include 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"; // Contact avatar: "" = default type badge (Z/T), "icon:" = a Material wallet-icon, // "img:" = a custom image (copied into /contact-avatars/). std::string avatar; 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 Re-attach contacts stuck on a legacy (drifting address-hash) scope to a stable wallet id. * Rewrites every non-global entry whose scope is NOT already a stable "w:"-prefixed id to * `scopeId`, and saves once if anything changed. Recovery for contacts orphaned when the * old address-set-hash scope shifted (e.g. after creating a new address). * @return number of entries re-scoped. */ int reattachLegacyScopes(const std::string& scopeId); /** * @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& entries() const { return entries_; } /** * @brief Get entry count */ size_t size() const { return entries_.size(); } /** * @brief UI-sweep ONLY: replace the in-memory entries WITHOUT persisting to disk, so the sweep can * seed demo contacts and restore the real book without a disk write. Do not use outside the sweep. */ void sweepSetEntries(std::vector e) { entries_ = std::move(e); } /** * @brief Check if empty */ bool empty() const { return entries_.empty(); } private: std::vector entries_; std::string file_path_; }; } // namespace data } // namespace dragonx