#pragma once // DragonX Wallet - HushChat service: turns harvested memo metadata into decrypted, threaded // messages. Owns the long-lived chat identity keypair (a secret) + the in-memory store. // Move-disabled (the secret stays pinned); wipes the secret on destruction/clear. // Not thread-safe — drive from the main thread (where refresh results are applied). #include "chat_crypto.h" // ChatKeyPair #include "chat_protocol.h" // HushChatTransactionMetadata #include "chat_outgoing.h" // OutgoingChatMemos, ChatComposeStatus #include "chat_store.h" #include #include #include #include namespace dragonx::chat { class ChatDatabase; // optional persistent backing (Phase 2); set via setPersistence class ChatService { public: ChatService() = default; ~ChatService(); ChatService(const ChatService&) = delete; ChatService& operator=(const ChatService&) = delete; ChatService(ChatService&&) = delete; ChatService& operator=(ChatService&&) = delete; // Provision (or replace) the chat identity. Copies the keypair — the caller should wipe // its own copy afterwards (see chat_identity.h ownership note). void setIdentity(const ChatKeyPair& keys); bool hasIdentity() const { return has_identity_; } void clearIdentity(); // wipes the held secret key // Decrypt/record each metadata entry (a Message is decrypted; a ContactRequest carries its // plaintext through) and thread it into the store. Each message is stamped with its own // transaction time via `txTimestamps` (keyed by txid), falling back to `fallbackTimestamp` // when the txid isn't present. Newly-added messages are also persisted (if a database is // attached). Returns the number of NEW messages added; 0 with no identity. Undecryptable // Messages are dropped silently (no logging of memo/plaintext). int ingest(const std::vector& metadata, const std::unordered_map& txTimestamps, std::int64_t fallbackTimestamp = 0); // Attach a persistent backing store (Phase 2). Not owned. Pass nullptr to detach. New messages // from ingest() are written through; loadFromDatabase() rehydrates the in-memory store from it. void setPersistence(ChatDatabase* db) { db_ = db; } // Load previously-persisted messages (already decrypted at ingest, re-encrypted at rest under // the seed-derived key) into the in-memory store. No-op without an unlocked database. void loadFromDatabase(); // --- Outgoing (compose) --- // My chat public key (hex), or "" without an identity — goes in an outgoing header's "p". std::string identityPublicKeyHex() const; // Construct the outgoing memos for an ENCRYPTED message, using the held identity to encrypt. ChatComposeStatus composeMessage(const std::string& myReplyZaddr, const std::string& peerPublicKeyHex, const std::string& peerZaddr, const std::string& conversationId, const std::string& plaintext, OutgoingChatMemos& out) const; // Construct the outgoing memos for a plaintext contact request (no peer key needed yet). ChatComposeStatus composeContactRequest(const std::string& myReplyZaddr, const std::string& peerZaddr, const std::string& conversationId, const std::string& requestText, OutgoingChatMemos& out) const; // Echo a locally-composed outgoing message into the store (and DB). Returns true if new. (We // never harvest our own sent memos — they land on the peer's address — so this echo is the // only local record of what we sent.) bool recordOutgoing(const ChatMessage& message); const ChatStore& store() const { return store_; } ChatStore& store() { return store_; } private: ChatKeyPair identity_{}; bool has_identity_ = false; ChatStore store_; ChatDatabase* db_ = nullptr; // optional; not owned }; } // namespace dragonx::chat