aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-16 21:18:37 -0800
committerAdam Soltys <[email protected]>2026-02-16 21:18:37 -0800
commit314c86901e8aadf731d0ab235ef371248670e940 (patch)
treef305cfdf0efb951012cc988322a701e58b01d1a2 /src
parentb22c813f9fb3b4d69dade75d22e90e6779919f07 (diff)
downloadcoinos-ui-314c86901e8aadf731d0ab235ef371248670e940.tar.gz
coinos-ui-314c86901e8aadf731d0ab235ef371248670e940.zip
account creation flow
Diffstat (limited to 'src')
-rw-r--r--src/components/Account.svelte18
-rw-r--r--src/routes/(app)/+layout.svelte52
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte59
-rw-r--r--src/routes/(app)/account/new/+page.server.ts8
-rw-r--r--src/routes/(app)/account/seed/+page.svelte4
5 files changed, 78 insertions, 63 deletions
diff --git a/src/components/Account.svelte b/src/components/Account.svelte
index 70bded13..88b08978 100644
--- a/src/components/Account.svelte
+++ b/src/components/Account.svelte
@@ -194,28 +194,36 @@
</div>
{/if}
</div>
- <div class="flex items-start gap-4 shrink-0 text-black">
+ <div class="flex items-center gap-1 shrink-0 text-black">
<a
href="/payments"
- class="hover:text-gray-800"
+ class="hover:text-gray-800 w-10 h-10 flex items-center justify-center"
aria-label="Payment history"
onclick={(e) => setAccount(e, "/payments")}
>
- <iconify-icon noobserver icon="ph:clock-counter-clockwise-bold" width="32"></iconify-icon>
+ <iconify-icon noobserver icon="ph:clock-counter-clockwise-bold" width="38"></iconify-icon>
</a>
<a
href={`/account/${id}`}
- class="hover:opacity-80"
+ class="hover:opacity-80 relative w-10 h-10 flex items-center justify-center"
aria-label="Account settings"
onclick={goSettings}
>
{#if isArk}
<img src="/images/ark.png" class="w-8 h-8 rounded-full object-cover" alt="Ark" />
{:else if seed || fingerprint}
- <iconify-icon noobserver icon="cryptocurrency-color:btc" width="32"></iconify-icon>
+ <div class="w-8 h-8 flex items-center justify-center">
+ <iconify-icon noobserver icon="cryptocurrency-color:btc" width="32"></iconify-icon>
+ </div>
{:else}
<img src="/images/icon.png" class="w-8 h-8" alt="Coinos" />
{/if}
+ <iconify-icon
+ noobserver
+ icon="ph:gear-bold"
+ width="20"
+ class="absolute -bottom-1 -right-1 text-black bg-base-100 rounded-full"
+ ></iconify-icon>
</a>
</div>
</div>
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index 0fde24fa..8cd580e2 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- import { getWallet, syncTransactions, settle, refresh, arkkey, arkaid, sendArk, arkSending, arkServerUrl } from "$lib/ark";
+ import { getWallet, syncTransactions, settle, refresh, arkkey, arkaid, sendArk, arkSending } from "$lib/ark";
import { SvelteToast } from "@zerodevx/svelte-toast";
import { onDestroy, onMount } from "svelte";
import { close, connect, send, socket } from "$lib/socket";
@@ -22,10 +22,10 @@
import AppHeader from "$comp/AppHeader.svelte";
import Nostr from "$comp/Nostr.svelte";
import Password from "$comp/Password.svelte";
- import { s, f, toFiat, success, getCookie, warning, post, versions } from "$lib/utils";
+ import { s, f, toFiat, success, getCookie, warning, post } from "$lib/utils";
import { t, locale, loading } from "$lib/translations";
import { goto, invalidate, afterNavigate, preloadData } from "$app/navigation";
- import { getCachedPrfKey } from "$lib/passwordCache";
+
let { data, children } = $props();
@@ -127,51 +127,6 @@
if (browser && user && $arkkey) initArk();
});
- let autoProvisionAccounts = async () => {
- if (!user) return;
- if (sessionStorage.getItem("accounts:provisioned")) return;
- let prfKey = getCachedPrfKey();
- if (!prfKey) {
- const { getWalletEntropy } = await import("$lib/walletEntropy");
- prfKey = await getWalletEntropy();
- }
- if (!prfKey) return;
-
- sessionStorage.setItem("accounts:provisioned", "1");
-
- try {
- const res = await fetch("/account", { headers: { accept: "application/json" } });
- const accounts = await res.json();
-
- const hasArk = accounts.some((a: any) => a.type === "ark");
- if (hasArk) return;
-
- const [{ HDKey }, { entropyToMnemonic, mnemonicToSeed }, { wordlist }] = await Promise.all([
- import("@scure/bip32"),
- import("@scure/bip39"),
- import("@scure/bip39/wordlists/english.js"),
- ]);
-
- const entropy = new Uint8Array(prfKey);
- const mnemonic = entropyToMnemonic(entropy, wordlist);
- const seed = await mnemonicToSeed(mnemonic);
- const master = HDKey.fromMasterSeed(seed, versions);
-
- const { SingleKey, Wallet } = await import("@arkade-os/sdk");
- const { bytesToHex } = await import("@noble/hashes/utils.js");
- const arkChild = master.derive("m/86'/0'/0'/0/0");
- const arkHex = bytesToHex(arkChild.privateKey!);
- const identity = SingleKey.fromHex(arkHex);
- const wallet = await Wallet.create({ identity, arkServerUrl });
- const arkAddress = await wallet.getAddress();
- await post("/account", { name: $t("accounts.savings"), type: "ark", arkAddress });
-
- invalidate("app:payments");
- } catch (e) {
- console.error("Auto-provision accounts failed:", e);
- sessionStorage.removeItem("accounts:provisioned");
- }
- };
onMount(async () => {
if (browser) {
@@ -186,7 +141,6 @@
localStorage.removeItem("arkkey:uid");
}
- autoProvisionAccounts();
}
// if (window.NDEFReader) {
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index b9af8756..b37c2009 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -3,10 +3,12 @@
import { onMount } from "svelte";
import Account from "$comp/Account.svelte";
import Balance from "$comp/Balance.svelte";
+ import Spinner from "$comp/Spinner.svelte";
import { t } from "$lib/translations";
import { installPrompt, password } from "$lib/store";
- import { afterNavigate, preloadData } from "$app/navigation";
+ import { afterNavigate, goto, invalidate, preloadData } from "$app/navigation";
import { page } from "$app/stores";
+ import { versions, post, fail } from "$lib/utils";
let { data } = $props();
@@ -17,6 +19,51 @@
let { accounts, subject, rates, user } = $derived(data as any);
let { locked } = $derived(user);
+ let hasArk = $derived(accounts.some((a: any) => a.type === "ark"));
+ let adding = $state(false);
+
+ let addAccount = async (e) => {
+ e.preventDefault();
+ if (!hasArk) {
+ goto("/account/new");
+ return;
+ }
+
+ adding = true;
+ try {
+ const { getWalletEntropy } = await import("$lib/walletEntropy");
+ const prfKey = await getWalletEntropy();
+ if (!prfKey) {
+ goto("/account/seed");
+ return;
+ }
+
+ const [{ HDKey }, { entropyToMnemonic, mnemonicToSeed }, { wordlist }] = await Promise.all([
+ import("@scure/bip32"),
+ import("@scure/bip39"),
+ import("@scure/bip39/wordlists/english.js"),
+ ]);
+
+ const entropy = new Uint8Array(prfKey);
+ const mn = entropyToMnemonic(entropy, wordlist);
+ const seed = await mnemonicToSeed(mn);
+ const master = HDKey.fromMasterSeed(seed, versions);
+ const child = master.derive("m/84'/0'/0'");
+ const pubkey = child.publicExtendedKey;
+ const fingerprint = child.fingerprint.toString(16).padStart(8, "0");
+ await post("/account", {
+ fingerprint,
+ pubkey,
+ name: $t("accounts.vault"),
+ type: "bitcoin",
+ accountIndex: 0,
+ });
+ invalidate("app:payments");
+ } catch (e: any) {
+ fail(e.message);
+ }
+ adding = false;
+ };
let install = async () => {
if (!$installPrompt) return;
@@ -64,12 +111,14 @@
{#each accounts as account}
<Account {user} {rates} {account} />
{/each}
- <a href="/account/seed" class="contents">
- <button class="btn btn-accent w-full">
+ <button class="btn btn-accent w-full" onclick={addAccount}>
+ {#if adding}
+ <Spinner />
+ {:else}
<iconify-icon noobserver icon="ph:plus-bold" width="24"></iconify-icon>
{$t("accounts.addAccount")}
- </button>
- </a>
+ {/if}
+ </button>
</div>
{#if locked}
diff --git a/src/routes/(app)/account/new/+page.server.ts b/src/routes/(app)/account/new/+page.server.ts
index d4a0d0cb..7615c6f6 100644
--- a/src/routes/(app)/account/new/+page.server.ts
+++ b/src/routes/(app)/account/new/+page.server.ts
@@ -1,5 +1,9 @@
+import { auth, get } from "$lib/utils";
import { redirect } from "@sveltejs/kit";
-export async function load() {
- redirect(307, "/account/seed");
+export async function load({ cookies }) {
+ const accounts = await get("/accounts", auth(cookies));
+ const hasArk = accounts.some((a: any) => a.type === "ark");
+ if (hasArk) redirect(307, "/account/seed");
+ return { hasArk };
}
diff --git a/src/routes/(app)/account/seed/+page.svelte b/src/routes/(app)/account/seed/+page.svelte
index 43b690b3..335d92f2 100644
--- a/src/routes/(app)/account/seed/+page.svelte
+++ b/src/routes/(app)/account/seed/+page.svelte
@@ -14,14 +14,13 @@
let { data } = $props();
let user = $derived(data.user);
- let creating = $state(false);
+ let creating = $state(true);
onMount(async () => {
if (!browser) return;
const { getWalletEntropy } = await import("$lib/walletEntropy");
const prfKey = await getWalletEntropy();
if (prfKey) {
- creating = true;
try {
const entropy = new Uint8Array(prfKey);
const mn = entropyToMnemonic(entropy, wordlist);
@@ -45,6 +44,7 @@
}
return;
}
+ creating = false;
if (!$mnemonic) await generate();
});