mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-26 22:51:14 +00:00
fix(wallet): decode advanced program accounts
This commit is contained in:
@@ -44,6 +44,8 @@ logos_module(
|
||||
src/NewPositionRuntime.cpp
|
||||
src/SequencerClient.h
|
||||
src/SequencerClient.cpp
|
||||
src/WalletIdlDecoder.h
|
||||
src/WalletIdlDecoder.cpp
|
||||
FIND_PACKAGES
|
||||
Qt6Gui
|
||||
Qt6Network
|
||||
@@ -55,12 +57,23 @@ logos_module(
|
||||
logos_wallet_access
|
||||
EXTERNAL_LIBS
|
||||
amm_client
|
||||
wallet_idl_decoder
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
config/idl/token-idl.json
|
||||
PROPERTIES QT_RESOURCE_ALIAS "idl/token-idl.json"
|
||||
)
|
||||
set_source_files_properties(
|
||||
config/idl/amm-idl.json
|
||||
PROPERTIES QT_RESOURCE_ALIAS "idl/amm-idl.json"
|
||||
)
|
||||
qt_add_resources(amm_ui_module_plugin amm_ui_config
|
||||
PREFIX "/amm"
|
||||
FILES
|
||||
config/networks.json
|
||||
config/idl/token-idl.json
|
||||
config/idl/amm-idl.json
|
||||
)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
|
||||
+6
-2
@@ -72,7 +72,7 @@
|
||||
logos_execution_zone = logosExecutionZoneModule;
|
||||
};
|
||||
|
||||
ammClientInput = (import ../../flake.nix).outputs {
|
||||
clientLibrariesInput = (import ../../flake.nix).outputs {
|
||||
inherit nixpkgs crane;
|
||||
};
|
||||
moduleBuild = logos-module-builder.lib.mkLogosQmlModule {
|
||||
@@ -83,7 +83,11 @@
|
||||
cmakeFlagsArray+=("-DLOGOS_WALLET_SOURCE_DIR=${../shared/wallet}")
|
||||
'';
|
||||
externalLibInputs = {
|
||||
amm_client = ammClientInput;
|
||||
amm_client = clientLibrariesInput;
|
||||
wallet_idl_decoder = {
|
||||
input = clientLibrariesInput;
|
||||
packages.default = "wallet_idl_decoder";
|
||||
};
|
||||
};
|
||||
postInstall = ''
|
||||
# The builder installs the view under lib/qml after this hook. Its
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"runtime": ["qt6.qtdeclarative", "zstd", "krb5", "abseil-cpp", "libbase58"]
|
||||
},
|
||||
"external_libraries": [
|
||||
{ "name": "amm_client" }
|
||||
{ "name": "amm_client" },
|
||||
{ "name": "wallet_idl_decoder" }
|
||||
],
|
||||
"cmake": {
|
||||
"find_packages": [],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "AmmUiBackend.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
@@ -17,7 +18,9 @@
|
||||
#include "LogosWalletProvider.h"
|
||||
#include "NewPositionRuntime.h"
|
||||
#include "SequencerClient.h"
|
||||
#include "WalletAccountId.h"
|
||||
#include "WalletController.h"
|
||||
#include "WalletIdlDecoder.h"
|
||||
#include "logos_api.h"
|
||||
|
||||
namespace {
|
||||
@@ -25,6 +28,12 @@ namespace {
|
||||
const int BLOCK_HASH_OFFSET = 40;
|
||||
const int BLOCK_HASH_SIZE = 32;
|
||||
|
||||
QByteArray resource(const QString& path)
|
||||
{
|
||||
QFile file(path);
|
||||
return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
|
||||
}
|
||||
|
||||
QByteArray jsonRpcBody(const QString& method, const QJsonArray& params)
|
||||
{
|
||||
return QJsonDocument(QJsonObject {
|
||||
@@ -58,6 +67,19 @@ namespace {
|
||||
return ActiveNetwork::isValidIdentity(channel) ? channel : QString();
|
||||
}
|
||||
|
||||
QString decodedDataText(const QJsonValue& value)
|
||||
{
|
||||
if (value.isObject()) {
|
||||
return QString::fromUtf8(
|
||||
QJsonDocument(value.toObject()).toJson(QJsonDocument::Indented)).trimmed();
|
||||
}
|
||||
if (value.isArray()) {
|
||||
return QString::fromUtf8(
|
||||
QJsonDocument(value.toArray()).toJson(QJsonDocument::Indented)).trimmed();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AmmUiBackend::AmmUiBackend(LogosAPI* logosAPI, QObject* parent)
|
||||
@@ -72,7 +94,9 @@ AmmUiBackend::AmmUiBackend(LogosAPI* logosAPI, QObject* parent)
|
||||
m_wallet.get(), m_ammClient.get(), m_sequencer.get())),
|
||||
m_net(new QNetworkAccessManager(this)),
|
||||
m_transactionTimer(new QTimer(this)),
|
||||
m_identityRetryTimer(new QTimer(this))
|
||||
m_identityRetryTimer(new QTimer(this)),
|
||||
m_tokenIdl(resource(QStringLiteral(":/amm/idl/token-idl.json"))),
|
||||
m_ammIdl(resource(QStringLiteral(":/amm/idl/amm-idl.json")))
|
||||
{
|
||||
setNewPositionQuoteResult({});
|
||||
setNewPositionSubmitResult({});
|
||||
@@ -86,6 +110,8 @@ AmmUiBackend::AmmUiBackend(LogosAPI* logosAPI, QObject* parent)
|
||||
connect(m_identityRetryTimer, &QTimer::timeout,
|
||||
this, &AmmUiBackend::probeNetworkIdentity);
|
||||
m_network.load();
|
||||
m_idlRegistry.registerProgram(
|
||||
m_network.snapshot().ammProgramId, QStringLiteral("AMM"), m_ammIdl);
|
||||
m_walletController->setDefaultSequencerAddress(m_network.snapshot().sequencerAddress);
|
||||
|
||||
connect(m_walletController.get(), &WalletController::stateChanged,
|
||||
@@ -175,10 +201,10 @@ void AmmUiBackend::refreshNewPositionContext(QVariantMap request)
|
||||
const quint64 generation = ++m_contextGeneration;
|
||||
m_newPosition->contextAsync(
|
||||
request, m_network.snapshot(), isWalletOpen(), refreshWalletAccounts,
|
||||
[this, generation](QVariantMap result) {
|
||||
[this, generation](QVariantMap result, QVector<WalletAccountRead> walletReads) {
|
||||
if (generation == m_contextGeneration) {
|
||||
result.insert(QStringLiteral("requestId"), generation);
|
||||
publishWalletAssets(result);
|
||||
publishWalletAssets(result, walletReads);
|
||||
setNewPositionContext(std::move(result));
|
||||
}
|
||||
});
|
||||
@@ -344,7 +370,9 @@ void AmmUiBackend::publishNetworkContext(bool refreshContext)
|
||||
refreshNewPositionContext(m_newPositionHints);
|
||||
}
|
||||
|
||||
void AmmUiBackend::publishWalletAssets(const QVariantMap& context)
|
||||
void AmmUiBackend::publishWalletAssets(
|
||||
const QVariantMap& context,
|
||||
const QVector<WalletAccountRead>& programReads)
|
||||
{
|
||||
const QString contextStatus = context.value(QStringLiteral("status")).toString();
|
||||
if (contextStatus == QStringLiteral("no_wallet")) {
|
||||
@@ -366,6 +394,29 @@ void AmmUiBackend::publishWalletAssets(const QVariantMap& context)
|
||||
QVariantList assets;
|
||||
QVariantList available;
|
||||
QVector<WalletAccountPresentation> presentations;
|
||||
const QVariantMap programIds = context.value(QStringLiteral("programIds")).toMap();
|
||||
const QString tokenProgramId = walletAccountIdFromBase58(
|
||||
programIds.value(QStringLiteral("token")).toString());
|
||||
m_idlRegistry.registerProgram(
|
||||
m_network.snapshot().ammProgramId, QStringLiteral("AMM"), m_ammIdl);
|
||||
m_idlRegistry.registerProgram(tokenProgramId, QStringLiteral("Token"), m_tokenIdl);
|
||||
|
||||
QHash<QString, QString> decodedDataByAccount;
|
||||
for (const WalletDecodedProgram& program : m_idlRegistry.decode(programReads)) {
|
||||
for (const WalletDecodedAccount& account : program.result.accounts) {
|
||||
if (account.status != QStringLiteral("decoded"))
|
||||
continue;
|
||||
WalletAccountPresentation presentation;
|
||||
presentation.address = account.id;
|
||||
presentation.kind = QStringLiteral("program");
|
||||
presentation.semanticName = account.typeName;
|
||||
presentation.programName = program.programName;
|
||||
presentation.accountType = account.typeName;
|
||||
presentation.decodedData = decodedDataText(account.value);
|
||||
decodedDataByAccount.insert(presentation.address, presentation.decodedData);
|
||||
presentations.append(std::move(presentation));
|
||||
}
|
||||
}
|
||||
bool hasUnavailableToken = false;
|
||||
for (const QVariant& value : context.value(QStringLiteral("tokens")).toList()) {
|
||||
const QVariantMap token = value.toMap();
|
||||
@@ -398,6 +449,7 @@ void AmmUiBackend::publishWalletAssets(const QVariantMap& context)
|
||||
QStringLiteral("holdingId")).toString();
|
||||
if (holdingId.isEmpty())
|
||||
continue;
|
||||
const QString decodedHoldingId = walletAccountIdFromBase58(holdingId);
|
||||
presentations.append({
|
||||
holdingId,
|
||||
QStringLiteral("token_holding"),
|
||||
@@ -406,6 +458,8 @@ void AmmUiBackend::publishWalletAssets(const QVariantMap& context)
|
||||
QStringLiteral("TokenHolding"),
|
||||
definitionId,
|
||||
true,
|
||||
decodedDataByAccount.value(
|
||||
decodedHoldingId.isEmpty() ? holdingId : decodedHoldingId),
|
||||
});
|
||||
}
|
||||
hasUnavailableToken = hasUnavailableToken || !ready;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
@@ -14,6 +15,7 @@
|
||||
|
||||
#include "ActiveNetwork.h"
|
||||
#include "WalletAccountModel.h"
|
||||
#include "WalletIdlDecoder.h"
|
||||
|
||||
class LogosAPI;
|
||||
class AmmClient;
|
||||
@@ -65,7 +67,8 @@ private:
|
||||
void syncWalletState();
|
||||
void probeNetworkIdentity();
|
||||
void publishNetworkContext(bool refreshContext = true);
|
||||
void publishWalletAssets(const QVariantMap& context);
|
||||
void publishWalletAssets(const QVariantMap& context,
|
||||
const QVector<WalletAccountRead>& programReads);
|
||||
void watchTransaction(const QVariantMap& result);
|
||||
void pollTransactions();
|
||||
void refreshAffectedAccounts(const QStringList& accountIds, int attempt = 0);
|
||||
@@ -82,6 +85,9 @@ private:
|
||||
QTimer* m_identityRetryTimer;
|
||||
|
||||
ActiveNetwork m_network;
|
||||
QByteArray m_tokenIdl;
|
||||
QByteArray m_ammIdl;
|
||||
WalletIdlRegistry m_idlRegistry;
|
||||
QVariantMap m_newPositionHints;
|
||||
bool m_identityProbeInFlight = false;
|
||||
bool m_walletSnapshotPending = false;
|
||||
|
||||
@@ -344,16 +344,16 @@ void NewPositionRuntime::contextAsync(const QVariantMap& request,
|
||||
const ActiveNetworkSnapshot& network,
|
||||
bool walletOpen,
|
||||
bool refreshPublicData,
|
||||
ResultCallback callback)
|
||||
ContextCallback callback)
|
||||
{
|
||||
const quint64 contextGeneration = ++m_contextGeneration;
|
||||
if (network.status != QStringLiteral("ready")) {
|
||||
callback(contextState(network.status, network).toVariantMap());
|
||||
callback(contextState(network.status, network).toVariantMap(), {});
|
||||
return;
|
||||
}
|
||||
if (!m_sequencer || !m_sequencer->isConfigured()) {
|
||||
callback(contextState(QStringLiteral("error"), network,
|
||||
QStringLiteral("sequencer_config_required")).toVariantMap());
|
||||
QStringLiteral("sequencer_config_required")).toVariantMap(), {});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ void NewPositionRuntime::contextAsync(const QVariantMap& request,
|
||||
QJsonObject { { QStringLiteral("ammProgramId"), network.ammProgramId } });
|
||||
if (!configResult.ok) {
|
||||
callback(contextState(QStringLiteral("error"), network,
|
||||
QStringLiteral("backend_error")).toVariantMap());
|
||||
QStringLiteral("backend_error")).toVariantMap(), {});
|
||||
return;
|
||||
}
|
||||
const QString configId = configResult.value.value(
|
||||
@@ -406,7 +406,7 @@ void NewPositionRuntime::contextAsync(const QVariantMap& request,
|
||||
: QStringLiteral("backend_error");
|
||||
callback(contextState(QStringLiteral("error"), network,
|
||||
code.isEmpty() ? QStringLiteral("backend_error") : code)
|
||||
.toVariantMap());
|
||||
.toVariantMap(), {});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -417,6 +417,7 @@ void NewPositionRuntime::contextAsync(const QVariantMap& request,
|
||||
}
|
||||
guard->m_sequencer->readAccounts(definitionIds, refreshPublicData,
|
||||
[guard, network, walletOpen, config, walletAccounts,
|
||||
walletReads = std::move(walletReads),
|
||||
configured, recent, resolved, contextGeneration,
|
||||
callback = std::move(callback)](
|
||||
QVector<WalletAccountRead> definitions) mutable {
|
||||
@@ -440,7 +441,8 @@ void NewPositionRuntime::contextAsync(const QVariantMap& request,
|
||||
callback((result.ok
|
||||
? result.value
|
||||
: contextState(QStringLiteral("error"), network,
|
||||
QStringLiteral("backend_error"))).toVariantMap());
|
||||
QStringLiteral("backend_error"))).toVariantMap(),
|
||||
std::move(walletReads));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ struct WalletAccountRead;
|
||||
class NewPositionRuntime : public QObject {
|
||||
public:
|
||||
using ResultCallback = std::function<void(QVariantMap)>;
|
||||
using ContextCallback = std::function<void(QVariantMap, QVector<WalletAccountRead>)>;
|
||||
|
||||
NewPositionRuntime(WalletProvider* wallet,
|
||||
AmmClient* client,
|
||||
@@ -32,7 +33,7 @@ public:
|
||||
const ActiveNetworkSnapshot& network,
|
||||
bool walletOpen,
|
||||
bool refreshPublicData,
|
||||
ResultCallback callback);
|
||||
ContextCallback callback);
|
||||
void quoteAsync(const QVariantMap& request,
|
||||
const ActiveNetworkSnapshot& network,
|
||||
bool walletOpen,
|
||||
|
||||
@@ -441,7 +441,9 @@ namespace {
|
||||
};
|
||||
}
|
||||
|
||||
bool waitForContext(NewPositionRuntime& runtime, bool forceRefresh)
|
||||
bool waitForContext(NewPositionRuntime& runtime,
|
||||
bool forceRefresh,
|
||||
QVector<WalletAccountRead>* reads = nullptr)
|
||||
{
|
||||
bool completed = false;
|
||||
QEventLoop loop;
|
||||
@@ -449,7 +451,9 @@ namespace {
|
||||
timeout.setSingleShot(true);
|
||||
QObject::connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
runtime.contextAsync({}, readyNetwork(), true, forceRefresh,
|
||||
[&](QVariantMap) {
|
||||
[&](QVariantMap, QVector<WalletAccountRead> result) {
|
||||
if (reads)
|
||||
*reads = std::move(result);
|
||||
completed = true;
|
||||
loop.quit();
|
||||
});
|
||||
@@ -850,13 +854,13 @@ int main(int argc, char** argv)
|
||||
int latestContextCallbacks = 0;
|
||||
staleContextRuntime.contextAsync(
|
||||
{}, readyNetwork(), true, false,
|
||||
[&](QVariantMap) { ++staleContextCallbacks; });
|
||||
[&](QVariantMap, QVector<WalletAccountRead>) { ++staleContextCallbacks; });
|
||||
if (!expect(waitForRequestCount(staleContextServer, 1),
|
||||
"first context should begin the config read"))
|
||||
return 1;
|
||||
staleContextRuntime.contextAsync(
|
||||
{}, readyNetwork(), true, false,
|
||||
[&](QVariantMap) { ++latestContextCallbacks; });
|
||||
[&](QVariantMap, QVector<WalletAccountRead>) { ++latestContextCallbacks; });
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||
if (!expect(staleContextServer.requestCount() == 1,
|
||||
"superseding context should share the active config read"))
|
||||
@@ -1091,12 +1095,23 @@ int main(int argc, char** argv)
|
||||
if (!expect(server.requestCount() == 2,
|
||||
"cached context should not reread accounts"))
|
||||
return 1;
|
||||
if (!expect(waitForContext(refreshRuntime, true),
|
||||
refreshClient.normalizedBalanceHex = QString(31, QLatin1Char('0'))
|
||||
+ QLatin1Char('1');
|
||||
QVector<WalletAccountRead> refreshedWalletReads;
|
||||
if (!expect(waitForContext(refreshRuntime, true, &refreshedWalletReads),
|
||||
"forced context should complete"))
|
||||
return 1;
|
||||
if (!expect(server.requestCount() == 4,
|
||||
"forced context should reread config and wallet holding"))
|
||||
return 1;
|
||||
if (!expect(refreshedWalletReads.size() == 1
|
||||
&& refreshedWalletReads.first().accountId == holding.address
|
||||
&& refreshedWalletReads.first().ok()
|
||||
&& refreshedWalletReads.first().balanceHex
|
||||
== refreshClient.normalizedBalanceHex,
|
||||
"forced context should return fresh wallet reads"))
|
||||
return 1;
|
||||
refreshClient.normalizedBalanceHex = QString(32, QLatin1Char('0'));
|
||||
|
||||
FakeWallet selectedWallet;
|
||||
NewPositionRuntime selectedRuntime(
|
||||
|
||||
@@ -23,23 +23,39 @@
|
||||
src = craneLib.cleanCargoSource ./.;
|
||||
commonArgs = {
|
||||
inherit src;
|
||||
pname = "amm_client";
|
||||
version = "0.1.0";
|
||||
strictDeps = true;
|
||||
};
|
||||
ammClientArgs = commonArgs // {
|
||||
pname = "amm_client";
|
||||
cargoExtraArgs = "-p amm_client";
|
||||
};
|
||||
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||
ammClient = craneLib.buildPackage (commonArgs // {
|
||||
inherit cargoArtifacts;
|
||||
ammClientArtifacts = craneLib.buildDepsOnly ammClientArgs;
|
||||
ammClient = craneLib.buildPackage (ammClientArgs // {
|
||||
cargoArtifacts = ammClientArtifacts;
|
||||
doCheck = false;
|
||||
postInstall = ''
|
||||
install -Dm644 ${./apps/amm/client/include/amm_client.h} \
|
||||
$out/include/amm_client.h
|
||||
'';
|
||||
});
|
||||
walletDecoderArgs = commonArgs // {
|
||||
pname = "wallet-idl-decoder";
|
||||
cargoExtraArgs = "-p wallet-idl-decoder";
|
||||
};
|
||||
walletDecoderArtifacts = craneLib.buildDepsOnly walletDecoderArgs;
|
||||
walletDecoder = craneLib.buildPackage (walletDecoderArgs // {
|
||||
cargoArtifacts = walletDecoderArtifacts;
|
||||
doCheck = false;
|
||||
postInstall = ''
|
||||
install -Dm644 ${./tools/wallet-idl-decoder/include/wallet_idl_decoder.h} \
|
||||
$out/include/wallet_idl_decoder.h
|
||||
'';
|
||||
});
|
||||
in {
|
||||
default = ammClient;
|
||||
amm_client = ammClient;
|
||||
wallet_idl_decoder = walletDecoder;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user