mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-20 15:54:22 +01:00
add node manager
This commit is contained in:
3
.env
Normal file
3
.env
Normal file
@@ -0,0 +1,3 @@
|
||||
VITE_NETWORK="regtest"
|
||||
VITE_PROXY="wss://p.mutinywallet.com"
|
||||
VITE_ESPLORA="http://localhost:3003"
|
||||
@@ -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
18
pnpm-lock.yaml
generated
@@ -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}
|
||||
|
||||
@@ -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 >
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
45
src/components/BalanceBox.tsx
Normal file
45
src/components/BalanceBox.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
76
src/logic/nodeManagerSetup.ts
Normal file
76
src/logic/nodeManagerSetup.ts
Normal 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
|
||||
}
|
||||
@@ -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 />
|
||||
|
||||
31
src/state/nodeManagerState.tsx
Normal file
31
src/state/nodeManagerState.tsx
Normal 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); }
|
||||
@@ -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"],
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user