aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-28 18:30:33 -0800
committerAdam Soltys <[email protected]>2026-02-28 18:30:33 -0800
commit41467c51e2def5c8932e1dd967f3cfe04e0cc563 (patch)
treeff2322fc5573ac32c3a6420d705bddd7fefc15f0 /src
parentba715da66afed730180750b567cc3b956a0c9181 (diff)
downloadcoinos-ui-41467c51e2def5c8932e1dd967f3cfe04e0cc563.tar.gz
coinos-ui-41467c51e2def5c8932e1dd967f3cfe04e0cc563.zip
service worker for ark wallet refresh
Diffstat (limited to 'src')
-rw-r--r--src/components/InvoiceData.svelte4
-rw-r--r--src/lib/ark.ts64
-rw-r--r--src/routes/(app)/+layout.svelte17
-rw-r--r--src/service-worker.js17
4 files changed, 94 insertions, 8 deletions
diff --git a/src/components/InvoiceData.svelte b/src/components/InvoiceData.svelte
index 88a535a5..0e5be51f 100644
--- a/src/components/InvoiceData.svelte
+++ b/src/components/InvoiceData.svelte
@@ -29,9 +29,7 @@
<a href={link}>
<img
src={`/qr/${encodeURIComponent(invoice.text)}/raw`}
- class="z-10 border-4 border-white opacity-0 transition-opacity duration-300"
- class:opacity-100={loaded}
- onload={load}
+ class="z-10 border-4 border-white"
alt={txt}
/>
</a>
diff --git a/src/lib/ark.ts b/src/lib/ark.ts
index 1a779f32..c5212f70 100644
--- a/src/lib/ark.ts
+++ b/src/lib/ark.ts
@@ -1,6 +1,6 @@
import { PUBLIC_COINOS_NETWORK } from "$env/static/public";
import { persistLocal } from "$lib/store";
-import { post } from "$lib/utils";
+import { post, versions } from "$lib/utils";
import { get } from "svelte/store";
export const arkServerUrl = {
@@ -64,6 +64,31 @@ export const getWallet = async () => {
return walletInstance;
};
+export const autoUnlockArk = async () => {
+ if (get(arkkey)) return;
+ const { getWalletEntropy } = await import("$lib/walletEntropy");
+ const entropy = await getWalletEntropy();
+ if (!entropy) return;
+ try {
+ const [{ HDKey }, { entropyToMnemonic, mnemonicToSeed }, { wordlist }] = await Promise.all([
+ import("@scure/bip32"),
+ import("@scure/bip39"),
+ import("@scure/bip39/wordlists/english.js"),
+ ]);
+ const { bytesToHex } = await import("@noble/hashes/utils.js");
+ 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 child = master.derive("m/86'/0'/0'/0/0");
+ const key = bytesToHex(child.privateKey!);
+ arkkey.set(key);
+ } catch (e) {
+ console.error("Ark auto-unlock failed:", e);
+ }
+};
+
+
const toHex = (bytes: Uint8Array) =>
Array.from(bytes)
.map((b) => b.toString(16).padStart(2, "0"))
@@ -339,7 +364,7 @@ export const sendArk = async (address: string, amount: number) => {
const freshBalance = await wallet.getBalance();
const preSendTotal = (freshBalance.available || 0) + (freshBalance.preconfirmed || 0);
if (freshBalance.available < amount) {
- throw new Error(`Insufficient funds: ${freshBalance.available} available, need ${amount}`);
+ throw new Error(`Insufficient funds: ${freshBalance.available} available, need ${amount}\nLocked balance: ${freshBalance.boarding || 0} boarding, ${freshBalance.preconfirmed || 0} preconfirmed, ${freshBalance.recoverable || 0} recoverable`);
}
let txid;
@@ -463,3 +488,38 @@ export const settle = async () => {
const vtxos = await wallet.getVtxos({ spendableOnly: false });
if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey));
};
+
+// Service Worker wallet for background fund notifications
+let swWallet: any;
+let swWalletKey: string | undefined;
+
+export const getServiceWorkerWallet = async () => {
+ const key = get(arkkey);
+ if (!key) {
+ await cleanupServiceWorkerWallet();
+ return;
+ }
+ if (swWallet && swWalletKey === key) return swWallet;
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return;
+
+ try {
+ const { ServiceWorkerWallet, SingleKey } = await loadArkSdk();
+ swWallet = await ServiceWorkerWallet.setup({
+ serviceWorkerPath: "/service-worker.js",
+ arkServerUrl: arkServerUrl!,
+ identity: SingleKey.fromHex(key),
+ });
+ swWalletKey = key;
+ return swWallet;
+ } catch (e) {
+ console.error("[ark] Service worker wallet init failed:", e);
+ }
+};
+
+export const cleanupServiceWorkerWallet = async () => {
+ if (swWallet) {
+ try { await swWallet.clear(); } catch {}
+ }
+ swWallet = undefined;
+ swWalletKey = undefined;
+};
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index 500880e8..a7224a15 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- import { getWallet, subscribeToAsp, syncTransactions, settle, refresh, arkkey, arkaid, sendArk, arkSending } from "$lib/ark";
+ import { getWallet, subscribeToAsp, syncTransactions, settle, refresh, arkkey, arkaid, sendArk, arkSending, autoUnlockArk, getServiceWorkerWallet, cleanupServiceWorkerWallet } from "$lib/ark";
import { SvelteToast } from "@zerodevx/svelte-toast";
import { onDestroy, onMount } from "svelte";
import { close, connect, send, socket } from "$lib/socket";
@@ -137,6 +137,18 @@
refresh().catch((e) => console.error("Ark refresh failed:", e));
settle().catch((e) => console.error("Ark settle failed:", e));
+ // Init service worker wallet for background fund notifications
+ getServiceWorkerWallet().catch((e) => console.error("SW wallet init:", e));
+
+ // Listen for VTXO/UTXO updates from the service worker
+ if ("serviceWorker" in navigator) {
+ navigator.serviceWorker.addEventListener("message", (event) => {
+ if (event.data?.type === "VTXO_UPDATE" || event.data?.type === "UTXO_UPDATE") {
+ arkSync("serviceWorker");
+ }
+ });
+ }
+
// Periodically recover expired VTXOs
if (arkRefreshTimer) clearInterval(arkRefreshTimer);
arkRefreshTimer = setInterval(() => {
@@ -167,6 +179,8 @@
localStorage.removeItem("arkkey:uid");
}
+ // Auto-unlock ark wallet if silent entropy is available
+ if (!$arkkey) autoUnlockArk();
}
// if (window.NDEFReader) {
@@ -222,6 +236,7 @@
clearTimeout(checkTimer);
if (arkRefreshTimer) clearInterval(arkRefreshTimer);
aspStopFunc?.();
+ cleanupServiceWorkerWallet();
}
});
</script>
diff --git a/src/service-worker.js b/src/service-worker.js
index 9d33d322..a366b9d8 100644
--- a/src/service-worker.js
+++ b/src/service-worker.js
@@ -1,6 +1,19 @@
-self.addEventListener("install", () => {});
-self.addEventListener("activate", () => {});
+import { Worker } from "@arkade-os/sdk";
+
+const worker = new Worker();
+// Only add message listener - we handle install/activate ourselves
+worker.start(false).catch(console.error);
+
+self.addEventListener("install", () => {
+ self.skipWaiting();
+});
+
+self.addEventListener("activate", (event) => {
+ event.waitUntil(self.clients.claim());
+});
+
self.addEventListener("fetch", () => {});
+
self.addEventListener("push", (event) => {
const { title, body, url } = event.data.json();
const icon = "/images/icon.png";