fix: Tier-2 involved mediums — send confirm re-validation + console JSON errors

- Send: re-validate against LIVE state at the "Confirm & Send" click (connected,
  not syncing, amount > 0, total <= available). The confirm dialog persists
  across frames, so a balance drop / sync restart / fee bump after Review could
  otherwise broadcast a now-invalid transaction; it now shows a clear message
  instead of sending.
- Console: a malformed JSON argument ({...}/[...] that fails to parse) was
  silently sent to the daemon as a plain string. Now the command fails with a
  clear "Argument N is not valid JSON" line instead of being silently mangled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-05 14:00:16 -05:00
parent b2a8c4487e
commit dd9bc0bd2b
4 changed files with 24 additions and 2 deletions

View File

@@ -794,9 +794,20 @@ void RenderSendConfirmPopup(App* app) {
Type().text(TypeStyle::Body2, TR("sending"));
} else {
if (TactileButton(TR("confirm_and_send"), ImVec2(S.button("tabs.send", "confirm-button").width, std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "confirm-button").font))) {
// Re-validate against LIVE state — the confirm dialog persists across frames, so the
// balance could have dropped or sync (re)started (or the fee bumped total over available)
// since Review. Don't broadcast a now-invalid transaction.
const auto& liveState = app->getWalletState();
double liveAvail = GetAvailableBalance(app);
if (!app->isConnected() || liveState.sync.syncing || s_amount <= 0 ||
(s_amount + s_fee) > liveAvail) {
s_tx_status = "Cannot send now — check connection, sync, and available balance.";
s_status_success = false;
s_status_timestamp = ImGui::GetTime();
}
// A locked encrypted lite wallet can't spend; prompt to unlock instead of sending
// (the backend would otherwise reject with "Wallet is locked").
if (app->liteWallet() && app->getWalletState().isLocked()) {
else if (app->liteWallet() && app->getWalletState().isLocked()) {
app->requestLiteUnlock();
} else {
s_sending = true;