fix(amm-ui): cache network snapshot to avoid remote calls on the hot path

networkSnapshot() rebuilt the new-position network context on every call —
deriving ammProgramId from $AMM_PROGRAM_BIN and resolving the $TOKENS_CONFIG
token ids, which run tokenList()'s remote account_id_from_base58 conversions.
It is called on the quote hot path (every keystroke) and, critically, from
inside runtime reply callbacks: after a create-pool submit, pool activation
runs refreshContext() from within a quoteNewPosition reply, so the nested
synchronous remote calls reentered the module connection and hung the reply.
refreshNewPositionContext never completed, contextLoading never cleared, and
the token selectors (gated on !contextLoading) stayed disabled — the view
became unusable after creating a pool.

$AMM_PROGRAM_BIN and $TOKENS_CONFIG are fixed for the process lifetime, so
resolve ammProgramId and the token ids once and cache them; networkSnapshot()
now returns the cached values with no per-call remote work. (Still gated to
"loading" until wallet state resolves, so the one-time resolve happens at
startup, not inside a callback.)
This commit is contained in:
r4bbit 2026-07-24 18:43:09 +02:00
parent 34c6c5e821
commit c53331934b
No known key found for this signature in database
GPG Key ID: E95F1E9447DC91A9
2 changed files with 31 additions and 11 deletions

View File

@ -322,20 +322,31 @@ ActiveNetworkSnapshot AmmUiBackend::networkSnapshot()
snapshot.status = QStringLiteral("loading");
return snapshot;
}
snapshot.ammProgramId = ammProgramIdHex();
// Resolve the AMM deployment id ($AMM_PROGRAM_BIN) and configured token set
// ($TOKENS_CONFIG) ONCE and cache — they're fixed for the process lifetime.
// networkSnapshot() runs on the quote hot path and from inside runtime reply
// callbacks, and tokenList() makes remote base58 conversions; recomputing each
// call reenters the module connection and hangs the reply.
if (!m_networkResolved) {
m_ammProgramIdCache = ammProgramIdHex();
m_tokenIdsCache.clear();
// 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())
m_tokenIdsCache.append(id);
}
m_networkResolved = true;
}
snapshot.ammProgramId = m_ammProgramIdCache;
// 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()
snapshot.fingerprint = m_ammProgramIdCache;
snapshot.tokenIds = m_tokenIdsCache;
snapshot.status = m_ammProgramIdCache.isEmpty()
? QStringLiteral("config_missing")
: QStringLiteral("ready");
return snapshot;

View File

@ -5,6 +5,7 @@
#include <QObject>
#include <QString>
#include <QStringList>
#include <QVariant>
#include <QVariantList>
#include <QVariantMap>
@ -101,6 +102,14 @@ private:
std::unique_ptr<NewPositionRuntime> m_newPosition;
QVariantMap m_newPositionHints;
// Network context is derived from $AMM_PROGRAM_BIN + $TOKENS_CONFIG, which are
// fixed for the process lifetime — resolve them once and cache. networkSnapshot()
// runs on the hot path (every quote) and from inside runtime callbacks, and
// tokenList() makes remote base58 conversions, so it must not recompute each call.
bool m_networkResolved = false;
QString m_ammProgramIdCache;
QStringList m_tokenIdsCache;
};
#endif // AMM_UI_BACKEND_H