From 16602be964893aebac09e65b305c674c140438de Mon Sep 17 00:00:00 2001 From: 2ro <17595647+2ro@users.noreply.github.com> Date: Tue, 14 Jul 2026 21:45:38 -0400 Subject: [PATCH] goblin: bounce an approved pay request that races the wallet lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WalletTask::NostrPayRequest ran nostr_pay/nostr_receive with no lock check, so an Approve tapped just before locking could spend or receive seconds later while the wallet was locked. Guard the task entry: when locked, the request is left Pending in the store (still approvable after unlock) and fail_send() flips the send phase to FAILED, which the activity card already consumes — the spinner clears and the reason line explains the bounce. Adds goblin.lock.approve_locked to all 10 locales. (cherry picked from commit ee47343259f12314932976c193739a2e0e96521d) --- locales/de.yml | 1 + locales/en.yml | 1 + locales/es.yml | 1 + locales/fr.yml | 1 + locales/ja.yml | 1 + locales/ko.yml | 1 + locales/ru.yml | 1 + locales/tr.yml | 1 + locales/zh-CN.yml | 1 + locales/zh-TW.yml | 1 + src/wallet/wallet.rs | 10 ++++++++++ 11 files changed, 20 insertions(+) diff --git a/locales/de.yml b/locales/de.yml index 299d5be9..c491a994 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -775,6 +775,7 @@ goblin: subtitle: "Zahlungen kommen auch im gesperrten Zustand weiter an. Gib dein Passwort ein, um zu entsperren und dein Guthaben zu nutzen." unlock: "Entsperren" close_wallet: "Wallet schließen" + approve_locked: "Die Wallet wurde gesperrt, bevor dies bezahlt werden konnte. Es ist weiter ausstehend — genehmige es erneut." trusted_sites: title: "Vertrauenswürdige Seiten" intro: "Seiten, denen du in dieser Sitzung vertraut hast, können risikoarme Aktionen ohne Nachfrage signieren. Bei Geld wird immer gefragt. Beende jede Sitzung hier." diff --git a/locales/en.yml b/locales/en.yml index 14b67f1a..993961be 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -775,6 +775,7 @@ goblin: subtitle: "Payments keep arriving while locked. Enter your password to unlock and use your funds." unlock: "Unlock" close_wallet: "Close wallet" + approve_locked: "The wallet was locked before this could be paid. It's still pending — approve it again." trusted_sites: title: "Trusted Sites" intro: "Sites you trusted this session can sign low-risk actions without asking. Money always asks. End any session here." diff --git a/locales/es.yml b/locales/es.yml index 7deea9ab..6bde3fdb 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -775,6 +775,7 @@ goblin: subtitle: "Los pagos siguen llegando mientras está bloqueada. Ingresa tu contraseña para desbloquear y usar tus fondos." unlock: "Desbloquear" close_wallet: "Cerrar billetera" + approve_locked: "La billetera se bloqueó antes de que esto pudiera pagarse. Sigue pendiente — apruébalo de nuevo." trusted_sites: title: "Sitios de confianza" intro: "Los sitios en los que confiaste esta sesión pueden firmar acciones de bajo riesgo sin preguntar. El dinero siempre pregunta. Termina cualquier sesión aquí." diff --git a/locales/fr.yml b/locales/fr.yml index 75f247fa..f818d8f0 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -775,6 +775,7 @@ goblin: subtitle: "Les paiements continuent d'arriver pendant le verrouillage. Saisissez votre mot de passe pour déverrouiller et utiliser vos fonds." unlock: "Déverrouiller" close_wallet: "Fermer le portefeuille" + approve_locked: "Le portefeuille a été verrouillé avant que ce paiement puisse être effectué. Il est toujours en attente — approuvez-le à nouveau." trusted_sites: title: "Sites de confiance" intro: "Les sites auxquels vous avez fait confiance cette session peuvent signer des actions à faible risque sans demander. L'argent demande toujours. Terminez toute session ici." diff --git a/locales/ja.yml b/locales/ja.yml index f4232402..f4188e8e 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -775,6 +775,7 @@ goblin: subtitle: "ロック中も支払いは届き続けます。パスワードを入力してロックを解除し、残高を使えるようにします。" unlock: "ロック解除" close_wallet: "ウォレットを閉じる" + approve_locked: "支払いが完了する前にウォレットがロックされました。リクエストは保留中のままです — もう一度承認してください。" trusted_sites: title: "信頼済みサイト" intro: "このセッションで信頼したサイトは、リスクの低い操作を確認なしで署名できます。お金が動く操作は必ず確認します。ここでいつでもセッションを終了できます。" diff --git a/locales/ko.yml b/locales/ko.yml index d6647650..f7051bef 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -775,6 +775,7 @@ goblin: subtitle: "잠긴 동안에도 결제는 계속 도착합니다. 비밀번호를 입력해 잠금을 풀고 자금을 사용하세요." unlock: "잠금 해제" close_wallet: "지갑 닫기" + approve_locked: "결제가 완료되기 전에 지갑이 잠겼습니다. 요청은 아직 대기 중입니다 — 다시 승인하세요." trusted_sites: title: "신뢰하는 사이트" intro: "이번 세션에 신뢰한 사이트는 확인 없이 위험도가 낮은 작업에 서명할 수 있습니다. 돈이 오갈 때는 항상 확인합니다. 여기에서 언제든 세션을 종료할 수 있습니다." diff --git a/locales/ru.yml b/locales/ru.yml index 207baf4e..178605b9 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -775,6 +775,7 @@ goblin: subtitle: "Платежи продолжают поступать, пока кошелёк заблокирован. Введите пароль, чтобы разблокировать и распоряжаться средствами." unlock: "Разблокировать" close_wallet: "Закрыть кошелёк" + approve_locked: "Кошелёк был заблокирован до того, как платёж прошёл. Запрос всё ещё ожидает — подтвердите его снова." trusted_sites: title: "Доверенные сайты" intro: "Сайты, которым вы доверились в этой сессии, могут подписывать действия с низким риском без запроса. Деньги всегда запрашивают подтверждение. Завершите любую сессию здесь." diff --git a/locales/tr.yml b/locales/tr.yml index f810e1a5..b516386c 100644 --- a/locales/tr.yml +++ b/locales/tr.yml @@ -775,6 +775,7 @@ goblin: subtitle: "Kilitliyken ödemeler gelmeye devam eder. Kilidi açıp bakiyeni kullanmak için parolanı gir." unlock: "Kilidi aç" close_wallet: "Cüzdanı kapat" + approve_locked: "Ödeme yapılamadan cüzdan kilitlendi. İstek hâlâ beklemede — yeniden onayla." trusted_sites: title: "Güvenilen Siteler" intro: "Bu oturumda güvendiğiniz siteler, düşük riskli eylemleri sormadan imzalayabilir. Para her zaman sorar. Herhangi bir oturumu buradan sonlandırın." diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index f40d7a39..9db141c5 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -775,6 +775,7 @@ goblin: subtitle: "锁定期间付款仍会到账。输入密码即可解锁并使用资金。" unlock: "解锁" close_wallet: "关闭钱包" + approve_locked: "付款完成前钱包已被锁定。请求仍在等待中——请重新批准。" trusted_sites: title: "受信任网站" intro: "本次会话中你信任的网站可在不询问的情况下签署低风险操作。资金操作始终询问。可在此结束任意会话。" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 04122b71..96941ee6 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -775,6 +775,7 @@ goblin: subtitle: "鎖定期間付款仍會到帳。輸入密碼即可解鎖並使用資金。" unlock: "解鎖" close_wallet: "關閉錢包" + approve_locked: "付款完成前錢包已被鎖定。請求仍在等待中——請重新批准。" trusted_sites: title: "受信任網站" intro: "本次會話中你信任的網站可在不詢問的情況下籤署低風險操作。資金操作始終詢問。可在此結束任意會話。" diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index 8b75527b..4127864c 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -3542,6 +3542,16 @@ async fn handle_task(w: &Wallet, t: WalletTask) { error!("nostr pay: request is not pending"); return; } + // Money-path lock guard: an Approve tapped just before (or racing) a + // lock must not spend or receive while locked. Bounce it the way the + // ingest gate defers — the request stays Pending in the store (still + // approvable after unlock) and the FAILED phase un-greys the card and + // surfaces why, instead of half-executing a seed operation. + if w.is_locked() { + info!("nostr pay: wallet locked, bouncing request back to pending"); + service.fail_send(t!("goblin.lock.approve_locked")); + return; + } // Drive the approve button's busy/failed state so it doesn't stay // greyed forever if the pay can't go through. service.set_send_phase(crate::nostr::send_phase::WORKING);