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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user