From 190c54ca67a5b8a2a6062647026633c7ab6d3df9 Mon Sep 17 00:00:00 2001 From: erhant Date: Mon, 29 Jun 2026 19:07:25 +0300 Subject: [PATCH] fix: account id now accepts base58 --- flake.lock | 8 +-- src/lez_explorer_ui_backend.cpp | 109 +++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 66b412c..663eeba 100644 --- a/flake.lock +++ b/flake.lock @@ -154,11 +154,11 @@ "logos-module-builder": "logos-module-builder_4" }, "locked": { - "lastModified": 1782740683, - "narHash": "sha256-5FMvtkt9MAzW3mJkJb25uN99PhVr6iH1HaaqzGol1f0=", + "lastModified": 1782747538, + "narHash": "sha256-Yqups9PRZuoStlehNUYwOnCT59cr43DE2bYS29nHM1g=", "ref": "main", - "rev": "78d33cccf7540b628d445b826b63d2d038aa9a1c", - "revCount": 54, + "rev": "f1c334555d93559330c9e80b9d0a02b4914d8f0a", + "revCount": 56, "type": "git", "url": "https://github.com/logos-blockchain/lez-indexer-module" }, diff --git a/src/lez_explorer_ui_backend.cpp b/src/lez_explorer_ui_backend.cpp index 4de8e89..1a2ccec 100644 --- a/src/lez_explorer_ui_backend.cpp +++ b/src/lez_explorer_ui_backend.cpp @@ -80,6 +80,105 @@ QString jsonStr(const QJsonValue& value) return {}; } +// Account ids are Base58 (matching the wallet UI and canonical LEZ); block/tx +// hashes are hex. These mirror the indexer module's codec so the explorer can +// recognise an account-shaped query and always display the canonical Base58 form. +const char* const kBase58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + +int base58Index(char c) +{ + for (int i = 0; i < 58; ++i) { + if (kBase58Alphabet[i] == c) { + return i; + } + } + return -1; +} + +// Decode a Base58 string (plain Bitcoin alphabet, no checksum) into raw bytes. +// Returns false on any non-Base58 character. Leading '1's map to leading zeros. +bool base58Decode(const QString& s, QByteArray& out) +{ + const QString t = s.trimmed(); + if (t.isEmpty()) { + return false; + } + int leadingZeros = 0; + for (int i = 0; i < t.size() && t.at(i) == QLatin1Char('1'); ++i) { + ++leadingZeros; + } + QByteArray digits; // base-256, least-significant first + for (const QChar qc : t) { + const int idx = qc.unicode() < 128 ? base58Index(static_cast(qc.unicode())) : -1; + if (idx < 0) { + return false; + } + int carry = idx; + for (int j = 0; j < digits.size(); ++j) { + carry += static_cast(digits[j]) * 58; + digits[j] = static_cast(carry & 0xFF); + carry >>= 8; + } + while (carry > 0) { + digits.append(static_cast(carry & 0xFF)); + carry >>= 8; + } + } + out = QByteArray(leadingZeros, '\0'); + for (int i = digits.size(); i-- > 0;) { + out.append(digits[i]); + } + return true; +} + +// Base58-encode raw bytes (plain Bitcoin alphabet). Inverse of base58Decode. +QString bytes32ToBase58(const QByteArray& data) +{ + int leadingZeros = 0; + while (leadingZeros < data.size() && data[leadingZeros] == '\0') { + ++leadingZeros; + } + QByteArray digits; // base-58, least-significant first + for (int i = 0; i < data.size(); ++i) { + int carry = static_cast(data[i]); + for (int j = 0; j < digits.size(); ++j) { + carry += static_cast(digits[j]) * 256; + digits[j] = static_cast(carry % 58); + carry /= 58; + } + while (carry > 0) { + digits.append(static_cast(carry % 58)); + carry /= 58; + } + } + QString out(leadingZeros, QLatin1Char('1')); + for (int i = digits.size(); i-- > 0;) { + out.append(QLatin1Char(kBase58Alphabet[static_cast(digits[i])])); + } + return out; +} + +// Canonical Base58 form of an account-id query, or "" if not account-shaped. +// Accepts Base58 (must decode to 32 bytes) or 64-char hex (optionally 0x): a hex +// search is normalised to Base58 so the result is displayed in canonical form. +QString canonicalAccountId(const QString& query) +{ + const QString trimmed = query.trimmed(); + QByteArray decoded; + if (base58Decode(trimmed, decoded) && decoded.size() == 32) { + return trimmed; + } + QString hex = trimmed; + if (hex.startsWith(QStringLiteral("0x"), Qt::CaseInsensitive)) { + hex = hex.mid(2); + } + static const QRegularExpression hexRe(QStringLiteral("^[0-9a-fA-F]{64}$")); + if (hexRe.match(hex).hasMatch()) { + return bytes32ToBase58(QByteArray::fromHex(hex.toLatin1())); + } + return {}; +} + QString timestampText(quint64 timestamp) { QDateTime dt; @@ -394,8 +493,14 @@ QVariantMap LezExplorerUiBackend::search(QString query) if (!tx.isEmpty()) { transactions.append(tx); } - const QVariantMap account = getAccount(trimmed); - // getAccount always injects the id; treat "no payload fields" as miss. + } + + // Account ids are Base58 (canonical) or 64-char hex; query and display by + // the canonical Base58 form. getAccount injects the id, so a payload with + // only that one field means "not found". + const QString accountId = canonicalAccountId(trimmed); + if (!accountId.isEmpty()) { + const QVariantMap account = getAccount(accountId); if (account.size() > 1) { accounts.append(account); }