aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-11 17:51:24 -0800
committerAdam Soltys <[email protected]>2026-02-11 17:51:24 -0800
commit13466248004327e9a026cb89f94926f9b060d1c1 (patch)
tree47b4972c33809ccdf866b043c92cd3e0ecf5dcd0 /src/lib
parent3aa9ce1b5985397d7f568887ec6f881df13c1034 (diff)
downloadcoinos-ui-13466248004327e9a026cb89f94926f9b060d1c1.tar.gz
coinos-ui-13466248004327e9a026cb89f94926f9b060d1c1.zip
linting
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/ark.ts224
-rw-r--r--src/lib/bip21.ts74
-rw-r--r--src/lib/cache.ts3
-rw-r--r--src/lib/handler.ts16
-rw-r--r--src/lib/invoice.ts54
-rw-r--r--src/lib/locales.ts2
-rw-r--r--src/lib/nip01.ts26
-rw-r--r--src/lib/nip07.ts79
-rw-r--r--src/lib/nip11.ts18
-rw-r--r--src/lib/nip17.ts594
-rw-r--r--src/lib/nip40.ts18
-rw-r--r--src/lib/nip44.ts298
-rw-r--r--src/lib/nip51.ts113
-rw-r--r--src/lib/nostr.ts328
-rw-r--r--src/lib/parse.ts214
-rw-r--r--src/lib/passwordCache.ts77
-rw-r--r--src/lib/punks.ts128
-rw-r--r--src/lib/random.ts28
-rw-r--r--src/lib/rates.ts10
-rw-r--r--src/lib/samples.ts34
-rw-r--r--src/lib/settings.ts34
-rw-r--r--src/lib/socket.ts138
-rw-r--r--src/lib/store.ts110
-rw-r--r--src/lib/ticket.ts44
-rw-r--r--src/lib/tickets.ts100
-rw-r--r--src/lib/types.ts24
-rw-r--r--src/lib/upload.ts30
-rw-r--r--src/lib/utils.ts781
28 files changed, 1880 insertions, 1719 deletions
diff --git a/src/lib/ark.ts b/src/lib/ark.ts
index d045049e..51e378c3 100644
--- a/src/lib/ark.ts
+++ b/src/lib/ark.ts
@@ -4,8 +4,8 @@ import { post } from "$lib/utils";
import { get } from "svelte/store";
export const arkServerUrl = {
- bitcoin: "https://arkade.computer",
- regtest: "http://localhost:7070",
+ bitcoin: "https://arkade.computer",
+ regtest: "http://localhost:7070",
}[PUBLIC_COINOS_NETWORK || "regtest"];
export const arkkey = persistLocal("arkkey", "");
@@ -16,166 +16,166 @@ let walletInstance: any;
let walletKey: string | undefined;
export const getWallet = async () => {
- const key = get(arkkey);
- if (!key) {
- walletInstance = undefined;
- walletKey = undefined;
- return;
- }
- if (walletInstance && walletKey === key) return walletInstance;
+ const key = get(arkkey);
+ if (!key) {
+ walletInstance = undefined;
+ walletKey = undefined;
+ return;
+ }
+ if (walletInstance && walletKey === key) return walletInstance;
- const { SingleKey, Wallet } = await loadArkSdk();
- const identity = SingleKey.fromHex(key);
+ const { SingleKey, Wallet } = await loadArkSdk();
+ const identity = SingleKey.fromHex(key);
- walletInstance = await Wallet.create({
- identity,
- arkServerUrl,
- });
- walletKey = key;
- return walletInstance;
+ walletInstance = await Wallet.create({
+ identity,
+ arkServerUrl,
+ });
+ walletKey = key;
+ return walletInstance;
};
export const getAddress = async (): Promise<string> => {
- const wallet = await getWallet();
- return wallet.getAddress();
+ const wallet = await getWallet();
+ return wallet.getAddress();
};
export const getBalance = async (): Promise<{ available: number }> => {
- const wallet = await getWallet();
- return wallet.getBalance();
+ const wallet = await getWallet();
+ return wallet.getBalance();
};
export const getVtxos = async () => {
- const wallet = await getWallet();
- if (!wallet) return [];
- return wallet.getVtxos({ spendableOnly: false });
+ const wallet = await getWallet();
+ if (!wallet) return [];
+ return wallet.getVtxos({ spendableOnly: false });
};
export const syncTransactions = async (aid: string) => {
- const wallet = await getWallet();
- if (!wallet) return;
+ const wallet = await getWallet();
+ if (!wallet) return;
- const transactions: any[] = [];
+ const transactions: any[] = [];
- const history = await wallet.getTransactionHistory();
- const historyTxids = new Set<string>();
- console.log("arkSync: history", history.length, history);
+ const history = await wallet.getTransactionHistory();
+ const historyTxids = new Set<string>();
+ console.log("arkSync: history", history.length, history);
- for (const tx of history) {
- if (tx.key.arkTxid) historyTxids.add(tx.key.arkTxid);
- if (tx.key.commitmentTxid) historyTxids.add(tx.key.commitmentTxid);
- transactions.push({
- arkTxid: tx.key.arkTxid || null,
- commitmentTxid: tx.key.commitmentTxid || null,
- amount: tx.type === "RECEIVED" ? tx.amount : -tx.amount,
- settled: tx.settled,
- createdAt: tx.createdAt,
- });
- }
+ for (const tx of history) {
+ if (tx.key.arkTxid) historyTxids.add(tx.key.arkTxid);
+ if (tx.key.commitmentTxid) historyTxids.add(tx.key.commitmentTxid);
+ transactions.push({
+ arkTxid: tx.key.arkTxid || null,
+ commitmentTxid: tx.key.commitmentTxid || null,
+ amount: tx.type === "RECEIVED" ? tx.amount : -tx.amount,
+ settled: tx.settled,
+ createdAt: tx.createdAt,
+ });
+ }
- // Include preconfirmed VTXOs not yet in transaction history
- const vtxos = await wallet.getVtxos({ spendableOnly: false });
- console.log("arkSync: vtxos", vtxos.length, vtxos);
- for (const v of vtxos) {
- if (v.virtualStatus?.state !== "preconfirmed") continue;
- const id = v.arkTxId || v.txid;
- if (!id || historyTxids.has(id)) continue;
- historyTxids.add(id);
- transactions.push({
- arkTxid: v.arkTxId || null,
- commitmentTxid: v.txid || null,
- amount: Number(v.value),
- settled: false,
- createdAt: v.createdAt ? new Date(v.createdAt).getTime() : Date.now(),
- });
- }
+ // Include preconfirmed VTXOs not yet in transaction history
+ const vtxos = await wallet.getVtxos({ spendableOnly: false });
+ console.log("arkSync: vtxos", vtxos.length, vtxos);
+ for (const v of vtxos) {
+ if (v.virtualStatus?.state !== "preconfirmed") continue;
+ const id = v.arkTxId || v.txid;
+ if (!id || historyTxids.has(id)) continue;
+ historyTxids.add(id);
+ transactions.push({
+ arkTxid: v.arkTxId || null,
+ commitmentTxid: v.txid || null,
+ amount: Number(v.value),
+ settled: false,
+ createdAt: v.createdAt ? new Date(v.createdAt).getTime() : Date.now(),
+ });
+ }
- if (!transactions.length) return;
+ if (!transactions.length) return;
- return post("/post/ark/sync", { transactions, aid });
+ return post("/post/ark/sync", { transactions, aid });
};
export const vtxoKey = (v: { txid: string; vout: number }) =>
- `${v.txid}:${v.vout}`;
+ `${v.txid}:${v.vout}`;
export const getProcessedVtxos = (): Set<string> => {
- try {
- return new Set(
- JSON.parse(localStorage.getItem("processedArkVtxos") || "[]"),
- );
- } catch {
- return new Set();
- }
+ try {
+ return new Set(
+ JSON.parse(localStorage.getItem("processedArkVtxos") || "[]"),
+ );
+ } catch {
+ return new Set();
+ }
};
export const addProcessedVtxos = (keys: string[]) => {
- const processed = getProcessedVtxos();
- for (const k of keys) processed.add(k);
- const arr = [...processed].slice(-200);
- localStorage.setItem("processedArkVtxos", JSON.stringify(arr));
+ const processed = getProcessedVtxos();
+ for (const k of keys) processed.add(k);
+ const arr = [...processed].slice(-200);
+ localStorage.setItem("processedArkVtxos", JSON.stringify(arr));
};
// Flag to suppress notifyIncomingFunds during sends
export let arkSending = false;
export const sendArk = async (address: string, amount: number) => {
- const wallet = await getWallet();
- if (!wallet) throw new Error("Ark wallet not available");
+ const wallet = await getWallet();
+ if (!wallet) throw new Error("Ark wallet not available");
- // Suppress incoming notifications during send to prevent
- // change VTXOs from being credited as incoming payments
- arkSending = true;
+ // Suppress incoming notifications during send to prevent
+ // change VTXOs from being credited as incoming payments
+ arkSending = true;
- try {
- const txid = await wallet.sendBitcoin({ address, amount });
+ try {
+ const txid = await wallet.sendBitcoin({ address, amount });
- // Mark post-send VTXOs as processed so notifyIncomingFunds
- // ignores change VTXOs from this send
- const vtxos = await wallet.getVtxos({ spendableOnly: false });
- if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey));
+ // Mark post-send VTXOs as processed so notifyIncomingFunds
+ // ignores change VTXOs from this send
+ const vtxos = await wallet.getVtxos({ spendableOnly: false });
+ if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey));
- return txid;
- } finally {
- arkSending = false;
- }
+ return txid;
+ } finally {
+ arkSending = false;
+ }
};
export const sendArkViaForward = async ({
- serverArkAddress,
- amount,
- aid,
- forward,
- user,
+ serverArkAddress,
+ amount,
+ aid,
+ forward,
+ user,
}: {
- serverArkAddress: string;
- amount: number;
- aid: string;
- forward: string;
- user: any;
+ serverArkAddress: string;
+ amount: number;
+ aid: string;
+ forward: string;
+ user: any;
}) => {
- const txid = await sendArk(serverArkAddress, amount);
+ const txid = await sendArk(serverArkAddress, amount);
- await post("/post/ark/vault-send", { hash: txid, amount, aid });
+ await post("/post/ark/vault-send", { hash: txid, amount, aid });
- const inv = await post("/post/invoice", {
- invoice: { type: "ark", amount, forward, aid },
- user,
- });
+ const inv = await post("/post/invoice", {
+ invoice: { type: "ark", amount, forward, aid },
+ user,
+ });
- return post("/post/ark/receive", { amount, hash: txid, iid: inv.id });
+ return post("/post/ark/receive", { amount, hash: txid, iid: inv.id });
};
export const settle = async () => {
- const wallet = await getWallet();
- if (!wallet) return;
+ const wallet = await getWallet();
+ if (!wallet) return;
- const balance = await wallet.getBalance();
- if (!balance.preconfirmed) return;
+ const balance = await wallet.getBalance();
+ if (!balance.preconfirmed) return;
- await wallet.settle();
+ await wallet.settle();
- // Mark post-settle VTXOs as processed so notifyIncomingFunds
- // ignores renewed VTXOs (they get new txids after an Ark round)
- const vtxos = await wallet.getVtxos({ spendableOnly: false });
- if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey));
+ // Mark post-settle VTXOs as processed so notifyIncomingFunds
+ // ignores renewed VTXOs (they get new txids after an Ark round)
+ const vtxos = await wallet.getVtxos({ spendableOnly: false });
+ if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey));
};
diff --git a/src/lib/bip21.ts b/src/lib/bip21.ts
index 4689246e..842782a5 100644
--- a/src/lib/bip21.ts
+++ b/src/lib/bip21.ts
@@ -1,50 +1,50 @@
export function decode(uri, allowedSchemes = ["bitcoin", "liquidnetwork"]) {
- // Determine the scheme from the URI
- const split = uri.indexOf(":");
- if (split === -1) {
- throw new Error("Invalid URI: Missing scheme");
- }
+ // Determine the scheme from the URI
+ const split = uri.indexOf(":");
+ if (split === -1) {
+ throw new Error("Invalid URI: Missing scheme");
+ }
- const urnScheme = uri.slice(0, split).toLowerCase();
- if (!allowedSchemes.includes(urnScheme)) {
- throw new Error(`Invalid scheme: ${urnScheme}`);
- }
+ const urnScheme = uri.slice(0, split).toLowerCase();
+ if (!allowedSchemes.includes(urnScheme)) {
+ throw new Error(`Invalid scheme: ${urnScheme}`);
+ }
- // Extract address and query string
- const queryStart = uri.indexOf("?");
- const address = uri.slice(
- split + 1,
- queryStart === -1 ? undefined : queryStart,
- );
- const query = queryStart === -1 ? "" : uri.slice(queryStart + 1);
- const options = Object.fromEntries(new URLSearchParams(query));
+ // Extract address and query string
+ const queryStart = uri.indexOf("?");
+ const address = uri.slice(
+ split + 1,
+ queryStart === -1 ? undefined : queryStart,
+ );
+ const query = queryStart === -1 ? "" : uri.slice(queryStart + 1);
+ const options = Object.fromEntries(new URLSearchParams(query));
- if (options.amount) {
- const n = Number(options.amount);
- if (!Number.isFinite(n)) throw new Error("Invalid amount");
- if (n < 0) throw new Error("Invalid amount");
- }
+ if (options.amount) {
+ const n = Number(options.amount);
+ if (!Number.isFinite(n)) throw new Error("Invalid amount");
+ if (n < 0) throw new Error("Invalid amount");
+ }
- return { scheme: urnScheme, address, options };
+ return { scheme: urnScheme, address, options };
}
export function encode(
- address,
- options = { amount: "" },
- urnScheme = "bitcoin",
+ address,
+ options = { amount: "" },
+ urnScheme = "bitcoin",
) {
- if (!address || typeof address !== "string") {
- throw new TypeError("Invalid address");
- }
+ if (!address || typeof address !== "string") {
+ throw new TypeError("Invalid address");
+ }
- if (!["bitcoin", "liquidnetwork"].includes(urnScheme)) {
- throw new Error(`Unsupported scheme: ${urnScheme}`);
- }
+ if (!["bitcoin", "liquidnetwork"].includes(urnScheme)) {
+ throw new Error(`Unsupported scheme: ${urnScheme}`);
+ }
- const n = Number(options?.amount);
- if (!Number.isFinite(n)) throw new Error("Invalid amount");
- if (n < 0) throw new Error("Invalid amount");
+ const n = Number(options?.amount);
+ if (!Number.isFinite(n)) throw new Error("Invalid amount");
+ if (n < 0) throw new Error("Invalid amount");
- const query = new URLSearchParams(options).toString();
- return `${urnScheme}:${address}${query ? `?${query}` : ""}`;
+ const query = new URLSearchParams(options).toString();
+ return `${urnScheme}:${address}${query ? `?${query}` : ""}`;
}
diff --git a/src/lib/cache.ts b/src/lib/cache.ts
index 079c561b..064b4632 100644
--- a/src/lib/cache.ts
+++ b/src/lib/cache.ts
@@ -1,10 +1,9 @@
-
// src/lib/server/cache.ts
let cachedData: any = null;
async function fetchData() {
// Fetch data from your API
- const response = await fetch('https://api.example.com/data');
+ const response = await fetch("https://api.example.com/data");
cachedData = await response.json();
}
diff --git a/src/lib/handler.ts b/src/lib/handler.ts
index baca442d..559761f0 100644
--- a/src/lib/handler.ts
+++ b/src/lib/handler.ts
@@ -2,12 +2,12 @@ import { goto } from "$app/navigation";
import { applyAction } from "$app/forms";
export default (toggle) => () => {
- toggle();
- return async ({ result }) => {
- if (result.type === "redirect") {
- goto(result.location);
- } else {
- await applyAction(result);
- }
- };
+ toggle();
+ return async ({ result }) => {
+ if (result.type === "redirect") {
+ goto(result.location);
+ } else {
+ await applyAction(result);
+ }
+ };
};
diff --git a/src/lib/invoice.ts b/src/lib/invoice.ts
index 91a69d88..015f2837 100644
--- a/src/lib/invoice.ts
+++ b/src/lib/invoice.ts
@@ -3,37 +3,37 @@ import { auth, post } from "$lib/utils";
import { redirect } from "@sveltejs/kit";
export default async ({ cookies, request }) => {
- const form = await request.formData();
- const aid = form.get("aid") || cookies.get("aid");
+ const form = await request.formData();
+ const aid = form.get("aid") || cookies.get("aid");
- const amount = parseInt(form.get("amount"));
- const rates = await getRates();
+ const amount = parseInt(form.get("amount"));
+ const rates = await getRates();
- let invoice = {
- aid,
- amount,
- items: JSON.parse(form.get("items")),
- memo: form.get("memo"),
- memoPrompt: form.get("memoPrompt"),
- tip: form.get("tip") ?? null,
- type: form.get("type"),
- prompt: form.get("prompt") === "true",
- rate: parseFloat(form.get("rate")) || rates[form.get("currency")],
- id: undefined,
- };
+ let invoice = {
+ aid,
+ amount,
+ items: JSON.parse(form.get("items")),
+ memo: form.get("memo"),
+ memoPrompt: form.get("memoPrompt"),
+ tip: form.get("tip") ?? null,
+ type: form.get("type"),
+ prompt: form.get("prompt") === "true",
+ rate: parseFloat(form.get("rate")) || rates[form.get("currency")],
+ id: undefined,
+ };
- const user = {
- username: form.get("username"),
- currency: form.get("currency"),
- };
+ const user = {
+ username: form.get("username"),
+ currency: form.get("currency"),
+ };
- invoice = await post("/invoice", { invoice, user }, auth(cookies));
- const { id } = invoice;
+ invoice = await post("/invoice", { invoice, user }, auth(cookies));
+ const { id } = invoice;
- if (invoice.prompt && invoice.tip === null)
- redirect(307, `/invoice/${id}/tip`);
+ if (invoice.prompt && invoice.tip === null)
+ redirect(307, `/invoice/${id}/tip`);
- if (invoice.memoPrompt && !invoice.memo) {
- redirect(307, `/invoice/${id}/memo`);
- } else redirect(307, `/invoice/${id}`);
+ if (invoice.memoPrompt && !invoice.memo) {
+ redirect(307, `/invoice/${id}/memo`);
+ } else redirect(307, `/invoice/${id}`);
};
diff --git a/src/lib/locales.ts b/src/lib/locales.ts
index 282eac22..83e7a1bf 100644
--- a/src/lib/locales.ts
+++ b/src/lib/locales.ts
@@ -37,5 +37,5 @@ export default {
it,
nl,
th,
- pl
+ pl,
};
diff --git a/src/lib/nip01.ts b/src/lib/nip01.ts
index a5d3f4b5..b2718b6b 100644
--- a/src/lib/nip01.ts
+++ b/src/lib/nip01.ts
@@ -1,19 +1,21 @@
-import { PUBLIC_DM_RELAYS, PUBLIC_COINOS_RELAY } from '$env/static/public';
-const DM_RELAYS_LIST = PUBLIC_DM_RELAYS.split(',');
-const USERINFO_RELAYS = [ ...DM_RELAYS_LIST, PUBLIC_COINOS_RELAY ];
+import { PUBLIC_DM_RELAYS, PUBLIC_COINOS_RELAY } from "$env/static/public";
+const DM_RELAYS_LIST = PUBLIC_DM_RELAYS.split(",");
+const USERINFO_RELAYS = [...DM_RELAYS_LIST, PUBLIC_COINOS_RELAY];
-import { SimplePool } from 'nostr-tools/pool';
+import { SimplePool } from "nostr-tools/pool";
const pool = new SimplePool();
// Returns the content of the kind-0 event of the provided pubkey.
export const getNostrUserInfo = async (pubkey: string) => {
- const events = await pool.querySync(
- USERINFO_RELAYS, { kinds: [0], limit: 1, authors: [pubkey] }
- );
+ const events = await pool.querySync(USERINFO_RELAYS, {
+ kinds: [0],
+ limit: 1,
+ authors: [pubkey],
+ });
- if (events.length == 0) {
- return null;
- }
+ if (events.length == 0) {
+ return null;
+ }
- return JSON.parse(events[0].content);
-}
+ return JSON.parse(events[0].content);
+};
diff --git a/src/lib/nip07.ts b/src/lib/nip07.ts
index 85f68377..4b32512f 100644
--- a/src/lib/nip07.ts
+++ b/src/lib/nip07.ts
@@ -1,41 +1,52 @@
-import { bytesToHex } from '@noble/hashes/utils.js';
-import { finalizeEvent } from 'nostr-tools/pure';
-import { getPrivateKey } from '$lib/nostr';
-import * as libnip44 from '$lib/nip44';
-import { u } from '$lib/nip44';
+import { bytesToHex } from "@noble/hashes/utils.js";
+import { finalizeEvent } from "nostr-tools/pure";
+import { getPrivateKey } from "$lib/nostr";
+import * as libnip44 from "$lib/nip44";
+import { u } from "$lib/nip44";
// Signs an event using NIP-07 if available, otherwise getPrivateKey()
export const signEvent = async (event: any, user: any): Promise<any> => {
- if (await (window as any).nostr.getPublicKey() === user.pubkey) {
- return (window as any).nostr.signEvent(event);
- } else {
- const sk = await getPrivateKey(user);
- return finalizeEvent(event, sk);
- }
-}
+ if ((await (window as any).nostr.getPublicKey()) === user.pubkey) {
+ return (window as any).nostr.signEvent(event);
+ } else {
+ const sk = await getPrivateKey(user);
+ return finalizeEvent(event, sk);
+ }
+};
// Encrypts an event in NIP-44 using NIP-07 if available, otherwise getPrivateKey()
-export const encrypt = async (json: any, user: any, receiverPK: string): Promise<string> => {
- if (await (window as any).nostr.getPublicKey() === user.pubkey) {
- return (window as any).nostr.nip44.encrypt(
- receiverPK, JSON.stringify(json));
- } else {
- const senderSK = await getPrivateKey(user);
- const senderSKString = bytesToHex(senderSK);
- const ckey = u.getConversationKey(senderSKString, receiverPK);
- return libnip44.encrypt(JSON.stringify(json), ckey);
- }
-}
+export const encrypt = async (
+ json: any,
+ user: any,
+ receiverPK: string,
+): Promise<string> => {
+ if ((await (window as any).nostr.getPublicKey()) === user.pubkey) {
+ return (window as any).nostr.nip44.encrypt(
+ receiverPK,
+ JSON.stringify(json),
+ );
+ } else {
+ const senderSK = await getPrivateKey(user);
+ const senderSKString = bytesToHex(senderSK);
+ const ckey = u.getConversationKey(senderSKString, receiverPK);
+ return libnip44.encrypt(JSON.stringify(json), ckey);
+ }
+};
// Decrypts an event in NIP-44 using NIP-07 if available, otherwise getPrivateKey()
-export const decrypt = async (encrypted: string, user: any, receiverPK: string): Promise<any> => {
- if (await (window as any).nostr.getPublicKey() === user.pubkey) {
- return JSON.parse(await (window as any).nostr.nip44.decrypt(
- receiverPK, encrypted));
- } else {
- const senderSK = await getPrivateKey(user);
- const senderSKString = bytesToHex(senderSK);
- const ckey = u.getConversationKey(senderSKString, receiverPK);
- return JSON.parse(libnip44.decrypt(encrypted, ckey));
- }
-}
+export const decrypt = async (
+ encrypted: string,
+ user: any,
+ receiverPK: string,
+): Promise<any> => {
+ if ((await (window as any).nostr.getPublicKey()) === user.pubkey) {
+ return JSON.parse(
+ await (window as any).nostr.nip44.decrypt(receiverPK, encrypted),
+ );
+ } else {
+ const senderSK = await getPrivateKey(user);
+ const senderSKString = bytesToHex(senderSK);
+ const ckey = u.getConversationKey(senderSKString, receiverPK);
+ return JSON.parse(libnip44.decrypt(encrypted, ckey));
+ }
+};
diff --git a/src/lib/nip11.ts b/src/lib/nip11.ts
index af254b7e..6e827ccb 100644
--- a/src/lib/nip11.ts
+++ b/src/lib/nip11.ts
@@ -1,15 +1,15 @@
-import { fetchRelayInformation } from 'nostr-tools/nip11';
+import { fetchRelayInformation } from "nostr-tools/nip11";
// Accepts a list of relays, and returns a list of the relays that support all the provided NIPs.
export const relaysSupporting = async (relays: string[], nips: number[]) => {
- let supportingRelays = [];
+ let supportingRelays = [];
- for (const relay of relays) {
- const relayInfo = await fetchRelayInformation(relay);
- if (nips.every(relay => relayInfo.supported_nips.includes(relay))) {
- supportingRelays.push(relay);
- }
+ for (const relay of relays) {
+ const relayInfo = await fetchRelayInformation(relay);
+ if (nips.every((relay) => relayInfo.supported_nips.includes(relay))) {
+ supportingRelays.push(relay);
}
+ }
- return supportingRelays;
-}
+ return supportingRelays;
+};
diff --git a/src/lib/nip17.ts b/src/lib/nip17.ts
index b039ebf8..32a18bd8 100644
--- a/src/lib/nip17.ts
+++ b/src/lib/nip17.ts
@@ -1,84 +1,101 @@
-import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
+import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
-import { finalizeEvent, getEventHash, generateSecretKey, getPublicKey } from 'nostr-tools/pure';
-import { SimplePool } from 'nostr-tools/pool';
-import { Relay } from 'nostr-tools/relay';
-import { unwrapEvent } from 'nostr-tools/nip17';
+import {
+ finalizeEvent,
+ getEventHash,
+ generateSecretKey,
+ getPublicKey,
+} from "nostr-tools/pure";
+import { SimplePool } from "nostr-tools/pool";
+import { Relay } from "nostr-tools/relay";
+import { unwrapEvent } from "nostr-tools/nip17";
-import { relaysSupporting } from '$lib/nip11';
-import { expired, expiration } from '$lib/nip40';
-import { encrypt, u } from '$lib/nip44';
-import { eventToSign, signer } from '$lib/store';
-import { get } from 'svelte/store';
+import { relaysSupporting } from "$lib/nip11";
+import { expired, expiration } from "$lib/nip40";
+import { encrypt, u } from "$lib/nip44";
+import { eventToSign, signer } from "$lib/store";
+import { get } from "svelte/store";
const TWO_DAYS = 2 * 24 * 60 * 60;
-const randomTimestamp = () => Math.floor(
- Date.now() / 1000 - Math.random() * TWO_DAYS);
+const randomTimestamp = () =>
+ Math.floor(Date.now() / 1000 - Math.random() * TWO_DAYS);
const pool = new SimplePool();
let relayListCache = new Map();
-import { PUBLIC_DM_RELAYS } from '$env/static/public';
-const DM_RELAYS_LIST = PUBLIC_DM_RELAYS.split(',');
+import { PUBLIC_DM_RELAYS } from "$env/static/public";
+const DM_RELAYS_LIST = PUBLIC_DM_RELAYS.split(",");
const DM_FETCH_LIMIT = 256;
// Ensures we have access to a secret key, triggering the signer modal if needed.
// Returns the sk as Uint8Array, or null if using NIP-07 extension.
const ensureSigner = async (userPubkey: string): Promise<Uint8Array | null> => {
- // Check if NIP-07 extension is available and matches user
- if ((window as any).nostr) {
- try {
- const extPubkey = await (window as any).nostr.getPublicKey();
- if (extPubkey === userPubkey) {
- return null; // Use NIP-07
- }
- } catch (e) {
- // Extension didn't work, fall through to signer
- }
+ // Check if NIP-07 extension is available and matches user
+ if ((window as any).nostr) {
+ try {
+ const extPubkey = await (window as any).nostr.getPublicKey();
+ if (extPubkey === userPubkey) {
+ return null; // Use NIP-07
+ }
+ } catch {
+ // Extension didn't work, fall through to signer
}
+ }
- // Check if signer already has a valid sk (64 hex chars = 32 bytes)
- const currentSigner: any = get(signer);
- if (currentSigner?.ready && currentSigner?.params?.sk && currentSigner.params.sk.length === 64) {
- return hexToBytes(currentSigner.params.sk);
- }
+ // Check if signer already has a valid sk (64 hex chars = 32 bytes)
+ const currentSigner: any = get(signer);
+ if (
+ currentSigner?.ready &&
+ currentSigner?.params?.sk &&
+ currentSigner.params.sk.length === 64
+ ) {
+ return hexToBytes(currentSigner.params.sk);
+ }
- // Clear invalid signer state before triggering modal
- if (currentSigner?.ready && (!currentSigner?.params?.sk || currentSigner.params.sk.length !== 64)) {
- signer.set(undefined);
- }
+ // Clear invalid signer state before triggering modal
+ if (
+ currentSigner?.ready &&
+ (!currentSigner?.params?.sk || currentSigner.params.sk.length !== 64)
+ ) {
+ signer.set(undefined);
+ }
- // Trigger the signer modal with a dummy event
- const dummyEvent = { kind: 14, content: 'DM encryption', tags: [], created_at: Math.floor(Date.now() / 1000) };
- eventToSign.set(dummyEvent);
+ // Trigger the signer modal with a dummy event
+ const dummyEvent = {
+ kind: 14,
+ content: "DM encryption",
+ tags: [],
+ created_at: Math.floor(Date.now() / 1000),
+ };
+ eventToSign.set(dummyEvent);
- // Wait for signer to be ready
- const signerValue = await new Promise<any>((resolve, reject) => {
- let unsubscribe: () => void;
- unsubscribe = signer.subscribe((v: any) => {
- if (v === 'cancel') {
- setTimeout(() => unsubscribe?.());
- eventToSign.set(null);
- reject(new Error('cancelled'));
- }
- if (v?.ready) {
- setTimeout(() => unsubscribe?.());
- eventToSign.set(null);
- resolve(v);
- }
- });
+ // Wait for signer to be ready
+ const signerValue = await new Promise<any>((resolve, reject) => {
+ let unsubscribe: () => void;
+ unsubscribe = signer.subscribe((v: any) => {
+ if (v === "cancel") {
+ setTimeout(() => unsubscribe?.());
+ eventToSign.set(null);
+ reject(new Error("cancelled"));
+ }
+ if (v?.ready) {
+ setTimeout(() => unsubscribe?.());
+ eventToSign.set(null);
+ resolve(v);
+ }
});
+ });
- if (signerValue?.params?.sk) {
- return hexToBytes(signerValue.params.sk);
- }
+ if (signerValue?.params?.sk) {
+ return hexToBytes(signerValue.params.sk);
+ }
- // If extension method was selected
- if (signerValue?.method === 'extension') {
- return null; // Use NIP-07
- }
+ // If extension method was selected
+ if (signerValue?.method === "extension") {
+ return null; // Use NIP-07
+ }
- throw new Error('No signing method available');
-}
+ throw new Error("No signing method available");
+};
// Create a NIP-17 message using a provided sender secret key.
// If wrapPK is specified and different than receiverPK,
@@ -87,11 +104,22 @@ const ensureSigner = async (userPubkey: string): Promise<Uint8Array | null> => {
// If expiryDays is specified, the message will expire in that many days,
// plus a random amount from 0 to 2 for security.
// (expiration time = random creation time + expiryDays + 2 days)
-export const createNIP17MessageSK = (text: string, senderSK: Uint8Array, receiverPK: string, wrapPK: string | null = null, expiryDays: number | null = null) => {
- const rumour = createRumour(text, getPublicKey(senderSK), receiverPK);
- const sealed = sealRumourSK(rumour, senderSK, wrapPK || receiverPK, expiryDays);
- return giftWrap(sealed, wrapPK || receiverPK, expiryDays);
-}
+export const createNIP17MessageSK = (
+ text: string,
+ senderSK: Uint8Array,
+ receiverPK: string,
+ wrapPK: string | null = null,
+ expiryDays: number | null = null,
+) => {
+ const rumour = createRumour(text, getPublicKey(senderSK), receiverPK);
+ const sealed = sealRumourSK(
+ rumour,
+ senderSK,
+ wrapPK || receiverPK,
+ expiryDays,
+ );
+ return giftWrap(sealed, wrapPK || receiverPK, expiryDays);
+};
// Create a NIP-17 message using NIP-07, requiring no sender secret key.
// If wrapPK is specified and different than receiverPK,
@@ -100,241 +128,311 @@ export const createNIP17MessageSK = (text: string, senderSK: Uint8Array, receive
// If expiryDays is specified, the message will expire in that many days,
// plus a random amount from 0 to 2 for security.
// (expiration time = random creation time + expiryDays + 2 days)
-export const createNIP17MessageNIP07 = async (text: string, senderPK: string, receiverPK: string, wrapPK: string | null = null, expiryDays: number | null = null) => {
- const rumour = createRumour(text, senderPK, receiverPK);
- const sealed = await sealRumourNip07(rumour, wrapPK || receiverPK, expiryDays);
- return giftWrap(sealed, wrapPK || receiverPK, expiryDays);
-}
+export const createNIP17MessageNIP07 = async (
+ text: string,
+ senderPK: string,
+ receiverPK: string,
+ wrapPK: string | null = null,
+ expiryDays: number | null = null,
+) => {
+ const rumour = createRumour(text, senderPK, receiverPK);
+ const sealed = await sealRumourNip07(
+ rumour,
+ wrapPK || receiverPK,
+ expiryDays,
+ );
+ return giftWrap(sealed, wrapPK || receiverPK, expiryDays);
+};
const createRumour = (text: string, senderPK: string, receiverPK: string) => {
- const rumour: any = {
- kind: 14, // rumour
- created_at: Math.floor(Date.now() / 1000),
- tags: [["p", receiverPK]],
- pubkey: senderPK,
- content: text
- };
- rumour.id = getEventHash(rumour); // do NOT sign rumours!
- return rumour;
-}
+ const rumour: any = {
+ kind: 14, // rumour
+ created_at: Math.floor(Date.now() / 1000),
+ tags: [["p", receiverPK]],
+ pubkey: senderPK,
+ content: text,
+ };
+ rumour.id = getEventHash(rumour); // do NOT sign rumours!
+ return rumour;
+};
-const sealRumourSK = (rumour: any, senderSK: Uint8Array, receiverPK: string, expiryDays: number | null = null) => {
- const senderSKString = bytesToHex(senderSK);
- const conversationKey = u.getConversationKey(senderSKString, receiverPK);
- const encryptedRumour = encrypt(JSON.stringify(rumour), conversationKey);
+const sealRumourSK = (
+ rumour: any,
+ senderSK: Uint8Array,
+ receiverPK: string,
+ expiryDays: number | null = null,
+) => {
+ const senderSKString = bytesToHex(senderSK);
+ const conversationKey = u.getConversationKey(senderSKString, receiverPK);
+ const encryptedRumour = encrypt(JSON.stringify(rumour), conversationKey);
- const created = randomTimestamp();
- const sealEvent: any = {
- kind: 13, // seal
- created_at: created,
- tags: [],
- pubkey: getPublicKey(senderSK),
- content: encryptedRumour
- }
- if (expiryDays != null) {
- const expiration = created + (expiryDays + 2) * 86400;
- sealEvent.tags.push(["expiration", expiration.toString()]);
- }
+ const created = randomTimestamp();
+ const sealEvent: any = {
+ kind: 13, // seal
+ created_at: created,
+ tags: [],
+ pubkey: getPublicKey(senderSK),
+ content: encryptedRumour,
+ };
+ if (expiryDays != null) {
+ const expiration = created + (expiryDays + 2) * 86400;
+ sealEvent.tags.push(["expiration", expiration.toString()]);
+ }
- return finalizeEvent(sealEvent, senderSK);
-}
+ return finalizeEvent(sealEvent, senderSK);
+};
-const sealRumourNip07 = async (rumour: any, receiverPK: string, expiryDays: number | null = null) => {
- const encryptedRumour = await (window as any).nostr.nip44.encrypt(
- receiverPK, JSON.stringify(rumour));
+const sealRumourNip07 = async (
+ rumour: any,
+ receiverPK: string,
+ expiryDays: number | null = null,
+) => {
+ const encryptedRumour = await (window as any).nostr.nip44.encrypt(
+ receiverPK,
+ JSON.stringify(rumour),
+ );
- const created = randomTimestamp();
- const sealEvent: any = {
- kind: 13, // seal
- created_at: created,
- tags: [],
- content: encryptedRumour
- }
- if (expiryDays != null) {
- const expiration = created + (expiryDays + 2) * 86400;
- sealEvent.tags.push(["expiration", expiration.toString()]);
- }
+ const created = randomTimestamp();
+ const sealEvent: any = {
+ kind: 13, // seal
+ created_at: created,
+ tags: [],
+ content: encryptedRumour,
+ };
+ if (expiryDays != null) {
+ const expiration = created + (expiryDays + 2) * 86400;
+ sealEvent.tags.push(["expiration", expiration.toString()]);
+ }
- return (window as any).nostr.signEvent(sealEvent);
-}
+ return (window as any).nostr.signEvent(sealEvent);
+};
-const giftWrap = (event: any, receiverPK: string, expiryDays: number | null = null) => {
- const secretKey = generateSecretKey();
- const conversationKey = u.getConversationKey(bytesToHex(secretKey), receiverPK);
- const encryptedEvent = encrypt(JSON.stringify(event), conversationKey);
- const created = randomTimestamp();
+const giftWrap = (
+ event: any,
+ receiverPK: string,
+ expiryDays: number | null = null,
+) => {
+ const secretKey = generateSecretKey();
+ const conversationKey = u.getConversationKey(
+ bytesToHex(secretKey),
+ receiverPK,
+ );
+ const encryptedEvent = encrypt(JSON.stringify(event), conversationKey);
+ const created = randomTimestamp();
- const giftWrapEvent: any = {
- kind: 1059, // gift wrap
- created_at: created,
- tags: [["p", receiverPK]],
- pubkey: getPublicKey(secretKey),
- content: encryptedEvent
- }
- if (expiryDays != null) {
- const expiration = created + (expiryDays + 2) * 86400;
- giftWrapEvent.tags.push(["expiration", expiration.toString()]);
- }
+ const giftWrapEvent: any = {
+ kind: 1059, // gift wrap
+ created_at: created,
+ tags: [["p", receiverPK]],
+ pubkey: getPublicKey(secretKey),
+ content: encryptedEvent,
+ };
+ if (expiryDays != null) {
+ const expiration = created + (expiryDays + 2) * 86400;
+ giftWrapEvent.tags.push(["expiration", expiration.toString()]);
+ }
- return finalizeEvent(giftWrapEvent, secretKey);
-}
+ return finalizeEvent(giftWrapEvent, secretKey);
+};
const decryptNIP17MessageNIP07 = async (wrapped: any): Promise<any> => {
- const sealedText = await (window as any).nostr.nip44.decrypt(
- wrapped.pubkey, wrapped.content);
- const sealed = JSON.parse(sealedText);
- const rumourText = await (window as any).nostr.nip44.decrypt(
- sealed.pubkey, sealed.content);
- return JSON.parse(rumourText);
-}
+ const sealedText = await (window as any).nostr.nip44.decrypt(
+ wrapped.pubkey,
+ wrapped.content,
+ );
+ const sealed = JSON.parse(sealedText);
+ const rumourText = await (window as any).nostr.nip44.decrypt(
+ sealed.pubkey,
+ sealed.content,
+ );
+ return JSON.parse(rumourText);
+};
// Decrypts the provided message, addressed to the user.
// If NIP-07 is available, it uses that, otherwise it triggers the signer modal.
export const decrypt = async (wrapped: any, user: any): Promise<any> => {
- const sk = await ensureSigner(user.pubkey);
+ const sk = await ensureSigner(user.pubkey);
- if (sk === null) {
- return decryptNIP17MessageNIP07(wrapped);
- } else {
- return unwrapEvent(wrapped, sk);
- }
-}
+ if (sk === null) {
+ return decryptNIP17MessageNIP07(wrapped);
+ } else {
+ return unwrapEvent(wrapped, sk);
+ }
+};
// Returns the rumours of the messages the provided user has sent or received.
// If the events have an expiration timestamp,
// that timestamp is added to its rumour object as the property `expiration`.
export const getMessageRumours = async (user: any): Promise<any[]> => {
- const preferredRelays = await getPreferredRelays(user.pubkey);
- // Always include default relays to catch messages that might be there
- const relays = Array.from(new Set([...preferredRelays, ...DM_RELAYS_LIST]));
- const wrapped = await pool.querySync(
- relays, { kinds: [1059], "#p": [user.pubkey], limit: DM_FETCH_LIMIT }
- );
+ const preferredRelays = await getPreferredRelays(user.pubkey);
+ // Always include default relays to catch messages that might be there
+ const relays = Array.from(new Set([...preferredRelays, ...DM_RELAYS_LIST]));
+ const wrapped = await pool.querySync(relays, {
+ kinds: [1059],
+ "#p": [user.pubkey],
+ limit: DM_FETCH_LIMIT,
+ });
- let rumours: any[] = [];
- // intentionally decrypting sequentially to avoid having a bunch of popups
- for (const event of wrapped) {
- if (expired(event)) {
- console.log("relay(s) contain expired event", event.id);
- continue;
- }
+ let rumours: any[] = [];
+ // intentionally decrypting sequentially to avoid having a bunch of popups
+ for (const event of wrapped) {
+ if (expired(event)) {
+ console.log("relay(s) contain expired event", event.id);
+ continue;
+ }
- try {
- const rumour: any = await decrypt(event, user);
+ try {
+ const rumour: any = await decrypt(event, user);
- const expires = expiration(event);
- if (expires) {
- rumour.expiration = expires;
- }
+ const expires = expiration(event);
+ if (expires) {
+ rumour.expiration = expires;
+ }
- rumours.push(rumour);
- } catch (e) {
- console.error('Failed to decrypt event:', event.id, e);
- }
+ rumours.push(rumour);
+ } catch (e) {
+ console.error("Failed to decrypt event:", event.id, e);
}
+ }
- return rumours;
-}
+ return rumours;
+};
// Returns the preferred relays of the user with the provided pubkey,
// according to their kind-10050 event.
export const getPreferredRelays = async (pubkey: string): Promise<string[]> => {
- if (relayListCache.has(pubkey))
- return relayListCache.get(pubkey);
+ if (relayListCache.has(pubkey)) return relayListCache.get(pubkey);
- const events = await pool.querySync(
- DM_RELAYS_LIST, { kinds: [10050], limit: 1, authors: [pubkey] }
- );
+ const events = await pool.querySync(DM_RELAYS_LIST, {
+ kinds: [10050],
+ limit: 1,
+ authors: [pubkey],
+ });
- let relays: string[] = [];
- for (const event of events) {
- for (const tag of event.tags) {
- if (tag.length >= 2 && tag[0] == "relay") {
- relays.push(tag[1]);
- }
- }
+ let relays: string[] = [];
+ for (const event of events) {
+ for (const tag of event.tags) {
+ if (tag.length >= 2 && tag[0] == "relay") {
+ relays.push(tag[1]);
+ }
}
+ }
- // Fall back to default DM relays if user hasn't published their preferred relays
- if (relays.length === 0) {
- relays = DM_RELAYS_LIST;
- }
+ // Fall back to default DM relays if user hasn't published their preferred relays
+ if (relays.length === 0) {
+ relays = DM_RELAYS_LIST;
+ }
- relayListCache.set(pubkey, relays);
- return relays;
-}
+ relayListCache.set(pubkey, relays);
+ return relays;
+};
// Publish to a single relay with NIP-42 auth support
-const publishWithAuth = async (relayUrl: string, event: any, sk: Uint8Array | null): Promise<string> => {
- const relay = await Relay.connect(relayUrl);
+const publishWithAuth = async (
+ relayUrl: string,
+ event: any,
+ sk: Uint8Array | null,
+): Promise<string> => {
+ const relay = await Relay.connect(relayUrl);
- const signAuthEvent = async (authEvent: any) => {
- if (sk) {
- return finalizeEvent(authEvent, sk);
- } else if ((window as any).nostr) {
- return await (window as any).nostr.signEvent(authEvent);
- }
- throw new Error('No signing method for auth');
- };
+ const signAuthEvent = async (authEvent: any) => {
+ if (sk) {
+ return finalizeEvent(authEvent, sk);
+ } else if ((window as any).nostr) {
+ return await (window as any).nostr.signEvent(authEvent);
+ }
+ throw new Error("No signing method for auth");
+ };
- try {
- return await relay.publish(event);
- } catch (e: any) {
- // Check if it's an auth error
- if (e?.message?.includes('auth-required')) {
- // Authenticate and retry
- await relay.auth(signAuthEvent);
- return await relay.publish(event);
- }
- throw e;
+ try {
+ return await relay.publish(event);
+ } catch (e: any) {
+ // Check if it's an auth error
+ if (e?.message?.includes("auth-required")) {
+ // Authenticate and retry
+ await relay.auth(signAuthEvent);
+ return await relay.publish(event);
}
+ throw e;
+ }
};
// Store the current sk for use in publish
let currentSk: Uint8Array | null = null;
-const publishToPreferred = async (event: any, pubkey: string, expiryEnabled: boolean): Promise<string> => {
- let preferredRelays = await getPreferredRelays(pubkey);
- if (expiryEnabled) {
- preferredRelays = await relaysSupporting(preferredRelays, [40]);
- }
- if (preferredRelays.length === 0) {
- throw new Error('No relays available to publish to');
- }
+const publishToPreferred = async (
+ event: any,
+ pubkey: string,
+ expiryEnabled: boolean,
+): Promise<string> => {
+ let preferredRelays = await getPreferredRelays(pubkey);
+ if (expiryEnabled) {
+ preferredRelays = await relaysSupporting(preferredRelays, [40]);
+ }
+ if (preferredRelays.length === 0) {
+ throw new Error("No relays available to publish to");
+ }
- const promises = preferredRelays.map(url => publishWithAuth(url, event, currentSk));
- try {
- return await (Promise as any).any(promises);
- } catch (e) {
- const results = await Promise.allSettled(promises);
- console.error('All relay publishes failed:', results);
- throw e;
- }
-}
+ const promises = preferredRelays.map((url) =>
+ publishWithAuth(url, event, currentSk),
+ );
+ try {
+ return await (Promise as any).any(promises);
+ } catch (e) {
+ const results = await Promise.allSettled(promises);
+ console.error("All relay publishes failed:", results);
+ throw e;
+ }
+};
// Sends a NIP-17 message from `user` to `recipient`.
// If expiryDays is set, messages will expire after that many days.
-export const send = async (message: string, user: any, recipient: any, expiryDays?: number) => {
- let event1, event2;
- const sk = await ensureSigner(user.pubkey);
+export const send = async (
+ message: string,
+ user: any,
+ recipient: any,
+ expiryDays?: number,
+) => {
+ let event1, event2;
+ const sk = await ensureSigner(user.pubkey);
- // Store sk for auth handler
- currentSk = sk;
+ // Store sk for auth handler
+ currentSk = sk;
- if (sk === null) {
- // Use NIP-07 extension
- event1 = await createNIP17MessageNIP07(
- message, user.pubkey, recipient.pubkey, recipient.pubkey, expiryDays);
- event2 = await createNIP17MessageNIP07(
- message, user.pubkey, recipient.pubkey, user.pubkey, expiryDays);
- } else {
- event1 = createNIP17MessageSK(
- message, sk, recipient.pubkey, recipient.pubkey, expiryDays);
- event2 = createNIP17MessageSK(
- message, sk, recipient.pubkey, user.pubkey, expiryDays);
- }
+ if (sk === null) {
+ // Use NIP-07 extension
+ event1 = await createNIP17MessageNIP07(
+ message,
+ user.pubkey,
+ recipient.pubkey,
+ recipient.pubkey,
+ expiryDays,
+ );
+ event2 = await createNIP17MessageNIP07(
+ message,
+ user.pubkey,
+ recipient.pubkey,
+ user.pubkey,
+ expiryDays,
+ );
+ } else {
+ event1 = createNIP17MessageSK(
+ message,
+ sk,
+ recipient.pubkey,
+ recipient.pubkey,
+ expiryDays,
+ );
+ event2 = createNIP17MessageSK(
+ message,
+ sk,
+ recipient.pubkey,
+ user.pubkey,
+ expiryDays,
+ );
+ }
- const p1 = publishToPreferred(event1, recipient.pubkey, expiryDays != null);
- const p2 = publishToPreferred(event2, user.pubkey, expiryDays != null);
- return Promise.all([p1, p2]);
-}
+ const p1 = publishToPreferred(event1, recipient.pubkey, expiryDays != null);
+ const p2 = publishToPreferred(event2, user.pubkey, expiryDays != null);
+ return Promise.all([p1, p2]);
+};
diff --git a/src/lib/nip40.ts b/src/lib/nip40.ts
index 030fe0df..760a6680 100644
--- a/src/lib/nip40.ts
+++ b/src/lib/nip40.ts
@@ -1,16 +1,16 @@
// Returns true if the message has an expiration in the past, false otherwise.
export const expired = (event: object): boolean => {
- const expiryTime = expiration(event);
- return expiryTime !== null && Date.now() / 1000 >= expiryTime;
-}
+ const expiryTime = expiration(event);
+ return expiryTime !== null && Date.now() / 1000 >= expiryTime;
+};
// Returns the provided event's expiration time, as a Unix ms integer.
// If it has none, returns null.
export const expiration = (event: object): number | null => {
- for (const tag of event.tags) {
- if (tag[0] == "expiration") {
- return parseInt(tag[1]);
- }
+ for (const tag of event.tags) {
+ if (tag[0] == "expiration") {
+ return parseInt(tag[1]);
}
- return null;
-}
+ }
+ return null;
+};
diff --git a/src/lib/nip44.ts b/src/lib/nip44.ts
index bd7efdfc..e05fc685 100644
--- a/src/lib/nip44.ts
+++ b/src/lib/nip44.ts
@@ -1,135 +1,163 @@
-// Copied from this public-domain code:
-// https://github.com/paulmillr/nip44/blob/main/javascript/index.ts
-// Modified slightly to deal with a dependency issue
-
-import { chacha20 } from '@noble/ciphers/chacha.js';
-import { equalBytes } from '@noble/ciphers/utils.js';
-import { secp256k1 } from '@noble/curves/secp256k1.js';
-import { extract as hkdf_extract, expand as hkdf_expand } from '@noble/hashes/hkdf.js';
-import { hmac } from '@noble/hashes/hmac.js';
-import { sha256 } from '@noble/hashes/sha2.js';
-import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils.js';
-import { base64 } from '@scure/base';
-
-declare const TextDecoder: any;
-
-const decoder = new TextDecoder();
-export const u = {
- minPlaintextSize: 0x0001, // 1b msg => padded to 32b
- maxPlaintextSize: 0xffff, // 65535 (64kb-1) => padded to 64kb
-
- utf8Encode: utf8ToBytes,
- utf8Decode(bytes: Uint8Array) {
- return decoder.decode(bytes);
- },
-
- getConversationKey(privkeyA: string, pubkeyB: string): Uint8Array {
- const sharedX = secp256k1.getSharedSecret(privkeyA, '02' + pubkeyB).subarray(1, 33);
- return hkdf_extract(sha256, sharedX, 'nip44-v2');
- },
-
- getMessageKeys(conversationKey: Uint8Array, nonce: Uint8Array) {
- // ensureBytes(conversationKey, 32);
- // ensureBytes(nonce, 32);
- const keys = hkdf_expand(sha256, conversationKey, nonce, 76);
- return {
- chacha_key: keys.subarray(0, 32),
- chacha_nonce: keys.subarray(32, 44),
- hmac_key: keys.subarray(44, 76),
- };
- },
-
- calcPaddedLen(len: number): number {
- if (!Number.isSafeInteger(len) || len < 1) throw new Error('expected positive integer');
- if (len <= 32) return 32;
- const nextPower = 1 << (Math.floor(Math.log2(len - 1)) + 1);
- const chunk = nextPower <= 256 ? 32 : nextPower / 8;
- return chunk * (Math.floor((len - 1) / chunk) + 1);
- },
-
- writeU16BE(num: number) {
- if (!Number.isSafeInteger(num) || num < u.minPlaintextSize || num > u.maxPlaintextSize)
- throw new Error('invalid plaintext size: must be between 1 and 65535 bytes');
- const arr = new Uint8Array(2);
- new DataView(arr.buffer).setUint16(0, num, false);
- return arr;
- },
-
- pad(plaintext: string): Uint8Array {
- const unpadded = u.utf8Encode(plaintext);
- const unpaddedLen = unpadded.length;
- const prefix = u.writeU16BE(unpaddedLen);
- const suffix = new Uint8Array(u.calcPaddedLen(unpaddedLen) - unpaddedLen);
- return concatBytes(prefix, unpadded, suffix);
- },
-
- unpad(padded: Uint8Array): string {
- const unpaddedLen = new DataView(padded.buffer).getUint16(0);
- const unpadded = padded.subarray(2, 2 + unpaddedLen);
- if (
- unpaddedLen < u.minPlaintextSize ||
- unpaddedLen > u.maxPlaintextSize ||
- unpadded.length !== unpaddedLen ||
- padded.length !== 2 + u.calcPaddedLen(unpaddedLen)
- )
- throw new Error('invalid padding');
- return u.utf8Decode(unpadded);
- },
-
- hmacAad(key: Uint8Array, message: Uint8Array, aad: Uint8Array) {
- if (aad.length !== 32) throw new Error('AAD associated data must be 32 bytes');
- const combined = concatBytes(aad, message);
- return hmac(sha256, key, combined);
- },
-
- // metadata: always 65b (version: 1b, nonce: 32b, max: 32b)
- // plaintext: 1b to 0xffff
- // padded plaintext: 32b to 0xffff
- // ciphertext: 32b+2 to 0xffff+2
- // raw payload: 99 (65+32+2) to 65603 (65+0xffff+2)
- // compressed payload (base64): 132b to 87472b
- decodePayload(payload: string) {
- if (typeof payload !== 'string') throw new Error('payload must be a valid string');
- const plen = payload.length;
- if (plen < 132 || plen > 87472) throw new Error('invalid payload length: ' + plen);
- if (payload[0] === '#') throw new Error('unknown encryption version');
- let data: Uint8Array;
- try {
- data = base64.decode(payload);
- } catch (error) {
- throw new Error('invalid base64: ' + (error as any).message);
- }
- const dlen = data.length;
- if (dlen < 99 || dlen > 65603) throw new Error('invalid data length: ' + dlen);
- const vers = data[0];
- if (vers !== 2) throw new Error('unknown encryption version ' + vers);
- return {
- nonce: data.subarray(1, 33),
- ciphertext: data.subarray(33, -32),
- mac: data.subarray(-32),
- };
- },
-};
-
-export function encrypt(plaintext: string, conversationKey: Uint8Array, nonce = randomBytes(32)): string {
- const { chacha_key, chacha_nonce, hmac_key } = u.getMessageKeys(conversationKey, nonce);
- const padded = u.pad(plaintext);
- const ciphertext = chacha20(chacha_key, chacha_nonce, padded);
- const mac = u.hmacAad(hmac_key, ciphertext, nonce);
- return base64.encode(concatBytes(new Uint8Array([2]), nonce, ciphertext, mac));
-}
-
-export function decrypt(payload: string, conversationKey: Uint8Array): string {
- const { nonce, ciphertext, mac } = u.decodePayload(payload);
- const { chacha_key, chacha_nonce, hmac_key } = u.getMessageKeys(conversationKey, nonce);
- const calculatedMac = u.hmacAad(hmac_key, ciphertext, nonce);
- if (!equalBytes(calculatedMac, mac)) throw new Error('invalid MAC');
- const padded = chacha20(chacha_key, chacha_nonce, ciphertext);
- return u.unpad(padded);
-}
-
-export const v2 = {
- utils: u,
- encrypt,
- decrypt,
-};
+// Copied from this public-domain code:
+// https://github.com/paulmillr/nip44/blob/main/javascript/index.ts
+// Modified slightly to deal with a dependency issue
+
+import { chacha20 } from "@noble/ciphers/chacha.js";
+import { equalBytes } from "@noble/ciphers/utils.js";
+import { secp256k1 } from "@noble/curves/secp256k1.js";
+import {
+ extract as hkdf_extract,
+ expand as hkdf_expand,
+} from "@noble/hashes/hkdf.js";
+import { hmac } from "@noble/hashes/hmac.js";
+import { sha256 } from "@noble/hashes/sha2.js";
+import { concatBytes, randomBytes, utf8ToBytes } from "@noble/hashes/utils.js";
+import { base64 } from "@scure/base";
+
+declare const TextDecoder: any;
+
+const decoder = new TextDecoder();
+export const u = {
+ minPlaintextSize: 0x0001, // 1b msg => padded to 32b
+ maxPlaintextSize: 0xffff, // 65535 (64kb-1) => padded to 64kb
+
+ utf8Encode: utf8ToBytes,
+ utf8Decode(bytes: Uint8Array) {
+ return decoder.decode(bytes);
+ },
+
+ getConversationKey(privkeyA: string, pubkeyB: string): Uint8Array {
+ const sharedX = secp256k1
+ .getSharedSecret(privkeyA, "02" + pubkeyB)
+ .subarray(1, 33);
+ return hkdf_extract(sha256, sharedX, "nip44-v2");
+ },
+
+ getMessageKeys(conversationKey: Uint8Array, nonce: Uint8Array) {
+ // ensureBytes(conversationKey, 32);
+ // ensureBytes(nonce, 32);
+ const keys = hkdf_expand(sha256, conversationKey, nonce, 76);
+ return {
+ chacha_key: keys.subarray(0, 32),
+ chacha_nonce: keys.subarray(32, 44),
+ hmac_key: keys.subarray(44, 76),
+ };
+ },
+
+ calcPaddedLen(len: number): number {
+ if (!Number.isSafeInteger(len) || len < 1)
+ throw new Error("expected positive integer");
+ if (len <= 32) return 32;
+ const nextPower = 1 << (Math.floor(Math.log2(len - 1)) + 1);
+ const chunk = nextPower <= 256 ? 32 : nextPower / 8;
+ return chunk * (Math.floor((len - 1) / chunk) + 1);
+ },
+
+ writeU16BE(num: number) {
+ if (
+ !Number.isSafeInteger(num) ||
+ num < u.minPlaintextSize ||
+ num > u.maxPlaintextSize
+ )
+ throw new Error(
+ "invalid plaintext size: must be between 1 and 65535 bytes",
+ );
+ const arr = new Uint8Array(2);
+ new DataView(arr.buffer).setUint16(0, num, false);
+ return arr;
+ },
+
+ pad(plaintext: string): Uint8Array {
+ const unpadded = u.utf8Encode(plaintext);
+ const unpaddedLen = unpadded.length;
+ const prefix = u.writeU16BE(unpaddedLen);
+ const suffix = new Uint8Array(u.calcPaddedLen(unpaddedLen) - unpaddedLen);
+ return concatBytes(prefix, unpadded, suffix);
+ },
+
+ unpad(padded: Uint8Array): string {
+ const unpaddedLen = new DataView(padded.buffer).getUint16(0);
+ const unpadded = padded.subarray(2, 2 + unpaddedLen);
+ if (
+ unpaddedLen < u.minPlaintextSize ||
+ unpaddedLen > u.maxPlaintextSize ||
+ unpadded.length !== unpaddedLen ||
+ padded.length !== 2 + u.calcPaddedLen(unpaddedLen)
+ )
+ throw new Error("invalid padding");
+ return u.utf8Decode(unpadded);
+ },
+
+ hmacAad(key: Uint8Array, message: Uint8Array, aad: Uint8Array) {
+ if (aad.length !== 32)
+ throw new Error("AAD associated data must be 32 bytes");
+ const combined = concatBytes(aad, message);
+ return hmac(sha256, key, combined);
+ },
+
+ // metadata: always 65b (version: 1b, nonce: 32b, max: 32b)
+ // plaintext: 1b to 0xffff
+ // padded plaintext: 32b to 0xffff
+ // ciphertext: 32b+2 to 0xffff+2
+ // raw payload: 99 (65+32+2) to 65603 (65+0xffff+2)
+ // compressed payload (base64): 132b to 87472b
+ decodePayload(payload: string) {
+ if (typeof payload !== "string")
+ throw new Error("payload must be a valid string");
+ const plen = payload.length;
+ if (plen < 132 || plen > 87472)
+ throw new Error("invalid payload length: " + plen);
+ if (payload[0] === "#") throw new Error("unknown encryption version");
+ let data: Uint8Array;
+ try {
+ data = base64.decode(payload);
+ } catch (error) {
+ throw new Error("invalid base64: " + (error as any).message);
+ }
+ const dlen = data.length;
+ if (dlen < 99 || dlen > 65603)
+ throw new Error("invalid data length: " + dlen);
+ const vers = data[0];
+ if (vers !== 2) throw new Error("unknown encryption version " + vers);
+ return {
+ nonce: data.subarray(1, 33),
+ ciphertext: data.subarray(33, -32),
+ mac: data.subarray(-32),
+ };
+ },
+};
+
+export function encrypt(
+ plaintext: string,
+ conversationKey: Uint8Array,
+ nonce = randomBytes(32),
+): string {
+ const { chacha_key, chacha_nonce, hmac_key } = u.getMessageKeys(
+ conversationKey,
+ nonce,
+ );
+ const padded = u.pad(plaintext);
+ const ciphertext = chacha20(chacha_key, chacha_nonce, padded);
+ const mac = u.hmacAad(hmac_key, ciphertext, nonce);
+ return base64.encode(
+ concatBytes(new Uint8Array([2]), nonce, ciphertext, mac),
+ );
+}
+
+export function decrypt(payload: string, conversationKey: Uint8Array): string {
+ const { nonce, ciphertext, mac } = u.decodePayload(payload);
+ const { chacha_key, chacha_nonce, hmac_key } = u.getMessageKeys(
+ conversationKey,
+ nonce,
+ );
+ const calculatedMac = u.hmacAad(hmac_key, ciphertext, nonce);
+ if (!equalBytes(calculatedMac, mac)) throw new Error("invalid MAC");
+ const padded = chacha20(chacha_key, chacha_nonce, ciphertext);
+ return u.unpad(padded);
+}
+
+export const v2 = {
+ utils: u,
+ encrypt,
+ decrypt,
+};
diff --git a/src/lib/nip51.ts b/src/lib/nip51.ts
index 0210bf8b..7560dd3d 100644
--- a/src/lib/nip51.ts
+++ b/src/lib/nip51.ts
@@ -1,70 +1,81 @@
-import { SimplePool } from 'nostr-tools/pool';
-import { signEvent, encrypt, decrypt } from '$lib/nip07';
+import { SimplePool } from "nostr-tools/pool";
+import { signEvent, encrypt, decrypt } from "$lib/nip07";
const pool = new SimplePool();
-import { PUBLIC_DM_RELAYS } from '$env/static/public';
-const DM_RELAYS_LIST = PUBLIC_DM_RELAYS.split(',');
+import { PUBLIC_DM_RELAYS } from "$env/static/public";
+const DM_RELAYS_LIST = PUBLIC_DM_RELAYS.split(",");
// Mute the provided pubkey, as the provided user.
export const mute = async (user: any, pubkey: string, hide: boolean) => {
- let { shown, hidden } = await getMuteLists(user);
- if (hide) {
- hidden.push(["p", pubkey]);
- } else {
- shown.push(["p", pubkey]);
- }
- await publishMuteList(user, shown, hidden);
-}
+ let { shown, hidden } = await getMuteLists(user);
+ if (hide) {
+ hidden.push(["p", pubkey]);
+ } else {
+ shown.push(["p", pubkey]);
+ }
+ await publishMuteList(user, shown, hidden);
+};
const tagMatches = (tag: string[], pubkey: string): boolean =>
- tag.length >= 2 && tag[0] === "p" && tag[1] === pubkey;
+ tag.length >= 2 && tag[0] === "p" && tag[1] === pubkey;
// Unmute the provided pubkey, as the provided user.
export const unmute = async (user: any, pubkey: string) => {
- let { shown, hidden } = await getMuteLists(user);
- shown = shown.filter((tag: string[]) => !tagMatches(tag, pubkey));
- hidden = hidden.filter((tag: string[]) => !tagMatches(tag, pubkey));
- await publishMuteList(user, shown, hidden);
-}
+ let { shown, hidden } = await getMuteLists(user);
+ shown = shown.filter((tag: string[]) => !tagMatches(tag, pubkey));
+ hidden = hidden.filter((tag: string[]) => !tagMatches(tag, pubkey));
+ await publishMuteList(user, shown, hidden);
+};
// Returns a set of the provided user's muted accounts.
export const mutedAccounts = async (user: any): Promise<Set<string>> => {
- const { shown, hidden } = await getMuteLists(user);
- let muted = new Set<string>();
- for (const tag of shown) {
- if (tag.length >= 2 && tag[0] === "p") {
- muted.add(tag[1]);
- }
+ const { shown, hidden } = await getMuteLists(user);
+ let muted = new Set<string>();
+ for (const tag of shown) {
+ if (tag.length >= 2 && tag[0] === "p") {
+ muted.add(tag[1]);
}
- for (const tag of hidden) {
- if (tag.length >= 2 && tag[0] === "p") {
- muted.add(tag[1]);
- }
+ }
+ for (const tag of hidden) {
+ if (tag.length >= 2 && tag[0] === "p") {
+ muted.add(tag[1]);
}
- return muted;
-}
+ }
+ return muted;
+};
const getMuteLists = async (user: any): Promise<any> => {
- const listEvents = await pool.querySync(
- DM_RELAYS_LIST, { kinds: [10000], "authors": [user.pubkey], limit: 1 }
- );
- if (listEvents.length == 0) {
- return { shown: [], hidden: [] };
- }
+ const listEvents = await pool.querySync(DM_RELAYS_LIST, {
+ kinds: [10000],
+ authors: [user.pubkey],
+ limit: 1,
+ });
+ if (listEvents.length == 0) {
+ return { shown: [], hidden: [] };
+ }
- return {
- shown: listEvents[0].tags,
- hidden: listEvents[0].content == "" ? [] :
- await decrypt(listEvents[0].content, user, user.pubkey)
- };
-}
+ return {
+ shown: listEvents[0].tags,
+ hidden:
+ listEvents[0].content == ""
+ ? []
+ : await decrypt(listEvents[0].content, user, user.pubkey),
+ };
+};
-const publishMuteList = async (user: any, shown: string[], hidden: string[]) => {
- const event = await signEvent({
- kind: 10000,
- created_at: Math.floor(Date.now() / 1000),
- tags: shown,
- content: await encrypt(hidden, user, user.pubkey)
- }, user);
- return Promise.any(pool.publish(DM_RELAYS_LIST, event));
-}
+const publishMuteList = async (
+ user: any,
+ shown: string[],
+ hidden: string[],
+) => {
+ const event = await signEvent(
+ {
+ kind: 10000,
+ created_at: Math.floor(Date.now() / 1000),
+ tags: shown,
+ content: await encrypt(hidden, user, user.pubkey),
+ },
+ user,
+ );
+ return Promise.any(pool.publish(DM_RELAYS_LIST, event));
+};
diff --git a/src/lib/nostr.ts b/src/lib/nostr.ts
index dd283987..50df6408 100644
--- a/src/lib/nostr.ts
+++ b/src/lib/nostr.ts
@@ -1,239 +1,239 @@
import { Buffer } from "buffer";
import { browser } from "$app/environment";
import {
- decrypted,
- eventToSign,
- password as pw,
- passwordPrompt,
- signer as $signer,
+ decrypted,
+ eventToSign,
+ password as pw,
+ passwordPrompt,
+ signer as $signer,
} from "$lib/store";
import { post, wait } from "$lib/utils";
import { bytesToHex } from "@noble/hashes/utils.js";
import type { EventTemplate } from "nostr-tools";
import { get } from "svelte/store";
-let nostrToolsPromise:
- | Promise<typeof import("nostr-tools")>
- | undefined;
+let nostrToolsPromise: Promise<typeof import("nostr-tools")> | undefined;
let nip44Promise: Promise<typeof import("nostr-tools/nip44")> | undefined;
let relayPromise: Promise<typeof import("nostr-tools/relay")> | undefined;
let nip49Promise: Promise<typeof import("nostr-tools/nip49")> | undefined;
-const loadNostrTools = () =>
- (nostrToolsPromise ||= import("nostr-tools"));
+const loadNostrTools = () => (nostrToolsPromise ||= import("nostr-tools"));
const loadNip44 = () => (nip44Promise ||= import("nostr-tools/nip44"));
const loadRelay = () => (relayPromise ||= import("nostr-tools/relay"));
const loadNip49 = () => (nip49Promise ||= import("nostr-tools/nip49"));
type User = {
- [key: string]: any;
+ [key: string]: any;
};
type EncryptParams = {
- message: string;
- recipient: string;
- user: User;
+ message: string;
+ recipient: string;
+ user: User;
};
export const encrypt = async ({ message, recipient, user }: EncryptParams) => {
- const sk = await getPrivateKey(user);
- const { nip04 } = await loadNostrTools();
- return nip04.encrypt(sk, recipient, message);
+ const sk = await getPrivateKey(user);
+ const { nip04 } = await loadNostrTools();
+ return nip04.encrypt(sk, recipient, message);
};
export const decrypt = async ({ event, user }: { event: any; user: any }) => {
- const cache = get(decrypted);
- try {
- let { content, pubkey, id } = event;
- if (cache[id]) return cache[id];
- if (pubkey === user.pubkey) pubkey = event.tags[0][1];
+ const cache = get(decrypted);
+ try {
+ let { content, pubkey, id } = event;
+ if (cache[id]) return cache[id];
+ if (pubkey === user.pubkey) pubkey = event.tags[0][1];
- const { nip04 } = await loadNostrTools();
- const message = await nip04.decrypt(
- await getPrivateKey(user),
- pubkey,
- content,
- );
+ const { nip04 } = await loadNostrTools();
+ const message = await nip04.decrypt(
+ await getPrivateKey(user),
+ pubkey,
+ content,
+ );
- cache[id] = Buffer.from(message).toString("utf8");
- decrypted.set(cache);
+ cache[id] = Buffer.from(message).toString("utf8");
+ decrypted.set(cache);
- return cache[id];
- } catch (e) {
- // console.log(e);
- }
+ return cache[id];
+ } catch {
+ // console.log(e);
+ }
};
export const getPrivateKey = async (user: User): Promise<Uint8Array> => {
- let k;
- if (browser) {
- k = localStorage.getItem("nsec");
- if (k) {
- const { nip19 } = await loadNostrTools();
- return nip19.decode(k).data as Uint8Array;
- }
- }
+ let k;
+ if (browser) {
+ k = localStorage.getItem("nsec");
+ if (k) {
+ const { nip19 } = await loadNostrTools();
+ return nip19.decode(k).data as Uint8Array;
+ }
+ }
- const { nsec } = user;
+ const { nsec } = user;
- if (nsec) {
- const { decrypt: nip49decrypt } = await loadNip49();
- k = nip49decrypt(nsec, await getPassword());
- } else {
- throw new Error("nsec not available");
- }
+ if (nsec) {
+ const { decrypt: nip49decrypt } = await loadNip49();
+ k = nip49decrypt(nsec, await getPassword());
+ } else {
+ throw new Error("nsec not available");
+ }
- const { nip19 } = await loadNostrTools();
- localStorage.setItem("nsec", nip19.nsecEncode(k));
- return k;
+ const { nip19 } = await loadNostrTools();
+ localStorage.setItem("nsec", nip19.nsecEncode(k));
+ return k;
};
const decodeNsec = async (nsec: string): Promise<Uint8Array> => {
- const { nip19 } = await loadNostrTools();
- const { type, data } = nip19.decode(nsec);
- if (type === "nsec" && data instanceof Uint8Array) return data;
- throw new Error("invalid nsec");
+ const { nip19 } = await loadNostrTools();
+ const { type, data } = nip19.decode(nsec);
+ if (type === "nsec" && data instanceof Uint8Array) return data;
+ throw new Error("invalid nsec");
};
export const getNsec = async (user: User) => {
- const { nip19 } = await loadNostrTools();
- return nip19.nsecEncode(await getPrivateKey(user));
+ const { nip19 } = await loadNostrTools();
+ return nip19.nsecEncode(await getPrivateKey(user));
};
export const setNsec = async (user: User, nsec: string) => {
- const { getPublicKey } = await loadNostrTools();
- user.pubkey = getPublicKey(await decodeNsec(nsec));
- user.nsec = await encryptNsec(nsec);
+ const { getPublicKey } = await loadNostrTools();
+ user.pubkey = getPublicKey(await decodeNsec(nsec));
+ user.nsec = await encryptNsec(nsec);
};
export const encryptNsec = async (nsec: string) => {
- const { encrypt: nip49encrypt } = await loadNip49();
- const d = await decodeNsec(nsec);
- return nip49encrypt(d, await getPassword());
+ const { encrypt: nip49encrypt } = await loadNip49();
+ const d = await decodeNsec(nsec);
+ return nip49encrypt(d, await getPassword());
};
export const sign = async (event: any, user?: any) => {
- if (user?.nsec && !get($signer)?.ready) {
- const sk = await getPrivateKey(user);
- const { getPublicKey } = await loadNostrTools();
+ if (user?.nsec && !get($signer)?.ready) {
+ const sk = await getPrivateKey(user);
+ const { getPublicKey } = await loadNostrTools();
- $signer.set({
- method: "nsec",
- ready: true,
- params: { sk: bytesToHex(sk), pk: getPublicKey(sk) },
- });
- }
+ $signer.set({
+ method: "nsec",
+ ready: true,
+ params: { sk: bytesToHex(sk), pk: getPublicKey(sk) },
+ });
+ }
- eventToSign.set(event);
- let unsubscribe;
- const { method, params } = await new Promise((r, j) => {
- unsubscribe = $signer.subscribe((v) => {
- if (v === "cancel") {
- j("cancelled");
- $signer.set(undefined);
- }
- v?.ready && r(v);
- });
- });
- await unsubscribe();
- const signedEvent = await signingMethods[method](event, params);
- eventToSign.set(signedEvent);
- setTimeout(() => eventToSign.set(null), 1000);
- return signedEvent;
+ eventToSign.set(event);
+ let unsubscribe;
+ const { method, params } = await new Promise((r, j) => {
+ unsubscribe = $signer.subscribe((v) => {
+ if (v === "cancel") {
+ j("cancelled");
+ $signer.set(undefined);
+ }
+ v?.ready && r(v);
+ });
+ });
+ await unsubscribe();
+ const signedEvent = await signingMethods[method](event, params);
+ eventToSign.set(signedEvent);
+ setTimeout(() => eventToSign.set(null), 1000);
+ return signedEvent;
};
export const send = (event: EventTemplate) => {
- return post("/events", { event });
+ return post("/events", { event });
};
const getPassword = async (): Promise<string> => {
- if (!get(pw)) passwordPrompt.set(true);
- await wait(() => !!get(pw));
- return get(pw) || "";
+ if (!get(pw)) passwordPrompt.set(true);
+ await wait(() => !!get(pw));
+ return get(pw) || "";
};
export const nostrConnectRelay = "wss://relay.nsec.app";
-const signingMethods: Record<string, (event: any, params: any) => Promise<any>> = {
- async connect(event: any, { sk, pk, pubkey }: any) {
- const id = crypto.randomUUID();
- const signEvent = {
- id,
- method: "sign_event",
- params: [JSON.stringify(event)],
- };
+const signingMethods: Record<
+ string,
+ (event: any, params: any) => Promise<any>
+> = {
+ async connect(event: any, { sk, pk, pubkey }: any) {
+ const id = crypto.randomUUID();
+ const signEvent = {
+ id,
+ method: "sign_event",
+ params: [JSON.stringify(event)],
+ };
- const { nip04, finalizeEvent } = await loadNostrTools();
- const content = await nip04.encrypt(sk, pk, JSON.stringify(signEvent));
+ const { nip04, finalizeEvent } = await loadNostrTools();
+ const content = await nip04.encrypt(sk, pk, JSON.stringify(signEvent));
- const signedSignEvent = finalizeEvent(
- {
- created_at: Math.round(Date.now() / 1000),
- kind: 24133,
- pubkey,
- content,
- tags: [["p", pk]],
- },
- sk,
- );
+ const signedSignEvent = finalizeEvent(
+ {
+ created_at: Math.round(Date.now() / 1000),
+ kind: 24133,
+ pubkey,
+ content,
+ tags: [["p", pk]],
+ },
+ sk,
+ );
- const { Relay } = await loadRelay();
- const relay = await Relay.connect(nostrConnectRelay);
- relay.publish(signedSignEvent);
+ const { Relay } = await loadRelay();
+ const relay = await Relay.connect(nostrConnectRelay);
+ relay.publish(signedSignEvent);
- return new Promise((resolve) => {
- relay.subscribe([{ kinds: [24133], "#p": [pubkey] }], {
- async onevent(event) {
- pk = event.pubkey;
- try {
- let response;
- try {
- const { nip04 } = await loadNostrTools();
- response = JSON.parse(await nip04.decrypt(sk, pk, event.content));
- } catch (e) {
- const { getConversationKey, decrypt: nip44decrypt } =
- await loadNip44();
- const ck = await getConversationKey(sk, pk);
- response = JSON.parse(await nip44decrypt(event.content, ck));
- }
+ return new Promise((resolve) => {
+ relay.subscribe([{ kinds: [24133], "#p": [pubkey] }], {
+ async onevent(event) {
+ pk = event.pubkey;
+ try {
+ let response;
+ try {
+ const { nip04 } = await loadNostrTools();
+ response = JSON.parse(await nip04.decrypt(sk, pk, event.content));
+ } catch {
+ const { getConversationKey, decrypt: nip44decrypt } =
+ await loadNip44();
+ const ck = await getConversationKey(sk, pk);
+ response = JSON.parse(await nip44decrypt(event.content, ck));
+ }
- if (response.id === id) resolve(JSON.parse(response.result));
- } catch (e) {
- console.log("failed to parse nostr connect event", e);
- }
- },
- });
- });
- },
+ if (response.id === id) resolve(JSON.parse(response.result));
+ } catch (e) {
+ console.log("failed to parse nostr connect event", e);
+ }
+ },
+ });
+ });
+ },
- async nsec(event: any, { sk }: any) {
- const { finalizeEvent } = await loadNostrTools();
- return finalizeEvent(event, sk);
- },
+ async nsec(event: any, { sk }: any) {
+ const { finalizeEvent } = await loadNostrTools();
+ return finalizeEvent(event, sk);
+ },
- async extension(event: any) {
- return window.nostr!.signEvent(event);
- },
+ async extension(event: any) {
+ return window.nostr!.signEvent(event);
+ },
- async signer(event: any, { pubkey, sig }: any) {
- event.pubkey = pubkey;
- const { getEventHash } = await loadNostrTools();
- const signedEvent = {
- ...event,
- id: getEventHash(event),
- sig,
- };
- return signedEvent;
- },
+ async signer(event: any, { pubkey, sig }: any) {
+ event.pubkey = pubkey;
+ const { getEventHash } = await loadNostrTools();
+ const signedEvent = {
+ ...event,
+ id: getEventHash(event),
+ sig,
+ };
+ return signedEvent;
+ },
};
// Returns all the keys mentioned in the provided event's 'p' tags.
export const pTagKeys = (event: any): string[] => {
- let keys: string[] = [];
- for (const tag of event.tags) {
- if (tag[0] === "p") {
- keys.push(tag[1]);
- }
- }
- return keys;
-}
+ let keys: string[] = [];
+ for (const tag of event.tags) {
+ if (tag[0] === "p") {
+ keys.push(tag[1]);
+ }
+ }
+ return keys;
+};
diff --git a/src/lib/parse.ts b/src/lib/parse.ts
index 3249b9db..38c32a54 100644
--- a/src/lib/parse.ts
+++ b/src/lib/parse.ts
@@ -4,137 +4,137 @@ import { auth, get, isLiquid, post, sats } from "$lib/utils";
import { redirect } from "@sveltejs/kit";
let validatePromise:
- | Promise<typeof import("bitcoin-address-validation")>
- | undefined;
+ | Promise<typeof import("bitcoin-address-validation")>
+ | undefined;
const loadValidate = () =>
- (validatePromise ||= import("bitcoin-address-validation"));
+ (validatePromise ||= import("bitcoin-address-validation"));
export default async (s, host, cookies) => {
- if (!s) return;
- let t = s.trim();
- let amount;
- let invoice;
- let user;
+ if (!s) return;
+ let t = s.trim();
+ let amount;
+ let invoice;
+ let user;
- if (t.startsWith("ark") || t.startsWith("tark"))
- redirect(307, `/send/ark/${t}`);
+ if (t.startsWith("ark") || t.startsWith("tark"))
+ redirect(307, `/send/ark/${t}`);
- if (t.startsWith("http")) redirect(307, t);
- if (t.startsWith(host)) redirect(307, `http://${t}`);
+ if (t.startsWith("http")) redirect(307, t);
+ if (t.startsWith(host)) redirect(307, `http://${t}`);
- if (t.includes("lightning=")) {
- const url = new URL(t);
- const params = new URLSearchParams(url.search);
- t = params.get("lightning");
- }
+ if (t.includes("lightning=")) {
+ const url = new URL(t);
+ const params = new URLSearchParams(url.search);
+ t = params.get("lightning");
+ }
- if (t.startsWith("lightning:")) t = t.replace("lightning:", "");
- if (t.endsWith(`@${PUBLIC_DOMAIN}`)) t = t.split("@")[0];
- if (t.includes("@") && t.includes(".")) {
- try {
- t = await get(`/encode?address=${t}`);
- } catch (e) {}
- }
+ if (t.startsWith("lightning:")) t = t.replace("lightning:", "");
+ if (t.endsWith(`@${PUBLIC_DOMAIN}`)) t = t.split("@")[0];
+ if (t.includes("@") && t.includes(".")) {
+ try {
+ t = await get(`/encode?address=${t}`);
+ } catch {}
+ }
- if (t.toLowerCase().startsWith("nostr:")) {
- t = t.split(":")[1];
- }
+ if (t.toLowerCase().startsWith("nostr:")) {
+ t = t.split(":")[1];
+ }
- if (["note", "nevent"].some((p) => t.startsWith(p))) redirect(307, `/e/${t}`);
- if (["nprofile", "npub"].some((p) => t.startsWith(p))) redirect(307, `/${t}`);
+ if (["note", "nevent"].some((p) => t.startsWith(p))) redirect(307, `/e/${t}`);
+ if (["nprofile", "npub"].some((p) => t.startsWith(p))) redirect(307, `/${t}`);
- // bitcoin
- if (
- t.toLowerCase().startsWith("bitcoin:") ||
- t.toLowerCase().startsWith("liquidnetwork:")
- ) {
- ({
- address: t,
- options: { amount },
- } = decode(t));
- }
+ // bitcoin
+ if (
+ t.toLowerCase().startsWith("bitcoin:") ||
+ t.toLowerCase().startsWith("liquidnetwork:")
+ ) {
+ ({
+ address: t,
+ options: { amount },
+ } = decode(t));
+ }
- const { validate } = await loadValidate();
- if (validate(t) || isLiquid(t)) {
- let r = `/send/bitcoin/${t}`;
- if (amount) r += `/${Math.round(amount * sats)}`;
- redirect(307, r);
- }
+ const { validate } = await loadValidate();
+ if (validate(t) || isLiquid(t)) {
+ let r = `/send/bitcoin/${t}`;
+ if (amount) r += `/${Math.round(amount * sats)}`;
+ redirect(307, r);
+ }
- // lightning
- if (t.toLowerCase().startsWith("lightning:"))
- t = t.toLowerCase().replace("lightning:", "");
- if (t.toLowerCase().startsWith("lnurl")) redirect(307, `/ln/${t}`);
- if (t.includes(":")) t = t.split(":")[1];
+ // lightning
+ if (t.toLowerCase().startsWith("lightning:"))
+ t = t.toLowerCase().replace("lightning:", "");
+ if (t.toLowerCase().startsWith("lnurl")) redirect(307, `/ln/${t}`);
+ if (t.includes(":")) t = t.split(":")[1];
- if (t.toLowerCase().startsWith("lnr")) {
- const { iid } = await post("/sendinvoice", { invreq: t }, auth(cookies));
- invoice = { id: iid };
- } else if (t.toLowerCase().startsWith("ln")) {
- try {
- const aid = cookies.get("aid");
- invoice = await get(`/invoice/${t}`);
- if (invoice.user.username === "mint") throw new Error("mint payment");
- if (aid && aid !== invoice.aid) throw new Error("cross-account");
- } catch (e) {
- if (t.toLowerCase().startsWith("lno")) {
- const { offer_amount_msat: a } = await get(`/decode/${t}`);
- if (a) ({ invoice: t } = await post("/fetchinvoice", { offer: t }));
- }
- redirect(307, `/send/lightning/${t}`);
- }
- }
+ if (t.toLowerCase().startsWith("lnr")) {
+ const { iid } = await post("/sendinvoice", { invreq: t }, auth(cookies));
+ invoice = { id: iid };
+ } else if (t.toLowerCase().startsWith("ln")) {
+ try {
+ const aid = cookies.get("aid");
+ invoice = await get(`/invoice/${t}`);
+ if (invoice.user.username === "mint") throw new Error("mint payment");
+ if (aid && aid !== invoice.aid) throw new Error("cross-account");
+ } catch {
+ if (t.toLowerCase().startsWith("lno")) {
+ const { offer_amount_msat: a } = await get(`/decode/${t}`);
+ if (a) ({ invoice: t } = await post("/fetchinvoice", { offer: t }));
+ }
+ redirect(307, `/send/lightning/${t}`);
+ }
+ }
- if (invoice) redirect(307, `/invoice/${invoice.id}`);
+ if (invoice) redirect(307, `/invoice/${invoice.id}`);
- if (t.match(/^🥜([\uFE00-\uFE0F]|[\uE0100-\uE01EF]+)$/)) {
- t = Array.from(t)
- .slice(1)
- .map((char) => {
- const codePoint = char.codePointAt(0);
+ if (t.match(/^🥜([\uFE00-\uFE0F]|[\uE0100-\uE01EF]+)$/)) {
+ t = Array.from(t)
+ .slice(1)
+ .map((char) => {
+ const codePoint = char.codePointAt(0);
- // Handle Variation Selectors (VS1-VS16): U+FE00 to U+FE0F
- if (codePoint >= 0xfe00 && codePoint <= 0xfe0f) {
- const byteValue = codePoint - 0xfe00; // Maps FE00->0, FE01->1, ..., FE0F->15
- return String.fromCharCode(byteValue);
- }
+ // Handle Variation Selectors (VS1-VS16): U+FE00 to U+FE0F
+ if (codePoint >= 0xfe00 && codePoint <= 0xfe0f) {
+ const byteValue = codePoint - 0xfe00; // Maps FE00->0, FE01->1, ..., FE0F->15
+ return String.fromCharCode(byteValue);
+ }
- // Handle Variation Selectors Supplement (VS17-VS256): U+E0100 to U+E01EF
- if (codePoint >= 0xe0100 && codePoint <= 0xe01ef) {
- const byteValue = codePoint - 0xe0100 + 16; // Maps E0100->16, E0101->17, ..., E01EF->255
- return String.fromCharCode(byteValue);
- }
- })
- .join("");
- }
+ // Handle Variation Selectors Supplement (VS17-VS256): U+E0100 to U+E01EF
+ if (codePoint >= 0xe0100 && codePoint <= 0xe01ef) {
+ const byteValue = codePoint - 0xe0100 + 16; // Maps E0100->16, E0101->17, ..., E01EF->255
+ return String.fromCharCode(byteValue);
+ }
+ })
+ .join("");
+ }
- if (t.startsWith("cashu")) {
- const { id } = await post("/cash", { token: t });
- redirect(307, `/ecash/${id}`);
- }
+ if (t.startsWith("cashu")) {
+ const { id } = await post("/cash", { token: t });
+ redirect(307, `/ecash/${id}`);
+ }
- if (t.startsWith("creq")) redirect(307, `/send/ecash/${t}`);
+ if (t.startsWith("creq")) redirect(307, `/send/ecash/${t}`);
- // user
- try {
- user = await get(`/users/${t.split("/")[0]}`);
- if (user.anon) user = null;
- } catch (e) {}
+ // user
+ try {
+ user = await get(`/users/${t.split("/")[0]}`);
+ if (user.anon) user = null;
+ } catch {}
- if (user) redirect(307, `/pay/${t}`);
+ if (user) redirect(307, `/pay/${t}`);
- let fund;
- if (t.includes("/fund")) redirect(307, t.substring(t.indexOf("/fund")));
+ let fund;
+ if (t.includes("/fund")) redirect(307, t.substring(t.indexOf("/fund")));
- try {
- fund = await get(`/fund/${t}`);
- } catch (e) {}
+ try {
+ fund = await get(`/fund/${t}`);
+ } catch {}
- if (fund) redirect(307, `/send/fund/${t}`);
+ if (fund) redirect(307, `/send/fund/${t}`);
- try {
- invoice ||= await get(`/invoice/${t}`);
- } catch (e) {}
+ try {
+ invoice ||= await get(`/invoice/${t}`);
+ } catch {}
- if (invoice) redirect(307, `/invoice/${invoice.id}`);
+ if (invoice) redirect(307, `/invoice/${invoice.id}`);
};
diff --git a/src/lib/passwordCache.ts b/src/lib/passwordCache.ts
index 4f764698..a67113e3 100644
--- a/src/lib/passwordCache.ts
+++ b/src/lib/passwordCache.ts
@@ -5,57 +5,62 @@ const PASSWORD_KEY = "walletPassword";
const EXPIRES_KEY = "walletPasswordExpiresAt";
export const rememberForOptions = [
- { label: "Don't remember", ms: 0 },
- { label: "8 hours", ms: 8 * 60 * 60 * 1000 },
- { label: "1 day", ms: 24 * 60 * 60 * 1000 },
- { label: "1 week", ms: 7 * 24 * 60 * 60 * 1000 },
- { label: "1 month", ms: 30 * 24 * 60 * 60 * 1000 },
- { label: "1 year", ms: 365 * 24 * 60 * 60 * 1000 },
+ { label: "Don't remember", ms: 0 },
+ { label: "8 hours", ms: 8 * 60 * 60 * 1000 },
+ { label: "1 day", ms: 24 * 60 * 60 * 1000 },
+ { label: "1 week", ms: 7 * 24 * 60 * 60 * 1000 },
+ { label: "1 month", ms: 30 * 24 * 60 * 60 * 1000 },
+ { label: "1 year", ms: 365 * 24 * 60 * 60 * 1000 },
];
export const defaultRememberForMs = 30 * 24 * 60 * 60 * 1000;
const clearStoredPassword = () => {
- if (!browser) return;
- localStorage.removeItem(PASSWORD_KEY);
- localStorage.removeItem(EXPIRES_KEY);
+ if (!browser) return;
+ localStorage.removeItem(PASSWORD_KEY);
+ localStorage.removeItem(EXPIRES_KEY);
};
const loadStoredPassword = () => {
- if (!browser) return undefined;
- const password = localStorage.getItem(PASSWORD_KEY) || undefined;
- if (!password) return undefined;
- const expiresAtRaw = localStorage.getItem(EXPIRES_KEY);
- const expiresAt = expiresAtRaw ? Number(expiresAtRaw) : undefined;
- if (expiresAt && Date.now() > expiresAt) {
- clearStoredPassword();
- return undefined;
- }
- return password;
+ if (!browser) return undefined;
+ const password = localStorage.getItem(PASSWORD_KEY) || undefined;
+ if (!password) return undefined;
+ const expiresAtRaw = localStorage.getItem(EXPIRES_KEY);
+ const expiresAt = expiresAtRaw ? Number(expiresAtRaw) : undefined;
+ if (expiresAt && Date.now() > expiresAt) {
+ clearStoredPassword();
+ return undefined;
+ }
+ return password;
};
-export const walletPassword = writable<string | undefined>(loadStoredPassword());
+export const walletPassword = writable<string | undefined>(
+ loadStoredPassword(),
+);
-export const rememberWalletPassword = (password: string, durationMs: number) => {
- if (!browser) return;
- if (!durationMs) {
- clearStoredPassword();
- walletPassword.set(undefined);
- return;
- }
- const expiresAt = Date.now() + durationMs;
- localStorage.setItem(PASSWORD_KEY, password);
- localStorage.setItem(EXPIRES_KEY, String(expiresAt));
- walletPassword.set(password);
+export const rememberWalletPassword = (
+ password: string,
+ durationMs: number,
+) => {
+ if (!browser) return;
+ if (!durationMs) {
+ clearStoredPassword();
+ walletPassword.set(undefined);
+ return;
+ }
+ const expiresAt = Date.now() + durationMs;
+ localStorage.setItem(PASSWORD_KEY, password);
+ localStorage.setItem(EXPIRES_KEY, String(expiresAt));
+ walletPassword.set(password);
};
export const forgetWalletPassword = () => {
- clearStoredPassword();
- walletPassword.set(undefined);
+ clearStoredPassword();
+ walletPassword.set(undefined);
};
export const getRememberedWalletPassword = () => {
- const password = loadStoredPassword();
- walletPassword.set(password);
- return password;
+ const password = loadStoredPassword();
+ walletPassword.set(password);
+ return password;
};
diff --git a/src/lib/punks.ts b/src/lib/punks.ts
index 8b7b53df..e5551062 100644
--- a/src/lib/punks.ts
+++ b/src/lib/punks.ts
@@ -1,66 +1,66 @@
export default [
- "6e1f1626a9308abd0b7732fecbc7d49366ced46dead63ef3603fb0f4ec7aa53d",
- "96ea95735b61c48c6f465db8ca0246a62539f34c3a977be6338770d92461da89",
- "8c18c036b257d7b9c5a970a786efa04c320f3cf5afa73fa310a6603744d1ee3a",
- "a7a6187e54e83acdb893d456c8b4ad6877e4738ea9e0d47c9b2070bf2c98a35f",
- "836d72150b6f3033b6220d15ac9d49457d81041a700d12898bd3b8e737b4d265",
- "e3d1c40538a8aed282a430647e3ff16ff94b64b8ed0a7cf427e8b0e2780a9b2a",
- "2155224ef35cfb4bbdb06d982b42bb9b93d97b2bc691c22c804e1759a0a04882",
- "5f6549ad9dd15dd57c204b1a9b60c68c376e77d7d114cfa13219e6a1a71f901f",
- "ba8dd5a7070aee7102d4f8bc83ac497091e2e473a0f448a1c513569167dd0791",
- "4f07e1da3685a858970a9f39300564971e97257f5d37b55a6d9882014aecc421",
- "01aa9348244c4b65de3df758d49c98076e17a109aef04bc211abc77cf42dc51d",
- "080574eb9e0613926e0e5d9b667c132e12965a1b1d47218a0b290a2d55c55d2f",
- "08ce4840184f63a779bcd1f91bbcb56a2e8cba75c3ba2dbd290a559a57e4238e",
- "27caa38c14a85ad64b4c4469a34e61a3965a1cbc4cad9a85707da374301c830d",
- "eccc90741370ba4acc70bc65f4ccc38fddb1327d977c366156018974bc52fe4a",
- "3759946d9e875ac2fc5c51ed850686193bb1e0716219e42efd52f3965993b275",
- "db5fe28b8ddb9ae5e4f5e0e757ee60bc938bffbdfdd38de807939e212d3c5066",
- "3f8f861e663accee51e68b987705ce647a7446b7f118abc94c632e158acbb44f",
- "43bab95a047112dca6705506f916a97252363a20da4a4c0a5ab7b676e11083fc",
- "d3018e45c66dd485697f738ad191af98267e6255d562496dd06b1a662e0c7a88",
- "0ec2e8d07e053d686140857493ece653cde52764a60aa1d25651d81811d44a3d",
- "4595bb092682af7177799464364c047fbfa7ede767336734949bce5d085fce15",
- "6524936709e0719f9e28bf1caac91f86f323d1a79f10ab65a03fa9ea2e6b3270",
- "8ba4736edc68da8e5b21c23af4de1f9131f47778c2a5b2a970a799223a9380b3",
- "2aaba839e8546a8cb6d04a81ff5be83ac4e700ca4491607a16ebd60dc4995750",
- "5f88f7f579ce50d7bcf60d6e9556f77acf361ba54caf3eb61dc4f2ca9c94c6a2",
- "d1ca262272613d7386fecce7f1ad268188940b59ea83fdd1f8d7ab85771e3f43",
- "5211890e4128d8d730802cc75ee105867e8878581d5e72ffc98a0d60027a5398",
- "c1dececcbd7b623bfaa726392512e6ea110db93a0649dd01bf9e559318514f7c",
- "43ce93c9835e983403a01329918d184832c5516e1b0ea4e0878b4c4def042777",
- "c84724a398bda8d20436fb0663d059c196628fa71192bb720fa5adf6137bed9b",
- "f307620a90a36045fa550316d61be4755c1f6c7f3fc81c95fc4d590ac568b2c4",
- "78ae0118856a1f17c13f292628fcf3dccc150c7278578caaceb9f35ca8bc9f55",
- "9d9a9a40c490f05d7dbafde8bf60d68aab7dcb79a7976eef62d1448a4549816b",
- "f6017bec50563dbbb24b979214ae225f96b01dea5b090b174038ff2842a1c6b1",
- "6dbec748ea87e959390b0df429c005fccffe9e672864f555c14c7a407b940870",
- "11701e76233395ebdba8d1d50334d1a0cc3a158f3452260c9a7b9a067b78d753",
- "7354f3829dd05c838aa16d66b00316f7994797927a4d3b594c8af04a2b4ae86a",
- "14d254333c6240fbdacbbbe261f7ae15be2e83bffcf084f4f59f459bae0a4571",
- "7849ac0f31056265efb0110038f86e61effe17b7db836377d88433b73312e65a",
- "9018be885177c675baf9b36978428d841ca30a9185e11de1a31238a08e3ec1af",
- "777c5715de7423597fea6c03c496ea46414f0508cd96e119261f002965beb805",
- "b742b7509a97c2b346ea762f0510e8475b30dc988417f0f146ae00a800c6dd56",
- "7af3029a826e6dfba23ad502901a3632ef4ee047bd45d618b1f106717b0af7ea",
- "82390020b2bca461204cc49b20ee6fec1b93dad476726f377785d04040a4d6c3",
- "6bc2990699be7f610c9f6a49bc7a19b034040727bc3de63fee89a1573fe00673",
- "50c7c2e8e8c7f4aabffb195187a1ac1105f5712859f02d491d794ba2d8498e08",
- "5d1d3769cd141603ce907df5dca159e389ca4d75338eddbe1b7db1c0766cbfd6",
- "4d8ad7a761d068b7ef9b058fb4ba924b276e7a45a3ad47a79b767e08e821e038",
- "d5b5b2167602f7277cdac76cfda8bdad0befcb8ff12df495dfabf40c91d1120c",
- "6f2618272bb0164247e9b0f40b19f020c1c9253510dc658a3cc078d079ef01bf",
- "fd502d5007bfc62a91eb7a34c1ae23b9de4a20cf5e1bbd86dadc421d7083099c",
- "914813583b901044970b9c7d6dcdfde4344339325f5988d0dd681d377f1243fb",
- "85cb7cf0245613d08b3027f879f58a6db645bf0db107242beb856b340ee49ee8",
- "d6dbb18d5df5e92c8c74ee59aa86d19dff25a5d0f9d030e84285a686f86804d7",
- "65309f82cd9f9926a578d09e1ad13909f68a06d02b853760731a4225f7aecef4",
- "433519d13f2b64fe82a90cd054797d4779507fea93c1054bd51c35ba846a0f7f",
- "62a55d4d6e4b6f992a1c1840f041585c957affcdbf875202cf9e1db9def05aeb",
- "e19eb1eeabdeffa04ccd792eb09776690cf2121887b26a7c5d20d66e3e8fd0fb",
- "a80951fc593035abb2dda045a2076d29a30f5cc470e0a20db39c3ec588ed61d8",
- "4f1b39906714266a7a1936bfa8e08c5d891285dd2b188b4bcd4afb0107671004",
- "f46310b2e60727af071d09e759814f52f933688a0ff0995c7da373b381e70005",
- "2cc94821f56d21918ba4f2770d97f280e06fd99f4199167e2f4ddc780f404c8a",
- "698a2ca7a09e88fa6594801da037f4dbce6ae841d1d33736aebcf8f6fe104e01",
+ "6e1f1626a9308abd0b7732fecbc7d49366ced46dead63ef3603fb0f4ec7aa53d",
+ "96ea95735b61c48c6f465db8ca0246a62539f34c3a977be6338770d92461da89",
+ "8c18c036b257d7b9c5a970a786efa04c320f3cf5afa73fa310a6603744d1ee3a",
+ "a7a6187e54e83acdb893d456c8b4ad6877e4738ea9e0d47c9b2070bf2c98a35f",
+ "836d72150b6f3033b6220d15ac9d49457d81041a700d12898bd3b8e737b4d265",
+ "e3d1c40538a8aed282a430647e3ff16ff94b64b8ed0a7cf427e8b0e2780a9b2a",
+ "2155224ef35cfb4bbdb06d982b42bb9b93d97b2bc691c22c804e1759a0a04882",
+ "5f6549ad9dd15dd57c204b1a9b60c68c376e77d7d114cfa13219e6a1a71f901f",
+ "ba8dd5a7070aee7102d4f8bc83ac497091e2e473a0f448a1c513569167dd0791",
+ "4f07e1da3685a858970a9f39300564971e97257f5d37b55a6d9882014aecc421",
+ "01aa9348244c4b65de3df758d49c98076e17a109aef04bc211abc77cf42dc51d",
+ "080574eb9e0613926e0e5d9b667c132e12965a1b1d47218a0b290a2d55c55d2f",
+ "08ce4840184f63a779bcd1f91bbcb56a2e8cba75c3ba2dbd290a559a57e4238e",
+ "27caa38c14a85ad64b4c4469a34e61a3965a1cbc4cad9a85707da374301c830d",
+ "eccc90741370ba4acc70bc65f4ccc38fddb1327d977c366156018974bc52fe4a",
+ "3759946d9e875ac2fc5c51ed850686193bb1e0716219e42efd52f3965993b275",
+ "db5fe28b8ddb9ae5e4f5e0e757ee60bc938bffbdfdd38de807939e212d3c5066",
+ "3f8f861e663accee51e68b987705ce647a7446b7f118abc94c632e158acbb44f",
+ "43bab95a047112dca6705506f916a97252363a20da4a4c0a5ab7b676e11083fc",
+ "d3018e45c66dd485697f738ad191af98267e6255d562496dd06b1a662e0c7a88",
+ "0ec2e8d07e053d686140857493ece653cde52764a60aa1d25651d81811d44a3d",
+ "4595bb092682af7177799464364c047fbfa7ede767336734949bce5d085fce15",
+ "6524936709e0719f9e28bf1caac91f86f323d1a79f10ab65a03fa9ea2e6b3270",
+ "8ba4736edc68da8e5b21c23af4de1f9131f47778c2a5b2a970a799223a9380b3",
+ "2aaba839e8546a8cb6d04a81ff5be83ac4e700ca4491607a16ebd60dc4995750",
+ "5f88f7f579ce50d7bcf60d6e9556f77acf361ba54caf3eb61dc4f2ca9c94c6a2",
+ "d1ca262272613d7386fecce7f1ad268188940b59ea83fdd1f8d7ab85771e3f43",
+ "5211890e4128d8d730802cc75ee105867e8878581d5e72ffc98a0d60027a5398",
+ "c1dececcbd7b623bfaa726392512e6ea110db93a0649dd01bf9e559318514f7c",
+ "43ce93c9835e983403a01329918d184832c5516e1b0ea4e0878b4c4def042777",
+ "c84724a398bda8d20436fb0663d059c196628fa71192bb720fa5adf6137bed9b",
+ "f307620a90a36045fa550316d61be4755c1f6c7f3fc81c95fc4d590ac568b2c4",
+ "78ae0118856a1f17c13f292628fcf3dccc150c7278578caaceb9f35ca8bc9f55",
+ "9d9a9a40c490f05d7dbafde8bf60d68aab7dcb79a7976eef62d1448a4549816b",
+ "f6017bec50563dbbb24b979214ae225f96b01dea5b090b174038ff2842a1c6b1",
+ "6dbec748ea87e959390b0df429c005fccffe9e672864f555c14c7a407b940870",
+ "11701e76233395ebdba8d1d50334d1a0cc3a158f3452260c9a7b9a067b78d753",
+ "7354f3829dd05c838aa16d66b00316f7994797927a4d3b594c8af04a2b4ae86a",
+ "14d254333c6240fbdacbbbe261f7ae15be2e83bffcf084f4f59f459bae0a4571",
+ "7849ac0f31056265efb0110038f86e61effe17b7db836377d88433b73312e65a",
+ "9018be885177c675baf9b36978428d841ca30a9185e11de1a31238a08e3ec1af",
+ "777c5715de7423597fea6c03c496ea46414f0508cd96e119261f002965beb805",
+ "b742b7509a97c2b346ea762f0510e8475b30dc988417f0f146ae00a800c6dd56",
+ "7af3029a826e6dfba23ad502901a3632ef4ee047bd45d618b1f106717b0af7ea",
+ "82390020b2bca461204cc49b20ee6fec1b93dad476726f377785d04040a4d6c3",
+ "6bc2990699be7f610c9f6a49bc7a19b034040727bc3de63fee89a1573fe00673",
+ "50c7c2e8e8c7f4aabffb195187a1ac1105f5712859f02d491d794ba2d8498e08",
+ "5d1d3769cd141603ce907df5dca159e389ca4d75338eddbe1b7db1c0766cbfd6",
+ "4d8ad7a761d068b7ef9b058fb4ba924b276e7a45a3ad47a79b767e08e821e038",
+ "d5b5b2167602f7277cdac76cfda8bdad0befcb8ff12df495dfabf40c91d1120c",
+ "6f2618272bb0164247e9b0f40b19f020c1c9253510dc658a3cc078d079ef01bf",
+ "fd502d5007bfc62a91eb7a34c1ae23b9de4a20cf5e1bbd86dadc421d7083099c",
+ "914813583b901044970b9c7d6dcdfde4344339325f5988d0dd681d377f1243fb",
+ "85cb7cf0245613d08b3027f879f58a6db645bf0db107242beb856b340ee49ee8",
+ "d6dbb18d5df5e92c8c74ee59aa86d19dff25a5d0f9d030e84285a686f86804d7",
+ "65309f82cd9f9926a578d09e1ad13909f68a06d02b853760731a4225f7aecef4",
+ "433519d13f2b64fe82a90cd054797d4779507fea93c1054bd51c35ba846a0f7f",
+ "62a55d4d6e4b6f992a1c1840f041585c957affcdbf875202cf9e1db9def05aeb",
+ "e19eb1eeabdeffa04ccd792eb09776690cf2121887b26a7c5d20d66e3e8fd0fb",
+ "a80951fc593035abb2dda045a2076d29a30f5cc470e0a20db39c3ec588ed61d8",
+ "4f1b39906714266a7a1936bfa8e08c5d891285dd2b188b4bcd4afb0107671004",
+ "f46310b2e60727af071d09e759814f52f933688a0ff0995c7da373b381e70005",
+ "2cc94821f56d21918ba4f2770d97f280e06fd99f4199167e2f4ddc780f404c8a",
+ "698a2ca7a09e88fa6594801da037f4dbce6ae841d1d33736aebcf8f6fe104e01",
];
diff --git a/src/lib/random.ts b/src/lib/random.ts
index 36dde0cc..605471ac 100644
--- a/src/lib/random.ts
+++ b/src/lib/random.ts
@@ -1,20 +1,20 @@
import {
- NumberDictionary,
- animals,
- colors,
- uniqueNamesGenerator,
+ NumberDictionary,
+ animals,
+ colors,
+ uniqueNamesGenerator,
} from "unique-names-generator";
export const randomName = () =>
- uniqueNamesGenerator({
- dictionaries: [animals, NumberDictionary.generate({ min: 10, max: 99 })],
- length: 2,
- separator: "",
- });
+ uniqueNamesGenerator({
+ dictionaries: [animals, NumberDictionary.generate({ min: 10, max: 99 })],
+ length: 2,
+ separator: "",
+ });
export const randomPassword = () =>
- uniqueNamesGenerator({
- dictionaries: [colors, NumberDictionary.generate({ min: 100, max: 999 })],
- length: 2,
- separator: "",
- });
+ uniqueNamesGenerator({
+ dictionaries: [colors, NumberDictionary.generate({ min: 100, max: 999 })],
+ length: 2,
+ separator: "",
+ });
diff --git a/src/lib/rates.ts b/src/lib/rates.ts
index 96aaeac2..5d55550d 100644
--- a/src/lib/rates.ts
+++ b/src/lib/rates.ts
@@ -4,10 +4,10 @@ let last;
let rates;
export default async function getRates() {
- if (!rates || Date.now() - last > 60000) {
- rates = await get("/rates");
- last = Date.now();
- }
+ if (!rates || Date.now() - last > 60000) {
+ rates = await get("/rates");
+ last = Date.now();
+ }
- return rates;
+ return rates;
}
diff --git a/src/lib/samples.ts b/src/lib/samples.ts
index ae7ef6d5..172efca0 100644
--- a/src/lib/samples.ts
+++ b/src/lib/samples.ts
@@ -1,52 +1,52 @@
import { PUBLIC_COINOS_URL, PUBLIC_DOMAIN } from "$env/static/public";
const api = PUBLIC_DOMAIN.includes("localhost")
- ? `${PUBLIC_COINOS_URL}`
- : `https://${PUBLIC_DOMAIN}/api`;
+ ? `${PUBLIC_COINOS_URL}`
+ : `https://${PUBLIC_DOMAIN}/api`;
export default {
- me: `curl "${api}/me" -H "content-type: application/json"`,
- update: `curl "${api}/user" -H "content-type: application/json" -d '{
+ me: `curl "${api}/me" -H "content-type: application/json"`,
+ update: `curl "${api}/user" -H "content-type: application/json" -d '{
"username": "demo",
}'`,
- register: `curl "${api}/register" -H "content-type: application/json" -d '{
+ register: `curl "${api}/register" -H "content-type: application/json" -d '{
"user": {
"username": "demo",
"password": "hunter2"
}
}'`,
- login: `curl "${api}/login" -H "content-type: application/json" -d '{
+ login: `curl "${api}/login" -H "content-type: application/json" -d '{
"username": "demo",
"password": "hunter2"
}'`,
- payments: `curl "${api}/payments"
+ payments: `curl "${api}/payments"
-H "content-type: application/json"
-H "Authorization: Bearer $token"
`,
- lightning: `curl "${api}/payments"
+ lightning: `curl "${api}/payments"
-H "content-type: application/json"
-H "Authorization: Bearer $token"
-d '{
"payreq": "lnbc1pj94d8fsp5n77k340ps4m9jn7kp8he8yynpddvurv6mcsrrqpnq5l2jxdxzlwqpp5m96pqhc5nrlk8cqsu9ufdxxa43sarp8vwf9egvm2pg9nl0zu9r8qdq2vdhkjmn0wvxqztgcqpjrzjqwhmav82kntsppmkp8jp4vg4h9nns78tsy8mg7ve4lq5txrkp0h56zlarvqqdtcqqsqqqqqqqqqqp6cq9q9qyysgqzxlypywzphyujm3ga5j5csfcmqvlnae0fgnvymkaaw94eeg7py5rqzysyjkr3ev2snq63qpsc69vf54adkd0szvmvwt5cuadjnuy95sq4xfa40"
}'`,
- bitcoin: `curl "${api}/bitcoin/send"
+ bitcoin: `curl "${api}/bitcoin/send"
-H "content-type: application/json"
-H "Authorization: Bearer $token"
-d '{
"amount": 5000,
"address": "bc1q3unh97w4rmelflrm2hvdwz37d8kray3vn4d5ca"
}'`,
- internal: `curl "${api}/send"
+ internal: `curl "${api}/send"
-H "content-type: application/json"
-H "Authorization: Bearer $token"
-d '{
"amount": 5000,
"username": "alice"
}'`,
- rates: `curl "${api}/rates"
+ rates: `curl "${api}/rates"
-H "content-type: application/json"
}'`,
- lightningInvoice: `curl "${api}/invoice"
+ lightningInvoice: `curl "${api}/invoice"
-H "content-type: application/json"
-H "Authorization: Bearer $token"
-d '{
@@ -55,7 +55,7 @@ export default {
"type": "lightning"
}
}'`,
- bitcoinAddress: `curl "${api}/invoice"
+ bitcoinAddress: `curl "${api}/invoice"
-H "content-type: application/json"
-H "Authorization: Bearer $token"
-d '{
@@ -64,7 +64,7 @@ export default {
"type": "bitcoin"
}
}'`,
- webhook: `curl "${api}/invoice"
+ webhook: `curl "${api}/invoice"
-H "content-type: application/json"
-H "Authorization: Bearer $token"
-d '{
@@ -74,9 +74,9 @@ export default {
"secret": "webhooksecret"
}
}'`,
- fetchInvoice: `curl "${api}/invoice/bc1qmhfk9stzffhd9umzmld92vff7zg3mdlh7rvvaj"
+ fetchInvoice: `curl "${api}/invoice/bc1qmhfk9stzffhd9umzmld92vff7zg3mdlh7rvvaj"
-H "content-type: application/json"'`,
- invoiceResponse: `{
+ invoiceResponse: `{
"amount":3141
"tip":0
"type":"bitcoin"
@@ -89,5 +89,5 @@ export default {
"received":0
"created":1677537428134
}`,
- socketAuth: `echo '{"type":"login","data":{"username":"user","password":"password"}}' | websocat -n https://coinos.io/ws`,
+ socketAuth: `echo '{"type":"login","data":{"username":"user","password":"password"}}' | websocat -n https://coinos.io/ws`,
};
diff --git a/src/lib/settings.ts b/src/lib/settings.ts
index 6f4d3f62..a3b875c0 100644
--- a/src/lib/settings.ts
+++ b/src/lib/settings.ts
@@ -2,28 +2,28 @@ import { auth, fd, post } from "$lib/utils";
import { fail } from "@sveltejs/kit";
type Error = {
- message: string;
+ message: string;
};
export default async ({ cookies, request }) => {
- const form = await fd(request);
+ const form = await fd(request);
- if (form.tab === "account") {
- form.notify = form.notify === "on";
- form.push = form.push === "on";
- form.nip5 = form.nip5 === "on";
- form.prompt = form.prompt === "on";
- form.autowithdraw = form.autowithdraw === "on";
- }
+ if (form.tab === "account") {
+ form.notify = form.notify === "on";
+ form.push = form.push === "on";
+ form.nip5 = form.nip5 === "on";
+ form.prompt = form.prompt === "on";
+ form.autowithdraw = form.autowithdraw === "on";
+ }
- try {
- const { user } = await post("/user", form, auth(cookies));
+ try {
+ const { user } = await post("/user", form, auth(cookies));
- if (user.language) cookies.set("lang", user.language, { path: "/" });
+ if (user.language) cookies.set("lang", user.language, { path: "/" });
- return { user, success: true };
- } catch (e) {
- const { message } = e as Error;
- return fail(400, { message });
- }
+ return { user, success: true };
+ } catch (e) {
+ const { message } = e as Error;
+ return fail(400, { message });
+ }
};
diff --git a/src/lib/socket.ts b/src/lib/socket.ts
index b87cc165..e40eb39b 100644
--- a/src/lib/socket.ts
+++ b/src/lib/socket.ts
@@ -12,57 +12,57 @@ let token;
export const auth = () => token && send("login", token);
export const send = async (type, data) => {
- try {
- await wait(() => socket?.readyState === 1, 1000, 10);
- } catch (e) {
- const { message } = e as Error;
- if (message === "timeout") reconnectToWebsocket();
- }
+ try {
+ await wait(() => socket?.readyState === 1, 1000, 10);
+ } catch (e) {
+ const { message } = e as Error;
+ if (message === "timeout") reconnectToWebsocket();
+ }
- await wait(() => socket?.readyState === 1, 1000, 10);
- socket.send(JSON.stringify({ type, data }));
+ await wait(() => socket?.readyState === 1, 1000, 10);
+ socket.send(JSON.stringify({ type, data }));
};
export const messages = (data) => ({
- id() {
- last.set(Date.now());
- },
- invoice() {
- invoice.set(data);
- },
- event() {
- event.set(data);
- },
+ id() {
+ last.set(Date.now());
+ },
+ invoice() {
+ invoice.set(data);
+ },
+ event() {
+ event.set(data);
+ },
- request() {
- request.set(data);
- invalidate("app:invoice");
- },
+ request() {
+ request.set(data);
+ invalidate("app:invoice");
+ },
- async payment() {
- const { amount, confirmed, iid } = data;
- invalidate("app:user");
- invalidate("app:invoice");
- invalidate("app:payments");
+ async payment() {
+ const { amount, confirmed, iid } = data;
+ invalidate("app:user");
+ invalidate("app:invoice");
+ invalidate("app:payments");
- const {
- url: { pathname },
- } = get(page);
+ const {
+ url: { pathname },
+ } = get(page);
- const username = cookies.get("username");
+ const username = cookies.get("username");
- if (amount > 0) {
- if (
- pathname.includes(`/${username}`) ||
- pathname.includes("/receive") ||
- pathname.includes("/invoice")
- ) {
- if (!get(navigating)) goto(`/invoice/${iid}`);
- } else success(`${confirmed ? "Received" : "Detected"} ⚡️${s(amount)}!`);
- }
- },
+ if (amount > 0) {
+ if (
+ pathname.includes(`/${username}`) ||
+ pathname.includes("/receive") ||
+ pathname.includes("/invoice")
+ ) {
+ if (!get(navigating)) goto(`/invoice/${iid}`);
+ } else success(`${confirmed ? "Received" : "Detected"} ⚡️${s(amount)}!`);
+ }
+ },
- connected: auth,
+ connected: auth,
});
const initialReconnectDelay = 1000;
@@ -72,51 +72,51 @@ let currentReconnectDelay = initialReconnectDelay;
let connecting;
export function connect(t) {
- if (connecting) return;
- connecting = true;
- setTimeout(() => {
- connecting = false;
- }, 5000);
+ if (connecting) return;
+ connecting = true;
+ setTimeout(() => {
+ connecting = false;
+ }, 5000);
- token = t;
+ token = t;
- if (socket) return auth();
+ if (socket) return auth();
- socket = new WebSocket(PUBLIC_SOCKET);
- socket.addEventListener("open", onWebsocketOpen);
- socket.addEventListener("close", onWebsocketClose);
- socket.addEventListener("message", onWebsocketMessage);
+ socket = new WebSocket(PUBLIC_SOCKET);
+ socket.addEventListener("open", onWebsocketOpen);
+ socket.addEventListener("close", onWebsocketClose);
+ socket.addEventListener("message", onWebsocketMessage);
}
export function close() {
- if (socket) socket.close();
+ if (socket) socket.close();
}
async function onWebsocketMessage(msg) {
- const { type, data } = JSON.parse(msg.data);
- if (get(navigating)) await sleep(2000);
+ const { type, data } = JSON.parse(msg.data);
+ if (get(navigating)) await sleep(2000);
- if (messages(data)[type]) messages(data)[type]();
+ if (messages(data)[type]) messages(data)[type]();
}
function onWebsocketOpen() {
- currentReconnectDelay = initialReconnectDelay;
- send("heartbeat", token);
+ currentReconnectDelay = initialReconnectDelay;
+ send("heartbeat", token);
}
function onWebsocketClose() {
- socket = null;
- setTimeout(
- () => {
- reconnectToWebsocket();
- },
- currentReconnectDelay + Math.floor(Math.random() * 3000),
- );
+ socket = null;
+ setTimeout(
+ () => {
+ reconnectToWebsocket();
+ },
+ currentReconnectDelay + Math.floor(Math.random() * 3000),
+ );
}
function reconnectToWebsocket() {
- if (currentReconnectDelay < maxReconnectDelay) {
- currentReconnectDelay *= 2;
- }
- connect(token);
+ if (currentReconnectDelay < maxReconnectDelay) {
+ currentReconnectDelay *= 2;
+ }
+ connect(token);
}
diff --git a/src/lib/store.ts b/src/lib/store.ts
index 7cd17aec..e14d107f 100644
--- a/src/lib/store.ts
+++ b/src/lib/store.ts
@@ -2,73 +2,73 @@ import { browser } from "$app/environment";
import { writable } from "svelte/store";
export const persistSession = (key, defaultValue = undefined) => {
- const s = writable(
- browser &&
- sessionStorage.getItem(key) &&
- sessionStorage.getItem(key) !== "undefined"
- ? JSON.parse(sessionStorage.getItem(key) || "")
- : defaultValue,
- );
+ const s = writable(
+ browser &&
+ sessionStorage.getItem(key) &&
+ sessionStorage.getItem(key) !== "undefined"
+ ? JSON.parse(sessionStorage.getItem(key) || "")
+ : defaultValue,
+ );
- s.subscribe((v) => {
- browser && sessionStorage.setItem(key, JSON.stringify(v));
- });
+ s.subscribe((v) => {
+ browser && sessionStorage.setItem(key, JSON.stringify(v));
+ });
- return s;
+ return s;
};
export const persistLocal = (key, defaultValue: any = undefined) => {
- const s = writable(
- browser &&
- localStorage.getItem(key) &&
- localStorage.getItem(key) !== "undefined"
- ? JSON.parse(localStorage.getItem(key) || "")
- : defaultValue,
- );
+ const s = writable(
+ browser &&
+ localStorage.getItem(key) &&
+ localStorage.getItem(key) !== "undefined"
+ ? JSON.parse(localStorage.getItem(key) || "")
+ : defaultValue,
+ );
- s.subscribe((v) => {
- try {
- if (browser) {
- if (v) {
- localStorage.setItem(key, JSON.stringify(v));
- } else {
- localStorage.removeItem(key);
- }
- }
- } catch (e) {
- console.log("problem setting key", v);
- console.log(e);
- }
- });
+ s.subscribe((v) => {
+ try {
+ if (browser) {
+ if (v) {
+ localStorage.setItem(key, JSON.stringify(v));
+ } else {
+ localStorage.removeItem(key);
+ }
+ }
+ } catch (e) {
+ console.log("problem setting key", v);
+ console.log(e);
+ }
+ });
- return s;
+ return s;
};
export const persistLocalValue = (key, defaultValue: any = undefined) => {
- const s = writable(
- browser &&
- localStorage.getItem(key) &&
- localStorage.getItem(key) !== "undefined"
- ? JSON.parse(localStorage.getItem(key) || "")
- : defaultValue,
- );
+ const s = writable(
+ browser &&
+ localStorage.getItem(key) &&
+ localStorage.getItem(key) !== "undefined"
+ ? JSON.parse(localStorage.getItem(key) || "")
+ : defaultValue,
+ );
- s.subscribe((v) => {
- try {
- if (browser) {
- if (v === undefined) {
- localStorage.removeItem(key);
- } else {
- localStorage.setItem(key, JSON.stringify(v));
- }
- }
- } catch (e) {
- console.log("problem setting key", v);
- console.log(e);
- }
- });
+ s.subscribe((v) => {
+ try {
+ if (browser) {
+ if (v === undefined) {
+ localStorage.removeItem(key);
+ } else {
+ localStorage.setItem(key, JSON.stringify(v));
+ }
+ }
+ } catch (e) {
+ console.log("problem setting key", v);
+ console.log(e);
+ }
+ });
- return s;
+ return s;
};
export const account = writable();
diff --git a/src/lib/ticket.ts b/src/lib/ticket.ts
index 0d93f390..48ed1a66 100644
--- a/src/lib/ticket.ts
+++ b/src/lib/ticket.ts
@@ -3,29 +3,29 @@ import { env } from "$env/dynamic/private";
import tickets from "$lib/tickets";
export const createTicket = async (cookies, username) => {
- const { ticket } = await get("/ticket");
+ const { ticket } = await get("/ticket");
- const { address } = await post(
- `/${username}/invoice`,
- { invoice: { network: "liquid" }, user: { username } },
- auth(cookies),
- );
+ const { address } = await post(
+ `/${username}/invoice`,
+ { invoice: { network: "liquid" }, user: { username } },
+ auth(cookies),
+ );
- const { asset } = await post(
- "/assets",
- {
- address,
- name: `Launch Party Ticket ${ticket}`,
- ticker: "LPT",
- precision: 0,
- asset_amount: "1",
- token_amount: 0,
- domain: "coinos.io",
- filename: tickets[ticket],
- },
- { authorization: `Bearer ${env.LAUNCH}` },
- );
+ const { asset } = await post(
+ "/assets",
+ {
+ address,
+ name: `Launch Party Ticket ${ticket}`,
+ ticker: "LPT",
+ precision: 0,
+ asset_amount: "1",
+ token_amount: 0,
+ domain: "coinos.io",
+ filename: tickets[ticket],
+ },
+ { authorization: `Bearer ${env.LAUNCH}` },
+ );
- await post("/ticket", { asset }, { authorization: `Bearer ${env.LAUNCH}` });
- return asset;
+ await post("/ticket", { asset }, { authorization: `Bearer ${env.LAUNCH}` });
+ return asset;
};
diff --git a/src/lib/tickets.ts b/src/lib/tickets.ts
index f37fad78..1f8cbdac 100644
--- a/src/lib/tickets.ts
+++ b/src/lib/tickets.ts
@@ -1,52 +1,52 @@
export default [
- "QmPbFkiXNuaQyfwD4W9GcXP7qWaNd6wFd9KCnJir5mes1J",
- "QmPVjvbaMxSJRpDxMLYTdUWQbknej1vYLckBQ2pAgxZgJN",
- "QmVasycSFzCYhyXrcvTE7mN3d6W78RFnrhzg5cRF6Dp6Zq",
- "QmTmnhjpTBjZdeHwXgT9Dftc3uWMW73qsZpZ5ECrTyiKKZ",
- "QmWDBiyVPyFkzUrEdvy7DSJe1rBQpene1m6quKAtyjBP6a",
- "QmcxxzQZwgG7SE9QbHNvoTiCi6Dhs9rrz9roqvxm2JQA88",
- "Qmd9ThEpXLLHimEzRUWEFrAGLSw5PAHzn2SmwfCnjNLwWt",
- "Qmc7pWw1yxc2ARBWitDMYiyPkWK61NsXXU8oYAQwNAvcx1",
- "QmX9Hgw1rMFUgBBuq8QfZZDzLjN5xCED19fwFdDeDDqwmr",
- "QmconCAyNyaagqKvvfKCbG7Eu8c6vCtkv2ivvxMoFN9YwA",
- "QmWbFmvViy5gx4JdD5sw5YJETet4ZQcXtYgijYnrsnSZ3a",
- "QmP6QiiJwoqkduZMqdd5e5htHZ4aYHCsvEu7WEEUknKYa5",
- "QmefHk8hvBUHsuKugntok5u75Q8i886cnPwsj4LZZB6uWg",
- "QmdrSDK5ByYHxc88hfDu8hQgK5XYFpXSfPorUXdyTbE437",
- "QmZ6ZycjRzYpsjnLm1Lfgk1fXXDdhcYRkyfmqYJZVGwADy",
- "QmSkBxS5yLPjB2VPs89oQRZANsoyodK6s8s52E8GKXr2zt",
- "QmSn8TCPwq3UZ1Z4eyko76CvQo2H2Hyef1NGboDQN22m5i",
- "QmP6CTH5zrKMzZwtjy7VaK9NXiq4Gw53kmuRjUbJjeovoe",
- "QmdwT4yvz9Xf8g3t6irKRoh3Wa7MwRQrcUJckQxyQe1Bc5",
- "QmcwuHH3bD5JExqjDvKqRQ3V5xEVGqs94qPNVDzaCqJa5a",
- "QmRfVduHwn8sXwSoyfgZpxSi8L7EjP4LMp6fmijo9VApDp",
- "Qmc4TGj1Ahbg9HJ9WDXDm2bXsNkT4zRDp8Wdw9wqqdEwuH",
- "QmY8uQiHMWA8ztf2wAurNFW7eiTHQEUiLTQ2vGceMwVqz3",
- "QmXiXbaD12F4mkbUSQcKhWshkPU4oi29cUawZGnPk86VVU",
- "QmZaiAGjvBZBw4suz33s9JQNzY2Kshean2mQQPCa6gKMQe",
- "Qme8bDSdzX7tanPYf672xXW8vPdkLkeSLqLzHJqidRXsYJ",
- "QmRJirjuUKgh2AMyBF4jNquaXHFPBtDJf3gKFC614hX7qu",
- "QmdRoSKSgabcrpekNBPB6T47RBtJnCETYE8ry2a3P37MUe",
- "QmXoqArruHSghrUGW35vVQHPknBG5Cj939iWWHHXU3B616",
- "QmYCxtDuoHLMAFhNpifUsLU9ptAeFKwyxSiSEJhDQLMZtN",
- "QmQZQBw8wgzUsrmVDEejdePK23AEJbk9aUracvxLPyHz2x",
- "QmSsbu7U3i62AQKuYqfisRo5E6nqdMZfHRKWYJgouaXDxD",
- "QmaP4adVQuWyqtN4QCXeYXj4nr19Je4EqsaTG1zM9ywqFS",
- "QmPPsvgfXsiN3wNqdAwHAhQVpw6SZWNpry9Na6R2Xb7zXY",
- "QmR7z6wQmcfZUvRsVK83GRRboyvwZEDQujLwumMY33nAit",
- "QmQXgkgorDLPUyQpFbW8aLUW6jioQjUKamtXw5UuNCttDg",
- "QmcwYBeXt1BPfdSrtSrXFpWctRZ7RDHvmKTttC5hPPTSzG",
- "QmTUexZnw3xPy3YVdUsqJALwTvy2qFax5b3QpJVMvTCJQb",
- "QmNLkYXFUvctpfeeDTZxruZE2f834EUnYMM6JJUeTcUB4J",
- "QmT7544UaTPgKFYMCpPE4unXphNGX71BLZBPKLkxAijSmj",
- "QmTpVR4dqX1QV4fae5pZ1ACtRcANHwEWCLwpMXFmwE95Sm",
- "Qmdfyps5fW8d8wstXMfpvpei2DTmaADBWgmxLT7e7uan4P",
- "QmUSK8byipYoG3H97QpmPrHw9vYrvHygyhkiM81XmaKyxS",
- "QmQUGRvp96LTw5i8Ly9f5LoMvUpeQHCLGNqC88KV74wEHK",
- "Qma693TFLh9Z1MeBFzhPHi6Yk4tmTcZY2jogwJ8cPkU2B4",
- "QmNu8FyMpmkFdbCXGRVG8mKJtMvvCoLP8LNzFsxBQwxQeS",
- "QmWZHE87PKujL1MGLuFgP3D12o5GQxYbvbeTRgr12f1XMu",
- "QmcZJn8gPGd3poS9yuiNJh6iP4cn9uy5fnypky71r4sSSw",
- "QmTwrAF4E9ucpdnrUZFj6EfrQamGv7Qyn7ceSx1DUQYceC",
- "QmexssxU5iTXypgNqrmrQwMDJnTNxyZUxor5wDLnwb5TZp",
+ "QmPbFkiXNuaQyfwD4W9GcXP7qWaNd6wFd9KCnJir5mes1J",
+ "QmPVjvbaMxSJRpDxMLYTdUWQbknej1vYLckBQ2pAgxZgJN",
+ "QmVasycSFzCYhyXrcvTE7mN3d6W78RFnrhzg5cRF6Dp6Zq",
+ "QmTmnhjpTBjZdeHwXgT9Dftc3uWMW73qsZpZ5ECrTyiKKZ",
+ "QmWDBiyVPyFkzUrEdvy7DSJe1rBQpene1m6quKAtyjBP6a",
+ "QmcxxzQZwgG7SE9QbHNvoTiCi6Dhs9rrz9roqvxm2JQA88",
+ "Qmd9ThEpXLLHimEzRUWEFrAGLSw5PAHzn2SmwfCnjNLwWt",
+ "Qmc7pWw1yxc2ARBWitDMYiyPkWK61NsXXU8oYAQwNAvcx1",
+ "QmX9Hgw1rMFUgBBuq8QfZZDzLjN5xCED19fwFdDeDDqwmr",
+ "QmconCAyNyaagqKvvfKCbG7Eu8c6vCtkv2ivvxMoFN9YwA",
+ "QmWbFmvViy5gx4JdD5sw5YJETet4ZQcXtYgijYnrsnSZ3a",
+ "QmP6QiiJwoqkduZMqdd5e5htHZ4aYHCsvEu7WEEUknKYa5",
+ "QmefHk8hvBUHsuKugntok5u75Q8i886cnPwsj4LZZB6uWg",
+ "QmdrSDK5ByYHxc88hfDu8hQgK5XYFpXSfPorUXdyTbE437",
+ "QmZ6ZycjRzYpsjnLm1Lfgk1fXXDdhcYRkyfmqYJZVGwADy",
+ "QmSkBxS5yLPjB2VPs89oQRZANsoyodK6s8s52E8GKXr2zt",
+ "QmSn8TCPwq3UZ1Z4eyko76CvQo2H2Hyef1NGboDQN22m5i",
+ "QmP6CTH5zrKMzZwtjy7VaK9NXiq4Gw53kmuRjUbJjeovoe",
+ "QmdwT4yvz9Xf8g3t6irKRoh3Wa7MwRQrcUJckQxyQe1Bc5",
+ "QmcwuHH3bD5JExqjDvKqRQ3V5xEVGqs94qPNVDzaCqJa5a",
+ "QmRfVduHwn8sXwSoyfgZpxSi8L7EjP4LMp6fmijo9VApDp",
+ "Qmc4TGj1Ahbg9HJ9WDXDm2bXsNkT4zRDp8Wdw9wqqdEwuH",
+ "QmY8uQiHMWA8ztf2wAurNFW7eiTHQEUiLTQ2vGceMwVqz3",
+ "QmXiXbaD12F4mkbUSQcKhWshkPU4oi29cUawZGnPk86VVU",
+ "QmZaiAGjvBZBw4suz33s9JQNzY2Kshean2mQQPCa6gKMQe",
+ "Qme8bDSdzX7tanPYf672xXW8vPdkLkeSLqLzHJqidRXsYJ",
+ "QmRJirjuUKgh2AMyBF4jNquaXHFPBtDJf3gKFC614hX7qu",
+ "QmdRoSKSgabcrpekNBPB6T47RBtJnCETYE8ry2a3P37MUe",
+ "QmXoqArruHSghrUGW35vVQHPknBG5Cj939iWWHHXU3B616",
+ "QmYCxtDuoHLMAFhNpifUsLU9ptAeFKwyxSiSEJhDQLMZtN",
+ "QmQZQBw8wgzUsrmVDEejdePK23AEJbk9aUracvxLPyHz2x",
+ "QmSsbu7U3i62AQKuYqfisRo5E6nqdMZfHRKWYJgouaXDxD",
+ "QmaP4adVQuWyqtN4QCXeYXj4nr19Je4EqsaTG1zM9ywqFS",
+ "QmPPsvgfXsiN3wNqdAwHAhQVpw6SZWNpry9Na6R2Xb7zXY",
+ "QmR7z6wQmcfZUvRsVK83GRRboyvwZEDQujLwumMY33nAit",
+ "QmQXgkgorDLPUyQpFbW8aLUW6jioQjUKamtXw5UuNCttDg",
+ "QmcwYBeXt1BPfdSrtSrXFpWctRZ7RDHvmKTttC5hPPTSzG",
+ "QmTUexZnw3xPy3YVdUsqJALwTvy2qFax5b3QpJVMvTCJQb",
+ "QmNLkYXFUvctpfeeDTZxruZE2f834EUnYMM6JJUeTcUB4J",
+ "QmT7544UaTPgKFYMCpPE4unXphNGX71BLZBPKLkxAijSmj",
+ "QmTpVR4dqX1QV4fae5pZ1ACtRcANHwEWCLwpMXFmwE95Sm",
+ "Qmdfyps5fW8d8wstXMfpvpei2DTmaADBWgmxLT7e7uan4P",
+ "QmUSK8byipYoG3H97QpmPrHw9vYrvHygyhkiM81XmaKyxS",
+ "QmQUGRvp96LTw5i8Ly9f5LoMvUpeQHCLGNqC88KV74wEHK",
+ "Qma693TFLh9Z1MeBFzhPHi6Yk4tmTcZY2jogwJ8cPkU2B4",
+ "QmNu8FyMpmkFdbCXGRVG8mKJtMvvCoLP8LNzFsxBQwxQeS",
+ "QmWZHE87PKujL1MGLuFgP3D12o5GQxYbvbeTRgr12f1XMu",
+ "QmcZJn8gPGd3poS9yuiNJh6iP4cn9uy5fnypky71r4sSSw",
+ "QmTwrAF4E9ucpdnrUZFj6EfrQamGv7Qyn7ceSx1DUQYceC",
+ "QmexssxU5iTXypgNqrmrQwMDJnTNxyZUxor5wDLnwb5TZp",
];
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 19277ec1..827f96aa 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -1,23 +1,23 @@
export interface Message {
- author: User;
- created_at: number;
- recipient: User;
+ author: User;
+ created_at: number;
+ recipient: User;
}
export interface Note {
- created_at: number;
- seen: number;
+ created_at: number;
+ seen: number;
}
export interface User {
- id: string;
+ id: string;
}
export interface Invoice {
- aid: string;
- id?: string;
- memoPrompt?: boolean;
- memo?: string;
- rate: number;
- type?: string;
+ aid: string;
+ id?: string;
+ memoPrompt?: boolean;
+ memo?: string;
+ rate: number;
+ type?: string;
}
diff --git a/src/lib/upload.ts b/src/lib/upload.ts
index 6d9d4230..aaeec007 100644
--- a/src/lib/upload.ts
+++ b/src/lib/upload.ts
@@ -1,18 +1,18 @@
export const upload = async (file, type, progress) => {
- const url = `/api/upload/${type}`;
- const formData = new FormData();
- formData.append("file", file);
+ const url = `/api/upload/${type}`;
+ const formData = new FormData();
+ formData.append("file", file);
- return new Promise((resolve, reject) => {
- const ajax = new XMLHttpRequest();
- ajax.addEventListener("load", () => {
- if (ajax.readyState === ajax.DONE) {
- if (ajax.status === 200) resolve(ajax.response);
- else reject(new Error(`Upload failed: ${ajax.response}`));
- }
- });
- ajax.upload.addEventListener("progress", progress, false);
- ajax.open("POST", url);
- ajax.send(formData);
- });
+ return new Promise((resolve, reject) => {
+ const ajax = new XMLHttpRequest();
+ ajax.addEventListener("load", () => {
+ if (ajax.readyState === ajax.DONE) {
+ if (ajax.status === 200) resolve(ajax.response);
+ else reject(new Error(`Upload failed: ${ajax.response}`));
+ }
+ });
+ ajax.upload.addEventListener("progress", progress, false);
+ ajax.open("POST", url);
+ ajax.send(formData);
+ });
};
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 45afb90b..ebec6caa 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -6,16 +6,16 @@ import { bytesToHex } from "@noble/hashes/utils.js";
import { fail as svelteFail, redirect } from "@sveltejs/kit";
import { toast } from "@zerodevx/svelte-toast";
import {
- differenceInDays,
- differenceInHours,
- differenceInMinutes,
- differenceInSeconds,
+ differenceInDays,
+ differenceInHours,
+ differenceInMinutes,
+ differenceInSeconds,
} from "date-fns";
import { get as getStore } from "svelte/store";
export function scroll(section: any) {
- if (getStore(page).url.pathname !== "/") goto("/");
- setTimeout(() => section.scrollIntoView({ behavior: "smooth" }), 500);
+ if (getStore(page).url.pathname !== "/") goto("/");
+ setTimeout(() => section.scrollIntoView({ behavior: "smooth" }), 500);
}
const base = browser ? "" : PUBLIC_COINOS_URL;
@@ -23,523 +23,530 @@ const base = browser ? "" : PUBLIC_COINOS_URL;
let getIp: (() => string | undefined) | undefined;
export const setIpGetter = (fn: () => string | undefined) => {
- getIp = fn;
+ getIp = fn;
};
const ipHeaders = (): Record<string, string> => {
- const ip = getIp?.();
- return ip ? { "cf-connecting-ip": ip } : {};
+ const ip = getIp?.();
+ return ip ? { "cf-connecting-ip": ip } : {};
};
export const punk = (k: string) =>
- `${Math.floor((parseInt(k.slice(-2), 16) / 256) * 64) + 1}.webp`;
+ `${Math.floor((parseInt(k.slice(-2), 16) / 256) * 64) + 1}.webp`;
export const g = (url: string, fetch: any, headers: object) =>
- fetch(base + url, { headers: { ...ipHeaders(), ...headers } })
- .then((r: Response) => r.text())
- .then((body: string) => {
- try {
- return JSON.parse(body);
- } catch (e) {
- throw new Error(body);
- }
- });
+ fetch(base + url, { headers: { ...ipHeaders(), ...headers } })
+ .then((r: Response) => r.text())
+ .then((body: string) => {
+ try {
+ return JSON.parse(body);
+ } catch {
+ throw new Error(body);
+ }
+ });
export const get = (url: string, headers = {}) => {
- return fetch(base + url, { headers: { ...ipHeaders(), ...headers } })
- .then(async (r) => {
- if (r.ok) return r.text();
- throw new Error(await r.text());
- })
- .then((body) => {
- try {
- return JSON.parse(body);
- } catch (e) {
- return body;
- }
- });
+ return fetch(base + url, { headers: { ...ipHeaders(), ...headers } })
+ .then(async (r) => {
+ if (r.ok) return r.text();
+ throw new Error(await r.text());
+ })
+ .then((body) => {
+ try {
+ return JSON.parse(body);
+ } catch {
+ return body;
+ }
+ });
};
export const post = async (
- url: string,
- body: object,
- headers: HeadersInit | undefined = undefined,
+ url: string,
+ body: object,
+ headers: HeadersInit | undefined = undefined,
) => {
- headers = {
- "content-type": "application/json",
- accept: "application/json",
- ...ipHeaders(),
- ...headers,
- };
+ headers = {
+ "content-type": "application/json",
+ accept: "application/json",
+ ...ipHeaders(),
+ ...headers,
+ };
- const response = await fetch(base + url, {
- method: "POST",
- body: JSON.stringify(body),
- headers,
- })
- .then(async (r) => {
- if (r.ok) return r.text();
- const text = await r.text();
+ const response = await fetch(base + url, {
+ method: "POST",
+ body: JSON.stringify(body),
+ headers,
+ })
+ .then(async (r) => {
+ if (r.ok) return r.text();
+ const text = await r.text();
- let message;
- try {
- ({ message } = JSON.parse(text));
- } catch (e) {}
+ let message;
+ try {
+ ({ message } = JSON.parse(text));
+ } catch {}
- if (message) throw new Error(message);
- throw new Error(text);
- })
- .then((body) => {
- let json;
- try {
- json = JSON.parse(body);
- } catch (e) {
- throw new Error(body);
- }
+ if (message) throw new Error(message);
+ throw new Error(text);
+ })
+ .then((body) => {
+ let json;
+ try {
+ json = JSON.parse(body);
+ } catch {
+ throw new Error(body);
+ }
- if (json.error instanceof Error) throw json.error;
- if (json instanceof Error) throw body;
- if (json.name === "Error") throw new Error(json.message);
+ if (json.error instanceof Error) throw json.error;
+ if (json instanceof Error) throw body;
+ if (json.name === "Error") throw new Error(json.message);
- return json;
- });
+ return json;
+ });
- return response;
+ return response;
};
export const copy = (text: string) => {
- navigator.clipboard.writeText(text);
- success("Copied!");
+ navigator.clipboard.writeText(text);
+ success("Copied!");
};
export const copyNoNewlines = (text: string) => {
- const stripped = text.replace(/\n/g, " ").replace(/\s+/g, " ");
- navigator.clipboard.writeText(stripped);
- success("Copied!");
+ const stripped = text.replace(/\n/g, " ").replace(/\s+/g, " ");
+ navigator.clipboard.writeText(stripped);
+ success("Copied!");
};
let recent: string[] = [];
export const success = (m: string, clear = true) => {
- if (recent.includes(m)) return;
- recent.push(m);
- setTimeout(() => (recent = []), 5000);
- if (clear) toast.pop();
- toast.push(m, {
- theme: {
- "--toastBarBackground": "#F5F7FA",
- },
- });
+ if (recent.includes(m)) return;
+ recent.push(m);
+ setTimeout(() => (recent = []), 5000);
+ if (clear) toast.pop();
+ toast.push(m, {
+ theme: {
+ "--toastBarBackground": "#F5F7FA",
+ },
+ });
};
export const warning = (m: string, clear = true) => {
- if (clear) toast.pop();
- toast.push(m, {
- theme: {
- "--toastBarBackground": "#FFCE22",
- },
- });
+ if (clear) toast.pop();
+ toast.push(m, {
+ theme: {
+ "--toastBarBackground": "#FFCE22",
+ },
+ });
};
export const fail = (m: string, clear = true) => {
- if (clear) toast.pop();
- toast.push(m, {
- theme: {
- "--toastBarBackground": "#E93535",
- },
- });
+ if (clear) toast.pop();
+ toast.push(m, {
+ theme: {
+ "--toastBarBackground": "#E93535",
+ },
+ });
};
export const info = (m: string) => {
- toast.pop();
- toast.push(m, {
- theme: {
- "--toastBarBackground": "white",
- },
- });
+ toast.pop();
+ toast.push(m, {
+ theme: {
+ "--toastBarBackground": "white",
+ },
+ });
};
export const login = async (
- user: {
- username: string;
- password: string;
- token?: string;
- challenge?: string;
- recaptcha?: string;
- },
- cookies,
- ip: string,
- host?: string,
- extraHeaders?: Record<string, string>,
+ user: {
+ username: string;
+ password: string;
+ token?: string;
+ challenge?: string;
+ recaptcha?: string;
+ },
+ cookies,
+ ip: string,
+ host?: string,
+ extraHeaders?: Record<string, string>,
) => {
- const maxAge = 380 * 24 * 60 * 60;
+ const maxAge = 380 * 24 * 60 * 60;
- const headers: Record<string, string> = {
- "content-type": "application/json",
- accept: "application/json",
- "cf-connecting-ip": ip,
- ...extraHeaders,
- };
- if (host) headers["x-forwarded-host"] = host;
+ const headers: Record<string, string> = {
+ "content-type": "application/json",
+ accept: "application/json",
+ "cf-connecting-ip": ip,
+ ...extraHeaders,
+ };
+ if (host) headers["x-forwarded-host"] = host;
- const res = await fetch(`${base}/login`, {
- method: "POST",
- body: JSON.stringify(user),
- headers,
- });
+ const res = await fetch(`${base}/login`, {
+ method: "POST",
+ body: JSON.stringify(user),
+ headers,
+ });
- const text = await res.text();
+ const text = await res.text();
- if (res.status === 401) {
- if (text.startsWith("2fa")) throw new Error("2fa");
- throw new Error(text);
- }
+ if (res.status === 401) {
+ if (text.startsWith("2fa")) throw new Error("2fa");
+ throw new Error(text);
+ }
- const { user: u, token } = JSON.parse(text);
- if (!token) throw new Error("Login failed");
+ const { user: u, token } = JSON.parse(text);
+ if (!token) throw new Error("Login failed");
- const expires = new Date();
- expires.setSeconds(expires.getSeconds() + maxAge);
+ const expires = new Date();
+ expires.setSeconds(expires.getSeconds() + maxAge);
- const opts = { path: "/", expires };
- if (u.language) cookies.set("lang", u.language, opts);
- cookies.set("username", user.username, opts);
- cookies.set("token", token, opts);
- if (u.nsec) {
- try {
- const { decrypt } = await import("nostr-tools/nip49");
- cookies.set("sk", bytesToHex(decrypt(u.nsec, user.password)), {
- ...opts,
- httpOnly: false,
- sameSite: "lax",
- });
- } catch (e) {}
- }
+ const opts = { path: "/", expires };
+ if (u.language) cookies.set("lang", u.language, opts);
+ cookies.set("username", user.username, opts);
+ cookies.set("token", token, opts);
+ if (u.nsec) {
+ try {
+ const { decrypt } = await import("nostr-tools/nip49");
+ cookies.set("sk", bytesToHex(decrypt(u.nsec, user.password)), {
+ ...opts,
+ httpOnly: false,
+ sameSite: "lax",
+ });
+ } catch {}
+ }
};
export const auth = (cookies) => ({
- authorization: `Bearer ${cookies.get("token")}`,
+ authorization: `Bearer ${cookies.get("token")}`,
});
export const isInvalidTokenError = (e: unknown) => {
- const message = (e as Error)?.message || "";
- if (!message) return false;
- const normalized = message.toLowerCase();
- return (
- normalized.includes("jwt") ||
- normalized.includes("invalid token") ||
- normalized.includes("token expired") ||
- normalized.includes("expired token") ||
- normalized.includes("unauthorized") ||
- normalized.includes("not authorized")
- );
+ const message = (e as Error)?.message || "";
+ if (!message) return false;
+ const normalized = message.toLowerCase();
+ return (
+ normalized.includes("jwt") ||
+ normalized.includes("invalid token") ||
+ normalized.includes("token expired") ||
+ normalized.includes("expired token") ||
+ normalized.includes("unauthorized") ||
+ normalized.includes("not authorized")
+ );
};
export const btc = (fiat: number, rate: number): number =>
- Math.round((fiat * sats) / rate);
+ Math.round((fiat * sats) / rate);
export const toFiat = (amount: number, rate: number): number =>
- (amount * rate) / sats;
+ (amount * rate) / sats;
export const fd = async (req: Request): Promise<any> => {
- const obj: Record<string, any> = Object.fromEntries(await req.formData());
- for (const k in obj)
- (obj[k] === "undefined" && (obj[k] = undefined)) ||
- (obj[k] === "false" && (obj[k] = false));
- return obj;
+ const obj: Record<string, any> = Object.fromEntries(await req.formData());
+ for (const k in obj)
+ (obj[k] === "undefined" && (obj[k] = undefined)) ||
+ (obj[k] === "false" && (obj[k] = false));
+ return obj;
};
const map = {
- en: {
- CAD: "en-CA",
- USD: "en-US",
- GBP: "en-GB",
- AUD: "en-AU",
- EUR: "en-IE", // Eurozone English (Ireland)
- NZD: "en-NZ",
- JPY: "en-JP", // English in Japan
- INR: "en-IN", // English in India
- ZAR: "en-ZA", // South Africa
- },
- fr: {
- CAD: "fr-CA", // French Canada
- EUR: "fr-FR", // France
- CHF: "fr-CH", // French Switzerland
- XOF: "fr-SN", // Francophone West Africa
- },
- de: {
- EUR: "de-DE", // Germany
- CHF: "de-CH", // German Switzerland
- USD: "de-US", // German for the US
- },
- es: {
- EUR: "es-ES", // Spain
- USD: "es-US", // Spanish for the US
- MXN: "es-MX", // Mexico
- ARS: "es-AR", // Argentina
- CLP: "es-CL", // Chile
- },
- pt: {
- EUR: "pt-PT", // Portugal
- BRL: "pt-BR", // Brazil
- },
- zh: {
- CNY: "zh-CN", // Simplified Chinese (Mainland China)
- HKD: "zh-HK", // Traditional Chinese (Hong Kong)
- TWD: "zh-TW", // Traditional Chinese (Taiwan)
- USD: "zh-US", // Chinese for the US
- },
- ja: {
- JPY: "ja-JP", // Japan
- USD: "ja-US", // Japanese for the US
- },
- ru: {
- RUB: "ru-RU", // Russia
- USD: "ru-US", // Russian for the US
- },
- el: {
- EUR: "el-GR", // Greece
- },
- fa: {
- IRR: "fa-IR", // Iran
- },
- // Global currency mappings (fallbacks)
- CAD: "en-CA",
- USD: "en-US",
- GBP: "en-GB",
- AUD: "en-AU",
- EUR: "en-IE",
- NZD: "en-NZ",
- JPY: "ja-JP",
- INR: "en-IN",
- ZAR: "en-ZA",
- CHF: "de-CH",
- MXN: "es-MX",
- BRL: "pt-BR",
- CNY: "zh-CN",
- HKD: "zh-HK",
- TWD: "zh-TW",
- RUB: "ru-RU",
- IRR: "fa-IR",
+ en: {
+ CAD: "en-CA",
+ USD: "en-US",
+ GBP: "en-GB",
+ AUD: "en-AU",
+ EUR: "en-IE", // Eurozone English (Ireland)
+ NZD: "en-NZ",
+ JPY: "en-JP", // English in Japan
+ INR: "en-IN", // English in India
+ ZAR: "en-ZA", // South Africa
+ },
+ fr: {
+ CAD: "fr-CA", // French Canada
+ EUR: "fr-FR", // France
+ CHF: "fr-CH", // French Switzerland
+ XOF: "fr-SN", // Francophone West Africa
+ },
+ de: {
+ EUR: "de-DE", // Germany
+ CHF: "de-CH", // German Switzerland
+ USD: "de-US", // German for the US
+ },
+ es: {
+ EUR: "es-ES", // Spain
+ USD: "es-US", // Spanish for the US
+ MXN: "es-MX", // Mexico
+ ARS: "es-AR", // Argentina
+ CLP: "es-CL", // Chile
+ },
+ pt: {
+ EUR: "pt-PT", // Portugal
+ BRL: "pt-BR", // Brazil
+ },
+ zh: {
+ CNY: "zh-CN", // Simplified Chinese (Mainland China)
+ HKD: "zh-HK", // Traditional Chinese (Hong Kong)
+ TWD: "zh-TW", // Traditional Chinese (Taiwan)
+ USD: "zh-US", // Chinese for the US
+ },
+ ja: {
+ JPY: "ja-JP", // Japan
+ USD: "ja-US", // Japanese for the US
+ },
+ ru: {
+ RUB: "ru-RU", // Russia
+ USD: "ru-US", // Russian for the US
+ },
+ el: {
+ EUR: "el-GR", // Greece
+ },
+ fa: {
+ IRR: "fa-IR", // Iran
+ },
+ // Global currency mappings (fallbacks)
+ CAD: "en-CA",
+ USD: "en-US",
+ GBP: "en-GB",
+ AUD: "en-AU",
+ EUR: "en-IE",
+ NZD: "en-NZ",
+ JPY: "ja-JP",
+ INR: "en-IN",
+ ZAR: "en-ZA",
+ CHF: "de-CH",
+ MXN: "es-MX",
+ BRL: "pt-BR",
+ CNY: "zh-CN",
+ HKD: "zh-HK",
+ TWD: "zh-TW",
+ RUB: "ru-RU",
+ IRR: "fa-IR",
};
export const loc = (user) => {
- if (!user) return;
- const { currency: c, language: l } = user;
- return map[l]?.[c] || map[c];
+ if (!user) return;
+ const { currency: c, language: l } = user;
+ return map[l]?.[c] || map[c];
};
export const f = (
- s: number,
- currency: string,
- locale = "en-US",
- minimumFractionDigits = 2,
- maximumFractionDigits = 2,
+ s: number,
+ currency: string,
+ locale = "en-US",
+ minimumFractionDigits = 2,
+ maximumFractionDigits = 2,
) => {
- const l = typeof locale === "function" ? locale() : locale || "en-US";
- const cur = currency || "USD";
- const region = l.length >= 2 ? l.slice(-2) : "";
- const currencyDisplay =
- region && cur.startsWith(region) ? "narrowSymbol" : "symbol";
- return new Intl.NumberFormat(l, {
- style: "currency",
- currency: cur,
- currencyDisplay,
- minimumFractionDigits,
- maximumFractionDigits,
- }).format(s);
+ const l = typeof locale === "function" ? locale() : locale || "en-US";
+ const cur = currency || "USD";
+ const region = l.length >= 2 ? l.slice(-2) : "";
+ const currencyDisplay =
+ region && cur.startsWith(region) ? "narrowSymbol" : "symbol";
+ return new Intl.NumberFormat(l, {
+ style: "currency",
+ currency: cur,
+ currencyDisplay,
+ minimumFractionDigits,
+ maximumFractionDigits,
+ }).format(s);
};
export const s = (s: number, locale = "en-US"): string => {
- const l = typeof locale === "function" ? locale() : locale || "en-US";
- return new Intl.NumberFormat(l, { maximumFractionDigits: 0 }).format(s);
+ const l = typeof locale === "function" ? locale() : locale || "en-US";
+ return new Intl.NumberFormat(l, { maximumFractionDigits: 0 }).format(s);
};
export const sat = (s: number): string => `${si(Math.abs(s)).toString()}`;
export const si = (
- n: number,
- minimumFractionDigits = 1,
- maximumFractionDigits = 2,
+ n: number,
+ minimumFractionDigits = 1,
+ maximumFractionDigits = 2,
) => {
- const units = ["", "K", "M", "G", "T", "P", "E", "Z"];
+ const units = ["", "K", "M", "G", "T", "P", "E", "Z"];
- let s = n;
- let d = 0;
- if (s >= 1000) {
- d = Math.floor(Math.log10(s) / 3); // Calculate the unit index
- s = n / 1000 ** d; // Scale the number to the correct unit
- }
+ let s = n;
+ let d = 0;
+ if (s >= 1000) {
+ d = Math.floor(Math.log10(s) / 3); // Calculate the unit index
+ s = n / 1000 ** d; // Scale the number to the correct unit
+ }
- return (
- new Intl.NumberFormat("en-US", {
- minimumFractionDigits,
- maximumFractionDigits,
- }).format(s) + units[d]
- );
+ return (
+ new Intl.NumberFormat("en-US", {
+ minimumFractionDigits,
+ maximumFractionDigits,
+ }).format(s) + units[d]
+ );
};
export const sats = 100000000;
export const back = (): any =>
- browser && (history.length ? history.go(-1) : goto("/"));
+ browser && (history.length ? history.go(-1) : goto("/"));
export const focus = (el: HTMLElement): any =>
- browser && screen.width > 1280 && setTimeout(() => el.focus(), 1);
+ browser && screen.width > 1280 && setTimeout(() => el.focus(), 1);
export const select = (el: HTMLInputElement): any =>
- browser && screen.width > 1280 && setTimeout(() => el.select(), 1);
+ browser && screen.width > 1280 && setTimeout(() => el.select(), 1);
export const sleep = (n: number): Promise<void> =>
- new Promise((r) => setTimeout(r, n));
+ new Promise((r) => setTimeout(r, n));
export const wait = async (
- f: () => boolean | Promise<boolean>,
- n = 100,
- s = 300,
+ f: () => boolean | Promise<boolean>,
+ n = 100,
+ s = 300,
): Promise<boolean> => {
- let i = 0;
- while (!(await f()) && i < s) {
- await sleep(n);
- i++;
- }
- if (i >= s) throw new Error("timeout");
- return f();
+ let i = 0;
+ while (!(await f()) && i < s) {
+ await sleep(n);
+ i++;
+ }
+ if (i >= s) throw new Error("timeout");
+ return f();
};
export const types = {
ark: "ark",
- bitcoin: "bitcoin",
- liquid: "liquid",
- lightning: "lightning",
- internal: "internal",
- fund: "fund",
- ecash: "ecash",
- bolt12: "bolt12",
+ bitcoin: "bitcoin",
+ liquid: "liquid",
+ lightning: "lightning",
+ internal: "internal",
+ fund: "fund",
+ ecash: "ecash",
+ bolt12: "bolt12",
};
export const ease = (t: number): number =>
- t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t;
+ t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t;
export function shuffleArray<T>(array: T[]): void {
- for (let i = array.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [array[i], array[j]] = [array[j], array[i]];
- }
+ for (let i = array.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [array[i], array[j]] = [array[j], array[i]];
+ }
}
export const closest = (a: number[], n: number): number =>
- a.reduce((prev, curr) =>
- Math.abs(curr - n) < Math.abs(prev - n) ? curr : prev,
- );
+ a.reduce((prev, curr) =>
+ Math.abs(curr - n) < Math.abs(prev - n) ? curr : prev,
+ );
export const isLiquid = (text: string): boolean =>
- text.startsWith("Az") ||
- text.startsWith("lq1") ||
- text.startsWith("VJL") ||
- text.startsWith("VT") ||
- text.startsWith("XR") ||
- text.startsWith("XC") ||
- ((text.startsWith("H") || text.startsWith("G") || text.startsWith("Q")) &&
- text.length === 34) ||
- (text.startsWith("ert1q") && text.length === 43) ||
- (text.startsWith("ex1q") && text.length === 42) ||
- text.startsWith("el1qq") ||
- text.startsWith("lq1qq");
+ text.startsWith("Az") ||
+ text.startsWith("lq1") ||
+ text.startsWith("VJL") ||
+ text.startsWith("VT") ||
+ text.startsWith("XR") ||
+ text.startsWith("XC") ||
+ ((text.startsWith("H") || text.startsWith("G") || text.startsWith("Q")) &&
+ text.length === 34) ||
+ (text.startsWith("ert1q") && text.length === 43) ||
+ (text.startsWith("ex1q") && text.length === 42) ||
+ text.startsWith("el1qq") ||
+ text.startsWith("lq1qq");
export function getCookie(name: string): string | null {
- const nameEQ = `${name}=`;
- const ca = document.cookie.split(";");
- for (let i = 0; i < ca.length; i++) {
- let c = ca[i];
- while (c.charAt(0) === " ") c = c.substring(1);
- if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
- }
- return null;
+ const nameEQ = `${name}=`;
+ const ca = document.cookie.split(";");
+ for (let i = 0; i < ca.length; i++) {
+ let c = ca[i];
+ while (c.charAt(0) === " ") c = c.substring(1);
+ if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
+ }
+ return null;
}
export function setCookie(name: string, value: string, seconds: number): void {
- const now = new Date();
- now.setTime(now.getTime() + seconds * 1000);
- const expires = `expires=${now.toUTCString()}`;
- document.cookie = `${name}=${value};${expires};path=/`;
+ const now = new Date();
+ now.setTime(now.getTime() + seconds * 1000);
+ const expires = `expires=${now.toUTCString()}`;
+ document.cookie = `${name}=${value};${expires};path=/`;
}
export const network = {
- bitcoin: {
- bech32: "bc",
- pubKeyHash: 0x00,
- scriptHash: 0x05,
- wif: 0x80,
- },
- regtest: {
- bech32: "bcrt",
- pubKeyHash: 0x6f,
- scriptHash: 0xc4,
- wif: 0xef,
- },
+ bitcoin: {
+ bech32: "bc",
+ pubKeyHash: 0x00,
+ scriptHash: 0x05,
+ wif: 0x80,
+ },
+ regtest: {
+ bech32: "bcrt",
+ pubKeyHash: 0x6f,
+ scriptHash: 0xc4,
+ wif: 0xef,
+ },
}[PUBLIC_COINOS_NETWORK];
export const versions = {
- bitcoin: {
- private: 0x0488ade4,
- public: 0x0488b21e,
- },
- regtest: {
- private: 0x04358394,
- public: 0x043587cf,
- },
+ bitcoin: {
+ private: 0x0488ade4,
+ public: 0x0488b21e,
+ },
+ regtest: {
+ private: 0x04358394,
+ public: 0x043587cf,
+ },
}[PUBLIC_COINOS_NETWORK];
export const str = (s) => s.toString();
export const ago = (t) => {
- const now = new Date();
- const date = new Date(t * 1000);
- const seconds = differenceInSeconds(now, date);
- if (seconds < 60) return `${seconds}s`;
+ const now = new Date();
+ const date = new Date(t * 1000);
+ const seconds = differenceInSeconds(now, date);
+ if (seconds < 60) return `${seconds}s`;
- const minutes = differenceInMinutes(now, date);
- if (minutes < 60) return `${minutes}m`;
+ const minutes = differenceInMinutes(now, date);
+ if (minutes < 60) return `${minutes}m`;
- const hours = differenceInHours(now, date);
- if (hours < 24) return `${hours}h`;
+ const hours = differenceInHours(now, date);
+ if (hours < 24) return `${hours}h`;
- const days = differenceInDays(now, date);
- return `${days}d`;
+ const days = differenceInDays(now, date);
+ return `${days}d`;
};
-export const register = async (user, ip, cookies, loginRedirect, host?, extraHeaders?: Record<string, string>) => {
- let error;
+export const register = async (
+ user,
+ ip,
+ cookies,
+ loginRedirect,
+ host?,
+ extraHeaders?: Record<string, string>,
+) => {
+ let error;
- const headers: Record<string, string> = {
- ...extraHeaders,
- };
- if (ip) headers["cf-connecting-ip"] = ip;
+ const headers: Record<string, string> = {
+ ...extraHeaders,
+ };
+ if (ip) headers["cf-connecting-ip"] = ip;
- let sk;
- try {
- ({ sk } = await post("/register", { user }, headers));
- } catch (e) {
- ({ message: error } = e as Error);
- }
+ let sk;
+ try {
+ ({ sk } = await post("/register", { user }, headers));
+ } catch (e) {
+ ({ message: error } = e as Error);
+ }
- try {
- await login(user, cookies, ip, host, extraHeaders);
- error = null;
- } catch (e) {
- const { message } = e as Error;
- error ||= message;
- }
+ try {
+ await login(user, cookies, ip, host, extraHeaders);
+ error = null;
+ } catch (e) {
+ const { message } = e as Error;
+ error ||= message;
+ }
- const expires = new Date();
- expires.setSeconds(expires.getSeconds() + 21000000);
- cookies.set("sk", sk, {
- path: "/",
- expires,
- httpOnly: false,
- sameSite: "lax",
- });
+ const expires = new Date();
+ expires.setSeconds(expires.getSeconds() + 21000000);
+ cookies.set("sk", sk, {
+ path: "/",
+ expires,
+ httpOnly: false,
+ sameSite: "lax",
+ });
- if (error) return svelteFail(400, { error });
- redirect(303, loginRedirect || `/${user.username}`);
+ if (error) return svelteFail(400, { error });
+ redirect(303, loginRedirect || `/${user.username}`);
};