aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-26 22:56:07 -0800
committerAdam Soltys <[email protected]>2026-02-26 22:56:07 -0800
commit73afb376b9e4984351d7352e8728208f90dcffc9 (patch)
treed875e85fd0b62e01a4d514f0ff18be3cbf7c107d /src
parent211511570b447ad0256a992bf8e3b5dfa8a82fb7 (diff)
downloadcoinos-ui-73afb376b9e4984351d7352e8728208f90dcffc9.tar.gz
coinos-ui-73afb376b9e4984351d7352e8728208f90dcffc9.zip
auto create ark account
Diffstat (limited to 'src')
-rw-r--r--src/components/Menu.svelte2
-rw-r--r--src/routes/(app)/account/ark/+page.svelte100
-rw-r--r--src/routes/(app)/account/new/+page.svelte52
3 files changed, 83 insertions, 71 deletions
diff --git a/src/components/Menu.svelte b/src/components/Menu.svelte
index 8bca1daf..f1584cde 100644
--- a/src/components/Menu.svelte
+++ b/src/components/Menu.svelte
@@ -16,7 +16,7 @@
// icon: "ph:storefront-bold",
// href: `https://coinosmerch.com`,
// },
- { key: "nav.dm", icon: "ph:chat-bold", href: `/dm` },
+ // { key: "nav.dm", icon: "ph:chat-bold", href: `/dm` },
{ key: "nav.dark", icon: "ph:moon-stars-bold", href: `/dark` },
{ key: "nav.signOut", icon: "ph:sign-out-bold", href: `/logout` },
];
diff --git a/src/routes/(app)/account/ark/+page.svelte b/src/routes/(app)/account/ark/+page.svelte
index 398cfc71..c80bb63d 100644
--- a/src/routes/(app)/account/ark/+page.svelte
+++ b/src/routes/(app)/account/ark/+page.svelte
@@ -1,6 +1,5 @@
<script lang="ts">
- import { preventDefault } from "svelte/legacy";
- import { tick, onMount } from "svelte";
+ import { onMount } from "svelte";
import { t } from "$lib/translations";
import Spinner from "$comp/Spinner.svelte";
import { bytesToHex } from "@noble/hashes/utils.js";
@@ -24,13 +23,13 @@
let hasSigner = $state(false);
let arkAddress = $state("");
- let submitting = $state(false);
+ let submitting = $state(true);
let needsPasskeyUnlock = $state(false);
let name = $t("accounts.savings");
let type = "ark";
- let deriveFromPrfKey = async (prfKey: ArrayBuffer) => {
+ let deriveAndCreate = async (prfKey: ArrayBuffer) => {
try {
const { SingleKey, Wallet } = await import("@arkade-os/sdk");
const entropy = new Uint8Array(prfKey).slice(0, 16);
@@ -42,84 +41,62 @@
const identity = SingleKey.fromHex(arkHex);
const wallet = await Wallet.create({ identity, arkServerUrl });
arkAddress = await wallet.getAddress();
+ await post("/account", { name, type, arkAddress });
+ goto(`/${user.username}`);
} catch (e: any) {
- console.log("PRF seed derivation failed", e);
- fail($t("accounts.failedToDecryptSeed"));
+ console.log("Ark account creation failed", e);
+ fail(e.message || $t("accounts.failedToDecryptSeed"));
+ submitting = false;
}
};
let unlockWithNostr = async () => {
try {
+ submitting = true;
const { deriveNostrEntropy } = await import("$lib/walletEntropy");
const entropy = await deriveNostrEntropy();
- hasPrfSeed = true;
- await deriveFromPrfKey(entropy);
- needsPasskeyUnlock = false;
+ await deriveAndCreate(entropy);
} catch (e: any) {
fail(e.message || $t("accounts.passkeyUnlockFailed"));
+ submitting = false;
}
};
let unlockWithPasskey = async () => {
try {
+ submitting = true;
const { prfKey } = await loginWithPasskey();
if (prfKey) {
rememberPrfKey(prfKey, defaultRememberForMs);
- await deriveFromPrfKey(prfKey);
- needsPasskeyUnlock = false;
+ await deriveAndCreate(prfKey);
} else {
fail($t("accounts.passkeyNoPrf"));
+ submitting = false;
}
} catch (e: any) {
if (e.name !== "NotAllowedError") {
fail(e.message || $t("accounts.passkeyUnlockFailed"));
}
+ submitting = false;
}
};
onMount(async () => {
+ submitting = true;
hasSigner = !!localStorage.getItem("signer");
- // Try wallet entropy (covers both cached prfKey and silent nsec derivation)
- if (!hasPrfSeed) {
- const { getWalletEntropy } = await import("$lib/walletEntropy");
- const entropy = await getWalletEntropy();
- if (entropy) {
- hasPrfSeed = true;
- await deriveFromPrfKey(entropy);
- return;
- }
- }
-
- if (hasPrfSeed) {
- const prfKey = getCachedPrfKey();
- if (prfKey) {
- await deriveFromPrfKey(prfKey);
- } else {
- needsPasskeyUnlock = true;
- }
- } else {
- needsPasskeyUnlock = true;
+ // Try wallet entropy (covers cached prfKey, nsec, and client-side password)
+ const { getWalletEntropy } = await import("$lib/walletEntropy");
+ const entropy = await getWalletEntropy();
+ if (entropy) {
+ await deriveAndCreate(entropy);
+ return;
}
- });
- let submit = async () => {
- submitting = true;
- await tick();
- try {
- if (!arkAddress) {
- const prfKey = getCachedPrfKey();
- if (prfKey) await deriveFromPrfKey(prfKey);
- else throw new Error("PRF key not available");
- }
- await post("/account", { name, type, arkAddress });
- goto(`/${user.username}`);
- } catch (e: any) {
- console.log(e);
- fail(e.message);
- }
+ // Need manual unlock
submitting = false;
- };
+ needsPasskeyUnlock = true;
+ });
</script>
<div class="space-y-5">
@@ -128,7 +105,11 @@
</div>
<div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
- {#if needsPasskeyUnlock}
+ {#if submitting}
+ <div class="flex justify-center py-8">
+ <Spinner />
+ </div>
+ {:else if needsPasskeyUnlock}
<div class="space-y-5">
<p class="text-secondary">
{$t("accounts.arkUnlockWithPasskey")}
@@ -143,25 +124,10 @@
<iconify-icon noobserver icon="ph:fingerprint-bold" width="24"></iconify-icon>
{$t("accounts.unlockWithPasskey")}
</button>
+ <a href={`/${user.username}`}>
+ <button type="button" class="btn">{$t("accounts.back")}</button>
+ </a>
</div>
- {:else}
- <form onsubmit={preventDefault(submit)} class="space-y-5">
- <p class="text-secondary">
- {$t("accounts.arkDerivedFromPasskey")}
- </p>
- <div class="flex gap-2">
- <a href={`/${user.username}`} class="contents">
- <button type="button" class="btn !w-auto grow">{$t("accounts.back")}</button>
- </a>
- <button disabled={submitting} type="submit" class="btn btn-accent !w-auto grow">
- {#if submitting}
- <Spinner />
- {:else}
- {$t("accounts.createWallet")}
- {/if}
- </button>
- </div>
- </form>
{/if}
</div>
</div>
diff --git a/src/routes/(app)/account/new/+page.svelte b/src/routes/(app)/account/new/+page.svelte
index 136e77b3..db0361f8 100644
--- a/src/routes/(app)/account/new/+page.svelte
+++ b/src/routes/(app)/account/new/+page.svelte
@@ -1,7 +1,49 @@
<script lang="ts">
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 { goto } from "$app/navigation";
+ import { arkServerUrl } from "$lib/ark";
+ import { HDKey } from "@scure/bip32";
+ import { entropyToMnemonic, mnemonicToSeed } from "@scure/bip39";
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
let { data } = $props();
+ let user = $derived(data.user);
+
+ let creatingArk = $state(false);
+
+ let createArk = async () => {
+ creatingArk = true;
+ try {
+ const { getWalletEntropy } = await import("$lib/walletEntropy");
+ const entropy = await getWalletEntropy();
+ if (!entropy) {
+ fail($t("accounts.failedToDecryptSeed"));
+ creatingArk = false;
+ return;
+ }
+
+ const { SingleKey, Wallet } = await import("@arkade-os/sdk");
+ const ent = new Uint8Array(entropy).slice(0, 16);
+ const mnemonic = entropyToMnemonic(ent, wordlist);
+ const seed = await mnemonicToSeed(mnemonic);
+ const master = HDKey.fromMasterSeed(seed, versions);
+ 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 });
+ goto(`/${user.username}`);
+ } catch (e: any) {
+ console.log("Ark account creation failed", e);
+ fail(e.message || $t("accounts.failedToDecryptSeed"));
+ creatingArk = false;
+ }
+ };
</script>
<div class="space-y-5">
@@ -20,9 +62,13 @@
</div>
</a>
{#if !data.hasArk}
- <a href="/account/ark" class="block">
+ <button type="button" class="block w-full text-left" onclick={createArk} disabled={creatingArk}>
<div class="card card-side shadow shadow-base-300 p-8 hover:bg-base-200 gap-4 items-center">
- <img src="/images/ark.png" class="w-12 h-12 rounded-full object-cover bg-neutral" alt="Ark" />
+ {#if creatingArk}
+ <Spinner />
+ {:else}
+ <img src="/images/ark.png" class="w-12 h-12 rounded-full object-cover bg-neutral" alt="Ark" />
+ {/if}
<div>
<div class="text-xl">Ark (Off-chain)</div>
<div class="text-secondary">
@@ -30,7 +76,7 @@
</div>
</div>
</div>
- </a>
+ </button>
{/if}
</div>
</div>