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

@@ -0,0 +1,217 @@
#include "wallet/lite_wallet_state_mapper.h"
#include <utility>
namespace dragonx::wallet {
namespace {
LiteWalletAppAddressModel mapAddress(const std::string& address, LiteWalletAppAddressKind kind)
{
LiteWalletAppAddressModel model;
model.address = address;
model.kind = kind;
return model;
}
LiteWalletAppSpendableOutputModel mapSpendableOutput(const LiteSpendableOutput& output)
{
LiteWalletAppSpendableOutputModel model;
model.kind = output.kind;
model.address = output.address;
model.createdInTxid = output.createdInTxid;
model.createdInBlock = output.createdInBlock;
model.valueZatoshis = output.value;
model.spent = output.spent;
model.unconfirmedSpent = output.unconfirmedSpent;
model.pending = output.pending;
model.spendable = output.spendable;
return model;
}
LiteWalletAppTransactionKind mapTransactionKind(LiteTransactionDirection direction)
{
switch (direction) {
case LiteTransactionDirection::Send: return LiteWalletAppTransactionKind::Send;
case LiteTransactionDirection::Receive: return LiteWalletAppTransactionKind::Receive;
case LiteTransactionDirection::Unknown: return LiteWalletAppTransactionKind::Unknown;
}
return LiteWalletAppTransactionKind::Unknown;
}
LiteWalletAppTransactionOutputModel mapTransactionOutput(const LiteTransactionOutput& output)
{
LiteWalletAppTransactionOutputModel model;
model.address = output.address;
model.valueZatoshis = output.value;
model.memo = output.memo;
return model;
}
LiteWalletAppTransactionModel mapTransaction(const LiteTransactionRecord& transaction)
{
LiteWalletAppTransactionModel model;
model.txid = transaction.txid;
model.kind = mapTransactionKind(transaction.direction);
model.timestamp = transaction.datetime;
model.blockHeight = transaction.blockHeight;
model.unconfirmed = transaction.unconfirmed;
model.address = transaction.address;
model.amountZatoshis = transaction.amount;
model.signedAmountZatoshis = model.kind == LiteWalletAppTransactionKind::Send
? -transaction.amount
: transaction.amount;
model.memo = transaction.memo;
model.position = transaction.position;
model.outgoingOutputs.reserve(transaction.outgoingMetadata.size());
for (const auto& output : transaction.outgoingMetadata) {
model.outgoingOutputs.push_back(mapTransactionOutput(output));
}
return model;
}
bool modelHasAnyMappedField(const LiteWalletAppRefreshModel& model)
{
return model.hasChainInfo || model.hasHeight || model.hasBalance || model.hasAddresses ||
model.hasSpendableOutputs || model.hasTransactions || model.hasSyncStatus;
}
void addIssue(LiteWalletStateMapResult& result, LiteWalletStateMapIssue issue, std::string message)
{
result.issues.push_back(LiteWalletStateMapIssueInfo{issue, std::move(message)});
}
} // namespace
const char* liteWalletAppAddressKindName(LiteWalletAppAddressKind kind)
{
switch (kind) {
case LiteWalletAppAddressKind::Shielded: return "shielded";
case LiteWalletAppAddressKind::Transparent: return "transparent";
}
return "unknown";
}
const char* liteWalletAppTransactionKindName(LiteWalletAppTransactionKind kind)
{
switch (kind) {
case LiteWalletAppTransactionKind::Unknown: return "unknown";
case LiteWalletAppTransactionKind::Send: return "send";
case LiteWalletAppTransactionKind::Receive: return "receive";
}
return "unknown";
}
const char* liteWalletStateMapIssueName(LiteWalletStateMapIssue issue)
{
switch (issue) {
case LiteWalletStateMapIssue::EmptyBundle: return "EmptyBundle";
case LiteWalletStateMapIssue::IncompleteBundle: return "IncompleteBundle";
}
return "Unknown";
}
LiteWalletStateMapResult mapLiteWalletRefreshBundle(const LiteWalletRefreshBundle& bundle)
{
LiteWalletStateMapResult result;
result.stateMutationAllowed = false;
result.model.complete = bundle.complete;
result.model.successfulCommandCount = bundle.successfulCommandCount;
if (bundle.hasInfo) {
result.model.hasChainInfo = true;
result.model.chain.chainName = bundle.info.chainName;
result.model.chain.version = bundle.info.version;
result.model.chain.vendor = bundle.info.vendor;
result.model.chain.latestBlockHeight = bundle.info.latestBlockHeight;
result.model.chain.difficulty = bundle.info.difficulty;
result.model.chain.longestChain = bundle.info.longestChain;
result.model.chain.notarized = bundle.info.notarized;
}
if (bundle.hasHeight) {
result.model.hasHeight = true;
result.model.height.height = bundle.height.height;
}
if (bundle.hasBalance) {
result.model.hasBalance = true;
result.model.balance.transparentZatoshis = bundle.balance.transparentBalance;
result.model.balance.shieldedZatoshis = bundle.balance.shieldedBalance;
result.model.balance.unconfirmedZatoshis = bundle.balance.unconfirmedBalance;
result.model.balance.verifiedShieldedZatoshis = bundle.balance.verifiedShieldedBalance;
result.model.balance.spendableShieldedZatoshis = bundle.balance.spendableShieldedBalance;
result.model.balance.totalZatoshis = bundle.balance.transparentBalance + bundle.balance.shieldedBalance;
}
if (bundle.hasAddresses) {
result.model.hasAddresses = true;
result.model.addresses.reserve(bundle.addresses.zAddresses.size() + bundle.addresses.tAddresses.size());
for (const auto& address : bundle.addresses.zAddresses) {
result.model.addresses.push_back(mapAddress(address, LiteWalletAppAddressKind::Shielded));
}
for (const auto& address : bundle.addresses.tAddresses) {
result.model.addresses.push_back(mapAddress(address, LiteWalletAppAddressKind::Transparent));
}
}
if (bundle.hasNotes) {
result.model.hasSpendableOutputs = true;
const auto outputCount = bundle.notes.unspentNotes.size() + bundle.notes.utxos.size() +
bundle.notes.pendingNotes.size() + bundle.notes.pendingUtxos.size();
result.model.spendableOutputs.reserve(outputCount);
for (const auto& output : bundle.notes.unspentNotes) {
result.model.spendableOutputs.push_back(mapSpendableOutput(output));
}
for (const auto& output : bundle.notes.utxos) {
result.model.spendableOutputs.push_back(mapSpendableOutput(output));
}
for (const auto& output : bundle.notes.pendingNotes) {
result.model.spendableOutputs.push_back(mapSpendableOutput(output));
}
for (const auto& output : bundle.notes.pendingUtxos) {
result.model.spendableOutputs.push_back(mapSpendableOutput(output));
}
}
if (bundle.hasTransactions) {
result.model.hasTransactions = true;
result.model.transactions.reserve(bundle.transactions.transactions.size());
for (const auto& transaction : bundle.transactions.transactions) {
result.model.transactions.push_back(mapTransaction(transaction));
}
}
if (bundle.hasSyncStatus) {
result.model.hasSyncStatus = true;
result.model.sync.walletHeight = bundle.syncStatus.syncedBlocks;
result.model.sync.chainHeight = bundle.syncStatus.totalBlocks;
result.model.sync.progress = bundle.syncStatus.progress;
result.model.sync.complete = bundle.syncStatus.complete;
}
if (!modelHasAnyMappedField(result.model)) {
addIssue(result, LiteWalletStateMapIssue::EmptyBundle, "lite refresh bundle has no mappable fields");
result.error = result.issues.back().message;
return result;
}
if (!bundle.complete) {
addIssue(result, LiteWalletStateMapIssue::IncompleteBundle, "lite refresh bundle is partial");
}
result.ok = true;
return result;
}
LiteWalletStateMapResult mapLiteWalletRefreshResult(const LiteWalletRefreshResult& result)
{
return mapLiteWalletRefreshBundle(result.bundle);
}
LiteWalletStateMapResult mapLiteWalletRefreshServiceResult(const LiteWalletRefreshServiceResult& result)
{
return mapLiteWalletRefreshBundle(result.bundle);
}
} // namespace dragonx::wallet