logos-execution-zone-wallet-ui/src/LEZWalletBackend.cpp

283 lines
9.8 KiB
C++
Raw Normal View History

2026-02-20 14:20:39 +01:00
#include "LEZWalletBackend.h"
2026-02-22 02:03:13 +05:30
#include <QAbstractItemModel>
#include <QClipboard>
#include <QCoreApplication>
2026-02-20 14:20:39 +01:00
#include <QDebug>
#include <QGuiApplication>
2026-02-22 02:03:13 +05:30
#include <QJsonArray>
2026-02-20 14:20:39 +01:00
#include <QSettings>
#include <QTimer>
2026-02-20 14:20:39 +01:00
#include <QUrl>
#include "logos_api.h"
#include "logos_sdk.h"
2026-02-20 14:20:39 +01:00
namespace {
const char SETTINGS_ORG[] = "Logos";
const char SETTINGS_APP[] = "ExecutionZoneWalletUI";
const char CONFIG_PATH_KEY[] = "configPath";
const char STORAGE_PATH_KEY[] = "storagePath";
const int WALLET_FFI_SUCCESS = 0;
// Convert a decimal amount string to 32-char hex (16 bytes little-endian)
// for transfer_public/transfer_private/transfer_private_owned.
QString amountToLe16Hex(const QString& amountStr) {
const QString trimmed = amountStr.trimmed();
if (trimmed.isEmpty()) return QString();
bool parseOk = false;
const quint64 value = trimmed.toULongLong(&parseOk);
if (!parseOk) return QString();
uint8_t bytes[16] = {0};
for (int i = 0; i < 8; ++i)
bytes[i] = static_cast<uint8_t>((value >> (i * 8)) & 0xff);
return QByteArray(reinterpret_cast<const char*>(bytes), 16).toHex();
}
// Normalise file:// URLs and OS paths to a plain local path.
QString toLocalPath(const QString& path) {
if (path.startsWith("file://") || path.contains("/"))
return QUrl::fromUserInput(path).toLocalFile();
return path;
}
2026-02-20 14:20:39 +01:00
}
LEZWalletBackend::LEZWalletBackend(LogosAPI* logosAPI, QObject* parent)
: LEZWalletBackendSimpleSource(parent),
2026-02-22 02:03:13 +05:30
m_accountModel(new LEZWalletAccountModel(this)),
m_filteredAccountModel(new LEZAccountFilterModel(this)),
m_privateAccountModel(new LEZAccountFilterModel(this)),
2026-04-20 15:17:40 +02:00
m_logosAPI(logosAPI ? logosAPI : new LogosAPI("lez_wallet_ui", this)),
m_logos(new LogosModules(m_logosAPI))
2026-02-20 14:20:39 +01:00
{
2026-02-22 02:03:13 +05:30
m_filteredAccountModel->setSourceModel(m_accountModel);
m_privateAccountModel->setFilterByPublic(false);
m_privateAccountModel->setSourceModel(m_accountModel);
2026-02-22 02:03:13 +05:30
// Initialise PROP defaults via the generated setters.
setIsWalletOpen(false);
setLastSyncedBlock(0);
setCurrentBlockHeight(0);
// Load persisted config/storage paths.
2026-02-20 14:20:39 +01:00
QSettings s(SETTINGS_ORG, SETTINGS_APP);
setConfigPath(s.value(CONFIG_PATH_KEY).toString());
setStoragePath(s.value(STORAGE_PATH_KEY).toString());
2026-02-20 14:20:39 +01:00
// ui-host runs our constructor inside initLogos(), synchronously, BEFORE
// it enables remoting and emits READY. Any blocking RPC here (open,
// list_accounts, block-height queries, sequencer lookup) would stall
// ui-host startup past ViewModuleHost's 30s ready watchdog and get the
// child SIGTERM'd. Defer the whole open+refresh chain to the first
// event-loop tick so ui-host finishes wiring itself up first.
QTimer::singleShot(0, this, [this]() { openIfPathsConfigured(); });
// Save wallet when app quits; host may not call destructors so this is best-effort.
connect(qApp, &QCoreApplication::aboutToQuit, this,
[this]() { saveWallet(); }, Qt::DirectConnection);
2026-02-20 14:20:39 +01:00
}
LEZWalletBackend::~LEZWalletBackend()
{
saveWallet();
delete m_logos;
2026-02-20 14:20:39 +01:00
}
void LEZWalletBackend::saveWallet()
{
if (isWalletOpen()) {
m_logos->logos_execution_zone.save();
2026-02-20 14:20:39 +01:00
}
}
void LEZWalletBackend::persistConfigPath(const QString& path)
2026-02-20 14:20:39 +01:00
{
const QString localPath = toLocalPath(path);
setConfigPath(localPath);
QSettings(SETTINGS_ORG, SETTINGS_APP).setValue(CONFIG_PATH_KEY, localPath);
2026-02-20 14:20:39 +01:00
}
void LEZWalletBackend::persistStoragePath(const QString& path)
2026-02-20 14:20:39 +01:00
{
const QString localPath = toLocalPath(path);
setStoragePath(localPath);
QSettings(SETTINGS_ORG, SETTINGS_APP).setValue(STORAGE_PATH_KEY, localPath);
2026-02-20 14:20:39 +01:00
}
void LEZWalletBackend::openIfPathsConfigured()
2026-02-20 14:20:39 +01:00
{
if (configPath().isEmpty() || storagePath().isEmpty()) return;
qDebug() << "LEZWalletBackend: opening wallet with config" << configPath()
<< "storage" << storagePath();
int err = m_logos->logos_execution_zone.open(configPath(), storagePath());
if (err == WALLET_FFI_SUCCESS) {
qDebug() << "LEZWalletBackend: wallet opened successfully";
setIsWalletOpen(true);
refreshAccounts();
refreshBlockHeights();
refreshSequencerAddr();
2026-02-20 14:20:39 +01:00
}
}
void LEZWalletBackend::refreshAccounts()
{
QJsonArray arr = m_logos->logos_execution_zone.list_accounts();
2026-02-22 02:03:13 +05:30
m_accountModel->replaceFromJsonArray(arr);
refreshBalances();
2026-02-22 02:03:13 +05:30
}
void LEZWalletBackend::refreshBalances()
{
2026-03-06 00:19:53 +00:00
refreshBlockHeights();
syncToBlock(currentBlockHeight());
if (!m_accountModel) return;
2026-02-22 02:03:13 +05:30
for (int i = 0; i < m_accountModel->count(); ++i) {
const QModelIndex idx = m_accountModel->index(i, 0);
const QString addr = m_accountModel->data(idx, LEZWalletAccountModel::AddressRole).toString();
const bool isPub = m_accountModel->data(idx, LEZWalletAccountModel::IsPublicRole).toBool();
m_accountModel->setBalanceByAddress(addr, getBalance(addr, isPub));
2026-02-20 14:20:39 +01:00
}
2026-03-07 16:17:57 +00:00
saveWallet();
2026-02-20 14:20:39 +01:00
}
void LEZWalletBackend::fetchAndUpdateBlockHeights()
2026-02-20 14:20:39 +01:00
{
const int lastVal = m_logos->logos_execution_zone.get_last_synced_block();
const int currentVal = m_logos->logos_execution_zone.get_current_block_height();
if (lastSyncedBlock() != lastVal)
setLastSyncedBlock(lastVal);
if (currentBlockHeight() != currentVal)
setCurrentBlockHeight(currentVal);
2026-02-20 14:20:39 +01:00
}
void LEZWalletBackend::refreshBlockHeights()
{
fetchAndUpdateBlockHeights();
if (currentBlockHeight() > 0
&& lastSyncedBlock() < currentBlockHeight()
&& syncToBlock(currentBlockHeight()))
{
fetchAndUpdateBlockHeights();
}
}
2026-02-20 14:20:39 +01:00
void LEZWalletBackend::refreshSequencerAddr()
{
const QString addr = m_logos->logos_execution_zone.get_sequencer_addr();
if (sequencerAddr() != addr)
setSequencerAddr(addr);
2026-02-20 14:20:39 +01:00
}
QString LEZWalletBackend::createAccountPublic()
{
QString result = m_logos->logos_execution_zone.create_account_public();
if (!result.isEmpty())
2026-02-20 14:20:39 +01:00
refreshAccounts();
return result;
2026-02-20 14:20:39 +01:00
}
QString LEZWalletBackend::createAccountPrivate()
{
QString result = m_logos->logos_execution_zone.create_account_private();
if (!result.isEmpty())
2026-02-20 14:20:39 +01:00
refreshAccounts();
return result;
2026-02-20 14:20:39 +01:00
}
QString LEZWalletBackend::getBalance(QString accountIdHex, bool isPublic)
2026-02-20 14:20:39 +01:00
{
return m_logos->logos_execution_zone.get_balance(accountIdHex, isPublic);
2026-02-20 14:20:39 +01:00
}
QString LEZWalletBackend::getPublicAccountKey(QString accountIdHex)
2026-02-20 14:20:39 +01:00
{
return m_logos->logos_execution_zone.get_public_account_key(accountIdHex);
2026-02-20 14:20:39 +01:00
}
QString LEZWalletBackend::getPrivateAccountKeys(QString accountIdHex)
2026-02-20 14:20:39 +01:00
{
return m_logos->logos_execution_zone.get_private_account_keys(accountIdHex);
2026-02-20 14:20:39 +01:00
}
bool LEZWalletBackend::syncToBlock(quint64 blockId)
{
int err = m_logos->logos_execution_zone.sync_to_block(blockId);
return err == WALLET_FFI_SUCCESS;
2026-02-20 14:20:39 +01:00
}
QString LEZWalletBackend::transferPublic(QString fromHex, QString toHex, QString amountStr)
2026-02-20 14:20:39 +01:00
{
const QString amountHex = amountToLe16Hex(amountStr);
if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount.");
return m_logos->logos_execution_zone.transfer_public(fromHex, toHex, amountHex);
2026-02-20 14:20:39 +01:00
}
QString LEZWalletBackend::transferPrivate(QString fromHex, QString toHex, QString amountStr)
2026-02-20 14:20:39 +01:00
{
const QString amountHex = amountToLe16Hex(amountStr);
if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount.");
QString keysPayload = toHex.trimmed();
// If "To" is not JSON (e.g. user pasted account id hex), resolve to keys.
if (!keysPayload.startsWith(QLatin1Char('{'))) {
qDebug() << "LEZWalletBackend::transferPrivate: resolving keys via get_private_account_keys";
const QString resolved = getPrivateAccountKeys(keysPayload);
if (!resolved.isEmpty())
keysPayload = resolved;
}
return m_logos->logos_execution_zone.transfer_private(fromHex, keysPayload, amountHex);
2026-02-20 14:20:39 +01:00
}
QString LEZWalletBackend::transferPrivateOwned(QString fromHex, QString toHex, QString amountStr)
2026-03-07 16:17:57 +00:00
{
const QString amountHex = amountToLe16Hex(amountStr);
2026-03-07 16:17:57 +00:00
if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount.");
return m_logos->logos_execution_zone.transfer_private_owned(fromHex, toHex.trimmed(), amountHex);
2026-03-07 16:17:57 +00:00
}
2026-06-04 23:34:11 -03:00
QString LEZWalletBackend::transferShielded(QString fromHex, QString toKeysJson, QString amountStr)
{
const QString amountHex = amountToLe16Hex(amountStr);
if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount.");
QString keysPayload = toKeysJson.trimmed();
if (!keysPayload.startsWith(QLatin1Char('{'))) {
qDebug() << "LEZWalletBackend::transferShielded: resolving keys via get_private_account_keys";
const QString resolved = getPrivateAccountKeys(keysPayload);
if (!resolved.isEmpty())
keysPayload = resolved;
}
return m_logos->logos_execution_zone.transfer_shielded(fromHex, keysPayload, amountHex);
}
QString LEZWalletBackend::transferShieldedOwned(QString fromHex, QString toHex, QString amountStr)
{
const QString amountHex = amountToLe16Hex(amountStr);
if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount.");
return m_logos->logos_execution_zone.transfer_shielded_owned(fromHex, toHex.trimmed(), amountHex);
}
bool LEZWalletBackend::createNew(QString configPath, QString storagePath, QString password)
2026-02-20 14:20:39 +01:00
{
const QString localPath = toLocalPath(configPath);
int err = m_logos->logos_execution_zone.create_new(localPath, storagePath, password);
2026-02-20 14:20:39 +01:00
if (err != WALLET_FFI_SUCCESS) return false;
persistConfigPath(localPath);
persistStoragePath(storagePath);
setIsWalletOpen(true);
2026-02-20 14:20:39 +01:00
refreshAccounts();
refreshBlockHeights();
refreshSequencerAddr();
return true;
}
2026-02-22 02:03:13 +05:30
void LEZWalletBackend::copyToClipboard(QString text)
{
if (QGuiApplication::clipboard())
QGuiApplication::clipboard()->setText(text);
}