diff options
| author | Adam Soltys <[email protected]> | 2026-02-26 22:56:07 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-26 22:56:07 -0800 |
| commit | 73afb376b9e4984351d7352e8728208f90dcffc9 (patch) | |
| tree | d875e85fd0b62e01a4d514f0ff18be3cbf7c107d /src | |
| parent | 211511570b447ad0256a992bf8e3b5dfa8a82fb7 (diff) | |
| download | coinos-ui-73afb376b9e4984351d7352e8728208f90dcffc9.tar.gz coinos-ui-73afb376b9e4984351d7352e8728208f90dcffc9.zip | |
auto create ark account
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/Menu.svelte | 2 | ||||
| -rw-r--r-- | src/routes/(app)/account/ark/+page.svelte | 100 | ||||
| -rw-r--r-- | src/routes/(app)/account/new/+page.svelte | 52 |
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> |
