aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2025-11-26 10:56:00 -0800
committerAdam Soltys <[email protected]>2026-02-11 15:33:08 -0800
commit5f1c31b6618f0b895fb98c8f2a841989625eaf16 (patch)
tree668a3cddaacbb03a1f3b5fdcf8700e49674dada0 /src
parent21023052918c0b787916ef78937e0be41f46142a (diff)
downloadcoinos-ui-5f1c31b6618f0b895fb98c8f2a841989625eaf16.tar.gz
coinos-ui-5f1c31b6618f0b895fb98c8f2a841989625eaf16.zip
ark
Diffstat (limited to 'src')
-rw-r--r--src/components/InvoiceActions.svelte79
-rw-r--r--src/components/InvoiceData.svelte2
-rw-r--r--src/lib/ark.ts15
-rw-r--r--src/lib/parse.ts1
-rw-r--r--src/lib/utils.ts1
-rw-r--r--src/routes/(app)/+layout.svelte32
-rw-r--r--src/routes/(app)/ark/+page.svelte13
-rw-r--r--src/routes/(app)/invoice/[id]/+layout.server.ts1
-rw-r--r--src/routes/(app)/invoice/[id]/+page.svelte8
-rw-r--r--src/routes/(app)/send/ark/[address]/+layout.server.ts25
-rw-r--r--src/routes/(app)/send/ark/[address]/+page.server.ts10
-rw-r--r--src/routes/(app)/send/ark/[address]/+page.svelte80
-rw-r--r--src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts40
-rw-r--r--src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte107
14 files changed, 373 insertions, 41 deletions
diff --git a/src/components/InvoiceActions.svelte b/src/components/InvoiceActions.svelte
index e2a31ab8..08f9920d 100644
--- a/src/components/InvoiceActions.svelte
+++ b/src/components/InvoiceActions.svelte
@@ -91,7 +91,7 @@
</button>
</div>
- {#if txt.length > 120}
+ {#if txt?.length > 120}
<div class="w-full flex justify-center gap-2 flex-wrap">
<button class="btn" onclick={() => (showQr = !showQr)}>
{#if showQr}
@@ -109,29 +109,51 @@
{/if}
<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"
+ {#if aid === user.id}
+ <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="m-auto">
- <iconify-icon
- noobserver
- icon="ph:lightning-fill"
- class="text-yellow-300 text-2xl"
- ></iconify-icon>
+ <div
+ class="bg-black rounded-full w-8 h-8 items-center justify-center flex"
+ >
+ <div class="m-auto">
+ <iconify-icon
+ noobserver
+ icon="ph:lightning-fill"
+ class="text-yellow-300 text-2xl"
+ ></iconify-icon>
+ </div>
</div>
- </div>
- <div class="my-auto text-lg">Lightning</div>
- </button>
+ <div class="my-auto text-lg">Lightning</div>
+ </button>
+ {:else}
+ <button
+ class="btn flex-nowrap !w-auto grow"
+ class:bg-base-300={type === types.ark}
+ class:text-secondary={type === types.ark}
+ onclick={() => {
+ $amountPrompt = false;
+ newAmount = undefined;
+ setType(types.ark);
+ }}
+ >
+ <div
+ class="bg-black rounded-full w-8 h-8 items-center justify-center flex"
+ >
+ <div class="m-auto">
+ <img src="/images/ark.png" class="w-8 h-8 rounded-full object-cover" alt="Bitcoin" />
+ </div>
+ </div>
+ <div class="my-auto text-lg">Ark</div>
+ </button>
+ {/if}
<button
class="btn flex-nowrap !w-auto grow"
@@ -144,10 +166,11 @@
</button>
</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" />
- <div class="my-auto">{t("payments.moreOptions")}</div>
- </button>
+ {#if aid === user.id}
+ <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" />
+ <div class="my-auto">{t("payments.moreOptions")}</div>
+ </button>
+ {/if}
</div>
diff --git a/src/components/InvoiceData.svelte b/src/components/InvoiceData.svelte
index 8edc9257..e08916fe 100644
--- a/src/components/InvoiceData.svelte
+++ b/src/components/InvoiceData.svelte
@@ -53,7 +53,7 @@
</div>
{/if}
-{#if !showQr || txt.length <= 120}
+{#if !showQr || txt?.length <= 120}
<div
class="break-all text-center text-secondary text-xl flex gap-1 items-center justify-center"
>
diff --git a/src/lib/ark.ts b/src/lib/ark.ts
index 0986983a..2dec57d9 100644
--- a/src/lib/ark.ts
+++ b/src/lib/ark.ts
@@ -1,5 +1,20 @@
import { persistLocal } from "$lib/store";
+import { SingleKey, Wallet } from "@arkade-os/sdk";
const prefix = "ark";
export const mnemonic = persistLocal(`${prefix}mnemonic`, "");
export const password = persistLocal(`${prefix}password`, "");
+export const arkkey = persistLocal(`${prefix}key`, "");
+
+export const getWallet = async () => {
+ // const identity = SingleKey.fromHex(get(arkkey));
+
+ const identity = SingleKey.fromHex(
+ "ce66c68f8875c0c98a502c666303dc183a21600130013c06f9d1edf60207abf2",
+ );
+
+ return Wallet.create({
+ identity,
+ arkServerUrl: "https://arkade.computer",
+ });
+};
diff --git a/src/lib/parse.ts b/src/lib/parse.ts
index 6ff4556c..c69032d1 100644
--- a/src/lib/parse.ts
+++ b/src/lib/parse.ts
@@ -11,6 +11,7 @@ export default async (s, host, cookies) => {
let invoice;
let user;
+ if (t.startsWith("ark")) redirect(307, `/send/ark/${t}`);
if (t.startsWith("http")) redirect(307, t);
if (t.startsWith(host)) redirect(307, `http://${t}`);
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 14a1370e..d3333280 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -398,6 +398,7 @@ export const wait = async (
};
export const types = {
+ ark: "ark",
bitcoin: "bitcoin",
liquid: "liquid",
lightning: "lightning",
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index 830e5291..cf29ad7c 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -1,4 +1,5 @@
<script>
+ import { getWallet } from "$lib/ark";
import { SvelteToast } from "@zerodevx/svelte-toast";
import { onDestroy, onMount } from "svelte";
import { close, connect, send, socket } from "$lib/socket";
@@ -19,7 +20,7 @@
import AppHeader from "$comp/AppHeader.svelte";
import Nostr from "$comp/Nostr.svelte";
import Password from "$comp/Password.svelte";
- import { getCookie, warning } from "$lib/utils";
+ import { s, success, post, getCookie, warning } from "$lib/utils";
import { t, locale, loading } from "$lib/translations";
import { goto, afterNavigate, preloadData } from "$app/navigation";
@@ -41,11 +42,40 @@
}
});
+ let handleArkPayment = async (notification) => {
+ console.log("NOTIF", notification);
+ let amount = notification.newVtxos.reduce((a, b) => a + b.value, 0);
+ console.log("AMOUNT", amount);
+ success(`Received ⚡️${s(amount)}!`);
+ const wallet = await getWallet();
+ const address = await wallet.getAddress();
+
+ let invoice = await post(`/invoice`, {
+ invoice: { aid, type: "ark", amount },
+ user,
+ });
+
+ let p = await post(`/post/ark/receive`, {
+ amount: vtxo.value,
+ iid: invoice.id,
+ });
+
+ goto(`/invoice/${p.iid}`, {
+ invalidateAll: true,
+ noScroll: true,
+ });
+ };
+
onMount(async () => {
if (browser) {
checkSocket();
$pin = getCookie("pin");
+ if (user) {
+ const wallet = await getWallet();
+ wallet.notifyIncomingFunds(handleArkPayment);
+ }
+
// if (window.NDEFReader) {
// try {
// $ndef = new NDEFReader();
diff --git a/src/routes/(app)/ark/+page.svelte b/src/routes/(app)/ark/+page.svelte
index 81ef9354..09d4d9a3 100644
--- a/src/routes/(app)/ark/+page.svelte
+++ b/src/routes/(app)/ark/+page.svelte
@@ -1,11 +1,7 @@
<script>
- import { SingleKey, Wallet } from "@arkade-os/sdk";
import { onMount, tick } from "svelte";
import { HDKey } from "@scure/bip32";
-
- const identity = SingleKey.fromHex(
- "ce66c68f8875c0c98a502c666303dc183a21600130013c06f9d1edf60207abf2",
- );
+ import { wallet } from "$lib/ark";
let loading = $state();
let balance = $state();
@@ -18,12 +14,7 @@
e?.preventDefault();
loading = true;
- const wallet = await Wallet.create({
- identity,
- arkServerUrl: "https://arkade.computer",
- });
-
- balance = await wallet.getBalance();
+ balance = await wallet().getBalance();
loading = false;
};
diff --git a/src/routes/(app)/invoice/[id]/+layout.server.ts b/src/routes/(app)/invoice/[id]/+layout.server.ts
index ab41dddc..a18dad4e 100644
--- a/src/routes/(app)/invoice/[id]/+layout.server.ts
+++ b/src/routes/(app)/invoice/[id]/+layout.server.ts
@@ -11,6 +11,7 @@ export async function load({ cookies, depends, params, url, parent }) {
if (id) {
invoice = await get(`/invoice/${id}`);
+ console.log("INV", invoice);
const options = !!url.searchParams.get("options");
let { amount, pending, received } = invoice;
amount = parseInt(amount);
diff --git a/src/routes/(app)/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte
index 29dd815d..30fa3dce 100644
--- a/src/routes/(app)/invoice/[id]/+page.svelte
+++ b/src/routes/(app)/invoice/[id]/+page.svelte
@@ -1,4 +1,5 @@
<script>
+ import { getWallet } from "$lib/ark";
import { enhance } from "$app/forms";
import { send } from "$lib/socket";
import {
@@ -8,6 +9,7 @@
copy,
f,
fail,
+ success,
get,
types,
sat,
@@ -97,6 +99,12 @@
if (typeof newAmount !== "undefined") invoice.amount = newAmount;
invoice.address_type = address_type;
invoice.type = type;
+
+ if (type === "ark") {
+ const wallet = await getWallet();
+ invoice.hash = await wallet.getAddress();
+ }
+
await update();
}
settingType = false;
diff --git a/src/routes/(app)/send/ark/[address]/+layout.server.ts b/src/routes/(app)/send/ark/[address]/+layout.server.ts
new file mode 100644
index 00000000..e7a216d6
--- /dev/null
+++ b/src/routes/(app)/send/ark/[address]/+layout.server.ts
@@ -0,0 +1,25 @@
+import { auth, get } from "$lib/utils";
+import { redirect } from "@sveltejs/kit";
+
+export async function load({ cookies, params, parent }) {
+ const { user } = await parent();
+ const aid = cookies.get("aid") || user.id;
+ const { address, amount } = params;
+ const { balance } = await get(`/account/${aid}`, auth(cookies));
+
+ let invoice;
+ try {
+ invoice = await get(`/invoice/${address}`);
+ } catch (e) {
+ // invoice not found
+ }
+
+ if (invoice) {
+ const recipient = await get(`/users/${invoice.uid}`);
+ let r = `/pay/${recipient.username}`;
+ if (amount) r += `/${amount}`;
+ if (recipient?.id !== user.id) redirect(307, r);
+ }
+
+ return { balance };
+}
diff --git a/src/routes/(app)/send/ark/[address]/+page.server.ts b/src/routes/(app)/send/ark/[address]/+page.server.ts
new file mode 100644
index 00000000..b7d3c5cf
--- /dev/null
+++ b/src/routes/(app)/send/ark/[address]/+page.server.ts
@@ -0,0 +1,10 @@
+import getRates from "$lib/rates";
+import { auth, get } from "$lib/utils";
+
+export async function load({ cookies, parent }) {
+ 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] };
+}
diff --git a/src/routes/(app)/send/ark/[address]/+page.svelte b/src/routes/(app)/send/ark/[address]/+page.svelte
new file mode 100644
index 00000000..6ff6ed58
--- /dev/null
+++ b/src/routes/(app)/send/ark/[address]/+page.svelte
@@ -0,0 +1,80 @@
+<script>
+ import { tick } from "svelte";
+ import { t } from "$lib/translations";
+ import Icon from "$comp/Icon.svelte";
+ import Numpad from "$comp/Numpad.svelte";
+ import { page } from "$app/stores";
+ import { rate } from "$lib/store";
+ import { loc, fail, s } from "$lib/utils";
+
+ let { data } = $props();
+
+ let { balance, user } = data;
+ let { address } = $page.params;
+ let { currency, username } = user;
+ let locale = loc(user);
+
+ let amount = $state(0);
+ let a = $state(0);
+ let submit = $state(),
+ fiat = $state();
+
+ $effect(() => ($rate = data.rate));
+ $effect(() => (amount = a));
+
+ let setMax = async (e) => {
+ e.preventDefault();
+ fiat = false;
+ amount = balance;
+ await tick();
+ submit.click();
+ };
+
+ let send = async () => {
+ const txid = await wallet.sendBitcoin({
+ address,
+ amount,
+ });
+
+ console.log(txid);
+ };
+</script>
+
+<div class="container px-4 max-w-xl mx-auto space-y-5 text-center">
+ <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t("payments.send")}</h1>
+
+ <div class="text-xl text-secondary break-all">{address}</div>
+
+ <Numpad
+ bind:amount={a}
+ bind:fiat
+ {currency}
+ {submit}
+ bind:rate={$rate}
+ {locale}
+ />
+
+ <div class="flex justify-center gap-2">
+ <button
+ type="button"
+ class="btn !w-auto grow"
+ onclick={setMax}
+ onkeydown={setMax}>Max ⚡️{s(balance)}</button
+ >
+
+ <form
+ action={`/send/ark/${address}/${amount}`}
+ class="contents"
+ onsubmit={send}
+ >
+ <button
+ use:focus
+ bind:this={submit}
+ type="submit"
+ class="btn !w-auto grow btn-accent"
+ >
+ {$t("payments.next")}
+ </button>
+ </form>
+ </div>
+</div>
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
new file mode 100644
index 00000000..926a141d
--- /dev/null
+++ b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts
@@ -0,0 +1,40 @@
+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 }) {
+ const rates = await getRates();
+ const { user } = await parent();
+ const aid = cookies.get("aid") || user.id;
+
+ const account = await get(`/account/${aid}`, auth(cookies));
+ const rate = rates[user.currency];
+ console.log("rate", rate);
+
+ return {
+ account,
+ amount,
+ address,
+ rate,
+ };
+}
+
+export const actions = {
+ default: async ({
+ cookies,
+ params: { address, amount, feeRate },
+ request,
+ }) => {
+ let p;
+ try {
+ const body = await fd(request);
+ p = await post("/ark/send", body, auth(cookies));
+ } catch (e) {
+ console.log("problem sending bitcoin", e);
+ const { message } = e as Error;
+ return fail(400, { address, amount, feeRate, message });
+ }
+
+ redirect(307, `/sent/${p.id}`);
+ },
+};
diff --git a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte
new file mode 100644
index 00000000..e77abdb5
--- /dev/null
+++ b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte
@@ -0,0 +1,107 @@
+<script>
+ import { tick } from "svelte";
+ import { t } from "$lib/translations";
+ import { enhance } from "$app/forms";
+ import Toggle from "$comp/Toggle.svelte";
+ import Spinner from "$comp/Spinner.svelte";
+ import { page } from "$app/stores";
+ import { toFiat, f, focus, s, sat, closest, network } from "$lib/utils";
+ import { pin } from "$lib/store";
+ import { goto, invalidate } from "$app/navigation";
+ import { rate } from "$lib/store";
+ import { applyAction } from "$app/forms";
+ import { getWallet } from "$lib/ark";
+
+ import Amount from "$comp/Amount.svelte";
+
+ let { data, form } = $props();
+
+ let passwordPrompt = $state();
+ let password = $state();
+ let cancel = $state(() => (passwordPrompt = false));
+
+ let togglePassword = () => (passwordPrompt = !passwordPrompt);
+ let handler = async ({ cancel }) => {
+ let wallet = await getWallet();
+ const txid = await wallet.sendBitcoin({
+ address,
+ amount,
+ });
+
+ console.log(txid);
+
+ return async ({ result }) => {
+ if (result.type === "redirect") {
+ goto(result.location);
+ } else {
+ await applyAction(result);
+ invalidate("app:payments");
+ }
+ };
+ };
+
+ let toggle = () => (submitting = !submitting);
+
+ let { account, amount, address, message } = $derived(data);
+
+ let { feeRate } = $state(data);
+
+ $effect(() => {
+ if (!$rate) $rate = data.rate;
+ });
+
+ let { balance, currency } = data.user;
+ let submitting = $state(),
+ submit = $state(),
+ showSettings;
+
+ let toggleSettings = () => (showSettings = !showSettings);
+</script>
+
+<div
+ class="container px-4 max-w-xl mx-auto space-y-5 text-center no-transition"
+>
+ <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t("payments.send")}</h1>
+
+ {#if form?.message || message}
+ <div class="mb-5">
+ <div class="text-red-600">{form?.message || message}</div>
+ </div>
+ {:else}
+ <div class="text-xl text-secondary break-all">{address}</div>
+
+ <Amount {amount} rate={$rate} {currency} />
+
+ <form method="POST" use:enhance={handler}>
+ <input name="pin" value={$pin} type="hidden" />
+ <input name="rate" value={$rate} type="hidden" />
+ <input name="aid" value={account.id} type="hidden" />
+
+ <div class="flex justify-center gap-2">
+ <button
+ use:focus
+ bind:this={submit}
+ type="submit"
+ class="btn btn-accent"
+ disabled={submitting}
+ >
+ {#if submitting}
+ <Spinner />
+ {:else}
+ {$t("payments.send")}
+ {/if}
+ </button>
+ </div>
+ </form>
+ {/if}
+</div>
+
+{#if passwordPrompt}
+ <WalletPass bind:password bind:cancel submit={signTx} />
+{/if}
+
+<style>
+ .no-transition {
+ view-transition-name: fee;
+ }
+</style>