mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-11 00:23:21 +00:00
factor(amm-ui): source new-position network context from AMM_PROGRAM_BIN/TOKENS_CONFIG/wallet
The create-pool / new-position flow carried its own network layer:
AMM_UI_NETWORK + AMM_UI_DEVNET_FILE (a devnet.json) or a bundled
config/networks.json supplied the AMM program id and token set, and a
JSON-RPC channel/checkpoint "identity probe" gated the flow to a verified
network. Main already exposes all of this the way the Swap view consumes it,
so collapse onto those sources instead of a parallel system:
- ammProgramId <- $AMM_PROGRAM_BIN (derived like swapExactInput's program id;
doubles as the quote's network fingerprint so a quote can't
be replayed against a different deployment)
- tokenIds <- $TOKENS_CONFIG (amm-tokens.json), same as the Swap picker
- sequencer <- the wallet config (already surfaced via syncWalletState)
networkSnapshot() builds the ActiveNetworkSnapshot from those; status is
"ready"/"config_missing", gated to "loading" until wallet state resolves so no
module reads happen during construction. The channel probe is gone — submit
needs no channelId (the wallet module supplies the channel via
submitPublicTransaction), so the whole verification apparatus was overhead.
Removes: AMM_UI_NETWORK / AMM_UI_DEVNET_FILE, devnet.json / networks.json, the
ActiveNetwork class (+ its test) and its QNetwork channel probe, and Qt6Network.
ActiveNetwork.h keeps only the ActiveNetworkSnapshot struct. Run command drops
the AMM_UI_* vars:
```
LEE_WALLET_HOME_DIR=… AMM_PROGRAM_BIN=… TOKENS_CONFIG=… nix run .#amm-ui
```
This commit is contained in:
parent
e01878faf7
commit
44b355ac7d
@ -37,17 +37,14 @@ logos_module(
|
|||||||
src/AmmUiBackend.h
|
src/AmmUiBackend.h
|
||||||
src/AmmUiBackend.cpp
|
src/AmmUiBackend.cpp
|
||||||
src/ActiveNetwork.h
|
src/ActiveNetwork.h
|
||||||
src/ActiveNetwork.cpp
|
|
||||||
src/AmmClient.h
|
src/AmmClient.h
|
||||||
src/AmmClient.cpp
|
src/AmmClient.cpp
|
||||||
src/NewPositionRuntime.h
|
src/NewPositionRuntime.h
|
||||||
src/NewPositionRuntime.cpp
|
src/NewPositionRuntime.cpp
|
||||||
FIND_PACKAGES
|
FIND_PACKAGES
|
||||||
Qt6Gui
|
Qt6Gui
|
||||||
Qt6Network
|
|
||||||
LINK_LIBRARIES
|
LINK_LIBRARIES
|
||||||
Qt6::Gui
|
Qt6::Gui
|
||||||
Qt6::Network
|
|
||||||
PkgConfig::BASE58
|
PkgConfig::BASE58
|
||||||
LINK_TARGETS
|
LINK_TARGETS
|
||||||
logos_wallet_access
|
logos_wallet_access
|
||||||
@ -56,21 +53,7 @@ logos_module(
|
|||||||
amm_client
|
amm_client
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_resources(amm_ui_module_plugin amm_ui_config
|
|
||||||
PREFIX "/amm"
|
|
||||||
FILES
|
|
||||||
config/networks.json
|
|
||||||
)
|
|
||||||
|
|
||||||
if(BUILD_TESTING)
|
if(BUILD_TESTING)
|
||||||
add_executable(amm_active_network_test
|
|
||||||
tests/cpp/ActiveNetworkTest.cpp
|
|
||||||
src/ActiveNetwork.cpp
|
|
||||||
)
|
|
||||||
target_include_directories(amm_active_network_test PRIVATE src)
|
|
||||||
target_link_libraries(amm_active_network_test PRIVATE Qt6::Core)
|
|
||||||
add_test(NAME amm_active_network COMMAND amm_active_network_test)
|
|
||||||
|
|
||||||
add_executable(amm_new_position_runtime_test
|
add_executable(amm_new_position_runtime_test
|
||||||
tests/cpp/NewPositionRuntimeTest.cpp
|
tests/cpp/NewPositionRuntimeTest.cpp
|
||||||
src/NewPositionRuntime.cpp
|
src/NewPositionRuntime.cpp
|
||||||
|
|||||||
@ -135,10 +135,12 @@ core module from `result-core/` and the UI plugin from `result-lgx/`:
|
|||||||
4. Choose the core module `.lgx` from `result-core/`, then the UI plugin `.lgx`
|
4. Choose the core module `.lgx` from `result-core/`, then the UI plugin `.lgx`
|
||||||
from `result-lgx/`
|
from `result-lgx/`
|
||||||
|
|
||||||
To actually use the Swap view you must also set `AMM_PROGRAM_BIN` and
|
To actually use the on-chain views (**Swap** and **Liquidity**) you must also
|
||||||
`TOKENS_CONFIG` (both explained below). Run this **from the repo root** — use
|
set `AMM_PROGRAM_BIN` and `TOKENS_CONFIG` (both explained below). Both views read
|
||||||
absolute paths (`$(pwd)/…`), because `nix run` may not preserve the working
|
the same two — the AMM program id is derived from `AMM_PROGRAM_BIN`, the token
|
||||||
directory, so relative paths won't resolve:
|
set from `TOKENS_CONFIG`, and the sequencer from the wallet config. Run this
|
||||||
|
**from the repo root** — use absolute paths (`$(pwd)/…`), because `nix run` may
|
||||||
|
not preserve the working directory, so relative paths won't resolve:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
AMM_PROGRAM_BIN=$(pwd)/programs/amm/methods/guest/target/riscv32im-risc0-zkvm-elf/docker/amm.bin \
|
AMM_PROGRAM_BIN=$(pwd)/programs/amm/methods/guest/target/riscv32im-risc0-zkvm-elf/docker/amm.bin \
|
||||||
@ -146,10 +148,10 @@ TOKENS_CONFIG=$(pwd)/amm-tokens.json \
|
|||||||
nix run .#amm-ui
|
nix run .#amm-ui
|
||||||
```
|
```
|
||||||
|
|
||||||
Without `AMM_PROGRAM_BIN` the Swap view stays disabled; without `TOKENS_CONFIG`
|
Without `AMM_PROGRAM_BIN` the Swap and Liquidity views stay disabled; without
|
||||||
the token picker is empty. Each is detailed below.
|
`TOKENS_CONFIG` the token picker is empty. Each is detailed below.
|
||||||
|
|
||||||
### AMM program binary (required for swaps)
|
### AMM program binary (required for swaps and liquidity)
|
||||||
|
|
||||||
To execute a swap, the app must submit a transaction against the **exact AMM
|
To execute a swap, the app must submit a transaction against the **exact AMM
|
||||||
program you deployed** (its ELF determines the program id, and therefore every
|
program you deployed** (its ELF determines the program id, and therefore every
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"testnet": {
|
|
||||||
"checkpointHash": "0d25d71fca70d7008a892f6b3f768a4c66badbcd64e67d79ca595b92f1db544a",
|
|
||||||
"ammProgramId": "77eeaa23668ad2675fb768cd7ecb1893387be464b9a51f16756006c1d307db07",
|
|
||||||
"tokenDefinitionIds": [
|
|
||||||
"7b464ff9dd0d3bc07f7e2e0b0667ccd066d85ad12be4c79fc55687a863910aa6",
|
|
||||||
"48c81cf032e601ca367fc9816b957dbf5c0e4c11cf7008e8f4581ec1a67aab42",
|
|
||||||
"159caef810ea545951b3bd913efe625ee45008c80865c330e72a72ed48b61649",
|
|
||||||
"75f33110b185717209e3955f228d4a4448801d0ce8ba438a4a268050eeff3f44",
|
|
||||||
"fbd107ca4bb66bc58f59ac2d32a759be3ee0fb453f8fecd1991c11837d9660c7",
|
|
||||||
"5547fcb72644d95a385d313b887a96be41ff263bce6150b49fd87276839822bf",
|
|
||||||
"fa43e74a97d79c5f907ff3edabda5ad89bfbd3b0922572e675d4ad3c7b6029c7",
|
|
||||||
"4f3231d8a01e1d79f163bc27fce0c860a4a2f6890280e9d135eafbde0d68ed79",
|
|
||||||
"fa32f354408857006f8ea396b0419823bd04436eadb2d273d2618a46b4793ed8",
|
|
||||||
"00fe99e4fbd4c71f92e47c384c6235244c8cce39b6d6367e1e338eca0ffe01cb"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
#include "ActiveNetwork.h"
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
const char NETWORK_ENV[] = "AMM_UI_NETWORK";
|
|
||||||
const char DEVNET_FILE_ENV[] = "AMM_UI_DEVNET_FILE";
|
|
||||||
|
|
||||||
bool isLowerHex(const QString& value, int size)
|
|
||||||
{
|
|
||||||
if (value.size() != size)
|
|
||||||
return false;
|
|
||||||
for (const QChar character : value) {
|
|
||||||
const bool isAsciiDigit = character >= QLatin1Char('0')
|
|
||||||
&& character <= QLatin1Char('9');
|
|
||||||
if (!isAsciiDigit
|
|
||||||
&& (character < QLatin1Char('a') || character > QLatin1Char('f'))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActiveNetwork::load()
|
|
||||||
{
|
|
||||||
m_network = {};
|
|
||||||
m_network.status = QStringLiteral("config_missing");
|
|
||||||
m_expectedIdentity.clear();
|
|
||||||
|
|
||||||
const QByteArray selected = qgetenv(NETWORK_ENV);
|
|
||||||
m_network.id = selected.isEmpty()
|
|
||||||
? QStringLiteral("testnet")
|
|
||||||
: QString::fromLocal8Bit(selected).trimmed();
|
|
||||||
|
|
||||||
QJsonObject entry;
|
|
||||||
if (isDevnet()) {
|
|
||||||
const QString path = QString::fromLocal8Bit(qgetenv(DEVNET_FILE_ENV));
|
|
||||||
QFile file(path);
|
|
||||||
if (path.isEmpty() || !file.open(QIODevice::ReadOnly))
|
|
||||||
return false;
|
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(file.readAll());
|
|
||||||
if (!document.isObject())
|
|
||||||
return false;
|
|
||||||
entry = document.object();
|
|
||||||
m_expectedIdentity = entry.value(QStringLiteral("channelId")).toString();
|
|
||||||
} else {
|
|
||||||
QFile file(QStringLiteral(":/amm/config/networks.json"));
|
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
|
||||||
return false;
|
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(file.readAll());
|
|
||||||
if (!document.isObject())
|
|
||||||
return false;
|
|
||||||
const QJsonObject networks = document.object();
|
|
||||||
entry = networks.value(m_network.id).toObject();
|
|
||||||
m_expectedIdentity = entry.value(QStringLiteral("checkpointHash")).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_network.ammProgramId = entry.value(QStringLiteral("ammProgramId")).toString();
|
|
||||||
if (!isValidIdentity(m_expectedIdentity)
|
|
||||||
|| !isLowerHex(m_network.ammProgramId, 64)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QJsonValue& value : entry.value(QStringLiteral("tokenDefinitionIds")).toArray()) {
|
|
||||||
const QString id = value.toString();
|
|
||||||
if (!isLowerHex(id, 64)) {
|
|
||||||
m_network.tokenIds.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m_network.tokenIds.append(id);
|
|
||||||
}
|
|
||||||
m_network.status = QStringLiteral("network_unknown");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActiveNetwork::isConfigured() const
|
|
||||||
{
|
|
||||||
return m_network.status != QStringLiteral("config_missing");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActiveNetwork::isDevnet() const
|
|
||||||
{
|
|
||||||
return m_network.id == QStringLiteral("devnet");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActiveNetwork::needsIdentityProbe() const
|
|
||||||
{
|
|
||||||
return m_network.status == QStringLiteral("loading")
|
|
||||||
|| m_network.status == QStringLiteral("network_unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActiveNetwork::sequencerChanged(bool available)
|
|
||||||
{
|
|
||||||
if (isConfigured())
|
|
||||||
clearIdentity(available ? QStringLiteral("loading") : QStringLiteral("network_unknown"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActiveNetwork::reachabilityChanged(bool reachable, bool wasReachable)
|
|
||||||
{
|
|
||||||
if (!isConfigured())
|
|
||||||
return;
|
|
||||||
if (!reachable)
|
|
||||||
clearIdentity(QStringLiteral("network_unknown"));
|
|
||||||
else if (!wasReachable)
|
|
||||||
clearIdentity(QStringLiteral("loading"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActiveNetwork::beginIdentityProbe()
|
|
||||||
{
|
|
||||||
if (isConfigured())
|
|
||||||
clearIdentity(QStringLiteral("loading"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActiveNetwork::finishIdentityProbe(const QString& identity)
|
|
||||||
{
|
|
||||||
if (identity.isEmpty()) {
|
|
||||||
clearIdentity(QStringLiteral("network_unknown"));
|
|
||||||
} else if (identity != m_expectedIdentity) {
|
|
||||||
clearIdentity(QStringLiteral("network_mismatch"));
|
|
||||||
} else {
|
|
||||||
m_network.status = QStringLiteral("ready");
|
|
||||||
m_network.fingerprint = (isDevnet() ? QStringLiteral("channel:")
|
|
||||||
: QStringLiteral("block10:"))
|
|
||||||
+ identity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActiveNetwork::isValidIdentity(const QString& value)
|
|
||||||
{
|
|
||||||
return isLowerHex(value, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActiveNetwork::clearIdentity(const QString& status)
|
|
||||||
{
|
|
||||||
m_network.status = status;
|
|
||||||
m_network.fingerprint.clear();
|
|
||||||
}
|
|
||||||
@ -3,6 +3,11 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
|
// Network context handed to the new-position flow. The AMM deployment identity
|
||||||
|
// (ammProgramId, from $AMM_PROGRAM_BIN) and the configured token set (tokenIds,
|
||||||
|
// from $TOKENS_CONFIG) are the same sources the Swap view uses; there is no
|
||||||
|
// separate network config file or channel-identity probe. `fingerprint` binds a
|
||||||
|
// quote to the deployment so a quote can't be replayed against a different one.
|
||||||
struct ActiveNetworkSnapshot {
|
struct ActiveNetworkSnapshot {
|
||||||
QString id;
|
QString id;
|
||||||
QString status;
|
QString status;
|
||||||
@ -10,27 +15,3 @@ struct ActiveNetworkSnapshot {
|
|||||||
QString ammProgramId;
|
QString ammProgramId;
|
||||||
QStringList tokenIds;
|
QStringList tokenIds;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ActiveNetwork final {
|
|
||||||
public:
|
|
||||||
bool load();
|
|
||||||
|
|
||||||
const QString& status() const { return m_network.status; }
|
|
||||||
bool isConfigured() const;
|
|
||||||
bool isDevnet() const;
|
|
||||||
bool needsIdentityProbe() const;
|
|
||||||
ActiveNetworkSnapshot snapshot() const { return m_network; }
|
|
||||||
|
|
||||||
void sequencerChanged(bool available);
|
|
||||||
void reachabilityChanged(bool reachable, bool wasReachable);
|
|
||||||
void beginIdentityProbe();
|
|
||||||
void finishIdentityProbe(const QString& identity);
|
|
||||||
|
|
||||||
static bool isValidIdentity(const QString& value);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void clearIdentity(const QString& status);
|
|
||||||
|
|
||||||
ActiveNetworkSnapshot m_network;
|
|
||||||
QString m_expectedIdentity;
|
|
||||||
};
|
|
||||||
|
|||||||
@ -14,9 +14,6 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonParseError>
|
#include <QJsonParseError>
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
@ -144,46 +141,6 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
const int CHECKPOINT_BLOCK_ID = 10;
|
|
||||||
const int BLOCK_HASH_OFFSET = 40;
|
|
||||||
const int BLOCK_HASH_SIZE = 32;
|
|
||||||
|
|
||||||
QByteArray jsonRpcBody(const QString& method, const QJsonArray& params)
|
|
||||||
{
|
|
||||||
return QJsonDocument(QJsonObject {
|
|
||||||
{ QStringLiteral("jsonrpc"), QStringLiteral("2.0") },
|
|
||||||
{ QStringLiteral("id"), 1 },
|
|
||||||
{ QStringLiteral("method"), method },
|
|
||||||
{ QStringLiteral("params"), params },
|
|
||||||
}).toJson(QJsonDocument::Compact);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString blockHashFromResponse(const QByteArray& payload)
|
|
||||||
{
|
|
||||||
QJsonParseError parseError;
|
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(payload, &parseError);
|
|
||||||
if (parseError.error != QJsonParseError::NoError || !document.isObject())
|
|
||||||
return {};
|
|
||||||
const QByteArray block =
|
|
||||||
QByteArray::fromBase64(document.object().value(QStringLiteral("result")).toString().toLatin1());
|
|
||||||
if (block.size() < BLOCK_HASH_OFFSET + BLOCK_HASH_SIZE)
|
|
||||||
return {};
|
|
||||||
return QString::fromLatin1(block.mid(BLOCK_HASH_OFFSET, BLOCK_HASH_SIZE).toHex());
|
|
||||||
}
|
|
||||||
|
|
||||||
QString channelIdFromResponse(const QByteArray& payload)
|
|
||||||
{
|
|
||||||
QJsonParseError parseError;
|
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(payload, &parseError);
|
|
||||||
if (parseError.error != QJsonParseError::NoError || !document.isObject())
|
|
||||||
return {};
|
|
||||||
const QString channel = document.object().value(QStringLiteral("result")).toString();
|
|
||||||
return ActiveNetwork::isValidIdentity(channel) ? channel : QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
AmmUiBackend::AmmUiBackend(LogosAPI* logosAPI, QObject* parent)
|
AmmUiBackend::AmmUiBackend(LogosAPI* logosAPI, QObject* parent)
|
||||||
: AmmUiBackendSimpleSource(parent),
|
: AmmUiBackendSimpleSource(parent),
|
||||||
m_logosAPI(logosAPI ? logosAPI : new LogosAPI("amm_ui", this)),
|
m_logosAPI(logosAPI ? logosAPI : new LogosAPI("amm_ui", this)),
|
||||||
@ -192,13 +149,11 @@ AmmUiBackend::AmmUiBackend(LogosAPI* logosAPI, QObject* parent)
|
|||||||
m_walletController(std::make_unique<WalletController>(
|
m_walletController(std::make_unique<WalletController>(
|
||||||
*m_wallet, QStringLiteral("AmmUI"))),
|
*m_wallet, QStringLiteral("AmmUI"))),
|
||||||
m_ammClient(std::make_unique<BundledAmmClient>()),
|
m_ammClient(std::make_unique<BundledAmmClient>()),
|
||||||
m_newPosition(std::make_unique<NewPositionRuntime>(m_wallet.get(), m_ammClient.get())),
|
m_newPosition(std::make_unique<NewPositionRuntime>(m_wallet.get(), m_ammClient.get()))
|
||||||
m_net(new QNetworkAccessManager(this))
|
|
||||||
{
|
{
|
||||||
setWalletStateReady(false);
|
setWalletStateReady(false);
|
||||||
m_network.load();
|
|
||||||
setNewPositionContext(m_newPosition->context(
|
setNewPositionContext(m_newPosition->context(
|
||||||
QVariantMap(), m_network.snapshot(), false, false));
|
QVariantMap(), networkSnapshot(), false, false));
|
||||||
|
|
||||||
connect(m_walletController.get(), &WalletController::stateChanged,
|
connect(m_walletController.get(), &WalletController::stateChanged,
|
||||||
this, &AmmUiBackend::syncWalletState);
|
this, &AmmUiBackend::syncWalletState);
|
||||||
@ -289,29 +244,25 @@ void AmmUiBackend::refreshNewPositionContext(QVariantMap request)
|
|||||||
else {
|
else {
|
||||||
request = m_newPositionHints;
|
request = m_newPositionHints;
|
||||||
}
|
}
|
||||||
if (m_network.status() == QStringLiteral("network_unknown"))
|
|
||||||
probeNetworkIdentity();
|
|
||||||
setNewPositionContext(m_newPosition->context(
|
setNewPositionContext(m_newPosition->context(
|
||||||
request, m_network.snapshot(), isWalletOpen(), refreshWalletAccounts));
|
request, networkSnapshot(), isWalletOpen(), refreshWalletAccounts));
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap AmmUiBackend::quoteNewPosition(QVariantMap request)
|
QVariantMap AmmUiBackend::quoteNewPosition(QVariantMap request)
|
||||||
{
|
{
|
||||||
return m_newPosition->quote(request, m_network.snapshot(), isWalletOpen());
|
return m_newPosition->quote(request, networkSnapshot(), isWalletOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap AmmUiBackend::submitNewPosition(QVariantMap request, QString quoteHash)
|
QVariantMap AmmUiBackend::submitNewPosition(QVariantMap request, QString quoteHash)
|
||||||
{
|
{
|
||||||
return m_newPosition->submit(
|
return m_newPosition->submit(
|
||||||
request, quoteHash, m_network.snapshot(), isWalletOpen());
|
request, quoteHash, networkSnapshot(), isWalletOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmmUiBackend::syncWalletState()
|
void AmmUiBackend::syncWalletState()
|
||||||
{
|
{
|
||||||
const WalletUiState& state = m_walletController->state();
|
const WalletUiState& state = m_walletController->state();
|
||||||
const bool walletWasOpen = isWalletOpen();
|
const bool walletWasOpen = isWalletOpen();
|
||||||
const bool wasReachable = sequencerReachable();
|
|
||||||
const QString previousAddress = sequencerAddr();
|
|
||||||
|
|
||||||
setIsWalletOpen(state.isWalletOpen);
|
setIsWalletOpen(state.isWalletOpen);
|
||||||
setWalletExists(state.walletExists);
|
setWalletExists(state.walletExists);
|
||||||
@ -323,69 +274,71 @@ void AmmUiBackend::syncWalletState()
|
|||||||
setSequencerAddr(state.sequencerAddress);
|
setSequencerAddr(state.sequencerAddress);
|
||||||
setSequencerReachable(state.sequencerReachable);
|
setSequencerReachable(state.sequencerReachable);
|
||||||
|
|
||||||
const bool addressChanged = previousAddress != state.sequencerAddress;
|
|
||||||
if (addressChanged)
|
|
||||||
m_network.sequencerChanged(!state.sequencerAddress.isEmpty());
|
|
||||||
if (addressChanged || wasReachable != state.sequencerReachable) {
|
|
||||||
m_network.reachabilityChanged(state.sequencerReachable, wasReachable);
|
|
||||||
}
|
|
||||||
if (walletWasOpen && !state.isWalletOpen)
|
if (walletWasOpen && !state.isWalletOpen)
|
||||||
m_newPosition->clearWalletAccounts();
|
m_newPosition->clearWalletAccounts();
|
||||||
|
|
||||||
publishNetworkContext();
|
publishNetworkContext();
|
||||||
if (state.sequencerReachable && m_network.needsIdentityProbe())
|
|
||||||
probeNetworkIdentity();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AmmUiBackend::probeNetworkIdentity()
|
|
||||||
{
|
|
||||||
if (m_identityProbeInFlight
|
|
||||||
|| !m_network.isConfigured()
|
|
||||||
|| sequencerAddr().isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_identityProbeInFlight = true;
|
|
||||||
m_network.beginIdentityProbe();
|
|
||||||
publishNetworkContext();
|
|
||||||
const QString address = sequencerAddr();
|
|
||||||
const bool devnet = m_network.isDevnet();
|
|
||||||
const QString method = devnet
|
|
||||||
? QStringLiteral("getChannelId")
|
|
||||||
: QStringLiteral("getBlock");
|
|
||||||
const QJsonArray params = devnet
|
|
||||||
? QJsonArray()
|
|
||||||
: QJsonArray { CHECKPOINT_BLOCK_ID };
|
|
||||||
|
|
||||||
QNetworkRequest request{QUrl(address)};
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
|
||||||
request.setTransferTimeout(4000);
|
|
||||||
QNetworkReply* reply = m_net->post(request, jsonRpcBody(method, params));
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply, address, devnet]() {
|
|
||||||
m_identityProbeInFlight = false;
|
|
||||||
if (address != sequencerAddr()) {
|
|
||||||
reply->deleteLater();
|
|
||||||
probeNetworkIdentity();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!sequencerReachable()) {
|
|
||||||
reply->deleteLater();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QByteArray payload = reply->readAll();
|
|
||||||
const QString actual = devnet
|
|
||||||
? channelIdFromResponse(payload)
|
|
||||||
: blockHashFromResponse(payload);
|
|
||||||
m_network.finishIdentityProbe(actual);
|
|
||||||
reply->deleteLater();
|
|
||||||
publishNetworkContext();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmmUiBackend::publishNetworkContext()
|
void AmmUiBackend::publishNetworkContext()
|
||||||
{
|
{
|
||||||
setNewPositionContext(m_newPosition->context(
|
setNewPositionContext(m_newPosition->context(
|
||||||
m_newPositionHints, m_network.snapshot(), isWalletOpen(), false));
|
m_newPositionHints, networkSnapshot(), isWalletOpen(), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AmmUiBackend::ammProgramIdHex()
|
||||||
|
{
|
||||||
|
const QByteArray elf = loadAmmElf();
|
||||||
|
if (elf.isEmpty())
|
||||||
|
return QString();
|
||||||
|
ProgramId ammId{};
|
||||||
|
if (!amm_client_program_id_from_elf(reinterpret_cast<const uint8_t*>(elf.constData()),
|
||||||
|
static_cast<uintptr_t>(elf.size()), &ammId)) {
|
||||||
|
qWarning() << "AmmUiBackend::ammProgramIdHex: amm_client_program_id_from_elf failed";
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
// 32 bytes, little-endian per u32 word, lowercase hex — same encoding as
|
||||||
|
// swapExactInput's program-id and `spel program-id`.
|
||||||
|
QByteArray bytes;
|
||||||
|
bytes.reserve(32);
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
const uint32_t word = ammId[i];
|
||||||
|
bytes.append(static_cast<char>(word & 0xff));
|
||||||
|
bytes.append(static_cast<char>((word >> 8) & 0xff));
|
||||||
|
bytes.append(static_cast<char>((word >> 16) & 0xff));
|
||||||
|
bytes.append(static_cast<char>((word >> 24) & 0xff));
|
||||||
|
}
|
||||||
|
return QString::fromLatin1(bytes.toHex());
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveNetworkSnapshot AmmUiBackend::networkSnapshot()
|
||||||
|
{
|
||||||
|
ActiveNetworkSnapshot snapshot;
|
||||||
|
snapshot.id = QStringLiteral("lez");
|
||||||
|
// Defer program/token resolution (which reaches the module) until wallet
|
||||||
|
// state is resolved; the constructor publishes an initial context before
|
||||||
|
// the module is up, and syncWalletState() republishes once it is.
|
||||||
|
if (!walletStateReady()) {
|
||||||
|
snapshot.status = QStringLiteral("loading");
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
snapshot.ammProgramId = ammProgramIdHex();
|
||||||
|
// Bind a quote to this AMM deployment: the program id changes per deployment,
|
||||||
|
// so it doubles as the network fingerprint (a quote can't be replayed against
|
||||||
|
// a different program). Empty when AMM_PROGRAM_BIN is unset — status gates it.
|
||||||
|
snapshot.fingerprint = snapshot.ammProgramId;
|
||||||
|
// Configured token set = the TOKENS_CONFIG definition ids, the same source
|
||||||
|
// the Swap view's token picker uses (tokenList normalizes them to hex).
|
||||||
|
const QVariantList tokens = tokenList();
|
||||||
|
for (const QVariant& entry : tokens) {
|
||||||
|
const QString id = entry.toMap().value(QStringLiteral("definitionId")).toString();
|
||||||
|
if (!id.isEmpty())
|
||||||
|
snapshot.tokenIds.append(id);
|
||||||
|
}
|
||||||
|
snapshot.status = snapshot.ammProgramId.isEmpty()
|
||||||
|
? QStringLiteral("config_missing")
|
||||||
|
: QStringLiteral("ready");
|
||||||
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AmmUiBackend::normalizeAccountId(const QString& id)
|
QString AmmUiBackend::normalizeAccountId(const QString& id)
|
||||||
|
|||||||
@ -23,7 +23,6 @@ struct LogosModules;
|
|||||||
class AmmClient;
|
class AmmClient;
|
||||||
class LogosWalletProvider;
|
class LogosWalletProvider;
|
||||||
class NewPositionRuntime;
|
class NewPositionRuntime;
|
||||||
class QNetworkAccessManager;
|
|
||||||
class WalletController;
|
class WalletController;
|
||||||
|
|
||||||
// Source-side implementation of the AmmUiBackend .rep interface.
|
// Source-side implementation of the AmmUiBackend .rep interface.
|
||||||
@ -67,9 +66,18 @@ public slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void syncWalletState();
|
void syncWalletState();
|
||||||
void probeNetworkIdentity();
|
|
||||||
void publishNetworkContext();
|
void publishNetworkContext();
|
||||||
|
|
||||||
|
// Builds the new-position network context from the same sources the Swap
|
||||||
|
// view uses: ammProgramId from $AMM_PROGRAM_BIN, tokenIds from
|
||||||
|
// $TOKENS_CONFIG. status is "ready" once AMM_PROGRAM_BIN resolves, else
|
||||||
|
// "config_missing". There is no separate network config or channel probe.
|
||||||
|
ActiveNetworkSnapshot networkSnapshot();
|
||||||
|
|
||||||
|
// 64-char lowercase-hex AMM program id derived from $AMM_PROGRAM_BIN (empty
|
||||||
|
// if unset/unreadable); matches swapExactInput's program-id encoding.
|
||||||
|
QString ammProgramIdHex();
|
||||||
|
|
||||||
// Normalizes an account id given as either 64-char lowercase/uppercase hex
|
// Normalizes an account id given as either 64-char lowercase/uppercase hex
|
||||||
// or base58 to lowercase hex. Returns an empty QString if `id` is neither
|
// or base58 to lowercase hex. Returns an empty QString if `id` is neither
|
||||||
// (or the base58 decode fails), so callers can detect and skip it.
|
// (or the base58 decode fails), so callers can detect and skip it.
|
||||||
@ -92,11 +100,7 @@ private:
|
|||||||
std::unique_ptr<AmmClient> m_ammClient;
|
std::unique_ptr<AmmClient> m_ammClient;
|
||||||
std::unique_ptr<NewPositionRuntime> m_newPosition;
|
std::unique_ptr<NewPositionRuntime> m_newPosition;
|
||||||
|
|
||||||
QNetworkAccessManager* m_net;
|
|
||||||
|
|
||||||
ActiveNetwork m_network;
|
|
||||||
QVariantMap m_newPositionHints;
|
QVariantMap m_newPositionHints;
|
||||||
bool m_identityProbeInFlight = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AMM_UI_BACKEND_H
|
#endif // AMM_UI_BACKEND_H
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
#include "ActiveNetwork.h"
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QTemporaryFile>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
bool expect(bool condition, const char* message)
|
|
||||||
{
|
|
||||||
if (!condition)
|
|
||||||
qCritical("%s", message);
|
|
||||||
return condition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
const QString identity(64, QLatin1Char('a'));
|
|
||||||
const QString programId(64, QLatin1Char('b'));
|
|
||||||
const QString tokenId(64, QLatin1Char('c'));
|
|
||||||
|
|
||||||
QTemporaryFile config;
|
|
||||||
if (!config.open())
|
|
||||||
return 1;
|
|
||||||
config.write(QJsonDocument(QJsonObject {
|
|
||||||
{ QStringLiteral("channelId"), identity },
|
|
||||||
{ QStringLiteral("ammProgramId"), programId },
|
|
||||||
{ QStringLiteral("tokenDefinitionIds"), QJsonArray { tokenId } },
|
|
||||||
}).toJson(QJsonDocument::Compact));
|
|
||||||
config.flush();
|
|
||||||
|
|
||||||
qputenv("AMM_UI_NETWORK", "devnet");
|
|
||||||
qputenv("AMM_UI_DEVNET_FILE", config.fileName().toLocal8Bit());
|
|
||||||
|
|
||||||
ActiveNetwork network;
|
|
||||||
if (!expect(network.load(), "devnet config should load"))
|
|
||||||
return 1;
|
|
||||||
if (!expect(network.snapshot().status == QStringLiteral("network_unknown"),
|
|
||||||
"loaded network should await identity"))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
network.sequencerChanged(true);
|
|
||||||
network.finishIdentityProbe(QString(64, QLatin1Char('d')));
|
|
||||||
if (!expect(network.status() == QStringLiteral("network_mismatch"),
|
|
||||||
"wrong identity should block the network"))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
network.reachabilityChanged(false, true);
|
|
||||||
network.reachabilityChanged(true, false);
|
|
||||||
network.finishIdentityProbe(identity);
|
|
||||||
const ActiveNetworkSnapshot snapshot = network.snapshot();
|
|
||||||
if (!expect(snapshot.status == QStringLiteral("ready"),
|
|
||||||
"matching identity should ready the network"))
|
|
||||||
return 1;
|
|
||||||
if (!expect(snapshot.fingerprint == QStringLiteral("channel:") + identity,
|
|
||||||
"devnet fingerprint should use channel identity"))
|
|
||||||
return 1;
|
|
||||||
if (!expect(snapshot.ammProgramId == programId
|
|
||||||
&& snapshot.tokenIds == QStringList { tokenId },
|
|
||||||
"network snapshot should preserve configured program and tokens"))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user