mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-21 14:29:41 +00:00
789 lines
38 KiB
C++
789 lines
38 KiB
C++
#include "NewPositionRuntime.h"
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <utility>
|
|
|
|
#include <QByteArray>
|
|
#include <QDateTime>
|
|
#include <QJsonObject>
|
|
#include <QScopedValueRollback>
|
|
#include <libbase58.h>
|
|
|
|
#include "AmmClient.h"
|
|
#include "SequencerClient.h"
|
|
#include "WalletProvider.h"
|
|
|
|
namespace {
|
|
const char SCHEMA[] = "new-position.v2";
|
|
constexpr qsizetype HASH_BYTES = 32;
|
|
constexpr std::size_t BASE58_BUFFER_SIZE = 45;
|
|
|
|
QString base58TransactionId(const QString& transactionHash)
|
|
{
|
|
const QByteArray hex = transactionHash.toLatin1();
|
|
const QByteArray bytes = QByteArray::fromHex(hex);
|
|
if (bytes.size() != HASH_BYTES || bytes.toHex() != hex)
|
|
return {};
|
|
|
|
std::array<char, BASE58_BUFFER_SIZE> encoded {};
|
|
std::size_t size = encoded.size();
|
|
if (!b58enc(encoded.data(), &size, bytes.constData(),
|
|
static_cast<std::size_t>(bytes.size()))) {
|
|
return {};
|
|
}
|
|
return QString::fromLatin1(
|
|
encoded.data(), static_cast<qsizetype>(size - 1));
|
|
}
|
|
|
|
QString accountIdHex(const QString& accountId)
|
|
{
|
|
const QByteArray encoded = accountId.toLatin1();
|
|
std::array<unsigned char, HASH_BYTES> bytes {};
|
|
std::size_t size = bytes.size();
|
|
if (!b58tobin(bytes.data(), &size, encoded.constData(),
|
|
static_cast<std::size_t>(encoded.size()))
|
|
|| size != bytes.size()) {
|
|
return {};
|
|
}
|
|
return QString::fromLatin1(
|
|
QByteArray(reinterpret_cast<const char*>(bytes.data()),
|
|
static_cast<qsizetype>(bytes.size())).toHex());
|
|
}
|
|
|
|
QJsonObject issue(const QString& code,
|
|
const QJsonArray& blockingFields = {})
|
|
{
|
|
return {
|
|
{ QStringLiteral("code"), code },
|
|
{ QStringLiteral("recoverable"), true },
|
|
{ QStringLiteral("blockingFields"), blockingFields },
|
|
{ QStringLiteral("details"), QJsonObject() },
|
|
};
|
|
}
|
|
|
|
QJsonObject publicError(const QString& code,
|
|
const QJsonArray& blockingFields = {},
|
|
const QJsonObject& details = {})
|
|
{
|
|
QJsonObject error = issue(code, blockingFields);
|
|
error.insert(QStringLiteral("details"), details);
|
|
return {
|
|
{ QStringLiteral("schema"), QString::fromLatin1(SCHEMA) },
|
|
{ QStringLiteral("status"), QStringLiteral("error") },
|
|
{ QStringLiteral("canSubmit"), false },
|
|
{ QStringLiteral("code"), code },
|
|
{ QStringLiteral("errors"), QJsonArray { error } },
|
|
{ QStringLiteral("warnings"), QJsonArray() },
|
|
{ QStringLiteral("accountPreview"), QJsonArray() },
|
|
};
|
|
}
|
|
|
|
QJsonObject contextState(const QString& status,
|
|
const ActiveNetworkSnapshot& network,
|
|
const QString& code = {})
|
|
{
|
|
QJsonObject state {
|
|
{ QStringLiteral("schema"), QString::fromLatin1(SCHEMA) },
|
|
{ QStringLiteral("status"), status },
|
|
{ QStringLiteral("networkId"), network.id },
|
|
{ QStringLiteral("networkFingerprint"), network.fingerprint },
|
|
{ QStringLiteral("tokens"), QJsonArray() },
|
|
{ QStringLiteral("feeTiers"), QJsonArray() },
|
|
{ QStringLiteral("warnings"), QJsonArray() },
|
|
};
|
|
if (!code.isEmpty())
|
|
state.insert(QStringLiteral("code"), code);
|
|
return state;
|
|
}
|
|
|
|
QJsonArray variantStringArray(const QVariant& value)
|
|
{
|
|
QJsonArray result;
|
|
for (const QVariant& item : value.toList())
|
|
result.append(item.toString());
|
|
return result;
|
|
}
|
|
|
|
QStringList jsonStringList(const QJsonArray& values)
|
|
{
|
|
QStringList result;
|
|
result.reserve(values.size());
|
|
for (const QJsonValue& value : values)
|
|
result.append(value.toString());
|
|
return result;
|
|
}
|
|
|
|
QVector<bool> jsonBoolList(const QJsonArray& values)
|
|
{
|
|
QVector<bool> result;
|
|
result.reserve(values.size());
|
|
for (const QJsonValue& value : values)
|
|
result.append(value.toBool());
|
|
return result;
|
|
}
|
|
|
|
QVector<quint32> jsonUIntList(const QJsonArray& values)
|
|
{
|
|
QVector<quint32> result;
|
|
result.reserve(values.size());
|
|
for (const QJsonValue& value : values)
|
|
result.append(static_cast<quint32>(value.toInteger()));
|
|
return result;
|
|
}
|
|
|
|
QJsonObject accountReadJson(const WalletAccountRead& read)
|
|
{
|
|
QJsonObject result {
|
|
{ QStringLiteral("id"), read.accountId },
|
|
{ QStringLiteral("status"), read.status },
|
|
};
|
|
if (read.ok()) {
|
|
result.insert(QStringLiteral("account"), QJsonObject {
|
|
{ QStringLiteral("program_owner"), read.programOwner },
|
|
{ QStringLiteral("balance"), read.balanceHex },
|
|
{ QStringLiteral("nonce"), read.nonceHex },
|
|
{ QStringLiteral("data"), read.dataHex },
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QJsonArray accountReadsJson(const QVector<WalletAccountRead>& reads)
|
|
{
|
|
QJsonArray result;
|
|
for (const WalletAccountRead& read : reads)
|
|
result.append(accountReadJson(read));
|
|
return result;
|
|
}
|
|
}
|
|
|
|
NewPositionRuntime::NewPositionRuntime(WalletProvider* wallet,
|
|
AmmClient* client,
|
|
SequencerClient* sequencer)
|
|
: m_wallet(wallet),
|
|
m_client(client),
|
|
m_sequencer(sequencer)
|
|
{
|
|
}
|
|
|
|
void NewPositionRuntime::clearWalletAccounts()
|
|
{
|
|
++m_walletGeneration;
|
|
m_walletPublicAccountIds.clear();
|
|
m_wallet->clearSnapshot();
|
|
}
|
|
|
|
void NewPositionRuntime::setWalletAccounts(const QVector<WalletAccount>& accounts)
|
|
{
|
|
QStringList publicAccountIds;
|
|
for (const WalletAccount& account : accounts) {
|
|
if (account.isPublic)
|
|
publicAccountIds.append(account.address);
|
|
}
|
|
if (publicAccountIds != m_walletPublicAccountIds)
|
|
++m_walletGeneration;
|
|
m_walletPublicAccountIds = std::move(publicAccountIds);
|
|
}
|
|
|
|
QJsonArray NewPositionRuntime::walletAccountReads(bool walletOpen, bool refresh) const
|
|
{
|
|
if (!walletOpen)
|
|
return {};
|
|
return accountReadsJson(m_wallet->snapshot(refresh).publicAccountReads);
|
|
}
|
|
|
|
QJsonObject NewPositionRuntime::buildQuoteInput(const QVariantMap& request,
|
|
const ActiveNetworkSnapshot& network,
|
|
bool walletOpen,
|
|
bool freshWalletAccounts,
|
|
QJsonObject* error) const
|
|
{
|
|
if (network.status != QStringLiteral("ready")) {
|
|
*error = publicError(network.status);
|
|
return {};
|
|
}
|
|
const AmmClientResult configResult = m_client->configId(
|
|
QJsonObject { { QStringLiteral("ammProgramId"), network.ammProgramId } });
|
|
if (!configResult.ok) {
|
|
*error = publicError(QStringLiteral("backend_error"));
|
|
return {};
|
|
}
|
|
const QJsonObject configManifest = configResult.value;
|
|
const QJsonObject config = accountReadJson(m_wallet->readPublicAccount(
|
|
configManifest.value(QStringLiteral("configId")).toString()));
|
|
const QJsonObject requestObject = QJsonObject::fromVariantMap(request);
|
|
const AmmClientResult pairResult = m_client->pairIds(
|
|
QJsonObject {
|
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
|
{ QStringLiteral("config"), config },
|
|
{ QStringLiteral("tokenAId"), requestObject.value(QStringLiteral("tokenAId")) },
|
|
{ QStringLiteral("tokenBId"), requestObject.value(QStringLiteral("tokenBId")) },
|
|
});
|
|
if (!pairResult.ok) {
|
|
*error = publicError(QStringLiteral("backend_error"));
|
|
return {};
|
|
}
|
|
const QJsonObject pairManifest = pairResult.value;
|
|
if (pairManifest.value(QStringLiteral("status")).toString() != QStringLiteral("ok")) {
|
|
*error = publicError(pairManifest.value(QStringLiteral("code")).toString());
|
|
return {};
|
|
}
|
|
|
|
const QJsonArray walletAccounts = walletAccountReads(walletOpen, freshWalletAccounts);
|
|
const QJsonObject snapshot {
|
|
{ QStringLiteral("config"), config },
|
|
{ QStringLiteral("tokenA"), accountReadJson(m_wallet->readPublicAccount(pairManifest.value(QStringLiteral("tokenAId")).toString())) },
|
|
{ QStringLiteral("tokenB"), accountReadJson(m_wallet->readPublicAccount(pairManifest.value(QStringLiteral("tokenBId")).toString())) },
|
|
{ QStringLiteral("pool"), accountReadJson(m_wallet->readPublicAccount(pairManifest.value(QStringLiteral("poolId")).toString())) },
|
|
{ QStringLiteral("vaultA"), accountReadJson(m_wallet->readPublicAccount(pairManifest.value(QStringLiteral("vaultAId")).toString())) },
|
|
{ QStringLiteral("vaultB"), accountReadJson(m_wallet->readPublicAccount(pairManifest.value(QStringLiteral("vaultBId")).toString())) },
|
|
{ QStringLiteral("lpDefinition"), accountReadJson(m_wallet->readPublicAccount(pairManifest.value(QStringLiteral("lpDefinitionId")).toString())) },
|
|
{ QStringLiteral("lpLockHolding"), accountReadJson(m_wallet->readPublicAccount(pairManifest.value(QStringLiteral("lpLockHoldingId")).toString())) },
|
|
{ QStringLiteral("currentTick"), accountReadJson(m_wallet->readPublicAccount(pairManifest.value(QStringLiteral("currentTickId")).toString())) },
|
|
{ QStringLiteral("clock"), accountReadJson(m_wallet->readPublicAccount(pairManifest.value(QStringLiteral("clockId")).toString())) },
|
|
{ QStringLiteral("walletAvailable"), walletOpen },
|
|
{ QStringLiteral("walletAccounts"), walletAccounts },
|
|
};
|
|
return {
|
|
{ QStringLiteral("networkId"), network.id },
|
|
{ QStringLiteral("networkFingerprint"), network.fingerprint },
|
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
|
{ QStringLiteral("request"), requestObject },
|
|
{ QStringLiteral("snapshot"), snapshot },
|
|
};
|
|
}
|
|
|
|
void NewPositionRuntime::contextAsync(const QVariantMap& request,
|
|
const ActiveNetworkSnapshot& network,
|
|
bool walletOpen,
|
|
bool refreshPublicData,
|
|
ResultCallback callback)
|
|
{
|
|
if (network.status != QStringLiteral("ready")) {
|
|
callback(contextState(network.status, network).toVariantMap());
|
|
return;
|
|
}
|
|
if (!m_sequencer || !m_sequencer->isConfigured()) {
|
|
callback(contextState(QStringLiteral("error"), network,
|
|
QStringLiteral("sequencer_config_required")).toVariantMap());
|
|
return;
|
|
}
|
|
|
|
const AmmClientResult configResult = m_client->configId(
|
|
QJsonObject { { QStringLiteral("ammProgramId"), network.ammProgramId } });
|
|
if (!configResult.ok) {
|
|
callback(contextState(QStringLiteral("error"), network,
|
|
QStringLiteral("backend_error")).toVariantMap());
|
|
return;
|
|
}
|
|
const QString configId = configResult.value.value(
|
|
QStringLiteral("configId")).toString();
|
|
m_sequencer->readAccounts({ configId }, refreshPublicData,
|
|
[this, request, network, walletOpen, refreshPublicData,
|
|
callback = std::move(callback)](QVector<WalletAccountRead> configReads) mutable {
|
|
const QJsonObject config = accountReadJson(configReads.value(0));
|
|
m_sequencer->readAccounts(m_walletPublicAccountIds, refreshPublicData,
|
|
[this, request, network, walletOpen, refreshPublicData, config,
|
|
callback = std::move(callback)](
|
|
QVector<WalletAccountRead> walletReads) mutable {
|
|
const QJsonObject hints = QJsonObject::fromVariantMap(request);
|
|
QJsonArray configured;
|
|
for (const QString& id : network.tokenIds)
|
|
configured.append(id);
|
|
const QJsonArray recent = variantStringArray(
|
|
hints.value(QStringLiteral("recentTokenIds")).toVariant());
|
|
const QJsonArray resolved = variantStringArray(
|
|
hints.value(QStringLiteral("resolvedTokenIds")).toVariant());
|
|
const QJsonArray walletAccounts = accountReadsJson(walletReads);
|
|
const AmmClientResult tokenResult = m_client->tokenIds(QJsonObject {
|
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
|
{ QStringLiteral("config"), config },
|
|
{ QStringLiteral("walletAccounts"), walletAccounts },
|
|
{ QStringLiteral("configuredTokenIds"), configured },
|
|
{ QStringLiteral("recentTokenIds"), recent },
|
|
{ QStringLiteral("resolvedTokenIds"), resolved },
|
|
});
|
|
if (!tokenResult.ok
|
|
|| tokenResult.value.value(QStringLiteral("status")).toString()
|
|
!= QStringLiteral("ok")) {
|
|
const QString code = tokenResult.ok
|
|
? tokenResult.value.value(QStringLiteral("code")).toString()
|
|
: QStringLiteral("backend_error");
|
|
callback(contextState(QStringLiteral("error"), network,
|
|
code.isEmpty() ? QStringLiteral("backend_error") : code)
|
|
.toVariantMap());
|
|
return;
|
|
}
|
|
|
|
QStringList definitionIds;
|
|
for (const QJsonValue& id : tokenResult.value
|
|
.value(QStringLiteral("tokenIds")).toArray()) {
|
|
definitionIds.append(id.toString());
|
|
}
|
|
m_sequencer->readAccounts(definitionIds, refreshPublicData,
|
|
[this, network, walletOpen, config, walletAccounts,
|
|
configured, recent, resolved,
|
|
callback = std::move(callback)](
|
|
QVector<WalletAccountRead> definitions) mutable {
|
|
const AmmClientResult result = m_client->context(QJsonObject {
|
|
{ QStringLiteral("networkId"), network.id },
|
|
{ QStringLiteral("networkFingerprint"), network.fingerprint },
|
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
|
{ QStringLiteral("walletAvailable"), walletOpen },
|
|
{ QStringLiteral("config"), config },
|
|
{ QStringLiteral("walletAccounts"), walletAccounts },
|
|
{ QStringLiteral("tokenDefinitions"),
|
|
accountReadsJson(definitions) },
|
|
{ QStringLiteral("configuredTokenIds"), configured },
|
|
{ QStringLiteral("recentTokenIds"), recent },
|
|
{ QStringLiteral("resolvedTokenIds"), resolved },
|
|
});
|
|
callback((result.ok
|
|
? result.value
|
|
: contextState(QStringLiteral("error"), network,
|
|
QStringLiteral("backend_error"))).toVariantMap());
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
void NewPositionRuntime::buildQuoteInputAsync(
|
|
const QVariantMap& request,
|
|
const ActiveNetworkSnapshot& network,
|
|
bool walletOpen,
|
|
bool forceRefresh,
|
|
std::function<void(QJsonObject, QJsonObject)> callback) const
|
|
{
|
|
if (network.status != QStringLiteral("ready")) {
|
|
callback({}, publicError(network.status));
|
|
return;
|
|
}
|
|
if (!m_sequencer || !m_sequencer->isConfigured()) {
|
|
callback({}, publicError(QStringLiteral("sequencer_config_required")));
|
|
return;
|
|
}
|
|
const AmmClientResult configResult = m_client->configId(
|
|
QJsonObject { { QStringLiteral("ammProgramId"), network.ammProgramId } });
|
|
if (!configResult.ok) {
|
|
callback({}, publicError(QStringLiteral("backend_error")));
|
|
return;
|
|
}
|
|
const QString configId = configResult.value.value(
|
|
QStringLiteral("configId")).toString();
|
|
m_sequencer->readAccounts({ configId }, forceRefresh,
|
|
[this, request, network, walletOpen, forceRefresh,
|
|
callback = std::move(callback)](QVector<WalletAccountRead> configReads) mutable {
|
|
const QJsonObject config = accountReadJson(configReads.value(0));
|
|
const QJsonObject requestObject = QJsonObject::fromVariantMap(request);
|
|
const AmmClientResult pairResult = m_client->pairIds(QJsonObject {
|
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
|
{ QStringLiteral("config"), config },
|
|
{ QStringLiteral("tokenAId"),
|
|
requestObject.value(QStringLiteral("tokenAId")) },
|
|
{ QStringLiteral("tokenBId"),
|
|
requestObject.value(QStringLiteral("tokenBId")) },
|
|
});
|
|
if (!pairResult.ok
|
|
|| pairResult.value.value(QStringLiteral("status")).toString()
|
|
!= QStringLiteral("ok")) {
|
|
const QString code = pairResult.ok
|
|
? pairResult.value.value(QStringLiteral("code")).toString()
|
|
: QStringLiteral("backend_error");
|
|
callback({}, publicError(code.isEmpty()
|
|
? QStringLiteral("backend_error") : code));
|
|
return;
|
|
}
|
|
const QJsonObject pair = pairResult.value;
|
|
const QStringList fixedIds {
|
|
pair.value(QStringLiteral("tokenAId")).toString(),
|
|
pair.value(QStringLiteral("tokenBId")).toString(),
|
|
pair.value(QStringLiteral("poolId")).toString(),
|
|
pair.value(QStringLiteral("vaultAId")).toString(),
|
|
pair.value(QStringLiteral("vaultBId")).toString(),
|
|
pair.value(QStringLiteral("lpDefinitionId")).toString(),
|
|
pair.value(QStringLiteral("lpLockHoldingId")).toString(),
|
|
pair.value(QStringLiteral("currentTickId")).toString(),
|
|
pair.value(QStringLiteral("clockId")).toString(),
|
|
};
|
|
m_sequencer->readAccounts(fixedIds, forceRefresh,
|
|
[this, requestObject, network, walletOpen, config, pair,
|
|
callback = std::move(callback)](
|
|
QVector<WalletAccountRead> fixedReads) mutable {
|
|
m_sequencer->readAccounts(m_walletPublicAccountIds, false,
|
|
[this, requestObject, network, walletOpen, config, pair,
|
|
fixedReads = std::move(fixedReads),
|
|
callback = std::move(callback)](
|
|
QVector<WalletAccountRead> walletReads) mutable {
|
|
QStringList selectedIds;
|
|
for (const QString& key : {
|
|
QStringLiteral("holdingAId"),
|
|
QStringLiteral("holdingBId"),
|
|
QStringLiteral("lpHoldingId") }) {
|
|
const QString id = accountIdHex(
|
|
requestObject.value(key).toString());
|
|
if (!id.isEmpty() && !selectedIds.contains(id))
|
|
selectedIds.append(id);
|
|
}
|
|
auto finish = [this, requestObject, network, walletOpen,
|
|
config, pair,
|
|
fixedReads = std::move(fixedReads),
|
|
walletReads = std::move(walletReads),
|
|
callback = std::move(callback)](
|
|
QVector<WalletAccountRead> selectedReads) mutable {
|
|
QHash<QString, WalletAccountRead> walletById;
|
|
for (const WalletAccountRead& read : walletReads)
|
|
walletById.insert(read.accountId, read);
|
|
for (const WalletAccountRead& read : selectedReads)
|
|
walletById.insert(read.accountId, read);
|
|
const QJsonArray walletAccounts = accountReadsJson(
|
|
walletById.values());
|
|
const QJsonObject snapshot {
|
|
{ QStringLiteral("config"), config },
|
|
{ QStringLiteral("tokenA"), accountReadJson(fixedReads.value(0)) },
|
|
{ QStringLiteral("tokenB"), accountReadJson(fixedReads.value(1)) },
|
|
{ QStringLiteral("pool"), accountReadJson(fixedReads.value(2)) },
|
|
{ QStringLiteral("vaultA"), accountReadJson(fixedReads.value(3)) },
|
|
{ QStringLiteral("vaultB"), accountReadJson(fixedReads.value(4)) },
|
|
{ QStringLiteral("lpDefinition"), accountReadJson(fixedReads.value(5)) },
|
|
{ QStringLiteral("lpLockHolding"), accountReadJson(fixedReads.value(6)) },
|
|
{ QStringLiteral("currentTick"), accountReadJson(fixedReads.value(7)) },
|
|
{ QStringLiteral("clock"), accountReadJson(fixedReads.value(8)) },
|
|
{ QStringLiteral("walletAvailable"), walletOpen },
|
|
{ QStringLiteral("walletAccounts"), walletAccounts },
|
|
};
|
|
callback(QJsonObject {
|
|
{ QStringLiteral("networkId"), network.id },
|
|
{ QStringLiteral("networkFingerprint"), network.fingerprint },
|
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
|
{ QStringLiteral("request"), requestObject },
|
|
{ QStringLiteral("snapshot"), snapshot },
|
|
}, {});
|
|
};
|
|
if (selectedIds.isEmpty())
|
|
finish({});
|
|
else
|
|
m_sequencer->readAccounts(
|
|
selectedIds, true, std::move(finish));
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
void NewPositionRuntime::quoteAsync(const QVariantMap& request,
|
|
const ActiveNetworkSnapshot& network,
|
|
bool walletOpen,
|
|
bool forceRefresh,
|
|
ResultCallback callback)
|
|
{
|
|
buildQuoteInputAsync(request, network, walletOpen, forceRefresh,
|
|
[this, callback = std::move(callback)](
|
|
QJsonObject input, QJsonObject error) mutable {
|
|
if (!error.isEmpty()) {
|
|
callback(error.toVariantMap());
|
|
return;
|
|
}
|
|
const AmmClientResult result = m_client->quote(input);
|
|
callback((result.ok ? result.value
|
|
: publicError(QStringLiteral("backend_error")))
|
|
.toVariantMap());
|
|
});
|
|
}
|
|
|
|
void NewPositionRuntime::submitAsync(const QVariantMap& request,
|
|
const QString& quoteHash,
|
|
const ActiveNetworkSnapshot& network,
|
|
bool walletCanSubmit,
|
|
ResultCallback callback)
|
|
{
|
|
if (m_submitInFlight) {
|
|
callback(publicError(QStringLiteral("submit_in_progress")).toVariantMap());
|
|
return;
|
|
}
|
|
if (!walletCanSubmit) {
|
|
callback(publicError(QStringLiteral("wallet_syncing")).toVariantMap());
|
|
return;
|
|
}
|
|
m_submitInFlight = true;
|
|
const quint64 walletGeneration = m_walletGeneration;
|
|
auto finish = [this, callback = std::move(callback)](QVariantMap result) mutable {
|
|
m_submitInFlight = false;
|
|
callback(std::move(result));
|
|
};
|
|
buildQuoteInputAsync(request, network, true, true,
|
|
[this, quoteHash, walletGeneration, finish = std::move(finish)](
|
|
QJsonObject input, QJsonObject error) mutable {
|
|
if (walletGeneration != m_walletGeneration) {
|
|
finish(publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
|
|
return;
|
|
}
|
|
if (!error.isEmpty()) {
|
|
finish(error.toVariantMap());
|
|
return;
|
|
}
|
|
const AmmClientResult quoteResult = m_client->quote(input);
|
|
if (!quoteResult.ok) {
|
|
finish(publicError(QStringLiteral("backend_error")).toVariantMap());
|
|
return;
|
|
}
|
|
const QJsonObject quote = quoteResult.value;
|
|
if (quote.value(QStringLiteral("quoteHash")).toString() != quoteHash) {
|
|
QJsonObject result = publicError(QStringLiteral("quote_changed"));
|
|
result.insert(QStringLiteral("quote"), quote);
|
|
finish(result.toVariantMap());
|
|
return;
|
|
}
|
|
if (!quote.value(QStringLiteral("canSubmit")).toBool(false)) {
|
|
QJsonObject result = publicError(QStringLiteral("quote_not_submittable"));
|
|
result.insert(QStringLiteral("quote"), quote);
|
|
finish(result.toVariantMap());
|
|
return;
|
|
}
|
|
|
|
QJsonValue freshLp;
|
|
if (quote.value(QStringLiteral("requiresFreshLp")).toBool(false)) {
|
|
if (walletGeneration != m_walletGeneration) {
|
|
finish(publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
|
|
return;
|
|
}
|
|
const WalletAccountCreation creation = m_wallet->createAccount(true);
|
|
if (!creation.ok() || !creation.publicAccount.ok()) {
|
|
finish(publicError(QStringLiteral("wallet_submission_failed")).toVariantMap());
|
|
return;
|
|
}
|
|
freshLp = accountReadJson(creation.publicAccount);
|
|
if (!m_walletPublicAccountIds.contains(creation.accountId))
|
|
m_walletPublicAccountIds.append(creation.accountId);
|
|
}
|
|
|
|
QJsonObject planInput = input;
|
|
planInput.insert(QStringLiteral("quoteHash"), quoteHash);
|
|
planInput.insert(QStringLiteral("nowMs"), QDateTime::currentMSecsSinceEpoch());
|
|
if (!freshLp.isUndefined())
|
|
planInput.insert(QStringLiteral("freshLp"), freshLp);
|
|
const AmmClientResult planResult = m_client->plan(planInput);
|
|
if (!planResult.ok) {
|
|
finish(publicError(QStringLiteral("backend_error")).toVariantMap());
|
|
return;
|
|
}
|
|
const QJsonObject plan = planResult.value;
|
|
if (plan.value(QStringLiteral("status")).toString()
|
|
!= QStringLiteral("ready")) {
|
|
const QString code = plan.value(QStringLiteral("code")).toString();
|
|
finish(publicError(code.isEmpty()
|
|
? QStringLiteral("wallet_submission_failed") : code).toVariantMap());
|
|
return;
|
|
}
|
|
|
|
const QStringList accountIds = jsonStringList(
|
|
plan.value(QStringLiteral("accountIds")).toArray());
|
|
const QVector<bool> signingRequirements = jsonBoolList(
|
|
plan.value(QStringLiteral("signingRequirements")).toArray());
|
|
const QVector<quint32> instruction = jsonUIntList(
|
|
plan.value(QStringLiteral("instruction")).toArray());
|
|
bool deadlineValid = false;
|
|
const qulonglong deadline = plan.value(QStringLiteral("deadlineMs"))
|
|
.toString().toULongLong(&deadlineValid);
|
|
if (!deadlineValid
|
|
|| static_cast<qulonglong>(QDateTime::currentMSecsSinceEpoch()) >= deadline) {
|
|
finish(publicError(QStringLiteral("transaction_deadline_expired")).toVariantMap());
|
|
return;
|
|
}
|
|
if (walletGeneration != m_walletGeneration) {
|
|
finish(publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
|
|
return;
|
|
}
|
|
const WalletSubmission submission = m_wallet->submitPublicTransaction({
|
|
plan.value(QStringLiteral("programId")).toString(),
|
|
accountIds,
|
|
signingRequirements,
|
|
instruction,
|
|
});
|
|
const QString transactionId = submission.accepted()
|
|
? base58TransactionId(submission.nativeHash) : QString();
|
|
if (transactionId.isEmpty()) {
|
|
finish(publicError(QStringLiteral("wallet_submission_failed")).toVariantMap());
|
|
return;
|
|
}
|
|
finish(QJsonObject {
|
|
{ QStringLiteral("schema"), QString::fromLatin1(SCHEMA) },
|
|
{ QStringLiteral("status"), QStringLiteral("submitted") },
|
|
{ QStringLiteral("transactionId"), transactionId },
|
|
{ QStringLiteral("nativeTransactionHash"), submission.nativeHash },
|
|
{ QStringLiteral("deadlineMs"), plan.value(QStringLiteral("deadlineMs")) },
|
|
{ QStringLiteral("affectedAccountIds"),
|
|
plan.value(QStringLiteral("affectedAccountIds")) },
|
|
}.toVariantMap());
|
|
});
|
|
}
|
|
|
|
QVariantMap NewPositionRuntime::context(const QVariantMap& request,
|
|
const ActiveNetworkSnapshot& network,
|
|
bool walletOpen,
|
|
bool refreshWalletAccounts)
|
|
{
|
|
if (network.status != QStringLiteral("ready"))
|
|
return contextState(network.status, network).toVariantMap();
|
|
|
|
const QJsonArray walletAccounts = walletAccountReads(walletOpen, refreshWalletAccounts);
|
|
|
|
const QJsonObject hints = QJsonObject::fromVariantMap(request);
|
|
const AmmClientResult configResult = m_client->configId(
|
|
QJsonObject { { QStringLiteral("ammProgramId"), network.ammProgramId } });
|
|
if (!configResult.ok)
|
|
return contextState(
|
|
QStringLiteral("error"), network, QStringLiteral("backend_error")).toVariantMap();
|
|
|
|
const QJsonObject configManifest = configResult.value;
|
|
const QJsonObject config = accountReadJson(m_wallet->readPublicAccount(
|
|
configManifest.value(QStringLiteral("configId")).toString()));
|
|
QJsonArray configured;
|
|
for (const QString& id : network.tokenIds)
|
|
configured.append(id);
|
|
const QJsonArray recent = variantStringArray(hints.value(QStringLiteral("recentTokenIds")).toVariant());
|
|
const QJsonArray resolved = variantStringArray(hints.value(QStringLiteral("resolvedTokenIds")).toVariant());
|
|
|
|
const AmmClientResult tokenResult = m_client->tokenIds(
|
|
QJsonObject {
|
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
|
{ QStringLiteral("config"), config },
|
|
{ QStringLiteral("walletAccounts"), walletAccounts },
|
|
{ QStringLiteral("configuredTokenIds"), configured },
|
|
{ QStringLiteral("recentTokenIds"), recent },
|
|
{ QStringLiteral("resolvedTokenIds"), resolved },
|
|
});
|
|
const QJsonObject tokenManifest = tokenResult.value;
|
|
if (!tokenResult.ok
|
|
|| tokenManifest.value(QStringLiteral("status")).toString() != QStringLiteral("ok")) {
|
|
const QString code = tokenResult.ok
|
|
? tokenManifest.value(QStringLiteral("code")).toString()
|
|
: QStringLiteral("backend_error");
|
|
return contextState(
|
|
QStringLiteral("error"),
|
|
network,
|
|
code.isEmpty() ? QStringLiteral("backend_error") : code).toVariantMap();
|
|
}
|
|
|
|
QJsonArray definitions;
|
|
for (const QJsonValue& id : tokenManifest.value(QStringLiteral("tokenIds")).toArray())
|
|
definitions.append(accountReadJson(m_wallet->readPublicAccount(id.toString())));
|
|
|
|
const AmmClientResult contextResult = m_client->context(
|
|
QJsonObject {
|
|
{ QStringLiteral("networkId"), network.id },
|
|
{ QStringLiteral("networkFingerprint"), network.fingerprint },
|
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
|
{ QStringLiteral("walletAvailable"), walletOpen },
|
|
{ QStringLiteral("config"), config },
|
|
{ QStringLiteral("walletAccounts"), walletAccounts },
|
|
{ QStringLiteral("tokenDefinitions"), definitions },
|
|
{ QStringLiteral("configuredTokenIds"), configured },
|
|
{ QStringLiteral("recentTokenIds"), recent },
|
|
{ QStringLiteral("resolvedTokenIds"), resolved },
|
|
});
|
|
return (contextResult.ok
|
|
? contextResult.value
|
|
: contextState(
|
|
QStringLiteral("error"), network, QStringLiteral("backend_error"))).toVariantMap();
|
|
}
|
|
|
|
QVariantMap NewPositionRuntime::quote(const QVariantMap& request,
|
|
const ActiveNetworkSnapshot& network,
|
|
bool walletOpen)
|
|
{
|
|
QJsonObject error;
|
|
const QJsonObject input = buildQuoteInput(request, network, walletOpen, false, &error);
|
|
if (!error.isEmpty())
|
|
return error.toVariantMap();
|
|
|
|
const AmmClientResult result = m_client->quote(input);
|
|
return (result.ok ? result.value : publicError(QStringLiteral("backend_error"))).toVariantMap();
|
|
}
|
|
|
|
QVariantMap NewPositionRuntime::submit(const QVariantMap& request,
|
|
const QString& quoteHash,
|
|
const ActiveNetworkSnapshot& network,
|
|
bool walletOpen)
|
|
{
|
|
if (m_submitInFlight)
|
|
return publicError(QStringLiteral("submit_in_progress")).toVariantMap();
|
|
if (!walletOpen)
|
|
return publicError(QStringLiteral("wallet_unavailable")).toVariantMap();
|
|
QScopedValueRollback<bool> submitGuard(m_submitInFlight, true);
|
|
|
|
QJsonObject error;
|
|
const QJsonObject input = buildQuoteInput(request, network, walletOpen, true, &error);
|
|
if (!error.isEmpty())
|
|
return error.toVariantMap();
|
|
|
|
const AmmClientResult quoteResult = m_client->quote(input);
|
|
if (!quoteResult.ok)
|
|
return publicError(QStringLiteral("backend_error")).toVariantMap();
|
|
const QJsonObject quote = quoteResult.value;
|
|
if (quote.value(QStringLiteral("quoteHash")).toString() != quoteHash) {
|
|
QJsonObject result = publicError(QStringLiteral("quote_changed"));
|
|
result.insert(QStringLiteral("quote"), quote);
|
|
return result.toVariantMap();
|
|
}
|
|
if (!quote.value(QStringLiteral("canSubmit")).toBool(false)) {
|
|
QJsonObject result = publicError(QStringLiteral("quote_not_submittable"));
|
|
result.insert(QStringLiteral("quote"), quote);
|
|
return result.toVariantMap();
|
|
}
|
|
|
|
QJsonValue freshLp;
|
|
if (quote.value(QStringLiteral("requiresFreshLp")).toBool(false)) {
|
|
const WalletAccountCreation creation = m_wallet->createAccount(true);
|
|
if (!creation.ok() || !creation.publicAccount.ok())
|
|
return publicError(QStringLiteral("wallet_submission_failed")).toVariantMap();
|
|
freshLp = accountReadJson(creation.publicAccount);
|
|
}
|
|
|
|
QJsonObject planInput = input;
|
|
planInput.insert(QStringLiteral("quoteHash"), quoteHash);
|
|
planInput.insert(QStringLiteral("nowMs"), QDateTime::currentMSecsSinceEpoch());
|
|
if (!freshLp.isUndefined())
|
|
planInput.insert(QStringLiteral("freshLp"), freshLp);
|
|
|
|
const AmmClientResult planResult = m_client->plan(planInput);
|
|
if (!planResult.ok)
|
|
return publicError(QStringLiteral("backend_error")).toVariantMap();
|
|
const QJsonObject plan = planResult.value;
|
|
if (plan.value(QStringLiteral("status")).toString() != QStringLiteral("ready")) {
|
|
const QString code = plan.value(QStringLiteral("code")).toString();
|
|
return publicError(code.isEmpty() ? QStringLiteral("wallet_submission_failed") : code)
|
|
.toVariantMap();
|
|
}
|
|
|
|
const QStringList accountIds = jsonStringList(plan.value(QStringLiteral("accountIds")).toArray());
|
|
const QVector<bool> signingRequirements = jsonBoolList(plan.value(QStringLiteral("signingRequirements")).toArray());
|
|
const QVector<quint32> instruction = jsonUIntList(plan.value(QStringLiteral("instruction")).toArray());
|
|
const QString programId = plan.value(QStringLiteral("programId")).toString();
|
|
bool deadlineValid = false;
|
|
const qulonglong deadline = plan.value(QStringLiteral("deadlineMs")).toString().toULongLong(&deadlineValid);
|
|
if (!deadlineValid
|
|
|| static_cast<qulonglong>(QDateTime::currentMSecsSinceEpoch()) >= deadline) {
|
|
return publicError(QStringLiteral("transaction_deadline_expired")).toVariantMap();
|
|
}
|
|
const WalletSubmission submission = m_wallet->submitPublicTransaction({
|
|
programId,
|
|
accountIds,
|
|
signingRequirements,
|
|
instruction,
|
|
});
|
|
if (!submission.accepted())
|
|
return publicError(QStringLiteral("wallet_submission_failed")).toVariantMap();
|
|
const QString transactionId = base58TransactionId(submission.nativeHash);
|
|
if (transactionId.isEmpty())
|
|
return publicError(QStringLiteral("wallet_submission_failed")).toVariantMap();
|
|
|
|
return QJsonObject {
|
|
{ QStringLiteral("schema"), QString::fromLatin1(SCHEMA) },
|
|
{ QStringLiteral("status"), QStringLiteral("submitted") },
|
|
{ QStringLiteral("transactionId"), transactionId },
|
|
{ QStringLiteral("nativeTransactionHash"), submission.nativeHash },
|
|
{ QStringLiteral("deadlineMs"), plan.value(QStringLiteral("deadlineMs")) },
|
|
}.toVariantMap();
|
|
}
|