add node manager

This commit is contained in:
Paul Miller
2023-04-04 13:14:13 -05:00
parent ae703183df
commit 3efac0814d
9 changed files with 192 additions and 33 deletions

3
.env Normal file
View File

@@ -0,0 +1,3 @@
VITE_NETWORK="regtest"
VITE_PROXY="wss://p.mutinywallet.com"
VITE_ESPLORA="http://localhost:3003"

View File

@@ -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",

18
pnpm-lock.yaml generated
View File

@@ -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}

View File

@@ -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 (
<div class="safe-top safe-left safe-right safe-bottom">
<div class="disable-scrollbars max-h-screen h-full overflow-y-scroll">
@@ -35,30 +35,7 @@ export default function App() {
<img src={logo} class="App-logo" alt="logo" />
</header>
{/* <ReloadPrompt /> */}
<Presence>
<Motion
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5, easing: [0.87, 0, 0.13, 1] }}
>
<div class='border border-white rounded-xl border-b-4 p-4 flex flex-col gap-2'>
<header class='text-sm font-semibold uppercase'>
Balance
</header>
<div>
<h1 class='text-4xl font-light'>
69,420 <span class='text-xl'>SAT</span>
</h1>
</div>
<div class="flex gap-2 py-4">
<button onClick={() => setOpen(true)} class='bg-[#1EA67F] p-4 flex-1 rounded-xl text-xl font-semibold '><span class="drop-shadow-sm shadow-black">Send</span></button>
<button class='bg-[#3B6CCC] p-4 flex-1 rounded-xl text-xl font-semibold '><span class="drop-shadow-sm shadow-black">Receive</span></button>
</div>
</div>
</Motion>
</Presence>
<BalanceBox />
<div class='rounded-xl p-4 flex flex-col gap-2 bg-[rgba(0,0,0,0.5)]'>
<header class='text-sm font-semibold uppercase'>
Activity
@@ -103,7 +80,5 @@ export default function App() {
</nav>
</div>
</div >
);
}

View File

@@ -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 (
<Presence>
<Motion
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5, easing: [0.87, 0, 0.13, 1] }}
>
<div class='border border-white rounded-xl border-b-4 p-4 flex flex-col gap-2'>
<header class='text-sm font-semibold uppercase'>
Balance
</header>
<div onClick={refetchBalance}>
<h1 class='text-4xl font-light'>
{balance() && prettyPrintBalance(balance())} <span class='text-xl'>SAT</span>
</h1>
</div>
<div class="flex gap-2 py-4">
<button class='bg-[#1EA67F] p-4 flex-1 rounded-xl text-xl font-semibold '><span class="drop-shadow-sm shadow-black">Send</span></button>
<button class='bg-[#3B6CCC] p-4 flex-1 rounded-xl text-xl font-semibold '><span class="drop-shadow-sm shadow-black">Receive</span></button>
</div>
</div>
</Motion>
</Presence>
)
}

View File

@@ -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<NodeManagerSettingStrings> {
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<NodeManager> {
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
}

View File

@@ -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<string>, Setter<string>] {
const [state, setState] = createSignal("");
@@ -34,7 +35,9 @@ export default function Home() {
<ReloadPrompt />
<Switch fallback={<>Loading...</>} >
<Match when={waitlistData() && waitlistData().approval_date}>
<NodeManagerProvider>
<App />
</NodeManagerProvider>
</Match>
<Match when={waitlistData() && waitlistData().date}>
<WaitlistAlreadyIn />

View File

@@ -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 (
<NodeManagerContext.Provider value={value}>
{props.children}
</NodeManagerContext.Provider>
)
}
export function useNodeManager() { return useContext(NodeManagerContext); }

View File

@@ -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<VitePWAOptions> = {
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"],
},
});