diff --git a/apps/amm/qml/components/liquidity/NewPositionForm.qml b/apps/amm/qml/components/liquidity/NewPositionForm.qml index 0c81dbe..5da13b8 100644 --- a/apps/amm/qml/components/liquidity/NewPositionForm.qml +++ b/apps/amm/qml/components/liquidity/NewPositionForm.qml @@ -1300,6 +1300,7 @@ AmmActionCard { "wallet_unavailable": qsTr("Wallet is unavailable."), "wallet_syncing": qsTr("Wallet is still syncing. Review the quote while it finishes."), "wallet_submission_failed": qsTr("Wallet submission failed. Review and retry manually."), + "fresh_lp_account_in_use": qsTr("A new LP account is already in use. No transaction was submitted. Retry to use a different account."), "submission_status_unknown": qsTr("Connection was lost before submission status was known. Check wallet activity before retrying."), "signature_rejected": qsTr("Wallet approval was rejected."), "quote_changed": qsTr("Pool or wallet state changed. Review the refreshed quote."), diff --git a/apps/amm/src/NewPositionRuntime.cpp b/apps/amm/src/NewPositionRuntime.cpp index 313e037..62fdec0 100644 --- a/apps/amm/src/NewPositionRuntime.cpp +++ b/apps/amm/src/NewPositionRuntime.cpp @@ -873,7 +873,7 @@ void NewPositionRuntime::validatePendingFreshLpAsync( guard->clearPendingFreshLp(); guard->finishSubmit( submitGeneration, - publicError(QStringLiteral("submission_status_unknown")).toVariantMap()); + publicError(QStringLiteral("fresh_lp_account_in_use")).toVariantMap()); return; } guard->submitPlanAsync( @@ -1128,7 +1128,7 @@ QVariantMap NewPositionRuntime::submit(const QVariantMap& request, return publicError(QStringLiteral("account_read_failed")).toVariantMap(); if (!isDefaultAccountRead(read, freshLpAccountId)) { clearPendingFreshLp(); - return publicError(QStringLiteral("submission_status_unknown")).toVariantMap(); + return publicError(QStringLiteral("fresh_lp_account_in_use")).toVariantMap(); } freshLp = accountReadJson(read); } diff --git a/apps/amm/tests/cpp/NewPositionRuntimeTest.cpp b/apps/amm/tests/cpp/NewPositionRuntimeTest.cpp index 24a1537..14e97b3 100644 --- a/apps/amm/tests/cpp/NewPositionRuntimeTest.cpp +++ b/apps/amm/tests/cpp/NewPositionRuntimeTest.cpp @@ -639,7 +639,7 @@ int main(int argc, char** argv) if (!expect(reservedAccount.value(QStringLiteral("code")).toString() == QStringLiteral("backend_error") && nonDefaultAccount.value(QStringLiteral("code")).toString() - == QStringLiteral("submission_status_unknown") + == QStringLiteral("fresh_lp_account_in_use") && replacementAccount.value(QStringLiteral("status")).toString() == QStringLiteral("submitted") && nonDefaultWallet.createdAccounts == 2, @@ -1425,6 +1425,45 @@ int main(int argc, char** argv) "sequencer should validate a newly created LP account")) return 1; + FakeWallet occupiedFreshLpWallet; + occupiedFreshLpWallet.failCreatedPublicRead = true; + FakeAmmClient occupiedFreshLpClient; + occupiedFreshLpClient.normalizedBalanceHex = QString(31, QLatin1Char('0')) + + QLatin1Char('1'); + SequencerClient occupiedFreshLpSequencer(&occupiedFreshLpClient); + if (!expect(occupiedFreshLpSequencer.configure(sequencerConfig.fileName()), + "occupied fresh-LP sequencer should configure")) + return 1; + NewPositionRuntime occupiedFreshLpRuntime( + &occupiedFreshLpWallet, &occupiedFreshLpClient, &occupiedFreshLpSequencer); + QVariantMap occupiedFreshLpResult; + occupiedFreshLpRuntime.submitAsync( + request, QStringLiteral("sha256:expected"), readyNetwork(), true, + [&](QVariantMap result) { + occupiedFreshLpResult = std::move(result); + }); + if (!expect(waitForCondition([&]() { return !occupiedFreshLpResult.isEmpty(); }), + "occupied fresh LP account should be rejected")) + return 1; + occupiedFreshLpWallet.failCreatedPublicRead = false; + QVariantMap occupiedFreshLpRetry; + occupiedFreshLpRuntime.submitAsync( + request, QStringLiteral("sha256:expected"), readyNetwork(), true, + [&](QVariantMap result) { + occupiedFreshLpRetry = std::move(result); + }); + if (!expect(waitForCondition([&]() { return !occupiedFreshLpRetry.isEmpty(); }), + "fresh LP retry should complete")) + return 1; + if (!expect(occupiedFreshLpResult.value(QStringLiteral("code")).toString() + == QStringLiteral("fresh_lp_account_in_use") + && occupiedFreshLpWallet.submissions == 1 + && occupiedFreshLpRetry.value(QStringLiteral("status")).toString() + == QStringLiteral("submitted") + && occupiedFreshLpWallet.createdAccounts == 2, + "occupied fresh LP account should not submit and should be replaced")) + return 1; + FakeWallet cancelledSubmissionWallet; cancelledSubmissionWallet.deferSubmission = true; cancelledSubmissionWallet.submissionFailuresRemaining = 1; diff --git a/apps/amm/tests/qml/tst_NewPositionForm.qml b/apps/amm/tests/qml/tst_NewPositionForm.qml index 530d8b9..c66a74f 100644 --- a/apps/amm/tests/qml/tst_NewPositionForm.qml +++ b/apps/amm/tests/qml/tst_NewPositionForm.qml @@ -728,6 +728,13 @@ TestCase { verify(form.contextBlocksForm()) } + function test_freshLpAccountInUseExplainsSafeRetry() { + var form = createForm() + + compare(form.issueText("fresh_lp_account_in_use"), + "A new LP account is already in use. No transaction was submitted. Retry to use a different account.") + } + function test_submittedBase58TransactionIdIsCopied() { var state = flowState(({})) state.transactionId = submittedTransactionId