feat(lite): lite wallet foundation (inherited working-tree state)

Preserve the previously-uncommitted lite wallet implementation and related dev WIP
under version control:
- src/wallet/ lite services: client bridge, bridge runtime, connection, lifecycle,
  sync, gateway, result parsers, state mapper, artifact contract/resolver, refresh
  services, UI adapters, wallet_backend/capabilities. (Includes two small M1 fixes:
  lifecycle walletReady now parses the response; default chain name -> "main".)
- src/chat/ chat protocol; tests/fixtures/ (lite + hushchat); tools/hushchat_fixture_check.cpp;
  scripts/build-lite-backend-artifact.sh.
- Pre-existing modified app_network/security/wizard, network_refresh_service, sidebar,
  mining_tab, bootstrap dialog, and version headers captured as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 21:15:28 -05:00
parent a78a13edf3
commit 863d015628
69 changed files with 39458 additions and 85 deletions

View File

@@ -6,6 +6,7 @@
#include <cstdlib>
#include <map>
#include <set>
#include <unordered_map>
using json = nlohmann::json;
@@ -136,6 +137,60 @@ void appendMissingPreviousTransactions(std::vector<TransactionInfo>& transaction
}
}
using HushChatMemoOutputMap = std::unordered_map<std::string, std::vector<chat::HushChatMemoOutput>>;
std::size_t readMemoPosition(const json& source, std::size_t fallback)
{
for (const char* key : {"position", "outputIndex", "outindex"}) {
auto value = readOptional<int>(source, key);
if (value && *value >= 0) return static_cast<std::size_t>(*value);
}
return fallback;
}
void recordHushChatMemoOutput(HushChatMemoOutputMap* outputs,
const std::string& txid,
const std::string& memo,
std::size_t position)
{
if (!outputs || txid.empty() || memo.empty()) return;
(*outputs)[txid].push_back(chat::HushChatMemoOutput{position, memo});
}
void appendExtractedHushChatMetadata(std::vector<chat::HushChatTransactionMetadata>& destination,
const std::string& txid,
const std::vector<chat::HushChatMemoOutput>& outputs)
{
auto extracted = chat::extractHushChatTransactionMetadata(chat::HushChatTransactionInput{txid, outputs});
for (auto& metadata : extracted.metadata) {
destination.push_back(std::move(metadata));
}
}
void appendExtractedHushChatMetadata(std::vector<chat::HushChatTransactionMetadata>& destination,
const std::string& txid,
const NetworkRefreshService::TransactionViewCacheEntry& entry)
{
if (!chat::hushChatFeatureEnabledAtBuild()) return;
std::vector<chat::HushChatMemoOutput> outputs;
outputs.reserve(entry.outgoing_outputs.size());
for (const auto& output : entry.outgoing_outputs) {
if (!output.memo.empty()) outputs.push_back(chat::HushChatMemoOutput{output.position, output.memo});
}
appendExtractedHushChatMetadata(destination, txid, outputs);
}
void appendExtractedHushChatMetadata(std::vector<chat::HushChatTransactionMetadata>& destination,
const HushChatMemoOutputMap& outputsByTxid)
{
if (!chat::hushChatFeatureEnabledAtBuild()) return;
for (const auto& [txid, outputs] : outputsByTxid) {
appendExtractedHushChatMetadata(destination, txid, outputs);
}
}
} // namespace
NetworkRefreshService::ConnectionInfoResult NetworkRefreshService::parseConnectionInfoResult(const json& info)
@@ -614,17 +669,23 @@ void NetworkRefreshService::appendShieldedReceivedTransactions(std::vector<Trans
std::set<std::string>& knownTxids,
const std::string& address,
const json& received,
const std::set<std::string>& miningAddresses)
const std::set<std::string>& miningAddresses,
HushChatMemoOutputMap* chatMemoOutputs)
{
if (received.is_null() || !received.is_array()) return;
std::size_t fallbackPosition = 0;
for (const auto& note : received) {
const std::size_t notePosition = readMemoPosition(note, fallbackPosition++);
auto txid = readOptional<std::string>(note, "txid");
if (!txid || txid->empty()) continue;
auto change = readOptional<bool>(note, "change");
if (change && *change) continue;
auto memoValue = readOptional<std::string>(note, "memoStr");
if (memoValue) recordHushChatMemoOutput(chatMemoOutputs, *txid, *memoValue, notePosition);
bool dominated = false;
for (const auto& existing : transactions) {
if (existing.txid == *txid && existing.type == "receive") {
@@ -641,7 +702,7 @@ void NetworkRefreshService::appendShieldedReceivedTransactions(std::vector<Trans
if (auto value = readOptional<double>(note, "amount")) info.amount = *value;
if (auto value = readOptional<int>(note, "confirmations")) info.confirmations = *value;
if (auto value = readOptional<std::int64_t>(note, "time")) info.timestamp = *value;
if (auto value = readOptional<std::string>(note, "memoStr")) info.memo = *value;
if (memoValue) info.memo = *memoValue;
knownTxids.insert(*txid);
transactions.push_back(std::move(info));
}
@@ -663,7 +724,9 @@ NetworkRefreshService::TransactionViewCacheEntry NetworkRefreshService::parseVie
}
if (viewTransaction.contains("outputs") && viewTransaction["outputs"].is_array()) {
std::size_t fallbackPosition = 0;
for (const auto& output : viewTransaction["outputs"]) {
const std::size_t outputPosition = readMemoPosition(output, fallbackPosition++);
bool outgoing = false;
if (auto value = readOptional<bool>(output, "outgoing")) outgoing = *value;
if (!outgoing) continue;
@@ -672,6 +735,7 @@ NetworkRefreshService::TransactionViewCacheEntry NetworkRefreshService::parseVie
if (auto value = readOptional<std::string>(output, "address")) out.address = *value;
if (auto value = readOptional<double>(output, "value")) out.value = *value;
if (auto value = readOptional<std::string>(output, "memoStr")) out.memo = *value;
out.position = outputPosition;
entry.outgoing_outputs.push_back(std::move(out));
}
}
@@ -738,6 +802,10 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
result.shieldedScanHeights = snapshot.shieldedScanHeights;
std::set<std::string> knownTxids;
HushChatMemoOutputMap hushChatReceivedOutputs;
HushChatMemoOutputMap* hushChatReceivedOutputsPtr = chat::hushChatFeatureEnabledAtBuild()
? &hushChatReceivedOutputs
: nullptr;
bool transactionRpcError = false;
try {
@@ -779,7 +847,12 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
nextSearchIndex = index + 1;
try {
json received = rpc.call("z_listreceivedbyaddress", json::array({address, 0}));
appendShieldedReceivedTransactions(result.transactions, knownTxids, address, received, snapshot.miningAddresses);
appendShieldedReceivedTransactions(result.transactions,
knownTxids,
address,
received,
snapshot.miningAddresses,
hushChatReceivedOutputsPtr);
if (currentBlockHeight >= 0) result.shieldedScanHeights[address] = currentBlockHeight;
} catch (const std::exception& e) {
transactionRpcError = true;
@@ -817,6 +890,7 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
if (cached != snapshot.viewTxCache.end()) {
if (!trackedSend || !cached->second.outgoing_outputs.empty()) {
appendViewTransactionOutputs(result.transactions, txid, cached->second);
appendExtractedHushChatMetadata(result.hushChatMetadata, txid, cached->second);
continue;
}
}
@@ -841,6 +915,7 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
auto entry = parseViewTransactionCacheEntry(viewTransaction);
appendViewTransactionOutputs(result.transactions, txid, entry);
appendExtractedHushChatMetadata(result.hushChatMetadata, txid, entry);
json rawTransaction;
bool hasRawTransaction = false;
@@ -885,6 +960,8 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectTr
}
}
appendExtractedHushChatMetadata(result.hushChatMetadata, hushChatReceivedOutputs);
if (!snapshot.previousTransactions.empty()) {
if (transactionRpcError) {
appendMissingPreviousTransactions(result.transactions, snapshot.previousTransactions, true, snapshot.pendingOpids);
@@ -909,6 +986,10 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectRe
result.transactions = snapshot.previousTransactions;
result.shieldedAddressCount = snapshot.shieldedAddresses.size();
result.shieldedScanHeights = snapshot.shieldedScanHeights;
HushChatMemoOutputMap hushChatReceivedOutputs;
HushChatMemoOutputMap* hushChatReceivedOutputsPtr = chat::hushChatFeatureEnabledAtBuild()
? &hushChatReceivedOutputs
: nullptr;
try {
std::set<std::string> recentTxids;
@@ -944,7 +1025,12 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectRe
std::vector<TransactionInfo> scannedTransactions;
std::set<std::string> scannedTxids;
json received = rpc.call("z_listreceivedbyaddress", json::array({address, 0}));
appendShieldedReceivedTransactions(scannedTransactions, scannedTxids, address, received, snapshot.miningAddresses);
appendShieldedReceivedTransactions(scannedTransactions,
scannedTxids,
address,
received,
snapshot.miningAddresses,
hushChatReceivedOutputsPtr);
for (auto& scanned : scannedTransactions) {
bool replaced = false;
for (auto& existing : result.transactions) {
@@ -967,6 +1053,8 @@ NetworkRefreshService::TransactionRefreshResult NetworkRefreshService::collectRe
(shieldedStart + shieldedLimit >= snapshot.shieldedAddresses.size()) ? 0 : shieldedStart + shieldedLimit;
result.shieldedScanComplete = true;
appendExtractedHushChatMetadata(result.hushChatMetadata, hushChatReceivedOutputs);
sortTransactionsNewestFirst(result.transactions);
return result;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "chat/chat_protocol.h"
#include "data/wallet_state.h"
#include "refresh_scheduler.h"
#include "rpc/rpc_worker.h"
@@ -159,6 +160,7 @@ public:
std::string address;
double value = 0.0;
std::string memo;
std::size_t position = 0;
};
std::vector<Output> outgoing_outputs;
};
@@ -180,6 +182,7 @@ public:
struct TransactionRefreshResult {
std::vector<TransactionInfo> transactions;
std::vector<chat::HushChatTransactionMetadata> hushChatMetadata;
int blockHeight = -1;
TransactionViewCache newViewTxEntries;
std::size_t nextShieldedScanStartIndex = 0;
@@ -260,7 +263,8 @@ public:
std::set<std::string>& knownTxids,
const std::string& address,
const nlohmann::json& received,
const std::set<std::string>& miningAddresses = {});
const std::set<std::string>& miningAddresses = {},
std::unordered_map<std::string, std::vector<chat::HushChatMemoOutput>>* chatMemoOutputs = nullptr);
static TransactionViewCacheEntry parseViewTransactionCacheEntry(const nlohmann::json& viewTransaction);
static void appendViewTransactionOutputs(std::vector<TransactionInfo>& transactions,
const std::string& txid,