aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-03-04 23:06:35 -0800
committerAdam Soltys <[email protected]>2026-03-04 23:06:35 -0800
commite5f37b003d93a17c2a9958697269b2d3228d8830 (patch)
treee2c3a3c4d1bec48d323e830f5be35d5832845a9a /src
parente6882c6a8987e23215a2538b242431ae603c7101 (diff)
downloadcoinos-ui-e5f37b003d93a17c2a9958697269b2d3228d8830.tar.gz
coinos-ui-e5f37b003d93a17c2a9958697269b2d3228d8830.zip
tuning
Diffstat (limited to 'src')
-rw-r--r--src/app.css8
-rw-r--r--src/app.html1
-rw-r--r--src/components/Account.svelte75
-rw-r--r--src/components/AppHeader.svelte12
-rw-r--r--src/components/InvoiceActions.svelte73
-rw-r--r--src/components/InvoiceData.svelte11
-rw-r--r--src/components/WalletPass.svelte100
-rw-r--r--src/hooks.server.ts21
-rw-r--r--src/lib/parse.ts3
-rw-r--r--src/lib/passwordCache.ts62
-rw-r--r--src/lib/utils.ts5
-rw-r--r--src/locales/ar.json7
-rw-r--r--src/locales/bn.json7
-rw-r--r--src/locales/de.json7
-rw-r--r--src/locales/el.json7
-rw-r--r--src/locales/en.json7
-rw-r--r--src/locales/es.json7
-rw-r--r--src/locales/fa.json7
-rw-r--r--src/locales/fr.json7
-rw-r--r--src/locales/hi.json7
-rw-r--r--src/locales/it.json7
-rw-r--r--src/locales/ja.json7
-rw-r--r--src/locales/ko.json7
-rw-r--r--src/locales/nl.json7
-rw-r--r--src/locales/pl.json7
-rw-r--r--src/locales/pt.json7
-rw-r--r--src/locales/ru.json7
-rw-r--r--src/locales/th.json7
-rw-r--r--src/locales/tr.json7
-rw-r--r--src/locales/zh.json7
-rw-r--r--src/routes/(app)/+layout.server.ts51
-rw-r--r--src/routes/(app)/+layout.svelte8
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte2
-rw-r--r--src/routes/(app)/account/new/+page.svelte16
-rw-r--r--src/routes/(app)/invoice/+page.server.ts2
-rw-r--r--src/routes/(app)/invoice/[id]/+layout.ts (renamed from src/routes/(app)/invoice/[id]/+layout.server.ts)25
-rw-r--r--src/routes/(app)/invoice/[id]/+page.svelte4
-rw-r--r--src/routes/(app)/invoice/[id]/tip/+page.server.ts5
-rw-r--r--src/routes/(app)/send/ark/[address]/+layout.server.ts15
-rw-r--r--src/routes/(app)/send/ark/[address]/+page.server.ts16
-rw-r--r--src/routes/(app)/send/ark/[address]/+page.svelte4
-rw-r--r--src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts14
-rw-r--r--src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte4
-rw-r--r--src/routes/(static)/login/+page.svelte13
-rw-r--r--src/routes/+layout.server.ts9
-rw-r--r--src/routes/+layout.svelte13
46 files changed, 243 insertions, 462 deletions
diff --git a/src/app.css b/src/app.css
index 3c12f531..0c11c09c 100644
--- a/src/app.css
+++ b/src/app.css
@@ -36,12 +36,6 @@
w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl;
}
-@font-face {
- font-family: "PublicSans";
- src: url("/fonts/Public_Sans/static/PublicSans-Regular.woff2") format("woff2");
- font-display: optional;
-}
-
:root {
--toastContainerTop: auto;
--toastContainerRight: auto;
@@ -51,7 +45,7 @@
}
html {
- font-family: PublicSans, sans-serif;
+ font-family: system-ui, sans-serif;
}
::selection {
diff --git a/src/app.html b/src/app.html
index 42d00a9e..9ed29d0c 100644
--- a/src/app.html
+++ b/src/app.html
@@ -10,7 +10,6 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="apple-touch-icon" href="/images/icon-160.png" />
- <link rel="preload" href="/fonts/Public_Sans/static/PublicSans-Regular.woff2" as="font" type="font/woff2" crossorigin />
<link rel="manifest" href="/manifest.webmanifest" />
%sveltekit.head%
<script>
diff --git a/src/components/Account.svelte b/src/components/Account.svelte
index e29e1765..2d4b1e48 100644
--- a/src/components/Account.svelte
+++ b/src/components/Account.svelte
@@ -23,27 +23,27 @@
let arkBalance = $state(0);
let arkLoading = $state(false);
- let tryUnlockArk = async (): Promise<string | false> => {
- const deriveArkKey = async (key: ArrayBuffer): Promise<string> => {
- const [{ HDKey }, { entropyToMnemonic, mnemonicToSeed }, { wordlist }] = await Promise.all([
- import("@scure/bip32"),
- import("@scure/bip39"),
- import("@scure/bip39/wordlists/english.js"),
- ]);
- let mnemonicStr: string;
- if (account.seed && isPrfEncrypted(account.seed)) {
- const ent = await prfDecrypt(key, account.seed);
- mnemonicStr = entropyToMnemonic(ent, wordlist);
- } else {
- const entropy = new Uint8Array(key).slice(0, 16);
- mnemonicStr = entropyToMnemonic(entropy, wordlist);
- }
- const s = await mnemonicToSeed(mnemonicStr);
- const master = HDKey.fromMasterSeed(s, versions);
- const arkChild = master.derive("m/86'/0'/0'/0/0");
- return bytesToHex(arkChild.privateKey!);
- };
+ let deriveArkKey = async (key: ArrayBuffer): Promise<string> => {
+ const [{ HDKey }, { entropyToMnemonic, mnemonicToSeed }, { wordlist }] = await Promise.all([
+ import("@scure/bip32"),
+ import("@scure/bip39"),
+ import("@scure/bip39/wordlists/english.js"),
+ ]);
+ let mnemonicStr: string;
+ if (account.seed && isPrfEncrypted(account.seed)) {
+ const ent = await prfDecrypt(key, account.seed);
+ mnemonicStr = entropyToMnemonic(ent, wordlist);
+ } else {
+ const entropy = new Uint8Array(key).slice(0, 16);
+ mnemonicStr = entropyToMnemonic(entropy, wordlist);
+ }
+ const s = await mnemonicToSeed(mnemonicStr);
+ const master = HDKey.fromMasterSeed(s, versions);
+ const arkChild = master.derive("m/86'/0'/0'/0/0");
+ return bytesToHex(arkChild.privateKey!);
+ };
+ let tryUnlockArk = async (): Promise<string | false> => {
// Try cached PRF key or silent nsec
const { getWalletEntropy, deriveNostrEntropy } = await import("$lib/walletEntropy");
const entropy = await getWalletEntropy();
@@ -65,24 +65,38 @@
}
};
- let setAccount = async (event, url) => {
+ let aidParam = $derived(`aid=${id}`);
+ let withAid = (url: string) => url + (url.includes("?") ? "&" : "?") + aidParam;
+
+ let setAccount = async (event, url, requireKey = false) => {
event.preventDefault();
event.stopPropagation();
- document.cookie = `aid=${id}; path=/; max-age=86400`;
if (isArk) {
if ($arkkey && $arkaid === id) {
- await goto(url);
- } else {
+ await goto(withAid(url));
+ } else if (requireKey) {
const key = await tryUnlockArk();
if (key) {
- await goto(url);
+ await goto(withAid(url));
$arkkey = key;
$arkaid = id;
}
+ } else {
+ // Try silent unlock only, navigate regardless
+ const { getWalletEntropy } = await import("$lib/walletEntropy");
+ const entropy = await getWalletEntropy();
+ if (entropy) {
+ try {
+ const key = await deriveArkKey(entropy);
+ $arkkey = key;
+ $arkaid = id;
+ } catch {}
+ }
+ goto(withAid(url));
}
} else {
$arkkey = undefined;
- goto(url);
+ goto(withAid(url));
}
};
@@ -106,8 +120,7 @@
let goSettings = (e) => {
e.preventDefault();
e.stopPropagation();
- document.cookie = `aid=${id}; path=/; max-age=86400`;
- goto(`/account/${id}`);
+ goto(`/account/${id}?${aidParam}`);
};
</script>
@@ -145,7 +158,7 @@
<!-- <PhClockCounterClockwiseBold width="38" /> -->
<!-- </a> -->
<a
- href={`/account/${id}`}
+ href={`/account/${id}?${aidParam}`}
class="hover:opacity-80 relative w-12 h-12 flex items-center justify-center"
aria-label="Account settings"
onclick={goSettings}
@@ -165,14 +178,14 @@
</div>
<div class="flex w-full text-xl gap-2">
- <a href={receiveUrl} class="contents" data-sveltekit-preload-data="tap" onclick={(e) => setAccount(e, receiveUrl)}>
+ <a href={receiveUrl} class="contents" onclick={(e) => e.stopPropagation()}>
<button class="btn !w-auto flex-1" data-testid="account-receive">
<PhHandCoinsBold width="32" style="transform: scaleX(-1)" />
<div class="my-auto">{$t("user.dashboard.receive")}</div>
</button>
</a>
- <a href={`/send`} class="contents" data-sveltekit-preload-data="tap" onclick={(e) => setAccount(e, "/send")}>
+ <a href={withAid("/send")} class="contents" onclick={(e) => setAccount(e, "/send")}>
<button type="button" class="btn !w-auto flex-1">
<PhPaperPlaneRightBold width="32" />
<div class="my-auto">{$t("user.dashboard.send")}</div>
diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte
index a12c656e..0d70bf49 100644
--- a/src/components/AppHeader.svelte
+++ b/src/components/AppHeader.svelte
@@ -4,12 +4,12 @@
import { goto } from "$app/navigation";
import Avatar from "$comp/Avatar.svelte";
import Menu from "$comp/Menu.svelte";
- import { loading, t } from "$lib/translations";
+ import { t } from "$lib/translations";
import { page } from "$app/stores";
let { user, subject = $bindable() } = $props();
- let w: number | undefined = $state();
+ let w: number = $state(globalThis.innerWidth || 1024);
$effect(() => (subject = subject || user));
let bg = $derived(
@@ -69,7 +69,6 @@
<svelte:window bind:innerWidth={w} />
-{#if !$loading}
<header
class="bg-gradient-to-r bg-primary bg-accent h-[175px] w-full relative bg-cover mb-20 !z-30"
style:background-image={bg}
@@ -79,7 +78,7 @@
{#each links as { href, icon: Icon, label }}
<a {href} data-sveltekit-preload-data="tap" aria-label={label}>
<button class="flex justify-center items-center bg-base-100 p-2 rounded-full w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl {opacity(href)}" aria-label={label}>
- <Icon width={(w ?? 0) > 640 ? 32 : 24} />
+ <Icon width={w > 640 ? 32 : 24} />
</button>
</a>
{/each}
@@ -90,15 +89,14 @@
</a>
{/if}
</nav>
- {#if subject}
+ {#if subject || user}
<div
class="absolute md:w-[64px] md:mx-auto lg:left-[154px] xl:left-[194px] left-[calc(50vw-64px)] -bottom-[64px] z-30"
>
- <Avatar user={subject} />
+ <Avatar user={subject || user} />
</div>
{/if}
</header>
-{/if}
<style>
header {
diff --git a/src/components/InvoiceActions.svelte b/src/components/InvoiceActions.svelte
index 1c34d5ac..65d218fd 100644
--- a/src/components/InvoiceActions.svelte
+++ b/src/components/InvoiceActions.svelte
@@ -24,6 +24,9 @@
} = $props();
let { aid, account, address_type, type } = $derived(invoice);
+ let isOwn = $derived(user?.id === invoice?.user?.id);
+ let isCustodial = $derived(aid === user?.id);
+ let showTypeSwitch = $derived(isCustodial);
</script>
<div class="text-secondary space-y-2 text-xl pt-2">
@@ -50,7 +53,7 @@
</button>
<div class="flex flex-wrap gap-2 justify-around text-secondary w-full">
- {#if user?.id === invoice?.user?.id}
+ {#if isOwn}
<button class="btn grow !w-auto" onclick={toggleAmount}>
<PhPencilSimpleBold width="32" />
<div class="my-auto">{t("payments.setAmount")}</div>
@@ -70,43 +73,43 @@
</button>
</div>
-
-
- <div class="flex gap-2">
- <button
- class="btn flex-nowrap !w-auto grow"
- class:bg-base-300={type === types.lightning}
- class:text-secondary={type === types.lightning}
- onclick={() => {
- $amountPrompt = false;
- newAmount = undefined;
- setType(types.lightning);
- }}
- >
- <div class="bg-black rounded-full w-8 h-8 items-center justify-center flex">
- <div class="m-auto">
- <PhLightningFill width="24" class="text-yellow-300" />
- </div>
- </div>
- <div class="my-auto text-lg">Lightning</div>
- </button>
-
- {#if !account?.seed && !(account?.pubkey && account?.fingerprint) && account?.type !== "ark"}
+ {#if showTypeSwitch}
+ <div class="flex gap-2">
<button
class="btn flex-nowrap !w-auto grow"
- class:bg-base-300={type === types.bitcoin}
- class:text-secondary={type === types.bitcoin}
- onclick={() => setType(types.bitcoin)}
+ class:bg-base-300={type === types.lightning}
+ class:text-secondary={type === types.lightning}
+ onclick={() => {
+ $amountPrompt = false;
+ newAmount = undefined;
+ setType(types.lightning);
+ }}
>
- <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" />
- <div class="my-auto text-lg">Bitcoin</div>
+ <div class="bg-black rounded-full w-8 h-8 items-center justify-center flex">
+ <div class="m-auto">
+ <PhLightningFill width="24" class="text-yellow-300" />
+ </div>
+ </div>
+ <div class="my-auto text-lg">Lightning</div>
</button>
- {/if}
- </div>
- <button type="button" class="btn" onclick={toggleType}>
- <img src="/images/liquid.svg" class="w-8" alt="Liquid" />
- <img src="/images/cash.png" class="w-8 my-auto" alt="Ecash" />
- <div class="my-auto">{t("payments.moreOptions")}</div>
- </button>
+ {#if !account?.seed && !(account?.pubkey && account?.fingerprint) && account?.type !== "ark"}
+ <button
+ class="btn flex-nowrap !w-auto grow"
+ class:bg-base-300={type === types.bitcoin}
+ class:text-secondary={type === types.bitcoin}
+ onclick={() => setType(types.bitcoin)}
+ >
+ <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" />
+ <div class="my-auto text-lg">Bitcoin</div>
+ </button>
+ {/if}
+ </div>
+
+ <button type="button" class="btn" onclick={toggleType}>
+ <img src="/images/liquid.svg" class="w-8" alt="Liquid" />
+ <img src="/images/cash.png" class="w-8 my-auto" alt="Ecash" />
+ <div class="my-auto">{t("payments.moreOptions")}</div>
+ </button>
+ {/if}
</div>
diff --git a/src/components/InvoiceData.svelte b/src/components/InvoiceData.svelte
index ece30457..a613256b 100644
--- a/src/components/InvoiceData.svelte
+++ b/src/components/InvoiceData.svelte
@@ -17,6 +17,7 @@
tip,
rate,
align = "",
+ qrDataUrl,
t,
}: any = $props();
@@ -24,9 +25,17 @@
let { memo } = $derived(invoice);
let qrSrc = $derived(`/qr/${encodeURIComponent(invoice.text)}/raw`);
- let srcs: string[] = $state([]);
+ let srcs: string[] = $state(qrDataUrl ? [qrDataUrl] : []);
+ let lastDataUrl = qrDataUrl;
$effect(() => {
+ // Update if the server-provided data URL changed (e.g. new invoice)
+ if (qrDataUrl && qrDataUrl !== lastDataUrl) {
+ lastDataUrl = qrDataUrl;
+ srcs = [qrDataUrl];
+ return;
+ }
+ if (qrDataUrl && srcs.length) return;
const next = qrSrc;
if (!srcs.length) {
srcs = [next];
diff --git a/src/components/WalletPass.svelte b/src/components/WalletPass.svelte
deleted file mode 100644
index a3f00994..00000000
--- a/src/components/WalletPass.svelte
+++ /dev/null
@@ -1,100 +0,0 @@
-<script lang="ts">
- import PhEyeBold from "virtual:icons/ph/eye-bold";
- import PhEyeSlashBold from "virtual:icons/ph/eye-slash-bold";
- import { preventDefault } from "svelte/legacy";
-
- import { focus } from "$lib/utils";
- import { t } from "$lib/translations";
- import {
- rememberForOptions,
- defaultRememberForMs,
- rememberWalletPassword,
- forgetWalletPassword,
- } from "$lib/passwordCache";
- let { cancel = $bindable(), password = $bindable(), submit } = $props();
- let revealPassword = $state();
- let rememberForMs = $state(defaultRememberForMs);
-
- let handleSubmit = async () => {
- if (rememberForMs) {
- rememberWalletPassword(password, rememberForMs);
- } else {
- forgetWalletPassword();
- }
- await submit();
- };
-</script>
-
-<div class="fixed inset-0 bg-gray-600/50 overflow-y-auto h-full w-full z-20">
- <div class="relative top-1/3 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5">
- <h1 class="text-center text-2xl font-semibold">{$t("payments.enterWalletPass")}</h1>
- <form onsubmit={preventDefault(handleSubmit)}>
- <div class="relative mb-5">
- {#if revealPassword}
- <input
- name="password"
- data-testid="walletpass-input"
- type="text"
- required
- bind:value={password}
- autocapitalize="none"
- use:focus
- />
- {:else}
- <input
- name="password"
- data-testid="walletpass-input"
- type="password"
- required
- bind:value={password}
- autocapitalize="none"
- use:focus
- />
- {/if}
- <button
- type="button"
- onclick={() => (revealPassword = !revealPassword)}
- class="absolute right-5 top-1/2"
- aria-label={revealPassword ? "Hide password" : "Show password"}
- >
- {#if revealPassword}
- <PhEyeBold width="32" />
- {:else}
- <PhEyeSlashBold width="32" />
- {/if}
- </button>
- </div>
- <div class="mb-5 space-y-2">
- <label for="rememberFor" class="text-sm text-gray-500">Remember for</label>
- <select
- id="rememberFor"
- class="w-full"
- value={rememberForMs}
- onchange={(e) => (rememberForMs = Number((e.target as HTMLSelectElement).value))}
- >
- {#each rememberForOptions as option}
- <option value={option.ms}>{option.label}</option>
- {/each}
- </select>
- </div>
- <div class="w-full flex justify-center gap-2">
- <button
- type="button"
- data-testid="walletpass-cancel"
- class="border rounded-2xl font-semibold mx-auto py-5 px-6 hover:opacity-80 mx-auto grow"
- onclick={cancel}
- onkeydown={cancel}
- >
- <div class="my-auto">Cancel</div>
- </button>
- <button
- type="submit"
- data-testid="walletpass-submit"
- class="border rounded-2xl font-semibold mx-auto py-5 px-6 hover:opacity-80 mx-auto grow"
- >
- <div class="my-auto">Submit</div>
- </button>
- </div>
- </form>
- </div>
-</div>
diff --git a/src/hooks.server.ts b/src/hooks.server.ts
index fcb1d461..7acb2a43 100644
--- a/src/hooks.server.ts
+++ b/src/hooks.server.ts
@@ -1,5 +1,6 @@
import { ipStore } from "$lib/server/ip";
import { setIpGetter } from "$lib/utils";
+import { redirect } from "@sveltejs/kit";
import type { Handle } from "@sveltejs/kit";
setIpGetter(() => ipStore.getStore());
@@ -17,6 +18,26 @@ export const handle: Handle = async ({ event, resolve }) => {
}
}
+ // Sync aid cookie from URL params
+ try {
+ const aid = event.url.searchParams.get("aid");
+ if (aid) event.cookies.set("aid", aid, { path: "/", maxAge: 86400, httpOnly: false });
+ } catch {}
+
+
+ // Auth check for (app) routes
+ const isAppRoute = event.route.id?.startsWith("/(app)");
+ const token = event.cookies.get("token");
+ const { pathname } = event.url;
+ const isSweep = pathname.match(/^\/fund\/[^/]+\/sweep/);
+ const isLogout = pathname === "/logout";
+ if (isLogout) {
+ // Clear token before layout load to prevent infinite redirect loops
+ event.cookies.set("token", "", { expires: new Date(0), path: "/" });
+ } else if (isAppRoute && !token && !isSweep) {
+ redirect(307, "/login");
+ }
+
return ipStore.run(ip, async () => {
const response = await resolve(event);
const { pathname } = event.url;
diff --git a/src/lib/parse.ts b/src/lib/parse.ts
index 5e43a6ad..b5ff5f42 100644
--- a/src/lib/parse.ts
+++ b/src/lib/parse.ts
@@ -12,8 +12,9 @@ export default async (s, host, cookies) => {
let amount;
let invoice;
let user;
+ const aid = cookies.get("aid");
- if (t.startsWith("ark") || t.startsWith("tark")) redirect(307, `/send/ark/${t}`);
+ if (t.startsWith("ark") || t.startsWith("tark")) redirect(307, `/send/ark/${t}${aid ? `?aid=${aid}` : ""}`);
if (t.startsWith("http")) redirect(307, t);
if (t.startsWith(host)) redirect(307, `http://${t}`);
diff --git a/src/lib/passwordCache.ts b/src/lib/passwordCache.ts
index 0ed36af1..8ea4a9c9 100644
--- a/src/lib/passwordCache.ts
+++ b/src/lib/passwordCache.ts
@@ -1,69 +1,11 @@
import { browser } from "$app/environment";
-import { writable } from "svelte/store";
-
-const PASSWORD_KEY = "walletPassword";
-const EXPIRES_KEY = "walletPasswordExpiresAt";
-
-export const rememberForOptions = [
- { label: "user.settings.dontRemember", ms: 0 },
- { label: "user.settings.8hours", ms: 8 * 60 * 60 * 1000 },
- { label: "user.settings.1day", ms: 24 * 60 * 60 * 1000 },
- { label: "user.settings.1week", ms: 7 * 24 * 60 * 60 * 1000 },
- { label: "user.settings.1month", ms: 30 * 24 * 60 * 60 * 1000 },
- { label: "user.settings.1year", ms: 365 * 24 * 60 * 60 * 1000 },
-];
-
-export const defaultRememberForMs = 30 * 24 * 60 * 60 * 1000;
-
-const clearStoredPassword = () => {
- if (!browser) return;
- localStorage.removeItem(PASSWORD_KEY);
- localStorage.removeItem(EXPIRES_KEY);
-};
-
-const loadStoredPassword = () => {
- if (!browser) return undefined;
- const password = localStorage.getItem(PASSWORD_KEY) || undefined;
- if (!password) return undefined;
- const expiresAtRaw = localStorage.getItem(EXPIRES_KEY);
- const expiresAt = expiresAtRaw ? Number(expiresAtRaw) : undefined;
- if (expiresAt && Date.now() > expiresAt) {
- clearStoredPassword();
- return undefined;
- }
- return password;
-};
-
-export const walletPassword = writable<string | undefined>(loadStoredPassword());
-
-export const rememberWalletPassword = (password: string, durationMs: number) => {
- if (!browser) return;
- if (!durationMs) {
- clearStoredPassword();
- walletPassword.set(undefined);
- return;
- }
- const expiresAt = Date.now() + durationMs;
- localStorage.setItem(PASSWORD_KEY, password);
- localStorage.setItem(EXPIRES_KEY, String(expiresAt));
- walletPassword.set(password);
-};
-
-export const forgetWalletPassword = () => {
- clearStoredPassword();
- walletPassword.set(undefined);
-};
-
-export const getRememberedWalletPassword = () => {
- const password = loadStoredPassword();
- walletPassword.set(password);
- return password;
-};
// PRF key cache
const PRF_KEY = "prfKey";
const PRF_EXPIRES_KEY = "prfKeyExpiresAt";
+export const defaultRememberForMs = 30 * 24 * 60 * 60 * 1000;
+
function hexFromBuffer(buf: ArrayBuffer): string {
return Array.from(new Uint8Array(buf))
.map((b) => b.toString(16).padStart(2, "0"))
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 74e05f17..9f1eec3a 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -46,7 +46,8 @@ export const get = (url: string, headers = {}) => {
return fetch(base + url, { headers: { ...ipHeaders(), ...headers } })
.then(async (r) => {
if (r.ok) return r.text();
- throw new Error(await r.text());
+ const text = await r.text();
+ throw new Error(text.replace(/^"|"$/g, ""));
})
.then((body) => {
try {
@@ -84,7 +85,7 @@ export const post = async (
} catch {}
if (message) throw new Error(message);
- throw new Error(text);
+ throw new Error(text.replace(/^"|"$/g, ""));
})
.then((body) => {
let json;
diff --git a/src/locales/ar.json b/src/locales/ar.json
index de4af690..44c2a25e 100644
--- a/src/locales/ar.json
+++ b/src/locales/ar.json
@@ -462,12 +462,6 @@
"yourPassword": "وكلمة السر الخاصة بك",
"welcome": "مرحباً!",
"clickBelow": "انقروا من هنا لينهيوا حسابكم",
- "dontRemember": "لا تتذكر",
- "8hours": "8 ساعات",
- "1day": "يوم واحد",
- "1week": "أسبوع واحد",
- "1month": "شهر واحد",
- "1year": "سنة واحدة",
"preferredDM": "DM Preferred التأخيرات",
"preferredDMDescription": "رسائل مباشرة عنيت لك سيتم نشرها في هذه المؤخرات للحصول على الـ \"دي إم\" على عدة مسارات، وضع كل عملية نقل على خطها الخاص.",
"noDMURLsWarning": "أنتِ لم تُعدي أي مُعادل صالحة لن تكون قادراً على استخدام الـ دي إم.",
@@ -495,7 +489,6 @@
"cancel": "إلغاء",
"failedToRoute": "ولا يمكن الوصول إلى المتلقي إذا لم يتم الدفع على الطريق. حاول إرسال كمية أصغر أو زيادة احتياطي رسوم الحد الأقصى.",
"message": "الرسالة",
- "enterWalletPass": "كلمة السر",
"legacyVersion": "النسخة الأصلية",
"newVersion": "صيغة جديدة",
"income": "وردت",
diff --git a/src/locales/bn.json b/src/locales/bn.json
index 5623697d..34993590 100644
--- a/src/locales/bn.json
+++ b/src/locales/bn.json
@@ -432,12 +432,6 @@
"yourPassword": "আপনার পাসওয়ার্ড হল %s",
"welcome": "স্বাগতম!",
"clickBelow": "আপনার অ্যাকাউন্ট সম্পন্ন করার জন্য নীচের ক্লিক করুন",
- "dontRemember": "মনে নেই",
- "8hours": "৮ ঘন্টা",
- "1day": "১ দিন",
- "1week": "১ সপ্তাহ",
- "1month": "১ মাস",
- "1year": "১ বছর",
"preferredDM": "পছন্দসই DM ফিক্স",
"preferredDMDescription": "সরাসরি বার্তা পাঠানো হয়েছে তোমার কাছে এই রিলে প্রকাশিত হবে। একাধিক রিলে ডিএমস্‌ পেতে, প্রতিটা রিলে রিলে করে তার নিজের লাইনে.",
"noDMURLsWarning": "আপনি কোন বৈধ রিলেআর ঠিকানা নির্দিষ্ট করেননি । তুমি ডিএমএস ব্যবহার করতে পারবেনা.",
@@ -465,7 +459,6 @@
"cancel": "বাতিল",
"failedToRoute": "টাকা প্রদান করতে ব্যর্থ, সম্ভবত নিম্নলিখিত ঠিকানায় প্রেরিত হবে। সামান্য বড় অঙ্কের টাকা দিয়ে অথবা তার মূল্য বাড়ানোর চেষ্টা করুন।.",
"message": "বার্তা",
- "enterWalletPass": "ওয়ালেট পাসওয়ার্ড",
"legacyVersion": "লিগ্যাসি সংস্করণ",
"newVersion": "নতুন সংস্করণ",
"income": "গৃহীত",
diff --git a/src/locales/de.json b/src/locales/de.json
index 69f26469..502947a6 100644
--- a/src/locales/de.json
+++ b/src/locales/de.json
@@ -371,12 +371,6 @@
"yourPassword": "und ihr passwort ist",
"welcome": "Willkommen!",
"clickBelow": "Klicken Sie unten, um die Einrichtung Ihres Kontos abzuschließen",
- "dontRemember": "Erinnere dich nicht",
- "8hours": "8 stunden",
- "1day": "1 tag",
- "1week": "1 woche",
- "1month": "1 monat",
- "1year": "1 jahr",
"preferredDM": "Bevorzugte DM Verzögerungen",
"preferredDMDescription": "Direkte Nachrichten für Sie werden in diesen Relais veröffentlicht. Um DMs auf mehrere Relais zu bekommen, setzen Sie jede Relais-URL auf ihre eigene Linie.",
"noDMURLsWarning": "Sie haben keine gültigen Relais-URLs eingestellt. Sie können DMs nicht verwenden.",
@@ -511,7 +505,6 @@
"showQr": "QR anzeigen",
"copyPr": "Lohnanforderung kopieren",
"amountPrompt": "Immer nach dem Betrag fragen",
- "enterWalletPass": "Wallet-Passwort",
"failedToRoute": "Die Weiterleitung der Zahlung ist fehlgeschlagen. Der Empfänger ist möglicherweise nicht erreichbar. Versuchen Sie, einen kleineren Betrag zu senden oder die maximale Gebührenreserve zu erhöhen.",
"ok": "Ok",
"cancel": "Abbrechen",
diff --git a/src/locales/el.json b/src/locales/el.json
index 5ef9e06e..64ee46a9 100644
--- a/src/locales/el.json
+++ b/src/locales/el.json
@@ -341,12 +341,6 @@
"yourPassword": "και ο κωδικός σας είναι",
"welcome": "Καλώς ήρθατε!",
"clickBelow": "Κάντε κλικ παρακάτω για να ολοκληρώσετε τη ρύθμιση του λογαριασμού σας",
- "dontRemember": "Δεν θυμάμαι",
- "8hours": "8 ώρες",
- "1day": "1 ημέρα",
- "1week": "1 εβδομάδα",
- "1month": "1 μήνας",
- "1year": "1 έτος",
"preferredDM": "Προτιμώμενη DM Ηλεκτρονόμοι",
"preferredDMDescription": "Άμεση μηνύματα που προορίζονται για σας θα δημοσιευθεί σε αυτά τα ρελέ. Για να πάρετε DMs σε πολλαπλά ρελέ, βάλτε κάθε URL ρελέ στη δική του γραμμή.",
"noDMURLsWarning": "Δεν έχετε ορίσει κανένα έγκυρο URL ρελέ. Δεν θα μπορείτε να χρησιμοποιήσετε DMs.",
@@ -472,7 +466,6 @@
"showQr": "Εμφάνιση QR",
"copyPr": "Αντιγραφή αιτήματος πληρωμής",
"amountPrompt": "Πάντα προτροπή για ποσό",
- "enterWalletPass": "Κωδικός πρόσβασης πορτοφολιού",
"failedToRoute": "Η δρομολόγηση της πληρωμής απέτυχε, ο παραλήπτης ενδέχεται να μην είναι προσβάσιμος. Δοκιμάστε να στείλετε ένα μικρότερο ποσό ή να αυξήσετε το μέγιστο απόθεμα χρέωσης.",
"ok": "Εντάξει",
"cancel": "Ακύρωση",
diff --git a/src/locales/en.json b/src/locales/en.json
index 82e78a81..c6e89bfd 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -440,12 +440,6 @@
"memoPrompt": "Checkout instructions",
"memoPromptPlaceholder": "e.g. Please enter your name for the order",
"rememberFor": "Remember for",
- "dontRemember": "Don't remember",
- "8hours": "8 hours",
- "1day": "1 day",
- "1week": "1 week",
- "1month": "1 month",
- "1year": "1 year",
"import": "Import secret key",
"destination": "Auto withdrawal address",
"destinationDesc": "The address that withdrawals will be sent to",
@@ -576,7 +570,6 @@
"cancel": "Cancel",
"failedToRoute": "Failed to route payment, recipient may not be reachable. Try sending a smaller amount or increasing the max fee reserve.",
"message": "Message",
- "enterWalletPass": "Wallet password",
"legacyVersion": "Legacy version",
"newVersion": "New version",
"income": "Received",
diff --git a/src/locales/es.json b/src/locales/es.json
index bbe2fa4c..2372641e 100644
--- a/src/locales/es.json
+++ b/src/locales/es.json
@@ -359,12 +359,6 @@
"yourPassword": "y su contraseña es",
"welcome": "¡Bienvenido!",
"clickBelow": "Haga clic a continuación para terminar de configurar su cuenta",
- "dontRemember": "No lo recuerdo",
- "8hours": "8 horas",
- "1day": "1 día",
- "1week": "1 semana",
- "1month": "1 mes",
- "1year": "1 año",
"preferredDM": "Preferentes DM Relays",
"preferredDMDescription": "Mensajes directos destinados a usted serán publicados a estos relés. Para obtener DMs en múltiples relés, ponga cada URL de relé en su propia línea.",
"noDMURLsWarning": "No has establecido ninguna URL válida de relé. No podrás usar DMs.",
@@ -485,7 +479,6 @@
"showQr": "Mostrar código QR",
"copyPr": "Copiar solicitud de pago",
"amountPrompt": "Siempre solicite la cantidad",
- "enterWalletPass": "Contraseña de la billetera",
"failedToRoute": "No se pudo enviar el pago; es posible que no se pueda contactar al destinatario. Intente enviar un monto menor o aumentar la reserva de tarifa máxima.",
"ok": "Ok",
"cancel": "Cancelar",
diff --git a/src/locales/fa.json b/src/locales/fa.json
index ac64fe7e..577f2569 100644
--- a/src/locales/fa.json
+++ b/src/locales/fa.json
@@ -359,12 +359,6 @@
"yourPassword": "و رمز عبور شما",
"welcome": "خوش آمدید!",
"clickBelow": "در زیر کلیک کنید تا حساب خود را تنظیم کنید",
- "dontRemember": "یادت نره",
- "8hours": "8 ساعت",
- "1day": "1 روز",
- "1week": "1 هفته",
- "1month": "1 ماه",
- "1year": "1 سال",
"preferredDM": "DM ترجیحی رله ها",
"preferredDMDescription": "پیام های مستقیم برای شما به این رله ها منتشر خواهد شد. برای دریافت DM ها در چندین رله، هر URL رله را در خط خود قرار دهید.",
"noDMURLsWarning": "شما هیچ URL رله معتبر تنظیم نکرده اید. شما قادر به استفاده از DM ها نخواهید بود.",
@@ -484,7 +478,6 @@
"showQr": "نمایش QR",
"copyPr": "کپی درخواست پرداخت",
"amountPrompt": "همیشه مقدار را درخواست کنید",
- "enterWalletPass": "رمز کیف پول",
"failedToRoute": "مسیر پرداخت انجام نشد، ممکن است گیرنده قابل دسترسی نباشد. سعی کنید مبلغ کمتری ارسال کنید یا حداکثر ذخیره هزینه را افزایش دهید.",
"ok": "Ok",
"cancel": "لغو",
diff --git a/src/locales/fr.json b/src/locales/fr.json
index 934577bf..3266fc76 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -358,12 +358,6 @@
"yourPassword": "et votre mot de passe est",
"welcome": "Bienvenue !",
"clickBelow": "Cliquez ci-dessous pour terminer la configuration de votre compte",
- "dontRemember": "Je ne me souviens pas",
- "8hours": "8 heures",
- "1day": "1 jour",
- "1week": "1 semaine",
- "1month": "1 mois",
- "1year": "1 an",
"continueSettingUp": "Configuration du compte",
"bip353": "Paiement BIP 353 DNS",
"bip353Description": "Faites découvrir votre adresse sous le nom d'utilisateur@coinos. io donc tout portefeuille compatible BIP 353 peut vous payer"
@@ -510,7 +504,6 @@
"showQr": "Afficher le QR",
"copyPr": "Copie de la demande de paiement",
"amountPrompt": "Toujours demander le montant",
- "enterWalletPass": "Mot de passe du portefeuille",
"failedToRoute": "Impossible d'acheminer le paiement, le destinataire n'est peut-être pas joignable. Essayez d'envoyer un montant inférieur ou d'augmenter les frais de transaction.",
"ok": "ok",
"cancel": "Annuler",
diff --git a/src/locales/hi.json b/src/locales/hi.json
index 3e831bfe..c29cf0a6 100644
--- a/src/locales/hi.json
+++ b/src/locales/hi.json
@@ -462,12 +462,6 @@
"yourPassword": "आपका पासवर्ड है",
"welcome": "स्वागत!",
"clickBelow": "अपने खाते की स्थापना समाप्त करने के लिए नीचे क्लिक करें",
- "dontRemember": "याद मत करो",
- "8hours": "8 घंटे",
- "1day": "1 दिन",
- "1week": "1 सप्ताह",
- "1month": "1 महीना",
- "1year": "1 वर्ष",
"preferredDM": "पसंदीदा DM रिले",
"preferredDMDescription": "प्रत्यक्ष संदेश जिसका मतलब है कि आप इन रिले में प्रकाशित होंगे। एकाधिक रिले पर डीएम प्राप्त करने के लिए, प्रत्येक रिले यूआरएल को अपनी लाइन पर डाल दें।.",
"noDMURLsWarning": "आपने किसी भी वैध रिले यूआरएल को सेट नहीं किया है। आप डीएम का उपयोग करने में सक्षम नहीं होंगे।.",
@@ -495,7 +489,6 @@
"cancel": "रद्द करना",
"failedToRoute": "भुगतान करने में विफल, प्राप्तकर्ता पहुंच योग्य नहीं हो सकता है। एक छोटी राशि भेजने या अधिकतम शुल्क आरक्षित बढ़ाने की कोशिश करें।.",
"message": "संदेश",
- "enterWalletPass": "वॉलेट पासवर्ड",
"legacyVersion": "विरासत संस्करण",
"newVersion": "नया संस्करण",
"income": "प्राप्त करना",
diff --git a/src/locales/it.json b/src/locales/it.json
index 0e01b1c9..19d682a3 100644
--- a/src/locales/it.json
+++ b/src/locales/it.json
@@ -462,12 +462,6 @@
"yourPassword": "e la tua password è",
"welcome": "Benvenuto!",
"clickBelow": "Clicca qui sotto per completare l'impostazione del tuo account",
- "dontRemember": "Non ricordo",
- "8hours": "8 ore",
- "1day": "1 giorno",
- "1week": "1 settimana",
- "1month": "1 mese",
- "1year": "1 anno",
"preferredDM": "DM preferenziale Relè",
"preferredDMDescription": "Messaggi diretti significati per voi saranno pubblicati a questi relè. Per ottenere DM su relè multipli, mettere ogni URL relè sulla propria linea.",
"noDMURLsWarning": "Non hai impostato alcun URL di relè valido. Non sarà in grado di utilizzare DM.",
@@ -495,7 +489,6 @@
"cancel": "Annulla",
"failedToRoute": "Impossibile inoltrare il pagamento, il destinatario potrebbe non essere raggiungibile. Prova ad inviare un importo inferiore o aumentando la quota di commissione.",
"message": "Messaggio",
- "enterWalletPass": "Portafoglio password",
"legacyVersion": "Versione legacy",
"newVersion": "Nuova versione",
"income": "Ricevuto",
diff --git a/src/locales/ja.json b/src/locales/ja.json
index 7023a219..84e2eeaa 100644
--- a/src/locales/ja.json
+++ b/src/locales/ja.json
@@ -359,12 +359,6 @@
"yourPassword": "パスワードは",
"welcome": "お問い合わせ!",
"clickBelow": "アカウントの設定を完了するには、下記をクリックしてください",
- "dontRemember": "覚えていない",
- "8hours": "8時間",
- "1day": "1 日",
- "1week": "1週間",
- "1month": "1ヶ月",
- "1year": "1 年",
"preferredDM": "推奨DM リレー",
"preferredDMDescription": "これらのリレーに公開されるダイレクトメッセージ。 複数のリレーでDMを取得するには、各リレーURLを各行に置きます.",
"noDMURLsWarning": "有効なリレー URL を設定していません。 DMは使用できません.",
@@ -484,7 +478,6 @@
"showQr": "QRコードを表示",
"copyPr": "支払い請求のコピー",
"amountPrompt": "常に金額の入力を求める",
- "enterWalletPass": "ウォレットパスワード",
"failedToRoute": "支払いのルーティングに失敗しました。受取人に連絡が取れない可能性があります。送金金額を減らすか、最大手数料準備金を増やしてみてください。",
"ok": "お問い合わせ",
"cancel": "キャンセル",
diff --git a/src/locales/ko.json b/src/locales/ko.json
index 87feb0f9..9ae9892f 100644
--- a/src/locales/ko.json
+++ b/src/locales/ko.json
@@ -462,12 +462,6 @@
"yourPassword": "비밀번호",
"welcome": "제품 정보!",
"clickBelow": "계정 설정하기",
- "dontRemember": "기억하지 마십시오",
- "8hours": "8시간",
- "1day": "1 일",
- "1week": "1 주일",
- "1month": "1 개월",
- "1year": "1 년",
"preferredDM": "선호하는 DM 연락처",
"preferredDMDescription": "직접 메시지는이 릴레이에 게시됩니다. 여러 개의 릴레이에 DM을 얻으려면 각 릴레이 URL을 자체 라인에 넣어.",
"noDMURLsWarning": "유효한 릴레이 URL을 설정하지 않았습니다. DM을 사용할 수 없습니다.",
@@ -495,7 +489,6 @@
"cancel": "이름 *",
"failedToRoute": "결제에 실패, 수신자는 도달 할 수 없습니다. 더 작은 금액을 보내거나 최대 수수료 예약을 증가하십시오.",
"message": "이름 *",
- "enterWalletPass": "지갑 암호",
"legacyVersion": "Legacy 버전",
"newVersion": "새 버전",
"income": "이름 *",
diff --git a/src/locales/nl.json b/src/locales/nl.json
index d06834de..e1ca21f0 100644
--- a/src/locales/nl.json
+++ b/src/locales/nl.json
@@ -462,12 +462,6 @@
"yourPassword": "Uw wachtwoord is:",
"welcome": "Welkom!",
"clickBelow": "Klik hieronder om het aanmaken van uw account af te ronden",
- "dontRemember": "Ik weet het niet meer",
- "8hours": "8 uur",
- "1day": "1 dag",
- "1week": "1 week",
- "1month": "1 maand",
- "1year": "1 jaar",
"preferredDM": "Voorkeur DM Relais",
"preferredDMDescription": "Directe berichten bedoeld voor u zullen worden gepubliceerd naar deze relais. Om DM's te krijgen op meerdere relais, zet elke relais URL op zijn eigen lijn.",
"noDMURLsWarning": "U heeft geen geldige relais-URL's ingesteld. U kunt geen DM's gebruiken.",
@@ -495,7 +489,6 @@
"cancel": "Annuleren",
"failedToRoute": "Het is mogelijk dat de ontvanger niet bereikbaar is. Probeer het verzenden van een kleiner bedrag of controleer met de ontvanger.",
"message": "Notitie",
- "enterWalletPass": "Wachtwoord portemonnee",
"legacyVersion": "Legacy versie",
"newVersion": "Nieuwe versie",
"income": "Ontvangen",
diff --git a/src/locales/pl.json b/src/locales/pl.json
index 313c7df3..b0634514 100644
--- a/src/locales/pl.json
+++ b/src/locales/pl.json
@@ -462,12 +462,6 @@
"yourPassword": "twoje hasło jest",
"welcome": "Witamy!",
"clickBelow": "Kliknij poniżej, aby skonfigurować konto",
- "dontRemember": "Nie pamiętam",
- "8hours": "8 godzin",
- "1day": "1 dzień",
- "1week": "1 tydzień",
- "1month": "1 miesiąc",
- "1year": "1 rok",
"preferredDM": "Preferowany DM Przekaźniki",
"preferredDMDescription": "Bezpośrednie wiadomości przeznaczone dla Ciebie zostaną opublikowane do tych przekaźników. Aby uzyskać DM na wielu przekaźników, umieścić każdy przekaźnik URL na własnej linii.",
"noDMURLsWarning": "Nie ustawiłeś żadnych poprawnych adresów przekaźnikowych. Nie będziesz mógł używać DM.",
@@ -495,7 +489,6 @@
"cancel": "Anuluj",
"failedToRoute": "Nie udało się dokonać płatności za trasę, odbiorca może nie być dostępny. Spróbuj wysłać mniejszą kwotę lub zwiększenie maksymalnej rezerwy opłat.",
"message": "Wiadomość",
- "enterWalletPass": "Hasło portfela",
"legacyVersion": "Wersja w języku litewskim",
"newVersion": "Nowa wersja",
"income": "Otrzymane",
diff --git a/src/locales/pt.json b/src/locales/pt.json
index 745c0853..93a03632 100644
--- a/src/locales/pt.json
+++ b/src/locales/pt.json
@@ -378,12 +378,6 @@
"yourPassword": "e sua senha é",
"welcome": "Bem-vindos!",
"clickBelow": "Clique abaixo para terminar de configurar sua conta",
- "dontRemember": "Não me lembro",
- "8hours": "8 horas",
- "1day": "1 dia",
- "1week": "1 semana",
- "1month": "1 mês",
- "1year": "1 ano",
"preferredDM": "DM Preferido Relés",
"preferredDMDescription": "Mensagens diretas destinadas a você serão publicadas para estes relés. Para obter DMs em vários relés, coloque cada URL relé em sua própria linha.",
"noDMURLsWarning": "Você não definiu nenhum URL de retransmissão válido. Você não poderá usar DMs.",
@@ -506,7 +500,6 @@
"showQr": "Mostrar QR",
"copyPr": "Copiar solicitação de pagamento",
"amountPrompt": "Sempre solicite o valor",
- "enterWalletPass": "Senha da carteira",
"failedToRoute": "Falha ao encaminhar o pagamento, o destinatário pode não estar acessível. Tente enviar uma quantia menor ou aumentar a reserva de taxa máxima.",
"ok": "Ok",
"cancel": "Cancelar",
diff --git a/src/locales/ru.json b/src/locales/ru.json
index 508f31ee..a4dfdecb 100644
--- a/src/locales/ru.json
+++ b/src/locales/ru.json
@@ -359,12 +359,6 @@
"yourPassword": "ваш пароль является",
"welcome": "Добро пожаловать!",
"clickBelow": "Нажмите ниже, чтобы закончить настройку вашей учетной записи",
- "dontRemember": "Не помни",
- "8hours": "8 часов",
- "1day": "1 день",
- "1week": "1 неделя",
- "1month": "1 месяц",
- "1year": "1 год",
"preferredDM": "Предпочтительный DM реле",
"preferredDMDescription": "Прямые сообщения, предназначенные для вас, будут опубликованы в этих реле. Чтобы получить DM на нескольких реле, поместите каждый URL реле на свою собственную строку.",
"noDMURLsWarning": "Вы не установили никаких действительных URL-адресов. Вы не сможете использовать DM.",
@@ -484,7 +478,6 @@
"showQr": "Показать QR-код",
"copyPr": "Копировать запрос на оплату",
"amountPrompt": "Всегда запрашивать сумму",
- "enterWalletPass": "Пароль кошелька",
"failedToRoute": "Не удалось направить платеж, получатель может быть недоступен. Попробуйте отправить меньшую сумму или увеличить максимальный резерв комиссии.",
"ok": "Хорошо",
"cancel": "Отменить",
diff --git a/src/locales/th.json b/src/locales/th.json
index 0de7077f..db3c19ba 100644
--- a/src/locales/th.json
+++ b/src/locales/th.json
@@ -432,12 +432,6 @@
"yourPassword": "และรหัสผ่านของคุณ",
"welcome": "ยินดีต้อนรับ!",
"clickBelow": "คลิกด้านล่างนี้เพื่อทําบัญชีให้เสร็จ",
- "dontRemember": "จําไม่ได้",
- "8hours": "8 ชั่วโมง",
- "1day": "1 วัน",
- "1week": "1 สัปดาห์",
- "1month": "1 เดือน",
- "1year": "1 ปี",
"preferredDM": "ค่า DM ที่ต้องการ อําลา",
"preferredDMDescription": "ข้อความตรงมีความหมายสําหรับคุณ จะถูกเผยแพร่ไปยังเครื่องส่งเหล่านี้ เพื่อเอา DMs มาวางบนเส้นผ่านหลาย ๆ เส้น วางที่อยู่ URL แต่ละสายบนเส้นของตัวเอง.",
"noDMURLsWarning": "คุณยังไม่ได้ตั้งค่าที่อยู่ URL ที่ถูกต้อง คุณจะไม่สามารถใช้ DMs ได้.",
@@ -465,7 +459,6 @@
"cancel": "ยกเลิก",
"failedToRoute": "การจ่ายเงินตามเส้นทางล้มเหลว อาจไม่สามารถเข้าถึงผู้รับได้ ลอง ส่ง เงิน ที่ มี ค่า น้อย กว่า หรือ ทํา การ เพิ่ม อัตรา ค่า ธรรมเนียม ให้ มาก ที่ สุด.",
"message": "จดหมาย",
- "enterWalletPass": "รหัสผ่านของกระเป๋าคุมข้อมูล",
"legacyVersion": "รุ่นของมรดก",
"newVersion": "รุ่นใหม่",
"income": "ได้รับ",
diff --git a/src/locales/tr.json b/src/locales/tr.json
index b2c198b0..054ba0e6 100644
--- a/src/locales/tr.json
+++ b/src/locales/tr.json
@@ -462,12 +462,6 @@
"yourPassword": "ve şifreniziz",
"welcome": "Hoşgeldiniz!",
"clickBelow": "Hesabınızı kurmak için aşağıda tıklayın",
- "dontRemember": "Unutma",
- "8hours": "8 saat 8",
- "1day": "1 gün",
- "1week": "1 hafta",
- "1month": "1 ay",
- "1year": "1 yıl",
"preferredDM": "Tercih edilen DM Relays",
"preferredDMDescription": "Doğrudan Mesajlar bu rölelere yayınlanmanız anlamına geliyordu. DM'leri birden fazla röleğe almak için, her röle URL'yi kendi çizgisine koyun.",
"noDMURLsWarning": "Herhangi bir geçerli röle URL ayarlamadınız. DM'leri kullanabileceksiniz.",
@@ -495,7 +489,6 @@
"cancel": "Cancel",
"failedToRoute": "Yol ödemesine başarısız oldu, alıcı ulaşılamaz olabilir. Daha küçük bir miktar göndermeye veya maksimum ücret rezervini arttırmaya çalışın.",
"message": "Mesaj",
- "enterWalletPass": "Cüzdan şifresi",
"legacyVersion": "Legacy Version",
"newVersion": "Yeni sürüm",
"income": "Kabul edildi",
diff --git a/src/locales/zh.json b/src/locales/zh.json
index 6c9cd0af..4e37db9e 100644
--- a/src/locales/zh.json
+++ b/src/locales/zh.json
@@ -359,12 +359,6 @@
"yourPassword": "而您的密码是",
"welcome": "欢迎光临!",
"clickBelow": "点击下面完成您的账户设置",
- "dontRemember": "不记得了",
- "8hours": "8小时",
- "1day": "1天(以千美元计)",
- "1week": "1个星期",
- "1month": "1个月",
- "1year": "1 岁",
"preferredDM": "首选管理部 中继",
"preferredDMDescription": "给您的直接信件将被发布到这些转发中 。 为了让DMs在多个中继器上,将每个中继URL放入自己的线路.",
"noDMURLsWarning": "您尚未设置任何有效的中继 URL 。 你将无法使用DMs.",
@@ -484,7 +478,6 @@
"showQr": "显示二维码",
"copyPr": "复制付款请求",
"amountPrompt": "始终提示金额",
- "enterWalletPass": "钱包密码",
"failedToRoute": "付款路线失败,收款人可能无法联系到。请尝试发送较少的金额或增加最大费用储备。",
"ok": "确认",
"cancel": "取消",
diff --git a/src/routes/(app)/+layout.server.ts b/src/routes/(app)/+layout.server.ts
index f863e026..09d6f841 100644
--- a/src/routes/(app)/+layout.server.ts
+++ b/src/routes/(app)/+layout.server.ts
@@ -1,54 +1,45 @@
import { auth, get, isInvalidTokenError, sleep } from "$lib/utils";
-import { error, redirect } from "@sveltejs/kit";
+import { redirect } from "@sveltejs/kit";
-export async function load({ cookies, url, params }) {
- const { pathname } = url;
- const { id, username } = params;
- const token = cookies.get("token");
+export async function load({ cookies, depends }) {
+ depends("app:user");
- const isSweep = pathname.match(/^\/fund\/[^/]+\/sweep/);
- if (!token && !isSweep) redirect(307, "/login");
+ const token = cookies.get("token");
let user;
let hasArk = false;
if (token) {
- try {
- user = await get("/me", auth(cookies));
- } catch (e) {
+ const authHeaders = auth(cookies);
+
+ // Fetch user and accounts in parallel
+ const [userResult, accounts] = await Promise.all([
+ get("/me", authHeaders).catch((e) => ({ _error: e })),
+ get("/accounts", authHeaders).catch(() => []),
+ ]);
+
+ if (userResult?._error) {
+ const e = userResult._error;
const { message } = e as Error;
if (message.startsWith("Rate")) {
await sleep(3000);
try {
- user = await get("/me", auth(cookies));
+ user = await get("/me", authHeaders);
} catch (retryError) {
- if (isInvalidTokenError(retryError) && pathname !== "/logout") {
+ if (isInvalidTokenError(retryError)) {
redirect(307, "/logout");
}
throw retryError;
}
- } else if (isInvalidTokenError(e) && pathname !== "/logout") {
+ } else if (isInvalidTokenError(e)) {
redirect(307, "/logout");
} else {
throw e;
}
+ } else {
+ user = userResult;
}
- hasArk = (await get("/accounts", auth(cookies))).some((a: any) => a.type === "ark");
- }
-
- let subject;
- if (url.pathname.includes("/invoice") && id) {
- try {
- ({ user: subject } = await get(`/invoice/${id}`));
- } catch (e) {
- console.log("unable to fetch invoice", id, e);
- }
- } else if (username) {
- try {
- subject = await get(`/users/${username}`);
- } catch {
- error(500, "Unable to retrieve user account data");
- }
+ hasArk = accounts.some((a: any) => a.type === "ark");
}
if (user?.needsMigration) {
@@ -57,5 +48,5 @@ export async function load({ cookies, url, params }) {
const theme = cookies.get("theme") || "light";
- return { user, token, subject, theme, hasArk };
+ return { user, token, theme, hasArk };
}
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index 991171be..5a9397b5 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -29,14 +29,14 @@
import Nostr from "$comp/Nostr.svelte";
import Password from "$comp/Password.svelte";
import { s, f, toFiat, success, getCookie, warning, post } from "$lib/utils";
- import { t, locale, loading } from "$lib/translations";
+ import { t, locale } from "$lib/translations";
import { goto, invalidate, afterNavigate } from "$app/navigation";
let { data, children } = $props();
let user = $derived(data.user);
- let subject = $derived(data.subject);
+ let subject = $derived(($page.data as any).subject);
let token = $derived(data.token);
let theme = $derived(data.theme);
@@ -332,7 +332,5 @@
<main class="pb-4 min-h-dvh" data-theme={theme}>
<OfflineBanner />
<AppHeader {user} {subject} />
- {#if !$loading}
- {@render children?.()}
- {/if}
+ {@render children?.()}
</main>
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index 123a14e8..9979d77c 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -124,7 +124,7 @@
</a>
{/if}
{#if user?.id && user.id === subject.id}
- <div class="space-y-4" data-sveltekit-preload-data="false">
+ <div class="space-y-4">
{#each accounts as account}
<Account {user} {rates} {account} />
{/each}
diff --git a/src/routes/(app)/account/new/+page.svelte b/src/routes/(app)/account/new/+page.svelte
index 4365dedd..310660a5 100644
--- a/src/routes/(app)/account/new/+page.svelte
+++ b/src/routes/(app)/account/new/+page.svelte
@@ -3,9 +3,10 @@
import { t } from "$lib/translations";
import Spinner from "$comp/Spinner.svelte";
import { bytesToHex } from "@noble/hashes/utils.js";
- import { fail, post, versions } from "$lib/utils";
+ import { fail, post, versions, wait } from "$lib/utils";
import { goto } from "$app/navigation";
import { arkServerUrl } from "$lib/ark";
+ import { password as pw, passwordPrompt } from "$lib/store";
import { HDKey } from "@scure/bip32";
import { entropyToMnemonic, mnemonicToSeed } from "@scure/bip39";
import { wordlist } from "@scure/bip39/wordlists/english.js";
@@ -19,11 +20,16 @@
creatingArk = true;
try {
const { getWalletEntropy } = await import("$lib/walletEntropy");
- const entropy = await getWalletEntropy();
+ let entropy = await getWalletEntropy();
+
if (!entropy) {
- fail($t("accounts.failedToDecryptSeed") as string);
- creatingArk = false;
- return;
+ if (!$pw) $passwordPrompt = true;
+ await wait(() => !!$pw);
+ const { deriveAuthKeypair, deriveWalletEntropy } = await import("$lib/deriveAuthKey");
+ const { sk } = await deriveAuthKeypair(user.username, $pw!);
+ entropy = await deriveWalletEntropy(sk);
+ const { rememberPrfKey, defaultRememberForMs } = await import("$lib/passwordCache");
+ rememberPrfKey(entropy, defaultRememberForMs);
}
const { SingleKey, Wallet } = await import("@arkade-os/sdk");
diff --git a/src/routes/(app)/invoice/+page.server.ts b/src/routes/(app)/invoice/+page.server.ts
index 9cf2088a..db54ff8b 100644
--- a/src/routes/(app)/invoice/+page.server.ts
+++ b/src/routes/(app)/invoice/+page.server.ts
@@ -5,7 +5,7 @@ import { error, redirect } from "@sveltejs/kit";
export const load = async ({ cookies, parent, url }) => {
const aid = url.searchParams.get("aid") || cookies.get("aid");
- if (aid) cookies.set("aid", aid, { path: "/", maxAge: 86400 });
+ if (aid) cookies.set("aid", aid, { path: "/", maxAge: 86400, httpOnly: false });
let { subject, user } = await parent();
const lnurl = url.searchParams.has("lnurl");
diff --git a/src/routes/(app)/invoice/[id]/+layout.server.ts b/src/routes/(app)/invoice/[id]/+layout.ts
index 24b7f1b0..411486c7 100644
--- a/src/routes/(app)/invoice/[id]/+layout.server.ts
+++ b/src/routes/(app)/invoice/[id]/+layout.ts
@@ -1,24 +1,23 @@
-import getRates from "$lib/rates";
import { get } from "$lib/utils";
import { redirect } from "@sveltejs/kit";
-export async function load({ cookies, depends, params, url, parent }) {
+export async function load({ params, url, parent, depends }) {
depends("app:invoice");
- const token = cookies.get("token");
- let { subject, user } = await parent();
+ const { user, token, theme } = await parent();
const { id } = params;
let invoice;
+ let subject;
if (id === "lnurl") {
- const rates = await getRates();
+ subject = user;
invoice = {
aid: user.id,
uid: user.id,
amount: 0,
tip: 0,
- rate: rates[user?.currency || "USD"],
- text: "",
+ rate: 0,
+ text: `${user.username}@${url.host}`,
hash: "",
type: "lightning",
items: [],
@@ -42,7 +41,6 @@ export async function load({ cookies, depends, params, url, parent }) {
subject = invoice.user;
- const aid = cookies.get("aid") || user?.id;
if (
user &&
invoice.uid !== user?.id &&
@@ -56,6 +54,13 @@ export async function load({ cookies, depends, params, url, parent }) {
}
}
- const theme = cookies.get("theme") || "light";
- return { id, invoice, subject, user, token, theme };
+ let qrDataUrl: string | undefined;
+ if (invoice?.text) {
+ try {
+ const QRCode = await import("qrcode");
+ qrDataUrl = await QRCode.toDataURL(invoice.text, { width: 600, margin: 1 });
+ } catch {}
+ }
+
+ return { id, invoice, subject, user, token, theme, qrDataUrl };
}
diff --git a/src/routes/(app)/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte
index 988ba647..7ce7c4e8 100644
--- a/src/routes/(app)/invoice/[id]/+page.svelte
+++ b/src/routes/(app)/invoice/[id]/+page.svelte
@@ -32,7 +32,7 @@
let user = $derived(data.user);
let hasArk = $derived(data.hasArk);
let invoice = $derived(data.invoice);
- let src = $derived(data.src);
+ let qrDataUrl = $derived(data.qrDataUrl);
let { aid, amount, rate, tip, hash, text, type } = $derived(invoice);
let memo = $state();
let { username, currency } = $derived(invoice.user);
@@ -185,7 +185,7 @@
<div class="invoice container mx-auto max-w-xl px-4 pb-4 space-y-2">
<InvoiceData
- {src}
+ {qrDataUrl}
{link}
{txt}
invoice={displayInvoice}
diff --git a/src/routes/(app)/invoice/[id]/tip/+page.server.ts b/src/routes/(app)/invoice/[id]/tip/+page.server.ts
index 91ccebca..84b218f3 100644
--- a/src/routes/(app)/invoice/[id]/tip/+page.server.ts
+++ b/src/routes/(app)/invoice/[id]/tip/+page.server.ts
@@ -4,8 +4,9 @@ import { fail, redirect } from "@sveltejs/kit";
export async function load({ cookies, params, parent }) {
const { id } = params;
- const rates = await getRates();
- let { invoice, subject, user } = await parent();
+ const [rates, { user }] = await Promise.all([getRates(), parent()]);
+ let invoice = await get(`/invoice/${id}`, auth(cookies));
+ const subject = invoice.user;
if (!invoice.amount) redirect(307, `/invoice/${invoice.id}`);
const pin = cookies.get("pin");
diff --git a/src/routes/(app)/send/ark/[address]/+layout.server.ts b/src/routes/(app)/send/ark/[address]/+layout.server.ts
index 6381463a..18fb7aed 100644
--- a/src/routes/(app)/send/ark/[address]/+layout.server.ts
+++ b/src/routes/(app)/send/ark/[address]/+layout.server.ts
@@ -1,11 +1,18 @@
import { auth, get } from "$lib/utils";
import { redirect } from "@sveltejs/kit";
-export async function load({ cookies, params, parent }) {
+export async function load({ cookies, params, parent, url }) {
const { user } = await parent();
- const aid = cookies.get("aid") || user.id;
+ let aid = url.searchParams.get("aid") || cookies.get("aid") || user.id;
const { address, amount } = params;
- const { balance } = await get(`/account/${aid}`, auth(cookies));
+ let account = await get(`/account/${aid}`, auth(cookies));
+
+ // Fall back to custodial if cookie points to ark account
+ if (account.type === "ark" && aid !== user.id) {
+ aid = user.id;
+ account = await get(`/account/${aid}`, auth(cookies));
+ cookies.set("aid", aid, { path: "/", maxAge: 86400, httpOnly: false });
+ }
let invoice;
try {
@@ -21,5 +28,5 @@ export async function load({ cookies, params, parent }) {
if (recipient?.id !== user.id && recipient.id === invoice.aid) redirect(307, r);
}
- return { balance };
+ return { balance: account.balance };
}
diff --git a/src/routes/(app)/send/ark/[address]/+page.server.ts b/src/routes/(app)/send/ark/[address]/+page.server.ts
index fdf129a0..83a5050f 100644
--- a/src/routes/(app)/send/ark/[address]/+page.server.ts
+++ b/src/routes/(app)/send/ark/[address]/+page.server.ts
@@ -1,10 +1,18 @@
import getRates from "$lib/rates";
import { auth, get } from "$lib/utils";
-export async function load({ cookies, parent }) {
+export async function load({ cookies, parent, url }) {
const { user } = await parent();
const rates = await getRates();
- const aid = cookies.get("aid") || user.id;
- const { balance } = await get(`/account/${aid}`, auth(cookies));
- return { balance, rate: rates[user.currency] };
+ let aid = url.searchParams.get("aid") || cookies.get("aid") || user.id;
+ let account = await get(`/account/${aid}`, auth(cookies));
+
+ // Fall back to custodial if cookie points to ark account
+ if (account.type === "ark" && aid !== user.id) {
+ aid = user.id;
+ account = await get(`/account/${aid}`, auth(cookies));
+ cookies.set("aid", aid, { path: "/", maxAge: 86400, httpOnly: false });
+ }
+
+ return { balance: account.balance, rate: rates[user.currency] };
}
diff --git a/src/routes/(app)/send/ark/[address]/+page.svelte b/src/routes/(app)/send/ark/[address]/+page.svelte
index 631f6689..251bca72 100644
--- a/src/routes/(app)/send/ark/[address]/+page.svelte
+++ b/src/routes/(app)/send/ark/[address]/+page.svelte
@@ -4,7 +4,7 @@
import Numpad from "$comp/Numpad.svelte";
import { page } from "$app/stores";
import { fiat as fiatStore, rate } from "$lib/store";
- import { loc, fail, s, f, sats } from "$lib/utils";
+ import { loc, s, f, sats } from "$lib/utils";
let { data } = $props();
let balance = $derived(data.balance);
@@ -47,7 +47,7 @@
{/if}
</button>
- <form action={`/send/ark/${address}/${amount}`} class="contents">
+ <form action={`/send/ark/${address}/${amount}${$page.url.search}`} class="contents">
<button bind:this={submit} type="submit" class="btn !w-auto grow btn-accent">
{$t("payments.next")}
</button>
diff --git a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts
index 9fa754d6..ee0775d3 100644
--- a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts
+++ b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts
@@ -2,12 +2,20 @@ import getRates from "$lib/rates";
import { auth, fd, get, post } from "$lib/utils";
import { fail, redirect } from "@sveltejs/kit";
-export async function load({ params: { address, amount }, cookies, parent }) {
+export async function load({ params: { address, amount }, cookies, parent, url }) {
const rates = await getRates();
const { user } = await parent();
- const aid = cookies.get("aid") || user.id;
+ let aid = url.searchParams.get("aid") || cookies.get("aid") || user.id;
- const account = await get(`/account/${aid}`, auth(cookies));
+ let account = await get(`/account/${aid}`, auth(cookies));
+
+ // If cookie points to an ark account but user is sending TO an ark address,
+ // fall back to custodial account — ark-to-ark sends use client-side wallet
+ if (account.type === "ark" && aid !== user.id) {
+ aid = user.id;
+ account = await get(`/account/${aid}`, auth(cookies));
+ cookies.set("aid", aid, { path: "/", maxAge: 86400, httpOnly: false });
+ }
if (account.type === "bitcoin") {
const inv = await post(
diff --git a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte
index ced8dfce..9936cabc 100644
--- a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte
+++ b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte
@@ -7,7 +7,7 @@
import { goto, invalidate } from "$app/navigation";
import { rate } from "$lib/store";
import { applyAction } from "$app/forms";
- import { sendArk, sendArkViaForward } from "$lib/ark";
+ import { sendArk, sendArkViaForward, arkkey } from "$lib/ark";
import Amount from "$comp/Amount.svelte";
@@ -16,7 +16,7 @@
let error = $state("");
let handler = ({ cancel }) => {
- if (account.type === "ark") {
+ if (account.type === "ark" && $arkkey) {
cancel();
(async () => {
diff --git a/src/routes/(static)/login/+page.svelte b/src/routes/(static)/login/+page.svelte
index 0e920d5c..8cb4cf45 100644
--- a/src/routes/(static)/login/+page.svelte
+++ b/src/routes/(static)/login/+page.svelte
@@ -217,6 +217,19 @@
console.log("Auth key derivation failed, falling back to legacy login", e);
return async ({ result }: any) => {
if (result.type === "success") {
+ try {
+ const { deriveAuthKeypair, deriveWalletEntropy } = await import("$lib/deriveAuthKey");
+ const { sk } = await deriveAuthKeypair(uname, pw);
+ const oldPrfKey = await deriveWalletEntropy(sk);
+ await resolveCanonicalKey(
+ passwordMethodId(),
+ sk.buffer as ArrayBuffer,
+ result.data?.encryptedKeys,
+ oldPrfKey,
+ );
+ } catch (e2) {
+ console.log("PRF key caching in legacy fallback failed", e2);
+ }
await invalidateAll();
goto(result.data?.redirectUrl || "/");
} else {
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts
index 61c46ea6..c14c6f47 100644
--- a/src/routes/+layout.server.ts
+++ b/src/routes/+layout.server.ts
@@ -1,8 +1,6 @@
import { defaultLocale, loadTranslations, locales } from "$lib/translations/index";
-export const load = async ({ cookies, request, url }) => {
- const { pathname } = url;
-
+export const load = async ({ cookies, request }) => {
// Try to get the locale from cookie
let locale = (cookies.get("lang") || "").toLowerCase();
@@ -21,13 +19,12 @@ export const load = async ({ cookies, request, url }) => {
locale = defaultLocale;
}
- await loadTranslations(locale, pathname); // keep this just before the `return`
+ await loadTranslations(locale, "/");
const theme = cookies.get("theme") || "light";
return {
theme,
- pathname,
- i18n: { locale, route: pathname },
+ i18n: { locale, route: "/" },
};
};
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 9047c382..3c44f905 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -3,14 +3,15 @@
import { browser } from "$app/environment";
import { PUBLIC_DOMAIN } from "$env/static/public";
import "../app.css";
- import { loading, t } from "$lib/translations";
+ import { t } from "$lib/translations";
import { onMount } from "svelte";
import { installPrompt, theme as themeStore } from "$lib/store";
+ import { page } from "$app/stores";
const map = { light: "lofi", dark: "black", system: "system" };
let { data, children } = $props<any>();
- let pathname = $derived(data.pathname);
+ let pathname = $derived($page.url.pathname);
let theme = $derived(data.theme);
$effect(() => {
$themeStore = theme;
@@ -94,11 +95,9 @@
<meta name="twitter:creator" content="@coinoswallet" />
</svelte:head>
-{#if !$loading}
- <main>
- {@render children?.()}
- </main>
-{/if}
+<main>
+ {@render children?.()}
+</main>
<style>
:global(:root) {