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:
@@ -0,0 +1,570 @@
|
||||
#include "wallet/lite_result_parsers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace dragonx::wallet {
|
||||
namespace {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
std::string fieldPath(const std::string& key)
|
||||
{
|
||||
return "$.'" + key + "'";
|
||||
}
|
||||
|
||||
std::string indexPath(const std::string& parentPath, std::size_t index)
|
||||
{
|
||||
return parentPath + "[" + std::to_string(index) + "]";
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
Result makeResult(LiteResultCommand command)
|
||||
{
|
||||
Result result;
|
||||
result.command = command;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool fail(Result& result,
|
||||
LiteResultParserError error,
|
||||
const std::string& path,
|
||||
const std::string& message)
|
||||
{
|
||||
result.ok = false;
|
||||
result.error = error;
|
||||
result.errorPath = path;
|
||||
result.errorMessage = message;
|
||||
result.issues.push_back({path, message});
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
void succeed(Result& result)
|
||||
{
|
||||
result.ok = true;
|
||||
result.error = LiteResultParserError::None;
|
||||
result.errorPath.clear();
|
||||
result.errorMessage.clear();
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool parseJsonText(const std::string& jsonText, Result& result, json& parsed)
|
||||
{
|
||||
parsed = json::parse(jsonText, nullptr, false);
|
||||
if (parsed.is_discarded()) {
|
||||
return fail(result, LiteResultParserError::InvalidJson, "$", "response is not valid JSON");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool requireObject(const json& value, const std::string& path, Result& result)
|
||||
{
|
||||
if (!value.is_object()) {
|
||||
return fail(result, LiteResultParserError::ExpectedObject, path, "expected a JSON object");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool requireArray(const json& value, const std::string& path, Result& result)
|
||||
{
|
||||
if (!value.is_array()) {
|
||||
return fail(result, LiteResultParserError::ExpectedArray, path, "expected a JSON array");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool jsonToUnsigned(const json& value, std::uint64_t& output)
|
||||
{
|
||||
try {
|
||||
if (value.is_number_unsigned()) {
|
||||
output = value.get<std::uint64_t>();
|
||||
return true;
|
||||
}
|
||||
if (value.is_number_integer()) {
|
||||
const auto signedValue = value.get<std::int64_t>();
|
||||
if (signedValue < 0) return false;
|
||||
output = static_cast<std::uint64_t>(signedValue);
|
||||
return true;
|
||||
}
|
||||
if (value.is_string()) {
|
||||
const auto text = value.get<std::string>();
|
||||
if (text.empty()) return false;
|
||||
std::size_t parsedSize = 0;
|
||||
const auto parsed = std::stoull(text, &parsedSize, 10);
|
||||
if (parsedSize != text.size()) return false;
|
||||
output = static_cast<std::uint64_t>(parsed);
|
||||
return true;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool jsonToSigned(const json& value, std::int64_t& output)
|
||||
{
|
||||
try {
|
||||
if (value.is_number_integer()) {
|
||||
output = value.get<std::int64_t>();
|
||||
return true;
|
||||
}
|
||||
if (value.is_number_unsigned()) {
|
||||
const auto unsignedValue = value.get<std::uint64_t>();
|
||||
if (unsignedValue > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) {
|
||||
return false;
|
||||
}
|
||||
output = static_cast<std::int64_t>(unsignedValue);
|
||||
return true;
|
||||
}
|
||||
if (value.is_string()) {
|
||||
const auto text = value.get<std::string>();
|
||||
if (text.empty()) return false;
|
||||
std::size_t parsedSize = 0;
|
||||
const auto parsed = std::stoll(text, &parsedSize, 10);
|
||||
if (parsedSize != text.size()) return false;
|
||||
output = static_cast<std::int64_t>(parsed);
|
||||
return true;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool readRequiredStringField(const json& object,
|
||||
const std::string& key,
|
||||
std::string& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!object.contains(key) || object.at(key).is_null()) {
|
||||
return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required string field");
|
||||
}
|
||||
if (!object.at(key).is_string()) {
|
||||
return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a string");
|
||||
}
|
||||
output = object.at(key).get<std::string>();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool readOptionalStringField(const json& object,
|
||||
const std::string& key,
|
||||
std::optional<std::string>& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!object.contains(key) || object.at(key).is_null()) return true;
|
||||
if (!object.at(key).is_string()) {
|
||||
return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a string");
|
||||
}
|
||||
output = object.at(key).get<std::string>();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool readOptionalStringField(const json& object,
|
||||
const std::string& key,
|
||||
std::string& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!object.contains(key) || object.at(key).is_null()) return true;
|
||||
if (!object.at(key).is_string()) {
|
||||
return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a string");
|
||||
}
|
||||
output = object.at(key).get<std::string>();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool readRequiredSignedField(const json& object,
|
||||
const std::string& key,
|
||||
std::int64_t& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!object.contains(key) || object.at(key).is_null()) {
|
||||
return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required integer field");
|
||||
}
|
||||
if (!jsonToSigned(object.at(key), output)) {
|
||||
return fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "expected a signed integer-compatible value");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool readRequiredUnsignedField(const json& object,
|
||||
const std::string& key,
|
||||
std::uint64_t& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!object.contains(key) || object.at(key).is_null()) {
|
||||
return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required unsigned integer field");
|
||||
}
|
||||
if (!jsonToUnsigned(object.at(key), output)) {
|
||||
return fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "expected an unsigned integer-compatible value");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool readOptionalSignedField(const json& object,
|
||||
const std::string& key,
|
||||
std::optional<std::int64_t>& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!object.contains(key) || object.at(key).is_null()) return true;
|
||||
std::int64_t parsed = 0;
|
||||
if (!jsonToSigned(object.at(key), parsed)) {
|
||||
return fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "expected a signed integer-compatible value");
|
||||
}
|
||||
output = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool readOptionalBoolField(const json& object,
|
||||
const std::string& key,
|
||||
bool& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!object.contains(key) || object.at(key).is_null()) return true;
|
||||
if (!object.at(key).is_boolean()) {
|
||||
return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a boolean");
|
||||
}
|
||||
output = object.at(key).get<bool>();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool readStringArrayField(const json& object,
|
||||
const std::string& key,
|
||||
std::vector<std::string>& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!object.contains(key) || object.at(key).is_null()) {
|
||||
return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required array field");
|
||||
}
|
||||
const auto& values = object.at(key);
|
||||
if (!requireArray(values, fieldPath(key), result)) return false;
|
||||
|
||||
output.clear();
|
||||
for (std::size_t index = 0; index < values.size(); ++index) {
|
||||
if (!values[index].is_string()) {
|
||||
return fail(result, LiteResultParserError::InvalidFieldType, indexPath(fieldPath(key), index), "expected a string array item");
|
||||
}
|
||||
output.push_back(values[index].get<std::string>());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool parseSpendableOutput(const json& value,
|
||||
LiteSpendableOutputKind kind,
|
||||
const std::string& path,
|
||||
LiteSpendableOutput& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!requireObject(value, path, result)) return false;
|
||||
|
||||
output.kind = kind;
|
||||
output.pending = kind == LiteSpendableOutputKind::PendingNote || kind == LiteSpendableOutputKind::PendingUtxo;
|
||||
if (!readRequiredStringField(value, "address", output.address, result)) return false;
|
||||
if (!readRequiredStringField(value, "created_in_txid", output.createdInTxid, result)) return false;
|
||||
if (!readOptionalSignedField(value, "created_in_block", output.createdInBlock, result)) return false;
|
||||
if (!readRequiredUnsignedField(value, "value", output.value, result)) return false;
|
||||
if (!readOptionalBoolField(value, "spent", output.spent, result)) return false;
|
||||
if (!readOptionalBoolField(value, "unconfirmed_spent", output.unconfirmedSpent, result)) return false;
|
||||
output.spendable = !output.pending && !output.spent && !output.unconfirmedSpent;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool parseSpendableOutputArray(const json& object,
|
||||
const std::string& key,
|
||||
LiteSpendableOutputKind kind,
|
||||
std::vector<LiteSpendableOutput>& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!object.contains(key) || object.at(key).is_null()) {
|
||||
return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required spendable output array");
|
||||
}
|
||||
const auto& values = object.at(key);
|
||||
if (!requireArray(values, fieldPath(key), result)) return false;
|
||||
|
||||
output.clear();
|
||||
for (std::size_t index = 0; index < values.size(); ++index) {
|
||||
LiteSpendableOutput parsed;
|
||||
if (!parseSpendableOutput(values[index], kind, indexPath(fieldPath(key), index), parsed, result)) return false;
|
||||
output.push_back(std::move(parsed));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool parseTransactionOutput(const json& value,
|
||||
const std::string& path,
|
||||
LiteTransactionOutput& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!requireObject(value, path, result)) return false;
|
||||
if (!readRequiredStringField(value, "address", output.address, result)) return false;
|
||||
if (!readRequiredSignedField(value, "value", output.value, result)) return false;
|
||||
if (!readOptionalStringField(value, "memo", output.memo, result)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Result>
|
||||
bool parseTransactionRecord(const json& value,
|
||||
const std::string& path,
|
||||
LiteTransactionRecord& output,
|
||||
Result& result)
|
||||
{
|
||||
if (!requireObject(value, path, result)) return false;
|
||||
if (!readRequiredStringField(value, "txid", output.txid, result)) return false;
|
||||
if (!readRequiredSignedField(value, "datetime", output.datetime, result)) return false;
|
||||
if (!readOptionalSignedField(value, "block_height", output.blockHeight, result)) return false;
|
||||
if (!readOptionalBoolField(value, "unconfirmed", output.unconfirmed, result)) return false;
|
||||
|
||||
if (value.contains("outgoing_metadata") && !value.at("outgoing_metadata").is_null()) {
|
||||
const auto& metadata = value.at("outgoing_metadata");
|
||||
if (!requireArray(metadata, path + ".outgoing_metadata", result)) return false;
|
||||
output.direction = LiteTransactionDirection::Send;
|
||||
for (std::size_t index = 0; index < metadata.size(); ++index) {
|
||||
LiteTransactionOutput parsedOutput;
|
||||
if (!parseTransactionOutput(metadata[index], path + ".outgoing_metadata[" + std::to_string(index) + "]", parsedOutput, result)) return false;
|
||||
output.amount += parsedOutput.value;
|
||||
output.outgoingMetadata.push_back(std::move(parsedOutput));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
output.direction = LiteTransactionDirection::Receive;
|
||||
if (!readRequiredStringField(value, "address", output.address, result)) return false;
|
||||
if (!readRequiredSignedField(value, "amount", output.amount, result)) return false;
|
||||
if (!readOptionalStringField(value, "memo", output.memo, result)) return false;
|
||||
if (!readOptionalSignedField(value, "position", output.position, result)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* liteResultCommandName(LiteResultCommand command)
|
||||
{
|
||||
switch (command) {
|
||||
case LiteResultCommand::Info: return "info";
|
||||
case LiteResultCommand::Height: return "height";
|
||||
case LiteResultCommand::Balance: return "balance";
|
||||
case LiteResultCommand::Addresses: return "addresses";
|
||||
case LiteResultCommand::List: return "list";
|
||||
case LiteResultCommand::Notes: return "notes";
|
||||
case LiteResultCommand::SyncStatus: return "syncstatus";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const char* liteResultParserErrorName(LiteResultParserError error)
|
||||
{
|
||||
switch (error) {
|
||||
case LiteResultParserError::None: return "none";
|
||||
case LiteResultParserError::InvalidJson: return "invalid_json";
|
||||
case LiteResultParserError::ExpectedObject: return "expected_object";
|
||||
case LiteResultParserError::ExpectedArray: return "expected_array";
|
||||
case LiteResultParserError::MissingField: return "missing_field";
|
||||
case LiteResultParserError::InvalidFieldType: return "invalid_field_type";
|
||||
case LiteResultParserError::InvalidFieldValue: return "invalid_field_value";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const char* liteSpendableOutputKindName(LiteSpendableOutputKind kind)
|
||||
{
|
||||
switch (kind) {
|
||||
case LiteSpendableOutputKind::UnspentNote: return "unspent_note";
|
||||
case LiteSpendableOutputKind::Utxo: return "utxo";
|
||||
case LiteSpendableOutputKind::PendingNote: return "pending_note";
|
||||
case LiteSpendableOutputKind::PendingUtxo: return "pending_utxo";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const char* liteTransactionDirectionName(LiteTransactionDirection direction)
|
||||
{
|
||||
switch (direction) {
|
||||
case LiteTransactionDirection::Unknown: return "unknown";
|
||||
case LiteTransactionDirection::Send: return "send";
|
||||
case LiteTransactionDirection::Receive: return "receive";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
LiteInfoParseResult parseLiteInfoResponse(const std::string& jsonText)
|
||||
{
|
||||
auto result = makeResult<LiteInfoParseResult>(LiteResultCommand::Info);
|
||||
json parsed;
|
||||
if (!parseJsonText(jsonText, result, parsed)) return result;
|
||||
return parseLiteInfoResponse(parsed);
|
||||
}
|
||||
|
||||
LiteInfoParseResult parseLiteInfoResponse(const json& value)
|
||||
{
|
||||
auto result = makeResult<LiteInfoParseResult>(LiteResultCommand::Info);
|
||||
if (!requireObject(value, "$", result)) return result;
|
||||
|
||||
if (!readOptionalStringField(value, "chain_name", result.info.chainName, result)) return result;
|
||||
if (!readOptionalStringField(value, "version", result.info.version, result)) return result;
|
||||
if (!readOptionalStringField(value, "vendor", result.info.vendor, result)) return result;
|
||||
if (!readOptionalSignedField(value, "latest_block_height", result.info.latestBlockHeight, result)) return result;
|
||||
if (!readOptionalSignedField(value, "difficulty", result.info.difficulty, result)) return result;
|
||||
if (!readOptionalSignedField(value, "longestchain", result.info.longestChain, result)) return result;
|
||||
if (!readOptionalSignedField(value, "notarized", result.info.notarized, result)) return result;
|
||||
if (!result.info.latestBlockHeight.has_value()) {
|
||||
fail(result, LiteResultParserError::MissingField, fieldPath("latest_block_height"), "info response is missing latest_block_height");
|
||||
return result;
|
||||
}
|
||||
|
||||
succeed(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteHeightParseResult parseLiteHeightResponse(const std::string& jsonText)
|
||||
{
|
||||
auto result = makeResult<LiteHeightParseResult>(LiteResultCommand::Height);
|
||||
json parsed;
|
||||
if (!parseJsonText(jsonText, result, parsed)) return result;
|
||||
return parseLiteHeightResponse(parsed);
|
||||
}
|
||||
|
||||
LiteHeightParseResult parseLiteHeightResponse(const json& value)
|
||||
{
|
||||
auto result = makeResult<LiteHeightParseResult>(LiteResultCommand::Height);
|
||||
std::int64_t parsedHeight = 0;
|
||||
if (jsonToSigned(value, parsedHeight)) {
|
||||
result.height.height = parsedHeight;
|
||||
succeed(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!requireObject(value, "$", result)) return result;
|
||||
for (const std::string key : {"height", "latest_block_height", "block_height"}) {
|
||||
if (!value.contains(key) || value.at(key).is_null()) continue;
|
||||
if (!jsonToSigned(value.at(key), parsedHeight)) {
|
||||
fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "height field is not an integer-compatible value");
|
||||
return result;
|
||||
}
|
||||
result.height.height = parsedHeight;
|
||||
succeed(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
fail(result, LiteResultParserError::MissingField, "$", "height response has no recognized height field");
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteBalanceParseResult parseLiteBalanceResponse(const std::string& jsonText)
|
||||
{
|
||||
auto result = makeResult<LiteBalanceParseResult>(LiteResultCommand::Balance);
|
||||
json parsed;
|
||||
if (!parseJsonText(jsonText, result, parsed)) return result;
|
||||
return parseLiteBalanceResponse(parsed);
|
||||
}
|
||||
|
||||
LiteBalanceParseResult parseLiteBalanceResponse(const json& value)
|
||||
{
|
||||
auto result = makeResult<LiteBalanceParseResult>(LiteResultCommand::Balance);
|
||||
if (!requireObject(value, "$", result)) return result;
|
||||
if (!readRequiredUnsignedField(value, "tbalance", result.balance.transparentBalance, result)) return result;
|
||||
if (!readRequiredUnsignedField(value, "zbalance", result.balance.shieldedBalance, result)) return result;
|
||||
if (!readRequiredUnsignedField(value, "unconfirmed", result.balance.unconfirmedBalance, result)) return result;
|
||||
if (!readRequiredUnsignedField(value, "verified_zbalance", result.balance.verifiedShieldedBalance, result)) return result;
|
||||
if (!readRequiredUnsignedField(value, "spendable_zbalance", result.balance.spendableShieldedBalance, result)) return result;
|
||||
succeed(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteAddressesParseResult parseLiteAddressesResponse(const std::string& jsonText)
|
||||
{
|
||||
auto result = makeResult<LiteAddressesParseResult>(LiteResultCommand::Addresses);
|
||||
json parsed;
|
||||
if (!parseJsonText(jsonText, result, parsed)) return result;
|
||||
return parseLiteAddressesResponse(parsed);
|
||||
}
|
||||
|
||||
LiteAddressesParseResult parseLiteAddressesResponse(const json& value)
|
||||
{
|
||||
auto result = makeResult<LiteAddressesParseResult>(LiteResultCommand::Addresses);
|
||||
if (!requireObject(value, "$", result)) return result;
|
||||
if (!readStringArrayField(value, "z_addresses", result.addresses.zAddresses, result)) return result;
|
||||
if (!readStringArrayField(value, "t_addresses", result.addresses.tAddresses, result)) return result;
|
||||
succeed(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteNotesParseResult parseLiteNotesResponse(const std::string& jsonText)
|
||||
{
|
||||
auto result = makeResult<LiteNotesParseResult>(LiteResultCommand::Notes);
|
||||
json parsed;
|
||||
if (!parseJsonText(jsonText, result, parsed)) return result;
|
||||
return parseLiteNotesResponse(parsed);
|
||||
}
|
||||
|
||||
LiteNotesParseResult parseLiteNotesResponse(const json& value)
|
||||
{
|
||||
auto result = makeResult<LiteNotesParseResult>(LiteResultCommand::Notes);
|
||||
if (!requireObject(value, "$", result)) return result;
|
||||
if (!parseSpendableOutputArray(value, "unspent_notes", LiteSpendableOutputKind::UnspentNote, result.notes.unspentNotes, result)) return result;
|
||||
if (!parseSpendableOutputArray(value, "utxos", LiteSpendableOutputKind::Utxo, result.notes.utxos, result)) return result;
|
||||
if (!parseSpendableOutputArray(value, "pending_notes", LiteSpendableOutputKind::PendingNote, result.notes.pendingNotes, result)) return result;
|
||||
if (!parseSpendableOutputArray(value, "pending_utxos", LiteSpendableOutputKind::PendingUtxo, result.notes.pendingUtxos, result)) return result;
|
||||
succeed(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteTransactionsParseResult parseLiteTransactionsResponse(const std::string& jsonText)
|
||||
{
|
||||
auto result = makeResult<LiteTransactionsParseResult>(LiteResultCommand::List);
|
||||
json parsed;
|
||||
if (!parseJsonText(jsonText, result, parsed)) return result;
|
||||
return parseLiteTransactionsResponse(parsed);
|
||||
}
|
||||
|
||||
LiteTransactionsParseResult parseLiteTransactionsResponse(const json& value)
|
||||
{
|
||||
auto result = makeResult<LiteTransactionsParseResult>(LiteResultCommand::List);
|
||||
if (!requireArray(value, "$", result)) return result;
|
||||
result.transactions.transactions.clear();
|
||||
for (std::size_t index = 0; index < value.size(); ++index) {
|
||||
LiteTransactionRecord record;
|
||||
if (!parseTransactionRecord(value[index], "$[" + std::to_string(index) + "]", record, result)) return result;
|
||||
result.transactions.transactions.push_back(std::move(record));
|
||||
}
|
||||
succeed(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteSyncStatusParseResult parseLiteSyncStatusResponse(const std::string& jsonText)
|
||||
{
|
||||
auto result = makeResult<LiteSyncStatusParseResult>(LiteResultCommand::SyncStatus);
|
||||
json parsed;
|
||||
if (!parseJsonText(jsonText, result, parsed)) return result;
|
||||
return parseLiteSyncStatusResponse(parsed);
|
||||
}
|
||||
|
||||
LiteSyncStatusParseResult parseLiteSyncStatusResponse(const json& value)
|
||||
{
|
||||
auto result = makeResult<LiteSyncStatusParseResult>(LiteResultCommand::SyncStatus);
|
||||
if (!requireObject(value, "$", result)) return result;
|
||||
if (!readRequiredUnsignedField(value, "synced_blocks", result.syncStatus.syncedBlocks, result)) return result;
|
||||
if (!readRequiredUnsignedField(value, "total_blocks", result.syncStatus.totalBlocks, result)) return result;
|
||||
if (result.syncStatus.totalBlocks > 0) {
|
||||
result.syncStatus.progress = std::min(1.0, static_cast<double>(result.syncStatus.syncedBlocks) / static_cast<double>(result.syncStatus.totalBlocks));
|
||||
result.syncStatus.complete = result.syncStatus.syncedBlocks >= result.syncStatus.totalBlocks;
|
||||
}
|
||||
succeed(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace dragonx::wallet
|
||||
Reference in New Issue
Block a user