diff --git a/.env b/.env new file mode 100644 index 0000000..e30db72 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +VITE_NETWORK="regtest" +VITE_PROXY="wss://p.mutinywallet.com" +VITE_ESPLORA="http://localhost:3003" \ No newline at end of file diff --git a/package.json b/package.json index c2b7f9c..d979d11 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "dev": "solid-start dev", "build": "solid-start build", "start": "solid-start start", - "lint": "eslint . --ext .ts,.tsx,.js" + "lint": "eslint src --ext .ts,.tsx,.js" }, "type": "module", "devDependencies": { @@ -20,10 +20,12 @@ "typescript": "^4.9.5", "vite": "^4.2.1", "vite-plugin-pwa": "^0.14.7", + "vite-plugin-wasm": "^3.2.2", "workbox-window": "^6.5.4" }, "dependencies": { "@motionone/solid": "^10.16.0", + "@mutinywallet/node-manager": "^0.2.2", "@nostr-dev-kit/ndk": "^0.0.13", "@solidjs/meta": "^0.28.4", "@solidjs/router": "^0.8.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa404c6..f1678ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ dependencies: '@motionone/solid': specifier: ^10.16.0 version: 10.16.0(solid-js@1.7.1) + '@mutinywallet/node-manager': + specifier: ^0.2.2 + version: 0.2.2 '@nostr-dev-kit/ndk': specifier: ^0.0.13 version: 0.0.13(typescript@4.9.5) @@ -69,6 +72,9 @@ devDependencies: vite-plugin-pwa: specifier: ^0.14.7 version: 0.14.7(vite@4.2.1)(workbox-build@6.5.4)(workbox-window@6.5.4) + vite-plugin-wasm: + specifier: ^3.2.2 + version: 3.2.2(vite@4.2.1) workbox-window: specifier: ^6.5.4 version: 6.5.4 @@ -1534,6 +1540,10 @@ packages: tslib: 2.5.0 dev: false + /@mutinywallet/node-manager@0.2.2: + resolution: {integrity: sha512-N/zZIXFV7eg6cv2oIdWZPZgqAuGU9tRyqYJPI1oAkDV74weTWQedwOYco9ABbTIyavqYlTv+fiEjieS2RMCLKg==} + dev: false + /@noble/curves@0.8.3: resolution: {integrity: sha512-OqaOf4RWDaCRuBKJLDURrgVxjLmneGsiCXGuzYB5y95YithZMA6w4uk34DHSm0rKMrrYiaeZj48/81EvaAScLQ==} dependencies: @@ -4928,6 +4938,14 @@ packages: transitivePeerDependencies: - supports-color + /vite-plugin-wasm@3.2.2(vite@4.2.1): + resolution: {integrity: sha512-cdbBUNR850AEoMd5nvLmnyeq63CSfoP1ctD/L2vLk/5+wsgAPlAVAzUK5nGKWO/jtehNlrSSHLteN+gFQw7VOA==} + peerDependencies: + vite: ^2 || ^3 || ^4 + dependencies: + vite: 4.2.1(@types/node@18.15.11) + dev: true + /vite@4.2.1(@types/node@18.15.11): resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==} engines: {node: ^14.18.0 || >=16.0.0} diff --git a/src/components/App.tsx b/src/components/App.tsx index 1f82417..0fae68e 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,4 +1,4 @@ -import { createSignal, For } from "solid-js"; +import { For } from "solid-js"; import { A } from "solid-start"; import { Motion, Presence } from "@motionone/solid"; @@ -7,6 +7,7 @@ import mutiny_m from '~/assets/icons/m.svg'; import scan from '~/assets/icons/scan.svg'; import settings from '~/assets/icons/settings.svg'; import send from '~/assets/icons/send.svg'; +import BalanceBox from "./BalanceBox"; // TODO: use this reload prompt for real // import ReloadPrompt from "./Reload"; @@ -26,7 +27,6 @@ function ActivityItem() { } export default function App() { - const [_isOpen, setOpen] = createSignal(false); return (
@@ -35,30 +35,7 @@ export default function App() { {/* */} - - -
-
- Balance -
-
-

- 69,420 SAT -

-
-
- - -
-
-
-
- +
Activity @@ -103,7 +80,5 @@ export default function App() {
- - ); } diff --git a/src/components/BalanceBox.tsx b/src/components/BalanceBox.tsx new file mode 100644 index 0000000..4b4680f --- /dev/null +++ b/src/components/BalanceBox.tsx @@ -0,0 +1,45 @@ +import { Motion, Presence } from "@motionone/solid"; +import { MutinyBalance } from "@mutinywallet/node-manager"; + +import { useNodeManager } from "~/state/nodeManagerState"; + +function prettyPrintAmount(n?: number | bigint): string { + if (!n || n.valueOf() === 0) { + return "0" + } + return n.toLocaleString().replaceAll(",", "_") +} + +function prettyPrintBalance(b: MutinyBalance): string { + return prettyPrintAmount(b.confirmed.valueOf() + b.lightning.valueOf()) +} + +export default function BalanceBox() { + const { balance, refetchBalance } = useNodeManager(); + + return ( + + +
+
+ Balance +
+
+

+ {balance() && prettyPrintBalance(balance())} SAT +

+
+
+ + +
+
+
+
+ ) +} diff --git a/src/logic/nodeManagerSetup.ts b/src/logic/nodeManagerSetup.ts new file mode 100644 index 0000000..9009b3f --- /dev/null +++ b/src/logic/nodeManagerSetup.ts @@ -0,0 +1,76 @@ + +import init, { NodeManager } from '@mutinywallet/node-manager'; + +export type NodeManagerSettingStrings = { + network?: string, proxy?: string, esplora?: string +} + +export function getExistingSettings(): NodeManagerSettingStrings { + const network = localStorage.getItem('MUTINY_SETTINGS_network') || import.meta.env.VITE_NETWORK; + const proxy = localStorage.getItem('MUTINY_SETTINGS_proxy') || import.meta.env.VITE_PROXY; + const esplora = localStorage.getItem('MUTINY_SETTINGS_esplora') || import.meta.env.VITE_ESPLORA; + + return { network, proxy, esplora } +} + +export async function setAndGetMutinySettings(settings?: NodeManagerSettingStrings): Promise { + let { network, proxy, esplora } = settings || {}; + + const existingSettings = getExistingSettings(); + try { + network = network || existingSettings.network; + proxy = proxy || existingSettings.proxy; + esplora = esplora || existingSettings.esplora; + + if (!network || !proxy || !esplora) { + throw new Error("Missing a default setting for network, proxy, or esplora. Check your .env file to make sure it looks like .env.sample") + } + localStorage.setItem('MUTINY_SETTINGS_network', network); + localStorage.setItem('MUTINY_SETTINGS_proxy', proxy); + localStorage.setItem('MUTINY_SETTINGS_esplora', esplora); + + return { network, proxy, esplora } + } catch (error) { + console.error(error) + throw error + } +} + +export async function checkForWasm() { + try { + if (typeof WebAssembly === "object" + && typeof WebAssembly.instantiate === "function") { + const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)); + if (!(module instanceof WebAssembly.Module)) { + throw new Error("Couldn't instantiate WASM Module") + } + } else { + throw new Error("No WebAssembly global object found") + } + } catch (e) { + console.error(e) + } +} + +export async function setupNodeManager(settings?: NodeManagerSettingStrings): Promise { + const _ = await init(); + + console.time("Setup"); + console.log("Starting setup...") + const { network, proxy, esplora } = await setAndGetMutinySettings(settings) + console.log("Initializing Node Manager") + console.log("Using network", network); + console.log("Using proxy", proxy); + console.log("Using esplora address", esplora); + + const nodeManager = await new NodeManager("", undefined, proxy, network, esplora) + + const nodes = await nodeManager.list_nodes(); + + // If we don't have any nodes yet, create one + if (!nodes.length) { + await nodeManager?.new_node() + } + + return nodeManager +} \ No newline at end of file diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 93170e8..46a5c26 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -4,6 +4,7 @@ import { Accessor, createEffect, createResource, Setter, createSignal, Switch, M import { WaitlistAlreadyIn } from "~/components/waitlist/WaitlistAlreadyIn"; import WaitlistForm from "~/components/waitlist/WaitlistForm"; import ReloadPrompt from "~/components/Reload"; +import { NodeManagerProvider } from "~/state/nodeManagerState"; function createWaitListSignal(): [Accessor, Setter] { const [state, setState] = createSignal(""); @@ -34,7 +35,9 @@ export default function Home() { Loading...} > - + + + diff --git a/src/state/nodeManagerState.tsx b/src/state/nodeManagerState.tsx new file mode 100644 index 0000000..3eaa042 --- /dev/null +++ b/src/state/nodeManagerState.tsx @@ -0,0 +1,31 @@ +import { NodeManager } from "@mutinywallet/node-manager"; +import { createContext, JSX, useContext, createResource } from "solid-js"; +import { setupNodeManager } from "~/logic/nodeManagerSetup"; + +const NodeManagerContext = createContext(); + +export function NodeManagerProvider(props: { children: JSX.Element }) { + const [nodeManager] = createResource({}, setupNodeManager); + + const fetchBalance = async (nm: NodeManager) => { + console.log("refetching balance"); + const balance = await nm.get_balance(); + return balance + }; + + const [balance, { refetch }] = createResource(nodeManager, fetchBalance); + + const value = { + nodeManager, + balance, + refetchBalance: refetch + }; + + return ( + + {props.children} + + ) +} + +export function useNodeManager() { return useContext(NodeManagerContext); } diff --git a/vite.config.ts b/vite.config.ts index b2623c7..a65f66e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,10 +1,12 @@ import solid from "solid-start/vite"; import { defineConfig } from "vite"; -import { VitePWA, VitePWAOptions } from 'vite-plugin-pwa' +import { VitePWA, VitePWAOptions } from "vite-plugin-pwa"; +import wasm from "vite-plugin-wasm"; import * as path from 'path' const pwaOptions: Partial = { + base: '/', registerType: "autoUpdate", devOptions: { enabled: true @@ -40,8 +42,12 @@ export default defineConfig({ server: { port: 3420, }, - plugins: [solid({ ssr: false }), VitePWA(pwaOptions)], + plugins: [wasm(), solid({ ssr: false }), VitePWA(pwaOptions)], resolve: { alias: [{ find: '~', replacement: path.resolve(__dirname, './src') }] - } + }, + optimizeDeps: { + // This is necessary because otherwise `vite dev` can't find the wasm + exclude: ["@mutinywallet/node-manager"], + }, });