feat(wallet): add reusable wallet modules

Add program-neutral wallet access, a stable account model, reusable QML controls, transaction confirmation, submitted-transaction presentation, and isolated contract tests.
This commit is contained in:
Ricardo Guilherme Schmidt
2026-07-24 09:21:25 +02:00
committed by r4bbit
parent e03164baf3
commit dfd19a9796
54 changed files with 3899 additions and 1959 deletions
@@ -0,0 +1,382 @@
#include "LogosWalletProvider.h"
#include <QDir>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QVariantList>
#include <QVariantMap>
#include "logos_sdk.h"
namespace {
constexpr int WALLET_FFI_SUCCESS = 0;
bool isHex(const QString& value, qsizetype size, bool lowercaseOnly = true)
{
if (value.size() != size)
return false;
for (const QChar character : value) {
const bool digit = character >= QLatin1Char('0')
&& character <= QLatin1Char('9');
const bool lowercase = character >= QLatin1Char('a')
&& character <= QLatin1Char('f');
const bool uppercase = character >= QLatin1Char('A')
&& character <= QLatin1Char('F');
if (!digit && !lowercase && (!uppercase || lowercaseOnly))
return false;
}
return true;
}
QString littleEndianU128ToDecimal(const QString& value)
{
if (!isHex(value, 32))
return {};
QString decimal = QStringLiteral("0");
for (int byteIndex = 15; byteIndex >= 0; --byteIndex) {
bool parsed = false;
int carry = value.mid(byteIndex * 2, 2).toInt(&parsed, 16);
if (!parsed)
return {};
for (qsizetype digit = decimal.size(); digit-- > 0;) {
const int next = (decimal.at(digit).unicode() - QLatin1Char('0').unicode())
* 256 + carry;
decimal[digit] = QLatin1Char(static_cast<char>('0' + next % 10));
carry = next / 10;
}
while (carry > 0) {
decimal.prepend(QLatin1Char(static_cast<char>('0' + carry % 10)));
carry /= 10;
}
}
return decimal;
}
WalletSession failedSession(WalletFailure failure)
{
WalletSession session;
session.failure = failure;
session.snapshot.failure = failure;
return session;
}
WalletCreation failedCreation(WalletFailure failure)
{
WalletCreation creation;
creation.failure = failure;
creation.snapshot.failure = failure;
return creation;
}
}
struct LogosWalletProvider::Impl {
explicit Impl(LogosAPI* api)
: ownedLogos(std::make_unique<LogosModules>(api)), logos(ownedLogos.get())
{
}
explicit Impl(LogosModules* value)
: logos(value)
{
}
std::unique_ptr<LogosModules> ownedLogos;
LogosModules* logos = nullptr;
};
LogosWalletProvider::LogosWalletProvider(LogosAPI* api)
: m_impl(std::make_unique<Impl>(api))
{
}
LogosWalletProvider::LogosWalletProvider(LogosModules* logos)
: m_impl(std::make_unique<Impl>(logos))
{
}
LogosWalletProvider::~LogosWalletProvider()
{
if (m_connected)
save();
}
WalletSession LogosWalletProvider::connect(const WalletPaths& paths)
{
clearSnapshot();
if (!m_impl->logos)
return failedSession(WalletFailure::WalletUnavailable);
WalletSession session;
if (sharedWalletIsOpen()) {
session.adopted = true;
} else {
if (!QFileInfo::exists(paths.storage))
return failedSession(WalletFailure::WalletMissing);
if (m_impl->logos->logos_execution_zone.open(paths.config, paths.storage)
!= WALLET_FFI_SUCCESS) {
return failedSession(WalletFailure::OpenFailed);
}
}
m_connected = true;
session.snapshot = snapshot(true);
session.failure = session.snapshot.failure;
return session;
}
WalletCreation LogosWalletProvider::createWallet(const WalletPaths& paths,
const QString& password)
{
clearSnapshot();
if (!m_impl->logos)
return failedCreation(WalletFailure::WalletUnavailable);
const QFileInfo configInfo(paths.config);
const QFileInfo storageInfo(paths.storage);
if (!QDir().mkpath(configInfo.absolutePath())
|| !QDir().mkpath(storageInfo.absolutePath())) {
return failedCreation(WalletFailure::CreateFailed);
}
WalletCreation creation;
creation.mnemonic = m_impl->logos->logos_execution_zone.create_new(
paths.config, paths.storage, password);
if (creation.mnemonic.isEmpty())
return failedCreation(WalletFailure::CreateFailed);
m_connected = true;
if (!save()) {
creation.failure = WalletFailure::SaveFailed;
creation.snapshot.failure = creation.failure;
return creation;
}
creation.snapshot = snapshot(true);
creation.failure = creation.snapshot.failure;
return creation;
}
WalletSnapshot LogosWalletProvider::snapshot(bool forceRefresh)
{
if (m_snapshotReady && !forceRefresh)
return m_snapshot;
if (!m_connected) {
WalletSnapshot result;
result.failure = WalletFailure::WalletUnavailable;
return result;
}
WalletSnapshot result = loadSnapshot();
if (result.ok()) {
m_snapshot = result;
m_snapshotReady = true;
}
return result;
}
void LogosWalletProvider::clearSnapshot()
{
m_snapshot = {};
m_snapshotReady = false;
}
WalletAccountCreation LogosWalletProvider::createAccount(bool isPublic)
{
WalletAccountCreation creation;
if (!m_connected || !m_impl->logos) {
creation.failure = WalletFailure::WalletUnavailable;
return creation;
}
creation.accountId = isPublic
? m_impl->logos->logos_execution_zone.create_account_public()
: m_impl->logos->logos_execution_zone.create_account_private();
if (!isHex(creation.accountId, 64)) {
creation.failure = WalletFailure::CreateFailed;
return creation;
}
if (!save()) {
creation.failure = WalletFailure::SaveFailed;
return creation;
}
if (isPublic)
creation.publicAccount = readPublicAccount(creation.accountId);
clearSnapshot();
creation.snapshot = snapshot(true);
return creation;
}
WalletAccountRead LogosWalletProvider::readPublicAccount(const QString& accountId) const
{
WalletAccountRead read;
read.accountId = accountId;
if (!m_impl->logos || !isHex(accountId, 64))
return read;
QJsonParseError parseError;
const QJsonDocument document = QJsonDocument::fromJson(
m_impl->logos->logos_execution_zone.get_account_public(accountId).toUtf8(),
&parseError);
if (parseError.error != QJsonParseError::NoError || !document.isObject())
return read;
const QJsonObject account = document.object();
const QString owner = account.value(QStringLiteral("program_owner")).toString();
const QString balance = account.value(QStringLiteral("balance")).toString();
const QString nonce = account.value(QStringLiteral("nonce")).toString();
const QString data = account.value(QStringLiteral("data")).toString();
if (!isHex(owner, 64)
|| !isHex(balance, 32)
|| !isHex(nonce, 32)
|| data.size() % 2 != 0
|| !isHex(data, data.size())) {
return read;
}
read.status = QStringLiteral("ok");
read.programOwner = owner;
read.balanceHex = balance;
read.nonceHex = nonce;
read.dataHex = data;
return read;
}
WalletSubmission LogosWalletProvider::submitPublicTransaction(
const WalletTransaction& transaction)
{
WalletSubmission submission;
if (!m_connected || !m_impl->logos) {
submission.failure = WalletFailure::WalletUnavailable;
return submission;
}
if (!isHex(transaction.programId, 64)
|| transaction.accountIds.size() != transaction.signingRequirements.size()) {
submission.failure = WalletFailure::InvalidRequest;
return submission;
}
for (const QString& accountId : transaction.accountIds) {
if (!isHex(accountId, 64)) {
submission.failure = WalletFailure::InvalidRequest;
return submission;
}
}
QVariantList signingRequirements;
signingRequirements.reserve(transaction.signingRequirements.size());
for (bool required : transaction.signingRequirements)
signingRequirements.append(required);
QVariantList instruction;
instruction.reserve(transaction.instruction.size());
for (quint32 word : transaction.instruction)
instruction.append(word);
const QString response =
m_impl->logos->logos_execution_zone.send_generic_public_transaction(
transaction.accountIds,
signingRequirements,
QVariant::fromValue(instruction),
transaction.programId);
QJsonParseError parseError;
const QJsonDocument document = QJsonDocument::fromJson(response.toUtf8(), &parseError);
if (parseError.error != QJsonParseError::NoError || !document.isObject()) {
submission.failure = WalletFailure::SubmissionFailed;
return submission;
}
const QJsonObject result = document.object();
const QJsonValue success = result.value(QStringLiteral("success"));
const QJsonValue error = result.value(QStringLiteral("error"));
const QString hash = result.value(QStringLiteral("tx_hash")).toString();
const bool emptyError = error.isUndefined()
|| error.isNull()
|| (error.isString() && error.toString().isEmpty());
if (!success.isBool()
|| !success.toBool()
|| !emptyError
|| !isHex(hash, 64, false)) {
submission.failure = WalletFailure::SubmissionFailed;
return submission;
}
submission.nativeHash = hash.toLower();
return submission;
}
void LogosWalletProvider::disconnect()
{
if (m_connected)
save();
clearSnapshot();
m_connected = false;
}
bool LogosWalletProvider::sharedWalletIsOpen() const
{
if (!m_impl->logos)
return false;
if (!m_impl->logos->logos_execution_zone.get_sequencer_addr().isEmpty())
return true;
return !m_impl->logos->logos_execution_zone.list_accounts().isEmpty();
}
WalletSnapshot LogosWalletProvider::loadSnapshot()
{
WalletSnapshot result;
result.currentBlockHeight = static_cast<quint64>(
qMax(0, m_impl->logos->logos_execution_zone.get_current_block_height()));
if (result.currentBlockHeight > 0
&& m_impl->logos->logos_execution_zone.sync_to_block(result.currentBlockHeight)
!= WALLET_FFI_SUCCESS) {
result.failure = WalletFailure::ReadFailed;
return result;
}
result.lastSyncedBlock = static_cast<quint64>(
qMax(0, m_impl->logos->logos_execution_zone.get_last_synced_block()));
result.sequencerAddress = m_impl->logos->logos_execution_zone.get_sequencer_addr();
const QVariantList entries = m_impl->logos->logos_execution_zone.list_accounts();
result.accounts.reserve(entries.size());
result.publicAccountReads.reserve(entries.size());
for (const QVariant& value : entries) {
const QVariantMap entry = value.toMap();
const QString address = entry.value(QStringLiteral("account_id")).toString();
if (entry.isEmpty() || !isHex(address, 64)) {
result.failure = WalletFailure::ReadFailed;
return result;
}
WalletAccount account;
account.address = address;
account.isPublic = entry.value(QStringLiteral("is_public"), true).toBool();
if (account.isPublic) {
const WalletAccountRead read = readPublicAccount(address);
result.publicAccountReads.append(read);
account.balance = read.ok()
? littleEndianU128ToDecimal(read.balanceHex)
: m_impl->logos->logos_execution_zone.get_balance(address, true);
} else {
account.balance = m_impl->logos->logos_execution_zone.get_balance(address, false);
}
result.accounts.append(account);
}
if (!save())
result.failure = WalletFailure::SaveFailed;
return result;
}
bool LogosWalletProvider::save() const
{
return m_impl->logos
&& m_impl->logos->logos_execution_zone.save() == WALLET_FFI_SUCCESS;
}
@@ -0,0 +1,37 @@
#pragma once
#include <memory>
#include "WalletProvider.h"
class LogosAPI;
struct LogosModules;
class LogosWalletProvider final : public WalletProvider {
public:
explicit LogosWalletProvider(LogosAPI* api);
explicit LogosWalletProvider(LogosModules* logos);
~LogosWalletProvider() override;
WalletSession connect(const WalletPaths& paths) override;
WalletCreation createWallet(const WalletPaths& paths,
const QString& password) override;
WalletSnapshot snapshot(bool forceRefresh = false) override;
void clearSnapshot() override;
WalletAccountCreation createAccount(bool isPublic) override;
WalletAccountRead readPublicAccount(const QString& accountId) const override;
WalletSubmission submitPublicTransaction(
const WalletTransaction& transaction) override;
void disconnect() override;
private:
bool sharedWalletIsOpen() const;
WalletSnapshot loadSnapshot();
bool save() const;
struct Impl;
std::unique_ptr<Impl> m_impl;
WalletSnapshot m_snapshot;
bool m_snapshotReady = false;
bool m_connected = false;
};
@@ -0,0 +1,61 @@
#include "WalletAccountModel.h"
WalletAccountModel::WalletAccountModel(QObject* parent)
: QAbstractListModel(parent)
{
}
int WalletAccountModel::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : m_accounts.size();
}
QVariant WalletAccountModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_accounts.size())
return {};
const Entry& account = m_accounts.at(index.row());
switch (role) {
case NameRole:
return account.name;
case AddressRole:
return account.address;
case BalanceRole:
return account.balance;
case IsPublicRole:
return account.isPublic;
default:
return {};
}
}
QHash<int, QByteArray> WalletAccountModel::roleNames() const
{
return {
{ NameRole, "name" },
{ AddressRole, "address" },
{ BalanceRole, "balance" },
{ IsPublicRole, "isPublic" },
};
}
void WalletAccountModel::replaceAccounts(const QVector<WalletAccount>& accounts)
{
beginResetModel();
const qsizetype oldCount = m_accounts.size();
m_accounts.clear();
m_accounts.reserve(accounts.size());
for (qsizetype index = 0; index < accounts.size(); ++index) {
const WalletAccount& account = accounts.at(index);
m_accounts.append({
QStringLiteral("Account %1").arg(index + 1),
account.address,
account.balance,
account.isPublic,
});
}
endResetModel();
if (oldCount != m_accounts.size())
emit countChanged();
}
@@ -0,0 +1,42 @@
#pragma once
#include <QAbstractListModel>
#include <QVector>
#include "WalletProvider.h"
class WalletAccountModel final : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
enum Role {
NameRole = Qt::UserRole + 1,
AddressRole,
BalanceRole,
IsPublicRole,
};
Q_ENUM(Role)
explicit WalletAccountModel(QObject* parent = nullptr);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
void replaceAccounts(const QVector<WalletAccount>& accounts);
int count() const { return m_accounts.size(); }
signals:
void countChanged();
private:
struct Entry {
QString name;
QString address;
QString balance;
bool isPublic = true;
};
QVector<Entry> m_accounts;
};
+238
View File
@@ -0,0 +1,238 @@
#include "WalletController.h"
#include <utility>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSettings>
#include <QTimer>
#include <QUrl>
#include "WalletAccountModel.h"
namespace {
const char SETTINGS_ORG[] = "Logos";
const char DISCONNECTED_KEY[] = "disconnected";
const char WALLET_HOME_ENV[] = "LEE_WALLET_HOME_DIR";
QString toLocalPath(const QString& path)
{
if (path.startsWith(QStringLiteral("file://")) || path.contains(QLatin1Char('/')))
return QUrl::fromUserInput(path).toLocalFile();
return path;
}
}
WalletController::WalletController(WalletProvider& wallet,
QString settingsApplication,
QObject* parent)
: QObject(parent),
m_wallet(wallet),
m_settingsApplication(std::move(settingsApplication)),
m_accountModel(new WalletAccountModel(this)),
m_network(new QNetworkAccessManager(this)),
m_reachabilityTimer(new QTimer(this))
{
m_state.walletHome = defaultWalletHome();
m_state.walletExists = QFileInfo::exists(defaultStoragePath());
m_reachabilityTimer->setInterval(10000);
connect(m_reachabilityTimer, &QTimer::timeout,
this, &WalletController::checkReachability);
}
WalletController::~WalletController() = default;
QString WalletController::defaultWalletHome()
{
const QByteArray override = qgetenv(WALLET_HOME_ENV);
if (!override.isEmpty())
return QString::fromLocal8Bit(override);
return QDir::homePath() + QStringLiteral("/.lee/wallet");
}
QString WalletController::defaultConfigPath() const
{
return m_state.walletHome + QStringLiteral("/wallet_config.json");
}
QString WalletController::defaultStoragePath() const
{
return m_state.walletHome + QStringLiteral("/storage.json");
}
void WalletController::start()
{
if (m_started)
return;
m_started = true;
m_reachabilityTimer->start();
QTimer::singleShot(0, this, &WalletController::openOnStartup);
}
void WalletController::openOnStartup()
{
if (QSettings(SETTINGS_ORG, m_settingsApplication)
.value(DISCONNECTED_KEY, false).toBool()) {
return;
}
const QString config = defaultConfigPath();
const QString storage = defaultStoragePath();
const WalletSession session = m_wallet.connect({ config, storage });
if (session.failure == WalletFailure::WalletMissing)
return;
if (!session.ok()) {
qWarning() << "WalletController: wallet connection failed"
<< walletFailureCode(session.failure);
return;
}
m_state.configPath = config;
m_state.storagePath = storage;
m_state.walletExists = QFileInfo::exists(storage) || session.adopted;
m_state.isWalletOpen = true;
applySnapshot(session.snapshot);
}
QString WalletController::createDefaultWallet(const QString& password)
{
return createWallet(defaultConfigPath(), defaultStoragePath(), password);
}
QString WalletController::createWallet(const QString& configPath,
const QString& storagePath,
const QString& password)
{
const QString config = toLocalPath(configPath);
const QString storage = toLocalPath(storagePath);
const WalletCreation creation = m_wallet.createWallet(
{ config, storage }, password);
if (creation.mnemonic.isEmpty()) {
qWarning() << "WalletController: wallet creation failed"
<< walletFailureCode(creation.failure);
return {};
}
m_state.configPath = config;
m_state.storagePath = storage;
m_state.walletExists = true;
QSettings(SETTINGS_ORG, m_settingsApplication).setValue(DISCONNECTED_KEY, false);
if (!creation.ok()) {
qWarning() << "WalletController: wallet creation failed"
<< walletFailureCode(creation.failure);
emit stateChanged();
return creation.mnemonic;
}
m_state.isWalletOpen = true;
applySnapshot(creation.snapshot);
return creation.mnemonic;
}
bool WalletController::open()
{
const QString config = m_state.configPath.isEmpty()
? defaultConfigPath() : m_state.configPath;
const QString storage = m_state.storagePath.isEmpty()
? defaultStoragePath() : m_state.storagePath;
const WalletSession session = m_wallet.connect({ config, storage });
if (!session.ok()) {
qWarning() << "WalletController: wallet open failed"
<< walletFailureCode(session.failure);
return false;
}
m_state.configPath = config;
m_state.storagePath = storage;
m_state.walletExists = true;
m_state.isWalletOpen = true;
QSettings(SETTINGS_ORG, m_settingsApplication).setValue(DISCONNECTED_KEY, false);
applySnapshot(session.snapshot);
return true;
}
void WalletController::disconnect()
{
m_wallet.disconnect();
m_state.isWalletOpen = false;
m_accountModel->replaceAccounts({});
QSettings(SETTINGS_ORG, m_settingsApplication).setValue(DISCONNECTED_KEY, true);
emit stateChanged();
}
QString WalletController::createAccount(bool isPublic)
{
const WalletAccountCreation creation = m_wallet.createAccount(isPublic);
if (!creation.ok()) {
qWarning() << "WalletController: account creation failed"
<< walletFailureCode(creation.failure);
return {};
}
if (creation.snapshot.ok()) {
applySnapshot(creation.snapshot);
} else {
qWarning() << "WalletController: account refresh failed"
<< walletFailureCode(creation.snapshot.failure);
}
return creation.accountId;
}
void WalletController::refresh()
{
const WalletSnapshot next = m_wallet.snapshot(true);
if (next.ok()) {
applySnapshot(next);
} else {
qWarning() << "WalletController: wallet refresh failed"
<< walletFailureCode(next.failure);
}
}
QString WalletController::balance(const QString& accountId, bool isPublic)
{
const WalletSnapshot current = m_wallet.snapshot();
for (const WalletAccount& account : current.accounts) {
if (account.address == accountId && account.isPublic == isPublic)
return account.balance;
}
return {};
}
void WalletController::applySnapshot(const WalletSnapshot& snapshot)
{
m_accountModel->replaceAccounts(snapshot.accounts);
m_state.lastSyncedBlock = static_cast<int>(snapshot.lastSyncedBlock);
m_state.currentBlockHeight = static_cast<int>(snapshot.currentBlockHeight);
m_state.sequencerAddress = snapshot.sequencerAddress;
emit stateChanged();
checkReachability();
}
void WalletController::checkReachability()
{
if (!m_state.isWalletOpen || m_state.sequencerAddress.isEmpty())
return;
QNetworkRequest request{QUrl(m_state.sequencerAddress)};
request.setTransferTimeout(4000);
QNetworkReply* reply = m_network->get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
if (!m_state.isWalletOpen) {
reply->deleteLater();
return;
}
const bool receivedHttp =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).isValid();
const bool reachable = receivedHttp || reply->error() == QNetworkReply::NoError;
if (m_state.sequencerReachable != reachable) {
m_state.sequencerReachable = reachable;
emit stateChanged();
}
reply->deleteLater();
});
}
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include <QObject>
#include <QString>
#include "WalletProvider.h"
class QNetworkAccessManager;
class QTimer;
class WalletAccountModel;
struct WalletUiState {
bool isWalletOpen = false;
bool walletExists = false;
QString configPath;
QString storagePath;
QString walletHome;
int lastSyncedBlock = 0;
int currentBlockHeight = 0;
QString sequencerAddress;
bool sequencerReachable = true;
};
class WalletController final : public QObject {
Q_OBJECT
public:
// The provider must outlive the controller.
explicit WalletController(WalletProvider& wallet,
QString settingsApplication,
QObject* parent = nullptr);
~WalletController() override;
WalletAccountModel* accountModel() const { return m_accountModel; }
const WalletUiState& state() const { return m_state; }
void start();
QString createAccount(bool isPublic);
void refresh();
QString balance(const QString& accountId, bool isPublic);
QString createDefaultWallet(const QString& password);
QString createWallet(const QString& configPath,
const QString& storagePath,
const QString& password);
bool open();
void disconnect();
signals:
void stateChanged();
private:
static QString defaultWalletHome();
QString defaultConfigPath() const;
QString defaultStoragePath() const;
void openOnStartup();
void applySnapshot(const WalletSnapshot& snapshot);
void checkReachability();
WalletProvider& m_wallet;
QString m_settingsApplication;
WalletUiState m_state;
WalletAccountModel* m_accountModel;
QNetworkAccessManager* m_network;
QTimer* m_reachabilityTimer;
bool m_started = false;
};
+26
View File
@@ -0,0 +1,26 @@
#include "WalletProvider.h"
QString walletFailureCode(WalletFailure failure)
{
switch (failure) {
case WalletFailure::None:
return {};
case WalletFailure::WalletMissing:
return QStringLiteral("wallet_missing");
case WalletFailure::WalletUnavailable:
return QStringLiteral("wallet_unavailable");
case WalletFailure::OpenFailed:
return QStringLiteral("open_failed");
case WalletFailure::CreateFailed:
return QStringLiteral("create_failed");
case WalletFailure::SaveFailed:
return QStringLiteral("save_failed");
case WalletFailure::ReadFailed:
return QStringLiteral("read_failed");
case WalletFailure::InvalidRequest:
return QStringLiteral("invalid_request");
case WalletFailure::SubmissionFailed:
return QStringLiteral("submission_failed");
}
return QStringLiteral("wallet_unavailable");
}
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include <QString>
#include <QStringList>
#include <QVector>
enum class WalletFailure {
None,
WalletMissing,
WalletUnavailable,
OpenFailed,
CreateFailed,
SaveFailed,
ReadFailed,
InvalidRequest,
SubmissionFailed,
};
QString walletFailureCode(WalletFailure failure);
struct WalletPaths {
QString config;
QString storage;
};
struct WalletAccountRead {
QString accountId;
QString status = QStringLiteral("read_failed");
QString programOwner;
QString balanceHex;
QString nonceHex;
QString dataHex;
bool ok() const { return status == QStringLiteral("ok"); }
};
struct WalletAccount {
QString address;
QString balance;
bool isPublic = true;
};
struct WalletSnapshot {
WalletFailure failure = WalletFailure::None;
QVector<WalletAccount> accounts;
QVector<WalletAccountRead> publicAccountReads;
quint64 lastSyncedBlock = 0;
quint64 currentBlockHeight = 0;
QString sequencerAddress;
bool ok() const { return failure == WalletFailure::None; }
};
struct WalletSession {
WalletFailure failure = WalletFailure::None;
WalletSnapshot snapshot;
bool adopted = false;
bool ok() const { return failure == WalletFailure::None; }
};
struct WalletCreation {
WalletFailure failure = WalletFailure::None;
QString mnemonic;
WalletSnapshot snapshot;
bool ok() const { return failure == WalletFailure::None; }
};
struct WalletAccountCreation {
WalletFailure failure = WalletFailure::None;
QString accountId;
WalletAccountRead publicAccount;
WalletSnapshot snapshot;
bool ok() const { return failure == WalletFailure::None; }
};
struct WalletTransaction {
QString programId;
QStringList accountIds;
QVector<bool> signingRequirements;
QVector<quint32> instruction;
};
struct WalletSubmission {
WalletFailure failure = WalletFailure::None;
QString nativeHash;
bool accepted() const { return failure == WalletFailure::None && !nativeHash.isEmpty(); }
};
class WalletProvider {
public:
virtual ~WalletProvider() = default;
virtual WalletSession connect(const WalletPaths& paths) = 0;
virtual WalletCreation createWallet(const WalletPaths& paths,
const QString& password) = 0;
virtual WalletSnapshot snapshot(bool forceRefresh = false) = 0;
virtual void clearSnapshot() = 0;
virtual WalletAccountCreation createAccount(bool isPublic) = 0;
virtual WalletAccountRead readPublicAccount(const QString& accountId) const = 0;
virtual WalletSubmission submitPublicTransaction(
const WalletTransaction& transaction) = 0;
virtual void disconnect() = 0;
};