aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-24 14:01:39 -0700
committerAdam Soltys <[email protected]>2026-06-24 14:01:39 -0700
commit1a92d81b792f9e6d2ecb284aab979985a36d34c7 (patch)
treee5b358bcad0ba648363f373f9aa87e4c2f65045a
parentb78b9a1c7e4e42b7f673b0e3f92424a762bd0986 (diff)
downloadcoinosv3-1a92d81b792f9e6d2ecb284aab979985a36d34c7.tar.gz
coinosv3-1a92d81b792f9e6d2ecb284aab979985a36d34c7.zip
Keep reclaimed gifts revocable until the coin is spent
Passive reclaim previously dropped the coin from tracking, so there was no way to revoke afterward. Now reclaim moves the coin from "reserved" to a "reclaimed" set: it becomes spendable, but its still-live link stays listed (tagged "still claimable") with a Revoke option, until the coin is actually spent (by a payment or by revoking), at which point it drops off. revokeGift and outstandingGifts handle both sets; existing reserved gifts are unaffected. Strings updated across all 19 locales. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
-rw-r--r--src/app.js18
-rw-r--r--src/i18n.js4
-rw-r--r--src/locales/ar.json6
-rw-r--r--src/locales/bn.json6
-rw-r--r--src/locales/de.json6
-rw-r--r--src/locales/es.json6
-rw-r--r--src/locales/fr.json6
-rw-r--r--src/locales/hi.json6
-rw-r--r--src/locales/id.json6
-rw-r--r--src/locales/ja.json6
-rw-r--r--src/locales/ko.json6
-rw-r--r--src/locales/mr.json6
-rw-r--r--src/locales/pt.json6
-rw-r--r--src/locales/ru.json6
-rw-r--r--src/locales/sw.json6
-rw-r--r--src/locales/ta.json6
-rw-r--r--src/locales/te.json6
-rw-r--r--src/locales/tr.json6
-rw-r--r--src/locales/ur.json6
-rw-r--r--src/locales/vi.json6
-rw-r--r--src/locales/zh.json6
-rw-r--r--src/wallet.js66
22 files changed, 134 insertions, 68 deletions
diff --git a/src/app.js b/src/app.js
index 0ef0787..07fe347 100644
--- a/src/app.js
+++ b/src/app.js
@@ -722,7 +722,7 @@ function createGiftLink() {
render();
}
function giftCard() {
- const active = wallet.activeGifts();
+ const active = wallet.outstandingGifts();
return h(
'div',
{ class: 'card col' },
@@ -750,24 +750,24 @@ function giftCard() {
active.length
? h('div', { class: 'col gap6', style: 'margin-top:4px' },
h('span', { class: 'small muted' }, t('giftReserved', { n: active.length })),
- ...active.map((id) => {
- const u = wallet.utxos.find((x) => utxoId(x) === id);
- const label = u ? fmtAmount(u.value) + ' ' + unitLabel() : id.slice(0, 12) + '…';
- if (ui.revokeId === id) {
+ ...active.map((g) => {
+ const amt = g.value != null ? fmtAmount(g.value) + ' ' + unitLabel() : g.id.slice(0, 12) + '…';
+ const label = amt + (g.reserved ? '' : ' · ' + t('giftReclaimedTag'));
+ if (ui.revokeId === g.id) {
return h('div', { class: 'col gap6' },
- h('span', { class: 'small muted' }, t('giftReclaimPrompt')),
+ h('span', { class: 'small muted' }, g.reserved ? t('giftReclaimPrompt') : t('giftRevokeConfirm')),
ui.busy
? h('button', { class: 'btn-primary btn-block', disabled: true }, h('span', { class: 'spinner' }))
: h('div', { class: 'row gap6' },
- h('button', { class: 'btn-ghost grow', onClick: () => doReclaim(id) }, t('giftReclaim')),
- h('button', { class: 'btn-primary grow', onClick: () => doRevoke(id) }, t('giftRevoke'))
+ g.reserved ? h('button', { class: 'btn-ghost grow', onClick: () => doReclaim(g.id) }, t('giftReclaim')) : null,
+ h('button', { class: 'btn-primary grow', onClick: () => doRevoke(g.id) }, t('giftRevoke'))
),
ui.busy ? null : h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.revokeId = null; render(); } }, t('back'))
);
}
return h('div', { class: 'row between' },
h('span', { class: 'small mono' }, label),
- h('button', { class: 'btn-sm', onClick: () => { ui.revokeId = id; render(); } }, t('giftReclaim'))
+ h('button', { class: 'btn-sm', onClick: () => { ui.revokeId = g.id; render(); } }, g.reserved ? t('giftReclaim') : t('giftRevoke'))
);
})
)
diff --git a/src/i18n.js b/src/i18n.js
index 48c2e47..57475dc 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -153,11 +153,13 @@ const STR = {
giftAmountLabel: 'Amount to gift',
giftAmountInvalid: 'Enter a valid amount.',
giftAnother: 'Create another',
- giftReserved: '{n} coin set aside for unclaimed gifts',
+ giftReserved: 'Unclaimed gift links ({n})',
giftReclaim: 'Reclaim',
giftReclaimPrompt: 'Reclaim frees this coin for your next payment, but the link stays claimable until you spend it. Pay a network fee to revoke it right now instead?',
giftReclaimed: 'Coin reclaimed',
+ giftReclaimedTag: 'still claimable',
giftRevoke: 'Revoke now',
+ giftRevokeConfirm: 'Revoke this gift now? This spends the coin back into your wallet and disables the link.',
giftRevoked: 'Gift revoked',
copyLink: 'Copy link',
claimBody: 'Claim it into a brand-new wallet that only you control.',
diff --git a/src/locales/ar.json b/src/locales/ar.json
index b474efe..923e8d2 100644
--- a/src/locales/ar.json
+++ b/src/locales/ar.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "المبلغ المراد إهداؤه",
"giftAmountInvalid": "أدخل مبلغاً صحيحاً.",
"giftAnother": "إنشاء آخر",
- "giftReserved": "{n} عملة مخصّصة للهدايا غير المُطالَب بها",
+ "giftReserved": "روابط الهدايا غير المُطالب بها ({n})",
"claimBody": "طالب بها في محفظة جديدة تماماً تتحكم بها وحدك.",
"claimBtn": "طالب بها",
"claimFailed": "تمت المطالبة بهذه الهدية بالفعل أو لم تعد متاحة.",
@@ -169,5 +169,7 @@
"giftRevoked": "تم إلغاء الهدية",
"giftReclaim": "استرجاع",
"giftReclaimPrompt": "الاسترجاع يحرر هذه العملة لدفعتك التالية، لكن يظل بالإمكان المطالبة بالرابط حتى تنفقها. هل تدفع رسوم الشبكة لإلغائه الآن بدلاً من ذلك؟",
- "giftReclaimed": "تم استرجاع العملة"
+ "giftReclaimed": "تم استرجاع العملة",
+ "giftRevokeConfirm": "إلغاء هذه الهدية الآن؟ يعيد ذلك العملة إلى محفظتك ويعطّل الرابط.",
+ "giftReclaimedTag": "لا يزال قابلاً للمطالبة"
} \ No newline at end of file
diff --git a/src/locales/bn.json b/src/locales/bn.json
index 06e7fa8..04f87a5 100644
--- a/src/locales/bn.json
+++ b/src/locales/bn.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "উপহারের পরিমাণ",
"giftAmountInvalid": "একটি সঠিক পরিমাণ লিখুন।",
"giftAnother": "আরেকটি তৈরি করুন",
- "giftReserved": "{n} কয়েন অদাবিকৃত উপহারের জন্য সরিয়ে রাখা হয়েছে",
+ "giftReserved": "দাবিহীন উপহার লিঙ্ক ({n})",
"claimBody": "এটি একটি একদম নতুন ওয়ালেটে দাবি করুন, যা কেবল আপনিই নিয়ন্ত্রণ করেন।",
"claimBtn": "দাবি করুন",
"claimFailed": "এই উপহারটি আগেই দাবি করা হয়েছে অথবা আর পাওয়া যাচ্ছে না।",
@@ -167,5 +167,7 @@
"giftRevoked": "উপহার বাতিল হয়েছে",
"giftReclaim": "ফেরত নিন",
"giftReclaimPrompt": "ফেরত নিলে এই কয়েনটি আপনার পরবর্তী পেমেন্টের জন্য মুক্ত হয়, তবে আপনি খরচ না করা পর্যন্ত লিঙ্কটি দাবিযোগ্য থাকে। বদলে নেটওয়ার্ক ফি দিয়ে এখনই বাতিল করবেন?",
- "giftReclaimed": "কয়েন ফেরত নেওয়া হয়েছে"
+ "giftReclaimed": "কয়েন ফেরত নেওয়া হয়েছে",
+ "giftRevokeConfirm": "এই উপহার এখনই বাতিল করবেন? এটি কয়েনটি আপনার ওয়ালেটে ফেরত পাঠায় এবং লিঙ্কটি নিষ্ক্রিয় করে।",
+ "giftReclaimedTag": "এখনও দাবিযোগ্য"
} \ No newline at end of file
diff --git a/src/locales/de.json b/src/locales/de.json
index cc26c6a..ef9346f 100644
--- a/src/locales/de.json
+++ b/src/locales/de.json
@@ -159,7 +159,7 @@
"giftAmountLabel": "Zu verschenkender Betrag",
"giftAmountInvalid": "Gib einen gültigen Betrag ein.",
"giftAnother": "Weiteres erstellen",
- "giftReserved": "{n} Coin für unbeanspruchte Geschenke zurückgelegt",
+ "giftReserved": "Nicht eingelöste Geschenk-Links ({n})",
"claimBody": "Beanspruche es in einer brandneuen Wallet, die nur du selbst kontrollierst.",
"claimBtn": "Beanspruchen",
"claimFailed": "Dieses Geschenk wurde bereits beansprucht oder ist nicht mehr verfügbar.",
@@ -169,5 +169,7 @@
"giftRevoked": "Geschenk widerrufen",
"giftReclaim": "Zurückholen",
"giftReclaimPrompt": "Zurückholen gibt diese Münze für deine nächste Zahlung frei, aber der Link bleibt einlösbar, bis du sie ausgibst. Stattdessen eine Netzwerkgebühr zahlen, um ihn jetzt zu widerrufen?",
- "giftReclaimed": "Münze zurückgeholt"
+ "giftReclaimed": "Münze zurückgeholt",
+ "giftRevokeConfirm": "Dieses Geschenk jetzt widerrufen? Die Münze geht zurück in deine Wallet und der Link wird deaktiviert.",
+ "giftReclaimedTag": "noch einlösbar"
} \ No newline at end of file
diff --git a/src/locales/es.json b/src/locales/es.json
index 6fd33ac..4c5a214 100644
--- a/src/locales/es.json
+++ b/src/locales/es.json
@@ -159,7 +159,7 @@
"giftAmountLabel": "Importe a regalar",
"giftAmountInvalid": "Introduce un importe válido.",
"giftAnother": "Crear otro",
- "giftReserved": "{n} de monedas reservadas para regalos sin reclamar",
+ "giftReserved": "Enlaces de regalo sin reclamar ({n})",
"claimBody": "Reclámalo en una billetera totalmente nueva que solo tú controlas.",
"claimBtn": "Reclamarlo",
"claimFailed": "Este regalo ya ha sido reclamado o ya no está disponible.",
@@ -169,5 +169,7 @@
"giftRevoked": "Regalo revocado",
"giftReclaim": "Reclamar",
"giftReclaimPrompt": "Reclamar libera esta moneda para tu próximo pago, pero el enlace seguirá siendo reclamable hasta que la gastes. ¿Pagar una comisión de red para revocarlo ahora mismo?",
- "giftReclaimed": "Moneda reclamada"
+ "giftReclaimed": "Moneda reclamada",
+ "giftRevokeConfirm": "¿Revocar este regalo ahora? Esto devuelve la moneda a tu monedero y desactiva el enlace.",
+ "giftReclaimedTag": "aún reclamable"
} \ No newline at end of file
diff --git a/src/locales/fr.json b/src/locales/fr.json
index c5a9992..d4c100b 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -159,7 +159,7 @@
"giftAmountLabel": "Montant à offrir",
"giftAmountInvalid": "Saisissez un montant valide.",
"giftAnother": "En créer un autre",
- "giftReserved": "{n} de fonds mis de côté pour des cadeaux non réclamés",
+ "giftReserved": "Liens cadeaux non réclamés ({n})",
"claimBody": "Récupérez-les dans un tout nouveau portefeuille que vous seul contrôlez.",
"claimBtn": "Récupérer",
"claimFailed": "Ce cadeau a déjà été réclamé ou n’est plus disponible.",
@@ -169,5 +169,7 @@
"giftRevoked": "Cadeau révoqué",
"giftReclaim": "Récupérer",
"giftReclaimPrompt": "Récupérer libère cette pièce pour votre prochain paiement, mais le lien reste réclamable jusqu’à ce que vous la dépensiez. Payer des frais de réseau pour le révoquer tout de suite ?",
- "giftReclaimed": "Pièce récupérée"
+ "giftReclaimed": "Pièce récupérée",
+ "giftRevokeConfirm": "Révoquer ce cadeau maintenant ? La pièce revient dans votre portefeuille et le lien est désactivé.",
+ "giftReclaimedTag": "encore réclamable"
} \ No newline at end of file
diff --git a/src/locales/hi.json b/src/locales/hi.json
index 5378657..880841d 100644
--- a/src/locales/hi.json
+++ b/src/locales/hi.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "उपहार में देने की राशि",
"giftAmountInvalid": "एक मान्य राशि दर्ज करें।",
"giftAnother": "एक और बनाएँ",
- "giftReserved": "{n} सिक्का बिना दावे वाले उपहारों के लिए अलग रखा गया",
+ "giftReserved": "बिना दावे वाले उपहार लिंक ({n})",
"claimBody": "इसे एक बिल्कुल नए वॉलेट में प्राप्त करें जिसे केवल आप नियंत्रित करते हैं।",
"claimBtn": "इसे प्राप्त करें",
"claimFailed": "इस उपहार का दावा पहले ही किया जा चुका है या यह अब उपलब्ध नहीं है।",
@@ -169,5 +169,7 @@
"giftRevoked": "उपहार रद्द किया गया",
"giftReclaim": "वापस लें",
"giftReclaimPrompt": "वापस लेने से यह सिक्का आपके अगले भुगतान के लिए मुक्त हो जाता है, लेकिन जब तक आप इसे खर्च नहीं करते, लिंक दावा-योग्य रहता है। इसके बजाय अभी रद्द करने के लिए नेटवर्क शुल्क चुकाएं?",
- "giftReclaimed": "सिक्का वापस लिया गया"
+ "giftReclaimed": "सिक्का वापस लिया गया",
+ "giftRevokeConfirm": "इस उपहार को अभी रद्द करें? यह सिक्का आपके वॉलेट में वापस भेजता है और लिंक को निष्क्रिय कर देता है।",
+ "giftReclaimedTag": "अब भी दावा-योग्य"
} \ No newline at end of file
diff --git a/src/locales/id.json b/src/locales/id.json
index 47ff864..614b355 100644
--- a/src/locales/id.json
+++ b/src/locales/id.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "Jumlah yang dihadiahkan",
"giftAmountInvalid": "Masukkan jumlah yang valid.",
"giftAnother": "Buat lagi",
- "giftReserved": "{n} koin disisihkan untuk hadiah yang belum diklaim",
+ "giftReserved": "Tautan hadiah belum diklaim ({n})",
"claimBody": "Klaim ke dompet baru yang hanya Anda kendalikan.",
"claimBtn": "Klaim",
"claimFailed": "Hadiah ini sudah diklaim atau tidak lagi tersedia.",
@@ -169,5 +169,7 @@
"giftRevoked": "Hadiah dicabut",
"giftReclaim": "Tarik kembali",
"giftReclaimPrompt": "Menarik kembali membebaskan koin ini untuk pembayaran berikutnya, tetapi tautan tetap bisa diklaim sampai Anda membelanjakannya. Bayar biaya jaringan untuk mencabutnya sekarang?",
- "giftReclaimed": "Koin ditarik kembali"
+ "giftReclaimed": "Koin ditarik kembali",
+ "giftRevokeConfirm": "Cabut hadiah ini sekarang? Ini mengembalikan koin ke dompet Anda dan menonaktifkan tautannya.",
+ "giftReclaimedTag": "masih bisa diklaim"
} \ No newline at end of file
diff --git a/src/locales/ja.json b/src/locales/ja.json
index 9d63cd7..b0ab956 100644
--- a/src/locales/ja.json
+++ b/src/locales/ja.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "贈る金額",
"giftAmountInvalid": "有効な金額を入力してください。",
"giftAnother": "もう一つ作成",
- "giftReserved": "未受取のギフト用に {n} のコインを確保中",
+ "giftReserved": "未請求のギフトリンク({n})",
"claimBody": "あなただけが管理できる真新しいウォレットに受け取りましょう。",
"claimBtn": "受け取る",
"claimFailed": "このギフトはすでに受け取られたか、現在利用できません。",
@@ -169,5 +169,7 @@
"giftRevoked": "ギフトを取り消しました",
"giftReclaim": "取り戻す",
"giftReclaimPrompt": "取り戻すと次の支払いにこのコインを使えますが、使うまでリンクは請求可能なままです。代わりにネットワーク手数料を払って今すぐ取り消しますか?",
- "giftReclaimed": "コインを取り戻しました"
+ "giftReclaimed": "コインを取り戻しました",
+ "giftRevokeConfirm": "このギフトを今すぐ取り消しますか?コインがウォレットに戻り、リンクが無効になります。",
+ "giftReclaimedTag": "まだ請求可能"
} \ No newline at end of file
diff --git a/src/locales/ko.json b/src/locales/ko.json
index a42c2f0..21e6ad7 100644
--- a/src/locales/ko.json
+++ b/src/locales/ko.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "선물할 금액",
"giftAmountInvalid": "올바른 금액을 입력하세요.",
"giftAnother": "또 만들기",
- "giftReserved": "받지 않은 선물을 위해 따로 둔 코인 {n}개",
+ "giftReserved": "청구되지 않은 선물 링크 ({n})",
"claimBody": "자신만 통제하는 완전히 새로운 지갑으로 받으세요.",
"claimBtn": "받기",
"claimFailed": "이 선물은 이미 수령되었거나 더 이상 이용할 수 없습니다.",
@@ -169,5 +169,7 @@
"giftRevoked": "선물이 취소됨",
"giftReclaim": "회수",
"giftReclaimPrompt": "회수하면 이 코인을 다음 결제에 쓸 수 있지만, 코인을 쓰기 전까지 링크는 청구 가능합니다. 대신 네트워크 수수료를 내고 지금 바로 취소할까요?",
- "giftReclaimed": "코인 회수됨"
+ "giftReclaimed": "코인 회수됨",
+ "giftRevokeConfirm": "이 선물을 지금 취소할까요? 코인이 지갑으로 돌아가고 링크가 비활성화됩니다.",
+ "giftReclaimedTag": "아직 청구 가능"
} \ No newline at end of file
diff --git a/src/locales/mr.json b/src/locales/mr.json
index fa5c175..01a24b4 100644
--- a/src/locales/mr.json
+++ b/src/locales/mr.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "भेट देण्याची रक्कम",
"giftAmountInvalid": "वैध रक्कम टाका.",
"giftAnother": "आणखी एक तयार करा",
- "giftReserved": "{n} नाणी न घेतलेल्या भेटींसाठी बाजूला ठेवली",
+ "giftReserved": "दावा न केलेल्या भेट लिंक ({n})",
"claimBody": "ते फक्त तुमच्याच नियंत्रणातील एका नव्या कोऱ्या वॉलेटमध्ये घ्या.",
"claimBtn": "ते घ्या",
"claimFailed": "ही भेट आधीच घेतली गेली आहे किंवा आता उपलब्ध नाही.",
@@ -169,5 +169,7 @@
"giftRevoked": "भेट रद्द केली",
"giftReclaim": "परत घ्या",
"giftReclaimPrompt": "परत घेतल्याने हे नाणे तुमच्या पुढील पेमेंटसाठी मोकळे होते, पण तुम्ही ते खर्च करेपर्यंत लिंक दावायोग्य राहते. त्याऐवजी आत्ताच रद्द करण्यासाठी नेटवर्क शुल्क भरायचे?",
- "giftReclaimed": "नाणे परत घेतले"
+ "giftReclaimed": "नाणे परत घेतले",
+ "giftRevokeConfirm": "ही भेट आत्ता रद्द करायची? हे नाणे तुमच्या वॉलेटमध्ये परत पाठवते आणि लिंक निष्क्रिय करते.",
+ "giftReclaimedTag": "अजूनही दावायोग्य"
} \ No newline at end of file
diff --git a/src/locales/pt.json b/src/locales/pt.json
index 876a14e..3865e98 100644
--- a/src/locales/pt.json
+++ b/src/locales/pt.json
@@ -159,7 +159,7 @@
"giftAmountLabel": "Valor a presentear",
"giftAmountInvalid": "Digite um valor válido.",
"giftAnother": "Criar outro",
- "giftReserved": "{n} de moeda reservada para presentes não resgatados",
+ "giftReserved": "Links de presente não resgatados ({n})",
"claimBody": "Resgate para uma carteira novinha que só você controla.",
"claimBtn": "Resgatar",
"claimFailed": "Este presente já foi resgatado ou não está mais disponível.",
@@ -169,5 +169,7 @@
"giftRevoked": "Presente revogado",
"giftReclaim": "Recuperar",
"giftReclaimPrompt": "Recuperar libera esta moeda para o seu próximo pagamento, mas o link continua resgatável até você gastá-la. Pagar uma taxa de rede para revogá-lo agora?",
- "giftReclaimed": "Moeda recuperada"
+ "giftReclaimed": "Moeda recuperada",
+ "giftRevokeConfirm": "Revogar este presente agora? Isso devolve a moeda à sua carteira e desativa o link.",
+ "giftReclaimedTag": "ainda resgatável"
} \ No newline at end of file
diff --git a/src/locales/ru.json b/src/locales/ru.json
index 67f7caf..cc3910a 100644
--- a/src/locales/ru.json
+++ b/src/locales/ru.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "Сумма подарка",
"giftAmountInvalid": "Введите корректную сумму.",
"giftAnother": "Создать ещё один",
- "giftReserved": "{n} монет отложено для невостребованных подарков",
+ "giftReserved": "Невостребованные подарочные ссылки ({n})",
"claimBody": "Получите его в совершенно новый кошелёк, которым управляете только вы.",
"claimBtn": "Получить",
"claimFailed": "Этот подарок уже получен или больше недоступен.",
@@ -169,5 +169,7 @@
"giftRevoked": "Подарок отозван",
"giftReclaim": "Вернуть",
"giftReclaimPrompt": "Возврат освобождает монету для следующего платежа, но ссылку можно получить, пока вы её не потратите. Заплатить комиссию сети, чтобы отозвать прямо сейчас?",
- "giftReclaimed": "Монета возвращена"
+ "giftReclaimed": "Монета возвращена",
+ "giftRevokeConfirm": "Отозвать этот подарок сейчас? Монета вернётся в ваш кошелёк, а ссылка будет отключена.",
+ "giftReclaimedTag": "ещё можно получить"
} \ No newline at end of file
diff --git a/src/locales/sw.json b/src/locales/sw.json
index cc2e271..05864a7 100644
--- a/src/locales/sw.json
+++ b/src/locales/sw.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "Kiasi cha kutoa zawadi",
"giftAmountInvalid": "Ingiza kiasi sahihi.",
"giftAnother": "Tengeneza nyingine",
- "giftReserved": "{n} sarafu imewekwa kando kwa zawadi ambazo hazijadaiwa",
+ "giftReserved": "Viungo vya zawadi visivyodaiwa ({n})",
"claimBody": "Idai kwenye pochi mpya kabisa unayoidhibiti wewe pekee.",
"claimBtn": "Idai",
"claimFailed": "Zawadi hii tayari imedaiwa au haipatikani tena.",
@@ -169,5 +169,7 @@
"giftRevoked": "Zawadi imebatilishwa",
"giftReclaim": "Rudisha",
"giftReclaimPrompt": "Kurudisha hufungua sarafu hii kwa malipo yako yajayo, lakini kiungo kinabaki kinaweza kudaiwa hadi uitumie. Badala yake ulipe ada ya mtandao kuibatilisha sasa hivi?",
- "giftReclaimed": "Sarafu imerudishwa"
+ "giftReclaimed": "Sarafu imerudishwa",
+ "giftRevokeConfirm": "Batilisha zawadi hii sasa? Hii inarudisha sarafu kwenye pochi yako na kuzima kiungo.",
+ "giftReclaimedTag": "bado inaweza kudaiwa"
} \ No newline at end of file
diff --git a/src/locales/ta.json b/src/locales/ta.json
index 3870c3a..e94aa7e 100644
--- a/src/locales/ta.json
+++ b/src/locales/ta.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "பரிசளிக்கும் தொகை",
"giftAmountInvalid": "சரியான தொகையை உள்ளிடவும்.",
"giftAnother": "மற்றொன்றை உருவாக்கு",
- "giftReserved": "உரிமை கொள்ளப்படாத பரிசுகளுக்காக {n} நாணயம் ஒதுக்கப்பட்டுள்ளது",
+ "giftReserved": "கோரப்படாத பரிசு இணைப்புகள் ({n})",
"claimBody": "நீங்கள் மட்டுமே கட்டுப்படுத்தும் புத்தம்புதிய வாலட்டிற்குள் இதை உரிமை கொள்ளுங்கள்.",
"claimBtn": "உரிமை கொள்",
"claimFailed": "இந்தப் பரிசு ஏற்கனவே உரிமை கொள்ளப்பட்டுவிட்டது அல்லது இனி கிடைக்கவில்லை.",
@@ -169,5 +169,7 @@
"giftRevoked": "பரிசு திரும்பப் பெறப்பட்டது",
"giftReclaim": "திரும்பப் பெறு",
"giftReclaimPrompt": "திரும்பப் பெறுவது இந்த நாணயத்தை உங்கள் அடுத்த கட்டணத்திற்கு விடுவிக்கும், ஆனால் நீங்கள் அதைச் செலவழிக்கும் வரை இணைப்பு கோரத்தக்கதாக இருக்கும். அதற்குப் பதிலாக இப்போதே ரத்து செய்ய நெட்வொர்க் கட்டணம் செலுத்தவா?",
- "giftReclaimed": "நாணயம் திரும்பப் பெறப்பட்டது"
+ "giftReclaimed": "நாணயம் திரும்பப் பெறப்பட்டது",
+ "giftRevokeConfirm": "இந்த பரிசை இப்போதே ரத்து செய்யவா? இது நாணயத்தை உங்கள் வாலெட்டுக்குத் திருப்பி, இணைப்பை முடக்கும்.",
+ "giftReclaimedTag": "இன்னும் கோரத்தக்கது"
} \ No newline at end of file
diff --git a/src/locales/te.json b/src/locales/te.json
index a8752cd..2334a2d 100644
--- a/src/locales/te.json
+++ b/src/locales/te.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "బహుమతిగా ఇచ్చే మొత్తం",
"giftAmountInvalid": "సరైన మొత్తాన్ని నమోదు చేయండి.",
"giftAnother": "మరొకటి సృష్టించు",
- "giftReserved": "క్లెయిమ్ చేయని బహుమతుల కోసం {n} నాణేలు పక్కన పెట్టబడ్డాయి",
+ "giftReserved": "దావా చేయని బహుమతి లింకులు ({n})",
"claimBody": "మీరు మాత్రమే నియంత్రించే సరికొత్త వాలెట్‌లోకి దాన్ని క్లెయిమ్ చేసుకోండి.",
"claimBtn": "క్లెయిమ్ చేసుకోండి",
"claimFailed": "ఈ బహుమతి ఇప్పటికే క్లెయిమ్ చేయబడింది లేదా ఇక అందుబాటులో లేదు.",
@@ -167,5 +167,7 @@
"giftRevoked": "బహుమతి రద్దు చేయబడింది",
"giftReclaim": "తిరిగి తీసుకో",
"giftReclaimPrompt": "తిరిగి తీసుకోవడం ఈ నాణేన్ని మీ తదుపరి చెల్లింపు కోసం విడుదల చేస్తుంది, కానీ మీరు ఖర్చు చేసే వరకు లింక్ క్లెయిమ్ చేయదగినదిగా ఉంటుంది. బదులుగా ఇప్పుడే రద్దు చేయడానికి నెట్‌వర్క్ ఫీజు చెల్లించాలా?",
- "giftReclaimed": "నాణెం తిరిగి తీసుకోబడింది"
+ "giftReclaimed": "నాణెం తిరిగి తీసుకోబడింది",
+ "giftRevokeConfirm": "ఈ బహుమతిని ఇప్పుడే రద్దు చేయాలా? ఇది నాణేన్ని మీ వాలెట్‌కు తిరిగి పంపి, లింక్‌ను నిష్క్రియం చేస్తుంది.",
+ "giftReclaimedTag": "ఇంకా క్లెయిమ్ చేయదగినది"
} \ No newline at end of file
diff --git a/src/locales/tr.json b/src/locales/tr.json
index c2173f4..d4331a4 100644
--- a/src/locales/tr.json
+++ b/src/locales/tr.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "Hediye edilecek tutar",
"giftAmountInvalid": "Geçerli bir tutar girin.",
"giftAnother": "Bir tane daha oluştur",
- "giftReserved": "Talep edilmemiş hediyeler için {n} coin ayrıldı",
+ "giftReserved": "Talep edilmemiş hediye bağlantıları ({n})",
"claimBody": "Yalnızca kendi kontrolünüzdeki yepyeni bir cüzdana alın.",
"claimBtn": "Talep et",
"claimFailed": "Bu hediye zaten talep edilmiş veya artık kullanılamıyor.",
@@ -169,5 +169,7 @@
"giftRevoked": "Hediye iptal edildi",
"giftReclaim": "Geri al",
"giftReclaimPrompt": "Geri almak bu parayı bir sonraki ödemen için serbest bırakır, ancak sen harcayana kadar bağlantı talep edilebilir kalır. Bunun yerine şimdi iptal etmek için ağ ücreti ödensin mi?",
- "giftReclaimed": "Para geri alındı"
+ "giftReclaimed": "Para geri alındı",
+ "giftRevokeConfirm": "Bu hediye şimdi iptal edilsin mi? Bu, parayı cüzdanınıza geri gönderir ve bağlantıyı devre dışı bırakır.",
+ "giftReclaimedTag": "hâlâ talep edilebilir"
} \ No newline at end of file
diff --git a/src/locales/ur.json b/src/locales/ur.json
index de980dd..0d523d6 100644
--- a/src/locales/ur.json
+++ b/src/locales/ur.json
@@ -159,7 +159,7 @@
"giftAmountLabel": "تحفے کی رقم",
"giftAmountInvalid": "درست رقم درج کریں۔",
"giftAnother": "ایک اور بنائیں",
- "giftReserved": "{n} سکے غیر وصول شدہ تحائف کے لیے الگ رکھے گئے",
+ "giftReserved": "غیر دعویٰ شدہ تحفہ لنکس ({n})",
"claimBody": "اسے ایک بالکل نئے والیٹ میں وصول کریں جو صرف آپ کے کنٹرول میں ہو۔",
"claimBtn": "وصول کریں",
"claimFailed": "اس تحفے کا پہلے ہی دعویٰ کیا جا چکا ہے یا یہ اب دستیاب نہیں۔",
@@ -169,5 +169,7 @@
"giftRevoked": "تحفہ منسوخ ہو گیا",
"giftReclaim": "واپس لیں",
"giftReclaimPrompt": "واپس لینے سے یہ سکہ آپ کی اگلی ادائیگی کے لیے آزاد ہو جاتا ہے، لیکن جب تک آپ اسے خرچ نہیں کرتے لنک قابلِ وصول رہتا ہے۔ اس کے بجائے ابھی منسوخ کرنے کے لیے نیٹ ورک فیس ادا کریں؟",
- "giftReclaimed": "سکہ واپس لے لیا گیا"
+ "giftReclaimed": "سکہ واپس لے لیا گیا",
+ "giftRevokeConfirm": "اس تحفے کو ابھی منسوخ کریں؟ یہ سکہ آپ کے والیٹ میں واپس بھیجتا ہے اور لنک کو غیر فعال کر دیتا ہے۔",
+ "giftReclaimedTag": "اب بھی قابلِ وصول"
} \ No newline at end of file
diff --git a/src/locales/vi.json b/src/locales/vi.json
index 79f0a07..3e05446 100644
--- a/src/locales/vi.json
+++ b/src/locales/vi.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "Số tiền muốn tặng",
"giftAmountInvalid": "Nhập một số tiền hợp lệ.",
"giftAnother": "Tạo cái khác",
- "giftReserved": "{n} coin được để dành cho các món quà chưa nhận",
+ "giftReserved": "Liên kết quà chưa nhận ({n})",
"claimBody": "Nhận nó vào một ví hoàn toàn mới mà chỉ riêng bạn kiểm soát.",
"claimBtn": "Nhận ngay",
"claimFailed": "Món quà này đã được nhận hoặc không còn khả dụng.",
@@ -169,5 +169,7 @@
"giftRevoked": "Đã thu hồi quà",
"giftReclaim": "Thu lại",
"giftReclaimPrompt": "Thu lại giải phóng đồng coin này cho lần thanh toán tới, nhưng liên kết vẫn có thể nhận được cho đến khi bạn tiêu nó. Trả phí mạng để thu hồi ngay bây giờ thay vào đó?",
- "giftReclaimed": "Đã thu lại coin"
+ "giftReclaimed": "Đã thu lại coin",
+ "giftRevokeConfirm": "Thu hồi quà này ngay? Việc này chuyển đồng coin về ví của bạn và vô hiệu hóa liên kết.",
+ "giftReclaimedTag": "vẫn có thể nhận"
} \ No newline at end of file
diff --git a/src/locales/zh.json b/src/locales/zh.json
index 22a8058..c3a3d6e 100644
--- a/src/locales/zh.json
+++ b/src/locales/zh.json
@@ -157,7 +157,7 @@
"giftAmountLabel": "赠送金额",
"giftAmountInvalid": "请输入有效金额。",
"giftAnother": "再创建一个",
- "giftReserved": "{n} 枚币已预留给未领取的礼物",
+ "giftReserved": "未领取的礼物链接({n})",
"claimBody": "将它领取到一个仅由你自己掌控的全新钱包中。",
"claimBtn": "领取",
"claimFailed": "此礼物已被领取或不再可用。",
@@ -169,5 +169,7 @@
"giftRevoked": "礼物已撤销",
"giftReclaim": "收回",
"giftReclaimPrompt": "收回会释放这笔币用于你的下一次付款,但在你花掉它之前链接仍可被领取。改为支付网络手续费立即撤销?",
- "giftReclaimed": "已收回"
+ "giftReclaimed": "已收回",
+ "giftRevokeConfirm": "立即撤销此礼物?这会将这笔币退回你的钱包并使链接失效。",
+ "giftReclaimedTag": "仍可领取"
} \ No newline at end of file
diff --git a/src/wallet.js b/src/wallet.js
index b5945ff..ad890d3 100644
--- a/src/wallet.js
+++ b/src/wallet.js
@@ -120,7 +120,8 @@ export class Wallet {
this.api.offline = offline;
this._account = null;
this._addrCache.clear();
- this._reserved = null; // gift-reserved outpoints, lazy-loaded per wallet
+ this._reserved = null; // gift coins set aside from spending (lazy-loaded)
+ this._reclaimed = null; // gift coins freed for spending but link still live
this._savedAt = 0;
try {
if (this.mnemonic) this.nostr.load(this.mnemonic, this.passphrase);
@@ -759,33 +760,47 @@ export class Wallet {
return { hex: t.hex, txid: t.id, fee: pl.fee, oldFee: prep.oldFee, feeRate: pl.rate, outputs, replaces: prep.txid };
}
- // --- gift links (coins set aside as claimable presigned transactions) ----
- // Outpoints reserved for unclaimed gifts are persisted per-wallet and skipped
- // by normal coin selection so a send can't spend a gift out from under it.
+ // --- gift links (coins backing claimable presigned transactions) ---------
+ // A gift coin is either RESERVED (set aside, skipped by coin selection) or
+ // RECLAIMED (freed for spending, but its link stays live until the coin is
+ // actually spent). Both are "outstanding"; both are persisted per-wallet.
_giftKey() {
return this._cacheKey() + ':gift';
}
- reservedSet() {
- if (!this._reserved) {
- try {
- this._reserved = new Set(JSON.parse(localStorage.getItem(this._giftKey()) || '[]'));
- } catch {
- this._reserved = new Set();
- }
+ _reclaimedKey() {
+ return this._cacheKey() + ':giftfree';
+ }
+ _loadSet(key) {
+ try {
+ return new Set(JSON.parse(localStorage.getItem(key) || '[]'));
+ } catch {
+ return new Set();
}
+ }
+ reservedSet() {
+ if (!this._reserved) this._reserved = this._loadSet(this._giftKey());
return this._reserved;
}
+ reclaimedSet() {
+ if (!this._reclaimed) this._reclaimed = this._loadSet(this._reclaimedKey());
+ return this._reclaimed;
+ }
isReserved(id) {
return this.reservedSet().has(id);
}
_saveReserved() {
- try {
- localStorage.setItem(this._giftKey(), JSON.stringify([...this.reservedSet()]));
- } catch {}
+ try { localStorage.setItem(this._giftKey(), JSON.stringify([...this.reservedSet()])); } catch {}
}
+ _saveReclaimed() {
+ try { localStorage.setItem(this._reclaimedKey(), JSON.stringify([...this.reclaimedSet()])); } catch {}
+ }
+ // Passive reclaim: free the coin for spending but keep tracking the live link
+ // so it can still be revoked later (until the coin is spent).
unreserve(id) {
this.reservedSet().delete(id);
+ this.reclaimedSet().add(id);
this._saveReserved();
+ this._saveReclaimed();
}
// Truly revoke an unclaimed gift: spend its coin back into our own wallet,
@@ -795,16 +810,27 @@ export class Wallet {
const draft = this.buildTx({ recipients: [{ address: this.freshChange().address }], feeRate, coinIds: [id], sendMax: true });
const hexTx = this.sign(draft.tx);
const txid = await this.broadcast(hexTx);
- this.unreserve(id);
+ this.reservedSet().delete(id);
+ this.reclaimedSet().delete(id);
+ this._saveReserved();
+ this._saveReclaimed();
return txid;
}
- // Active gifts whose coin is still unspent (drops claimed/spent ones).
- activeGifts() {
+ // Outstanding gifts (reserved + reclaimed) whose coin is still unspent; prunes
+ // any whose coin has since been claimed/spent. { id, reserved, value }.
+ outstandingGifts() {
const live = new Set(this.utxos.map((u) => utxoId(u)));
+ const res = this.reservedSet();
+ const rec = this.reclaimedSet();
let changed = false;
- for (const id of [...this.reservedSet()]) if (!live.has(id)) { this.reservedSet().delete(id); changed = true; }
- if (changed) this._saveReserved();
- return [...this.reservedSet()];
+ for (const id of [...res]) if (!live.has(id)) { res.delete(id); changed = true; }
+ for (const id of [...rec]) if (!live.has(id)) { rec.delete(id); changed = true; }
+ if (changed) { this._saveReserved(); this._saveReclaimed(); }
+ return [...new Set([...res, ...rec])].map((id) => ({
+ id,
+ reserved: res.has(id),
+ value: (this.utxos.find((u) => utxoId(u) === id) || {}).value,
+ }));
}
// Build a SIGHASH_SINGLE-signed gift: input = a single coin, output0 = change