- Paying
+ {{ $t("PayInvoiceDialog.invoice.paying") }}
{{
payInvoiceData.meltQuote.response &&
payInvoiceData.meltQuote.response.amount > 0
diff --git a/src/i18n/ar-SA/index.ts b/src/i18n/ar-SA/index.ts
index be07e5802..6c6986a08 100644
--- a/src/i18n/ar-SA/index.ts
+++ b/src/i18n/ar-SA/index.ts
@@ -1345,6 +1345,9 @@ export default {
},
invoice: {
title: "دفع { value }",
+ paying: "جاري الدفع",
+ paid: "تم الدفع",
+ fee: "الرسوم",
memo: {
label: "مذكرة",
},
diff --git a/src/i18n/de-DE/index.ts b/src/i18n/de-DE/index.ts
index 4e83150b7..0b635131b 100644
--- a/src/i18n/de-DE/index.ts
+++ b/src/i18n/de-DE/index.ts
@@ -1362,6 +1362,9 @@ export default {
},
invoice: {
title: "{ value } bezahlen",
+ paying: "Wird bezahlt",
+ paid: "Bezahlt",
+ fee: "Gebühr",
memo: {
label: "Memo",
},
diff --git a/src/i18n/el-GR/index.ts b/src/i18n/el-GR/index.ts
index 59493940a..02a5db72f 100644
--- a/src/i18n/el-GR/index.ts
+++ b/src/i18n/el-GR/index.ts
@@ -1359,6 +1359,9 @@ export default {
},
invoice: {
title: "Πληρωμή { value }",
+ paying: "Πληρώνεται",
+ paid: "Πληρώθηκε",
+ fee: "Χρέωση",
memo: {
label: "Σημείωμα",
},
diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts
index 6c02904b8..a41579537 100644
--- a/src/i18n/en-US/index.ts
+++ b/src/i18n/en-US/index.ts
@@ -1417,6 +1417,9 @@ export default {
},
invoice: {
title: "Pay { value }",
+ paying: "Paying",
+ paid: "Paid",
+ fee: "Fee",
memo: {
label: "Memo",
},
diff --git a/src/i18n/es-ES/index.ts b/src/i18n/es-ES/index.ts
index 125945726..09c9f41eb 100644
--- a/src/i18n/es-ES/index.ts
+++ b/src/i18n/es-ES/index.ts
@@ -1356,6 +1356,9 @@ export default {
},
invoice: {
title: "Pagar { value }",
+ paying: "Pagando",
+ paid: "Pagado",
+ fee: "Tarifa",
memo: {
label: "Memo",
},
diff --git a/src/i18n/fr-FR/index.ts b/src/i18n/fr-FR/index.ts
index a165bdb0a..7f548c24b 100644
--- a/src/i18n/fr-FR/index.ts
+++ b/src/i18n/fr-FR/index.ts
@@ -1362,6 +1362,9 @@ export default {
},
invoice: {
title: "Payer { value }",
+ paying: "Paiement en cours",
+ paid: "Payé",
+ fee: "Frais",
memo: {
label: "Mémo",
},
diff --git a/src/i18n/it-IT/index.ts b/src/i18n/it-IT/index.ts
index 2c2f860ea..753b38774 100644
--- a/src/i18n/it-IT/index.ts
+++ b/src/i18n/it-IT/index.ts
@@ -1326,6 +1326,9 @@ export default {
},
invoice: {
title: "Paga { value }",
+ paying: "Pagamento in corso",
+ paid: "Pagato",
+ fee: "Commissione",
memo: {
label: "Memo",
},
diff --git a/src/i18n/ja-JP/index.ts b/src/i18n/ja-JP/index.ts
index f02c86f7a..218357207 100644
--- a/src/i18n/ja-JP/index.ts
+++ b/src/i18n/ja-JP/index.ts
@@ -1349,6 +1349,9 @@ export default {
},
invoice: {
title: "{ value }を支払う",
+ paying: "支払い中",
+ paid: "支払い済み",
+ fee: "手数料",
memo: {
label: "メモ",
},
diff --git a/src/i18n/sv-SE/index.ts b/src/i18n/sv-SE/index.ts
index 3ea8d4958..bd983940f 100644
--- a/src/i18n/sv-SE/index.ts
+++ b/src/i18n/sv-SE/index.ts
@@ -1352,6 +1352,9 @@ export default {
},
invoice: {
title: "Betala { value }",
+ paying: "Betalar",
+ paid: "Betald",
+ fee: "Avgift",
memo: {
label: "Memo",
},
diff --git a/src/i18n/th-TH/index.ts b/src/i18n/th-TH/index.ts
index f63a24204..25cece36e 100644
--- a/src/i18n/th-TH/index.ts
+++ b/src/i18n/th-TH/index.ts
@@ -1348,6 +1348,9 @@ export default {
},
invoice: {
title: "ชำระเงิน { value }",
+ paying: "กำลังชำระเงิน",
+ paid: "ชำระแล้ว",
+ fee: "ค่าธรรมเนียม",
memo: {
label: "บันทึก",
},
diff --git a/src/i18n/tr-TR/index.ts b/src/i18n/tr-TR/index.ts
index 9ba8407e3..e93567ff6 100644
--- a/src/i18n/tr-TR/index.ts
+++ b/src/i18n/tr-TR/index.ts
@@ -1363,6 +1363,9 @@ export default {
},
invoice: {
title: "{ value } öde",
+ paying: "Ödeniyor",
+ paid: "Ödendi",
+ fee: "Ücret",
memo: {
label: "Memo",
},
diff --git a/src/i18n/zh-CN/index.ts b/src/i18n/zh-CN/index.ts
index 07cce95a6..b895786b7 100644
--- a/src/i18n/zh-CN/index.ts
+++ b/src/i18n/zh-CN/index.ts
@@ -1342,6 +1342,9 @@ export default {
},
invoice: {
title: "支付 { value }",
+ paying: "支付中",
+ paid: "已支付",
+ fee: "费用",
memo: {
label: "备忘录",
},
From a31eb74198daadc14d11f22a50c201f872956fc4 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 22 Nov 2025 22:02:05 +0100
Subject: [PATCH 11/73] fix swap amount
---
src/stores/swap.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/stores/swap.ts b/src/stores/swap.ts
index f60b230ad..dff0901ec 100644
--- a/src/stores/swap.ts
+++ b/src/stores/swap.ts
@@ -101,13 +101,13 @@ export const useSwapStore = defineStore("swap", {
const fromMintUrl = token.getMint(tokenJson);
const unit = token.getUnit(tokenJson);
const tokenAmount = proofsStore.sumProofs(token.getProofs(tokenJson));
- let meltAmount = tokenAmount - Math.max(2, tokenAmount * 0.02);
+ let meltAmount = tokenAmount - Math.max(2, Math.ceil(tokenAmount * 0.02));
try {
// walletStore.mintWallet(fromMintUrl, unit); will fail if we don't have fromMintUrl yet
const fromWallet = walletStore.mintWallet(fromMintUrl, unit);
const proofs = token.getProofs(tokenJson);
meltAmount -= fromWallet.getFeesForProofs(proofs);
- } catch (e) {}
+ } catch (e) { }
return tokenAmount - meltAmount;
},
meltProofsToMint: async function (tokenJson: Token, mint: Mint) {
@@ -121,7 +121,7 @@ export const useSwapStore = defineStore("swap", {
this.swapBlocking = true;
try {
const tokenAmount = proofsStore.sumProofs(token.getProofs(tokenJson));
- let meltAmount = tokenAmount - Math.max(2, tokenAmount * 0.02);
+ let meltAmount = tokenAmount - Math.max(2, Math.ceil(tokenAmount * 0.02));
const unit = token.getUnit(tokenJson);
const fromMintUrl = token.getMint(tokenJson);
const fromWallet = walletStore.mintWallet(fromMintUrl, unit);
From 80902a86cd2ea2bcce8633adc93d433dfc4b7a9f Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 22 Nov 2025 22:03:11 +0100
Subject: [PATCH 12/73] fix swap
---
src/stores/swap.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/stores/swap.ts b/src/stores/swap.ts
index dff0901ec..902a47bdb 100644
--- a/src/stores/swap.ts
+++ b/src/stores/swap.ts
@@ -107,7 +107,7 @@ export const useSwapStore = defineStore("swap", {
const fromWallet = walletStore.mintWallet(fromMintUrl, unit);
const proofs = token.getProofs(tokenJson);
meltAmount -= fromWallet.getFeesForProofs(proofs);
- } catch (e) { }
+ } catch (e) {}
return tokenAmount - meltAmount;
},
meltProofsToMint: async function (tokenJson: Token, mint: Mint) {
@@ -121,7 +121,8 @@ export const useSwapStore = defineStore("swap", {
this.swapBlocking = true;
try {
const tokenAmount = proofsStore.sumProofs(token.getProofs(tokenJson));
- let meltAmount = tokenAmount - Math.max(2, Math.ceil(tokenAmount * 0.02));
+ let meltAmount =
+ tokenAmount - Math.max(2, Math.ceil(tokenAmount * 0.02));
const unit = token.getUnit(tokenJson);
const fromMintUrl = token.getMint(tokenJson);
const fromWallet = walletStore.mintWallet(fromMintUrl, unit);
From 9ed53a6cbbf575926c50bd3d3b58ff1ff13dba9a Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 24 Nov 2025 22:04:23 +0100
Subject: [PATCH 13/73] import wallet backup
---
src/components/SettingsView.vue | 91 +++++++++++++++++++++++++++++++++
src/i18n/en-US/index.ts | 8 +++
2 files changed, 99 insertions(+)
diff --git a/src/components/SettingsView.vue b/src/components/SettingsView.vue
index 7527eb256..78c39e3f2 100644
--- a/src/components/SettingsView.vue
+++ b/src/components/SettingsView.vue
@@ -1772,6 +1772,70 @@
+
+
+
+
+ {{
+ $t("Settings.advanced.developer.import_wallet.button")
+ }}
+
+
+
+ {{
+ $t(
+ "Settings.advanced.developer.import_wallet.description"
+ )
+ }}
+
+
+
+ {{
+ $t(
+ "Settings.advanced.developer.import_wallet.confirm_question"
+ )
+ }}
+ {{
+ $t("Settings.advanced.developer.import_wallet.cancel")
+ }}
+ {{
+ $t("Settings.advanced.developer.import_wallet.confirm")
+ }}
+
+
+
+
@@ -1905,6 +1969,7 @@ export default defineComponent({
hideMnemonic: true,
confirmMnemonic: false,
confirmNuke: false,
+ confirmImport: false,
nip46Token: "",
nip07SignerAvailable: false,
newRelay: "",
@@ -2233,6 +2298,32 @@ export default defineComponent({
localStorage.clear();
window.location.href = "/";
},
+ browseBackupFile: function () {
+ this.$refs.fileUpload.click();
+ },
+ onChangeFileUpload: function () {
+ const file = this.$refs.fileUpload.files[0];
+ if (file) {
+ this.readBackupFile(file);
+ }
+ },
+ readBackupFile: function (file) {
+ const reader = new FileReader();
+ reader.onload = (f) => {
+ try {
+ const content = f.target.result;
+ const backup = JSON.parse(content);
+ this.restoreFromBackup(backup);
+ } catch (error) {
+ console.error("Error reading backup file:", error);
+ this.notifyError("Invalid backup file format");
+ }
+ };
+ reader.onerror = () => {
+ this.notifyError("Error reading file");
+ };
+ reader.readAsText(file);
+ },
addRelay: function () {
if (this.newRelay) {
this.newRelay = this.newRelay.trim();
diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts
index a41579537..9f92c3515 100644
--- a/src/i18n/en-US/index.ts
+++ b/src/i18n/en-US/index.ts
@@ -500,6 +500,14 @@ export default {
description:
"Download a dump of your wallet. You can restore your wallet from this file in the welcome screen of a new wallet. This file will be out of sync if you keep using your wallet after exporting it.",
},
+ import_wallet: {
+ button: "Import wallet backup",
+ description:
+ "Restore your wallet from a previously exported backup file. This will replace your current wallet data with the backup.",
+ confirm_question: "Are you sure you want to restore your wallet data?",
+ cancel: "Cancel",
+ confirm: "IMPORT WALLET BACKUP",
+ },
},
},
},
From d4f183383e5161cc1cf66baf05de4be38aae1664 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 24 Nov 2025 22:05:08 +0100
Subject: [PATCH 14/73] format
---
src/i18n/en-US/index.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts
index 9f92c3515..decb61a44 100644
--- a/src/i18n/en-US/index.ts
+++ b/src/i18n/en-US/index.ts
@@ -504,7 +504,8 @@ export default {
button: "Import wallet backup",
description:
"Restore your wallet from a previously exported backup file. This will replace your current wallet data with the backup.",
- confirm_question: "Are you sure you want to restore your wallet data?",
+ confirm_question:
+ "Are you sure you want to restore your wallet data?",
cancel: "Cancel",
confirm: "IMPORT WALLET BACKUP",
},
From 24ee7ae2c723a4efce0dfedb66d265d4e31f63dc Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Thu, 4 Dec 2025 14:32:09 +0400
Subject: [PATCH 15/73] prevent second payment after success
---
src/components/PayInvoiceDialog.vue | 53 ++++++++++++++++++++++-------
1 file changed, 41 insertions(+), 12 deletions(-)
diff --git a/src/components/PayInvoiceDialog.vue b/src/components/PayInvoiceDialog.vue
index 4367e1cdd..a0e0d7892 100644
--- a/src/components/PayInvoiceDialog.vue
+++ b/src/components/PayInvoiceDialog.vue
@@ -411,8 +411,21 @@
class="col-12 col-sm-11 col-md-8 q-px-md"
style="max-width: 600px"
>
+
+
+
+
+
+
| null,
};
},
watch: {
@@ -653,11 +663,20 @@ export default defineComponent({
this.payInvoiceData.show = true; // Prevent close
// Wait 2 seconds then allow close
- await new Promise((resolve) => setTimeout(resolve, 2000));
- this.isPaid = false;
- this.payInvoiceData.show = false;
+ this.autoCloseTimeout = setTimeout(() => {
+ if (this.isPaid) {
+ // Only auto-close if still in paid state (user hasn't manually closed)
+ this.isPaid = false;
+ this.payInvoiceData.show = false;
+ }
+ this.autoCloseTimeout = null;
+ }, 2000);
} else if (!val) {
- // Normal close - reset states
+ // Normal close - reset states and clear any pending timeout
+ if (this.autoCloseTimeout) {
+ clearTimeout(this.autoCloseTimeout);
+ this.autoCloseTimeout = null;
+ }
this.showNumericKeyboard = false;
this.isPaying = false;
this.isPaid = false;
@@ -811,6 +830,16 @@ export default defineComponent({
);
},
closeParseDialog: function () {},
+ closeDialog: function () {
+ // Clear any pending auto-close timeout
+ if (this.autoCloseTimeout) {
+ clearTimeout(this.autoCloseTimeout);
+ this.autoCloseTimeout = null;
+ }
+ this.payInvoiceData.show = false;
+ this.isPaying = false;
+ this.isPaid = false;
+ },
decodeAndQuote: async function (request: string) {
await this.decodeRequest(request);
},
From dc8510bef47445cd00300c8f68f265418b237bff Mon Sep 17 00:00:00 2001
From: Pavol Rusnak
Date: Sun, 7 Dec 2025 23:24:36 +0100
Subject: [PATCH 16/73] fix lnurl-p callback separator
---
src/stores/wallet.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts
index 8ee399313..6c1e4ad82 100644
--- a/src/stores/wallet.ts
+++ b/src/stores/wallet.ts
@@ -1590,8 +1590,10 @@ export const useWalletStore = defineStore("wallet", {
const usdAmount = amount;
amount = Math.floor(usdAmount * satPrice);
}
+ const callback = this.payInvoiceData.lnurlpay.callback;
+ const separator = callback.includes("?") ? "&" : "?";
var { data } = await axios.get(
- `${this.payInvoiceData.lnurlpay.callback}?amount=${amount * 1000}`
+ `${callback}${separator}amount=${amount * 1000}`
);
// check http error
if (data.status == "ERROR") {
From 5a1d722626742622348416804878272a572742bd Mon Sep 17 00:00:00 2001
From: b-l-u-e
Date: Mon, 8 Dec 2025 13:08:57 +0300
Subject: [PATCH 17/73] fix(nostr): respect verbose flag in backup error
notifications
---
src/stores/nostrMintBackup.ts | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/src/stores/nostrMintBackup.ts b/src/stores/nostrMintBackup.ts
index 424000613..14794482d 100644
--- a/src/stores/nostrMintBackup.ts
+++ b/src/stores/nostrMintBackup.ts
@@ -146,6 +146,19 @@ export const useNostrMintBackupStore = defineStore("nostrMintBackup", {
const settingsStore = useSettingsStore();
const mintsStore = useMintsStore();
+ // Check if relays are configured
+ if (
+ !settingsStore.defaultNostrRelays ||
+ settingsStore.defaultNostrRelays.length === 0
+ ) {
+ const errorMsg = "No Nostr relays configured";
+ console.error(errorMsg);
+ if (verbose) {
+ notifyError(`Failed to backup mint list to Nostr: ${errorMsg}`);
+ }
+ throw new Error(errorMsg);
+ }
+
const currentMints = mintsStore.mints.map((mint) => mint.url);
if (currentMints.length === 0) {
@@ -200,9 +213,13 @@ export const useNostrMintBackupStore = defineStore("nostrMintBackup", {
console.log("Mint backup published to Nostr:", event.id);
} catch (error) {
console.error("Failed to backup mints to Nostr:", error);
- notifyError(
- "Failed to backup mint list to Nostr: " + (error as Error).message
- );
+ // Only show error notification if verbose is true
+ // This prevents showing errors for automatic backups that fail silently
+ if (verbose) {
+ notifyError(
+ "Failed to backup mint list to Nostr: " + (error as Error).message
+ );
+ }
throw error;
} finally {
this.backupInProgress = false;
From 876395d1f9f749516c8e356248041da31e9ce424 Mon Sep 17 00:00:00 2001
From: b-l-u-e
Date: Mon, 8 Dec 2025 12:56:42 +0300
Subject: [PATCH 18/73] fix: update Docker to Node.js 22 and remove obsolete
compose version
---
Dockerfile | 2 +-
README.md | 2 +-
docker-compose.yaml | 1 -
3 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 22dfeac3b..a9e32e481 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Stage 1: Build Phase
-FROM node:20.10.0-bullseye AS builder
+FROM node:24-bullseye AS builder
WORKDIR /app
diff --git a/README.md b/README.md
index e0d136791..b8c6ec08c 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ Cashu Wallet
## One-liner build & run
```
-docker-compose up -d
+docker compose up -d
```
access at http://localhost:3000 or serve it behind a reverse proxy.
diff --git a/docker-compose.yaml b/docker-compose.yaml
index a92cc6731..354aa2c94 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,4 +1,3 @@
-version: "2"
services:
cashu.me:
image: cashu.me
From bc6997959b4034e83c16e1e00fca9f13a075d5f9 Mon Sep 17 00:00:00 2001
From: Pavol Rusnak
Date: Sun, 7 Dec 2025 23:52:17 +0100
Subject: [PATCH 19/73] do not use var, use const or let
---
src/boot/base.js | 4 ++--
src/components/BalanceView.vue | 2 +-
src/components/NoMintWarnBanner.vue | 2 +-
src/js/base64.js | 13 ++++++-------
src/pages/WalletPage.vue | 4 ++--
src/stores/price.ts | 2 +-
src/stores/storage.ts | 18 +++++++++---------
src/stores/ui.ts | 2 +-
src/stores/wallet.ts | 8 ++++----
9 files changed, 27 insertions(+), 28 deletions(-)
diff --git a/src/boot/base.js b/src/boot/base.js
index 2c1c1c277..d4a9a7968 100644
--- a/src/boot/base.js
+++ b/src/boot/base.js
@@ -175,7 +175,7 @@ window.windowMixin = {
return new Intl.NumberFormat(window.LOCALE).format(value) + " msat";
},
notifyApiError: function (error) {
- var types = {
+ const types = {
400: "warning",
401: "warning",
500: "negative",
@@ -304,7 +304,7 @@ window.windowMixin = {
// addEventListener("beforeunload", (event) => {
// event.preventDefault();
- // var dialogText = "Are you sure about this?";
+ // const dialogText = "Are you sure about this?";
// event.returnValue = dialogText;
// return dialogText;
// });
diff --git a/src/components/BalanceView.vue b/src/components/BalanceView.vue
index ab1485b7d..5b42675d0 100644
--- a/src/components/BalanceView.vue
+++ b/src/components/BalanceView.vue
@@ -224,7 +224,7 @@ export default defineComponent({
);
},
getBalance: function () {
- var balance = this.activeProofs
+ const balance = this.activeProofs
.flat()
.reduce((sum, el) => (sum += el.amount), 0);
return balance;
diff --git a/src/components/NoMintWarnBanner.vue b/src/components/NoMintWarnBanner.vue
index d358f044a..12eb0a3d5 100644
--- a/src/components/NoMintWarnBanner.vue
+++ b/src/components/NoMintWarnBanner.vue
@@ -77,7 +77,7 @@ export default defineComponent({
return getShortUrl(this.activeMintUrl);
},
getBalance: function () {
- var balance = this.activeProofs
+ const balance = this.activeProofs
.map((t) => t)
.flat()
.reduce((sum, el) => (sum += el.amount), 0);
diff --git a/src/js/base64.js b/src/js/base64.js
index 7923f84c9..72480e57c 100644
--- a/src/js/base64.js
+++ b/src/js/base64.js
@@ -11,22 +11,21 @@ function escapeBase64Url(str) {
const uint8ToBase64 = (function (exports) {
"use strict";
- var fromCharCode = String.fromCharCode;
- var encode = function encode(uint8array) {
- var output = [];
+ const encode = function encode(uint8array) {
+ let output = [];
- for (var i = 0, length = uint8array.length; i < length; i++) {
- output.push(fromCharCode(uint8array[i]));
+ for (let i = 0; i < uint8array.length; i++) {
+ output.push(String.fromCharCode(uint8array[i]));
}
return btoa(output.join(""));
};
- var asCharCode = function asCharCode(c) {
+ const asCharCode = function asCharCode(c) {
return c.charCodeAt(0);
};
- var decode = function decode(chars) {
+ const decode = function decode(chars) {
return Uint8Array.from(atob(chars), asCharCode);
};
diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue
index ecbf641e1..1c545157a 100644
--- a/src/pages/WalletPage.vue
+++ b/src/pages/WalletPage.vue
@@ -652,8 +652,8 @@ export default {
let tokenBase64 = params.get("token") || hash.split("token=")[1];
// make sure to react only to tokens not in the users history
let seen = false;
- for (var i = 0; i < this.historyTokens.length; i++) {
- var thisToken = this.historyTokens[i].token;
+ for (let i = 0; i < this.historyTokens.length; i++) {
+ const thisToken = this.historyTokens[i].token;
if (thisToken == tokenBase64 && this.historyTokens[i].amount > 0) {
seen = true;
}
diff --git a/src/stores/price.ts b/src/stores/price.ts
index c70f56be8..9cbe76e13 100644
--- a/src/stores/price.ts
+++ b/src/stores/price.ts
@@ -45,7 +45,7 @@ export const usePriceStore = defineStore("price", {
return;
}
try {
- var { data } = await axios.get(
+ const { data } = await axios.get(
"https://api.coinbase.com/v2/exchange-rates?currency=BTC"
);
this.bitcoinPrices = data.data.rates;
diff --git a/src/stores/storage.ts b/src/stores/storage.ts
index 8cacee112..76a43564f 100644
--- a/src/stores/storage.ts
+++ b/src/stores/storage.ts
@@ -35,27 +35,27 @@ export const useStorageStore = defineStore("storage", {
}
},
exportWalletState: async function () {
- var jsonToSave: any = {};
- for (var i = 0; i < localStorage.length; i++) {
- var k = localStorage.key(i);
+ let jsonToSave: any = {};
+ for (let i = 0; i < localStorage.length; i++) {
+ const k = localStorage.key(i);
if (!k) {
continue;
}
- var v = localStorage.getItem(k);
+ const v = localStorage.getItem(k);
jsonToSave[k] = v;
}
// proofs table *magic*
const proofs = await useProofsStore().getProofs();
jsonToSave["cashu.dexie.db.proofs"] = JSON.stringify(proofs);
- var textToSave = JSON.stringify(jsonToSave);
- var textToSaveAsBlob = new Blob([textToSave], {
+ const textToSave = JSON.stringify(jsonToSave);
+ const textToSaveAsBlob = new Blob([textToSave], {
type: "text/plain",
});
- var textToSaveAsURL = window.URL.createObjectURL(textToSaveAsBlob);
+ const textToSaveAsURL = window.URL.createObjectURL(textToSaveAsBlob);
const fileName = `cashu_me_backup_${currentDateStr()}.json`;
- var downloadLink = document.createElement("a");
+ let downloadLink = document.createElement("a");
downloadLink.download = fileName;
downloadLink.innerHTML = "Download File";
downloadLink.href = textToSaveAsURL;
@@ -145,7 +145,7 @@ export const useStorageStore = defineStore("storage", {
0,
sortedTokens.length - max_history
);
- for (var i = 0; i < deleteTokens.length; i++) {
+ for (let i = 0; i < deleteTokens.length; i++) {
deleteTokens[i].token = undefined;
}
}
diff --git a/src/stores/ui.ts b/src/stores/ui.ts
index 76e6ddf5f..228bebe57 100644
--- a/src/stores/ui.ts
+++ b/src/stores/ui.ts
@@ -127,7 +127,7 @@ export const useUiStore = defineStore("ui", {
return;
}
// enable debug terminal
- var script = document.createElement("script");
+ let script = document.createElement("script");
script.src = "//cdn.jsdelivr.net/npm/eruda";
document.body.appendChild(script);
script.onload = function () {
diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts
index 6c1e4ad82..8f4f5b3be 100644
--- a/src/stores/wallet.ts
+++ b/src/stores/wallet.ts
@@ -1424,7 +1424,7 @@ export const useWalletStore = defineStore("wallet", {
} else if (tag.name === "timestamp") {
cleanInvoice.timestamp = tag.value;
} else if (tag.name === "expiry") {
- var expireDate = new Date(
+ const expireDate = new Date(
(cleanInvoice.timestamp + tag.value) * 1000
);
cleanInvoice.expireDate = date.formatDate(
@@ -1504,8 +1504,8 @@ export const useWalletStore = defineStore("wallet", {
uiStore.closeDialogs();
},
lnurlPayFirst: async function (address: string) {
- var host;
- var data;
+ let host;
+ let data;
if (address.split("@").length == 2) {
let [user, lnaddresshost] = address.split("@");
host = `https://${lnaddresshost}/.well-known/lnurlp/${user}`;
@@ -1592,7 +1592,7 @@ export const useWalletStore = defineStore("wallet", {
}
const callback = this.payInvoiceData.lnurlpay.callback;
const separator = callback.includes("?") ? "&" : "?";
- var { data } = await axios.get(
+ const { data } = await axios.get(
`${callback}${separator}amount=${amount * 1000}`
);
// check http error
From 4f862d1ca6af03db347c29b07b1f2e25dec1ccd0 Mon Sep 17 00:00:00 2001
From: Pavol Rusnak
Date: Mon, 8 Dec 2025 11:42:11 +0100
Subject: [PATCH 20/73] use const where possible instead of let
---
.eslintrc.js | 10 ++++++++++
package.json | 2 +-
src/boot/base.js | 4 ++--
src/components/DisplayTokenComponent.vue | 6 +++---
src/components/InvoiceDetailDialog.vue | 2 +-
src/components/MintAuditWarningBox.vue | 2 +-
src/components/MintSettings.vue | 4 ++--
src/components/MultinutPaymentDialog.vue | 5 ++---
src/components/ReceiveTokenDialog.vue | 2 +-
src/components/SendTokenDialog.vue | 8 ++++----
src/components/SettingsView.vue | 16 ++++++++--------
src/components/TokenInformation.vue | 16 +++++++++-------
src/components/WelcomeDialog.vue | 12 ++++++------
src/js/base64.js | 2 +-
src/pages/WalletPage.vue | 8 ++++----
src/pages/welcome/WelcomeSlide3.vue | 2 +-
src/stores/mints.ts | 2 +-
src/stores/nostrUser.ts | 4 ++--
src/stores/nwc.ts | 20 ++++++++++----------
src/stores/p2pk.ts | 14 +++++++-------
src/stores/proofs.ts | 16 +++++++++-------
src/stores/receiveTokensStore.ts | 4 +++-
src/stores/storage.ts | 16 ++++++++--------
src/stores/ui.ts | 2 +-
src/stores/wallet.ts | 16 ++++++++--------
src/stores/workers.ts | 5 ++++-
26 files changed, 109 insertions(+), 91 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index 9acc1d132..75d8c7ea9 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -60,6 +60,16 @@ module.exports = {
// allow debugger during development only
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
+
+ "no-var": "error",
+ "no-const-assign": "error",
+ "prefer-const": [
+ "error",
+ {
+ destructuring: "any",
+ ignoreReadBeforeAssign: false,
+ },
+ ],
},
overrides: [
{
diff --git a/package.json b/package.json
index 07b933778..a974ffdaa 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"build:pwa": "quasar build -m pwa",
"build:electron": "quasar build -m electron",
"quasar": "quasar",
- "lint": "eslint --ext .js,.vue ./",
+ "lint": "eslint --ext .js,.vue,.ts ./",
"format": "prettier --write .",
"checkformat": "prettier --check .",
"test": "vitest",
diff --git a/src/boot/base.js b/src/boot/base.js
index d4a9a7968..f3dfef603 100644
--- a/src/boot/base.js
+++ b/src/boot/base.js
@@ -116,8 +116,8 @@ window.windowMixin = {
updateStatusBarMeta();
},
copyText: function (text, message, position) {
- let notify = this.$q.notify;
- let i18n = this.$i18n;
+ const notify = this.$q.notify;
+ const i18n = this.$i18n;
copyToClipboard(text).then(function () {
notify({
message:
diff --git a/src/components/DisplayTokenComponent.vue b/src/components/DisplayTokenComponent.vue
index b1d695aaf..c33299128 100644
--- a/src/components/DisplayTokenComponent.vue
+++ b/src/components/DisplayTokenComponent.vue
@@ -396,15 +396,15 @@ export default defineComponent({
...mapState(useSettingsStore, ["nfcEncoding"]),
// display helpers
sumProofs: function () {
- let proofs = token.getProofs(token.decode(this.sendData.tokensBase64));
+ const proofs = token.getProofs(token.decode(this.sendData.tokensBase64));
return proofs.flat().reduce((sum, el) => (sum += el.amount), 0);
},
displayUnit: function () {
- let display = this.formatCurrency(this.sumProofs, this.tokenUnit);
+ const display = this.formatCurrency(this.sumProofs, this.tokenUnit);
return display;
},
tokenUnit: function () {
- let unit = token.getUnit(token.decode(this.sendData.tokensBase64));
+ const unit = token.getUnit(token.decode(this.sendData.tokensBase64));
return unit;
},
paidFees: function () {
diff --git a/src/components/InvoiceDetailDialog.vue b/src/components/InvoiceDetailDialog.vue
index 0dae1c837..0c03aff5a 100644
--- a/src/components/InvoiceDetailDialog.vue
+++ b/src/components/InvoiceDetailDialog.vue
@@ -182,7 +182,7 @@ export default defineComponent({
...mapState(useWorkersStore, ["invoiceWorkerRunning"]),
...mapWritableState(useUiStore, ["showInvoiceDetails"]),
displayUnit: function () {
- let display = (this as any).formatCurrency(
+ const display = (this as any).formatCurrency(
this.invoiceData.amount,
this.invoiceData.unit,
true
diff --git a/src/components/MintAuditWarningBox.vue b/src/components/MintAuditWarningBox.vue
index 102c38c18..4877577d3 100644
--- a/src/components/MintAuditWarningBox.vue
+++ b/src/components/MintAuditWarningBox.vue
@@ -112,7 +112,7 @@ export default defineComponent({
const baseMessage = "The last swap attempt has failed.";
if (recentSwaps.length > 0) {
- let successMessage = `${successfulRecentSwaps.length} of ${recentSwaps.length} swaps in the last ${props.recentDaysThreshold} days succeeded.`;
+ const successMessage = `${successfulRecentSwaps.length} of ${recentSwaps.length} swaps in the last ${props.recentDaysThreshold} days succeeded.`;
// Add "However, " prefix if success rate is above threshold
if (recentSuccessRate >= props.successRateThreshold) {
diff --git a/src/components/MintSettings.vue b/src/components/MintSettings.vue
index 37b22a9b0..901b07795 100644
--- a/src/components/MintSettings.vue
+++ b/src/components/MintSettings.vue
@@ -515,7 +515,7 @@ export default defineComponent({
);
return;
}
- let urlObj = new URL(this.addMintData.url);
+ const urlObj = new URL(this.addMintData.url);
urlObj.hostname = urlObj.hostname.toLowerCase();
this.addMintData.url = urlObj.toString();
this.addMintData.url = this.addMintData.url.replace(/\/$/, "");
@@ -534,7 +534,7 @@ export default defineComponent({
return new MintClass(mint);
},
swapAmountDataOptions: function () {
- let options = [];
+ const options = [];
for (const [i, m] of Object.entries(this.mints)) {
const unitStr = "sat";
const unitBalance = this.mintClass(m).unitBalance(unitStr);
diff --git a/src/components/MultinutPaymentDialog.vue b/src/components/MultinutPaymentDialog.vue
index 571018c23..855fecca6 100644
--- a/src/components/MultinutPaymentDialog.vue
+++ b/src/components/MultinutPaymentDialog.vue
@@ -732,7 +732,7 @@ export default defineComponent({
if (partialAmounts[largestMint.url] > mintBalance) {
// If the largest mint can't handle the adjustment, distribute the difference
partialAmounts[largestMint.url] = mintBalance;
- const remainingDifference =
+ let remainingDifference =
difference - (mintBalance - largestAmount);
// Find other mints that can handle the remaining difference
@@ -863,8 +863,7 @@ export default defineComponent({
this.multiMeltButtonLoading = true;
let mintsToQuotes = [];
- let mintsToAmounts = [];
- let remainder = 0.0;
+ const mintsToAmounts = [];
let data;
try {
diff --git a/src/components/ReceiveTokenDialog.vue b/src/components/ReceiveTokenDialog.vue
index 07650aeb0..b20fbcf93 100644
--- a/src/components/ReceiveTokenDialog.vue
+++ b/src/components/ReceiveTokenDialog.vue
@@ -596,7 +596,7 @@ export default defineComponent({
};
// Check all input chars for peanut data
for (const char of chars) {
- let byte = fromVariationSelector(char);
+ const byte = fromVariationSelector(char);
if (byte === null && decoded.length > 0) {
break;
} else if (byte === null) {
diff --git a/src/components/SendTokenDialog.vue b/src/components/SendTokenDialog.vue
index 280e12ad7..59ee7df67 100644
--- a/src/components/SendTokenDialog.vue
+++ b/src/components/SendTokenDialog.vue
@@ -534,13 +534,13 @@ export default defineComponent({
if (!this.sendData.amount) {
throw new Error("Amount is required");
}
- let sendAmount = Math.floor(
+ const sendAmount = Math.floor(
this.sendData.amount * this.activeUnitCurrencyMultiplyer
);
try {
// keep firstProofs, send scndProofs and delete them (invalidate=true)
const mintWallet = this.mintWallet(this.activeMintUrl, this.activeUnit);
- let { _, sendProofs } = await this.sendToLock(
+ const { _, sendProofs } = await this.sendToLock(
this.activeProofs,
mintWallet,
sendAmount,
@@ -583,12 +583,12 @@ export default defineComponent({
}
try {
- let sendAmount = Math.floor(
+ const sendAmount = Math.floor(
this.sendData.amount * this.activeUnitCurrencyMultiplyer
);
const mintWallet = this.mintWallet(this.activeMintUrl, this.activeUnit);
// keep firstProofs, send scndProofs and delete them (invalidate=true)
- let { _, sendProofs } = await this.send(
+ const { _, sendProofs } = await this.send(
this.activeProofs,
mintWallet,
sendAmount,
diff --git a/src/components/SettingsView.vue b/src/components/SettingsView.vue
index 78c39e3f2..9973ddb0a 100644
--- a/src/components/SettingsView.vue
+++ b/src/components/SettingsView.vue
@@ -2050,7 +2050,7 @@ export default defineComponent({
keysetCountersByMint() {
const mints = this.mints;
const keysetCountersByMint = {}; // {mintUrl: [keysetCounter: {id: string, count: number}, ...]}
- for (let mint of mints) {
+ for (const mint of mints) {
const mintIds = mint.keysets.map((keyset) => keyset.id);
const keysetCounterThisMint = this.keysetCounters.filter((entry) =>
mintIds.includes(entry.id)
@@ -2192,21 +2192,21 @@ export default defineComponent({
},
checkActiveProofsSpendable: async function () {
// iterate over this.activeProofs in batches of 50 and check if they are spendable
- let wallet = useWalletStore().mintWallet(
+ const wallet = useWalletStore().mintWallet(
this.activeMintUrl,
this.activeUnit
);
- let proofs = this.activeProofs.flat();
+ const proofs = this.activeProofs.flat();
console.log("Checking proofs", proofs);
- let allSpentProofs = [];
- let batch_size = 50;
+ const allSpentProofs = [];
+ const batch_size = 50;
for (let i = 0; i < proofs.length; i += batch_size) {
console.log("Checking proofs", i, i + batch_size);
- let batch = proofs.slice(i, i + batch_size);
- let spent = await this.checkProofsSpendable(batch, wallet, true);
+ const batch = proofs.slice(i, i + batch_size);
+ const spent = await this.checkProofsSpendable(batch, wallet, true);
allSpentProofs.push(spent);
}
- let spentProofs = allSpentProofs.flat();
+ const spentProofs = allSpentProofs.flat();
if (spentProofs.length > 0) {
console.log("Spent proofs", spentProofs);
this.notifySuccess("Removed " + spentProofs.length + " spent proofs");
diff --git a/src/components/TokenInformation.vue b/src/components/TokenInformation.vue
index fa56661ad..e18e5b2c2 100644
--- a/src/components/TokenInformation.vue
+++ b/src/components/TokenInformation.vue
@@ -201,18 +201,18 @@ export default defineComponent({
return token.getProofs(token.decode(this.encodedToken));
},
sumProofs: function () {
- let proofs = token.getProofs(token.decode(this.encodedToken));
+ const proofs = token.getProofs(token.decode(this.encodedToken));
return proofs.flat().reduce((sum, el) => (sum += el.amount), 0);
},
displayUnit: function () {
- let display = this.formatCurrency(this.sumProofs, this.tokenUnit, true);
+ const display = this.formatCurrency(this.sumProofs, this.tokenUnit, true);
return display;
},
tokenUnit: function () {
return token.getUnit(token.decode(this.encodedToken));
},
tokenMintUrl: function () {
- let mint = token.getMint(token.decode(this.encodedToken));
+ const mint = token.getMint(token.decode(this.encodedToken));
return getShortUrl(mint);
},
displayMemo: function () {
@@ -248,13 +248,15 @@ export default defineComponent({
...mapActions(useP2PKStore, ["isLocked", "isLockedToUs"]),
getProofsMint: function (proofs) {
// unique keyset IDs of proofs
- let uniqueIds = [...new Set(proofs.map((p) => p.id))];
+ const uniqueIds = [...new Set(proofs.map((p) => p.id))];
// mints that have any of the keyset IDs
- let mints_keysets = this.mints.filter((m) =>
+ const mints_keysets = this.mints.filter((m) =>
m.keysets.some((r) => uniqueIds.indexOf(r) >= 0)
);
// what we put into the JSON
- let mints = mints_keysets.map((m) => [{ url: m.url, ids: m.keysets }][0]);
+ const mints = mints_keysets.map(
+ (m) => [{ url: m.url, ids: m.keysets }][0]
+ );
if (mints.length == 0) {
return "";
} else {
@@ -263,7 +265,7 @@ export default defineComponent({
},
mintKnownToUs: function (proofs) {
// unique keyset IDs of proofs
- let uniqueIds = [...new Set(proofs.map((p) => p.id))];
+ const uniqueIds = [...new Set(proofs.map((p) => p.id))];
// mints that have any of the keyset IDs
return (
this.mints.filter((m) =>
diff --git a/src/components/WelcomeDialog.vue b/src/components/WelcomeDialog.vue
index fbaa5cd75..cf17030bb 100644
--- a/src/components/WelcomeDialog.vue
+++ b/src/components/WelcomeDialog.vue
@@ -122,10 +122,10 @@ export default defineComponent({
methods: {
...mapActions(useStorageStore, ["restoreFromBackup"]),
readFile(file) {
- let reader = new FileReader();
+ const reader = new FileReader();
reader.onload = (f) => {
- let content = f.target.result;
- let backup = JSON.parse(content);
+ const content = f.target.result;
+ const backup = JSON.parse(content);
this.restoreFromBackup(backup);
};
@@ -134,8 +134,8 @@ export default defineComponent({
dragFile(ev) {
ev.preventDefault();
- let files = ev.dataTransfer.files;
- let file = files[0];
+ const files = ev.dataTransfer.files;
+ const file = files[0];
this.readFile(file);
},
@@ -143,7 +143,7 @@ export default defineComponent({
ev.preventDefault();
},
onChangeFileUpload() {
- let file = this.$refs.fileUpload.files[0];
+ const file = this.$refs.fileUpload.files[0];
this.readFile(file);
},
diff --git a/src/js/base64.js b/src/js/base64.js
index 72480e57c..4877f23a1 100644
--- a/src/js/base64.js
+++ b/src/js/base64.js
@@ -12,7 +12,7 @@ const uint8ToBase64 = (function (exports) {
"use strict";
const encode = function encode(uint8array) {
- let output = [];
+ const output = [];
for (let i = 0; i < uint8array.length; i++) {
output.push(String.fromCharCode(uint8array[i]));
diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue
index 1c545157a..5de0def13 100644
--- a/src/pages/WalletPage.vue
+++ b/src/pages/WalletPage.vue
@@ -630,12 +630,12 @@ export default {
// check if another tab is open
this.registerBroadcastChannel();
- let params = new URL(document.location).searchParams;
- let hash = new URL(document.location).hash;
+ const params = new URL(document.location).searchParams;
+ const hash = new URL(document.location).hash;
// mint url
if (params.get("mint")) {
- let addMintUrl = params.get("mint");
+ const addMintUrl = params.get("mint");
await this.setTab("mints");
this.showAddMintDialog = true;
this.addMintData = { url: addMintUrl };
@@ -649,7 +649,7 @@ export default {
// get token to receive tokens from a link
if (params.get("token") || hash.includes("token")) {
- let tokenBase64 = params.get("token") || hash.split("token=")[1];
+ const tokenBase64 = params.get("token") || hash.split("token=")[1];
// make sure to react only to tokens not in the users history
let seen = false;
for (let i = 0; i < this.historyTokens.length; i++) {
diff --git a/src/pages/welcome/WelcomeSlide3.vue b/src/pages/welcome/WelcomeSlide3.vue
index 4fbd5aa88..9410be72e 100644
--- a/src/pages/welcome/WelcomeSlide3.vue
+++ b/src/pages/welcome/WelcomeSlide3.vue
@@ -82,7 +82,7 @@ export default {
const welcomeStore = useWelcomeStore();
const walletStore = useWalletStore();
const $q = useQuasar();
- let hideMnemonic = ref(true);
+ const hideMnemonic = ref(true);
onMounted(() => {
// Ensure mnemonic is generated for new-wallet flow only
diff --git a/src/stores/mints.ts b/src/stores/mints.ts
index f1b957608..f707f1a05 100644
--- a/src/stores/mints.ts
+++ b/src/stores/mints.ts
@@ -418,7 +418,7 @@ export const useMintsStore = defineStore("mints", {
workers.clearAllWorkers();
// create new mint.api instance because we can't store it in local storage
- let previousUrl = this.activeMintUrl;
+ const previousUrl = this.activeMintUrl;
await uIStore.lockMutex();
try {
this.activeMintUrl = mint.url;
diff --git a/src/stores/nostrUser.ts b/src/stores/nostrUser.ts
index 9ffbbe4e2..7df637840 100644
--- a/src/stores/nostrUser.ts
+++ b/src/stores/nostrUser.ts
@@ -237,10 +237,10 @@ export const useNostrUserStore = defineStore("nostrUser", {
`[nostrUser] Crawling web of trust from ${source} up to ${maxHops} hops…`
);
// Determine resume vs fresh crawl
- let hop1Saved = (await db.meta.get("wot.crawl.hop1"))?.value as
+ const hop1Saved = (await db.meta.get("wot.crawl.hop1"))?.value as
| string[]
| undefined;
- let nextIndexSaved = (await db.meta.get("wot.crawl.nextIndex"))
+ const nextIndexSaved = (await db.meta.get("wot.crawl.nextIndex"))
?.value as number | undefined;
let hop1: string[] = [];
let startIndex = 0;
diff --git a/src/stores/nwc.ts b/src/stores/nwc.ts
index dfb5dc835..8b5fe18a0 100644
--- a/src/stores/nwc.ts
+++ b/src/stores/nwc.ts
@@ -312,10 +312,10 @@ export const useNWCStore = defineStore("nwc", {
};
},
mapToNwcTransaction(invoice: InvoiceHistory): NWCTransaction {
- let type = invoice.amount > 0 ? "incoming" : "outgoing";
- let amount = Math.abs(invoice.amount) * 1000;
- let created_at = Math.floor(new Date(invoice.date).getTime() / 1000);
- let settled_at =
+ const type = invoice.amount > 0 ? "incoming" : "outgoing";
+ const amount = Math.abs(invoice.amount) * 1000;
+ const created_at = Math.floor(new Date(invoice.date).getTime() / 1000);
+ const settled_at =
invoice.status == "paid"
? Math.floor(new Date(invoice.date).getTime() / 1000)
: null;
@@ -336,7 +336,7 @@ export const useNWCStore = defineStore("nwc", {
conn: NWCConnection
) {
// reply to NWC with result
- let replyEvent = new NDKEvent(event.ndk);
+ const replyEvent = new NDKEvent(event.ndk);
replyEvent.kind = 23195;
console.log("### replying with", JSON.stringify(result));
replyEvent.content = await nip04.encrypt(
@@ -359,7 +359,7 @@ export const useNWCStore = defineStore("nwc", {
conn: NWCConnection
) {
// parse command to JSON object {method: 'pay_invoice', params: {invoice: '1234'}}
- let nwcCommand: NWCCommand = JSON.parse(command);
+ const nwcCommand: NWCCommand = JSON.parse(command);
let result: NWCResult | NWCError;
console.log("### nwcCommand", nwcCommand);
// parse "get_info" without params
@@ -447,11 +447,11 @@ export const useNWCStore = defineStore("nwc", {
try {
// let's fetch the info event from the relay to see if we need to republish it
// use NWCKind.NWCInfo as an integer here
- let filterInfoEvent: NDKFilter = {
+ const filterInfoEvent: NDKFilter = {
kinds: [NWCKind.NWCInfo],
authors: [conn.walletPublicKey],
};
- let eventsInfoEvent = await this.ndk.fetchEvents(filterInfoEvent);
+ const eventsInfoEvent = await this.ndk.fetchEvents(filterInfoEvent);
if (eventsInfoEvent.size === 0) {
await nip47InfoEvent.publish();
console.log("### published nip47InfoEvent", nip47InfoEvent);
@@ -473,7 +473,7 @@ export const useNWCStore = defineStore("nwc", {
const currentUnitTime = Math.floor(Date.now() / 1000);
const subscribeSince = currentUnitTime - 60; // 1 minute
- let filter = {
+ const filter = {
kinds: [NWCKind.NWCRequest as NDKKind],
since: subscribeSince,
authors: [conn.connectionPublicKey],
@@ -523,7 +523,7 @@ export const useNWCStore = defineStore("nwc", {
},
unsubscribeNWC: function () {
console.log("### unsubscribing from NWC");
- for (let sub of this.subscriptions) {
+ for (const sub of this.subscriptions) {
sub.stop();
}
this.subscriptions = [];
diff --git a/src/stores/p2pk.ts b/src/stores/p2pk.ts
index 4a1bf1324..1d26b84b9 100644
--- a/src/stores/p2pk.ts
+++ b/src/stores/p2pk.ts
@@ -69,9 +69,9 @@ export const useP2PKStore = defineStore("p2pk", {
console.log("input was not an nsec");
return;
}
- let sk = nip19.decode(nsec).data as Uint8Array; // `sk` is a Uint8Array
- let pk = "02" + getPublicKey(sk); // `pk` is a hex string
- let skHex = bytesToHex(sk);
+ const sk = nip19.decode(nsec).data as Uint8Array; // `sk` is a Uint8Array
+ const pk = "02" + getPublicKey(sk); // `pk` is a hex string
+ const skHex = bytesToHex(sk);
if (this.haveThisKey(pk)) {
console.log("nsec already exists in p2pk keystore");
return;
@@ -85,9 +85,9 @@ export const useP2PKStore = defineStore("p2pk", {
this.p2pkKeys = this.p2pkKeys.concat(keyPair);
},
generateKeypair: function () {
- let sk = generateSecretKey(); // `sk` is a Uint8Array
- let pk = "02" + getPublicKey(sk); // `pk` is a hex string
- let skHex = bytesToHex(sk);
+ const sk = generateSecretKey(); // `sk` is a Uint8Array
+ const pk = "02" + getPublicKey(sk); // `pk` is a hex string
+ const skHex = bytesToHex(sk);
const keyPair: P2PKKey = {
publicKey: pk,
privateKey: skHex,
@@ -98,7 +98,7 @@ export const useP2PKStore = defineStore("p2pk", {
},
getSecretP2PKPubkey: function (secret: string): string {
try {
- let secretObject = JSON.parse(secret);
+ const secretObject = JSON.parse(secret);
if (secretObject[0] != "P2PK" || secretObject[1]["data"] == undefined) {
console.log("not p2pk locked");
return ""; // not p2pk locked
diff --git a/src/stores/proofs.ts b/src/stores/proofs.ts
index 03baa8783..62ca37a48 100644
--- a/src/stores/proofs.ts
+++ b/src/stores/proofs.ts
@@ -118,23 +118,23 @@ export const useProofsStore = defineStore("proofs", {
serializeProofs: function (proofs: Proof[]): string {
const mintStore = useMintsStore();
// unique keyset IDs of proofs
- let uniqueIds = [...new Set(proofs.map((p) => p.id))];
+ const uniqueIds = [...new Set(proofs.map((p) => p.id))];
// keysets with these uniqueIds
- let keysets = mintStore.mints.flatMap((m) =>
+ const keysets = mintStore.mints.flatMap((m) =>
m.keysets.filter((k) => uniqueIds.includes(k.id))
);
if (keysets.length === 0) {
throw new Error("No keysets found for proofs");
}
// mints that have any of the keyset.id
- let mints = mintStore.mints.filter((m) =>
+ const mints = mintStore.mints.filter((m) =>
m.keysets.some((k) => uniqueIds.includes(k.id))
);
if (mints.length === 0) {
throw new Error("No mints found for proofs");
}
// unit of keysets
- let unit = keysets[0].unit;
+ const unit = keysets[0].unit;
const token = {
mint: mints[0].url,
proofs: proofs,
@@ -158,13 +158,15 @@ export const useProofsStore = defineStore("proofs", {
getProofsMint: function (proofs: WalletProof[]) {
const mintStore = useMintsStore();
// unique keyset IDs of proofs
- let uniqueIds = [...new Set(proofs.map((p) => p.id))];
+ const uniqueIds = [...new Set(proofs.map((p) => p.id))];
// mints that have any of the keyset IDs
- let mints_keysets = mintStore.mints.filter((m) =>
+ const mints_keysets = mintStore.mints.filter((m) =>
m.keysets.some((k) => uniqueIds.includes(k.id))
);
// what we put into the JSON
- let mints = mints_keysets.map((m) => [{ url: m.url, ids: m.keysets }][0]);
+ const mints = mints_keysets.map(
+ (m) => [{ url: m.url, ids: m.keysets }][0]
+ );
return mints[0];
},
},
diff --git a/src/stores/receiveTokensStore.ts b/src/stores/receiveTokensStore.ts
index b8e043bcd..20a8d0424 100644
--- a/src/stores/receiveTokensStore.ts
+++ b/src/stores/receiveTokensStore.ts
@@ -35,7 +35,9 @@ export const useReceiveTokensStore = defineStore("receiveTokensStore", {
},
knowThisMintOfTokenJson: function (tokenJson: Token) {
const mintStore = useMintsStore();
- let uniqueIds = [...new Set(token.getProofs(tokenJson).map((p) => p.id))];
+ const uniqueIds = [
+ ...new Set(token.getProofs(tokenJson).map((p) => p.id)),
+ ];
return mintStore.mints
.map((m) => m.url)
.includes(token.getMint(tokenJson));
diff --git a/src/stores/storage.ts b/src/stores/storage.ts
index 76a43564f..7f9df86fd 100644
--- a/src/stores/storage.ts
+++ b/src/stores/storage.ts
@@ -35,7 +35,7 @@ export const useStorageStore = defineStore("storage", {
}
},
exportWalletState: async function () {
- let jsonToSave: any = {};
+ const jsonToSave: any = {};
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (!k) {
@@ -55,7 +55,7 @@ export const useStorageStore = defineStore("storage", {
const textToSaveAsURL = window.URL.createObjectURL(textToSaveAsBlob);
const fileName = `cashu_me_backup_${currentDateStr()}.json`;
- let downloadLink = document.createElement("a");
+ const downloadLink = document.createElement("a");
downloadLink.download = fileName;
downloadLink.innerHTML = "Download File";
downloadLink.href = textToSaveAsURL;
@@ -80,7 +80,7 @@ export const useStorageStore = defineStore("storage", {
// store 10kb of data in local storage to check if it fails
const localStorageSize = JSON.stringify(localStorage).length;
console.log(`Local storage size: ${localStorageSize} bytes`);
- let data = new Array(10240).join("x");
+ const data = new Array(10240).join("x");
try {
localStorage.setItem("cashu.test", data);
localStorage.removeItem("cashu.test");
@@ -95,7 +95,7 @@ export const useStorageStore = defineStore("storage", {
},
cleanUpLocalStorageScheduler: function () {
const cleanUpInterval = 1000 * 60 * 60 * 24 * 7; // 7 day
- let lastCleanUp = this.lastLocalStorageCleanUp;
+ const lastCleanUp = this.lastLocalStorageCleanUp;
if (
!lastCleanUp ||
isNaN(new Date(lastCleanUp).getTime()) ||
@@ -115,12 +115,12 @@ export const useStorageStore = defineStore("storage", {
// from all paid invoices in this.invoiceHistory, delete the oldest so that only max 100 remain
const max_history = 200;
- let paidInvoices = walletStore.invoiceHistory.filter(
+ const paidInvoices = walletStore.invoiceHistory.filter(
(i) => i.status == "paid"
);
if (paidInvoices.length > max_history) {
- let sortedInvoices = paidInvoices.sort((a, b) => {
+ const sortedInvoices = paidInvoices.sort((a, b) => {
return new Date(a.date).getTime() - new Date(b.date).getTime();
});
const deleteInvoices = sortedInvoices.slice(
@@ -133,12 +133,12 @@ export const useStorageStore = defineStore("storage", {
}
// walk through the oldest paid tokenStore.historyTokens and delete the token
- let paidTokens = tokenStore.historyTokens.filter(
+ const paidTokens = tokenStore.historyTokens.filter(
(t) => t.status == "paid"
);
if (paidTokens.length > max_history) {
- let sortedTokens = paidTokens.sort((a, b) => {
+ const sortedTokens = paidTokens.sort((a, b) => {
return new Date(a.date).getTime() - new Date(b.date).getTime();
});
const deleteTokens = sortedTokens.slice(
diff --git a/src/stores/ui.ts b/src/stores/ui.ts
index 228bebe57..e1e36f9c9 100644
--- a/src/stores/ui.ts
+++ b/src/stores/ui.ts
@@ -127,7 +127,7 @@ export const useUiStore = defineStore("ui", {
return;
}
// enable debug terminal
- let script = document.createElement("script");
+ const script = document.createElement("script");
script.src = "//cdn.jsdelivr.net/npm/eruda";
document.body.appendChild(script);
script.onload = function () {
diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts
index 8f4f5b3be..7d5ae8642 100644
--- a/src/stores/wallet.ts
+++ b/src/stores/wallet.ts
@@ -311,7 +311,7 @@ export const useWalletStore = defineStore("wallet", {
if (base64Proofs.length > 0) {
base64Proofs.sort((a, b) => b.amount - a.amount);
let sum = 0;
- let selectedProofs: WalletProof[] = [];
+ const selectedProofs: WalletProof[] = [];
for (let i = 0; i < base64Proofs.length; i++) {
const proof = base64Proofs[i];
sum += proof.amount;
@@ -488,14 +488,14 @@ export const useWalletStore = defineStore("wallet", {
if (tokenJson == undefined) {
throw new Error("no tokens provided.");
}
- let proofs = token.getProofs(tokenJson);
+ const proofs = token.getProofs(tokenJson);
if (proofs.length == 0) {
throw new Error("no proofs found.");
}
const inputAmount = proofs.reduce((s, t) => (s += t.amount), 0);
let fee = 0;
- let mintInToken = token.getMint(tokenJson);
- let unitInToken = token.getUnit(tokenJson);
+ const mintInToken = token.getMint(tokenJson);
+ const unitInToken = token.getUnit(tokenJson);
const historyToken = {
amount: inputAmount,
@@ -860,7 +860,7 @@ export const useWalletStore = defineStore("wallet", {
// delete spent tokens from db
await proofsStore.removeProofs(sendProofs);
- let amount_paid = amount - proofsStore.sumProofs(data.change);
+ const amount_paid = amount - proofsStore.sumProofs(data.change);
useUiStore().vibrate();
if (!silent) {
notifySuccess(
@@ -1399,7 +1399,7 @@ export const useWalletStore = defineStore("wallet", {
this.payInvoiceData.show = false;
throw error;
}
- let cleanInvoice = {
+ const cleanInvoice = {
bolt11: invoice.paymentRequest,
memo: "",
msat: 0,
@@ -1507,12 +1507,12 @@ export const useWalletStore = defineStore("wallet", {
let host;
let data;
if (address.split("@").length == 2) {
- let [user, lnaddresshost] = address.split("@");
+ const [user, lnaddresshost] = address.split("@");
host = `https://${lnaddresshost}/.well-known/lnurlp/${user}`;
const resp = await axios.get(host); // Moved it here: we don't want 2 potential calls
data = resp.data;
} else if (address.toLowerCase().slice(0, 6) === "lnurl1") {
- let decoded = bech32.decode(address, 20000);
+ const decoded = bech32.decode(address, 20000);
const words = bech32.fromWords(decoded.words);
const uint8Array = new Uint8Array(words);
host = new TextDecoder().decode(uint8Array);
diff --git a/src/stores/workers.ts b/src/stores/workers.ts
index 4a4889c6c..16c971e20 100644
--- a/src/stores/workers.ts
+++ b/src/stores/workers.ts
@@ -77,7 +77,10 @@ export const useWorkersStore = defineStore("workers", {
this.clearAllWorkers();
}
console.log("### checkTokenSpendableWorker setInterval", nInterval);
- let paid = await walletStore.checkTokenSpendable(historyToken, false);
+ const paid = await walletStore.checkTokenSpendable(
+ historyToken,
+ false
+ );
if (paid) {
console.log("### stopping token check worker");
this.clearAllWorkers();
From 56811666920c5fa52ba788ff42c47b80e969e6f4 Mon Sep 17 00:00:00 2001
From: Pavol Rusnak
Date: Wed, 10 Dec 2025 16:35:10 +0100
Subject: [PATCH 21/73] enable eslint:recommended but silence some common
errors/warnings
---
.eslintrc.js | 12 +++++++++++-
quasar.config.js | 1 -
src/components/MintAuditSwapsBarChart.vue | 2 +-
3 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index 75d8c7ea9..a8dbfac1f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -17,7 +17,7 @@ module.exports = {
// Rules order is important, please avoid shuffling them
extends: [
// Base ESLint recommended rules
- // 'eslint:recommended',
+ "eslint:recommended",
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
@@ -70,6 +70,16 @@ module.exports = {
ignoreReadBeforeAssign: false,
},
],
+
+ // remove some warnings/errors from eslint:recommended for now
+ // which are quite common in the current codebase
+ // we will deal with them later on
+ "no-unused-vars": "off",
+ "no-undef": "off",
+ "no-empty": "off",
+ "no-useless-catch": "off",
+ "no-case-declarations": "off",
+ "no-constant-condition": "off",
},
overrides: [
{
diff --git a/quasar.config.js b/quasar.config.js
index c14336a68..4753890d0 100644
--- a/quasar.config.js
+++ b/quasar.config.js
@@ -59,7 +59,6 @@ module.exports = configure(function (/* ctx */) {
"roboto-font", // optional, you are not bound to it
"material-icons", // optional, you are not bound to it
- ,
],
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
diff --git a/src/components/MintAuditSwapsBarChart.vue b/src/components/MintAuditSwapsBarChart.vue
index 30973c61e..f188f24b2 100644
--- a/src/components/MintAuditSwapsBarChart.vue
+++ b/src/components/MintAuditSwapsBarChart.vue
@@ -357,7 +357,7 @@ export default defineComponent({
};
const formatDate = (dateStr: string) => {
- const hasTimezone = /([Zz]|[+\-]\d{2}:\d{2})$/.test(dateStr);
+ const hasTimezone = /([Zz]|[+-]\d{2}:\d{2})$/.test(dateStr);
let utcDateStr = dateStr;
if (!hasTimezone) {
From 00661892944f7216ccf53b1fa0e4fcd684f47a58 Mon Sep 17 00:00:00 2001
From: Pavol Rusnak
Date: Wed, 10 Dec 2025 21:52:15 +0100
Subject: [PATCH 22/73] fix eslint case-declarations
---
.eslintrc.js | 1 -
src/components/DisplayTokenComponent.vue | 3 ++-
src/stores/receiveTokensStore.ts | 9 ++++++---
3 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index a8dbfac1f..111fdd199 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -78,7 +78,6 @@ module.exports = {
"no-undef": "off",
"no-empty": "off",
"no-useless-catch": "off",
- "no-case-declarations": "off",
"no-constant-condition": "off",
},
overrides: [
diff --git a/src/components/DisplayTokenComponent.vue b/src/components/DisplayTokenComponent.vue
index c33299128..1f1561db3 100644
--- a/src/components/DisplayTokenComponent.vue
+++ b/src/components/DisplayTokenComponent.vue
@@ -602,7 +602,7 @@ export default defineComponent({
},
];
break;
- case "binary":
+ case "binary": {
const decoded = getDecodedToken(
this.sendData.tokensBase64
);
@@ -615,6 +615,7 @@ export default defineComponent({
},
];
break;
+ }
default:
throw new Error(
`Unknown NFC encoding: ${this.nfcEncoding}`
diff --git a/src/stores/receiveTokensStore.ts b/src/stores/receiveTokensStore.ts
index 20a8d0424..b92657aa7 100644
--- a/src/stores/receiveTokensStore.ts
+++ b/src/stores/receiveTokensStore.ts
@@ -151,7 +151,7 @@ export const useReceiveTokensStore = defineStore("receiveTokensStore", {
const recordType = record.recordType;
let tokenStr = "";
switch (recordType) {
- case "text":
+ case "text": {
const text = new TextDecoder().decode(record.data);
if (!text.startsWith("cashu")) {
throw new Error(
@@ -160,7 +160,8 @@ export const useReceiveTokensStore = defineStore("receiveTokensStore", {
}
tokenStr = text;
break;
- case "url":
+ }
+ case "url": {
const url = new TextDecoder().decode(record.data);
const i = url.indexOf("#token=cashu");
if (i === -1) {
@@ -168,7 +169,8 @@ export const useReceiveTokensStore = defineStore("receiveTokensStore", {
}
tokenStr = url.substring(i + 7);
break;
- case "mime":
+ }
+ case "mime": {
if (record.mediaType !== "application/octet-stream") {
throw new Error("binary data expected");
}
@@ -182,6 +184,7 @@ export const useReceiveTokensStore = defineStore("receiveTokensStore", {
const token = getDecodedTokenBinary(data);
tokenStr = getEncodedToken(token);
break;
+ }
default:
throw new Error(`unsupported recordType ${recordType}`);
}
From 58bdd31ef8508ae7dbb1201a7fa0d099baa153a2 Mon Sep 17 00:00:00 2001
From: Pavol Rusnak
Date: Thu, 11 Dec 2025 09:17:08 +0100
Subject: [PATCH 23/73] use node:24 in Dockerfile
---
Dockerfile | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index a9e32e481..2214a9a03 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Stage 1: Build Phase
-FROM node:24-bullseye AS builder
+FROM node:24 AS builder
WORKDIR /app
@@ -23,4 +23,3 @@ COPY --from=builder /app/dist/pwa /usr/share/nginx/html
# Expose the port your app will run on
EXPOSE 80
-
From 7fe371a5cbd0112698508ad4e47d35fdc295c747 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hynek=20J=C3=ADna?=
<26002916+hynek-jina@users.noreply.github.com>
Date: Mon, 22 Dec 2025 16:01:24 +0100
Subject: [PATCH 24/73] added czech translations
---
package-lock.json | 30 -
src/components/SettingsView.vue | 1 +
src/i18n/cs-CZ/index.ts | 1544 +++++++++++++++++++++++++++
src/i18n/index.ts | 2 +
src/pages/WelcomePage.vue | 1 +
src/pages/welcome/WelcomeSlide1.vue | 1 +
6 files changed, 1549 insertions(+), 30 deletions(-)
create mode 100644 src/i18n/cs-CZ/index.ts
diff --git a/package-lock.json b/package-lock.json
index 5a0ef9ebe..d9ea72c8e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -116,7 +116,6 @@
"version": "7.24.5",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.2",
@@ -1943,7 +1942,6 @@
"node_modules/@capacitor/core": {
"version": "6.2.0",
"license": "MIT",
- "peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -3402,7 +3400,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/core": "^0.14.0"
@@ -3439,7 +3436,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3452,7 +3448,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3477,7 +3472,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0",
@@ -3521,7 +3515,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3645,7 +3638,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3658,7 +3650,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3674,7 +3665,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -5453,7 +5443,6 @@
"version": "20.16.1",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~6.19.2"
}
@@ -5587,7 +5576,6 @@
"version": "5.62.0",
"dev": true,
"license": "BSD-2-Clause",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0",
@@ -5740,7 +5728,6 @@
"version": "2.3.4",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12.0.0"
},
@@ -6036,7 +6023,6 @@
"version": "8.11.3",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -7508,7 +7494,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668",
@@ -10389,7 +10374,6 @@
"version": "8.57.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -15222,7 +15206,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.8",
"picocolors": "^1.1.1",
@@ -15482,7 +15465,6 @@
"node_modules/qrcode": {
"version": "1.5.3",
"license": "MIT",
- "peer": true,
"dependencies": {
"dijkstrajs": "^1.0.1",
"encode-utf8": "^1.0.3",
@@ -15520,7 +15502,6 @@
"node_modules/quasar": {
"version": "2.18.2",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">= 10.18.1",
"npm": ">= 6.13.4",
@@ -16255,7 +16236,6 @@
"version": "2.77.3",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -17790,7 +17770,6 @@
"version": "4.0.3",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -18150,7 +18129,6 @@
"version": "5.5.4",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -18486,7 +18464,6 @@
"version": "2.9.18",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.14.27",
"postcss": "^8.4.13",
@@ -18639,7 +18616,6 @@
"version": "4.0.3",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -18944,7 +18920,6 @@
"version": "4.0.3",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -18994,7 +18969,6 @@
"version": "6.3.6",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -19067,7 +19041,6 @@
"node_modules/vue": {
"version": "3.4.38",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/compiler-dom": "3.4.38",
"@vue/compiler-sfc": "3.4.38",
@@ -19156,7 +19129,6 @@
"node_modules/vue-router": {
"version": "4.3.2",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/devtools-api": "^6.5.1"
},
@@ -19416,7 +19388,6 @@
"version": "6.6.1",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@apideck/better-ajv-errors": "^0.3.1",
"@babel/core": "^7.11.1",
@@ -19480,7 +19451,6 @@
"version": "8.13.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"json-schema-traverse": "^1.0.0",
diff --git a/src/components/SettingsView.vue b/src/components/SettingsView.vue
index 9973ddb0a..90b787f29 100644
--- a/src/components/SettingsView.vue
+++ b/src/components/SettingsView.vue
@@ -1928,6 +1928,7 @@ export default defineComponent({
{ label: "Italiano", value: "it-IT" },
{ label: "Deutsch", value: "de-DE" },
{ label: "Français", value: "fr-FR" },
+ { label: "Čeština", value: "cs-CZ" },
{ label: "Svenska", value: "sv-SE" },
{ label: "Ελληνικά", value: "el-GR" },
{ label: "Türkçe", value: "tr-TR" },
diff --git a/src/i18n/cs-CZ/index.ts b/src/i18n/cs-CZ/index.ts
new file mode 100644
index 000000000..a360acf3e
--- /dev/null
+++ b/src/i18n/cs-CZ/index.ts
@@ -0,0 +1,1544 @@
+export default {
+ global: {
+ copy_to_clipboard: {
+ success: "Zkopírováno do schránky!",
+ },
+ actions: {
+ add_mint: {
+ label: "Přidat mint",
+ },
+ cancel: {
+ label: "Zrušit",
+ },
+ copy: {
+ label: "Kopírovat",
+ },
+ close: {
+ label: "Zavřít",
+ },
+ enter: {
+ label: "Potvrdit",
+ },
+ lock: {
+ label: "Uzamknout",
+ },
+ paste: {
+ label: "Vložit",
+ },
+ receive: {
+ label: "Přijmout",
+ },
+ scan: {
+ label: "Skenovat",
+ },
+ send: {
+ label: "Odeslat",
+ },
+ pay: {
+ label: "Zaplatit",
+ },
+ swap: {
+ label: "Směnit",
+ },
+ update: {
+ label: "Aktualizovat",
+ },
+ },
+ inputs: {
+ mint_url: {
+ label: "URL mintu",
+ },
+ },
+ },
+ common: {
+ fee: "Poplatek",
+ },
+ MultinutPicker: {
+ payment: "Platba Multinut",
+ selectMints:
+ "Vyberte jeden nebo více mintů, ze kterých se má platba provést.",
+ totalSelectedBalance: "Celkový vybraný zůstatek",
+ multiMintPay: "Platba z více mintů",
+ balanceNotEnough: "Zůstatek napříč minty nestačí k uhrazení této faktury",
+ failed: "Zpracování selhalo: {error}",
+ paid: "Zaplaceno {amount} přes Lightning",
+ },
+ wallet: {
+ notifications: {
+ balance_too_low: "Zůstatek je příliš nízký",
+ received: "Přijato {amount}",
+ fee: " (poplatek: {fee})",
+ could_not_request_mint: "Nepodařilo se požádat mint",
+ invoice_still_pending: "Faktura zatím nevyřízena",
+ paid_lightning: "Zaplaceno {amount} přes Lightning",
+ payment_pending_refresh: "Platba nevyřízena. Obnovte fakturu ručně.",
+ sent: "Odesláno {amount}",
+ token_still_pending: "Token stále nevyřízen",
+ received_lightning: "Přijato {amount} přes Lightning",
+ lightning_payment_failed: "Lightning platba selhala",
+ failed_to_decode_invoice: "Nepodařilo se dekódovat fakturu",
+ invalid_lnurl: "Neplatné LNURL",
+ lnurl_error: "Chyba LNURL",
+ no_amount: "Nebyla zadána částka",
+ no_lnurl_data: "Žádná LNURL data",
+ no_price_data: "Žádná cenová data.",
+ please_try_again: "Zkuste to prosím znovu.",
+ },
+ mint: {
+ notifications: {
+ already_added: "Mint je již přidán",
+ added: "Mint přidán",
+ not_found: "Mint nebyl nalezen",
+ activation_failed: "Aktivace mintu selhala",
+ no_active_mint: "Žádný aktivní mint",
+ unit_activation_failed: "Aktivace jednotky selhala",
+ unit_not_supported: "Jednotka není mintem podporována",
+ activated: "Mint aktivován",
+ could_not_connect: "Nelze se připojit k mintu",
+ could_not_get_info: "Nelze získat informace o mintu",
+ could_not_get_keys: "Nelze získat klíče mintu",
+ could_not_get_keysets: "Nelze získat keysety mintu",
+ mint_validation_error: "Chyba ověření mintu",
+ removed: "Mint odebrán",
+ error: "Chyba mintu",
+ },
+ },
+ },
+ MainHeader: {
+ menu: {
+ settings: {
+ title: "Nastavení",
+ settings: {
+ title: "Nastavení",
+ caption: "Nastavení peněženky",
+ },
+ },
+ terms: {
+ title: "Podmínky",
+ terms: {
+ title: "Podmínky",
+ caption: "Podmínky služby",
+ },
+ },
+ links: {
+ title: "Odkazy",
+ cashuSpace: {
+ title: "Cashu.space",
+ caption: "cashu.space",
+ },
+ github: {
+ title: "GitHub",
+ caption: "github.com/cashubtc",
+ },
+ telegram: {
+ title: "Telegram",
+ caption: "t.me/CashuMe",
+ },
+ twitter: {
+ title: "Twitter",
+ caption: "{'@'}CashuBTC",
+ },
+ donate: {
+ title: "Darovat",
+ caption: "Podpořit Cashu",
+ },
+ },
+ },
+ offline: {
+ warning: {
+ text: "Offline",
+ },
+ },
+ reload: {
+ warning: {
+ text: "Obnovení za { countdown }",
+ },
+ },
+ staging: {
+ warning: {
+ text: "Staging – nepoužívejte se skutečnými prostředky!",
+ },
+ },
+ },
+ FullscreenHeader: {
+ actions: {
+ back: {
+ label: "Peněženka",
+ },
+ },
+ },
+ Settings: {
+ language: {
+ title: "Jazyk",
+ description: "Vyberte si preferovaný jazyk ze seznamu níže.",
+ },
+ sections: {
+ backup_restore: "ZÁLOHA A OBNOVA",
+ lightning_address: "LIGHTNING ADRESA",
+ nostr_keys: "NOSTR KLÍČE",
+ nostr: {
+ title: "NOSTR",
+ relays: {
+ expand_label: "Klikněte pro úpravu relayů",
+ add: {
+ title: "Přidat relay",
+ description:
+ "Vaše peněženka používá tyto relaye pro Nostr operace, jako jsou žádosti o platbu, Nostr Wallet Connect a zálohy.",
+ },
+ list: {
+ title: "Relaye",
+ description: "Vaše peněženka se připojí k těmto relayům.",
+ copy_tooltip: "Kopírovat relay",
+ remove_tooltip: "Odebrat relay",
+ },
+ },
+ },
+ payment_requests: "ŽÁDOSTI O PLATBU",
+ nostr_wallet_connect: "NOSTR WALLET CONNECT",
+ hardware_features: "HARDWAROVÉ FUNKCE",
+ p2pk_features: "P2PK FUNKCE",
+ privacy: "SOUKROMÍ",
+ experimental: "EXPERIMENTÁLNÍ",
+ appearance: "VZHLED",
+ },
+ backup_restore: {
+ backup_seed: {
+ title: "Záloha seed fráze",
+ description:
+ "Seed fráze umožňuje obnovit vaši peněženku. Uchovávejte ji v bezpečí a v soukromí.",
+ seed_phrase_label: "Seed fráze",
+ },
+ restore_ecash: {
+ title: "Obnovit ecash",
+ description:
+ "Průvodce obnovou vám umožní obnovit ztracený ecash pomocí mnemotechnické seed fráze. Seed fráze vaší aktuální peněženky zůstane nedotčena – průvodce umožňuje obnovu ecashe pouze z jiné seed fráze.",
+ button: "Obnovit",
+ },
+ },
+ lightning_address: {
+ title: "Lightning adresa",
+ description: "Přijímejte platby na svou Lightning adresu.",
+ enable: {
+ toggle: "Povolit",
+ description: "Lightning adresa s npub.cash",
+ },
+ address: {
+ copy_tooltip: "Kopírovat Lightning adresu",
+ },
+ automatic_claim: {
+ toggle: "Přijímat automaticky",
+ description: "Automaticky přijímat příchozí platby.",
+ },
+ npc_v2: {
+ choose_mint_title: "Vyberte mint pro npub.cash v2",
+ choose_mint_placeholder: "Vyberte mint…",
+ },
+ },
+
+ nostr_keys: {
+ title: "Vaše Nostr klíče",
+ description:
+ "Vaše Nostr klíče budou použity k určení vaší Lightning adresy.",
+ wallet_seed: {
+ title: "Seed fráze peněženky",
+ description: "Vygenerovat Nostr klíčový pár ze seedu peněženky",
+ copy_nsec: "Kopírovat nsec",
+ },
+ nsec_bunker: {
+ title: "Nsec Bunker",
+ description: "Použít NIP-46 bunker",
+ delete_tooltip: "Odstranit připojení",
+ },
+ use_nsec: {
+ title: "Použít vlastní nsec",
+ description: "Tato metoda je nebezpečná a nedoporučuje se",
+ delete_tooltip: "Odstranit nsec",
+ },
+ signing_extension: {
+ title: "Podepisovací rozšíření",
+ description: "Použít podepisovací rozšíření NIP-07",
+ not_found: "Nenalezeno žádné podepisovací rozšíření NIP-07",
+ },
+ },
+
+ payment_requests: {
+ title: "Žádosti o platbu",
+ description:
+ "Žádosti o platbu vám umožňují přijímat platby přes Nostr. Pokud tuto funkci povolíte, vaše peněženka se přihlásí k odběru vašich Nostr relayů.",
+ enable_toggle: "Povolit žádosti o platbu",
+ claim_automatically: {
+ toggle: "Přijímat automaticky",
+ description: "Automaticky přijímat příchozí platby.",
+ },
+ },
+
+ nostr_wallet_connect: {
+ title: "Nostr Wallet Connect (NWC)",
+ description:
+ "Použijte NWC k ovládání své peněženky z jakékoli jiné aplikace.",
+ enable_toggle: "Povolit NWC",
+ payments_note:
+ "NWC lze použít pouze pro platby z vašeho bitcoinového zůstatku. Platby budou prováděny z aktivního mintu.",
+ connection: {
+ copy_tooltip: "Kopírovat připojovací řetězec",
+ qr_tooltip: "Zobrazit QR kód",
+ allowance_label: "Zbývající limit (sat)",
+ },
+ },
+
+ hardware_features: {
+ webnfc: {
+ title: "WebNFC",
+ description: "Vyberte kódování pro zápis na NFC karty",
+ text: {
+ title: "Text",
+ description: "Uložit token jako prostý text",
+ },
+ weburl: {
+ title: "URL",
+ description: "Uložit URL této peněženky spolu s tokenem",
+ },
+ binary: {
+ title: "Binární",
+ description: "Uložit tokeny jako binární data",
+ },
+ quick_access: {
+ toggle: "Rychlý přístup k NFC",
+ description:
+ "Rychlé skenování NFC karet v nabídce Přijmout ecash. Tato volba přidá tlačítko NFC do nabídky Přijmout ecash.",
+ },
+ },
+ },
+
+ p2pk_features: {
+ title: "P2PK",
+ description:
+ "Vygenerujte klíčový pár pro příjem ecashe uzamčeného pomocí P2PK. Varování: Tato funkce je experimentální. Používejte ji pouze s malými částkami. Pokud ztratíte své soukromé klíče, nikdo už nebude schopen ecash uzamčený k nim odemknout.",
+ generate_button: "Vygenerovat klíč",
+ import_button: "Importovat nsec",
+ quick_access: {
+ toggle: "Rychlý přístup k uzamčení",
+ description:
+ "Použijte tuto možnost pro rychlé zobrazení vašeho P2PK uzamykacího klíče v nabídce Přijmout ecash.",
+ },
+ keys_expansion: {
+ label: "Klikněte pro zobrazení {count} klíčů",
+ used_badge: "použito",
+ },
+ },
+
+ privacy: {
+ title: "Soukromí",
+ description: "Tato nastavení ovlivňují vaše soukromí.",
+ check_incoming: {
+ toggle: "Kontrolovat příchozí faktury",
+ description:
+ "Pokud je povoleno, peněženka bude na pozadí kontrolovat nejnovější fakturu. To zvyšuje odezvu peněženky, ale také usnadňuje fingerprinting. Nezaplacené faktury můžete kontrolovat ručně na kartě Faktury.",
+ },
+ check_startup: {
+ toggle: "Kontrolovat čekající faktury při spuštění",
+ description:
+ "Pokud je povoleno, peněženka při spuštění zkontroluje čekající faktury z posledních 24 hodin.",
+ },
+ check_all: {
+ toggle: "Kontrolovat všechny faktury",
+ description:
+ "Pokud je povoleno, peněženka bude až po dobu dvou týdnů periodicky kontrolovat nezaplacené faktury na pozadí. To zvyšuje síťovou aktivitu peněženky a usnadňuje fingerprinting. Nezaplacené faktury můžete kontrolovat ručně na kartě Faktury.",
+ },
+ check_sent: {
+ toggle: "Kontrolovat odeslaný ecash",
+ description:
+ "Pokud je povoleno, peněženka bude pomocí periodických kontrol na pozadí zjišťovat, zda byly odeslané tokeny uplatněny. To zvyšuje síťovou aktivitu peněženky a usnadňuje fingerprinting.",
+ },
+ websockets: {
+ toggle: "Používat WebSockety",
+ description:
+ "Pokud je povoleno, peněženka bude používat dlouhodobá WebSocket připojení pro přijímání aktualizací o zaplacených fakturách a utracených tokenech z mintů. To zvyšuje odezvu peněženky, ale také usnadňuje fingerprinting.",
+ },
+ bitcoin_price: {
+ toggle: "Získávat kurz z Coinbase",
+ description:
+ "Pokud je povoleno, aktuální kurz Bitcoinu bude načítán z coinbase.com a převedený zůstatek bude zobrazen.",
+ currency: {
+ title: "Fiat měna",
+ description: "Vyberte fiat měnu pro zobrazení ceny Bitcoinu.",
+ },
+ },
+ },
+
+ experimental: {
+ title: "Experimentální",
+ description: "Tyto funkce jsou experimentální.",
+ receive_swaps: {
+ toggle: "Přijímat swapy",
+ badge: "Beta",
+ description:
+ "Možnost směnit přijatý ecash na váš aktivní mint v dialogu Přijmout ecash.",
+ },
+ auto_paste: {
+ toggle: "Automaticky vkládat ecash",
+ description:
+ "Automaticky vloží ecash ze schránky při stisknutí Přijmout → Ecash → Vložit. Automatické vkládání může na iOS způsobovat problémy v UI – pokud k nim dochází, vypněte tuto funkci.",
+ },
+ auditor: {
+ toggle: "Povolit auditora",
+ badge: "Beta",
+ description:
+ "Pokud je povoleno, peněženka zobrazí informace o auditorovi v dialogu detailů mintu. Auditor je služba třetí strany, která sleduje spolehlivost mintů.",
+ url_label: "URL auditora",
+ api_url_label: "API URL auditora",
+ },
+ multinut: {
+ toggle: "Povolit Multinut",
+ description:
+ "Pokud je povoleno, peněženka použije Multinut k placení faktur z více mintů najednou.",
+ },
+ nostr_mint_backup: {
+ toggle: "Zálohovat seznam mintů na Nostr",
+ description:
+ "Pokud je povoleno, váš seznam mintů bude automaticky zálohován na Nostr relaye pomocí nakonfigurovaných Nostr klíčů. To umožňuje obnovu seznamu mintů napříč zařízeními.",
+ notifications: {
+ enabled: "Záloha mintů na Nostr povolena",
+ disabled: "Záloha mintů na Nostr zakázána",
+ failed: "Nepodařilo se povolit zálohu mintů na Nostr",
+ },
+ },
+ },
+ appearance: {
+ keyboard: {
+ title: "Klávesnice na obrazovce",
+ description: "Použít číselnou klávesnici pro zadávání částek.",
+ toggle: "Použít číselnou klávesnici",
+ toggle_description:
+ "Pokud je povoleno, pro zadávání částek bude použita číselná klávesnice.",
+ },
+ theme: {
+ title: "Vzhled",
+ description: "Změňte vzhled své peněženky.",
+ tooltips: {
+ mono: "mono",
+ cyber: "cyber",
+ freedom: "freedom",
+ nostr: "nostr",
+ bitcoin: "bitcoin",
+ mint: "mint",
+ nut: "nut",
+ blu: "blu",
+ flamingo: "flamingo",
+ },
+ },
+ bip177: {
+ title: "Symbol Bitcoinu",
+ description: "Používat symbol ₿ místo satů.",
+ toggle: "Používat symbol ₿",
+ },
+ },
+ web_of_trust: {
+ title: "Web of trust",
+ known_pubkeys: "Známé pubkey: {wotCount}",
+ continue_crawl: "Pokračovat v procházení",
+ crawl_odell: "Procházet ODELLŮV WEB OF TRUST",
+ crawl_wot: "Procházet web of trust",
+ pause: "Pozastavit",
+ reset: "Resetovat",
+ progress: "{crawlProcessed} / {crawlTotal}",
+ },
+ npub_cash: {
+ use_npubx: "Použít npubx.cash",
+ copy_lightning_address: "Kopírovat Lightning adresu",
+ v2_mint: "mint pro npub.cash v2",
+ },
+ multinut: {
+ use_multinut: "Použít Multinut",
+ },
+ advanced: {
+ title: "Pokročilé",
+ developer: {
+ title: "Vývojářská nastavení",
+ description: "Následující nastavení slouží pro vývoj a ladění.",
+ new_seed: {
+ button: "Vygenerovat novou seed frázi",
+ description:
+ "Tímto se vygeneruje nová seed fráze. Abyste ji mohli později obnovit, musíte si nejprve poslat celý zůstatek sami sobě.",
+ confirm_question: "Opravdu chcete vygenerovat novou seed frázi?",
+ cancel: "Zrušit",
+ confirm: "Potvrdit",
+ },
+ remove_spent: {
+ button: "Odstranit utracené proofy",
+ description:
+ "Zkontroluje, zda jsou ecash tokeny z vašich aktivních mintů utracené, a odstraní ty utracené z peněženky. Používejte pouze v případě, že je peněženka zaseknutá.",
+ },
+ debug_console: {
+ button: "Přepnout ladicí konzoli",
+ description:
+ "Otevře ladicí Javascript konzoli. Nikdy sem nevkládejte nic, čemu nerozumíte. Útočník by vás mohl přimět vložit sem škodlivý kód.",
+ },
+ export_proofs: {
+ button: "Exportovat aktivní proofy",
+ description:
+ "Zkopíruje celý váš zůstatek z aktivního mintu jako Cashu token do schránky. Exportuje se pouze vybraný mint a jednotka. Pro kompletní export vyberte jiný mint a jednotku a export opakujte.",
+ },
+ keyset_counters: {
+ title: "Navýšit čítače keysetů",
+ description:
+ "Kliknutím na ID keysetu zvýšíte čítače derivační cesty pro keysety ve vaší peněžence. To je užitečné, pokud se zobrazí chyba „outputs have already been signed“.",
+ counter: "čítač: {count}",
+ },
+ unset_reserved: {
+ button: "Zrušit rezervaci všech tokenů",
+ description:
+ "Tato peněženka označuje čekající odchozí ecash jako rezervovaný (a odečítá jej ze zůstatku), aby zabránila pokusům o double-spend. Toto tlačítko zruší rezervaci všech tokenů, aby je bylo možné znovu použít. Pokud tak učiníte, peněženka může obsahovat utracené proofy. Pro jejich odstranění použijte tlačítko „Odstranit utracené proofy“.",
+ },
+ show_onboarding: {
+ button: "Zobrazit onboarding",
+ description: "Znovu zobrazí úvodní obrazovky.",
+ },
+ reset_wallet: {
+ button: "Resetovat data peněženky",
+ description:
+ "Resetuje data vaší peněženky. Varování: Tímto dojde ke smazání všeho! Nezapomeňte si nejprve vytvořit zálohu.",
+ confirm_question: "Opravdu chcete smazat data peněženky?",
+ cancel: "Zrušit",
+ confirm: "Smazat peněženku",
+ },
+ export_wallet: {
+ button: "Exportovat data peněženky",
+ description:
+ "Stáhne výpis vaší peněženky. Z tohoto souboru můžete obnovit peněženku na úvodní obrazovce nové peněženky. Pokud budete peněženku po exportu dál používat, soubor nebude aktuální.",
+ },
+ import_wallet: {
+ button: "Importovat zálohu peněženky",
+ description:
+ "Obnoví peněženku z dříve exportovaného záložního souboru. Tímto nahradíte aktuální data peněženky daty ze zálohy.",
+ confirm_question: "Opravdu chcete obnovit data peněženky?",
+ cancel: "Zrušit",
+ confirm: "IMPORTOVAT ZÁLOHU PENĚŽENKY",
+ },
+ },
+ },
+ },
+ NoMintWarnBanner: {
+ title: "Připojte mint",
+ subtitle:
+ "Ještě jste se nepřipojili k žádnému Cashu mintu. Přidejte URL mintu v nastavení nebo přijměte ecash z nového mintu, abyste mohli začít.",
+ actions: {
+ add_mint: {
+ label: "@:global.actions.add_mint.label",
+ },
+ receive: {
+ label: "Přijmout Ecash",
+ },
+ },
+ },
+ WalletPage: {
+ actions: {
+ send: {
+ label: "@:global.actions.send.label",
+ },
+ receive: {
+ label: "@:global.actions.receive.label",
+ },
+ },
+ tabs: {
+ history: {
+ label: "Historie",
+ },
+ invoices: {
+ label: "Faktury",
+ },
+ mints: {
+ label: "Minty",
+ },
+ },
+ install: {
+ text: "Instalovat",
+ tooltip: "Nainstalovat Cashu",
+ },
+ },
+ AlreadyRunning: {
+ title: "Ne.",
+ text: "Jiná karta už běží. Zavřete ji a zkuste to znovu.",
+ actions: {
+ retry: {
+ label: "Zkusit znovu",
+ },
+ },
+ },
+ ErrorNotFound: {
+ title: "404",
+ text: "Oops. Nic tu není…",
+ actions: {
+ home: {
+ label: "Zpět domů",
+ },
+ },
+ },
+ BalanceView: {
+ mintUrl: {
+ label: "Mint",
+ },
+ mintBalance: {
+ label: "Zůstatek",
+ },
+ mintError: {
+ label: "Chyba mintu",
+ },
+ pending: {
+ label: "Čekající",
+ tooltip: "Zkontrolovat všechny čekající tokeny",
+ },
+ },
+ WelcomePage: {
+ actions: {
+ previous: {
+ label: "Předchozí",
+ },
+ next: {
+ label: "Další",
+ },
+ },
+ },
+ WelcomeSlide1: {
+ title: "Vítejte v Cashu",
+ text: "Cashu.me je bezplatná a open-source bitcoinová peněženka, která používá ecash pro bezpečné a soukromé uchování vašich prostředků.",
+ actions: {
+ more: {
+ label: "Klikněte pro více informací",
+ },
+ },
+ p1: {
+ text: "Cashu je bezplatný a open-source ecash protokol pro Bitcoin. Více informací najdete na { link }.",
+ link: {
+ text: "cashu.space",
+ },
+ },
+ p2: {
+ text: "Tato peněženka není spojena s žádným mintem. Chcete-li ji používat, musíte se připojit k jednomu nebo více Cashu mintům, kterým důvěřujete.",
+ },
+ p3: {
+ text: "Tato peněženka uchovává ecash, ke kterému máte přístup pouze vy. Pokud smažete data prohlížeče bez zálohy seed fráze, ztratíte své tokeny.",
+ },
+ p4: {
+ text: "Tato peněženka je v beta verzi. Nenese odpovědnost za ztrátu přístupu k prostředkům. Používejte na vlastní riziko! Tento kód je open-source a licencován pod licencí MIT.",
+ },
+ },
+ WelcomeSlide2: {
+ title: "Nainstalujte PWA",
+ alt: {
+ pwa_example: "Příklad instalace PWA",
+ },
+ installing: "Instaluje se…",
+ instruction: {
+ intro: {
+ text: "Pro nejlepší zážitek používejte tuto peněženku ve webovém prohlížeči vašeho zařízení a nainstalujte ji jako Progressive Web App.",
+ },
+ android: {
+ title: "Android (Chrome)",
+ step1: {
+ item: "1. { icon } { text }",
+ text: "Klepněte na menu (vpravo nahoře)",
+ },
+ step2: {
+ item: "2. { icon } { text }",
+ text: "Stiskněte { buttonText }",
+ buttonText: "@:AndroidPWAPrompt.buttonText",
+ },
+ },
+ ios: {
+ title: "iOS (Safari)",
+ step1: {
+ item: "1. { icon } { text }",
+ text: "Klepněte na sdílení (dole)",
+ },
+ step2: {
+ item: "2. { icon } { text }",
+ text: "Stiskněte { buttonText }",
+ buttonText: "@:iOSPWAPrompt.buttonText",
+ },
+ },
+ outro: {
+ text: "Jakmile tuto aplikaci nainstalujete, zavřete okno prohlížeče a používejte aplikaci z domovské obrazovky.",
+ },
+ },
+ pwa: {
+ success: {
+ title: "Úspěch!",
+ text: "Používáte Cashu jako PWA. Zavřete všechny ostatní okna prohlížeče a používejte aplikaci z domovské obrazovky.",
+ nextSteps:
+ "Nyní můžete zavřít tuto kartu prohlížeče a otevřít aplikaci z domovské obrazovky.",
+ },
+ },
+ },
+ iOSPWAPrompt: {
+ text: "Klepněte na { icon } a { buttonText }",
+ buttonText: "Přidat na domovskou obrazovku",
+ },
+ AndroidPWAPrompt: {
+ text: "Klepněte na { icon } a { buttonText }",
+ buttonText: "Přidat na domovskou obrazovku",
+ },
+ WelcomeSlide3: {
+ title: "Vaše seed fráze",
+ text: "Uložte svou seed frázi do správce hesel nebo na papír. Vaše seed fráze je jediný způsob, jak obnovit prostředky, pokud ztratíte přístup k tomuto zařízení.",
+ inputs: {
+ seed_phrase: {
+ label: "Seed fráze",
+ caption: "Seed frázi najdete v nastavení.",
+ },
+ checkbox: {
+ label: "Zapsal(a) jsem si ji",
+ },
+ },
+ },
+ WelcomeSlide4: {
+ title: "Podmínky",
+ actions: {
+ more: {
+ label: "Přečíst Podmínky služby",
+ },
+ },
+ inputs: {
+ checkbox: {
+ label: "Přečetl(a) jsem a souhlasím s těmito podmínkami",
+ },
+ },
+ },
+ WelcomeSlideChoice: {
+ title: "Nastavte svou peněženku",
+ text: "Chcete obnovit ze seed fráze nebo vytvořit novou peněženku?",
+ options: {
+ new: {
+ title: "Vytvořit novou peněženku",
+ subtitle: "Vygenerovat nový seed a přidat minty.",
+ },
+ recover: {
+ title: "Obnovit peněženku",
+ subtitle: "Zadejte svou seed frázi, obnovte minty a ecash.",
+ },
+ },
+ },
+ WelcomeMintSetup: {
+ title: "Přidat minty",
+ text: "Minty jsou servery, které vám pomáhají odesílat a přijímat ecash. Vyberte nalezený mint nebo přidejte ručně. Přeskočte a přidejte minty později.",
+ sections: {
+ your_mints: "Vaše minty",
+ },
+ restoring: "Obnovují se minty…",
+ placeholder: {
+ mint_url: "https://",
+ },
+ },
+ WelcomeRecoverSeed: {
+ title: "Zadejte seed frázi",
+ text: "Vložte nebo napište svou 12slovnou seed frázi pro obnovení.",
+ inputs: {
+ word: "Slovo { index }",
+ },
+ actions: {
+ paste_all: "Vložit vše",
+ },
+ disclaimer:
+ "Vaše seed fráze se používá pouze lokálně pro odvození klíčů peněženky.",
+ },
+ WelcomeRestoreEcash: {
+ title: "Obnovte svůj ecash",
+ text: "Skenujte nevyužité proofy na nakonfigurovaných mintech a přidejte je do peněženky.",
+ },
+ MintRatings: {
+ title: "Recenze mintu",
+ reviews: "recenze",
+ ratings: "Hodnocení",
+ no_reviews: "Žádné recenze nebyly nalezeny",
+ your_review: "Vaše recenze",
+ no_reviews_to_display: "Žádné recenze k zobrazení.",
+ no_rating: "Bez hodnocení",
+ out_of: "z",
+ rows: "Recenze",
+ sort: "Třídit",
+ sort_options: {
+ newest: "Nejnovější",
+ oldest: "Nejstarší",
+ highest: "Nejvyšší",
+ lowest: "Nejnižší",
+ },
+ actions: {
+ write_review: "Napsat recenzi",
+ },
+ empty_state_subtitle:
+ "Pomozte tím, že napíšete recenzi. Sdílejte svou zkušenost s tímto mintem a pomozte ostatním.",
+ },
+
+ CreateMintReview: {
+ title: "Recenze mintu",
+ publishing_as: "Publikujete jako",
+ inputs: {
+ rating: { label: "Hodnocení" },
+ review: { label: "Recenze (volitelně)" },
+ },
+ actions: {
+ publish: { label: "Odeslat recenzi", in_progress: "Odesílání…" },
+ },
+ },
+
+ RestoreView: {
+ seed_phrase: {
+ label: "Obnovit ze seed fráze",
+ caption:
+ "Zadejte svou seed frázi pro obnovení peněženky. Před obnovením se ujistěte, že jste přidali všechny minty, které jste dříve používali.",
+ inputs: {
+ seed_phrase: {
+ label: "Seed fráze",
+ caption: "Seed frázi najdete v nastavení.",
+ },
+ },
+ },
+ information: {
+ label: "Informace",
+ caption:
+ "Tento průvodce obnoví pouze ecash z jiné seed fráze, nebudete moci tuto seed frázi používat ani měnit seed frázi aktuální peněženky. Obnovený ecash tak nebude chráněn vaší aktuální seed frází, dokud ho jednou nepošlete sami sobě.",
+ },
+ restore_mints: {
+ label: "Obnovit minty",
+ caption:
+ 'Vyberte mint k obnovení. Další minty můžete přidat na hlavní obrazovce pod "Minty" a obnovit je zde.',
+ },
+ actions: {
+ paste: {
+ error: "Nepodařilo se načíst obsah schránky.",
+ },
+ validate: {
+ error: "Mnemonic musí mít alespoň 12 slov.",
+ },
+ select_all: {
+ label: "Vybrat vše",
+ },
+ deselect_all: {
+ label: "Odznačit vše",
+ },
+ restore: {
+ label: "Obnovit",
+ in_progress: "Obnovuje se mint …",
+ error: "Chyba při obnově mintu: { error }",
+ },
+ restore_all_mints: {
+ label: "Obnovit všechny minty",
+ in_progress: "Obnovuje se mint { index } z { length } …",
+ success: "Obnova dokončena úspěšně",
+ error: "Chyba při obnově mintů: { error }",
+ },
+ restore_selected_mints: {
+ label: "Obnovit vybrané minty ({count})",
+ in_progress: "Obnovuje se mint { index } z { length } …",
+ success: "Úspěšně obnoven(a) {count} mint(y)",
+ error: "Chyba při obnově vybraných mintů: { error }",
+ },
+ },
+ nostr_mints: {
+ label: "Obnovit minty z Nostr",
+ caption:
+ "Hledejte zálohy mintů uložené na Nostr relays pomocí vaší seed fráze. Pomůže to objevit minty, které jste dříve používali.",
+ search_button: "Hledat zálohy mintů",
+ select_all: "Vybrat vše",
+ deselect_all: "Odznačit vše",
+ backed_up: "Zálohováno",
+ already_added: "Již přidáno",
+ add_selected: "Přidat vybrané ({count})",
+ no_backups_found: "Nenalezeny žádné zálohy mintů",
+ no_backups_hint:
+ "Ujistěte se, že záloha mintů na Nostr je v nastavení povolena, aby se váš seznam mintů automaticky zálohoval.",
+ invalid_mnemonic: "Než budete hledat, zadejte platnou seed frázi.",
+ search_error: "Nepodařilo se vyhledat zálohy mintů.",
+ add_error: "Nepodařilo se přidat vybrané minty.",
+ },
+ },
+
+ MintSettings: {
+ add: {
+ title: "Přidat mint",
+ description:
+ "Zadejte URL Cashu mintu, ke kterému se chcete připojit. Tato peněženka není spojena s žádným mintem.",
+ inputs: {
+ nickname: {
+ placeholder: "Přezdívka (např. Testnet)",
+ },
+ },
+ actions: {
+ add_mint: {
+ label: "@:global.actions.add_mint.label",
+ error_invalid_url: "Neplatná URL",
+ },
+ scan: {
+ label: "Skenovat QR kód",
+ },
+ },
+ },
+ discover: {
+ title: "Objevte minty",
+ overline: "Objevovat",
+ caption: "Objevte minty, které doporučili jiní uživatelé na Nostr.",
+ actions: {
+ discover: {
+ label: "Objevovat minty",
+ in_progress: "Načítá se…",
+ error_no_mints: "Nenalezeny žádné minty",
+ success: "Nalezeno { length } mintů",
+ },
+ },
+ recommendations: {
+ overline: "Nalezeno { length } mintů",
+ caption:
+ "Tyto minty doporučili ostatní uživatelé Nostr. Buďte opatrní a prověřte mint před použitím.",
+ actions: {
+ browse: {
+ label: "Klikněte pro prohlížení mintů",
+ },
+ },
+ },
+ },
+ swap: {
+ title: "Swap",
+ overline: "Multimint Swapy",
+ actions: {
+ receove_to_trusted_mint: {
+ label: "Přijmout do důvěryhodného mintu",
+ },
+ swap: {
+ label: "@:global.actions.swap.label",
+ in_progress: "@:MintSettings.swap.actions.swap.label",
+ },
+ },
+ caption:
+ "Prohození prostředků mezi minty přes Lightning. Poznámka: Nechte rezervu na poplatky Lightning. Pokud příchozí platba neproběhne, zkontrolujte fakturu manuálně.",
+ inputs: {
+ from: {
+ label: "Odesílatel",
+ },
+ to: {
+ label: "Příjemce",
+ },
+ amount: {
+ label: "Částka ({ ticker })",
+ },
+ },
+ },
+ error_badge: "Chyba",
+ reviews_text: "recenze",
+ no_reviews_yet: "Žádné recenze",
+ discover_mints_button: "Objevovat minty",
+ },
+
+ QrcodeReader: {
+ progress: {
+ text: "{ percentage }{ addon }",
+ percentage: "{ percentage }%",
+ keep_scanning_text: " - Pokračovat ve skenování",
+ },
+ actions: {
+ paste: {
+ label: "@:global.actions.paste.label",
+ },
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ },
+ },
+
+ InvoiceDetailDialog: {
+ title: "Přijmout Lightning",
+ create_invoice_title: "Vytvořit fakturu",
+ inputs: {
+ amount: {
+ label: "Částka ({ ticker }) *",
+ },
+ },
+ actions: {
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ create: {
+ label: "Vytvořit fakturu",
+ label_blocked: "Vytváří se faktura…",
+ in_progress: "Vytváří se",
+ },
+ },
+ invoice: {
+ caption: "Faktura Lightning",
+ status_paid_text: "Zaplaceno!",
+ actions: {
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ copy: {
+ label: "@:global.actions.copy.label",
+ },
+ },
+ },
+ },
+
+ SendDialog: {
+ title: "Odeslat",
+ actions: {
+ ecash: {
+ label: "Ecash",
+ error_no_mints: "Žádné minty k dispozici",
+ },
+ lightning: {
+ label: "Lightning",
+ error_no_mints: "Žádné minty k dispozici",
+ },
+ },
+ },
+
+ SendTokenDialog: {
+ title: "Odeslat Ecash",
+ title_ecash_text: "Ecash",
+ badge_offline_text: "Offline",
+ inputs: {
+ amount: {
+ label: "Částka ({ ticker }) *",
+ invalid_too_much_error_text: "Příliš mnoho",
+ },
+ p2pk_pubkey: {
+ label: "Veřejný klíč příjemce",
+ label_invalid: "Neplatný veřejný klíč příjemce",
+ },
+ },
+ actions: {
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ close_card_scanner: {
+ label: "@:global.actions.close.label",
+ },
+ copy_emoji: {
+ label: "🥜",
+ tooltip_text: "Kopírovat emoji",
+ },
+ copy_tokens: {
+ label: "@:global.actions.copy.label",
+ },
+ copy_link: {
+ tooltip_text: "Kopírovat odkaz",
+ },
+ share: {
+ tooltip_text: "Sdílet ecash",
+ },
+ lock: {
+ label: "@:global.actions.lock.label",
+ },
+ paste_p2pk_pubkey: {
+ tooltip_text: "@:global.actions.paste.label",
+ },
+ pay: {
+ label: "@:global.actions.pay.label",
+ },
+ send: {
+ label: "@:global.actions.send.label",
+ },
+ delete: {
+ tooltip_text: "Smazat z historie",
+ },
+ write_tokens_to_card: {
+ tooltips: {
+ ndef_supported_text: "Zapsat na NFC kartu",
+ ndef_unsupported_text: "NDEF není podporováno",
+ },
+ },
+ },
+ errors: {
+ amount_required: "Nejprve zadejte částku.",
+ serialization_failed: "Nepodařilo se připravit ecash token.",
+ },
+ },
+
+ SendPaymentRequest: {
+ actions: {
+ pay: {
+ label: "Zaplatit",
+ },
+ pay_via: {
+ label: "Zaplatit přes {transport}",
+ },
+ },
+ info: {
+ pay_to: "Zaplatit {target}",
+ invalid_url: "Neplatná URL",
+ },
+ },
+
+ PaymentRequestInfo: {
+ title_with_transport: "Platební požadavek přes {transport}",
+ title: "Platební požadavek",
+ subtitle: "Zaplatit {target}",
+ subtitle_fallback: "Platební požadavek",
+ invalid_url: "Neplatná URL",
+ },
+
+ ReceiveDialog: {
+ title: "Přijmout",
+ actions: {
+ ecash: {
+ label: "Ecash",
+ error_no_mints: "Žádné minty k dispozici",
+ },
+ lightning: {
+ label: "Lightning",
+ error_no_mints: "Pro příjem přes Lightning se musíte připojit k mintu",
+ },
+ },
+ },
+ ReceiveEcashDrawer: {
+ title: "Přijmout Ecash",
+ actions: {
+ paste: {
+ label: "@:global.actions.paste.label",
+ },
+ scan: {
+ label: "@:global.actions.scan.label",
+ },
+ request: {
+ label: "Požádat",
+ },
+ lock: {
+ label: "@:global.actions.lock.label",
+ },
+ nfc: {
+ label: "NFC",
+ scanning_text: "Skenuje se…",
+ },
+ },
+ },
+
+ ReceiveTokenDialog: {
+ title: "Přijmout Ecash",
+ title_ecash_text: "Ecash",
+ inputs: {
+ tokens_base64: {
+ label: "Vložit Cashu token",
+ },
+ },
+ errors: {
+ invalid_token: {
+ label: "Neplatný token",
+ },
+ p2pk_lock_mismatch: {
+ label:
+ "Nelze přijmout. P2PK zámek tohoto tokenu neodpovídá vašemu veřejnému klíči.",
+ },
+ },
+ unknown_mint_info_text:
+ "Neznámý mint. Bude přidán po přijetí tohoto tokenu.",
+ swap_section: {
+ title: "Swap",
+ source_label: "Odesílatel",
+ destination_label: "Příjemce",
+ fee_info: "Tento swap podléhá poplatkům Lightning sítě.",
+ },
+ actions: {
+ paste: {
+ label: "@:global.actions.paste.label",
+ },
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ scan: {
+ label: "@:global.actions.scan.label",
+ },
+ receive: {
+ label: "@:global.actions.receive.label",
+ label_known_mint: "@:ReceiveTokenDialog.actions.receive.label",
+ label_adding_mint: "Přidává se mint…",
+ },
+ swap: {
+ label: "Přijmout do důvěryhodného mintu",
+ tooltip_text: "Swap do důvěryhodného mintu",
+ caption: "Swap { value }",
+ processing: "Probíhá swap…",
+ failed: "Swap selhal",
+ },
+ cancel_swap: {
+ label: "@:global.actions.cancel.label",
+ tooltip_text: "Zrušit swap",
+ },
+ confirm_swap: {
+ label: "@:ReceiveTokenDialog.actions.swap.label",
+ tooltip_text: "@:ReceiveTokenDialog.actions.swap.tooltip_text",
+ in_progress: "@:ReceiveTokenDialog.actions.confirm_swap.label",
+ },
+ receive_to_selected_mint: {
+ label: "Přijmout do vybraného mintu",
+ },
+ later: {
+ label: "Přijmout později",
+ tooltip_text: "Přidat do historie pro pozdější příjem",
+ already_in_history_success_text: "Ecash již v historii",
+ added_to_history_success_text: "Ecash přidán do historie",
+ },
+ nfc: {
+ label: "NFC",
+ tooltips: {
+ ndef_supported_text: "Přečíst z NFC karty",
+ ndef_unsupported_text: "NDEF není podporováno",
+ },
+ },
+ },
+ },
+
+ P2PKDialog: {
+ p2pk: {
+ caption: "P2PK klíč",
+ description: "Přijímat ecash uzamčený tímto klíčem",
+ used_warning_text:
+ "Varování: Tento klíč byl již použit. Pro lepší soukromí použijte nový klíč.",
+ },
+ actions: {
+ copy: {
+ label: "@:global.actions.copy.label",
+ },
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ new_key: {
+ label: "Vygenerovat nový klíč",
+ },
+ },
+ },
+
+ PaymentRequestDialog: {
+ payment_request: {
+ caption: "Platební požadavek",
+ description: "Přijímat platby přes Nostr",
+ },
+ received_total: "Celkem přijato",
+ no_payments_yet: "Žádné platby dosud",
+ actions: {
+ copy: {
+ label: "@:global.actions.copy.label",
+ },
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ new_request: {
+ label: "Nový požadavek",
+ },
+ add_amount: {
+ label: "Přidat částku",
+ },
+ use_active_mint: {
+ label: "Libovolný mint",
+ },
+ },
+ inputs: {
+ amount: {
+ placeholder: "Zadejte částku",
+ },
+ },
+ },
+
+ NumericKeyboard: {
+ actions: {
+ close: {
+ label: "@:global.actions.close.label",
+ closed_info_text:
+ "Klávesnice zakázána. Opět ji můžete povolit v nastavení.",
+ },
+ enter: {
+ label: "@:global.actions.enter.label",
+ },
+ },
+ },
+
+ NWCDialog: {
+ nwc: {
+ caption: "Nostr Wallet Connect",
+ description:
+ "Ovládejte svou peněženku vzdáleně pomocí NWC. Stiskněte QR kód pro propojení peněženky s kompatibilní aplikací.",
+ warning_text:
+ "Varování: kdokoli s přístupem k tomuto spojovacímu řetězci může provádět platby z vaší peněženky. Nesdílejte!",
+ },
+ actions: {
+ copy: {
+ label: "@:global.actions.copy.label",
+ },
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ },
+ },
+
+ MintMotdMessage: {
+ title: "Zpráva mintu",
+ },
+
+ MintDetailsDialog: {
+ contact: {
+ title: "Kontakt",
+ },
+ details: {
+ title: "Detaily mintu",
+ url: {
+ label: "URL",
+ },
+ nuts: {
+ label: "Nuts",
+ actions: {
+ show: {
+ label: "Zobrazit vše",
+ },
+ hide: {
+ label: "Skrýt",
+ },
+ },
+ },
+ currency: {
+ label: "Měna",
+ },
+ currencies: {
+ label: "@:MintDetailsDialog.details.currency.label",
+ },
+ version: {
+ label: "Verze",
+ },
+ },
+ actions: {
+ title: "Akce",
+ copy_mint_url: {
+ label: "Kopírovat URL mintu",
+ },
+ delete: {
+ label: "Smazat mint",
+ },
+ edit: {
+ label: "Upravit mint",
+ },
+ },
+ },
+ ChooseMint: {
+ title: "Vyberte mint",
+ placeholder: "Vyberte mint",
+ available_text: "dostupný",
+ sheet_title: "Vybrat mint",
+ badge_mint_error_text: "Chyba",
+ badge_option_mint_error_text: "@:ChooseMint.badge_mint_error_text",
+ },
+
+ HistoryTable: {
+ empty_text: "Žádná historie zatím",
+ row: {
+ type_label: "Ecash",
+ date_label: "před { value }",
+ },
+ actions: {
+ check_status: {
+ tooltip_text: "Zkontrolovat stav",
+ },
+ receive: {
+ tooltip_text: "Přijmout",
+ },
+ filter_pending: {
+ label: "Filtrovat čekající",
+ },
+ show_all: {
+ label: "Zobrazit vše",
+ },
+ },
+ old_token_not_found_error_text: "Starý token nenalezen",
+ },
+
+ InvoiceTable: {
+ empty_text: "Žádné faktury zatím",
+ row: {
+ type_label: "Lightning",
+ type_tooltip_text: "Klikněte pro kopírování",
+ date_label: "před { value }",
+ },
+ actions: {
+ check_status: {
+ tooltip_text: "Zkontrolovat stav",
+ },
+ filter_pending: {
+ label: "Filtrovat čekající",
+ },
+ show_all: {
+ label: "Zobrazit vše",
+ },
+ },
+ },
+
+ RemoveMintDialog: {
+ title: "Opravdu chcete smazat tento mint?",
+ nickname: {
+ label: "Přezdívka",
+ },
+ balances: {
+ label: "Zůstatky",
+ },
+ warning_text:
+ "Poznámka: Protože je tato peněženka paranoidní, váš ecash z tohoto mintu nebude skutečně smazán, ale zůstane uložen na zařízení. Uvidíte ho znovu, pokud tento mint později znovu přidáte.",
+ inputs: {
+ mint_url: {
+ label: "@:global.inputs.mint_url.label",
+ },
+ },
+ actions: {
+ confirm: {
+ label: "Odstranit mint",
+ },
+ cancel: {
+ label: "@:global.actions.cancel.label",
+ },
+ },
+ },
+
+ ParseInputComponent: {
+ placeholder: {
+ default: "Cashu token nebo Lightning adresa",
+ receive: "Cashu token",
+ pay: "Lightning adresa nebo faktura",
+ },
+ qr_scanner: {
+ title: "Skenovat QR kód",
+ description: "Klikněte pro skenování adresy",
+ },
+ paste_button: {
+ label: "@:global.actions.paste.label",
+ },
+ },
+
+ PayInvoiceDialog: {
+ input_data: {
+ title: "Zaplatit Lightning",
+ inputs: {
+ invoice_data: {
+ label: "Lightning faktura nebo adresa",
+ },
+ },
+ actions: {
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ enter: {
+ label: "@:global.actions.enter.label",
+ },
+ paste: {
+ label: "@:global.actions.paste.label",
+ },
+ scan: {
+ label: "@:global.actions.scan.label",
+ },
+ },
+ },
+ lnurlpay: {
+ amount_exact_label: "{ payee } požaduje { value } { ticker }",
+ amount_range_label:
+ "{ payee } požaduje{br}mezi { min } a { max } { ticker }",
+ sending_to_lightning_address: "Odesílám na { address }",
+ inputs: {
+ amount: {
+ label: "Částka ({ ticker }) *",
+ },
+ comment: {
+ label: "Komentář (volitelné)",
+ },
+ },
+ actions: {
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ send: {
+ label: "@:global.actions.send.label",
+ },
+ },
+ },
+ invoice: {
+ title: "Zaplatit { value }",
+ paying: "Probíhá platba",
+ paid: "Zaplaceno",
+ fee: "Poplatek",
+ memo: {
+ label: "Poznámka",
+ },
+ processing_info_text: "Zpracovává se…",
+ balance_too_low_warning_text: "Zůstatek příliš nízký",
+ actions: {
+ close: {
+ label: "@:global.actions.close.label",
+ },
+ pay: {
+ label: "Zaplatit",
+ in_progress: "@:PayInvoiceDialog.invoice.processing_info_text",
+ error: "Chyba",
+ },
+ },
+ },
+ },
+
+ EditMintDialog: {
+ title: "Upravit mint",
+ inputs: {
+ nickname: {
+ label: "Přezdívka",
+ },
+ mint_url: {
+ label: "@:global.inputs.mint_url.label",
+ },
+ },
+ actions: {
+ cancel: {
+ label: "@:global.actions.cancel.label",
+ },
+ update: {
+ label: "@:global.actions.update.label",
+ },
+ },
+ },
+ AddMintDialog: {
+ title: "Důvěřujete tomuto mintu?",
+ description:
+ "Před použitím tohoto mintu se ujistěte, že mu důvěřujete. Mints mohou kdykoli přestat fungovat nebo se stát škodlivými.",
+ inputs: {
+ mint_url: {
+ label: "@:global.inputs.mint_url.label",
+ },
+ },
+ actions: {
+ cancel: {
+ label: "@:global.actions.cancel.label",
+ },
+ add_mint: {
+ label: "@:global.actions.add_mint.label",
+ in_progress: "Přidává se mint",
+ },
+ },
+ },
+ restore: {
+ mnemonic_error_text: "Zadejte mnemotechnickou frázi",
+ restore_mint_error_text: "Chyba při obnově mintu: { error }",
+ prepare_info_text: "Připravuji proces obnovy…",
+ restored_proofs_for_keyset_info_text:
+ "Obnoveno { restoreCounter } důkazů pro keyset { keysetId }",
+ checking_proofs_for_keyset_info_text:
+ "Kontroluji důkazy { startIndex } až { endIndex } pro keyset { keysetId }",
+ no_proofs_info_text: "Nebyly nalezeny žádné důkazy k obnově",
+ restored_amount_success_text: "Obnoveno { amount }",
+ },
+ swap: {
+ in_progress_warning_text: "Probíhá swap",
+ invalid_swap_data_error_text: "Neplatná data pro swap",
+ swap_error_text: "Chyba při swapu",
+ },
+ TokenInformation: {
+ fee: "Poplatek",
+ unit: "Jednotka",
+ fiat: "Fiat",
+ p2pk: "P2PK",
+ locked: "Uzamčeno",
+ locked_to_you: "Uzamčeno pro vás",
+ mint: "Mint",
+ memo: "Poznámka",
+ payment_request: "Platební požadavek",
+ nostr: "Nostr",
+ token_copied: "Token zkopírován do schránky",
+ },
+};
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index ee622021c..1ddc2e72f 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -3,6 +3,7 @@ import esES from "./es-ES";
import itIT from "./it-IT";
import deDE from "./de-DE";
import frFR from "./fr-FR";
+import csCZ from "./cs-CZ";
import svSE from "./sv-SE";
import elGR from "./el-GR";
import trTR from "./tr-TR";
@@ -17,6 +18,7 @@ export default {
"it-IT": itIT,
"de-DE": deDE,
"fr-FR": frFR,
+ "cs-CZ": csCZ,
"sv-SE": svSE,
"el-GR": elGR,
"tr-TR": trTR,
diff --git a/src/pages/WelcomePage.vue b/src/pages/WelcomePage.vue
index c2db0df81..96e63bcfc 100644
--- a/src/pages/WelcomePage.vue
+++ b/src/pages/WelcomePage.vue
@@ -128,6 +128,7 @@ export default {
{ label: "Italiano", value: "it-IT" },
{ label: "Deutsch", value: "de-DE" },
{ label: "Français", value: "fr-FR" },
+ { label: "Čeština", value: "cs-CZ" },
{ label: "Svenska", value: "sv-SE" },
{ label: "Ελληνικά", value: "el-GR" },
{ label: "Türkçe", value: "tr-TR" },
diff --git a/src/pages/welcome/WelcomeSlide1.vue b/src/pages/welcome/WelcomeSlide1.vue
index a5c0e5f74..73e3c3396 100644
--- a/src/pages/welcome/WelcomeSlide1.vue
+++ b/src/pages/welcome/WelcomeSlide1.vue
@@ -427,6 +427,7 @@ export default {
{ label: "Italiano", value: "it-IT" },
{ label: "Deutsch", value: "de-DE" },
{ label: "Français", value: "fr-FR" },
+ { label: "Čeština", value: "cs-CZ" },
{ label: "Svenska", value: "sv-SE" },
{ label: "Ελληνικά", value: "el-GR" },
{ label: "Türkçe", value: "tr-TR" },
From 128f8c0768afce3c4b1d619f8b5703e01147f40a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hynek=20J=C3=ADna?=
<26002916+hynek-jina@users.noreply.github.com>
Date: Mon, 22 Dec 2025 20:13:26 +0100
Subject: [PATCH 25/73] revert of unwanted change in package-lock.json
---
package-lock.json | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/package-lock.json b/package-lock.json
index d9ea72c8e..5a0ef9ebe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -116,6 +116,7 @@
"version": "7.24.5",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.2",
@@ -1942,6 +1943,7 @@
"node_modules/@capacitor/core": {
"version": "6.2.0",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -3400,6 +3402,7 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/core": "^0.14.0"
@@ -3436,6 +3439,7 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3448,6 +3452,7 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3472,6 +3477,7 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0",
@@ -3515,6 +3521,7 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3638,6 +3645,7 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3650,6 +3658,7 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3665,6 +3674,7 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -5443,6 +5453,7 @@
"version": "20.16.1",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~6.19.2"
}
@@ -5576,6 +5587,7 @@
"version": "5.62.0",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0",
@@ -5728,6 +5740,7 @@
"version": "2.3.4",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12.0.0"
},
@@ -6023,6 +6036,7 @@
"version": "8.11.3",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -7494,6 +7508,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668",
@@ -10374,6 +10389,7 @@
"version": "8.57.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -15206,6 +15222,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.8",
"picocolors": "^1.1.1",
@@ -15465,6 +15482,7 @@
"node_modules/qrcode": {
"version": "1.5.3",
"license": "MIT",
+ "peer": true,
"dependencies": {
"dijkstrajs": "^1.0.1",
"encode-utf8": "^1.0.3",
@@ -15502,6 +15520,7 @@
"node_modules/quasar": {
"version": "2.18.2",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">= 10.18.1",
"npm": ">= 6.13.4",
@@ -16236,6 +16255,7 @@
"version": "2.77.3",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -17770,6 +17790,7 @@
"version": "4.0.3",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -18129,6 +18150,7 @@
"version": "5.5.4",
"devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -18464,6 +18486,7 @@
"version": "2.9.18",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.14.27",
"postcss": "^8.4.13",
@@ -18616,6 +18639,7 @@
"version": "4.0.3",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -18920,6 +18944,7 @@
"version": "4.0.3",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -18969,6 +18994,7 @@
"version": "6.3.6",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -19041,6 +19067,7 @@
"node_modules/vue": {
"version": "3.4.38",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/compiler-dom": "3.4.38",
"@vue/compiler-sfc": "3.4.38",
@@ -19129,6 +19156,7 @@
"node_modules/vue-router": {
"version": "4.3.2",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/devtools-api": "^6.5.1"
},
@@ -19388,6 +19416,7 @@
"version": "6.6.1",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@apideck/better-ajv-errors": "^0.3.1",
"@babel/core": "^7.11.1",
@@ -19451,6 +19480,7 @@
"version": "8.13.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"json-schema-traverse": "^1.0.0",
From cd146123a8b1c8776b0bb58d070e56149a71e9ca Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Thu, 25 Dec 2025 15:34:01 +0700
Subject: [PATCH 26/73] 10s
---
src/components/PayInvoiceDialog.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/PayInvoiceDialog.vue b/src/components/PayInvoiceDialog.vue
index a0e0d7892..d4838f91a 100644
--- a/src/components/PayInvoiceDialog.vue
+++ b/src/components/PayInvoiceDialog.vue
@@ -670,7 +670,7 @@ export default defineComponent({
this.payInvoiceData.show = false;
}
this.autoCloseTimeout = null;
- }, 2000);
+ }, 10000);
} else if (!val) {
// Normal close - reset states and clear any pending timeout
if (this.autoCloseTimeout) {
From 52969598d006308468fed6f577225cfef151c54f Mon Sep 17 00:00:00 2001
From: Rob Woodgate
Date: Tue, 6 Jan 2026 21:00:25 +0000
Subject: [PATCH 27/73] Normalize and validate BIP39 mnemonic on init/restore
---
src/components/RestoreView.vue | 17 ++++----
src/i18n/en-US/index.ts | 2 +-
src/pages/welcome/WelcomeRecoverSeed.vue | 9 +++-
src/stores/wallet.ts | 2 +-
test/vitest/__tests__/bip39seed.test.ts | 52 ++++++++++++++++++++++++
5 files changed, 70 insertions(+), 12 deletions(-)
create mode 100644 test/vitest/__tests__/bip39seed.test.ts
diff --git a/src/components/RestoreView.vue b/src/components/RestoreView.vue
index cd44c3104..089abdbd7 100644
--- a/src/components/RestoreView.vue
+++ b/src/components/RestoreView.vue
@@ -272,6 +272,8 @@ import { useWalletStore } from "src/stores/wallet";
import { useUiStore } from "src/stores/ui";
import { notifyError, notifySuccess } from "src/js/notify";
import NostrMintRestore from "./NostrMintRestore.vue";
+import { validateMnemonic } from "@scure/bip39";
+import { wordlist } from '@scure/bip39/wordlists/english';
export default defineComponent({
name: "RestoreView",
@@ -304,11 +306,7 @@ export default defineComponent({
"restoreStatus",
]),
isMnemonicValid() {
- if (!this.mnemonicToRestore) {
- return false;
- }
- const words = this.mnemonicToRestore.trim().split(/\s+/);
- return words.length >= 12;
+ return this.validateMnemonic();
},
allSelected() {
return (
@@ -422,13 +420,14 @@ export default defineComponent({
}
},
validateMnemonic() {
- // Simple validation: check if mnemonicToRestore has at least 12 words
- const words = this.mnemonicToRestore.trim().split(/\s+/);
- if (words.length < 12) {
+ // use @scure/bip39 validation
+ const words = this.mnemonicToRestore.trim().toLowerCase();
+ if (!validateMnemonic(words, wordlist)) {
this.mnemonicError = this.$i18n.t("RestoreView.actions.validate.error");
return false;
}
this.mnemonicError = "";
+ this.mnemonicToRestore = words; // normalize
return true;
},
async restoreMintForMint(mintUrl) {
@@ -456,7 +455,7 @@ export default defineComponent({
async pasteMnemonic() {
try {
const text = await this.pasteFromClipboard();
- this.mnemonicToRestore = text.trim();
+ this.mnemonicToRestore = text.trim().toLowerCase();
} catch (error) {
notifyError(this.$i18n.t("RestoreView.actions.paste.error"));
}
diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts
index decb61a44..373c07019 100644
--- a/src/i18n/en-US/index.ts
+++ b/src/i18n/en-US/index.ts
@@ -800,7 +800,7 @@ export default {
error: "Failed to read clipboard contents.",
},
validate: {
- error: "Mnemonic should be at least 12 words.",
+ error: "Mnemonic is not a valid BIP39 seed phrase.",
},
select_all: {
label: "Select All",
diff --git a/src/pages/welcome/WelcomeRecoverSeed.vue b/src/pages/welcome/WelcomeRecoverSeed.vue
index 8493fbddc..53ade511c 100644
--- a/src/pages/welcome/WelcomeRecoverSeed.vue
+++ b/src/pages/welcome/WelcomeRecoverSeed.vue
@@ -61,6 +61,9 @@ import { useWelcomeStore } from "src/stores/welcome";
import { useRestoreStore } from "src/stores/restore";
import { useWalletStore } from "src/stores/wallet";
import { useUiStore } from "src/stores/ui";
+import { validateMnemonic } from "@scure/bip39";
+import { wordlist } from '@scure/bip39/wordlists/english';
+import { i18n } from "../../boot/i18n";
export default {
name: "WelcomeRecoverSeed",
@@ -84,13 +87,17 @@ export default {
return words.value
.filter((w) => w.trim())
.join(" ")
- .trim();
+ .trim()
+ .toLowerCase();
});
const errorMsg = computed(() => {
const filledWords = words.value.filter((w) => w.trim()).length;
if (filledWords === 0) return "";
if (filledWords < 12) return `${filledWords}/12 words entered`;
+ if (!validateMnemonic(mnemonic.value, wordlist)) {
+ return i18n.global.t("RestoreView.actions.validate.error");
+ }
return "";
});
diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts
index 7d5ae8642..e97bc4a90 100644
--- a/src/stores/wallet.ts
+++ b/src/stores/wallet.ts
@@ -183,7 +183,7 @@ export const useWalletStore = defineStore("wallet", {
},
actions: {
setMnemonicFromUser: function (mnemonic: string) {
- this.mnemonic = mnemonic;
+ this.mnemonic = mnemonic.trim().toLowerCase(); // normalize
},
mintWallet(url: string, unit: string): CashuWallet {
// short-lived wallet for mint operations
diff --git a/test/vitest/__tests__/bip39seed.test.ts b/test/vitest/__tests__/bip39seed.test.ts
new file mode 100644
index 000000000..bec952296
--- /dev/null
+++ b/test/vitest/__tests__/bip39seed.test.ts
@@ -0,0 +1,52 @@
+import { test, describe, expect } from 'vitest';
+import { mnemonicToSeedSync, validateMnemonic, mnemonicToSeed } from "@scure/bip39";
+import { wordlist } from '@scure/bip39/wordlists/english';
+
+describe('mnemonicToSeedSync', () => {
+ test('converts same mnemonic consistently', () => {
+ const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
+ expect(validateMnemonic(mnem, wordlist)).toBeTruthy();
+ const seed1 = mnemonicToSeedSync(mnem);
+ const seed2 = mnemonicToSeedSync(mnem);
+ expect(seed1).toEqual(seed2);
+ });
+ test('converts same mnemonic consistently sync/async', async () => {
+ const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
+ expect(validateMnemonic(mnem, wordlist)).toBeTruthy();
+ const seed1 = await mnemonicToSeed(mnem);
+ const seed2 = mnemonicToSeedSync(mnem);
+ expect(seed1).toEqual(seed2);
+ });
+ test('varies with capitalization', () => {
+ const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow'; // [w]inner
+ const mNem = 'legal Winner thank year wave sausage worth useful legal winner thank yellow'; // [W]inner
+ expect(validateMnemonic(mnem, wordlist)).toBeTruthy();
+ expect(validateMnemonic(mNem, wordlist)).toBeFalsy();
+ const lowerSeed = mnemonicToSeedSync(mnem);
+ const mixedSeed = mnemonicToSeedSync(mNem);
+ expect(lowerSeed).not.toEqual(mixedSeed);
+ });
+ test('fails with extra/missing spacing', () => {
+ const mnem1 = 'legal winner thank year wave sausage worth useful legal winner thank yellow'; // 2 spaces
+ const mnem2 = 'legalwinner thank year wave sausage worth useful legal winner thank yellow'; // missing space
+ const mnem3 = ' legalwinner thank year wave sausage worth useful legal winner thank yellow '; // untrimmed
+ expect(validateMnemonic(mnem1, wordlist)).toBeFalsy();
+ expect(validateMnemonic(mnem2, wordlist)).toBeFalsy();
+ expect(validateMnemonic(mnem3, wordlist)).toBeFalsy();
+ expect(() => mnemonicToSeedSync(mnem1)).toThrow();
+ expect(() => mnemonicToSeedSync(mnem2)).toThrow();
+ expect(() => mnemonicToSeedSync(mnem3)).toThrow();
+ });
+ test('converts any words/order does not matter', () => {
+ const mnem1 = 'legal thank winner year wave sausage worth useful legal winner yellow thank'; // invalid checksum
+ expect(validateMnemonic(mnem1, wordlist)).toBeFalsy();
+ expect(() => mnemonicToSeedSync(mnem1)).not.toThrow();
+ const mnem2 = 'a b c d e f g h i j k l'; // 12 "words"
+ expect(validateMnemonic(mnem2, wordlist)).toBeFalsy();
+ expect(() => mnemonicToSeedSync(mnem2)).not.toThrow();
+ const mnem3 = 'lega winn than year wave saus wort usef lega winn than yell'; // first 4
+ expect(validateMnemonic(mnem3, wordlist)).toBeFalsy();
+ expect(() => mnemonicToSeedSync(mnem3)).not.toThrow();
+ expect(mnemonicToSeedSync(mnem1)).not.toEqual(mnemonicToSeedSync(mnem3));
+ });
+});
From bdd3b24dd7954915fecdc51f50a5694390638e48 Mon Sep 17 00:00:00 2001
From: Rob Woodgate
Date: Tue, 6 Jan 2026 21:11:33 +0000
Subject: [PATCH 28/73] format
---
package-lock.json | 30 --------------
src/components/RestoreView.vue | 2 +-
src/pages/welcome/WelcomeRecoverSeed.vue | 2 +-
test/vitest/__tests__/bip39seed.test.ts | 50 +++++++++++++++---------
4 files changed, 33 insertions(+), 51 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 5a0ef9ebe..d9ea72c8e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -116,7 +116,6 @@
"version": "7.24.5",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.2",
@@ -1943,7 +1942,6 @@
"node_modules/@capacitor/core": {
"version": "6.2.0",
"license": "MIT",
- "peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -3402,7 +3400,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/core": "^0.14.0"
@@ -3439,7 +3436,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3452,7 +3448,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3477,7 +3472,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0",
@@ -3521,7 +3515,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3645,7 +3638,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3658,7 +3650,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -3674,7 +3665,6 @@
"version": "0.14.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.7.2",
"@jimp/utils": "^0.14.0"
@@ -5453,7 +5443,6 @@
"version": "20.16.1",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~6.19.2"
}
@@ -5587,7 +5576,6 @@
"version": "5.62.0",
"dev": true,
"license": "BSD-2-Clause",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0",
@@ -5740,7 +5728,6 @@
"version": "2.3.4",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12.0.0"
},
@@ -6036,7 +6023,6 @@
"version": "8.11.3",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -7508,7 +7494,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668",
@@ -10389,7 +10374,6 @@
"version": "8.57.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -15222,7 +15206,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.8",
"picocolors": "^1.1.1",
@@ -15482,7 +15465,6 @@
"node_modules/qrcode": {
"version": "1.5.3",
"license": "MIT",
- "peer": true,
"dependencies": {
"dijkstrajs": "^1.0.1",
"encode-utf8": "^1.0.3",
@@ -15520,7 +15502,6 @@
"node_modules/quasar": {
"version": "2.18.2",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">= 10.18.1",
"npm": ">= 6.13.4",
@@ -16255,7 +16236,6 @@
"version": "2.77.3",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -17790,7 +17770,6 @@
"version": "4.0.3",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -18150,7 +18129,6 @@
"version": "5.5.4",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -18486,7 +18464,6 @@
"version": "2.9.18",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.14.27",
"postcss": "^8.4.13",
@@ -18639,7 +18616,6 @@
"version": "4.0.3",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -18944,7 +18920,6 @@
"version": "4.0.3",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -18994,7 +18969,6 @@
"version": "6.3.6",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -19067,7 +19041,6 @@
"node_modules/vue": {
"version": "3.4.38",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/compiler-dom": "3.4.38",
"@vue/compiler-sfc": "3.4.38",
@@ -19156,7 +19129,6 @@
"node_modules/vue-router": {
"version": "4.3.2",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/devtools-api": "^6.5.1"
},
@@ -19416,7 +19388,6 @@
"version": "6.6.1",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@apideck/better-ajv-errors": "^0.3.1",
"@babel/core": "^7.11.1",
@@ -19480,7 +19451,6 @@
"version": "8.13.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"json-schema-traverse": "^1.0.0",
diff --git a/src/components/RestoreView.vue b/src/components/RestoreView.vue
index 089abdbd7..10c247f58 100644
--- a/src/components/RestoreView.vue
+++ b/src/components/RestoreView.vue
@@ -273,7 +273,7 @@ import { useUiStore } from "src/stores/ui";
import { notifyError, notifySuccess } from "src/js/notify";
import NostrMintRestore from "./NostrMintRestore.vue";
import { validateMnemonic } from "@scure/bip39";
-import { wordlist } from '@scure/bip39/wordlists/english';
+import { wordlist } from "@scure/bip39/wordlists/english";
export default defineComponent({
name: "RestoreView",
diff --git a/src/pages/welcome/WelcomeRecoverSeed.vue b/src/pages/welcome/WelcomeRecoverSeed.vue
index 53ade511c..8d66ab9cb 100644
--- a/src/pages/welcome/WelcomeRecoverSeed.vue
+++ b/src/pages/welcome/WelcomeRecoverSeed.vue
@@ -62,7 +62,7 @@ import { useRestoreStore } from "src/stores/restore";
import { useWalletStore } from "src/stores/wallet";
import { useUiStore } from "src/stores/ui";
import { validateMnemonic } from "@scure/bip39";
-import { wordlist } from '@scure/bip39/wordlists/english';
+import { wordlist } from "@scure/bip39/wordlists/english";
import { i18n } from "../../boot/i18n";
export default {
diff --git a/test/vitest/__tests__/bip39seed.test.ts b/test/vitest/__tests__/bip39seed.test.ts
index bec952296..e34039c36 100644
--- a/test/vitest/__tests__/bip39seed.test.ts
+++ b/test/vitest/__tests__/bip39seed.test.ts
@@ -1,35 +1,46 @@
-import { test, describe, expect } from 'vitest';
-import { mnemonicToSeedSync, validateMnemonic, mnemonicToSeed } from "@scure/bip39";
-import { wordlist } from '@scure/bip39/wordlists/english';
+import { test, describe, expect } from "vitest";
+import {
+ mnemonicToSeedSync,
+ validateMnemonic,
+ mnemonicToSeed,
+} from "@scure/bip39";
+import { wordlist } from "@scure/bip39/wordlists/english";
-describe('mnemonicToSeedSync', () => {
- test('converts same mnemonic consistently', () => {
- const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
+describe("mnemonicToSeedSync", () => {
+ test("converts same mnemonic consistently", () => {
+ const mnem =
+ "legal winner thank year wave sausage worth useful legal winner thank yellow";
expect(validateMnemonic(mnem, wordlist)).toBeTruthy();
const seed1 = mnemonicToSeedSync(mnem);
const seed2 = mnemonicToSeedSync(mnem);
expect(seed1).toEqual(seed2);
});
- test('converts same mnemonic consistently sync/async', async () => {
- const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
+ test("converts same mnemonic consistently sync/async", async () => {
+ const mnem =
+ "legal winner thank year wave sausage worth useful legal winner thank yellow";
expect(validateMnemonic(mnem, wordlist)).toBeTruthy();
const seed1 = await mnemonicToSeed(mnem);
const seed2 = mnemonicToSeedSync(mnem);
expect(seed1).toEqual(seed2);
});
- test('varies with capitalization', () => {
- const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow'; // [w]inner
- const mNem = 'legal Winner thank year wave sausage worth useful legal winner thank yellow'; // [W]inner
+ test("varies with capitalization", () => {
+ const mnem =
+ "legal winner thank year wave sausage worth useful legal winner thank yellow"; // [w]inner
+ const mNem =
+ "legal Winner thank year wave sausage worth useful legal winner thank yellow"; // [W]inner
expect(validateMnemonic(mnem, wordlist)).toBeTruthy();
expect(validateMnemonic(mNem, wordlist)).toBeFalsy();
const lowerSeed = mnemonicToSeedSync(mnem);
const mixedSeed = mnemonicToSeedSync(mNem);
expect(lowerSeed).not.toEqual(mixedSeed);
});
- test('fails with extra/missing spacing', () => {
- const mnem1 = 'legal winner thank year wave sausage worth useful legal winner thank yellow'; // 2 spaces
- const mnem2 = 'legalwinner thank year wave sausage worth useful legal winner thank yellow'; // missing space
- const mnem3 = ' legalwinner thank year wave sausage worth useful legal winner thank yellow '; // untrimmed
+ test("fails with extra/missing spacing", () => {
+ const mnem1 =
+ "legal winner thank year wave sausage worth useful legal winner thank yellow"; // 2 spaces
+ const mnem2 =
+ "legalwinner thank year wave sausage worth useful legal winner thank yellow"; // missing space
+ const mnem3 =
+ " legalwinner thank year wave sausage worth useful legal winner thank yellow "; // untrimmed
expect(validateMnemonic(mnem1, wordlist)).toBeFalsy();
expect(validateMnemonic(mnem2, wordlist)).toBeFalsy();
expect(validateMnemonic(mnem3, wordlist)).toBeFalsy();
@@ -37,14 +48,15 @@ describe('mnemonicToSeedSync', () => {
expect(() => mnemonicToSeedSync(mnem2)).toThrow();
expect(() => mnemonicToSeedSync(mnem3)).toThrow();
});
- test('converts any words/order does not matter', () => {
- const mnem1 = 'legal thank winner year wave sausage worth useful legal winner yellow thank'; // invalid checksum
+ test("converts any words/order does not matter", () => {
+ const mnem1 =
+ "legal thank winner year wave sausage worth useful legal winner yellow thank"; // invalid checksum
expect(validateMnemonic(mnem1, wordlist)).toBeFalsy();
expect(() => mnemonicToSeedSync(mnem1)).not.toThrow();
- const mnem2 = 'a b c d e f g h i j k l'; // 12 "words"
+ const mnem2 = "a b c d e f g h i j k l"; // 12 "words"
expect(validateMnemonic(mnem2, wordlist)).toBeFalsy();
expect(() => mnemonicToSeedSync(mnem2)).not.toThrow();
- const mnem3 = 'lega winn than year wave saus wort usef lega winn than yell'; // first 4
+ const mnem3 = "lega winn than year wave saus wort usef lega winn than yell"; // first 4
expect(validateMnemonic(mnem3, wordlist)).toBeFalsy();
expect(() => mnemonicToSeedSync(mnem3)).not.toThrow();
expect(mnemonicToSeedSync(mnem1)).not.toEqual(mnemonicToSeedSync(mnem3));
From 273046ff80bdc6815e968553d8a6c3244cbabce4 Mon Sep 17 00:00:00 2001
From: Rob Woodgate
Date: Tue, 6 Jan 2026 22:58:28 +0000
Subject: [PATCH 29/73] Tidy up and improve UX
---
src/components/RestoreView.vue | 82 ++++++++++-----
src/pages/welcome/WelcomeRecoverSeed.vue | 124 +++++++++++------------
2 files changed, 116 insertions(+), 90 deletions(-)
diff --git a/src/components/RestoreView.vue b/src/components/RestoreView.vue
index 10c247f58..2328ee2e3 100644
--- a/src/components/RestoreView.vue
+++ b/src/components/RestoreView.vue
@@ -15,7 +15,7 @@
@@ -272,7 +272,7 @@ import { useWalletStore } from "src/stores/wallet";
import { useUiStore } from "src/stores/ui";
import { notifyError, notifySuccess } from "src/js/notify";
import NostrMintRestore from "./NostrMintRestore.vue";
-import { validateMnemonic } from "@scure/bip39";
+import { validateMnemonic as validateBip39Mnemonic } from "@scure/bip39";
import { wordlist } from "@scure/bip39/wordlists/english";
export default defineComponent({
@@ -286,7 +286,6 @@ export default defineComponent({
},
data() {
return {
- mnemonicError: "",
restoreAllMintsText: this.$i18n.t(
"RestoreView.actions.restore_all_mints.label"
),
@@ -305,8 +304,46 @@ export default defineComponent({
"restoringMint",
"restoreStatus",
]),
- isMnemonicValid() {
- return this.validateMnemonic();
+
+ mnemonicInput: {
+ get(): string {
+ return this.mnemonicToRestore || "";
+ },
+ set(v: string) {
+ // lowercase live, keep spacing as typed to avoid cursor jumps
+ this.mnemonicToRestore = (v || "").toLowerCase();
+ },
+ },
+ normalisedMnemonic(): string {
+ return (this.mnemonicToRestore || "")
+ .trim()
+ .toLowerCase()
+ .split(/\s+/)
+ .filter(Boolean)
+ .join(" ");
+ },
+ mnemonicWordCount(): number {
+ return this.normalisedMnemonic
+ ? this.normalisedMnemonic.split(" ").length
+ : 0;
+ },
+ isMnemonicValid(): boolean {
+ if (this.onboarding) return true;
+ if (this.mnemonicWordCount < 12) return false;
+ return validateBip39Mnemonic(this.normalisedMnemonic, wordlist);
+ },
+ mnemonicError(): string {
+ if (this.onboarding) return "";
+
+ const count = this.mnemonicWordCount;
+ if (count === 0) return "";
+ if (count < 12) return `${count}/12 words entered`;
+
+ if (!validateBip39Mnemonic(this.normalisedMnemonic, wordlist)) {
+ return this.$i18n.t("RestoreView.actions.validate.error");
+ }
+
+ return "";
},
allSelected() {
return (
@@ -381,8 +418,9 @@ export default defineComponent({
return;
}
- if (!this.onboarding && !this.validateMnemonic()) {
- return;
+ if (!this.onboarding) {
+ if (!this.isMnemonicValid) return;
+ this.mnemonicToRestore = this.normalisedMnemonic;
}
const selectedMintUrls = Array.from(this.selectedMints);
@@ -419,21 +457,12 @@ export default defineComponent({
);
}
},
- validateMnemonic() {
- // use @scure/bip39 validation
- const words = this.mnemonicToRestore.trim().toLowerCase();
- if (!validateMnemonic(words, wordlist)) {
- this.mnemonicError = this.$i18n.t("RestoreView.actions.validate.error");
- return false;
- }
- this.mnemonicError = "";
- this.mnemonicToRestore = words; // normalize
- return true;
- },
async restoreMintForMint(mintUrl) {
- if (!this.onboarding && !this.validateMnemonic()) {
- return;
+ if (!this.onboarding) {
+ if (!this.isMnemonicValid) return;
+ this.mnemonicToRestore = this.normalisedMnemonic;
}
+
try {
this.restoreAllMintsText = this.$i18n.t(
"RestoreView.actions.restore.in_progress"
@@ -455,15 +484,20 @@ export default defineComponent({
async pasteMnemonic() {
try {
const text = await this.pasteFromClipboard();
- this.mnemonicToRestore = text.trim().toLowerCase();
+ this.mnemonicToRestore = (text || "")
+ .trim()
+ .split(/\s+/)
+ .filter(Boolean)
+ .join(" ");
} catch (error) {
notifyError(this.$i18n.t("RestoreView.actions.paste.error"));
}
},
async restoreAllMints() {
let i = 0;
- if (!this.validateMnemonic()) {
- return;
+ if (!this.onboarding) {
+ if (!this.isMnemonicValid) return;
+ this.mnemonicToRestore = this.normalisedMnemonic;
}
try {
for (const mint of this.mints) {
diff --git a/src/pages/welcome/WelcomeRecoverSeed.vue b/src/pages/welcome/WelcomeRecoverSeed.vue
index 8d66ab9cb..d73fc9a0a 100644
--- a/src/pages/welcome/WelcomeRecoverSeed.vue
+++ b/src/pages/welcome/WelcomeRecoverSeed.vue
@@ -23,14 +23,20 @@
{{ index }}
@@ -61,7 +67,7 @@ import { useWelcomeStore } from "src/stores/welcome";
import { useRestoreStore } from "src/stores/restore";
import { useWalletStore } from "src/stores/wallet";
import { useUiStore } from "src/stores/ui";
-import { validateMnemonic } from "@scure/bip39";
+import { validateMnemonic as validateBip39Mnemonic } from "@scure/bip39";
import { wordlist } from "@scure/bip39/wordlists/english";
import { i18n } from "../../boot/i18n";
@@ -82,6 +88,40 @@ export default {
}
};
+ type QInputModel = string | number | null;
+
+ const updateWord = (val: QInputModel, index: number) => {
+ const lower = String(val ?? "").toLowerCase();
+
+ // Multi word (paste or user typed spaces)
+ if (/\s/.test(lower.trim()) && lower.trim().split(/\s+/).length > 1) {
+ const splitWords = lower.trim().split(/\s+/);
+ splitWords.forEach((w, i) => {
+ if (index + i < 12) {
+ words.value[index + i] = w;
+ }
+ });
+ const nextIndex = Math.min(index + splitWords.length, 11);
+ setTimeout(() => {
+ inputRefs.value[nextIndex]?.focus();
+ }, 50);
+
+ return;
+ }
+
+ // Space to advance, store trimmed
+ if (lower.endsWith(" ") && index < 11) {
+ words.value[index] = lower.trim();
+ setTimeout(() => {
+ inputRefs.value[index + 1]?.focus();
+ }, 50);
+ return;
+ }
+
+ // Normal typing
+ words.value[index] = lower;
+ };
+
// Combine individual words into mnemonic
const mnemonic = computed(() => {
return words.value
@@ -91,11 +131,17 @@ export default {
.toLowerCase();
});
+ // Count of words filled
+ const filledWords = computed(
+ () => words.value.filter((w) => w.trim()).length
+ );
+
+ // Message to show
const errorMsg = computed(() => {
- const filledWords = words.value.filter((w) => w.trim()).length;
- if (filledWords === 0) return "";
- if (filledWords < 12) return `${filledWords}/12 words entered`;
- if (!validateMnemonic(mnemonic.value, wordlist)) {
+ if (filledWords.value === 0) return "";
+ if (filledWords.value < 12)
+ return `${filledWords.value}/12 words entered`;
+ if (!validateBip39Mnemonic(mnemonic.value, wordlist)) {
return i18n.global.t("RestoreView.actions.validate.error");
}
return "";
@@ -104,14 +150,10 @@ export default {
// Watch mnemonic and update store
watch(mnemonic, (val) => {
restore.mnemonicToRestore = val;
- const wordCount = val
- .trim()
- .split(/\s+/)
- .filter((w) => w).length;
- const valid = wordCount === 12;
+ const valid = validateBip39Mnemonic(val, wordlist);
welcome.seedEnteredValid = valid;
if (valid) {
- wallet.setMnemonicFromUser(val.trim());
+ wallet.setMnemonicFromUser(val);
}
});
@@ -119,64 +161,14 @@ export default {
const handlePaste = (event: ClipboardEvent, index: number) => {
event.preventDefault();
const pastedText = event.clipboardData?.getData("text") || "";
- const pastedWords = pastedText.trim().split(/\s+/);
-
- // If pasting multiple words, distribute them
- if (pastedWords.length > 1) {
- pastedWords.forEach((word, i) => {
- if (index + i < 12) {
- words.value[index + i] = word.trim();
- }
- });
- // Focus next empty field or last field
- const nextIndex = Math.min(index + pastedWords.length, 11);
- setTimeout(() => {
- inputRefs.value[nextIndex]?.focus();
- }, 50);
- } else {
- // Single word paste
- words.value[index] = pastedText.trim();
- // Move to next field
- if (index < 11) {
- setTimeout(() => {
- inputRefs.value[index + 1]?.focus();
- }, 50);
- }
- }
- };
-
- // Handle input and auto-move to next field
- const handleInput = (index: number) => {
- const word = words.value[index];
- // If word contains space, it might be multiple words pasted
- if (word.includes(" ")) {
- const splitWords = word.trim().split(/\s+/);
- splitWords.forEach((w, i) => {
- if (index + i < 12) {
- words.value[index + i] = w.trim();
- }
- });
- } else if (word.trim() && index < 11) {
- // Auto-advance on space
- if (word.endsWith(" ")) {
- words.value[index] = word.trim();
- setTimeout(() => {
- inputRefs.value[index + 1]?.focus();
- }, 50);
- }
- }
+ updateWord(pastedText, index);
};
// Paste all from clipboard
const paste = async () => {
try {
const text = await ui.pasteFromClipboard();
- const pastedWords = (text || "").trim().split(/\s+/);
- pastedWords.forEach((word, i) => {
- if (i < 12) {
- words.value[i] = word.trim();
- }
- });
+ updateWord(text, 0);
} catch {}
};
@@ -188,7 +180,7 @@ export default {
errorMsg,
paste,
handlePaste,
- handleInput,
+ updateWord,
setInputRef,
};
},
From a26d0c9ea42881e9ae54f7d1be77a85be109b2aa Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 10 Jan 2026 17:25:53 +0700
Subject: [PATCH 30/73] async wallet
---
src/components/CreateInvoiceDialog.vue | 5 ++-
src/components/MultinutPaymentDialog.vue | 10 +++--
src/components/SendTokenDialog.vue | 6 +--
src/components/SettingsView.vue | 2 +-
src/components/TokenInformation.vue | 2 +-
src/stores/mints.ts | 50 +++++++++++++-----------
src/stores/swap.ts | 18 +++++----
src/stores/wallet.ts | 49 ++++++++++++++++++-----
8 files changed, 91 insertions(+), 51 deletions(-)
diff --git a/src/components/CreateInvoiceDialog.vue b/src/components/CreateInvoiceDialog.vue
index cfbd03a9d..db0e32297 100644
--- a/src/components/CreateInvoiceDialog.vue
+++ b/src/components/CreateInvoiceDialog.vue
@@ -186,9 +186,10 @@ export default defineComponent({
this.invoiceData.amount * this.activeUnitCurrencyMultiplyer
);
this.createInvoiceButtonBlocked = true;
- const wallet = this.mintWallet(
+ const wallet = await this.mintWallet(
mintStore.activeMintUrl,
- mintStore.activeUnit
+ mintStore.activeUnit,
+ true
);
const mintQuote = await this.requestMint(amount, wallet);
// Switch to QR display dialog
diff --git a/src/components/MultinutPaymentDialog.vue b/src/components/MultinutPaymentDialog.vue
index 855fecca6..485561f66 100644
--- a/src/components/MultinutPaymentDialog.vue
+++ b/src/components/MultinutPaymentDialog.vue
@@ -908,9 +908,10 @@ export default defineComponent({
return null;
}
console.log(`Quoting mint: ${mint.url}`);
- const mintWallet = useWalletStore().mintWallet(
+ const mintWallet = await useWalletStore().mintWallet(
mint.url,
- useMintsStore().activeUnit
+ useMintsStore().activeUnit,
+ true
);
try {
this.setMintState(mint.url, "requesting");
@@ -938,9 +939,10 @@ export default defineComponent({
try {
// Move to paying state
this.setMintState(mint.url, "paying");
- const mintWallet = useWalletStore().mintWallet(
+ const mintWallet = await useWalletStore().mintWallet(
mint.url,
- activeUnit
+ activeUnit,
+ true
);
const mintClass = new MintClass(mint);
const proofs = mintClass.unitProofs(activeUnit);
diff --git a/src/components/SendTokenDialog.vue b/src/components/SendTokenDialog.vue
index 59ee7df67..35c4df511 100644
--- a/src/components/SendTokenDialog.vue
+++ b/src/components/SendTokenDialog.vue
@@ -495,7 +495,7 @@ export default defineComponent({
const sendAmount = Math.floor(
this.sendData.amount * this.activeUnitCurrencyMultiplyer
);
- const mintWallet = this.mintWallet(this.activeMintUrl, this.activeUnit);
+ const mintWallet = await this.mintWallet(this.activeMintUrl, this.activeUnit, true);
const { sendProofs } = await this.send(
this.activeProofs,
mintWallet,
@@ -539,7 +539,7 @@ export default defineComponent({
);
try {
// keep firstProofs, send scndProofs and delete them (invalidate=true)
- const mintWallet = this.mintWallet(this.activeMintUrl, this.activeUnit);
+ const mintWallet = await this.mintWallet(this.activeMintUrl, this.activeUnit, true);
const { _, sendProofs } = await this.sendToLock(
this.activeProofs,
mintWallet,
@@ -586,7 +586,7 @@ export default defineComponent({
const sendAmount = Math.floor(
this.sendData.amount * this.activeUnitCurrencyMultiplyer
);
- const mintWallet = this.mintWallet(this.activeMintUrl, this.activeUnit);
+ const mintWallet = await this.mintWallet(this.activeMintUrl, this.activeUnit, true);
// keep firstProofs, send scndProofs and delete them (invalidate=true)
const { _, sendProofs } = await this.send(
this.activeProofs,
diff --git a/src/components/SettingsView.vue b/src/components/SettingsView.vue
index 90b787f29..6d4ee8446 100644
--- a/src/components/SettingsView.vue
+++ b/src/components/SettingsView.vue
@@ -2193,7 +2193,7 @@ export default defineComponent({
},
checkActiveProofsSpendable: async function () {
// iterate over this.activeProofs in batches of 50 and check if they are spendable
- const wallet = useWalletStore().mintWallet(
+ const wallet = await useWalletStore().mintWallet(
this.activeMintUrl,
this.activeUnit
);
diff --git a/src/components/TokenInformation.vue b/src/components/TokenInformation.vue
index e18e5b2c2..23adddcc0 100644
--- a/src/components/TokenInformation.vue
+++ b/src/components/TokenInformation.vue
@@ -236,7 +236,7 @@ export default defineComponent({
const mintUrl = token.getMint(tokenJson);
const unit = token.getUnit(tokenJson);
const walletStore = useWalletStore();
- const wallet = walletStore.mintWallet(mintUrl, unit);
+ const wallet = walletStore.mintWalletSync(mintUrl, unit);
const fee = wallet.getFeesForProofs(proofs);
return fee || 0;
} catch (e) {
diff --git a/src/stores/mints.ts b/src/stores/mints.ts
index f707f1a05..faa89e66f 100644
--- a/src/stores/mints.ts
+++ b/src/stores/mints.ts
@@ -32,6 +32,8 @@ export type Mint = {
errored?: boolean;
motdDismissed?: boolean;
multinutSelected?: boolean;
+ lastInfoUpdated?: string;
+ lastKeysetsUpdated?: string;
// initialize api: new CashuMint(url) on activation
};
@@ -408,6 +410,16 @@ export const useMintsStore = defineStore("mints", {
const worker = useWorkersStore();
worker.clearAllWorkers();
},
+ updateMintInfoAndKeys: async function (mint: Mint) {
+ const newMintInfo = await this.fetchMintInfo(mint);
+ this.triggerMintInfoMotdChanged(newMintInfo, mint);
+ const mintToUpdate = this.mints.filter((m) => m.url === mint.url)[0];
+ mintToUpdate.info = newMintInfo;
+ mintToUpdate.errored = false;
+ mintToUpdate.lastInfoUpdated = new Date().toISOString();
+ mint = await this.fetchMintKeys(mint);
+ return mint;
+ },
activateMint: async function (mint: Mint, verbose = false, force = false) {
if (mint.url === this.activeMintUrl && !force) {
return;
@@ -421,18 +433,12 @@ export const useMintsStore = defineStore("mints", {
const previousUrl = this.activeMintUrl;
await uIStore.lockMutex();
try {
- this.activeMintUrl = mint.url;
- console.log("### this.activeMintUrl", this.activeMintUrl);
- const newMintInfo = await this.fetchMintInfo(mint);
- this.triggerMintInfoMotdChanged(newMintInfo, mint);
- mint.info = newMintInfo;
- console.log("### activateMint: Mint info: ", mint.info);
- mint = await this.fetchMintKeys(mint);
+ mint = await this.updateMintInfoAndKeys(mint);
this.toggleActiveUnitForMint(mint);
if (verbose) {
await notifySuccess(this.t("wallet.mint.notifications.activated"));
}
- this.mints.filter((m) => m.url === mint.url)[0].errored = false;
+ this.activeMintUrl = mint.url;
console.log("### activateMint: Mint activated: ", this.activeMintUrl);
} catch (error: any) {
// restore previous values because the activation errored
@@ -489,7 +495,7 @@ export const useMintsStore = defineStore("mints", {
console.error(error);
try {
// notifyApiError(error, this.t("wallet.mint.notifications.could_not_get_info"));
- } catch {}
+ } catch { }
throw error;
}
},
@@ -531,14 +537,6 @@ export const useMintsStore = defineStore("mints", {
try {
const mintClass = new MintClass(mint);
const keysets = await this.fetchMintKeysets(mint);
- if (keysets.length > 0) {
- // check for keyset id collisions with other mints
- await this.checkForMintKeysetIdCollisions(mint, keysets);
- // store keysets in mint and update local storage
- // TODO: do not overwrite anykeyset, but append new keysets and update existing ones
- this.mints.filter((m) => m.url === mint.url)[0].keysets = keysets;
- }
-
// if we do not have any keys yet, fetch them
if (mint.keys.length === 0 || mint.keys.length == undefined) {
const keys = await mintClass.api.getKeys();
@@ -559,27 +557,33 @@ export const useMintsStore = defineStore("mints", {
}
}
+ this.mints.filter((m) => m.url === mint.url)[0].lastKeysetsUpdated = new Date().toISOString();
// return the mint with keys set
return this.mints.filter((m) => m.url === mint.url)[0];
} catch (error: any) {
console.error(error);
try {
// notifyApiError(error, this.t("wallet.mint.notifications.could_not_get_keys"));
- } catch {}
+ } catch { }
throw error;
}
},
fetchMintKeysets: async function (mint: Mint) {
- // attention: this function overwrites this.keysets
+ // fetches and stores keysets for a mint
try {
const mintClass = new MintClass(mint);
const data = await mintClass.api.getKeySets();
- return data.keysets;
+ const keysets = data.keysets;
+ if (keysets.length > 0) {
+ // check for keyset id collisions with other mints
+ await this.checkForMintKeysetIdCollisions(mint, keysets);
+ // store keysets in mint and update local storage
+ // TODO: do not overwrite anykeyset, but append new keysets and update existing ones
+ this.mints.filter((m) => m.url === mint.url)[0].keysets = keysets;
+ }
+ return keysets;
} catch (error: any) {
console.error(error);
- try {
- // notifyApiError(error, this.t("wallet.mint.notifications.could_not_get_keysets"));
- } catch {}
throw error;
}
},
diff --git a/src/stores/swap.ts b/src/stores/swap.ts
index 902a47bdb..7de0457f8 100644
--- a/src/stores/swap.ts
+++ b/src/stores/swap.ts
@@ -59,9 +59,10 @@ export const useSwapStore = defineStore("swap", {
try {
// get invoice
// await mintStore.activateMintUrl(swapAmountData.toUrl);
- const toWallet = walletStore.mintWallet(
+ const toWallet = await walletStore.mintWallet(
swapAmountData.toUrl,
- mintStore.activeUnit
+ mintStore.activeUnit,
+ true
);
const mintQuote = await walletStore.requestMint(
swapAmountData.amount,
@@ -69,9 +70,10 @@ export const useSwapStore = defineStore("swap", {
);
// pay invoice
- const fromWallet = walletStore.mintWallet(
+ const fromWallet = await walletStore.mintWallet(
swapAmountData.fromUrl,
- mintStore.activeUnit
+ mintStore.activeUnit,
+ true
);
const meltQuote = await walletStore.meltQuote(
fromWallet,
@@ -95,7 +97,7 @@ export const useSwapStore = defineStore("swap", {
this.swapBlocking = false;
}
},
- meltToMintFees: function (tokenJson: Token) {
+ meltToMintFees: async function (tokenJson: Token) {
const proofsStore = useProofsStore();
const walletStore = useWalletStore();
const fromMintUrl = token.getMint(tokenJson);
@@ -104,7 +106,7 @@ export const useSwapStore = defineStore("swap", {
let meltAmount = tokenAmount - Math.max(2, Math.ceil(tokenAmount * 0.02));
try {
// walletStore.mintWallet(fromMintUrl, unit); will fail if we don't have fromMintUrl yet
- const fromWallet = walletStore.mintWallet(fromMintUrl, unit);
+ const fromWallet = await walletStore.mintWallet(fromMintUrl, unit);
const proofs = token.getProofs(tokenJson);
meltAmount -= fromWallet.getFeesForProofs(proofs);
} catch (e) {}
@@ -125,8 +127,8 @@ export const useSwapStore = defineStore("swap", {
tokenAmount - Math.max(2, Math.ceil(tokenAmount * 0.02));
const unit = token.getUnit(tokenJson);
const fromMintUrl = token.getMint(tokenJson);
- const fromWallet = walletStore.mintWallet(fromMintUrl, unit);
- const toWallet = walletStore.mintWallet(mint.url, unit);
+ const fromWallet = await walletStore.mintWallet(fromMintUrl, unit, true);
+ const toWallet = await walletStore.mintWallet(mint.url, unit, true);
const proofs = token.getProofs(tokenJson);
meltAmount -= fromWallet.getFeesForProofs(proofs);
diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts
index 7d5ae8642..a48bd6cdd 100644
--- a/src/stores/wallet.ts
+++ b/src/stores/wallet.ts
@@ -185,15 +185,45 @@ export const useWalletStore = defineStore("wallet", {
setMnemonicFromUser: function (mnemonic: string) {
this.mnemonic = mnemonic;
},
- mintWallet(url: string, unit: string): CashuWallet {
+ async mintWallet(url: string, unit: string, updateKeysets: boolean = false): Promise
{
// short-lived wallet for mint operations
// note: the unit of the wallet will be activeUnit by default,
// overwrite wallet.unit if needed
+ const mints = useMintsStore() as any;
+ let storedMint = mints.mints.find((m: any) => m.url === url);
+ if (!storedMint) {
+ throw new Error("mint not found");
+ }
+ // if updateKeysets is true and keysetsLastFetched is older than 1 hour, fetch the keysets for the mint
+ const ONE_HOUR = 60 * 60 * 1000;
+ const lastUpdated = storedMint.lastKeysetsUpdated
+ ? new Date(storedMint.lastKeysetsUpdated).getTime()
+ : 0;
+ const mintNeedsUpdate = updateKeysets && (lastUpdated < Date.now() - ONE_HOUR);
+ if (mintNeedsUpdate) {
+ console.log("updating mint info and keys for mint", storedMint.url);
+ try {
+ await mints.updateMintInfoAndKeys(storedMint);
+ // Re-fetch mint after update to get fresh keysets
+ storedMint = mints.mints.find((m: any) => m.url === url);
+ } catch (error: any) {
+ console.error("Failed to update mint info/keys:", error);
+ // Continue with potentially stale keysets rather than failing
+ }
+ }
+ return this.createWalletInstance(storedMint, url, unit, mints);
+ },
+ // Synchronous wallet creation for non-critical operations (e.g., fee calculation display)
+ // Use mintWallet() with updateKeysets=true for critical operations
+ mintWalletSync(url: string, unit: string): CashuWallet {
const mints = useMintsStore() as any;
const storedMint = mints.mints.find((m: any) => m.url === url);
if (!storedMint) {
throw new Error("mint not found");
}
+ return this.createWalletInstance(storedMint, url, unit, mints);
+ },
+ createWalletInstance(storedMint: any, url: string, unit: string, mints: any): CashuWallet {
const unitKeysets = mints.mintUnitKeysets(storedMint, unit);
const mint = new CashuMint(url);
if (this.mnemonic == "") {
@@ -504,7 +534,7 @@ export const useWalletStore = defineStore("wallet", {
mint: mintInToken,
fee: fee,
};
- const mintWallet = this.mintWallet(historyToken.mint, historyToken.unit);
+ const mintWallet = await this.mintWallet(historyToken.mint, historyToken.unit, true);
const mint = mintStore.mints.find((m) => m.url === historyToken.mint);
if (!mint) {
throw new Error("mint not found");
@@ -639,7 +669,7 @@ export const useWalletStore = defineStore("wallet", {
const mintStore = useMintsStore();
const uIStore = useUiStore();
const keysetId = this.getKeyset(invoice.mint, invoice.unit);
- const mintWallet = this.mintWallet(invoice.mint, invoice.unit);
+ const mintWallet = await this.mintWallet(invoice.mint, invoice.unit, true);
const mint = mintStore.mints.find((m) => m.url === invoice.mint);
if (!mint) {
throw new Error("mint not found");
@@ -766,9 +796,10 @@ export const useWalletStore = defineStore("wallet", {
}
const mintStore = useMintsStore();
- const mintWallet = this.mintWallet(
+ const mintWallet = await this.mintWallet(
mintStore.activeMintUrl,
- mintStore.activeUnit
+ mintStore.activeUnit,
+ true
);
return await this.melt(mintStore.activeProofs, quote, mintWallet, silent);
},
@@ -988,7 +1019,7 @@ export const useWalletStore = defineStore("wallet", {
throw new Error("no tokens provided.");
}
const proofs = token.getProofs(tokenJson);
- const mintWallet = this.mintWallet(historyToken.mint, historyToken.unit);
+ const mintWallet = await this.mintWallet(historyToken.mint, historyToken.unit);
const mint = mintStore.mints.find((m) => m.url === historyToken.mint);
if (!mint) {
@@ -1066,7 +1097,7 @@ export const useWalletStore = defineStore("wallet", {
if (!invoice) {
throw new Error("invoice not found");
}
- const mintWallet = this.mintWallet(invoice.mint, invoice.unit);
+ const mintWallet = await this.mintWallet(invoice.mint, invoice.unit);
const mint = mintStore.mints.find((m) => m.url === invoice.mint);
if (!mint) {
throw new Error("mint not found");
@@ -1111,7 +1142,7 @@ export const useWalletStore = defineStore("wallet", {
if (!invoice) {
throw new Error("invoice not found");
}
- const mintWallet = this.mintWallet(invoice.mint, invoice.unit);
+ const mintWallet = await this.mintWallet(invoice.mint, invoice.unit);
const mint = mintStore.mints.find((m) => m.url === invoice.mint);
if (!mint) {
throw new Error("mint not found");
@@ -1250,7 +1281,7 @@ export const useWalletStore = defineStore("wallet", {
if (!invoice) {
throw new Error("invoice not found");
}
- const mintWallet = this.mintWallet(invoice.mint, invoice.unit);
+ const mintWallet = await this.mintWallet(invoice.mint, invoice.unit);
const mint = mintStore.mints.find((m) => m.url === invoice.mint);
if (!mint) {
From 13a1a9957b059c6087475a1564eca1b1abd52515 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 10 Jan 2026 17:33:13 +0700
Subject: [PATCH 31/73] fix test mock
---
test/vitest/setup-file.js | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/test/vitest/setup-file.js b/test/vitest/setup-file.js
index af8df861d..27f4359ed 100644
--- a/test/vitest/setup-file.js
+++ b/test/vitest/setup-file.js
@@ -11,3 +11,27 @@ beforeEach(() => {
console.log("Setting up Pinia");
setActivePinia(createPinia()); // Fresh instance for each test
});
+
+// Mock localStorage
+const localStorageMock = (function () {
+ let store = {};
+ return {
+ getItem: function (key) {
+ return store[key] || null;
+ },
+ setItem: function (key, value) {
+ store[key] = value.toString();
+ },
+ removeItem: function (key) {
+ delete store[key];
+ },
+ clear: function () {
+ store = {};
+ },
+ };
+})();
+
+Object.defineProperty(window, "localStorage", {
+ value: localStorageMock,
+ writable: true,
+});
From 9857c4df3043a04432bc4dd593c51bfe03604d11 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 10 Jan 2026 17:36:50 +0700
Subject: [PATCH 32/73] refactor: async mintWallet and automatic keyset updates
---
src/stores/wallet.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts
index a48bd6cdd..006a35406 100644
--- a/src/stores/wallet.ts
+++ b/src/stores/wallet.ts
@@ -196,10 +196,11 @@ export const useWalletStore = defineStore("wallet", {
}
// if updateKeysets is true and keysetsLastFetched is older than 1 hour, fetch the keysets for the mint
const ONE_HOUR = 60 * 60 * 1000;
+ const TEN_SECONDS = 10 * 1000; // for testing
const lastUpdated = storedMint.lastKeysetsUpdated
? new Date(storedMint.lastKeysetsUpdated).getTime()
: 0;
- const mintNeedsUpdate = updateKeysets && (lastUpdated < Date.now() - ONE_HOUR);
+ const mintNeedsUpdate = updateKeysets && (lastUpdated < Date.now() - TEN_SECONDS);
if (mintNeedsUpdate) {
console.log("updating mint info and keys for mint", storedMint.url);
try {
From a7fd3a45cdee9b148e567a9a11ced3c78b540f89 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 10 Jan 2026 17:41:28 +0700
Subject: [PATCH 33/73] back to one hour
---
src/stores/wallet.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts
index 006a35406..a48bd6cdd 100644
--- a/src/stores/wallet.ts
+++ b/src/stores/wallet.ts
@@ -196,11 +196,10 @@ export const useWalletStore = defineStore("wallet", {
}
// if updateKeysets is true and keysetsLastFetched is older than 1 hour, fetch the keysets for the mint
const ONE_HOUR = 60 * 60 * 1000;
- const TEN_SECONDS = 10 * 1000; // for testing
const lastUpdated = storedMint.lastKeysetsUpdated
? new Date(storedMint.lastKeysetsUpdated).getTime()
: 0;
- const mintNeedsUpdate = updateKeysets && (lastUpdated < Date.now() - TEN_SECONDS);
+ const mintNeedsUpdate = updateKeysets && (lastUpdated < Date.now() - ONE_HOUR);
if (mintNeedsUpdate) {
console.log("updating mint info and keys for mint", storedMint.url);
try {
From e919e93949455823306886c6a7d19ad593eb0b51 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 10 Jan 2026 18:01:12 +0700
Subject: [PATCH 34/73] update keyset for send only if swap is needed
---
src/components/SendTokenDialog.vue | 2 +-
src/stores/mints.ts | 12 +++++++++---
src/stores/wallet.ts | 10 +++++++---
3 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/src/components/SendTokenDialog.vue b/src/components/SendTokenDialog.vue
index 35c4df511..8b65060f4 100644
--- a/src/components/SendTokenDialog.vue
+++ b/src/components/SendTokenDialog.vue
@@ -586,7 +586,7 @@ export default defineComponent({
const sendAmount = Math.floor(
this.sendData.amount * this.activeUnitCurrencyMultiplyer
);
- const mintWallet = await this.mintWallet(this.activeMintUrl, this.activeUnit, true);
+ const mintWallet = await this.mintWallet(this.activeMintUrl, this.activeUnit, false);
// keep firstProofs, send scndProofs and delete them (invalidate=true)
const { _, sendProofs } = await this.send(
this.activeProofs,
diff --git a/src/stores/mints.ts b/src/stores/mints.ts
index faa89e66f..03a50098f 100644
--- a/src/stores/mints.ts
+++ b/src/stores/mints.ts
@@ -413,11 +413,10 @@ export const useMintsStore = defineStore("mints", {
updateMintInfoAndKeys: async function (mint: Mint) {
const newMintInfo = await this.fetchMintInfo(mint);
this.triggerMintInfoMotdChanged(newMintInfo, mint);
+ mint = await this.fetchMintKeys(mint);
+
const mintToUpdate = this.mints.filter((m) => m.url === mint.url)[0];
- mintToUpdate.info = newMintInfo;
mintToUpdate.errored = false;
- mintToUpdate.lastInfoUpdated = new Date().toISOString();
- mint = await this.fetchMintKeys(mint);
return mint;
},
activateMint: async function (mint: Mint, verbose = false, force = false) {
@@ -490,6 +489,13 @@ export const useMintsStore = defineStore("mints", {
try {
const mintClass = new MintClass(mint);
const data = await mintClass.api.getInfo();
+
+ // if we have this mint in localstorage, update it
+ const storedMint = this.mints.find((m) => m.url === mint.url);
+ if (storedMint) {
+ storedMint.info = data;
+ storedMint.lastInfoUpdated = new Date().toISOString();
+ }
return data;
} catch (error: any) {
console.error(error);
diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts
index a48bd6cdd..05cb27a8d 100644
--- a/src/stores/wallet.ts
+++ b/src/stores/wallet.ts
@@ -196,10 +196,11 @@ export const useWalletStore = defineStore("wallet", {
}
// if updateKeysets is true and keysetsLastFetched is older than 1 hour, fetch the keysets for the mint
const ONE_HOUR = 60 * 60 * 1000;
+ const TEN_SECONDS = 10 * 1000;
const lastUpdated = storedMint.lastKeysetsUpdated
? new Date(storedMint.lastKeysetsUpdated).getTime()
: 0;
- const mintNeedsUpdate = updateKeysets && (lastUpdated < Date.now() - ONE_HOUR);
+ const mintNeedsUpdate = updateKeysets && (lastUpdated < Date.now() - TEN_SECONDS);
if (mintNeedsUpdate) {
console.log("updating mint info and keys for mint", storedMint.url);
try {
@@ -451,14 +452,17 @@ export const useWalletStore = defineStore("wallet", {
let sendProofs: Proof[] = [];
if (totalAmount != targetAmount) {
+ // we need to swap!
+ // get a new wallet with potentially updated keysets / info
+ const swapWallet = await this.mintWallet(wallet.mint.mintUrl, wallet.unit, true);
const counter = this.keysetCounter(keysetId);
proofsToSend = this.coinSelect(
spendableProofs,
- wallet,
+ swapWallet,
targetAmount,
true
);
- ({ keep: keepProofs, send: sendProofs } = await wallet.send(
+ ({ keep: keepProofs, send: sendProofs } = await swapWallet.send(
targetAmount,
proofsToSend,
{ counter, keysetId, proofsWeHave: spendableProofs }
From db1b0b98febe3caa0b42a7801ac726567dbbf255 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 10 Jan 2026 18:01:39 +0700
Subject: [PATCH 35/73] npm run format
---
src/components/SendTokenDialog.vue | 18 +++++++++++---
src/stores/mints.ts | 7 +++---
src/stores/swap.ts | 6 ++++-
src/stores/wallet.ts | 39 ++++++++++++++++++++++++------
4 files changed, 56 insertions(+), 14 deletions(-)
diff --git a/src/components/SendTokenDialog.vue b/src/components/SendTokenDialog.vue
index 8b65060f4..ef007b74c 100644
--- a/src/components/SendTokenDialog.vue
+++ b/src/components/SendTokenDialog.vue
@@ -495,7 +495,11 @@ export default defineComponent({
const sendAmount = Math.floor(
this.sendData.amount * this.activeUnitCurrencyMultiplyer
);
- const mintWallet = await this.mintWallet(this.activeMintUrl, this.activeUnit, true);
+ const mintWallet = await this.mintWallet(
+ this.activeMintUrl,
+ this.activeUnit,
+ true
+ );
const { sendProofs } = await this.send(
this.activeProofs,
mintWallet,
@@ -539,7 +543,11 @@ export default defineComponent({
);
try {
// keep firstProofs, send scndProofs and delete them (invalidate=true)
- const mintWallet = await this.mintWallet(this.activeMintUrl, this.activeUnit, true);
+ const mintWallet = await this.mintWallet(
+ this.activeMintUrl,
+ this.activeUnit,
+ true
+ );
const { _, sendProofs } = await this.sendToLock(
this.activeProofs,
mintWallet,
@@ -586,7 +594,11 @@ export default defineComponent({
const sendAmount = Math.floor(
this.sendData.amount * this.activeUnitCurrencyMultiplyer
);
- const mintWallet = await this.mintWallet(this.activeMintUrl, this.activeUnit, false);
+ const mintWallet = await this.mintWallet(
+ this.activeMintUrl,
+ this.activeUnit,
+ false
+ );
// keep firstProofs, send scndProofs and delete them (invalidate=true)
const { _, sendProofs } = await this.send(
this.activeProofs,
diff --git a/src/stores/mints.ts b/src/stores/mints.ts
index 03a50098f..226844d82 100644
--- a/src/stores/mints.ts
+++ b/src/stores/mints.ts
@@ -501,7 +501,7 @@ export const useMintsStore = defineStore("mints", {
console.error(error);
try {
// notifyApiError(error, this.t("wallet.mint.notifications.could_not_get_info"));
- } catch { }
+ } catch {}
throw error;
}
},
@@ -563,14 +563,15 @@ export const useMintsStore = defineStore("mints", {
}
}
- this.mints.filter((m) => m.url === mint.url)[0].lastKeysetsUpdated = new Date().toISOString();
+ this.mints.filter((m) => m.url === mint.url)[0].lastKeysetsUpdated =
+ new Date().toISOString();
// return the mint with keys set
return this.mints.filter((m) => m.url === mint.url)[0];
} catch (error: any) {
console.error(error);
try {
// notifyApiError(error, this.t("wallet.mint.notifications.could_not_get_keys"));
- } catch { }
+ } catch {}
throw error;
}
},
diff --git a/src/stores/swap.ts b/src/stores/swap.ts
index 7de0457f8..7c68a1356 100644
--- a/src/stores/swap.ts
+++ b/src/stores/swap.ts
@@ -127,7 +127,11 @@ export const useSwapStore = defineStore("swap", {
tokenAmount - Math.max(2, Math.ceil(tokenAmount * 0.02));
const unit = token.getUnit(tokenJson);
const fromMintUrl = token.getMint(tokenJson);
- const fromWallet = await walletStore.mintWallet(fromMintUrl, unit, true);
+ const fromWallet = await walletStore.mintWallet(
+ fromMintUrl,
+ unit,
+ true
+ );
const toWallet = await walletStore.mintWallet(mint.url, unit, true);
const proofs = token.getProofs(tokenJson);
meltAmount -= fromWallet.getFeesForProofs(proofs);
diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts
index 05cb27a8d..fae31bc01 100644
--- a/src/stores/wallet.ts
+++ b/src/stores/wallet.ts
@@ -185,7 +185,11 @@ export const useWalletStore = defineStore("wallet", {
setMnemonicFromUser: function (mnemonic: string) {
this.mnemonic = mnemonic;
},
- async mintWallet(url: string, unit: string, updateKeysets: boolean = false): Promise {
+ async mintWallet(
+ url: string,
+ unit: string,
+ updateKeysets: boolean = false
+ ): Promise {
// short-lived wallet for mint operations
// note: the unit of the wallet will be activeUnit by default,
// overwrite wallet.unit if needed
@@ -200,7 +204,8 @@ export const useWalletStore = defineStore("wallet", {
const lastUpdated = storedMint.lastKeysetsUpdated
? new Date(storedMint.lastKeysetsUpdated).getTime()
: 0;
- const mintNeedsUpdate = updateKeysets && (lastUpdated < Date.now() - TEN_SECONDS);
+ const mintNeedsUpdate =
+ updateKeysets && lastUpdated < Date.now() - TEN_SECONDS;
if (mintNeedsUpdate) {
console.log("updating mint info and keys for mint", storedMint.url);
try {
@@ -224,7 +229,12 @@ export const useWalletStore = defineStore("wallet", {
}
return this.createWalletInstance(storedMint, url, unit, mints);
},
- createWalletInstance(storedMint: any, url: string, unit: string, mints: any): CashuWallet {
+ createWalletInstance(
+ storedMint: any,
+ url: string,
+ unit: string,
+ mints: any
+ ): CashuWallet {
const unitKeysets = mints.mintUnitKeysets(storedMint, unit);
const mint = new CashuMint(url);
if (this.mnemonic == "") {
@@ -454,7 +464,11 @@ export const useWalletStore = defineStore("wallet", {
if (totalAmount != targetAmount) {
// we need to swap!
// get a new wallet with potentially updated keysets / info
- const swapWallet = await this.mintWallet(wallet.mint.mintUrl, wallet.unit, true);
+ const swapWallet = await this.mintWallet(
+ wallet.mint.mintUrl,
+ wallet.unit,
+ true
+ );
const counter = this.keysetCounter(keysetId);
proofsToSend = this.coinSelect(
spendableProofs,
@@ -538,7 +552,11 @@ export const useWalletStore = defineStore("wallet", {
mint: mintInToken,
fee: fee,
};
- const mintWallet = await this.mintWallet(historyToken.mint, historyToken.unit, true);
+ const mintWallet = await this.mintWallet(
+ historyToken.mint,
+ historyToken.unit,
+ true
+ );
const mint = mintStore.mints.find((m) => m.url === historyToken.mint);
if (!mint) {
throw new Error("mint not found");
@@ -673,7 +691,11 @@ export const useWalletStore = defineStore("wallet", {
const mintStore = useMintsStore();
const uIStore = useUiStore();
const keysetId = this.getKeyset(invoice.mint, invoice.unit);
- const mintWallet = await this.mintWallet(invoice.mint, invoice.unit, true);
+ const mintWallet = await this.mintWallet(
+ invoice.mint,
+ invoice.unit,
+ true
+ );
const mint = mintStore.mints.find((m) => m.url === invoice.mint);
if (!mint) {
throw new Error("mint not found");
@@ -1023,7 +1045,10 @@ export const useWalletStore = defineStore("wallet", {
throw new Error("no tokens provided.");
}
const proofs = token.getProofs(tokenJson);
- const mintWallet = await this.mintWallet(historyToken.mint, historyToken.unit);
+ const mintWallet = await this.mintWallet(
+ historyToken.mint,
+ historyToken.unit
+ );
const mint = mintStore.mints.find((m) => m.url === historyToken.mint);
if (!mint) {
From 942ca27d0ea85cdc2533e2af472d9f1befd8d516 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 10 Jan 2026 18:09:29 +0700
Subject: [PATCH 36/73] one hour
---
src/stores/wallet.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts
index fae31bc01..563c9ca2e 100644
--- a/src/stores/wallet.ts
+++ b/src/stores/wallet.ts
@@ -200,12 +200,11 @@ export const useWalletStore = defineStore("wallet", {
}
// if updateKeysets is true and keysetsLastFetched is older than 1 hour, fetch the keysets for the mint
const ONE_HOUR = 60 * 60 * 1000;
- const TEN_SECONDS = 10 * 1000;
const lastUpdated = storedMint.lastKeysetsUpdated
? new Date(storedMint.lastKeysetsUpdated).getTime()
: 0;
const mintNeedsUpdate =
- updateKeysets && lastUpdated < Date.now() - TEN_SECONDS;
+ updateKeysets && lastUpdated < Date.now() - ONE_HOUR;
if (mintNeedsUpdate) {
console.log("updating mint info and keys for mint", storedMint.url);
try {
From c46bd5aa0d5368a62137c4585e002ad8ea56eac4 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 10 Jan 2026 18:16:15 +0700
Subject: [PATCH 37/73] add AGENTS.md
---
AGENTS.md | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 180 insertions(+)
create mode 100644 AGENTS.md
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 000000000..8d2fce864
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,180 @@
+# Cashu.me Developer Guide for Agents
+
+This document provides a comprehensive overview of the **Cashu.me** codebase. It is designed to help coding agents understand the architecture, tech stack, conventions, and patterns used in this project.
+
+## 1. Tech Stack
+
+### Core Frameworks
+
+- **Framework:** [Quasar Framework](https://quasar.dev/) (Vue.js 3 + Vite)
+- **Language:** TypeScript (mostly) and JavaScript.
+- **State Management:** [Pinia](https://pinia.vuejs.org/)
+- **Routing:** Vue Router (standard Quasar setup)
+- **Build Tool:** Vite (via Quasar CLI)
+- **CSS:** SCSS/Sass with Quasar's utility classes.
+
+### Mobile & Desktop
+
+- **Mobile:** [Capacitor](https://capacitorjs.com/) (Android & iOS)
+- **Desktop:** Electron (via Quasar mode)
+- **PWA:** Supported and primary delivery method for web.
+
+### Cashu & Cryptography
+
+- **Cashu Library:** [`@cashu/cashu-ts`](https://github.com/cashubtc/cashu-ts) (Core Cashu wallet logic)
+- **Crypto:** `@cashu/crypto`, `@noble/secp256k1`
+- **Lightning/Bitcoin:** `light-bolt11-decoder`, `bech32`
+
+### Persistence
+
+- **Database:** [Dexie.js](https://dexie.org/) (IndexedDB wrapper) for storing Cashu proofs (tokens).
+- **Local Storage:** `@vueuse/core` (`useLocalStorage`) for user settings, history, and simpler state.
+
+### Testing & Linting
+
+- **Testing:** Vitest
+- **Linting:** ESLint + Prettier
+
+---
+
+## 2. Project Structure
+
+```
+.
+├── src/
+│ ├── assets/ # Static assets (images, icons)
+│ ├── boot/ # Quasar boot files (initialization logic)
+│ ├── components/ # Vue components (UI elements)
+│ ├── css/ # Global styles (SCSS)
+│ ├── i18n/ # Internationalization (locales)
+│ ├── js/ # Utility functions (non-component logic)
+│ ├── layouts/ # App layouts (MainLayout, FullscreenLayout)
+│ ├── pages/ # Route pages (WalletPage, Settings, etc.)
+│ ├── router/ # Vue Router configuration
+│ ├── stores/ # Pinia stores (Critical business logic)
+│ ├── App.vue # Root component
+│ └── main.js # Entry point
+├── src-capacitor/ # Capacitor configuration and native projects
+├── src-electron/ # Electron main/preload scripts
+├── src-pwa/ # PWA service worker and manifest
+└── quasar.config.js # Quasar configuration
+```
+
+---
+
+## 3. Architecture & Key Stores
+
+The application logic is heavily centralized in Pinia stores found in `src/stores/`.
+
+### Critical Stores
+
+- **`wallet.ts` (`useWalletStore`):** The **primary controller** for the wallet. It handles:
+ - Sending, receiving, melting (paying invoices), and minting tokens.
+ - interacting with the `cashu-ts` library (`CashuWallet`, `CashuMint`).
+ - managing invoice history.
+- **`mints.ts` (`useMintsStore`):** Manages the list of connected mints, their keysets, and URLs.
+- **`proofs.ts` (`useProofsStore`):** Manages the collection of proofs (ecash tokens). Handles CRUD operations for proofs in memory and syncs with storage.
+- **`tokens.ts` (`useTokensStore`):** Manages token history (spent/received tokens log).
+- **`dexie.ts` (`useDexieStore`):** Wrapper around Dexie.js for persistent storage of proofs.
+- **`ui.ts` (`useUiStore`):** Manages UI state (loaders, dialog visibility, tab selection).
+
+### Database Schema (Dexie)
+
+The `proofs` table in Dexie stores the actual ecash tokens:
+
+- `secret` (string, PK)
+- `amount` (number)
+- `C` (string, curve point)
+- `id` (string, keyset ID)
+- `reserved` (boolean, locked for pending operations)
+- `quote` (string, optional)
+
+---
+
+## 4. Coding Conventions
+
+### Component Style
+
+The project predominantly uses the **Options API** with **Pinia mappers** within `.vue` files, even though it is a Vue 3 project.
+
+**Pattern:**
+
+```typescript
+import { defineComponent } from "vue";
+import { mapState, mapActions, mapWritableState } from "pinia";
+import { useWalletStore } from "src/stores/wallet";
+
+export default defineComponent({
+ name: "MyComponent",
+ mixins: [windowMixin], // Common mixin used for global window props
+ components: { ... },
+ data() {
+ return { ... };
+ },
+ computed: {
+ ...mapState(useWalletStore, ["someState"]),
+ ...mapWritableState(useWalletStore, ["someWritableState"]),
+ },
+ methods: {
+ ...mapActions(useWalletStore, ["someAction"]),
+ myMethod() {
+ // Logic here
+ }
+ }
+});
+```
+
+**Note:** While `
+
+
diff --git a/src/components/wizard/SetupWizard.vue b/src/components/wizard/SetupWizard.vue
new file mode 100644
index 000000000..461f7a8d1
--- /dev/null
+++ b/src/components/wizard/SetupWizard.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/wizard/StepDone.vue b/src/components/wizard/StepDone.vue
new file mode 100644
index 000000000..7906d69e0
--- /dev/null
+++ b/src/components/wizard/StepDone.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
You're all set!
+
+
+ Your Flash Ecash wallet is ready to send and receive payments.
+
+
+
+
Your Flash Address
+
+ {{ flashStore.address }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/wizard/StepNostrKey.vue b/src/components/wizard/StepNostrKey.vue
new file mode 100644
index 000000000..db3d5b5c2
--- /dev/null
+++ b/src/components/wizard/StepNostrKey.vue
@@ -0,0 +1,159 @@
+
+
+
+
🔑
+
Connect your Nostr key
+
+ Your Nostr key lets you sign in across apps and receive tips.
+
+
+
+
+
+
+
+
+
+ Nostr key loaded
+
+
+ {{ shortPubkey }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/wizard/StepUsername.vue b/src/components/wizard/StepUsername.vue
new file mode 100644
index 000000000..87960f2e1
--- /dev/null
+++ b/src/components/wizard/StepUsername.vue
@@ -0,0 +1,128 @@
+
+
+
+
⚡
+
+ Choose your Flash Address
+
+
+ Receive ecash payments at
+ you@ecash.flashapp.me
+
+
+
+
+
+ @ecash.flashapp.me
+
+
+
+
+
+ Checking availability…
+
+
+
+ {{ username }}@ecash.flashapp.me is
+ available!
+
+
+ That username is taken. Try another.
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue
index bd61b1688..b30cd3408 100644
--- a/src/pages/WalletPage.vue
+++ b/src/pages/WalletPage.vue
@@ -1,158 +1,165 @@
-
-
-
-
-
-
-
-
-
-
{{ $t("WalletPage.actions.receive.label") }}
+
+
+
+
+
+
+
+
+
+
+ {{ $t("WalletPage.actions.receive.label") }}
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t("WalletPage.install.text") }}{{
- $t("WalletPage.install.tooltip")
- }}
+ @click="triggerPwaInstall()"
+ >{{ $t("WalletPage.install.text") }}{{
+ $t("WalletPage.install.tooltip")
+ }}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
+
-
-
-
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
⚡
+
Claim ecash
+
Check for pending payments at your Flash Address
+
+
+
+
+
+
+
Pending payment
+
-- sats
+
+
+
+
📫
No pending payments found.
Payments arrive shortly after being sent.
+
✅
Redirecting to wallet…
Opening Flash Ecash to redeem your tokens.
+
+
+
+
+