mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-25 14:11:09 +00:00
feat(amm-ui): complete new position liquidity flow
Implement one create-pool and add-liquidity workflow. Add searchable token resolution, direct opening-price and deposit editing, optimistic pool activation, and base58 transaction IDs. Isolate network, wallet, AMM client, and runtime boundaries. Stabilize quote, submission, refresh, and pool-probe state while consolidating liquidity tests and removing obsolete liquidity paths.
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
#include "AmmClient.h"
|
||||
#include "NewPositionRuntime.h"
|
||||
#include "WalletProvider.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
namespace {
|
||||
bool expect(bool condition, const char* message)
|
||||
{
|
||||
if (!condition)
|
||||
qCritical("%s", message);
|
||||
return condition;
|
||||
}
|
||||
|
||||
class FakeWallet final : public WalletProvider {
|
||||
public:
|
||||
WalletSession connect(const WalletPaths&) override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
WalletCreation createWallet(const WalletPaths&, const QString&) override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
WalletSnapshot snapshot(bool) override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void clearSnapshot() override {}
|
||||
|
||||
WalletAccountCreation createAccount(bool isPublic) override
|
||||
{
|
||||
++createdAccounts;
|
||||
WalletAccountCreation creation;
|
||||
creation.accountId = QStringLiteral("fresh-lp");
|
||||
if (isPublic)
|
||||
creation.publicAccount = readPublicAccount(creation.accountId);
|
||||
return creation;
|
||||
}
|
||||
|
||||
WalletAccountRead readPublicAccount(const QString& accountId) const override
|
||||
{
|
||||
WalletAccountRead read;
|
||||
read.accountId = accountId;
|
||||
read.status = QStringLiteral("ok");
|
||||
return read;
|
||||
}
|
||||
|
||||
WalletSubmission submitPublicTransaction(const WalletTransaction& transaction) override
|
||||
{
|
||||
++submissions;
|
||||
submitted = transaction;
|
||||
return { WalletFailure::None, transactionHash };
|
||||
}
|
||||
|
||||
void disconnect() override {}
|
||||
|
||||
QString transactionHash = QStringLiteral(
|
||||
"000102030405060708090a0b0c0d0e0f"
|
||||
"101112131415161718191a1b1c1d1e1f");
|
||||
int createdAccounts = 0;
|
||||
int submissions = 0;
|
||||
WalletTransaction submitted;
|
||||
};
|
||||
|
||||
class FakeAmmClient final : public AmmClient {
|
||||
public:
|
||||
AmmClientResult configId(const QJsonObject&) const override
|
||||
{
|
||||
return success({ { QStringLiteral("configId"), QStringLiteral("config") } });
|
||||
}
|
||||
|
||||
AmmClientResult tokenIds(const QJsonObject&) const override
|
||||
{
|
||||
return success({ { QStringLiteral("status"), QStringLiteral("ok") } });
|
||||
}
|
||||
|
||||
AmmClientResult pairIds(const QJsonObject&) const override
|
||||
{
|
||||
return success({
|
||||
{ QStringLiteral("status"), QStringLiteral("ok") },
|
||||
{ QStringLiteral("tokenAId"), QStringLiteral("token-a") },
|
||||
{ QStringLiteral("tokenBId"), QStringLiteral("token-b") },
|
||||
{ QStringLiteral("poolId"), QStringLiteral("pool") },
|
||||
{ QStringLiteral("vaultAId"), QStringLiteral("vault-a") },
|
||||
{ QStringLiteral("vaultBId"), QStringLiteral("vault-b") },
|
||||
{ QStringLiteral("lpDefinitionId"), QStringLiteral("lp") },
|
||||
{ QStringLiteral("lpLockHoldingId"), QStringLiteral("lp-lock") },
|
||||
{ QStringLiteral("currentTickId"), QStringLiteral("tick") },
|
||||
{ QStringLiteral("clockId"), QStringLiteral("clock") },
|
||||
});
|
||||
}
|
||||
|
||||
AmmClientResult context(const QJsonObject&) const override
|
||||
{
|
||||
return success({});
|
||||
}
|
||||
|
||||
AmmClientResult quote(const QJsonObject&) const override
|
||||
{
|
||||
return success({
|
||||
{ QStringLiteral("schema"), QStringLiteral("new-position.v1") },
|
||||
{ QStringLiteral("status"), QStringLiteral("ok") },
|
||||
{ QStringLiteral("canSubmit"), true },
|
||||
{ QStringLiteral("quoteHash"), quoteHash },
|
||||
{ QStringLiteral("requiresFreshLp"), requiresFreshLp },
|
||||
});
|
||||
}
|
||||
|
||||
AmmClientResult plan(const QJsonObject& request) const override
|
||||
{
|
||||
sawFreshLp = request.contains(QStringLiteral("freshLp"));
|
||||
return success({
|
||||
{ QStringLiteral("status"), QStringLiteral("ready") },
|
||||
{ QStringLiteral("accountIds"), QJsonArray { QStringLiteral("account") } },
|
||||
{ QStringLiteral("signingRequirements"), QJsonArray { true } },
|
||||
{ QStringLiteral("instruction"), QJsonArray { 1 } },
|
||||
{ QStringLiteral("programId"), QStringLiteral("program") },
|
||||
{ QStringLiteral("deadlineMs"),
|
||||
QString::number(QDateTime::currentMSecsSinceEpoch() + 60'000) },
|
||||
});
|
||||
}
|
||||
|
||||
static AmmClientResult success(const QJsonObject& value)
|
||||
{
|
||||
return { true, value };
|
||||
}
|
||||
|
||||
QString quoteHash = QStringLiteral("sha256:expected");
|
||||
bool requiresFreshLp = true;
|
||||
mutable bool sawFreshLp = false;
|
||||
};
|
||||
|
||||
ActiveNetworkSnapshot readyNetwork()
|
||||
{
|
||||
return {
|
||||
QStringLiteral("testnet"),
|
||||
QStringLiteral("ready"),
|
||||
QStringLiteral("block10:identity"),
|
||||
QStringLiteral("program"),
|
||||
{},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
const QVariantMap request {
|
||||
{ QStringLiteral("schema"), QStringLiteral("new-position.v1") },
|
||||
{ QStringLiteral("tokenAId"), QStringLiteral("token-a") },
|
||||
{ QStringLiteral("tokenBId"), QStringLiteral("token-b") },
|
||||
{ QStringLiteral("feeBps"), 30 },
|
||||
};
|
||||
|
||||
FakeWallet wallet;
|
||||
FakeAmmClient client;
|
||||
NewPositionRuntime runtime(&wallet, &client);
|
||||
const QVariantMap result = runtime.submit(
|
||||
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||
if (!expect(result.value(QStringLiteral("status")).toString()
|
||||
== QStringLiteral("submitted"),
|
||||
"valid plan should submit"))
|
||||
return 1;
|
||||
if (!expect(result.value(QStringLiteral("transactionId")).toString()
|
||||
== QStringLiteral("1thX6LZfHDZZKUs92febYZhYRcXddmzfzF2NvTkPNE"),
|
||||
"native hash should become the expected base58 transaction ID"))
|
||||
return 1;
|
||||
if (!expect(wallet.createdAccounts == 1 && client.sawFreshLp,
|
||||
"fresh LP account should enter the plan"))
|
||||
return 1;
|
||||
if (!expect(wallet.submissions == 1
|
||||
&& wallet.submitted.accountIds
|
||||
== QStringList { QStringLiteral("account") }
|
||||
&& wallet.submitted.signingRequirements.size() == 1
|
||||
&& wallet.submitted.signingRequirements.constFirst()
|
||||
&& wallet.submitted.instruction.size() == 1
|
||||
&& wallet.submitted.instruction.constFirst() == 1
|
||||
&& wallet.submitted.programId == QStringLiteral("program"),
|
||||
"runtime should dispatch the unchanged plan once"))
|
||||
return 1;
|
||||
|
||||
FakeWallet orderedBytesWallet;
|
||||
orderedBytesWallet.transactionHash = QStringLiteral(
|
||||
"0102030405060708090a0b0c0d0e0f10"
|
||||
"1112131415161718191a1b1c1d1e1f20");
|
||||
FakeAmmClient orderedBytesClient;
|
||||
orderedBytesClient.requiresFreshLp = false;
|
||||
NewPositionRuntime orderedBytesRuntime(&orderedBytesWallet, &orderedBytesClient);
|
||||
const QVariantMap orderedBytes = orderedBytesRuntime.submit(
|
||||
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||
if (!expect(orderedBytes.value(QStringLiteral("transactionId")).toString()
|
||||
== QStringLiteral("4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw"),
|
||||
"ordered native hash bytes should preserve byte order"))
|
||||
return 1;
|
||||
|
||||
FakeWallet invalidHashWallet;
|
||||
invalidHashWallet.transactionHash = QStringLiteral("not-a-hash");
|
||||
FakeAmmClient invalidHashClient;
|
||||
invalidHashClient.requiresFreshLp = false;
|
||||
NewPositionRuntime invalidHashRuntime(&invalidHashWallet, &invalidHashClient);
|
||||
const QVariantMap invalidHash = invalidHashRuntime.submit(
|
||||
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||
if (!expect(invalidHash.value(QStringLiteral("code")).toString()
|
||||
== QStringLiteral("wallet_submission_failed")
|
||||
&& !invalidHash.contains(QStringLiteral("transactionId")),
|
||||
"invalid native hash should fail without a hex fallback"))
|
||||
return 1;
|
||||
if (!expect(invalidHashWallet.submissions == 1,
|
||||
"hash conversion should happen after wallet submission"))
|
||||
return 1;
|
||||
|
||||
FakeWallet staleWallet;
|
||||
FakeAmmClient staleClient;
|
||||
staleClient.quoteHash = QStringLiteral("sha256:changed");
|
||||
NewPositionRuntime staleRuntime(&staleWallet, &staleClient);
|
||||
const QVariantMap stale = staleRuntime.submit(
|
||||
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||
if (!expect(stale.value(QStringLiteral("code")).toString()
|
||||
== QStringLiteral("quote_changed"),
|
||||
"changed quote should stop submission"))
|
||||
return 1;
|
||||
if (!expect(staleWallet.createdAccounts == 0 && staleWallet.submissions == 0,
|
||||
"stale quote should have no wallet side effects"))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user