aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-14 15:46:09 -0800
committerAdam Soltys <[email protected]>2026-02-14 15:46:09 -0800
commit40fc5fe98c08357dff41539de48532d554aa73cb (patch)
tree289e6f1af7f256b1e8ddab75d74e272ead7baa2f /src
parentd242a387279119cfc6e191eff3b4e2228cbb5a4e (diff)
downloadcoinos-ui-40fc5fe98c08357dff41539de48532d554aa73cb.tar.gz
coinos-ui-40fc5fe98c08357dff41539de48532d554aa73cb.zip
custodial ark sends
Diffstat (limited to 'src')
-rw-r--r--src/components/Account.svelte2
-rw-r--r--src/components/InvoiceActions.svelte2
-rw-r--r--src/components/InvoiceTypes.svelte17
-rw-r--r--src/lib/ark.ts14
-rw-r--r--src/locales/en.json6
-rw-r--r--src/routes/(app)/+layout.svelte21
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte17
-rw-r--r--src/routes/(app)/account/[id]/+page.svelte26
-rw-r--r--src/routes/(app)/invoice/[id]/+page.svelte2
9 files changed, 68 insertions, 39 deletions
diff --git a/src/components/Account.svelte b/src/components/Account.svelte
index b077c30b..adea5c70 100644
--- a/src/components/Account.svelte
+++ b/src/components/Account.svelte
@@ -146,7 +146,7 @@
</div>
{/if}
</div>
- <div class="flex items-start gap-4 shrink-0 text-gray-600">
+ <div class="flex items-start gap-4 shrink-0 text-black">
<div class="flex items-center justify-center w-8 h-8 leading-[0]">
{#if isArk}
<img src="/images/ark.png" class="w-8 h-8 rounded-full object-cover" alt="Ark" />
diff --git a/src/components/InvoiceActions.svelte b/src/components/InvoiceActions.svelte
index 53447fec..e7fb57e3 100644
--- a/src/components/InvoiceActions.svelte
+++ b/src/components/InvoiceActions.svelte
@@ -90,7 +90,7 @@
</button>
{/if}
- {#if !account?.seed && !(account?.pubkey && account?.fingerprint)}
+ {#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}
diff --git a/src/components/InvoiceTypes.svelte b/src/components/InvoiceTypes.svelte
index 5845f699..23735a49 100644
--- a/src/components/InvoiceTypes.svelte
+++ b/src/components/InvoiceTypes.svelte
@@ -71,7 +71,7 @@
}}
>
<div class="bg-black rounded-full w-8 h-8 items-center justify-center flex">
- <div class="m-auto">
+ <div class="m-auto mt-1">
<iconify-icon noobserver icon="ph:lightning-fill" class="text-yellow-300 text-2xl"
></iconify-icon>
</div>
@@ -86,7 +86,7 @@
onclick={() => (activeOnly ? toggleType() : setType(types.bolt12))}
>
<div class="bg-black rounded-full w-8 h-8 items-center justify-center flex">
- <div class="m-auto">
+ <div class="m-auto mt-1">
<iconify-icon noobserver icon="ph:lightning-fill" class="text-yellow-300 text-2xl"
></iconify-icon>
</div>
@@ -110,7 +110,7 @@
}}
>
<div class="bg-black rounded-full w-8 h-8 items-center justify-center flex">
- <div class="m-auto">
+ <div class="m-auto mt-1">
<iconify-icon noobserver icon="ph:lightning-fill" class="text-yellow-300 text-2xl"
></iconify-icon>
</div>
@@ -142,6 +142,17 @@
<img src="/images/liquid.svg" class="w-8" alt="Liquid" />
<div class="my-auto text-lg">Liquid</div>
</button>
+
+ <button
+ class="btn flex-nowrap !w-auto grow"
+ class:bg-base-300={type === types.ark}
+ class:text-secondary={type === types.ark}
+ class:hidden={activeOnly && type !== types.ark}
+ onclick={() => (activeOnly ? toggleType() : setType("ark"))}
+ >
+ <img src="/images/ark.png" class="w-8 rounded-full" alt="Ark" />
+ <div class="my-auto text-lg">Ark</div>
+ </button>
</div>
<div></div>
diff --git a/src/lib/ark.ts b/src/lib/ark.ts
index b9902a5a..dcc42868 100644
--- a/src/lib/ark.ts
+++ b/src/lib/ark.ts
@@ -125,7 +125,19 @@ export const sendArk = async (address: string, amount: number) => {
arkSending = true;
try {
- const txid = await wallet.sendBitcoin({ address, amount });
+ let txid;
+ try {
+ txid = await wallet.sendBitcoin({ address, amount });
+ } catch (e: any) {
+ if (e?.message?.includes("VTXO_ALREADY_REGISTERED")) {
+ await wallet.settle();
+ const vtxos = await wallet.getVtxos({ spendableOnly: false });
+ if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey));
+ txid = await wallet.sendBitcoin({ address, amount });
+ } else {
+ throw e;
+ }
+ }
// Mark post-send VTXOs as processed so notifyIncomingFunds
// ignores change VTXOs from this send
diff --git a/src/locales/en.json b/src/locales/en.json
index 9f23dd47..2ed0d54d 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -53,7 +53,7 @@
"revealMnemonic": "Reveal seed phrase",
"regenerate": "Regenerate",
"deleteAccount": "Delete account",
- "addAccount": "Create a non-custodial account",
+ "addAccount": "Add account",
"create": "Create",
"import": "Import",
"accountSetup": "Account setup",
@@ -101,7 +101,7 @@
"dm": {
"chatHeader": "Chats",
"newChatHeader": "New Chat",
- "header": "Direct Messages",
+ "header": "Messages",
"searchPrompt": "username, nip-05 or pubkey",
"findByUsername": "Find by username",
"findByNIP05": "Find by NIP-05",
@@ -244,7 +244,7 @@
"light": "Light",
"about": "About",
"docs": "Docs",
- "dm": "Nostr Messages",
+ "dm": "Messages",
"messages": "Messages",
"funds": "Funds",
"map": "Map",
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index 2c96fc7c..2d252385 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- import { getWallet, syncTransactions, settle, arkkey, arkaid } from "$lib/ark";
+ import { getWallet, syncTransactions, settle, arkkey, arkaid, sendArk } from "$lib/ark";
import { SvelteToast } from "@zerodevx/svelte-toast";
import { onDestroy, onMount } from "svelte";
import { close, connect, send, socket } from "$lib/socket";
@@ -22,7 +22,7 @@
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 } 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";
@@ -52,6 +52,7 @@
});
let arkSyncing = false;
+ let arkForwarding = false;
let arkSync = async () => {
if (arkSyncing) return;
@@ -61,11 +62,23 @@
const aid = $arkaid || getCookie("aid") || user.id;
const result = await syncTransactions(aid);
- if (result?.received > 0) {
+ if (result?.forward && !arkForwarding) {
+ arkForwarding = true;
+ try {
+ const { serverArkAddress, amount, iid } = result.forward;
+ const txid = await sendArk(serverArkAddress, amount);
+ await post("/post/ark/vault-send", { hash: txid, amount, aid });
+ await post("/post/ark/receive", { amount, hash: txid, iid });
+ } catch (e) {
+ console.error("Ark custodial forward failed:", e);
+ } finally {
+ arkForwarding = false;
+ }
+ } else if (result?.received > 0) {
if ($fiat && $rateStore && user?.currency) {
success(`Received ${f(toFiat(result.received, $rateStore as number), user.currency)}!`);
} else {
- success(`Received ⚡️${s(result.received)}!`);
+ success(`Received ${s(result.received)}!`);
}
}
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index 966925d5..4885189b 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -64,17 +64,12 @@
{#each accounts as account}
<Account {user} {rates} {account} />
{/each}
- <div class="flex justify-end">
- <a href="/account/new" class="contents" aria-label={$t("accounts.addAccount")}>
- <button
- class="btn btn-circle btn-accent !w-14 !h-14 !rounded-full"
- aria-label={$t("accounts.addAccount")}
- title={$t("accounts.addAccount")}
- >
- <iconify-icon noobserver icon="ph:plus-bold" width="28"></iconify-icon>
- </button>
- </a>
- </div>
+ <a href="/account/new" class="contents">
+ <button class="btn btn-accent w-full">
+ <iconify-icon noobserver icon="ph:plus-bold" width="24"></iconify-icon>
+ {$t("accounts.addAccount")}
+ </button>
+ </a>
</div>
{#if locked}
diff --git a/src/routes/(app)/account/[id]/+page.svelte b/src/routes/(app)/account/[id]/+page.svelte
index fa9d744d..157dd096 100644
--- a/src/routes/(app)/account/[id]/+page.svelte
+++ b/src/routes/(app)/account/[id]/+page.svelte
@@ -30,7 +30,7 @@
let currency = $state(data.account.currency || data.user.currency);
let autowithdraw = $state(data.account.autowithdraw || false);
let threshold = $state(data.account.threshold || data.user.threshold || 1000000);
- let reserve = $state(data.account.reserve || data.user.reserve || 0);
+ let reserve = $state(data.account.reserve ?? 0);
let destination = $state(data.account.destination || data.user.destination || "");
let rate = $derived(rates[currency]);
@@ -113,21 +113,19 @@
</script>
<div class="space-y-5">
- <h1 class="text-center text-3xl font-semibold">{$t("payments.accountSettings")}</h1>
+ <h1 class="text-center text-3xl font-semibold flex items-center justify-center gap-2">
+ {#if account.type === "ark"}
+ <img src="/images/ark.png" class="w-8 h-8 rounded-full object-cover" alt="Ark" />
+ {:else if seed}
+ <iconify-icon noobserver icon="cryptocurrency-color:btc" width="32"></iconify-icon>
+ {:else}
+ <img src="/images/icon.png" class="w-8 h-8" alt="Coinos" />
+ {/if}
+ {displayType} {$t("payments.accountSettings").toLowerCase()}
+ </h1>
<div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-2">
<form class="space-y-5 pb-8" method="POST" use:enhance>
- <div class="flex items-center gap-2 text-lg">
- <span class="font-bold">{$t("accounts.accountType")}:</span>
- {#if account.type === "ark"}
- <img src="/images/ark.png" class="w-6 h-6 rounded-full object-cover" alt="Ark" />
- {:else if seed}
- <iconify-icon noobserver icon="cryptocurrency-color:btc" width="24"></iconify-icon>
- {:else}
- <img src="/images/icon.png" class="w-6 h-6" alt="Coinos" />
- {/if}
- {displayType}
- </div>
<div class="space-y-1">
<label for="name" class="font-bold block">{$t("accounts.name")}</label>
@@ -213,7 +211,7 @@
</button>
{/if}
- {#if seed}
+ {#if seed || account.type === "ark"}
<button onclick={del} type="button" class="btn">
<iconify-icon noobserver icon="ph:trash-bold" width="32"></iconify-icon>
<div class="my-auto">{$t("accounts.deleteAccount")}</div>
diff --git a/src/routes/(app)/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte
index d8f30fd1..e4a5b85e 100644
--- a/src/routes/(app)/invoice/[id]/+page.svelte
+++ b/src/routes/(app)/invoice/[id]/+page.svelte
@@ -94,7 +94,7 @@
invoice.address_type = address_type;
invoice.type = type;
- if (type === "ark") {
+ if (type === "ark" && invoice.aid !== invoice.uid) {
const wallet = await getWallet();
invoice.hash = await wallet.getAddress();
}